diff --git a/src/components/spreadSheet/__tests__/index.test.tsx b/src/components/spreadSheet/__tests__/index.test.tsx
index dcb78daaa..83f304ab9 100644
--- a/src/components/spreadSheet/__tests__/index.test.tsx
+++ b/src/components/spreadSheet/__tests__/index.test.tsx
@@ -1,20 +1,82 @@
import React from 'react';
-import SpreadSheet from '../index';
-import { render } from '@testing-library/react';
+import { fireEvent, render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
+import SpreadSheet from '../index';
+
+// mock the CopyUtils
+jest.mock('../../utils/copy', () => {
+ class CopyUtilsMock {
+ copy = jest.fn();
+ }
+ return CopyUtilsMock;
+});
+
describe('test spreadSheet ', () => {
+ const columns = ['name', 'gender', 'age', 'address'];
+ const data = [
+ ['zhangsan', 'male', '20', 'xihu'],
+ ['lisi', 'male', '18', 'yuhang'],
+ ];
test('should render SpreadSheet custom className', () => {
- const { container } = render(
+ const { container, getByText, unmount } = render(
+
+ );
+ expect(container.firstChild).toHaveClass('testSpreadSheet');
+ expect(getByText('zhangsan')).toBeInTheDocument();
+ unmount();
+ });
+
+ test('renders without data', () => {
+ const { getByText, unmount } = render();
+ expect(getByText('暂无数据')).toBeInTheDocument();
+ unmount();
+ });
+
+ test('copy value without header', () => {
+ const { getByText, unmount } = render();
+ const cell = getByText('zhangsan');
+ fireEvent.contextMenu(cell);
+ 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();
+ });
+
+ test('copy value with header', () => {
+ const { getByText, unmount } = render(
);
- expect(container.firstChild).toHaveClass('testSpreadSheet');
+ const cell = getByText('zhangsan');
+ fireEvent.contextMenu(cell);
+ const copyBtn = getByText('复制列名和值');
+ expect(copyBtn).toBeInTheDocument();
+ fireEvent.click(copyBtn);
+ unmount();
+ });
+
+ test('should call componentDidUpdate when props are updated', () => {
+ const rerenderData = [['wangwu', 'male', '18', 'yuhang']];
+ jest.useFakeTimers();
+ const { rerender, getByText } = render();
+ rerender();
+ jest.runAllTimers();
+ expect(getByText('wangwu')).toBeInTheDocument();
});
});
diff --git a/src/components/spreadSheet/index.tsx b/src/components/spreadSheet/index.tsx
index ac8569b03..3d6622123 100644
--- a/src/components/spreadSheet/index.tsx
+++ b/src/components/spreadSheet/index.tsx
@@ -1,18 +1,18 @@
import React from 'react';
-
-import CopyUtils from '../utils/copy';
-import { HotTable } from '@handsontable/react';
import type { HotTableProps } from '@handsontable/react';
+import { HotTable } from '@handsontable/react';
import classNames from 'classnames';
-import 'handsontable/dist/handsontable.full.css';
import 'handsontable/languages/zh-CN.js';
+import CopyUtils from '../utils/copy';
+import 'handsontable/dist/handsontable.full.css';
+
type IOptions = HotTableProps & {
- /** 是否展示复制值以及列名 */
- showCopyWithHeader?: boolean;
+ // 右击右键菜单中展示的选项 复制值/复制列名/复制列名和值 按钮 */
+ copyTypes?: Array<'copyData' | 'copyHeaders' | 'copyHeadersAndData'>;
};
-export interface SpreadSheetProps {
+export interface ISpreadSheetProps {
data: Array>;
columns: any;
className?: string;
@@ -27,7 +27,7 @@ export interface SpreadSheetProps {
hotTableInstanceRef?: (instance: any) => void;
}
-class SpreadSheet extends React.PureComponent {
+class SpreadSheet extends React.PureComponent {
tableRef: any = React.createRef();
copyUtils = new CopyUtils();
_renderTimer: any;
@@ -54,7 +54,7 @@ class SpreadSheet extends React.PureComponent {
componentWillUnmount() {
this.removeRenderClock();
}
- getData() {
+ getShowData() {
const { data, columns = [] } = this.props;
let showData = data;
if (!showData || !showData.length) {
@@ -78,63 +78,110 @@ class SpreadSheet extends React.PureComponent {
}
return null;
}
- beforeCopy(arr: any, _arr2?: any) {
- /**
- * 去除格式化
- */
+
+ /**
+ * 去除格式化
+ */
+ beforeCopy(arr: Array>) {
const value = arr
.map((row: any) => {
return row.join('\t');
})
.join('\n');
+
this.copyUtils.copy(value);
return false;
}
getContextMenu() {
const that = this;
const { columns = [], options } = this.props;
- const items = {
- copy: {
- name: '复制',
- callback: function (_key) {
- const indexArr = this.getSelected();
- // eslint-disable-next-line prefer-spread
- const copyDataArr = this.getData.apply(this, indexArr[0]);
- that.beforeCopy(copyDataArr);
- },
- },
+ const { copyTypes = [] } = options || {};
+
+ // 获取值
+ const getCopyData = (_that) => {
+ // _that 调用的是 handsontable 的方法(在 handsontable.d.ts), this/that 调用的是当前文件的方法
+ const selectedIndexArr = _that.getSelected();
+ let dataArr = [];
+
+ if (Array.isArray(selectedIndexArr)) {
+ selectedIndexArr.forEach((arr, index) => {
+ const [r, c, r2, c2] = arr || [];
+ const colData: [] = _that.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 (options?.showCopyWithHeader) {
- const copyWithHeaderItem = {
- name: '复制值以及列名',
- callback: function (_key, selection) {
- 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) => {
+ // _that 调用的是 handsontable 的方法(在 handsontable.d.ts), this/that 调用的是当前文件的方法
+ let headerArr = [];
+ 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];
- }
- that.beforeCopy(copyDataArr);
- },
- };
- // 目前版本不支持 copy_with_column_headers 暂时用 cut 代替,以达到与copy类似的表现
- items['cut'] = copyWithHeaderItem;
+ });
+ }
+ return headerArr;
+ };
+
+ const copyDataItem = {
+ name: '复制值',
+ callback: function (_key) {
+ const copyDataArr = getCopyData(this);
+ that.beforeCopy(copyDataArr);
+ },
+ };
+ const copyHeadersItem = {
+ name: '复制列名',
+ callback: function (_key, selection) {
+ const copyHeaders = getCopyHeaders(selection);
+ that.beforeCopy([copyHeaders]);
+ },
+ };
+ const copyHeadersAndDataItem = {
+ name: '复制列名和值',
+ callback: function (_key, selection) {
+ const copyDataArr = getCopyData(this);
+ const copyHeaders = getCopyHeaders(selection);
+ that.beforeCopy([copyHeaders, ...copyDataArr]);
+ },
+ };
+
+ // 目前 items 在 https://github.com/handsontable/handsontable/blob/6.2.2/handsontable.d.ts#L779,自定义方法也可以被执行
+ const items = {};
+ 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;
}
render() {
const { columns = [], className = '', options, columnTypes = [] } = this.props;
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { trimWhitespace = true, showCopyWithHeader, ...restOptions } = options || {};
- const showData = this.getData();
+ const { trimWhitespace = true, ...restOptions } = options || {};
+ const showData = this.getShowData();
// 空数组情况,不显示colHeaders,否则colHeaders默认会按照 A、B...显示
// 具体可见 https://handsontable.com/docs/7.1.1/Options.html#colHeaders
let isShowColHeaders = false;
@@ -153,7 +200,9 @@ class SpreadSheet extends React.PureComponent {
if (!isShowColHeaders) return false;
// handsontable 不支持 renderCustomHeader,所以只能用 html string 实现 tooltip
const fieldTypeStr = columnTypes?.[index]?.type;
- const title = fieldTypeStr ? `${columns?.[index]}: ${fieldTypeStr}` : columns?.[index];
+ const title = fieldTypeStr
+ ? `${columns?.[index]}: ${fieldTypeStr}`
+ : columns?.[index];
return `${title}`;
}}
data={showData}
diff --git a/src/components/spreadSheet/style.scss b/src/components/spreadSheet/style.scss
index 1ac8d861e..48195d842 100644
--- a/src/components/spreadSheet/style.scss
+++ b/src/components/spreadSheet/style.scss
@@ -1,4 +1,5 @@
.dtc-handsontable-no-border {
+ width: 100%;
.handsontable {
thead tr:first-child th {
border-top: 0;
@@ -6,5 +7,15 @@
th:first-child {
border-left: 0;
}
+ th:last-child {
+ border-right: 0;
+ }
+ td:last-child {
+ border-right: 0;
+ }
+ // 最后一行
+ tr:last-child th, tr:last-child td {
+ border-bottom: 0;
+ }
}
}
diff --git a/src/components/utils/copy.tsx b/src/components/utils/copy.tsx
index 13a7aa3e0..476361c95 100644
--- a/src/components/utils/copy.tsx
+++ b/src/components/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;
}
diff --git a/src/stories/spreadSheert.stories.tsx b/src/stories/spreadSheert.stories.tsx
index 739628437..ffbc8ca1c 100644
--- a/src/stories/spreadSheert.stories.tsx
+++ b/src/stories/spreadSheert.stories.tsx
@@ -22,11 +22,11 @@ const propDefinitions = [
defaultValue: '',
},
{
- property: 'options.showCopyWithHeader',
- propType: 'Boolean',
+ property: 'options.copyTypes',
+ propType: 'Array<"copyData" \| "copyHeaders" \| "copyHeadersAndData">',
required: false,
- description: '右键菜单中是否展示“复制值以及列名”按钮',
- defaultValue: '',
+ description: '右键菜单中展示的选项 复制值/复制列名/复制列名和值 按钮',
+ defaultValue: '["copyData"]',
},
{
property: 'options.trimWhitespace',
@@ -41,11 +41,31 @@ const otherDependencies = `import { SpreadSheet } from 'dt-react-component';`;
const functionCode = '';
-const code = ``;
+const code1 = ``;
+
+const code2 = ``;
const stories = storiesOf('SpreadSheet 多功能表', module);
stories.add(
@@ -56,8 +76,9 @@ stories.add(
何时使用
表格内容右键可复制,表格大小可拖动
示例
+ 右键菜单:复制值、复制列名
+
+
+
+ 右键菜单:复制值、复制列名、复制列名和值
+
+
@@ -81,9 +127,18 @@ stories.add(
代码示例:
~~~js
import { SpreadSheet } from 'dt-react-component'
+
~~~
`,