@qin-ui/antd-vue-pro 2.3.3 → 2.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 (166) hide show
  1. package/README.md +7 -5
  2. package/api.json +1 -25
  3. package/es/__tests__/BaseField.spec.d.ts +2 -0
  4. package/es/__tests__/BaseField.spec.d.ts.map +1 -0
  5. package/es/__tests__/BaseFormItem.spec.d.ts +2 -0
  6. package/es/__tests__/BaseFormItem.spec.d.ts.map +1 -0
  7. package/es/__tests__/ProForm.spec.d.ts +2 -0
  8. package/es/__tests__/ProForm.spec.d.ts.map +1 -0
  9. package/es/__tests__/ProTable.spec.d.ts +2 -0
  10. package/es/__tests__/ProTable.spec.d.ts.map +1 -0
  11. package/es/__tests__/SearchForm.spec.d.ts +2 -0
  12. package/es/__tests__/SearchForm.spec.d.ts.map +1 -0
  13. package/es/__tests__/controls.spec.d.ts +2 -0
  14. package/es/__tests__/controls.spec.d.ts.map +1 -0
  15. package/es/__tests__/helpers/fixtures.d.ts +15 -0
  16. package/es/__tests__/helpers/fixtures.d.ts.map +1 -0
  17. package/es/__tests__/helpers/mount.d.ts +38 -0
  18. package/es/__tests__/helpers/mount.d.ts.map +1 -0
  19. package/es/__tests__/hooks-locale.spec.d.ts +2 -0
  20. package/es/__tests__/hooks-locale.spec.d.ts.map +1 -0
  21. package/es/__tests__/index.spec.d.ts +2 -0
  22. package/es/__tests__/index.spec.d.ts.map +1 -0
  23. package/es/__tests__/integration.spec.d.ts +2 -0
  24. package/es/__tests__/integration.spec.d.ts.map +1 -0
  25. package/es/__tests__/setup.d.ts +14 -0
  26. package/es/__tests__/setup.d.ts.map +1 -0
  27. package/es/antd-vue-pro.css +13 -13
  28. package/es/index.js +435 -319
  29. package/es/index.js.map +1 -1
  30. package/es/{components → src/components}/component-provider/components/index.vue.d.ts +5 -1
  31. package/es/src/components/component-provider/components/index.vue.d.ts.map +1 -0
  32. package/es/src/components/component-provider/constants/index.d.ts.map +1 -0
  33. package/es/src/components/component-provider/index.d.ts.map +1 -0
  34. package/es/src/components/component-provider/types/index.d.ts.map +1 -0
  35. package/es/{components → src/components}/form/components/BaseField/index.vue.d.ts +4 -1
  36. package/es/src/components/form/components/BaseField/index.vue.d.ts.map +1 -0
  37. package/es/src/components/form/components/BaseField/utils/index.d.ts +5 -0
  38. package/es/src/components/form/components/BaseField/utils/index.d.ts.map +1 -0
  39. package/es/{components → src/components}/form/components/BaseFormItem/index.vue.d.ts +5 -1
  40. package/es/src/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -0
  41. package/es/{components → src/components}/form/components/ContainerFragment/index.vue.d.ts +3 -1
  42. package/es/src/components/form/components/ContainerFragment/index.vue.d.ts.map +1 -0
  43. package/es/src/components/form/components/ProForm/index.vue.d.ts.map +1 -0
  44. package/es/src/components/form/components/SlotComponent/index.vue.d.ts +11 -0
  45. package/es/src/components/form/components/SlotComponent/index.vue.d.ts.map +1 -0
  46. package/es/src/components/form/components/index.d.ts.map +1 -0
  47. package/es/{components → src/components}/form/constants/index.d.ts +1 -1
  48. package/es/src/components/form/constants/index.d.ts.map +1 -0
  49. package/es/src/components/form/hooks/index.d.ts.map +1 -0
  50. package/es/src/components/form/hooks/useFields.d.ts.map +1 -0
  51. package/es/src/components/form/hooks/useForm.d.ts.map +1 -0
  52. package/es/src/components/form/hooks/useFormRef.d.ts.map +1 -0
  53. package/es/src/components/form/hooks/useGroupFieldAttrs.d.ts +22 -0
  54. package/es/src/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -0
  55. package/es/src/components/form/index.d.ts.map +1 -0
  56. package/es/{components → src/components}/form/types/index.d.ts +26 -2
  57. package/es/src/components/form/types/index.d.ts.map +1 -0
  58. package/es/{components → src/components}/table/components/ColumnControl.vue.d.ts +4 -1
  59. package/es/src/components/table/components/ColumnControl.vue.d.ts.map +1 -0
  60. package/es/src/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +1 -0
  61. package/es/src/components/table/components/DefaultTableContainer.vue.d.ts.map +1 -0
  62. package/es/{components → src/components}/table/components/ProTable.vue.d.ts +0 -2
  63. package/es/src/components/table/components/ProTable.vue.d.ts.map +1 -0
  64. package/es/{components → src/components}/table/components/SearchForm.vue.d.ts +14 -4
  65. package/es/src/components/table/components/SearchForm.vue.d.ts.map +1 -0
  66. package/es/src/components/table/components/SizeControl.vue.d.ts.map +1 -0
  67. package/es/src/components/table/components/icons/ColumnHeightOutlined.vue.d.ts.map +1 -0
  68. package/es/src/components/table/components/icons/DownOutlined.vue.d.ts.map +1 -0
  69. package/es/src/components/table/components/icons/FilterOutlined.vue.d.ts.map +1 -0
  70. package/es/src/components/table/components/index.d.ts.map +1 -0
  71. package/es/src/components/table/hooks/index.d.ts.map +1 -0
  72. package/es/src/components/table/hooks/useTable.d.ts.map +1 -0
  73. package/es/src/components/table/index.d.ts.map +1 -0
  74. package/es/src/components/table/types/index.d.ts.map +1 -0
  75. package/es/{index.d.ts → src/index.d.ts} +1 -1
  76. package/es/src/index.d.ts.map +1 -0
  77. package/es/src/locale/index.d.ts.map +1 -0
  78. package/es/src/shared/core/index.d.ts +6 -0
  79. package/es/src/shared/core/index.d.ts.map +1 -0
  80. package/es/src/shared/ui/index.d.ts.map +1 -0
  81. package/package.json +8 -3
  82. package/src/components/component-provider/components/index.vue +37 -8
  83. package/src/components/component-provider/constants/index.ts +2 -1
  84. package/src/components/form/components/BaseField/index.vue +61 -34
  85. package/src/components/form/components/BaseField/utils/index.ts +6 -11
  86. package/src/components/form/components/BaseFormItem/index.vue +91 -94
  87. package/src/components/form/components/ContainerFragment/index.vue +4 -2
  88. package/src/components/form/components/ProForm/index.vue +70 -18
  89. package/src/components/form/components/SlotComponent/index.vue +6 -4
  90. package/src/components/form/constants/index.ts +1 -5
  91. package/src/components/form/hooks/useGroupFieldAttrs.ts +126 -0
  92. package/src/components/form/types/index.ts +26 -2
  93. package/src/components/table/components/ColumnControl.vue +16 -15
  94. package/src/components/table/components/ProTable.vue +92 -65
  95. package/src/components/table/components/SearchForm.vue +80 -59
  96. package/src/components/table/components/SizeControl.vue +4 -2
  97. package/src/index.ts +1 -1
  98. package/src/shared/core/index.ts +4 -2
  99. package/es/components/component-provider/components/index.vue.d.ts.map +0 -1
  100. package/es/components/component-provider/constants/index.d.ts.map +0 -1
  101. package/es/components/component-provider/index.d.ts.map +0 -1
  102. package/es/components/component-provider/types/index.d.ts.map +0 -1
  103. package/es/components/form/components/BaseField/index.vue.d.ts.map +0 -1
  104. package/es/components/form/components/BaseField/utils/index.d.ts +0 -5
  105. package/es/components/form/components/BaseField/utils/index.d.ts.map +0 -1
  106. package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +0 -1
  107. package/es/components/form/components/ContainerFragment/index.vue.d.ts.map +0 -1
  108. package/es/components/form/components/GroupedFieldAttrs/index.vue.d.ts +0 -29
  109. package/es/components/form/components/GroupedFieldAttrs/index.vue.d.ts.map +0 -1
  110. package/es/components/form/components/PathProvider/index.vue.d.ts +0 -24
  111. package/es/components/form/components/PathProvider/index.vue.d.ts.map +0 -1
  112. package/es/components/form/components/ProForm/index.vue.d.ts.map +0 -1
  113. package/es/components/form/components/SlotComponent/index.vue.d.ts +0 -7
  114. package/es/components/form/components/SlotComponent/index.vue.d.ts.map +0 -1
  115. package/es/components/form/components/index.d.ts.map +0 -1
  116. package/es/components/form/constants/index.d.ts.map +0 -1
  117. package/es/components/form/hooks/index.d.ts.map +0 -1
  118. package/es/components/form/hooks/useFields.d.ts.map +0 -1
  119. package/es/components/form/hooks/useForm.d.ts.map +0 -1
  120. package/es/components/form/hooks/useFormRef.d.ts.map +0 -1
  121. package/es/components/form/index.d.ts.map +0 -1
  122. package/es/components/form/types/index.d.ts.map +0 -1
  123. package/es/components/table/components/ColumnControl.vue.d.ts.map +0 -1
  124. package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +0 -1
  125. package/es/components/table/components/DefaultTableContainer.vue.d.ts.map +0 -1
  126. package/es/components/table/components/ProTable.vue.d.ts.map +0 -1
  127. package/es/components/table/components/SearchForm.vue.d.ts.map +0 -1
  128. package/es/components/table/components/SizeControl.vue.d.ts.map +0 -1
  129. package/es/components/table/components/icons/ColumnHeightOutlined.vue.d.ts.map +0 -1
  130. package/es/components/table/components/icons/DownOutlined.vue.d.ts.map +0 -1
  131. package/es/components/table/components/icons/FilterOutlined.vue.d.ts.map +0 -1
  132. package/es/components/table/components/index.d.ts.map +0 -1
  133. package/es/components/table/hooks/index.d.ts.map +0 -1
  134. package/es/components/table/hooks/useTable.d.ts.map +0 -1
  135. package/es/components/table/index.d.ts.map +0 -1
  136. package/es/components/table/types/index.d.ts.map +0 -1
  137. package/es/index.d.ts.map +0 -1
  138. package/es/locale/index.d.ts.map +0 -1
  139. package/es/shared/core/index.d.ts +0 -6
  140. package/es/shared/core/index.d.ts.map +0 -1
  141. package/es/shared/ui/index.d.ts.map +0 -1
  142. package/src/components/form/components/GroupedFieldAttrs/index.vue +0 -118
  143. package/src/components/form/components/PathProvider/index.vue +0 -19
  144. /package/es/{components → src/components}/component-provider/constants/index.d.ts +0 -0
  145. /package/es/{components → src/components}/component-provider/index.d.ts +0 -0
  146. /package/es/{components → src/components}/component-provider/types/index.d.ts +0 -0
  147. /package/es/{components → src/components}/form/components/ProForm/index.vue.d.ts +0 -0
  148. /package/es/{components → src/components}/form/components/index.d.ts +0 -0
  149. /package/es/{components → src/components}/form/hooks/index.d.ts +0 -0
  150. /package/es/{components → src/components}/form/hooks/useFields.d.ts +0 -0
  151. /package/es/{components → src/components}/form/hooks/useForm.d.ts +0 -0
  152. /package/es/{components → src/components}/form/hooks/useFormRef.d.ts +0 -0
  153. /package/es/{components → src/components}/form/index.d.ts +0 -0
  154. /package/es/{components → src/components}/table/components/DefaultSearchFormContainer.vue.d.ts +0 -0
  155. /package/es/{components → src/components}/table/components/DefaultTableContainer.vue.d.ts +0 -0
  156. /package/es/{components → src/components}/table/components/SizeControl.vue.d.ts +0 -0
  157. /package/es/{components → src/components}/table/components/icons/ColumnHeightOutlined.vue.d.ts +0 -0
  158. /package/es/{components → src/components}/table/components/icons/DownOutlined.vue.d.ts +0 -0
  159. /package/es/{components → src/components}/table/components/icons/FilterOutlined.vue.d.ts +0 -0
  160. /package/es/{components → src/components}/table/components/index.d.ts +0 -0
  161. /package/es/{components → src/components}/table/hooks/index.d.ts +0 -0
  162. /package/es/{components → src/components}/table/hooks/useTable.d.ts +0 -0
  163. /package/es/{components → src/components}/table/index.d.ts +0 -0
  164. /package/es/{components → src/components}/table/types/index.d.ts +0 -0
  165. /package/es/{locale → src/locale}/index.d.ts +0 -0
  166. /package/es/{shared → src/shared}/ui/index.d.ts +0 -0
@@ -33,9 +33,10 @@ import {
33
33
  inject,
34
34
  mergeProps,
35
35
  ref,
36
+ toValue,
36
37
  useAttrs,
37
38
  } from 'vue';
38
- import { cloneDeep, omit } from '../../../../shared/core';
39
+ import { cloneDeep } from '../../../../shared/core';
39
40
  import {
40
41
  useInjectDisabled,
41
42
  useInjectFormItemContext,
@@ -43,7 +44,10 @@ import {
43
44
  import type { Field } from '../../types';
44
45
  import { ContainerFragment, SlotComponent } from '..';
45
46
  import { componentMap, InjectionFormSlotsKey } from '../../constants';
46
- import { INJECT_COMPONENTS } from '../../../component-provider/constants';
47
+ import {
48
+ INJECT_COMPONENTS,
49
+ getInjectConfig,
50
+ } from '../../../component-provider/constants';
47
51
  import { useForm } from '../../hooks/useForm';
48
52
  import { getInitProps } from './utils';
49
53
  import { useLocale } from '../../../../locale';
@@ -54,12 +58,14 @@ type Props = {
54
58
  component?: string | Component;
55
59
  path?: string;
56
60
  };
57
- const { component = undefined, path = '' } = defineProps<Props>();
61
+ const props = withDefaults(defineProps<Props>(), {
62
+ component: undefined,
63
+ path: '',
64
+ });
58
65
 
59
66
  const { t } = useLocale();
60
67
 
61
68
  const form = useForm(false);
62
- const { getFormData, setFormData } = form;
63
69
 
64
70
  const componentRef = ref<any>();
65
71
 
@@ -70,14 +76,23 @@ const triggerFormItemChange = () => {
70
76
 
71
77
  const attrs: any = useAttrs();
72
78
 
73
- function getOldValue() {
74
- return cloneDeep(getFormData?.(path));
75
- }
79
+ // 全局默认配置注入:key = 组件名 + picker 修饰段;inject 须在 setup 同步期调用
80
+ const pickerVal = attrs.picker;
81
+ const injectConfigKey =
82
+ typeof props.component === 'string'
83
+ ? [props.component, pickerVal].filter(Boolean).join('.')
84
+ : '';
85
+ const componentInjectConfig = injectConfigKey
86
+ ? getInjectConfig(injectConfigKey)
87
+ : undefined;
88
+ const rawInjectComponentProps = componentInjectConfig
89
+ ? inject(componentInjectConfig.injectionKey, componentInjectConfig.default)
90
+ : undefined;
76
91
 
77
92
  // setter: 向 formData 写入值,支持 valueFormatter / valueFormatter.set 转换
78
93
  const value = computed({
79
94
  get() {
80
- let val = getFormData?.(path);
95
+ let val = form.getFormData?.(props.path);
81
96
  const { valueFormatter } = groupedAttrs.value;
82
97
  if (
83
98
  typeof valueFormatter === 'object' &&
@@ -90,14 +105,23 @@ const value = computed({
90
105
  set(val) {
91
106
  let newVal = val;
92
107
  const { valueFormatter } = groupedAttrs.value;
93
- if (valueFormatter) {
94
- if (typeof valueFormatter === 'function') {
95
- newVal = valueFormatter(val, getOldValue());
96
- } else if (typeof valueFormatter.set === 'function') {
97
- newVal = valueFormatter.set(val, getOldValue());
98
- }
108
+ const setFormatter =
109
+ typeof valueFormatter === 'function'
110
+ ? valueFormatter
111
+ : typeof valueFormatter === 'object' &&
112
+ typeof valueFormatter?.set === 'function'
113
+ ? valueFormatter.set
114
+ : undefined;
115
+
116
+ if (setFormatter) {
117
+ // 仅当 set 转换函数需要旧值参数时,才获取旧值上下文
118
+ const oldVal =
119
+ setFormatter.length > 1
120
+ ? cloneDeep(form.getFormData?.(props.path))
121
+ : undefined;
122
+ newVal = setFormatter(val, oldVal);
99
123
  }
100
- setFormData?.(path, newVal);
124
+ form.setFormData?.(props.path, newVal);
101
125
  triggerFormItemChange();
102
126
  },
103
127
  });
@@ -109,28 +133,25 @@ const parentDisabled = useInjectDisabled();
109
133
  * 属性分为三类:绑定属性(v-model)、组件属性(传递给组件)、插槽(动态渲染)
110
134
  */
111
135
  const groupedAttrs = computed(() => {
136
+ const currentInjectProps = toValue(rawInjectComponentProps) || {};
112
137
  const initProps = getInitProps(
113
- {
114
- component: component,
115
- picker: attrs.picker,
116
- } as Field,
138
+ { component: props.component, picker: attrs.picker } as Field,
139
+ currentInjectProps,
117
140
  t
118
141
  );
142
+ const effectiveModelProp = attrs.modelProp ?? initProps.modelProp ?? 'value';
119
143
  const mergedProps = mergeProps(
120
144
  initProps,
121
145
  attrs,
122
146
  { class: attrs.componentClass, style: attrs.componentStyle },
123
147
  { class: initProps.componentClass, style: initProps.componentStyle },
124
148
  { disabled: attrs.disabled ?? parentDisabled.value ?? initProps.disabled },
125
- { modelProp: attrs.modelProp ?? initProps.modelProp ?? 'value' }
149
+ { modelProp: effectiveModelProp }
126
150
  ) as Required<Field>;
127
151
 
128
152
  // prettier-ignore
129
153
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
130
- const { valueFormatter, modelProp, slots, componentClass, componentStyle, componentContainer, ...rest } = mergedProps
131
-
132
- // 从剩余属性中移除 v-model 绑定属性,避免重复绑定
133
- const bindAttrs = omit(rest, [modelProp, `onUpdate:${modelProp}`]);
154
+ const { valueFormatter, modelProp, slots, componentClass, componentStyle, componentContainer, [effectiveModelProp]: _modelVal, [`onUpdate:${effectiveModelProp}`]: _onUpdateModelVal, ...bindAttrs } = mergedProps as any;
134
155
 
135
156
  return {
136
157
  attrs: bindAttrs,
@@ -154,16 +175,19 @@ const customComponents = inject(INJECT_COMPONENTS, {});
154
175
  * ProForm 插槽 → 自定义组件映射 → 内置组件映射 → 原始 component 值
155
176
  */
156
177
  const is = computed(() => {
157
- const teleportSlot = path && formSlots?.[path];
178
+ const teleportSlot = props.path && formSlots?.[props.path];
158
179
  if (teleportSlot) return teleportSlot;
159
- if (typeof component === 'string') {
160
- return (
161
- (customComponents as any)[component] ||
162
- (componentMap as any)[component] ||
163
- component
180
+ if (typeof props.component === 'string') {
181
+ const resolved =
182
+ (customComponents as any)[props.component] ||
183
+ (componentMap as any)[props.component];
184
+ if (resolved) return resolved;
185
+ console.warn(
186
+ `[pro-components] 未识别的组件类型 "${props.component}",已降级为原生元素渲染。如为自定义组件请在 componentMap 或 ProComponentProvider 中注册。`
164
187
  );
188
+ return props.component;
165
189
  }
166
- return component;
190
+ return props.component;
167
191
  });
168
192
 
169
193
  defineExpose({
@@ -173,21 +197,24 @@ defineExpose({
173
197
  </script>
174
198
 
175
199
  <template>
176
- <ContainerFragment :component="groupedAttrs.componentContainer" :path="path">
200
+ <ContainerFragment
201
+ :component="groupedAttrs.componentContainer"
202
+ :path="props.path"
203
+ >
177
204
  <component
178
205
  :is="is"
179
206
  v-if="is"
180
207
  v-bind="groupedAttrs.attrs"
181
208
  ref="componentRef"
182
209
  v-model:[`${groupedAttrs.modelProp}`]="value"
183
- :path="path"
210
+ :path="props.path"
184
211
  >
185
212
  <template
186
213
  v-for="(slot, name) in groupedAttrs.slots"
187
214
  :key="name"
188
215
  #[name]="scoped"
189
216
  >
190
- <SlotComponent :path="path" v-bind="scoped" :component="slot" />
217
+ <SlotComponent :path="props.path" v-bind="scoped" :component="slot" />
191
218
  </template>
192
219
  </component>
193
220
  </ContainerFragment>
@@ -1,6 +1,4 @@
1
1
  import { Field } from '../../../types';
2
- import { getInjectConfig } from '../../../../component-provider/constants';
3
- import { inject } from 'vue';
4
2
 
5
3
  type LocaleT = (
6
4
  key: string,
@@ -17,11 +15,7 @@ const PLACEHOLDER_KEYS: Record<string, string> = {
17
15
  cascader: 'select.placeholder',
18
16
  };
19
17
 
20
- /**
21
- * 根据组件 key 兜底生成 locale 占位符
22
- *
23
- * @description 用户通过 componentVars 或字段属性显式设置了占位符时不再兜底。
24
- */
18
+ /** 根据组件 key 兜底生成 locale 占位符;已显式设置占位符(componentVars 或字段属性)时不兜底 */
25
19
  const getPlaceholderProps = (
26
20
  key: string,
27
21
  injectProps: Record<string, any>,
@@ -32,14 +26,15 @@ const getPlaceholderProps = (
32
26
  return { placeholder: t(placeholderKey) };
33
27
  };
34
28
 
35
- export const getInitProps = (field: Field, t: LocaleT): Record<string, any> => {
29
+ export const getInitProps = (
30
+ field: Field,
31
+ injectProps: Record<string, any> = {},
32
+ t: LocaleT
33
+ ): Record<string, any> => {
36
34
  const { component } = field as any;
37
35
  const picker = component === 'date-picker' ? '' : (field as any).picker;
38
36
  if (typeof component === 'string') {
39
37
  const k = [component, picker].filter(Boolean).join('.');
40
- const config = getInjectConfig(k);
41
- if (!config) return getPlaceholderProps(k, {}, t);
42
- const injectProps = inject(config.injectionKey, config.default);
43
38
  return { ...injectProps, ...getPlaceholderProps(k, injectProps, t) };
44
39
  }
45
40
  return {};
@@ -13,12 +13,9 @@
13
13
  * ## 渲染流程
14
14
  *
15
15
  * 1. 遍历 fields 数组,过滤 hidden 字段
16
- * 2. 对每个字段:
17
- * a. 通过 PathProvider 提供当前字段路径
18
- * b. 通过 GroupedFieldAttrs 将 field 配置拆分为 gridItemProps / formItemProps / componentProps
19
- * c. 如果字段有 `fields` 子字段 → 递归渲染 BaseFormItem(嵌套表单)
20
- * d. 否则 → 渲染 BaseField(输入组件)
21
- * 3. 通过 ref 回调收集 FormItem 和 Component 实例,注入到 field 对象上
16
+ * 2. 通过 useGroupFieldAttrs Hook 纯计算将 field 配置拆分为 gridItemProps / formItemProps / componentProps / slots
17
+ * 3. 递归渲染 BaseFormItem(嵌套表单)或 BaseField(输入组件)
18
+ * 4. 通过 ref 回调收集 FormItem 和 Component 实例,注入到 field 对象上
22
19
  *
23
20
  * @param {Fields<D>} [fields] - 字段配置数组
24
21
  * @param {boolean | GridProps} [grid] - 是否启用网格布局
@@ -34,11 +31,10 @@ import {
34
31
  GridProps,
35
32
  useProviderDisabled,
36
33
  } from '../../../../shared/ui';
37
- import { computed, inject, toValue } from 'vue';
34
+ import { computed, inject, toRaw, toValue } from 'vue';
38
35
  import type { Fields, Grid } from '../../types';
39
36
  import { BaseField, SlotComponent, ContainerFragment } from '..';
40
- import PathProvider from '../PathProvider/index.vue';
41
- import GroupedFieldAttrs from '../GroupedFieldAttrs/index.vue';
37
+ import { useGroupFieldAttrs } from '../../hooks/useGroupFieldAttrs';
42
38
  import { INJECT_CONFIG } from '../../../component-provider/constants';
43
39
 
44
40
  defineOptions({ name: 'BaseFormItem', inheritAttrs: false });
@@ -48,50 +44,67 @@ type Props = {
48
44
  fields?: Fields;
49
45
  disabled?: boolean;
50
46
  };
51
- const {
52
- grid = undefined,
53
- fields = [],
54
- disabled = undefined,
55
- } = defineProps<Props>();
47
+ const props = withDefaults(defineProps<Props>(), {
48
+ grid: undefined,
49
+ fields: () => [],
50
+ disabled: undefined,
51
+ });
56
52
 
57
- const validFields = computed(() => fields.filter?.(field => !field.hidden));
53
+ const validFields = computed(() =>
54
+ (props.fields ?? []).filter(field => !field.hidden)
55
+ );
58
56
 
59
- useProviderDisabled(computed(() => disabled));
57
+ useProviderDisabled(computed(() => props.disabled));
60
58
 
61
59
  const config = INJECT_CONFIG['pro-form'];
62
-
63
- const { grid: injectGrid } = inject(config.injectionKey, config.default);
60
+ const injectFormConfig = inject(config.injectionKey, config.default);
61
+ const injectGrid = computed(() => toValue(injectFormConfig)?.grid);
64
62
 
65
63
  const enableGrid = computed(() => {
66
- if (grid !== undefined) return !!grid;
67
- return !!injectGrid;
64
+ if (props.grid !== undefined) return !!props.grid;
65
+ return !!injectGrid.value;
68
66
  });
69
67
 
70
68
  const computedGridProps = computed<GridProps>(() => {
71
69
  return enableGrid.value
72
- ? { ...getObject(injectGrid), ...getObject(grid) }
70
+ ? { ...getObject(injectGrid.value), ...getObject(props.grid) }
73
71
  : {};
74
72
  });
75
73
 
76
- const formItemRefs: any[] = [];
77
- const componentRefs: any[] = [];
74
+ /**
75
+ * 以字段原始对象为 key 登记 FormItem / 控件实例,供 getFormItemRef / getComponentRef 读取;
76
+ * WeakMap 不参与响应式追踪,条目随字段对象回收自动释放。
77
+ */
78
+ const formItemInstanceMap = new WeakMap<object, any>();
79
+ const componentInstanceMap = new WeakMap<object, any>();
78
80
 
79
- const onFormItemMounted = (index: number, formItemProps?: any) => {
80
- const field = validFields.value[index];
81
- Object.assign(field, {
82
- getFormItemRef: () => formItemRefs[index],
83
- getFormItemComputedProps: () => formItemProps,
84
- });
81
+ const setFormItemRef = (field: any, el: any, formItemProps?: any) => {
82
+ if (!field || typeof field !== 'object') return;
83
+ const raw = toRaw(field);
84
+ if (el) {
85
+ formItemInstanceMap.set(raw, el);
86
+ raw.getFormItemRef = () => formItemInstanceMap.get(raw);
87
+ raw.getFormItemComputedProps = () => formItemProps;
88
+ } else {
89
+ formItemInstanceMap.delete(raw);
90
+ }
85
91
  };
86
92
 
87
- const onComponentMounted = (index: number) => {
88
- const field = validFields.value[index];
89
- Object.assign(field, {
90
- getComponentRef: () => componentRefs[index]?.getComponentRef(),
91
- getComponentComputedProps: () =>
92
- componentRefs[index]?.getComponentComputedProps(),
93
- });
93
+ const setComponentRef = (field: any, el: any) => {
94
+ if (!field || typeof field !== 'object') return;
95
+ const raw = toRaw(field);
96
+ if (el) {
97
+ componentInstanceMap.set(raw, el);
98
+ raw.getComponentRef = () =>
99
+ componentInstanceMap.get(raw)?.getComponentRef();
100
+ raw.getComponentComputedProps = () =>
101
+ componentInstanceMap.get(raw)?.getComponentComputedProps();
102
+ } else {
103
+ componentInstanceMap.delete(raw);
104
+ }
94
105
  };
106
+
107
+ const groupedFields = useGroupFieldAttrs(validFields);
95
108
  </script>
96
109
 
97
110
  <template>
@@ -99,65 +112,49 @@ const onComponentMounted = (index: number) => {
99
112
  :component="enableGrid ? AGrid : undefined"
100
113
  v-bind="computedGridProps"
101
114
  >
102
- <PathProvider
103
- v-for="({ path: _path, name: _name, ...field }, index) of validFields"
104
- :key="toPath(toValue((_name ?? _path) as any)).join('.') || index"
105
- v-bind="{ path: (_name ?? _path) as any }"
106
- >
107
- <template #default="{ path }">
108
- <GroupedFieldAttrs :field="field">
109
- <template
110
- #default="{
111
- gridItemProps,
112
- formItemProps: { formItemContainer, ...formItemProps },
113
- componentProps,
114
- formItemSlots,
115
- }"
115
+ <template v-for="(item, index) of groupedFields" :key="item.path || index">
116
+ <ContainerFragment
117
+ :component="enableGrid ? AGridItem : undefined"
118
+ v-bind="item.gridItemProps"
119
+ >
120
+ <ContainerFragment
121
+ :component="item.formItemContainer"
122
+ :path="item.path"
123
+ >
124
+ <FormItem
125
+ v-bind="item.formItemProps"
126
+ :ref="el => setFormItemRef(item.rawField, el, item.formItemProps)"
127
+ :name="toPath(item.path)"
128
+ :path="item.path"
116
129
  >
117
- <ContainerFragment
118
- :component="enableGrid ? AGridItem : undefined"
119
- v-bind="gridItemProps"
130
+ <template
131
+ v-for="(slot, name) in item.formItemSlots"
132
+ :key="name"
133
+ #[name]="scoped"
120
134
  >
121
- <ContainerFragment :component="formItemContainer" :path="path">
122
- <FormItem
123
- v-bind="formItemProps"
124
- :ref="el => (formItemRefs[index] = el)"
125
- :name="toPath(path)"
126
- :path="path"
127
- @vue:mounted="onFormItemMounted(index, formItemProps)"
128
- >
129
- <template
130
- v-for="(slot, name) in formItemSlots"
131
- :key="name"
132
- #[name]="scoped"
133
- >
134
- <SlotComponent
135
- :path="path"
136
- :component="slot"
137
- v-bind="scoped"
138
- />
139
- </template>
140
- <template v-if="field.fields">
141
- <BaseFormItem
142
- :grid="(field as any).grid ?? grid"
143
- :fields="field.fields"
144
- :disabled="toValue(field.disabled)"
145
- />
146
- </template>
147
- <template v-else>
148
- <BaseField
149
- :ref="el => (componentRefs[index] = el)"
150
- v-bind="componentProps"
151
- :path="path"
152
- @vue:mounted="onComponentMounted(index)"
153
- />
154
- </template>
155
- </FormItem>
156
- </ContainerFragment>
157
- </ContainerFragment>
158
- </template>
159
- </GroupedFieldAttrs>
160
- </template>
161
- </PathProvider>
135
+ <SlotComponent
136
+ :path="item.path"
137
+ :component="slot"
138
+ v-bind="scoped"
139
+ />
140
+ </template>
141
+ <template v-if="item.rawField.fields">
142
+ <BaseFormItem
143
+ :grid="(item.rawField as any).grid ?? props.grid"
144
+ :fields="item.rawField.fields"
145
+ :disabled="toValue(item.rawField.disabled)"
146
+ />
147
+ </template>
148
+ <template v-else>
149
+ <BaseField
150
+ :ref="el => setComponentRef(item.rawField, el)"
151
+ v-bind="item.componentProps"
152
+ :path="item.path"
153
+ />
154
+ </template>
155
+ </FormItem>
156
+ </ContainerFragment>
157
+ </ContainerFragment>
158
+ </template>
162
159
  </ContainerFragment>
163
160
  </template>
@@ -11,13 +11,15 @@ type Props = {
11
11
  component?: ContainerComponent;
12
12
  };
13
13
 
14
- defineProps<Props>();
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ component: undefined,
16
+ });
15
17
 
16
18
  defineOptions({ name: 'ContainerFragment', inheritAttrs: false });
17
19
  </script>
18
20
 
19
21
  <template>
20
- <component :is="component" v-if="component" v-bind="$attrs">
22
+ <component :is="props.component" v-if="props.component" v-bind="$attrs">
21
23
  <slot />
22
24
  </component>
23
25
  <slot v-else />
@@ -43,38 +43,89 @@
43
43
  * ```
44
44
  */
45
45
  import { Form as AForm, FormProps as AFormProps } from '../../../../shared/ui';
46
- import { inject, mergeProps, provide, reactive, type Slot } from 'vue';
46
+ import { computed, inject, mergeProps, provide, toValue, type Slot } from 'vue';
47
47
  import { INJECT_CONFIG } from '../../../component-provider/constants';
48
48
  import { BaseFormItem } from '..';
49
49
  import { InjectionFormKey, InjectionFormSlotsKey } from '../../constants';
50
50
  import type { Grid, VModelProps, PathProps } from '../../types';
51
51
  import { Path, camelizeProperties } from '../../../../shared/core';
52
- import type { Form } from '../../hooks/useForm';
52
+ import { useForm, type Form } from '../../hooks/useForm';
53
53
 
54
54
  defineOptions({ name: 'ProForm', inheritAttrs: false });
55
55
 
56
- type FormProps = Partial<Omit<AFormProps, 'model'>>;
56
+ type MyFormProps = Partial<Omit<AFormProps, 'model'>>;
57
57
 
58
- type Props = { grid?: Grid; form?: F } & /* @vue-ignore */ FormProps;
59
- const { grid = false, form = {} as F } = defineProps<Props>();
58
+ type Props = { grid?: Grid; form?: F } & /* @vue-ignore */ MyFormProps;
59
+ const props = withDefaults(defineProps<Props>(), {
60
+ grid: false,
61
+ form: undefined,
62
+ });
60
63
 
61
64
  /**
62
65
  * 依赖注入中心:
63
- * 将 Core 包生成的 Form 实例注入到 Vue 的 Context 中。
64
- * 供深层子组件(如 BaseFormItem, BaseField, 自定义插槽)跨层级直接访问数据和方法。
66
+ * 若外部未传 form 实例,则自动初始化内部表单实例;
67
+ * 将 Form 实例注入到 Vue Context 中,供深层子组件(如 BaseFormItem, BaseField, 自定义插槽)跨层级直接访问数据和方法。
65
68
  */
66
- provide(InjectionFormKey, form as F);
69
+ const fallbackForm = props.form ? undefined : useForm();
70
+ const currentForm = computed(
71
+ () => props.form ?? (fallbackForm as unknown as F)
72
+ );
73
+
74
+ /**
75
+ * 表单实例代理:操作全部转发至 currentForm 当前实例;
76
+ * 下游持有本代理即可,:form 运行期替换无需重绑。
77
+ */
78
+ const formProxy = new Proxy({} as F, {
79
+ get(_, prop) {
80
+ const target = currentForm.value as any;
81
+ const val = target?.[prop];
82
+ if (typeof val === 'function') {
83
+ return (...args: any[]) => (currentForm.value as any)?.[prop]?.(...args);
84
+ }
85
+ return val;
86
+ },
87
+ set(_, prop, value) {
88
+ if (currentForm.value) {
89
+ (currentForm.value as any)[prop] = value;
90
+ }
91
+ return true;
92
+ },
93
+ has(_, prop) {
94
+ return Boolean(currentForm.value && prop in currentForm.value);
95
+ },
96
+ });
97
+
98
+ provide(InjectionFormKey, formProxy);
67
99
 
68
- const { formData, fields, setFormRef } = form as F;
100
+ const fields = computed(() => {
101
+ const f = currentForm.value?.fields;
102
+ return (Array.isArray(f) ? f : f?.value) ?? [];
103
+ });
104
+ const formData = computed(() => currentForm.value?.formData);
105
+ const setFormRef = (el: any) => currentForm.value?.setFormRef?.(el);
106
+
107
+ let formInstanceCounter = 0;
108
+ const formInstanceKeyMap = new WeakMap<object, number>();
109
+ const getFormKey = (f: object) => {
110
+ if (!formInstanceKeyMap.has(f)) {
111
+ formInstanceKeyMap.set(f, ++formInstanceCounter);
112
+ }
113
+ return formInstanceKeyMap.get(f)!;
114
+ };
115
+ const formKey = computed(() => getFormKey(currentForm.value as any));
69
116
 
70
117
  const config = INJECT_CONFIG['pro-form'];
118
+ const injectFormConfig = inject(config.injectionKey, config.default);
71
119
 
72
- // 从 ProComponentProvider 获取全局配置,与传入的 props 合并
73
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
74
- const { grid: _injectGrid, ...injectAttrs } = inject(
75
- config.injectionKey,
76
- config.default
77
- );
120
+ /**
121
+ * 从 ProComponentProvider 获取全局配置,响应式派生以支持动态配置变更
122
+ */
123
+ const injectAttrs = computed(() => {
124
+ const raw = toValue(injectFormConfig);
125
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
126
+ const { grid: _injectGrid, ...rest } = raw || {};
127
+ return rest;
128
+ });
78
129
 
79
130
  type ExtractPath<T> = T extends Form<infer D> ? Path<D> : string;
80
131
  type FieldSlotProps = VModelProps &
@@ -87,17 +138,18 @@ const slots = defineSlots<Partial<FieldSlots & { default: Slot }>>();
87
138
  * 动态插槽映射中心 (Slot Teleporting):
88
139
  * 在 setup 同步期直接提供响应式 slots 上下文容器,供深层 BaseField 按需动态获取插槽
89
140
  */
90
- provide(InjectionFormSlotsKey, reactive(slots) as any);
141
+ provide(InjectionFormSlotsKey, slots);
91
142
  </script>
92
143
 
93
144
  <template>
94
145
  <AForm
95
- :ref="(el: any) => setFormRef?.(el)"
146
+ :key="formKey"
147
+ :ref="setFormRef"
96
148
  :model="formData"
97
149
  v-bind="mergeProps(injectAttrs, camelizeProperties($attrs))"
98
150
  class="pro-form"
99
151
  >
100
- <BaseFormItem :fields="fields" :grid="grid" />
152
+ <BaseFormItem :fields="fields" :grid="props.grid" />
101
153
  <slot />
102
154
  </AForm>
103
155
  </template>
@@ -13,14 +13,16 @@ defineOptions({ name: 'SlotComponent', inheritAttrs: false });
13
13
  type Props = {
14
14
  component?: SlotComponentType;
15
15
  };
16
- defineProps<Props>();
16
+ const props = withDefaults(defineProps<Props>(), {
17
+ component: undefined,
18
+ });
17
19
  </script>
18
20
 
19
21
  <template>
20
22
  <component
21
- :is="component"
22
- v-if="isVNode(component) || typeof component === 'function'"
23
+ :is="props.component"
24
+ v-if="isVNode(props.component) || typeof props.component === 'function'"
23
25
  v-bind="$attrs"
24
26
  />
25
- <template v-else>{{ component }}</template>
27
+ <template v-else>{{ props.component }}</template>
26
28
  </template>
@@ -126,8 +126,4 @@ export const componentMap: BaseComponentMap = {
126
126
  }
127
127
 
128
128
  // inject keys
129
- export {
130
- InjectionFormKey,
131
- InjectionPathKey,
132
- InjectionFormSlotsKey,
133
- } from '../../../shared/core';
129
+ export { InjectionFormKey, InjectionFormSlotsKey } from '../../../shared/core';