@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 ADDED
@@ -0,0 +1,135 @@
1
+ # @bsg-export/vue
2
+
3
+ [![npm](https://img.shields.io/npm/v/@bsg-export/vue)](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":"AA4FA;;;;;;;;;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;AA4ED,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;;;;;;;;;cA3GR,OAAO;sBAEC,MAAM;iBAEX,MAAM;6EA+GpB,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"}
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 type { ExportTableOptions, ExportTablesXlsxOptions, ExportCsvBatchOptions, ExportXlsxBatchOptions, ExportTablesBatchOptions, } from './use-exporter';
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
@@ -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,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAGxB,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,GACjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
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
  };
@@ -19,66 +19,7 @@
19
19
  * </template>
20
20
  * ```
21
21
  */
22
- import type { ExportFormat, ExportDataOptions, SheetConfig, BatchSheetConfig, DataRow } from '@bsg-export/types';
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) => void;
98
- exportData: (data: DataRow[], options?: ExportDataOptions) => void;
99
- exportTablesXlsx: (options: ExportTablesXlsxOptions) => void;
100
- exportCsvBatch: (options: ExportCsvBatchOptions) => Promise<void>;
101
- exportXlsxBatch: (options: ExportXlsxBatchOptions) => Promise<void>;
102
- exportTablesBatch: (options: ExportTablesBatchOptions) => Promise<void>;
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,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAEhB,OAAO,EACR,MAAM,mBAAmB,CAAC;AAE3B,yBAAyB;AACzB,MAAM,WAAW,kBAAkB;IACjC,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,iBAAiB;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,kBAAkB;AAClB,MAAM,WAAW,uBAAuB;IACtC,iBAAiB;IACjB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,YAAY;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAqB;AACrB,MAAM,WAAW,qBAAqB;IACpC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,sBAAsB;AACtB,MAAM,WAAW,sBAAsB;IACrC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,oBAAoB;AACpB,MAAM,WAAW,wBAAwB;IACvC,iBAAiB;IACjB,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,YAAY;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAuBD;;;;;GAKG;AACH,wBAAgB,WAAW;IA2IvB,oBAAoB;;IAEpB,aAAa;;IAEb,mBAAmB;;IAEnB,WAAW;;2BAnFiB,kBAAkB;uBAetB,OAAO,EAAE,YAAY,iBAAiB;gCAU7B,uBAAuB;8BAWnB,qBAAqB;+BAepB,sBAAsB;iCAcpB,wBAAwB;EA2BnE"}
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"}
@@ -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.6",
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.0",
44
- "typescript": "^5.7.0",
45
- "vue": "^3.5.0",
46
- "vue-tsc": "^2.2.0"
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
  }