@tmagic/form 1.8.0-beta.2 → 1.8.0-beta.21
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 +120 -27
- package/dist/es/FormBox.vue_vue_type_script_setup_true_lang.js +19 -3
- package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +30 -7
- package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +56 -14
- package/dist/es/containers/Col.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +51 -36
- package/dist/es/containers/Fieldset.vue_vue_type_script_setup_true_lang.js +5 -3
- package/dist/es/containers/FlexLayout.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/FormLabel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +7 -6
- package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +42 -32
- package/dist/es/containers/Panel.vue_vue_type_script_setup_true_lang.js +17 -11
- package/dist/es/containers/Row.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/Step.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/Tabs.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/table/ActionsColumn.vue_vue_type_script_setup_true_lang.js +14 -7
- package/dist/es/containers/table/SortColumn.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +7 -5
- package/dist/es/containers/table/useTableColumns.js +5 -1
- package/dist/es/containers/table-group-list/TableGroupList.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/es/fields/Cascader.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/Checkbox.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/CheckboxGroup.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/ColorPicker.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Date.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/DateTime.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/Daterange.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/Display.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/DynamicField.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Hidden.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/Link.vue_vue_type_script_setup_true_lang.js +30 -8
- package/dist/es/fields/Number.vue_vue_type_script_setup_true_lang.js +8 -3
- package/dist/es/fields/NumberRange.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/RadioGroup.vue_vue_type_script_setup_true_lang.js +21 -10
- package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +7 -3
- package/dist/es/fields/Switch.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Text.vue_vue_type_script_setup_true_lang.js +113 -78
- package/dist/es/fields/Textarea.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Time.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Timerange.vue_vue_type_script_setup_true_lang.js +7 -4
- package/dist/es/index.js +5 -4
- package/dist/es/plugin.js +4 -0
- package/dist/es/schema.js +12 -1
- package/dist/es/style.css +133 -11
- package/dist/es/submitForm.js +415 -56
- package/dist/es/utils/config.js +4 -1
- package/dist/es/utils/form.js +84 -9
- package/dist/es/utils/typeMatch.js +487 -0
- package/dist/style.css +133 -11
- package/dist/themes/magic-admin.css +509 -0
- package/dist/tmagic-form.umd.cjs +1773 -399
- package/package.json +5 -5
- package/src/Form.vue +163 -30
- package/src/FormBox.vue +11 -0
- package/src/FormDialog.vue +25 -3
- package/src/FormDrawer.vue +51 -12
- package/src/containers/Col.vue +1 -0
- package/src/containers/Container.vue +32 -11
- package/src/containers/Fieldset.vue +5 -2
- package/src/containers/GroupList.vue +1 -1
- package/src/containers/GroupListItem.vue +28 -22
- package/src/containers/Panel.vue +10 -3
- package/src/containers/table/ActionsColumn.vue +10 -4
- package/src/containers/table/Table.vue +1 -0
- package/src/containers/table/useTableColumns.ts +7 -3
- package/src/containers/table-group-list/TableGroupList.vue +7 -1
- package/src/fields/Link.vue +16 -3
- package/src/fields/Number.vue +2 -1
- package/src/fields/RadioGroup.vue +24 -6
- package/src/fields/Text.vue +43 -19
- package/src/index.ts +11 -0
- package/src/plugin.ts +12 -0
- package/src/schema.ts +13 -1
- package/src/submitForm.ts +699 -66
- package/src/theme/container.scss +3 -0
- package/src/theme/fieldset.scss +51 -0
- package/src/theme/form-dialog.scss +1 -1
- package/src/theme/form.scss +6 -0
- package/src/theme/group-list.scss +19 -8
- package/src/theme/index.scss +1 -0
- package/src/theme/link.scss +17 -0
- package/src/theme/panel.scss +1 -1
- package/src/theme/radio-group.scss +23 -0
- package/src/theme/table.scss +11 -1
- package/src/theme/tabs.scss +2 -2
- package/src/theme/text.scss +38 -0
- package/src/theme/themes/magic-admin/index.scss +106 -0
- package/src/utils/config.ts +7 -1
- package/src/utils/form.ts +144 -22
- package/src/utils/typeMatch.ts +870 -0
- package/types/index.d.ts +408 -21
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.21",
|
|
3
3
|
"name": "@tmagic/form",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"@vue/test-utils": "^2.4.6"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"vue": "^3.5.
|
|
53
|
+
"vue": "^3.5.40",
|
|
54
54
|
"typescript": "^6.0.3",
|
|
55
|
-
"@tmagic/design": "1.8.0-beta.
|
|
56
|
-
"@tmagic/
|
|
57
|
-
"@tmagic/
|
|
55
|
+
"@tmagic/design": "1.8.0-beta.21",
|
|
56
|
+
"@tmagic/utils": "1.8.0-beta.21",
|
|
57
|
+
"@tmagic/form-schema": "1.8.0-beta.21"
|
|
58
58
|
},
|
|
59
59
|
"peerDependenciesMeta": {
|
|
60
60
|
"typescript": {
|
package/src/Form.vue
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<TMagicForm
|
|
3
|
-
class="
|
|
3
|
+
:class="[
|
|
4
|
+
'm-form',
|
|
5
|
+
effectiveTheme ? `m-form--${effectiveTheme}` : '',
|
|
6
|
+
effectiveTheme ? `m-theme--${effectiveTheme}` : '',
|
|
7
|
+
]"
|
|
4
8
|
ref="tMagicForm"
|
|
5
9
|
:model="values"
|
|
6
10
|
:label-width="labelWidth"
|
|
@@ -32,15 +36,27 @@
|
|
|
32
36
|
</template>
|
|
33
37
|
|
|
34
38
|
<script setup lang="ts">
|
|
35
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
computed,
|
|
41
|
+
inject,
|
|
42
|
+
nextTick,
|
|
43
|
+
provide,
|
|
44
|
+
reactive,
|
|
45
|
+
ref,
|
|
46
|
+
shallowRef,
|
|
47
|
+
toRaw,
|
|
48
|
+
useTemplateRef,
|
|
49
|
+
watch,
|
|
50
|
+
watchEffect,
|
|
51
|
+
} from 'vue';
|
|
36
52
|
import { cloneDeep, isEqual } from 'lodash-es';
|
|
37
53
|
|
|
38
|
-
import { TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
|
54
|
+
import { M_THEME_KEY, TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
|
39
55
|
import { setValueByKeyPath } from '@tmagic/utils';
|
|
40
56
|
|
|
41
57
|
import Container from './containers/Container.vue';
|
|
42
58
|
import { getConfig } from './utils/config';
|
|
43
|
-
import { initValue } from './utils/form';
|
|
59
|
+
import { applyExtendState, initValue } from './utils/form';
|
|
44
60
|
import type {
|
|
45
61
|
ChangeRecord,
|
|
46
62
|
ContainerChangeEventData,
|
|
@@ -50,7 +66,7 @@ import type {
|
|
|
50
66
|
FormValue,
|
|
51
67
|
ValidateError,
|
|
52
68
|
} from './schema';
|
|
53
|
-
import { FORM_DIFF_CONFIG_KEY } from './schema';
|
|
69
|
+
import { FORM_DIFF_CONFIG_KEY, FORM_TYPE_MATCH_VALID_KEY } from './schema';
|
|
54
70
|
|
|
55
71
|
defineOptions({
|
|
56
72
|
name: 'MForm',
|
|
@@ -70,6 +86,15 @@ const props = withDefaults(
|
|
|
70
86
|
isCompare?: boolean;
|
|
71
87
|
parentValues?: Record<string, any>;
|
|
72
88
|
labelWidth?: string;
|
|
89
|
+
/** 是否开启类型匹配校验 */
|
|
90
|
+
typeMatchValid?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* 初始化(`config` / `initValues` 就绪)后是否立即执行一次表单校验。
|
|
93
|
+
*
|
|
94
|
+
* - `false`(默认):不自动校验,避免打开表单时就展示错误态;
|
|
95
|
+
* - `true`:初始化完成后在 `nextTick` 中调用 `validate()`。
|
|
96
|
+
*/
|
|
97
|
+
validateOnInit?: boolean;
|
|
73
98
|
disabled?: boolean;
|
|
74
99
|
height?: string;
|
|
75
100
|
stepActive?: string | number;
|
|
@@ -79,6 +104,13 @@ const props = withDefaults(
|
|
|
79
104
|
keyProp?: string;
|
|
80
105
|
popperClass?: string;
|
|
81
106
|
preventSubmitDefault?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* 表单校验失败时,错误提示前缀是否使用字段的 text 文案(通过 `getTextByName` 从 config 中查找)。
|
|
109
|
+
*
|
|
110
|
+
* - `true`(默认):错误提示形如 `字段文案 -> 错误信息`,找不到 text 时回退为字段 name;
|
|
111
|
+
* - `false`:跳过查找,直接使用字段 name 作为错误提示前缀(形如 `字段name -> 错误信息`)。
|
|
112
|
+
*/
|
|
113
|
+
useFieldTextInError?: boolean;
|
|
82
114
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
83
115
|
/**
|
|
84
116
|
* 自定义"是否展示对比内容"的判断函数(仅在 `isCompare === true` 时生效)。
|
|
@@ -107,6 +139,15 @@ const props = withDefaults(
|
|
|
107
139
|
* 通过 provide 下发,对整棵表单的所有层级 Container 生效,只需在 MForm 这一层传一次。
|
|
108
140
|
*/
|
|
109
141
|
selfDiffFieldTypes?: string[] | ((_defaultTypes: string[]) => string[]);
|
|
142
|
+
/**
|
|
143
|
+
* 主题名称:对应 `packages/form/src/theme/themes/<theme>/index.scss` 的目录名。
|
|
144
|
+
*
|
|
145
|
+
* 设置后会在表单根元素上追加 `m-form--<theme>` 修饰类,配合按需引入
|
|
146
|
+
* `@tmagic/form/dist/themes/<theme>.css` 即可启用主题样式。
|
|
147
|
+
*
|
|
148
|
+
* 例如:`<MForm theme="magic-admin" />` + `import '@tmagic/form/dist/themes/magic-admin.css'`。
|
|
149
|
+
*/
|
|
150
|
+
theme?: string;
|
|
110
151
|
}>(),
|
|
111
152
|
{
|
|
112
153
|
config: () => [],
|
|
@@ -121,11 +162,18 @@ const props = withDefaults(
|
|
|
121
162
|
inline: false,
|
|
122
163
|
labelPosition: 'right',
|
|
123
164
|
keyProp: '__key',
|
|
165
|
+
useFieldTextInError: true,
|
|
166
|
+
validateOnInit: false,
|
|
124
167
|
},
|
|
125
168
|
);
|
|
126
169
|
|
|
127
170
|
const emit = defineEmits(['change', 'error', 'field-input', 'field-change', 'update:stepActive']);
|
|
128
171
|
|
|
172
|
+
provide(
|
|
173
|
+
FORM_TYPE_MATCH_VALID_KEY,
|
|
174
|
+
computed(() => props.typeMatchValid),
|
|
175
|
+
);
|
|
176
|
+
|
|
129
177
|
const tMagicFormRef = useTemplateRef('tMagicForm');
|
|
130
178
|
const initialized = ref(false);
|
|
131
179
|
const values = ref<FormValue>({});
|
|
@@ -134,6 +182,27 @@ const fields = new Map<string, any>();
|
|
|
134
182
|
|
|
135
183
|
const requestFuc = getConfig('request') as Function;
|
|
136
184
|
|
|
185
|
+
/**
|
|
186
|
+
* 当前表单生效的主题名称:
|
|
187
|
+
* - 优先用本组件自己的 `props.theme`;
|
|
188
|
+
* - 没设置时回退到最近祖先 `<MEditor>` / `<MForm>` provide 的主题,便于内嵌于编辑器
|
|
189
|
+
* 时自动跟随外层主题,无需在每个 `MForm` 上重复传 `theme`。
|
|
190
|
+
*
|
|
191
|
+
* 同时把合并后的值再 provide 出去(见下方 `provide(M_THEME_KEY, ...)`),让 form 子树
|
|
192
|
+
* 里再嵌套的 portal 组件(`TMagicPopover` 等)依然能拿到非空主题。
|
|
193
|
+
*/
|
|
194
|
+
const ancestorTheme = inject(M_THEME_KEY, null);
|
|
195
|
+
const effectiveTheme = computed(() => props.theme || ancestorTheme?.value || '');
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 拼到 `formState.popperClass` 上的主题修饰类(仅 `m-theme--<theme>`,
|
|
199
|
+
* 不带 `m-form` / `m-editor` 前缀,因为 Element Plus 弹层节点本身既不是 form 也不是 editor)。
|
|
200
|
+
*
|
|
201
|
+
* 这条类会随所有读 `mForm.popperClass` 的字段(Select / DateTime / Cascader 等)下发到
|
|
202
|
+
* Element Plus 的 `popper-class`,让 portal 节点也命中 `m-theme--<theme>` 上的 CSS 变量。
|
|
203
|
+
*/
|
|
204
|
+
const themeClass = computed(() => (effectiveTheme.value ? `m-theme--${effectiveTheme.value}` : ''));
|
|
205
|
+
|
|
137
206
|
/**
|
|
138
207
|
* formState 实现说明:
|
|
139
208
|
*
|
|
@@ -150,13 +219,21 @@ const requestFuc = getConfig('request') as Function;
|
|
|
150
219
|
* 把最新值刷进 formState。
|
|
151
220
|
* - accessor 描述符(`{ get stage() { return ... } }`)按原样写入,调用方可以控制
|
|
152
221
|
* 读时求值,每次读取都会重新执行 getter。
|
|
222
|
+
*
|
|
223
|
+
* 4. `popperClass` 会自动拼接 `themeClass`:调用方传入的 `popperClass` + 当前主题
|
|
224
|
+
* 修饰类(含祖先 `<MEditor>` provide 的主题)。这样所有透传到 Element Plus 弹层
|
|
225
|
+
* `popper-class` 的字段(Select / DateTime / Cascader 等)能自带主题作用域。
|
|
153
226
|
*/
|
|
154
227
|
const formState: FormState = reactive<FormState>({
|
|
155
228
|
get keyProp() {
|
|
156
229
|
return props.keyProp;
|
|
157
230
|
},
|
|
158
231
|
get popperClass() {
|
|
159
|
-
|
|
232
|
+
const userClass = props.popperClass ?? '';
|
|
233
|
+
const tc = themeClass.value;
|
|
234
|
+
if (!userClass) return tc;
|
|
235
|
+
if (!tc) return userClass;
|
|
236
|
+
return `${userClass} ${tc}`;
|
|
160
237
|
},
|
|
161
238
|
get config() {
|
|
162
239
|
return props.config;
|
|
@@ -192,6 +269,18 @@ const formState: FormState = reactive<FormState>({
|
|
|
192
269
|
},
|
|
193
270
|
});
|
|
194
271
|
|
|
272
|
+
/**
|
|
273
|
+
* formState 的内置 key 快照(keyProp / values / $emit / fields / post 等)。
|
|
274
|
+
*
|
|
275
|
+
* 在 `extendState` 首次合并前捕获,`applyExtendState` 会据此禁止 `extendState`
|
|
276
|
+
* 覆盖这些已有字段(只能新增字段),避免表单核心状态被外部意外改写。
|
|
277
|
+
*
|
|
278
|
+
* 之所以在此处(effect 之外)捕获而不是在 `applyExtendState` 内动态取:
|
|
279
|
+
* `watchEffect` 会在依赖变化时重跑,若动态取,`extendState` 自己新增的字段在第二次
|
|
280
|
+
* 合并时也会被当成「已有 key」而拒绝刷新;这里只锁定内置字段即可规避该问题。
|
|
281
|
+
*/
|
|
282
|
+
const reservedStateKeys = new Set<string | symbol>(Reflect.ownKeys(formState));
|
|
283
|
+
|
|
195
284
|
/**
|
|
196
285
|
* `extendState` 的同步段(直到第一个 `await` 之前)所访问的任何响应式数据,
|
|
197
286
|
* 都会被 `watchEffect` 自动跟踪。这样可以兼容历史用法 ——
|
|
@@ -207,11 +296,10 @@ const formState: FormState = reactive<FormState>({
|
|
|
207
296
|
* 这里不再重复同步;因此 `props.initValues` 这类高频变化也不会再触发
|
|
208
297
|
* `extendState` 重跑(旧版的性能问题修复点)。
|
|
209
298
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* 可以自行控制读时求值;强制 `configurable: true` 以便下一次重跑可再 define。
|
|
299
|
+
* 实现细节:合并逻辑统一收口在 `applyExtendState`(utils/form)——
|
|
300
|
+
* data 描述符走 reactive proxy 的 set 触发依赖通知(与旧版「逐项赋值」语义等价),
|
|
301
|
+
* accessor 描述符按原样 defineProperty 支持读时求值;
|
|
302
|
+
* props 派生的只读 getter 字段(keyProp 等)以普通字段形式返回时会被跳过并告警。
|
|
215
303
|
*/
|
|
216
304
|
watchEffect(async (onCleanup) => {
|
|
217
305
|
const { extendState } = props;
|
|
@@ -231,18 +319,18 @@ watchEffect(async (onCleanup) => {
|
|
|
231
319
|
}
|
|
232
320
|
if (stale) return;
|
|
233
321
|
|
|
234
|
-
|
|
235
|
-
if ('value' in descriptor) {
|
|
236
|
-
(formState as any)[key] = (state as any)[key];
|
|
237
|
-
} else {
|
|
238
|
-
descriptor.configurable = true;
|
|
239
|
-
Object.defineProperty(formState, key, descriptor);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
322
|
+
applyExtendState(formState, state, reservedStateKeys);
|
|
242
323
|
});
|
|
243
324
|
|
|
244
325
|
provide('mForm', formState);
|
|
245
326
|
|
|
327
|
+
/**
|
|
328
|
+
* 把生效主题(自身或祖先)再 provide 出去,供 form 子树内含 `Teleport` 的组件
|
|
329
|
+
* (如 `TMagicPopover`)在传送目标上挂 `m-theme--<theme>` 类。
|
|
330
|
+
* 详见 `@tmagic/design/theme.ts`。
|
|
331
|
+
*/
|
|
332
|
+
provide(M_THEME_KEY, effectiveTheme);
|
|
333
|
+
provide('formInline', props.inline);
|
|
246
334
|
// 对比相关配置单独通过 provide 下发,所有层级的 Container 通过 inject 获取,无需逐层透传 prop。
|
|
247
335
|
// 用 getter 对象保证读取时回到最新的 props 值,维持响应式。
|
|
248
336
|
provide(FORM_DIFF_CONFIG_KEY, {
|
|
@@ -272,6 +360,12 @@ watch(
|
|
|
272
360
|
values.value = value;
|
|
273
361
|
// 非对比模式,初始化完成
|
|
274
362
|
initialized.value = !props.isCompare;
|
|
363
|
+
|
|
364
|
+
if (props.validateOnInit) {
|
|
365
|
+
nextTick(() => {
|
|
366
|
+
tMagicFormRef.value?.validate();
|
|
367
|
+
});
|
|
368
|
+
}
|
|
275
369
|
});
|
|
276
370
|
|
|
277
371
|
if (props.isCompare) {
|
|
@@ -352,6 +446,27 @@ const getTextByName = (name: string, config: FormConfig = props.config): string
|
|
|
352
446
|
return findInConfig(config, nameParts);
|
|
353
447
|
};
|
|
354
448
|
|
|
449
|
+
/**
|
|
450
|
+
* 将校验返回的 invalidFields 汇总为可读的错误文案(多条以 `<br>` 拼接)。
|
|
451
|
+
*
|
|
452
|
+
* 抽离为独立方法,供 `submitForm`(提交校验)与 `validate`(返回错误文案的校验)复用,
|
|
453
|
+
* 保证两种校验入口产出的错误文案格式完全一致。
|
|
454
|
+
*/
|
|
455
|
+
const formatValidateError = (invalidFields: Record<string, any>): string => {
|
|
456
|
+
const error: string[] = [];
|
|
457
|
+
|
|
458
|
+
Object.entries(invalidFields).forEach(([prop, validateError]) => {
|
|
459
|
+
(validateError as ValidateError[]).forEach(({ field, message }) => {
|
|
460
|
+
const name = field || prop;
|
|
461
|
+
const text = (props.useFieldTextInError ? getTextByName(name, props.config) : undefined) || name;
|
|
462
|
+
|
|
463
|
+
error.push(`${text} -> ${message}`);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return error.join('<br>');
|
|
468
|
+
};
|
|
469
|
+
|
|
355
470
|
defineExpose({
|
|
356
471
|
values,
|
|
357
472
|
lastValuesProcessed,
|
|
@@ -379,18 +494,36 @@ defineExpose({
|
|
|
379
494
|
} catch (invalidFields: any) {
|
|
380
495
|
emit('error', invalidFields);
|
|
381
496
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
(ValidateError as ValidateError[]).forEach(({ field, message }) => {
|
|
386
|
-
const name = field || prop;
|
|
387
|
-
const text = getTextByName(name, props.config) || name;
|
|
388
|
-
|
|
389
|
-
error.push(`${text} -> ${message}`);
|
|
390
|
-
});
|
|
391
|
-
});
|
|
497
|
+
throw new Error(formatValidateError(invalidFields));
|
|
498
|
+
}
|
|
499
|
+
},
|
|
392
500
|
|
|
393
|
-
|
|
501
|
+
/**
|
|
502
|
+
* 校验:对表单当前值执行校验,返回汇总后的错误文案。
|
|
503
|
+
*
|
|
504
|
+
* 与 `submitForm` 的区别:
|
|
505
|
+
* - 校验失败时不抛异常、不触发 `error` 事件,而是以返回值形式给出错误文案;
|
|
506
|
+
* - 不重置 `changeRecords`,不改变提交语义,仅用于「探测」当前配置是否合法。
|
|
507
|
+
*
|
|
508
|
+
* 注意:本方法只改变「校验结果的返回方式」,并不负责「不污染页面表单状态」——
|
|
509
|
+
* 若需对一份独立的「配置 + 值」做完全不影响页面上已渲染表单的校验,请使用 `validateForm`
|
|
510
|
+
* (内部会新建一个隐藏的 MForm 实例,通过 `initValues` 传入待校验值,用完即卸载)。
|
|
511
|
+
*
|
|
512
|
+
* 典型用途:作为 `validateForm` 内部复用的校验实现;也可在已渲染的表单实例上主动调用,
|
|
513
|
+
* 根据返回的错误文案自行决定后续处理(如记录节点错误状态)。
|
|
514
|
+
*
|
|
515
|
+
* @returns 校验通过返回空字符串 `''`,否则返回以 `<br>` 拼接的错误文案。
|
|
516
|
+
*/
|
|
517
|
+
validate: async (): Promise<string> => {
|
|
518
|
+
try {
|
|
519
|
+
const result = await tMagicFormRef.value?.validate();
|
|
520
|
+
// tdesign 通过返回值返回校验结果,element-plus 通过 throw error
|
|
521
|
+
if (result !== true) {
|
|
522
|
+
throw result;
|
|
523
|
+
}
|
|
524
|
+
return '';
|
|
525
|
+
} catch (invalidFields: any) {
|
|
526
|
+
return formatValidateError(invalidFields);
|
|
394
527
|
}
|
|
395
528
|
},
|
|
396
529
|
|
package/src/FormBox.vue
CHANGED
|
@@ -13,7 +13,10 @@
|
|
|
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"
|
|
18
|
+
:type-match-valid="typeMatchValid"
|
|
19
|
+
:validate-on-init="validateOnInit"
|
|
17
20
|
@change="changeHandler"
|
|
18
21
|
></Form>
|
|
19
22
|
<slot></slot>
|
|
@@ -55,18 +58,26 @@ const props = withDefaults(
|
|
|
55
58
|
width?: number;
|
|
56
59
|
height?: number;
|
|
57
60
|
labelWidth?: string;
|
|
61
|
+
/** 是否开启类型匹配校验 */
|
|
62
|
+
typeMatchValid?: boolean;
|
|
63
|
+
/** 透传给内部 `MForm`,初始化完成后是否立即校验(默认 `false`) */
|
|
64
|
+
validateOnInit?: boolean;
|
|
58
65
|
disabled?: boolean;
|
|
59
66
|
size?: 'small' | 'default' | 'large';
|
|
60
67
|
confirmText?: string;
|
|
61
68
|
inline?: boolean;
|
|
62
69
|
labelPosition?: string;
|
|
63
70
|
preventSubmitDefault?: boolean;
|
|
71
|
+
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
|
|
72
|
+
useFieldTextInError?: boolean;
|
|
64
73
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
65
74
|
}>(),
|
|
66
75
|
{
|
|
67
76
|
config: () => [],
|
|
68
77
|
values: () => ({}),
|
|
69
78
|
confirmText: '确定',
|
|
79
|
+
useFieldTextInError: true,
|
|
80
|
+
validateOnInit: false,
|
|
70
81
|
},
|
|
71
82
|
);
|
|
72
83
|
|
package/src/FormDialog.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<TMagicDialog
|
|
3
3
|
v-model="dialogVisible"
|
|
4
|
-
class="m-form-dialog"
|
|
4
|
+
:class="['m-form-dialog', effectiveTheme ? `m-theme--${effectiveTheme}` : '']"
|
|
5
5
|
top="20px"
|
|
6
6
|
append-to-body
|
|
7
7
|
:title="title"
|
|
@@ -31,7 +31,11 @@
|
|
|
31
31
|
:label-position="labelPosition"
|
|
32
32
|
:inline="inline"
|
|
33
33
|
:prevent-submit-default="preventSubmitDefault"
|
|
34
|
+
:use-field-text-in-error="useFieldTextInError"
|
|
35
|
+
:type-match-valid="typeMatchValid"
|
|
36
|
+
:validate-on-init="validateOnInit"
|
|
34
37
|
:extend-state="extendState"
|
|
38
|
+
:theme="effectiveTheme"
|
|
35
39
|
@change="changeHandler"
|
|
36
40
|
></Form>
|
|
37
41
|
<slot></slot>
|
|
@@ -64,9 +68,9 @@
|
|
|
64
68
|
</template>
|
|
65
69
|
|
|
66
70
|
<script setup lang="ts">
|
|
67
|
-
import { computed, ref } from 'vue';
|
|
71
|
+
import { computed, inject, provide, ref } from 'vue';
|
|
68
72
|
|
|
69
|
-
import { TMagicButton, TMagicCol, TMagicDialog, TMagicRow } from '@tmagic/design';
|
|
73
|
+
import { M_THEME_KEY, TMagicButton, TMagicCol, TMagicDialog, TMagicRow } from '@tmagic/design';
|
|
70
74
|
|
|
71
75
|
import Form from './Form.vue';
|
|
72
76
|
import { ContainerChangeEventData, FormConfig, FormState, FormValue, StepConfig } from './schema';
|
|
@@ -82,6 +86,10 @@ const props = withDefaults(
|
|
|
82
86
|
parentValues?: Object;
|
|
83
87
|
width?: string | number;
|
|
84
88
|
labelWidth?: string;
|
|
89
|
+
/** 是否开启类型匹配校验 */
|
|
90
|
+
typeMatchValid?: boolean;
|
|
91
|
+
/** 透传给内部 `MForm`,初始化完成后是否立即校验(默认 `false`) */
|
|
92
|
+
validateOnInit?: boolean;
|
|
85
93
|
fullscreen?: boolean;
|
|
86
94
|
disabled?: boolean;
|
|
87
95
|
title?: string;
|
|
@@ -96,8 +104,16 @@ const props = withDefaults(
|
|
|
96
104
|
destroyOnClose?: boolean;
|
|
97
105
|
showClose?: boolean;
|
|
98
106
|
showCancel?: boolean;
|
|
107
|
+
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
|
|
108
|
+
useFieldTextInError?: boolean;
|
|
99
109
|
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
|
100
110
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
111
|
+
/**
|
|
112
|
+
* 主题名。优先级:传入 `theme` prop > 祖先 `provide(M_THEME_KEY)` > 空串。
|
|
113
|
+
* 计算结果会再次 `provide` 出去,使得 Dialog 被 Teleport 到 body 后,内部子树
|
|
114
|
+
* (包括 `MForm` 以及更深的 popover / dropdown)仍能拿到主题。
|
|
115
|
+
*/
|
|
116
|
+
theme?: string;
|
|
101
117
|
}>(),
|
|
102
118
|
{
|
|
103
119
|
config: () => [],
|
|
@@ -108,9 +124,15 @@ const props = withDefaults(
|
|
|
108
124
|
destroyOnClose: false,
|
|
109
125
|
showClose: true,
|
|
110
126
|
showCancel: true,
|
|
127
|
+
useFieldTextInError: true,
|
|
128
|
+
validateOnInit: false,
|
|
111
129
|
},
|
|
112
130
|
);
|
|
113
131
|
|
|
132
|
+
const ancestorTheme = inject(M_THEME_KEY, null);
|
|
133
|
+
const effectiveTheme = computed(() => props.theme || ancestorTheme?.value || '');
|
|
134
|
+
provide(M_THEME_KEY, effectiveTheme);
|
|
135
|
+
|
|
114
136
|
const emit = defineEmits(['close', 'submit', 'error', 'change']);
|
|
115
137
|
|
|
116
138
|
const form = ref<InstanceType<typeof Form>>();
|
package/src/FormDrawer.vue
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<TMagicDrawer
|
|
3
|
-
class="m-form-drawer"
|
|
3
|
+
:class="['m-form-drawer', effectiveTheme ? `m-theme--${effectiveTheme}` : '']"
|
|
4
4
|
ref="drawer"
|
|
5
|
-
v-model="
|
|
5
|
+
v-model="dialogVisible"
|
|
6
6
|
:title="title"
|
|
7
7
|
:close-on-press-escape="closeOnPressEscape"
|
|
8
8
|
:append-to-body="true"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
@close="closeHandler"
|
|
17
17
|
@closed="closedHandler"
|
|
18
18
|
>
|
|
19
|
-
<div v-if="
|
|
19
|
+
<div v-if="dialogVisible" ref="drawerBody" class="m-drawer-body">
|
|
20
20
|
<Form
|
|
21
21
|
ref="form"
|
|
22
22
|
:size="size"
|
|
@@ -28,7 +28,11 @@
|
|
|
28
28
|
:label-position="labelPosition"
|
|
29
29
|
:inline="inline"
|
|
30
30
|
:prevent-submit-default="preventSubmitDefault"
|
|
31
|
+
:use-field-text-in-error="useFieldTextInError"
|
|
32
|
+
:type-match-valid="typeMatchValid"
|
|
33
|
+
:validate-on-init="validateOnInit"
|
|
31
34
|
:extend-state="extendState"
|
|
35
|
+
:theme="effectiveTheme"
|
|
32
36
|
@change="changeHandler"
|
|
33
37
|
></Form>
|
|
34
38
|
<slot></slot>
|
|
@@ -55,24 +59,28 @@
|
|
|
55
59
|
</template>
|
|
56
60
|
|
|
57
61
|
<script setup lang="ts">
|
|
58
|
-
import { ref, watchEffect } from 'vue';
|
|
62
|
+
import { computed, inject, provide, ref, watchEffect } from 'vue';
|
|
59
63
|
|
|
60
|
-
import { TMagicButton, TMagicCol, TMagicDrawer, TMagicRow } from '@tmagic/design';
|
|
64
|
+
import { M_THEME_KEY, TMagicButton, TMagicCol, TMagicDrawer, TMagicRow } from '@tmagic/design';
|
|
61
65
|
|
|
62
66
|
import Form from './Form.vue';
|
|
63
67
|
import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from './schema';
|
|
64
68
|
|
|
65
69
|
defineOptions({
|
|
66
|
-
name: '
|
|
70
|
+
name: 'MFormDrawer',
|
|
67
71
|
});
|
|
68
72
|
|
|
69
|
-
withDefaults(
|
|
73
|
+
const props = withDefaults(
|
|
70
74
|
defineProps<{
|
|
71
75
|
config?: FormConfig;
|
|
72
76
|
values?: Object;
|
|
73
77
|
parentValues?: Object;
|
|
74
78
|
width?: string | number;
|
|
75
79
|
labelWidth?: string;
|
|
80
|
+
/** 是否开启类型匹配校验 */
|
|
81
|
+
typeMatchValid?: boolean;
|
|
82
|
+
/** 透传给内部 `MForm`,初始化完成后是否立即校验(默认 `false`) */
|
|
83
|
+
validateOnInit?: boolean;
|
|
76
84
|
disabled?: boolean;
|
|
77
85
|
closeOnPressEscape?: boolean;
|
|
78
86
|
title?: string;
|
|
@@ -82,25 +90,39 @@ withDefaults(
|
|
|
82
90
|
inline?: boolean;
|
|
83
91
|
labelPosition?: string;
|
|
84
92
|
preventSubmitDefault?: boolean;
|
|
93
|
+
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
|
|
94
|
+
useFieldTextInError?: boolean;
|
|
85
95
|
/** 关闭前的回调,会暂停 Drawer 的关闭; done 是个 function type 接受一个 boolean 参数, 执行 done 使用 true 参数或不提供参数将会终止关闭 */
|
|
86
96
|
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
|
|
87
97
|
/** 透传给内部 `MForm`,用于扩展 `formState`(如注入 `$message` / `$store` 等) */
|
|
88
98
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
99
|
+
/**
|
|
100
|
+
* 主题名。优先级:传入 `theme` prop > 祖先 `provide(M_THEME_KEY)` > 空串。
|
|
101
|
+
* 计算结果会再次 `provide` 出去,使得 Drawer 被 Teleport 到 body 后,内部子树
|
|
102
|
+
* (包括 `MForm` 以及更深的 popover / dropdown)仍能拿到主题。
|
|
103
|
+
*/
|
|
104
|
+
theme?: string;
|
|
89
105
|
}>(),
|
|
90
106
|
{
|
|
91
107
|
closeOnPressEscape: true,
|
|
92
108
|
config: () => [],
|
|
93
109
|
values: () => ({}),
|
|
94
110
|
confirmText: '确定',
|
|
111
|
+
useFieldTextInError: true,
|
|
112
|
+
validateOnInit: false,
|
|
95
113
|
},
|
|
96
114
|
);
|
|
97
115
|
|
|
116
|
+
const ancestorTheme = inject(M_THEME_KEY, null);
|
|
117
|
+
const effectiveTheme = computed(() => props.theme || ancestorTheme?.value || '');
|
|
118
|
+
provide(M_THEME_KEY, effectiveTheme);
|
|
119
|
+
|
|
98
120
|
const emit = defineEmits(['close', 'closed', 'submit', 'error', 'change', 'open', 'opened']);
|
|
99
121
|
|
|
100
122
|
const drawer = ref<InstanceType<typeof TMagicDrawer>>();
|
|
101
123
|
const form = ref<InstanceType<typeof Form>>();
|
|
102
124
|
const drawerBody = ref<HTMLDivElement>();
|
|
103
|
-
const
|
|
125
|
+
const dialogVisible = ref(false);
|
|
104
126
|
const saveFetch = ref(false);
|
|
105
127
|
const bodyHeight = ref(0);
|
|
106
128
|
|
|
@@ -141,25 +163,42 @@ const closedHandler = () => {
|
|
|
141
163
|
};
|
|
142
164
|
|
|
143
165
|
const show = () => {
|
|
144
|
-
|
|
166
|
+
dialogVisible.value = true;
|
|
145
167
|
};
|
|
146
168
|
|
|
147
169
|
const hide = () => {
|
|
148
|
-
|
|
170
|
+
dialogVisible.value = false;
|
|
171
|
+
};
|
|
172
|
+
const close = () => {
|
|
173
|
+
dialogVisible.value = false;
|
|
174
|
+
};
|
|
175
|
+
const cancel = () => {
|
|
176
|
+
hide();
|
|
149
177
|
};
|
|
150
|
-
|
|
151
178
|
/** 用于关闭 Drawer, 该方法会调用传入的 before-close 方法 */
|
|
152
179
|
const handleClose = () => {
|
|
153
180
|
drawer.value?.handleClose();
|
|
154
181
|
};
|
|
155
182
|
|
|
183
|
+
const save = async () => {
|
|
184
|
+
try {
|
|
185
|
+
const changeRecords = [...(form.value?.changeRecords || [])];
|
|
186
|
+
const values = await form.value?.submitForm();
|
|
187
|
+
emit('submit', values, { changeRecords });
|
|
188
|
+
} catch (e) {
|
|
189
|
+
emit('error', e);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
156
193
|
defineExpose({
|
|
157
194
|
form,
|
|
158
195
|
saveFetch,
|
|
159
196
|
bodyHeight,
|
|
160
|
-
|
|
197
|
+
close,
|
|
161
198
|
show,
|
|
162
199
|
hide,
|
|
200
|
+
save,
|
|
201
|
+
cancel,
|
|
163
202
|
handleClose,
|
|
164
203
|
});
|
|
165
204
|
</script>
|