@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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.11",
2
+ "version": "1.8.0-beta.12",
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.38",
53
+ "vue": "^3.5.40",
54
54
  "typescript": "^6.0.3",
55
- "@tmagic/form-schema": "1.8.0-beta.11",
56
- "@tmagic/design": "1.8.0-beta.11",
57
- "@tmagic/utils": "1.8.0-beta.11"
55
+ "@tmagic/design": "1.8.0-beta.12",
56
+ "@tmagic/utils": "1.8.0-beta.12",
57
+ "@tmagic/form-schema": "1.8.0-beta.12"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "typescript": {
package/src/Form.vue CHANGED
@@ -32,7 +32,7 @@
32
32
  </template>
33
33
 
34
34
  <script setup lang="ts">
35
- import { provide, reactive, ref, shallowRef, toRaw, useTemplateRef, watch, watchEffect } from 'vue';
35
+ import { computed, nextTick, provide, reactive, ref, shallowRef, toRaw, useTemplateRef, watch, watchEffect } from 'vue';
36
36
  import { cloneDeep, isEqual } from 'lodash-es';
37
37
 
38
38
  import { TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
@@ -50,7 +50,7 @@ import type {
50
50
  FormValue,
51
51
  ValidateError,
52
52
  } from './schema';
53
- import { FORM_DIFF_CONFIG_KEY } from './schema';
53
+ import { FORM_DIFF_CONFIG_KEY, FORM_TYPE_MATCH_VALID_KEY } from './schema';
54
54
 
55
55
  defineOptions({
56
56
  name: 'MForm',
@@ -70,6 +70,8 @@ const props = withDefaults(
70
70
  isCompare?: boolean;
71
71
  parentValues?: Record<string, any>;
72
72
  labelWidth?: string;
73
+ /** 是否开启类型匹配校验 */
74
+ typeMatchValid?: boolean;
73
75
  disabled?: boolean;
74
76
  height?: string;
75
77
  stepActive?: string | number;
@@ -134,6 +136,11 @@ const props = withDefaults(
134
136
 
135
137
  const emit = defineEmits(['change', 'error', 'field-input', 'field-change', 'update:stepActive']);
136
138
 
139
+ provide(
140
+ FORM_TYPE_MATCH_VALID_KEY,
141
+ computed(() => props.typeMatchValid),
142
+ );
143
+
137
144
  const tMagicFormRef = useTemplateRef('tMagicForm');
138
145
  const initialized = ref(false);
139
146
  const values = ref<FormValue>({});
@@ -280,6 +287,10 @@ watch(
280
287
  values.value = value;
281
288
  // 非对比模式,初始化完成
282
289
  initialized.value = !props.isCompare;
290
+
291
+ nextTick(() => {
292
+ tMagicFormRef.value?.validate();
293
+ });
283
294
  });
284
295
 
285
296
  if (props.isCompare) {
@@ -360,6 +371,27 @@ const getTextByName = (name: string, config: FormConfig = props.config): string
360
371
  return findInConfig(config, nameParts);
361
372
  };
362
373
 
374
+ /**
375
+ * 将校验返回的 invalidFields 汇总为可读的错误文案(多条以 `<br>` 拼接)。
376
+ *
377
+ * 抽离为独立方法,供 `submitForm`(提交校验)与 `validate`(返回错误文案的校验)复用,
378
+ * 保证两种校验入口产出的错误文案格式完全一致。
379
+ */
380
+ const formatValidateError = (invalidFields: Record<string, any>): string => {
381
+ const error: string[] = [];
382
+
383
+ Object.entries(invalidFields).forEach(([prop, validateError]) => {
384
+ (validateError as ValidateError[]).forEach(({ field, message }) => {
385
+ const name = field || prop;
386
+ const text = (props.useFieldTextInError ? getTextByName(name, props.config) : undefined) || name;
387
+
388
+ error.push(`${text} -> ${message}`);
389
+ });
390
+ });
391
+
392
+ return error.join('<br>');
393
+ };
394
+
363
395
  defineExpose({
364
396
  values,
365
397
  lastValuesProcessed,
@@ -387,18 +419,36 @@ defineExpose({
387
419
  } catch (invalidFields: any) {
388
420
  emit('error', invalidFields);
389
421
 
390
- const error: string[] = [];
391
-
392
- Object.entries(invalidFields).forEach(([prop, ValidateError]) => {
393
- (ValidateError as ValidateError[]).forEach(({ field, message }) => {
394
- const name = field || prop;
395
- const text = (props.useFieldTextInError ? getTextByName(name, props.config) : undefined) || name;
396
-
397
- error.push(`${text} -> ${message}`);
398
- });
399
- });
422
+ throw new Error(formatValidateError(invalidFields));
423
+ }
424
+ },
400
425
 
401
- throw new Error(error.join('<br>'));
426
+ /**
427
+ * 校验:对表单当前值执行校验,返回汇总后的错误文案。
428
+ *
429
+ * 与 `submitForm` 的区别:
430
+ * - 校验失败时不抛异常、不触发 `error` 事件,而是以返回值形式给出错误文案;
431
+ * - 不重置 `changeRecords`,不改变提交语义,仅用于「探测」当前配置是否合法。
432
+ *
433
+ * 注意:本方法只改变「校验结果的返回方式」,并不负责「不污染页面表单状态」——
434
+ * 若需对一份独立的「配置 + 值」做完全不影响页面上已渲染表单的校验,请使用 `validateForm`
435
+ * (内部会新建一个隐藏的 MForm 实例,通过 `initValues` 传入待校验值,用完即卸载)。
436
+ *
437
+ * 典型用途:作为 `validateForm` 内部复用的校验实现;也可在已渲染的表单实例上主动调用,
438
+ * 根据返回的错误文案自行决定后续处理(如记录节点错误状态)。
439
+ *
440
+ * @returns 校验通过返回空字符串 `''`,否则返回以 `<br>` 拼接的错误文案。
441
+ */
442
+ validate: async (): Promise<string> => {
443
+ try {
444
+ const result = await tMagicFormRef.value?.validate();
445
+ // tdesign 通过返回值返回校验结果,element-plus 通过 throw error
446
+ if (result !== true) {
447
+ throw result;
448
+ }
449
+ return '';
450
+ } catch (invalidFields: any) {
451
+ return formatValidateError(invalidFields);
402
452
  }
403
453
  },
404
454
 
package/src/FormBox.vue CHANGED
@@ -15,6 +15,7 @@
15
15
  :prevent-submit-default="preventSubmitDefault"
16
16
  :use-field-text-in-error="useFieldTextInError"
17
17
  :extend-state="extendState"
18
+ :type-match-valid="typeMatchValid"
18
19
  @change="changeHandler"
19
20
  ></Form>
20
21
  <slot></slot>
@@ -56,6 +57,8 @@ const props = withDefaults(
56
57
  width?: number;
57
58
  height?: number;
58
59
  labelWidth?: string;
60
+ /** 是否开启类型匹配校验 */
61
+ typeMatchValid?: boolean;
59
62
  disabled?: boolean;
60
63
  size?: 'small' | 'default' | 'large';
61
64
  confirmText?: string;
@@ -32,6 +32,7 @@
32
32
  :inline="inline"
33
33
  :prevent-submit-default="preventSubmitDefault"
34
34
  :use-field-text-in-error="useFieldTextInError"
35
+ :type-match-valid="typeMatchValid"
35
36
  :extend-state="extendState"
36
37
  @change="changeHandler"
37
38
  ></Form>
@@ -83,6 +84,8 @@ const props = withDefaults(
83
84
  parentValues?: Object;
84
85
  width?: string | number;
85
86
  labelWidth?: string;
87
+ /** 是否开启类型匹配校验 */
88
+ typeMatchValid?: boolean;
86
89
  fullscreen?: boolean;
87
90
  disabled?: boolean;
88
91
  title?: string;
@@ -29,6 +29,7 @@
29
29
  :inline="inline"
30
30
  :prevent-submit-default="preventSubmitDefault"
31
31
  :use-field-text-in-error="useFieldTextInError"
32
+ :type-match-valid="typeMatchValid"
32
33
  :extend-state="extendState"
33
34
  @change="changeHandler"
34
35
  ></Form>
@@ -74,6 +75,8 @@ withDefaults(
74
75
  parentValues?: Object;
75
76
  width?: string | number;
76
77
  labelWidth?: string;
78
+ /** 是否开启类型匹配校验 */
79
+ typeMatchValid?: boolean;
77
80
  disabled?: boolean;
78
81
  closeOnPressEscape?: boolean;
79
82
  title?: string;
@@ -249,7 +249,7 @@ import type {
249
249
  FormValue,
250
250
  ToolTipConfigType,
251
251
  } from '../schema';
252
- import { FORM_DIFF_CONFIG_KEY } from '../schema';
252
+ import { FORM_DIFF_CONFIG_KEY, FORM_TYPE_MATCH_VALID_KEY } from '../schema';
253
253
  import { getField } from '../utils/config';
254
254
  import { createObjectProp, display as displayFunction, filterFunction, getRules } from '../utils/form';
255
255
 
@@ -296,6 +296,8 @@ const mForm = inject<FormState | undefined>('mForm');
296
296
  // 对比相关配置由 MForm 通过 provide 下发,这里直接 inject,无需逐层透传 prop。
297
297
  const diffConfig = inject<FormDiffConfig>(FORM_DIFF_CONFIG_KEY, {});
298
298
 
299
+ const typeMatchValid = inject(FORM_TYPE_MATCH_VALID_KEY);
300
+
299
301
  const expand = ref(false);
300
302
 
301
303
  const name = computed(() => props.config.name || '');
@@ -411,7 +413,7 @@ const tooltip = computed(() => {
411
413
  };
412
414
  });
413
415
 
414
- const rule = computed(() => getRules(mForm, props.config.rules, props));
416
+ const rule = computed(() => getRules(mForm, props.config.rules, props, typeMatchValid));
415
417
 
416
418
  const display = computed((): boolean => {
417
419
  const value = displayFunction(mForm, props.config.display, props);
@@ -36,6 +36,9 @@ const itemComponent = computed(() => (props.config.childType === 'button' ? TMag
36
36
  const emit = defineEmits(['change']);
37
37
 
38
38
  const clickHandler = (item: string | number | boolean) => {
39
+ if (props.disabled) {
40
+ return;
41
+ }
39
42
  // 再次点击取消选中
40
43
  emit('change', props.model[props.name] === item ? '' : item);
41
44
  };
package/src/index.ts CHANGED
@@ -63,6 +63,17 @@ export {
63
63
  registerField as registerFormField,
64
64
  } from './utils/config';
65
65
 
66
+ export {
67
+ clearTypeMatchRules,
68
+ deleteTypeMatchRule,
69
+ getTypeMatchRule,
70
+ registerTypeMatchRule,
71
+ registerTypeMatchRules,
72
+ validateTypeMatch,
73
+ } from './utils/typeMatch';
74
+
75
+ export type { TypeMatchValidateContext, TypeMatchValidator } from './utils/typeMatch';
76
+
66
77
  export type { FormInstallOptions } from './plugin';
67
78
 
68
79
  export const createForm = <T extends [] = []>(config: FormConfig | T) => config;
package/src/plugin.ts CHANGED
@@ -47,14 +47,19 @@ import Textarea from './fields/Textarea.vue';
47
47
  import Time from './fields/Time.vue';
48
48
  import Timerange from './fields/Timerange.vue';
49
49
  import { setConfig } from './utils/config';
50
+ import { registerTypeMatchRules, type TypeMatchValidator } from './utils/typeMatch';
50
51
  import Form from './Form.vue';
51
52
  import FormDialog from './FormDialog.vue';
52
53
 
53
54
  import './theme/index.scss';
54
55
 
56
+ // #region FormInstallOptions
55
57
  export interface FormInstallOptions {
58
+ /** 自定义字段 type 的 typeMatch 校验规则,可覆盖内置规则或扩展业务字段 */
59
+ typeMatchRules?: Record<string, TypeMatchValidator>;
56
60
  [key: string]: any;
57
61
  }
62
+ // #endregion FormInstallOptions
58
63
 
59
64
  const defaultInstallOpt: FormInstallOptions = {};
60
65
 
@@ -65,6 +70,10 @@ export default {
65
70
  app.config.globalProperties.$MAGIC_FORM = option;
66
71
  setConfig(option);
67
72
 
73
+ if (option.typeMatchRules) {
74
+ registerTypeMatchRules(option.typeMatchRules);
75
+ }
76
+
68
77
  app.component('m-form', Form);
69
78
  app.component('m-form-dialog', FormDialog);
70
79
  app.component('m-form-container', Container);
package/src/schema.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { InjectionKey } from 'vue';
1
+ import type { ComputedRef, InjectionKey } from 'vue';
2
2
 
3
3
  import type { FormItemConfig } from '@tmagic/form-schema';
4
4
 
@@ -27,6 +27,7 @@ export interface FormDiffConfig {
27
27
  }
28
28
 
29
29
  export const FORM_DIFF_CONFIG_KEY: InjectionKey<FormDiffConfig> = Symbol('mFormDiffConfig');
30
+ export const FORM_TYPE_MATCH_VALID_KEY: InjectionKey<ComputedRef<boolean>> = Symbol('mFormTypeMatchValid');
30
31
 
31
32
  export interface ValidateError {
32
33
  message: string;