本篇帶你使用 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,就可以渲染出列表了。
效果圖:

