@qin-ui/antd-vue-pro 2.5.1 → 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/antd-vue-pro.css +6 -6
- 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/components/table/components/ProTable.vue.d.ts.map +1 -1
- package/es/index.js +318 -161
- package/es/index.js.map +1 -1
- package/package.json +2 -2
- 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
- package/src/components/table/components/ProTable.vue +24 -1
|
@@ -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
|
+
};
|
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
* 2. 分页变化:分页参数同步后发起查询
|
|
33
33
|
* 3. 重置:查询参数恢复初始值后重新查询
|
|
34
34
|
*
|
|
35
|
+
* ## 列来源
|
|
36
|
+
*
|
|
37
|
+
* 显式 `:columns` 优先于 `:table` 实例的 `columns`;显式列按原样透传给宿主,不补齐 `key`,
|
|
38
|
+
* 需由调用方提供 `key` / `dataIndex` / `prop`。显式模式下 `setColumn` 与列控入口均不生效。
|
|
39
|
+
*
|
|
35
40
|
* @template T - 表格实例类型(useTable 返回值)
|
|
36
41
|
*
|
|
37
42
|
* @param {T} [table] - 列表表格对象,来自 useTable
|
|
@@ -364,7 +369,7 @@ const indexColumn = computed<ColumnType>(() => ({
|
|
|
364
369
|
customRender: ({ index }) => getIndex(index),
|
|
365
370
|
}));
|
|
366
371
|
|
|
367
|
-
// 列来源优先级:显式绑定 > table 实例(useTable 创建的 columns
|
|
372
|
+
// 列来源优先级:显式绑定 > table 实例(useTable 创建的 columns);显式列原样透传,不补齐 key
|
|
368
373
|
const computedColumns = computed<Columns<RecordType>>(() => {
|
|
369
374
|
const cols = (resolveOwnedProp('columns') ??
|
|
370
375
|
toValue(props.table?.columns) ??
|
|
@@ -454,8 +459,26 @@ const computedPagination = computed<PaginationProps | false>(() => {
|
|
|
454
459
|
};
|
|
455
460
|
});
|
|
456
461
|
|
|
462
|
+
// 显式 pagination 的 value 键覆盖 pageParam 时,宿主回写不会反映到分页
|
|
463
|
+
let warnedPaginationShadow = false;
|
|
464
|
+
const warnPaginationShadow = (keys: Array<'current' | 'pageSize'>) => {
|
|
465
|
+
if (warnedPaginationShadow) return;
|
|
466
|
+
warnedPaginationShadow = true;
|
|
467
|
+
console.warn(
|
|
468
|
+
`[ProTable] 显式 :pagination 的 ${keys.join(' / ')} 覆盖了 table.pageParam,翻页或修改页大小后分页不会更新;请移除显式值或在回调中同步更新绑定。`
|
|
469
|
+
);
|
|
470
|
+
};
|
|
471
|
+
|
|
457
472
|
const onTableChange: TableProps['onChange'] = (...args) => {
|
|
458
473
|
props.table?.setPageParam?.(args[0]);
|
|
474
|
+
const nextPagination = computedPagination.value;
|
|
475
|
+
if (props.table?.pageParam && nextPagination !== false) {
|
|
476
|
+
const shadowed = (['current', 'pageSize'] as const).filter(
|
|
477
|
+
key =>
|
|
478
|
+
args[0]?.[key] !== undefined && nextPagination[key] !== args[0][key]
|
|
479
|
+
);
|
|
480
|
+
if (shadowed.length) warnPaginationShadow(shadowed);
|
|
481
|
+
}
|
|
459
482
|
// change 回放:模板监听器优先,未绑定时回退 Provider 全局配置
|
|
460
483
|
(attrs.onChange ?? injectProps.value.onChange)?.(...args);
|
|
461
484
|
_search();
|