本篇帶你使用 AntDesign 組件庫爲咱們的系統換上產品級的UI!javascript
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" }] ] }
咱們計劃把系統改形成這個樣子:css
上方顯示LOGO,下方左側顯示一個菜單欄,右側顯示頁面的主要內容。html
因此新的HomeLayout應該包括LOGO和Menu部分,而後HomeLayout的children放置在Content區域。java
Menu咱們使用AntDesign提供的Menu組件來完成,菜單項爲:react
來看新的組件代碼:git
/** * 佈局組件 */ 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
這個樣式文件,樣式代碼爲:github
@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; }
如今的首頁是這個樣子:npm
逼格立馬就上來了有沒?json
因爲如今有菜單了,就不須要右側那個HomePage裏的連接了,把他去掉,而後放個Welcome吧(HomeLayout也去掉了,在下面會提到):api
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裏有一個菜單了,菜單有展開狀態須要維護,若是仍是像之前那樣在每一個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和登陸頁面組件相似,可是在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中使用了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組件,這裏須要把組件中的原生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,就能夠渲染出列表了。
效果圖: