@skyfox2000/webui 1.0.14 → 1.2.1
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-CBUcsUnR.js +170 -0
- package/lib/assets/modules/form-validate-CgX7aR7T.js +297 -0
- package/lib/assets/modules/index-Civhd8xG.js +112 -0
- package/lib/assets/modules/index-DQMdt51R.js +726 -0
- package/lib/assets/modules/{index-BEWJ_qAH.js → index-DmWrkTXX.js} +1 -1
- package/lib/assets/modules/{menuTabs-BXdbFZor.js → menuTabs-BRYvFWA-.js} +131 -121
- package/lib/assets/modules/settingInfo-BZakNKIN.js +999 -0
- package/lib/assets/modules/uploadList-B7XoxGOh.js +278 -0
- package/lib/components/common/icon/index.vue.d.ts +1 -1
- package/lib/components/content/dialog/index.vue.d.ts +1 -1
- package/lib/components/content/drawer/index.vue.d.ts +1 -1
- package/lib/components/content/form/index.vue.d.ts +1 -1
- package/lib/components/content/search/index.vue.d.ts +1 -1
- package/lib/components/content/table/index.vue.d.ts +1 -1
- package/lib/components/content/table/tableOperate.vue.d.ts +1 -1
- package/lib/components/content/toolbar/icontool.vue.d.ts +1 -1
- package/lib/components/content/toolbar/index.vue.d.ts +1 -1
- package/lib/components/content/tree/index.vue.d.ts +1 -1
- package/lib/components/form/transfer/transferTable.vue.d.ts +1 -1
- package/lib/components/form/treeSelect/index.vue.d.ts +1 -1
- package/lib/components/form/upload/uploadList.vue.d.ts +1 -1
- package/lib/const/options.d.ts +32 -0
- package/lib/directives/enter-submit.d.ts +4 -0
- package/lib/directives/index.d.ts +2 -0
- package/lib/directives/permission.d.ts +5 -0
- package/lib/es/AceEditor/index.js +9 -8
- package/lib/es/BasicLayout/index.js +28 -24
- package/lib/es/Error403/index.js +15 -10
- package/lib/es/Error404/index.js +15 -10
- package/lib/es/ExcelForm/index.js +380 -175
- package/lib/es/UploadForm/index.js +23 -20
- package/lib/index.d.ts +43 -2
- package/lib/router/index.d.ts +16 -0
- package/lib/stores/appInfo.d.ts +34 -0
- package/lib/stores/hostInfo.d.ts +9 -0
- package/lib/stores/pageInfo.d.ts +18 -0
- package/lib/stores/pinia.d.ts +3 -0
- package/lib/stores/settingInfo.d.ts +8 -0
- package/lib/stores/userInfo.d.ts +21 -0
- package/lib/typings/data.d.ts +80 -0
- package/lib/typings/form.d.ts +171 -0
- package/lib/typings/menu.d.ts +7 -0
- package/lib/typings/option.d.ts +175 -0
- package/lib/typings/page.d.ts +69 -0
- package/lib/typings/table.d.ts +181 -0
- package/lib/typings/tools.d.ts +130 -0
- package/lib/typings/tree.d.ts +72 -0
- package/lib/typings/upload.d.ts +161 -0
- package/lib/typings/urls.d.ts +69 -0
- package/lib/utils/cache.d.ts +23 -0
- package/lib/utils/data.d.ts +6 -0
- package/lib/utils/download.d.ts +4 -0
- package/lib/utils/eventbus.d.ts +16 -0
- package/lib/utils/export-table.d.ts +12 -0
- package/lib/utils/file-upload.d.ts +15 -0
- package/lib/utils/form-excel.d.ts +30 -0
- package/lib/utils/form-validate.d.ts +29 -0
- package/lib/utils/form.d.ts +9 -0
- package/lib/utils/icon-loader.d.ts +125 -0
- package/lib/utils/isEmpty.d.ts +1 -0
- package/lib/utils/main-openapis.d.ts +9 -0
- package/lib/utils/menu.d.ts +6 -0
- package/lib/utils/options.d.ts +10 -0
- package/lib/utils/page.d.ts +25 -0
- package/lib/utils/table.d.ts +21 -0
- package/lib/utils/tools.d.ts +18 -0
- package/lib/utils/tree.d.ts +3 -0
- package/lib/vite-env.d.ts +8 -0
- package/lib/webui.css +1 -1
- package/lib/webui.es.js +890 -724
- package/package.json +7 -6
- package/src/components/common/icon/appicon.vue +1 -1
- package/src/components/common/icon/fullscreen.vue +2 -1
- package/src/components/common/icon/index.vue +1 -1
- package/src/components/common/icon/layoutIcon.vue +1 -1
- package/src/components/common/icon/projectIcon.vue +1 -1
- package/src/components/common/icon/toolIcon.vue +1 -1
- package/src/components/content/dialog/excelForm.vue +2 -2
- package/src/components/content/dialog/index.vue +1 -1
- package/src/components/content/dialog/uploadForm.vue +7 -6
- package/src/components/content/drawer/index.vue +43 -18
- package/src/components/content/form/formItem.vue +1 -1
- package/src/components/content/form/index.vue +1 -1
- package/src/components/content/search/index.vue +1 -1
- package/src/components/content/search/searchItem.vue +1 -1
- package/src/components/content/table/index.vue +3 -3
- package/src/components/content/table/tableOperate.vue +2 -2
- package/src/components/content/toolbar/icontool.vue +2 -2
- package/src/components/content/toolbar/index.vue +3 -2
- package/src/components/content/tree/index.vue +1 -1
- package/src/components/error/error403.vue +2 -2
- package/src/components/error/error404.vue +2 -2
- package/src/components/form/autoComplete/index.vue +1 -1
- package/src/components/form/cascader/index.vue +1 -2
- package/src/components/form/checkbox/index.vue +11 -5
- package/src/components/form/datePicker/index.vue +1 -1
- package/src/components/form/input/index.vue +1 -1
- package/src/components/form/input/inputNumber.vue +1 -1
- package/src/components/form/input/inputPassword.vue +1 -1
- package/src/components/form/radio/index.vue +1 -1
- package/src/components/form/radio/radioStatus.vue +1 -1
- package/src/components/form/rangePicker/index.vue +1 -1
- package/src/components/form/select/index.vue +1 -1
- package/src/components/form/switch/index.vue +7 -3
- package/src/components/form/textarea/index.vue +1 -1
- package/src/components/form/transfer/index.vue +1 -1
- package/src/components/form/transfer/transferTable.vue +42 -22
- package/src/components/form/treeSelect/index.vue +2 -3
- package/src/components/form/upload/uploadList.vue +1 -1
- package/src/components/layout/breadcrumb/index.vue +1 -1
- package/src/components/layout/header/headerExits.vue +1 -1
- package/src/components/layout/header/index.vue +1 -1
- package/src/components/layout/header/user.vue +2 -1
- package/src/components/layout/menu/index.vue +9 -3
- package/src/components/layout/menu/menuTabs.vue +10 -12
- package/src/components/layout/page/basicLayout.vue +1 -1
- package/src/const/options.ts +114 -0
- package/src/directives/enter-submit.ts +13 -0
- package/src/directives/index.ts +26 -0
- package/src/directives/permission.ts +144 -0
- package/src/index.ts +202 -0
- package/src/router/index.ts +196 -0
- package/src/stores/appInfo.ts +471 -0
- package/src/stores/hostInfo.ts +117 -0
- package/src/stores/pageInfo.ts +131 -0
- package/src/stores/pinia.ts +10 -0
- package/src/stores/settingInfo.ts +53 -0
- package/src/stores/userInfo.ts +392 -0
- package/src/typings/data.d.ts +81 -0
- package/src/typings/form.d.ts +172 -0
- package/src/typings/menu.d.ts +7 -0
- package/src/typings/option.d.ts +177 -0
- package/src/typings/page.d.ts +70 -0
- package/src/typings/table.d.ts +182 -0
- package/src/typings/tools.d.ts +131 -0
- package/src/typings/tree.d.ts +73 -0
- package/src/typings/upload.d.ts +162 -0
- package/src/typings/urls.d.ts +70 -0
- package/src/utils/cache.ts +175 -0
- package/src/utils/data.ts +189 -0
- package/src/utils/download.ts +80 -0
- package/src/utils/eventbus.ts +78 -0
- package/src/utils/export-table.ts +155 -0
- package/src/utils/file-upload.ts +304 -0
- package/src/utils/form-excel.ts +523 -0
- package/src/utils/form-validate.ts +368 -0
- package/src/utils/form.ts +188 -0
- package/src/utils/icon-loader.ts +412 -0
- package/src/utils/isEmpty.ts +18 -0
- package/src/utils/main-openapis.ts +72 -0
- package/src/utils/menu.ts +89 -0
- package/src/utils/options.ts +324 -0
- package/src/utils/page.ts +262 -0
- package/src/utils/table.ts +274 -0
- package/src/utils/tools.ts +362 -0
- package/src/utils/tree.ts +28 -0
- package/tsconfig.json +1 -8
- package/vite.config.ts +7 -4
- package/lib/assets/modules/index-BahGnrAq.js +0 -415
- package/lib/assets/modules/index-BoKIa2sr.js +0 -109
- package/lib/assets/modules/index-D47Ci-T3.js +0 -107
- package/lib/assets/modules/uploadList-Dzlg47V0.js +0 -182
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { inject, toRaw, ref, provide, watch, Ref } from 'vue';
|
|
2
|
+
import Validator from 'async-validator';
|
|
3
|
+
import { EditorControl, InputFactoryItems, ValidateError, ValidateRule } from '@/typings/form.d';
|
|
4
|
+
import { ProviderKeys } from '@/typings/page.d';
|
|
5
|
+
import { AnyData} from '@skyfox2000/fapi';
|
|
6
|
+
import { isEmpty } from './isEmpty';
|
|
7
|
+
|
|
8
|
+
export let validMessages: Validator;
|
|
9
|
+
/**
|
|
10
|
+
* 全局文字提示参数配置
|
|
11
|
+
*/
|
|
12
|
+
export const initValidate = (messages?: Record<string, any>) => {
|
|
13
|
+
validMessages = new Validator({});
|
|
14
|
+
validMessages.messages({
|
|
15
|
+
required: '${label}不能为空',
|
|
16
|
+
types: {
|
|
17
|
+
number: '${label}必须是数字',
|
|
18
|
+
},
|
|
19
|
+
enum: '${label}必须是${enum}中的一个',
|
|
20
|
+
string: {
|
|
21
|
+
len: '${label}长度必须为${len}',
|
|
22
|
+
min: '${label}长度不能小于${min}',
|
|
23
|
+
max: '${label}长度不能大于${max}',
|
|
24
|
+
range: '${label}长度必须在${min}和${max}之间',
|
|
25
|
+
},
|
|
26
|
+
number: {
|
|
27
|
+
len: '${label}必须为${len}',
|
|
28
|
+
min: '${label}不能小于${min}',
|
|
29
|
+
max: '${label}不能大于${max}',
|
|
30
|
+
range: '${label}必须在${min}和${max}之间',
|
|
31
|
+
},
|
|
32
|
+
array: {
|
|
33
|
+
len: '${label}长度必须为${len}',
|
|
34
|
+
min: '${label}长度不能小于${min}',
|
|
35
|
+
max: '${label}长度不能大于${max}',
|
|
36
|
+
range: '${label}长度必须在${min}和${max}之间',
|
|
37
|
+
},
|
|
38
|
+
...messages,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 获取验证规则的文字描述
|
|
44
|
+
* @param rules 规则对象
|
|
45
|
+
* @returns 规则描述数组,格式为{field: string; rules: string[]}[]
|
|
46
|
+
*/
|
|
47
|
+
export const getRuleTexts = (rules?: Record<string, ValidateRule>) => {
|
|
48
|
+
if (!rules || isEmpty(rules)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result: { field: string; rules: string[] }[] = [];
|
|
53
|
+
|
|
54
|
+
// 获取预设消息模板
|
|
55
|
+
const messages = validMessages.messages();
|
|
56
|
+
|
|
57
|
+
// 处理单个规则函数,根据规则类型和属性获取相应的消息
|
|
58
|
+
const handleRule = (field: string, item: any, ruleTexts: string[]) => {
|
|
59
|
+
// 检查类型规则
|
|
60
|
+
const type = item.type;
|
|
61
|
+
if (type && messages.types && type in messages.types) {
|
|
62
|
+
// 类型要求
|
|
63
|
+
const typeMsg = messages.types[type as keyof typeof messages.types];
|
|
64
|
+
addMessage(typeMsg, field, { type }, ruleTexts);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 根据类型处理特定规则
|
|
68
|
+
const ruleTypes = ['string', 'number', 'array'] as const;
|
|
69
|
+
for (const ruleType of ruleTypes) {
|
|
70
|
+
if (type === ruleType && messages[ruleType]) {
|
|
71
|
+
const typeMessages = messages[ruleType] as Record<string, any>;
|
|
72
|
+
const props = ['len', 'min', 'max', 'range'] as const;
|
|
73
|
+
|
|
74
|
+
for (const prop of props) {
|
|
75
|
+
if (prop === 'range' && item.min != null && item.max != null && typeMessages.range) {
|
|
76
|
+
// 范围规则
|
|
77
|
+
addMessage(typeMessages.range, field, { min: item.min, max: item.max }, ruleTexts);
|
|
78
|
+
} else if (prop !== 'range' && item[prop] != null && typeMessages[prop]) {
|
|
79
|
+
// 单一属性规则
|
|
80
|
+
addMessage(typeMessages[prop], field, { [prop]: item[prop] }, ruleTexts);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 处理必填项
|
|
87
|
+
if (item.required && messages.required) {
|
|
88
|
+
addMessage(messages.required, field, {}, ruleTexts);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 处理正则表达式规则
|
|
92
|
+
if (item.pattern && messages.pattern) {
|
|
93
|
+
addMessage(item.message, field, {}, ruleTexts);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 处理枚举规则
|
|
97
|
+
if (item.enum && Array.isArray(item.enum) && messages.enum) {
|
|
98
|
+
const enumStr = item.enum.join(', ');
|
|
99
|
+
addMessage(messages.enum, field, { enum: enumStr }, ruleTexts);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// 将消息添加到结果数组中
|
|
104
|
+
const addMessage = (msgTemplate: any, field: string, params: Record<string, any>, ruleTexts: string[]) => {
|
|
105
|
+
if (!msgTemplate) return; // 如果没有消息模板,直接返回
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
let msg: string;
|
|
109
|
+
if (typeof msgTemplate === 'function') {
|
|
110
|
+
// 尝试调用函数
|
|
111
|
+
const args = [field];
|
|
112
|
+
for (const key in params) {
|
|
113
|
+
args.push(params[key]);
|
|
114
|
+
}
|
|
115
|
+
msg = msgTemplate.apply(null, args);
|
|
116
|
+
if (msg) ruleTexts.push(msg);
|
|
117
|
+
} else {
|
|
118
|
+
// 字符串替换
|
|
119
|
+
msg = String(msgTemplate);
|
|
120
|
+
for (const key in params) {
|
|
121
|
+
msg = msg.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), params[key]);
|
|
122
|
+
}
|
|
123
|
+
msg = msg.replace(/\$\{label\}/g, '');
|
|
124
|
+
if (msg) ruleTexts.push(msg);
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
// 出错时忽略该消息
|
|
128
|
+
console.error('格式化验证消息出错:', error);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// 处理每个字段的规则
|
|
133
|
+
Object.keys(rules).forEach((field) => {
|
|
134
|
+
const ruleItem = rules[field];
|
|
135
|
+
const ruleTexts: string[] = [];
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
handleRule(field, ruleItem, ruleTexts);
|
|
139
|
+
|
|
140
|
+
// 处理数组形式的规则
|
|
141
|
+
if (Array.isArray(ruleItem)) {
|
|
142
|
+
ruleItem.forEach((item) => {
|
|
143
|
+
if (item && typeof item === 'object') {
|
|
144
|
+
handleRule(field, item, ruleTexts);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 处理嵌套对象规则 - 如果有嵌套验证规则但没有消息配置,则不显示
|
|
150
|
+
if (ruleItem && typeof ruleItem === 'object' && 'fields' in ruleItem) {
|
|
151
|
+
// 嵌套对象规则不显示任何消息
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('处理验证规则出错:', error);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 确保不重复添加相同的规则文字
|
|
158
|
+
const uniqueTexts = Array.from(new Set(ruleTexts));
|
|
159
|
+
|
|
160
|
+
if (field && uniqueTexts.length > 0) {
|
|
161
|
+
result.push({
|
|
162
|
+
field,
|
|
163
|
+
rules: uniqueTexts,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 表单数据验证
|
|
173
|
+
* @param editorCtrl 表单控制对象
|
|
174
|
+
*/
|
|
175
|
+
export const formValidate = async <T>(editorCtrl: EditorControl<T>) => {
|
|
176
|
+
const formData: Record<string, AnyData> = editorCtrl.formData.value as Record<string, AnyData>;
|
|
177
|
+
const rules = editorCtrl.formRules.value;
|
|
178
|
+
if (!isEmpty(rules)) {
|
|
179
|
+
resetRules(editorCtrl);
|
|
180
|
+
if (!editorCtrl.ruleValidator) {
|
|
181
|
+
/**
|
|
182
|
+
* 设置自定义文字显示与验证条件
|
|
183
|
+
*/
|
|
184
|
+
editorCtrl.ruleValidator = new Validator({});
|
|
185
|
+
editorCtrl.ruleValidator.messages(validMessages.messages());
|
|
186
|
+
editorCtrl.ruleValidator.define(rules!);
|
|
187
|
+
}
|
|
188
|
+
editorCtrl.ruleResults.value = await editorCtrl.ruleValidator
|
|
189
|
+
.validate(formData)
|
|
190
|
+
.then(() => {
|
|
191
|
+
return;
|
|
192
|
+
})
|
|
193
|
+
.catch(({ fields }) => {
|
|
194
|
+
return fields;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 验证数据
|
|
201
|
+
* @param data 数据
|
|
202
|
+
* @param rules 规则
|
|
203
|
+
* @param validator? 验证器
|
|
204
|
+
* @returns 验证结果
|
|
205
|
+
*/
|
|
206
|
+
export const validateData = async (
|
|
207
|
+
data: Record<string, AnyData>,
|
|
208
|
+
rules: Record<string, ValidateRule>,
|
|
209
|
+
validator?: Validator,
|
|
210
|
+
) => {
|
|
211
|
+
if (!validator) {
|
|
212
|
+
validator = new Validator({});
|
|
213
|
+
validator.messages(validMessages.messages());
|
|
214
|
+
validator.define(rules);
|
|
215
|
+
}
|
|
216
|
+
return validator
|
|
217
|
+
.validate(data)
|
|
218
|
+
.then(() => {
|
|
219
|
+
/// 验证通过
|
|
220
|
+
return;
|
|
221
|
+
})
|
|
222
|
+
.catch(({ fields }) => {
|
|
223
|
+
/// 验证失败
|
|
224
|
+
return fields;
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 重置表单验证结果
|
|
230
|
+
* @param editorCtrl 表单控制对象
|
|
231
|
+
*/
|
|
232
|
+
export const resetRules = <T>(editorCtrl: EditorControl<T>) => {
|
|
233
|
+
editorCtrl.ruleResults.value = undefined;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 工厂参数
|
|
238
|
+
*/
|
|
239
|
+
interface RuleFactoryOptions {
|
|
240
|
+
/**
|
|
241
|
+
* 标签文字
|
|
242
|
+
*/
|
|
243
|
+
label?: string;
|
|
244
|
+
/**
|
|
245
|
+
* 表单项验证规则名
|
|
246
|
+
*/
|
|
247
|
+
rule?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 输入组件工厂处理
|
|
252
|
+
*/
|
|
253
|
+
export const useInputFactory = (): InputFactoryItems<AnyData> => {
|
|
254
|
+
const editorCtrl = inject(ProviderKeys.EditorControl, undefined) as EditorControl<AnyData> | undefined;
|
|
255
|
+
const labelText = ref<string>(inject(ProviderKeys.LabelText, ''));
|
|
256
|
+
const ruleKey = ref<string>(inject(ProviderKeys.RuleKey, ''));
|
|
257
|
+
const errInfo = inject(ProviderKeys.ErrInfo, undefined) as Ref<{ msg: string; errClass: string }> | undefined;
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
editorCtrl,
|
|
261
|
+
labelText,
|
|
262
|
+
ruleKey,
|
|
263
|
+
errInfo,
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const computedErrInfo = (options: RuleFactoryOptions, editorCtrl?: EditorControl<AnyData>) => {
|
|
268
|
+
let errInfo = ref({
|
|
269
|
+
msg: '',
|
|
270
|
+
errClass: '',
|
|
271
|
+
});
|
|
272
|
+
if (!options.label || !options.rule) {
|
|
273
|
+
return errInfo;
|
|
274
|
+
}
|
|
275
|
+
const ruleResults = editorCtrl?.ruleResults;
|
|
276
|
+
watch(
|
|
277
|
+
() => ruleResults?.value,
|
|
278
|
+
(newVal) => {
|
|
279
|
+
errInfo.value.errClass = '';
|
|
280
|
+
errInfo.value.msg = '';
|
|
281
|
+
if (!isEmpty(newVal)) {
|
|
282
|
+
// 处理错误逻辑
|
|
283
|
+
const errMsg = updateErrMsg(
|
|
284
|
+
{
|
|
285
|
+
label: options.label,
|
|
286
|
+
rule: options.rule,
|
|
287
|
+
},
|
|
288
|
+
editorCtrl?.formRules.value,
|
|
289
|
+
toRaw(newVal),
|
|
290
|
+
);
|
|
291
|
+
errInfo.value.errClass = errMsg.errClass.value;
|
|
292
|
+
errInfo.value.msg = errMsg.msg.value;
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{ deep: true },
|
|
296
|
+
);
|
|
297
|
+
return errInfo;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// 更新错误消息
|
|
301
|
+
const updateErrMsg = (
|
|
302
|
+
options: RuleFactoryOptions,
|
|
303
|
+
formRules?: Record<string, ValidateRule>,
|
|
304
|
+
ruleResults?: Record<string, ValidateError[]>,
|
|
305
|
+
) => {
|
|
306
|
+
const ruleErrors = ref<ValidateError[]>([]);
|
|
307
|
+
const msg = ref<string>('');
|
|
308
|
+
const errClass = ref('');
|
|
309
|
+
|
|
310
|
+
if (!options.rule || !formRules || !ruleResults) {
|
|
311
|
+
return { msg, ruleErrors, errClass };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const rules = options.rule.split('|');
|
|
315
|
+
rules.forEach((rule: string) => {
|
|
316
|
+
rule = rule.trim();
|
|
317
|
+
if (rule) {
|
|
318
|
+
if (ruleResults[rule]) {
|
|
319
|
+
ruleErrors.value.push(...ruleResults[rule]);
|
|
320
|
+
} else {
|
|
321
|
+
// 检查嵌套错误
|
|
322
|
+
const nestedErrors: ValidateError[] = [];
|
|
323
|
+
for (let key in ruleResults) {
|
|
324
|
+
if (key !== rule && key.startsWith(rule + '.')) {
|
|
325
|
+
nestedErrors.push(...ruleResults[key]);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (nestedErrors.length > 0) {
|
|
329
|
+
ruleErrors.value.push(...nestedErrors);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const reg = /\${\w+}/g;
|
|
336
|
+
// 构建错误消息
|
|
337
|
+
ruleErrors.value.forEach((err: ValidateError) => {
|
|
338
|
+
if (msg.value) msg.value += ' / ';
|
|
339
|
+
msg.value += err.message!.replace('${label}', options.label ?? '');
|
|
340
|
+
const rule = formRules[err.field!];
|
|
341
|
+
if (rule) {
|
|
342
|
+
const match = msg.value.match(reg);
|
|
343
|
+
if (match) {
|
|
344
|
+
match.forEach((item: string) => {
|
|
345
|
+
const key = item.replace('${', '').replace('}', '');
|
|
346
|
+
msg.value = msg.value.replace(item, (rule as any)[key] ?? '');
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
// 设置错误样式
|
|
352
|
+
if (ruleErrors.value.length > 0) {
|
|
353
|
+
errClass.value = 'error';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return { msg, ruleErrors, errClass };
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 表单项工厂处理
|
|
361
|
+
*/
|
|
362
|
+
export const useFormItemFactory = (options: RuleFactoryOptions, editorCtrl?: EditorControl<AnyData>) => {
|
|
363
|
+
provide(ProviderKeys.LabelText, options.label || '');
|
|
364
|
+
provide(ProviderKeys.RuleKey, options.rule || '');
|
|
365
|
+
const errInfo = computedErrInfo(options, editorCtrl);
|
|
366
|
+
provide(ProviderKeys.ErrInfo, errInfo);
|
|
367
|
+
return errInfo;
|
|
368
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { EditorControl, ValidateError } from '@/typings/form.d';
|
|
2
|
+
import message from 'vue-m-message';
|
|
3
|
+
import { AnyData, AnyJsonData, ResStatus } from '@skyfox2000/fapi';
|
|
4
|
+
import { doSave } from './data';
|
|
5
|
+
import { getRecordDetail } from './table';
|
|
6
|
+
import { formValidate, resetRules } from './form-validate';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 表单存储事件
|
|
10
|
+
* @param pageCtrl 页面控制对象
|
|
11
|
+
*/
|
|
12
|
+
export const onFormSave = <T>(editorCtrl: EditorControl<T>) => {
|
|
13
|
+
saveForm(editorCtrl);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 表单另存为事件
|
|
18
|
+
* @param pageCtrl 页面控制对象
|
|
19
|
+
*/
|
|
20
|
+
export const onFormSaveAs = <T>(editorCtrl: EditorControl<T>) => {
|
|
21
|
+
const pageCtrl = editorCtrl.page;
|
|
22
|
+
const formData = editorCtrl.formData.value as Record<string, AnyData>;
|
|
23
|
+
formData[editorCtrl.primaryKey ?? pageCtrl.primaryKey] = null;
|
|
24
|
+
saveForm(editorCtrl);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 关闭表单事件
|
|
29
|
+
* @param pageCtrl 页面控制对象
|
|
30
|
+
*/
|
|
31
|
+
export const onFormClose = <T>(editorCtrl: EditorControl<T>) => {
|
|
32
|
+
editorCtrl.visible.value = false;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 执行存储表单数据
|
|
37
|
+
* @param pageCtrl 页面控制对象
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
export const saveForm = async <T>(editorCtrl: EditorControl<T>): Promise<void> => {
|
|
41
|
+
const pageCtrl = editorCtrl.page;
|
|
42
|
+
editorCtrl.isFormSaving.value = true;
|
|
43
|
+
resetRules(editorCtrl);
|
|
44
|
+
|
|
45
|
+
if (editorCtrl.beforeSave) {
|
|
46
|
+
const result = editorCtrl.beforeSave();
|
|
47
|
+
if (result === false) {
|
|
48
|
+
editorCtrl.isFormSaving.value = false;
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (editorCtrl.formRules.value) {
|
|
54
|
+
/**
|
|
55
|
+
* 表单验证
|
|
56
|
+
*/
|
|
57
|
+
await formValidate(editorCtrl);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (editorCtrl.ruleResults.value) {
|
|
61
|
+
message.error('提交表单时,验证失败!');
|
|
62
|
+
console.error('表单验证失败!', editorCtrl.ruleResults.value);
|
|
63
|
+
/**
|
|
64
|
+
* 验证失败
|
|
65
|
+
*/
|
|
66
|
+
editorCtrl.isFormSaving.value = false;
|
|
67
|
+
|
|
68
|
+
return Promise.resolve();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const formData = editorCtrl.formData.value as Record<string, AnyData>;
|
|
72
|
+
|
|
73
|
+
// 使用通用的保存方法
|
|
74
|
+
return doSave<AnyJsonData>(
|
|
75
|
+
{
|
|
76
|
+
page: pageCtrl, // 转换为AnyControl接口
|
|
77
|
+
reload: editorCtrl.grid?.reload,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
url: editorCtrl.saveUrl,
|
|
81
|
+
primaryKey: editorCtrl.primaryKey ?? pageCtrl.primaryKey,
|
|
82
|
+
params: {
|
|
83
|
+
Data: formData,
|
|
84
|
+
Query: {},
|
|
85
|
+
},
|
|
86
|
+
hideErrorToast: true, // 隐藏自动错误提示,特殊判断控制
|
|
87
|
+
loadingState: editorCtrl.isFormSaving,
|
|
88
|
+
},
|
|
89
|
+
).then((result) => {
|
|
90
|
+
if (result?.status === ResStatus.SUCCESS) {
|
|
91
|
+
message.success('保存成功!');
|
|
92
|
+
if (editorCtrl.grid) {
|
|
93
|
+
editorCtrl.grid!.reload.value = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (editorCtrl.afterSave) {
|
|
97
|
+
editorCtrl.afterSave();
|
|
98
|
+
}
|
|
99
|
+
if (editorCtrl!.autoClose !== false) {
|
|
100
|
+
editorCtrl!.visible.value = false;
|
|
101
|
+
}
|
|
102
|
+
} else if (result?.msg && result?.msg?.indexOf('Duplicate entry') > -1) {
|
|
103
|
+
// 获取重复字段
|
|
104
|
+
const duplicateField = result?.msg?.match(/Duplicate entry '([^']+)' for key '([^']+)'/);
|
|
105
|
+
if (duplicateField && duplicateField.length > 2 && duplicateField[1] && duplicateField[2]) {
|
|
106
|
+
const fieldValue = duplicateField[1];
|
|
107
|
+
const fieldNames = duplicateField[2].replace(/Idx_/g, '').split('_');
|
|
108
|
+
const formRules = editorCtrl.formRules.value;
|
|
109
|
+
if (formRules && fieldNames.length > 0) {
|
|
110
|
+
const ruleResults: Record<string, ValidateError[]> = {};
|
|
111
|
+
fieldNames.forEach((fieldName) => {
|
|
112
|
+
const rule = formRules[fieldName];
|
|
113
|
+
if (rule) {
|
|
114
|
+
ruleResults[fieldName] = [
|
|
115
|
+
{
|
|
116
|
+
field: fieldName,
|
|
117
|
+
fieldValue: fieldValue,
|
|
118
|
+
message: `数据重复`,
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
if (Object.keys(ruleResults).length > 0) {
|
|
124
|
+
console.error('保存失败!', ruleResults);
|
|
125
|
+
editorCtrl.ruleResults.value = ruleResults;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
message.error(`保存失败!数据 \`${fieldValue}\` 已存在!`);
|
|
129
|
+
} else message.error(result?.msg ?? '保存失败!');
|
|
130
|
+
} else {
|
|
131
|
+
message.error(result?.msg ?? '保存失败!');
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 获取表单数据详情
|
|
138
|
+
* @param pageCtrl 页面控制对象
|
|
139
|
+
* @returns
|
|
140
|
+
*/
|
|
141
|
+
export const getFormDetail = async <T>(editorCtrl: EditorControl<T>): Promise<void> => {
|
|
142
|
+
const pageCtrl = editorCtrl.page;
|
|
143
|
+
const editorGrid = editorCtrl.grid!;
|
|
144
|
+
editorCtrl.isFormLoading.value = true;
|
|
145
|
+
|
|
146
|
+
const formData = editorCtrl.formData.value as Record<string, AnyData>;
|
|
147
|
+
const primaryKey = editorCtrl.primaryKey ?? pageCtrl.primaryKey;
|
|
148
|
+
|
|
149
|
+
return getRecordDetail(editorGrid, { [primaryKey]: formData[primaryKey] }, editorCtrl.detailUrl).then((result) => {
|
|
150
|
+
editorCtrl.isFormLoading.value = false;
|
|
151
|
+
if (result) setFormData(editorCtrl, result as T);
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 打开新输入表单
|
|
157
|
+
* @param pageCtrl 页面控制对象
|
|
158
|
+
*/
|
|
159
|
+
export const openNewForm = <T>(editorCtrl: EditorControl<T>) => {
|
|
160
|
+
const gridCtrl = editorCtrl.grid;
|
|
161
|
+
if (gridCtrl) gridCtrl.rowData.value = undefined;
|
|
162
|
+
if (editorCtrl) {
|
|
163
|
+
resetForm(editorCtrl);
|
|
164
|
+
if (editorCtrl.visible) editorCtrl.visible.value = false;
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
editorCtrl.visible.value = true;
|
|
167
|
+
}, 1);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 重置表单,并设置表单数据
|
|
173
|
+
* @param editorCtrl 表单控制对象
|
|
174
|
+
*/
|
|
175
|
+
export const setFormData = <T>(editorCtrl: EditorControl<T>, formData?: T) => {
|
|
176
|
+
resetForm(editorCtrl);
|
|
177
|
+
editorCtrl.formData.value = { ...editorCtrl.formData.value, ...formData } as T;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 重置表单数据,重置表单验证结果
|
|
182
|
+
* @param editorCtrl 表单控制对象
|
|
183
|
+
*/
|
|
184
|
+
export const resetForm = <T>(editorCtrl: EditorControl<T>) => {
|
|
185
|
+
editorCtrl.formData.value = editorCtrl.default ? JSON.parse(JSON.stringify(editorCtrl.default)) : {};
|
|
186
|
+
|
|
187
|
+
resetRules(editorCtrl);
|
|
188
|
+
};
|