├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmignore ├── README.md ├── dist ├── components │ └── BatchOperationTables │ │ ├── index.css │ │ └── index.js └── index.js ├── next.js ├── package.json └── src ├── components └── BatchOperationTables │ ├── index.css │ └── index.js └── index.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | Makefile 3 | node_modules 4 | npm-debug.log 5 | .DS_Store 6 | .idea 7 | package-lock.json 8 | yarn.lock 9 | .vscode -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .git 4 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # armour-antd 2 | 3 | **基于 Ant Design 封装的定制化功能组件库。** 4 | 5 | ## 安装 6 | 7 | **使用 npm 或者 yarn 安装:** 8 | 9 | > $ npm install armour-antd --save 10 | 11 | > $ yarn add armour-antd 12 | 13 | **如果网络状况不佳可以使用 cnpm 安装。** 14 | 15 | ## 示例 16 | 17 | ```js 18 | import { BatchOperationTables } from 'armour-antd'; 19 | 20 | ReactDOM.render(, mountNode); 21 | ``` 22 | 23 | ## 按需加载 24 | 25 | **使用 [`babel-plugin-armour-import`](https://github.com/shenqiuhui/babel-plugin-armour-import) 插件, 在 `.babelrc` 或者 `webpack` 配置文件 `babel-loader` 配置项中进行配置。** 26 | 27 | ```js 28 | plugins: [ 29 | [ 30 | 'armour-import', 31 | { libararyName: 'armour-antd' } 32 | ] 33 | ] 34 | ``` 35 | 36 | **然后只需从 `armour-antd` 引入模块即可,无需单独引入样式,等同于下面手动引入的方式。** 37 | 38 | ```js 39 | import { BatchOperationTables } from 'armour-antd'; 40 | ``` 41 | 42 | **手动引入** 43 | 44 | ```js 45 | import BatchOperationTables from 'armour-antd/dist/components/BatchOperationTables'; 46 | ``` 47 | 48 | ## 组件使用 49 | 50 | ### BatchOperationTables 组件 51 | 52 | 该组件用于帮助收集 `Ant Design` 的 `Table` 组件通过请求每页数据变化时,所有被勾选的数据,同时提供收集数据列表方便你进一步操作选中数据,并在页码切换渲染每页新数据时帮你回显已收集数据的勾选状态,处理收集数据列表操作和数据表格勾选操作时勾选状态的联动。 53 | 54 | #### Example 55 | 56 | - [单表格 Demo](https://codesandbox.io/s/xj66m00p5z) 57 | - [双表格 Demo](https://codesandbox.io/s/m56qmr69yx) 58 | 59 | #### API 60 | 61 | | 参数 | 说明 | 类型 | 默认值 | 62 | | --- | --- | --- | --- | 63 | | columns | 表格列的配置描述,同 `Ant Design` 的 `Table` , 具体见 https://ant.design/components/table-cn/#Column | ColumnProps[] | - | 64 | | dataSource | 数据数组 | object[] | [] | 65 | | rowId | `dataSource` 中每一项数据的唯一标识字段 | string | 'id' | 66 | | selectedRows | 存储数据表格中被选中项,可以是父级组件的 `state` 或 `redux` 等状态管理的 `state` 中某一个属性的引用 | object[] | [] | 67 | | total | 数据表格的数据总数,用于实现分页功能 | number | 0 | 68 | | current | 分页器默认选中的页码 | number | 1 | 69 | | pageSize | 数据表格默认每页数据条数 | number | 10 | 70 | | collectTable | 是否展示已选中数据列表,如果显示,则 `columns` 配置第一项为序号配置,最后一项为操作配置 | boolean | false | 71 | | showQuickJumper | 是否可以快速跳转至某页,该配置同时在数据表格和已选中数据列表生效 | boolean | false | 72 | | showSizeChanger | 是否可以改变 `pageSize`,该配置同时在数据表格和已选中数据列表生效 | boolean | false | 73 | | loading | 是否加载 `loading`,同 `Ant Design` 的 `Table` 中的 `loading`,该配置同时在数据表格和已选中数据列表生效 | boolean | false | 74 | | pageSizeOptions | 指定每页可以显示多少条,该配置同时在数据表格和已选中数据列表生效 | string[] | ['10', '20', '30', '40', '50'] | 75 | | updatePagination | 修改数据表格分页器页码 `current` 和每页条数 `pageSize` 时触发,提供当前选中页码数和每页条数 | Function(page, pageSize) | noop | 76 | | ejectCollectData | 数据表格勾选状态发生变化时触发,提供所有页码被勾选后的数据合集 | Function(selectedRows) | noop | 77 | | styleOptions | 用于配置 `BatchOperationTables` 组件的样式选项集合 | styleOptionsProps{} | - | 78 | | textObtions | 用于配置 `BatchOperationTables` 组件的文本选项集合 | textObtions{} | - | 79 | 80 | #### styleOptions 81 | 82 | | 参数 | 说明 | 类型 | 默认值 | 83 | | --- | --- | --- | --- | 84 | | wrapperStyle | 组件容器的类名,用于设置组件容器的样式 | string | 'default-wrapper-class' | 85 | | totalStyle | 组件数据总数信息的类名,用于设置组件数据总数信息样式 | string | 'default-total-class' | 86 | | tableStyle | 组件表格容器类名,用于设置组件表格的样式 | string | 'default-table-class' | 87 | | size | 表格大小,同 `Ant Design` 的 `Table` | default \| middle \| small | default | 88 | | bordered | 是否展示外边框和列边框,同 `Ant Design` 的 `Table` | boolean | false | 89 | 90 | #### textObtions 91 | 92 | | 参数 | 说明 | 类型 | 默认值 | 93 | | --- | --- | --- | --- | 94 | | dataEmptyText | 数据表格无数据提示文本 | string | '暂无数据' | 95 | | collectEmptyText | 已选中数据列表无数据提示文本 | string | '暂无选中数据' | 96 | | dataTotalText | 数据总数文本 | string[] | ['共', '项'] | 97 | | collectTotalText | 已选中数据总数文本 | string[] | ['已选择', '项'] | 98 | | sepText | 单表格(`collectTable` 未设置或值为 `false`)时,数据总数文本和已选中数据总数文本的分隔符,双表格时该配置不生效 | string | \  | 99 | -------------------------------------------------------------------------------- /dist/components/BatchOperationTables/index.css: -------------------------------------------------------------------------------- 1 | .default-total-class { 2 | padding: 10px; 3 | font-size: 12px; 4 | color: #aaa; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /dist/components/BatchOperationTables/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | require("core-js/modules/es6.array.sort"); 9 | 10 | var _react = _interopRequireWildcard(require("react")); 11 | 12 | var _antd = require("antd"); 13 | 14 | require("antd/lib/table/style/css"); 15 | 16 | require("./index.css"); 17 | 18 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } 19 | 20 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } 21 | 22 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 23 | 24 | class BatchOperationTables extends _react.Component { 25 | constructor(...args) { 26 | super(...args); 27 | 28 | _defineProperty(this, "state", { 29 | selectedRowsMap: {}, 30 | // 选中数据集合 31 | selectedRowKeys: [], 32 | // 选中数据在当前页索引集合 33 | pageSizeCollect: 10 // 选中数据默认条数 34 | 35 | /** 36 | * 给数据数组的每一项增加 key 字段 37 | * @param {Array} [data] 存储数据的数组 38 | */ 39 | 40 | }); 41 | 42 | _defineProperty(this, "addKeyToData", data => data.reduce((prev, next, current) => { 43 | let item = JSON.parse(JSON.stringify(next)); 44 | item.key = current; 45 | return prev.push(item), prev; 46 | }, [])); 47 | 48 | _defineProperty(this, "selectedMapToArray", selectedRowsMap => Object.keys(selectedRowsMap).reduce((target, rowId) => { 49 | return selectedRowsMap[rowId].selected && target.push(selectedRowsMap[rowId].record), target; 50 | }, []).sort((a, b) => b.orderMark - a.orderMark)); 51 | 52 | _defineProperty(this, "selectedArrayToMap", (rowId, selected, source, target = {}) => { 53 | return source.forEach(value => target[value[rowId]] = { 54 | [rowId]: value[rowId], 55 | record: value, 56 | selected 57 | }), target; 58 | }); 59 | 60 | _defineProperty(this, "findSelectedRowKeys", (rowId, selectedRows) => selectedRows.map(record => record[rowId])); 61 | 62 | _defineProperty(this, "createOrderMark", source => { 63 | let { 64 | orderMark = 0 65 | } = this.selectedMapToArray(this.state.selectedRowsMap)[0] || {}; 66 | let newSelectRows = Array.isArray(source) ? source : [source]; 67 | return newSelectRows.reverse().forEach((v, idx) => v.orderMark = ++idx + orderMark), newSelectRows; 68 | }); 69 | 70 | _defineProperty(this, "sendDataOutComponent", (rowId, selected, source, callback) => { 71 | this.setState({ 72 | selectedRowsMap: _objectSpread({}, this.state.selectedRowsMap, this.selectedArrayToMap(rowId, selected, this.createOrderMark(source))) 73 | }, callback); 74 | }); 75 | 76 | _defineProperty(this, "initSelectedRowMapAndKeys", props => { 77 | let { 78 | selectedRows = [], 79 | rowId = 'id' 80 | } = props; // 计算 Map 和选中项索引集合存入 state 81 | 82 | let selectedRowsMap = this.selectedArrayToMap(rowId, true, selectedRows); 83 | let selectedRowKeys = this.findSelectedRowKeys(rowId, selectedRows); 84 | this.setState({ 85 | selectedRowsMap, 86 | selectedRowKeys 87 | }); 88 | }); 89 | } 90 | 91 | // 第一次挂载初始化(将合集数组转换 Map,计算当前选中项索引集合) 92 | componentDidMount() { 93 | this.initSelectedRowMapAndKeys(this.props); 94 | } // 每次更新变化时初始化(将合集数组转换 Map,计算当前选中项索引集合) 95 | 96 | 97 | componentWillReceiveProps(nextProps) { 98 | this.initSelectedRowMapAndKeys(nextProps); 99 | } 100 | 101 | render() { 102 | const { 103 | selectedRowKeys, 104 | pageSizeCollect 105 | } = this.state; 106 | const { 107 | rowId = 'id', 108 | columns = [], 109 | // 表格规则,遵循 antd(数组) 110 | dataSource = [], 111 | // 数据源(数组) 112 | selectedRows = [], 113 | // 选中的数据(数组) 114 | total = 0, 115 | // 数据总数(含未请求) 116 | current = 1, 117 | // 当前页码,默认为第 1 页,有可能后端数据的页码参数不是从 1 开始,而是从 0 开始,也可能是每一页数据的 start 值,所以需要这个参数来设置页码,表格组件只接受从 1 到 n 的也码数,可在外面自行计算 118 | pageSize = 10, 119 | // 每页数据条数,默认 10 条 120 | loading = false, 121 | // 是否加载 loading 122 | showQuickJumper = false, 123 | // 是否增加快速跳转页码 124 | showSizeChanger = false, 125 | // 是否增加改变每页条数 126 | collectTable = false, 127 | // 是否显示收集的表格 128 | pageSizeOptions = ['10', '20', '30', '40', '50'], 129 | // 表格默认支持分页选项 130 | styleOptions = {}, 131 | // 样式选项 132 | textObtions = {}, 133 | // 文本选项 134 | updatePagination, 135 | // 更新页码函数 136 | ejectCollectData // 将目标表格收集的数据合集(selectedRows)更新到外层组件或 Redux 的方法 137 | 138 | } = this.props; 139 | const { 140 | wrapperStyle = 'default-wrapper-class', 141 | // 容器样式的类名 142 | tableStyle = 'default-table-class', 143 | // 表格样式的类名 144 | totalStyle = 'default-total-class', 145 | // 数量信息样式的类名 146 | size = 'default', 147 | // 表格大小,可选 small、middle 148 | bordered = false // 是否有边框 149 | 150 | } = styleOptions; 151 | const { 152 | dataEmptyText = '暂无数据', 153 | // 数据表格无数据提示文本 154 | collectEmptyText = '暂无选中数据', 155 | // 收集数据表格无选中提示文本 156 | dataTotalText = ['共', '项'], 157 | // 数据总数文本 158 | collectTotalText = ['已选择', '项'], 159 | // 已选中数据总数文本 160 | sepText = '' // 单表格时数据总数文本和已选中数据总数文本分隔符,默认为一个制表符 161 | 162 | } = textObtions; // 复选框操作配置 163 | 164 | const rowSelection = { 165 | selectedRowKeys, 166 | onSelect: (record, selected) => this.sendDataOutComponent(rowId, selected, record, () => { 167 | ejectCollectData && ejectCollectData(this.selectedMapToArray(this.state.selectedRowsMap)); 168 | }), 169 | onSelectAll: (selected, selectedRows, changeRows) => this.sendDataOutComponent(rowId, selected, changeRows, () => { 170 | ejectCollectData && ejectCollectData(this.selectedMapToArray(this.state.selectedRowsMap)); 171 | }) 172 | }; // 未搜索到数据提示 173 | 174 | const noData = { 175 | emptyText: dataEmptyText 176 | }; // 选中数据为空提示 177 | 178 | const noCollect = { 179 | emptyText: collectEmptyText 180 | }; // 分页器配置 181 | 182 | const pagination = { 183 | pageSize, 184 | current, 185 | total, 186 | showQuickJumper, 187 | showSizeChanger, 188 | pageSizeOptions, 189 | onChange: (current, pageSize) => updatePagination && updatePagination(current, pageSize), 190 | onShowSizeChange: (current, pageSize) => updatePagination && updatePagination(current, pageSize) 191 | }; 192 | const paginationCollect = { 193 | pageSize: pageSizeCollect, 194 | showQuickJumper, 195 | showSizeChanger, 196 | onShowSizeChange: (current, pageSizeCollect) => this.setState({ 197 | pageSizeCollect 198 | }) 199 | }; // 将合集数据源处理并渲染(处理 key) 200 | 201 | const selectedRowsList = collectTable && this.addKeyToData(selectedRows); 202 | return _react.default.createElement("div", { 203 | className: wrapperStyle 204 | }, _react.default.createElement("div", null, _react.default.createElement("div", { 205 | className: totalStyle 206 | }, _react.default.createElement("span", null, dataTotalText[0], " ", total, " ", dataTotalText[1]), !collectTable && _react.default.createElement(_react.Fragment, null, sepText ? _react.default.createElement("span", null, sepText) : _react.default.createElement("span", null, "\u2002"), _react.default.createElement("span", null, collectTotalText[0], " ", selectedRows.length, " ", collectTotalText[1]))), _react.default.createElement(_antd.Table, { 207 | rowKey: rowId, 208 | className: tableStyle, 209 | bordered: bordered, 210 | size: size, 211 | rowSelection: rowSelection, 212 | columns: collectTable ? columns.slice(1, -1) : columns, 213 | pagination: pagination, 214 | dataSource: this.addKeyToData(dataSource), 215 | locale: noData, 216 | loading: loading 217 | })), dataSource.length > 0 && collectTable && _react.default.createElement("div", null, _react.default.createElement("div", { 218 | className: totalStyle 219 | }, collectTotalText[0], " ", selectedRowsList.length, " ", collectTotalText[1]), _react.default.createElement(_antd.Table, { 220 | className: tableStyle, 221 | bordered: bordered, 222 | size: size, 223 | columns: columns, 224 | pagination: paginationCollect, 225 | dataSource: selectedRowsList, 226 | locale: noCollect 227 | }))); 228 | } 229 | 230 | } 231 | 232 | exports.default = BatchOperationTables; -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "BatchOperationTables", { 7 | enumerable: true, 8 | get: function () { 9 | return _BatchOperationTables.default; 10 | } 11 | }); 12 | 13 | var _BatchOperationTables = _interopRequireDefault(require("./components/BatchOperationTables")); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /next.js: -------------------------------------------------------------------------------- 1 | export * from 'src'; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "armour-antd", 3 | "version": "0.0.13", 4 | "description": "The custom components for Ant Design!", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/shenqiuhui/armour-antd" 8 | }, 9 | "main": "dist", 10 | "jsnext:main": "next.js", 11 | "scripts": { 12 | "start": "npm run build", 13 | "build": "babel src -d dist --ignore 'src/**/*.css' --copy-files", 14 | "clean": "rm -rf dist", 15 | "prebuild": "npm run clean", 16 | "prepublish": "npm run build" 17 | }, 18 | "keywords": [ 19 | "armour", 20 | "antd", 21 | "custom", 22 | "component", 23 | "components" 24 | ], 25 | "author": "Panda Shen", 26 | "license": "MIT", 27 | "peerDependencies": { 28 | "react": "^16.3.2", 29 | "react-dom": "^16.3.2", 30 | "antd": "^3.9.0" 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.0.0", 34 | "@babel/core": "^7.0.0", 35 | "@babel/plugin-proposal-class-properties": "^7.0.0", 36 | "@babel/polyfill": "^7.0.0", 37 | "@babel/preset-env": "^7.0.0", 38 | "@babel/preset-react": "^7.0.0" 39 | }, 40 | "babel": { 41 | "presets": [ 42 | [ 43 | "@babel/preset-env", 44 | { 45 | "targets": { 46 | "node": "6.9.0" 47 | }, 48 | "useBuiltIns": "usage" 49 | } 50 | ], 51 | "@babel/preset-react" 52 | ], 53 | "plugins": [ 54 | "@babel/plugin-proposal-class-properties" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/BatchOperationTables/index.css: -------------------------------------------------------------------------------- 1 | .default-total-class { 2 | padding: 10px; 3 | font-size: 12px; 4 | color: #aaa; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/components/BatchOperationTables/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { Table } from 'antd'; 3 | import 'antd/lib/table/style/css'; 4 | import './index.css'; 5 | 6 | export default class BatchOperationTables extends Component { 7 | state = { 8 | selectedRowsMap: {}, // 选中数据集合 9 | selectedRowKeys: [], // 选中数据在当前页索引集合 10 | pageSizeCollect: 10 // 选中数据默认条数 11 | } 12 | 13 | /** 14 | * 给数据数组的每一项增加 key 字段 15 | * @param {Array} [data] 存储数据的数组 16 | */ 17 | addKeyToData = data => data.reduce((prev, next, current) => { 18 | let item = JSON.parse(JSON.stringify(next)); 19 | item.key = current; 20 | return (prev.push(item), prev); 21 | }, []); 22 | 23 | /** 24 | * 将 Map 转换成数组进行存储(过滤 selected 为 false 的项) 25 | * @param {Object} [selectedRowsMap] 存储选中数据的 Map 26 | */ 27 | selectedMapToArray = selectedRowsMap => Object.keys(selectedRowsMap).reduce((target, rowId) => { 28 | return (selectedRowsMap[rowId].selected && target.push(selectedRowsMap[rowId].record), target); 29 | }, []).sort((a, b) => b.orderMark - a.orderMark); 30 | 31 | /** 32 | * 将数组转换成 Map 进行比对 33 | * @param {String} [rowId] 遍历数据的唯一标识字段 34 | * @param {Boolean} [selected] 设定 Map 每项是否选中 35 | * @param {Array} [source] 存储选中数据的 Map 36 | * @param {Object} [target] 生成 Map 的初始值 37 | */ 38 | selectedArrayToMap = (rowId, selected, source, target = {}) => { 39 | return (source.forEach(value => (target[value[rowId]] = { [rowId]: value[rowId], record: value, selected })), target); 40 | } 41 | 42 | /** 43 | * 检测当前数据源,找到索引回显选中状态(方法) 44 | * @param {String} [rowId] 遍历数据的唯一标识字段 45 | * @param {Array} [dataSource] 数据源 46 | * @param {Object} [selectedRowsMap] 存储选中数据的 Map 47 | */ 48 | findSelectedRowKeys = (rowId, selectedRows) => selectedRows.map((record) => record[rowId]); 49 | 50 | /** 51 | * 对选中数据增加排序字段 52 | * @param {Object|Array} [source] 当前被勾选的某项或某几项 53 | */ 54 | createOrderMark = source => { 55 | let { orderMark = 0 } = this.selectedMapToArray(this.state.selectedRowsMap)[0] || {}; 56 | let newSelectRows = Array.isArray(source) ? source : [source]; 57 | 58 | return (newSelectRows.reverse().forEach((v, idx) => (v.orderMark = ++idx + orderMark)), newSelectRows); 59 | } 60 | 61 | /** 62 | * 将新选中的项与之前选中项求和发射到组件外部(父组件的 state 或 Redux 等状态管理中) 63 | * @param {String} [rowId] 遍历数据的唯一标识字段 64 | * @param {Boolean} [selected] 设定 Map 每项是否选中 65 | * @param {Array} [source] 存储选中数据的 Map 66 | * @param {Function} [callback] 在组件 state 内部更新 selectedRowsMap 后执行的回调 67 | */ 68 | sendDataOutComponent = (rowId, selected, source, callback) => { 69 | this.setState({ 70 | selectedRowsMap: { 71 | ...this.state.selectedRowsMap, 72 | ...this.selectedArrayToMap(rowId, selected, this.createOrderMark(source)) 73 | }, 74 | }, callback); 75 | } 76 | 77 | /** 78 | * 初始化组件计算 Map 和选中项索引集合存入 state 的 init 方法 79 | * @param {Object} [props] 组件外部传入的属性 80 | */ 81 | initSelectedRowMapAndKeys = props => { 82 | let { selectedRows = [], rowId = 'id' } = props; 83 | 84 | // 计算 Map 和选中项索引集合存入 state 85 | let selectedRowsMap = this.selectedArrayToMap(rowId, true, selectedRows); 86 | let selectedRowKeys = this.findSelectedRowKeys(rowId, selectedRows); 87 | 88 | this.setState({ selectedRowsMap, selectedRowKeys }); 89 | } 90 | 91 | // 第一次挂载初始化(将合集数组转换 Map,计算当前选中项索引集合) 92 | componentDidMount() { 93 | this.initSelectedRowMapAndKeys(this.props); 94 | } 95 | 96 | // 每次更新变化时初始化(将合集数组转换 Map,计算当前选中项索引集合) 97 | componentWillReceiveProps(nextProps) { 98 | this.initSelectedRowMapAndKeys(nextProps); 99 | } 100 | 101 | render() { 102 | const { selectedRowKeys, pageSizeCollect } = this.state; 103 | 104 | const { 105 | rowId = 'id', 106 | columns = [], // 表格规则,遵循 antd(数组) 107 | dataSource = [], // 数据源(数组) 108 | selectedRows = [], // 选中的数据(数组) 109 | total = 0,// 数据总数(含未请求) 110 | current = 1, // 当前页码,默认为第 1 页,有可能后端数据的页码参数不是从 1 开始,而是从 0 开始,也可能是每一页数据的 start 值,所以需要这个参数来设置页码,表格组件只接受从 1 到 n 的也码数,可在外面自行计算 111 | pageSize = 10, // 每页数据条数,默认 10 条 112 | loading = false, // 是否加载 loading 113 | showQuickJumper = false, // 是否增加快速跳转页码 114 | showSizeChanger = false, // 是否增加改变每页条数 115 | collectTable = false, // 是否显示收集的表格 116 | pageSizeOptions = ['10', '20', '30', '40', '50'], // 表格默认支持分页选项 117 | styleOptions = {}, // 样式选项 118 | textObtions = {}, // 文本选项 119 | updatePagination, // 更新页码函数 120 | ejectCollectData, // 将目标表格收集的数据合集(selectedRows)更新到外层组件或 Redux 的方法 121 | } = this.props; 122 | 123 | const { 124 | wrapperStyle = 'default-wrapper-class', // 容器样式的类名 125 | tableStyle = 'default-table-class', // 表格样式的类名 126 | totalStyle = 'default-total-class', // 数量信息样式的类名 127 | size = 'default', // 表格大小,可选 small、middle 128 | bordered = false // 是否有边框 129 | } = styleOptions; 130 | 131 | const { 132 | dataEmptyText = '暂无数据', // 数据表格无数据提示文本 133 | collectEmptyText = '暂无选中数据', // 收集数据表格无选中提示文本 134 | dataTotalText = ['共', '项'], // 数据总数文本 135 | collectTotalText = ['已选择', '项'], // 已选中数据总数文本 136 | sepText = '', // 单表格时数据总数文本和已选中数据总数文本分隔符,默认为一个制表符 137 | } = textObtions; 138 | 139 | // 复选框操作配置 140 | const rowSelection = { 141 | selectedRowKeys, 142 | onSelect: (record, selected) => this.sendDataOutComponent(rowId, selected, record, () => { 143 | ejectCollectData && ejectCollectData(this.selectedMapToArray(this.state.selectedRowsMap)); 144 | }), 145 | onSelectAll: (selected, selectedRows, changeRows) => this.sendDataOutComponent(rowId, selected, changeRows, () => { 146 | ejectCollectData && ejectCollectData(this.selectedMapToArray(this.state.selectedRowsMap)); 147 | }) 148 | }; 149 | 150 | // 未搜索到数据提示 151 | const noData = { 152 | emptyText: dataEmptyText 153 | }; 154 | 155 | // 选中数据为空提示 156 | const noCollect = { 157 | emptyText: collectEmptyText 158 | }; 159 | 160 | // 分页器配置 161 | const pagination = { 162 | pageSize, 163 | current, 164 | total, 165 | showQuickJumper, 166 | showSizeChanger, 167 | pageSizeOptions, 168 | onChange: (current, pageSize) => updatePagination && updatePagination(current, pageSize), 169 | onShowSizeChange: (current, pageSize) => updatePagination && updatePagination(current, pageSize) 170 | }; 171 | 172 | const paginationCollect = { 173 | pageSize: pageSizeCollect, 174 | showQuickJumper, 175 | showSizeChanger, 176 | onShowSizeChange: (current, pageSizeCollect) => this.setState({ pageSizeCollect }) 177 | }; 178 | 179 | // 将合集数据源处理并渲染(处理 key) 180 | const selectedRowsList = collectTable && this.addKeyToData(selectedRows); 181 | 182 | return ( 183 |
184 |
185 |
186 | {dataTotalText[0]} {total} {dataTotalText[1]} 187 | { 188 | !collectTable && ( 189 | 190 | { 191 | sepText ? {sepText} : 192 | } 193 | {collectTotalText[0]} {selectedRows.length} {collectTotalText[1]} 194 | 195 | ) 196 | } 197 |
198 | 210 | 211 | { 212 | dataSource.length > 0 && collectTable && ( 213 |
214 |
215 | {collectTotalText[0]} {selectedRowsList.length} {collectTotalText[1]} 216 |
217 |
226 | 227 | ) 228 | } 229 | 230 | ) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as BatchOperationTables } from './components/BatchOperationTables'; 2 | --------------------------------------------------------------------------------