react 项目结合 antd 实现强大的后台管理系统表格组件
简介: antd 本身的表格组件十分强大,十分灵活,这里基于 antd 的 Table 组件做业务封装,支持功能:表格索引,loading,分页,按钮操作,数据格式化,数据选择等。
项目主要依赖:(先安装,步骤略)
create-react-app:3.0.0
1 | { |
2 | "react": "16.8.6", |
3 | "react-router-dom": "5.0.0", |
4 | "antd": "^3.19.2", |
5 | "axios": "^0.19.0", |
6 | "prop-types": "^15.7.2" |
7 | } |
1.组件
src/components/AppTable/index.jsx
1 | import React, { Component, Fragment } from "react"; |
2 | import { Table, Button, Modal, Layout, Icon } from "antd"; |
3 | import PropTypes from "prop-types"; |
4 | const { Content } = Layout; |
5 | |
6 | class AppTable extends Component { |
7 | state = { |
8 | // 大图数据 |
9 | imgSrc: "" |
10 | }; |
11 | |
12 | // props类型检查 |
13 | static propTypes = { |
14 | // 操作栏宽度 |
15 | operationWidth: PropTypes.number, |
16 | // 配置 |
17 | config: PropTypes.object.isRequired, |
18 | // 是否需要操作栏 |
19 | hasOperation: PropTypes.bool, |
20 | // 是否需要索引 |
21 | hasIndex: PropTypes.bool, |
22 | // 是否需要选择 |
23 | hasSelect: PropTypes.bool, |
24 | // 监听分页改变 |
25 | handlePageChange: PropTypes.func, |
26 | // 监听选择表格 |
27 | handleSelecet: PropTypes.func, |
28 | // 监听操作栏 |
29 | handleTableOption: PropTypes.func, |
30 | // 点击表格单元格 |
31 | handleClickCell: PropTypes.func, |
32 | // 上边距 |
33 | marginTop: PropTypes.number |
34 | }; |
35 | |
36 | // 默认的props |
37 | static defaultProps = { |
38 | // 操作栏宽度 |
39 | operationWidth: 100, |
40 | // 是否需要操作栏 |
41 | hasOperation: true, |
42 | // 是否需要索引 |
43 | hasIndex: true, |
44 | // 是否需要选择 |
45 | hasSelect: false, |
46 | // 监听分页改变 |
47 | handlePageChange: null, |
48 | // 监听选择表格 |
49 | handleSelecet: null, |
50 | // 监听操作栏 |
51 | handleTableOption: null, |
52 | // 点击表格单元格 |
53 | handleClickCell: null, |
54 | // 上边距 |
55 | marginTop: 20 |
56 | }; |
57 | |
58 | componentWillUnmount() { |
59 | this.setState = (state, callback) => { |
60 | return; |
61 | }; |
62 | } |
63 | |
64 | // 点击查看大图 |
65 | handleClickImage(src) { |
66 | Modal.info({ |
67 | title: "大图", |
68 | okText: "确定", |
69 | style: { top: "15vh" }, |
70 | content: ( |
71 | <div style={{ textAlign: "center" }}> |
72 | <img src={src} alt="大图" /> |
73 | </div> |
74 | ) |
75 | }); |
76 | } |
77 |
|
78 | render() { |
79 | let { |
80 | config, |
81 | operationWidth, |
82 | hasOperation, |
83 | hasIndex, |
84 | hasSelect, |
85 | handlePageChange, |
86 | handleSelecet, |
87 | handleTableOption, |
88 | marginTop |
89 | } = this.props; |
90 | let { columns, pagination } = config; |
91 |
|
92 | // 特殊表格处理(如需更多特殊显示,添加case即可解决) |
93 | columns.forEach(item => { |
94 | if (item.format) { |
95 | switch (item.format) { |
96 | // 图片显示 |
97 | case "image": |
98 | item.render = (value, row, index) => { |
99 | return ( |
100 | <img |
101 | key={row.key} |
102 | onClick={this.handleClickImage.bind(this, value)} |
103 | width={30} |
104 | src={value} |
105 | alt="图片损坏" |
106 | /> |
107 | ); |
108 | }; |
109 | break; |
110 | // 金额显示 |
111 | case "money": |
112 | item.render = value => "¥" + value / 100; // 显示为金额,返回的单位是分,处理时除以100换算成元 |
113 | break; |
114 | default: |
115 | } |
116 | } |
117 | }); |
118 | |
119 | // 默认添加索引 |
120 | if (hasIndex && columns.length) { |
121 | const Index = columns[0]; |
122 | const { current, pageSize } = pagination; |
123 | const indexItem = { |
124 | title: "#", |
125 | key: "index", |
126 | fixed: "left", |
127 | format: "index", |
128 | width: 60, |
129 | render: (value, row, index) => ( |
130 | <span key={index}>{pageSize * (current - 1) + index + 1}</span> |
131 | ) |
132 | }; |
133 | if (Index.title !== "#") { |
134 | columns.unshift(indexItem); |
135 | } else { |
136 | columns[0] = indexItem; |
137 | } |
138 | } |
139 |
|
140 | // 默认添加操作栏 |
141 | const buttonContent = option => { |
142 | let type = ""; |
143 | switch (option) { |
144 | case "查看": |
145 | type = "eye"; |
146 | break; |
147 | case "显示": |
148 | type = "eye"; |
149 | break; |
150 | case "隐藏": |
151 | type = "eye-invisible"; |
152 | break; |
153 | case "编辑": |
154 | type = "edit"; |
155 | break; |
156 | case "删除": |
157 | type = "delete"; |
158 | break; |
159 | case "排序": |
160 | type = "sort-ascending"; |
161 | break; |
162 | default: |
163 | break; |
164 | } |
165 | if (type) { |
166 | return ( |
167 | <Fragment> |
168 | <Icon type={type} style={{ paddingRight: 5 }} /> {option} |
169 | </Fragment> |
170 | ); |
171 | } else { |
172 | return option; |
173 | } |
174 | }; |
175 |
|
176 | const operation = columns[columns.length - 1]; |
177 | if (columns.length && hasOperation && operation.title !== "操作") { |
178 | columns.push({ |
179 | title: "操作", |
180 | dataIndex: "operation", |
181 | width: operationWidth, |
182 | format: "operation", |
183 | key: "operation", |
184 | fixed: "right", |
185 | render: (value, row, index) => { |
186 | const buttons = row.buttonsName ? row.buttonsName : []; |
187 | return ( |
188 | <div> |
189 | {buttons.map((option, index) => { |
190 | return ( |
191 | <Button |
192 | size="small" |
193 | type={index === 0 ? "primary" : ""} |
194 | key={index} |
195 | style={{ marginLeft: 10 }} |
196 | onClick={() => handleTableOption(row, option)} |
197 | > |
198 | {buttonContent(option)} |
199 | </Button> |
200 | ); |
201 | })} |
202 | </div> |
203 | ); |
204 | } |
205 | }); |
206 | } |
207 |
|
208 | // 添加可以是否选择表格 |
209 | const rowSelection = { |
210 | onChange: (key, rows) => { |
211 | handleSelecet(key, rows); |
212 | } |
213 | }; |
214 | const selectConfig = hasSelect ? { rowSelection } : {}; |
215 |
|
216 | // 默认表格配置 |
217 | const defaultConfig = { |
218 | bordered: true, |
219 | scroll: { x: 960 } |
220 | }; |
221 |
|
222 | // 默认分页配置 |
223 | const defaultPagination = { |
224 | showQuickJumper: true, |
225 | showSizeChanger: true, |
226 | pageSizeOptions: ["20", "40", "100"] |
227 | }; |
228 | config.pagination = { ...defaultPagination, ...pagination }; |
229 |
|
230 | return ( |
231 | <Content |
232 | style={{ |
233 | background: "#fff", |
234 | padding: 24, |
235 | marginTop |
236 | }} |
237 | > |
238 | {this.props.children} |
239 | <Table |
240 | {...defaultConfig} |
241 | {...selectConfig} |
242 | {...config} |
243 | onChange={handlePageChange} |
244 | /> |
245 | </Content> |
246 | ); |
247 | } |
248 | } |
249 |
|
250 | export default AppTable; |
补充全局 less (需添加到项目中)
1 | :global(#root .ant-table-thead > tr > th) { |
2 | padding: 8px 7px 7px 22px; |
3 | height: 50px; |
4 | box-sizing: border-box; |
5 | overflow: hidden; |
6 | white-space: nowrap; |
7 | } |
8 | |
9 | :global(#root .ant-table-tbody > tr > td) { |
10 | padding: 8px 7px 7px 22px; |
11 | height: 50px; |
12 | box-sizing: border-box; |
13 | overflow: hidden; |
14 | white-space: nowrap; |
15 | } |
2.使用
1 | import React, { Component } from "react"; |
2 | import { withRouter } from "react-router-dom"; |
3 | import AppTable from "@/components/AppTable"; |
4 | |
5 | // 后端接口,根据自己项目更换 |
6 | import UserServe from "@/api/user"; |
7 | |
8 | @withRouter // 修饰器需添加babel插件 |
9 | class User extends Component { |
10 | state = { |
11 | // 用户列表 |
12 | userList: [], // 数据见步骤3 |
13 | // 分页总数 |
14 | total: 0, |
15 | // 表格loading |
16 | loading: false, |
17 | // 分页参数 |
18 | params: { |
19 | perPage: 5, |
20 | page: 1 |
21 | }, |
22 | // 操作栏按钮 |
23 | buttonsName: { |
24 | normal: ["查看", "排序"] |
25 | // special: ['排序'] |
26 | }, |
27 | // 表格操作栏宽度 |
28 | operationWidth: 100, |
29 | // 表格配置 |
30 | columns: [ |
31 | { |
32 | title: "头像", |
33 | dataIndex: "avatarUrl", |
34 | width: 80, |
35 | key: "avatarUrl", |
36 | format: "image" // 显示为图片 |
37 | }, |
38 | { |
39 | title: "昵称", |
40 | dataIndex: "nickname", |
41 | key: "nickname", |
42 | width: 140 |
43 | }, |
44 | { |
45 | title: "真实姓名", |
46 | dataIndex: "userName", |
47 | key: "userName", |
48 | width: 140 |
49 | }, |
50 | { |
51 | title: "电话", |
52 | dataIndex: "userPhone", |
53 | key: "userPhone", |
54 | width: 140 |
55 | }, |
56 | { |
57 | title: "用户身份", |
58 | dataIndex: "userType", |
59 | key: "userType", |
60 | width: 140 |
61 | }, |
62 | { |
63 | title: "账户余额", |
64 | dataIndex: "lastAmount", |
65 | key: "lastAmount", |
66 | width: 140, |
67 | format: "money" // 显示为金额,返回的单位是分,处理时除以100换算成元 |
68 | }, |
69 | { |
70 | title: "预订次数", |
71 | dataIndex: "reserveTimes", |
72 | key: "reserveTimes", |
73 | width: 140 |
74 | }, |
75 | |
76 | { |
77 | title: "上次预订时间", |
78 | dataIndex: "lastReserveTime", |
79 | key: "lastReserveTime" |
80 | } |
81 | ] |
82 | }; |
83 | |
84 | componentDidMount() { |
85 | this.getList(); |
86 | } |
87 | |
88 | componentWillUnmount() { |
89 | this.handleTableOption = null; |
90 | this.handlePageChange = null; |
91 | this.setState = (state, callback) => { |
92 | return; |
93 | }; |
94 | } |
95 | |
96 | // 获取用户列表 |
97 | getList = async () => { |
98 | try { |
99 | this.setState({ loading: true }); |
100 | const { |
101 | buttonsName: { normal } |
102 | } = this.state; |
103 | |
104 | // 拿到的服务端数据格式见步骤3 |
105 | let { |
106 | items, |
107 | page: { total } |
108 | } = await UserServe.getUserList(this.state.params); |
109 | items.forEach(item => { |
110 | item.buttonsName = normal; |
111 | item.key = item.userUuid; |
112 | }); |
113 | |
114 | this.setState({ userList: items, total, loading: false }); |
115 | } catch (error) { |
116 | console.log(error); |
117 | } |
118 | }; |
119 | |
120 | // 操作栏 |
121 | handleTableOption = (row, option) => { |
122 | if (option === "查看") { |
123 | const { history } = this.props; |
124 | history.push(`/user/details?userUuid=${row.userUuid}`); |
125 | } |
126 | }; |
127 | |
128 | // 分页监听 |
129 | handlePageChange = pageInfo => { |
130 | const { current, pageSize } = pageInfo; |
131 | this.setState( |
132 | () => { |
133 | return { params: { perPage: pageSize, page: current } }; |
134 | }, |
135 | () => { |
136 | this.getList(); |
137 | } |
138 | ); |
139 | }; |
140 | |
141 | render() { |
142 | const { |
143 | userList: dataSource, |
144 | total, |
145 | loading, |
146 | params, |
147 | columns, |
148 | operationWidth |
149 | } = this.state; |
150 | const tableConfig = { |
151 | columns, |
152 | dataSource, |
153 | pagination: { |
154 | total, |
155 | pageSize: params.perPage, |
156 | current: params.page |
157 | }, |
158 | loading |
159 | }; |
160 | |
161 | return ( |
162 | <div> |
163 | <AppTable |
164 | config={tableConfig} |
165 | operationWidth={operationWidth} |
166 | handleTableOption={this.handleTableOption} |
167 | handlePageChange={this.handlePageChange} |
168 | /> |
169 | </div> |
170 | ); |
171 | } |
172 | } |
173 |
|
174 | export default User; |
3.补充表格数据
1 | { |
2 | "data": { |
3 | "items": [ |
4 | { |
5 | "avatarUrl": "https://wx.qlogo.cn/mmopen/vi_32/H40abp83zlOAiaqGanY4VpH7kzy6uyOr30Gm634ru0obw9UfSLic4OBxeuH7Oosud15BCwfiazBoCALBIbdsCSjsg/132", |
6 | "lastAmount": 0, |
7 | "lastReserveTime": "2019-08-28 14:38:00", |
8 | "nickname": "\u65e5\u4e45\u751f\u60c5", |
9 | "reserveTimes": 0, |
10 | "userName": null, |
11 | "userPhone": null, |
12 | "userType": "\u666e\u901a\u7528\u6237", |
13 | "userUuid": "60C8546BC95E11E9BD27525400AE34BF" |
14 | }, |
15 | { |
16 | "avatarUrl": "https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTIia1wXh7HxQ2C2CkVyjQpH7zOK2hpDeN75yH0C0wS9z8U8eUt8uibK9ckKAekWSPibDScicaMaoPlqHA/132", |
17 | "lastAmount": 0, |
18 | "lastReserveTime": "2019-08-23 11:46:53", |
19 | "nickname": "\u8def\u4eba\u7532", |
20 | "reserveTimes": 0, |
21 | "userName": null, |
22 | "userPhone": null, |
23 | "userType": "\u666e\u901a\u7528\u6237", |
24 | "userUuid": "A561122FC55811E9BD27525400AE34BF" |
25 | }, |
26 | { |
27 | "avatarUrl": "https://wx.qlogo.cn/mmopen/vi_32/uJpqs0geoUyTbKHdibj3VL05wUn0IgVXosu4D3WdqdcwjhoJ75ukt6an5nSZRYfQzfPvLkhk9e26Ol4ufrkibqvg/132", |
28 | "lastAmount": 0, |
29 | "lastReserveTime": "2019-08-21 14:44:52", |
30 | "nickname": "\u5c0f\u871c\u8702", |
31 | "reserveTimes": 0, |
32 | "userName": null, |
33 | "userPhone": null, |
34 | "userType": "\u666e\u901a\u7528\u6237", |
35 | "userUuid": "2DC231A1C3DF11E9BD27525400AE34BF" |
36 | }, |
37 | { |
38 | "avatarUrl": "https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKD8ffFgoFh3BXZtyP032lhBWJaBUUXzGxgl4IXHf01bLicvPlb4nUpqU2JpcpFRicgW9p0X8S5OcQw/132", |
39 | "lastAmount": 0, |
40 | "lastReserveTime": "2019-07-20 20:58:52", |
41 | "nickname": "\u5e03\u5170\u742a", |
42 | "reserveTimes": 0, |
43 | "userName": null, |
44 | "userPhone": null, |
45 | "userType": "\u666e\u901a\u7528\u6237", |
46 | "userUuid": "1FD14EFBAAEE11E9BD27525400AE34BF" |
47 | }, |
48 | { |
49 | "avatarUrl": "https://wx.qlogo.cn/mmopen/vi_32/U10j5gX0UTCI5Sjbh7wakujtcofvHS4FWhHToJSqrFCSe7bUN1YuX1qkaEWbxQ0B09PXlUabeLC7bAN0rYlXkA/132", |
50 | "lastAmount": 0, |
51 | "lastReserveTime": "2019-07-17 14:44:43", |
52 | "nickname": "\u7f57\u4f1fHalo", |
53 | "reserveTimes": 0, |
54 | "userName": null, |
55 | "userPhone": "13684001024", |
56 | "userType": "\u666e\u901a\u7528\u6237", |
57 | "userUuid": "5B67D4BDA85E11E9BD27525400AE34BF" |
58 | } |
59 | ], |
60 | "page": { |
61 | "currentPage": 1, |
62 | "firstPage": 1, |
63 | "hasNext": true, |
64 | "hasPrev": false, |
65 | "lastPage": 5, |
66 | "nextPage": 2, |
67 | "pageCount": 5, |
68 | "pages": [1, 2, 3, 4, 5], |
69 | "perPage": 5, |
70 | "prevPage": 0, |
71 | "total": 24, |
72 | "totalPages": 5 |
73 | } |
74 | }, |
75 | "status": 1 |
76 | } |
4.使用效果