@skyfox2000/webui 1.3.3 → 1.3.5

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 (38) hide show
  1. package/lib/assets/modules/{file-upload-D4Pqs8h3.js → file-upload-C0twqMV5.js} +1 -1
  2. package/lib/assets/modules/{index-V1j9haWy.js → index-C4CryM-R.js} +1 -1
  3. package/lib/assets/modules/index-CKJIxasX.js +333 -0
  4. package/lib/assets/modules/{index-CSnwbbQT.js → index-D1XAa1Uo.js} +2 -2
  5. package/lib/assets/modules/{menuTabs-e8XoJN7m.js → menuTabs-BrYQa4UO.js} +2 -2
  6. package/lib/assets/modules/{toolIcon-BSF7eiPf.js → toolIcon-B-g9pyE4.js} +1 -1
  7. package/lib/assets/modules/{uploadList-DA4TRDWR.js → uploadList-0f2FA_5s.js} +490 -455
  8. package/lib/assets/modules/{uploadList-Bcf7g1bf.js → uploadList-DCWRIxPJ.js} +4 -4
  9. package/lib/components/content/table/index.vue.d.ts +95 -4
  10. package/lib/es/AceEditor/index.js +3 -3
  11. package/lib/es/BasicLayout/index.js +3 -3
  12. package/lib/es/Error403/index.js +1 -1
  13. package/lib/es/Error404/index.js +1 -1
  14. package/lib/es/ExcelForm/index.js +339 -202
  15. package/lib/es/UploadForm/index.js +4 -4
  16. package/lib/index.d.ts +3 -2
  17. package/lib/typings/option.d.ts +2 -2
  18. package/lib/utils/excel-view.d.ts +25 -0
  19. package/lib/utils/form-csv.d.ts +18 -0
  20. package/lib/utils/form-excel.d.ts +2 -13
  21. package/lib/utils/options.d.ts +2 -2
  22. package/lib/webui.css +1 -1
  23. package/lib/webui.es.js +897 -871
  24. package/package.json +2 -2
  25. package/src/components/common/loading/index.vue +1 -1
  26. package/src/components/content/dialog/excelForm.vue +386 -107
  27. package/src/components/content/table/index.vue +9 -6
  28. package/src/components/form/autoComplete/index.vue +9 -3
  29. package/src/components/form/cascader/index.vue +8 -6
  30. package/src/index.ts +25 -2
  31. package/src/typings/option.d.ts +2 -2
  32. package/src/utils/excel-view.ts +340 -0
  33. package/src/utils/form-csv.ts +55 -0
  34. package/src/utils/form-excel.ts +59 -192
  35. package/src/utils/options.ts +80 -22
  36. package/src/utils/table.ts +15 -2
  37. package/vite.config.ts +0 -1
  38. package/lib/assets/modules/form-excel-D1vXB4c4.js +0 -235
@@ -7,23 +7,9 @@ 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
11
 
11
- /**
12
- * Excel数据处理需要标记的单元格
13
- */
14
- export type ExcelMarkCell = {
15
- row: number;
16
- col: number;
17
- color?: string;
18
- };
19
-
20
- /**
21
- * Excel标记类型
22
- */
23
- export type ExcelMarkInfo = {
24
- markCells: ExcelMarkCell[];
25
- markHeaders?: string[];
26
- };
12
+ // ExcelMarkCell 和 ExcelMarkInfo 类型已移至 excel-view.ts 统一管理
27
13
 
28
14
  /**
29
15
  * 处理Excel文件的通用函数,用于提取表头和数据
@@ -133,31 +119,23 @@ export const processExcelFile = async (excelBuffer: ArrayBuffer) => {
133
119
  };
134
120
 
135
121
  /**
136
- * 生成标记了错误/重复数据的Excel文件
122
+ * 创建带错误标记的Excel预览
137
123
  * @param excelBuffer 原始Excel文件的ArrayBuffer
138
- * @param markInfo 标记信息,包含要标记的单元格和表头
139
- * @returns 标记后的Excel文件Blob
124
+ * @param markInfo 标记信息
125
+ * @param fileName 文件名
126
+ * @returns 标记后的Excel预览结果
140
127
  */
141
- export const createMarkedExcelBlob = async (
142
- excelBuffer: ArrayBuffer,
143
- markInfo: ExcelMarkInfo,
144
- ): Promise<{
145
- hasError: boolean;
146
- errBlob?: Blob;
147
- }> => {
148
- const ExcelJS = await import('exceljs');
149
- // 处理Excel文件
128
+ export const createMarkedExcelView = async (excelBuffer: ArrayBuffer, markInfo: ExcelMarkInfo, fileName: string) => {
129
+ // 处理Excel文件获取数据
150
130
  const excelData = await processExcelFile(excelBuffer);
151
- if (!excelData) return { hasError: true };
152
-
153
- const { worksheet, headers: originalHeaders } = excelData;
154
- const { markCells, markHeaders } = markInfo;
155
-
156
- if (markCells.length === 0 && (!markHeaders || markHeaders.length === 0)) {
157
- return { hasError: false }; // 没有需要标记的内容
131
+ if (!excelData) {
132
+ return { success: false, error: 'Excel文件处理失败' };
158
133
  }
159
134
 
160
- // 处理缺失字段,将缺失字段添加到headers
135
+ const { headers: originalHeaders, excelRows } = excelData;
136
+ const { markHeaders } = markInfo;
137
+
138
+ // 处理缺失字段,添加到表头
161
139
  const headers = [...originalHeaders];
162
140
  if (markHeaders && markHeaders.length > 0) {
163
141
  markHeaders.forEach((field) => {
@@ -167,158 +145,18 @@ export const createMarkedExcelBlob = async (
167
145
  });
168
146
  }
169
147
 
170
- // 创建一个新的工作簿和工作表
171
- const newWorkbook = new ExcelJS.default.Workbook();
172
- const newWorksheet = newWorkbook.addWorksheet('Sheet1');
173
-
174
- // 默认列宽
175
- const defaultColumnWidth = 15;
176
-
177
- // 创建错误单元格位置的查询表,用于快速检查
178
- const cellMarkMap = new Map<string, string>();
179
- markCells.forEach(({ row, col, color }) => {
180
- const cellKey = `${row}-${col}`;
181
- cellMarkMap.set(cellKey, color || 'FFFF0000'); // 默认红色
182
- });
183
-
184
- // 复制列宽并应用于新工作表
185
- for (let i = 0; i < headers.length; i++) {
186
- const column = newWorksheet.getColumn(i + 1);
187
-
188
- if (i < worksheet.columnCount && i < originalHeaders.length) {
189
- const originalCol = worksheet.getColumn(i + 1);
190
- if (originalCol && originalCol.width) {
191
- column.width = originalCol.width;
192
- } else {
193
- column.width = defaultColumnWidth;
194
- }
195
- } else {
196
- column.width = defaultColumnWidth;
197
- }
198
- }
199
-
200
- // 复制表头行
201
- const headerRow = newWorksheet.getRow(1);
202
- headers.forEach((header, index) => {
203
- const headerCell = headerRow.getCell(index + 1);
204
- headerCell.value = header;
205
-
206
- const isOriginalHeader = index < originalHeaders.length;
207
- if (isOriginalHeader && index < worksheet.columnCount) {
208
- // 复制原表头样式
209
- const originalCell = worksheet.getRow(1).getCell(index + 1);
210
-
211
- // 复制样式
212
- if (originalCell.style) headerCell.style = JSON.parse(JSON.stringify(originalCell.style));
213
- if (originalCell.font) headerCell.font = JSON.parse(JSON.stringify(originalCell.font));
214
- if (originalCell.alignment) headerCell.alignment = JSON.parse(JSON.stringify(originalCell.alignment));
215
- if (originalCell.border) headerCell.border = JSON.parse(JSON.stringify(originalCell.border));
216
- if (originalCell.numFmt) headerCell.numFmt = originalCell.numFmt;
217
- if (originalCell.fill) headerCell.fill = JSON.parse(JSON.stringify(originalCell.fill));
218
- }
219
-
220
- // 仅标记缺失的表头字段,不标记数据重复的表头
221
- if (markHeaders && markHeaders.includes(header) && !isOriginalHeader) {
222
- headerCell.fill = {
223
- type: 'pattern',
224
- pattern: 'solid',
225
- fgColor: { argb: 'FFFF0000' }, // 红色背景
226
- };
227
-
228
- // 添加白色文字
229
- headerCell.font = {
230
- name: 'Arial',
231
- size: 10,
232
- bold: true,
233
- color: { argb: 'FFFFFFFF' }, // 白色文字
234
- };
148
+ // 扩展数据行以匹配新的表头长度
149
+ const rows = excelRows.map((row) => {
150
+ const newRow = [...row];
151
+ // 为新增的表头字段添加空值
152
+ while (newRow.length < headers.length) {
153
+ newRow.push(null);
235
154
  }
155
+ return newRow;
236
156
  });
237
- headerRow.commit();
238
-
239
- // 处理数据行
240
- worksheet.eachRow((row, rowNumber) => {
241
- if (rowNumber > 1) {
242
- // 跳过表头行
243
- const newRow = newWorksheet.getRow(rowNumber);
244
-
245
- // 使用表头长度来循环每个单元格
246
- for (let colIndex = 0; colIndex < headers.length; colIndex++) {
247
- const newCell = newRow.getCell(colIndex + 1);
248
- const isOriginalHeader = colIndex < originalHeaders.length;
249
-
250
- if (isOriginalHeader && colIndex < worksheet.columnCount) {
251
- // 对原始数据中存在的列
252
- const originalCell = row.getCell(colIndex + 1);
253
-
254
- // 复制值
255
- newCell.value = originalCell.value;
256
-
257
- // 处理对象类型的单元格值,确保正确显示
258
- if (newCell.value !== null && newCell.value !== undefined && typeof newCell.value === 'object') {
259
- // 对于RichText,保留原格式
260
- if (
261
- !('richText' in newCell.value) &&
262
- !('formula' in newCell.value) &&
263
- !('hyperlink' in newCell.value) &&
264
- !(newCell.value instanceof Date)
265
- ) {
266
- // 尝试转换为字符串显示
267
- try {
268
- if ('text' in newCell.value && typeof newCell.value.text === 'string') {
269
- newCell.value = newCell.value.text;
270
- } else {
271
- // 其他对象类型尝试JSON序列化
272
- newCell.value = JSON.stringify(newCell.value);
273
- }
274
- } catch {
275
- // 如果转换失败,使用toString
276
- newCell.value = String(newCell.value);
277
- }
278
- }
279
- }
280
157
 
281
- // 复制样式
282
- if (originalCell.style) newCell.style = JSON.parse(JSON.stringify(originalCell.style));
283
- if (originalCell.font) newCell.font = JSON.parse(JSON.stringify(originalCell.font));
284
- if (originalCell.alignment) newCell.alignment = JSON.parse(JSON.stringify(originalCell.alignment));
285
- if (originalCell.border) newCell.border = JSON.parse(JSON.stringify(originalCell.border));
286
- if (originalCell.numFmt) newCell.numFmt = originalCell.numFmt;
287
- if (originalCell.fill) newCell.fill = JSON.parse(JSON.stringify(originalCell.fill));
288
-
289
- // 检查是否为需要标记的单元格
290
- const cellKey = `${rowNumber}-${colIndex + 1}`;
291
- if (cellMarkMap.has(cellKey)) {
292
- // 设置背景颜色
293
- newCell.fill = {
294
- type: 'pattern',
295
- pattern: 'solid',
296
- fgColor: { argb: cellMarkMap.get(cellKey) },
297
- };
298
- }
299
- } else {
300
- // 为新增列设置空值
301
- newCell.value = null;
302
- }
303
- }
304
-
305
- // 复制行属性
306
- if (row.height) newRow.height = row.height;
307
- if (row.outlineLevel) newRow.outlineLevel = row.outlineLevel;
308
-
309
- newRow.commit();
310
- }
311
- });
312
-
313
- // 生成Excel文件Buffer
314
- const excelOutput = await newWorkbook.xlsx.writeBuffer();
315
-
316
- // 创建Blob对象
317
- const excelBlob = new Blob([excelOutput], {
318
- type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
319
- });
320
-
321
- return { hasError: true, errBlob: excelBlob };
158
+ // 调用excel-view的方法生成带标记的Excel
159
+ return await toExcel({ headers, rows }, fileName, markInfo);
322
160
  };
323
161
 
324
162
  /**
@@ -374,11 +212,24 @@ export const validateExcel = async (
374
212
  color: 'FFFF0000', // 红色
375
213
  }));
376
214
 
377
- // 创建标记过的Excel文件,确保传入所有表头(包括缺失的)
378
- return createMarkedExcelBlob(excelBuffer, {
379
- markCells,
380
- markHeaders: missingFields,
381
- });
215
+ // 使用excel-view统一创建标记Excel
216
+ const markResult = await createMarkedExcelView(
217
+ excelBuffer,
218
+ {
219
+ markCells,
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
+ }
382
233
  }
383
234
 
384
235
  return { hasError: false }; // 没有错误时返回null
@@ -522,8 +373,24 @@ export const checkExcelDuplicates = async (
522
373
  });
523
374
  });
524
375
 
525
- // 创建标记过的Excel文件
526
- return createMarkedExcelBlob(excelBuffer, { markCells, markHeaders: missingDuplicateFields });
376
+ // 使用excel-view统一创建标记Excel
377
+ const markResult = await createMarkedExcelView(
378
+ excelBuffer,
379
+ {
380
+ markCells,
381
+ markHeaders: missingDuplicateFields,
382
+ },
383
+ 'duplicate_errors.xlsx',
384
+ );
385
+
386
+ if (markResult.success && markResult.blobUrl) {
387
+ // 从blob URL创建blob对象
388
+ const response = await fetch(markResult.blobUrl);
389
+ const errBlob = await response.blob();
390
+ return { hasError: true, errBlob };
391
+ } else {
392
+ return { hasError: true };
393
+ }
527
394
  }
528
395
 
529
396
  return { hasError: false }; // 没有重复数据
@@ -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 options.filter((option) => values.includes(option.value));
233
+ return findMatchedOptions(values, options);
196
234
  }
197
235
  // 如果 values 是单个值,返回匹配的单个选项对象
198
236
  else {
199
- return options.find((option) => option.value === values);
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
- // selectedValues.forEach((selectedValue) => {
239
- // Object.entries(outFields).forEach(([targetKey, sourceKey]) => {
240
- // const value = selectedValue[sourceKey];
241
- // setNestedValue(formData, targetKey, value);
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
- };
@@ -25,8 +25,9 @@ interface TableColumn {
25
25
  */
26
26
  export const filterColumns = (columns: TableColumn[], toolCtl?: boolean) => {
27
27
  const userInfoStore = useUserInfo();
28
-
29
- return columns.filter((column) => {
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) {
package/vite.config.ts CHANGED
@@ -47,7 +47,6 @@ export default defineConfig(({ mode }: ConfigEnv) => {
47
47
  '@': path.resolve('./src'),
48
48
  '@skyfox2000/fapi': path.resolve('../502417_fapi'),
49
49
  '@skyfox2000/microbase': path.resolve('../502424_MicroBase'),
50
- '@skyfox2000/webbase': path.resolve('../502428_WebBase'),
51
50
  },
52
51
  extensions: ['.js', '.ts', '.vue', 'json'],
53
52
  },