sa2kit 1.0.0 → 1.0.2

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.
Files changed (99) hide show
  1. package/dist/UniversalFileService-CEZRJ87g.d.mts +727 -0
  2. package/dist/UniversalFileService-CEZRJ87g.d.ts +727 -0
  3. package/dist/api/index.d.mts +248 -0
  4. package/dist/api/index.d.ts +248 -0
  5. package/dist/api/index.js +294 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/index.mjs +290 -0
  8. package/dist/api/index.mjs.map +1 -0
  9. package/dist/auth/client/index.d.mts +52 -3
  10. package/dist/auth/client/index.d.ts +52 -3
  11. package/dist/auth/components/index.d.mts +149 -4
  12. package/dist/auth/components/index.d.ts +149 -4
  13. package/dist/auth/components/index.js +243 -9
  14. package/dist/auth/components/index.js.map +1 -1
  15. package/dist/auth/components/index.mjs +237 -4
  16. package/dist/auth/components/index.mjs.map +1 -1
  17. package/dist/auth/hooks/index.d.mts +31 -2
  18. package/dist/auth/hooks/index.d.ts +31 -2
  19. package/dist/auth/index.d.mts +5 -5
  20. package/dist/auth/index.d.ts +5 -5
  21. package/dist/auth/index.js +49 -17
  22. package/dist/auth/index.mjs +1 -1
  23. package/dist/auth/routes/index.d.mts +103 -5
  24. package/dist/auth/routes/index.d.ts +103 -5
  25. package/dist/auth/routes/index.js +37 -5
  26. package/dist/auth/routes/index.mjs +1 -1
  27. package/dist/chunk-42IJ7HEI.js +573 -0
  28. package/dist/chunk-42IJ7HEI.js.map +1 -0
  29. package/dist/chunk-7XLFSPDG.mjs +31 -0
  30. package/dist/chunk-7XLFSPDG.mjs.map +1 -0
  31. package/dist/chunk-GCVOKQZP.js +36 -0
  32. package/dist/chunk-GCVOKQZP.js.map +1 -0
  33. package/dist/chunk-IBLB7ARJ.mjs +560 -0
  34. package/dist/chunk-IBLB7ARJ.mjs.map +1 -0
  35. package/dist/{chunk-6FNUWAIV.js → chunk-LX4XX6W7.js} +54 -8
  36. package/dist/chunk-LX4XX6W7.js.map +1 -0
  37. package/dist/{chunk-HXFFYNIF.mjs → chunk-T5OZHYVM.mjs} +54 -8
  38. package/dist/chunk-T5OZHYVM.mjs.map +1 -0
  39. package/dist/config/server/index.d.mts +1533 -0
  40. package/dist/config/server/index.d.ts +1533 -0
  41. package/dist/config/server/index.js +1177 -0
  42. package/dist/config/server/index.js.map +1 -0
  43. package/dist/config/server/index.mjs +1138 -0
  44. package/dist/config/server/index.mjs.map +1 -0
  45. package/dist/i18n/index.d.mts +2 -1
  46. package/dist/i18n/index.d.ts +2 -1
  47. package/dist/i18n/index.js +125 -61
  48. package/dist/i18n/index.js.map +1 -1
  49. package/dist/i18n/index.mjs +126 -62
  50. package/dist/i18n/index.mjs.map +1 -1
  51. package/dist/index.js +6 -6
  52. package/dist/index.mjs +1 -1
  53. package/dist/mmd/index.d.mts +346 -0
  54. package/dist/mmd/index.d.ts +346 -0
  55. package/dist/mmd/index.js +1535 -0
  56. package/dist/mmd/index.js.map +1 -0
  57. package/dist/mmd/index.mjs +1503 -0
  58. package/dist/mmd/index.mjs.map +1 -0
  59. package/dist/storage/index.d.mts +1 -0
  60. package/dist/storage/index.d.ts +1 -0
  61. package/dist/storage/index.js +9 -9
  62. package/dist/storage/index.mjs +1 -1
  63. package/dist/{index-8VoHap_4.d.mts → types-CroexXnI.d.ts} +38 -44
  64. package/dist/{index-8VoHap_4.d.ts → types-DmsXCWvm.d.mts} +38 -44
  65. package/dist/{types-DAxQ1MeY.d.ts → types-Dt0oqeFM.d.mts} +1 -1
  66. package/dist/{types-DT8LVCvE.d.mts → types-zK6kDzDQ.d.ts} +1 -1
  67. package/dist/universalExport/index.js +17 -32
  68. package/dist/universalExport/index.js.map +1 -1
  69. package/dist/universalExport/index.mjs +2 -29
  70. package/dist/universalExport/index.mjs.map +1 -1
  71. package/dist/universalExport/server/index.d.mts +849 -8
  72. package/dist/universalExport/server/index.d.ts +849 -8
  73. package/dist/universalExport/server/index.js +1382 -2
  74. package/dist/universalExport/server/index.js.map +1 -1
  75. package/dist/universalExport/server/index.mjs +1355 -3
  76. package/dist/universalExport/server/index.mjs.map +1 -1
  77. package/dist/universalFile/index.d.mts +54 -3
  78. package/dist/universalFile/index.d.ts +54 -3
  79. package/dist/universalFile/index.js +272 -0
  80. package/dist/universalFile/index.js.map +1 -1
  81. package/dist/universalFile/index.mjs +267 -1
  82. package/dist/universalFile/index.mjs.map +1 -1
  83. package/dist/universalFile/server/index.d.mts +2541 -469
  84. package/dist/universalFile/server/index.d.ts +2541 -469
  85. package/dist/universalFile/server/index.js +830 -64
  86. package/dist/universalFile/server/index.js.map +1 -1
  87. package/dist/universalFile/server/index.mjs +803 -66
  88. package/dist/universalFile/server/index.mjs.map +1 -1
  89. package/package.json +47 -23
  90. package/dist/chunk-6FNUWAIV.js.map +0 -1
  91. package/dist/chunk-APY57REU.js +0 -300
  92. package/dist/chunk-APY57REU.js.map +0 -1
  93. package/dist/chunk-C64RY2OW.mjs +0 -295
  94. package/dist/chunk-C64RY2OW.mjs.map +0 -1
  95. package/dist/chunk-HXFFYNIF.mjs.map +0 -1
  96. package/dist/types-CoGG1rNV.d.mts +0 -258
  97. package/dist/types-CoGG1rNV.d.ts +0 -258
  98. package/dist/types-DW9qar-w.d.mts +0 -52
  99. package/dist/types-DW9qar-w.d.ts +0 -52
@@ -1,4 +1,1185 @@
1
+ import { ExportConfigError, ExportDataError, ExportFileError } from '../../chunk-7XLFSPDG.mjs';
2
+ import { createLogger } from '../../chunk-KQGP6BTS.mjs';
1
3
  import '../../chunk-BJTO5JO5.mjs';
4
+ import * as XLSX from 'xlsx';
5
+ import { pgTable, timestamp, text, integer, boolean, jsonb } from 'drizzle-orm/pg-core';
6
+ import { sql, relations, eq, and, desc } from 'drizzle-orm';
7
+
8
+ var logger = createLogger("UniversalExportService");
9
+ var DEFAULT_CONFIG = {
10
+ defaultFormat: "csv",
11
+ defaultDelimiter: ",",
12
+ defaultEncoding: "utf-8",
13
+ defaultAddBOM: true,
14
+ maxFileSize: 100 * 1024 * 1024,
15
+ // 100MB
16
+ maxRowsLimit: 1e5,
17
+ maxConcurrentExports: 5,
18
+ exportTimeout: 3e5,
19
+ // 5分钟
20
+ cache: {
21
+ configTTL: 3600,
22
+ // 1小时
23
+ resultTTL: 1800
24
+ // 30分钟
25
+ }
26
+ };
27
+ var DEFAULT_FORMATTERS = {
28
+ // 日期格式化
29
+ date: (value) => {
30
+ if (!value) return "";
31
+ const date = new Date(value);
32
+ return date.toISOString().split("T")[0];
33
+ },
34
+ // 时间格式化
35
+ datetime: (value) => {
36
+ if (!value) return "";
37
+ const date = new Date(value);
38
+ return date.toLocaleString("zh-CN");
39
+ },
40
+ // 数字格式化
41
+ number: (value) => {
42
+ if (value === null || value === void 0) return "";
43
+ return String(value);
44
+ },
45
+ // 货币格式化
46
+ currency: (value) => {
47
+ if (value === null || value === void 0) return "";
48
+ return `\xA5${Number(value).toFixed(2)}`;
49
+ },
50
+ // 百分比格式化
51
+ percentage: (value) => {
52
+ if (value === null || value === void 0) return "";
53
+ return `${(Number(value) * 100).toFixed(2)}%`;
54
+ },
55
+ // 布尔值格式化
56
+ boolean: (value) => {
57
+ if (value === null || value === void 0) return "";
58
+ return value ? "\u662F" : "\u5426";
59
+ },
60
+ // 数组格式化
61
+ array: (value) => {
62
+ if (!Array.isArray(value)) return "";
63
+ return value.join(", ");
64
+ },
65
+ // 对象格式化
66
+ object: (value) => {
67
+ if (!value || typeof value !== "object") return "";
68
+ return JSON.stringify(value);
69
+ }
70
+ };
71
+ var UniversalExportService = class {
72
+ constructor(config, client) {
73
+ this.eventListeners = /* @__PURE__ */ new Map();
74
+ this.activeExports = /* @__PURE__ */ new Map();
75
+ this.configCache = /* @__PURE__ */ new Map();
76
+ this.resultCache = /* @__PURE__ */ new Map();
77
+ this.config = { ...DEFAULT_CONFIG, ...config };
78
+ this.client = client;
79
+ }
80
+ // ============= 配置管理 =============
81
+ /**
82
+ * 创建导出配置
83
+ */
84
+ async createConfig(config) {
85
+ try {
86
+ this.validateConfig({
87
+ ...config,
88
+ id: "temp",
89
+ createdAt: /* @__PURE__ */ new Date(),
90
+ updatedAt: /* @__PURE__ */ new Date()
91
+ });
92
+ if (!this.client) {
93
+ throw new ExportConfigError("\u672A\u63D0\u4F9B\u5BFC\u51FA\u5BA2\u6237\u7AEF\u670D\u52A1", config);
94
+ }
95
+ const newConfig = await this.client.createConfig(config);
96
+ this.configCache.set(newConfig.id, {
97
+ config: newConfig,
98
+ timestamp: Date.now()
99
+ });
100
+ this.emitEvent({
101
+ type: "config:save",
102
+ exportId: newConfig.id,
103
+ timestamp: /* @__PURE__ */ new Date(),
104
+ data: { config: newConfig }
105
+ });
106
+ return newConfig;
107
+ } catch (error) {
108
+ throw new ExportConfigError(
109
+ `\u521B\u5EFA\u5BFC\u51FA\u914D\u7F6E\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`,
110
+ { originalError: error }
111
+ );
112
+ }
113
+ }
114
+ /**
115
+ * 获取导出配置
116
+ */
117
+ async getConfig(configId) {
118
+ const cached = this.configCache.get(configId);
119
+ if (cached && Date.now() - cached.timestamp < this.config.cache.configTTL * 1e3) {
120
+ return cached.config;
121
+ }
122
+ return null;
123
+ }
124
+ /**
125
+ * 更新导出配置
126
+ */
127
+ async updateConfig(configId, updates) {
128
+ const existing = await this.getConfig(configId);
129
+ if (!existing) {
130
+ throw new ExportConfigError(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configId}`);
131
+ }
132
+ const updatedConfig = {
133
+ ...existing,
134
+ ...updates,
135
+ updatedAt: /* @__PURE__ */ new Date()
136
+ };
137
+ this.validateConfig(updatedConfig);
138
+ this.configCache.set(configId, {
139
+ config: updatedConfig,
140
+ timestamp: Date.now()
141
+ });
142
+ this.emitEvent({
143
+ type: "config:save",
144
+ exportId: configId,
145
+ timestamp: /* @__PURE__ */ new Date(),
146
+ data: { config: updatedConfig }
147
+ });
148
+ return updatedConfig;
149
+ }
150
+ /**
151
+ * 删除导出配置
152
+ */
153
+ async deleteConfig(configId) {
154
+ const existing = await this.getConfig(configId);
155
+ if (!existing) {
156
+ throw new ExportConfigError(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configId}`);
157
+ }
158
+ this.configCache.delete(configId);
159
+ this.emitEvent({
160
+ type: "config:delete",
161
+ exportId: configId,
162
+ timestamp: /* @__PURE__ */ new Date(),
163
+ data: { configId }
164
+ });
165
+ }
166
+ /**
167
+ * 获取模块的配置列表
168
+ */
169
+ async getConfigsByModule(moduleId, businessId) {
170
+ if (!this.client) {
171
+ throw new ExportConfigError("\u672A\u63D0\u4F9B\u5BFC\u51FA\u5BA2\u6237\u7AEF\u670D\u52A1", { moduleId, businessId });
172
+ }
173
+ return await this.client.getConfigsByModule(moduleId, businessId);
174
+ }
175
+ // ============= 导出执行 =============
176
+ /**
177
+ * 执行导出
178
+ */
179
+ async export(request) {
180
+ const exportId = this.generateId();
181
+ const startTime = /* @__PURE__ */ new Date();
182
+ logger.info("\u{1F680} [UniversalExportService] \u5F00\u59CB\u5BFC\u51FA:", {
183
+ exportId,
184
+ configId: request.configId,
185
+ hasDataSource: !!request.dataSource,
186
+ hasCallbacks: !!request.callbacks
187
+ });
188
+ try {
189
+ let config;
190
+ if (typeof request.configId === "object" && request.configId !== null) {
191
+ config = request.configId;
192
+ logger.info("\u{1F4CB} [UniversalExportService] \u4F7F\u7528\u76F4\u63A5\u4F20\u5165\u7684\u914D\u7F6E:", {
193
+ configId: config.id,
194
+ configName: config.name,
195
+ format: config.format,
196
+ fieldsCount: config.fields.length,
197
+ hasGrouping: !!config.grouping,
198
+ groupingEnabled: config.grouping?.enabled,
199
+ groupingFieldsCount: config.grouping?.fields?.length || 0,
200
+ groupingFields: config.grouping?.fields?.map((f) => ({ key: f.key, mergeCells: f.mergeCells })) || []
201
+ });
202
+ } else {
203
+ logger.info("\u{1F50D} [UniversalExportService] \u4ECE\u7F13\u5B58\u83B7\u53D6\u914D\u7F6E:", request.configId);
204
+ const cachedConfig = await this.getConfig(request.configId);
205
+ if (!cachedConfig) {
206
+ throw new ExportConfigError(`\u5BFC\u51FA\u914D\u7F6E\u4E0D\u5B58\u5728: ${request.configId}`);
207
+ }
208
+ config = cachedConfig;
209
+ logger.info("\u2705 [UniversalExportService] \u6210\u529F\u83B7\u53D6\u7F13\u5B58\u914D\u7F6E:", {
210
+ configId: config.id,
211
+ configName: config.name
212
+ });
213
+ }
214
+ const progress = {
215
+ exportId,
216
+ status: "pending",
217
+ progress: 0,
218
+ processedRows: 0,
219
+ totalRows: 0,
220
+ startTime
221
+ };
222
+ this.activeExports.set(exportId, progress);
223
+ this.emitEvent({
224
+ type: "export:start",
225
+ exportId,
226
+ timestamp: startTime,
227
+ data: { config, request }
228
+ });
229
+ if (request.callbacks?.onProgress) {
230
+ logger.info("\u{1F4DE} [UniversalExportService] \u8C03\u7528 onProgress \u56DE\u8C03 - \u5F00\u59CB");
231
+ request.callbacks.onProgress(progress);
232
+ }
233
+ logger.info("\u{1F4CA} [UniversalExportService] \u5F00\u59CB\u83B7\u53D6\u6570\u636E...");
234
+ const data = await this.getData(request);
235
+ logger.info("\u2705 [UniversalExportService] \u6570\u636E\u83B7\u53D6\u6210\u529F:", {
236
+ dataLength: data.length,
237
+ firstItem: data[0] ? Object.keys(data[0]) : [],
238
+ sampleData: data.slice(0, 2)
239
+ });
240
+ progress.totalRows = data.length;
241
+ progress.status = "processing";
242
+ if (request.callbacks?.onProgress) {
243
+ logger.info("\u{1F4DE} [UniversalExportService] \u8C03\u7528 onProgress \u56DE\u8C03 - \u6570\u636E\u5904\u7406");
244
+ progress.progress = 30;
245
+ request.callbacks.onProgress(progress);
246
+ }
247
+ logger.info("\u{1F504} [UniversalExportService] \u5F00\u59CB\u5904\u7406\u6570\u636E...");
248
+ const processedData = await this.processData(data, request, config);
249
+ logger.info("\u2705 [UniversalExportService] \u6570\u636E\u5904\u7406\u5B8C\u6210:", {
250
+ originalLength: data.length,
251
+ processedLength: processedData.length
252
+ });
253
+ if (request.callbacks?.onProgress) {
254
+ logger.info("\u{1F4DE} [UniversalExportService] \u8C03\u7528 onProgress \u56DE\u8C03 - \u6570\u636E\u5B8C\u6210");
255
+ progress.progress = 60;
256
+ request.callbacks.onProgress(progress);
257
+ }
258
+ logger.info("\u{1F4C4} [UniversalExportService] \u5F00\u59CB\u751F\u6210\u6587\u4EF6...");
259
+ const result = await this.generateFile(processedData, config, request, exportId);
260
+ logger.info("\u2705 [UniversalExportService] \u6587\u4EF6\u751F\u6210\u6210\u529F:", {
261
+ fileName: result.fileName,
262
+ fileSize: result.fileSize,
263
+ exportedRows: result.exportedRows
264
+ });
265
+ progress.status = "completed";
266
+ progress.progress = 100;
267
+ progress.processedRows = data.length;
268
+ if (request.callbacks?.onSuccess) {
269
+ logger.info("\u{1F4DE} [UniversalExportService] \u8C03\u7528 onSuccess \u56DE\u8C03");
270
+ request.callbacks.onSuccess(result);
271
+ }
272
+ this.emitEvent({
273
+ type: "export:complete",
274
+ exportId,
275
+ timestamp: /* @__PURE__ */ new Date(),
276
+ data: { result }
277
+ });
278
+ this.resultCache.set(exportId, {
279
+ result,
280
+ timestamp: Date.now()
281
+ });
282
+ this.activeExports.delete(exportId);
283
+ return result;
284
+ } catch (error) {
285
+ const errorObj = {
286
+ code: "EXPORT_FAILED",
287
+ message: error instanceof Error ? error.message : "\u5BFC\u51FA\u5931\u8D25",
288
+ details: { originalError: error },
289
+ timestamp: /* @__PURE__ */ new Date()
290
+ };
291
+ const progress = this.activeExports.get(exportId);
292
+ if (progress) {
293
+ progress.status = "failed";
294
+ progress.error = errorObj.message;
295
+ this.activeExports.delete(exportId);
296
+ }
297
+ if (request.callbacks?.onError) {
298
+ logger.info("\u{1F4DE} [UniversalExportService] \u8C03\u7528 onError \u56DE\u8C03");
299
+ request.callbacks.onError(errorObj);
300
+ }
301
+ this.emitEvent({
302
+ type: "export:error",
303
+ exportId,
304
+ timestamp: /* @__PURE__ */ new Date(),
305
+ error: errorObj.message,
306
+ data: { error: errorObj }
307
+ });
308
+ throw error;
309
+ }
310
+ }
311
+ /**
312
+ * 获取导出进度
313
+ */
314
+ getExportProgress(exportId) {
315
+ return this.activeExports.get(exportId) || null;
316
+ }
317
+ /**
318
+ * 取消导出
319
+ */
320
+ cancelExport(exportId) {
321
+ const progress = this.activeExports.get(exportId);
322
+ if (!progress) {
323
+ return false;
324
+ }
325
+ progress.status = "cancelled";
326
+ this.activeExports.delete(exportId);
327
+ this.emitEvent({
328
+ type: "export:cancel",
329
+ exportId,
330
+ timestamp: /* @__PURE__ */ new Date(),
331
+ data: { progress }
332
+ });
333
+ return true;
334
+ }
335
+ // ============= 事件管理 =============
336
+ /**
337
+ * 添加事件监听器
338
+ */
339
+ addEventListener(type, listener) {
340
+ if (!this.eventListeners.has(type)) {
341
+ this.eventListeners.set(type, []);
342
+ }
343
+ this.eventListeners.get(type).push(listener);
344
+ }
345
+ /**
346
+ * 移除事件监听器
347
+ */
348
+ removeEventListener(type, listener) {
349
+ const listeners = this.eventListeners.get(type);
350
+ if (listeners) {
351
+ const index = listeners.indexOf(listener);
352
+ if (index > -1) {
353
+ listeners.splice(index, 1);
354
+ }
355
+ }
356
+ }
357
+ // ============= 私有方法 =============
358
+ /**
359
+ * 生成唯一ID
360
+ */
361
+ generateId() {
362
+ return `export_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
363
+ }
364
+ /**
365
+ * 验证配置
366
+ */
367
+ validateConfig(config) {
368
+ if (!config.name || config.name.trim() === "") {
369
+ throw new ExportConfigError("\u914D\u7F6E\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
370
+ }
371
+ if (!config.fields || config.fields.length === 0) {
372
+ throw new ExportConfigError("\u81F3\u5C11\u9700\u8981\u5B9A\u4E49\u4E00\u4E2A\u5B57\u6BB5");
373
+ }
374
+ const enabledFields = config.fields.filter((f) => f.enabled);
375
+ if (enabledFields.length === 0) {
376
+ throw new ExportConfigError("\u81F3\u5C11\u9700\u8981\u542F\u7528\u4E00\u4E2A\u5B57\u6BB5");
377
+ }
378
+ const keys = config.fields.map((f) => f.key);
379
+ const uniqueKeys = new Set(keys);
380
+ if (keys.length !== uniqueKeys.size) {
381
+ throw new ExportConfigError("\u5B57\u6BB5\u952E\u540D\u5FC5\u987B\u552F\u4E00");
382
+ }
383
+ }
384
+ /**
385
+ * 获取数据
386
+ */
387
+ async getData(request) {
388
+ logger.info("\u{1F50D} [UniversalExportService] getData \u5F00\u59CB\u6267\u884C...");
389
+ try {
390
+ if (typeof request.dataSource === "function") {
391
+ logger.info("\u{1F4DE} [UniversalExportService] \u8C03\u7528\u6570\u636E\u6E90\u51FD\u6570...");
392
+ const data = await request.dataSource();
393
+ logger.info("\u2705 [UniversalExportService] \u6570\u636E\u6E90\u51FD\u6570\u6267\u884C\u6210\u529F:", {
394
+ dataType: typeof data,
395
+ isArray: Array.isArray(data),
396
+ length: Array.isArray(data) ? data.length : "N/A"
397
+ });
398
+ return data;
399
+ } else {
400
+ console.error("\u274C [UniversalExportService] \u6570\u636E\u6E90\u4E0D\u662F\u51FD\u6570:", typeof request.dataSource);
401
+ throw new ExportDataError("\u6682\u4E0D\u652F\u6301\u5B57\u7B26\u4E32\u6570\u636E\u6E90");
402
+ }
403
+ } catch (error) {
404
+ console.error("\u274C [UniversalExportService] \u83B7\u53D6\u6570\u636E\u5931\u8D25:", error);
405
+ throw new ExportDataError(
406
+ `\u83B7\u53D6\u6570\u636E\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`,
407
+ { originalError: error }
408
+ );
409
+ }
410
+ }
411
+ /**
412
+ * 处理数据
413
+ */
414
+ async processData(data, request, config) {
415
+ logger.info("\u{1F504} [UniversalExportService] processData \u5F00\u59CB\u6267\u884C:", {
416
+ dataLength: data.length,
417
+ hasFilters: !!(request.filters && request.filters.length > 0),
418
+ hasSortBy: !!(request.sortBy && request.sortBy.length > 0),
419
+ hasPagination: !!request.pagination,
420
+ hasGrouping: !!(config.grouping && config.grouping.enabled),
421
+ maxRows: config.maxRows
422
+ });
423
+ logger.info("\u{1F50D} [UniversalExportService] \u8BE6\u7EC6\u5206\u7EC4\u914D\u7F6E\u68C0\u67E5:", {
424
+ configGrouping: config.grouping,
425
+ groupingExists: !!config.grouping,
426
+ groupingEnabled: config.grouping?.enabled,
427
+ groupingFields: config.grouping?.fields,
428
+ groupingFieldsLength: config.grouping?.fields?.length
429
+ });
430
+ let processedData = [...data];
431
+ if (request.filters && request.filters.length > 0) {
432
+ logger.info("\u{1F50D} [UniversalExportService] \u5E94\u7528\u8FC7\u6EE4\u5668...");
433
+ processedData = this.applyFilters(processedData, request.filters);
434
+ logger.info("\u2705 [UniversalExportService] \u8FC7\u6EE4\u5668\u5E94\u7528\u5B8C\u6210:", {
435
+ beforeLength: data.length,
436
+ afterLength: processedData.length
437
+ });
438
+ }
439
+ if (request.sortBy && request.sortBy.length > 0) {
440
+ logger.info("\u{1F4CA} [UniversalExportService] \u5E94\u7528\u6392\u5E8F...");
441
+ processedData = this.applySorting(processedData, request.sortBy);
442
+ logger.info("\u2705 [UniversalExportService] \u6392\u5E8F\u5E94\u7528\u5B8C\u6210");
443
+ }
444
+ if (config.grouping && config.grouping.enabled) {
445
+ logger.info("\u{1F4CA} [UniversalExportService] \u5E94\u7528\u5206\u7EC4...");
446
+ processedData = this.applyGrouping(processedData, config.grouping);
447
+ logger.info("\u2705 [UniversalExportService] \u5206\u7EC4\u5E94\u7528\u5B8C\u6210:", {
448
+ groupsCount: this.countGroups(processedData),
449
+ resultLength: processedData.length
450
+ });
451
+ }
452
+ if (request.pagination) {
453
+ logger.info("\u{1F4C4} [UniversalExportService] \u5E94\u7528\u5206\u9875...");
454
+ const { page, pageSize } = request.pagination;
455
+ const start = (page - 1) * pageSize;
456
+ const end = start + pageSize;
457
+ processedData = processedData.slice(start, end);
458
+ logger.info("\u2705 [UniversalExportService] \u5206\u9875\u5E94\u7528\u5B8C\u6210:", {
459
+ page,
460
+ pageSize,
461
+ start,
462
+ end,
463
+ resultLength: processedData.length
464
+ });
465
+ }
466
+ if (config.maxRows && processedData.length > config.maxRows) {
467
+ logger.info("\u{1F4CF} [UniversalExportService] \u5E94\u7528\u884C\u6570\u9650\u5236...");
468
+ processedData = processedData.slice(0, config.maxRows);
469
+ logger.info("\u2705 [UniversalExportService] \u884C\u6570\u9650\u5236\u5E94\u7528\u5B8C\u6210:", {
470
+ maxRows: config.maxRows,
471
+ resultLength: processedData.length
472
+ });
473
+ }
474
+ logger.info("\u2705 [UniversalExportService] processData \u6267\u884C\u5B8C\u6210:", {
475
+ originalLength: data.length,
476
+ finalLength: processedData.length
477
+ });
478
+ return processedData;
479
+ }
480
+ /**
481
+ * 应用过滤器
482
+ */
483
+ applyFilters(data, filters) {
484
+ return data.filter((item) => {
485
+ return filters.every((filter) => {
486
+ const value = this.getNestedValue(item, filter.field);
487
+ switch (filter.operator) {
488
+ case "eq":
489
+ return value === filter.value;
490
+ case "ne":
491
+ return value !== filter.value;
492
+ case "gt":
493
+ return value > filter.value;
494
+ case "gte":
495
+ return value >= filter.value;
496
+ case "lt":
497
+ return value < filter.value;
498
+ case "lte":
499
+ return value <= filter.value;
500
+ case "contains":
501
+ return String(value).includes(String(filter.value));
502
+ case "startsWith":
503
+ return String(value).startsWith(String(filter.value));
504
+ case "endsWith":
505
+ return String(value).endsWith(String(filter.value));
506
+ case "in":
507
+ return Array.isArray(filter.value) && filter.value.includes(value);
508
+ case "notIn":
509
+ return Array.isArray(filter.value) && !filter.value.includes(value);
510
+ default:
511
+ return true;
512
+ }
513
+ });
514
+ });
515
+ }
516
+ /**
517
+ * 应用排序
518
+ */
519
+ applySorting(data, sortBy) {
520
+ return data.sort((a, b) => {
521
+ for (const sort of sortBy) {
522
+ const aValue = this.getNestedValue(a, sort.field);
523
+ const bValue = this.getNestedValue(b, sort.field);
524
+ if (aValue < bValue) {
525
+ return sort.direction === "asc" ? -1 : 1;
526
+ }
527
+ if (aValue > bValue) {
528
+ return sort.direction === "asc" ? 1 : -1;
529
+ }
530
+ }
531
+ return 0;
532
+ });
533
+ }
534
+ /**
535
+ * 获取嵌套值
536
+ */
537
+ getNestedValue(obj, path) {
538
+ return path.split(".").reduce((current, key) => {
539
+ return current && current[key] !== void 0 ? current[key] : null;
540
+ }, obj);
541
+ }
542
+ /**
543
+ * 过滤掉所有行都为空值的字段
544
+ */
545
+ filterEmptyFields(data, fields) {
546
+ const filteredFields = fields.filter((field) => {
547
+ const forceKeepFields = ["pickupMethod", "notes", "adminNotes"];
548
+ if (forceKeepFields.includes(field.key)) {
549
+ logger.info(`\u{1F527} [UniversalExportService] \u5F3A\u5236\u4FDD\u7559\u5B57\u6BB5 "${field.key}" (${field.label})`);
550
+ return true;
551
+ }
552
+ const hasValue = data.some((item) => {
553
+ const value = this.getNestedValue(item, field.key);
554
+ return value !== null && value !== void 0 && value !== "";
555
+ });
556
+ if (!hasValue) {
557
+ logger.info(
558
+ `\u{1F50D} [UniversalExportService] \u5B57\u6BB5 "${field.key}" (${field.label}) \u88AB\u8FC7\u6EE4\u6389 - \u6240\u6709\u884C\u90FD\u4E3A\u7A7A\u503C`
559
+ );
560
+ }
561
+ return hasValue;
562
+ });
563
+ logger.info("\u{1F4CA} [UniversalExportService] \u5B57\u6BB5\u8FC7\u6EE4\u7ED3\u679C:", {
564
+ \u539F\u59CB\u5B57\u6BB5\u6570: fields.length,
565
+ \u8FC7\u6EE4\u540E\u5B57\u6BB5\u6570: filteredFields.length,
566
+ \u88AB\u8FC7\u6EE4\u7684\u5B57\u6BB5: fields.filter((f) => !filteredFields.includes(f)).map((f) => f.key),
567
+ \u4FDD\u7559\u7684\u5B57\u6BB5: filteredFields.map((f) => f.key)
568
+ });
569
+ return filteredFields;
570
+ }
571
+ /**
572
+ * 生成文件
573
+ */
574
+ async generateFile(data, config, request, exportId) {
575
+ const startTime = /* @__PURE__ */ new Date();
576
+ const enabledFields = config.fields.filter((f) => f.enabled);
577
+ logger.info("\u{1F4C4} [UniversalExportService] generateFile \u5F00\u59CB\u6267\u884C:", {
578
+ dataLength: data.length,
579
+ enabledFieldsCount: enabledFields.length,
580
+ format: config.format,
581
+ enabledFields: enabledFields.map((f) => ({ key: f.key, label: f.label }))
582
+ });
583
+ try {
584
+ let content;
585
+ let fileName;
586
+ switch (config.format) {
587
+ case "csv":
588
+ logger.info("\u{1F4CA} [UniversalExportService] \u751F\u6210CSV\u683C\u5F0F...");
589
+ content = this.generateCSV(data, enabledFields, config);
590
+ fileName = this.generateFileName(
591
+ request.customFileName || config.fileNameTemplate,
592
+ "csv"
593
+ );
594
+ logger.info("\u2705 [UniversalExportService] CSV\u751F\u6210\u5B8C\u6210:", {
595
+ contentLength: content.length,
596
+ fileName
597
+ });
598
+ break;
599
+ case "excel":
600
+ logger.info("\u{1F4CA} [UniversalExportService] \u751F\u6210Excel\u683C\u5F0F...");
601
+ const excelBuffer = this.generateExcel(data, enabledFields, config);
602
+ fileName = this.generateFileName(
603
+ request.customFileName || config.fileNameTemplate,
604
+ "xlsx"
605
+ );
606
+ logger.info("\u2705 [UniversalExportService] Excel\u751F\u6210\u5B8C\u6210:", {
607
+ bufferLength: excelBuffer.byteLength,
608
+ fileName
609
+ });
610
+ const excelBlob = new Blob([excelBuffer], { type: this.getMimeType(config.format) });
611
+ const endTime2 = /* @__PURE__ */ new Date();
612
+ const duration2 = endTime2.getTime() - startTime.getTime();
613
+ return {
614
+ exportId,
615
+ fileName,
616
+ fileSize: excelBlob.size,
617
+ fileBlob: excelBlob,
618
+ exportedRows: data.length,
619
+ startTime,
620
+ endTime: endTime2,
621
+ duration: duration2,
622
+ statistics: {
623
+ totalRows: data.length,
624
+ filteredRows: data.length,
625
+ exportedRows: data.length,
626
+ skippedRows: 0
627
+ }
628
+ };
629
+ case "json":
630
+ logger.info("\u{1F4C4} [UniversalExportService] \u751F\u6210JSON\u683C\u5F0F...");
631
+ content = this.generateJSON(data, enabledFields);
632
+ fileName = this.generateFileName(
633
+ request.customFileName || config.fileNameTemplate,
634
+ "json"
635
+ );
636
+ logger.info("\u2705 [UniversalExportService] JSON\u751F\u6210\u5B8C\u6210:", {
637
+ contentLength: content.length,
638
+ fileName
639
+ });
640
+ break;
641
+ default:
642
+ console.error("\u274C [UniversalExportService] \u4E0D\u652F\u6301\u7684\u683C\u5F0F:", config.format);
643
+ throw new ExportFileError(`\u4E0D\u652F\u6301\u7684\u5BFC\u51FA\u683C\u5F0F: ${config.format}`);
644
+ }
645
+ const blob = new Blob([content], { type: this.getMimeType(config.format) });
646
+ if (blob.size > this.config.maxFileSize) {
647
+ throw new ExportFileError(`\u6587\u4EF6\u5927\u5C0F\u8D85\u8FC7\u9650\u5236: ${blob.size} > ${this.config.maxFileSize}`);
648
+ }
649
+ const endTime = /* @__PURE__ */ new Date();
650
+ const duration = endTime.getTime() - startTime.getTime();
651
+ return {
652
+ exportId,
653
+ fileName,
654
+ fileSize: blob.size,
655
+ fileBlob: blob,
656
+ exportedRows: data.length,
657
+ startTime,
658
+ endTime,
659
+ duration,
660
+ statistics: {
661
+ totalRows: data.length,
662
+ filteredRows: data.length,
663
+ exportedRows: data.length,
664
+ skippedRows: 0
665
+ }
666
+ };
667
+ } catch (error) {
668
+ throw new ExportFileError(
669
+ `\u751F\u6210\u6587\u4EF6\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`,
670
+ { originalError: error }
671
+ );
672
+ }
673
+ }
674
+ /**
675
+ * 生成CSV内容
676
+ */
677
+ generateCSV(data, fields, config) {
678
+ logger.info("\u{1F4CA} [UniversalExportService] generateCSV \u5F00\u59CB\u6267\u884C:", {
679
+ dataLength: data.length,
680
+ fieldsCount: fields.length,
681
+ includeHeader: config.includeHeader,
682
+ delimiter: config.delimiter,
683
+ addBOM: config.addBOM
684
+ });
685
+ const lines = [];
686
+ if (config.addBOM) {
687
+ lines.push("\uFEFF");
688
+ logger.info("\u{1F4DD} [UniversalExportService] \u6DFB\u52A0BOM");
689
+ }
690
+ const nonEmptyFields = this.filterEmptyFields(data, fields);
691
+ logger.info("\u{1F4CA} [UniversalExportService] \u8FC7\u6EE4\u7A7A\u5B57\u6BB5:", {
692
+ originalFieldsCount: fields.length,
693
+ nonEmptyFieldsCount: nonEmptyFields.length,
694
+ removedFields: fields.filter((f) => !nonEmptyFields.includes(f)).map((f) => f.key)
695
+ });
696
+ if (config.includeHeader) {
697
+ const headers = nonEmptyFields.map((f) => this.escapeCSVField(f.label));
698
+ lines.push(headers.join(config.delimiter));
699
+ logger.info("\u{1F4CB} [UniversalExportService] \u6DFB\u52A0\u8868\u5934:", headers);
700
+ }
701
+ logger.info("\u{1F4CA} [UniversalExportService] \u5F00\u59CB\u5904\u7406\u6570\u636E\u884C...");
702
+ for (let i = 0; i < data.length; i++) {
703
+ const item = data[i];
704
+ if (i === 0) {
705
+ logger.info("\u{1F4CA} [UniversalExportService] \u7B2C\u4E00\u884C\u6570\u636E\u793A\u4F8B:", item);
706
+ }
707
+ const row = nonEmptyFields.map((field) => {
708
+ if (item.__isGroupHeader) {
709
+ return this.escapeCSVField(item[field.key] || "");
710
+ }
711
+ let value = this.getNestedValue(item, field.key);
712
+ if (field.formatter) {
713
+ value = field.formatter(value);
714
+ } else if (DEFAULT_FORMATTERS[field.type]) {
715
+ value = DEFAULT_FORMATTERS[field.type](value);
716
+ } else {
717
+ value = String(value || "");
718
+ }
719
+ return this.escapeCSVField(value);
720
+ });
721
+ lines.push(row.join(config.delimiter));
722
+ if (i === 0) {
723
+ logger.info("\u{1F4CA} [UniversalExportService] \u7B2C\u4E00\u884C\u5904\u7406\u7ED3\u679C:", row);
724
+ }
725
+ }
726
+ const result = lines.join("\n");
727
+ logger.info("\u2705 [UniversalExportService] CSV\u751F\u6210\u5B8C\u6210:", {
728
+ totalLines: lines.length,
729
+ resultLength: result.length
730
+ });
731
+ return result;
732
+ }
733
+ /**
734
+ * 生成JSON内容
735
+ */
736
+ generateJSON(data, fields) {
737
+ const processedData = data.map((item) => {
738
+ const processed = {};
739
+ for (const field of fields) {
740
+ let value = this.getNestedValue(item, field.key);
741
+ if (field.formatter) {
742
+ value = field.formatter(value);
743
+ } else if (DEFAULT_FORMATTERS[field.type]) {
744
+ value = DEFAULT_FORMATTERS[field.type](value);
745
+ }
746
+ processed[field.key] = value;
747
+ }
748
+ return processed;
749
+ });
750
+ return JSON.stringify(processedData, null, 2);
751
+ }
752
+ /**
753
+ * 转义CSV字段
754
+ */
755
+ escapeCSVField(value) {
756
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
757
+ return `"${value.replace(/"/g, '""')}"`;
758
+ }
759
+ return value;
760
+ }
761
+ /**
762
+ * 生成文件名
763
+ */
764
+ generateFileName(template, extension) {
765
+ const now = /* @__PURE__ */ new Date();
766
+ const dateStr = now.toISOString().split("T")[0];
767
+ const timeStr = now.toTimeString().split(" ")[0].replace(/:/g, "-");
768
+ return template.replace("{date}", dateStr).replace("{time}", timeStr).replace("{timestamp}", now.getTime().toString()) + `.${extension}`;
769
+ }
770
+ /**
771
+ * 获取MIME类型
772
+ */
773
+ getMimeType(format) {
774
+ switch (format) {
775
+ case "csv":
776
+ return "text/csv; charset=utf-8";
777
+ case "json":
778
+ return "application/json; charset=utf-8";
779
+ case "excel":
780
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
781
+ default:
782
+ return "application/octet-stream";
783
+ }
784
+ }
785
+ /**
786
+ * 触发事件
787
+ */
788
+ emitEvent(event) {
789
+ const listeners = this.eventListeners.get(event.type);
790
+ if (listeners) {
791
+ listeners.forEach((listener) => {
792
+ try {
793
+ listener(event);
794
+ } catch (error) {
795
+ console.error("\u4E8B\u4EF6\u76D1\u542C\u5668\u6267\u884C\u5931\u8D25:", error);
796
+ }
797
+ });
798
+ }
799
+ }
800
+ // ============= 分组相关方法 =============
801
+ /**
802
+ * 应用分组
803
+ */
804
+ applyGrouping(data, groupingConfig) {
805
+ logger.info("\u{1F4CA} [UniversalExportService] applyGrouping \u5F00\u59CB\u6267\u884C:", {
806
+ dataLength: data.length,
807
+ groupingFields: groupingConfig.fields.map((f) => f.key),
808
+ preserveOrder: groupingConfig.preserveOrder
809
+ });
810
+ if (!groupingConfig.fields || groupingConfig.fields.length === 0) {
811
+ return data;
812
+ }
813
+ const grouped = this.groupDataByFields(data, groupingConfig);
814
+ const result = this.processGroupedData(grouped, groupingConfig);
815
+ logger.info("\u2705 [UniversalExportService] applyGrouping \u6267\u884C\u5B8C\u6210:", {
816
+ originalLength: data.length,
817
+ groupedLength: result.length
818
+ });
819
+ return result;
820
+ }
821
+ /**
822
+ * 按字段分组数据
823
+ */
824
+ groupDataByFields(data, groupingConfig) {
825
+ const groups = /* @__PURE__ */ new Map();
826
+ for (const item of data) {
827
+ const groupKey = this.generateGroupKey(item, groupingConfig.fields);
828
+ if (!groups.has(groupKey)) {
829
+ groups.set(groupKey, []);
830
+ }
831
+ groups.get(groupKey).push(item);
832
+ }
833
+ return groups;
834
+ }
835
+ /**
836
+ * 生成分组键
837
+ */
838
+ generateGroupKey(item, groupingFields) {
839
+ const keyParts = groupingFields.map((field) => {
840
+ const value = this.getNestedValue(item, field.key);
841
+ if (value === null || value === void 0 || value === "") {
842
+ return "__NULL__";
843
+ }
844
+ return String(value);
845
+ });
846
+ return keyParts.join("|");
847
+ }
848
+ /**
849
+ * 处理分组后的数据
850
+ */
851
+ processGroupedData(groups, groupingConfig) {
852
+ const result = [];
853
+ for (const [groupKey, groupItems] of groups) {
854
+ if (groupItems.length === 0) continue;
855
+ const groupValues = groupKey.split("|");
856
+ const processedGroup = this.processGroup(groupItems, groupingConfig, groupValues);
857
+ result.push(...processedGroup);
858
+ }
859
+ return result;
860
+ }
861
+ /**
862
+ * 处理单个分组
863
+ */
864
+ processGroup(groupItems, groupingConfig, groupValues) {
865
+ const result = [];
866
+ const showGroupHeader = groupingConfig.fields.some((f) => f.showGroupHeader);
867
+ if (showGroupHeader) {
868
+ const groupHeader = this.createGroupHeader(groupValues, groupingConfig.fields);
869
+ result.push(groupHeader);
870
+ }
871
+ if (groupingConfig.fields.length > 1) {
872
+ result.push(...this.processMultiFieldMergeMode(groupItems, groupingConfig.fields));
873
+ } else {
874
+ const primaryGroupField = groupingConfig.fields[0];
875
+ switch (primaryGroupField.mode) {
876
+ case "merge":
877
+ result.push(...this.processMergeMode(groupItems, primaryGroupField));
878
+ break;
879
+ case "separate":
880
+ result.push(...groupItems);
881
+ break;
882
+ case "nested":
883
+ result.push(...this.processNestedMode(groupItems, groupingConfig));
884
+ break;
885
+ default:
886
+ result.push(...groupItems);
887
+ }
888
+ }
889
+ return result;
890
+ }
891
+ /**
892
+ * 创建分组头行
893
+ */
894
+ createGroupHeader(groupValues, groupingFields) {
895
+ const header = { __isGroupHeader: true };
896
+ groupingFields.forEach((field, index) => {
897
+ const value = groupValues[index] === "__NULL__" ? "" : groupValues[index];
898
+ const template = field.groupHeaderTemplate || `${field.label}: {value}`;
899
+ header[field.key] = template.replace("{value}", value);
900
+ });
901
+ return header;
902
+ }
903
+ /**
904
+ * 处理合并模式
905
+ */
906
+ processMergeMode(groupItems, groupField) {
907
+ if (groupItems.length === 0) return [];
908
+ const result = [];
909
+ const firstItem = { ...groupItems[0] };
910
+ firstItem.__groupSize = groupItems.length;
911
+ firstItem.__isGroupFirst = true;
912
+ result.push(firstItem);
913
+ for (let i = 1; i < groupItems.length; i++) {
914
+ const item = { ...groupItems[i] };
915
+ item[groupField.key] = "";
916
+ item.__isGroupChild = true;
917
+ item.__groupIndex = i;
918
+ result.push(item);
919
+ }
920
+ return result;
921
+ }
922
+ /**
923
+ * 处理多字段合并模式
924
+ */
925
+ processMultiFieldMergeMode(groupItems, groupFields) {
926
+ if (groupItems.length === 0) return [];
927
+ const result = [];
928
+ const firstItem = { ...groupItems[0] };
929
+ firstItem.__groupSize = groupItems.length;
930
+ firstItem.__isGroupFirst = true;
931
+ groupFields.forEach((field) => {
932
+ firstItem[`__${field.key}_groupSize`] = groupItems.length;
933
+ firstItem[`__${field.key}_isGroupFirst`] = true;
934
+ });
935
+ result.push(firstItem);
936
+ logger.info("\u{1F517} [UniversalExportService] \u5904\u7406\u591A\u5B57\u6BB5\u5408\u5E76\u6A21\u5F0F:", {
937
+ groupItemsLength: groupItems.length,
938
+ groupFields: groupFields.map((f) => f.key),
939
+ firstItem
940
+ });
941
+ for (let i = 1; i < groupItems.length; i++) {
942
+ const item = { ...groupItems[i] };
943
+ groupFields.forEach((field) => {
944
+ item[field.key] = "";
945
+ });
946
+ item.__isGroupChild = true;
947
+ item.__groupIndex = i;
948
+ result.push(item);
949
+ }
950
+ return result;
951
+ }
952
+ /**
953
+ * 处理嵌套模式
954
+ */
955
+ processNestedMode(groupItems, groupingConfig) {
956
+ if (groupingConfig.fields.length === 1) {
957
+ return this.processMergeMode(groupItems, groupingConfig.fields[0]);
958
+ }
959
+ const subGroupingConfig = {
960
+ ...groupingConfig,
961
+ fields: groupingConfig.fields.slice(1)
962
+ };
963
+ return this.applyGrouping(groupItems, subGroupingConfig);
964
+ }
965
+ /**
966
+ * 统计分组数量
967
+ */
968
+ countGroups(data) {
969
+ const groupHeaders = data.filter((item) => item.__isGroupHeader);
970
+ const groupFirsts = data.filter((item) => item.__isGroupFirst);
971
+ return Math.max(groupHeaders.length, groupFirsts.length);
972
+ }
973
+ /**
974
+ * 生成Excel文件
975
+ */
976
+ generateExcel(data, fields, config) {
977
+ logger.info("\u{1F4CA} [UniversalExportService] generateExcel \u5F00\u59CB\u6267\u884C:", {
978
+ dataLength: data.length,
979
+ fieldsCount: fields.length,
980
+ hasGrouping: !!(config.grouping && config.grouping.enabled)
981
+ });
982
+ const workbook = XLSX.utils.book_new();
983
+ const nonEmptyFields = this.filterEmptyFields(data, fields);
984
+ const worksheetData = this.prepareExcelData(data, nonEmptyFields, config);
985
+ const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
986
+ if (config.grouping && config.grouping.enabled) {
987
+ this.applyExcelGrouping(
988
+ worksheet,
989
+ data,
990
+ nonEmptyFields,
991
+ config.grouping,
992
+ config.includeHeader
993
+ );
994
+ }
995
+ this.setExcelColumnWidths(worksheet, nonEmptyFields);
996
+ this.applyExcelDataStyles(worksheet, config.includeHeader);
997
+ XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
998
+ const excelBuffer = XLSX.write(workbook, {
999
+ bookType: "xlsx",
1000
+ type: "array",
1001
+ cellStyles: true
1002
+ });
1003
+ logger.info("\u2705 [UniversalExportService] generateExcel \u6267\u884C\u5B8C\u6210");
1004
+ return excelBuffer;
1005
+ }
1006
+ /**
1007
+ * 准备Excel数据
1008
+ */
1009
+ prepareExcelData(data, fields, config) {
1010
+ const result = [];
1011
+ logger.info("\u{1F4CA} [UniversalExportService] \u51C6\u5907Excel\u6570\u636E:", {
1012
+ dataLength: data.length,
1013
+ fieldsCount: fields.length,
1014
+ includeHeader: config.includeHeader,
1015
+ hasGrouping: !!(config.grouping && config.grouping.enabled)
1016
+ });
1017
+ if (config.includeHeader) {
1018
+ const headers = fields.map((field) => field.label);
1019
+ result.push(headers);
1020
+ logger.info("\u{1F4CB} [UniversalExportService] \u6DFB\u52A0\u8868\u5934:", headers);
1021
+ }
1022
+ for (let i = 0; i < data.length; i++) {
1023
+ const item = data[i];
1024
+ const row = fields.map((field) => {
1025
+ if (item.__isGroupHeader) {
1026
+ return item[field.key] || "";
1027
+ }
1028
+ let value = this.getNestedValue(item, field.key);
1029
+ if (field.formatter) {
1030
+ value = field.formatter(value);
1031
+ } else if (DEFAULT_FORMATTERS[field.type]) {
1032
+ value = DEFAULT_FORMATTERS[field.type](value);
1033
+ } else {
1034
+ value = String(value || "");
1035
+ }
1036
+ return value;
1037
+ });
1038
+ result.push(row);
1039
+ if (i === 0) {
1040
+ logger.info("\u{1F4CA} [UniversalExportService] \u7B2C\u4E00\u884C\u6570\u636E\u793A\u4F8B:", row);
1041
+ }
1042
+ }
1043
+ logger.info("\u2705 [UniversalExportService] Excel\u6570\u636E\u51C6\u5907\u5B8C\u6210:", {
1044
+ totalRows: result.length,
1045
+ headerRows: config.includeHeader ? 1 : 0,
1046
+ dataRows: result.length - (config.includeHeader ? 1 : 0)
1047
+ });
1048
+ return result;
1049
+ }
1050
+ /**
1051
+ * 应用Excel分组和合并单元格
1052
+ */
1053
+ applyExcelGrouping(worksheet, data, fields, groupingConfig, includeHeader = true) {
1054
+ if (!worksheet["!merges"]) {
1055
+ worksheet["!merges"] = [];
1056
+ }
1057
+ const headerOffset = includeHeader ? 1 : 0;
1058
+ let currentRow = headerOffset;
1059
+ logger.info("\u{1F4CA} [UniversalExportService] \u5F00\u59CB\u5904\u7406Excel\u5206\u7EC4\u548C\u5408\u5E76\u5355\u5143\u683C:", {
1060
+ dataLength: data.length,
1061
+ headerOffset,
1062
+ groupingFields: groupingConfig.fields.map((f) => ({ key: f.key, mergeCells: f.mergeCells }))
1063
+ });
1064
+ for (let i = 0; i < data.length; i++) {
1065
+ const item = data[i];
1066
+ if (item.__isGroupFirst && item.__groupSize > 1) {
1067
+ logger.info("\u{1F517} [UniversalExportService] \u5904\u7406\u5206\u7EC4\u5408\u5E76:", {
1068
+ row: currentRow,
1069
+ groupSize: item.__groupSize,
1070
+ item
1071
+ });
1072
+ groupingConfig.fields.forEach((groupField) => {
1073
+ if (groupField.mergeCells) {
1074
+ const fieldIndex = fields.findIndex((f) => f.key === groupField.key);
1075
+ if (fieldIndex >= 0) {
1076
+ const groupSize = item[`__${groupField.key}_groupSize`] || item.__groupSize;
1077
+ const mergeRange = {
1078
+ s: { r: currentRow, c: fieldIndex },
1079
+ // 开始行列
1080
+ e: { r: currentRow + groupSize - 1, c: fieldIndex }
1081
+ // 结束行列
1082
+ };
1083
+ logger.info("\u{1F4CA} [UniversalExportService] \u6DFB\u52A0\u5408\u5E76\u533A\u57DF:", {
1084
+ field: groupField.key,
1085
+ fieldIndex,
1086
+ groupSize,
1087
+ mergeRange
1088
+ });
1089
+ worksheet["!merges"].push(mergeRange);
1090
+ const startCellAddress = XLSX.utils.encode_cell(mergeRange.s);
1091
+ if (worksheet[startCellAddress]) {
1092
+ worksheet[startCellAddress].s = {
1093
+ ...worksheet[startCellAddress].s,
1094
+ alignment: { horizontal: "center", vertical: "middle" },
1095
+ fill: { fgColor: { rgb: "F2F2F2" } },
1096
+ border: {
1097
+ top: { style: "thin", color: { rgb: "000000" } },
1098
+ bottom: { style: "thin", color: { rgb: "000000" } },
1099
+ left: { style: "thin", color: { rgb: "000000" } },
1100
+ right: { style: "thin", color: { rgb: "000000" } }
1101
+ }
1102
+ };
1103
+ }
1104
+ }
1105
+ }
1106
+ });
1107
+ }
1108
+ currentRow++;
1109
+ }
1110
+ logger.info("\u2705 [UniversalExportService] Excel\u5206\u7EC4\u548C\u5408\u5E76\u5355\u5143\u683C\u5904\u7406\u5B8C\u6210:", {
1111
+ totalMerges: worksheet["!merges"]?.length || 0
1112
+ });
1113
+ }
1114
+ /**
1115
+ * 设置Excel列宽和样式
1116
+ */
1117
+ setExcelColumnWidths(worksheet, fields) {
1118
+ const colWidths = fields.map((field) => ({
1119
+ wch: field.width || 15
1120
+ // 默认宽度15字符
1121
+ }));
1122
+ worksheet["!cols"] = colWidths;
1123
+ if (worksheet["!ref"]) {
1124
+ const range = XLSX.utils.decode_range(worksheet["!ref"]);
1125
+ for (let col = range.s.c; col <= range.e.c; col++) {
1126
+ const cellAddress = XLSX.utils.encode_cell({ r: 0, c: col });
1127
+ if (worksheet[cellAddress]) {
1128
+ worksheet[cellAddress].s = {
1129
+ font: { bold: true, color: { rgb: "FFFFFF" } },
1130
+ fill: { fgColor: { rgb: "4472C4" } },
1131
+ alignment: { horizontal: "center", vertical: "center" },
1132
+ border: {
1133
+ top: { style: "thin", color: { rgb: "000000" } },
1134
+ bottom: { style: "thin", color: { rgb: "000000" } },
1135
+ left: { style: "thin", color: { rgb: "000000" } },
1136
+ right: { style: "thin", color: { rgb: "000000" } }
1137
+ }
1138
+ };
1139
+ }
1140
+ }
1141
+ }
1142
+ logger.info("\u2705 [UniversalExportService] Excel\u5217\u5BBD\u548C\u6837\u5F0F\u8BBE\u7F6E\u5B8C\u6210:", {
1143
+ columnsCount: colWidths.length,
1144
+ columnWidths: colWidths.map((col, index) => ({ field: fields[index]?.key, width: col.wch }))
1145
+ });
1146
+ }
1147
+ /**
1148
+ * 为Excel数据单元格应用样式
1149
+ */
1150
+ applyExcelDataStyles(worksheet, includeHeader = true) {
1151
+ if (!worksheet["!ref"]) return;
1152
+ const range = XLSX.utils.decode_range(worksheet["!ref"]);
1153
+ const startRow = includeHeader ? 1 : 0;
1154
+ logger.info("\u{1F3A8} [UniversalExportService] \u5F00\u59CB\u5E94\u7528Excel\u6570\u636E\u6837\u5F0F:", {
1155
+ totalRows: range.e.r + 1,
1156
+ totalCols: range.e.c + 1,
1157
+ startRow
1158
+ });
1159
+ for (let row = startRow; row <= range.e.r; row++) {
1160
+ for (let col = range.s.c; col <= range.e.c; col++) {
1161
+ const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
1162
+ if (worksheet[cellAddress]) {
1163
+ const existingStyle = worksheet[cellAddress].s || {};
1164
+ worksheet[cellAddress].s = {
1165
+ ...existingStyle,
1166
+ border: {
1167
+ top: { style: "thin", color: { rgb: "CCCCCC" } },
1168
+ bottom: { style: "thin", color: { rgb: "CCCCCC" } },
1169
+ left: { style: "thin", color: { rgb: "CCCCCC" } },
1170
+ right: { style: "thin", color: { rgb: "CCCCCC" } }
1171
+ },
1172
+ alignment: {
1173
+ ...existingStyle.alignment,
1174
+ vertical: "center"
1175
+ }
1176
+ };
1177
+ }
1178
+ }
1179
+ }
1180
+ logger.info("\u2705 [UniversalExportService] Excel\u6570\u636E\u6837\u5F0F\u5E94\u7528\u5B8C\u6210");
1181
+ }
1182
+ };
2
1183
 
3
1184
  // src/universalExport/server/factory.ts
4
1185
  function createExportServiceConfig(options = {}) {
@@ -222,9 +1403,9 @@ function escapeCsvValue(value) {
222
1403
  return value;
223
1404
  }
224
1405
  function generateUniqueFilename(prefix, extension) {
225
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5);
1406
+ const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5);
226
1407
  const random = Math.random().toString(36).substring(2, 8);
227
- return `${prefix}_${timestamp}_${random}.${extension}`;
1408
+ return `${prefix}_${timestamp2}_${random}.${extension}`;
228
1409
  }
229
1410
  function calculateProgress(processed, total) {
230
1411
  if (total === 0) return 0;
@@ -236,7 +1417,178 @@ function estimateRemainingTime(processed, total, elapsedMs) {
236
1417
  const remainingRows = total - processed;
237
1418
  return Math.round(remainingRows * avgTimePerRow / 1e3);
238
1419
  }
1420
+ var exportConfigs = pgTable("ExportConfig", {
1421
+ /** 主键ID */
1422
+ id: text("id").primaryKey().$defaultFn(() => `export_config_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`),
1423
+ /** 配置名称 */
1424
+ name: text("name").notNull(),
1425
+ /** 配置描述 */
1426
+ description: text("description"),
1427
+ /** 导出格式:'csv' | 'excel' | 'json' */
1428
+ format: text("format").notNull(),
1429
+ /** 导出字段配置(JSON数组) */
1430
+ fields: jsonb("fields").notNull(),
1431
+ // ExportField[]
1432
+ /** 分组配置 */
1433
+ grouping: jsonb("grouping"),
1434
+ // GroupingConfig
1435
+ /** 文件名模板 */
1436
+ fileNameTemplate: text("fileNameTemplate").notNull(),
1437
+ /** 是否包含表头 */
1438
+ includeHeader: boolean("includeHeader").default(true).notNull(),
1439
+ /** 分隔符(用于CSV格式) */
1440
+ delimiter: text("delimiter").default(",").notNull(),
1441
+ /** 编码格式 */
1442
+ encoding: text("encoding").default("utf-8").notNull(),
1443
+ /** 是否添加BOM(用于Excel打开UTF-8 CSV) */
1444
+ addBOM: boolean("addBOM").default(true).notNull(),
1445
+ /** 最大导出行数 */
1446
+ maxRows: integer("maxRows"),
1447
+ /** 模块ID */
1448
+ moduleId: text("moduleId").notNull(),
1449
+ /** 业务ID */
1450
+ businessId: text("businessId"),
1451
+ /** 创建者ID */
1452
+ createdBy: text("createdBy"),
1453
+ /** 创建时间 */
1454
+ createdAt: timestamp("createdAt", { precision: 3, mode: "date" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
1455
+ /** 更新时间 */
1456
+ updatedAt: timestamp("updatedAt", { precision: 3, mode: "date" }).default(sql`CURRENT_TIMESTAMP`).notNull()
1457
+ });
1458
+ var exportHistory = pgTable("ExportHistory", {
1459
+ /** 主键ID */
1460
+ id: text("id").primaryKey().$defaultFn(() => `export_history_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`),
1461
+ /** 关联的配置ID */
1462
+ configId: text("configId").notNull(),
1463
+ /** 导出的文件名 */
1464
+ fileName: text("fileName").notNull(),
1465
+ /** 文件大小(字节) */
1466
+ fileSize: integer("fileSize").notNull(),
1467
+ /** 导出的行数 */
1468
+ exportedRows: integer("exportedRows").notNull(),
1469
+ /** 导出状态:'pending' | 'processing' | 'completed' | 'failed' */
1470
+ status: text("status").notNull(),
1471
+ /** 错误信息(如果失败) */
1472
+ error: text("error"),
1473
+ /** 导出耗时(毫秒) */
1474
+ duration: integer("duration"),
1475
+ /** 开始时间 */
1476
+ startTime: timestamp("startTime", { precision: 3, mode: "date" }).notNull(),
1477
+ /** 结束时间 */
1478
+ endTime: timestamp("endTime", { precision: 3, mode: "date" }),
1479
+ /** 创建者ID */
1480
+ createdBy: text("createdBy"),
1481
+ /** 创建时间 */
1482
+ createdAt: timestamp("createdAt", { precision: 3, mode: "date" }).default(sql`CURRENT_TIMESTAMP`).notNull()
1483
+ });
1484
+ var exportConfigsRelations = relations(exportConfigs, ({ many }) => ({
1485
+ history: many(exportHistory)
1486
+ }));
1487
+ var exportHistoryRelations = relations(exportHistory, ({ one }) => ({
1488
+ config: one(exportConfigs, {
1489
+ fields: [exportHistory.configId],
1490
+ references: [exportConfigs.id]
1491
+ })
1492
+ }));
1493
+ var ExportConfigDatabaseService = class {
1494
+ constructor(db) {
1495
+ this.db = db;
1496
+ }
1497
+ /**
1498
+ * 创建导出配置
1499
+ */
1500
+ async createConfig(config) {
1501
+ const id = `export_config_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1502
+ const now = /* @__PURE__ */ new Date();
1503
+ const [newConfig] = await this.db.insert(exportConfigs).values({
1504
+ id,
1505
+ ...config,
1506
+ createdAt: now,
1507
+ updatedAt: now
1508
+ }).returning();
1509
+ return newConfig;
1510
+ }
1511
+ /**
1512
+ * 根据ID获取配置
1513
+ */
1514
+ async getConfigById(id) {
1515
+ const [config] = await this.db.select().from(exportConfigs).where(eq(exportConfigs.id, id)).limit(1);
1516
+ return config || null;
1517
+ }
1518
+ /**
1519
+ * 根据模块和业务ID获取配置列表
1520
+ */
1521
+ async getConfigsByModule(moduleId, businessId) {
1522
+ const whereConditions = businessId ? and(eq(exportConfigs.moduleId, moduleId), eq(exportConfigs.businessId, businessId)) : eq(exportConfigs.moduleId, moduleId);
1523
+ return await this.db.select().from(exportConfigs).where(whereConditions).orderBy(desc(exportConfigs.updatedAt));
1524
+ }
1525
+ /**
1526
+ * 更新配置
1527
+ */
1528
+ async updateConfig(id, updates) {
1529
+ const [updatedConfig] = await this.db.update(exportConfigs).set({
1530
+ ...updates,
1531
+ updatedAt: /* @__PURE__ */ new Date()
1532
+ }).where(eq(exportConfigs.id, id)).returning();
1533
+ return updatedConfig || null;
1534
+ }
1535
+ /**
1536
+ * 删除配置
1537
+ */
1538
+ async deleteConfig(id) {
1539
+ await this.db.delete(exportConfigs).where(eq(exportConfigs.id, id));
1540
+ return true;
1541
+ }
1542
+ /**
1543
+ * 根据用户ID获取配置列表
1544
+ */
1545
+ async getConfigsByUser(userId) {
1546
+ return await this.db.select().from(exportConfigs).where(eq(exportConfigs.createdBy, userId)).orderBy(desc(exportConfigs.updatedAt));
1547
+ }
1548
+ };
1549
+ var ExportHistoryDatabaseService = class {
1550
+ constructor(db) {
1551
+ this.db = db;
1552
+ }
1553
+ /**
1554
+ * 创建导出历史记录
1555
+ */
1556
+ async createHistory(history) {
1557
+ const id = `export_history_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1558
+ const [newHistory] = await this.db.insert(exportHistory).values({
1559
+ id,
1560
+ ...history,
1561
+ createdAt: /* @__PURE__ */ new Date()
1562
+ }).returning();
1563
+ return newHistory;
1564
+ }
1565
+ /**
1566
+ * 根据配置ID获取历史记录
1567
+ */
1568
+ async getHistoryByConfigId(configId) {
1569
+ return await this.db.select().from(exportHistory).where(eq(exportHistory.configId, configId)).orderBy(desc(exportHistory.createdAt));
1570
+ }
1571
+ /**
1572
+ * 根据用户ID获取历史记录
1573
+ */
1574
+ async getHistoryByUser(userId) {
1575
+ return await this.db.select().from(exportHistory).where(eq(exportHistory.createdBy, userId)).orderBy(desc(exportHistory.createdAt));
1576
+ }
1577
+ /**
1578
+ * 获取最近的导出历史记录
1579
+ */
1580
+ async getRecentHistory(limit = 10) {
1581
+ return await this.db.select().from(exportHistory).orderBy(desc(exportHistory.createdAt)).limit(limit);
1582
+ }
1583
+ };
1584
+ function createExportDatabaseServices(options) {
1585
+ const { db } = options;
1586
+ return {
1587
+ configDB: new ExportConfigDatabaseService(db),
1588
+ historyDB: new ExportHistoryDatabaseService(db)
1589
+ };
1590
+ }
239
1591
 
240
- export { ConfigValidationError as ExportConfigValidationError, calculateProgress, convertFieldType, createBatchExportPreset, createExportServiceConfig, createExportServiceFromEnv, createLargeAppPreset, createMediumAppPreset, createRealtimeExportPreset, createSmallAppPreset, createSmartExportPreset, createUniversalExportService, escapeCsvValue, estimateRemainingTime, formatFieldValue, generateUniqueFilename, getNestedValue, validateExportConfig, validateEnvironment as validateExportEnvironment, validateFieldValue };
1592
+ export { ExportConfigDatabaseService, ConfigValidationError as ExportConfigValidationError, ExportHistoryDatabaseService, UniversalExportService, calculateProgress, convertFieldType, createBatchExportPreset, createExportDatabaseServices, createExportServiceConfig, createExportServiceFromEnv, createLargeAppPreset, createMediumAppPreset, createRealtimeExportPreset, createSmallAppPreset, createSmartExportPreset, createUniversalExportService, escapeCsvValue, estimateRemainingTime, exportConfigs, exportConfigsRelations, exportHistory, exportHistoryRelations, formatFieldValue, generateUniqueFilename, getNestedValue, validateExportConfig, validateEnvironment as validateExportEnvironment, validateFieldValue };
241
1593
  //# sourceMappingURL=index.mjs.map
242
1594
  //# sourceMappingURL=index.mjs.map