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\/.+$/, ],