Browse Source

feat: table add shouldCellUpdate API (#2584)

Co-authored-by: shijia.me <[email protected]>
Shi Jia 1 year ago
parent
commit
2cec6ef7fb

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

@@ -5429,6 +5429,7 @@ import { Table } from '@douyinfe/semi-ui';
 | sortOrder | The controlled property of the sorting, the sorting of this control column can be set to 'ascend'\|'descended '\|false | boolean | false |
 | sorter | Sorting function, local sorting uses a function (refer to the compareFunction of Array.sort), requiring a server-side sorting can be set to true. **An independent dataIndex must be set for the sort column, and an independent key must be set for each data item in the dataSource** | boolean\|(r1: RecordType, r2: RecordType, sortOrder: 'ascend' \| 'descend') => number | true |
 | sortIcon |Customize the sort icon. The returned node controls the entire sort button, including ascending and descending buttons. Need to control highlighting behavior based on sortOrder | (props: { sortOrder }) => ReactNode | | **2.50.0** |
+| shouldCellUpdate | Self control whether cell should be updated | (props: TableCellProps, prevProps: TableCellProps) => boolean | | **2.71.0** |
 | title | Column header displays text. When a function is passed in, title will use the return value of the function; when other types are passed in, they will be aggregated with sorter and filter. It needs to be used with useFullRender to obtain parameters such as filter in the function type | string \| ReactNode\|({ filter: ReactNode, sorter: ReactNode, selection: ReactNode }) => ReactNode. |  | Function type requires **0.34.0** |
 | useFullRender | Whether to completely customize the rendering, see [Full Custom Rendering](#Fully-custom-rendering) for usage details, enabling this feature will cause a certain performance loss | boolean | false | **0.34.0** |
 | width | Column width | string \| number |  |
@@ -5459,6 +5460,7 @@ type Filter = {
 | getCheckboxProps | Default property configuration for the selection box | (record: RecordType) => object |  |  |
 | hidden | Hide selection column or not | boolean | false | **0.34.0** |
 | selectedRowKeys | Specifies the key array of the selected item, which needs to work with onChange | string [] |  |  |
+| shouldCellUpdate | Self control whether cell should be updated | (props: TableCellProps, prevProps: TableCellProps) => boolean | | **2.71.0** |
 | renderCell         | Custom rendering checkbox                                                                                 | ({ selected: boolean, record: RecordType, originNode: JSX.Element, inHeader: boolean, disabled: boolean, indeterminate: boolean, index?: number, selectRow?: (selected: boolean, e: Event) => void, selectAll?: (selected: boolean, e: Event) => void }) => ReactNode |        |      **2.52.0**      |
 | width | Custom list selection box width | string | number |  |
 | onChange | A callback in the event of a change in the selected item. The first parameter will save the row keys selected last time, even if you do paging control or update the dataSource [FAQ](#faq) | (selectedRowKeys: number[]\|string[], selectedRows: RecordType[]) => void |  |  |

+ 15 - 13
content/show/table/index.md

@@ -5557,6 +5557,7 @@ import { Table } from '@douyinfe/semi-ui';
 | sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 'ascend'\|'descend'\|false | boolean\| string | false |
 | sorter | 排序函数,本地排序使用一个函数(参考 Array.sort 的 compareFunction),需要服务端排序可设为 true。**必须给排序列设置一个独立的 dataIndex,必须为 dataSource 里面的每条数据项设置独立的 key** | boolean\|(r1: RecordType, r2: RecordType, sortOrder: 'ascend' \| 'descend') => number | true |
 | sortIcon | 自定义 sort 图标,返回的节点控制了整个排序按钮,包含升序和降序。需根据 sortOrder 控制高亮行为 | (props: { sortOrder }) => ReactNode | | **2.50.0** |
+| shouldCellUpdate | 自定义控制单元格是否渲染。默认 cell 会深对比 props 和 nextProps 是否变化,来决定是否渲染单元格。如果你的 props 中的 record 比较复杂,建议使用 `shouldCellUpdate` 接管单元格的渲染。 | (props: TableCellProps, prevProps: TableCellProps) => boolean | | **2.71.0** |
 | title | 列头显示文字。传入 function 时,title 将使用函数的返回值;传入其他类型,将会和 sorter、filter 进行聚合。需要搭配 useFullRender 获取函数类型中的 filter 等参数 | ReactNode\|({ filter: ReactNode, sorter: ReactNode, selection: ReactNode }) => ReactNode |  | Function 类型需要**0.34.0** |
 | useFullRender | 是否完全自定义渲染,用法详见[完全自定义渲染](#完全自定义渲染), 开启此功能会造成一定的性能损耗 | boolean | false | **0.34.0** |
 | width | 列宽度 | string \| number |  |
@@ -5581,19 +5582,20 @@ type Filter = {
 
 ## rowSelection
 
-| 属性             | 说明                                                                                                         | 类型                                                                                                 | 默认值 | 版本       |
-|------------------|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|--------|------------|
-| className        | 所处列样式名                                                                                                 | string                                                                                               |        |            |
-| disabled         | 表头的 `Checkbox` 是否禁用                                                                                   | boolean                                                                                              | false  | **0.32.0** |
-| fixed            | 把选择框列固定在左边                                                                                         | boolean                                                                                              | false  |            |
-| getCheckboxProps | 选择框的默认属性配置                                                                                         | (record: RecordType) => object                                                                       |        |            |
-| hidden           | 是否隐藏选择列                                                                                               | boolean                                                                                              | false  | **0.34.0** |
-| renderCell         | 自定义渲染勾选框                                                                                 | ({ selected: boolean, record: RecordType, originNode: JSX.Element, inHeader: boolean, disabled: boolean, indeterminate: boolean, index?: number, selectRow?: (selected: boolean, e: Event) => void, selectAll?: (selected: boolean, e: Event) => void }) => ReactNode |        |      **2.52.0**     |
-| selectedRowKeys  | 指定选中项的 key 数组,需要和 onChange 进行配合                                                               | string[]                                                                                             |        |            |
-| width            | 自定义列表选择框宽度                                                                                         | string\|number                                                                                       |        |            |
-| onChange         | 选中项发生变化时的回调。第一个参数会保存上次选中的 row keys,即使你做了分页受控或更新了 dataSource [FAQ](#faq) | (selectedRowKeys: number[]\|string[], selectedRows: RecordType[]) => void                            |        |            |
-| onSelect         | 用户手动点击某行选择框的回调                                                                                 | (record: RecordType, selected: boolean, selectedRows: RecordType[], nativeEvent: MouseEvent) => void |        |            |
-| onSelectAll      | 用户手动点击表头选择框的回调,会选中/取消选中 dataSource 里的所有可选行                                       | (selected: boolean, selectedRows: RecordType[], changedRows: RecordType[]) => void                   |        |            |
+| 属性             | 说明                                                                                                                                                                           | 类型                                                                                                                                                                                                                                                                  | 默认值 | 版本       |
+|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|------------|
+| className        | 所处列样式名                                                                                                                                                                   | string                                                                                                                                                                                                                                                                |        |            |
+| disabled         | 表头的 `Checkbox` 是否禁用                                                                                                                                                     | boolean                                                                                                                                                                                                                                                               | false  | **0.32.0** |
+| fixed            | 把选择框列固定在左边                                                                                                                                                           | boolean                                                                                                                                                                                                                                                               | false  |            |
+| getCheckboxProps | 选择框的默认属性配置                                                                                                                                                           | (record: RecordType) => object                                                                                                                                                                                                                                        |        |            |
+| hidden           | 是否隐藏选择列                                                                                                                                                                 | boolean                                                                                                                                                                                                                                                               | false  | **0.34.0** |
+| renderCell       | 自定义渲染勾选框                                                                                                                                                               | ({ selected: boolean, record: RecordType, originNode: JSX.Element, inHeader: boolean, disabled: boolean, indeterminate: boolean, index?: number, selectRow?: (selected: boolean, e: Event) => void, selectAll?: (selected: boolean, e: Event) => void }) => ReactNode |        | **2.52.0** |
+| selectedRowKeys  | 指定选中项的 key 数组,需要和 onChange 进行配合                                                                                                                                 | string[]                                                                                                                                                                                                                                                              |        |            |
+| shouldCellUpdate | 自定义控制单元格是否渲染。默认 cell 会深对比 props 和 nextProps 是否变化,来决定是否渲染单元格。如果你的 props 中的 record 比较复杂,建议使用 `shouldCellUpdate` 接管单元格的渲染。 | (props: TableCellProps, prevProps: TableCellProps) => boolean                                                                                                                                                                                                         |        | **2.71.0** |
+| width            | 自定义列表选择框宽度                                                                                                                                                           | string\|number                                                                                                                                                                                                                                                        |        |            |
+| onChange         | 选中项发生变化时的回调。第一个参数会保存上次选中的 row keys,即使你做了分页受控或更新了 dataSource [FAQ](#faq)                                                                   | (selectedRowKeys: number[]\|string[], selectedRows: RecordType[]) => void                                                                                                                                                                                             |        |            |
+| onSelect         | 用户手动点击某行选择框的回调                                                                                                                                                   | (record: RecordType, selected: boolean, selectedRows: RecordType[], nativeEvent: MouseEvent) => void                                                                                                                                                                  |        |            |
+| onSelectAll      | 用户手动点击表头选择框的回调,会选中/取消选中 dataSource 里的所有可选行                                                                                                         | (selected: boolean, selectedRows: RecordType[], changedRows: RecordType[]) => void                                                                                                                                                                                    |        |            |
 
 ## scroll
 

+ 6 - 1
packages/semi-ui/table/TableCell.tsx

@@ -1,7 +1,7 @@
 import React, { createRef, Fragment, ReactNode } from 'react';
 import classnames from 'classnames';
 import PropTypes from 'prop-types';
-import { get, noop, set, omit, isEqual, merge } from 'lodash';
+import { get, noop, set, omit, merge, isEqual } from 'lodash';
 
 import { cssClasses, numbers } from '@douyinfe/semi-foundation/table/constants';
 import TableCellFoundation, { TableCellAdapter } from '@douyinfe/semi-foundation/table/cellFoundation';
@@ -130,6 +130,11 @@ export default class TableCell extends BaseComponent<TableCellProps, Record<stri
         const props = this.props;
         const { column, expandIcon } = props;
         const cellInSelectionColumn = isSelectionColumn(column);
+
+        const { shouldCellUpdate } = column;
+        if (typeof shouldCellUpdate === 'function') {
+            return shouldCellUpdate(nextProps, props);
+        }
         // The expand button may be in a separate column or in the first data column
         const columnHasExpandIcon = isExpandedColumn(column) || expandIcon;
         if ((cellInSelectionColumn || columnHasExpandIcon) && !isEqual(nextProps, this.props)) {

+ 235 - 0
packages/semi-ui/table/_story/Perf/DeepEqual/index.tsx

@@ -0,0 +1,235 @@
+import React, { useEffect, useState } from 'react';
+import { Table, Typography, Tag, Popover, Button, Switch } from '@douyinfe/semi-ui';
+import { cloneDeep } from 'lodash';
+import { ColumnProps } from 'table/interface';
+
+export default function App() {
+    const [count, setCount] = useState(100);
+    const [data, setData] = useState([]);
+
+    const handleSwitchChange = (options: { checked; record; index }) => {
+        const { checked, index } = options;
+        const newData = cloneDeep(data);
+        newData[index].completeStatus = checked;
+        setData(newData);
+    };
+
+    const src = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bag.jpeg';
+
+    const shouldCellUpdate: ColumnProps['shouldCellUpdate'] = (props, prevProps) => {
+        return props.record !== prevProps.record;
+    };
+
+    const columns: ColumnProps[] = [
+        {
+            title: '需求标题',
+            dataIndex: 'featureTitle',
+            render: (text, record, index) => (
+                <a rel="noreferrer" href="https://semi.design/zh-CN/show/table" target="_blank">
+                    {text}
+                </a>
+            ),
+            filterIcon: <div>test</div>,
+            shouldCellUpdate
+        },
+        {
+            title: '文档',
+            dataIndex: 'doc',
+            width: 150,
+            render: (text, record, index) => (
+                <Typography.Text link ellipsis={{ showTooltip: true }} style={{ width: 150, breakWord: 'break-all' }}>
+                    {text}
+                </Typography.Text>
+            ),
+            shouldCellUpdate
+        },
+        {
+            title: '需求状态',
+            dataIndex: 'featureStatus',
+            width: 100,
+            render: (text, record, index) => (
+                <Tag style={{ width: 50 }}>
+                    <Typography.Text ellipsis={{ showTooltip: true }} style={{ width: 50 }}>
+                        {text}
+                    </Typography.Text>
+                </Tag>
+            ),
+            shouldCellUpdate
+        },
+        {
+            title: '优先级',
+            dataIndex: 'priority',
+            render: (text, record, index) => <Tag>{text}</Tag>,
+            shouldCellUpdate
+        },
+        {
+            title: 'PM',
+            dataIndex: 'pm',
+            render: (text, record, index) => (
+                <Popover
+                    showArrow
+                    content={
+                        <article>
+                            Hi ByteDancer, this is a popover.
+                            <br /> We have 2 lines.
+                        </article>
+                    }
+                    key={index}
+                >
+                    <Tag avatarSrc={src} avatarShape="circle">
+                        {text}
+                    </Tag>
+                </Popover>
+            ),
+            shouldCellUpdate
+        },
+        {
+            title: '产品线',
+            dataIndex: 'productLine',
+            render: (text, record, index) => (
+                <Tag>
+                    <Typography.Text ellipsis={{ showTooltip: true }} style={{ width: 50 }}>
+                        {text}
+                    </Typography.Text>
+                </Tag>
+            ),
+            shouldCellUpdate
+        },
+        {
+            title: '前端',
+            dataIndex: 'fe',
+            render: (text, record, index) => (
+                <Popover
+                    showArrow
+                    content={
+                        <article>
+                            Hi ByteDancer, this is a popover.
+                            <br /> We have 2 lines.
+                        </article>
+                    }
+                    key={index}
+                >
+                    <Tag color="blue">{text}</Tag>
+                </Popover>
+            ),
+            shouldCellUpdate
+        },
+        {
+            title: '服务端',
+            dataIndex: 'server',
+            render: (text, record, index) => (
+                <Popover
+                    showArrow
+                    content={
+                        <article>
+                            Hi ByteDancer, this is a popover.
+                            <br /> We have 2 lines.
+                        </article>
+                    }
+                    key={index}
+                >
+                    <Tag avatarSrc={src}>{text}</Tag>
+                </Popover>
+            ),
+            shouldCellUpdate
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            render: (text, record, index) => (
+                <Typography.Text
+                    ellipsis={{ showTooltip: true }}
+                    style={{ width: 50 }}
+                    onClick={() => {
+                        console.log('click createTime', record);
+                    }}
+                >
+                    {text}
+                </Typography.Text>
+            ),
+            shouldCellUpdate
+        },
+        {
+            title: '完成时间',
+            dataIndex: 'completeTime',
+            render: (text, record, index) => (
+                <Typography.Text
+                    ellipsis={{ showTooltip: true }}
+                    style={{ width: 50 }}
+                    onClick={() => {
+                        console.log('click completeTime', record);
+                    }}
+                >
+                    {text}
+                </Typography.Text>
+            ),
+            shouldCellUpdate
+        },
+        {
+            title: '完成状态',
+            dataIndex: 'completeStatus',
+            render: (text, record, index) => (
+                <Switch
+                    checked={record.completeStatus}
+                    onChange={checked => handleSwitchChange({ checked, record, index })}
+                ></Switch>
+            ),
+            shouldCellUpdate
+        },
+    ];
+
+    useEffect(() => {
+        const getData = () => {
+            const data = Array.from(
+                {
+                    length: count,
+                },
+                (_, key) => {
+                    const rowRandom = Math.round(Math.random() * 1000);
+                    const prioritySet = ['P0', 'P1', 'P2'];
+                    const priority = prioritySet[Math.round(Math.random() * 2)];
+                    const featureStatusSet = ['待埋点', '开始', '待需详评', '测试', '已完成'];
+                    const featureStatus = featureStatusSet[Math.round(Math.random() * 4)];
+                    const doc = 'https://semi.design';
+                    const createTime = new Date().valueOf();
+                    return {
+                        key,
+                        featureTitle: `需求-${rowRandom}`,
+                        doc,
+                        featureStatus,
+                        priority,
+                        pm: 'Li',
+                        productLine: 'Hotsoon',
+                        fe: '姜鹏志',
+                        server: 'ZhuYi',
+                        createTime,
+                        completeTime: createTime + rowRandom,
+                        completeStatus: false,
+                    };
+                }
+            );
+            return data;
+        };
+
+        const newData = getData();
+        setData(newData);
+    }, [count]);
+
+    const scroll = { y: 500 };
+
+    return (
+        <>
+            <div>
+                <Button onClick={() => setCount(count * 2)}>count * 2</Button>
+            </div>
+            <Table
+                title={`数据条数:${data.length}`}
+                rowSelection
+                columns={columns}
+                dataSource={data}
+                pagination={false}
+                scroll={scroll}
+            />
+        </>
+    );
+}

+ 2 - 1
packages/semi-ui/table/_story/Perf/index.jsx

@@ -5,4 +5,5 @@ export { default as ControlledSelection } from './Render/controlledSelection';
 export { default as PerfContext } from './Render/context';
 export { default as PerfComplexRender } from './Render/complex';
 export { default as PerfResizableSelection } from './Render/resizableSelection';
-export { default as PerfOnRow } from './Render/onRow';
+export { default as PerfOnRow } from './Render/onRow';
+export { default as DeepEqual } from './DeepEqual';

+ 3 - 0
packages/semi-ui/table/_story/table.stories.jsx

@@ -38,6 +38,7 @@ import {
   PerfRender,
   PerfResizableSelection,
   PerfVirtualized,
+  DeepEqual
 } from './Perf';
 import RenderPagination from './RenderPagination';
 import ControlledSortOrder from './ControlledSortOrder';
@@ -123,6 +124,8 @@ export {
 } from './v2';
 export { default as FixSelectAll325 } from './Demos/rowSelection';
 
+export { DeepEqual };
+
 // empty table
 
 const emptyColumn = [

+ 11 - 2
packages/semi-ui/table/interface.ts

@@ -21,6 +21,7 @@ import type {
     BaseEllipsis
 } from '@douyinfe/semi-foundation/table/foundation';
 import type { ColumnFilterProps } from './ColumnFilter';
+import { TableCellProps } from './TableCell';
 
 export interface TableProps<RecordType extends Record<string, any> = any> extends BaseProps {
     bordered?: boolean;
@@ -116,7 +117,11 @@ export interface ColumnProps<RecordType extends Record<string, any> = any> {
     onHeaderCell?: OnHeaderCell<RecordType>;
     ellipsis?: BaseEllipsis;
     resize?: boolean;
-    showSortTip?: boolean
+    showSortTip?: boolean;
+    /**
+     * self control whether to update cell for performance reasons
+     */
+    shouldCellUpdate?: (props: TableCellProps, prevProps: TableCellProps) => boolean
 }
 
 export type Align = BaseAlign;
@@ -250,7 +255,11 @@ export interface RowSelectionProps<RecordType> {
     onSelectAll?: RowSelectionOnSelectAll<RecordType>;
     onCell?: ColumnProps['onCell'];
     onHeaderCell?: ColumnProps['onHeaderCell'];
-    renderCell?: RowSelectionRenderCell<RecordType>
+    renderCell?: RowSelectionRenderCell<RecordType>;
+    /**
+     * self control whether to update cell for performance reasons
+     */
+    shouldCellUpdate?: (props: TableCellProps, prevProps: TableCellProps) => boolean
 }
 
 export type RowSelectionRenderCell<RecordType> = (renderCellArgs: {

+ 89 - 0
yarn.lock

@@ -1585,11 +1585,25 @@
     "@douyinfe/semi-animation-styled" "2.65.0"
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]":
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.69.2.tgz#b47565c64dae7f4e1a7c5a9a21a244d59986b3fd"
+  integrity sha512-N6bdju90nnQdNHmnp5C8n8oqSDqqzgO6rzCPwwb6Ef4+aC/csdU1/Dsdp6JA6QKQ768oHGPT5YJs3QiKSGZZcw==
+  dependencies:
+    "@douyinfe/semi-animation" "2.69.2"
+    "@douyinfe/semi-animation-styled" "2.69.2"
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.65.0.tgz#8c56047a5704a45b05cc9809a2a126cc24526ea1"
   integrity sha512-YFF8Ptcz/jwS0phm28XZV7ROqMQ233sjVR0Uy33FImCITr6EAPe5wcCeEmzVZoYS7x3tUFR30SF+0hSO01rQUg==
 
+"@douyinfe/[email protected]":
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.69.2.tgz#18c16a959c92e908aa4fad521fe7e0fe83296034"
+  integrity sha512-HHHR2qS7BRCtP78qp9N/OL9RWPvoxxRg6uC6kUm8l4t5FCcr0QrdhkzYIpohAVa90BedpTPwhRHhh3aiXfnx9A==
+
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.65.0.tgz#f544a6b420c3e948c09836019e6b63f1382cd12c"
@@ -1597,6 +1611,13 @@
   dependencies:
     bezier-easing "^2.1.0"
 
+"@douyinfe/[email protected]":
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.69.2.tgz#4023340747eb202f5e3b2d48dfd4efea94d815cb"
+  integrity sha512-elut0fb5eKr5pnrZKgaOS97nw+KxkoL4N+tho4u099a3K5GFwzvyzVPOK0ALReCWnO0tSxUbwPpUQxMomG+vKA==
+  dependencies:
+    bezier-easing "^2.1.0"
+
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.65.0.tgz#20466a9b4baacdde2249930fb709ba035c5a7bea"
@@ -1616,6 +1637,25 @@
     remark-gfm "^4.0.0"
     scroll-into-view-if-needed "^2.2.24"
 
+"@douyinfe/[email protected]":
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.69.2.tgz#2a782760511e509410df87e473e956465e8b1a6f"
+  integrity sha512-qiN1uBxEs+ofIAGOw6oF7AgTXDlfgmbl9xYTDsS5D3tSH0p0hjAsqW2d59/lScr3P7dYzxKOXZ6aJrC5TXW3Wg==
+  dependencies:
+    "@douyinfe/semi-animation" "2.69.2"
+    "@mdx-js/mdx" "^3.0.1"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    date-fns "^2.29.3"
+    date-fns-tz "^1.3.8"
+    fast-copy "^3.0.1 "
+    lodash "^4.17.21"
+    lottie-web "^5.12.2"
+    memoize-one "^5.2.1"
+    prismjs "^1.29.0"
+    remark-gfm "^4.0.0"
+    scroll-into-view-if-needed "^2.2.24"
+
 "@douyinfe/[email protected]", "@douyinfe/semi-icons@latest":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.65.0.tgz#af39cbd5431ebccedcf7d9ce689646e54bebc432"
@@ -1623,11 +1663,23 @@
   dependencies:
     classnames "^2.2.6"
 
+"@douyinfe/[email protected]", "@douyinfe/semi-icons@^2.0.0":
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.69.2.tgz#92ade6402237c1a98d4f39a6d447a874848ea066"
+  integrity sha512-0Wzb4bd5DYZjlcR9JS2Cv5D7LeSApy0TD4BMp8rHRStp9iNIGnB/Fob7OFAz9ZuceJ8nb+IN4uxQyf0GuNh/tA==
+  dependencies:
+    classnames "^2.2.6"
+
 "@douyinfe/[email protected]":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.65.0.tgz#9916c540c91222a1d9f48cd34a941d28b8a05d2f"
   integrity sha512-1IhOztyBYiSu8WrcvN+oWWtcJTC9+x6zbnYtufx4ToISs5UO1te1PQofABpkDzIJYFtW9yYLxg4uoL4wGjqYMA==
 
+"@douyinfe/[email protected]":
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.69.2.tgz#aac0c5c65c1363c86ab6dcfd702a0d4d9a3a1c23"
+  integrity sha512-rdRB6ZZ2zo2c/e0Hkffq84i0w90BMmhBGskvei8AWDlfiuTJhXL6qZ3ixZiE+fjPQO12mk/QNG+LNv8Tr5yFfQ==
+
 "@douyinfe/[email protected]":
   version "2.23.2"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-scss-compile/-/semi-scss-compile-2.23.2.tgz#30884bb194ee9ae1e81877985e5663c3297c1ced"
@@ -1699,6 +1751,38 @@
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.61.0.tgz#a7e9bf9534721c12af1d0eeb5d5a2de615896a23"
   integrity sha512-obn/DOw4vZyKFAlWvZxHTpBLAK9FO9kygTSm2GROgvi+UDB2PPU6l20cuUCsdGUNWJRSqYlTTVZ1tNYIyFZ5Sg==
 
+"@douyinfe/[email protected]":
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.69.2.tgz#6256c55f07e34b8f134e5a7a2f9fe85970387bf3"
+  integrity sha512-pyol1EFUwuErp6Tlw+VLs4nVjnj1PSEC5P3YB0MpIcDWZsH/ixRsgMAGuvWI/+owwZ6KnzRDA0t+XcGm+e17qA==
+
+"@douyinfe/semi-ui@^2.0.0":
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.69.2.tgz#9f74898cc865fb01c622aa58f6205cbd11f3aa47"
+  integrity sha512-oDI3jLlwugpF8vNx6R+ivwTh1hu7Sr8Yrb3+8nsxNlc9+C+RHA01uu4xGmORwHQdYe2+YRn+kFP8+tu2Ch90Nw==
+  dependencies:
+    "@dnd-kit/core" "^6.0.8"
+    "@dnd-kit/sortable" "^7.0.2"
+    "@dnd-kit/utilities" "^3.2.1"
+    "@douyinfe/semi-animation" "2.69.2"
+    "@douyinfe/semi-animation-react" "2.69.2"
+    "@douyinfe/semi-foundation" "2.69.2"
+    "@douyinfe/semi-icons" "2.69.2"
+    "@douyinfe/semi-illustrations" "2.69.2"
+    "@douyinfe/semi-theme-default" "2.69.2"
+    async-validator "^3.5.0"
+    classnames "^2.2.6"
+    copy-text-to-clipboard "^2.1.1"
+    date-fns "^2.29.3"
+    date-fns-tz "^1.3.8"
+    fast-copy "^3.0.1 "
+    lodash "^4.17.21"
+    prop-types "^15.7.2"
+    react-resizable "^3.0.5"
+    react-window "^1.8.2"
+    scroll-into-view-if-needed "^2.2.24"
+    utility-types "^3.10.0"
+
 "@douyinfe/semi-ui@latest":
   version "2.65.0"
   resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.65.0.tgz#295eb0dd8e9e961adb4ddd7c7bbce3468d1b7430"
@@ -11831,6 +11915,11 @@ eslint-plugin-react@^7.20.6, eslint-plugin-react@^7.24.0:
     string.prototype.matchall "^4.0.11"
     string.prototype.repeat "^1.0.0"
 
+eslint-plugin-semi-design@^2.33.0:
+  version "2.69.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-semi-design/-/eslint-plugin-semi-design-2.69.2.tgz#971d43053d9201a816881a124f149c8abb0181e8"
+  integrity sha512-veVWSt17xITeAk8ztbc2EihiTfkMQk8P0U60he83tjRf5Qm4W6AAtecFOnZE9lauz4+D5FaDFjGkSfrOMuEwww==
+
 eslint-rule-composer@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"