From ec502242864fdb6be3b78f729d968f8aa773a430 Mon Sep 17 00:00:00 2001 From: haiminovo <85284863+haiminovo@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:57:23 +0800 Subject: [PATCH 1/8] change question mark size to 16px in component blockHeader (#417) * change afterTitle question mark to 16px at blockheader component * change question mark size to 16px in blockTitle component --- src/blockHeader/index.tsx | 3 +-- src/blockHeader/style.scss | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/blockHeader/index.tsx b/src/blockHeader/index.tsx index 52d16a479..511de406b 100644 --- a/src/blockHeader/index.tsx +++ b/src/blockHeader/index.tsx @@ -63,10 +63,9 @@ const BlockHeader: React.FC = function (props) { const questionTooltip = tooltip && ( - + ); - const newAfterTitle = afterTitle || questionTooltip; let bottomStyle; if (hasBottom) bottomStyle = { marginBottom: 16 }; diff --git a/src/blockHeader/style.scss b/src/blockHeader/style.scss index 4575c9b49..a872a457e 100644 --- a/src/blockHeader/style.scss +++ b/src/blockHeader/style.scss @@ -37,6 +37,7 @@ $card_prefix: "dtc-block-header"; &-pointer { cursor: pointer; } + .#{$card_prefix}-title-box { flex: 1; display: flex; @@ -50,8 +51,14 @@ $card_prefix: "dtc-block-header"; margin-right: 4px; } .#{$card_prefix}-after-title { + display: flex; + align-items: center; color: #8B8FA8; font-size: 12px; + &-icon { + font-size: 16px; + color: #B1B4C5; + } } } From c9b7bd20a075654190e5982077ebaec6b56deff4 Mon Sep 17 00:00:00 2001 From: haiminovo <85284863+haiminovo@users.noreply.github.com> Date: Fri, 1 Dec 2023 17:11:58 +0800 Subject: [PATCH 2/8] add parameter 'clearData' to useList mutate function (#418) * optimized useList clear data before mutate request * add unit testing and desc * add demo to mutate options --- src/useList/__tests__/useList.test.ts | 21 +++++ src/useList/demos/mutateOptions.tsx | 129 ++++++++++++++++++++++++++ src/useList/index.md | 2 + src/useList/index.ts | 10 ++ 4 files changed, 162 insertions(+) create mode 100644 src/useList/demos/mutateOptions.tsx diff --git a/src/useList/__tests__/useList.test.ts b/src/useList/__tests__/useList.test.ts index a9d871d8d..965b765c0 100644 --- a/src/useList/__tests__/useList.test.ts +++ b/src/useList/__tests__/useList.test.ts @@ -133,4 +133,25 @@ describe('Test useList hook', () => { await awaitTimers(); expect(result.current.loading).toBe(false); }); + + it('Should support clear data before mutate', async () => { + const fetcher = jest.fn().mockResolvedValue({ + total: 1, + data: [{ uuid: 1 }], + error: new Error('testError'), + }); + const { result } = renderHook(() => + useList(fetcher, { current: 1, pageSize: 20, search: '' }) + ); + expect(fetcher).toBeCalledTimes(1); + await waitFor(() => { + expect(result.current.data.length).toBe(1); + }); + act(() => { + result.current.mutate({ search: 'test' }, { clearData: true }); + }); + expect(result.current.data).toStrictEqual([]); + expect(result.current.params.total).toBe(0); + expect(result.current.error).toBe(undefined); + }); }); diff --git a/src/useList/demos/mutateOptions.tsx b/src/useList/demos/mutateOptions.tsx new file mode 100644 index 000000000..9ae4bfacd --- /dev/null +++ b/src/useList/demos/mutateOptions.tsx @@ -0,0 +1,129 @@ +import React, { useState } from 'react'; +import { Button, Form, Input, Result, Select, Switch, Table } from 'antd'; +import { useList } from 'dt-react-component'; +import type { Fetcher } from 'dt-react-component/useList'; + +import getMockData, { type MockData } from './data'; + +const fetcher: Fetcher = ( + params +) => { + return new Promise<{ + data: MockData[]; + total: number; + }>((resolve) => { + setTimeout(() => { + resolve(getMockData(params)); + }, 150); + }); +}; + +export default () => { + const { error, params, loading, data, mutate } = useList(fetcher, { current: 1, pageSize: 20 }); + const [form] = Form.useForm(); + + if (error) return ; + + const handleSearch = async () => { + const values = await form.validateFields(); + mutate({ ...values }, { revalidate, clearData }); + }; + + const handleReset = async () => { + form.resetFields(); + const values = await form.validateFields(); + // 当传入值有 undefined 的时候,采用 functional 的写法。 + // 因为底层使用的 lodash 的 merge,采用赋值写法不会对 undefined 做合并 + mutate((pre) => ({ ...pre, ...values }), { revalidate, clearData }); + }; + const [revalidate, setRevalidate] = useState(true); + const [clearData, setClearData] = useState(true); + const onChangeRevalidate = () => { + setRevalidate(!revalidate); + }; + const onChangeClearData = () => { + setClearData(!clearData); + }; + + return ( + <> +
+ + + + + + + + + + revalidate + + clearData + + +
+ + mutate( + { current: pagination.current, pageSize: pagination.pageSize }, + { revalidate, clearData } + ) + } + size="small" + scroll={{ y: 200 }} + dataSource={data} + pagination={{ + current: params.current, + pageSize: params.pageSize, + total: params.total, + }} + rowKey="uuid" + bordered + /> + + ); +}; diff --git a/src/useList/index.md b/src/useList/index.md index 28f15f0a1..660a9754f 100644 --- a/src/useList/index.md +++ b/src/useList/index.md @@ -16,6 +16,7 @@ toc: content + ## API @@ -49,3 +50,4 @@ toc: content | 参数 | 说明 | 类型 | 默认值 | | ---------- | ---------------------- | --------- | ------ | | revalidate | 修改后是否重新请求数据 | `boolean` | `true` | +| clearData | 请求数据前是否清除数据 | `boolean` | `true` | diff --git a/src/useList/index.ts b/src/useList/index.ts index 16725b207..f98b64b7e 100644 --- a/src/useList/index.ts +++ b/src/useList/index.ts @@ -8,6 +8,10 @@ export interface IMutateOptions { * 是否数据重新获取 */ revalidate?: boolean; + /** + * 在mutate发起请求前是否清空data(包括data total error) + */ + clearData?: boolean; } export interface IUseListOptions { @@ -49,6 +53,7 @@ export default function useList, P extends Record< const mutate = (next: Partial

| ((prev: P) => P) = params, options: IMutateOptions = {}) => { const defaultOptions: IMutateOptions = { revalidate: true, + clearData: true, }; const nextOptions = merge(defaultOptions, options); @@ -56,6 +61,11 @@ export default function useList, P extends Record< setParams(tmp); if (nextOptions.revalidate) { + if (nextOptions.clearData) { + setData([]); + setTotal(0); + setError(undefined); + } performFetch(tmp); } }; From a458488d3000728aa2a530b9263721731c51e383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=89=E6=98=93?= Date: Wed, 10 Jan 2024 19:21:08 +0800 Subject: [PATCH 3/8] feat: blockHeader title support ReactNode (#432) --- README.md | 2 +- src/blockHeader/demos/style.scss | 3 +++ src/blockHeader/demos/title.tsx | 20 ++++++++++++++++++++ src/blockHeader/index.md | 3 ++- src/blockHeader/index.tsx | 2 +- 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 src/blockHeader/demos/style.scss create mode 100644 src/blockHeader/demos/title.tsx diff --git a/README.md b/README.md index 93affbd91..8597393d4 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ pnpm install dt-react-component ```jsx import React from 'react'; import { BlockHeader } from 'dt-react-component'; -const App = () => ; +const App = () => ; ``` ### Load on demand diff --git a/src/blockHeader/demos/style.scss b/src/blockHeader/demos/style.scss new file mode 100644 index 000000000..a4168fd9a --- /dev/null +++ b/src/blockHeader/demos/style.scss @@ -0,0 +1,3 @@ +.demo-title { + width: 300px; +} diff --git a/src/blockHeader/demos/title.tsx b/src/blockHeader/demos/title.tsx new file mode 100644 index 000000000..14d5d3274 --- /dev/null +++ b/src/blockHeader/demos/title.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { BlockHeader, EllipsisText } from 'dt-react-component'; + +import './style.scss'; + +export default () => { + return ( +

+ + } + /> +
+ ); +}; diff --git a/src/blockHeader/index.md b/src/blockHeader/index.md index c2fd25228..b1fb9fc8a 100644 --- a/src/blockHeader/index.md +++ b/src/blockHeader/index.md @@ -21,6 +21,7 @@ demo: 带提示信息的标题 自定义 icon 展开/收起内容 +标题超长 ## API @@ -28,7 +29,7 @@ demo: | 参数 | 说明 | 类型 | 默认值 | | ----------------- | ----------------------------------------- | --------------------------- | ------- | -| title | 标题 | `string` | - | +| title | 标题 | `React.ReactNode` | - | | beforeTitle | 标题前的图标,默认是一个色块 | `React.ReactNode` | - | | afterTitle | 标题后的提示图标或文案 | `React.ReactNode` | - | | tooltip | 默认展示问号提示(优先级低于 `afterTitle`) | `React.ReactNode` | - | diff --git a/src/blockHeader/index.tsx b/src/blockHeader/index.tsx index 511de406b..aa022e15b 100644 --- a/src/blockHeader/index.tsx +++ b/src/blockHeader/index.tsx @@ -9,7 +9,7 @@ export declare type SizeType = 'small' | 'middle' | undefined; export interface IBlockHeaderProps { // 标题 - title: string; + title: ReactNode; // 标题前的图标,默认是一个色块 beforeTitle?: ReactNode; // 标题后的提示图标或文案 From ba59dff2904cd120903b5327a5fc29106467b683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=89=E6=98=93?= Date: Wed, 21 Aug 2024 10:32:41 +0800 Subject: [PATCH 4/8] docs: blockHeader docs use size (#468) --- .../__tests__/blockHeader.test.tsx | 4 +-- src/blockHeader/index.md | 30 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/blockHeader/__tests__/blockHeader.test.tsx b/src/blockHeader/__tests__/blockHeader.test.tsx index c31ef0559..f1318c85c 100644 --- a/src/blockHeader/__tests__/blockHeader.test.tsx +++ b/src/blockHeader/__tests__/blockHeader.test.tsx @@ -90,13 +90,13 @@ describe('test BlockHeader render', () => { expect(getByText('说明文字')).toHaveClass(`${prefixCls}-after-title`); expect(getByText('Icon')).toBeTruthy(); }); - test('should render BlockHeader className when isSmall is small', () => { + test('should render BlockHeader props without background', () => { const props = { title: '测试1', showBackground: false }; const { container } = render(); const wrap = container.firstChild; expect(wrap!.firstChild).not.toHaveClass(`background`); }); - test('should render BlockHeader className when isSmall is small', () => { + test('should render BlockHeader className when size is small', () => { const { container, getByText } = render(); const wrap = container.firstChild!; expect(wrap).toHaveClass(`${prefixCls}`); diff --git a/src/blockHeader/index.md b/src/blockHeader/index.md index b1fb9fc8a..f6f159e92 100644 --- a/src/blockHeader/index.md +++ b/src/blockHeader/index.md @@ -27,18 +27,18 @@ demo: ### BlockHeader -| 参数 | 说明 | 类型 | 默认值 | -| ----------------- | ----------------------------------------- | --------------------------- | ------- | -| title | 标题 | `React.ReactNode` | - | -| beforeTitle | 标题前的图标,默认是一个色块 | `React.ReactNode` | - | -| afterTitle | 标题后的提示图标或文案 | `React.ReactNode` | - | -| tooltip | 默认展示问号提示(优先级低于 `afterTitle`) | `React.ReactNode` | - | -| isSmall | 大标题、小标题,默认为大标题 | `boolean` | `false` | -| titleRowClassName | 标题一行的样式类名 | `string` | - | -| titleClassName | 标题的样式类名 | `string` | - | -| showBackground | 是否显示背景 | `boolean` | `true` | -| defaultExpand | 是否默认展开内容 | `boolean` | `true` | -| hasBottom | 是否有默认下边距 16px | `boolean` | `false` | -| spaceBottom | 自定义下边距,优先级高于 hasBottom | `number` | `0` | -| children | 展开/收起的内容 | `React.ReactNode` | - | -| onChange | 展开/收起时的回调 | `(expand: boolean) => void` | - | +| 参数 | 说明 | 类型 | 默认值 | +| ----------------- | ----------------------------------------- | --------------------------- | -------- | -------- | +| title | 标题 | `React.ReactNode` | - | +| beforeTitle | 标题前的图标,默认是一个色块 | `React.ReactNode` | - | +| afterTitle | 标题后的提示图标或文案 | `React.ReactNode` | - | +| tooltip | 默认展示问号提示(优先级低于 `afterTitle`) | `React.ReactNode` | - | +| size | 小标题(small)、中标题(middle) | `small` | `middle` | `middle` | +| titleRowClassName | 标题一行的样式类名 | `string` | - | +| titleClassName | 标题的样式类名 | `string` | - | +| showBackground | 是否显示背景 | `boolean` | `true` | +| defaultExpand | 是否默认展开内容 | `boolean` | `true` | +| hasBottom | 是否有默认下边距 16px | `boolean` | `false` | +| spaceBottom | 自定义下边距,优先级高于 hasBottom | `number` | `0` | +| children | 展开/收起的内容 | `React.ReactNode` | - | +| onChange | 展开/收起时的回调 | `(expand: boolean) => void` | - | From 6d40b27c1e6b0c9b440eff546d908f93d44644ac Mon Sep 17 00:00:00 2001 From: lalala <56628290+jin-sir@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:25:44 +0800 Subject: [PATCH 5/8] fix: using ref to solve closure problems (#497) --- src/cookies/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cookies/index.tsx b/src/cookies/index.tsx index e62b60114..730a7cbb2 100644 --- a/src/cookies/index.tsx +++ b/src/cookies/index.tsx @@ -28,9 +28,11 @@ const useCookieListener = ( options: ICookieOptions = defaultOptions ) => { const { timeout, immediately } = options; + const isWatchAll = !watchFields.length; const timerRef = useRef(); const currentCookiesRef = useRef(document.cookie); - const isWatchAll = !watchFields.length; + const handlerRef = useRef(); + handlerRef.current = handler; useEffect(() => { timerRef.current = window.setInterval(() => { @@ -54,7 +56,7 @@ const useCookieListener = ( changedFields.push({ key, value: newValue }); } } - changedFields.length && handler({ changedFields, prevCookies, nextCookies }); + changedFields.length && handlerRef.current?.({ changedFields, prevCookies, nextCookies }); }; const compareValue = () => { @@ -62,7 +64,7 @@ const useCookieListener = ( const nextCookies = document.cookie; if (prevCookies !== nextCookies) { isWatchAll - ? handler({ prevCookies, nextCookies }) + ? handlerRef.current?.({ prevCookies, nextCookies }) : handleFieldsChange(prevCookies, nextCookies); currentCookiesRef.current = nextCookies; } From d06369262c2c449ba45dd9d4a55ef767ef7a67b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=89=E6=98=93?= Date: Thu, 8 May 2025 19:09:43 +0800 Subject: [PATCH 6/8] refactor: add copyTypes and remove showCopyWithHeader 4.x (#545) * feat(SpreadSheet): add copyTypes and remove showCopyWithHeader * docs: demo and changelog * fix: copy events compatible with old browsers --- CHANGELOG.md | 1 - docs/guide/migration-v4.md | 3 +- src/spreadSheet/__tests__/index.test.tsx | 22 +++- src/spreadSheet/demos/basic.tsx | 39 +++++-- src/spreadSheet/demos/changeData.tsx | 51 +++++++++ src/spreadSheet/index.md | 19 ++-- src/spreadSheet/index.tsx | 133 +++++++++++++++-------- src/utils/copy.tsx | 5 +- 8 files changed, 205 insertions(+), 68 deletions(-) create mode 100644 src/spreadSheet/demos/changeData.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 054fa8d5c..972d0a19f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,6 @@ All notable changes to this project will be documented in this file. See [standa ### Bug Fixes - fix immediate option in userList ([#378](https://github.com/DTStack/dt-react-component/issues/378)) ([b8992f3](https://github.com/DTStack/dt-react-component/commit/b8992f38f918dde10cb6349e39aca2c524e70853)) - > > > > > > > 272326794683ecd3ed1846ad38fd4258b23dc0f7 ## [4.2.0](https://github.com/DTStack/dt-react-component/compare/v4.1.0...v4.2.0) (2023-08-24) diff --git a/docs/guide/migration-v4.md b/docs/guide/migration-v4.md index 614e8cc60..7a6962041 100644 --- a/docs/guide/migration-v4.md +++ b/docs/guide/migration-v4.md @@ -150,9 +150,10 @@ useCookieListener( - 新增 `observerEle` 属性,支持自定义监听元素。 -#### SpreadSheet [#325](https://github.com/DTStack/dt-react-component/pull/325) +#### SpreadSheet [#325](https://github.com/DTStack/dt-react-component/pull/325)、[#545](https://github.com/DTStack/dt-react-component/pull/545) - 新增 `className` 属性,可自定义外层组件的 class 名。 +- 删除 `showCopyWithHeader` 属性,使用 `copyTypes` 属性代替,值为数组,可传入 'copyData'、'copyHeaders'、'copyHeadersAndData',分别代表的功能为:复制值、复制列名、复制列名和值。 #### KeyEventListener [#326](https://github.com/DTStack/dt-react-component/pull/326) diff --git a/src/spreadSheet/__tests__/index.test.tsx b/src/spreadSheet/__tests__/index.test.tsx index 9c147915f..83f304ab9 100644 --- a/src/spreadSheet/__tests__/index.test.tsx +++ b/src/spreadSheet/__tests__/index.test.tsx @@ -37,7 +37,7 @@ describe('test spreadSheet ', () => { const { getByText, unmount } = render(); const cell = getByText('zhangsan'); fireEvent.contextMenu(cell); - const copyBtn = getByText('复制'); + const copyBtn = getByText('复制值'); expect(copyBtn).toBeInTheDocument(); fireEvent.click(copyBtn); unmount(); @@ -45,11 +45,27 @@ describe('test spreadSheet ', () => { test('copy value with header', () => { const { getByText, unmount } = render( - + ); const cell = getByText('zhangsan'); fireEvent.contextMenu(cell); - const copyBtn = getByText('复制值以及列名'); + const copyBtn = getByText('复制值'); + expect(copyBtn).toBeInTheDocument(); + fireEvent.click(copyBtn); + unmount(); + }); + + test('copy value with header', () => { + const { getByText, unmount } = render( + + ); + const cell = getByText('zhangsan'); + fireEvent.contextMenu(cell); + const copyBtn = getByText('复制列名和值'); expect(copyBtn).toBeInTheDocument(); fireEvent.click(copyBtn); unmount(); diff --git a/src/spreadSheet/demos/basic.tsx b/src/spreadSheet/demos/basic.tsx index 5e308a4a1..e2c984980 100644 --- a/src/spreadSheet/demos/basic.tsx +++ b/src/spreadSheet/demos/basic.tsx @@ -3,14 +3,35 @@ import { SpreadSheet } from 'dt-react-component'; export default () => { return ( - + <> + 右键菜单:复制值、复制列名 + + +
+ 右键菜单:复制值、复制列名、复制列名和值 + + ); }; diff --git a/src/spreadSheet/demos/changeData.tsx b/src/spreadSheet/demos/changeData.tsx new file mode 100644 index 000000000..dc27ebc72 --- /dev/null +++ b/src/spreadSheet/demos/changeData.tsx @@ -0,0 +1,51 @@ +import React, { useRef, useState } from 'react'; +import { Button } from 'antd'; +import { SpreadSheet } from 'dt-react-component'; + +export default () => { + const _columns = ['name', 'gender', 'age', 'address']; + const _data = [ + ['zhangsan', 'male', '20', 'xihu'], + ['lisi', 'male', '18', 'yuhang'], + [' 前面有空格', '后面有空格 ', '中间有 空 格', 'yuhang'], + ]; + const [columns, setColumns] = useState(_columns); + const [data, setData] = useState(_data); + const hotTableInstanceRef = useRef(null); + + const handleData = () => { + setData(data?.length === 2 ? _data : _data.slice(0, 2)); + }; + + const handleColumns = () => { + setColumns(columns?.length === 3 ? _columns : _columns.slice(0, 3)); + }; + + const handleRef = () => { + console.log(hotTableInstanceRef?.current?.hotInstance?.getData()); + }; + + return ( + <> + + + + + + + ); +}; diff --git a/src/spreadSheet/index.md b/src/spreadSheet/index.md index 06aab0348..12c8c2972 100644 --- a/src/spreadSheet/index.md +++ b/src/spreadSheet/index.md @@ -3,7 +3,7 @@ title: SpreadSheet 多功能表 group: 组件 toc: content demo: - cols: 2 + cols: 1 --- # SpreadSheet 多功能表 @@ -14,16 +14,17 @@ demo: ## 示例 -基础使用 + + ## API ### SpreadSheet -| 参数 | 说明 | 类型 | 默认值 | -| -------------------------- | -------------------------------------- | ----------------- | ------ | -| data | 表格数据 | `Array(二维数组)` | - | -| columns | 列名 | `Array` | - | -| className | 外层组件的 class 名 | `string` | - | -| options.showCopyWithHeader | 右键菜单中是否展示“复制值以及列名”按钮 | `boolean` | - | -| options.trimWhitespace | 是否去除内容里的空格 | `boolean` | true | +| 参数 | 说明 | 类型 | 默认值 | +| ---------------------- | ------------------------------------------------------ | ------------------------------------------------------------ | -------------- | +| data | 表格数据 | `Array(二维数组)` | - | +| columns | 列名 | `Array` | - | +| className | 外层组件的 class 名 | `string` | - | +| options.copyTypes | 右键菜单中展示的选项 复制值/复制列名/复制列名和值 按钮 | `Array<'copyData' \| 'copyHeaders' \| 'copyHeadersAndData'>` | "['copyData']" | +| options.trimWhitespace | 是否去除内容里的空格 | `boolean` | true | diff --git a/src/spreadSheet/index.tsx b/src/spreadSheet/index.tsx index 6086683f9..758935477 100644 --- a/src/spreadSheet/index.tsx +++ b/src/spreadSheet/index.tsx @@ -8,9 +8,11 @@ import CopyUtils from '../utils/copy'; import 'handsontable/dist/handsontable.full.css'; import './style.scss'; +type ICopyType = 'copyData' | 'copyHeaders' | 'copyHeadersAndData'; + type IOptions = HotTableProps & { - /** 是否展示复制值以及列名 */ - showCopyWithHeader?: boolean; + // 右键菜单中展示的选项 复制值/复制列名/复制列名和值 按钮 */ + copyTypes?: ICopyType[]; }; export interface ISpreadSheetProps { @@ -33,10 +35,8 @@ const SpreadSheet: React.FC = forwardRef( const tableRef = useRef(null); const copyUtils = new CopyUtils(); const _timer = useRef(); - const { showCopyWithHeader, ...restProps } = options || {}; - useImperativeHandle(ref, () => ({ - tableRef, - })); + const { copyTypes = [], ...restProps } = options || {}; + useImperativeHandle(ref, () => tableRef.current); useEffect(() => { if (tableRef.current) { removeRenderClock(); @@ -53,7 +53,7 @@ const SpreadSheet: React.FC = forwardRef( clearTimeout(_timer.current); }; - const getData = () => { + const getShowData = () => { let showData = data; if (!showData?.length) { const emptyArr = new Array(columns.length).fill('', 0, columns.length); @@ -75,56 +75,101 @@ const SpreadSheet: React.FC = forwardRef( } }; - const beforeCopy = (arr: any[]) => { - /** - * 去除格式化 - */ + /** + * 去除格式化 + */ + const beforeCopy = (arr: Array>) => { const value = arr .map((row: any[]) => { return row.join('\t'); }) .join('\n'); + copyUtils.copy(value); return false; }; const getContextMenu = () => { - const items: Record = { - copy: { - name: '复制', - callback: function (this: any, _key: any) { - const indexArr = this.getSelected(); - // eslint-disable-next-line prefer-spread - const copyDataArr = this.getData.apply(this, indexArr[0]); - beforeCopy(copyDataArr); - }, - }, + // 获取值 + const getCopyData = () => { + // 调用的是 handsontable 的方法(在 handsontable.d.ts) + const selectedIndexArr = tableRef.current?.hotInstance?.getSelected(); + let dataArr: Array = []; + + if (Array.isArray(selectedIndexArr)) { + selectedIndexArr.forEach((arr, index) => { + const [r, c, r2, c2] = arr || []; + const colData: [] = + tableRef.current?.hotInstance?.getData(r, c, r2, c2) || []; + if (index === 0) { + dataArr.push(...colData); + } else { + dataArr = dataArr.map((item: any[], index: number) => { + return item.concat(colData[index]); + }); + } + }); + } + return dataArr; }; - if (showCopyWithHeader) { - const copyWithHeaderItem = { - name: '复制值以及列名', - callback: function (this: any, _key: any, selection: any) { - const indexArr = this.getSelected(); - // eslint-disable-next-line prefer-spread - let copyDataArr = this.getData.apply(this, indexArr[0]); - const columnStart = selection?.[0]?.start?.col; - const columnEnd = selection?.[0]?.end?.col; - let columnArr; + // 获取列名 + const getCopyHeaders = (selection: Array) => { + let headerArr: Array = []; + if (Array.isArray(selection)) { + selection.forEach((it) => { + const columnStart = it.start?.col; + const columnEnd = it.end?.col; if (columnStart !== undefined && columnEnd !== undefined) { - columnArr = columns.slice(columnStart, columnEnd + 1); - } - if (columnArr) { - copyDataArr = [columnArr, ...copyDataArr]; + headerArr = headerArr.concat(columns.slice(columnStart, columnEnd + 1)); } - beforeCopy(copyDataArr); - }, - }; - // 目前版本不支持 copy_with_column_headers 暂时用 cut 代替,以达到与copy类似的表现 - items['cut'] = copyWithHeaderItem; + }); + } + return headerArr; + }; + + const copyDataItem = { + name: '复制值', + callback: function (_key: string) { + const copyDataArr = getCopyData(); + beforeCopy(copyDataArr); + }, + }; + const copyHeadersItem = { + name: '复制列名', + callback: function (_key: string, selection: Array) { + const copyHeaders = getCopyHeaders(selection); + beforeCopy([copyHeaders]); + }, + }; + const copyHeadersAndDataItem = { + name: '复制列名和值', + callback: function (_key: string, selection: Array) { + const copyDataArr = getCopyData(); + const copyHeaders = getCopyHeaders(selection); + beforeCopy([copyHeaders, ...copyDataArr]); + }, + }; + + // 目前 items 在 https://github.com/handsontable/handsontable/blob/6.2.2/handsontable.d.ts#L779,自定义方法也可以被执行 + const items: Partial> = {}; + if (Array.isArray(copyTypes) && copyTypes?.length) { + // 复制值 + if (copyTypes.includes('copyData')) { + items['copyData'] = copyDataItem; + } + // 复制列名 + if (copyTypes.includes('copyHeaders')) { + items['copyHeaders'] = copyHeadersItem; + } + // 复制列名和值 + if (copyTypes.includes('copyHeadersAndData')) { + items['copyHeadersAndData'] = copyHeadersAndDataItem; + } + } else { + items['copyData'] = copyDataItem; } - return { - items, - } as any; + + return { items } as any; }; return ( @@ -143,7 +188,7 @@ const SpreadSheet: React.FC = forwardRef( : columns?.[index as number]; return `${title}`; }} - data={getData()} + data={getShowData()} mergeCells={getMergeCells()} cell={getCell()} readOnly diff --git a/src/utils/copy.tsx b/src/utils/copy.tsx index 896deb616..ac7a4e0bc 100644 --- a/src/utils/copy.tsx +++ b/src/utils/copy.tsx @@ -60,7 +60,10 @@ export default class CopyUtils { let succeeded; try { - succeeded = document.execCommand('copy'); + // 浏览器兼容性处理,当前语法已废弃,延迟处理可以保证复制成功 + setTimeout(() => { + succeeded = document.execCommand('copy'); + }); } catch (err) { succeeded = false; } From 8c8b92dfdd4aa86f3a1c8891931f592f7b3adada Mon Sep 17 00:00:00 2001 From: lalala <56628290+jin-sir@users.noreply.github.com> Date: Fri, 30 May 2025 17:25:15 +0800 Subject: [PATCH 7/8] fix(ellipsisText): ellipsisRef.current may be null (#572) --- src/ellipsisText/index.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ellipsisText/index.tsx b/src/ellipsisText/index.tsx index 65662dfd4..280bb67fb 100644 --- a/src/ellipsisText/index.tsx +++ b/src/ellipsisText/index.tsx @@ -78,6 +78,9 @@ const EllipsisText = (props: IEllipsisTextProps) => { * @return {*} */ const getStyle = (dom: NewHTMLElement, attr: string) => { + if (!dom) { + return null; + } // Compatible width IE8 // @ts-ignore return window.getComputedStyle(dom)[attr] || dom.currentStyle[attr]; @@ -203,7 +206,11 @@ const EllipsisText = (props: IEllipsisTextProps) => { * @return {*} */ const onResize = () => { - const ellipsisNode = ellipsisRef.current!; + if (!ellipsisRef.current) { + return; + } + + const ellipsisNode = ellipsisRef.current; const parentElement = ellipsisNode.parentElement!; const rangeWidth = getRangeWidth(ellipsisNode); const containerWidth = getContainerWidth(parentElement); From e88c400d5b4e7c7e7cf1c335e1977e42f67f4ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9C=9C=E5=BA=8F?= <976060700@qq.com> Date: Wed, 25 Jun 2025 14:32:39 +0800 Subject: [PATCH 8/8] feat(configprovider): 4.x support locale provider for RC (#548) * feat(configprovider): support provider for RC * feat(configprovider): migrate locale type to useLocale * fix(configprovider): fix unit test error * fix(configprovier): update ConfigProvider ts and en locale text * feat(configprovider): change localeFromContext * feat(configprovider): update ConfigProvider ts and en locale text --- package.json | 3 +- src/blockHeader/index.tsx | 5 ++- src/configProvider/index.tsx | 9 +++++ src/copy/index.tsx | 6 ++-- src/dropdown/select.tsx | 9 +++-- src/errorBoundary/loadError.tsx | 12 ++++--- src/fullscreen/index.tsx | 7 +++- src/globalLoading/index.tsx | 5 ++- src/index.ts | 3 ++ src/input/match.tsx | 45 ++++++++++++----------- src/locale/en-US.ts | 57 +++++++++++++++++++++++++++++ src/locale/useLocale.tsx | 63 +++++++++++++++++++++++++++++++++ src/locale/zh-CN.ts | 51 ++++++++++++++++++++++++++ src/modal/form.tsx | 7 ++-- src/mxGraph/index.tsx | 5 ++- src/notFound/index.tsx | 4 ++- src/spreadSheet/index.tsx | 12 ++++--- 17 files changed, 261 insertions(+), 42 deletions(-) create mode 100644 src/configProvider/index.tsx create mode 100644 src/locale/en-US.ts create mode 100644 src/locale/useLocale.tsx create mode 100644 src/locale/zh-CN.ts diff --git a/package.json b/package.json index e2e14dab8..4c44191c6 100644 --- a/package.json +++ b/package.json @@ -133,5 +133,6 @@ }, "resolutions": { "rc-motion": "2.6.2" - } + }, + "packageManager": "pnpm@6.32.12" } diff --git a/src/blockHeader/index.tsx b/src/blockHeader/index.tsx index aa022e15b..72813b6f4 100644 --- a/src/blockHeader/index.tsx +++ b/src/blockHeader/index.tsx @@ -3,6 +3,7 @@ import { QuestionCircleOutlined, UpOutlined } from '@ant-design/icons'; import { Tooltip } from 'antd'; import classNames from 'classnames'; +import useLocale from '../locale/useLocale'; import './style.scss'; export declare type SizeType = 'small' | 'middle' | undefined; @@ -59,6 +60,8 @@ const BlockHeader: React.FC = function (props) { const [expand, setExpand] = useState(defaultExpand); + const locale = useLocale('BlockHeader'); + const preTitleRowCls = `${prefixCls}-title-row`; const questionTooltip = tooltip && ( @@ -103,7 +106,7 @@ const BlockHeader: React.FC = function (props) { {addonAfter &&
{addonAfter}
} {children && (
-
{expand ? '收起' : '展开'}
+
{expand ? locale.collapse : locale.expand}
)} diff --git a/src/configProvider/index.tsx b/src/configProvider/index.tsx new file mode 100644 index 000000000..d24d70ab0 --- /dev/null +++ b/src/configProvider/index.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +import { Locale, LocaleContext } from '../locale/useLocale'; + +const ConfigProvider = ({ locale, children }: { locale: Locale; children: React.ReactNode }) => { + return {children}; +}; + +export default ConfigProvider; diff --git a/src/copy/index.tsx b/src/copy/index.tsx index 9622e930e..b7b85f792 100644 --- a/src/copy/index.tsx +++ b/src/copy/index.tsx @@ -4,6 +4,7 @@ import { message, Tooltip } from 'antd'; import classNames from 'classnames'; import useClippy from 'use-clippy'; +import useLocale from '../locale/useLocale'; import './style.scss'; export interface ICopyProps { @@ -17,14 +18,15 @@ export interface ICopyProps { } const Copy: React.FC = (props) => { + const locale = useLocale('Copy'); const { button = , text, - title = '复制', + title = locale.copy, hideTooltip, style, className, - onCopy = () => message.success('复制成功'), + onCopy = () => message.success(locale.copied), } = props; const [_, setClipboard] = useClippy(); diff --git a/src/dropdown/select.tsx b/src/dropdown/select.tsx index b0b6323a1..8b420894a 100644 --- a/src/dropdown/select.tsx +++ b/src/dropdown/select.tsx @@ -6,6 +6,7 @@ import classNames from 'classnames'; import { isEqual } from 'lodash'; import List from 'rc-virtual-list'; +import useLocale from '../locale/useLocale'; import './style.scss'; interface IDropdownSelectProps @@ -31,6 +32,8 @@ export default function Select({ }: IDropdownSelectProps) { const [visible, setVisible] = useState(false); + const locale = useLocale('Dropdown'); + const handleCheckedAll = (e: CheckboxChangeEvent) => { if (e.target.checked) { onChange?.(options?.map((i) => i.value) || []); @@ -119,7 +122,7 @@ export default function Select({ checked={checkAll} indeterminate={indeterminate} > - 全选 + {locale.selectAll}
@@ -158,10 +161,10 @@ export default function Select({ diff --git a/src/errorBoundary/loadError.tsx b/src/errorBoundary/loadError.tsx index 8d75380d2..b10fbd491 100644 --- a/src/errorBoundary/loadError.tsx +++ b/src/errorBoundary/loadError.tsx @@ -1,22 +1,24 @@ import React from 'react'; +import useLocale from '../locale/useLocale'; + const LoadError: React.FC = function () { + const locale = useLocale('LoadError'); return (
- 

- 发现新版本,请 + {locale.please} { location.reload(); }} > - 刷新 + {locale.refresh} - 获取新版本。 + {locale.get}

-

若该提示长时间存在,请联系管理员。

+

{locale.title}

); diff --git a/src/fullscreen/index.tsx b/src/fullscreen/index.tsx index ece3b7502..97b5ea09f 100644 --- a/src/fullscreen/index.tsx +++ b/src/fullscreen/index.tsx @@ -2,6 +2,7 @@ import React, { CSSProperties, HTMLAttributes, ReactNode, useEffect, useState } import { Button } from 'antd'; import KeyEventListener from '../keyEventListener'; +import useLocale from '../locale/useLocale'; import MyIcon from './icon'; const { KeyCombiner } = KeyEventListener; @@ -44,7 +45,11 @@ export default function Fullscreen({ ...other }: IFullscreenProps) { const [isFullScreen, setIsFullScreen] = useState(false); + + const locale = useLocale('Fullscreen'); + const customIcon = isFullScreen ? exitFullIcon : fullIcon; + useEffect(() => { const propsDom = document.getElementById(target); const domEle = propsDom || document.body; @@ -188,7 +193,7 @@ export default function Fullscreen({ ) : ( )} diff --git a/src/globalLoading/index.tsx b/src/globalLoading/index.tsx index 1269cf757..9cc2ff50b 100644 --- a/src/globalLoading/index.tsx +++ b/src/globalLoading/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import classNames from 'classnames'; +import useLocale from '../locale/useLocale'; import './style.scss'; export interface IGlobalLoadingProps { @@ -12,8 +13,10 @@ export interface IGlobalLoadingProps { } const GlobalLoading: React.FC = function (props) { + const locale = useLocale('GlobalLoading'); + const { - loadingTitle = '应用加载中,请等候~', + loadingTitle = locale.loading, mainBackground = '#F2F7FA', circleBackground = '#1D78FF', titleColor = '#3D446E', diff --git a/src/index.ts b/src/index.ts index e284f6601..5c0822dde 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export { default as BlockHeader } from './blockHeader'; export { default as CollapsibleActionItems } from './collapsibleActionItems'; +export { default as ConfigProvider } from './configProvider'; export { default as ContextMenu } from './contextMenu'; export { default as useCookieListener } from './cookies'; export { default as Copy } from './copy'; @@ -13,6 +14,8 @@ export { default as Fullscreen } from './fullscreen'; export { default as GlobalLoading } from './globalLoading'; export { default as Input } from './input'; export { default as KeyEventListener } from './keyEventListener'; +export { default as enUS } from './locale/en-US'; +export { default as zhCN } from './locale/zh-CN'; export { default as MarkdownRender } from './markdownRender'; export { default as Modal } from './modal'; export { default as MxGraphContainer, WIDGETS_PREFIX } from './mxGraph'; diff --git a/src/input/match.tsx b/src/input/match.tsx index a13c1c176..020ee844e 100644 --- a/src/input/match.tsx +++ b/src/input/match.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Input, type InputProps, Tooltip } from 'antd'; import classNames from 'classnames'; +import useLocale from '../locale/useLocale'; import { CaseSensitiveIcon, FrontIcon, PreciseIcon, TailIcon } from './icons'; import './match.scss'; @@ -29,30 +30,11 @@ interface IMatchProps extends Omit { onSearch?: (value: string, searchType: SearchType) => void; } -const searchTypeList = [ - { - key: 'caseSensitive', - tip: '区分大小写匹配', - }, - { - key: 'precise', - tip: '精确匹配', - }, - { - key: 'front', - tip: '头部匹配', - }, - { - key: 'tail', - tip: '尾部匹配', - }, -] as const; - export default function Match({ className, value, searchType, - filterOptions = searchTypeList.map((i) => i.key), + filterOptions: propFilterOptions, onTypeChange, onSearch, onChange, @@ -62,12 +44,35 @@ export default function Match({ const [internalValue, setValue] = useState(''); const [internalSearchType, setSearchType] = useState('fuzzy'); + const locale = useLocale('Input'); + const handleTypeChange = (key: SearchType) => { const next = realSearchType === key ? 'fuzzy' : key; onTypeChange?.(next); setSearchType(next); }; + const searchTypeList = [ + { + key: 'caseSensitive', + tip: locale.case, + }, + { + key: 'precise', + tip: locale.precise, + }, + { + key: 'front', + tip: locale.front, + }, + { + key: 'tail', + tip: locale.tail, + }, + ] as const; + + const filterOptions = propFilterOptions || searchTypeList.map((i) => i.key); + const options = searchTypeList.filter((i) => filterOptions.includes(i.key)); const realSearchType = searchType || internalSearchType; diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts new file mode 100644 index 000000000..264b9df7d --- /dev/null +++ b/src/locale/en-US.ts @@ -0,0 +1,57 @@ +import { Locale } from './useLocale'; + +const localeValues: Locale = { + locale: 'en-US', + BlockHeader: { + expand: 'Expand', + collapse: 'Collapse', + }, + + Copy: { + copied: 'Copied', + + copy: 'Copy', + }, + Fullscreen: { + exitFull: 'Exit Full Screen', + full: 'Full Screen', + }, + GlobalLoading: { + loading: 'The application is loading, please wait~', + }, + LoadError: { + please: 'A new version has been found. Please', + get: 'to get the new version.', + refresh: ' refresh ', + title: 'If this prompt persists for a long time, please contact the administrator.', + }, + Modal: { + okText: 'Ok', + cancelText: 'Cancel', + }, + Dropdown: { + resetText: 'Cancel', + okText: 'Ok', + selectAll: 'Select All', + }, + Input: { + case: 'Case-sensitive match', + precise: 'Exact match', + front: 'Head match', + tail: 'Tail match', + }, + MxGraph: { + newNode: 'New node', + }, + NotFound: { + description: 'Dear, did you go to the wrong place?', + }, + SpreadSheet: { + description: 'No Data', + copy: 'Copy values', + copyCol: 'Copy column names', + copyAll: 'Copy values ​​and column names', + }, +}; + +export default localeValues; diff --git a/src/locale/useLocale.tsx b/src/locale/useLocale.tsx new file mode 100644 index 000000000..d9ecc517f --- /dev/null +++ b/src/locale/useLocale.tsx @@ -0,0 +1,63 @@ +import { createContext, useContext, useMemo } from 'react'; + +import defaultLocaleData from './zh-CN'; + +export interface Locale { + locale: string; + BlockHeader: { expand: string; collapse: string }; + Copy: { copied: string; copy: string }; + Dropdown: { selectAll: string; resetText: string; okText: string }; + Fullscreen: { exitFull: string; full: string }; + GlobalLoading: { loading: string }; + Input: { + case: string; + precise: string; + front: string; + tail: string; + }; + LoadError: { + please: string; + get: string; + refresh: string; + title: string; + }; + Modal: { + okText: string; + cancelText: string; + }; + MxGraph: { newNode: string }; + NotFound: { + description: string; + }; + SpreadSheet: { + description: string; + copy: string; + copyCol: string; + copyAll: string; + }; +} + +export interface LocaleContextProps { + locale: Locale; +} + +export const LocaleContext = createContext({ locale: {} as Locale }); + +export type LocaleComponentName = keyof Locale; + +const useLocale = ( + componentName: C +): NonNullable => { + const fullLocale = useContext(LocaleContext); + + const getLocale = useMemo(() => { + const locale = defaultLocaleData[componentName] ?? {}; + const localeFromContext = fullLocale?.locale[componentName] ?? {}; + + return Object.assign({}, locale, localeFromContext) as NonNullable; + }, [componentName, fullLocale]); + + return getLocale; +}; + +export default useLocale; diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts new file mode 100644 index 000000000..ac716da58 --- /dev/null +++ b/src/locale/zh-CN.ts @@ -0,0 +1,51 @@ +import { Locale } from './useLocale'; + +const localeValues: Locale = { + locale: 'zh-CN', + BlockHeader: { + expand: '展开', + collapse: '收起', + }, + Copy: { + copied: '复制成功', + copy: '复制', + }, + Dropdown: { selectAll: '全选', resetText: '重置', okText: '确定' }, + Fullscreen: { + exitFull: '退出全屏', + full: '全屏', + }, + GlobalLoading: { + loading: '应用加载中,请等候~', + }, + LoadError: { + please: '发现新版本,请', + get: '获取新版本。', + refresh: '刷新', + title: '若该提示长时间存在,请联系管理员。', + }, + Input: { + case: '区分大小写匹配', + precise: '精确匹配', + front: '头部匹配', + tail: '尾部匹配', + }, + Modal: { + okText: '确定', + cancelText: '取消', + }, + MxGraph: { + newNode: '新节点', + }, + NotFound: { + description: '亲,是不是走错地方了?', + }, + SpreadSheet: { + description: '暂无数据', + copy: '复制值', + copyCol: '复制列名', + copyAll: '复制列名和值', + }, +}; + +export default localeValues; diff --git a/src/modal/form.tsx b/src/modal/form.tsx index f0dfcf518..885e2efd0 100644 --- a/src/modal/form.tsx +++ b/src/modal/form.tsx @@ -2,6 +2,7 @@ import React, { ReactElement, useMemo } from 'react'; import { FormProps, Modal, ModalProps } from 'antd'; import Form from '../form'; +import useLocale from '../locale/useLocale'; import Utils from '../utils'; import { FORM_PROPS, MODAL_PROPS } from '../utils/antdProps'; @@ -23,9 +24,11 @@ export interface IModalFormProps } const ModalForm = (props: IModalFormProps) => { + const locale = useLocale('Modal'); + const { - okText = '确定', - cancelText = '取消', + okText = locale.okText, + cancelText = locale.cancelText, layout = 'vertical', maskClosable = false, children, diff --git a/src/mxGraph/index.tsx b/src/mxGraph/index.tsx index 2822141fd..38ee86fc6 100644 --- a/src/mxGraph/index.tsx +++ b/src/mxGraph/index.tsx @@ -20,6 +20,7 @@ import { mxPopupMenuHandler, } from 'mxgraph'; +import useLocale from '../locale/useLocale'; import MxFactory from './factory'; import './style.scss'; @@ -293,6 +294,8 @@ function MxGraphContainer( const keybindingsRef = useRef([]); const [current, setCurrent] = useState(null); + const locale = useLocale('MxGraph'); + useImperativeHandle(ref, () => ({ /** * 在某一位置插入节点 @@ -427,7 +430,7 @@ function MxGraphContainer( onGetPreview?.(node) || (() => { const dom = document.createElement('div'); - dom.innerHTML = `新节点`; + dom.innerHTML = `${locale.newNode}`; return dom; })(); diff --git a/src/notFound/index.tsx b/src/notFound/index.tsx index 67fb6d00c..08cd61952 100644 --- a/src/notFound/index.tsx +++ b/src/notFound/index.tsx @@ -1,13 +1,15 @@ import React from 'react'; import { FrownOutlined } from '@ant-design/icons'; +import useLocale from '../locale/useLocale'; import './style.scss'; const NotFound: React.FC = function () { + const locale = useLocale('NotFound'); return (

- 亲,是不是走错地方了? + {locale.description}

); diff --git a/src/spreadSheet/index.tsx b/src/spreadSheet/index.tsx index 758935477..991465743 100644 --- a/src/spreadSheet/index.tsx +++ b/src/spreadSheet/index.tsx @@ -4,6 +4,7 @@ import { HotTable } from '@handsontable/react'; import classNames from 'classnames'; import 'handsontable/languages/zh-CN.js'; +import useLocale from '../locale/useLocale'; import CopyUtils from '../utils/copy'; import 'handsontable/dist/handsontable.full.css'; import './style.scss'; @@ -36,7 +37,10 @@ const SpreadSheet: React.FC = forwardRef( const copyUtils = new CopyUtils(); const _timer = useRef(); const { copyTypes = [], ...restProps } = options || {}; + useImperativeHandle(ref, () => tableRef.current); + const locale = useLocale('SpreadSheet'); + useEffect(() => { if (tableRef.current) { removeRenderClock(); @@ -57,7 +61,7 @@ const SpreadSheet: React.FC = forwardRef( let showData = data; if (!showData?.length) { const emptyArr = new Array(columns.length).fill('', 0, columns.length); - emptyArr[0] = '暂无数据'; + emptyArr[0] = locale.description; showData = [emptyArr]; } return showData; @@ -128,21 +132,21 @@ const SpreadSheet: React.FC = forwardRef( }; const copyDataItem = { - name: '复制值', + name: locale.copy, callback: function (_key: string) { const copyDataArr = getCopyData(); beforeCopy(copyDataArr); }, }; const copyHeadersItem = { - name: '复制列名', + name: locale.copyCol, callback: function (_key: string, selection: Array) { const copyHeaders = getCopyHeaders(selection); beforeCopy([copyHeaders]); }, }; const copyHeadersAndDataItem = { - name: '复制列名和值', + name: locale.copyAll, callback: function (_key: string, selection: Array) { const copyDataArr = getCopyData(); const copyHeaders = getCopyHeaders(selection);