@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.
- package/AI-CONTEXT.md +26 -2
- package/README.md +41 -30
- package/api.json +69 -38
- package/es/components/component-provider/components/index.vue.d.ts +10 -3
- package/es/components/component-provider/components/index.vue.d.ts.map +1 -1
- package/es/components/component-provider/constants/index.d.ts +22 -4
- package/es/components/component-provider/constants/index.d.ts.map +1 -1
- package/es/components/component-provider/types/index.d.ts +17 -3
- package/es/components/component-provider/types/index.d.ts.map +1 -1
- package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -1
- package/es/components/form/components/BaseField/utils/index.d.ts +15 -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 -1
- package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -1
- 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.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 +8 -2
- package/es/components/form/constants/index.d.ts.map +1 -1
- package/es/components/form/hooks/useFields.d.ts.map +1 -1
- package/es/components/form/hooks/useForm.d.ts.map +1 -1
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts +9 -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 +65 -50
- 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 +1 -1
- package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -1
- package/es/components/table/components/ProTable.vue.d.ts +15 -1
- package/es/components/table/components/ProTable.vue.d.ts.map +1 -1
- package/es/components/table/components/SearchForm.vue.d.ts +14 -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/components/TableColumnRenderer.vue.d.ts +14 -14
- package/es/components/table/components/TableColumnRenderer.vue.d.ts.map +1 -1
- package/es/components/table/hooks/index.d.ts +2 -0
- package/es/components/table/hooks/index.d.ts.map +1 -0
- package/es/components/table/{useTable.d.ts → hooks/useTable.d.ts} +3 -3
- package/es/components/table/hooks/useTable.d.ts.map +1 -0
- package/es/components/table/index.d.ts +6 -2
- package/es/components/table/index.d.ts.map +1 -1
- package/es/components/table/types/index.d.ts +16 -12
- package/es/components/table/types/index.d.ts.map +1 -1
- package/es/element-plus-pro.css +14 -14
- package/es/index.d.ts +9 -6
- package/es/index.d.ts.map +1 -1
- package/es/index.js +577 -432
- package/es/index.js.map +1 -1
- package/es/locale/index.d.ts +2 -1
- 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 +20 -2
- package/es/shared/ui/index.d.ts.map +1 -1
- package/package.json +8 -5
- package/src/components/component-provider/components/index.vue +48 -21
- package/src/components/component-provider/constants/index.ts +34 -6
- package/src/components/component-provider/types/index.ts +21 -7
- package/src/components/form/components/BaseField/index.vue +32 -15
- package/src/components/form/components/BaseField/utils/index.ts +16 -1
- package/src/components/form/components/BaseFormItem/index.vue +61 -15
- package/src/components/form/components/ContainerFragment/index.vue +3 -1
- package/src/components/form/components/ProForm/index.vue +54 -27
- package/src/components/form/components/SlotComponent/index.vue +4 -2
- package/src/components/form/components/index.ts +8 -1
- package/src/components/form/constants/index.ts +9 -2
- package/src/components/form/hooks/useFields.ts +5 -2
- package/src/components/form/hooks/useForm.ts +8 -1
- package/src/components/form/hooks/useGroupFieldAttrs.ts +29 -30
- package/src/components/form/index.ts +1 -3
- package/src/components/form/types/index.ts +71 -52
- package/src/components/form/utils/fieldKey.ts +27 -0
- package/src/components/table/components/ColumnControl.vue +20 -8
- package/src/components/table/components/ProTable.vue +194 -96
- package/src/components/table/components/SearchForm.vue +62 -23
- package/src/components/table/components/SizeControl.vue +23 -1
- package/src/components/table/components/TableColumnRenderer.vue +93 -61
- package/src/components/table/hooks/index.ts +1 -0
- package/src/components/table/{useTable.ts → hooks/useTable.ts} +6 -4
- package/src/components/table/index.ts +11 -2
- package/src/components/table/types/index.ts +17 -13
- package/src/index.ts +13 -7
- package/src/locale/index.ts +4 -2
- package/src/shared/core/index.ts +11 -0
- package/src/shared/ui/index.ts +29 -5
- package/tsconfig.json +22 -0
- 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
|
|
21
|
+
* @param {Fields} [fields] - 字段配置数组
|
|
21
22
|
* @param {boolean | GridProps} [grid] - 是否启用网格布局
|
|
23
|
+
* @param {boolean} [disabled] - 是否禁用所有子字段
|
|
22
24
|
*
|
|
23
25
|
* @internal
|
|
24
26
|
*/
|
|
25
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
|
51
|
-
(props.fields ?? []).filter(
|
|
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
|
-
*
|
|
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
|
|
82
|
-
|
|
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
|
|
96
|
-
componentInstanceMap.get(raw)?.getComponentRef()
|
|
97
|
-
|
|
98
|
-
|
|
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="
|
|
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,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
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
|
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:
|
|
81
|
+
grid: undefined,
|
|
61
82
|
form: undefined,
|
|
62
83
|
});
|
|
63
84
|
|
|
64
85
|
/**
|
|
65
86
|
* 依赖注入中心:
|
|
66
|
-
*
|
|
87
|
+
* 未传 form 实例(含运行期置空)时回退内部表单实例;
|
|
67
88
|
* 将 Form 实例注入到 Vue Context 中,供深层子组件跨层级直接访问数据和方法。
|
|
68
89
|
*/
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
*
|
|
153
|
+
* 向深层 BaseField 提供 ProForm 的具名插槽,父级运行期增删插槽后下游可感知
|
|
131
154
|
*/
|
|
132
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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())
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
| string
|
|
102
|
-
| number[]
|
|
103
|
-
| undefined;
|
|
102
|
+
// 字段标识经统一解析(prop 优先,path 兜底);name 作为控件自身属性经 rest 透传
|
|
104
103
|
return {
|
|
105
|
-
path:
|
|
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:
|
|
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 {
|