diff --git a/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-spec.ts.snap b/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-spec.ts.snap index 7721a2ad22..d594d88be4 100644 --- a/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-spec.ts.snap +++ b/packages/s2-core/__tests__/unit/utils/export/__snapshots__/export-spec.ts.snap @@ -31,6 +31,20 @@ exports[`PivotSheet Export Test Custom Tree Export Test should export correct da 自定义节点 a-2 - -" `; +exports[`PivotSheet Export Test should called with cell view meta when export formatted data 1`] = ` +" type 家具 家具 办公用品 办公用品 + sub_type 桌子 沙发 笔 纸张 +province city number number number number +浙江省 杭州市 - - - - +浙江省 绍兴市 - - - - +浙江省 宁波市 - - - - +浙江省 舟山市 - - - - +四川省 成都市 - - - - +四川省 绵阳市 - - - - +四川省 南充市 - - - - +四川省 乐山市 - - - -" +`; + exports[`PivotSheet Export Test should export correct data by {formatHeader: true} 1`] = ` " 类型 家具-type 家具-type 办公用品-type 办公用品-type 子类型 桌子-sub_type 沙发-sub_type 笔-sub_type 纸张-sub_type @@ -234,6 +248,87 @@ province city 数值 数值 数值 数值 四川省 乐山市 2330 2445 2458 352" `; +exports[`TableSheet Export Test should called with cell view meta when export formatted data 1`] = ` +"province city type sub_type number +浙江省 杭州市 家具 桌子 - +浙江省 绍兴市 家具 桌子 - +浙江省 宁波市 家具 桌子 - +浙江省 舟山市 家具 桌子 - +浙江省 杭州市 家具 沙发 - +浙江省 绍兴市 家具 沙发 - +浙江省 宁波市 家具 沙发 - +浙江省 舟山市 家具 沙发 - +浙江省 杭州市 办公用品 笔 - +浙江省 绍兴市 办公用品 笔 - +浙江省 宁波市 办公用品 笔 - +浙江省 舟山市 办公用品 笔 - +浙江省 杭州市 办公用品 纸张 - +浙江省 绍兴市 办公用品 纸张 - +浙江省 宁波市 办公用品 纸张 - +浙江省 舟山市 办公用品 纸张 - +四川省 成都市 家具 桌子 - +四川省 绵阳市 家具 桌子 - +四川省 南充市 家具 桌子 - +四川省 乐山市 家具 桌子 - +四川省 成都市 家具 沙发 - +四川省 绵阳市 家具 沙发 - +四川省 南充市 家具 沙发 - +四川省 乐山市 家具 沙发 - +四川省 成都市 办公用品 笔 - +四川省 绵阳市 办公用品 笔 - +四川省 南充市 办公用品 笔 - +四川省 乐山市 办公用品 笔 - +四川省 成都市 办公用品 纸张 - +四川省 绵阳市 办公用品 纸张 - +四川省 南充市 办公用品 纸张 - +四川省 乐山市 办公用品 纸张 - +- - 家具 桌子 - +- - 家具 - - +- - 家具 沙发 - +- - 办公用品 - - +- - 办公用品 笔 - +- - 办公用品 纸张 - +浙江省 - 家具 桌子 - +浙江省 - 家具 沙发 - +浙江省 - 办公用品 笔 - +浙江省 - 办公用品 纸张 - +四川省 - 家具 桌子 - +四川省 - 家具 沙发 - +四川省 - 办公用品 笔 - +四川省 - 办公用品 纸张 - +浙江省 杭州市 家具 - - +浙江省 杭州市 办公用品 - - +浙江省 杭州市 - - - +浙江省 绍兴市 家具 - - +浙江省 绍兴市 办公用品 - - +浙江省 绍兴市 - - - +浙江省 宁波市 家具 - - +浙江省 宁波市 办公用品 - - +浙江省 宁波市 - - - +浙江省 舟山市 家具 - - +浙江省 舟山市 办公用品 - - +浙江省 舟山市 - - - +四川省 成都市 家具 - - +四川省 成都市 办公用品 - - +四川省 成都市 - - - +四川省 绵阳市 家具 - - +四川省 绵阳市 办公用品 - - +四川省 绵阳市 - - - +四川省 南充市 家具 - - +四川省 南充市 办公用品 - - +四川省 南充市 - - - +四川省 乐山市 家具 - - +四川省 乐山市 办公用品 - - +四川省 乐山市 - - - +浙江省 - 家具 - - +浙江省 - 办公用品 - - +浙江省 - - - - +四川省 - 家具 - - +四川省 - 办公用品 - - +四川省 - - - - +- - - - -" +`; + exports[`TableSheet Export Test should export correct data with no series number 1`] = ` "province city type sub_type number 浙江省 杭州市 家具 桌子 7789 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 ede508cc8c..ecb9a5985e 100644 --- a/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts @@ -556,6 +556,33 @@ describe('List Table Core Data Process', () => { expect(getCopyPlainContent(s2)).toMatchInlineSnapshot(`"custom data"`); }); + + it('should called with cell view meta when brush select formatted data', async () => { + const formatter = jest.fn(); + + s2.setOptions({ + interaction: { + copy: { + withFormat: true, + }, + }, + }); + s2.setDataCfg({ + meta: [{ field: 'number', formatter }], + }); + + await s2.render(); + + s2.interaction.changeState({ + cells: s2.facet.getDataCells().map((cell) => getCellMeta(cell)), + stateName: InteractionStateName.SELECTED, + }); + + const viewMeta = s2.facet.getCellMeta(18, 5); + + getCopyPlainContent(s2); + expect(formatter).toHaveBeenLastCalledWith(1943, viewMeta.data, viewMeta); + }); }); describe('Pivot Table Core Data Process', () => { @@ -1226,6 +1253,28 @@ describe('Pivot Table Core Data Process', () => { expect(getCopyPlainContent(s2)).toMatchInlineSnapshot(`"custom data"`); }); + + // https://github.com/antvis/S2/issues/2866 + it('should called with cell view meta when brush select formatted data', async () => { + const formatter = jest.fn(); + + s2.setOptions({ + interaction: { + copy: { enable: true, withHeader: true, withFormat: true }, + }, + }); + + const meta: Meta[] = [{ field: 'number', formatter }]; + + s2.setDataCfg(getDataCfg(meta)); + + await s2.render(); + + const viewMeta = s2.facet.getCellMeta(10, 4); + + getCopyPlainContent(s2); + expect(formatter).toHaveBeenLastCalledWith(16838, viewMeta.data, viewMeta); + }); }); describe('Tree Table Core Data Process', () => { diff --git a/packages/s2-core/__tests__/unit/utils/export/export-spec.ts b/packages/s2-core/__tests__/unit/utils/export/export-spec.ts index 7eae467905..b07c53ac88 100644 --- a/packages/s2-core/__tests__/unit/utils/export/export-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/export-spec.ts @@ -54,6 +54,27 @@ describe('TableSheet Export Test', () => { await expectMatchSnapshot(s2); }); + + it('should called with cell view meta when export formatted data', async () => { + const formatter = jest.fn(); + + const s2 = new TableSheet( + getContainer(), + assembleDataCfg({ + meta: [{ field: 'number', formatter }], + fields: { + columns: ['province', 'city', 'type', 'sub_type', 'number'], + }, + }), + assembleOptions(), + ); + + await expectMatchSnapshot(s2); + + const viewMeta = s2.facet.getCellMeta(76, 4); + + expect(formatter).toHaveBeenLastCalledWith(78868, viewMeta.data, viewMeta); + }); }); describe('PivotSheet Export Test', () => { @@ -390,6 +411,30 @@ describe('PivotSheet Export Test', () => { }); }); + // https://github.com/antvis/S2/issues/2866 + it('should called with cell view meta when export formatted data', async () => { + const formatter = jest.fn(); + + const s2 = new PivotSheet( + getContainer(), + assembleDataCfg({ + meta: [ + { + field: 'number', + formatter, + }, + ], + }), + s2Options, + ); + + await expectMatchSnapshot(s2); + + const viewMeta = s2.facet.getCellMeta(7, 3); + + expect(formatter).toHaveBeenLastCalledWith(352, viewMeta.data, viewMeta); + }); + describe('Custom Tree Export Test', () => { let s2: SpreadSheet; diff --git a/packages/s2-core/src/common/interface/basic.ts b/packages/s2-core/src/common/interface/basic.ts index 01a224c8e4..2a84236549 100644 --- a/packages/s2-core/src/common/interface/basic.ts +++ b/packages/s2-core/src/common/interface/basic.ts @@ -29,7 +29,7 @@ export type { GetCellMeta, LayoutResult } from './facet'; export type Formatter = ( value: DataItem, data?: DataItem | ViewMetaData | ViewMetaData[], - meta?: Node | ViewMeta, + meta?: Node | ViewMeta | null | undefined, ) => SimpleData; export interface FormatResult { diff --git a/packages/s2-core/src/facet/pivot-facet.ts b/packages/s2-core/src/facet/pivot-facet.ts index 8049f9d19e..27659d35e1 100644 --- a/packages/s2-core/src/facet/pivot-facet.ts +++ b/packages/s2-core/src/facet/pivot-facet.ts @@ -104,7 +104,7 @@ export class PivotFacet extends FrozenFacet { /** * 根据行列索引获取单元格元数据 */ - public getCellMeta(rowIndex = 0, colIndex = 0) { + public getCellMeta(rowIndex: number, colIndex: number) { const { options, dataSet } = this.spreadsheet; const { rowLeafNodes, colLeafNodes } = this.getLayoutResult(); const row = rowLeafNodes[rowIndex]; diff --git a/packages/s2-core/src/facet/table-facet.ts b/packages/s2-core/src/facet/table-facet.ts index f1d66fb883..44c55c76a4 100644 --- a/packages/s2-core/src/facet/table-facet.ts +++ b/packages/s2-core/src/facet/table-facet.ts @@ -2,6 +2,7 @@ import { Group, Rect } from '@antv/g'; import { isBoolean, isEmpty, + isNil, isNumber, keys, last, @@ -358,12 +359,12 @@ export class TableFacet extends FrozenFacet { }; } - public getCellMeta(rowIndex = 0, colIndex = 0) { + public getCellMeta(rowIndex: number, colIndex: number) { const { options, dataSet } = this.spreadsheet; const colLeafNodes = this.getColLeafNodes(); const colNode = colLeafNodes[colIndex]; - if (!colNode) { + if (isNil(rowIndex) || !colNode) { return null; } diff --git a/packages/s2-core/src/utils/export/copy/base-data-cell-copy.ts b/packages/s2-core/src/utils/export/copy/base-data-cell-copy.ts index 0350abe815..8201f5fd91 100644 --- a/packages/s2-core/src/utils/export/copy/base-data-cell-copy.ts +++ b/packages/s2-core/src/utils/export/copy/base-data-cell-copy.ts @@ -2,6 +2,7 @@ import { AsyncRenderThreshold, TAB_SEPARATOR, type DataItem, + type Formatter, type SimpleData, } from '../../../common'; import type { @@ -70,4 +71,24 @@ export abstract class BaseDataCellCopy { this.matrixHtmlTransformer(dataMatrix), ]; } + + protected getFormatter(options: { + field: string; + rowIndex: number; + colIndex: number; + }): Formatter { + const { field, rowIndex, colIndex } = options; + + if (this.config.formatData) { + const viewMeta = this.spreadsheet.facet.getCellMeta(rowIndex, colIndex); + + return (value) => { + const formatter = this.spreadsheet.dataSet.getFieldFormatter(field!); + + return formatter?.(value, viewMeta?.data, viewMeta); + }; + } + + return ((value) => value) as Formatter; + } } diff --git a/packages/s2-core/src/utils/export/copy/common.ts b/packages/s2-core/src/utils/export/copy/common.ts index 45978101e6..d47f3ef743 100644 --- a/packages/s2-core/src/utils/export/copy/common.ts +++ b/packages/s2-core/src/utils/export/copy/common.ts @@ -1,5 +1,5 @@ import { escape, map, max } from 'lodash'; -import type { DataItem, Formatter, SimpleData } from '../../../common'; +import type { DataItem, SimpleData } from '../../../common'; import { LINE_SEPARATOR, ROOT_NODE_ID, TAB_SEPARATOR } from '../../../common'; import { CopyMIMEType, @@ -13,7 +13,6 @@ import { type Transformer, } from '../../../common/interface/export'; import type { Node } from '../../../facet/layout/node'; -import type { BaseDataSet } from './../../../data-set/base-data-set'; // 把 string[][] 矩阵转换成 CopyablePlain export const matrixPlainTextTransformer = ( @@ -60,18 +59,6 @@ export const Transformers: { [CopyMIMEType.HTML]: matrixHtmlTransformer, }; -export function getFormatter( - field: string, - formatData = false, - dataSet: BaseDataSet, -): Formatter { - if (formatData) { - return dataSet.getFieldFormatter(field!); - } - - return ((value) => value) as Formatter; -} - // 生成矩阵:https://gw.alipayobjects.com/zos/antfincdn/bxBVt0nXx/a182c1d4-81bf-469f-b868-8b2e29acfc5f.png export const assembleMatrix = ({ rowMatrix, diff --git a/packages/s2-core/src/utils/export/copy/pivot-data-cell-copy.ts b/packages/s2-core/src/utils/export/copy/pivot-data-cell-copy.ts index ef1e0b6387..9bcb9d383d 100644 --- a/packages/s2-core/src/utils/export/copy/pivot-data-cell-copy.ts +++ b/packages/s2-core/src/utils/export/copy/pivot-data-cell-copy.ts @@ -33,12 +33,10 @@ import { getSelectedCols, getSelectedRows, } from '../method'; -import type { BaseDataSet } from './../../../data-set/base-data-set'; import { BaseDataCellCopy } from './base-data-cell-copy'; import { assembleMatrix, completeMatrix, - getFormatter, getMaxRowLen, getNodeFormatData, } from './common'; @@ -127,14 +125,11 @@ export class PivotDataCellCopy extends BaseDataCellCopy { return map(this.leafRowNodes, (rowNode) => this.leafColNodes.map((colNode) => { - // const return this.getDataCellValue({ rowNode, colNode, config: { measureQuery, - isPivotMode: this.spreadsheet.isPivotMode, - dataSet: this.spreadsheet.dataSet, }, }); }), @@ -177,8 +172,6 @@ export class PivotDataCellCopy extends BaseDataCellCopy { rowNode, colNode, config: { - isPivotMode: this.spreadsheet.isPivotMode, - dataSet: this.spreadsheet.dataSet, measureQuery, }, }); @@ -216,13 +209,11 @@ export class PivotDataCellCopy extends BaseDataCellCopy { rowNode: Node; colNode: Node; config: { - isPivotMode: () => boolean; - dataSet: BaseDataSet; measureQuery: MeasureQuery; }; }): DataItem => { - const { isPivotMode, dataSet, measureQuery } = config; - const cellData = dataSet.getCellData({ + const { measureQuery } = config; + const cellData = this.spreadsheet.dataSet.getCellData({ query: { ...rowNode.query, ...colNode.query, @@ -239,18 +230,18 @@ export class PivotDataCellCopy extends BaseDataCellCopy { const formatNode = this.spreadsheet.isValueInCols() ? colNode : rowNode; let field: string | undefined = - getColNodeFieldFromNode(isPivotMode, formatNode) ?? ''; + getColNodeFieldFromNode(this.spreadsheet.isPivotMode, formatNode) ?? ''; // 主要解决只有一个度量时,总计小计对应的值无法格式化的问题 - const values = this.spreadsheet.dataCfg.fields.values; + const { values } = this.spreadsheet.dataCfg.fields; field = values?.includes(field) ? field : values?.[0]; - const formatter = getFormatter( - field ?? colNode.field, - this.config.formatData, - dataSet, - ); + const formatter = this.getFormatter({ + field: field ?? colNode.field, + rowIndex: rowNode.rowIndex, + colIndex: colNode.colIndex, + }); const fieldValue = (cellData as CellData)?.[VALUE_FIELD]; const isChartData = isPlainObject( @@ -362,8 +353,6 @@ export class PivotDataCellCopy extends BaseDataCellCopy { rowNode: rowNode!, colNode: colNode!, config: { - isPivotMode: this.spreadsheet.isPivotMode, - dataSet: this.spreadsheet.dataSet, measureQuery, }, }); diff --git a/packages/s2-core/src/utils/export/copy/table-copy.ts b/packages/s2-core/src/utils/export/copy/table-copy.ts index d83c929899..22d281044e 100644 --- a/packages/s2-core/src/utils/export/copy/table-copy.ts +++ b/packages/s2-core/src/utils/export/copy/table-copy.ts @@ -18,7 +18,7 @@ import { getSelectedRows, } from '../method'; import { BaseDataCellCopy } from './base-data-cell-copy'; -import { assembleMatrix, getFormatter } from './common'; +import { assembleMatrix } from './common'; import { getHeaderNodeFromMeta } from './core'; class TableDataCellCopy extends BaseDataCellCopy { @@ -64,18 +64,18 @@ class TableDataCellCopy extends BaseDataCellCopy { const { seriesNumber } = this.spreadsheet.options; return this.displayData.map((row, i) => - this.columnNodes.map((node) => { + this.columnNodes.map((node, j) => { const field = node?.field; if (SERIES_NUMBER_FIELD === field && seriesNumber?.enable) { return (i + 1).toString(); } - const formatter = getFormatter( + const formatter = this.getFormatter({ field, - this.config.formatData, - this.spreadsheet.dataSet, - ); + rowIndex: i, + colIndex: j, + }); const value = row?.[field]; return formatter(value); @@ -121,11 +121,11 @@ class TableDataCellCopy extends BaseDataCellCopy { continue; } - const formatter = getFormatter( + const formatter = this.getFormatter({ field, - this.config.formatData, - this.spreadsheet.dataSet, - ); + rowIndex, + colIndex: i, + }); const value = rowData[field]; const dataItem = formatter(value); @@ -178,11 +178,11 @@ class TableDataCellCopy extends BaseDataCellCopy { ? meta.rowIndex + 1 : this.displayData[meta.rowIndex]?.[field]; - const formatter = getFormatter( - field!, - this.config.formatData, - this.spreadsheet.dataSet, - ); + const formatter = this.getFormatter({ + field, + rowIndex: meta.rowIndex, + colIndex: meta.colIndex, + }); return formatter(value); }; @@ -192,7 +192,7 @@ class TableDataCellCopy extends BaseDataCellCopy { // 因为通过复制数据单元格的方式和通过行列头复制的方式不同,所以不能复用 getDataMatrix 方法 const dataMatrix = map(cellMetaMatrix, (cellsMeta) => - map(cellsMeta, (it) => convertString(this.getValueFromMeta(it))), + map(cellsMeta, (meta) => convertString(this.getValueFromMeta(meta))), ) as string[][]; if (!copy?.withHeader) { diff --git a/s2-site/docs/api/basic-class/base-facet.zh.md b/s2-site/docs/api/basic-class/base-facet.zh.md index b3c6f32e70..3286b10fb8 100644 --- a/s2-site/docs/api/basic-class/base-facet.zh.md +++ b/s2-site/docs/api/basic-class/base-facet.zh.md @@ -116,6 +116,7 @@ s2.facet.getRealWidth() | getCells | 获取所有单元格 (角头,行头,列头,数值) (不含可视区域) | (ids: string[]) => [S2CellType](/docs/api/basic-class/base-cell)[] | | | getInitColLeafNodes | 获取初始化时的记录的列头叶子节点 | () => [Node](/docs/api/basic-class/node)[] | | | clearInitColLeafNodes | 清楚初始化时的记录的列头叶子节点 | () => void | | +| getCellMeta | 根据行列索引获取数值单元格信息 | (rowIndex: `number`, colIndex: `number`) => [ViewMeta](#viewmeta) | | ### CellScrollOffset