本篇带你使用 AntDesign 组件库为我们的系统换上产品级的UI!

安装组件库

  • 在项目目录下执行:npm i antd@3.3.0 -S 或 yarn add antd 安装组件包
  • 执行:npm i babel-plugin-import -D 安装一个babel插件用于做组件的按需加载(否则项目会打包整个组件库,非常大)
  • 根目录下新建.roadhogrc文件(别忘了前面的点,这是roadhog工具的配置文件,下面的代码用于加载上一个命令安装的import插件),写入:
{
"extraBabelPlugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "lib",
"style": "css"
}]
]
}

改造HomeLayout

我们计划把系统改造成这个样子:

上方显示LOGO,下方左侧显示一个菜单栏,右侧显示页面的主要内容。

所以新的HomeLayout应该包括LOGO和Menu部分,然后HomeLayout的children放置在Content区域。

Menu我们使用AntDesign提供的Menu组件来完成,菜单项为:

  • 用户管理

    • 用户列表
    • 添加用户
  • 图书管理 
    • 图书列表
    • 添加图书

来看新的组件代码:

/**
* 布局组件
*/
import React from 'react';
// 路由
import { Link } from 'react-router';
// Menu 导航菜单 Icon 图标
import { Menu, Icon } from 'antd';
import '../styles/home-layout.less'; // 左侧菜单栏
const SubMenu = Menu.SubMenu; class HomeLayout extends React.Component {
render () {
const {children} = this.props;
return (
<div>
<header className="header">
<Link to="/">ReactManager</Link>
</header> <main className="main">
<div className="menu">
<Menu mode="inline" theme="dark" style={{width: '240'}}>
<SubMenu key="user" title={<span><Icon type="user"/><span>用户管理</span></span>}>
<Menu.Item key="user-list">
<Link to="/user/list">用户列表</Link>
</Menu.Item>
<Menu.Item key="user-add">
<Link to="/user/add">添加用户</Link>
</Menu.Item>
</SubMenu> <SubMenu key="book" title={<span><Icon type="book"/><span>图书管理</span></span>}>
<Menu.Item key="book-list">
<Link to="/book/list">图书列表</Link>
</Menu.Item>
<Menu.Item key="book-add">
<Link to="/book/add">添加图书</Link>
</Menu.Item>
</SubMenu>
</Menu>
</div> <div className="content">
{children}
</div>
</main>
</div>
);
}
} export default HomeLayout;

HomeLayout引用了/src/styles/home-layout.less这个样式文件,样式代码为:

@import '~antd/dist/antd.css'; // 引入antd样式表
.main {
height: 100vh;
padding-top: 50px;
} .header {
position: absolute;
top: 0;
height: 50px;
width: 100%;
font-size: 18px;
padding: 0 20px;
line-height: 50px;
background-color: #108ee9;
color: #fff; a {
color: inherit;
}
} .menu {
height: 100%;
width: 240px;
float: left;
background-color: #404040;
} .content {
height: 100%;
padding: 12px;
overflow: auto;
margin-left: 240px;
align-self: stretch;
}

现在的首页是这个样子:

逼格立马就上来了有没?

改造HomePage

由于现在有菜单了,就不需要右侧那个HomePage里的链接了,把他去掉,然后放个Welcome吧(HomeLayout也去掉了,在下面会提到):

src / pages / Home.js

/**
* 主页
*/
import React from 'react';
// 引入样式表
import '../styles/home-page.less'; class Home extends React.Component {
// 构造器
constructor(props) {
super(props);
// 定义初始化状态
this.state = {};
} render() {
return (
<div className="welcome">
Welcome
</div>
);
}
} export default Home;

新增样式文件/src/styles/home-page.less,代码:

.welcome{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
}

优化HomeLayout使用方式

现在的HomeLayout里有一个菜单了,菜单有展开状态需要维护,如果还是像以前那样在每个page组件里单独使用HomeLayout,会导致菜单的展开状态被重置(跳转页面之后都会渲染一个新的HomeLayout),所以需要将HomeLayout放到父级路由中来使用:

src / index.js

/**
* 配置路由
*/
import React from 'react';
import ReactDOM from 'react-dom';
// 引入react-router
import { Router, Route, hashHistory } from 'react-router';
// 引入布局组件
import HomeLayout from './layouts/HomeLayout';
import HomePage from './pages/Home'; // 首页
import LoginPage from './pages/Login'; // 登录页
import UserAddPage from './pages/UserAdd'; // 添加用户页
import UserListPage from './pages/UserList'; // 用户列表页
import UserEditPage from './pages/UserEdit'; // 用户编辑页面
import BookAddPage from './pages/BookAdd'; // 添加图书页
import BookListPage from './pages/BookList'; // 图书列表页
import BookEditPage from './pages/BookEdit'; // 用户编辑页面 // 渲染
ReactDOM.render((
<Router history={hashHistory}>
<Route component={HomeLayout}>
<Route path="/" component={HomePage} />
<Route path="/user/add" component={UserAddPage} />
<Route path="/user/list" component={UserListPage} />
<Route path="/user/edit/:id" component={UserEditPage} />
<Route path="/book/add" component={BookAddPage} />
<Route path="/book/list" component={BookListPage} />
<Route path="/book/edit/:id" component={BookEditPage} />
</Route>
<Route path="/login" component={LoginPage} />
</Router>
), document.getElementById('root'));

效果图:

然后需要在各个页面中移除HomeLayout:

src / pages / BookAdd.js

/**
* 图书添加页面
* 这个组件除了返回BookEditor没有做任何事,其实可以直接export default BookEditor
*/
import React from 'react';
// 编辑组件
import BookEditor from '../components/BookEditor'; class BookAdd extends React.Component {
render() {
return (
<BookEditor />
);
}
} export default BookAdd;

src / pages / BookEdit.js

...
render () {
const {book} = this.state;
return book ? <BookEditor editTarget={book}/> : <span>加载中...</span>;
}
...

src / pages / BookList.js

...
render () {
...
return (
<table>
...
</table>
);
}
...

剩下的UserAdd.js、UserEdit.js、UserList.js与上面Book对应的组件做相同更改。

还有登录页组件在下面说。

升级登录页面

下面来对登录页面进行升级,修改/src/pages/Login.js文件:

/**
* 登录页
*/
import React from 'react';
// 引入antd组件
import { Icon, Form, Input, Button, message } from 'antd';
// 引入 封装后的fetch工具类
import { post } from '../utils/request';
// 引入样式表
import styles from '../styles/login-page.less';
// 引入 prop-types
import PropTypes from 'prop-types'; const FormItem = Form.Item; class Login extends React.Component {
// 构造器
constructor () {
super();
this.handleSubmit = this.handleSubmit.bind(this);
} handleSubmit (e) {
// 通知 Web 浏览器不要执行与事件关联的默认动作
e.preventDefault();
// 表单验证
this.props.form.validateFields((err, values) => {
if(!err){
// 发起请求
post('http://localhost:8000/login', values)
// 成功的回调
.then((res) => {
if(res){
message.info('登录成功');
// 页面跳转
this.context.router.push('/');
}else{
message.info('登录失败,账号或密码错误');
}
});
}
});
} render () {
const { form } = this.props;
// 验证规则
const { getFieldDecorator } = form;
return (
<div className={styles.wrapper}>
<div className={styles.body}>
<header className={styles.header}>
ReactManager
</header> <section className={styles.form}>
<Form onSubmit={this.handleSubmit}>
<FormItem>
{getFieldDecorator('account',{
rules: [
{
required: true,
message: '请输入管理员帐号',
type: 'string'
}
]
})(
<Input type="text" prefix={<Icon type="user" />} />
)}
</FormItem> <FormItem>
{getFieldDecorator('password',{
rules: [
{
required: true,
message: '请输入密码',
type: 'string'
}
]
})(
<Input type="password" prefix={<Icon type="lock" />} />
)}
</FormItem> <Button className={styles.btn} type="primary" htmlType="submit">登录</Button>
</Form>
</section>
</div>
</div>
);
}
} Login.contextTypes = {
router: PropTypes.object.isRequired
}; Login = Form.create()(Login); export default Login;

新建样式文件/src/styles/login-page.less,样式代码:

.wrapper {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
} .body {
width: 360px;
box-shadow: 1px 1px 10px 0 rgba(0, 0, 0, .3);
} .header {
color: #fff;
font-size: 24px;
padding: 30px 20px;
background-color: #108ee9;
} .form {
margin-top: 12px;
padding: 24px;
} .btn {
width: 100%;
}

酷酷的登录页面:

改造后的登录页组件使用了antd提供的Form组件,Form组件提供了一个create方法,和我们之前写的formProvider一样,是一个高阶组件。使用Form.create({ ... })(Login)处理之后的Login组件会接收到一个props.form,使用props.form下的一系列方法,可以很方便地创造表单,上面有一段代码:

...
<FormItem>
{getFieldDecorator('account',{
rules: [
{
required: true,
message: '请输入管理员帐号',
type: 'string'
}
]
})(
<Input type="text" prefix={<Icon type="user" />} />
)}
</FormItem>
...

这里使用了props.form.getFieldDecorator方法来包装一个Input输入框组件,传入的第一个参数表示这个字段的名称,第二个参数是一个配置对象,这里设置了表单控件的校验规则rules(更多配置项请查看文档)。使用getFieldDecorator方法包装后的组件会自动表单组件的value以及onChange事件;此外,这里还用到了Form.Item这个表单项目组件(上面的FormItem),这个组件可用于配置表单项目的标签、布局等。

在handleSubmit方法中,使用了props.form.validateFields方法对表单的各个字段进行校验,校验完成后会调用传入的回调方法,回调方法可以接收到错误信息err和表单值对象values,方便对校验结果进行处理:

...
handleSubmit (e) {
// 通知 Web 浏览器不要执行与事件关联的默认动作
e.preventDefault();
// 表单验证
this.props.form.validateFields((err, values) => {
if(!err){
// 发起请求
post('http://localhost:8000/login', values)
// 成功的回调
.then((res) => {
if(res){
message.info('登录成功');
// 页面跳转
this.context.router.push('/');
}else{
message.info('登录失败,账号或密码错误');
}
});
}
});
}
...

升级UserEditor

升级UserEditor和登录页面组件类似,但是在componentWillMount里需要使用this.props.setFieldsValue将editTarget的值设置到表单:

src/components/UserEditor.js

/**
* 用户编辑器组件
*/
import React from 'react';
// 引入 antd 组件
import { Form, Input, InputNumber, Select, Button, message } from 'antd';
// 引入 prop-types
import PropTypes from 'prop-types';
// 引入 封装fetch工具类
import request from '../utils/request'; const FormItem = Form.Item; const formLayout = {
labelCol: {
span: 4
},
wrapperCol: {
span: 16
}
}; class UserEditor extends React.Component {
// 生命周期--组件加载完毕
componentDidMount(){
/**
* 在componentWillMount里使用form.setFieldsValue无法设置表单的值
* 所以在componentDidMount里进行赋值
*/
const { editTarget, form } = this.props;
if(editTarget){
// 将editTarget的值设置到表单
form.setFieldsValue(editTarget);
}
} // 按钮提交事件
handleSubmit(e){
// 阻止表单submit事件自动跳转页面的动作
e.preventDefault();
// 定义常量
const { form, editTarget } = this.props; // 组件传值 // 验证
form.validateFields((err, values) => {
if(!err){
// 默认值
let editType = '添加';
let apiUrl = 'http://localhost:8000/user';
let method = 'post';
// 判断类型
if(editTarget){
editType = '编辑';
apiUrl += '/' + editTarget.id;
method = 'put';
} // 发送请求
request(method,apiUrl,values)
// 成功的回调
.then((res) => {
// 当添加成功时,返回的json对象中应包含一个有效的id字段
// 所以可以使用res.id来判断添加是否成功
if(res.id){
message.success(editType + '添加用户成功!');
// 跳转到用户列表页面
this.context.router.push('/user/list');
return;
}else{
message.error(editType + '添加用户失败!');
}
})
// 失败的回调
.catch((err) => console.error(err));
}else{
message.warn(err);
}
});
} render() {
// 定义常量
const { form } = this.props;
const { getFieldDecorator } = form;
return (
<div style={{width: '400'}}>
<Form onSubmit={(e) => this.handleSubmit(e)}>
<FormItem label="用户名:" {...formLayout}>
{getFieldDecorator('name',{
rules: [
{
required: true,
message: '请输入用户名'
},
{
pattern: /^.{1,4}$/,
message: '用户名最多4个字符'
}
]
})(
<Input type="text" />
)}
</FormItem> <FormItem label="年龄:" {...formLayout}>
{getFieldDecorator('age',{
rules: [
{
required: true,
message: '请输入年龄',
type: 'number'
},
{
min: 1,
max: 100,
message: '请输入1~100的年龄',
type: 'number'
}
]
})(
<InputNumber />
)}
</FormItem> <FormItem label="性别:" {...formLayout}>
{getFieldDecorator('gender',{
rules: [
{
required: true,
message: '请选择性别'
}
]
})(
<Select placeholder="请选择">
<Select.Option value="male">男</Select.Option>
<Select.Option value="female">女</Select.Option>
</Select>
)}
</FormItem> <FormItem wrapperCol={{...formLayout.wrapperCol, offset: formLayout.labelCol.span}}>
<Button type="primary" htmlType="submit">提交</Button>
</FormItem>
</Form>
</div>
);
}
} // 必须给UserEditor定义一个包含router属性的contextTypes
// 使得组件中可以通过this.context.router来使用React Router提供的方法
UserEditor.contextTypes = {
router: PropTypes.object.isRequired
}; /**
* 使用Form.create({ ... })(UserEditor)处理之后的UserEditor组件会接收到一个props.form
* 使用props.form下的一系列方法,可以很方便地创造表单
*/
UserEditor = Form.create()(UserEditor); export default UserEditor;

升级BookEditor

BookEditor中使用了AutoComplete组件,但是由于antd提供的AutoComplete组件有一些问题(见issue),这里暂时使用我们之前实现的AutoComplete。

src/components/BookEditor.js

/**
* 图书编辑器组件
*/
import React from 'react';
// 引入 antd 组件
import { Input, InputNumber, Form, Button, message } from 'antd';
// 引入 prop-types
import PropTypes from 'prop-types';
// 引入自动完成组件
import AutoComplete from '../components/AutoComplete'; // 也可以写为 './AutoComplete'
// 引入 封装fetch工具类
import request,{get} from '../utils/request'; // const Option = AutoComplete.Option;
const FormItem = Form.Item;
// 表单布局
const formLayout = {
// label 标签布局,同 <Col> 组件
labelCol: {
span: 4
},
wrapperCol: {
span: 16
}
}; class BookEditor extends React.Component {
// 构造器
constructor(props) {
super(props); this.state = {
recommendUsers: []
};
// 绑定this
this.handleSubmit = this.handleSubmit.bind(this);
this.handleOwnerIdChange = this.handleOwnerIdChange.bind(this);
} // 生命周期--组件加载完毕
componentDidMount(){
/**
* 在componentWillMount里使用form.setFieldsValue无法设置表单的值
* 所以在componentDidMount里进行赋值
*/
const {editTarget, form} = this.props;
if(editTarget){
form.setFieldsValue(editTarget);
}
} // 按钮提交事件
handleSubmit(e){
// 阻止submit默认行为
e.preventDefault();
// 定义常量
const { form, editTarget } = this.props; // 组件传值
// 验证
form.validateFields((err, values) => {
if(err){
message.warn(err);
return;
} // 默认值
let editType = '添加';
let apiUrl = 'http://localhost:8000/book';
let method = 'post';
// 判断类型
if(editTarget){
editType = '编辑';
apiUrl += '/' + editTarget.id;
method = 'put';
} // 发送请求
request(method,apiUrl,values)
// 成功的回调
.then((res) => {
// 当添加成功时,返回的json对象中应包含一个有效的id字段
// 所以可以使用res.id来判断添加是否成功
if(res.id){
message.success(editType + '添加图书成功!');
// 跳转到用户列表页面
this.context.router.push('/book/list');
}else{
message.error(editType + '添加图书失败!');
}
})
// 失败的回调
.catch((err) => console.error(err));
});
} // 获取推荐用户信息
getRecommendUsers (partialUserId) {
// 请求数据
get('http://localhost:8000/user?id_like=' + partialUserId)
.then((res) => {
if(res.length === 1 && res[0].id === partialUserId){
// 如果结果只有1条且id与输入的id一致,说明输入的id已经完整了,没必要再设置建议列表
return;
} // 设置建议列表
this.setState({
recommendUsers: res.map((user) => {
return {
text: `${user.id}(${user.name})`,
value: user.id
}
})
});
})
} // 计时器
timer = 0;
handleOwnerIdChange(value){
this.setState({
recommendUsers: []
}); // 使用"节流"的方式进行请求,防止用户输入的过程中过多地发送请求
if(this.timer){
// 清除计时器
clearTimeout(this.timer);
} if(value){
// 200毫秒内只会发送1次请求
this.timer = setTimeout(() => {
// 真正的请求方法
this.getRecommendUsers(value);
this.timer = 0;
}, 200);
}
} render() {
// 定义常量
const {recommendUsers} = this.state;
const {form} = this.props;
const {getFieldDecorator} = form; return (
<Form onSubmit={this.handleSubmit} style={{width:'400'}}>
<FormItem label="书名:" {...formLayout}>
{getFieldDecorator('name',{
rules: [
{
required: true,
message: '请输入书名'
}
]
})(
<Input type="text" />
)}
</FormItem> <FormItem label="价格:" {...formLayout}>
{getFieldDecorator('price',{
rules: [
{
required: true,
message: '请输入价格',
type: 'number'
},
{
min: 1,
max: 99999,
type: 'number',
message: '请输入1~99999的数字'
}
]
})(
<InputNumber />
)}
</FormItem> <FormItem label="所有者:" {...formLayout}>
{getFieldDecorator('owner_id',{
rules: [
{
required: true,
message: '请输入所有者ID'
},
{
pattern: /^\d*$/,
message: '请输入正确的ID'
}
]
})(
<AutoComplete
options={recommendUsers}
onChange={this.handleOwnerIdChange}
/>
)}
</FormItem> <FormItem wrapperCol={{span: formLayout.wrapperCol.span, offset: formLayout.labelCol.span}}>
<Button type="primary" htmlType="submit">提交</Button>
</FormItem>
</Form>
);
}
} // 必须给BookEditor定义一个包含router属性的contextTypes
// 使得组件中可以通过this.context.router来使用React Router提供的方法
BookEditor.contextTypes = {
router: PropTypes.object.isRequired
}; BookEditor = Form.create()(BookEditor); export default BookEditor;

升级AutoComplete

因为要继续使用自己的AutoComplete组件,这里需要把组件中的原生input控件替换为antd的Input组件,并且在Input组件加了两个事件处理onFocus、onBlur和state.show,用于在输入框失去焦点时隐藏下拉框:

src/components/AutoComplete.js

/**
* 自动完成组件
*/
import React from 'react';
// 引入 antd 组件
import { Input } from 'antd';
// 引入 prop-types
import PropTypes from 'prop-types';
// 引入样式
import styles from '../styles/auto-complete.less'; // 获得当前元素value值
function getItemValue (item) {
return item.value || item;
} class AutoComplete extends React.Component {
// 构造器
constructor(props) {
super(props);
// 定义初始化状态
this.state = {
show: false, // 新增的下拉框显示控制开关
displayValue: '',
activeItemIndex: -1
}; // 对上下键、回车键进行监听处理
this.handleKeyDown = this.handleKeyDown.bind(this);
// 对鼠标移出进行监听处理
this.handleLeave = this.handleLeave.bind(this);
} // 处理输入框改变事件
handleChange(value){
// 选择列表项的时候重置内部状态
this.setState({
activeItemIndex: -1,
displayValue: ''
});
/**
* 通过回调将新的值传递给组件使用者
* 原来的onValueChange改为了onChange以适配antd的getFieldDecorator
*/
this.props.onChange(value);
} // 处理上下键、回车键点击事件
handleKeyDown(e){
const {activeItemIndex} = this.state;
const {options} = this.props; /**
* 判断键码
*/
switch (e.keyCode) {
// 13为回车键的键码(keyCode)
case 13: {
// 判断是否有列表项处于选中状态
if(activeItemIndex >= 0){
// 防止按下回车键后自动提交表单
e.preventDefault();
e.stopPropagation();
// 输入框改变事件
this.handleChange(getItemValue(options[activeItemIndex]));
}
break;
}
// 38为上方向键,40为下方向键
case 38:
case 40: {
e.preventDefault();
// 使用moveItem方法对更新或取消选中项
this.moveItem(e.keyCode === 38 ? 'up' : 'down');
break;
}
default: {
//
}
}
} // 使用moveItem方法对更新或取消选中项
moveItem(direction){
const {activeItemIndex} = this.state;
const {options} = this.props;
const lastIndex = options.length - 1;
let newIndex = -1; // 计算新的activeItemIndex
if(direction === 'up'){ // 点击上方向键
if(activeItemIndex === -1){
// 如果没有选中项则选择最后一项
newIndex = lastIndex;
}else{
newIndex = activeItemIndex - 1;
}
}else{ // 点击下方向键
if(activeItemIndex < lastIndex){
newIndex = activeItemIndex + 1;
}
} // 获取新的displayValue
let newDisplayValue = '';
if(newIndex >= 0){
newDisplayValue = getItemValue(options[newIndex]);
} // 更新状态
this.setState({
displayValue: newDisplayValue,
activeItemIndex: newIndex
});
} // 处理鼠标移入事件
handleEnter(index){
const currentItem = this.props.options[index];
this.setState({
activeItemIndex: index,
displayValue: getItemValue(currentItem)
});
} // 处理鼠标移出事件
handleLeave(){
this.setState({
activeItemIndex: -1,
displayValue: ''
});
} // 渲染
render() {
const {show, displayValue, activeItemIndex} = this.state;
// 组件传值
const {value, options} = this.props;
return (
<div className={styles.wrapper}>
<Input
value={displayValue || value}
onChange={e => this.handleChange(e.target.value)}
onKeyDown={this.handleKeyDown}
onFocus={() => this.setState({show: true})}
onBlur={() => this.setState({show: false})}
/>
{show && options.length > 0 && (
<ul className={styles.options} onMouseLeave={this.handleLeave}>
{
options.map((item, index) => {
return (
<li
key={index}
className={index === activeItemIndex ? styles.active : ''}
onMouseEnter={() => this.handleEnter(index)}
onClick={() => this.handleChange(getItemValue(item))}
>
{item.text || item}
</li>
);
})
}
</ul>
)}
</div>
);
}
} /**
* 由于使用了antd的form.getFieldDecorator来包装组件
* 这里取消了原来props的isRequired约束以防止报错
*/
AutoComplete.propTypes = {
value: PropTypes.any, // 任意类型
options: PropTypes.array, // 数组
onChange: PropTypes.func // 函数
}; // 向外暴露
export default AutoComplete;

同时也更新了组件的样式/src/styles/auto-complete.less,给.options加了一个z-index:

.options {
z-index: 2;
background-color:#fff;
...
}

升级列表页组件

最后还剩下两个列表页组件,我们使用antd的Table组件来实现这两个列表:

src/pages/BookList.js

/**
* 图书列表页面
*/
import React from 'react';
// 引入 antd 组件
import { message, Table, Button, Popconfirm } from 'antd';
// 引入 prop-types
import PropTypes from 'prop-types';
// 引入 封装fetch工具类
import { get, del } from '../utils/request'; class BookList extends React.Component {
// 构造器
constructor(props) {
super(props);
// 定义初始化状态
this.state = {
bookList: []
};
} /**
* 生命周期
* componentWillMount
* 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次
*/
componentWillMount(){
// 请求数据
get('http://localhost:8000/book')
.then((res) => {
/**
* 成功的回调
* 数据赋值
*/
this.setState({
bookList: res
});
});
} /**
* 编辑
*/
handleEdit(book){
// 跳转编辑页面
this.context.router.push('/book/edit/' + book.id);
} /**
* 删除
*/
handleDel(book){
// 执行删除数据操作
del('http://localhost:8000/book/' + book.id, {
})
.then(res => {
/**
* 设置状态
* array.filter
* 把Array的某些元素过滤掉,然后返回剩下的元素
*/
this.setState({
bookList: this.state.bookList.filter(item => item.id !== book.id)
});
message.success('删除用户成功');
})
.catch(err => {
console.error(err);
message.error('删除用户失败');
});
} render() {
// 定义变量
const { bookList } = this.state;
// antd的Table组件使用一个columns数组来配置表格的列
const columns = [
{
title: '图书ID',
dataIndex: 'id'
},
{
title: '书名',
dataIndex: 'name'
},
{
title: '价格',
dataIndex: 'price',
render: (text, record) => <span>¥{record.price / 100}</span>
},
{
title: '所有者ID',
dataIndex: 'owner_id'
},
{
title: '操作',
render: (text, record) => (
<Button.Group type="ghost">
<Button size="small" onClick={() => this.handleEdit(record)}>编辑</Button>
<Popconfirm
title="确定要删除吗?"
okText="确定"
cancelText="取消"
onConfirm={() => this.handleDel(record)}>
<Button size="small">删除</Button>
</Popconfirm>
</Button.Group>
)
}
]; return (
<Table columns={columns} dataSource={bookList} rowKey={row => row.id} />
);
}
} /**
* 任何使用this.context.xxx的地方,必须在组件的contextTypes里定义对应的PropTypes
*/
BookList.contextTypes = {
router: PropTypes.object.isRequired
}; export default BookList;

src/pages/UserList.js

/**
* 用户列表页面
*/
import React from 'react';
// 引入 antd 组件
import { message, Table, Button, Popconfirm } from 'antd';
// 引入 prop-types
import PropTypes from 'prop-types';
// 引入 封装后的fetch工具类
import { get, del } from '../utils/request'; class UserList extends React.Component {
// 构造器
constructor(props) {
super(props);
// 定义初始化状态
this.state = {
userList: []
};
} /**
* 生命周期
* componentWillMount
* 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次
*/
componentWillMount(){
// 请求数据
get('http://localhost:8000/user')
.then((res) => {
/**
* 成功的回调
* 数据赋值
*/
this.setState({
userList: res
});
});
} /**
* 编辑
*/
handleEdit(user){
// 跳转编辑页面
this.context.router.push('/user/edit/' + user.id);
} /**
* 删除
*/
handleDel(user){
// 执行删除数据操作
del('http://localhost:8000/user/' + user.id, {
})
.then((res) => {
/**
* 设置状态
* array.filter
* 把Array的某些元素过滤掉,然后返回剩下的元素
*/
this.setState({
userList: this.state.userList.filter(item => item.id !== user.id)
});
message.success('删除用户成功');
})
.catch(err => {
console.error(err);
message.error('删除用户失败');
});
} render() {
// 定义变量
const { userList } = this.state;
// antd的Table组件使用一个columns数组来配置表格的列
const columns = [
{
title: '用户ID',
dataIndex: 'id'
},
{
title: '用户名',
dataIndex: 'name'
},
{
title: '性别',
dataIndex: 'gender'
},
{
title: '年龄',
dataIndex: 'age'
},
{
title: '操作',
render: (text, record) => {
return (
<Button.Group type="ghost">
<Button size="small" onClick={() => this.handleEdit(record)}>编辑</Button>
<Popconfirm
title="确定要删除吗?"
okText="确定"
cancelText="取消"
onConfirm={() => this.handleDel(record)}>
<Button size="small">删除</Button>
</Popconfirm>
</Button.Group>
);
}
}
]; return (
<Table columns={columns} dataSource={userList} rowKey={row => row.id} />
);
}
} /**
* 任何使用this.context.xxx的地方,必须在组件的contextTypes里定义对应的PropTypes
*/
UserList.contextTypes = {
router: PropTypes.object.isRequired
}; export default UserList;

antd的Table组件使用一个columns数组来配置表格的列,这个columns数组的元素可以包含title(列名)、dataIndex(该列数据的索引)、render(自定义的列单元格渲染方法)等字段(更多配置请参考文档)。

然后将表格数据列表传入Table的dataSource,传入一个rowKey来指定每一列的key,就可以渲染出列表了。

效果图:

react 项目实战(十)引入AntDesign组件库的更多相关文章

  1. react 项目实战(四)组件化表单/表单控件 高阶组件

    高阶组件:formProvider 高阶组件就是返回组件的组件(函数) 为什么要通过一个组件去返回另一个组件? 使用高阶组件可以在不修改原组件代码的情况下,修改原组件的行为或增强功能. 我们现在已经有 ...

  2. React项目实战:react-redux-router基本原理

    React相关 React 是一个采用声明式,高效而且灵活的用来构建用户界面的框架. JSX 本质上来讲,JSX 只是为React.createElement(component, props, .. ...

  3. ④SpringCloud 实战:引入Hystrix组件,分布式系统容错

    这是SpringCloud实战系列中第4篇文章,了解前面第两篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 ②SpringCloud 实战:引入F ...

  4. uni-app开发微信小程序引入UI组件库(Vant-weapp)步骤

    uni-app开发微信小程序引入UI组件库(Vant-weapp)步骤 这里以vant-weapp为例 uni-app官方文档介绍引入组件的方法 1. 新建相关目录 根目录下创建 wxcomponen ...

  5. ②SpringCloud 实战:引入Feign组件,完善服务间调用

    这是SpringCloud实战系列中第二篇文章,了解前面第一篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 简介 Feign 是一个声明式的 RE ...

  6. ⑤SpringCloud 实战:引入Zuul组件,开启网关路由

    这是SpringCloud实战系列中第4篇文章,了解前面第两篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 ②SpringCloud 实战:引入F ...

  7. ⑥SpringCloud 实战:引入gateway组件,开启网关路由功能

    这是SpringCloud实战系列中第4篇文章,了解前面第两篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 ②SpringCloud 实战:引入F ...

  8. ⑦SpringCloud 实战:引入Sleuth组件,完善服务链路跟踪

    这是SpringCloud实战系列中第7篇文章,了解前面第两篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 ②SpringCloud 实战:引入F ...

  9. 《Node+MongoDB+React 项目实战开发》已出版

    前言 从深圳回长沙已经快4个月了,除了把车开熟练了外,并没有什么值得一提的,长沙这边要么就是连续下一个月雨,要么就是连续一个月高温暴晒,上班更是没啥子意思,长沙这边的公司和深圳落差挺大的,薪资也是断崖 ...

随机推荐

  1. cf536c——思路题

    题目 题目:Lunar New Year and Number Division 题目大意:给定一个数字序列,可以任意分组(可调整顺序),但每组至少两个,求每组内数字和的平方的最小值 思路 首先,易证 ...

  2. 经常遇到的js兼容问题大总结----最全总结

    001.获取滚动条滚动的距离 var sTop = document.documentElement.scrollTop || document.body.scrollTop 002.获取非行间样式 ...

  3. CAD参数绘制椭圆弧(com接口)

    在CAD设计时,需要绘制椭圆弧,用户可以设置椭圆弧基本属性. 主要用到函数说明: _DMxDrawX::DrawEllipseArc 绘制椭圆弧.详细说明如下: 参数 说明 DOUBLE dCente ...

  4. HR教你面试时怎么谈出高工资

    不是任何时候谈钱都会伤感情,比如跟客户谈合同报价,跟房东谈房租,以及面试时和公司HR谈新工作的薪酬待遇. 这事儿一般不需要你先开口.在面试进入尾声的时候,如果HR对你还算满意,通常就会开始问你目前的薪 ...

  5. MSYS2 使用

    在Windows下编译mongo-c-driver 1.3.x 在Windows下编译mongo-c-driver 1.3.x 1.安装 MSYS2https://sourceforge.net/pr ...

  6. SQL server 表操作语句(原创)

    CREATE TABLE [dbo].[test] ([id11] int NOT NULL ,[as] varchar(1) COLLATE Chinese_PRC_CI_AS NULL ,[asd ...

  7. MRC转ARC(2)

    春节前抽空花了一天的时间将手头的工程从MRC转成了ARC,然后陆陆续续地修复一部分因为转ARC引起的内存泄漏和崩溃,到目前为止工程也算是比较稳定了,抽空记上一笔.(虽说这种事情这辈子估计都只会做这么一 ...

  8. sysbench--mysql测试

    1.下载sysbench-0.4.12.14.tar.gz 2.解压.tar -zxf sysbench-0.4.12.14.tar.gz 3.编译: 填写mysql路劲. ./configure - ...

  9. Python之购物车

    Python之购物车 msg_list = [ ['iphone',8888], ['coffe',38], ['book',90], ['Tesla',100000], ['RR',10000000 ...

  10. python面向对象编程设计与开发

    一.什么是面向对象的程序设计 1.何为数据结构? 数据结构是指相互之间存在一种或多种特定关系的数据元素的集合,如列表.字典. 2.何为编程? 编程是指程序员用特定的语法+数据结构+算法,组成的代码,告 ...