@qin-ui/antd-vue-pro 2.4.4 → 2.5.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.
- package/AI-CONTEXT.md +26 -2
- package/README.md +44 -37
- package/api.json +24 -9
- package/es/antd-vue-pro.css +15 -15
- package/es/components/component-provider/components/index.vue.d.ts +3 -3
- package/es/components/component-provider/components/index.vue.d.ts.map +1 -1
- package/es/components/component-provider/constants/index.d.ts +20 -4
- package/es/components/component-provider/constants/index.d.ts.map +1 -1
- package/es/components/component-provider/types/index.d.ts +8 -4
- package/es/components/component-provider/types/index.d.ts.map +1 -1
- package/es/components/form/components/BaseField/index.vue.d.ts +7 -1
- package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -1
- package/es/components/form/components/BaseField/utils/index.d.ts +18 -0
- package/es/components/form/components/BaseField/utils/index.d.ts.map +1 -1
- package/es/components/form/components/BaseFormItem/index.vue.d.ts +3 -0
- package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -1
- package/es/components/form/components/ContainerFragment/index.vue.d.ts +1 -0
- package/es/components/form/components/ContainerFragment/index.vue.d.ts.map +1 -1
- package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -1
- package/es/components/form/components/SlotComponent/index.vue.d.ts +1 -0
- package/es/components/form/components/SlotComponent/index.vue.d.ts.map +1 -1
- package/es/components/form/components/index.d.ts +4 -0
- package/es/components/form/components/index.d.ts.map +1 -1
- package/es/components/form/constants/index.d.ts.map +1 -1
- package/es/components/form/hooks/useFields.d.ts +2 -0
- package/es/components/form/hooks/useFields.d.ts.map +1 -1
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts +10 -0
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -1
- package/es/components/form/index.d.ts +2 -2
- package/es/components/form/index.d.ts.map +1 -1
- package/es/components/form/types/index.d.ts +75 -44
- package/es/components/form/types/index.d.ts.map +1 -1
- package/es/components/form/utils/fieldKey.d.ts +13 -0
- package/es/components/form/utils/fieldKey.d.ts.map +1 -0
- package/es/components/table/components/ColumnControl.vue.d.ts +3 -0
- package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -1
- package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +1 -1
- package/es/components/table/components/DefaultTableContainer.vue.d.ts.map +1 -1
- package/es/components/table/components/ProTable.vue.d.ts +8 -1
- package/es/components/table/components/ProTable.vue.d.ts.map +1 -1
- package/es/components/table/components/SearchForm.vue.d.ts +10 -2
- package/es/components/table/components/SearchForm.vue.d.ts.map +1 -1
- package/es/components/table/components/SizeControl.vue.d.ts +7 -2
- package/es/components/table/components/SizeControl.vue.d.ts.map +1 -1
- package/es/components/table/hooks/useTable.d.ts.map +1 -1
- package/es/components/table/index.d.ts +4 -1
- package/es/components/table/index.d.ts.map +1 -1
- package/es/components/table/types/index.d.ts +14 -6
- package/es/components/table/types/index.d.ts.map +1 -1
- package/es/index.d.ts +8 -4
- package/es/index.d.ts.map +1 -1
- package/es/index.js +528 -343
- package/es/index.js.map +1 -1
- package/es/locale/index.d.ts +2 -0
- package/es/locale/index.d.ts.map +1 -1
- package/es/shared/core/index.d.ts +1 -1
- package/es/shared/core/index.d.ts.map +1 -1
- package/es/shared/ui/index.d.ts +15 -3
- package/es/shared/ui/index.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/components/component-provider/components/index.vue +26 -12
- package/src/components/component-provider/constants/index.ts +33 -6
- package/src/components/component-provider/types/index.ts +17 -9
- package/src/components/form/components/BaseField/index.vue +48 -16
- package/src/components/form/components/BaseField/utils/index.ts +19 -1
- package/src/components/form/components/BaseFormItem/index.vue +65 -17
- package/src/components/form/components/ContainerFragment/index.vue +3 -1
- package/src/components/form/components/ProForm/index.vue +30 -18
- package/src/components/form/components/SlotComponent/index.vue +14 -4
- package/src/components/form/components/index.ts +8 -1
- package/src/components/form/constants/index.ts +3 -4
- package/src/components/form/hooks/useFields.ts +3 -1
- package/src/components/form/hooks/useGroupFieldAttrs.ts +34 -27
- package/src/components/form/index.ts +1 -3
- package/src/components/form/types/index.ts +79 -46
- package/src/components/form/utils/fieldKey.ts +27 -0
- package/src/components/table/components/ColumnControl.vue +21 -5
- package/src/components/table/components/DefaultSearchFormContainer.vue +5 -0
- package/src/components/table/components/DefaultTableContainer.vue +5 -0
- package/src/components/table/components/ProTable.vue +239 -66
- package/src/components/table/components/SearchForm.vue +58 -21
- package/src/components/table/components/SizeControl.vue +21 -1
- package/src/components/table/hooks/useTable.ts +1 -1
- package/src/components/table/index.ts +8 -3
- package/src/components/table/types/index.ts +15 -7
- package/src/index.ts +12 -5
- package/src/locale/index.ts +2 -0
- package/src/shared/core/index.ts +11 -0
- package/src/shared/ui/index.ts +22 -4
- package/tsconfig.json +22 -0
|
@@ -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 递归渲染)
|
|
@@ -13,35 +14,40 @@
|
|
|
13
14
|
* ## 渲染流程
|
|
14
15
|
*
|
|
15
16
|
* 1. 遍历 fields 数组,过滤 hidden 字段
|
|
16
|
-
* 2.
|
|
17
|
+
* 2. 将字段属性分流为 GridItem / FormItem / 组件 / 插槽四类
|
|
17
18
|
* 3. 递归渲染 BaseFormItem(嵌套表单)或 BaseField(输入组件)
|
|
18
|
-
* 4.
|
|
19
|
+
* 4. 渲染后字段对象上可读取 FormItem 与组件实例
|
|
19
20
|
*
|
|
20
|
-
* @param {Fields
|
|
21
|
+
* @param {Fields} [fields] - 字段配置数组
|
|
21
22
|
* @param {boolean | GridProps} [grid] - 是否启用网格布局
|
|
22
23
|
* @param {boolean} [disabled] - 是否禁用所有子字段
|
|
23
24
|
*
|
|
24
25
|
* @internal
|
|
25
26
|
*/
|
|
26
|
-
import { getObject, toPath } from '../../../../shared/core';
|
|
27
|
+
import { getObject, isObjectItem, toPath } from '../../../../shared/core';
|
|
28
|
+
import type { GridProps } from '../../../../shared/ui';
|
|
27
29
|
import {
|
|
28
30
|
FormItem,
|
|
29
31
|
Grid as AGrid,
|
|
30
32
|
GridItem as AGridItem,
|
|
31
|
-
GridProps,
|
|
32
33
|
useProviderDisabled,
|
|
33
34
|
} from '../../../../shared/ui';
|
|
34
35
|
import { computed, inject, toRaw, toValue } from 'vue';
|
|
35
36
|
import type { Fields, Grid } from '../../types';
|
|
36
|
-
import
|
|
37
|
+
import BaseField from '../BaseField/index.vue';
|
|
38
|
+
import SlotComponent from '../SlotComponent/index.vue';
|
|
39
|
+
import ContainerFragment from '../ContainerFragment/index.vue';
|
|
37
40
|
import { useGroupFieldAttrs } from '../../hooks/useGroupFieldAttrs';
|
|
38
41
|
import { INJECT_CONFIG } from '../../../component-provider/constants';
|
|
39
42
|
|
|
40
43
|
defineOptions({ name: 'BaseFormItem', inheritAttrs: false });
|
|
41
44
|
|
|
42
45
|
type Props = {
|
|
46
|
+
/** 是否启用网格布局 */
|
|
43
47
|
grid?: Grid;
|
|
48
|
+
/** 字段配置数组 */
|
|
44
49
|
fields?: Fields;
|
|
50
|
+
/** 是否禁用所有子字段 */
|
|
45
51
|
disabled?: boolean;
|
|
46
52
|
};
|
|
47
53
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -51,8 +57,10 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
const validFields = computed(() =>
|
|
54
|
-
// hidden 支持 Ref/ComputedRef
|
|
55
|
-
(props.fields ?? []).filter(
|
|
60
|
+
// hidden 支持 Ref / ComputedRef 响应式形态(统一使用 toValue 取值)
|
|
61
|
+
(props.fields ?? []).filter(
|
|
62
|
+
field => isObjectItem(field) && !toValue((field as any).hidden)
|
|
63
|
+
)
|
|
56
64
|
);
|
|
57
65
|
|
|
58
66
|
// 向下注入表单级禁用上下文,BaseField 经 useInjectDisabled 消费
|
|
@@ -75,18 +83,56 @@ const computedGridProps = computed<GridProps>(() => {
|
|
|
75
83
|
|
|
76
84
|
/**
|
|
77
85
|
* 以字段原始对象为 key 登记 FormItem / 控件实例,供 getFormItemRef / getComponentRef 读取;
|
|
78
|
-
*
|
|
86
|
+
* 字段对象被回收后对应条目不再可读。
|
|
79
87
|
*/
|
|
80
88
|
const formItemInstanceMap = new WeakMap<object, any>();
|
|
81
89
|
const componentInstanceMap = new WeakMap<object, any>();
|
|
82
90
|
|
|
91
|
+
/** 已告警过的字段对象(不可写场景去重) */
|
|
92
|
+
const warnedFrozenFields = new WeakSet<object>();
|
|
93
|
+
const defineFieldAccessors = (
|
|
94
|
+
raw: object,
|
|
95
|
+
accessors: Record<string, () => any>
|
|
96
|
+
) => {
|
|
97
|
+
try {
|
|
98
|
+
for (const [key, getter] of Object.entries(accessors)) {
|
|
99
|
+
// 访问器仅用于字段对象读取,不参与属性遍历与字段配置透传
|
|
100
|
+
Object.defineProperty(raw, key, {
|
|
101
|
+
value: getter,
|
|
102
|
+
enumerable: false,
|
|
103
|
+
configurable: true,
|
|
104
|
+
writable: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
if (!warnedFrozenFields.has(raw)) {
|
|
109
|
+
warnedFrozenFields.add(raw);
|
|
110
|
+
console.warn(
|
|
111
|
+
'[pro-components] 字段配置对象不可写(如被 Object.freeze),getFormItemRef / getComponentRef 等实例访问器挂载失败。'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
* 字段值变更经 BaseField 的 field-value-change 事件回传,由 FormItem 暴露的 onFieldChange 触发校验;
|
|
119
|
+
* autoLink 为 false 时保持用户自行校验的语义
|
|
120
|
+
*/
|
|
121
|
+
const onFieldValueChange = (item: any) => {
|
|
122
|
+
if (item.formItemProps?.autoLink === false) return;
|
|
123
|
+
const instance = formItemInstanceMap.get(toRaw(item.rawField));
|
|
124
|
+
instance?.onFieldChange?.();
|
|
125
|
+
};
|
|
126
|
+
|
|
83
127
|
const setFormItemRef = (field: any, el: any, formItemProps?: any) => {
|
|
84
128
|
if (!field || typeof field !== 'object') return;
|
|
85
129
|
const raw = toRaw(field);
|
|
86
130
|
if (el) {
|
|
87
131
|
formItemInstanceMap.set(raw, el);
|
|
88
|
-
raw
|
|
89
|
-
|
|
132
|
+
defineFieldAccessors(raw, {
|
|
133
|
+
getFormItemRef: () => formItemInstanceMap.get(raw),
|
|
134
|
+
getFormItemComputedProps: () => formItemProps,
|
|
135
|
+
});
|
|
90
136
|
} else {
|
|
91
137
|
formItemInstanceMap.delete(raw);
|
|
92
138
|
delete raw.getFormItemRef;
|
|
@@ -99,10 +145,11 @@ const setComponentRef = (field: any, el: any) => {
|
|
|
99
145
|
const raw = toRaw(field);
|
|
100
146
|
if (el) {
|
|
101
147
|
componentInstanceMap.set(raw, el);
|
|
102
|
-
raw
|
|
103
|
-
componentInstanceMap.get(raw)?.getComponentRef()
|
|
104
|
-
|
|
105
|
-
|
|
148
|
+
defineFieldAccessors(raw, {
|
|
149
|
+
getComponentRef: () => componentInstanceMap.get(raw)?.getComponentRef(),
|
|
150
|
+
getComponentComputedProps: () =>
|
|
151
|
+
componentInstanceMap.get(raw)?.getComponentComputedProps(),
|
|
152
|
+
});
|
|
106
153
|
} else {
|
|
107
154
|
componentInstanceMap.delete(raw);
|
|
108
155
|
delete raw.getComponentRef;
|
|
@@ -118,7 +165,7 @@ const groupedFields = useGroupFieldAttrs(validFields);
|
|
|
118
165
|
:component="enableGrid ? AGrid : undefined"
|
|
119
166
|
v-bind="computedGridProps"
|
|
120
167
|
>
|
|
121
|
-
<template v-for="
|
|
168
|
+
<template v-for="item of groupedFields" :key="item.renderKey">
|
|
122
169
|
<ContainerFragment
|
|
123
170
|
:component="enableGrid ? AGridItem : undefined"
|
|
124
171
|
v-bind="item.gridItemProps"
|
|
@@ -156,6 +203,7 @@ const groupedFields = useGroupFieldAttrs(validFields);
|
|
|
156
203
|
:ref="el => setComponentRef(item.rawField, el)"
|
|
157
204
|
v-bind="item.componentProps"
|
|
158
205
|
:path="item.path"
|
|
206
|
+
@field-value-change="onFieldValueChange(item)"
|
|
159
207
|
/>
|
|
160
208
|
</template>
|
|
161
209
|
</FormItem>
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
/**
|
|
3
|
+
* 容器分片渲染组件。用于动态渲染表单行或表单项的外部包裹容器(例如 Grid 行、Col 列等布局容器)。
|
|
4
|
+
*
|
|
3
5
|
* @component ContainerFragment
|
|
4
|
-
* @description 容器分片渲染组件。用于动态渲染表单行或表单项的外部包裹容器(例如 Grid 行、Col 列等布局容器)。
|
|
5
6
|
*
|
|
6
7
|
* @public
|
|
7
8
|
*/
|
|
8
9
|
import type { ContainerComponent } from '../../types';
|
|
9
10
|
|
|
10
11
|
type Props = {
|
|
12
|
+
/** 容器组件,未提供时不渲染包裹层 */
|
|
11
13
|
component?: ContainerComponent;
|
|
12
14
|
};
|
|
13
15
|
|
|
@@ -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/antd-vue-pro 配置驱动表单组件
|
|
5
6
|
*
|
|
6
7
|
* 架构设计说明 (Architecture Overview)
|
|
7
8
|
*
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
* @template F - 表单实例类型
|
|
26
27
|
*
|
|
27
28
|
* @param {F} [form] - useForm 返回的表单实例
|
|
28
|
-
* @param {boolean | GridProps} [grid
|
|
29
|
+
* @param {boolean | GridProps} [grid] - 是否启用网格布局;未提供时回退 Provider 注入的 `grid` 配置
|
|
29
30
|
* @param {...FormProps} ...attrs - Ant Design Vue Form 组件的其他属性
|
|
30
31
|
*
|
|
31
32
|
* @slot default - 默认插槽,可用于添加额外内容
|
|
@@ -42,24 +43,29 @@
|
|
|
42
43
|
* </template>
|
|
43
44
|
* ```
|
|
44
45
|
*/
|
|
45
|
-
import {
|
|
46
|
+
import type { FormProps as AFormProps } from '../../../../shared/ui';
|
|
47
|
+
import { Form as AForm } from '../../../../shared/ui';
|
|
46
48
|
import {
|
|
47
49
|
computed,
|
|
48
50
|
inject,
|
|
49
51
|
mergeProps,
|
|
52
|
+
onUpdated,
|
|
50
53
|
provide,
|
|
54
|
+
shallowRef,
|
|
51
55
|
toValue,
|
|
52
|
-
|
|
56
|
+
triggerRef,
|
|
53
57
|
type Slot,
|
|
54
58
|
} from 'vue';
|
|
55
59
|
import { INJECT_CONFIG } from '../../../component-provider/constants';
|
|
56
|
-
import
|
|
60
|
+
import BaseFormItem from '../BaseFormItem/index.vue';
|
|
57
61
|
import { InjectionFormKey, InjectionFormSlotsKey } from '../../constants';
|
|
58
62
|
import type { Grid, VModelProps, PathProps } from '../../types';
|
|
63
|
+
import type { Path } from '../../../../shared/core';
|
|
59
64
|
import {
|
|
60
|
-
Path,
|
|
61
65
|
camelizeProperties,
|
|
62
66
|
createFormProxy,
|
|
67
|
+
omitBy,
|
|
68
|
+
useReactiveAttrs,
|
|
63
69
|
} from '../../../../shared/core';
|
|
64
70
|
import { useForm, type Form } from '../../hooks/useForm';
|
|
65
71
|
|
|
@@ -69,19 +75,18 @@ type MyFormProps = Partial<Omit<AFormProps, 'model'>>;
|
|
|
69
75
|
|
|
70
76
|
type Props = { grid?: Grid; form?: F } & /* @vue-ignore */ MyFormProps;
|
|
71
77
|
const props = withDefaults(defineProps<Props>(), {
|
|
72
|
-
grid:
|
|
78
|
+
grid: undefined,
|
|
73
79
|
form: undefined,
|
|
74
80
|
});
|
|
75
81
|
|
|
76
82
|
/**
|
|
77
83
|
* 依赖注入中心:
|
|
78
|
-
*
|
|
84
|
+
* 未传 form 实例(含运行期置空)时回退内部表单实例;
|
|
79
85
|
* 将 Form 实例注入到 Vue Context 中,供深层子组件(如 BaseFormItem, BaseField, 自定义插槽)跨层级直接访问数据和方法。
|
|
80
86
|
*/
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
);
|
|
87
|
+
// 兜底表单实例:setup 同步期创建,未传 :form(含运行期置空)时经 currentForm 生效
|
|
88
|
+
const fallbackForm = useForm() as unknown as F;
|
|
89
|
+
const currentForm = computed(() => props.form ?? fallbackForm);
|
|
85
90
|
|
|
86
91
|
/**
|
|
87
92
|
* 表单实例代理:操作全部转发至 currentForm 当前实例;
|
|
@@ -105,6 +110,7 @@ const setFormRef = (el: any) => currentForm.value?.setFormRef?.(el);
|
|
|
105
110
|
let formInstanceCounter = 0;
|
|
106
111
|
const formInstanceKeyMap = new WeakMap<object, number>();
|
|
107
112
|
const getFormKey = (f: object) => {
|
|
113
|
+
if (f == null) return 0;
|
|
108
114
|
if (!formInstanceKeyMap.has(f)) {
|
|
109
115
|
formInstanceKeyMap.set(f, ++formInstanceCounter);
|
|
110
116
|
}
|
|
@@ -115,20 +121,22 @@ const formKey = computed(() => getFormKey(currentForm.value as any));
|
|
|
115
121
|
const config = INJECT_CONFIG['pro-form'];
|
|
116
122
|
const injectFormConfig = inject(config.injectionKey, config.default);
|
|
117
123
|
|
|
118
|
-
const attrs =
|
|
124
|
+
const attrs = useReactiveAttrs();
|
|
119
125
|
|
|
120
126
|
/**
|
|
121
127
|
* 从 ProComponentProvider 获取全局配置,响应式派生以支持动态配置变更
|
|
122
128
|
*/
|
|
123
129
|
const injectAttrs = computed(() => {
|
|
124
130
|
const raw = toValue(injectFormConfig);
|
|
125
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
126
131
|
const { grid: _injectGrid, ...rest } = raw || {};
|
|
127
132
|
return rest;
|
|
128
133
|
});
|
|
129
134
|
|
|
130
135
|
const formAttrs = computed(() => {
|
|
131
|
-
return mergeProps(
|
|
136
|
+
return mergeProps(
|
|
137
|
+
injectAttrs.value,
|
|
138
|
+
omitBy(camelizeProperties(attrs), value => value === undefined)
|
|
139
|
+
);
|
|
132
140
|
});
|
|
133
141
|
|
|
134
142
|
type ExtractPath<T> = T extends Form<infer D> ? Path<D> : string;
|
|
@@ -138,11 +146,15 @@ type FieldSlots = Record<ExtractPath<F>, Slot<FieldSlotProps>>;
|
|
|
138
146
|
|
|
139
147
|
const slots = defineSlots<Partial<FieldSlots & { default: Slot }>>();
|
|
140
148
|
|
|
141
|
-
|
|
149
|
+
/*
|
|
142
150
|
* 动态插槽映射中心 (Slot Teleporting):
|
|
143
|
-
*
|
|
151
|
+
* 向深层 BaseField 提供 ProForm 的具名插槽,父级运行期增删插槽后下游可感知
|
|
144
152
|
*/
|
|
145
|
-
|
|
153
|
+
const formSlotsRef = shallowRef<Record<string, any>>(slots);
|
|
154
|
+
onUpdated(() => {
|
|
155
|
+
triggerRef(formSlotsRef);
|
|
156
|
+
});
|
|
157
|
+
provide(InjectionFormSlotsKey, formSlotsRef);
|
|
146
158
|
</script>
|
|
147
159
|
|
|
148
160
|
<template>
|
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
/**
|
|
3
|
+
* 插槽渲染辅助组件。用于在表单或表格的自定义插槽中动态渲染外部传入的 VNode、渲染函数、组件定义对象或静态字符串。
|
|
4
|
+
*
|
|
3
5
|
* @component SlotComponent
|
|
4
|
-
* @description 插槽渲染辅助组件。用于在表单或表格的自定义插槽中动态渲染外部传入的 VNode、渲染函数或静态字符串。
|
|
5
6
|
*
|
|
6
7
|
* @public
|
|
7
8
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import { SlotComponentType } from '../../types';
|
|
9
|
+
import type { SlotComponentType } from '../../types';
|
|
10
10
|
|
|
11
11
|
defineOptions({ name: 'SlotComponent', inheritAttrs: false });
|
|
12
12
|
|
|
13
13
|
type Props = {
|
|
14
|
+
/** 插槽内容:VNode、渲染函数、组件定义对象或文本 */
|
|
14
15
|
component?: SlotComponentType;
|
|
15
16
|
};
|
|
16
17
|
const props = withDefaults(defineProps<Props>(), {
|
|
17
18
|
component: undefined,
|
|
18
19
|
});
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* 可组件化渲染判定:VNode / 函数(含函数式组件)/ 组件定义对象
|
|
23
|
+
* 其余(string/number/boolean/null/undefined)一律按文本渲染,不作为 <component :is> 的标签名解析
|
|
24
|
+
*/
|
|
25
|
+
const isRenderable = (c: Props['component']) =>
|
|
26
|
+
c !== null &&
|
|
27
|
+
c !== undefined &&
|
|
28
|
+
(typeof c === 'function' || typeof c === 'object');
|
|
19
29
|
</script>
|
|
20
30
|
|
|
21
31
|
<template>
|
|
22
32
|
<component
|
|
23
33
|
:is="props.component"
|
|
24
|
-
v-if="
|
|
34
|
+
v-if="isRenderable(props.component)"
|
|
25
35
|
v-bind="$attrs"
|
|
26
36
|
/>
|
|
27
37
|
<template v-else>{{ props.component }}</template>
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
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 的组件实例类型,用于 `ref` 标注与 `defineExpose` 访问 */
|
|
11
15
|
export type ProFormInstance = ComponentExposed<typeof ProForm>;
|
|
16
|
+
/** ProForm 的组件属性类型 */
|
|
12
17
|
export type ProFormProps = ComponentProps<typeof ProForm>;
|
|
13
18
|
|
|
19
|
+
/** BaseFormItem 的组件实例类型 */
|
|
14
20
|
export type ProFormItemInstance = ComponentExposed<typeof BaseFormItem>;
|
|
21
|
+
/** BaseFormItem 的组件属性类型 */
|
|
15
22
|
export type ProFormItemProps = ComponentProps<typeof BaseFormItem>;
|
|
@@ -45,14 +45,13 @@ export const FORM_ITEM_SLOT_KEYS = [
|
|
|
45
45
|
* }
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
|
-
// eslint-disable-
|
|
49
|
-
export interface ComponentMap {}
|
|
48
|
+
export interface ComponentMap {} // eslint-disable-line @typescript-eslint/no-empty-object-type
|
|
50
49
|
|
|
50
|
+
// prettier-ignore
|
|
51
51
|
/**
|
|
52
52
|
* 内置组件映射表
|
|
53
53
|
* @description Ant Design Vue 内置支持的组件类型映射
|
|
54
54
|
*/
|
|
55
|
-
// prettier-ignore
|
|
56
55
|
export type BaseComponentMap = {
|
|
57
56
|
'input': typeof Input;
|
|
58
57
|
'textarea': typeof Textarea;
|
|
@@ -102,10 +101,10 @@ export type GetComponentType<K extends ComponentName> =
|
|
|
102
101
|
? BaseComponentMap[K]
|
|
103
102
|
: never;
|
|
104
103
|
|
|
104
|
+
// prettier-ignore
|
|
105
105
|
/**
|
|
106
106
|
* 组件名称到 Ant Design Vue 组件的映射
|
|
107
107
|
*/
|
|
108
|
-
// prettier-ignore
|
|
109
108
|
export const componentMap: BaseComponentMap = {
|
|
110
109
|
'input': Input,
|
|
111
110
|
'textarea': Textarea,
|
|
@@ -1,4 +1,4 @@
|
|
|
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,
|
|
@@ -21,6 +21,8 @@ import type { ComponentName } from '../constants';
|
|
|
21
21
|
* - 字段的添加、插入、删除
|
|
22
22
|
* - 父级字段查找
|
|
23
23
|
*
|
|
24
|
+
* 字段标识按 `name`、`path` 的顺序取首个非 `null` / `undefined` 的值。
|
|
25
|
+
*
|
|
24
26
|
* @template D - 表单数据类型
|
|
25
27
|
* @param {Fields<D>} [initFields] - 初始字段配置数组
|
|
26
28
|
*
|
|
@@ -1,11 +1,12 @@
|
|
|
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
11
|
import { FORM_ITEM_SLOT_KEYS } from '../constants';
|
|
11
12
|
import type { Base, Field } from '../types';
|
|
@@ -14,19 +15,28 @@ const formItemPropKeys = Object.keys(formItemProps()).concat([
|
|
|
14
15
|
'formItemContainer',
|
|
15
16
|
]);
|
|
16
17
|
const gridItemPropKeys = Object.keys(gridItemProps());
|
|
17
|
-
// 模块级 Set:字段循环内 O(1) 命中,替代数组 includes 线性扫描
|
|
18
18
|
const gridItemPropKeySet = new Set<string>(gridItemPropKeys);
|
|
19
19
|
const formItemPropKeySet = new Set<string>(formItemPropKeys);
|
|
20
20
|
const formItemSlotKeySet = new Set<string>(FORM_ITEM_SLOT_KEYS);
|
|
21
21
|
|
|
22
|
+
/** 单个字段拆解后的渲染分组 */
|
|
22
23
|
export interface GroupedField {
|
|
24
|
+
/** 解析后的字段标识(`name` 优先于 `path`) */
|
|
23
25
|
path: string;
|
|
26
|
+
/** 透传给栅格容器(GridItem)的属性 */
|
|
24
27
|
gridItemProps: GridItemProps;
|
|
28
|
+
/** 透传给 FormItem 的属性 */
|
|
25
29
|
formItemProps: FormItemProps;
|
|
30
|
+
/** 归属 FormItem 的插槽 */
|
|
26
31
|
formItemSlots: Record<(typeof FORM_ITEM_SLOT_KEYS)[number], any>;
|
|
32
|
+
/** 透传给输入组件的属性(含组件插槽) */
|
|
27
33
|
componentProps: Record<string, any>;
|
|
34
|
+
/** 该字段的外层包裹容器 */
|
|
28
35
|
formItemContainer?: Base['formItemContainer'];
|
|
36
|
+
/** 字段原始对象,供实例登记与访问器挂载使用 */
|
|
29
37
|
rawField: Field;
|
|
38
|
+
/** 渲染 key,取自字段对象身份 */
|
|
39
|
+
renderKey: string | number;
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
/**
|
|
@@ -50,31 +60,25 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
|
50
60
|
|
|
51
61
|
if (isPlainObject(field)) {
|
|
52
62
|
// prettier-ignore
|
|
53
|
-
|
|
54
|
-
const { path, name, fields, formItemClass, formItemStyle, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, formItemContainer, ...rest } =
|
|
63
|
+
const { path: _path, name: _name, fields, formItemClass, formItemStyle, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, formItemContainer, ...rest } =
|
|
55
64
|
field as any;
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
style: injectStyle,
|
|
60
|
-
span: injectSpan,
|
|
61
|
-
xs,
|
|
62
|
-
sm,
|
|
63
|
-
md,
|
|
64
|
-
lg,
|
|
65
|
-
xl,
|
|
66
|
-
xxl,
|
|
67
|
-
formItemContainer: injectFormItemContainer,
|
|
68
|
-
...injectRest
|
|
69
|
-
} = rawInject;
|
|
66
|
+
// prettier-ignore
|
|
67
|
+
const { class: injectClassName, style: injectStyle, span: injectSpan, xs, sm, md, lg, xl, xxl, formItemContainer: injectFormItemContainer, ...injectRest } = rawInject;
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
/*
|
|
70
|
+
* 字段显式指定 span 时整体接管栅格,不再叠加注入的响应式断点;
|
|
71
|
+
* 未指定 span 时使用注入的断点配置
|
|
72
|
+
*/
|
|
72
73
|
const initGridItemProps =
|
|
73
74
|
span || span === 0
|
|
74
75
|
? { span }
|
|
75
76
|
: { xs, sm, md, lg, xl, xxl, span: injectSpan };
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
// 分组字段未指定 span 时整行渲染(span 兜底 24),注入的响应式断点保持生效
|
|
79
|
+
gridItemProps = fields
|
|
80
|
+
? { ...initGridItemProps, span: span ?? 24 }
|
|
81
|
+
: initGridItemProps;
|
|
78
82
|
// 字段级容器优先,回退注入级配置
|
|
79
83
|
const container = formItemContainer ?? injectFormItemContainer;
|
|
80
84
|
formItemProps = mergeProps(
|
|
@@ -103,30 +107,33 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
|
103
107
|
}
|
|
104
108
|
});
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
// label 插槽与字段 label 同时存在时由插槽渲染,剥离 label 属性
|
|
111
|
+
if (formItemSlots.label && formItemProps.label !== undefined) {
|
|
112
|
+
delete formItemProps.label;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const raw = resolveFieldIdentity(field);
|
|
110
116
|
return {
|
|
111
|
-
path:
|
|
117
|
+
path: raw,
|
|
112
118
|
gridItemProps: gridItemProps as GridItemProps,
|
|
113
119
|
formItemProps: formItemProps as any,
|
|
114
120
|
formItemSlots,
|
|
115
121
|
componentProps,
|
|
116
122
|
formItemContainer: container,
|
|
117
123
|
rawField: field,
|
|
124
|
+
renderKey: getFieldKey(field),
|
|
118
125
|
};
|
|
119
126
|
}
|
|
120
127
|
|
|
121
|
-
const raw =
|
|
122
|
-
toValue((field as any)?.name) ?? toValue((field as any)?.path);
|
|
128
|
+
const raw = resolveFieldIdentity(field);
|
|
123
129
|
return {
|
|
124
|
-
path:
|
|
130
|
+
path: raw,
|
|
125
131
|
gridItemProps: gridItemProps as GridItemProps,
|
|
126
132
|
formItemProps: formItemProps as any,
|
|
127
133
|
formItemSlots,
|
|
128
134
|
componentProps,
|
|
129
135
|
rawField: field,
|
|
136
|
+
renderKey: getFieldKey(field),
|
|
130
137
|
};
|
|
131
138
|
});
|
|
132
139
|
});
|
|
@@ -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 {
|