@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.
- package/lib/assets/modules/{file-upload-BBlFaIXB.js → file-upload-C0twqMV5.js} +1 -1
- package/lib/assets/modules/{index-BG1SqSVl.js → index-C4CryM-R.js} +1 -1
- package/lib/assets/modules/{index-4kDAt8nS.js → index-CKJIxasX.js} +2 -2
- package/lib/assets/modules/{index-m5rogIyM.js → index-D1XAa1Uo.js} +2 -2
- package/lib/assets/modules/{menuTabs-tPIz4a89.js → menuTabs-BrYQa4UO.js} +2 -2
- package/lib/assets/modules/{toolIcon-DwWoD9TN.js → toolIcon-B-g9pyE4.js} +1 -1
- package/lib/assets/modules/{uploadList-D_Z-Y2tw.js → uploadList-0f2FA_5s.js} +489 -454
- package/lib/assets/modules/{uploadList-Da7mQUNK.js → uploadList-DCWRIxPJ.js} +4 -4
- package/lib/components/content/table/index.vue.d.ts +95 -4
- 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 +28 -21
- package/lib/es/UploadForm/index.js +4 -4
- package/lib/typings/option.d.ts +2 -2
- package/lib/utils/options.d.ts +2 -2
- package/lib/webui.css +1 -1
- package/lib/webui.es.js +371 -374
- package/package.json +1 -1
- package/src/components/common/loading/index.vue +1 -1
- package/src/components/content/dialog/excelForm.vue +9 -8
- 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/typings/option.d.ts +2 -2
- package/src/utils/options.ts +80 -22
- package/src/utils/table.ts +15 -2
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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?:
|
|
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 |
|
|
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
|
-
|
|
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
|
|
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/typings/option.d.ts
CHANGED
|
@@ -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>>,
|
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) {
|