@bsg-export/vue 1.0.7 → 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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## 简介
8
8
 
9
- 提供 `useExporter` Composable 和 `ExportButton` 组件,自动管理 WASM 初始化、导出状态和进度追踪。
9
+ 提供 `useExporter` Composable、`useWorkerExporter` Composable 和 `ExportButton` 组件,自动管理 WASM 初始化、导出状态和进度追踪。`useWorkerExporter` 支持将导出计算移至 Worker 线程。
10
10
 
11
11
  ## 安装
12
12
 
@@ -78,6 +78,31 @@ import { ExportButton, ExportFormat } from '@bsg-export/vue';
78
78
  | `exportXlsxBatch` | `(options) => Promise` | XLSX 分批导出 |
79
79
  | `exportTablesBatch` | `(options) => Promise` | 多 Sheet 分批导出 |
80
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
+
81
106
  ### `<ExportButton>` Props
82
107
 
83
108
  | Prop | 类型 | 默认值 | 说明 |
@@ -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;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * @packageDocumentation
7
7
  */
8
8
  export { useExporter } from './use-exporter';
9
+ export { useWorkerExporter } from './use-worker-exporter';
9
10
  export { default as ExportButton } from './ExportButton.vue';
10
11
  export type { Column, MergeCellValue, CellValue, MergeableCellValue, DataRow, ExportDataOptions, SheetConfig, BatchSheetConfig, ProgressCallback, ExportTableOptions, ExportTablesXlsxOptions, ExportCsvBatchOptions, ExportXlsxBatchOptions, ExportTablesBatchOptions, } from '@bsg-export/types';
11
12
  export { ExportFormat } from '@bsg-export/types';
@@ -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;AAI7C,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"}
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
  };
@@ -1 +1 @@
1
- {"version":3,"file":"use-exporter.d.ts","sourceRoot":"","sources":["../src/use-exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAEV,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EAExB,OAAO,EACR,MAAM,mBAAmB,CAAC;AAyB3B;;;;;GAKG;AACH,wBAAgB,WAAW;IA+IvB,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;
@@ -90,7 +93,8 @@ function useExporter() {
90
93
  wasmModule.export_tables_xlsx(
91
94
  options.sheets,
92
95
  options.filename,
93
- createProgressCallback()
96
+ createProgressCallback(),
97
+ options.strictProgressCallback
94
98
  );
95
99
  });
96
100
  };
@@ -103,7 +107,8 @@ function useExporter() {
103
107
  options.batchSize,
104
108
  options.excludeHidden,
105
109
  createProgressCallback(),
106
- options.withBom
110
+ options.withBom,
111
+ options.strictProgressCallback
107
112
  );
108
113
  });
109
114
  };
@@ -115,7 +120,8 @@ function useExporter() {
115
120
  options.filename,
116
121
  options.batchSize,
117
122
  options.excludeHidden,
118
- createProgressCallback()
123
+ createProgressCallback(),
124
+ options.strictProgressCallback
119
125
  );
120
126
  });
121
127
  };
@@ -125,7 +131,8 @@ function useExporter() {
125
131
  options.sheets,
126
132
  options.filename,
127
133
  options.batchSize,
128
- createProgressCallback()
134
+ createProgressCallback(),
135
+ options.strictProgressCallback
129
136
  );
130
137
  });
131
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.7",
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
  }