@qin-ui/element-plus-pro 1.3.4 → 1.4.0

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.
Files changed (93) hide show
  1. package/AI-CONTEXT.md +26 -2
  2. package/README.md +41 -30
  3. package/api.json +69 -38
  4. package/es/components/component-provider/components/index.vue.d.ts +10 -3
  5. package/es/components/component-provider/components/index.vue.d.ts.map +1 -1
  6. package/es/components/component-provider/constants/index.d.ts +22 -4
  7. package/es/components/component-provider/constants/index.d.ts.map +1 -1
  8. package/es/components/component-provider/types/index.d.ts +17 -3
  9. package/es/components/component-provider/types/index.d.ts.map +1 -1
  10. package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -1
  11. package/es/components/form/components/BaseField/utils/index.d.ts +15 -0
  12. package/es/components/form/components/BaseField/utils/index.d.ts.map +1 -1
  13. package/es/components/form/components/BaseFormItem/index.vue.d.ts +3 -1
  14. package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -1
  15. package/es/components/form/components/ContainerFragment/index.vue.d.ts.map +1 -1
  16. package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -1
  17. package/es/components/form/components/SlotComponent/index.vue.d.ts.map +1 -1
  18. package/es/components/form/components/index.d.ts +4 -0
  19. package/es/components/form/components/index.d.ts.map +1 -1
  20. package/es/components/form/constants/index.d.ts +8 -2
  21. package/es/components/form/constants/index.d.ts.map +1 -1
  22. package/es/components/form/hooks/useFields.d.ts.map +1 -1
  23. package/es/components/form/hooks/useForm.d.ts.map +1 -1
  24. package/es/components/form/hooks/useGroupFieldAttrs.d.ts +9 -0
  25. package/es/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -1
  26. package/es/components/form/index.d.ts +2 -2
  27. package/es/components/form/index.d.ts.map +1 -1
  28. package/es/components/form/types/index.d.ts +65 -50
  29. package/es/components/form/types/index.d.ts.map +1 -1
  30. package/es/components/form/utils/fieldKey.d.ts +13 -0
  31. package/es/components/form/utils/fieldKey.d.ts.map +1 -0
  32. package/es/components/table/components/ColumnControl.vue.d.ts +1 -1
  33. package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -1
  34. package/es/components/table/components/ProTable.vue.d.ts +15 -1
  35. package/es/components/table/components/ProTable.vue.d.ts.map +1 -1
  36. package/es/components/table/components/SearchForm.vue.d.ts +14 -2
  37. package/es/components/table/components/SearchForm.vue.d.ts.map +1 -1
  38. package/es/components/table/components/SizeControl.vue.d.ts +7 -2
  39. package/es/components/table/components/SizeControl.vue.d.ts.map +1 -1
  40. package/es/components/table/components/TableColumnRenderer.vue.d.ts +14 -14
  41. package/es/components/table/components/TableColumnRenderer.vue.d.ts.map +1 -1
  42. package/es/components/table/hooks/index.d.ts +2 -0
  43. package/es/components/table/hooks/index.d.ts.map +1 -0
  44. package/es/components/table/{useTable.d.ts → hooks/useTable.d.ts} +3 -3
  45. package/es/components/table/hooks/useTable.d.ts.map +1 -0
  46. package/es/components/table/index.d.ts +6 -2
  47. package/es/components/table/index.d.ts.map +1 -1
  48. package/es/components/table/types/index.d.ts +16 -12
  49. package/es/components/table/types/index.d.ts.map +1 -1
  50. package/es/element-plus-pro.css +14 -14
  51. package/es/index.d.ts +9 -6
  52. package/es/index.d.ts.map +1 -1
  53. package/es/index.js +577 -432
  54. package/es/index.js.map +1 -1
  55. package/es/locale/index.d.ts +2 -1
  56. package/es/locale/index.d.ts.map +1 -1
  57. package/es/shared/core/index.d.ts +1 -1
  58. package/es/shared/core/index.d.ts.map +1 -1
  59. package/es/shared/ui/index.d.ts +20 -2
  60. package/es/shared/ui/index.d.ts.map +1 -1
  61. package/package.json +8 -5
  62. package/src/components/component-provider/components/index.vue +48 -21
  63. package/src/components/component-provider/constants/index.ts +34 -6
  64. package/src/components/component-provider/types/index.ts +21 -7
  65. package/src/components/form/components/BaseField/index.vue +32 -15
  66. package/src/components/form/components/BaseField/utils/index.ts +16 -1
  67. package/src/components/form/components/BaseFormItem/index.vue +61 -15
  68. package/src/components/form/components/ContainerFragment/index.vue +3 -1
  69. package/src/components/form/components/ProForm/index.vue +54 -27
  70. package/src/components/form/components/SlotComponent/index.vue +4 -2
  71. package/src/components/form/components/index.ts +8 -1
  72. package/src/components/form/constants/index.ts +9 -2
  73. package/src/components/form/hooks/useFields.ts +5 -2
  74. package/src/components/form/hooks/useForm.ts +8 -1
  75. package/src/components/form/hooks/useGroupFieldAttrs.ts +29 -30
  76. package/src/components/form/index.ts +1 -3
  77. package/src/components/form/types/index.ts +71 -52
  78. package/src/components/form/utils/fieldKey.ts +27 -0
  79. package/src/components/table/components/ColumnControl.vue +20 -8
  80. package/src/components/table/components/ProTable.vue +194 -96
  81. package/src/components/table/components/SearchForm.vue +62 -23
  82. package/src/components/table/components/SizeControl.vue +23 -1
  83. package/src/components/table/components/TableColumnRenderer.vue +93 -61
  84. package/src/components/table/hooks/index.ts +1 -0
  85. package/src/components/table/{useTable.ts → hooks/useTable.ts} +6 -4
  86. package/src/components/table/index.ts +11 -2
  87. package/src/components/table/types/index.ts +17 -13
  88. package/src/index.ts +13 -7
  89. package/src/locale/index.ts +4 -2
  90. package/src/shared/core/index.ts +11 -0
  91. package/src/shared/ui/index.ts +29 -5
  92. package/tsconfig.json +22 -0
  93. package/es/components/table/useTable.d.ts.map +0 -1
@@ -1,7 +1,8 @@
1
1
  <script lang="ts" setup>
2
2
  /**
3
+ * 表单字段渲染引擎
4
+ *
3
5
  * @component BaseFormItem
4
- * @description 表单字段渲染引擎
5
6
  *
6
7
  * 负责将字段配置数组渲染为实际的表单项,支持:
7
8
  * - 字段分组(嵌套 fields 递归渲染,嵌套字段 path 须为完整点分路径)
@@ -17,21 +18,29 @@
17
18
  * 3. 递归渲染 BaseFormItem(嵌套表单)或 BaseField(输入组件)
18
19
  * 4. 通过 ref 回调收集 FormItem 和 Component 实例,注入到 field 对象上
19
20
  *
20
- * @param {Fields<D>} [fields] - 字段配置数组
21
+ * @param {Fields} [fields] - 字段配置数组
21
22
  * @param {boolean | GridProps} [grid] - 是否启用网格布局
23
+ * @param {boolean} [disabled] - 是否禁用所有子字段
22
24
  *
23
25
  * @internal
24
26
  */
25
- import { getObject, normalizePathKey } from '../../../../shared/core';
27
+ import {
28
+ getObject,
29
+ isObjectItem,
30
+ normalizePathKey,
31
+ } from '../../../../shared/core';
32
+ import type { GridProps } from '../../../../shared/ui';
26
33
  import {
27
34
  FormItem,
28
35
  Grid as ElGrid,
29
36
  GridItem as ElGridItem,
30
- GridProps,
37
+ useProviderDisabled,
31
38
  } from '../../../../shared/ui';
32
39
  import { computed, inject, toRaw, toValue } from 'vue';
33
40
  import type { Fields, Grid } from '../../types';
34
- import { BaseField, SlotComponent, ContainerFragment } from '..';
41
+ import BaseField from '../BaseField/index.vue';
42
+ import SlotComponent from '../SlotComponent/index.vue';
43
+ import ContainerFragment from '../ContainerFragment/index.vue';
35
44
  import { useGroupFieldAttrs } from '../../hooks/useGroupFieldAttrs';
36
45
  import { INJECT_CONFIG } from '../../../component-provider/constants';
37
46
 
@@ -40,15 +49,22 @@ defineOptions({ name: 'BaseFormItem', inheritAttrs: false });
40
49
  type Props = {
41
50
  grid?: Grid;
42
51
  fields?: Fields;
52
+ disabled?: boolean;
43
53
  };
44
54
  const props = withDefaults(defineProps<Props>(), {
45
55
  grid: undefined,
46
56
  fields: () => [],
57
+ disabled: undefined,
47
58
  });
48
59
 
60
+ // 分组字段的禁用状态向下注入,供 BaseField 经禁用上下文消费
61
+ useProviderDisabled(computed(() => props.disabled));
62
+
49
63
  const validFields = computed(() =>
50
- // hidden 支持 Ref/ComputedRef/getter 等响应式形态(统一使用 toValue 取值)
51
- (props.fields ?? []).filter(field => !toValue(field.hidden))
64
+ // hidden 支持 Ref / ComputedRef 响应式形态(统一使用 toValue 取值)
65
+ (props.fields ?? []).filter(
66
+ field => isObjectItem(field) && !toValue((field as any).hidden)
67
+ )
52
68
  );
53
69
 
54
70
  const config = INJECT_CONFIG['pro-form'];
@@ -68,18 +84,46 @@ const computedGridProps = computed<GridProps>(() => {
68
84
 
69
85
  /**
70
86
  * 以字段原始对象为 key 登记 FormItem / 控件实例,供 getFormItemRef / getComponentRef 读取;
71
- * WeakMap 不参与响应式追踪,条目随字段对象回收自动释放。
87
+ * 字段对象被回收后对应条目不再可读。
72
88
  */
73
89
  const formItemInstanceMap = new WeakMap<object, any>();
74
90
  const componentInstanceMap = new WeakMap<object, any>();
75
91
 
92
+ /** 已告警过的字段对象(不可写场景去重) */
93
+ const warnedFrozenFields = new WeakSet<object>();
94
+ const defineFieldAccessors = (
95
+ raw: object,
96
+ accessors: Record<string, () => any>
97
+ ) => {
98
+ try {
99
+ for (const [key, getter] of Object.entries(accessors)) {
100
+ // 访问器仅用于字段对象读取,不参与属性遍历与字段配置透传
101
+ Object.defineProperty(raw, key, {
102
+ value: getter,
103
+ enumerable: false,
104
+ configurable: true,
105
+ writable: true,
106
+ });
107
+ }
108
+ } catch {
109
+ if (!warnedFrozenFields.has(raw)) {
110
+ warnedFrozenFields.add(raw);
111
+ console.warn(
112
+ '[pro-components] 字段配置对象不可写(如被 Object.freeze),getFormItemRef / getComponentRef 等实例访问器挂载失败。'
113
+ );
114
+ }
115
+ }
116
+ };
117
+
76
118
  const setFormItemRef = (field: any, el: any, formItemProps?: any) => {
77
119
  if (!field || typeof field !== 'object') return;
78
120
  const raw = toRaw(field);
79
121
  if (el) {
80
122
  formItemInstanceMap.set(raw, el);
81
- raw.getFormItemRef = () => formItemInstanceMap.get(raw);
82
- raw.getFormItemComputedProps = () => formItemProps;
123
+ defineFieldAccessors(raw, {
124
+ getFormItemRef: () => formItemInstanceMap.get(raw),
125
+ getFormItemComputedProps: () => formItemProps,
126
+ });
83
127
  } else {
84
128
  formItemInstanceMap.delete(raw);
85
129
  delete raw.getFormItemRef;
@@ -92,10 +136,11 @@ const setComponentRef = (field: any, el: any) => {
92
136
  const raw = toRaw(field);
93
137
  if (el) {
94
138
  componentInstanceMap.set(raw, el);
95
- raw.getComponentRef = () =>
96
- componentInstanceMap.get(raw)?.getComponentRef();
97
- raw.getComponentComputedProps = () =>
98
- componentInstanceMap.get(raw)?.getComponentComputedProps();
139
+ defineFieldAccessors(raw, {
140
+ getComponentRef: () => componentInstanceMap.get(raw)?.getComponentRef(),
141
+ getComponentComputedProps: () =>
142
+ componentInstanceMap.get(raw)?.getComponentComputedProps(),
143
+ });
99
144
  } else {
100
145
  componentInstanceMap.delete(raw);
101
146
  delete raw.getComponentRef;
@@ -112,7 +157,7 @@ const groupedFields = useGroupFieldAttrs(validFields);
112
157
  v-bind="computedGridProps"
113
158
  style="width: 100%"
114
159
  >
115
- <template v-for="(item, index) of groupedFields" :key="item.path || index">
160
+ <template v-for="item of groupedFields" :key="item.renderKey">
116
161
  <ContainerFragment
117
162
  :component="enableGrid ? ElGridItem : undefined"
118
163
  v-bind="item.gridItemProps"
@@ -139,6 +184,7 @@ const groupedFields = useGroupFieldAttrs(validFields);
139
184
  <BaseFormItem
140
185
  :grid="(item.rawField as any).grid ?? props.grid"
141
186
  :fields="item.rawField.fields"
187
+ :disabled="toValue(item.rawField.disabled)"
142
188
  />
143
189
  </template>
144
190
  <template v-else>
@@ -1,7 +1,9 @@
1
1
  <script lang="ts" setup>
2
2
  /**
3
+ * 容器分片渲染组件
4
+ *
3
5
  * @component ContainerFragment
4
- * @description 容器分片渲染组件。用于动态渲染表单行或表单项的外部包裹容器(例如 Grid 行、Col 列等布局容器)。
6
+ * @description 用于动态渲染表单行或表单项的外部包裹容器(例如 Grid 行、Col 列等布局容器)
5
7
  *
6
8
  * @public
7
9
  */
@@ -1,7 +1,8 @@
1
1
  <script lang="ts" setup generic="F extends Form<any> = Form">
2
2
  /**
3
+ * 配置驱动表单组件
4
+ *
3
5
  * @component ProForm
4
- * @description @qin-ui/element-plus-pro 配置驱动表单组件
5
6
  *
6
7
  * 架构设计说明 (Architecture Overview)
7
8
  *
@@ -12,39 +13,59 @@
12
13
  * 2. 上下文透传:通过 `provide` 将核心状态下发给子组件。
13
14
  * 3. 递归渲染:网格布局和具体字段的解析交由 `<BaseFormItem>` 递归完成。
14
15
  *
16
+ * @public
17
+ *
15
18
  * 通过配置驱动的方式快速构建表单,支持:
16
- * - 字段联动、嵌套字段、自定义组件、网格布局
19
+ * - 字段联动(通过 setField、watch 等实现)
20
+ * - 嵌套字段(通过 fields 递归配置)
21
+ * - 自定义组件(通过 component 属性和 custom 类型)
22
+ * - 网格布局(通过 grid 属性)
23
+ * - 动态插槽(以字段 path 命名的插槽)
24
+ * - 全局配置注入(通过 ProComponentProvider)
17
25
  *
18
26
  * @template F - 表单实例类型
27
+ *
19
28
  * @param {F} [form] - useForm 返回的表单实例
20
- * @param {boolean | GridProps} [grid=false] - 是否启用网格布局
29
+ * @param {boolean | GridProps} [grid] - 是否启用网格布局;未提供时回退 Provider 注入的 `grid` 配置
30
+ * @param {...FormProps} ...attrs - Element Plus Form 组件的其他属性
21
31
  *
22
- * @slot default - 默认插槽
23
- * @slot [path] - 以字段 path 命名的动态插槽
24
-
25
- * @public
32
+ * @slot default - 默认插槽,可用于添加额外内容
33
+ * @slot [path] - 以字段 path 命名的动态插槽,可用于自定义字段渲染
34
+ *
35
+ * @example
36
+ * ```vue
37
+ * <template>
38
+ * <ProForm :form="form" @submit="handleSubmit">
39
+ * <template #name="{ value, disabled }">
40
+ * <el-input :model-value="value" :disabled="disabled" />
41
+ * </template>
42
+ * </ProForm>
43
+ * </template>
44
+ * ```
26
45
  */
46
+ import type { FormProps as ElFormProps } from '../../../../shared/ui';
47
+ import { Form as ElForm } from '../../../../shared/ui';
27
48
  import {
28
- Form as ElForm,
29
- FormProps as ElFormProps,
30
- } from '../../../../shared/ui';
31
- import {
49
+ computed,
32
50
  inject,
33
51
  mergeProps,
52
+ onUpdated,
34
53
  provide,
54
+ shallowRef,
35
55
  toValue,
56
+ triggerRef,
36
57
  type Slot,
37
- computed,
38
- useAttrs,
39
58
  } from 'vue';
40
59
  import { INJECT_CONFIG } from '../../../component-provider/constants';
41
- import { BaseFormItem } from '..';
60
+ import BaseFormItem from '../BaseFormItem/index.vue';
42
61
  import { InjectionFormKey, InjectionFormSlotsKey } from '../../constants';
43
62
  import type { Grid, VModelProps, PathProps } from '../../types';
63
+ import type { Path } from '../../../../shared/core';
44
64
  import {
45
- Path,
46
65
  camelizeProperties,
47
66
  createFormProxy,
67
+ omitBy,
68
+ useReactiveAttrs,
48
69
  } from '../../../../shared/core';
49
70
  import { useForm, type Form } from '../../hooks/useForm';
50
71
 
@@ -57,19 +78,18 @@ type Props = {
57
78
  form?: F;
58
79
  } & /* @vue-ignore */ FormProps;
59
80
  const props = withDefaults(defineProps<Props>(), {
60
- grid: false,
81
+ grid: undefined,
61
82
  form: undefined,
62
83
  });
63
84
 
64
85
  /**
65
86
  * 依赖注入中心:
66
- * 若外部未传 form 实例,则自动初始化内部表单实例;
87
+ * 未传 form 实例(含运行期置空)时回退内部表单实例;
67
88
  * 将 Form 实例注入到 Vue Context 中,供深层子组件跨层级直接访问数据和方法。
68
89
  */
69
- const fallbackForm = props.form ? undefined : useForm();
70
- const currentForm = computed(
71
- () => props.form ?? (fallbackForm as unknown as F)
72
- );
90
+ // 兜底表单实例:setup 同步期创建,未传 :form(含运行期置空)时经 currentForm 生效
91
+ const fallbackForm = useForm() as unknown as F;
92
+ const currentForm = computed(() => props.form ?? fallbackForm);
73
93
 
74
94
  /**
75
95
  * 表单实例代理:操作全部转发至 currentForm 当前实例;
@@ -93,6 +113,7 @@ const setFormRef = (el: any) => currentForm.value?.setFormRef?.(el);
93
113
  let formInstanceCounter = 0;
94
114
  const formInstanceKeyMap = new WeakMap<object, number>();
95
115
  const getFormKey = (f: object) => {
116
+ if (f == null) return 0;
96
117
  if (!formInstanceKeyMap.has(f)) {
97
118
  formInstanceKeyMap.set(f, ++formInstanceCounter);
98
119
  }
@@ -102,20 +123,22 @@ const formKey = computed(() => getFormKey(currentForm.value as any));
102
123
 
103
124
  const config = INJECT_CONFIG['pro-form'];
104
125
  const injectFormConfig = inject(config.injectionKey, config.default);
105
- const attrs = useAttrs();
126
+ const attrs = useReactiveAttrs();
106
127
 
107
128
  /**
108
129
  * 从 ProComponentProvider 获取全局配置,响应式派生以支持动态配置变更
109
130
  */
110
131
  const injectAttrs = computed(() => {
111
132
  const raw = toValue(injectFormConfig);
112
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
113
133
  const { grid: _injectGrid, ...rest } = raw || {};
114
134
  return rest;
115
135
  });
116
136
 
117
137
  const formAttrs = computed(() => {
118
- return mergeProps(injectAttrs.value, camelizeProperties(attrs));
138
+ return mergeProps(
139
+ injectAttrs.value,
140
+ omitBy(camelizeProperties(attrs), value => value === undefined)
141
+ );
119
142
  });
120
143
 
121
144
  type ExtractPath<T> = T extends Form<infer D> ? Path<D> : string;
@@ -125,11 +148,15 @@ type FieldSlots = Record<ExtractPath<F>, Slot<FieldSlotProps>>;
125
148
 
126
149
  const slots = defineSlots<Partial<FieldSlots & { default: Slot }>>();
127
150
 
128
- /**
151
+ /*
129
152
  * 动态插槽映射中心 (Slot Teleporting):
130
- * 在 setup 同步期直接提供响应式 slots 上下文容器,供深层 BaseField 按需动态获取插槽
153
+ * 向深层 BaseField 提供 ProForm 的具名插槽,父级运行期增删插槽后下游可感知
131
154
  */
132
- provide(InjectionFormSlotsKey, slots);
155
+ const formSlotsRef = shallowRef<Record<string, any>>(slots);
156
+ onUpdated(() => {
157
+ triggerRef(formSlotsRef);
158
+ });
159
+ provide(InjectionFormSlotsKey, formSlotsRef);
133
160
  </script>
134
161
 
135
162
  <template>
@@ -1,11 +1,13 @@
1
1
  <script lang="ts" setup>
2
2
  /**
3
+ * 插槽渲染辅助组件
4
+ *
3
5
  * @component SlotComponent
4
- * @description 插槽渲染辅助组件。用于在表单或表格的自定义插槽中动态渲染外部传入的 VNode、渲染函数或静态字符串。
6
+ * @description 用于在表单或表格的自定义插槽中动态渲染外部传入的 VNode、渲染函数、组件定义对象或静态字符串
5
7
  *
6
8
  * @public
7
9
  */
8
- import { SlotComponentType } from '../../types';
10
+ import type { SlotComponentType } from '../../types';
9
11
 
10
12
  defineOptions({ name: 'SlotComponent', inheritAttrs: false });
11
13
 
@@ -1,4 +1,7 @@
1
- import { ComponentProps, ComponentExposed } from 'vue-component-type-helpers';
1
+ import type {
2
+ ComponentProps,
3
+ ComponentExposed,
4
+ } from 'vue-component-type-helpers';
2
5
  import ProForm from './ProForm/index.vue';
3
6
  import BaseFormItem from './BaseFormItem/index.vue';
4
7
 
@@ -8,8 +11,12 @@ export { default as SlotComponent } from './SlotComponent/index.vue';
8
11
  export { ProForm, BaseFormItem };
9
12
 
10
13
  // instance
14
+ /** ProForm 组件实例 */
11
15
  export type ProFormInstance = ComponentExposed<typeof ProForm>;
16
+ /** ProForm 组件属性 */
12
17
  export type ProFormProps = ComponentProps<typeof ProForm>;
13
18
 
19
+ /** BaseFormItem 组件实例(字段级 FormItem) */
14
20
  export type ProFormItemInstance = ComponentExposed<typeof BaseFormItem>;
21
+ /** BaseFormItem 组件属性(字段级 FormItem) */
15
22
  export type ProFormItemProps = ComponentProps<typeof BaseFormItem>;
@@ -57,7 +57,7 @@ export type BaseComponentMap = {
57
57
  * }
58
58
  * }
59
59
  * ```
60
-
60
+ *
61
61
  * @public
62
62
  */
63
63
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -66,7 +66,7 @@ export interface ComponentMap {}
66
66
  /**
67
67
  * 组件名称联合类型
68
68
  * @description 所有支持的组件名称
69
-
69
+ *
70
70
  * @public
71
71
  */
72
72
  export type ComponentName =
@@ -106,5 +106,12 @@ export const componentMap: BaseComponentMap = {
106
106
  'transfer': Transfer,
107
107
  }
108
108
 
109
+ /**
110
+ * 字段标识键序:el-form-item 的 prop 优先,path 兜底
111
+ *
112
+ * 与字段读写绑定、表单域标识、字段查找 API 共用同一解析入口
113
+ */
114
+ export const ELEMENT_FIELD_IDENTITY_KEYS = ['prop', 'path'] as const;
115
+
109
116
  // inject keys
110
117
  export { InjectionFormKey, InjectionFormSlotsKey } from '../../../shared/core';
@@ -1,10 +1,11 @@
1
- import { FormItemInstance } from '../../../shared/ui';
1
+ import type { FormItemInstance } from '../../../shared/ui';
2
2
  import {
3
3
  useFields as coreUseFields,
4
4
  type UseFieldsReturn,
5
5
  } from '../../../shared/core';
6
6
  import type { Data } from '../../../shared/core';
7
7
  import type { Field, Fields } from '../types';
8
+ import { ELEMENT_FIELD_IDENTITY_KEYS } from '../constants';
8
9
  import type { ComponentName } from '../constants';
9
10
 
10
11
  /**
@@ -58,7 +59,9 @@ export function useFields<D extends Data = Data>(
58
59
  initFields?: Fields<D>
59
60
  ): UseFieldsReturn<D, Field<ComponentName, D>, FormItemInstance>;
60
61
  export function useFields(initFields?: any) {
61
- return coreUseFields(initFields);
62
+ return coreUseFields(initFields, {
63
+ fieldIdentityKeys: ELEMENT_FIELD_IDENTITY_KEYS,
64
+ });
62
65
  }
63
66
 
64
67
  /** useFields 返回值类型
@@ -4,6 +4,7 @@ import {
4
4
  } from '../../../shared/core';
5
5
  import type { Data, ExtendWithAny, DeepPartial } from '../../../shared/core';
6
6
  import type { Field } from '../types';
7
+ import { ELEMENT_FIELD_IDENTITY_KEYS } from '../constants';
7
8
  import type { ComponentName } from '../constants';
8
9
  import type { FormInstance } from '../../../shared/ui';
9
10
 
@@ -107,5 +108,11 @@ export function useForm<D extends Data = Data>(
107
108
  root?: boolean
108
109
  ): Form<D, Field<ComponentName, D>>;
109
110
  export function useForm(...args: any[]) {
110
- return coreUseForm(...(args as any));
111
+ // root 单参重载不承载字段配置,标识键无适用对象
112
+ if (args.length === 1 && typeof args[0] === 'boolean') {
113
+ return coreUseForm(args[0]);
114
+ }
115
+ return coreUseForm(args[0], args[1], args[2], {
116
+ fieldIdentityKeys: ELEMENT_FIELD_IDENTITY_KEYS,
117
+ });
111
118
  }
@@ -1,24 +1,23 @@
1
1
  import { computed, inject, mergeProps, toValue, type ComputedRef } from 'vue';
2
+ import { getFieldKey } from '../utils/fieldKey';
2
3
  import {
3
4
  formItemProps,
4
5
  gridItemProps,
5
6
  type FormItemProps,
6
7
  type GridItemProps,
7
8
  } from '../../../shared/ui';
8
- import { isPlainObject } from '../../../shared/core';
9
+ import { isPlainObject, resolveFieldIdentity } from '../../../shared/core';
9
10
  import { INJECT_CONFIG } from '../../component-provider/constants';
10
- import { FORM_ITEM_SLOT_KEYS } from '../constants';
11
+ import { ELEMENT_FIELD_IDENTITY_KEYS, FORM_ITEM_SLOT_KEYS } from '../constants';
11
12
  import type { Base, Field } from '../types';
12
13
 
13
- const formItemPropKeys = Object.keys(formItemProps()).filter(
14
- k => k !== 'disabled'
15
- );
14
+ const formItemPropKeys = Object.keys(formItemProps());
16
15
  const gridItemPropKeys = Object.keys(gridItemProps());
17
- // 模块级 Set:字段循环内 O(1) 命中,替代数组 includes 线性扫描
18
16
  const gridItemPropKeySet = new Set<string>(gridItemPropKeys);
19
17
  const formItemPropKeySet = new Set<string>(formItemPropKeys);
20
18
  const formItemSlotKeySet = new Set<string>(FORM_ITEM_SLOT_KEYS);
21
19
 
20
+ /** 单个字段的分组结果:栅格 / FormItem / 组件属性与插槽 */
22
21
  export interface GroupedField {
23
22
  path: string;
24
23
  gridItemProps: GridItemProps;
@@ -27,10 +26,18 @@ export interface GroupedField {
27
26
  componentProps: Record<string, any>;
28
27
  container?: Base['formItemContainer'];
29
28
  rawField: Field;
29
+ renderKey: string | number;
30
30
  }
31
31
 
32
32
  /**
33
33
  * 将 fields 配置数组逐个拆分为 grid / formItem / component / slots 分组。
34
+ *
35
+ * @description
36
+ * 1. 响应式集成 ProComponentProvider 的 `pro-form-item` 全局变量配置;
37
+ * 2. 剥离 `extraProps`(用户自定义字段标识袋):保留在字段原始对象上供业务层存取,渲染层经解构剥离,不落入 componentProps 透传。
38
+ *
39
+ * @param {ComputedRef<Field[]>} validFields - 已过滤的字段配置数组
40
+ * @returns {ComputedRef<GroupedField[]>} 分组后的字段属性数组
34
41
  */
35
42
  export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
36
43
  const config = INJECT_CONFIG['pro-form-item'];
@@ -46,30 +53,25 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
46
53
 
47
54
  if (isPlainObject(field)) {
48
55
  // prettier-ignore
49
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
50
- const { path, name, fields, formItemClass, formItemStyle, formItemContainer, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, ...rest } =
56
+ const { path: _path, prop: _prop, fields, formItemClass, formItemStyle, formItemContainer, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, ...rest } =
51
57
  field as any;
52
58
 
53
- const {
54
- class: injectClassName,
55
- style: injectStyle,
56
- span: injectSpan,
57
- formItemContainer: injectFormItemContainer,
58
- xs,
59
- sm,
60
- md,
61
- lg,
62
- xl,
63
- ...injectRest
64
- } = rawInjectProps;
59
+ // prettier-ignore
60
+ const { class: injectClassName, style: injectStyle, span: injectSpan, formItemContainer: injectFormItemContainer, xs, sm, md, lg, xl, ...injectRest } = rawInjectProps;
65
61
 
66
- // 字段显式 span 优先,否则回退注入配置的响应式断点
62
+ /*
63
+ * 字段显式指定 span 时整体接管栅格,不再叠加注入的响应式断点;
64
+ * 未指定 span 时使用注入的断点配置
65
+ */
67
66
  const initGridItemProps =
68
67
  span || span === 0
69
68
  ? { span }
70
69
  : { xs, sm, md, lg, xl, span: injectSpan };
71
70
 
72
- gridItemProps = fields ? { span: span ?? 24 } : initGridItemProps;
71
+ // 分组字段未指定 span 时整行渲染(span 兜底 24),注入的响应式断点保持生效
72
+ gridItemProps = fields
73
+ ? { ...initGridItemProps, span: span ?? 24 }
74
+ : initGridItemProps;
73
75
  formItemProps = mergeProps(
74
76
  injectRest as any,
75
77
  { class: injectClassName, style: injectStyle },
@@ -97,30 +99,27 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
97
99
  }
98
100
  });
99
101
 
100
- const raw = (toValue(name) ?? toValue(path)) as
101
- | string
102
- | number[]
103
- | undefined;
102
+ // 字段标识经统一解析(prop 优先,path 兜底);name 作为控件自身属性经 rest 透传
104
103
  return {
105
- path: Array.isArray(raw) ? raw.join('.') : (raw as string) || '',
104
+ path: resolveFieldIdentity(field, ELEMENT_FIELD_IDENTITY_KEYS),
106
105
  gridItemProps: gridItemProps as GridItemProps,
107
106
  formItemProps: formItemProps as any,
108
107
  formItemSlots,
109
108
  componentProps,
110
109
  container,
111
110
  rawField: field,
111
+ renderKey: getFieldKey(field),
112
112
  };
113
113
  }
114
114
 
115
- const raw =
116
- toValue((field as any)?.name) ?? toValue((field as any)?.path);
117
115
  return {
118
- path: Array.isArray(raw) ? raw.join('.') : (raw as string) || '',
116
+ path: resolveFieldIdentity(field, ELEMENT_FIELD_IDENTITY_KEYS),
119
117
  gridItemProps: gridItemProps as GridItemProps,
120
118
  formItemProps: formItemProps as any,
121
119
  formItemSlots,
122
120
  componentProps,
123
121
  rawField: field,
122
+ renderKey: getFieldKey(field),
124
123
  };
125
124
  });
126
125
  });
@@ -6,15 +6,13 @@ export {
6
6
  SlotComponent,
7
7
  type ProFormProps,
8
8
  type ProFormInstance,
9
- type ProFormItemProps,
10
- type ProFormItemInstance,
11
9
  } from './components';
12
10
 
13
11
  // 2. 仅导出允许模块合并扩展的 ComponentMap 以及组件名类型
14
12
  export { type ComponentMap, type ComponentName } from './constants';
15
13
 
16
14
  // 3. 仅导出核心表单配置 Schema 类型
17
- export { type Field, type Fields } from './types';
15
+ export { type Field, type Fields, type ValueFormatter } from './types';
18
16
 
19
17
  // 4. 仅导出核心业务 Hooks
20
18
  export {