@skyfox2000/webui 1.4.16 → 1.4.18
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/{baseLayout-DQQ-vZCD.js → baseLayout-D3_NxEzk.js} +5 -5
- package/lib/assets/modules/{file-upload-Dc61Tbof.js → file-upload-CNBcbAAZ.js} +1 -1
- package/lib/assets/modules/{index-DrGkF0qX.js → index-CDr74akE.js} +2 -2
- package/lib/assets/modules/{index-CX7vtghj.js → index-D14BsF7C.js} +1 -1
- package/lib/assets/modules/{index-Dz624mC-.js → index-DOlO_4KL.js} +2 -2
- package/lib/assets/modules/{menuTabs-BwAVx2tx.js → menuTabs-CqAhoF--.js} +7 -7
- package/lib/assets/modules/{toolIcon-B2btIpdL.js → toolIcon-BnkqBipR.js} +1 -1
- package/lib/assets/modules/{upload-template-BqG06zPn.js → upload-template-KI-IFzyp.js} +217 -208
- package/lib/assets/modules/{uploadList-DlbvQOtR.js → uploadList-DnFXg_B3.js} +4 -4
- package/lib/es/AceEditor/index.js +3 -3
- package/lib/es/BasicLayout/index.js +6 -6
- package/lib/es/Error403/index.js +1 -1
- package/lib/es/Error404/index.js +1 -1
- package/lib/es/ExcelForm/index.js +5 -5
- package/lib/es/MenuLayout/index.js +8 -8
- package/lib/es/TemplateFile/index.js +5 -5
- package/lib/es/UploadForm/index.js +4 -4
- package/lib/utils/export-table.d.ts +7 -2
- package/lib/utils/form-validate.d.ts +2 -1
- package/lib/webui.es.js +1099 -1097
- package/package.json +1 -1
- package/src/components/content/form/formItem.vue +2 -16
- package/src/components/content/search/searchItem.vue +32 -8
- package/src/utils/export-table.ts +92 -61
- package/src/utils/form-validate.ts +20 -1
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { EditorControl, ProviderKeys, useFormItemFactory
|
|
2
|
+
import { EditorControl, ProviderKeys, useFormItemFactory } from '@/index';
|
|
3
3
|
import { FormItem, message } from 'ant-design-vue';
|
|
4
4
|
import { Helper } from '../../common';
|
|
5
5
|
import { computed, inject, ref, useAttrs } from 'vue';
|
|
6
6
|
import { AnyData } from '@skyfox2000/fapi';
|
|
7
|
+
import { getRule } from '@/utils/form-validate';
|
|
7
8
|
|
|
8
9
|
const props = defineProps<{
|
|
9
10
|
/**
|
|
@@ -48,21 +49,6 @@ setTimeout(() => {
|
|
|
48
49
|
visible.value = true;
|
|
49
50
|
}, 30);
|
|
50
51
|
|
|
51
|
-
/**
|
|
52
|
-
* 递归获取规则
|
|
53
|
-
* - async-validator的语法规范
|
|
54
|
-
*/
|
|
55
|
-
const getRule = (rule: Array<string>, ruleObj: Record<string, any> | undefined): ValidateRuleItem | undefined => {
|
|
56
|
-
if (!ruleObj) {
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
const [key, ...rest] = rule;
|
|
60
|
-
if (rule.length === 1) {
|
|
61
|
-
return ruleObj[key];
|
|
62
|
-
}
|
|
63
|
-
if (!ruleObj[key]) return undefined;
|
|
64
|
-
return getRule(rest, ruleObj[key].fields as Record<string, any>);
|
|
65
|
-
};
|
|
66
52
|
/**
|
|
67
53
|
* 是否必填
|
|
68
54
|
* - 如果rule为空,则不显示必填标记
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { inject, useAttrs } from 'vue';
|
|
2
|
+
import { computed, inject, useAttrs } from 'vue';
|
|
3
3
|
import { ProviderKeys, EditorControl, useFormItemFactory } from '@/index';
|
|
4
4
|
import { FormItem } from 'ant-design-vue';
|
|
5
5
|
import { AnyData } from '@skyfox2000/fapi';
|
|
6
|
+
import { getRule } from '@/utils/form-validate';
|
|
7
|
+
import message from 'vue-m-message';
|
|
6
8
|
|
|
7
9
|
const props = defineProps<{
|
|
8
10
|
/**
|
|
@@ -26,16 +28,38 @@ const attrs = useAttrs(); // 手动获取 $attrs
|
|
|
26
28
|
|
|
27
29
|
const editorCtrl = inject(ProviderKeys.EditorControl, undefined) as EditorControl<AnyData> | undefined;
|
|
28
30
|
const errInfo = useFormItemFactory(props, editorCtrl);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 是否必填
|
|
34
|
+
* - 如果rule为空,则不显示必填标记
|
|
35
|
+
* - 如果rule不为空,则根据formRules中的required属性判断是否必填
|
|
36
|
+
*/
|
|
37
|
+
const required = computed(() => {
|
|
38
|
+
if (!props.rule) {
|
|
39
|
+
// 如果rule为空,则不显示必填标记
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// 如果rule包含.,则表示是对象属性
|
|
43
|
+
const rule = getRule(props.rule.split('.'), editorCtrl?.formRules?.value);
|
|
44
|
+
if (!rule) {
|
|
45
|
+
message.error(`"${props.label}" 的验证规则 \`${props.rule}\` 不存在`);
|
|
46
|
+
errInfo.value.errClass = 'text-[#ff4d4f]';
|
|
47
|
+
errInfo.value.msg = `规则 \`${props.rule}\` 不存在,请检查代码!`;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (!rule.required) {
|
|
51
|
+
// 如果rule.required为false,则表示该字段是可选字段,不需要验证
|
|
52
|
+
errInfo.value.errClass = '';
|
|
53
|
+
errInfo.value.msg = '';
|
|
54
|
+
}
|
|
55
|
+
return rule.required ?? true;
|
|
56
|
+
});
|
|
57
|
+
|
|
29
58
|
</script>
|
|
30
59
|
<template>
|
|
31
60
|
<div class="w-1/3 relative mb-1">
|
|
32
|
-
<FormItem
|
|
33
|
-
:
|
|
34
|
-
class="w-[90%] relative"
|
|
35
|
-
v-bind="attrs"
|
|
36
|
-
:class="[rule ? '' : 'mb-3', width]"
|
|
37
|
-
:labelCol="{ span: 6 }"
|
|
38
|
-
>
|
|
61
|
+
<FormItem :required="required" class="w-[90%] relative" v-bind="attrs" :class="[rule ? '' : 'mb-3', width]"
|
|
62
|
+
:labelCol="{ span: 6 }">
|
|
39
63
|
<template #label v-if="label">
|
|
40
64
|
<span :class="[errInfo.errClass ? 'text-[#ff4d4f]' : '', 'w-full']"> {{ label }}</span>
|
|
41
65
|
</template>
|
|
@@ -11,13 +11,92 @@ import { LoginExpiredError, useUserInfo } from '@/stores/userInfo';
|
|
|
11
11
|
// 表格列类型定义 (适配 ant-design-vue)
|
|
12
12
|
export interface TableColumn {
|
|
13
13
|
title: string;
|
|
14
|
-
dataIndex?: string;
|
|
14
|
+
dataIndex?: string | string[];
|
|
15
15
|
key?: string;
|
|
16
16
|
visible?: boolean; // 是否显示列
|
|
17
17
|
export?: boolean; // 是否导出列
|
|
18
|
-
customRender?: (
|
|
18
|
+
customRender?: (options: { text: any; record: Record<string, any>; index: number; column: TableColumn }) => any;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 将 dataIndex 转换为字符串形式
|
|
23
|
+
* 如果是数组则转换为点号连接的字符串,否则直接返回原值或空字符串
|
|
24
|
+
* @param dataIndex 列的 dataIndex
|
|
25
|
+
*/
|
|
26
|
+
const toString = (dataIndex: string | string[] | undefined): string => {
|
|
27
|
+
if (!dataIndex) return '';
|
|
28
|
+
if (Array.isArray(dataIndex)) {
|
|
29
|
+
return dataIndex.join('.');
|
|
30
|
+
}
|
|
31
|
+
return dataIndex;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 准备 CSV 字段配置
|
|
36
|
+
* @param columns 需要导出的列配置
|
|
37
|
+
*/
|
|
38
|
+
const getFields = (columns: TableColumn[]) => {
|
|
39
|
+
return columns.map((col) => ({
|
|
40
|
+
label: col.title,
|
|
41
|
+
value: toString(col.dataIndex) || col.key || '',
|
|
42
|
+
}));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 处理数据行,应用自定义渲染
|
|
47
|
+
* @param data 原始数据
|
|
48
|
+
* @param columns 列配置
|
|
49
|
+
*/
|
|
50
|
+
const processData = <T extends Record<string, any>>(data: T[], columns: TableColumn[]) => {
|
|
51
|
+
return data.map((row, index) => {
|
|
52
|
+
const newRow: Record<string, any> = { ...row };
|
|
53
|
+
|
|
54
|
+
columns.forEach((col) => {
|
|
55
|
+
const field = toString(col.dataIndex) || col.key;
|
|
56
|
+
if (!field) return;
|
|
57
|
+
|
|
58
|
+
// 应用自定义渲染
|
|
59
|
+
if (col.customRender) {
|
|
60
|
+
newRow[field] = col.customRender({
|
|
61
|
+
text: row[field],
|
|
62
|
+
record: row,
|
|
63
|
+
index: index,
|
|
64
|
+
column: col,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return newRow;
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 生成并下载 CSV 文件
|
|
74
|
+
* @param fileName 文件名
|
|
75
|
+
* @param fields CSV 字段配置
|
|
76
|
+
* @param data 处理后的数据
|
|
77
|
+
*/
|
|
78
|
+
const downloadCSV = async <T extends Record<string, any>>(
|
|
79
|
+
fileName: string,
|
|
80
|
+
fields: Array<{ label: string; value: string }>,
|
|
81
|
+
data: T[],
|
|
82
|
+
) => {
|
|
83
|
+
try {
|
|
84
|
+
const mod = await import('@json2csv/plainjs');
|
|
85
|
+
const JSON2CSVParser = mod.Parser;
|
|
86
|
+
|
|
87
|
+
// 生成 CSV 内容
|
|
88
|
+
const parser = new JSON2CSVParser({ fields });
|
|
89
|
+
const csvContent = parser.parse(data);
|
|
90
|
+
|
|
91
|
+
// 创建并下载文件
|
|
92
|
+
const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv' });
|
|
93
|
+
downloadBlob(blob, fileName);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('导出失败:', error);
|
|
96
|
+
message.error('文件导出失败,请稍后重试');
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
21
100
|
/**
|
|
22
101
|
* 导出选中行数据为 CSV 文件
|
|
23
102
|
* @param fileName 文件名(支持 {YYYY}、{YY}、{MM}、{DD}、{HH}、{mm}、{ss} 时间格式)
|
|
@@ -30,8 +109,6 @@ export const exportSelectedRows = async <T extends Record<string, any>>(
|
|
|
30
109
|
selectRows: T[],
|
|
31
110
|
) => {
|
|
32
111
|
try {
|
|
33
|
-
const mod = await import('@json2csv/plainjs');
|
|
34
|
-
const JSON2CSVParser = mod.Parser;
|
|
35
112
|
// 1. 处理文件名中的日期格式
|
|
36
113
|
const processedFileName = formatFileName(fileName);
|
|
37
114
|
|
|
@@ -39,37 +116,15 @@ export const exportSelectedRows = async <T extends Record<string, any>>(
|
|
|
39
116
|
const exportColumns = columns.filter((col) => col.visible !== false && col.export !== false);
|
|
40
117
|
|
|
41
118
|
// 3. 准备 CSV 字段配置
|
|
42
|
-
const fields = exportColumns
|
|
43
|
-
label: col.title,
|
|
44
|
-
value: col.dataIndex || col.key || '',
|
|
45
|
-
}));
|
|
119
|
+
const fields = getFields(exportColumns);
|
|
46
120
|
|
|
47
121
|
// 4. 处理数据行
|
|
48
|
-
const processedData = selectRows
|
|
49
|
-
const newRow: Record<string, any> = { ...row };
|
|
50
|
-
|
|
51
|
-
exportColumns.forEach((col) => {
|
|
52
|
-
const field = col.dataIndex || col.key;
|
|
53
|
-
if (!field) return;
|
|
54
|
-
|
|
55
|
-
// 应用自定义渲染
|
|
56
|
-
if (col.customRender) {
|
|
57
|
-
newRow[field] = col.customRender(row[field], row);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
return newRow;
|
|
61
|
-
});
|
|
122
|
+
const processedData = processData(selectRows, exportColumns);
|
|
62
123
|
|
|
63
|
-
// 5. 生成 CSV
|
|
64
|
-
|
|
65
|
-
const csvContent = parser.parse(processedData);
|
|
66
|
-
|
|
67
|
-
// 6. 创建并下载文件
|
|
68
|
-
const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv' });
|
|
69
|
-
downloadBlob(blob, processedFileName);
|
|
124
|
+
// 5. 生成 CSV 内容并下载
|
|
125
|
+
await downloadCSV(processedFileName, fields, processedData);
|
|
70
126
|
} catch (error) {
|
|
71
127
|
console.error('导出失败:', error);
|
|
72
|
-
// throw new Error('文件导出失败,请稍后重试');
|
|
73
128
|
message.error('文件导出失败,请稍后重试');
|
|
74
129
|
}
|
|
75
130
|
};
|
|
@@ -91,8 +146,6 @@ export const exportResults = async <T extends Record<string, any>>(
|
|
|
91
146
|
url: IUrlInfo,
|
|
92
147
|
) => {
|
|
93
148
|
try {
|
|
94
|
-
const mod = await import('@json2csv/plainjs');
|
|
95
|
-
const JSON2CSVParser = mod.Parser;
|
|
96
149
|
// 1. 处理文件名中的日期格式
|
|
97
150
|
const processedFileName = formatFileName(fileName);
|
|
98
151
|
|
|
@@ -100,10 +153,7 @@ export const exportResults = async <T extends Record<string, any>>(
|
|
|
100
153
|
const exportColumns = columns.filter((col) => col.visible !== false);
|
|
101
154
|
|
|
102
155
|
// 3. 准备 CSV 字段配置
|
|
103
|
-
const fields = exportColumns
|
|
104
|
-
label: col.title,
|
|
105
|
-
value: col.dataIndex || col.key || '',
|
|
106
|
-
}));
|
|
156
|
+
const fields = getFields(exportColumns);
|
|
107
157
|
|
|
108
158
|
// 4. 获取数据
|
|
109
159
|
let pageCtrl = gridCtrl.page;
|
|
@@ -113,34 +163,16 @@ export const exportResults = async <T extends Record<string, any>>(
|
|
|
113
163
|
if (url.authorize === undefined) url.authorize = pageCtrl.authorize;
|
|
114
164
|
|
|
115
165
|
gridCtrl.isGridLoading.value = true;
|
|
116
|
-
return httpPost<T>(url, newParams).then((result: ApiResponse<T> | null) => {
|
|
166
|
+
return httpPost<T>(url, newParams).then(async (result: ApiResponse<T> | null) => {
|
|
117
167
|
gridCtrl.isGridLoading.value = false;
|
|
118
168
|
if (result?.status === ResStatus.SUCCESS) {
|
|
119
169
|
if (result.data) {
|
|
120
170
|
// 5. 处理数据行
|
|
121
171
|
let results = result.data as unknown as T[];
|
|
122
|
-
const processedData = results
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const field = col.dataIndex || col.key;
|
|
127
|
-
if (!field) return;
|
|
128
|
-
|
|
129
|
-
// 应用自定义渲染
|
|
130
|
-
if (col.customRender) {
|
|
131
|
-
newRow[field] = col.customRender(row[field], row);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
return newRow;
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// 5. 生成 CSV 内容
|
|
138
|
-
const parser = new JSON2CSVParser({ fields });
|
|
139
|
-
const csvContent = parser.parse(processedData);
|
|
140
|
-
|
|
141
|
-
// 6. 创建并下载文件
|
|
142
|
-
const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv' });
|
|
143
|
-
downloadBlob(blob, processedFileName);
|
|
172
|
+
const processedData = processData(results, exportColumns);
|
|
173
|
+
|
|
174
|
+
// 6. 生成 CSV 内容并下载
|
|
175
|
+
await downloadCSV(processedFileName, fields, processedData);
|
|
144
176
|
}
|
|
145
177
|
} else if (result?.errno == LoginExpiredError) {
|
|
146
178
|
const userInfoStore = useUserInfo();
|
|
@@ -151,10 +183,9 @@ export const exportResults = async <T extends Record<string, any>>(
|
|
|
151
183
|
});
|
|
152
184
|
} catch (error) {
|
|
153
185
|
console.error('导出失败:', error);
|
|
154
|
-
// throw new Error('文件导出失败,请稍后重试');
|
|
155
186
|
message.error('文件导出失败,请稍后重试');
|
|
156
187
|
}
|
|
157
188
|
};
|
|
158
189
|
|
|
159
190
|
// 后端处理
|
|
160
|
-
// 下载文件或二进制输出文件
|
|
191
|
+
// 下载文件或二进制输出文件
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { inject, toRaw, ref, provide, watch, Ref } from 'vue';
|
|
2
2
|
import Validator from 'async-validator';
|
|
3
|
-
import { EditorControl, InputFactoryItems, ValidateError, ValidateRule } from '@/typings/form.d';
|
|
3
|
+
import { EditorControl, InputFactoryItems, ValidateError, ValidateRule, ValidateRuleItem } from '@/typings/form.d';
|
|
4
4
|
import { ProviderKeys } from '@/typings/page.d';
|
|
5
5
|
import { AnyData } from '@skyfox2000/fapi';
|
|
6
6
|
import { isEmpty } from './isEmpty';
|
|
@@ -383,3 +383,22 @@ export const useFormItemFactory = (options: RuleFactoryOptions, editorCtrl?: Edi
|
|
|
383
383
|
provide(ProviderKeys.ErrInfo, errInfo);
|
|
384
384
|
return errInfo;
|
|
385
385
|
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* 递归获取规则
|
|
389
|
+
* - async-validator的语法规范
|
|
390
|
+
*/
|
|
391
|
+
export const getRule = (
|
|
392
|
+
rule: Array<string>,
|
|
393
|
+
ruleObj: Record<string, any> | undefined,
|
|
394
|
+
): ValidateRuleItem | undefined => {
|
|
395
|
+
if (!ruleObj) {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
const [key, ...rest] = rule;
|
|
399
|
+
if (rule.length === 1) {
|
|
400
|
+
return ruleObj[key];
|
|
401
|
+
}
|
|
402
|
+
if (!ruleObj[key]) return undefined;
|
|
403
|
+
return getRule(rest, ruleObj[key].fields as Record<string, any>);
|
|
404
|
+
};
|