Преглед изворни кода

feat(table): support defaultFilteredValue (#545)

走鹃 пре 3 година
родитељ
комит
2e8cb82ecb

+ 1 - 0
content/show/table/index-en-US.md

@@ -4539,6 +4539,7 @@ import { Table } from '@douyinfe/semi-ui';
 | className                     | Column style name                                                                                                                                                                                    | string                                                                                             |         |
 | colSpan                       | When header columns merge, set to 0, do not render                                                                                                                                                   | number                                                                                             |         |
 | dataIndex                     | The key corresponding to the column data in the data item. It is required when using sorter or filter.                                                                                                                                           | string                                                                                             |         |
+| defaultFilteredValue          | Default value of the filter, the filter state of the external control column with a value of the screened value array                            | any[]                                                                                           |        | **2.5.0** |
 | defaultSortOrder              | The default value of sortOrder, one of 'ascend'\|'descend'\|false                                         | boolean\| string                                                                                          |  false  | **1.31.0**
 | filterChildrenRecord          | Whether the child data needs to be filtered locally. If this function is enabled, if the child meets the filtering criteria, the parent will retain it even if it does not meet the criteria.        | boolean                                                                                            |         | **0.29.0**                         |
 | filterDropdown                | You can customize the filter menu. This function is only responsible for rendering the layer and needs to write a variety of interactions.                                                           | ReactNode                                                                                          |         |

+ 1 - 0
content/show/table/index.md

@@ -4550,6 +4550,7 @@ import { Table } from '@douyinfe/semi-ui';
 | children                      | 表头合并时用于子列的设置                                                                               | Column[]                                                                                        |        |
 | colSpan                       | 表头列合并,设置为 0 时,不渲染                                                                         | number                                                                                          |        |
 | dataIndex                     | 列数据在数据项中对应的 key,使用排序或筛选时必传                                                       | string                                                                                          |        |
+| defaultFilteredValue          | 筛选的默认值,值为已筛选的 value 数组                                    | any[]                                                                                           |        | **2.5.0** |
 | defaultSortOrder              | 排序的默认值,可设置为 'ascend'\|'descend'\|false                                                      | boolean\| string                                                                                | false  | **1.31.0**                  |
 | filterChildrenRecord          | 是否需要对子级数据进行本地过滤,开启该功能后如果子级符合过滤标准,父级即使不符合仍然会保留             | boolean                                                                                         |        | **0.29.0**                  |
 | filterDropdown                | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互                                         | ReactNode                                                                                       |        |

+ 14 - 0
cypress/integration/table.spec.js

@@ -24,4 +24,18 @@ describe('table', () => {
                 cy.get('.semi-page-item').contains('2').should('have.class', 'semi-page-item-active');
             });
     });
+
+    it('defaultFilteredValue', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=table--default-filtered-value&args=&viewMode=story');
+        // 筛选为默认值
+        cy.contains('显示第 1 条-第 10 条,共 23 条');
+        // 动态变更数据后,默认筛选生效
+        cy.get('.semi-button').contains('toggle change dataSource (46/25)').click();
+        cy.contains('显示第 1 条-第 10 条,共 12 条');
+        // 筛选手动设置为空后,动态变更数据,筛选值为空
+        cy.get('.semi-table-column-filter').click();
+        cy.get('.semi-dropdown-menu .semi-dropdown-item:nth-child(2)').click();
+        cy.get('.semi-button').contains('toggle change dataSource (46/25)').click();
+        cy.contains('显示第 1 条-第 10 条,共 46 条');
+    });
 });

+ 23 - 10
packages/semi-foundation/table/foundation.ts

@@ -34,6 +34,7 @@ export interface BaseColumnProps<RecordType> {
     className?: string;
     colSpan?: number;
     dataIndex?: string;
+    defaultFilteredValue?: any[];
     defaultSortOrder?: BaseSortOrder;
     filterChildrenRecord?: boolean;
     filterDropdown?: any;
@@ -200,13 +201,23 @@ class TableFoundation<RecordType> extends BaseFoundation<TableAdapter<RecordType
      */
     getFilteredSortedDataSource(dataSource: RecordType[], queries: BaseColumnProps<RecordType>[]) {
         const filteredDataSource = this.filterDataSource(dataSource, queries.filter(
-            query => (
-                isFunction(query.onFilter) &&
-                Array.isArray(query.filters) &&
-                query.filters.length &&
-                Array.isArray(query.filteredValue) &&
-                query.filteredValue.length
-            )
+            query => {
+                /**
+                 * 这里无需判断 filteredValue 是否为数组,初始化时它是 `undefined`,点击选择空时为 `[]`
+                 * 初始化时我们应该用 `defaultFilteredValue`,点击后我们应该用 `filteredValue`
+                 * 
+                 * There is no need to judge whether `filteredValue` is an array here, because it is `undefined` when initialized, and `[]` when you click to select empty
+                 * When initializing we should use `defaultFilteredValue`, after clicking we should use `filteredValue`
+                 */
+                const currentFilteredValue = query.filteredValue ? query.filteredValue : query.defaultFilteredValue;
+                return (
+                    isFunction(query.onFilter) &&
+                    Array.isArray(query.filters) &&
+                    query.filters.length &&
+                    Array.isArray(currentFilteredValue) &&
+                    currentFilteredValue.length
+                );
+            }
         ));
         const sortedDataSource = this.sortDataSource(filteredDataSource, queries.filter(query => query && isFunction(query.sorter)));
         return sortedDataSource;
@@ -371,8 +382,9 @@ class TableFoundation<RecordType> extends BaseFoundation<TableAdapter<RecordType
         const childrenRecordName = this.getProp('childrenRecordName');
 
         each(filters, filterObj => {
-            const { onFilter, filteredValue, filterChildrenRecord } = filterObj;
-            if (typeof onFilter === 'function' && Array.isArray(filteredValue) && filteredValue.length) {
+            const { onFilter, filteredValue, filterChildrenRecord, defaultFilteredValue } = filterObj;
+            const currentFilteredValue = Array.isArray(filteredValue) ? filteredValue : defaultFilteredValue;
+            if (typeof onFilter === 'function' && Array.isArray(currentFilteredValue) && currentFilteredValue.length) {
                 hasValidFilters = true;
 
                 if (filteredData === null) {
@@ -381,7 +393,7 @@ class TableFoundation<RecordType> extends BaseFoundation<TableAdapter<RecordType
                     dataSource = Array.from(filteredData && filteredData.values());
                     filteredData = new Map();
                 }
-                each(filteredValue, value => {
+                each(currentFilteredValue, value => {
                     each(dataSource, record => {
                         const childrenRecords = get(record, childrenRecordName);
                         const recordKey = this.getRecordKey(record);
@@ -1165,6 +1177,7 @@ export interface BaseChangeInfoFilter<RecordType> {
     filters?: BaseFilter[];
     onFilter?: (filteredValue?: any, record?: RecordType) => boolean;
     filteredValue?: any[];
+    defaultFilteredValue?: any[];
     children?: BaseFilter[];
     filterChildrenRecord?: boolean;
 }

+ 4 - 0
packages/semi-ui/table/Table.tsx

@@ -943,11 +943,15 @@ class Table<RecordType extends Record<string, any>> extends BaseComponent<Normal
                 titleArr.push(sorter);
             }
 
+            const stateFilteredValue = get(curQuery, 'filteredValue');
+            const defaultFilteredValue = get(curQuery, 'defaultFilteredValue');
+            const filteredValue = stateFilteredValue ? stateFilteredValue : defaultFilteredValue;
             if ((Array.isArray(column.filters) && column.filters.length) || isValidElement(column.filterDropdown)) {
                 const filter = (
                     <ColumnFilter
                         key={strings.DEFAULT_KEY_COLUMN_FILTER}
                         {...curQuery}
+                        filteredValue={filteredValue}
                         onFilterDropdownVisibleChange={(visible: boolean) => this.foundation.toggleShowFilter(dataIndex, visible)}
                         onSelect={(data: OnSelectData) => this.foundation.handleFilterSelect(dataIndex, data)}
                     />

+ 1 - 2
packages/semi-ui/table/_story/table.stories.js

@@ -76,8 +76,7 @@ export { default as ScrollBar } from './ScrollBar';
 export { default as TableSpan } from './TableSpan';
 export { default as FixRenderReturnProps } from './FixRenderReturnProps';
 export { default as WarnColumnWithoutDataIndex } from './WarnColumnWithoutDataIndex';
-export { default as FixedColumnsChange } from './v2/FixedColumnsChange';
-export { default as FixedZIndex } from './v2/FixedZIndex';
+export * from './v2';
 
 // empty table
 

+ 123 - 0
packages/semi-ui/table/_story/v2/defaultFilteredValue.tsx

@@ -0,0 +1,123 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import { Table, Avatar, Button } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+import { ColumnProps, ChangeInfoFilter } from '@douyinfe/semi-ui/table';
+
+const DAY = 24 * 60 * 60 * 1000;
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+function App() {
+    const [dataSource, setData] = useState([]);
+    const [filteredValue, setFilteredValue] = useState(['Semi Pro 设计稿']);
+
+    const scroll = useMemo(() => ({ y: 300 }), []);
+
+    const columns: ColumnProps[] = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            filters: [
+                {
+                    text: 'Semi Design 设计稿',
+                    value: 'Semi Design 设计稿',
+                },
+                {
+                    text: 'Semi Pro 设计稿',
+                    value: 'Semi Pro 设计稿',
+                },
+            ],
+            onFilter: (value, record) => record.name.includes(value),
+            sorter: (a, b) => a.name.length - b.name.length > 0 ? 1 : -1,
+            // filterMultiple: false,
+            // filteredValue: filteredValue,
+            defaultFilteredValue: filteredValue,
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+            render: (text) => `${text} KB`
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>{typeof text === 'string' && text.slice(0, 1)}</Avatar>
+                        {text}
+                    </div>
+                );
+            }
+    
+        },
+        {
+            title: '更新日期',
+            dataIndex: 'updateTime',
+            sorter: (a, b) => a.updateTime - b.updateTime > 0 ? 1 : -1,
+            render: (value) => {
+                return dateFns.format(new Date(value), 'yyyy-MM-dd');
+            }
+        }
+    ];
+
+    const getData = (total) => {
+        const data = [];
+        for (let i = 0; i < total; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return data;
+    };
+
+    const handleFilterChange = (filters: ChangeInfoFilter<any>[]) => {
+        console.log('filters', filters);
+        if (Array.isArray(filters) && filters.length) {
+            const { filteredValue } = filters.find(filter => filter.dataIndex === 'name');
+            setFilteredValue(filteredValue);
+        }
+    };
+
+    const handleChange = (options) => {
+        const { filters } = options;
+        handleFilterChange(filters);
+    };
+
+    const toggleChangeData = () => {
+        const length = dataSource.length;
+        const newData = getData(length === 46 ? 25 : 46);
+        setData(newData);
+    };
+
+    useEffect(() => {
+        const data = getData(46);
+        setData(data);
+    }, []);
+
+    return (
+        <div>
+            <Button onClick={toggleChangeData}>toggle change dataSource (46/25)</Button>
+            <Table columns={columns} dataSource={dataSource} scroll={scroll} onChange={handleChange} />
+        </div>
+    );
+}
+
+App.storyName = 'defaultFilteredValue';
+export default App;

+ 3 - 0
packages/semi-ui/table/_story/v2/index.js

@@ -0,0 +1,3 @@
+export { default as DefaultFilteredValue } from './defaultFilteredValue';
+export { default as FixedColumnsChange } from './FixedColumnsChange';
+export { default as FixedZIndex } from './FixedZIndex';

+ 1 - 0
packages/semi-ui/table/interface.ts

@@ -79,6 +79,7 @@ export interface ColumnProps<RecordType extends Record<string, any> = any> {
     className?: string;
     colSpan?: number;
     dataIndex?: string;
+    defaultFilteredValue?: any[];
     defaultSortOrder?: SortOrder;
     filterChildrenRecord?: boolean;
     filterDropdown?: React.ReactNode;