@tmagic/form 1.8.0-manmanyu.11 → 1.8.0-manmanyu.13
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/package.json +4 -4
- package/src/Form.vue +9 -1
- package/src/FormBox.vue +4 -0
- package/src/FormDialog.vue +4 -0
- package/src/FormDrawer.vue +4 -0
- package/src/submitForm.ts +171 -17
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-manmanyu.
|
|
2
|
+
"version": "1.8.0-manmanyu.13",
|
|
3
3
|
"name": "@tmagic/form",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"vue": "^3.5.38",
|
|
54
54
|
"typescript": "^6.0.3",
|
|
55
|
-
"@tmagic/
|
|
56
|
-
"@tmagic/utils": "1.8.0-manmanyu.
|
|
57
|
-
"@tmagic/
|
|
55
|
+
"@tmagic/form-schema": "1.8.0-manmanyu.13",
|
|
56
|
+
"@tmagic/utils": "1.8.0-manmanyu.13",
|
|
57
|
+
"@tmagic/design": "1.8.0-manmanyu.13"
|
|
58
58
|
},
|
|
59
59
|
"peerDependenciesMeta": {
|
|
60
60
|
"typescript": {
|
package/src/Form.vue
CHANGED
|
@@ -83,6 +83,13 @@ const props = withDefaults(
|
|
|
83
83
|
keyProp?: string;
|
|
84
84
|
popperClass?: string;
|
|
85
85
|
preventSubmitDefault?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* 表单校验失败时,错误提示前缀是否使用字段的 text 文案(通过 `getTextByName` 从 config 中查找)。
|
|
88
|
+
*
|
|
89
|
+
* - `true`(默认):错误提示形如 `字段文案 -> 错误信息`,找不到 text 时回退为字段 name;
|
|
90
|
+
* - `false`:跳过查找,直接使用字段 name 作为错误提示前缀(形如 `字段name -> 错误信息`)。
|
|
91
|
+
*/
|
|
92
|
+
useFieldTextInError?: boolean;
|
|
86
93
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
87
94
|
/**
|
|
88
95
|
* 自定义"是否展示对比内容"的判断函数(仅在 `isCompare === true` 时生效)。
|
|
@@ -134,6 +141,7 @@ const props = withDefaults(
|
|
|
134
141
|
inline: false,
|
|
135
142
|
labelPosition: 'right',
|
|
136
143
|
keyProp: '__key',
|
|
144
|
+
useFieldTextInError: true,
|
|
137
145
|
},
|
|
138
146
|
);
|
|
139
147
|
|
|
@@ -433,7 +441,7 @@ defineExpose({
|
|
|
433
441
|
Object.entries(invalidFields).forEach(([prop, ValidateError]) => {
|
|
434
442
|
(ValidateError as ValidateError[]).forEach(({ field, message }) => {
|
|
435
443
|
const name = field || prop;
|
|
436
|
-
const text = getTextByName(name, props.config) || name;
|
|
444
|
+
const text = (props.useFieldTextInError ? getTextByName(name, props.config) : undefined) || name;
|
|
437
445
|
|
|
438
446
|
error.push(`${text} -> ${message}`);
|
|
439
447
|
});
|
package/src/FormBox.vue
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
:label-position="labelPosition"
|
|
14
14
|
:inline="inline"
|
|
15
15
|
:prevent-submit-default="preventSubmitDefault"
|
|
16
|
+
:use-field-text-in-error="useFieldTextInError"
|
|
16
17
|
:extend-state="extendState"
|
|
17
18
|
@change="changeHandler"
|
|
18
19
|
></Form>
|
|
@@ -61,12 +62,15 @@ const props = withDefaults(
|
|
|
61
62
|
inline?: boolean;
|
|
62
63
|
labelPosition?: string;
|
|
63
64
|
preventSubmitDefault?: boolean;
|
|
65
|
+
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
|
|
66
|
+
useFieldTextInError?: boolean;
|
|
64
67
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
65
68
|
}>(),
|
|
66
69
|
{
|
|
67
70
|
config: () => [],
|
|
68
71
|
values: () => ({}),
|
|
69
72
|
confirmText: '确定',
|
|
73
|
+
useFieldTextInError: true,
|
|
70
74
|
},
|
|
71
75
|
);
|
|
72
76
|
|
package/src/FormDialog.vue
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
:label-position="labelPosition"
|
|
32
32
|
:inline="inline"
|
|
33
33
|
:prevent-submit-default="preventSubmitDefault"
|
|
34
|
+
:use-field-text-in-error="useFieldTextInError"
|
|
34
35
|
:extend-state="extendState"
|
|
35
36
|
:theme="effectiveTheme"
|
|
36
37
|
@change="changeHandler"
|
|
@@ -97,6 +98,8 @@ const props = withDefaults(
|
|
|
97
98
|
destroyOnClose?: boolean;
|
|
98
99
|
showClose?: boolean;
|
|
99
100
|
showCancel?: boolean;
|
|
101
|
+
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
|
|
102
|
+
useFieldTextInError?: boolean;
|
|
100
103
|
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
|
101
104
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
102
105
|
/**
|
|
@@ -115,6 +118,7 @@ const props = withDefaults(
|
|
|
115
118
|
destroyOnClose: false,
|
|
116
119
|
showClose: true,
|
|
117
120
|
showCancel: true,
|
|
121
|
+
useFieldTextInError: true,
|
|
118
122
|
},
|
|
119
123
|
);
|
|
120
124
|
|
package/src/FormDrawer.vue
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
:label-position="labelPosition"
|
|
29
29
|
:inline="inline"
|
|
30
30
|
:prevent-submit-default="preventSubmitDefault"
|
|
31
|
+
:use-field-text-in-error="useFieldTextInError"
|
|
31
32
|
:extend-state="extendState"
|
|
32
33
|
:theme="effectiveTheme"
|
|
33
34
|
@change="changeHandler"
|
|
@@ -83,6 +84,8 @@ const props = withDefaults(
|
|
|
83
84
|
inline?: boolean;
|
|
84
85
|
labelPosition?: string;
|
|
85
86
|
preventSubmitDefault?: boolean;
|
|
87
|
+
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
|
|
88
|
+
useFieldTextInError?: boolean;
|
|
86
89
|
/** 关闭前的回调,会暂停 Drawer 的关闭; done 是个 function type 接受一个 boolean 参数, 执行 done 使用 true 参数或不提供参数将会终止关闭 */
|
|
87
90
|
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
|
|
88
91
|
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
|
@@ -99,6 +102,7 @@ const props = withDefaults(
|
|
|
99
102
|
config: () => [],
|
|
100
103
|
values: () => ({}),
|
|
101
104
|
confirmText: '确定',
|
|
105
|
+
useFieldTextInError: true,
|
|
102
106
|
},
|
|
103
107
|
);
|
|
104
108
|
|
package/src/submitForm.ts
CHANGED
|
@@ -45,6 +45,11 @@ export interface SubmitFormOptions {
|
|
|
45
45
|
keyProp?: string;
|
|
46
46
|
popperClass?: string;
|
|
47
47
|
preventSubmitDefault?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* 表单校验失败时,错误提示前缀是否使用字段的 text 文案(通过 `getTextByName` 从 config 中查找)。
|
|
50
|
+
* 默认 `true`,置为 `false` 时直接使用字段 name。
|
|
51
|
+
*/
|
|
52
|
+
useFieldTextInError?: boolean;
|
|
48
53
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
49
54
|
/** 透传给 Form.submitForm 的参数:是否直接返回原始响应式 values */
|
|
50
55
|
native?: boolean;
|
|
@@ -60,6 +65,15 @@ export interface SubmitFormOptions {
|
|
|
60
65
|
appContext?: AppContext | null;
|
|
61
66
|
/** 等待表单初始化的最长时间(毫秒),超时将以错误 reject。默认 10000ms */
|
|
62
67
|
timeout?: number;
|
|
68
|
+
/**
|
|
69
|
+
* 调试模式。默认 `false`。
|
|
70
|
+
*
|
|
71
|
+
* - `false`:表单以隐藏方式挂载,初始化完成后自动提交(原有行为)。
|
|
72
|
+
* - `true`:将表单以弹层形式可见地渲染在页面上,需手动点击「确定」才会触发校验/提交,
|
|
73
|
+
* 点击「取消」则以 reject 中断;校验失败时保留弹层并展示错误信息,便于修正后重试。
|
|
74
|
+
* 调试模式下 `timeout` 不生效(等待人工操作)。
|
|
75
|
+
*/
|
|
76
|
+
debug?: boolean;
|
|
63
77
|
}
|
|
64
78
|
// #endregion SubmitFormOptions
|
|
65
79
|
|
|
@@ -102,14 +116,24 @@ export interface SubmitFormResult {
|
|
|
102
116
|
* initValues: { name: 'foo' },
|
|
103
117
|
* returnChangeRecords: true,
|
|
104
118
|
* });
|
|
119
|
+
*
|
|
120
|
+
* // 调试模式:可见地渲染表单,点击「确定」才提交:
|
|
121
|
+
* const values = await submitForm({
|
|
122
|
+
* config: [...],
|
|
123
|
+
* initValues: { name: 'foo' },
|
|
124
|
+
* debug: true,
|
|
125
|
+
* });
|
|
105
126
|
* ```
|
|
106
127
|
*/
|
|
107
128
|
export const submitForm = (options: SubmitFormOptions): Promise<any> => {
|
|
108
|
-
const { native, appContext, timeout = 10000, returnChangeRecords, ...formProps } = options;
|
|
129
|
+
const { native, appContext, timeout = 10000, returnChangeRecords, debug = false, ...formProps } = options;
|
|
109
130
|
|
|
110
131
|
return new Promise((resolve, reject) => {
|
|
111
132
|
const container = document.createElement('div');
|
|
112
|
-
|
|
133
|
+
// 调试模式下需要把表单展示出来,普通模式则隐藏挂载
|
|
134
|
+
if (!debug) {
|
|
135
|
+
container.style.display = 'none';
|
|
136
|
+
}
|
|
113
137
|
document.body.appendChild(container);
|
|
114
138
|
|
|
115
139
|
let cleaned = false;
|
|
@@ -119,25 +143,154 @@ export const submitForm = (options: SubmitFormOptions): Promise<any> => {
|
|
|
119
143
|
name: 'MFormSubmitWrapper',
|
|
120
144
|
setup() {
|
|
121
145
|
const formRef = ref<any>(null);
|
|
146
|
+
// 调试模式下用于展示校验失败信息
|
|
147
|
+
const errorMsg = ref('');
|
|
122
148
|
|
|
149
|
+
const doSubmit = async () => {
|
|
150
|
+
try {
|
|
151
|
+
// 等待子组件(FormItem 等)完成首次渲染,确保 validate 能拿到所有字段
|
|
152
|
+
await nextTick();
|
|
153
|
+
// submitForm 校验通过后会清空 changeRecords,需在调用前先做快照
|
|
154
|
+
const changeRecords: ChangeRecord[] = [...(formRef.value?.changeRecords ?? [])];
|
|
155
|
+
const result = await formRef.value.submitForm(native);
|
|
156
|
+
resolve(returnChangeRecords ? { values: result, changeRecords } : result);
|
|
157
|
+
cleanup();
|
|
158
|
+
} catch (err) {
|
|
159
|
+
// 调试模式下校验失败时保留弹层并展示错误,便于修正后重新提交
|
|
160
|
+
if (debug) {
|
|
161
|
+
errorMsg.value = err instanceof Error ? err.message : String(err);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
reject(err);
|
|
165
|
+
cleanup();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// 调试模式:可见地渲染表单,点击「确定」才提交,点击「取消」则中断
|
|
170
|
+
if (debug) {
|
|
171
|
+
const handleCancel = () => {
|
|
172
|
+
reject(new Error('submitForm canceled in debug mode.'));
|
|
173
|
+
cleanup();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const btnBase = {
|
|
177
|
+
padding: '8px 20px',
|
|
178
|
+
fontSize: '14px',
|
|
179
|
+
lineHeight: '1',
|
|
180
|
+
border: '1px solid #dcdfe6',
|
|
181
|
+
borderRadius: '4px',
|
|
182
|
+
cursor: 'pointer',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return () =>
|
|
186
|
+
h(
|
|
187
|
+
'div',
|
|
188
|
+
{
|
|
189
|
+
style: {
|
|
190
|
+
position: 'fixed',
|
|
191
|
+
inset: '0',
|
|
192
|
+
zIndex: '10000',
|
|
193
|
+
display: 'flex',
|
|
194
|
+
alignItems: 'center',
|
|
195
|
+
justifyContent: 'center',
|
|
196
|
+
background: 'rgba(0, 0, 0, 0.5)',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
[
|
|
200
|
+
h(
|
|
201
|
+
'div',
|
|
202
|
+
{
|
|
203
|
+
style: {
|
|
204
|
+
display: 'flex',
|
|
205
|
+
flexDirection: 'column',
|
|
206
|
+
width: '600px',
|
|
207
|
+
maxWidth: '90vw',
|
|
208
|
+
maxHeight: '85vh',
|
|
209
|
+
background: '#fff',
|
|
210
|
+
borderRadius: '8px',
|
|
211
|
+
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.2)',
|
|
212
|
+
overflow: 'hidden',
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
[
|
|
216
|
+
h(
|
|
217
|
+
'div',
|
|
218
|
+
{
|
|
219
|
+
style: {
|
|
220
|
+
padding: '16px 20px',
|
|
221
|
+
fontSize: '16px',
|
|
222
|
+
fontWeight: '600',
|
|
223
|
+
borderBottom: '1px solid #ebeef5',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
'submitForm 调试',
|
|
227
|
+
),
|
|
228
|
+
h(
|
|
229
|
+
'div',
|
|
230
|
+
{
|
|
231
|
+
style: {
|
|
232
|
+
flex: '1',
|
|
233
|
+
padding: '20px',
|
|
234
|
+
overflow: 'auto',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
[
|
|
238
|
+
h(Form as Component, { ...formProps, ref: formRef }),
|
|
239
|
+
errorMsg.value
|
|
240
|
+
? h('div', {
|
|
241
|
+
style: {
|
|
242
|
+
marginTop: '12px',
|
|
243
|
+
color: '#f56c6c',
|
|
244
|
+
fontSize: '13px',
|
|
245
|
+
lineHeight: '1.5',
|
|
246
|
+
},
|
|
247
|
+
innerHTML: errorMsg.value,
|
|
248
|
+
})
|
|
249
|
+
: null,
|
|
250
|
+
],
|
|
251
|
+
),
|
|
252
|
+
h(
|
|
253
|
+
'div',
|
|
254
|
+
{
|
|
255
|
+
style: {
|
|
256
|
+
display: 'flex',
|
|
257
|
+
justifyContent: 'flex-end',
|
|
258
|
+
gap: '12px',
|
|
259
|
+
padding: '12px 20px',
|
|
260
|
+
borderTop: '1px solid #ebeef5',
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
[
|
|
264
|
+
h('button', { type: 'button', onClick: handleCancel, style: { ...btnBase } }, '取消'),
|
|
265
|
+
h(
|
|
266
|
+
'button',
|
|
267
|
+
{
|
|
268
|
+
type: 'button',
|
|
269
|
+
onClick: doSubmit,
|
|
270
|
+
style: {
|
|
271
|
+
...btnBase,
|
|
272
|
+
color: '#fff',
|
|
273
|
+
background: '#409eff',
|
|
274
|
+
borderColor: '#409eff',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
'确定',
|
|
278
|
+
),
|
|
279
|
+
],
|
|
280
|
+
),
|
|
281
|
+
],
|
|
282
|
+
),
|
|
283
|
+
],
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 普通模式:表单初始化完成后自动提交
|
|
123
288
|
const stop = watch(
|
|
124
289
|
() => formRef.value?.initialized,
|
|
125
|
-
|
|
290
|
+
(initialized) => {
|
|
126
291
|
if (!initialized) return;
|
|
127
292
|
stop();
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
// 等待子组件(FormItem 等)完成首次渲染,确保 validate 能拿到所有字段
|
|
131
|
-
await nextTick();
|
|
132
|
-
// submitForm 校验通过后会清空 changeRecords,需在调用前先做快照
|
|
133
|
-
const changeRecords: ChangeRecord[] = [...(formRef.value.changeRecords ?? [])];
|
|
134
|
-
const result = await formRef.value.submitForm(native);
|
|
135
|
-
resolve(returnChangeRecords ? { values: result, changeRecords } : result);
|
|
136
|
-
} catch (err) {
|
|
137
|
-
reject(err);
|
|
138
|
-
} finally {
|
|
139
|
-
cleanup();
|
|
140
|
-
}
|
|
293
|
+
doSubmit();
|
|
141
294
|
},
|
|
142
295
|
{ flush: 'post', immediate: true },
|
|
143
296
|
);
|
|
@@ -168,7 +321,8 @@ export const submitForm = (options: SubmitFormOptions): Promise<any> => {
|
|
|
168
321
|
container.parentNode?.removeChild(container);
|
|
169
322
|
};
|
|
170
323
|
|
|
171
|
-
|
|
324
|
+
// 调试模式等待人工操作,不应用超时
|
|
325
|
+
if (timeout > 0 && !debug) {
|
|
172
326
|
timer = setTimeout(() => {
|
|
173
327
|
if (!cleaned) {
|
|
174
328
|
reject(new Error(`submitForm timeout after ${timeout}ms: form is not initialized.`));
|