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.使用效果

在这里插入图片描述