@bsg-export/vue 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -0
- package/dist/ExportButton.vue.d.ts +2 -2
- package/dist/ExportButton.vue.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/use-exporter.d.ts +7 -66
- package/dist/use-exporter.d.ts.map +1 -1
- package/dist/use-exporter.js +23 -12
- package/dist/use-worker-exporter.d.ts +49 -0
- package/dist/use-worker-exporter.d.ts.map +1 -0
- package/dist/use-worker-exporter.js +172 -0
- package/package.json +6 -6
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# @bsg-export/vue
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@bsg-export/vue)
|
|
4
|
+
|
|
5
|
+
> [belobog-stellar-grid](https://github.com/kurisu994/belobog-stellar-grid) 的 Vue 3 官方封装
|
|
6
|
+
|
|
7
|
+
## 简介
|
|
8
|
+
|
|
9
|
+
提供 `useExporter` Composable、`useWorkerExporter` Composable 和 `ExportButton` 组件,自动管理 WASM 初始化、导出状态和进度追踪。`useWorkerExporter` 支持将导出计算移至 Worker 线程。
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @bsg-export/vue belobog-stellar-grid
|
|
15
|
+
# 或
|
|
16
|
+
pnpm add @bsg-export/vue belobog-stellar-grid
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**前置依赖**:`vue >= 3.3.0`、`belobog-stellar-grid >= 1.0.0`
|
|
20
|
+
|
|
21
|
+
## 快速开始
|
|
22
|
+
|
|
23
|
+
### useExporter Composable
|
|
24
|
+
|
|
25
|
+
```vue
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { useExporter, ExportFormat } from '@bsg-export/vue';
|
|
28
|
+
|
|
29
|
+
const { initialized, loading, progress, exportTable } = useExporter();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<button
|
|
34
|
+
:disabled="!initialized || loading"
|
|
35
|
+
@click="exportTable({ tableId: 'my-table', filename: '报表.xlsx', format: ExportFormat.Xlsx })"
|
|
36
|
+
>
|
|
37
|
+
{{ loading ? `导出中 ${Math.round(progress)}%` : '导出 Excel' }}
|
|
38
|
+
</button>
|
|
39
|
+
</template>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### ExportButton 组件
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { ExportButton, ExportFormat } from '@bsg-export/vue';
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<ExportButton
|
|
51
|
+
table-id="my-table"
|
|
52
|
+
filename="报表.xlsx"
|
|
53
|
+
:format="ExportFormat.Xlsx"
|
|
54
|
+
@success="console.log('导出成功')"
|
|
55
|
+
@error="(err) => console.error('导出失败', err)"
|
|
56
|
+
>
|
|
57
|
+
导出 Excel
|
|
58
|
+
</ExportButton>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### `useExporter()` 返回值
|
|
65
|
+
|
|
66
|
+
所有返回值均为 Vue 3 响应式 `Ref`。
|
|
67
|
+
|
|
68
|
+
| 属性/方法 | 类型 | 说明 |
|
|
69
|
+
|-----------|------|------|
|
|
70
|
+
| `initialized` | `Ref<boolean>` | WASM 是否初始化完成 |
|
|
71
|
+
| `loading` | `Ref<boolean>` | 是否正在导出 |
|
|
72
|
+
| `progress` | `Ref<number>` | 导出进度 (0-100) |
|
|
73
|
+
| `error` | `Ref<Error \| null>` | 错误信息 |
|
|
74
|
+
| `exportTable` | `(options) => void` | DOM 表格导出 |
|
|
75
|
+
| `exportData` | `(data, options?) => void` | 纯数据导出 |
|
|
76
|
+
| `exportTablesXlsx` | `(options) => void` | 多 Sheet 导出 |
|
|
77
|
+
| `exportCsvBatch` | `(options) => Promise` | CSV 分批导出 |
|
|
78
|
+
| `exportXlsxBatch` | `(options) => Promise` | XLSX 分批导出 |
|
|
79
|
+
| `exportTablesBatch` | `(options) => Promise` | 多 Sheet 分批导出 |
|
|
80
|
+
|
|
81
|
+
### `useWorkerExporter(createWorker)` 返回值
|
|
82
|
+
|
|
83
|
+
将导出计算移至 Worker 线程,主线程不阻塞。需要传入 Worker 工厂函数。
|
|
84
|
+
|
|
85
|
+
```vue
|
|
86
|
+
<script setup lang="ts">
|
|
87
|
+
import { useWorkerExporter } from '@bsg-export/vue';
|
|
88
|
+
import ExportWorkerScript from '@bsg-export/worker/worker?worker';
|
|
89
|
+
|
|
90
|
+
const { initialized, loading, progress, exportData } = useWorkerExporter(
|
|
91
|
+
() => new ExportWorkerScript()
|
|
92
|
+
);
|
|
93
|
+
</script>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
| 属性/方法 | 类型 | 说明 |
|
|
97
|
+
|-----------|------|------|
|
|
98
|
+
| `initialized` | `Ref<boolean>` | Worker 中 WASM 是否初始化完成 |
|
|
99
|
+
| `loading` | `Ref<boolean>` | 是否正在导出 |
|
|
100
|
+
| `progress` | `Ref<number>` | 导出进度 (0-100) |
|
|
101
|
+
| `error` | `Ref<Error \| null>` | 错误信息 |
|
|
102
|
+
| `exportData` | `(data, opts?) => Promise<boolean>` | Worker 生成并下载 |
|
|
103
|
+
| `generateBytes` | `(data, opts?) => Promise<Uint8Array>` | 仅生成字节 |
|
|
104
|
+
| `terminate` | `() => void` | 销毁 Worker |
|
|
105
|
+
|
|
106
|
+
### `<ExportButton>` Props
|
|
107
|
+
|
|
108
|
+
| Prop | 类型 | 默认值 | 说明 |
|
|
109
|
+
|------|------|--------|------|
|
|
110
|
+
| `table-id` | `string` | — | 要导出的表格 ID(必填) |
|
|
111
|
+
| `filename` | `string` | — | 导出文件名 |
|
|
112
|
+
| `format` | `ExportFormat` | `Csv` | 导出格式 |
|
|
113
|
+
| `exclude-hidden` | `boolean` | `false` | 排除隐藏行/列 |
|
|
114
|
+
| `with-bom` | `boolean` | `false` | 添加 UTF-8 BOM |
|
|
115
|
+
| `disabled` | `boolean` | `false` | 是否禁用按钮 |
|
|
116
|
+
| `initializing-text` | `string` | `'初始化中...'` | 初始化中按钮文本 |
|
|
117
|
+
| `loading-text` | `string` | `'导出中 {progress}%'` | 导出中按钮文本 |
|
|
118
|
+
|
|
119
|
+
### 事件
|
|
120
|
+
|
|
121
|
+
| 事件 | 参数 | 说明 |
|
|
122
|
+
|------|------|------|
|
|
123
|
+
| `success` | — | 导出成功 |
|
|
124
|
+
| `error` | `Error` | 导出失败 |
|
|
125
|
+
| `progress` | `number` | 进度变化 |
|
|
126
|
+
|
|
127
|
+
### 插槽
|
|
128
|
+
|
|
129
|
+
| 插槽 | 说明 |
|
|
130
|
+
|------|------|
|
|
131
|
+
| `default` | 按钮内容(默认显示"导出") |
|
|
132
|
+
|
|
133
|
+
## 许可证
|
|
134
|
+
|
|
135
|
+
MIT OR Apache-2.0
|
|
@@ -33,13 +33,13 @@ type __VLS_Slots = {} & {
|
|
|
33
33
|
default?: (props: typeof __VLS_1) => any;
|
|
34
34
|
};
|
|
35
35
|
declare const __VLS_component: import("vue").DefineComponent<ExportButtonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
36
|
-
success: () => any;
|
|
37
36
|
error: (error: Error) => any;
|
|
38
37
|
progress: (progress: number) => any;
|
|
38
|
+
success: () => any;
|
|
39
39
|
}, string, import("vue").PublicProps, Readonly<ExportButtonProps> & Readonly<{
|
|
40
|
-
onSuccess?: (() => any) | undefined;
|
|
41
40
|
onError?: ((error: Error) => any) | undefined;
|
|
42
41
|
onProgress?: ((progress: number) => any) | undefined;
|
|
42
|
+
onSuccess?: (() => any) | undefined;
|
|
43
43
|
}>, {
|
|
44
44
|
disabled: boolean;
|
|
45
45
|
initializingText: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportButton.vue.d.ts","sourceRoot":"","sources":["../src/ExportButton.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ExportButton.vue.d.ts","sourceRoot":"","sources":["../src/ExportButton.vue"],"names":[],"mappings":"AAiGA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,eAAe;AACf,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW;IACX,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,gBAAgB;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAiFD,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAwB/C,QAAA,MAAM,eAAe;;;;;;;;;cAhHR,OAAO;sBAEC,MAAM;iBAEX,MAAM;6EAoHpB,CAAC;wBACkB,eAAe,CAAC,OAAO,eAAe,EAAE,WAAW,CAAC;AAAzE,wBAA0E;AAa1E,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
8
|
export { useExporter } from './use-exporter';
|
|
9
|
-
export
|
|
9
|
+
export { useWorkerExporter } from './use-worker-exporter';
|
|
10
10
|
export { default as ExportButton } from './ExportButton.vue';
|
|
11
|
-
export type { Column, MergeCellValue, CellValue, MergeableCellValue, DataRow, ExportDataOptions, SheetConfig, BatchSheetConfig, ProgressCallback, } from '@bsg-export/types';
|
|
11
|
+
export type { Column, MergeCellValue, CellValue, MergeableCellValue, DataRow, ExportDataOptions, SheetConfig, BatchSheetConfig, ProgressCallback, ExportTableOptions, ExportTablesXlsxOptions, ExportCsvBatchOptions, ExportXlsxBatchOptions, ExportTablesBatchOptions, } from '@bsg-export/types';
|
|
12
12
|
export { ExportFormat } from '@bsg-export/types';
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG1D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7D,YAAY,EACV,MAAM,EACN,cAAc,EACd,SAAS,EACT,kBAAkB,EAClB,OAAO,EACP,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useExporter } from "./use-exporter";
|
|
2
|
+
import { useWorkerExporter } from "./use-worker-exporter";
|
|
2
3
|
import { default as default2 } from "./ExportButton.vue";
|
|
3
4
|
import { ExportFormat } from "@bsg-export/types";
|
|
4
5
|
export {
|
|
5
6
|
default2 as ExportButton,
|
|
6
7
|
ExportFormat,
|
|
7
|
-
useExporter
|
|
8
|
+
useExporter,
|
|
9
|
+
useWorkerExporter
|
|
8
10
|
};
|
package/dist/use-exporter.d.ts
CHANGED
|
@@ -19,66 +19,7 @@
|
|
|
19
19
|
* </template>
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
import type {
|
|
23
|
-
/** export_table 的参数配置 */
|
|
24
|
-
export interface ExportTableOptions {
|
|
25
|
-
/** 要导出的 HTML 表格元素的 ID */
|
|
26
|
-
tableId: string;
|
|
27
|
-
/** 导出文件名 */
|
|
28
|
-
filename?: string;
|
|
29
|
-
/** 导出格式 */
|
|
30
|
-
format?: ExportFormat;
|
|
31
|
-
/** 是否排除隐藏行/列 */
|
|
32
|
-
excludeHidden?: boolean;
|
|
33
|
-
/** 是否添加 UTF-8 BOM(仅 CSV 有效) */
|
|
34
|
-
withBom?: boolean;
|
|
35
|
-
/** 回调失败是否中断导出 */
|
|
36
|
-
strictProgressCallback?: boolean;
|
|
37
|
-
}
|
|
38
|
-
/** 多工作表导出的参数配置 */
|
|
39
|
-
export interface ExportTablesXlsxOptions {
|
|
40
|
-
/** Sheet 配置数组 */
|
|
41
|
-
sheets: SheetConfig[];
|
|
42
|
-
/** 导出文件名 */
|
|
43
|
-
filename?: string;
|
|
44
|
-
}
|
|
45
|
-
/** 分批导出 CSV 的参数配置 */
|
|
46
|
-
export interface ExportCsvBatchOptions {
|
|
47
|
-
/** 要导出的 HTML 表格元素的 ID */
|
|
48
|
-
tableId: string;
|
|
49
|
-
/** 可选的独立 tbody ID */
|
|
50
|
-
tbodyId?: string;
|
|
51
|
-
/** 导出文件名 */
|
|
52
|
-
filename?: string;
|
|
53
|
-
/** 每批处理行数 */
|
|
54
|
-
batchSize?: number;
|
|
55
|
-
/** 是否排除隐藏行/列 */
|
|
56
|
-
excludeHidden?: boolean;
|
|
57
|
-
/** 是否添加 UTF-8 BOM */
|
|
58
|
-
withBom?: boolean;
|
|
59
|
-
}
|
|
60
|
-
/** 分批导出 XLSX 的参数配置 */
|
|
61
|
-
export interface ExportXlsxBatchOptions {
|
|
62
|
-
/** 要导出的 HTML 表格元素的 ID */
|
|
63
|
-
tableId: string;
|
|
64
|
-
/** 可选的独立 tbody ID */
|
|
65
|
-
tbodyId?: string;
|
|
66
|
-
/** 导出文件名 */
|
|
67
|
-
filename?: string;
|
|
68
|
-
/** 每批处理行数 */
|
|
69
|
-
batchSize?: number;
|
|
70
|
-
/** 是否排除隐藏行/列 */
|
|
71
|
-
excludeHidden?: boolean;
|
|
72
|
-
}
|
|
73
|
-
/** 多工作表分批导出的参数配置 */
|
|
74
|
-
export interface ExportTablesBatchOptions {
|
|
75
|
-
/** Sheet 配置数组 */
|
|
76
|
-
sheets: BatchSheetConfig[];
|
|
77
|
-
/** 导出文件名 */
|
|
78
|
-
filename?: string;
|
|
79
|
-
/** 每批处理行数 */
|
|
80
|
-
batchSize?: number;
|
|
81
|
-
}
|
|
22
|
+
import type { ExportDataOptions, ExportTableOptions, ExportTablesXlsxOptions, ExportCsvBatchOptions, ExportXlsxBatchOptions, ExportTablesBatchOptions, DataRow } from '@bsg-export/types';
|
|
82
23
|
/**
|
|
83
24
|
* WASM 导出管理 Composable
|
|
84
25
|
*
|
|
@@ -94,11 +35,11 @@ export declare function useExporter(): {
|
|
|
94
35
|
progress: import("vue").Ref<number, number>;
|
|
95
36
|
/** 错误信息 */
|
|
96
37
|
error: import("vue").Ref<Error | null, Error | null>;
|
|
97
|
-
exportTable: (options: ExportTableOptions) =>
|
|
98
|
-
exportData: (data: DataRow[], options?: ExportDataOptions) =>
|
|
99
|
-
exportTablesXlsx: (options: ExportTablesXlsxOptions) =>
|
|
100
|
-
exportCsvBatch: (options: ExportCsvBatchOptions) => Promise<
|
|
101
|
-
exportXlsxBatch: (options: ExportXlsxBatchOptions) => Promise<
|
|
102
|
-
exportTablesBatch: (options: ExportTablesBatchOptions) => Promise<
|
|
38
|
+
exportTable: (options: ExportTableOptions) => boolean;
|
|
39
|
+
exportData: (data: DataRow[], options?: ExportDataOptions) => boolean;
|
|
40
|
+
exportTablesXlsx: (options: ExportTablesXlsxOptions) => boolean;
|
|
41
|
+
exportCsvBatch: (options: ExportCsvBatchOptions) => Promise<boolean>;
|
|
42
|
+
exportXlsxBatch: (options: ExportXlsxBatchOptions) => Promise<boolean>;
|
|
43
|
+
exportTablesBatch: (options: ExportTablesBatchOptions) => Promise<boolean>;
|
|
103
44
|
};
|
|
104
45
|
//# sourceMappingURL=use-exporter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-exporter.d.ts","sourceRoot":"","sources":["../src/use-exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"use-exporter.d.ts","sourceRoot":"","sources":["../src/use-exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EAExB,OAAO,EACR,MAAM,mBAAmB,CAAC;AA+B3B;;;;;GAKG;AACH,wBAAgB,WAAW;IAmJvB,oBAAoB;;IAEpB,aAAa;;IAEb,mBAAmB;;IAEnB,WAAW;;2BAvFiB,kBAAkB;uBAetB,OAAO,EAAE,YAAY,iBAAiB;gCAU7B,uBAAuB;8BAYnB,qBAAqB;+BAgBpB,sBAAsB;iCAepB,wBAAwB;EA4BnE"}
|
package/dist/use-exporter.js
CHANGED
|
@@ -8,6 +8,9 @@ async function initWasm() {
|
|
|
8
8
|
await mod.default();
|
|
9
9
|
wasmModule = mod;
|
|
10
10
|
return mod;
|
|
11
|
+
}).catch((err) => {
|
|
12
|
+
wasmInitPromise = null;
|
|
13
|
+
throw err;
|
|
11
14
|
});
|
|
12
15
|
}
|
|
13
16
|
return wasmInitPromise;
|
|
@@ -35,35 +38,39 @@ function useExporter() {
|
|
|
35
38
|
};
|
|
36
39
|
};
|
|
37
40
|
const wrapSync = (fn) => {
|
|
38
|
-
if (!initialized.value || !wasmModule) return;
|
|
41
|
+
if (!initialized.value || !wasmModule) return false;
|
|
39
42
|
loading.value = true;
|
|
40
43
|
progress.value = 0;
|
|
41
44
|
error.value = null;
|
|
42
45
|
try {
|
|
43
46
|
fn();
|
|
44
47
|
if (mounted) progress.value = 100;
|
|
48
|
+
return true;
|
|
45
49
|
} catch (err) {
|
|
46
50
|
if (mounted) error.value = err instanceof Error ? err : new Error(String(err));
|
|
51
|
+
return false;
|
|
47
52
|
} finally {
|
|
48
53
|
if (mounted) loading.value = false;
|
|
49
54
|
}
|
|
50
55
|
};
|
|
51
56
|
const wrapAsync = async (fn) => {
|
|
52
|
-
if (!initialized.value || !wasmModule) return;
|
|
57
|
+
if (!initialized.value || !wasmModule) return false;
|
|
53
58
|
loading.value = true;
|
|
54
59
|
progress.value = 0;
|
|
55
60
|
error.value = null;
|
|
56
61
|
try {
|
|
57
62
|
await fn();
|
|
58
63
|
if (mounted) progress.value = 100;
|
|
64
|
+
return true;
|
|
59
65
|
} catch (err) {
|
|
60
66
|
if (mounted) error.value = err instanceof Error ? err : new Error(String(err));
|
|
67
|
+
return false;
|
|
61
68
|
} finally {
|
|
62
69
|
if (mounted) loading.value = false;
|
|
63
70
|
}
|
|
64
71
|
};
|
|
65
72
|
const exportTable = (options) => {
|
|
66
|
-
wrapSync(() => {
|
|
73
|
+
return wrapSync(() => {
|
|
67
74
|
wasmModule.export_table(
|
|
68
75
|
options.tableId,
|
|
69
76
|
options.filename,
|
|
@@ -76,22 +83,23 @@ function useExporter() {
|
|
|
76
83
|
});
|
|
77
84
|
};
|
|
78
85
|
const exportData = (data, options) => {
|
|
79
|
-
wrapSync(() => {
|
|
86
|
+
return wrapSync(() => {
|
|
80
87
|
const opts = options ? { ...options, progressCallback: options.progressCallback ?? createProgressCallback() } : { progressCallback: createProgressCallback() };
|
|
81
88
|
wasmModule.export_data(data, opts);
|
|
82
89
|
});
|
|
83
90
|
};
|
|
84
91
|
const exportTablesXlsx = (options) => {
|
|
85
|
-
wrapSync(() => {
|
|
92
|
+
return wrapSync(() => {
|
|
86
93
|
wasmModule.export_tables_xlsx(
|
|
87
94
|
options.sheets,
|
|
88
95
|
options.filename,
|
|
89
|
-
createProgressCallback()
|
|
96
|
+
createProgressCallback(),
|
|
97
|
+
options.strictProgressCallback
|
|
90
98
|
);
|
|
91
99
|
});
|
|
92
100
|
};
|
|
93
101
|
const exportCsvBatch = async (options) => {
|
|
94
|
-
await wrapAsync(async () => {
|
|
102
|
+
return await wrapAsync(async () => {
|
|
95
103
|
await wasmModule.export_table_to_csv_batch(
|
|
96
104
|
options.tableId,
|
|
97
105
|
options.tbodyId,
|
|
@@ -99,29 +107,32 @@ function useExporter() {
|
|
|
99
107
|
options.batchSize,
|
|
100
108
|
options.excludeHidden,
|
|
101
109
|
createProgressCallback(),
|
|
102
|
-
options.withBom
|
|
110
|
+
options.withBom,
|
|
111
|
+
options.strictProgressCallback
|
|
103
112
|
);
|
|
104
113
|
});
|
|
105
114
|
};
|
|
106
115
|
const exportXlsxBatch = async (options) => {
|
|
107
|
-
await wrapAsync(async () => {
|
|
116
|
+
return await wrapAsync(async () => {
|
|
108
117
|
await wasmModule.export_table_to_xlsx_batch(
|
|
109
118
|
options.tableId,
|
|
110
119
|
options.tbodyId,
|
|
111
120
|
options.filename,
|
|
112
121
|
options.batchSize,
|
|
113
122
|
options.excludeHidden,
|
|
114
|
-
createProgressCallback()
|
|
123
|
+
createProgressCallback(),
|
|
124
|
+
options.strictProgressCallback
|
|
115
125
|
);
|
|
116
126
|
});
|
|
117
127
|
};
|
|
118
128
|
const exportTablesBatch = async (options) => {
|
|
119
|
-
await wrapAsync(async () => {
|
|
129
|
+
return await wrapAsync(async () => {
|
|
120
130
|
await wasmModule.export_tables_to_xlsx_batch(
|
|
121
131
|
options.sheets,
|
|
122
132
|
options.filename,
|
|
123
133
|
options.batchSize,
|
|
124
|
-
createProgressCallback()
|
|
134
|
+
createProgressCallback(),
|
|
135
|
+
options.strictProgressCallback
|
|
125
136
|
);
|
|
126
137
|
});
|
|
127
138
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useWorkerExporter - Worker 线程导出管理 Composable
|
|
3
|
+
*
|
|
4
|
+
* 将 CSV/XLSX 生成移至 Worker 线程,主线程不阻塞。
|
|
5
|
+
* 用户需要传入一个已创建的 Worker 实例。
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```vue
|
|
9
|
+
* <script setup lang="ts">
|
|
10
|
+
* import { useWorkerExporter } from '@bsg-export/vue';
|
|
11
|
+
*
|
|
12
|
+
* // Vite
|
|
13
|
+
* import ExportWorkerScript from '@bsg-export/worker/worker?worker';
|
|
14
|
+
* const { initialized, loading, progress, exportData } = useWorkerExporter(() => new ExportWorkerScript());
|
|
15
|
+
* </script>
|
|
16
|
+
*
|
|
17
|
+
* <template>
|
|
18
|
+
* <button :disabled="!initialized || loading" @click="exportData(data, { columns, filename: '报表.xlsx', format: 1 })">
|
|
19
|
+
* {{ loading ? `导出中 ${Math.round(progress)}%` : '导出' }}
|
|
20
|
+
* </button>
|
|
21
|
+
* </template>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import type { ExportDataOptions, DataRow } from '@bsg-export/types';
|
|
25
|
+
/**
|
|
26
|
+
* Worker 线程导出管理 Composable
|
|
27
|
+
*
|
|
28
|
+
* 接收一个 Worker 工厂函数,自动管理 Worker 生命周期和 WASM 初始化。
|
|
29
|
+
* 导出计算在 Worker 线程执行,主线程保持响应。
|
|
30
|
+
*
|
|
31
|
+
* @param createWorker - 创建 Worker 实例的工厂函数
|
|
32
|
+
*/
|
|
33
|
+
export declare function useWorkerExporter(createWorker: () => Worker): {
|
|
34
|
+
/** Worker 中的 WASM 是否已初始化完成 */
|
|
35
|
+
initialized: import("vue").Ref<boolean, boolean>;
|
|
36
|
+
/** 是否正在导出 */
|
|
37
|
+
loading: import("vue").Ref<boolean, boolean>;
|
|
38
|
+
/** 导出进度 (0-100) */
|
|
39
|
+
progress: import("vue").Ref<number, number>;
|
|
40
|
+
/** 错误信息 */
|
|
41
|
+
error: import("vue").Ref<Error | null, Error | null>;
|
|
42
|
+
/** 在 Worker 中生成文件并触发下载 */
|
|
43
|
+
exportData: (data: DataRow[], options?: Omit<ExportDataOptions, "progressCallback">) => Promise<boolean>;
|
|
44
|
+
/** 在 Worker 中生成文件字节(不触发下载) */
|
|
45
|
+
generateBytes: (data: DataRow[], options?: Omit<ExportDataOptions, "progressCallback">) => Promise<Uint8Array | null>;
|
|
46
|
+
/** 销毁 Worker */
|
|
47
|
+
terminate: () => void;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=use-worker-exporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-worker-exporter.d.ts","sourceRoot":"","sources":["../src/use-worker-exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EACV,iBAAiB,EAEjB,OAAO,EACR,MAAM,mBAAmB,CAAC;AAiC3B;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,MAAM;IAiMxD,8BAA8B;;IAE9B,aAAa;;IAEb,mBAAmB;;IAEnB,WAAW;;IAEX,0BAA0B;uBA9DpB,OAAO,EAAE,YACL,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,KACpD,OAAO,CAAC,OAAO,CAAC;IA8DjB,8BAA8B;0BAzCxB,OAAO,EAAE,YACL,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,KACpD,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAyC3B,gBAAgB;;EAGnB"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { ref, onMounted, onUnmounted } from "vue";
|
|
2
|
+
let requestCounter = 0;
|
|
3
|
+
function generateId() {
|
|
4
|
+
return `req_${++requestCounter}_${Date.now()}`;
|
|
5
|
+
}
|
|
6
|
+
function useWorkerExporter(createWorker) {
|
|
7
|
+
const initialized = ref(false);
|
|
8
|
+
const loading = ref(false);
|
|
9
|
+
const progress = ref(0);
|
|
10
|
+
const error = ref(null);
|
|
11
|
+
let worker = null;
|
|
12
|
+
const pending = /* @__PURE__ */ new Map();
|
|
13
|
+
let mounted = true;
|
|
14
|
+
const handleMessage = (event) => {
|
|
15
|
+
if (!mounted) return;
|
|
16
|
+
const { type, id, bytes, message, progress: prog } = event.data;
|
|
17
|
+
const req = pending.get(id);
|
|
18
|
+
if (!req) return;
|
|
19
|
+
switch (type) {
|
|
20
|
+
case "ready":
|
|
21
|
+
pending.delete(id);
|
|
22
|
+
req.resolve(new ArrayBuffer(0));
|
|
23
|
+
break;
|
|
24
|
+
case "result":
|
|
25
|
+
pending.delete(id);
|
|
26
|
+
req.resolve(bytes);
|
|
27
|
+
break;
|
|
28
|
+
case "error":
|
|
29
|
+
pending.delete(id);
|
|
30
|
+
req.reject(new Error(message ?? "\u672A\u77E5\u9519\u8BEF"));
|
|
31
|
+
break;
|
|
32
|
+
case "progress":
|
|
33
|
+
if (req.onProgress && prog !== void 0) {
|
|
34
|
+
req.onProgress(prog);
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const handleError = (event) => {
|
|
40
|
+
if (!mounted) return;
|
|
41
|
+
const err = new Error(`Worker \u9519\u8BEF: ${event.message}`);
|
|
42
|
+
for (const [id, req] of pending) {
|
|
43
|
+
req.reject(err);
|
|
44
|
+
pending.delete(id);
|
|
45
|
+
}
|
|
46
|
+
error.value = err;
|
|
47
|
+
};
|
|
48
|
+
onMounted(() => {
|
|
49
|
+
mounted = true;
|
|
50
|
+
worker = createWorker();
|
|
51
|
+
worker.addEventListener("message", handleMessage);
|
|
52
|
+
worker.addEventListener("error", handleError);
|
|
53
|
+
const id = generateId();
|
|
54
|
+
const initPromise = new Promise((resolve, reject) => {
|
|
55
|
+
pending.set(id, { resolve, reject });
|
|
56
|
+
});
|
|
57
|
+
const request = { type: "init", id };
|
|
58
|
+
worker.postMessage(request);
|
|
59
|
+
initPromise.then(() => {
|
|
60
|
+
if (mounted) initialized.value = true;
|
|
61
|
+
}).catch((err) => {
|
|
62
|
+
if (mounted) error.value = err instanceof Error ? err : new Error(String(err));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
onUnmounted(() => {
|
|
66
|
+
mounted = false;
|
|
67
|
+
for (const [, req] of pending) {
|
|
68
|
+
req.reject(new Error("\u7EC4\u4EF6\u5DF2\u5378\u8F7D"));
|
|
69
|
+
}
|
|
70
|
+
pending.clear();
|
|
71
|
+
worker?.terminate();
|
|
72
|
+
worker = null;
|
|
73
|
+
});
|
|
74
|
+
const sendGenerate = (data, options) => {
|
|
75
|
+
if (!worker) return Promise.reject(new Error("Worker \u672A\u521B\u5EFA"));
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const id = generateId();
|
|
78
|
+
pending.set(id, {
|
|
79
|
+
resolve,
|
|
80
|
+
reject,
|
|
81
|
+
onProgress: (p) => {
|
|
82
|
+
if (mounted) progress.value = p;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
const request = {
|
|
86
|
+
type: "generate",
|
|
87
|
+
id,
|
|
88
|
+
data,
|
|
89
|
+
options: options ?? {}
|
|
90
|
+
};
|
|
91
|
+
worker.postMessage(request);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const downloadFile = (bytes, filename, format) => {
|
|
95
|
+
const isXlsx = format === 1;
|
|
96
|
+
const mimeType = isXlsx ? "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" : "text/csv;charset=utf-8";
|
|
97
|
+
const defaultExt = isXlsx ? "xlsx" : "csv";
|
|
98
|
+
let finalFilename = filename ?? `export.${defaultExt}`;
|
|
99
|
+
if (!finalFilename.endsWith(`.${defaultExt}`)) {
|
|
100
|
+
finalFilename = `${finalFilename}.${defaultExt}`;
|
|
101
|
+
}
|
|
102
|
+
const blob = new Blob([bytes.buffer], { type: mimeType });
|
|
103
|
+
const url = URL.createObjectURL(blob);
|
|
104
|
+
const anchor = document.createElement("a");
|
|
105
|
+
anchor.href = url;
|
|
106
|
+
anchor.download = finalFilename;
|
|
107
|
+
anchor.click();
|
|
108
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
109
|
+
};
|
|
110
|
+
const exportData = async (data, options) => {
|
|
111
|
+
if (!initialized.value) return false;
|
|
112
|
+
loading.value = true;
|
|
113
|
+
progress.value = 0;
|
|
114
|
+
error.value = null;
|
|
115
|
+
try {
|
|
116
|
+
const buffer = await sendGenerate(data, options);
|
|
117
|
+
const bytes = new Uint8Array(buffer);
|
|
118
|
+
downloadFile(bytes, options?.filename, options?.format);
|
|
119
|
+
if (mounted) progress.value = 100;
|
|
120
|
+
return true;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if (mounted) error.value = err instanceof Error ? err : new Error(String(err));
|
|
123
|
+
return false;
|
|
124
|
+
} finally {
|
|
125
|
+
if (mounted) loading.value = false;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const generateBytes = async (data, options) => {
|
|
129
|
+
if (!initialized.value) return null;
|
|
130
|
+
loading.value = true;
|
|
131
|
+
progress.value = 0;
|
|
132
|
+
error.value = null;
|
|
133
|
+
try {
|
|
134
|
+
const buffer = await sendGenerate(data, options);
|
|
135
|
+
if (mounted) progress.value = 100;
|
|
136
|
+
return new Uint8Array(buffer);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
if (mounted) error.value = err instanceof Error ? err : new Error(String(err));
|
|
139
|
+
return null;
|
|
140
|
+
} finally {
|
|
141
|
+
if (mounted) loading.value = false;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const terminate = () => {
|
|
145
|
+
for (const [, req] of pending) {
|
|
146
|
+
req.reject(new Error("Worker \u5DF2\u88AB\u624B\u52A8\u9500\u6BC1"));
|
|
147
|
+
}
|
|
148
|
+
pending.clear();
|
|
149
|
+
worker?.terminate();
|
|
150
|
+
worker = null;
|
|
151
|
+
if (mounted) initialized.value = false;
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
/** Worker 中的 WASM 是否已初始化完成 */
|
|
155
|
+
initialized,
|
|
156
|
+
/** 是否正在导出 */
|
|
157
|
+
loading,
|
|
158
|
+
/** 导出进度 (0-100) */
|
|
159
|
+
progress,
|
|
160
|
+
/** 错误信息 */
|
|
161
|
+
error,
|
|
162
|
+
/** 在 Worker 中生成文件并触发下载 */
|
|
163
|
+
exportData,
|
|
164
|
+
/** 在 Worker 中生成文件字节(不触发下载) */
|
|
165
|
+
generateBytes,
|
|
166
|
+
/** 销毁 Worker */
|
|
167
|
+
terminate
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export {
|
|
171
|
+
useWorkerExporter
|
|
172
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsg-export/vue",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "belobog-stellar-grid 的 Vue 3 官方封装组件",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"build": "vue-tsc --declaration --emitDeclarationOnly --outDir dist && esbuild src/index.ts src/use-exporter.ts --outdir=dist --format=esm --bundle=false",
|
|
18
|
+
"build": "vue-tsc --declaration --emitDeclarationOnly --outDir dist && esbuild src/index.ts src/use-exporter.ts src/use-worker-exporter.ts --outdir=dist --format=esm --bundle=false",
|
|
19
19
|
"typecheck": "vue-tsc --noEmit"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"@bsg-export/types": "file:../types"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"esbuild": "^0.25.
|
|
44
|
-
"typescript": "^5.
|
|
45
|
-
"vue": "^3.5.
|
|
46
|
-
"vue-tsc": "^2.2.
|
|
43
|
+
"esbuild": "^0.25.12",
|
|
44
|
+
"typescript": "^5.9.3",
|
|
45
|
+
"vue": "^3.5.29",
|
|
46
|
+
"vue-tsc": "^2.2.12"
|
|
47
47
|
}
|
|
48
48
|
}
|