本篇带你使用 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={{ '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; 100%; font-size: 18px; padding: 0 20px; line-height: 50px; background-color: #108ee9; color: #fff; a { color: inherit; } } .menu { height: 100%; 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{ 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 { 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 { 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={{ '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={{'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,就可以渲染出列表了。
效果图: