@skyfox2000/webui 1.3.4 → 1.3.6
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-BBlFaIXB.js → file-upload-DhPgqGdk.js} +50 -50
- package/lib/assets/modules/index-02J2AYth.js +377 -0
- package/lib/assets/modules/{index-m5rogIyM.js → index-C31q4LHC.js} +2 -2
- package/lib/assets/modules/{index-BG1SqSVl.js → index-CCpTizF9.js} +1 -1
- package/lib/assets/modules/{menuTabs-tPIz4a89.js → menuTabs-DyhSKN9r.js} +2 -2
- package/lib/assets/modules/{toolIcon-DwWoD9TN.js → toolIcon-CqM4gBIc.js} +1 -1
- package/lib/assets/modules/{uploadList-D_Z-Y2tw.js → uploadList-DAVjJkqz.js} +511 -476
- package/lib/assets/modules/{uploadList-Da7mQUNK.js → uploadList-ZajZKqaS.js} +4 -4
- 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/content/table/index.vue.d.ts +95 -4
- 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 +317 -274
- 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/typings/option.d.ts +2 -2
- package/lib/utils/excel-preview.d.ts +24 -0
- package/lib/utils/form-excel.d.ts +17 -4
- package/lib/utils/options.d.ts +2 -2
- package/lib/webui.css +1 -1
- package/lib/webui.es.js +759 -747
- package/package.json +1 -1
- 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/common/loading/index.vue +1 -1
- package/src/components/content/dialog/excelForm.vue +343 -313
- package/src/components/content/form/formItem.vue +6 -2
- package/src/components/content/table/index.vue +9 -6
- package/src/components/form/autoComplete/index.vue +9 -3
- package/src/components/form/cascader/index.vue +8 -6
- 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/typings/option.d.ts +2 -2
- package/src/utils/excel-preview.ts +188 -0
- package/src/utils/file-upload.ts +0 -2
- package/src/utils/form-excel.ts +132 -126
- package/src/utils/options.ts +80 -22
- package/src/utils/table.ts +15 -2
- package/lib/assets/modules/index-4kDAt8nS.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
|
/**
|
package/src/utils/options.ts
CHANGED
|
@@ -179,25 +179,77 @@ const queryOptions = <T>(
|
|
|
179
179
|
|
|
180
180
|
/**
|
|
181
181
|
* 获取选中的选项对象或对象数组
|
|
182
|
+
* - 支持单选或者多选
|
|
183
|
+
* - 支持子级选项
|
|
182
184
|
* @param values 当前选择的值
|
|
183
185
|
* @param options 所有选项对象数组
|
|
186
|
+
* @param keepChildren 是否保留子选项,默认为 true
|
|
184
187
|
* @returns 选中的选项对象或对象数组
|
|
185
188
|
*/
|
|
186
189
|
export const getSelectedValues = (
|
|
187
190
|
values: undefined | string | number | (string | number | undefined)[],
|
|
188
191
|
options: OptionItemProps[],
|
|
192
|
+
keepChildren?: boolean,
|
|
189
193
|
): OptionItemProps | OptionItemProps[] | undefined => {
|
|
190
194
|
// 如果 values 为 undefined,直接返回 undefined
|
|
191
195
|
if (values === undefined) return undefined;
|
|
192
196
|
|
|
197
|
+
// 深拷贝选项以确保数据隔离
|
|
198
|
+
const deepCloneOption = (option: OptionItemProps): OptionItemProps => {
|
|
199
|
+
const cloned = { ...option };
|
|
200
|
+
if (keepChildren && option.children && option.children.length > 0) {
|
|
201
|
+
cloned.children = option.children.map((child: OptionItemProps) => deepCloneOption(child));
|
|
202
|
+
} else {
|
|
203
|
+
delete cloned.children;
|
|
204
|
+
}
|
|
205
|
+
return cloned;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// 递归查找匹配的选项
|
|
209
|
+
const findMatchedOptions = (
|
|
210
|
+
searchValues: (string | number | undefined)[],
|
|
211
|
+
searchOptions: OptionItemProps[],
|
|
212
|
+
): OptionItemProps[] => {
|
|
213
|
+
const matched: OptionItemProps[] = [];
|
|
214
|
+
|
|
215
|
+
for (const option of searchOptions) {
|
|
216
|
+
// 检查当前选项是否匹配
|
|
217
|
+
if (searchValues.includes(option.value)) {
|
|
218
|
+
matched.push(deepCloneOption(option));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 递归检查子选项
|
|
222
|
+
if (option.children && option.children.length > 0) {
|
|
223
|
+
const childMatched = findMatchedOptions(searchValues, option.children);
|
|
224
|
+
matched.push(...childMatched);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return matched;
|
|
229
|
+
};
|
|
230
|
+
|
|
193
231
|
// 如果 values 是数组,返回所有匹配的选项对象数组
|
|
194
232
|
if (Array.isArray(values)) {
|
|
195
|
-
return
|
|
233
|
+
return findMatchedOptions(values, options);
|
|
196
234
|
}
|
|
197
235
|
// 如果 values 是单个值,返回匹配的单个选项对象
|
|
198
236
|
else {
|
|
199
|
-
|
|
237
|
+
const matched = findMatchedOptions([values], options);
|
|
238
|
+
return matched.length > 0 ? matched[0] : undefined;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 获取选中的选项文字内容
|
|
244
|
+
* @param selectedValues 选中的选项对象或对象数
|
|
245
|
+
* @returns 选中的选项文字内容
|
|
246
|
+
*/
|
|
247
|
+
export const getSelectedLabels = (selectedValues: OptionItemProps | OptionItemProps[] | undefined): string[] => {
|
|
248
|
+
if (selectedValues === undefined) return [];
|
|
249
|
+
if (Array.isArray(selectedValues)) {
|
|
250
|
+
return selectedValues.map((option) => option.label);
|
|
200
251
|
}
|
|
252
|
+
return [selectedValues.label];
|
|
201
253
|
};
|
|
202
254
|
|
|
203
255
|
// 辅助函数:根据路径设置值
|
|
@@ -216,7 +268,10 @@ const setNestedValue = (obj: Record<string, any>, path: string, value: any) => {
|
|
|
216
268
|
|
|
217
269
|
/**
|
|
218
270
|
* 将选中的值根据 outFields 映射到 formData 上
|
|
219
|
-
* @param formData
|
|
271
|
+
* @param formData 需要更新的数据对
|
|
272
|
+
* 输出字段转换控制
|
|
273
|
+
* - Key:目的字段,支持 "." 嵌套
|
|
274
|
+
* - Value:源字段,支持模板 ${} 或者 ${index} 或者 ${index}.${key}
|
|
220
275
|
* @param outFields 输出字段转换映射关系
|
|
221
276
|
* @param selectedValues 选中的选项对象或对象数组
|
|
222
277
|
*/
|
|
@@ -235,12 +290,28 @@ export const outFormDataFields = (
|
|
|
235
290
|
|
|
236
291
|
// 如果 selectedValues 是数组
|
|
237
292
|
if (Array.isArray(selectedValues)) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
293
|
+
Object.entries(outFields).forEach(([targetKey, sourceKey]) => {
|
|
294
|
+
// 必须是 ${index} 或者 ${index}.${key},否则输出错误并跳过
|
|
295
|
+
const reg = /^\$\{\d+\}/;
|
|
296
|
+
// 第一个必须是 ${index},否则输出错误并跳过
|
|
297
|
+
if (reg.test(sourceKey)) {
|
|
298
|
+
try {
|
|
299
|
+
const index = parseInt(sourceKey.match(/\$\{(\d+)\}/)?.[1] ?? '0');
|
|
300
|
+
const targetValue = selectedValues[index];
|
|
301
|
+
const restKey = sourceKey.replace(/\$\{\d+\}\./, '');
|
|
302
|
+
if (restKey && targetValue) {
|
|
303
|
+
const value = parseFieldTemplate(restKey, targetValue);
|
|
304
|
+
setNestedValue(formData, targetKey, value);
|
|
305
|
+
} else {
|
|
306
|
+
setNestedValue(formData, targetKey, targetValue);
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error('outFields 格式错误:' + sourceKey, '必须是 ${index} 或者 ${index}.${key}');
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
console.error('outFields 格式错误:' + sourceKey, '必须是 ${index} 或者 ${index}.${key}');
|
|
313
|
+
}
|
|
314
|
+
});
|
|
244
315
|
}
|
|
245
316
|
// 如果 selectedValues 是单个对象
|
|
246
317
|
else {
|
|
@@ -320,16 +391,3 @@ export const onOptionChanged = (
|
|
|
320
391
|
}
|
|
321
392
|
return selectedValues;
|
|
322
393
|
};
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* 获取选中的选项文字内容
|
|
326
|
-
* @param selectedValues 选中的选项对象或对象数
|
|
327
|
-
* @returns 选中的选项文字内容
|
|
328
|
-
*/
|
|
329
|
-
export const getSelectedLabels = (selectedValues: OptionItemProps | OptionItemProps[] | undefined): string[] => {
|
|
330
|
-
if (selectedValues === undefined) return [];
|
|
331
|
-
if (Array.isArray(selectedValues)) {
|
|
332
|
-
return selectedValues.map((option) => option.label);
|
|
333
|
-
}
|
|
334
|
-
return [selectedValues.label];
|
|
335
|
-
};
|
package/src/utils/table.ts
CHANGED
|
@@ -25,8 +25,9 @@ interface TableColumn {
|
|
|
25
25
|
*/
|
|
26
26
|
export const filterColumns = (columns: TableColumn[], toolCtl?: boolean) => {
|
|
27
27
|
const userInfoStore = useUserInfo();
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// 检查是否存在enabled:false的列
|
|
29
|
+
// 并且设置没有权限的列为enabled:false
|
|
30
|
+
const enabledColumns = columns.filter((column) => {
|
|
30
31
|
// 角色权限检查
|
|
31
32
|
if (column.role && !userInfoStore.hasRole(column.role)) {
|
|
32
33
|
return false;
|
|
@@ -37,6 +38,18 @@ export const filterColumns = (columns: TableColumn[], toolCtl?: boolean) => {
|
|
|
37
38
|
return false;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
if (column.enabled === false) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof column.enabled === 'function') {
|
|
46
|
+
return column.enabled();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return enabledColumns.filter((column) => {
|
|
40
53
|
if (!toolCtl) {
|
|
41
54
|
// 可见性检查
|
|
42
55
|
if (column.visible === false) {
|