Skip to content

Commit

Permalink
feat: 导出组件支持复制 HTML (text/html) 格式的数据 close #2828
Browse files Browse the repository at this point in the history
  • Loading branch information
lijinke666 committed Aug 16, 2024
1 parent 3dcf491 commit 11f9c54
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 108 deletions.
5 changes: 3 additions & 2 deletions packages/s2-core/src/common/interface/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,14 @@ export interface CopyAllDataParams {
formatOptions?: FormatOptions;

/**
* 导出时支持自定义 (transformer) 数据导出格式化方法
* 自定义数据转换器
* @see https://s2.antv.antgroup.com/manual/advanced/interaction/copy
* @example https://s2.antv.antgroup.com/examples/interaction/basic#copy-export
*/
customTransformer?: (transformer: Transformer) => Partial<Transformer>;

/**
* 是否开启异步复制/导出
* 是否开启异步复制
*/
async?: boolean;
}
Expand Down
34 changes: 33 additions & 1 deletion packages/s2-core/src/utils/export/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const download = (dataString: string, fileName: string) => {
};

/**
* 异步获取文本数据
* 异步获取文本数据 (text/plain)
* @example
const data = await asyncGetAllPlainData({
sheetInstance: s2,
Expand All @@ -129,3 +129,35 @@ export const asyncGetAllPlainData = async (params: CopyAllDataParams) => {

return result[0].content;
};

/**
* 异步获取富文本数据 (text/html)
* @example
const data = await asyncGetAllHtmlData({
sheetInstance: s2,
split: '\t',
formatOptions: true,
});
*/
export const asyncGetAllHtmlData = async (params: CopyAllDataParams) => {
const result = await asyncProcessAllSelected(params);

return result[1].content;
};

/**
* 异步获取数据
* - 文本 (text/plain)
* - 富文本 (text/html)
* @example
const data = await asyncGetAllData({
sheetInstance: s2,
split: '\t',
formatOptions: true,
});
*/
export const asyncGetAllData = async (params: CopyAllDataParams) => {
const result = await asyncProcessAllSelected(params);

return result;
};
31 changes: 25 additions & 6 deletions packages/s2-react/src/components/export/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import {
S2_PREFIX_CLS,
SpreadSheet,
TAB_SEPARATOR,
asyncGetAllData,
asyncGetAllPlainData,
copyToClipboard,
download,
i18n,
type CopyAllDataParams,
type Copyable,
} from '@antv/s2';
import { Button, Dropdown, message, type DropDownProps } from 'antd';
import cx from 'classnames';
Expand All @@ -28,7 +30,9 @@ export interface ExportBaseProps {
async?: boolean;
// ref: https://ant.design/components/dropdown-cn/#API
dropdown?: DropDownProps;
customCopyMethod?: (params: CopyAllDataParams) => Promise<string> | string;
customCopyMethod?: (
params: CopyAllDataParams,
) => Promise<string> | string | Promise<Copyable> | Copyable;
}

export interface ExportProps extends ExportBaseProps {
Expand Down Expand Up @@ -59,24 +63,39 @@ export const Export: React.FC<ExportProps> = React.memo((props) => {

const [messageApi, contextHolder] = message.useMessage();

const getPlainData = async (split: string, isFormat: boolean) => {
const getData = async (
split: string,
isFormat: boolean,
method: ExportBaseProps['customCopyMethod'],
) => {
const params: CopyAllDataParams = {
sheetInstance: sheet,
split,
formatOptions: isFormat,
async,
};

const data = await (customCopyMethod?.(params) ||
asyncGetAllPlainData(params));
const data = await (customCopyMethod?.(params) || method?.(params));

return data;
};

const getPlainData = async (split: string, isFormat: boolean) => {
const result = await getData(split, isFormat, asyncGetAllPlainData);

return result as string;
};

const getAllData = async (split: string, isFormat: boolean) => {
const result = await getData(split, isFormat, asyncGetAllData);

return result;
};

const copyData = async (isFormat: boolean) => {
const data = await getPlainData(TAB_SEPARATOR, isFormat);
const data = await getAllData(TAB_SEPARATOR, isFormat);

copyToClipboard(data, async)
copyToClipboard(data!, async)
.then(() => {
messageApi.success(successText);
})
Expand Down
2 changes: 1 addition & 1 deletion s2-site/docs/api/components/export.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ tag: Updated
| fileName | 自定义下载文件名 (csv) | `string` | `sheet` | |
| async | 异步复制/导出数据 (默认异步) | `boolean` | `true` | |
| dropdown | 下拉菜单配置,透传给 `antd``Dropdown` 组件 | [DropdownProps](https://ant.design/components/dropdown-cn/#API) | | |
| customCopyMethod | 自定义导出组件内部复制处理逻辑 | (params: [CopyAllDataParams](#copyalldataparams)) => `Promise<string> \| string` | | |
| customCopyMethod | 自定义导出组件内部复制处理逻辑 | (params: [CopyAllDataParams](#copyalldataparams)) => `Promise<string> \| string \| Promise<Copyable> \| Copyable` | | |

<embed src="@/docs/common/copy-export.zh.md"></embed>
159 changes: 105 additions & 54 deletions s2-site/docs/common/copy-export.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,28 @@ order: 8

### 1. 全量复制

:::warning{title="注意"}
S2 会在复制的时候往剪贴板写入两种类型的元数据

- `text/html`
- `text/plain`

粘贴的时候,取决于`接收方选择什么类型的数据`,对于富文本一般来说接收的是 `text/html`, 对于 Excel 之类的就是 `text/plain`, 即带制表符 `\t` 的纯文本,支持自定义修改。
:::

内置是三个 API, 详见 [下方文档](#api)

- `asyncGetAllData`
- `asyncGetAllPlainData`
- `asyncGetAllHtmlData`

#### 1.1 在 `@antv/s2` 中使用

```ts | pure
import { asyncGetAllPlainData, copyToClipboard } from '@antv/s2'
import { asyncGetAllData, copyToClipboard } from '@antv/s2'

// 拿到复制数据
const data = await asyncGetAllPlainData({
// 1. 拿到表格数据
const data = await asyncGetAllData({
sheetInstance: s2,
split: '\t',
formatOptions: true,
Expand All @@ -26,6 +43,7 @@ const data = await asyncGetAllPlainData({
// async: false
});

// 2. 写入到剪切板 (同时包含 `text/html` 和 `text/plain`)
// 同步复制:copyToClipboard(data, false)
copyToClipboard(data)
.then(() => {
Expand All @@ -38,11 +56,23 @@ copyToClipboard(data)

[查看示例](/examples/interaction/basic/#copy-export)

#### 1.1 原始数据全量复制
#### 1.2 在 `@antv/s2-react` 中使用

:::info{title="提示"}
组件层的复制,导出等功能,基于核心层 `@antv/s2` 透出的一系列工具方法封装,也可以根据实际业务,基于工具方法自行封装。
:::

开启表头,可以直接复制/下载数据,具体请查看 [分析组件-导出](/manual/advanced/analysis/export) 章节。

```ts | pure
<SheetComponent header={{ export: { open: true } }} />
```

##### 1.2.1 原始数据全量复制

<img alt="originFullCopy" src="https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*pfSsTrvuJ0UAAAAAAAAAAAAAARQnAQ" width="1000" />

#### 1.2 格式化数据全量复制
##### 1.2.2 格式化数据全量复制

如果配置了 [`S2DataConfig.meta`](/api/general/s2-data-config#meta) 对数据有 [格式化处理](/manual/basic/formatter), 那么可以开启 `withFormat`, 这样复制时会拿到格式化之后的数据。

Expand Down Expand Up @@ -125,22 +155,60 @@ const s2Options = {

<img alt="withHeader" src="https://gw.alipayobjects.com/zos/antfincdn/wSBjSYKSM/3eee7bc2-7f8e-4dd9-8836-52a978d9718a.png" width="1000"/>

## 导出
### 3. 自定义数据转换

### 原始导出方法
默认获取 `text/plain``text/html` 两种类型的全量表格数据,可以通过 `customTransformer` 自定义数据转换。

:::info{title="提示"}
组件层的复制,导出等功能,基于核心层 `@antv/s2` 透出的一系列工具方法封装,可以根据实际业务,基于工具方法自行封装
:::
```ts | pure
import { asyncGetAllData } from '@antv/s2'

const data = await asyncGetAllData({
sheetInstance: s2,
split: '\t',
formatOptions: true,
customTransformer: () => {
return {
'text/plain': (data) => {
return {
type: 'text/plain',
content: ``
};
},
'text/html': (data) => {
return {
type: 'text/html',
content: `<td></td>`
};
},
};
},
})
```

也可以写在 `s2Options` 中:

#### 1. 导出 CSV
```ts
const s2Options = {
interaction: {
copy: {
customTransformer: () => {}
}
}
}
```

[查看示例](/examples/interaction/basic/#copy-export)

## 导出

默认只提供 `csv` 纯文本格式的导出,如果想导出 `xlsx`, 保留单元格样式,可以结合 [exceljs](https://github.com/exceljs/exceljs), [sheetjs]( https://github.com/SheetJS/sheetjs) 等工具自行处理。

### 1. 导出 CSV

```ts | pure
import { asyncGetAllPlainData, download } from '@antv/s2'

// 拿到复制数据
// 拿到复制数据 (text/plain)
const data = await asyncGetAllPlainData({
sheetInstance: s2,
split: ',',
Expand All @@ -158,57 +226,40 @@ const data = await asyncGetAllPlainData({
download(data, 'filename') // filename.csv
```

#### 2. 复制数据到剪贴板

:::warning{title="注意"}
S2 会在复制的时候往剪贴板写入两种类型的元数据

- `text/html`
- `text/plain`
#### API

粘贴的时候,取决于`接收方选择什么类型的数据`,对于富文本一般来说接收的是 `text/html`, 对于 Excel 之类的就是 `text/plain`, 即带制表符 `\t` 的纯文本,支持自定义修改。
:::
##### asyncGetAllData

```ts | pure
import { copyToClipboard } from '@antv/s2'
获取 `text/plain``text/html` 两种类型的数据,用于复制

// 同步复制:copyToClipboard(data, false)
copyToClipboard(data)
.then(() => {
console.log('复制成功')
})
.catch(() => {
console.log('复制失败')
})
```ts
[
{
"type": "text/plain",
"content": "省份、t 城市、t 类别、r\n 浙江省、t 杭州市、t 家具、r\n 浙江省、t 绍兴市、t 家具"
},
{
"type": "text/html",
"content": "<meta charset=\"utf-8\"><table><tbody><tr><td>省份</td><td>城市</td><td>类别</td></tr><tr><td>浙江省</td><td>杭州市</td><td>家具</td></tr><tr><td>浙江省</td><td>绍兴市</td><td>家具</td></tr></tbody></table>"
}
]
```

#### 3. 自定义导出类型
##### asyncGetAllPlainData

```ts | pure
import { asyncGetAllPlainData, CopyMIMEType } from '@antv/s2'
获取 `text/plain` 类型的数据,用于导出

// 复制到 word、语雀等场景会成为一个空表格
const data = await asyncGetAllPlainData({
sheetInstance: s2,
split: '\t',
formatOptions: true,
// 自定义导出类型
customTransformer: () => {
return {
[CopyMIMEType.HTML]: () => {
return {
type: CopyMIMEType.HTML,
content: `<td></td>`
};
},
};
},
});
```ts
"省份、t 城市、t 类别、r\n 浙江省、t 杭州市、t 家具、r\n 浙江省、t 绍兴市、t 家具"
```

#### API
##### asyncGetAllHtmlData

##### asyncGetAllPlainData
获取 `text/html` 类型的数据,用于导出

```ts
"<meta charset=\"utf-8\"><table><tbody><tr><td>省份</td><td>城市</td><td>类别</td></tr><tr><td>浙江省</td><td>杭州市</td><td>家具</td></tr><tr><td>浙江省</td><td>绍兴市</td><td>家具</td></tr></tbody></table>"
```

| 参数 | 说明 | 类型 | 默认值 | 必选 |
| ------------|-----------------|---------------|---------------| --- |
Expand Down Expand Up @@ -292,5 +343,5 @@ interface Transformer {

| 参数 | 说明 | 类型 | 默认值 | 必选 |
| --- | --- |--------------------------|-----| --- |
| type | 复制内容的 MIMEType | [CopyMIMEType](#copymimetype) | ||
| type | 复制内容的 MIMEType | [`CopyMIMEType`](#copymimetype) | ||
| transformer | 处理函数 | `MatrixHTMLTransformer \| MatrixPlainTransformer` | ||
4 changes: 2 additions & 2 deletions s2-site/docs/manual/advanced/analysis/export.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ const App = () => {
:::info{title="提示"}
开启导出功能后,点击右上角的下拉菜单,可以复制和导出(原始/全量)数据。

1. `复制原始数据/复制格式化数据`: 可以直接粘贴到 `Excel`,`语雀` 等常用应用中。
2. `下载原始数据/下载格式化数据`: 生成 `csv` 文件,可以直接使用 `Excel` 打开。
1. `复制原始数据/复制格式化数据`: 可以直接粘贴到 `Excel (text/plain)`, `语雀 (text/html)` 等常用应用中。
2. `下载原始数据/下载格式化数据`: 生成 `csv` 文件,可以直接使用 `Excel (text/plain)` 打开。

:::

Expand Down
Loading

0 comments on commit 11f9c54

Please sign in to comment.