@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
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import { onMounted, Ref, ref, watch, provide, useAttrs, onActivated, computed } from 'vue';
3
- import { Table, TablePaginationConfig, TableProps } from 'ant-design-vue';
3
+ import { Table, PaginationProps, TableProps } from 'ant-design-vue';
4
4
  import {
5
5
  AppRouter,
6
6
  gridQueryFind,
@@ -17,7 +17,7 @@ import Switch from '../../form/switch/index.vue';
17
17
  import { AnyData } from '@skyfox2000/fapi';
18
18
  import { ProviderKeys } from '@/index';
19
19
 
20
- const props = defineProps<{
20
+ const props = withDefaults(defineProps<{
21
21
  /**
22
22
  * 表格数据控制
23
23
  */
@@ -37,7 +37,7 @@ const props = defineProps<{
37
37
  /**
38
38
  * 自定义分页控制
39
39
  */
40
- pagination?: TableProps['pagination'];
40
+ pagination?: false | PaginationProps;
41
41
  /**
42
42
  * 表格大小配置
43
43
  */
@@ -46,7 +46,10 @@ const props = defineProps<{
46
46
  * 是否禁用启用状态
47
47
  */
48
48
  statusDisabled?: Function;
49
- }>();
49
+ }>(), {
50
+ pagination: undefined,
51
+ });
52
+
50
53
  // 关闭自动继承属性到根元素
51
54
  defineOptions({
52
55
  inheritAttrs: false,
@@ -67,7 +70,7 @@ const curPageSize = ref(gridCtrl.pageSize.value);
67
70
  const curPageNo = ref(gridCtrl.pageNo.value);
68
71
 
69
72
  const dataList = ref<Record<string, AnyData>[]>([]);
70
- const pagination: Ref<false | TablePaginationConfig> = ref<false | TablePaginationConfig>({
73
+ const pagination: Ref<false | PaginationProps> = ref<false | PaginationProps>({
71
74
  ...{
72
75
  total: 0,
73
76
  current: 1,
@@ -89,7 +92,7 @@ const pagination: Ref<false | TablePaginationConfig> = ref<false | TablePaginati
89
92
  }
90
93
  },
91
94
  },
92
- ...props.pagination,
95
+ ...(props.pagination === false ? {} : props.pagination),
93
96
  });
94
97
 
95
98
  if (props.pagination === false) {
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, onUnmounted, useAttrs, watch, shallowRef, PropType } from 'vue';
3
- import { AutoComplete } from 'ant-design-vue';
3
+ import { AutoComplete, Input } from 'ant-design-vue';
4
4
  import {
5
5
  useInputFactory,
6
6
  OptionCommProps,
@@ -143,9 +143,10 @@ onUnmounted(() => {
143
143
  :options="selectOptions"
144
144
  @search="onSearch"
145
145
  @select="onSelected"
146
- :placeholder="'请输入并选择' + labelText"
147
146
  v-bind="attrs"
147
+ :allow-clear="false"
148
148
  >
149
+ <Input allow-clear :placeholder="'请输入并选择' + labelText" />
149
150
  <template #option="{ label }">
150
151
  {{ label }}
151
152
  </template>
@@ -158,4 +159,9 @@ onUnmounted(() => {
158
159
  border-color: #ff4d4f80;
159
160
  box-shadow: 0 0 3px 0 #ff4d4f;
160
161
  }
161
- </style>
162
+
163
+ :deep(input::-webkit-search-cancel-button),
164
+ :deep(input::-webkit-clear-button) {
165
+ display: none !important;
166
+ }
167
+ </style>
@@ -8,6 +8,9 @@ import {
8
8
  loadOption,
9
9
  unloadOption,
10
10
  formValidate,
11
+ onOptionChanged,
12
+ getSelectedLabels,
13
+ SelectValue,
11
14
  } from '@/index';
12
15
  import { Cascader } from 'ant-design-vue';
13
16
  import { DefaultOptionType, ValueType } from 'ant-design-vue/es/vc-cascader/Cascader';
@@ -61,7 +64,7 @@ if (optionCtrl) {
61
64
  (newOptions) => {
62
65
  selectOptions.value = newOptions || [];
63
66
  },
64
- { immediate: true, deep: true }
67
+ { immediate: true, deep: true },
65
68
  );
66
69
  }
67
70
 
@@ -71,13 +74,12 @@ const onChanged = (_: ValueType, selected: DefaultOptionType[]) => {
71
74
  emit('update:value', []);
72
75
  return;
73
76
  }
74
- const labels: string[] = selected.map((item) => item.label!);
77
+ const values = selected.map((item) => item.value);
78
+ const selectedOptions = onOptionChanged(optionCtrl, props, values as unknown as SelectValue);
79
+ const labels: string[] = getSelectedLabels(selectedOptions);
75
80
 
76
81
  emit('update:labels', labels);
77
- emit(
78
- 'update:value',
79
- selected.map((item) => item.value),
80
- );
82
+ emit('update:value', values);
81
83
  if (errInfo?.value.errClass && editorCtrl) {
82
84
  /// 重新开始验证
83
85
  formValidate(editorCtrl);
package/src/index.ts CHANGED
@@ -162,8 +162,31 @@ export {
162
162
  } from '@/utils/form-validate';
163
163
 
164
164
  // form-excel 工具
165
- export { validateExcel, checkExcelDuplicates, processExcelFile, appendExcelData } from '@/utils/form-excel';
166
- export type { ExcelMarkCell, ExcelMarkInfo } from '@/utils/form-excel';
165
+ export {
166
+ validateExcel,
167
+ checkExcelDuplicates,
168
+ processExcelFile,
169
+ appendExcelData,
170
+ createMarkedExcelView,
171
+ } from '@/utils/form-excel';
172
+ // ExcelMarkCell, ExcelMarkInfo 已移至 excel-view 统一管理
173
+
174
+ // form-csv 工具
175
+ export { csvToExcelBlob, processCsvFile } from '@/utils/form-csv';
176
+
177
+ // excel-view 工具
178
+ export {
179
+ csvToNormalized,
180
+ excelToNormalized,
181
+ toExcel,
182
+ csvToExcelView,
183
+ excelToExcelView,
184
+ normalizedToExcelView,
185
+ type NormalizedData,
186
+ type ExcelViewResult,
187
+ type ExcelMarkCell,
188
+ type ExcelMarkInfo,
189
+ } from '@/utils/excel-view';
167
190
 
168
191
  // table 工具
169
192
  export {
@@ -100,8 +100,8 @@ export const OptionCommProps = {
100
100
  },
101
101
  /**
102
102
  * 输出字段转换控制
103
- * - Key:目的字段
104
- * - Value:源字段,支持模板 ${}
103
+ * - Key:目的字段,支持 "." 嵌套
104
+ * - Value:源字段,支持模板 ${} 或者 ${index} 或者 ${index}.${key}
105
105
  */
106
106
  outFields: {
107
107
  type: Object as PropType<Record<string, string>>,
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Excel视图预处理工具
3
+ * 统一处理Excel和CSV数据的显示预处理,支持错误标记
4
+ */
5
+
6
+ /**
7
+ * Excel错误标记单元格
8
+ */
9
+ export interface ExcelMarkCell {
10
+ /** 行号(从1开始) */
11
+ row: number;
12
+ /** 列号(从1开始) */
13
+ col: number;
14
+ /** 标记颜色,默认红色 */
15
+ color?: string;
16
+ }
17
+
18
+ /**
19
+ * Excel标记信息
20
+ */
21
+ export interface ExcelMarkInfo {
22
+ /** 要标记的单元格列表 */
23
+ markCells: ExcelMarkCell[];
24
+ /** 要标记的表头字段列表 */
25
+ markHeaders?: string[];
26
+ }
27
+
28
+ /**
29
+ * 规范数组数据格式
30
+ * @interface NormalizedData
31
+ * @property {string[]} headers - 表头数组,如: ["姓名", "年龄", "地址"]
32
+ * @property {(string|number|null)[][]} rows - 数据行数组,每行对应headers的值
33
+ *
34
+ * @example
35
+ * {
36
+ * headers: ["姓名", "年龄", "地址"],
37
+ * rows: [
38
+ * ["张三", 25, "北京市"],
39
+ * ["李四", 30, "上海市"],
40
+ * ["王五", null, "广州市"]
41
+ * ]
42
+ * }
43
+ */
44
+ export interface NormalizedData {
45
+ /** 表头数组 */
46
+ headers: string[];
47
+ /** 数据行数组,每行为一个数组,对应headers的顺序 */
48
+ rows: any[][];
49
+ }
50
+
51
+ /**
52
+ * Excel预览结果
53
+ */
54
+ export interface ExcelViewResult {
55
+ /** 是否成功 */
56
+ success: boolean;
57
+ /** blob URL用于预览 */
58
+ blobUrl?: string;
59
+ /** 处理后的文件名 */
60
+ fileName?: string;
61
+ /** 错误信息 */
62
+ error?: string;
63
+ }
64
+
65
+ /**
66
+ * 将CSV文本转换为规范数组格式
67
+ * @param csvContent CSV文本内容
68
+ * @returns 规范数组数据
69
+ */
70
+ export const csvToNormalized = (csvContent: string): NormalizedData => {
71
+ const lines = csvContent.split('\n').filter((line) => line.trim() !== '');
72
+
73
+ if (lines.length === 0) {
74
+ return { headers: [], rows: [] };
75
+ }
76
+
77
+ // 简单的CSV解析,处理逗号分隔
78
+ const allRows = lines.map((line) => {
79
+ return line.split(',').map((cell) => {
80
+ const trimmed = cell.trim();
81
+ // 尝试转换为数字
82
+ if (trimmed === '' || trimmed === 'null') return null;
83
+ const num = Number(trimmed);
84
+ return isNaN(num) ? trimmed : num;
85
+ });
86
+ });
87
+
88
+ const headers = allRows[0].map((h) => String(h || ''));
89
+ const rows = allRows.slice(1);
90
+
91
+ return { headers, rows };
92
+ };
93
+
94
+ /**
95
+ * 将Excel ArrayBuffer转换为规范数组格式
96
+ * @param excelBuffer Excel文件的ArrayBuffer
97
+ * @returns 规范数组数据
98
+ */
99
+ export const excelToNormalized = async (excelBuffer: ArrayBuffer): Promise<NormalizedData> => {
100
+ try {
101
+ // 动态导入 exceljs
102
+ const ExcelJS = await import('exceljs');
103
+ const workbook = new ExcelJS.default.Workbook();
104
+ await workbook.xlsx.load(excelBuffer);
105
+
106
+ // 获取第一个工作表
107
+ const worksheet = workbook.worksheets[0];
108
+ if (!worksheet) {
109
+ return { headers: [], rows: [] };
110
+ }
111
+
112
+ const headers: string[] = [];
113
+ const rows: any[][] = [];
114
+
115
+ // 提取表头(第一行)
116
+ worksheet.getRow(1).eachCell((cell) => {
117
+ let headerValue = '';
118
+ if (cell.value !== null && cell.value !== undefined) {
119
+ if (typeof cell.value === 'object') {
120
+ // 处理RichText类型
121
+ if ('richText' in cell.value && Array.isArray(cell.value.richText)) {
122
+ headerValue = cell.value.richText.map((rt: any) => rt.text || '').join('');
123
+ } else if ('text' in cell.value && typeof cell.value.text === 'string') {
124
+ headerValue = cell.value.text;
125
+ } else if (cell.value instanceof Date) {
126
+ headerValue = cell.value.toLocaleDateString();
127
+ } else {
128
+ headerValue = String(cell.value);
129
+ }
130
+ } else {
131
+ headerValue = String(cell.value);
132
+ }
133
+ }
134
+ headers.push(headerValue);
135
+ });
136
+
137
+ // 提取数据行
138
+ worksheet.eachRow((row, rowNumber) => {
139
+ if (rowNumber > 1) {
140
+ // 跳过表头
141
+ const rowData: any[] = [];
142
+ headers.forEach((_, idx) => {
143
+ const cell = row.getCell(idx + 1);
144
+ const cellValue = cell.value;
145
+
146
+ if (cellValue !== null && cellValue !== undefined) {
147
+ if (typeof cellValue === 'object') {
148
+ if ('richText' in cellValue && Array.isArray(cellValue.richText)) {
149
+ rowData.push(cellValue.richText.map((rt: any) => rt.text || '').join(''));
150
+ } else if ('text' in cellValue && typeof cellValue.text === 'string') {
151
+ rowData.push(cellValue.text);
152
+ } else if (cellValue instanceof Date) {
153
+ rowData.push(cellValue);
154
+ } else {
155
+ rowData.push(cellValue);
156
+ }
157
+ } else {
158
+ rowData.push(cellValue);
159
+ }
160
+ } else {
161
+ rowData.push(null);
162
+ }
163
+ });
164
+ rows.push(rowData);
165
+ }
166
+ });
167
+
168
+ return { headers, rows };
169
+ } catch (error) {
170
+ console.error('Excel解析失败:', error);
171
+ return { headers: [], rows: [] };
172
+ }
173
+ };
174
+
175
+ /**
176
+ * 将规范数组数据转换为Excel预览,支持错误标记
177
+ * @param data 规范数组数据
178
+ * @param fileName 文件名
179
+ * @param markInfo 可选的标记信息
180
+ * @returns Excel预览结果
181
+ */
182
+ export const toExcel = async (
183
+ data: NormalizedData,
184
+ fileName: string,
185
+ markInfo?: ExcelMarkInfo,
186
+ ): Promise<ExcelViewResult> => {
187
+ try {
188
+ // 动态导入 exceljs
189
+ const ExcelJS = await import('exceljs');
190
+ const workbook = new ExcelJS.default.Workbook();
191
+ const worksheet = workbook.addWorksheet('Sheet1');
192
+
193
+ const { headers, rows } = data;
194
+
195
+ if (headers.length === 0) {
196
+ throw new Error('数据为空');
197
+ }
198
+
199
+ // 创建错误单元格映射
200
+ const cellMarkMap = new Map<string, string>();
201
+ if (markInfo?.markCells) {
202
+ markInfo.markCells.forEach(({ row, col, color }) => {
203
+ const cellKey = `${row}-${col}`;
204
+ cellMarkMap.set(cellKey, color || 'FFFF0000');
205
+ });
206
+ }
207
+
208
+ // 添加表头
209
+ const headerRow = worksheet.getRow(1);
210
+ headers.forEach((header, index) => {
211
+ const headerCell = headerRow.getCell(index + 1);
212
+ headerCell.value = header;
213
+
214
+ // 设置默认表头样式
215
+ headerCell.font = { bold: true };
216
+ headerCell.fill = {
217
+ type: 'pattern',
218
+ pattern: 'solid',
219
+ fgColor: { argb: 'FFE0E0E0' },
220
+ };
221
+
222
+ // 标记缺失的表头字段
223
+ if (markInfo?.markHeaders && markInfo.markHeaders.includes(header)) {
224
+ headerCell.fill = {
225
+ type: 'pattern',
226
+ pattern: 'solid',
227
+ fgColor: { argb: 'FFFF0000' },
228
+ };
229
+ headerCell.font = {
230
+ name: 'Arial',
231
+ size: 10,
232
+ bold: true,
233
+ color: { argb: 'FFFFFFFF' },
234
+ };
235
+ }
236
+ });
237
+
238
+ // 添加数据行
239
+ rows.forEach((row, index) => {
240
+ const worksheetRow = worksheet.getRow(index + 2);
241
+ row.forEach((cell, cellIndex) => {
242
+ const worksheetCell = worksheetRow.getCell(cellIndex + 1);
243
+ worksheetCell.value = cell;
244
+
245
+ // 检查是否需要标记单元格
246
+ const cellKey = `${index + 2}-${cellIndex + 1}`;
247
+ if (cellMarkMap.has(cellKey)) {
248
+ worksheetCell.fill = {
249
+ type: 'pattern',
250
+ pattern: 'solid',
251
+ fgColor: { argb: cellMarkMap.get(cellKey) },
252
+ };
253
+ }
254
+ });
255
+ });
256
+
257
+ // 自动调整列宽
258
+ worksheet.columns.forEach((column) => {
259
+ column.width = 15;
260
+ });
261
+
262
+ // 生成Excel文件Buffer
263
+ const excelBuffer = await workbook.xlsx.writeBuffer();
264
+
265
+ // 创建Blob对象
266
+ const blob = new Blob([excelBuffer], {
267
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
268
+ });
269
+
270
+ // 创建blob URL
271
+ const blobUrl = URL.createObjectURL(blob);
272
+
273
+ return {
274
+ success: true,
275
+ blobUrl,
276
+ fileName: fileName.replace(/\.(csv|xlsx?)$/i, '.xlsx'),
277
+ };
278
+ } catch (error) {
279
+ console.error('转换Excel失败:', error);
280
+ return {
281
+ success: false,
282
+ error: error instanceof Error ? error.message : '未知错误',
283
+ };
284
+ }
285
+ };
286
+
287
+ /**
288
+ * CSV文本转Excel预览
289
+ * @param csvContent CSV文本内容
290
+ * @param fileName 文件名
291
+ * @returns Excel预览结果
292
+ */
293
+ export const csvToExcelView = async (csvContent: string, fileName: string): Promise<ExcelViewResult> => {
294
+ try {
295
+ const normalized = csvToNormalized(csvContent);
296
+ return await toExcel(normalized, fileName);
297
+ } catch (error) {
298
+ return {
299
+ success: false,
300
+ error: error instanceof Error ? error.message : 'CSV处理失败',
301
+ };
302
+ }
303
+ };
304
+
305
+ /**
306
+ * Excel ArrayBuffer转Excel预览
307
+ * @param excelBuffer Excel文件的ArrayBuffer
308
+ * @param fileName 文件名
309
+ * @returns Excel预览结果
310
+ */
311
+ export const excelToExcelView = async (excelBuffer: ArrayBuffer, fileName: string): Promise<ExcelViewResult> => {
312
+ try {
313
+ // Excel文件直接创建blob URL
314
+ const blob = new Blob([excelBuffer], {
315
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
316
+ });
317
+ const blobUrl = URL.createObjectURL(blob);
318
+
319
+ return {
320
+ success: true,
321
+ blobUrl,
322
+ fileName,
323
+ };
324
+ } catch (error) {
325
+ return {
326
+ success: false,
327
+ error: error instanceof Error ? error.message : 'Excel处理失败',
328
+ };
329
+ }
330
+ };
331
+
332
+ /**
333
+ * 规范数组转Excel预览
334
+ * @param data 规范数组数据
335
+ * @param fileName 文件名
336
+ * @returns Excel预览结果
337
+ */
338
+ export const normalizedToExcelView = async (data: NormalizedData, fileName: string): Promise<ExcelViewResult> => {
339
+ return await toExcel(data, fileName);
340
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * CSV数据处理工具 - 统一使用excel-view处理
3
+ */
4
+ import { csvToExcelView } from './excel-view';
5
+
6
+ /**
7
+ * 将CSV文本内容转换为Excel blob URL用于预览
8
+ * @param csvContent CSV文本内容
9
+ * @param fileName 文件名
10
+ * @returns blob URL和文件信息
11
+ * @deprecated 使用 csvToExcelView 替代
12
+ */
13
+ export const csvToExcelBlob = async (csvContent: string, fileName: string) => {
14
+ const result = await csvToExcelView(csvContent, fileName);
15
+ if (result.success) {
16
+ return {
17
+ blobUrl: result.blobUrl!,
18
+ fileName: result.fileName!,
19
+ blob: null, // 不再返回blob对象,使用URL即可
20
+ };
21
+ } else {
22
+ throw new Error(result.error || 'CSV转Excel失败');
23
+ }
24
+ };
25
+
26
+ /**
27
+ * 处理CSV文件上传前的预览
28
+ * @param csvBuffer CSV文件的ArrayBuffer
29
+ * @param fileName 文件名
30
+ * @returns Promise包含处理结果
31
+ */
32
+ export const processCsvFile = async (csvBuffer: ArrayBuffer, fileName: string) => {
33
+ try {
34
+ // 将ArrayBuffer转换为文本
35
+ const decoder = new TextDecoder('utf-8');
36
+ const text = decoder.decode(csvBuffer);
37
+
38
+ // 使用excel-view统一处理
39
+ const result = await csvToExcelView(text, fileName);
40
+
41
+ return {
42
+ success: result.success,
43
+ blobUrl: result.blobUrl,
44
+ fileName: result.fileName,
45
+ error: result.error,
46
+ csvContent: text, // 保留原始CSV内容
47
+ };
48
+ } catch (error) {
49
+ console.error('CSV文件处理失败:', error);
50
+ return {
51
+ success: false,
52
+ error: error instanceof Error ? error.message : '未知错误',
53
+ };
54
+ }
55
+ };