@skyfox2000/webui 1.3.5 → 1.3.7
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/.vscode/settings.json +1 -1
- package/lib/assets/modules/{file-upload-C0twqMV5.js → file-upload-CKliLHBF.js} +50 -50
- package/lib/assets/modules/{index-D1XAa1Uo.js → index-CTOX9glc.js} +2 -2
- package/lib/assets/modules/index-Cm_1mhHh.js +377 -0
- package/lib/assets/modules/{index-C4CryM-R.js → index-DTFrhJdN.js} +1 -1
- package/lib/assets/modules/{menuTabs-BrYQa4UO.js → menuTabs-ylnnzjNu.js} +2 -2
- package/lib/assets/modules/{toolIcon-B-g9pyE4.js → toolIcon-CNfvNkNh.js} +1 -1
- package/lib/assets/modules/{uploadList-DCWRIxPJ.js → uploadList-DroJGtBI.js} +4 -4
- package/lib/assets/modules/{uploadList-0f2FA_5s.js → uploadList-M21hxVQy.js} +129 -128
- package/lib/components/common/alert/index.vue.d.ts +13 -0
- package/lib/components/common/icon/helper.vue.d.ts +1 -0
- package/lib/components/common/index.d.ts +2 -0
- package/lib/components/content/form/formItem.vue.d.ts +1 -0
- package/lib/components/form/input/index.vue.d.ts +4 -1
- package/lib/components/form/select/index.vue.d.ts +2 -0
- package/lib/components/form/treeSelect/index.vue.d.ts +11 -2
- package/lib/components/index.d.ts +1 -1
- package/lib/es/AceEditor/index.js +3 -3
- package/lib/es/BasicLayout/index.js +3 -3
- package/lib/es/Error403/index.js +1 -1
- package/lib/es/Error404/index.js +1 -1
- package/lib/es/ExcelForm/index.js +309 -275
- package/lib/es/UploadForm/index.js +4 -4
- package/lib/index.d.ts +1 -1
- package/lib/typings/form.d.ts +2 -2
- package/lib/utils/excel-preview.d.ts +24 -0
- package/lib/utils/form-excel.d.ts +17 -4
- package/lib/webui.css +1 -1
- package/lib/webui.es.js +677 -662
- package/package.json +2 -2
- package/src/components/common/alert/index.vue +76 -0
- package/src/components/common/icon/helper.vue +7 -1
- package/src/components/common/index.ts +4 -1
- package/src/components/content/dialog/excelForm.vue +337 -311
- package/src/components/content/form/formItem.vue +6 -2
- package/src/components/form/input/index.vue +16 -3
- package/src/components/form/select/index.vue +5 -11
- package/src/components/form/treeSelect/index.vue +22 -17
- package/src/components/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/typings/form.d.ts +2 -2
- package/src/utils/data.ts +10 -1
- package/src/utils/excel-preview.ts +189 -0
- package/src/utils/file-upload.ts +0 -2
- package/src/utils/form-excel.ts +132 -126
- package/lib/assets/modules/index-CKJIxasX.js +0 -333
package/src/utils/form-excel.ts
CHANGED
|
@@ -7,7 +7,7 @@ import message from 'vue-m-message';
|
|
|
7
7
|
import { ValidateRule } from '@/typings/form';
|
|
8
8
|
import { validMessages } from './form-validate';
|
|
9
9
|
import { UploadFile } from '@/typings/upload';
|
|
10
|
-
import { toExcel, type ExcelMarkInfo } from './excel-view';
|
|
10
|
+
import { toExcel, type ExcelMarkInfo, excelToNormalized } from './excel-view';
|
|
11
11
|
|
|
12
12
|
// ExcelMarkCell 和 ExcelMarkInfo 类型已移至 excel-view.ts 统一管理
|
|
13
13
|
|
|
@@ -17,105 +17,31 @@ import { toExcel, type ExcelMarkInfo } from './excel-view';
|
|
|
17
17
|
* @returns 包含工作簿、工作表、表头和数据的对象
|
|
18
18
|
*/
|
|
19
19
|
export const processExcelFile = async (excelBuffer: ArrayBuffer) => {
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// 获取第一个工作表
|
|
26
|
-
const worksheet = workbook.worksheets[0];
|
|
27
|
-
if (!worksheet) {
|
|
20
|
+
// 使用excel-view.ts中的统一方法
|
|
21
|
+
const normalized = await excelToNormalized(excelBuffer);
|
|
22
|
+
|
|
23
|
+
if (normalized.headers.length === 0) {
|
|
28
24
|
message.error('Excel文件不包含工作表');
|
|
29
25
|
return null;
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
//
|
|
33
|
-
const headers: string[] = [];
|
|
34
|
-
// 表头对象数据行
|
|
28
|
+
// 转换为原有格式以保持兼容性
|
|
35
29
|
const excelData: Record<string, any>[] = [];
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// 处理不同类型的单元格值,确保对象类型值被正确转换为字符串
|
|
42
|
-
let headerValue = '';
|
|
43
|
-
if (cell.value !== null && cell.value !== undefined) {
|
|
44
|
-
// 处理不同类型的单元格值
|
|
45
|
-
if (typeof cell.value === 'object') {
|
|
46
|
-
// 处理RichText类型
|
|
47
|
-
if ('richText' in cell.value && Array.isArray(cell.value.richText)) {
|
|
48
|
-
headerValue = cell.value.richText.map((rt: any) => rt.text || '').join('');
|
|
49
|
-
}
|
|
50
|
-
// 处理带有text属性的对象
|
|
51
|
-
else if ('text' in cell.value && typeof cell.value.text === 'string') {
|
|
52
|
-
headerValue = cell.value.text;
|
|
53
|
-
}
|
|
54
|
-
// 处理日期类型
|
|
55
|
-
else if (cell.value instanceof Date) {
|
|
56
|
-
headerValue = cell.value.toLocaleDateString();
|
|
57
|
-
}
|
|
58
|
-
// 其他对象类型,尝试获取有意义的字符串表示
|
|
59
|
-
else {
|
|
60
|
-
try {
|
|
61
|
-
headerValue = JSON.stringify(cell.value);
|
|
62
|
-
} catch {
|
|
63
|
-
headerValue = String(cell.value);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
// 非对象类型直接转字符串
|
|
68
|
-
headerValue = String(cell.value);
|
|
30
|
+
normalized.rows.forEach((row) => {
|
|
31
|
+
const rowData: Record<string, any> = {};
|
|
32
|
+
normalized.headers.forEach((header, idx) => {
|
|
33
|
+
if (header) {
|
|
34
|
+
rowData[header] = row[idx];
|
|
69
35
|
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// 提取数据行
|
|
75
|
-
worksheet.eachRow((row, rowNumber) => {
|
|
76
|
-
if (rowNumber > 1) {
|
|
77
|
-
// 跳过表头
|
|
78
|
-
const rowData: Record<string, any> = {};
|
|
79
|
-
const rowDataArray: any[] = [];
|
|
80
|
-
headers.forEach((header, idx) => {
|
|
81
|
-
if (header) {
|
|
82
|
-
const cell = row.getCell(idx + 1);
|
|
83
|
-
const cellValue = cell.value;
|
|
84
|
-
|
|
85
|
-
// 使用与表头处理相同的逻辑来提取单元格值
|
|
86
|
-
if (cellValue !== null && cellValue !== undefined) {
|
|
87
|
-
if (typeof cellValue === 'object') {
|
|
88
|
-
// 处理RichText类型
|
|
89
|
-
if ('richText' in cellValue && Array.isArray(cellValue.richText)) {
|
|
90
|
-
rowData[header] = cellValue.richText.map((rt: any) => rt.text || '').join('');
|
|
91
|
-
}
|
|
92
|
-
// 处理带有text属性的对象
|
|
93
|
-
else if ('text' in cellValue && typeof cellValue.text === 'string') {
|
|
94
|
-
rowData[header] = cellValue.text;
|
|
95
|
-
}
|
|
96
|
-
// 日期类型保留原样,便于验证处理
|
|
97
|
-
else if (cellValue instanceof Date) {
|
|
98
|
-
rowData[header] = cellValue;
|
|
99
|
-
}
|
|
100
|
-
// 其他对象类型
|
|
101
|
-
else {
|
|
102
|
-
rowData[header] = cellValue;
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
rowData[header] = cellValue;
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
rowData[header] = null;
|
|
109
|
-
}
|
|
110
|
-
rowDataArray.push(rowData[header]);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
excelData.push(rowData);
|
|
114
|
-
excelRows.push(rowDataArray);
|
|
115
|
-
}
|
|
36
|
+
});
|
|
37
|
+
excelData.push(rowData);
|
|
116
38
|
});
|
|
117
39
|
|
|
118
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
headers: normalized.headers,
|
|
42
|
+
excelData,
|
|
43
|
+
excelRows: normalized.rows,
|
|
44
|
+
};
|
|
119
45
|
};
|
|
120
46
|
|
|
121
47
|
/**
|
|
@@ -170,7 +96,8 @@ export const validateExcel = async (
|
|
|
170
96
|
rules?: Record<string, ValidateRule>,
|
|
171
97
|
): Promise<{
|
|
172
98
|
hasError: boolean;
|
|
173
|
-
|
|
99
|
+
markCells?: Array<{ row: number; col: number; color: string }>;
|
|
100
|
+
markHeaders?: string[];
|
|
174
101
|
}> => {
|
|
175
102
|
if (!rules || isEmpty(rules)) {
|
|
176
103
|
return { hasError: false };
|
|
@@ -203,7 +130,7 @@ export const validateExcel = async (
|
|
|
203
130
|
// 验证数据(仅验证非缺失字段)
|
|
204
131
|
const validationErrors = await validateExcelData(headers, excelJson, validator);
|
|
205
132
|
|
|
206
|
-
//
|
|
133
|
+
// 如果有验证错误或缺失字段,返回标记信息
|
|
207
134
|
if (validationErrors.length > 0 || missingFields.length > 0) {
|
|
208
135
|
// 准备需要标记的单元格
|
|
209
136
|
const markCells = validationErrors.map((error) => ({
|
|
@@ -212,27 +139,14 @@ export const validateExcel = async (
|
|
|
212
139
|
color: 'FFFF0000', // 红色
|
|
213
140
|
}));
|
|
214
141
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
markHeaders: missingFields,
|
|
221
|
-
},
|
|
222
|
-
'validation_errors.xlsx',
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
if (markResult.success && markResult.blobUrl) {
|
|
226
|
-
// 从blob URL创建blob对象
|
|
227
|
-
const response = await fetch(markResult.blobUrl);
|
|
228
|
-
const errBlob = await response.blob();
|
|
229
|
-
return { hasError: true, errBlob };
|
|
230
|
-
} else {
|
|
231
|
-
return { hasError: true };
|
|
232
|
-
}
|
|
142
|
+
return {
|
|
143
|
+
hasError: true,
|
|
144
|
+
markCells,
|
|
145
|
+
markHeaders: missingFields,
|
|
146
|
+
};
|
|
233
147
|
}
|
|
234
148
|
|
|
235
|
-
return { hasError: false };
|
|
149
|
+
return { hasError: false };
|
|
236
150
|
};
|
|
237
151
|
|
|
238
152
|
type ExcelValidationError = ValidateError & {
|
|
@@ -296,7 +210,8 @@ export const checkExcelDuplicates = async (
|
|
|
296
210
|
url?: IUrlInfo,
|
|
297
211
|
): Promise<{
|
|
298
212
|
hasError: boolean;
|
|
299
|
-
|
|
213
|
+
markCells?: Array<{ row: number; col: number; color: string }>;
|
|
214
|
+
markHeaders?: string[];
|
|
300
215
|
}> => {
|
|
301
216
|
if (!duplicateRules || duplicateRules.length === 0) {
|
|
302
217
|
return { hasError: false };
|
|
@@ -328,14 +243,18 @@ export const checkExcelDuplicates = async (
|
|
|
328
243
|
|
|
329
244
|
rows.forEach((rowData, index) => {
|
|
330
245
|
// 构建唯一键,将所有重复规则字段的值连接起来
|
|
331
|
-
const
|
|
246
|
+
const keyValues = duplicateRules.map((field) => rowData[field]);
|
|
247
|
+
const uniqueKey = keyValues.join('|');
|
|
332
248
|
allKeys.push(uniqueKey);
|
|
333
249
|
|
|
334
|
-
|
|
250
|
+
// 只检测非空值的重复,避免多个空行被误判为重复
|
|
251
|
+
const hasValidValue = keyValues.some((value) => value !== null && value !== undefined && value !== '');
|
|
252
|
+
|
|
253
|
+
if (hasValidValue && uniqueValues.has(uniqueKey)) {
|
|
335
254
|
// 找到重复行,记录当前索引和第一次出现的索引
|
|
336
255
|
duplicateIndices.add(index); // 添加当前重复行
|
|
337
256
|
duplicateIndices.add(uniqueValues.get(uniqueKey)!); // 添加第一次出现的行
|
|
338
|
-
} else {
|
|
257
|
+
} else if (hasValidValue) {
|
|
339
258
|
uniqueValues.set(uniqueKey, index);
|
|
340
259
|
}
|
|
341
260
|
});
|
|
@@ -367,33 +286,120 @@ export const checkExcelDuplicates = async (
|
|
|
367
286
|
markCells.push({
|
|
368
287
|
row: rowIndex + 2, // Excel行号 = 数组索引 + 2(表头和1-based索引)
|
|
369
288
|
col: colIndex + 1, // Excel列号 = 数组索引 + 1(1-based索引)
|
|
370
|
-
color: '
|
|
289
|
+
color: 'FFA500', // 黄橙色
|
|
371
290
|
});
|
|
372
291
|
}
|
|
373
292
|
});
|
|
374
293
|
});
|
|
375
294
|
|
|
376
|
-
|
|
295
|
+
return {
|
|
296
|
+
hasError: true,
|
|
297
|
+
markCells,
|
|
298
|
+
markHeaders: missingDuplicateFields,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return { hasError: false }; // 没有重复数据
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 统一验证Excel数据(格式验证和重复验证)
|
|
307
|
+
* @param excelBuffer Excel文件的ArrayBuffer
|
|
308
|
+
* @param rules 验证规则
|
|
309
|
+
* @param duplicateRules 重复规则
|
|
310
|
+
* @param duplicateUrl 重复检查URL
|
|
311
|
+
* @returns 统一验证结果
|
|
312
|
+
*/
|
|
313
|
+
export const validateExcelUnified = async (
|
|
314
|
+
excelBuffer: ArrayBuffer,
|
|
315
|
+
rules?: Record<string, ValidateRule>,
|
|
316
|
+
duplicateRules?: string[],
|
|
317
|
+
duplicateUrl?: IUrlInfo,
|
|
318
|
+
): Promise<{
|
|
319
|
+
hasError: boolean;
|
|
320
|
+
errBlob?: Blob;
|
|
321
|
+
validationMsg: string;
|
|
322
|
+
duplicateMsg: string;
|
|
323
|
+
}> => {
|
|
324
|
+
const allMarkCells: Array<{ row: number; col: number; color: string }> = [];
|
|
325
|
+
const allMarkHeaders: string[] = [];
|
|
326
|
+
let hasValidationError = false;
|
|
327
|
+
let hasDuplicateError = false;
|
|
328
|
+
let validationMsg = '数据验证成功';
|
|
329
|
+
let duplicateMsg = '数据验证通过';
|
|
330
|
+
|
|
331
|
+
// 1. 格式验证
|
|
332
|
+
const validationResult = await validateExcel(excelBuffer, rules);
|
|
333
|
+
if (validationResult.hasError) {
|
|
334
|
+
hasValidationError = true;
|
|
335
|
+
validationMsg = '数据验证失败';
|
|
336
|
+
if (validationResult.markCells) {
|
|
337
|
+
allMarkCells.push(...validationResult.markCells);
|
|
338
|
+
}
|
|
339
|
+
if (validationResult.markHeaders) {
|
|
340
|
+
allMarkHeaders.push(...validationResult.markHeaders);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 2. 重复验证
|
|
345
|
+
if (duplicateRules && duplicateRules.length > 0) {
|
|
346
|
+
const duplicateResult = await checkExcelDuplicates(excelBuffer, duplicateRules, duplicateUrl);
|
|
347
|
+
if (duplicateResult.hasError) {
|
|
348
|
+
hasDuplicateError = true;
|
|
349
|
+
duplicateMsg = '检测到重复数据';
|
|
350
|
+
if (duplicateResult.markCells) {
|
|
351
|
+
// 检查是否有重复标记的单元格,如果有则改为深红色
|
|
352
|
+
duplicateResult.markCells.forEach((duplicateCell) => {
|
|
353
|
+
const existingCell = allMarkCells.find(
|
|
354
|
+
(cell) => cell.row === duplicateCell.row && cell.col === duplicateCell.col,
|
|
355
|
+
);
|
|
356
|
+
if (existingCell) {
|
|
357
|
+
existingCell.color = '8B0000'; // 深红色
|
|
358
|
+
} else {
|
|
359
|
+
allMarkCells.push(duplicateCell);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
if (duplicateResult.markHeaders) {
|
|
364
|
+
allMarkHeaders.push(...duplicateResult.markHeaders);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 3. 如果有任何错误,创建统一的标记Excel
|
|
370
|
+
if (hasValidationError || hasDuplicateError) {
|
|
377
371
|
const markResult = await createMarkedExcelView(
|
|
378
372
|
excelBuffer,
|
|
379
373
|
{
|
|
380
|
-
markCells,
|
|
381
|
-
markHeaders:
|
|
374
|
+
markCells: allMarkCells,
|
|
375
|
+
markHeaders: allMarkHeaders,
|
|
382
376
|
},
|
|
383
|
-
'
|
|
377
|
+
'validation_errors.xlsx',
|
|
384
378
|
);
|
|
385
379
|
|
|
386
380
|
if (markResult.success && markResult.blobUrl) {
|
|
387
|
-
// 从blob URL创建blob对象
|
|
388
381
|
const response = await fetch(markResult.blobUrl);
|
|
389
382
|
const errBlob = await response.blob();
|
|
390
|
-
return {
|
|
383
|
+
return {
|
|
384
|
+
hasError: true,
|
|
385
|
+
errBlob,
|
|
386
|
+
validationMsg,
|
|
387
|
+
duplicateMsg,
|
|
388
|
+
};
|
|
391
389
|
} else {
|
|
392
|
-
return {
|
|
390
|
+
return {
|
|
391
|
+
hasError: true,
|
|
392
|
+
validationMsg,
|
|
393
|
+
duplicateMsg,
|
|
394
|
+
};
|
|
393
395
|
}
|
|
394
396
|
}
|
|
395
397
|
|
|
396
|
-
return {
|
|
398
|
+
return {
|
|
399
|
+
hasError: false,
|
|
400
|
+
validationMsg,
|
|
401
|
+
duplicateMsg,
|
|
402
|
+
};
|
|
397
403
|
};
|
|
398
404
|
|
|
399
405
|
/**
|
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import D from "async-validator";
|
|
2
|
-
import { httpPost as S, ResStatus as T } from "@skyfox2000/fapi";
|
|
3
|
-
import { i as _, ag as j } from "./uploadList-0f2FA_5s.js";
|
|
4
|
-
import d from "vue-m-message";
|
|
5
|
-
import { defineComponent as C, useAttrs as V, createElementBlock as A, openBlock as U, createVNode as O, unref as F, mergeProps as B } from "vue";
|
|
6
|
-
import { Spin as H } from "ant-design-vue";
|
|
7
|
-
const k = (u) => {
|
|
8
|
-
const o = u.split(`
|
|
9
|
-
`).filter((i) => i.trim() !== "");
|
|
10
|
-
if (o.length === 0)
|
|
11
|
-
return { headers: [], rows: [] };
|
|
12
|
-
const s = o.map((i) => i.split(",").map((r) => {
|
|
13
|
-
const e = r.trim();
|
|
14
|
-
if (e === "" || e === "null") return null;
|
|
15
|
-
const t = Number(e);
|
|
16
|
-
return isNaN(t) ? e : t;
|
|
17
|
-
})), a = s[0].map((i) => String(i || "")), l = s.slice(1);
|
|
18
|
-
return { headers: a, rows: l };
|
|
19
|
-
}, z = async (u) => {
|
|
20
|
-
try {
|
|
21
|
-
const o = await import("exceljs"), s = new o.default.Workbook();
|
|
22
|
-
await s.xlsx.load(u);
|
|
23
|
-
const a = s.worksheets[0];
|
|
24
|
-
if (!a)
|
|
25
|
-
return { headers: [], rows: [] };
|
|
26
|
-
const l = [], i = [];
|
|
27
|
-
return a.getRow(1).eachCell((r) => {
|
|
28
|
-
let e = "";
|
|
29
|
-
r.value !== null && r.value !== void 0 && (typeof r.value == "object" ? "richText" in r.value && Array.isArray(r.value.richText) ? e = r.value.richText.map((t) => t.text || "").join("") : "text" in r.value && typeof r.value.text == "string" ? e = r.value.text : r.value instanceof Date ? e = r.value.toLocaleDateString() : e = String(r.value) : e = String(r.value)), l.push(e);
|
|
30
|
-
}), a.eachRow((r, e) => {
|
|
31
|
-
if (e > 1) {
|
|
32
|
-
const t = [];
|
|
33
|
-
l.forEach((n, c) => {
|
|
34
|
-
const h = r.getCell(c + 1).value;
|
|
35
|
-
h != null ? typeof h == "object" ? "richText" in h && Array.isArray(h.richText) ? t.push(h.richText.map((p) => p.text || "").join("")) : "text" in h && typeof h.text == "string" ? t.push(h.text) : (h instanceof Date, t.push(h)) : t.push(h) : t.push(null);
|
|
36
|
-
}), i.push(t);
|
|
37
|
-
}
|
|
38
|
-
}), { headers: l, rows: i };
|
|
39
|
-
} catch (o) {
|
|
40
|
-
return console.error("Excel解析失败:", o), { headers: [], rows: [] };
|
|
41
|
-
}
|
|
42
|
-
}, m = async (u, o, s) => {
|
|
43
|
-
try {
|
|
44
|
-
const a = await import("exceljs"), l = new a.default.Workbook(), i = l.addWorksheet("Sheet1"), { headers: r, rows: e } = u;
|
|
45
|
-
if (r.length === 0)
|
|
46
|
-
throw new Error("数据为空");
|
|
47
|
-
const t = /* @__PURE__ */ new Map();
|
|
48
|
-
s != null && s.markCells && s.markCells.forEach(({ row: p, col: w, color: x }) => {
|
|
49
|
-
const E = `${p}-${w}`;
|
|
50
|
-
t.set(E, x || "FFFF0000");
|
|
51
|
-
});
|
|
52
|
-
const n = i.getRow(1);
|
|
53
|
-
r.forEach((p, w) => {
|
|
54
|
-
const x = n.getCell(w + 1);
|
|
55
|
-
x.value = p, x.font = { bold: !0 }, x.fill = {
|
|
56
|
-
type: "pattern",
|
|
57
|
-
pattern: "solid",
|
|
58
|
-
fgColor: { argb: "FFE0E0E0" }
|
|
59
|
-
}, s != null && s.markHeaders && s.markHeaders.includes(p) && (x.fill = {
|
|
60
|
-
type: "pattern",
|
|
61
|
-
pattern: "solid",
|
|
62
|
-
fgColor: { argb: "FFFF0000" }
|
|
63
|
-
}, x.font = {
|
|
64
|
-
name: "Arial",
|
|
65
|
-
size: 10,
|
|
66
|
-
bold: !0,
|
|
67
|
-
color: { argb: "FFFFFFFF" }
|
|
68
|
-
});
|
|
69
|
-
}), e.forEach((p, w) => {
|
|
70
|
-
const x = i.getRow(w + 2);
|
|
71
|
-
p.forEach((E, b) => {
|
|
72
|
-
const y = x.getCell(b + 1);
|
|
73
|
-
y.value = E;
|
|
74
|
-
const v = `${w + 2}-${b + 1}`;
|
|
75
|
-
t.has(v) && (y.fill = {
|
|
76
|
-
type: "pattern",
|
|
77
|
-
pattern: "solid",
|
|
78
|
-
fgColor: { argb: t.get(v) }
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}), i.columns.forEach((p) => {
|
|
82
|
-
p.width = 15;
|
|
83
|
-
});
|
|
84
|
-
const c = await l.xlsx.writeBuffer(), f = new Blob([c], {
|
|
85
|
-
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
86
|
-
});
|
|
87
|
-
return {
|
|
88
|
-
success: !0,
|
|
89
|
-
blobUrl: URL.createObjectURL(f),
|
|
90
|
-
fileName: o.replace(/\.(csv|xlsx?)$/i, ".xlsx")
|
|
91
|
-
};
|
|
92
|
-
} catch (a) {
|
|
93
|
-
return console.error("转换Excel失败:", a), {
|
|
94
|
-
success: !1,
|
|
95
|
-
error: a instanceof Error ? a.message : "未知错误"
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}, N = async (u, o) => {
|
|
99
|
-
try {
|
|
100
|
-
const s = k(u);
|
|
101
|
-
return await m(s, o);
|
|
102
|
-
} catch (s) {
|
|
103
|
-
return {
|
|
104
|
-
success: !1,
|
|
105
|
-
error: s instanceof Error ? s.message : "CSV处理失败"
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}, M = async (u, o) => {
|
|
109
|
-
try {
|
|
110
|
-
const s = new Blob([u], {
|
|
111
|
-
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
112
|
-
});
|
|
113
|
-
return {
|
|
114
|
-
success: !0,
|
|
115
|
-
blobUrl: URL.createObjectURL(s),
|
|
116
|
-
fileName: o
|
|
117
|
-
};
|
|
118
|
-
} catch (s) {
|
|
119
|
-
return {
|
|
120
|
-
success: !1,
|
|
121
|
-
error: s instanceof Error ? s.message : "Excel处理失败"
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
}, $ = async (u, o) => await m(u, o), X = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
125
|
-
__proto__: null,
|
|
126
|
-
csvToExcelView: N,
|
|
127
|
-
csvToNormalized: k,
|
|
128
|
-
excelToExcelView: M,
|
|
129
|
-
excelToNormalized: z,
|
|
130
|
-
normalizedToExcelView: $,
|
|
131
|
-
toExcel: m
|
|
132
|
-
}, Symbol.toStringTag, { value: "Module" })), g = async (u) => {
|
|
133
|
-
const o = await import("exceljs"), s = new o.default.Workbook();
|
|
134
|
-
await s.xlsx.load(u);
|
|
135
|
-
const a = s.worksheets[0];
|
|
136
|
-
if (!a)
|
|
137
|
-
return d.error("Excel文件不包含工作表"), null;
|
|
138
|
-
const l = [], i = [], r = [];
|
|
139
|
-
return a.getRow(1).eachCell((e) => {
|
|
140
|
-
let t = "";
|
|
141
|
-
if (e.value !== null && e.value !== void 0)
|
|
142
|
-
if (typeof e.value == "object")
|
|
143
|
-
if ("richText" in e.value && Array.isArray(e.value.richText))
|
|
144
|
-
t = e.value.richText.map((n) => n.text || "").join("");
|
|
145
|
-
else if ("text" in e.value && typeof e.value.text == "string")
|
|
146
|
-
t = e.value.text;
|
|
147
|
-
else if (e.value instanceof Date)
|
|
148
|
-
t = e.value.toLocaleDateString();
|
|
149
|
-
else
|
|
150
|
-
try {
|
|
151
|
-
t = JSON.stringify(e.value);
|
|
152
|
-
} catch {
|
|
153
|
-
t = String(e.value);
|
|
154
|
-
}
|
|
155
|
-
else
|
|
156
|
-
t = String(e.value);
|
|
157
|
-
l.push(t);
|
|
158
|
-
}), a.eachRow((e, t) => {
|
|
159
|
-
if (t > 1) {
|
|
160
|
-
const n = {}, c = [];
|
|
161
|
-
l.forEach((f, h) => {
|
|
162
|
-
if (f) {
|
|
163
|
-
const w = e.getCell(h + 1).value;
|
|
164
|
-
w != null ? typeof w == "object" ? "richText" in w && Array.isArray(w.richText) ? n[f] = w.richText.map((x) => x.text || "").join("") : "text" in w && typeof w.text == "string" ? n[f] = w.text : (w instanceof Date, n[f] = w) : n[f] = w : n[f] = null, c.push(n[f]);
|
|
165
|
-
}
|
|
166
|
-
}), i.push(n), r.push(c);
|
|
167
|
-
}
|
|
168
|
-
}), { workbook: s, worksheet: a, headers: l, excelData: i, excelRows: r };
|
|
169
|
-
}, R = async (u, o, s) => {
|
|
170
|
-
const a = await g(u);
|
|
171
|
-
if (!a)
|
|
172
|
-
return { success: !1, error: "Excel文件处理失败" };
|
|
173
|
-
const { headers: l, excelRows: i } = a, { markHeaders: r } = o, e = [...l];
|
|
174
|
-
r && r.length > 0 && r.forEach((n) => {
|
|
175
|
-
e.includes(n) || e.push(n);
|
|
176
|
-
});
|
|
177
|
-
const t = i.map((n) => {
|
|
178
|
-
const c = [...n];
|
|
179
|
-
for (; c.length < e.length; )
|
|
180
|
-
c.push(null);
|
|
181
|
-
return c;
|
|
182
|
-
});
|
|
183
|
-
return await m({ headers: e, rows: t }, s, o);
|
|
184
|
-
}, Y = async (u, o) => {
|
|
185
|
-
if (!o || _(o))
|
|
186
|
-
return { hasError: !1 };
|
|
187
|
-
const s = await g(u);
|
|
188
|
-
if (!s) return { hasError: !0 };
|
|
189
|
-
const { headers: a, excelData: l } = s, i = [];
|
|
190
|
-
if (Object.keys(o).forEach((t) => {
|
|
191
|
-
a.includes(t) || i.push(t);
|
|
192
|
-
}), a.length === 0 || l.length === 0)
|
|
193
|
-
return d.error("Excel文件不包含足够的数据"), { hasError: !0 };
|
|
194
|
-
const r = new D({});
|
|
195
|
-
r.messages(j.messages()), r.define(o);
|
|
196
|
-
const e = await L(a, l, r);
|
|
197
|
-
if (e.length > 0 || i.length > 0) {
|
|
198
|
-
const t = e.map((c) => ({
|
|
199
|
-
row: c.row + 2,
|
|
200
|
-
// 转为Excel行号(+2是因为表头占一行,且是1-based索引)
|
|
201
|
-
col: c.col + 1,
|
|
202
|
-
// 转为Excel列号(+1是因为是1-based索引)
|
|
203
|
-
color: "FFFF0000"
|
|
204
|
-
// 红色
|
|
205
|
-
})), n = await R(
|
|
206
|
-
u,
|
|
207
|
-
{
|
|
208
|
-
markCells: t,
|
|
209
|
-
markHeaders: i
|
|
210
|
-
},
|
|
211
|
-
"validation_errors.xlsx"
|
|
212
|
-
);
|
|
213
|
-
return n.success && n.blobUrl ? { hasError: !0, errBlob: await (await fetch(n.blobUrl)).blob() } : { hasError: !0 };
|
|
214
|
-
}
|
|
215
|
-
return { hasError: !1 };
|
|
216
|
-
}, L = async (u, o, s) => {
|
|
217
|
-
const a = [];
|
|
218
|
-
for (let l = 0; l < o.length; l++) {
|
|
219
|
-
const i = o[l];
|
|
220
|
-
try {
|
|
221
|
-
await s.validate(i).catch(({ errors: r }) => {
|
|
222
|
-
const e = [];
|
|
223
|
-
r.forEach((t) => {
|
|
224
|
-
const n = u.indexOf(t.field);
|
|
225
|
-
n >= 0 && (e.some((f) => f.row === l && f.col === n) || e.push({
|
|
226
|
-
row: l,
|
|
227
|
-
col: n,
|
|
228
|
-
header: t.field,
|
|
229
|
-
message: t.message.replace("${label}", u[n])
|
|
230
|
-
}));
|
|
231
|
-
}), a.push(...e);
|
|
232
|
-
});
|
|
233
|
-
} catch (r) {
|
|
234
|
-
console.error("验证表格数据时发生错误:", r), d.error("验证表格数据时发生错误:" + r);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return a;
|
|
238
|
-
}, Z = async (u, o, s) => {
|
|
239
|
-
if (!o || o.length === 0)
|
|
240
|
-
return { hasError: !1 };
|
|
241
|
-
const a = await g(u);
|
|
242
|
-
if (!a) return { hasError: !0 };
|
|
243
|
-
const { headers: l, excelData: i } = a, r = [];
|
|
244
|
-
if (o.forEach((c) => {
|
|
245
|
-
l.includes(c) || r.push(c);
|
|
246
|
-
}), r.length > 0)
|
|
247
|
-
return d.error(`表头缺少重复检测所需字段: ${r.join(", ")}`), { hasError: !0 };
|
|
248
|
-
const e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Set(), n = new Array();
|
|
249
|
-
if (i.forEach((c, f) => {
|
|
250
|
-
const h = o.map((p) => c[p]).join("|");
|
|
251
|
-
n.push(h), e.has(h) ? (t.add(f), t.add(e.get(h))) : e.set(h, f);
|
|
252
|
-
}), s) {
|
|
253
|
-
const c = await S(s, {
|
|
254
|
-
Data: n
|
|
255
|
-
});
|
|
256
|
-
if (c != null && c.data && c.data.forEach((f) => {
|
|
257
|
-
t.add(f);
|
|
258
|
-
}), (c == null ? void 0 : c.status) === T.ERROR)
|
|
259
|
-
throw new Error(c.msg);
|
|
260
|
-
}
|
|
261
|
-
if (t.size > 0) {
|
|
262
|
-
const c = [];
|
|
263
|
-
t.forEach((h) => {
|
|
264
|
-
o.forEach((p) => {
|
|
265
|
-
const w = l.indexOf(p);
|
|
266
|
-
w >= 0 && c.push({
|
|
267
|
-
row: h + 2,
|
|
268
|
-
// Excel行号 = 数组索引 + 2(表头和1-based索引)
|
|
269
|
-
col: w + 1,
|
|
270
|
-
// Excel列号 = 数组索引 + 1(1-based索引)
|
|
271
|
-
color: "FFFF0000"
|
|
272
|
-
// 红色
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
const f = await R(
|
|
277
|
-
u,
|
|
278
|
-
{
|
|
279
|
-
markCells: c,
|
|
280
|
-
markHeaders: r
|
|
281
|
-
},
|
|
282
|
-
"duplicate_errors.xlsx"
|
|
283
|
-
);
|
|
284
|
-
return f.success && f.blobUrl ? { hasError: !0, errBlob: await (await fetch(f.blobUrl)).blob() } : { hasError: !0 };
|
|
285
|
-
}
|
|
286
|
-
return { hasError: !1 };
|
|
287
|
-
}, I = async (u, o, s) => {
|
|
288
|
-
const a = u.originFileObj;
|
|
289
|
-
if (a) {
|
|
290
|
-
const l = await a.arrayBuffer(), i = await g(l);
|
|
291
|
-
if (!i) {
|
|
292
|
-
d.error("上传的文件不是Excel文件");
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const { headers: r, excelRows: e, excelData: t } = i;
|
|
296
|
-
s && s.length > 0 ? s.forEach((n) => {
|
|
297
|
-
switch (n) {
|
|
298
|
-
case "Headers":
|
|
299
|
-
o.Headers = r;
|
|
300
|
-
break;
|
|
301
|
-
case "RawRows":
|
|
302
|
-
o.RawRows = e;
|
|
303
|
-
break;
|
|
304
|
-
case "Records":
|
|
305
|
-
o.Records = t;
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
}) : (o.Headers = r, o.RawRows = e, o.Records = t);
|
|
309
|
-
}
|
|
310
|
-
}, J = { class: "absolute z-[9999] w-full h-full top-0 flex flex-flow row items-center justify-center" }, ee = /* @__PURE__ */ C({
|
|
311
|
-
__name: "index",
|
|
312
|
-
setup(u) {
|
|
313
|
-
const o = V();
|
|
314
|
-
return (s, a) => (U(), A("div", J, [
|
|
315
|
-
O(F(H), B({ style: { "margin-top": "-10%" } }, F(o)), null, 16)
|
|
316
|
-
]));
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
export {
|
|
320
|
-
ee as _,
|
|
321
|
-
Z as a,
|
|
322
|
-
I as b,
|
|
323
|
-
N as c,
|
|
324
|
-
R as d,
|
|
325
|
-
k as e,
|
|
326
|
-
z as f,
|
|
327
|
-
M as g,
|
|
328
|
-
X as h,
|
|
329
|
-
$ as n,
|
|
330
|
-
g as p,
|
|
331
|
-
m as t,
|
|
332
|
-
Y as v
|
|
333
|
-
};
|