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/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/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/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/__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/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..f6f159e92 100644
--- a/src/blockHeader/index.md
+++ b/src/blockHeader/index.md
@@ -21,23 +21,24 @@ demo:
带提示信息的标题
自定义 icon
展开/收起内容
+标题超长
## API
### BlockHeader
-| 参数 | 说明 | 类型 | 默认值 |
-| ----------------- | ----------------------------------------- | --------------------------- | ------- |
-| title | 标题 | `string` | - |
-| 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` | - |
diff --git a/src/blockHeader/index.tsx b/src/blockHeader/index.tsx
index 52d16a479..72813b6f4 100644
--- a/src/blockHeader/index.tsx
+++ b/src/blockHeader/index.tsx
@@ -3,13 +3,14 @@ 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;
export interface IBlockHeaderProps {
// 标题
- title: string;
+ title: ReactNode;
// 标题前的图标,默认是一个色块
beforeTitle?: ReactNode;
// 标题后的提示图标或文案
@@ -59,14 +60,15 @@ const BlockHeader: React.FC = function (props) {
const [expand, setExpand] = useState(defaultExpand);
+ const locale = useLocale('BlockHeader');
+
const preTitleRowCls = `${prefixCls}-title-row`;
const questionTooltip = tooltip && (
-
+
);
-
const newAfterTitle = afterTitle || questionTooltip;
let bottomStyle;
if (hasBottom) bottomStyle = { marginBottom: 16 };
@@ -104,7 +106,7 @@ const BlockHeader: React.FC = function (props) {
{addonAfter && {addonAfter}
}
{children && (
-
{expand ? '收起' : '展开'}
+
{expand ? locale.collapse : locale.expand}
)}
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;
+ }
}
}
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/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;
}
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/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);
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.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/__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..991465743 100644
--- a/src/spreadSheet/index.tsx
+++ b/src/spreadSheet/index.tsx
@@ -4,13 +4,16 @@ 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';
+type ICopyType = 'copyData' | 'copyHeaders' | 'copyHeadersAndData';
+
type IOptions = HotTableProps & {
- /** 是否展示复制值以及列名 */
- showCopyWithHeader?: boolean;
+ // 右键菜单中展示的选项 复制值/复制列名/复制列名和值 按钮 */
+ copyTypes?: ICopyType[];
};
export interface ISpreadSheetProps {
@@ -33,10 +36,11 @@ 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);
+ const locale = useLocale('SpreadSheet');
+
useEffect(() => {
if (tableRef.current) {
removeRenderClock();
@@ -53,11 +57,11 @@ 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);
- emptyArr[0] = '暂无数据';
+ emptyArr[0] = locale.description;
showData = [emptyArr];
}
return showData;
@@ -75,56 +79,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);
+ headerArr = headerArr.concat(columns.slice(columnStart, columnEnd + 1));
}
- if (columnArr) {
- copyDataArr = [columnArr, ...copyDataArr];
- }
- beforeCopy(copyDataArr);
- },
- };
- // 目前版本不支持 copy_with_column_headers 暂时用 cut 代替,以达到与copy类似的表现
- items['cut'] = copyWithHeaderItem;
+ });
+ }
+ return headerArr;
+ };
+
+ const copyDataItem = {
+ name: locale.copy,
+ callback: function (_key: string) {
+ const copyDataArr = getCopyData();
+ beforeCopy(copyDataArr);
+ },
+ };
+ const copyHeadersItem = {
+ name: locale.copyCol,
+ callback: function (_key: string, selection: Array) {
+ const copyHeaders = getCopyHeaders(selection);
+ beforeCopy([copyHeaders]);
+ },
+ };
+ const copyHeadersAndDataItem = {
+ name: locale.copyAll,
+ 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 +192,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/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);
}
};
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;
}