From 3c0884b4c95efc65279db375327860a3b4335af8 Mon Sep 17 00:00:00 2001 From: Jinke Li Date: Wed, 15 Nov 2023 16:36:03 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(interaction):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=A1=8C=E5=88=97=E5=A4=B4=E5=9C=88=E9=80=89=E5=90=8E=E6=BB=91?= =?UTF-8?q?=E5=87=BA=E5=8F=AF=E8=A7=86=E8=8C=83=E5=9B=B4=E5=90=8E,=20?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E9=80=89=E6=8B=A9=E4=BA=86=E6=95=B0?= =?UTF-8?q?=E5=80=BC=E5=8D=95=E5=85=83=E6=A0=BC=20close=20#2340=20(#2411)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(interaction): 修复行列头圈选后滑出可视范围后, 错误的选择了数值单元格 close #2340 * fix: brush selected --- .../s2-core/__tests__/bugs/issue-2340-spec.ts | 78 +++++++++++++++++++ packages/s2-core/src/cell/data-cell.ts | 2 +- packages/s2-core/src/cell/header-cell.ts | 1 + .../src/common/constant/interaction.ts | 1 + packages/s2-core/src/facet/header/col.ts | 6 +- .../brush-selection/col-brush-selection.ts | 8 +- .../data-cell-brush-selection.ts | 3 +- .../brush-selection/row-brush-selection.ts | 2 +- packages/s2-core/src/interaction/root.ts | 5 +- 9 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 packages/s2-core/__tests__/bugs/issue-2340-spec.ts diff --git a/packages/s2-core/__tests__/bugs/issue-2340-spec.ts b/packages/s2-core/__tests__/bugs/issue-2340-spec.ts new file mode 100644 index 0000000000..1f6bc1e71e --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2340-spec.ts @@ -0,0 +1,78 @@ +/** + * @description spec for issue #2340 + * https://github.com/antvis/S2/issues/2340 + */ +import { map } from 'lodash'; +import { + CellTypes, + InteractionStateName, + getCellMeta, + type S2Options, + HeaderCell, +} from '../../src'; +import { createPivotSheet, sleep } from '../util/helpers'; + +const s2Options: S2Options = { + width: 800, + height: 600, + style: { + cellCfg: { + width: 200, + height: 200, + }, + }, +}; + +describe('Header Brush Selection Tests', () => { + test.each([CellTypes.COL_CELL, CellTypes.ROW_CELL])( + 'should not trigger data cell selected when header selected and scroll out of viewport', + async (cellType) => { + const s2 = createPivotSheet(s2Options, { useSimpleData: false }); + s2.render(); + + const isRow = cellType === CellTypes.ROW_CELL; + const targetCells = isRow + ? s2.interaction.getAllRowHeaderCells() + : s2.interaction.getAllColHeaderCells(); + + const cells = [ + (targetCells as HeaderCell[]).find((cell) => { + const meta = cell.getMeta(); + return meta.isLeaf; + }), + ]; + + s2.interaction.changeState({ + cells: map(cells, getCellMeta), + stateName: InteractionStateName.BRUSH_SELECTED, + }); + + await sleep(500); + + const offsetKey = isRow ? 'offsetY' : 'offsetX'; + // 将圈选的单元格滑出可视范围 + s2.facet.updateScrollOffset({ + [offsetKey]: { value: 300 }, + }); + + await sleep(500); + + // 还原 + s2.facet.updateScrollOffset({ + [offsetKey]: { value: 0 }, + }); + + expect(s2.interaction.getActiveCells()).toHaveLength(1); + expect(s2.interaction.getCurrentStateName()).toEqual( + InteractionStateName.BRUSH_SELECTED, + ); + + // 交互过的不应该有 dataCell (未触发过列头多选) + s2.interaction.getInteractedCells().forEach((cell) => { + expect(cell.cellType).toEqual( + isRow ? CellTypes.ROW_CELL : CellTypes.COL_CELL, + ); + }); + }, + ); +}); diff --git a/packages/s2-core/src/cell/data-cell.ts b/packages/s2-core/src/cell/data-cell.ts index 0703ee72c6..189785e151 100644 --- a/packages/s2-core/src/cell/data-cell.ts +++ b/packages/s2-core/src/cell/data-cell.ts @@ -393,7 +393,7 @@ export class DataCell extends BaseCell { ); } - // dataCell根据state 改变当前样式, + // dataCell 根据 state 改变当前样式, protected changeRowColSelectState(indexType: ViewMetaIndexType) { const { interaction } = this.spreadsheet; const currentIndex = get(this.meta, indexType); diff --git a/packages/s2-core/src/cell/header-cell.ts b/packages/s2-core/src/cell/header-cell.ts index 0bfebe0b5f..a2dfd1eb3f 100644 --- a/packages/s2-core/src/cell/header-cell.ts +++ b/packages/s2-core/src/cell/header-cell.ts @@ -415,6 +415,7 @@ export abstract class HeaderCell extends BaseCell { switch (stateInfo?.stateName) { case InteractionStateName.SELECTED: + case InteractionStateName.BRUSH_SELECTED: this.handleSelect(cells, stateInfo?.nodes); break; case InteractionStateName.HOVER_FOCUS: diff --git a/packages/s2-core/src/common/constant/interaction.ts b/packages/s2-core/src/common/constant/interaction.ts index 75a9650318..b63e1c6f11 100644 --- a/packages/s2-core/src/common/constant/interaction.ts +++ b/packages/s2-core/src/common/constant/interaction.ts @@ -18,6 +18,7 @@ export enum InteractionName { export enum InteractionStateName { ALL_SELECTED = 'allSelected', SELECTED = 'selected', + BRUSH_SELECTED = 'brushSelected', UNSELECTED = 'unselected', HOVER = 'hover', HOVER_FOCUS = 'hoverFocus', diff --git a/packages/s2-core/src/facet/header/col.ts b/packages/s2-core/src/facet/header/col.ts index 7bd98f46ba..60c2df7002 100644 --- a/packages/s2-core/src/facet/header/col.ts +++ b/packages/s2-core/src/facet/header/col.ts @@ -77,14 +77,14 @@ export class ColHeader extends BaseHeader { return this.scrollGroup; } - protected isColCellInRect(item: Node): boolean { + protected isColCellInRect(node: Node): boolean { const { spreadsheet, cornerWidth, width, scrollX } = this.headerConfig; return ( // don't care about scrollY, because there is only freeze col-header exist - width + scrollX > item.x && + width + scrollX > node.x && scrollX - (spreadsheet.isFrozenRowHeader() ? 0 : cornerWidth) < - item.x + item.width + node.x + node.width ); } diff --git a/packages/s2-core/src/interaction/brush-selection/col-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/col-brush-selection.ts index 38881a5676..8536d46440 100644 --- a/packages/s2-core/src/interaction/brush-selection/col-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/col-brush-selection.ts @@ -96,16 +96,14 @@ export class ColBrushSelection extends BaseBrushSelection { ); } - // 最终刷选的cell + // 最终刷选的 cell protected updateSelectedCells() { const { interaction } = this.spreadsheet; interaction.changeState({ cells: map(this.brushRangeCells, getCellMeta), - stateName: InteractionStateName.SELECTED, - onUpdateCells: (root) => { - root.updateCells(root.getAllColHeaderCells()); - }, + stateName: InteractionStateName.BRUSH_SELECTED, + onUpdateCells: this.onUpdateCells, }); this.spreadsheet.emit( diff --git a/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts index ea3faf6704..dabfa25ae2 100644 --- a/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts @@ -94,13 +94,14 @@ export class DataCellBrushSelection extends BaseBrushSelection { return metas; }; - // 最终刷选的cell + // 最终刷选的 cell protected updateSelectedCells() { const brushRange = this.getBrushRange(); const selectedCellMetas = this.getSelectedCellMetas(brushRange); this.spreadsheet.interaction.changeState({ cells: selectedCellMetas, + // TODO: 怕上层有直接消费 stateName, 暂时保留, 2.0 版本改成 InteractionStateName.BRUSH_SELECTED stateName: InteractionStateName.SELECTED, onUpdateCells: afterSelectDataCells, }); diff --git a/packages/s2-core/src/interaction/brush-selection/row-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/row-brush-selection.ts index 95225bcc90..57e842c95d 100644 --- a/packages/s2-core/src/interaction/brush-selection/row-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/row-brush-selection.ts @@ -96,7 +96,7 @@ export class RowBrushSelection extends BaseBrushSelection { this.spreadsheet.interaction.changeState({ cells: selectedCellMetas, - stateName: InteractionStateName.SELECTED, + stateName: InteractionStateName.BRUSH_SELECTED, onUpdateCells: this.onUpdateCells, }); diff --git a/packages/s2-core/src/interaction/root.ts b/packages/s2-core/src/interaction/root.ts index 2e87577dab..03c4c66c56 100644 --- a/packages/s2-core/src/interaction/root.ts +++ b/packages/s2-core/src/interaction/root.ts @@ -144,7 +144,10 @@ export class RootInteraction { } public isSelectedState() { - return this.isStateOf(InteractionStateName.SELECTED); + return ( + this.isStateOf(InteractionStateName.SELECTED) || + this.isStateOf(InteractionStateName.BRUSH_SELECTED) + ); } public isAllSelectedState() { From 708fde479bb48b941445b3adaf1f56cf5cb6b301 Mon Sep 17 00:00:00 2001 From: Jinke Li Date: Wed, 15 Nov 2023 16:43:36 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(copy):=20=E4=BF=AE=E5=A4=8D=E5=88=B7?= =?UTF-8?q?=E9=80=89=E5=A4=8D=E5=88=B6=E8=A1=8C=E5=88=97=E5=A4=B4=E6=97=B6?= =?UTF-8?q?,=20=E6=95=B0=E5=80=BC=E5=8D=95=E5=85=83=E6=A0=BC=E6=9C=AA?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=20&=20=E5=AD=98=E5=9C=A8=E7=9C=81?= =?UTF-8?q?=E7=95=A5=E5=8F=B7=E6=97=B6=E6=9C=AA=E5=A4=8D=E5=88=B6=E5=8E=9F?= =?UTF-8?q?=E5=A7=8B=E5=80=BC=20(#2410)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(copy): 修复刷选复制行列头时, 数值单元格未格式化 --- .../__tests__/unit/utils/export/copy-spec.ts | 151 +++++++++++++++++- packages/s2-core/src/utils/export/copy.ts | 59 ++++--- packages/s2-react/playground/config.ts | 2 + s2-site/docs/common/export.zh.md | 40 +++-- .../manual/basic/analysis/copy-export.zh.md | 81 +++++++--- 5 files changed, 268 insertions(+), 65 deletions(-) diff --git a/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts b/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts index aedca14297..eb39fc36d9 100644 --- a/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts @@ -1040,6 +1040,80 @@ describe('Tree Table Core Data Process', () => { 总计 26193 23516 49709 12321 16838" `); }); + + it('should copy normal data with header for custom field name', () => { + s2.setOptions({ + interaction: { + copyWithHeader: true, + }, + }); + s2.setDataCfg({ + meta: [ + { + field: 'number', + name: '数量', + }, + ], + }); + s2.render(); + + setSelectedVisibleCell(); + + expect(getSelectedData(s2)).toMatchInlineSnapshot(` + " 家具 家具 家具 办公用品 办公用品 + 桌子 沙发 小计 笔 纸张 + 数量 数量 数量 数量 + 浙江省 18375 14043 32418 4826 5854 + 浙江省 7789 5343 13132 945 1343 + 浙江省 2367 632 2999 1304 1354 + 浙江省 3877 7234 11111 1145 1523 + 浙江省 4342 834 5176 1432 1634 + 四川省 7818 9473 17291 7495 10984 + 四川省 1723 2451 4174 2335 4004 + 四川省 1822 2244 4066 245 3077 + 四川省 1943 2333 4276 2457 3551 + 四川省 2330 2445 4775 2458 352 + 总计 26193 23516 49709 12321 16838" + `); + }); + + it('should copy normal data with header for custom field formatter if enable copyWithFormat', () => { + s2.setOptions({ + interaction: { + copyWithHeader: true, + copyWithFormat: true, + }, + }); + s2.setDataCfg({ + meta: [ + { + field: 'number', + name: '数量', + formatter: (value) => `${value}-@`, + }, + ], + }); + s2.render(); + + setSelectedVisibleCell(); + + expect(getSelectedData(s2)).toMatchInlineSnapshot(` + " 家具 家具 家具 办公用品 办公用品 + 桌子 沙发 小计 笔 纸张 + 数量 数量 数量 数量 + 浙江省 18375-@ 14043-@ 32418 4826-@ 5854-@ + 浙江省 7789-@ 5343-@ 13132 945-@ 1343-@ + 浙江省 2367-@ 632-@ 2999 1304-@ 1354-@ + 浙江省 3877-@ 7234-@ 11111 1145-@ 1523-@ + 浙江省 4342-@ 834-@ 5176 1432-@ 1634-@ + 四川省 7818-@ 9473-@ 17291 7495-@ 10984-@ + 四川省 1723-@ 2451-@ 4174 2335-@ 4004-@ + 四川省 1822-@ 2244-@ 4066 245-@ 3077-@ + 四川省 1943-@ 2333-@ 4276 2457-@ 3551-@ + 四川省 2330-@ 2445-@ 4775 2458-@ 352-@ + 总计 26193-@ 23516-@ 49709 12321-@ 16838-@" + `); + }); }); describe('List Table getCopyData', () => { @@ -1114,6 +1188,7 @@ describe('Pivot Table getBrushHeaderCopyable', () => { }); const s2 = new PivotSheet(getContainer(), dataCfg, options); + beforeEach(() => { s2.render(); }); @@ -1141,6 +1216,37 @@ describe('Pivot Table getBrushHeaderCopyable', () => { `); }); + test('should copy all original row data in grid mode if contains text ellipses', () => { + s2.setOptions({ + style: { + rowCfg: { + // 展示省略号 + width: 10, + }, + }, + }); + const cells = s2.interaction.getAllRowHeaderCells(); + + s2.interaction.changeState({ + cells: map(cells, getCellMeta), + stateName: InteractionStateName.SELECTED, + onUpdateCells: (root) => { + root.updateCells(root.getAllRowHeaderCells()); + }, + }); + + expect(getSelectedData(s2)).toMatchInlineSnapshot(` + "浙江省 杭州市 + 浙江省 绍兴市 + 浙江省 宁波市 + 浙江省 舟山市 + 四川省 成都市 + 四川省 绵阳市 + 四川省 南充市 + 四川省 乐山市" + `); + }); + test('should copy all col data in grid mode', () => { const cells = s2.interaction.getAllColHeaderCells(); @@ -1160,6 +1266,35 @@ describe('Pivot Table getBrushHeaderCopyable', () => { `); }); + test('should copy all col data in grid mode for custom field meta', () => { + s2.setDataCfg({ + meta: [ + { + field: 'number', + name: '数量', + }, + ], + }); + + s2.render(); + + const cells = s2.interaction.getAllColHeaderCells(); + + s2.interaction.changeState({ + cells: map(cells, getCellMeta), + stateName: InteractionStateName.SELECTED, + onUpdateCells: (root) => { + root.updateCells(root.getAllColHeaderCells()); + }, + }); + + expect(getSelectedData(s2)).toMatchInlineSnapshot(` + "家具 家具 办公用品 办公用品 + 桌子 沙发 笔 纸张 + 数量 数量 数量 数量" + `); + }); + test('should copy selection row data in grid mode', () => { const ss = new PivotSheet( getContainer(), @@ -1216,7 +1351,7 @@ describe('Pivot Table getBrushHeaderCopyable', () => { }); test('should copy selection col data in grid mode', () => { - const ss = new PivotSheet( + const sheet = new PivotSheet( getContainer(), assembleDataCfg({ meta: [], @@ -1228,13 +1363,13 @@ describe('Pivot Table getBrushHeaderCopyable', () => { }), options, ); - ss.render(); + sheet.render(); - const cells = ss.interaction.getAllColHeaderCells().filter((c) => { + const cells = sheet.interaction.getAllColHeaderCells().filter((c) => { const meta = c.getMeta(); return (meta.level === 3 || meta.level === 4) && meta.x < 480; }); - ss.interaction.changeState({ + sheet.interaction.changeState({ cells: map(cells, getCellMeta), stateName: InteractionStateName.SELECTED, onUpdateCells: (root) => { @@ -1242,23 +1377,23 @@ describe('Pivot Table getBrushHeaderCopyable', () => { }, }); - expect(getSelectedData(ss)).toMatchInlineSnapshot(` + expect(getSelectedData(sheet)).toMatchInlineSnapshot(` "桌子 沙发 笔 纸张 桌子 number number number number number" `); - const cells2 = ss.interaction.getAllColHeaderCells().filter((c) => { + const cells2 = sheet.interaction.getAllColHeaderCells().filter((c) => { const meta = c.getMeta(); return (meta.level === 0 || meta.level === 1) && meta.x < 480; }); - ss.interaction.changeState({ + sheet.interaction.changeState({ cells: map(cells2, getCellMeta), stateName: InteractionStateName.SELECTED, onUpdateCells: (root) => { root.updateCells(root.getAllColHeaderCells()); }, }); - expect(getSelectedData(ss)).toMatchInlineSnapshot(` + expect(getSelectedData(sheet)).toMatchInlineSnapshot(` "浙江省 浙江省 杭州市 绍兴市" `); diff --git a/packages/s2-core/src/utils/export/copy.ts b/packages/s2-core/src/utils/export/copy.ts index b2a11af36a..fe5c3f5d59 100644 --- a/packages/s2-core/src/utils/export/copy.ts +++ b/packages/s2-core/src/utils/export/copy.ts @@ -4,7 +4,6 @@ import { filter, forEach, isEmpty, - isEqual, isNil, map, max, @@ -12,26 +11,26 @@ import { reduce, zip, } from 'lodash'; -import type { ColCell, RowCell } from '../../cell'; +import type { ColCell, HeaderCell, RowCell } from '../../cell'; import { - type CellMeta, CellTypes, CopyType, EMPTY_PLACEHOLDER, EXTRA_FIELD, ID_SEPARATOR, InteractionStateName, - type RowData, SERIES_NUMBER_FIELD, VALUE_FIELD, + type CellMeta, + type RowData, } from '../../common'; import type { DataType } from '../../data-set/interface'; import type { Node } from '../../facet/layout/node'; import type { SpreadSheet } from '../../sheet-type'; import { copyToClipboard } from '../../utils/export'; import { flattenDeep } from '../data-set-operate'; -import { getEmptyPlaceholder } from '../text'; import { getHeaderTotalStatus } from '../dataset/pivot-data-set'; +import { getEmptyPlaceholder } from '../text'; export function keyEqualTo(key: string, compareKey: string) { if (!key || !compareKey) { @@ -148,7 +147,7 @@ export const convertString = (v: string) => { * @param headerId * @param startLevel 层级 */ -const getHeaderList = (headerId: string, startLevel?: number) => { +const getHeaderMeasureFields = (headerId: string, startLevel?: number) => { const headerList = headerId.split(ID_SEPARATOR); if (startLevel) { return headerList.slice(headerList.length - startLevel); @@ -157,6 +156,15 @@ const getHeaderList = (headerId: string, startLevel?: number) => { return headerList; }; +const getHeaderMeasureFieldNames = ( + fields: string[], + spreadsheet: SpreadSheet, +): string[] => { + return map(fields, (field) => { + return spreadsheet.dataSet.getFieldName(field); + }); +}; + type MatrixTransformer = (data: string[][]) => CopyableItem; export enum CopyMIMEType { @@ -423,8 +431,12 @@ const getPivotWithHeaderCopyData = ( leafRowNodes: Node[], leafColNodes: Node[], ): Copyable => { - const rowMatrix = map(leafRowNodes, (n) => getHeaderList(n.id)); - const colMatrix = zip(...map(leafColNodes, (n) => getHeaderList(n.id))); + const rowMatrix = map(leafRowNodes, (node) => + getHeaderMeasureFields(node.id), + ); + const colMatrix = zip( + ...map(leafColNodes, (node) => getHeaderMeasureFields(node.id)), + ); const dataMatrix = getDataMatrix(leafRowNodes, leafColNodes, spreadsheet); return assembleMatrix(rowMatrix, colMatrix, dataMatrix); }; @@ -620,17 +632,23 @@ const getDataWithHeaderMatrix = ( const colMatrix = zip( ...map(cellMetaMatrix[0], (cellMeta) => { const colId = cellMeta.id.split(EMPTY_PLACEHOLDER)?.[1] ?? ''; - return getHeaderList(colId); + return getHeaderMeasureFieldNames( + getHeaderMeasureFields(colId), + spreadsheet, + ); }), ); const rowMatrix = map(cellMetaMatrix, (cellsMeta) => { const rowId = cellsMeta[0].id.split(EMPTY_PLACEHOLDER)?.[0] ?? ''; - return getHeaderList(rowId); + return getHeaderMeasureFieldNames( + getHeaderMeasureFields(rowId), + spreadsheet, + ); }); const dataMatrix = map(cellMetaMatrix, (cellsMeta) => { - return map(cellsMeta, (it) => format(it, displayData, spreadsheet)); + return map(cellsMeta, (meta) => format(meta, displayData, spreadsheet)); }); return assembleMatrix(rowMatrix, colMatrix, dataMatrix); @@ -682,25 +700,18 @@ function getTotalCellMatrixId(meta: Node, maxLevel: number) { } function getCellMatrix( - lastLevelCells: Array, + lastLevelCells: Array, maxLevel: number, allLevel: Set, ) { - return map(lastLevelCells, (cell: RowCell | ColCell) => { + return map(lastLevelCells, (cell) => { const meta = cell.getMeta(); - const { id, label, isTotals } = meta; - let cellId = id; - - if (isTotals) { - cellId = getTotalCellMatrixId(meta, maxLevel); - } + const { id, isTotals, spreadsheet } = meta; + const cellId = isTotals ? getTotalCellMatrixId(meta, maxLevel) : id; // 将指标维度单元格的标签替换为实际文本 - const actualText = cell.getActualText(); - const headerList = getHeaderList(cellId, allLevel.size); - return map(headerList, (header) => - isEqual(header, label) ? actualText : header, - ); + const headerFields = getHeaderMeasureFields(cellId, allLevel.size); + return getHeaderMeasureFieldNames(headerFields, spreadsheet); }); } diff --git a/packages/s2-react/playground/config.ts b/packages/s2-react/playground/config.ts index ccb83ab7ac..d4a2919710 100644 --- a/packages/s2-react/playground/config.ts +++ b/packages/s2-react/playground/config.ts @@ -83,6 +83,8 @@ export const s2Options: SheetComponentOptions = { showSeriesNumber: false, interaction: { enableCopy: true, + copyWithHeader: true, + copyWithFormat: true, // 防止 mac 触摸板横向滚动触发浏览器返回, 和移动端下拉刷新 overscrollBehavior: 'none', brushSelection: { diff --git a/s2-site/docs/common/export.zh.md b/s2-site/docs/common/export.zh.md index 75d70fabef..7b477d00c8 100644 --- a/s2-site/docs/common/export.zh.md +++ b/s2-site/docs/common/export.zh.md @@ -7,10 +7,10 @@ order: 8 组件层的复制,导出等功能,基于核心层 `@antv/s2` 透出的一系列工具方法封装,可以根据实际业务,基于工具方法自行封装 -```tsx -import { copyData, copyToClipboard, download } from '@antv/s2' +```ts +import { copyData, copyToClipboard, download, registerTransformer, CopyMIMEType } from '@antv/s2' -// 拿到复制数据 +// 拿到复制数据 (选中的单元格) const data = copyData(spreadsheet, '\t', false) // 复制数据到剪贴板 @@ -23,17 +23,15 @@ copyToClipboard(data) console.log('复制失败') }) -// 导出数据 +// 导出数据 (filename.csv) download(data, 'filename') - -// 自定义导出类型 +// 自定义复制导出转换 (复制到 word、语雀等场景会成为一个空表格) registerTransformer(CopyMIMEType.HTML, (matrix) => { return `` }) const data = copyData(spreadsheet, '\t', false) -// 复制到word、语雀等场景会成为一个空表格 ``` @@ -52,16 +50,30 @@ const data = copyData(spreadsheet, '\t', false) | data | 数据源 | `string` | | ✓ | | sync | 是否同步复制数据 (默认异步) | `boolean` | `false` | | -### registerTransformer - -| 参数 | 说明 | 类型 | 默认值 | 必选 | -| --- | --- | ------- | ----- | --- | -| type | 复制内容的MIMEType | `CopyMIMEType` | | ✓ | -| transformer | 处理函数 | `MatrixTransformer` | | ✓ | - ### download | 参数 | 说明 | 类型 | 默认值 | 必选 | | ------- | ------- | ------- | ----- | --- | | data | 数据源 | `string` | | ✓ | | filename | 文件名称 | `string` | | ✓ | + +### registerTransformer + +```ts +enum CopyMIMEType { + PLAIN = 'text/plain', + HTML = 'text/html', +} + +type MatrixTransformer = (data: string[][]) => CopyableItem; + +type CopyableItem = { + type: CopyMIMEType; + content: string; +}; +``` + +| 参数 | 说明 | 类型 | 默认值 | 必选 | +| --- | --- | ------- | ----- | --- | +| type | 复制内容的 MIMEType | `CopyMIMEType` | | ✓ | +| transformer | 处理函数 | `MatrixTransformer` | | ✓ | diff --git a/s2-site/docs/manual/basic/analysis/copy-export.zh.md b/s2-site/docs/manual/basic/analysis/copy-export.zh.md index b28bc5b0a8..2cdcb63104 100644 --- a/s2-site/docs/manual/basic/analysis/copy-export.zh.md +++ b/s2-site/docs/manual/basic/analysis/copy-export.zh.md @@ -5,11 +5,11 @@ order: 11 ## 简介 -复制与导出的内容都可以直接放入 Excel 中进行展示,S2 已经完成了格式上的兼容。 +复制与导出的内容都可以直接放入 `Excel` 中进行展示,S2 已经完成了格式上的兼容。 ### 复制 -此功能可快速将表格内容复制到剪切板 +此功能可快速将表格内容复制到剪切板中, `@antv/s2` 核心层提供了基础的复制功能,可配置 `enableCopy` 开启 #### 全量复制 @@ -26,41 +26,54 @@ S2 的导出组件,分别提供了原始数据的复制和格式化后数据 #### 局部复制 -使用快捷键 `command/ctrl + c` 即可复制选中区域(局部复制) +通过 [内置交互](/manual/advanced/interaction/basic) (单选/多选/圈选), 使用快捷键 `Command/Ctrl + C` 即可复制选中区域(局部复制) ```ts const s2Options = { interaction: { // 是否开启复制 enableCopy: true, + // 复制格式化后的数据 (s2DataConfig.meta 中配置的 formatter) + copyWithFormat: false, + // 复制数值时是否携带所对应的行列头维值 + copyWithHeader: true, // 圈选复制前,需要开启圈选功能 brushSelection: { - data: true, // 默认开启 - row: true, - col: true, + data: true, // 圈选数值单元格 (默认开启) + row: true, // 圈选行头单元格 + col: true, // 圈选列头单元格 } } }; ``` -- 复制到 Excel +##### 复制粘贴到 Excel + +excelCopy + +
+ +##### 复制粘贴到富文本编辑器中 (带 `HTML` 格式) + +HTMLCopy + +
-excelCopy +##### 复制行头单元格 -- 复制带 HTML 格式 +CopyCol -HTMLCopy +
-- 复制行头内容 +##### 复制列头单元格 -CopyCol +CopyRow -- 复制列头内容 +
-CopyRow +##### 带表头复制 -- 带表头复制 -**copyWithHeader**: 复制数据是否带表头信息,默认为 `false` +**copyWithHeader**: 复制数据时是否携带相对应**行列表头**信息,默认为 `false` ```ts const s2Options = { @@ -71,13 +84,43 @@ const s2Options = { }; ``` -copyWithHeader +copyWithHeader -### 导出 +
+ +##### 复制格式化后的数据 -`@antv/s2-react` 组件层提供了导出功能 +**copyWithFormat**: 当 `S2DataConfig` 的 `meta` 中配置了 [自定义格式函数时](/api/general/s2-data-config#meta), 是否按照 `formatter` 复制数据 ```ts +const s2DataConfig = { + fields: { ... } + meta: [ + { + field: 'city', + name: '城市', + formatter: (value) => `${value}-xx` + } + ] +} + +const s2Options = { + interaction: { + enableCopy: true, + copyWithFormat: true, + } +}; +``` + +### 导出 + +`@antv/s2-react` 组件层提供了开箱即用的导出功能 + +:::info{title='使用 `@antv/s2` 如何导出?'} +`@antv/s2` 内置了一系列工具函数,[见下方文档](#原始导出方法) +::: + +```tsx import { SheetComponent } from '@antv/s2-react'