From 86a4cd01dc61a7b7ad8814166517e96c6a460c7d Mon Sep 17 00:00:00 2001
From: campcc <1210131782@qq.com>
Date: Wed, 30 Sep 2020 17:07:44 +0800
Subject: [PATCH] feat: base impl for BiciTable, add custom filters(search,
select, range & date)
---
.eslintignore | 3 +-
.eslintrc | 7 +-
docs/components/BiciTable.md | 144 ++++++++++++++++++
docs/hooks.md | 57 +++++++
jsconfig.json | 8 +
package.json | 5 +-
src/assets/css/property.css | 24 +++
src/components/BiciDatePicker/index.js | 29 ++++
src/components/BiciTable/BiciTable.module.css | 12 ++
.../BiciTable/FilterDropdown/DropdownDate.js | 72 +++++++++
.../BiciTable/FilterDropdown/DropdownRange.js | 68 +++++++++
.../FilterDropdown/DropdownSearch.js | 50 ++++++
.../FilterDropdown/DropdownSelect.js | 96 ++++++++++++
.../BiciTable/FilterDropdown/index.js | 44 ++++++
src/components/BiciTable/FilterTags/index.js | 87 +++++++++++
src/components/BiciTable/constants.js | 9 ++
src/components/BiciTable/index.js | 39 ++++-
src/components/BiciTable/utils.js | 101 ++++++++++++
webpack.config.js | 7 +-
19 files changed, 850 insertions(+), 12 deletions(-)
create mode 100644 docs/hooks.md
create mode 100644 jsconfig.json
create mode 100644 src/components/BiciDatePicker/index.js
create mode 100644 src/components/BiciTable/FilterDropdown/DropdownDate.js
create mode 100644 src/components/BiciTable/FilterDropdown/DropdownRange.js
create mode 100644 src/components/BiciTable/FilterDropdown/DropdownSearch.js
create mode 100644 src/components/BiciTable/FilterDropdown/DropdownSelect.js
create mode 100644 src/components/BiciTable/FilterDropdown/index.js
create mode 100644 src/components/BiciTable/FilterTags/index.js
create mode 100644 src/components/BiciTable/constants.js
create mode 100644 src/components/BiciTable/utils.js
diff --git a/.eslintignore b/.eslintignore
index 1daaae6..354237d 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1 +1,2 @@
-.umi
\ No newline at end of file
+.umi
+webpack.config.js
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
index 8377cd0..24cc3be 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,3 +1,8 @@
{
- "extends": "eslint-config-bicitech"
+ "extends": "eslint-config-bicitech",
+ "rules": {
+ "react/forbid-prop-types": 0,
+ "react/prop-types": 0,
+ "no-console": 0
+ }
}
diff --git a/docs/components/BiciTable.md b/docs/components/BiciTable.md
index 7e2f286..2bee5c5 100644
--- a/docs/components/BiciTable.md
+++ b/docs/components/BiciTable.md
@@ -5,3 +5,147 @@ group:
title: 组件
order: 5
---
+
+# BiciTable
+
+抽象至博智云创大量数据中后台产品的表格,用于展示行列数据。
+
+## 何时使用
+
+- 当有大量结构化的数据需要展现时;
+- 当需要对数据进行排序、搜索、分页、自定义操作等复杂行为时。
+
+## 代码演示
+
+### 基础表格
+
+```jsx
+import React from 'react';
+import { Tag, Space } from 'antd';
+import { BiciTable } from '../../dist/bici-design.js';
+
+const columns = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ filterType: 'search',
+ onFilter: val => console.log(val),
+ render: text => {text},
+ },
+ {
+ title: 'Select',
+ dataIndex: 'select',
+ filterType: 'select',
+ multiple: false,
+ selectData: [
+ { id: 1, name: 'Bici' },
+ { id: 2, name: 'FED' },
+ { id: 3, name: 'Table', dataStatus: -1 },
+ { id: 4, name: 'BiciTable', dataStatus: 2 },
+ ],
+ onFilter: (val, extra) => console.log(val, extra),
+ key: 'select',
+ },
+ {
+ title: 'Multiple',
+ dataIndex: 'multiple',
+ filterType: 'select',
+ multiple: true,
+ selectData: [
+ { id: 1, name: 'Bici' },
+ { id: 2, name: 'FED' },
+ { id: 3, name: 'Table', dataStatus: -1 },
+ { id: 4, name: 'BiciTable', dataStatus: 2 },
+ ],
+ onFilter: (val, extra) => console.log(val, extra),
+ key: 'multiple',
+ },
+ {
+ title: 'Address',
+ dataIndex: 'address',
+ key: 'address',
+ filterType: 'range',
+ onFilter: val => console.log(val),
+ },
+ {
+ title: 'Date',
+ dataIndex: 'date',
+ key: 'date',
+ filterType: 'date',
+ isRange: true,
+ picker: 'date',
+ format: 'YYYY-MM-DD HH:mm:ss',
+ showTime: { format: 'HH:mm:ss' },
+ onFilter: val => console.log(val),
+ },
+ {
+ title: 'Tags',
+ key: 'tags',
+ dataIndex: 'tags',
+ render: tags => (
+ <>
+ {tags.map(tag => {
+ let color = tag.length > 5 ? 'geekblue' : 'green';
+ if (tag === 'loser') {
+ color = 'volcano';
+ }
+ return (
+
+ {tag.toUpperCase()}
+
+ );
+ })}
+ >
+ ),
+ },
+ {
+ title: 'Action',
+ key: 'action',
+ render: (text, record) => (
+
+ Invite {record.name}
+
+ ),
+ },
+];
+
+const data = [
+ {
+ key: '1',
+ name: 'John Brown',
+ select: 'Bici',
+ multiple: 'FED',
+ address: 'New York No. 1 Lake Park',
+ tags: ['nice', 'developer'],
+ date: '2020-05-20',
+ },
+ {
+ key: '2',
+ name: 'Jim Green',
+ select: 'Bici',
+ multiple: 'FED',
+ address: 'London No. 1 Lake Park',
+ tags: ['loser'],
+ date: '2020-05-20',
+ },
+ {
+ key: '3',
+ name: 'Joe Black',
+ select: 'Bici',
+ multiple: 'FED',
+ address: 'Sidney No. 1 Lake Park',
+ tags: ['cool', 'teacher'],
+ date: '2020-05-20',
+ },
+];
+
+export default () => (
+
+);
+```
diff --git a/docs/hooks.md b/docs/hooks.md
new file mode 100644
index 0000000..b7fc2e7
--- /dev/null
+++ b/docs/hooks.md
@@ -0,0 +1,57 @@
+# Hooks
+
+## Class base 迁移
+
+### 1. 执行初始化操作,模拟生命周期
+
+- componentDidMount:useEffect(() => {}, []),第二个参数传空数组,useEffect 作为函数内的同步代码,至少会执行一次,没有依赖项时,不会触发二次执行
+- componentDidMount & componentDidUpdate:useEffect(() => {}),不传第二个参数
+
+### 2. 做一些清理操作
+
+useEffect 第一个参数的返回函数,React 会在每次执行新的 Effect 之前,执行该函数
+
+### 3. useState 与 Class 版本的区别
+
+Class 版本会做 state 合并,我们在 setState 的时候只需要增量 set,Hooks 中的修改 state 需要传入完整的修改后的 state,因为会覆盖之前的 state。
+
+### 4. 减少不必要的渲染
+
+Class Component 开发时,我们可以通过 shouldComponentUpdate 优化渲染,Hooks 中如何减少不必要的渲染 ?
+
+- React.memo
+
+这不是一个 Hook,你可以把它理解为 Class base 里的 PureComponent,会做 props 的浅比较
+
+- useMemo
+
+这是一个 Hook,我们通过官方例子直观了解下。
+
+```js
+function Parent({ a, b }) {
+ // Only re-rendered if `a` changes:
+ const child1 = useMemo(() => , [a]);
+ // Only re-rendered if `b` changes:
+ const child2 = useMemo(() => , [b]);
+ return (
+ <>
+ {child1}
+ {child2}
+ >
+ );
+}
+```
+
+用法有点类似于 useEffect,第二个参数是一个依赖数组,只有依赖项发生变化时,才会触发 re-render 重新渲染。
+
+### 5. 为什么不能用 condition 包裹 useHook 语句
+
+与 Hooks 实现原理有关,React Hooks 并不是通过 Proxy 或者 getters 实现的,而是通过数组实现的,每次 useState 都会改变下标,如果 useState 被包裹在 condition 中,那每次执行的下标就可能对不上,导致 useState 导出的 setter 更新错数据。
+
+### 6. 异步如何访问过去或未来的 state ?
+
+使用 useRef 缓存 state,useRef 的作用相当于让你在 Class 组件的 this 上添加属性。
+
+### 7. state 初始值优化
+
+初始值很复杂时,推荐使用 useState 函数形式,useState 允许传入一个函数,React 只会在首次渲染时调用这个函数。
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..abe04df
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/package.json b/package.json
index fda611b..99568e5 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,8 @@
]
},
"dependencies": {
+ "@ant-design/colors": "^4.0.5",
+ "@ant-design/icons": "^4.2.2",
"antd": "^4.6.2",
"antd-dayjs-webpack-plugin": "^1.0.1",
"babel-plugin-import": "^1.13.0",
@@ -38,7 +40,8 @@
"lodash": "^4.17.20",
"prop-types": "^15.7.2",
"react": "^16.13.1",
- "react-dom": "^16.13.1"
+ "react-dom": "^16.13.1",
+ "uuid": "^8.3.0"
},
"devDependencies": {
"@babel/core": "^7.11.5",
diff --git a/src/assets/css/property.css b/src/assets/css/property.css
index 851a54a..b5eb978 100644
--- a/src/assets/css/property.css
+++ b/src/assets/css/property.css
@@ -3,3 +3,27 @@
overflow: hidden;
text-overflow: ellipsis;
}
+
+.width100 {
+ width: 100%;
+}
+
+.mr4 {
+ margin-right: 4px;
+}
+.mr8 {
+ margin-right: 8px;
+}
+.mr12 {
+ margin-right: 12px;
+}
+
+.ml4 {
+ margin-left: 4px;
+}
+.ml8 {
+ margin-left: 8px;
+}
+.ml12 {
+ margin-left: 12px;
+}
diff --git a/src/components/BiciDatePicker/index.js b/src/components/BiciDatePicker/index.js
new file mode 100644
index 0000000..478bc78
--- /dev/null
+++ b/src/components/BiciDatePicker/index.js
@@ -0,0 +1,29 @@
+/**
+ * @File: BiciDatePiker
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+import { DatePicker } from 'antd';
+import _ from 'lodash';
+import 'dayjs/locale/zh-cn';
+import locale from 'antd/es/date-picker/locale/zh_CN';
+
+const { RangePicker } = DatePicker;
+
+function BiciDatePicker(props) {
+ const { type } = props;
+ const omitProps = _.omit(props, ['type']);
+ const Picker = type === 'range' ? RangePicker : DatePicker;
+
+ return ;
+}
+
+BiciDatePicker.defaultProps = {
+ type: 'date',
+};
+
+BiciDatePicker.propTypes = {
+ type: PropTypes.oneOf(['date', 'range']),
+};
+
+export default BiciDatePicker;
diff --git a/src/components/BiciTable/BiciTable.module.css b/src/components/BiciTable/BiciTable.module.css
index e69de29..d0d7025 100644
--- a/src/components/BiciTable/BiciTable.module.css
+++ b/src/components/BiciTable/BiciTable.module.css
@@ -0,0 +1,12 @@
+.filterDropdown {
+ display: flex;
+ align-items: center;
+ padding: 8px;
+ border-radius: 6px;
+ background: #fff;
+ box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
+}
+
+.filterDropdownSelect {
+ width: 100%;
+}
diff --git a/src/components/BiciTable/FilterDropdown/DropdownDate.js b/src/components/BiciTable/FilterDropdown/DropdownDate.js
new file mode 100644
index 0000000..92a0d1b
--- /dev/null
+++ b/src/components/BiciTable/FilterDropdown/DropdownDate.js
@@ -0,0 +1,72 @@
+/**
+ * @File: 日期筛选,高度可配,支持传入所有的 Ant Design DatePicker props
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+import { v4 as uuidv4 } from 'uuid';
+import _ from 'lodash';
+import BiciDatePicker from '@/components/BiciDatePicker';
+import styles from '@/components/BiciTable/BiciTable.module.css';
+
+const controlProps = [
+ 'title',
+ 'isRange',
+ 'clearFilters',
+ 'confirm',
+ 'dataIndex',
+ 'filterType',
+ 'filters',
+ 'prefixCls',
+ 'onFilter',
+ 'selectedKeys',
+ 'setSelectedKeys',
+ 'updateFilters',
+ 'value',
+ 'visible',
+];
+
+function DropdownDate(props) {
+ const { size, isRange, value, showTime, updateFilters, onFilter } = props;
+ const type = isRange ? 'range' : 'date';
+ const controlValue = isRange ? value : value[0];
+ // 强制挂载 Picker 浮层,防止因滑动导致的定位问题
+ const containerId = uuidv4();
+ const omitProps = _.omit(props, controlProps);
+
+ const handleOk = (e, extra) => {
+ // 范围值交互优化,需要同时选择起始值后确定回调才会生效
+ const [start, end] = isRange ? e : [];
+ const isOk = isRange ? start && end : !!e;
+ if (isOk) updateFilters({ visible: false, value: isRange ? e : [e] });
+ if (isOk && onFilter) onFilter(e, extra);
+ };
+
+ return (
+
+ document.getElementById(containerId)}
+ onChange={(e, extra) => {
+ updateFilters({ value: isRange ? e : [e], visible: !!showTime });
+ if (!showTime && onFilter) onFilter(e, extra);
+ }}
+ onOk={handleOk}
+ />
+
+ );
+}
+
+DropdownDate.defaultProps = {
+ isRange: true,
+ format: 'YYYY-MM-DD',
+};
+
+DropdownDate.propTypes = {
+ isRange: PropTypes.bool,
+};
+
+export default DropdownDate;
diff --git a/src/components/BiciTable/FilterDropdown/DropdownRange.js b/src/components/BiciTable/FilterDropdown/DropdownRange.js
new file mode 100644
index 0000000..d8bbc68
--- /dev/null
+++ b/src/components/BiciTable/FilterDropdown/DropdownRange.js
@@ -0,0 +1,68 @@
+/**
+ * @File: 范围筛选
+ */
+import React, { useEffect, useRef } from 'react';
+import { InputNumber, Button } from 'antd';
+import _ from 'lodash';
+import styles from '@/components/BiciTable/BiciTable.module.css';
+
+function DropdownRange(props) {
+ const { size, visible, confirm, value, _value, updateFilters, onFilter } = props;
+ const [min, max] = value;
+ const disabled = _.isNumber(min) && _.isNumber(max) && min > max;
+ const ref = useRef(null);
+
+ const handleSubmit = () => {
+ // 关闭面板触发提交的条件为:缓存值变化且范围值合法
+ const shouldSubmit = onFilter && !disabled && !_.isEqual(_value, value);
+ if (shouldSubmit) onFilter(value);
+ };
+
+ useEffect(() => {
+ if (visible) {
+ // 缓存值用于提交判断做节流,如果值未变化不触发 onFilter
+ updateFilters({ _value: value });
+ setTimeout(() => ref.current.select(), 100);
+ } else {
+ handleSubmit();
+ }
+ }, [visible]);
+
+ return (
+
+ {
+ const reset = !_.isNumber(e) && !_.isNumber(max);
+ updateFilters({ value: reset ? [] : [e, max] });
+ }}
+ />
+ ~
+ {
+ const reset = !_.isNumber(e) && !_.isNumber(min);
+ updateFilters({ value: reset ? [] : [min, e] });
+ }}
+ />
+
+
+ );
+}
+
+export default DropdownRange;
diff --git a/src/components/BiciTable/FilterDropdown/DropdownSearch.js b/src/components/BiciTable/FilterDropdown/DropdownSearch.js
new file mode 100644
index 0000000..6782840
--- /dev/null
+++ b/src/components/BiciTable/FilterDropdown/DropdownSearch.js
@@ -0,0 +1,50 @@
+/**
+ * @File: 模糊筛选
+ */
+import React, { useEffect, useRef } from 'react';
+import { Input, Button } from 'antd';
+import _ from 'lodash';
+import styles from '@/components/BiciTable/BiciTable.module.css';
+
+function DropdownSearch(props) {
+ const { size, title, value, _value, visible, confirm, updateFilters, onFilter } = props;
+ const inputRef = useRef(null);
+
+ const handleSubmit = () => {
+ confirm();
+ const shouldSubmit = onFilter && !_.isEqual(value, _value);
+ if (shouldSubmit) onFilter(value[0]);
+ };
+
+ // @optimize,体验优化,
+ // 输入框筛选面板显示时自动聚焦
+ useEffect(() => {
+ if (visible) {
+ // 缓存值用于提交判断做节流,如果值未变化不触发 onFilter
+ updateFilters({ _value: value });
+ setTimeout(() => inputRef.current.select(), 100);
+ } else {
+ // 关闭面板时判断是否需要触发提交
+ handleSubmit();
+ }
+ }, [visible]);
+
+ return (
+
+ updateFilters({ value: e.target.value ? [e.target.value] : [] })}
+ onPressEnter={handleSubmit}
+ />
+
+
+ );
+}
+
+export default DropdownSearch;
diff --git a/src/components/BiciTable/FilterDropdown/DropdownSelect.js b/src/components/BiciTable/FilterDropdown/DropdownSelect.js
new file mode 100644
index 0000000..2cc4f3d
--- /dev/null
+++ b/src/components/BiciTable/FilterDropdown/DropdownSelect.js
@@ -0,0 +1,96 @@
+/**
+ * @File: 下拉筛选
+ */
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { Select, Button } from 'antd';
+import _ from 'lodash';
+import styles from '@/components/BiciTable/BiciTable.module.css';
+
+const { Option } = Select;
+
+function renderOptions(options = []) {
+ return options.map(option => {
+ const { id, name, dataStatus } = option;
+ const hasRemoved = dataStatus === -1;
+ const disabled = dataStatus === 2;
+ const prefixStyle = { display: hasRemoved ? 'none' : 'block' };
+ return (
+
+ );
+ });
+}
+
+function DropdownSelect(props) {
+ const {
+ size,
+ title,
+ visible,
+ selectData,
+ multiple,
+ confirm,
+ value,
+ _value,
+ updateFilters,
+ onFilter,
+ } = props;
+ const controlValue = multiple ? value.map(op => op.id) : (value[0] || {}).id;
+ const extraProps = multiple ? { mode: 'multiple' } : {};
+ const shouldSubmit = onFilter && !_.isEqual(value, _value);
+
+ useEffect(() => {
+ // 缓存值用于提交判断做节流,如果值未变化不触发 onFilter
+ if (visible) updateFilters({ _value: value });
+ // 多选模式下对面板关闭做提交判断
+ if (multiple && !visible && shouldSubmit) onFilter(controlValue, value);
+ }, [visible]);
+
+ return (
+
+
+ {multiple && (
+
+ )}
+
+ );
+}
+
+DropdownSelect.defaultProps = {
+ selectData: [],
+ multiple: false,
+};
+
+DropdownSelect.propTypes = {
+ multiple: PropTypes.bool,
+};
+
+export default DropdownSelect;
diff --git a/src/components/BiciTable/FilterDropdown/index.js b/src/components/BiciTable/FilterDropdown/index.js
new file mode 100644
index 0000000..55a4665
--- /dev/null
+++ b/src/components/BiciTable/FilterDropdown/index.js
@@ -0,0 +1,44 @@
+/**
+ * @File: 整合所有支持的自定义筛选器
+ */
+import React from 'react';
+import { FilterTypes } from '@/components/BiciTable/constants';
+import DropdownSearch from './DropdownSearch';
+import DropdownSelect from './DropdownSelect';
+import DropdownDate from './DropdownDate';
+import DropdownRange from './DropdownRange';
+
+function FilterDropdown(props) {
+ const { filterType: type } = props;
+ const filterType = type.toUpperCase();
+
+ const supportedTypes = Object.keys(FilterTypes);
+ if (filterType && !supportedTypes.includes(filterType)) {
+ console.error(
+ `filterType '${filterType}' is not supported, expect one of ${supportedTypes.toString()}.`,
+ );
+ }
+
+ let Dropdown = React.Fragment;
+
+ switch (filterType) {
+ case FilterTypes.SEARCH:
+ Dropdown = DropdownSearch;
+ break;
+ case FilterTypes.SELECT:
+ Dropdown = DropdownSelect;
+ break;
+ case FilterTypes.DATE:
+ Dropdown = DropdownDate;
+ break;
+ case FilterTypes.RANGE:
+ Dropdown = DropdownRange;
+ break;
+ default:
+ break;
+ }
+
+ return ;
+}
+
+export default FilterDropdown;
diff --git a/src/components/BiciTable/FilterTags/index.js b/src/components/BiciTable/FilterTags/index.js
new file mode 100644
index 0000000..98347f7
--- /dev/null
+++ b/src/components/BiciTable/FilterTags/index.js
@@ -0,0 +1,87 @@
+/**
+ * @File: 筛选条件
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Tag } from 'antd';
+import _ from 'lodash';
+import { FilterTypes } from '@/components/BiciTable/constants';
+
+function isEmpty(arr) {
+ return !arr.length || arr.every(v => !_.isNumber(v) && _.isEmpty(v));
+}
+
+function renderSeachTag(value) {
+ const searchText = value[0];
+ return searchText;
+}
+
+function renderSelectTag(value, tag) {
+ const { multiple } = tag;
+ const options = value.map(option => option.name);
+ return multiple ? options.join(',') : options[0];
+}
+
+function renderRangeTag(value) {
+ const [start, end] = value;
+ return `${start || ''} ~ ${end || ''}`;
+}
+
+function renderDateTag(value, tag) {
+ const { format, isRange } = tag;
+ const [begin, end] = value;
+ const dateRangeStr = isRange ? `${begin.format(format)} ~ ${end.format(format)}` : '';
+ const dateStr = value[0].format(format);
+ return isRange ? dateRangeStr : dateStr;
+}
+
+function renderTag(value, tag) {
+ const { filterType } = tag;
+
+ switch (filterType.toUpperCase()) {
+ case FilterTypes.SEARCH:
+ return renderSeachTag(value, tag);
+ case FilterTypes.SELECT:
+ return renderSelectTag(value, tag);
+ case FilterTypes.RANGE:
+ return renderRangeTag(value, tag);
+ case FilterTypes.DATE:
+ return renderDateTag(value, tag);
+ default:
+ return '';
+ }
+}
+
+function FilterTags(props) {
+ const { filters, setFilters } = props;
+ const renderTags = filters.filter(filter => !isEmpty(filter.value));
+ const tags = renderTags.map(tag => {
+ const { title, key, value } = tag;
+ return (
+ {
+ const updatedFilters = filters.map(f =>
+ f.key === key ? { ...f, value: [], visible: false } : f,
+ );
+ if (setFilters) setFilters(updatedFilters);
+ }}
+ >
+ {title}:{renderTag(value, tag)}
+
+ );
+ });
+ return 筛选条件:{tags}
;
+}
+
+FilterTags.defaultProps = {
+ filters: [],
+};
+
+FilterTags.propTypes = {
+ filters: PropTypes.array,
+ setFilters: PropTypes.func,
+};
+
+export default FilterTags;
diff --git a/src/components/BiciTable/constants.js b/src/components/BiciTable/constants.js
new file mode 100644
index 0000000..2d3f354
--- /dev/null
+++ b/src/components/BiciTable/constants.js
@@ -0,0 +1,9 @@
+/**
+ * @File: 表格相关的常量
+ */
+
+export const FilterTypes = { SEARCH: 'SEARCH', SELECT: 'SELECT', DATE: 'DATE', RANGE: 'RANGE' };
+
+export const controlledProps = ['columns', 'pagination'];
+
+export default { FilterTypes, controlledProps };
diff --git a/src/components/BiciTable/index.js b/src/components/BiciTable/index.js
index ffe1d16..8507185 100644
--- a/src/components/BiciTable/index.js
+++ b/src/components/BiciTable/index.js
@@ -1,14 +1,43 @@
/**
- * @File: ComplexTable based on Antd Table,
- * support custom filters, column options, presets, etc.
+ * @File: 提炼至企业级中后台场景的复杂表格,
+ * 基于 Ant Design Table & 博智云创 PC 端设计规范,
+ * 支持分页预设,列表选项,快捷筛选(模糊搜索,下拉搜索,范围数值搜索,时间范围搜索)等大量可扩展功能
*/
import React, { useState } from 'react';
import PropTypes from 'prop-types';
+import { Table } from 'antd';
+import _ from 'lodash';
+import FilterTags from '@/components/BiciTable/FilterTags';
+import {
+ getInitialFilters,
+ getEnhanceColumns,
+ getTablePresets,
+} from '@/components/BiciTable/utils';
+import { controlledProps } from '@/components/BiciTable/constants';
-function BiciTable(props) {}
+function BiciTable(props) {
+ const { size, columns: antColumns, pagination } = props;
+ const [filters, setFilters] = useState(getInitialFilters(antColumns));
+ const enhanceColumns = getEnhanceColumns(size, antColumns, filters, setFilters);
+ const presets = getTablePresets(pagination);
+ const omitProps = _.omit(props, controlledProps);
-BiciTable.defaultProps = {};
+ return (
+
+ );
+}
-BiciTable.propTypes = {};
+BiciTable.defaultProps = {
+ columns: [],
+ pagination: false,
+};
+
+BiciTable.propTypes = {
+ columns: PropTypes.array,
+ pagination: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
+};
export default BiciTable;
diff --git a/src/components/BiciTable/utils.js b/src/components/BiciTable/utils.js
new file mode 100644
index 0000000..4393032
--- /dev/null
+++ b/src/components/BiciTable/utils.js
@@ -0,0 +1,101 @@
+/**
+ * @File: 表格相关工具函数
+ */
+import React from 'react';
+import { Tooltip } from 'antd';
+import { SearchOutlined } from '@ant-design/icons';
+import _ from 'lodash';
+import FilterDropdown from '@/components/BiciTable/FilterDropdown';
+import { FilterTypes } from '@/components/BiciTable/constants';
+
+// 表格 size 映射,作用于内部组件
+// 默认 middle & small 会应用到到内部组件的 small size
+const sizeMap = { default: 'default', middle: 'small', small: 'small' };
+
+function checkSupported(type) {
+ const supportedTypes = Object.keys(FilterTypes);
+ return type && supportedTypes.includes(type.toUpperCase());
+}
+
+export function getTablePresets(antPagination) {
+ const showTotal = (total, range) => `${range[0]} - ${range[1]}条,共 ${total} 条`;
+ const presetPagination = {
+ showTotal,
+ showQuickJumper: true,
+ showSizeChanger: true,
+ ...antPagination,
+ };
+ const pagination = !antPagination ? false : presetPagination;
+ const presets = { bordered: true, pagination };
+ return presets;
+}
+
+export function getInitialFilters(columns) {
+ return columns.map(column => {
+ const { dataIndex: key } = column;
+ const omitProps = _.omit(column, ['render']);
+ // 扩展筛选项需要的属性,visible,值,缓存值,Diff 更新需要的 key 等
+ // 筛选项的值初始化为 Array 类型,易于扩展
+ // visible 用于控制表格自定义 Dropdown 的显隐
+ const extendsProps = { key, value: [], _value: [], visible: false };
+ return { ...omitProps, ...extendsProps };
+ });
+}
+
+export function getEnhanceColumns(size, columns, filters, setFilters) {
+ return columns.map(column => {
+ const { dataIndex: key, filterType, render } = column;
+ const matchFilter = filters.filter(filter => filter.key === key)[0] || {};
+ const { value, visible } = matchFilter;
+ // 提供一个快捷更新 filters 的闭包函数
+ const updateFilters = updateProps => {
+ setFilters(filters.map(f => (f.key === key ? { ...f, ...updateProps } : f)));
+ };
+ const filterIcon = filtered => (
+
+ );
+ const filterDropdown = props => {
+ const filterDropdownProps = {
+ size: sizeMap[size],
+ ...props,
+ ...column,
+ ...matchFilter,
+ updateFilters,
+ };
+ return ;
+ };
+ const onFilterDropdownVisibleChange = v => {
+ // @hack,范围数值筛选时,范围值不合法不允许关闭浮层
+ const [min, max] = value;
+ const invalid = _.isNumber(min) && _.isNumber(max) && min > max;
+ const dropdownVisible = filterType.toUpperCase() === FilterTypes.RANGE && invalid ? true : v;
+ updateFilters({ visible: dropdownVisible });
+ };
+ const filterOptions = {
+ filterIcon,
+ filterDropdown,
+ filteredValue: value,
+ filterDropdownVisible: visible,
+ onFilterDropdownVisibleChange,
+ };
+ // 只对支持的筛选做扩展
+ const isSupport = checkSupported(filterType);
+ const extraOptions = isSupport ? filterOptions : {};
+ // 扩展表格列支持溢出展示
+ const ellipseRender = text => {
+ return (
+
+ {text}
+
+ );
+ };
+ const enhanceColumn = {
+ ..._.omit(column, ['onFilter', 'render']),
+ ...extraOptions,
+ render: render || ellipseRender,
+ };
+ return enhanceColumn;
+ });
+}
+
+export default { getInitialFilters, getEnhanceColumns, getTablePresets };
diff --git a/webpack.config.js b/webpack.config.js
index 6a0d93b..2db73ab 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -36,10 +36,7 @@ module.exports = {
{
test: /\.css$/,
include: resolve('src/components'),
- use: [
- 'style-loader',
- { loader: 'css-loader', options: { modules: true } },
- ],
+ use: ['style-loader', { loader: 'css-loader', options: { modules: true } }],
},
{
test: /\.m?js$/,
@@ -77,7 +74,9 @@ module.exports = {
'react-dom',
'prop-types',
'lodash',
+ 'uuid',
'dayjs',
+ /^@ant-design\/.+$/,
/^dayjs\/plugin\/.+$/,
/^antd\/es\/.+$/,
],