@skyfox2000/webui 1.3.4 → 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 (28) hide show
  1. package/lib/assets/modules/{file-upload-BBlFaIXB.js → file-upload-C0twqMV5.js} +1 -1
  2. package/lib/assets/modules/{index-BG1SqSVl.js → index-C4CryM-R.js} +1 -1
  3. package/lib/assets/modules/{index-4kDAt8nS.js → index-CKJIxasX.js} +2 -2
  4. package/lib/assets/modules/{index-m5rogIyM.js → index-D1XAa1Uo.js} +2 -2
  5. package/lib/assets/modules/{menuTabs-tPIz4a89.js → menuTabs-BrYQa4UO.js} +2 -2
  6. package/lib/assets/modules/{toolIcon-DwWoD9TN.js → toolIcon-B-g9pyE4.js} +1 -1
  7. package/lib/assets/modules/{uploadList-D_Z-Y2tw.js → uploadList-0f2FA_5s.js} +489 -454
  8. package/lib/assets/modules/{uploadList-Da7mQUNK.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 +28 -21
  15. package/lib/es/UploadForm/index.js +4 -4
  16. package/lib/typings/option.d.ts +2 -2
  17. package/lib/utils/options.d.ts +2 -2
  18. package/lib/webui.css +1 -1
  19. package/lib/webui.es.js +371 -374
  20. package/package.json +1 -1
  21. package/src/components/common/loading/index.vue +1 -1
  22. package/src/components/content/dialog/excelForm.vue +9 -8
  23. package/src/components/content/table/index.vue +9 -6
  24. package/src/components/form/autoComplete/index.vue +9 -3
  25. package/src/components/form/cascader/index.vue +8 -6
  26. package/src/typings/option.d.ts +2 -2
  27. package/src/utils/options.ts +80 -22
  28. package/src/utils/table.ts +15 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyfox2000/webui",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "description": "后台前端通用组件定义",
5
5
  "type": "module",
6
6
  "keywords": [],
@@ -4,7 +4,7 @@ import { useAttrs } from 'vue';
4
4
  const attrs = useAttrs()
5
5
  </script>
6
6
  <template>
7
- <div class="absolute z-9999 w-full h-full top-0 flex flex-flow row items-center justify-center">
7
+ <div class="absolute z-[9999] w-full h-full top-0 flex flex-flow row items-center justify-center">
8
8
  <Spin style="margin-top: -10%" v-bind="attrs">
9
9
  </Spin>
10
10
  </div>
@@ -171,9 +171,9 @@ const duplicateUrl = ref(uploadParams.value?.duplicateUrl);
171
171
  const loadPreviewFile = async () => {
172
172
  if (!props.previewUrl || !excelCtrl) return;
173
173
 
174
+ excelCtrl.isFormLoading.value = true;
174
175
  try {
175
176
  let result: ApiResponse<AnyData> | null = null;
176
- excelCtrl.isFormLoading.value = true;
177
177
 
178
178
  // 根据请求方法选择不同的处理方式
179
179
  if (props.previewUrl.method === 'GET') {
@@ -215,7 +215,9 @@ const loadPreviewFile = async () => {
215
215
  console.error('预览文件加载错误:', error);
216
216
  message.error('文件加载失败:' + (error?.message || '未知错误'));
217
217
  } finally {
218
- excelCtrl.isFormLoading.value = false;
218
+ setTimeout(() => {
219
+ excelCtrl.isFormLoading.value = false;
220
+ }, 1000);
219
221
  }
220
222
  };
221
223
 
@@ -241,11 +243,10 @@ const processExcelContent = (content: string, mimeType?: string, filename: strin
241
243
  const isCsvContent = (data: string) => data.includes(',') || data.includes('\n');
242
244
 
243
245
  // 判断是否为CSV类型
244
- const isCsvType = (type?: string, filename?: string) =>
245
- type === 'text/csv' || filename?.toLowerCase().includes('.csv');
246
+ const isCsvType = (type?: string, filename?: string) => type === 'text/csv' || filename?.toLowerCase().includes('.csv');
246
247
 
247
- // 判断是否为Excel类型
248
- const isExcelType = (type?: string, filename?: string) =>
248
+ // 判断是否为Excel类型
249
+ const isExcelType = (type?: string, filename?: string) =>
249
250
  type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
250
251
  type === 'application/vnd.ms-excel' ||
251
252
  filename?.toLowerCase().match(/\.(xlsx|xls)$/);
@@ -656,9 +657,9 @@ const handleError = () => {
656
657
  </div>
657
658
  </div>
658
659
 
659
- <div class="flex gap-4">
660
+ <div class="flex gap-4 relative">
660
661
  <!-- 左侧Excel显示区域 -->
661
- <Loading v-if="excelCtrl.isFormLoading" />
662
+ <Loading size="large" v-if="excelCtrl.isFormLoading.value" />
662
663
  <div
663
664
  class="flex-shrink-0 relative border border-gray-200 rounded-md overflow-hidden"
664
665
  :class="[validationRules.length === 0 ? 'w-[100%]' : 'w-[80%]']"
@@ -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,
@@ -141,11 +141,12 @@ onUnmounted(() => {
141
141
  v-model:value="innerValue"
142
142
  :class="['w-full', errInfo?.errClass]"
143
143
  :options="selectOptions"
144
- :placeholder="'请输入并选择' + labelText"
145
144
  @search="onSearch"
146
145
  @select="onSelected"
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);
@@ -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>>,
@@ -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) {