@tmagic/form 1.8.0-beta.11 → 1.8.0-beta.12
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/dist/es/Form.vue_vue_type_script_setup_true_lang.js +50 -11
- package/dist/es/FormBox.vue_vue_type_script_setup_true_lang.js +6 -2
- package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +7 -3
- package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/fields/RadioGroup.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/index.js +5 -4
- package/dist/es/plugin.js +2 -0
- package/dist/es/schema.js +2 -1
- package/dist/es/style.css +6 -2
- package/dist/es/submitForm.js +374 -142
- package/dist/es/utils/form.js +45 -5
- package/dist/es/utils/typeMatch.js +403 -0
- package/dist/style.css +6 -2
- package/dist/tmagic-form.umd.cjs +2542 -1708
- package/package.json +5 -5
- package/src/Form.vue +63 -13
- package/src/FormBox.vue +3 -0
- package/src/FormDialog.vue +3 -0
- package/src/FormDrawer.vue +3 -0
- package/src/containers/Container.vue +4 -2
- package/src/fields/RadioGroup.vue +3 -0
- package/src/index.ts +11 -0
- package/src/plugin.ts +9 -0
- package/src/schema.ts +2 -1
- package/src/submitForm.ts +574 -197
- package/src/theme/form-dialog.scss +1 -1
- package/src/theme/table.scss +11 -1
- package/src/utils/form.ts +93 -22
- package/src/utils/typeMatch.ts +728 -0
- package/types/index.d.ts +172 -17
package/dist/es/submitForm.js
CHANGED
|
@@ -2,6 +2,178 @@ import Form_default from "./Form.js";
|
|
|
2
2
|
import { createApp, defineComponent, h, nextTick, ref, watch } from "vue";
|
|
3
3
|
//#region packages/form/src/submitForm.ts
|
|
4
4
|
/**
|
|
5
|
+
* submitForm / validateForm 的公共脚手架:
|
|
6
|
+
*
|
|
7
|
+
* 创建临时容器 → 挂载一个 wrapper 组件(内含 MForm)→ 提供统一的 cleanup / timeout / appContext 继承 →
|
|
8
|
+
* 由 `createWrapper` 决定「何时调用 MForm 的哪个方法、如何 resolve/reject」。
|
|
9
|
+
*
|
|
10
|
+
* 调用方只需关注差异逻辑(调用 `submitForm` 还是 `validate`、结果如何包装、是否需要调试弹层),
|
|
11
|
+
* 容器创建、卸载、超时、上下文注入等模板代码在此统一收口。
|
|
12
|
+
*/
|
|
13
|
+
var mountFormInstance = (options) => {
|
|
14
|
+
const { formProps, appContext, timeout, timeoutMessage, hidden = true, skipTimeout = false, createWrapper } = options;
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const container = document.createElement("div");
|
|
17
|
+
if (hidden) container.style.display = "none";
|
|
18
|
+
document.body.appendChild(container);
|
|
19
|
+
let cleaned = false;
|
|
20
|
+
let timer = null;
|
|
21
|
+
const instance = { app: null };
|
|
22
|
+
const cleanup = () => {
|
|
23
|
+
if (cleaned) return;
|
|
24
|
+
cleaned = true;
|
|
25
|
+
if (timer) {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
timer = null;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
instance.app?.unmount();
|
|
31
|
+
} catch {}
|
|
32
|
+
container.parentNode?.removeChild(container);
|
|
33
|
+
};
|
|
34
|
+
const formRef = ref(null);
|
|
35
|
+
const { extendState, ...restFormProps } = formProps;
|
|
36
|
+
const userWrapper = createWrapper({
|
|
37
|
+
formRef,
|
|
38
|
+
formProps: restFormProps,
|
|
39
|
+
cleanup,
|
|
40
|
+
resolve,
|
|
41
|
+
reject
|
|
42
|
+
});
|
|
43
|
+
const app = createApp(typeof extendState === "function" ? defineComponent({
|
|
44
|
+
name: "MFormExtendStateInjector",
|
|
45
|
+
setup() {
|
|
46
|
+
watch(() => formRef.value, (form) => {
|
|
47
|
+
if (!form) return;
|
|
48
|
+
let result;
|
|
49
|
+
try {
|
|
50
|
+
result = extendState(form.formState);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error("[MForm] extendState failed:", e);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const apply = (state) => {
|
|
56
|
+
if (!state) return;
|
|
57
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(state))) if ("value" in descriptor) form.formState[key] = state[key];
|
|
58
|
+
else {
|
|
59
|
+
descriptor.configurable = true;
|
|
60
|
+
Object.defineProperty(form.formState, key, descriptor);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
if (result && typeof result.then === "function") result.then(apply, (e) => console.error("[MForm] extendState failed:", e));
|
|
64
|
+
else apply(result);
|
|
65
|
+
}, {
|
|
66
|
+
flush: "sync",
|
|
67
|
+
immediate: true
|
|
68
|
+
});
|
|
69
|
+
return () => h(userWrapper);
|
|
70
|
+
}
|
|
71
|
+
}) : userWrapper);
|
|
72
|
+
instance.app = app;
|
|
73
|
+
if (appContext) Object.assign(app._context, appContext);
|
|
74
|
+
if (timeout > 0 && !skipTimeout) timer = setTimeout(() => {
|
|
75
|
+
if (!cleaned) {
|
|
76
|
+
reject(new Error(timeoutMessage));
|
|
77
|
+
cleanup();
|
|
78
|
+
}
|
|
79
|
+
}, timeout);
|
|
80
|
+
try {
|
|
81
|
+
app.mount(container);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
reject(err);
|
|
84
|
+
cleanup();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 构造调试模式下的可见弹层 wrapper:以 fixed 遮罩居中渲染 MForm,提供「确定 / 取消」按钮与错误展示区。
|
|
90
|
+
*
|
|
91
|
+
* submitForm 与 validateForm 的调试弹层 UI 完全一致,仅「确定」时调用的实例方法、错误处理与取消文案不同,
|
|
92
|
+
* 这些差异通过 `onConfirm` / `onCancel` 注入,弹层结构在此统一收口。
|
|
93
|
+
*/
|
|
94
|
+
var createDebugWrapper = (options) => {
|
|
95
|
+
const { formRef, formProps, title, name, onConfirm, onCancel } = options;
|
|
96
|
+
const btnBase = {
|
|
97
|
+
padding: "8px 20px",
|
|
98
|
+
fontSize: "14px",
|
|
99
|
+
lineHeight: "1",
|
|
100
|
+
border: "1px solid #dcdfe6",
|
|
101
|
+
borderRadius: "4px",
|
|
102
|
+
cursor: "pointer"
|
|
103
|
+
};
|
|
104
|
+
return defineComponent({
|
|
105
|
+
name,
|
|
106
|
+
setup() {
|
|
107
|
+
const errorMsg = ref("");
|
|
108
|
+
const setError = (msg) => {
|
|
109
|
+
errorMsg.value = msg;
|
|
110
|
+
};
|
|
111
|
+
return () => h("div", { style: {
|
|
112
|
+
position: "fixed",
|
|
113
|
+
inset: "0",
|
|
114
|
+
zIndex: "10000",
|
|
115
|
+
display: "flex",
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
justifyContent: "center",
|
|
118
|
+
background: "rgba(0, 0, 0, 0.5)"
|
|
119
|
+
} }, [h("div", { style: {
|
|
120
|
+
display: "flex",
|
|
121
|
+
flexDirection: "column",
|
|
122
|
+
width: "600px",
|
|
123
|
+
maxWidth: "90vw",
|
|
124
|
+
maxHeight: "85vh",
|
|
125
|
+
background: "#fff",
|
|
126
|
+
borderRadius: "8px",
|
|
127
|
+
boxShadow: "0 8px 24px rgba(0, 0, 0, 0.2)",
|
|
128
|
+
overflow: "hidden"
|
|
129
|
+
} }, [
|
|
130
|
+
h("div", { style: {
|
|
131
|
+
padding: "16px 20px",
|
|
132
|
+
fontSize: "16px",
|
|
133
|
+
fontWeight: "600",
|
|
134
|
+
borderBottom: "1px solid #ebeef5"
|
|
135
|
+
} }, title),
|
|
136
|
+
h("div", { style: {
|
|
137
|
+
flex: "1",
|
|
138
|
+
padding: "20px",
|
|
139
|
+
overflow: "auto"
|
|
140
|
+
} }, [h(Form_default, {
|
|
141
|
+
...formProps,
|
|
142
|
+
ref: formRef
|
|
143
|
+
}), errorMsg.value ? h("div", {
|
|
144
|
+
style: {
|
|
145
|
+
marginTop: "12px",
|
|
146
|
+
color: "#f56c6c",
|
|
147
|
+
fontSize: "13px",
|
|
148
|
+
lineHeight: "1.5"
|
|
149
|
+
},
|
|
150
|
+
innerHTML: errorMsg.value
|
|
151
|
+
}) : null]),
|
|
152
|
+
h("div", { style: {
|
|
153
|
+
display: "flex",
|
|
154
|
+
justifyContent: "flex-end",
|
|
155
|
+
gap: "12px",
|
|
156
|
+
padding: "12px 20px",
|
|
157
|
+
borderTop: "1px solid #ebeef5"
|
|
158
|
+
} }, [h("button", {
|
|
159
|
+
type: "button",
|
|
160
|
+
onClick: onCancel,
|
|
161
|
+
style: { ...btnBase }
|
|
162
|
+
}, "取消"), h("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
onClick: () => onConfirm(setError),
|
|
165
|
+
style: {
|
|
166
|
+
...btnBase,
|
|
167
|
+
color: "#fff",
|
|
168
|
+
background: "#409eff",
|
|
169
|
+
borderColor: "#409eff"
|
|
170
|
+
}
|
|
171
|
+
}, "确定")])
|
|
172
|
+
])]);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
5
177
|
* 以命令式方式调用 Form.vue 完成一次表单校验/提交。
|
|
6
178
|
*
|
|
7
179
|
* 类似 ElMessage 的用法:传入 props(包含 `config`/`initValues` 等),函数内部会临时挂载
|
|
@@ -39,152 +211,212 @@ import { createApp, defineComponent, h, nextTick, ref, watch } from "vue";
|
|
|
39
211
|
*/
|
|
40
212
|
var submitForm = (options) => {
|
|
41
213
|
const { native, appContext, timeout = 1e4, returnChangeRecords, debug = false, ...formProps } = options;
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
214
|
+
return mountFormInstance({
|
|
215
|
+
formProps,
|
|
216
|
+
appContext,
|
|
217
|
+
timeout,
|
|
218
|
+
hidden: !debug,
|
|
219
|
+
skipTimeout: debug,
|
|
220
|
+
timeoutMessage: `submitForm timeout after ${timeout}ms: form is not initialized.`,
|
|
221
|
+
createWrapper: ({ formRef, formProps, cleanup, resolve, reject }) => {
|
|
222
|
+
/**
|
|
223
|
+
* 执行一次提交:nextTick 等待子组件渲染 → 快照 changeRecords → 调用实例 submitForm → resolve。
|
|
224
|
+
* 校验失败时交给 `onValidateError` 决定「保留弹层展示错误(debug)」还是「reject 并清理(普通)」,
|
|
225
|
+
* 从而让 debug 与普通模式共享同一份提交逻辑。
|
|
226
|
+
*/
|
|
227
|
+
const doSubmit = async (onValidateError) => {
|
|
228
|
+
try {
|
|
229
|
+
await nextTick();
|
|
230
|
+
const changeRecords = [...formRef.value?.changeRecords ?? []];
|
|
231
|
+
const result = await formRef.value.submitForm(native);
|
|
232
|
+
resolve(returnChangeRecords ? {
|
|
233
|
+
values: result,
|
|
234
|
+
changeRecords
|
|
235
|
+
} : result);
|
|
236
|
+
cleanup();
|
|
237
|
+
} catch (err) {
|
|
238
|
+
onValidateError(err);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
if (debug) return createDebugWrapper({
|
|
242
|
+
formRef,
|
|
243
|
+
formProps,
|
|
244
|
+
name: "MFormSubmitWrapper",
|
|
245
|
+
title: "submitForm 调试",
|
|
246
|
+
onConfirm: (setError) => doSubmit((err) => {
|
|
247
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
248
|
+
}),
|
|
249
|
+
onCancel: () => {
|
|
250
|
+
reject(/* @__PURE__ */ new Error("submitForm canceled in debug mode."));
|
|
251
|
+
cleanup();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
return defineComponent({
|
|
255
|
+
name: "MFormSubmitWrapper",
|
|
256
|
+
setup() {
|
|
257
|
+
const stop = watch(() => formRef.value?.initialized, (initialized) => {
|
|
258
|
+
if (!initialized) return;
|
|
259
|
+
stop();
|
|
260
|
+
doSubmit((err) => {
|
|
261
|
+
reject(err);
|
|
262
|
+
cleanup();
|
|
263
|
+
});
|
|
264
|
+
}, {
|
|
265
|
+
flush: "post",
|
|
266
|
+
immediate: true
|
|
267
|
+
});
|
|
268
|
+
return () => h(Form_default, {
|
|
269
|
+
...formProps,
|
|
270
|
+
ref: formRef
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* 深拷贝配置值,保留函数(display / onTabClick 等回调)引用,避免破坏配置中的回调。
|
|
279
|
+
*/
|
|
280
|
+
var cloneConfigValue = (value) => {
|
|
281
|
+
if (Array.isArray(value)) return value.map(cloneConfigValue);
|
|
282
|
+
if (value && typeof value === "object") return Object.keys(value).reduce((acc, key) => {
|
|
283
|
+
acc[key] = cloneConfigValue(value[key]);
|
|
284
|
+
return acc;
|
|
285
|
+
}, {});
|
|
286
|
+
return value;
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* 深度遍历配置,去掉所有 type 为 'tab' 的容器中各标签页(items)的 lazy 配置。
|
|
290
|
+
*/
|
|
291
|
+
var removeTabItemsLazy = (node) => {
|
|
292
|
+
if (Array.isArray(node)) {
|
|
293
|
+
node.forEach(removeTabItemsLazy);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!node || typeof node !== "object") return;
|
|
297
|
+
if (node.type === "tab" && Array.isArray(node.items)) node.items.forEach((pane) => {
|
|
298
|
+
if (pane && typeof pane === "object") delete pane.lazy;
|
|
299
|
+
});
|
|
300
|
+
Object.keys(node).forEach((key) => {
|
|
301
|
+
removeTabItemsLazy(node[key]);
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* 返回一份去除了 tab 标签页 lazy 的配置副本。
|
|
306
|
+
*
|
|
307
|
+
* tab 容器开启 lazy 时,非激活标签页的内容不会渲染,导致 validateForm 静默挂载后
|
|
308
|
+
* 无法校验到这些标签页内的字段。校验场景需要一次性渲染全部字段,故在此统一去除 lazy。
|
|
309
|
+
* 处理基于深拷贝,不会污染调用方传入的原始 config。
|
|
310
|
+
*/
|
|
311
|
+
var stripTabItemsLazy = (config) => {
|
|
312
|
+
const cloned = cloneConfigValue(config);
|
|
313
|
+
removeTabItemsLazy(cloned);
|
|
314
|
+
return cloned;
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* 以命令式方式对一份「表单配置 + 值」做一次静默校验,**不依赖也不影响任何已渲染的表单**。
|
|
318
|
+
*
|
|
319
|
+
* 与 `submitForm` 类似,内部会临时挂载一个不可见的 MForm 实例,等待其初始化完成后调用
|
|
320
|
+
* 实例的 `validate` 方法,返回汇总后的错误文案,随后自动卸载实例。
|
|
321
|
+
*
|
|
322
|
+
* 与 `submitForm` 的区别:
|
|
323
|
+
* - 「静默」:校验失败不抛异常、不触发 `error` 事件、不返回表单值;
|
|
324
|
+
* - 仅用于「探测」配置是否合法,适合源码保存后校验、批量校验组件配置等场景。
|
|
325
|
+
*
|
|
326
|
+
* 由于每次都新建一个独立的 MForm 实例,调用方无需持有任何表单 ref,也不会污染
|
|
327
|
+
* 页面上正在展示的表单状态。
|
|
328
|
+
*
|
|
329
|
+
* @returns 校验通过返回空字符串 `''`,否则返回以 `<br>` 拼接的错误文案。
|
|
330
|
+
* 仅在初始化超时或挂载失败等异常情况下才会 reject。
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```ts
|
|
334
|
+
* import { validateForm } from '@tmagic/form';
|
|
335
|
+
*
|
|
336
|
+
* const error = await validateForm({
|
|
337
|
+
* config: [...],
|
|
338
|
+
* initValues: { name: 'foo' },
|
|
339
|
+
* appContext: getCurrentInstance()?.appContext,
|
|
340
|
+
* });
|
|
341
|
+
* if (error) {
|
|
342
|
+
* // 配置不合法,error 为错误文案
|
|
343
|
+
* }
|
|
344
|
+
*
|
|
345
|
+
* // 调试模式:可见地渲染表单,点击「确定」才校验,校验失败保留弹层可修正重试:
|
|
346
|
+
* const error = await validateForm({
|
|
347
|
+
* config: [...],
|
|
348
|
+
* initValues: { name: 'foo' },
|
|
349
|
+
* debug: true,
|
|
350
|
+
* });
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
var validateForm = (options) => {
|
|
354
|
+
const { appContext, timeout = 1e4, debug = false, config, ...rest } = options;
|
|
355
|
+
return mountFormInstance({
|
|
356
|
+
formProps: {
|
|
357
|
+
...rest,
|
|
358
|
+
config: stripTabItemsLazy(config)
|
|
359
|
+
},
|
|
360
|
+
appContext,
|
|
361
|
+
timeout,
|
|
362
|
+
hidden: !debug,
|
|
363
|
+
skipTimeout: debug,
|
|
364
|
+
timeoutMessage: `validateForm timeout after ${timeout}ms: form is not initialized.`,
|
|
365
|
+
createWrapper: ({ formRef, formProps, cleanup, resolve, reject }) => {
|
|
366
|
+
/**
|
|
367
|
+
* 执行一次校验:nextTick 等待子组件渲染 → 调用实例 validate → 通过则 resolve ''。
|
|
368
|
+
* 校验失败(返回非空错误文案)时交给 `onInvalid` 决定「静默 resolve 错误文案(普通)」
|
|
369
|
+
* 还是「在弹层展示错误并保留供重试(debug)」,从而让两种模式共享同一份校验逻辑。
|
|
370
|
+
*/
|
|
371
|
+
const doValidate = async (onInvalid) => {
|
|
372
|
+
try {
|
|
373
|
+
await nextTick();
|
|
374
|
+
const error = await formRef.value.validate();
|
|
375
|
+
if (error) onInvalid(error);
|
|
376
|
+
else {
|
|
377
|
+
resolve("");
|
|
69
378
|
cleanup();
|
|
70
379
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
reject(/* @__PURE__ */ new Error("submitForm canceled in debug mode."));
|
|
75
|
-
cleanup();
|
|
76
|
-
};
|
|
77
|
-
const btnBase = {
|
|
78
|
-
padding: "8px 20px",
|
|
79
|
-
fontSize: "14px",
|
|
80
|
-
lineHeight: "1",
|
|
81
|
-
border: "1px solid #dcdfe6",
|
|
82
|
-
borderRadius: "4px",
|
|
83
|
-
cursor: "pointer"
|
|
84
|
-
};
|
|
85
|
-
return () => h("div", { style: {
|
|
86
|
-
position: "fixed",
|
|
87
|
-
inset: "0",
|
|
88
|
-
zIndex: "10000",
|
|
89
|
-
display: "flex",
|
|
90
|
-
alignItems: "center",
|
|
91
|
-
justifyContent: "center",
|
|
92
|
-
background: "rgba(0, 0, 0, 0.5)"
|
|
93
|
-
} }, [h("div", { style: {
|
|
94
|
-
display: "flex",
|
|
95
|
-
flexDirection: "column",
|
|
96
|
-
width: "600px",
|
|
97
|
-
maxWidth: "90vw",
|
|
98
|
-
maxHeight: "85vh",
|
|
99
|
-
background: "#fff",
|
|
100
|
-
borderRadius: "8px",
|
|
101
|
-
boxShadow: "0 8px 24px rgba(0, 0, 0, 0.2)",
|
|
102
|
-
overflow: "hidden"
|
|
103
|
-
} }, [
|
|
104
|
-
h("div", { style: {
|
|
105
|
-
padding: "16px 20px",
|
|
106
|
-
fontSize: "16px",
|
|
107
|
-
fontWeight: "600",
|
|
108
|
-
borderBottom: "1px solid #ebeef5"
|
|
109
|
-
} }, "submitForm 调试"),
|
|
110
|
-
h("div", { style: {
|
|
111
|
-
flex: "1",
|
|
112
|
-
padding: "20px",
|
|
113
|
-
overflow: "auto"
|
|
114
|
-
} }, [h(Form_default, {
|
|
115
|
-
...formProps,
|
|
116
|
-
ref: formRef
|
|
117
|
-
}), errorMsg.value ? h("div", {
|
|
118
|
-
style: {
|
|
119
|
-
marginTop: "12px",
|
|
120
|
-
color: "#f56c6c",
|
|
121
|
-
fontSize: "13px",
|
|
122
|
-
lineHeight: "1.5"
|
|
123
|
-
},
|
|
124
|
-
innerHTML: errorMsg.value
|
|
125
|
-
}) : null]),
|
|
126
|
-
h("div", { style: {
|
|
127
|
-
display: "flex",
|
|
128
|
-
justifyContent: "flex-end",
|
|
129
|
-
gap: "12px",
|
|
130
|
-
padding: "12px 20px",
|
|
131
|
-
borderTop: "1px solid #ebeef5"
|
|
132
|
-
} }, [h("button", {
|
|
133
|
-
type: "button",
|
|
134
|
-
onClick: handleCancel,
|
|
135
|
-
style: { ...btnBase }
|
|
136
|
-
}, "取消"), h("button", {
|
|
137
|
-
type: "button",
|
|
138
|
-
onClick: doSubmit,
|
|
139
|
-
style: {
|
|
140
|
-
...btnBase,
|
|
141
|
-
color: "#fff",
|
|
142
|
-
background: "#409eff",
|
|
143
|
-
borderColor: "#409eff"
|
|
144
|
-
}
|
|
145
|
-
}, "确定")])
|
|
146
|
-
])]);
|
|
380
|
+
} catch (err) {
|
|
381
|
+
reject(err);
|
|
382
|
+
cleanup();
|
|
147
383
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
app.mount(container);
|
|
183
|
-
} catch (err) {
|
|
184
|
-
reject(err);
|
|
185
|
-
cleanup();
|
|
384
|
+
};
|
|
385
|
+
if (debug) return createDebugWrapper({
|
|
386
|
+
formRef,
|
|
387
|
+
formProps,
|
|
388
|
+
name: "MFormValidateWrapper",
|
|
389
|
+
title: "validateForm 调试",
|
|
390
|
+
onConfirm: (setError) => doValidate((error) => {
|
|
391
|
+
setError(error);
|
|
392
|
+
}),
|
|
393
|
+
onCancel: () => {
|
|
394
|
+
reject(/* @__PURE__ */ new Error("validateForm canceled in debug mode."));
|
|
395
|
+
cleanup();
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
return defineComponent({
|
|
399
|
+
name: "MFormValidateWrapper",
|
|
400
|
+
setup() {
|
|
401
|
+
const stop = watch(() => formRef.value?.initialized, (initialized) => {
|
|
402
|
+
if (!initialized) return;
|
|
403
|
+
stop();
|
|
404
|
+
doValidate((error) => {
|
|
405
|
+
resolve(error);
|
|
406
|
+
cleanup();
|
|
407
|
+
});
|
|
408
|
+
}, {
|
|
409
|
+
flush: "post",
|
|
410
|
+
immediate: true
|
|
411
|
+
});
|
|
412
|
+
return () => h(Form_default, {
|
|
413
|
+
...formProps,
|
|
414
|
+
ref: formRef
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
});
|
|
186
418
|
}
|
|
187
419
|
});
|
|
188
420
|
};
|
|
189
421
|
//#endregion
|
|
190
|
-
export { submitForm };
|
|
422
|
+
export { submitForm, validateForm };
|
package/dist/es/utils/form.js
CHANGED
|
@@ -1,9 +1,44 @@
|
|
|
1
|
+
import { createTypeMatchValidator } from "./typeMatch.js";
|
|
1
2
|
import { readonly } from "vue";
|
|
2
3
|
import { cloneDeep } from "lodash-es";
|
|
4
|
+
import { getDesignConfig } from "@tmagic/design";
|
|
3
5
|
import { getValueByKeyPath } from "@tmagic/utils";
|
|
4
6
|
import dayjs from "dayjs";
|
|
5
7
|
import utc from "dayjs/plugin/utc";
|
|
6
8
|
//#region packages/form/src/utils/form.ts
|
|
9
|
+
var isTDesignAdapter = () => getDesignConfig("adapterType") === "tdesign-vue-next";
|
|
10
|
+
/**
|
|
11
|
+
* 将 async-validator(Element Plus)风格的 validator 适配到当前 UI 库。
|
|
12
|
+
* TDesign 调用签名为 `(val) => boolean | CustomValidateObj | Promise`,无 callback。
|
|
13
|
+
*/
|
|
14
|
+
var adaptFormValidator = (validator) => {
|
|
15
|
+
return (arg1, arg2, arg3, arg4, arg5) => {
|
|
16
|
+
if (!isTDesignAdapter()) return validator(arg1, arg2, arg3, arg4, arg5);
|
|
17
|
+
const value = arg1;
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let settled = false;
|
|
20
|
+
const callback = (error) => {
|
|
21
|
+
if (settled) return;
|
|
22
|
+
settled = true;
|
|
23
|
+
if (error) resolve({
|
|
24
|
+
result: false,
|
|
25
|
+
message: typeof error === "string" ? error : error.message
|
|
26
|
+
});
|
|
27
|
+
else resolve(true);
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
const result = validator(void 0, value, callback);
|
|
31
|
+
if (result !== null && typeof result.then === "function") Promise.resolve(result).then(() => {
|
|
32
|
+
if (!settled) callback();
|
|
33
|
+
}, (err) => {
|
|
34
|
+
callback(err instanceof Error ? err : new Error(String(err)));
|
|
35
|
+
});
|
|
36
|
+
} catch (e) {
|
|
37
|
+
callback(e instanceof Error ? e : new Error(String(e)));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
};
|
|
7
42
|
var TABLE_SELECT_TYPES = /* @__PURE__ */ new Set(["table-select", "tableSelect"]);
|
|
8
43
|
var isTableSelect = (type) => typeof type === "string" && TABLE_SELECT_TYPES.has(type);
|
|
9
44
|
var asyncLoadConfig = (value, initValue, { asyncLoad, name, type }) => {
|
|
@@ -99,13 +134,18 @@ var display = function(mForm, config, props) {
|
|
|
99
134
|
if (config === false) return false;
|
|
100
135
|
return true;
|
|
101
136
|
};
|
|
102
|
-
var getRules = function(mForm,
|
|
103
|
-
rules = cloneDeep(
|
|
137
|
+
var getRules = function(mForm, r = [], props, typeMatchValid) {
|
|
138
|
+
let rules = cloneDeep(r);
|
|
104
139
|
if (typeof rules === "object" && !Array.isArray(rules)) rules = [rules];
|
|
140
|
+
if (typeMatchValid?.value && !rules.some((r) => typeof r.typeMatch !== "undefined")) rules.push({ typeMatch: true });
|
|
105
141
|
return rules.map((item) => {
|
|
142
|
+
if (item.typeMatch) {
|
|
143
|
+
item.validator = adaptFormValidator(createTypeMatchValidator(mForm, props, item));
|
|
144
|
+
return item;
|
|
145
|
+
}
|
|
106
146
|
if (typeof item.validator === "function") {
|
|
107
147
|
const fnc = item.validator;
|
|
108
|
-
item.validator = (rule, value, callback, source, options) => fnc({
|
|
148
|
+
item.validator = adaptFormValidator((rule, value, callback, source, options) => fnc({
|
|
109
149
|
rule,
|
|
110
150
|
value: props.config.names ? props.model : value,
|
|
111
151
|
callback,
|
|
@@ -118,7 +158,7 @@ var getRules = function(mForm, rules = [], props) {
|
|
|
118
158
|
formValue: mForm?.values || props.model,
|
|
119
159
|
prop: props.prop,
|
|
120
160
|
config: props.config
|
|
121
|
-
}, mForm);
|
|
161
|
+
}, mForm));
|
|
122
162
|
}
|
|
123
163
|
return item;
|
|
124
164
|
});
|
|
@@ -172,4 +212,4 @@ var createObjectProp = (prop, key, name) => {
|
|
|
172
212
|
return `${[...itemPath, key].join(".")}`;
|
|
173
213
|
};
|
|
174
214
|
//#endregion
|
|
175
|
-
export { createObjectProp, createValues, datetimeFormatter, display, filterFunction, getDataByPage, getRules, initValue, sortArray, sortChange };
|
|
215
|
+
export { adaptFormValidator, createObjectProp, createValues, datetimeFormatter, display, filterFunction, getDataByPage, getRules, initValue, sortArray, sortChange };
|