@qin-ui/antd-vue-pro 2.5.2 → 2.5.3
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/api.json +2 -2
- package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -1
- package/es/components/form/components/BaseFormItem/index.vue.d.ts +59 -11
- package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -1
- package/es/components/form/components/BaseFormItems/index.vue.d.ts +16 -0
- package/es/components/form/components/BaseFormItems/index.vue.d.ts.map +1 -0
- package/es/components/form/components/index.d.ts +6 -6
- package/es/components/form/components/index.d.ts.map +1 -1
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts +13 -3
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -1
- package/es/components/form/utils/fieldKey.d.ts +12 -0
- package/es/components/form/utils/fieldKey.d.ts.map +1 -1
- package/es/index.js +297 -158
- package/es/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/components/BaseField/index.vue +30 -9
- package/src/components/form/components/BaseFormItem/index.vue +153 -186
- package/src/components/form/components/BaseFormItems/index.vue +186 -0
- package/src/components/form/components/ProForm/index.vue +5 -5
- package/src/components/form/components/index.ts +6 -6
- package/src/components/form/hooks/useGroupFieldAttrs.ts +114 -88
- package/src/components/form/utils/fieldKey.ts +63 -0
|
@@ -3,12 +3,12 @@ import type {
|
|
|
3
3
|
ComponentExposed,
|
|
4
4
|
} from 'vue-component-type-helpers';
|
|
5
5
|
import ProForm from './ProForm/index.vue';
|
|
6
|
-
import
|
|
6
|
+
import BaseFormItems from './BaseFormItems/index.vue';
|
|
7
7
|
|
|
8
8
|
export { default as BaseField } from './BaseField/index.vue';
|
|
9
9
|
export { default as ContainerFragment } from './ContainerFragment/index.vue';
|
|
10
10
|
export { default as SlotComponent } from './SlotComponent/index.vue';
|
|
11
|
-
export { ProForm,
|
|
11
|
+
export { ProForm, BaseFormItems };
|
|
12
12
|
|
|
13
13
|
// instance
|
|
14
14
|
/** ProForm 的组件实例类型,用于 `ref` 标注与 `defineExpose` 访问 */
|
|
@@ -16,7 +16,7 @@ export type ProFormInstance = ComponentExposed<typeof ProForm>;
|
|
|
16
16
|
/** ProForm 的组件属性类型 */
|
|
17
17
|
export type ProFormProps = ComponentProps<typeof ProForm>;
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
export type
|
|
21
|
-
/**
|
|
22
|
-
export type
|
|
19
|
+
/** BaseFormItems 的组件实例类型 */
|
|
20
|
+
export type ProFormItemsInstance = ComponentExposed<typeof BaseFormItems>;
|
|
21
|
+
/** BaseFormItems 的组件属性类型 */
|
|
22
|
+
export type ProFormItemsProps = ComponentProps<typeof BaseFormItems>;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
inject,
|
|
4
|
+
mergeProps,
|
|
5
|
+
toValue,
|
|
6
|
+
type ComputedRef,
|
|
7
|
+
type MaybeRefOrGetter,
|
|
8
|
+
} from 'vue';
|
|
3
9
|
import {
|
|
4
10
|
formItemProps,
|
|
5
11
|
gridItemProps,
|
|
@@ -35,16 +41,102 @@ export interface GroupedField {
|
|
|
35
41
|
formItemContainer?: Base['formItemContainer'];
|
|
36
42
|
/** 字段原始对象,供实例登记与访问器挂载使用 */
|
|
37
43
|
rawField: Field;
|
|
38
|
-
/** 渲染 key,取自字段对象身份 */
|
|
39
|
-
renderKey: string | number;
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
/** 将单个字段配置拆分为 grid / formItem / component / slots 分组 */
|
|
47
|
+
const groupField = (
|
|
48
|
+
field: Field,
|
|
49
|
+
rawInject: Record<string, any>
|
|
50
|
+
): GroupedField => {
|
|
51
|
+
let gridItemProps: Record<string, any> = {};
|
|
52
|
+
let formItemProps: Record<string, any> = {};
|
|
53
|
+
const componentProps: Record<string, any> = { slots: {} };
|
|
54
|
+
const formItemSlots: Record<string, any> = {};
|
|
55
|
+
|
|
56
|
+
if (isPlainObject(field)) {
|
|
57
|
+
// prettier-ignore
|
|
58
|
+
const { path: _path, name: _name, fields, formItemClass, formItemStyle, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, formItemContainer, ...rest } =
|
|
59
|
+
field as any;
|
|
60
|
+
|
|
61
|
+
// prettier-ignore
|
|
62
|
+
const { class: injectClassName, style: injectStyle, span: injectSpan, xs, sm, md, lg, xl, xxl, formItemContainer: injectFormItemContainer, ...injectRest } = rawInject;
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* 字段显式指定 span 时整体接管栅格,不再叠加注入的响应式断点;
|
|
66
|
+
* 未指定 span 时使用注入的断点配置
|
|
67
|
+
*/
|
|
68
|
+
const initGridItemProps =
|
|
69
|
+
span || span === 0
|
|
70
|
+
? { span }
|
|
71
|
+
: { xs, sm, md, lg, xl, xxl, span: injectSpan };
|
|
72
|
+
|
|
73
|
+
// 分组字段未指定 span 时整行渲染(span 兜底 24),注入的响应式断点保持生效
|
|
74
|
+
gridItemProps = fields
|
|
75
|
+
? { ...initGridItemProps, span: span ?? 24 }
|
|
76
|
+
: initGridItemProps;
|
|
77
|
+
// 字段级容器优先,回退注入级配置
|
|
78
|
+
const container = formItemContainer ?? injectFormItemContainer;
|
|
79
|
+
formItemProps = mergeProps(
|
|
80
|
+
injectRest,
|
|
81
|
+
{ class: injectClassName, style: injectStyle },
|
|
82
|
+
{ class: toValue(formItemClass), style: toValue(formItemStyle) },
|
|
83
|
+
formItemDataAttrs
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
Object.keys(rest).forEach(k => {
|
|
87
|
+
if (gridItemPropKeySet.has(k)) {
|
|
88
|
+
gridItemProps[k] = rest[k];
|
|
89
|
+
} else if (formItemPropKeySet.has(k)) {
|
|
90
|
+
formItemProps[k] = rest[k];
|
|
91
|
+
} else {
|
|
92
|
+
componentProps[k] = rest[k];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
Object.assign(componentProps, componentDataAttrs);
|
|
97
|
+
Object.keys(slots).forEach(k => {
|
|
98
|
+
if (formItemSlotKeySet.has(k)) {
|
|
99
|
+
formItemSlots[k] = slots[k];
|
|
100
|
+
} else {
|
|
101
|
+
componentProps.slots[k] = slots[k];
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// label 插槽与字段 label 同时存在时由插槽渲染,剥离 label 属性
|
|
106
|
+
if (formItemSlots.label && formItemProps.label !== undefined) {
|
|
107
|
+
delete formItemProps.label;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
path: resolveFieldIdentity(field),
|
|
112
|
+
gridItemProps: gridItemProps as GridItemProps,
|
|
113
|
+
formItemProps: formItemProps as any,
|
|
114
|
+
formItemSlots,
|
|
115
|
+
componentProps,
|
|
116
|
+
formItemContainer: container,
|
|
117
|
+
rawField: field,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
path: resolveFieldIdentity(field),
|
|
123
|
+
gridItemProps: gridItemProps as GridItemProps,
|
|
124
|
+
formItemProps: formItemProps as any,
|
|
125
|
+
formItemSlots,
|
|
126
|
+
componentProps,
|
|
127
|
+
rawField: field,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
42
131
|
/**
|
|
43
132
|
* 将 fields 配置数组逐个拆分为 grid / formItem / component / slots 分组。
|
|
44
133
|
*
|
|
45
134
|
* @description
|
|
46
135
|
* 1. 响应式集成 ProComponentProvider 的 `pro-form-item` 全局变量配置;
|
|
47
136
|
* 2. 剥离 `extraProps`(用户自定义字段标识袋):保留在字段原始对象上供业务层存取,渲染层经解构剥离,不落入 componentProps 透传。
|
|
137
|
+
*
|
|
138
|
+
* @param {ComputedRef<Field[]>} validFields - 已过滤隐藏字段的字段配置数组
|
|
139
|
+
* @returns 字段渲染分组数组
|
|
48
140
|
*/
|
|
49
141
|
export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
50
142
|
const config = INJECT_CONFIG['pro-form-item'];
|
|
@@ -52,89 +144,23 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
|
52
144
|
|
|
53
145
|
return computed<GroupedField[]>(() => {
|
|
54
146
|
const rawInject = toValue(injectFormItemProps) || {};
|
|
55
|
-
return validFields.value.map(field =>
|
|
56
|
-
let gridItemProps: Record<string, any> = {};
|
|
57
|
-
let formItemProps: Record<string, any> = {};
|
|
58
|
-
const componentProps: Record<string, any> = { slots: {} };
|
|
59
|
-
const formItemSlots: Record<string, any> = {};
|
|
60
|
-
|
|
61
|
-
if (isPlainObject(field)) {
|
|
62
|
-
// prettier-ignore
|
|
63
|
-
const { path: _path, name: _name, fields, formItemClass, formItemStyle, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, formItemContainer, ...rest } =
|
|
64
|
-
field as any;
|
|
65
|
-
|
|
66
|
-
// prettier-ignore
|
|
67
|
-
const { class: injectClassName, style: injectStyle, span: injectSpan, xs, sm, md, lg, xl, xxl, formItemContainer: injectFormItemContainer, ...injectRest } = rawInject;
|
|
68
|
-
|
|
69
|
-
/*
|
|
70
|
-
* 字段显式指定 span 时整体接管栅格,不再叠加注入的响应式断点;
|
|
71
|
-
* 未指定 span 时使用注入的断点配置
|
|
72
|
-
*/
|
|
73
|
-
const initGridItemProps =
|
|
74
|
-
span || span === 0
|
|
75
|
-
? { span }
|
|
76
|
-
: { xs, sm, md, lg, xl, xxl, span: injectSpan };
|
|
77
|
-
|
|
78
|
-
// 分组字段未指定 span 时整行渲染(span 兜底 24),注入的响应式断点保持生效
|
|
79
|
-
gridItemProps = fields
|
|
80
|
-
? { ...initGridItemProps, span: span ?? 24 }
|
|
81
|
-
: initGridItemProps;
|
|
82
|
-
// 字段级容器优先,回退注入级配置
|
|
83
|
-
const container = formItemContainer ?? injectFormItemContainer;
|
|
84
|
-
formItemProps = mergeProps(
|
|
85
|
-
injectRest,
|
|
86
|
-
{ class: injectClassName, style: injectStyle },
|
|
87
|
-
{ class: toValue(formItemClass), style: toValue(formItemStyle) },
|
|
88
|
-
formItemDataAttrs
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
Object.keys(rest).forEach(k => {
|
|
92
|
-
if (gridItemPropKeySet.has(k)) {
|
|
93
|
-
gridItemProps[k] = rest[k];
|
|
94
|
-
} else if (formItemPropKeySet.has(k)) {
|
|
95
|
-
formItemProps[k] = rest[k];
|
|
96
|
-
} else {
|
|
97
|
-
componentProps[k] = rest[k];
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
Object.assign(componentProps, componentDataAttrs);
|
|
102
|
-
Object.keys(slots).forEach(k => {
|
|
103
|
-
if (formItemSlotKeySet.has(k)) {
|
|
104
|
-
formItemSlots[k] = slots[k];
|
|
105
|
-
} else {
|
|
106
|
-
componentProps.slots[k] = slots[k];
|
|
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);
|
|
116
|
-
return {
|
|
117
|
-
path: raw,
|
|
118
|
-
gridItemProps: gridItemProps as GridItemProps,
|
|
119
|
-
formItemProps: formItemProps as any,
|
|
120
|
-
formItemSlots,
|
|
121
|
-
componentProps,
|
|
122
|
-
formItemContainer: container,
|
|
123
|
-
rawField: field,
|
|
124
|
-
renderKey: getFieldKey(field),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const raw = resolveFieldIdentity(field);
|
|
129
|
-
return {
|
|
130
|
-
path: raw,
|
|
131
|
-
gridItemProps: gridItemProps as GridItemProps,
|
|
132
|
-
formItemProps: formItemProps as any,
|
|
133
|
-
formItemSlots,
|
|
134
|
-
componentProps,
|
|
135
|
-
rawField: field,
|
|
136
|
-
renderKey: getFieldKey(field),
|
|
137
|
-
};
|
|
138
|
-
});
|
|
147
|
+
return validFields.value.map(field => groupField(field, rawInject));
|
|
139
148
|
});
|
|
140
149
|
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 将单个字段配置拆分为 grid / formItem / component / slots 分组
|
|
153
|
+
*
|
|
154
|
+
* @description 拆分规则与 `useGroupFieldAttrs` 一致,结果依赖该字段自身配置与 Provider 的 `pro-form-item` 默认值。
|
|
155
|
+
*
|
|
156
|
+
* @param {MaybeRefOrGetter<Field>} field - 待拆分的字段配置
|
|
157
|
+
* @returns {ComputedRef<GroupedField>} 单字段渲染分组
|
|
158
|
+
*/
|
|
159
|
+
export function useGroupedField(field: MaybeRefOrGetter<Field>) {
|
|
160
|
+
const config = INJECT_CONFIG['pro-form-item'];
|
|
161
|
+
const injectFormItemProps = inject(config.injectionKey, config.default);
|
|
162
|
+
|
|
163
|
+
return computed<GroupedField>(() =>
|
|
164
|
+
groupField(toValue(field), toValue(injectFormItemProps) || {})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import { toRaw } from 'vue';
|
|
2
|
+
import { resolveFieldIdentity, toPath } from '../../../shared/core';
|
|
2
3
|
|
|
3
4
|
const fieldKeyMap = new WeakMap<object, number>();
|
|
4
5
|
let fieldKeySeed = 0;
|
|
5
6
|
|
|
7
|
+
/** 数据行对象身份编号 */
|
|
8
|
+
const rowKeyMap = new WeakMap<object, number>();
|
|
9
|
+
let rowKeySeed = 0;
|
|
10
|
+
|
|
11
|
+
const getRowKey = (row: object) => {
|
|
12
|
+
const raw = toRaw(row);
|
|
13
|
+
let key = rowKeyMap.get(raw);
|
|
14
|
+
if (key === undefined) {
|
|
15
|
+
key = ++rowKeySeed;
|
|
16
|
+
rowKeyMap.set(raw, key);
|
|
17
|
+
}
|
|
18
|
+
return key;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** 数组下标段判定:仅十进制非负整数段按数组下标处理 */
|
|
22
|
+
const isIndexSegment = (segment: string) => /^(?:0|[1-9]\d*)$/.test(segment);
|
|
23
|
+
|
|
6
24
|
/**
|
|
7
25
|
* 字段渲染 key
|
|
8
26
|
*
|
|
@@ -25,3 +43,48 @@ export const getFieldKey = (field: unknown): string | number => {
|
|
|
25
43
|
}
|
|
26
44
|
return key;
|
|
27
45
|
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 字段的数据行渲染 key
|
|
49
|
+
*
|
|
50
|
+
* @description 沿字段路径逐段解析原始表单数据,取路径经过的对象数组行元素身份与行内剩余路径组成 key;
|
|
51
|
+
* 同一数据行对象在数组增删 / 重排后恒得同一 key。路径不经过对象数组行、行元素为原始值或数据缺失时,
|
|
52
|
+
* 回退到 {@link getFieldKey} 的字段对象身份。同 key 的去重由调用方按兄弟列表收敛。
|
|
53
|
+
*
|
|
54
|
+
* @param field - 字段配置项
|
|
55
|
+
* @param rawFormData - 原始表单数据(非响应式对象)
|
|
56
|
+
* @returns 数据行 key;无法定位数据行时返回字段对象身份 key
|
|
57
|
+
*/
|
|
58
|
+
export const getFieldDataKey = (
|
|
59
|
+
field: unknown,
|
|
60
|
+
rawFormData: unknown
|
|
61
|
+
): string | number => {
|
|
62
|
+
const path = resolveFieldIdentity(field);
|
|
63
|
+
if (!path || rawFormData === null || rawFormData === undefined) {
|
|
64
|
+
return getFieldKey(field);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const segments = toPath(path);
|
|
68
|
+
let node: any = rawFormData;
|
|
69
|
+
const rowIds: number[] = [];
|
|
70
|
+
let lastRowIndex = -1;
|
|
71
|
+
|
|
72
|
+
for (let i = 0; i < segments.length; i++) {
|
|
73
|
+
if (Array.isArray(node)) {
|
|
74
|
+
const segment = segments[i];
|
|
75
|
+
if (!isIndexSegment(segment)) break;
|
|
76
|
+
const element = node[Number(segment)];
|
|
77
|
+
if (element === null || typeof element !== 'object') break;
|
|
78
|
+
rowIds.push(getRowKey(toRaw(element) as object));
|
|
79
|
+
lastRowIndex = i;
|
|
80
|
+
node = element;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (node === null || typeof node !== 'object') break;
|
|
84
|
+
node = node[segments[i]];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (lastRowIndex === -1) return getFieldKey(field);
|
|
88
|
+
const rest = segments.slice(lastRowIndex + 1).join('.');
|
|
89
|
+
return `data:${rowIds.join('/')}:${rest}`;
|
|
90
|
+
};
|