@qin-ui/element-plus-pro 1.0.8 → 1.1.1
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 +49 -37
- package/LICENSE +9 -0
- package/api.json +45 -28
- package/bin/init-ai.mjs +0 -0
- package/es/components/component-provider/components/index.vue.d.ts +27 -0
- package/es/components/component-provider/components/index.vue.d.ts.map +1 -0
- package/es/components/component-provider/constants/index.d.ts +16 -0
- package/es/components/component-provider/constants/index.d.ts.map +1 -0
- package/es/components/component-provider/index.d.ts +4 -0
- package/es/components/component-provider/index.d.ts.map +1 -0
- package/es/components/component-provider/types/index.d.ts +35 -0
- package/es/components/component-provider/types/index.d.ts.map +1 -0
- package/es/components/form/components/BaseField/index.vue.d.ts +13 -0
- package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -0
- package/es/components/form/components/BaseField/utils/index.d.ts +3 -0
- package/es/components/form/components/BaseField/utils/index.d.ts.map +1 -0
- package/es/components/form/components/BaseFormItem/index.vue.d.ts +9 -0
- package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -0
- package/es/components/form/components/ContainerFragment/index.vue.d.ts +23 -0
- package/es/components/form/components/ContainerFragment/index.vue.d.ts.map +1 -0
- package/es/components/form/components/GroupedFieldAttrs/index.vue.d.ts +31 -0
- package/es/components/form/components/GroupedFieldAttrs/index.vue.d.ts.map +1 -0
- package/es/components/form/components/PathProvider/index.vue.d.ts +24 -0
- package/es/components/form/components/PathProvider/index.vue.d.ts.map +1 -0
- package/es/components/form/components/ProForm/index.vue.d.ts +36 -0
- package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -0
- package/es/components/form/components/SlotComponent/index.vue.d.ts +7 -0
- package/es/components/form/components/SlotComponent/index.vue.d.ts.map +1 -0
- package/es/components/form/components/index.d.ts +12 -0
- package/es/components/form/components/index.d.ts.map +1 -0
- package/es/components/form/constants/index.d.ts +67 -0
- package/es/components/form/constants/index.d.ts.map +1 -0
- package/es/components/form/hooks/index.d.ts +5 -0
- package/es/components/form/hooks/index.d.ts.map +1 -0
- package/es/components/form/hooks/useFields.d.ts +57 -0
- package/es/components/form/hooks/useFields.d.ts.map +1 -0
- package/es/components/form/hooks/useForm.d.ts +93 -0
- package/es/components/form/hooks/useForm.d.ts.map +1 -0
- package/es/components/form/hooks/useFormRef.d.ts +30 -0
- package/es/components/form/hooks/useFormRef.d.ts.map +1 -0
- package/es/components/form/index.d.ts +7 -0
- package/es/components/form/index.d.ts.map +1 -0
- package/es/components/form/types/index.d.ts +234 -0
- package/es/components/form/types/index.d.ts.map +1 -0
- package/es/components/table/components/ColumnControl.vue.d.ts +9 -0
- package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -0
- package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts +18 -0
- package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +1 -0
- package/es/components/table/components/DefaultTableContainer.vue.d.ts +18 -0
- package/es/components/table/components/DefaultTableContainer.vue.d.ts.map +1 -0
- package/es/components/table/components/ProTable.vue.d.ts +47 -0
- package/es/components/table/components/ProTable.vue.d.ts.map +1 -0
- package/es/components/table/components/SearchForm.vue.d.ts +48 -0
- package/es/components/table/components/SearchForm.vue.d.ts.map +1 -0
- package/es/components/table/components/SizeControl.vue.d.ts +12 -0
- package/es/components/table/components/SizeControl.vue.d.ts.map +1 -0
- package/es/components/table/components/TableColumnRenderer.vue.d.ts +17 -0
- package/es/components/table/components/TableColumnRenderer.vue.d.ts.map +1 -0
- package/es/components/table/components/icons/ColumnHeightOutlined.vue.d.ts +3 -0
- package/es/components/table/components/icons/ColumnHeightOutlined.vue.d.ts.map +1 -0
- package/es/components/table/components/icons/DownOutlined.vue.d.ts +3 -0
- package/es/components/table/components/icons/DownOutlined.vue.d.ts.map +1 -0
- package/es/components/table/components/icons/FilterOutlined.vue.d.ts +3 -0
- package/es/components/table/components/icons/FilterOutlined.vue.d.ts.map +1 -0
- package/es/components/table/components/index.d.ts +5 -0
- package/es/components/table/components/index.d.ts.map +1 -0
- package/es/components/table/index.d.ts +33 -0
- package/es/components/table/index.d.ts.map +1 -0
- package/es/components/table/types/index.d.ts +67 -0
- package/es/components/table/types/index.d.ts.map +1 -0
- package/es/components/table/useTable.d.ts +111 -0
- package/es/components/table/useTable.d.ts.map +1 -0
- package/es/element-plus-pro.css +10 -10
- package/es/index.d.ts +76 -1472
- package/es/index.d.ts.map +1 -0
- package/es/index.js +1534 -29
- package/es/index.js.map +1 -0
- package/es/shared/core/index.d.ts +6 -0
- package/es/shared/core/index.d.ts.map +1 -0
- package/es/shared/ui/index.d.ts +12 -0
- package/es/shared/ui/index.d.ts.map +1 -0
- package/package.json +20 -13
- package/src/components/component-provider/components/index.vue +67 -0
- package/src/components/component-provider/constants/index.ts +209 -0
- package/src/components/component-provider/index.ts +6 -0
- package/src/components/component-provider/types/index.ts +64 -0
- package/src/components/form/components/BaseField/index.vue +181 -0
- package/src/components/form/components/BaseField/utils/index.ts +15 -0
- package/src/components/form/components/BaseFormItem/index.vue +170 -0
- package/src/components/form/components/ContainerFragment/index.vue +24 -0
- package/src/components/form/components/GroupedFieldAttrs/index.vue +115 -0
- package/src/components/form/components/PathProvider/index.vue +19 -0
- package/src/components/form/components/ProForm/index.vue +106 -0
- package/src/components/form/components/SlotComponent/index.vue +26 -0
- package/src/components/form/components/index.ts +15 -0
- package/src/components/form/constants/index.ts +116 -0
- package/src/components/form/hooks/index.ts +5 -0
- package/src/components/form/hooks/useFields.ts +67 -0
- package/src/components/form/hooks/useForm.ts +110 -0
- package/src/components/form/hooks/useFormRef.ts +35 -0
- package/src/components/form/index.ts +29 -0
- package/src/components/form/types/index.ts +300 -0
- package/src/components/table/components/ColumnControl.vue +136 -0
- package/src/components/table/components/DefaultSearchFormContainer.vue +14 -0
- package/src/components/table/components/DefaultTableContainer.vue +14 -0
- package/src/components/table/components/ProTable.vue +420 -0
- package/src/components/table/components/SearchForm.vue +302 -0
- package/src/components/table/components/SizeControl.vue +55 -0
- package/src/components/table/components/TableColumnRenderer.vue +71 -0
- package/src/components/table/components/icons/ColumnHeightOutlined.vue +4 -0
- package/src/components/table/components/icons/DownOutlined.vue +4 -0
- package/src/components/table/components/icons/FilterOutlined.vue +4 -0
- package/src/components/table/components/index.ts +4 -0
- package/src/components/table/index.ts +38 -0
- package/src/components/table/types/index.ts +77 -0
- package/src/components/table/useTable.ts +144 -0
- package/src/index.ts +81 -0
- package/src/shared/core/index.ts +38 -0
- package/src/shared/ui/index.ts +106 -0
- package/es/component-provider/index-Bmx9Q9Q-.js +0 -220
- package/es/component-provider/index.js +0 -4
- package/es/core/index-C0Pbv44C.js +0 -360
- package/es/form/index-D8btGgqa.js +0 -470
- package/es/form/index.js +0 -13
- package/es/table/index.js +0 -856
- package/es/vendor/utils/lodash-es-p6jau26B.js +0 -1120
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { RequiredComponentVars } from '../types';
|
|
2
|
+
import { InjectionKey, Component } from 'vue';
|
|
3
|
+
|
|
4
|
+
export type InjectConfigEntry<T = any> = {
|
|
5
|
+
injectionKey: InjectionKey<T>;
|
|
6
|
+
default: T;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const INJECT_CONFIG: {
|
|
10
|
+
[key in keyof RequiredComponentVars]: {
|
|
11
|
+
injectionKey: InjectionKey<RequiredComponentVars[key]>;
|
|
12
|
+
default: RequiredComponentVars[key];
|
|
13
|
+
};
|
|
14
|
+
} = {
|
|
15
|
+
'pro-table': {
|
|
16
|
+
injectionKey: Symbol(''),
|
|
17
|
+
default: {
|
|
18
|
+
pagination: {
|
|
19
|
+
layout: 'total, sizes, prev, pager, next, jumper',
|
|
20
|
+
pageSizes: [10, 20, 30, 40, 50, 100],
|
|
21
|
+
background: true,
|
|
22
|
+
},
|
|
23
|
+
searchFormConfig: {
|
|
24
|
+
layout: 'grid',
|
|
25
|
+
expand: { minExpandRows: 2, expandStatus: false },
|
|
26
|
+
},
|
|
27
|
+
control: true,
|
|
28
|
+
addIndexColumn: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
'pro-form': {
|
|
32
|
+
injectionKey: Symbol(''),
|
|
33
|
+
default: { grid: { gutter: 24 } } as any,
|
|
34
|
+
},
|
|
35
|
+
'pro-form-item': {
|
|
36
|
+
injectionKey: Symbol(''),
|
|
37
|
+
default: { span: 8 } as any,
|
|
38
|
+
},
|
|
39
|
+
input: {
|
|
40
|
+
injectionKey: Symbol(''),
|
|
41
|
+
default: { maxlength: 100, clearable: true, placeholder: '请输入' },
|
|
42
|
+
},
|
|
43
|
+
'input.textarea': {
|
|
44
|
+
injectionKey: Symbol(''),
|
|
45
|
+
default: {
|
|
46
|
+
maxlength: 200,
|
|
47
|
+
autosize: { minRows: 3, maxRows: 6 },
|
|
48
|
+
showWordLimit: true,
|
|
49
|
+
clearable: true,
|
|
50
|
+
placeholder: '请输入',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
'input.password': {
|
|
54
|
+
injectionKey: Symbol(''),
|
|
55
|
+
default: {
|
|
56
|
+
showPassword: true,
|
|
57
|
+
maxlength: 100,
|
|
58
|
+
clearable: true,
|
|
59
|
+
placeholder: '请输入',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
'input-number': {
|
|
63
|
+
injectionKey: Symbol(''),
|
|
64
|
+
default: {
|
|
65
|
+
max: 10 ** 15 - 1,
|
|
66
|
+
min: -(10 ** 15 + 1),
|
|
67
|
+
controls: false,
|
|
68
|
+
placeholder: '请输入',
|
|
69
|
+
style: { width: '100%' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
autocomplete: {
|
|
73
|
+
injectionKey: Symbol(''),
|
|
74
|
+
default: { clearable: true, placeholder: '请输入' },
|
|
75
|
+
},
|
|
76
|
+
select: {
|
|
77
|
+
injectionKey: Symbol(''),
|
|
78
|
+
default: { clearable: true, placeholder: '请选择' },
|
|
79
|
+
},
|
|
80
|
+
cascader: {
|
|
81
|
+
injectionKey: Symbol(''),
|
|
82
|
+
default: { clearable: true, placeholder: '请选择' },
|
|
83
|
+
},
|
|
84
|
+
'date-picker': {
|
|
85
|
+
injectionKey: Symbol(''),
|
|
86
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
87
|
+
},
|
|
88
|
+
'date-picker.date': {
|
|
89
|
+
injectionKey: Symbol(''),
|
|
90
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
91
|
+
},
|
|
92
|
+
'date-picker.dates': {
|
|
93
|
+
injectionKey: Symbol(''),
|
|
94
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
95
|
+
},
|
|
96
|
+
'date-picker.daterange': {
|
|
97
|
+
injectionKey: Symbol(''),
|
|
98
|
+
default: {
|
|
99
|
+
style: { width: '100%' },
|
|
100
|
+
startPlaceholder: '开始日期',
|
|
101
|
+
endPlaceholder: '结束日期',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
'date-picker.week': {
|
|
105
|
+
injectionKey: Symbol(''),
|
|
106
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
107
|
+
},
|
|
108
|
+
'date-picker.month': {
|
|
109
|
+
injectionKey: Symbol(''),
|
|
110
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
111
|
+
},
|
|
112
|
+
'date-picker.monthrange': {
|
|
113
|
+
injectionKey: Symbol(''),
|
|
114
|
+
default: {
|
|
115
|
+
style: { width: '100%' },
|
|
116
|
+
startPlaceholder: '开始月份',
|
|
117
|
+
endPlaceholder: '结束月份',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
'date-picker.months': {
|
|
121
|
+
injectionKey: Symbol(''),
|
|
122
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
123
|
+
},
|
|
124
|
+
'date-picker.yearrange': {
|
|
125
|
+
injectionKey: Symbol(''),
|
|
126
|
+
default: {
|
|
127
|
+
style: { width: '100%' },
|
|
128
|
+
startPlaceholder: '开始年份',
|
|
129
|
+
endPlaceholder: '结束年份',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
'date-picker.year': {
|
|
133
|
+
injectionKey: Symbol(''),
|
|
134
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
135
|
+
},
|
|
136
|
+
'date-picker.years': {
|
|
137
|
+
injectionKey: Symbol(''),
|
|
138
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
139
|
+
},
|
|
140
|
+
'date-picker.datetime': {
|
|
141
|
+
injectionKey: Symbol(''),
|
|
142
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
143
|
+
},
|
|
144
|
+
'date-picker.datetimerange': {
|
|
145
|
+
injectionKey: Symbol(''),
|
|
146
|
+
default: {
|
|
147
|
+
style: { width: '100%' },
|
|
148
|
+
startPlaceholder: '开始时间',
|
|
149
|
+
endPlaceholder: '结束时间',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
'time-picker': {
|
|
153
|
+
injectionKey: Symbol(''),
|
|
154
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
155
|
+
},
|
|
156
|
+
'time-select': {
|
|
157
|
+
injectionKey: Symbol(''),
|
|
158
|
+
default: { style: { width: '100%' }, placeholder: '请选择' },
|
|
159
|
+
},
|
|
160
|
+
'checkbox-group': {
|
|
161
|
+
injectionKey: Symbol(''),
|
|
162
|
+
default: {},
|
|
163
|
+
},
|
|
164
|
+
'radio-group': {
|
|
165
|
+
injectionKey: Symbol(''),
|
|
166
|
+
default: {},
|
|
167
|
+
},
|
|
168
|
+
switch: {
|
|
169
|
+
injectionKey: Symbol(''),
|
|
170
|
+
default: { modelProp: 'modelValue' },
|
|
171
|
+
},
|
|
172
|
+
slider: {
|
|
173
|
+
injectionKey: Symbol(''),
|
|
174
|
+
default: {},
|
|
175
|
+
},
|
|
176
|
+
'tree-select': {
|
|
177
|
+
injectionKey: Symbol(''),
|
|
178
|
+
default: { clearable: true, placeholder: '请选择' },
|
|
179
|
+
},
|
|
180
|
+
transfer: {
|
|
181
|
+
injectionKey: Symbol(''),
|
|
182
|
+
default: {},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const INJECT_COMPONENTS: InjectionKey<
|
|
187
|
+
Partial<Record<string, Component>>
|
|
188
|
+
> = Symbol('INJECT_COMPONENTS');
|
|
189
|
+
|
|
190
|
+
const DYNAMIC_INJECT_CONFIG: Record<string, InjectConfigEntry> =
|
|
191
|
+
Object.create(null);
|
|
192
|
+
|
|
193
|
+
export const getInjectConfig = (key: string): InjectConfigEntry | undefined => {
|
|
194
|
+
return (
|
|
195
|
+
(INJECT_CONFIG as Record<string, InjectConfigEntry>)[key] ||
|
|
196
|
+
DYNAMIC_INJECT_CONFIG[key]
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const ensureInjectConfig = (key: string): InjectConfigEntry => {
|
|
201
|
+
const existing = getInjectConfig(key);
|
|
202
|
+
if (existing) return existing;
|
|
203
|
+
const created: InjectConfigEntry = {
|
|
204
|
+
injectionKey: Symbol(`dynamic:${key}`),
|
|
205
|
+
default: {},
|
|
206
|
+
};
|
|
207
|
+
DYNAMIC_INJECT_CONFIG[key] = created;
|
|
208
|
+
return created;
|
|
209
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GridItemProps,
|
|
3
|
+
FormItemProps,
|
|
4
|
+
TableProps,
|
|
5
|
+
PaginationProps,
|
|
6
|
+
DatePicker,
|
|
7
|
+
} from '../../../shared/ui';
|
|
8
|
+
import { ComponentName, GetComponentType } from '../../form/constants';
|
|
9
|
+
import { Base, Grid } from '../../form/types';
|
|
10
|
+
import { ProFormProps } from '../../form';
|
|
11
|
+
import { AllowedComponentProps } from 'vue';
|
|
12
|
+
import type { ComponentProps } from 'vue-component-type-helpers';
|
|
13
|
+
|
|
14
|
+
type PP<T extends Record<string, any>> = Partial<T & AllowedComponentProps>;
|
|
15
|
+
|
|
16
|
+
type FP<T extends Record<string, any>> = Partial<
|
|
17
|
+
T &
|
|
18
|
+
Pick<
|
|
19
|
+
Base,
|
|
20
|
+
| 'valueFormatter'
|
|
21
|
+
| 'componentContainer'
|
|
22
|
+
| 'modelProp'
|
|
23
|
+
| 'componentClass'
|
|
24
|
+
| 'componentStyle'
|
|
25
|
+
> &
|
|
26
|
+
AllowedComponentProps
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
type DatePickerType = NonNullable<ComponentProps<typeof DatePicker>['type']>;
|
|
30
|
+
|
|
31
|
+
export type RequiredComponentVars = {
|
|
32
|
+
'pro-table': PP<
|
|
33
|
+
TableProps & {
|
|
34
|
+
pagination?: Partial<PaginationProps>;
|
|
35
|
+
control?: boolean | { sizeControl?: boolean; columnControl?: boolean };
|
|
36
|
+
searchFormConfig?: any;
|
|
37
|
+
immediateSearch?: boolean;
|
|
38
|
+
addIndexColumn?: boolean;
|
|
39
|
+
tableContainer?: any;
|
|
40
|
+
}
|
|
41
|
+
>;
|
|
42
|
+
'pro-form': PP<
|
|
43
|
+
Omit<ProFormProps, 'form' | 'grid'> & {
|
|
44
|
+
grid: Exclude<Grid, undefined | boolean>;
|
|
45
|
+
}
|
|
46
|
+
>;
|
|
47
|
+
'pro-form-item': PP<
|
|
48
|
+
FormItemProps &
|
|
49
|
+
Pick<GridItemProps, 'span' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'> &
|
|
50
|
+
Pick<Base, 'formItemContainer'>
|
|
51
|
+
>;
|
|
52
|
+
'input.password': FP<ComponentProps<GetComponentType<'input'>>>;
|
|
53
|
+
'input.textarea': FP<ComponentProps<GetComponentType<'input'>>>;
|
|
54
|
+
} & {
|
|
55
|
+
[K in DatePickerType as `date-picker.${K}`]: FP<
|
|
56
|
+
ComponentProps<GetComponentType<'date-picker'>>
|
|
57
|
+
>;
|
|
58
|
+
} & {
|
|
59
|
+
[K in Exclude<ComponentName, 'custom'>]: FP<
|
|
60
|
+
ComponentProps<GetComponentType<K>>
|
|
61
|
+
>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type ComponentVars = Partial<RequiredComponentVars>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
/**
|
|
3
|
+
* @component BaseField
|
|
4
|
+
* @description 字段级渲染组件,负责将 field 配置渲染为实际的输入组件
|
|
5
|
+
*
|
|
6
|
+
* ## 核心职责
|
|
7
|
+
*
|
|
8
|
+
* 1. **组件解析**:按优先级解析要渲染的组件(teleport 插槽 > 自定义组件 > 内置组件映射 > 原始值)
|
|
9
|
+
* 2. **v-model 双向绑定**:通过 computed getter/setter 将组件值与 formData 的深层路径绑定
|
|
10
|
+
* 3. **valueFormatter 处理**:支持 get/set 格式化函数,用于数据转换(如日期格式化)
|
|
11
|
+
* 4. **属性合并**:将注入的默认 props、组件传入的 attrs 合并为最终属性
|
|
12
|
+
*
|
|
13
|
+
* ## 组件解析优先级
|
|
14
|
+
*
|
|
15
|
+
* ```
|
|
16
|
+
* teleport 组件(ProForm 插槽注入)
|
|
17
|
+
* ↓ 未匹配
|
|
18
|
+
* 自定义组件(ProComponentProvider 的 componentMap)
|
|
19
|
+
* ↓ 未匹配
|
|
20
|
+
* 内置组件映射(componentMap: input → Input, select → Select ...)
|
|
21
|
+
* ↓ 未匹配
|
|
22
|
+
* 原始 component 值(可能是字符串或 Vue 组件对象)
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @param {string | Component} [component] - 组件名或组件对象
|
|
26
|
+
* @param {string} [path] - 字段路径,用于绑定表单数据
|
|
27
|
+
*
|
|
28
|
+
* @slot default - 默认插槽(透传给组件)
|
|
29
|
+
*/
|
|
30
|
+
import {
|
|
31
|
+
type Component,
|
|
32
|
+
computed,
|
|
33
|
+
inject,
|
|
34
|
+
mergeProps,
|
|
35
|
+
ref,
|
|
36
|
+
useAttrs,
|
|
37
|
+
} from 'vue';
|
|
38
|
+
import { cloneDeep, omit } from '../../../../shared/core';
|
|
39
|
+
import { useDisabledContext } from '../../../../shared/ui';
|
|
40
|
+
import type { Field } from '../../types';
|
|
41
|
+
import { ContainerFragment, SlotComponent } from '..';
|
|
42
|
+
import { componentMap, TeleportComponentNamePrefix } from '../../constants';
|
|
43
|
+
import { INJECT_COMPONENTS } from '../../../component-provider/constants';
|
|
44
|
+
import { useForm } from '../../hooks/useForm';
|
|
45
|
+
import { getInitProps } from './utils';
|
|
46
|
+
|
|
47
|
+
defineOptions({ name: 'BaseField', inheritAttrs: false });
|
|
48
|
+
|
|
49
|
+
type Props = {
|
|
50
|
+
component?: string | Component;
|
|
51
|
+
path?: string;
|
|
52
|
+
};
|
|
53
|
+
const { component = undefined, path = '' } = defineProps<Props>();
|
|
54
|
+
|
|
55
|
+
const form = useForm(false);
|
|
56
|
+
const { getFormData, setFormData } = form;
|
|
57
|
+
|
|
58
|
+
const componentRef = ref<any>();
|
|
59
|
+
|
|
60
|
+
const attrs: any = useAttrs();
|
|
61
|
+
|
|
62
|
+
function getOldValue() {
|
|
63
|
+
return cloneDeep(getFormData?.(path));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// setter: 向 formData 写入值,支持 valueFormatter / valueFormatter.set 转换
|
|
67
|
+
const value = computed({
|
|
68
|
+
get() {
|
|
69
|
+
let val = getFormData?.(path);
|
|
70
|
+
const { valueFormatter } = groupedAttrs.value;
|
|
71
|
+
if (
|
|
72
|
+
typeof valueFormatter === 'object' &&
|
|
73
|
+
typeof valueFormatter.get === 'function'
|
|
74
|
+
) {
|
|
75
|
+
val = valueFormatter.get(val);
|
|
76
|
+
}
|
|
77
|
+
return val;
|
|
78
|
+
},
|
|
79
|
+
set(val) {
|
|
80
|
+
let newVal = val;
|
|
81
|
+
const { valueFormatter } = groupedAttrs.value;
|
|
82
|
+
if (valueFormatter) {
|
|
83
|
+
if (typeof valueFormatter === 'function') {
|
|
84
|
+
newVal = valueFormatter(val, getOldValue());
|
|
85
|
+
} else if (typeof valueFormatter.set === 'function') {
|
|
86
|
+
newVal = valueFormatter.set(val, getOldValue());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
setFormData?.(path, newVal);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const parentDisabled = useDisabledContext();
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* 将注入的默认 props、外部传入的 attrs、field 配置合并为最终属性
|
|
97
|
+
* 属性分为三类:绑定属性(v-model)、组件属性(传递给组件)、插槽(动态渲染)
|
|
98
|
+
*/
|
|
99
|
+
const groupedAttrs = computed(() => {
|
|
100
|
+
const initProps = getInitProps({
|
|
101
|
+
component: component,
|
|
102
|
+
type: attrs.type,
|
|
103
|
+
} as unknown as Field);
|
|
104
|
+
const modelPropName = attrs.modelProp ?? initProps.modelProp ?? 'modelValue';
|
|
105
|
+
const mergedProps = mergeProps(
|
|
106
|
+
initProps,
|
|
107
|
+
attrs,
|
|
108
|
+
{ class: attrs.componentClass, style: attrs.componentStyle },
|
|
109
|
+
{ class: initProps.componentClass, style: initProps.componentStyle },
|
|
110
|
+
{ disabled: attrs.disabled ?? parentDisabled.value ?? initProps.disabled },
|
|
111
|
+
{ modelProp: modelPropName }
|
|
112
|
+
) as Required<Field>;
|
|
113
|
+
// prettier-ignore
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
115
|
+
const { valueFormatter, modelProp, slots, componentClass, componentStyle, componentContainer, ...rest } = mergedProps
|
|
116
|
+
const modelBindingProp = modelProp ?? 'modelValue';
|
|
117
|
+
// 从剩余属性中移除 v-model 绑定属性,避免重复绑定
|
|
118
|
+
const bindAttrs = omit(rest as Record<string, any>, [
|
|
119
|
+
modelBindingProp,
|
|
120
|
+
`onUpdate:${modelBindingProp}`,
|
|
121
|
+
]);
|
|
122
|
+
return {
|
|
123
|
+
attrs: bindAttrs,
|
|
124
|
+
slots,
|
|
125
|
+
componentContainer,
|
|
126
|
+
modelProp: modelBindingProp,
|
|
127
|
+
valueFormatter,
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// 从 ProForm 插槽注入的 teleport 组件(优先级最高)
|
|
132
|
+
const teleportComponent = inject(
|
|
133
|
+
`${TeleportComponentNamePrefix}${path}`,
|
|
134
|
+
undefined
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// 从 ProComponentProvider 注入的自定义组件映射
|
|
138
|
+
const customComponents = inject(INJECT_COMPONENTS, {});
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* 按优先级解析最终要渲染的组件:
|
|
142
|
+
* teleport 插槽 → 自定义组件映射 → 内置组件映射 → 原始 component 值
|
|
143
|
+
*/
|
|
144
|
+
const is = computed(() => {
|
|
145
|
+
if (teleportComponent) return teleportComponent;
|
|
146
|
+
if (typeof component === 'string') {
|
|
147
|
+
return (
|
|
148
|
+
(customComponents as any)[component] ||
|
|
149
|
+
(componentMap as any)[component] ||
|
|
150
|
+
component
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return component;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
defineExpose({
|
|
157
|
+
getComponentRef: () => componentRef.value,
|
|
158
|
+
getComponentComputedProps: () => groupedAttrs.value.attrs as any,
|
|
159
|
+
});
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<template>
|
|
163
|
+
<ContainerFragment :component="groupedAttrs.componentContainer" :path="path">
|
|
164
|
+
<component
|
|
165
|
+
:is="is"
|
|
166
|
+
v-if="is"
|
|
167
|
+
v-bind="groupedAttrs.attrs"
|
|
168
|
+
ref="componentRef"
|
|
169
|
+
v-model:[`${groupedAttrs.modelProp}`]="value"
|
|
170
|
+
:path="path"
|
|
171
|
+
>
|
|
172
|
+
<template
|
|
173
|
+
v-for="(slot, name) in groupedAttrs.slots"
|
|
174
|
+
:key="name"
|
|
175
|
+
#[name]="scoped"
|
|
176
|
+
>
|
|
177
|
+
<SlotComponent :path="path" v-bind="scoped" :component="slot" />
|
|
178
|
+
</template>
|
|
179
|
+
</component>
|
|
180
|
+
</ContainerFragment>
|
|
181
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Field } from '../../../types';
|
|
2
|
+
import { getInjectConfig } from '../../../../component-provider/constants';
|
|
3
|
+
import { inject } from 'vue';
|
|
4
|
+
|
|
5
|
+
export const getInitProps = (field: Field): Record<string, any> => {
|
|
6
|
+
const { component, type } = field as any;
|
|
7
|
+
if (typeof component === 'string') {
|
|
8
|
+
const k = [component, type].filter(Boolean).join('.');
|
|
9
|
+
const config = getInjectConfig(k);
|
|
10
|
+
if (!config) return {};
|
|
11
|
+
const injectProps = inject(config.injectionKey, config.default);
|
|
12
|
+
return injectProps;
|
|
13
|
+
}
|
|
14
|
+
return {};
|
|
15
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
/**
|
|
3
|
+
* @component BaseFormItem
|
|
4
|
+
* @description 表单字段渲染引擎
|
|
5
|
+
*
|
|
6
|
+
* 负责将字段配置数组渲染为实际的表单项,支持:
|
|
7
|
+
* - 字段分组(嵌套 fields 递归渲染)
|
|
8
|
+
* - 网格布局(grid 属性控制)
|
|
9
|
+
* - FormItem 包装(label、rules、校验等)
|
|
10
|
+
* - 组件绑定(通过 BaseField 渲染具体组件)
|
|
11
|
+
* - 插槽透传(label、error 等 FormItem 插槽)
|
|
12
|
+
*
|
|
13
|
+
* ## 渲染流程
|
|
14
|
+
*
|
|
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 对象上
|
|
22
|
+
*
|
|
23
|
+
* @param {Fields<D>} [fields] - 字段配置数组
|
|
24
|
+
* @param {boolean | GridProps} [grid] - 是否启用网格布局
|
|
25
|
+
* @param {boolean} [disabled] - 是否禁用所有子字段
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
import { getObject, toPath } from '../../../../shared/core';
|
|
30
|
+
import {
|
|
31
|
+
FormItem,
|
|
32
|
+
Grid as ElGrid,
|
|
33
|
+
GridItem as ElGridItem,
|
|
34
|
+
GridProps,
|
|
35
|
+
useDisabledContextProvider,
|
|
36
|
+
} from '../../../../shared/ui';
|
|
37
|
+
import { computed, inject, toValue } from 'vue';
|
|
38
|
+
import type { Fields, Grid } from '../../types';
|
|
39
|
+
import { BaseField, SlotComponent, ContainerFragment } from '..';
|
|
40
|
+
import PathProvider from '../PathProvider/index.vue';
|
|
41
|
+
import GroupedFieldAttrs from '../GroupedFieldAttrs/index.vue';
|
|
42
|
+
import { INJECT_CONFIG } from '../../../component-provider/constants';
|
|
43
|
+
|
|
44
|
+
defineOptions({ name: 'BaseFormItem', inheritAttrs: false });
|
|
45
|
+
|
|
46
|
+
type Props = {
|
|
47
|
+
grid?: Grid;
|
|
48
|
+
fields?: Fields;
|
|
49
|
+
disabled?: boolean;
|
|
50
|
+
};
|
|
51
|
+
const {
|
|
52
|
+
grid = undefined,
|
|
53
|
+
fields = [],
|
|
54
|
+
disabled = undefined,
|
|
55
|
+
} = defineProps<Props>();
|
|
56
|
+
|
|
57
|
+
const validFields = computed(() => fields.filter?.(field => !field.hidden));
|
|
58
|
+
|
|
59
|
+
useDisabledContextProvider(computed(() => disabled) as any);
|
|
60
|
+
|
|
61
|
+
const config = INJECT_CONFIG['pro-form'];
|
|
62
|
+
|
|
63
|
+
const { grid: injectGrid } = inject(config.injectionKey, config.default);
|
|
64
|
+
|
|
65
|
+
const enableGrid = computed(() => {
|
|
66
|
+
if (grid !== undefined) return !!grid;
|
|
67
|
+
return !!injectGrid;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const computedGridProps = computed<GridProps>(() => {
|
|
71
|
+
return enableGrid.value
|
|
72
|
+
? { ...getObject(injectGrid), ...getObject(grid) }
|
|
73
|
+
: {};
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const formItemRefs: any[] = [];
|
|
77
|
+
const componentRefs: any[] = [];
|
|
78
|
+
|
|
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
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
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
|
+
});
|
|
94
|
+
};
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<ContainerFragment
|
|
99
|
+
:component="enableGrid ? ElGrid : undefined"
|
|
100
|
+
v-bind="computedGridProps"
|
|
101
|
+
style="width: 100%"
|
|
102
|
+
>
|
|
103
|
+
<PathProvider
|
|
104
|
+
v-for="({ path: _path, ...field }, index) of validFields"
|
|
105
|
+
:key="toPath(toValue((_path ?? index) as any)).join('.') || index"
|
|
106
|
+
v-bind="{ path: _path as any }"
|
|
107
|
+
>
|
|
108
|
+
<template #default="{ path }">
|
|
109
|
+
<GroupedFieldAttrs :field="field">
|
|
110
|
+
<template
|
|
111
|
+
#default="{
|
|
112
|
+
gridItemProps,
|
|
113
|
+
formItemProps: { container, ...formItemProps },
|
|
114
|
+
componentProps,
|
|
115
|
+
formItemSlots,
|
|
116
|
+
}"
|
|
117
|
+
>
|
|
118
|
+
<ContainerFragment
|
|
119
|
+
:component="enableGrid ? ElGridItem : undefined"
|
|
120
|
+
v-bind="gridItemProps"
|
|
121
|
+
>
|
|
122
|
+
<ContainerFragment :component="container" :path="path">
|
|
123
|
+
<FormItem
|
|
124
|
+
v-bind="formItemProps"
|
|
125
|
+
:ref="el => (formItemRefs[index] = el)"
|
|
126
|
+
:prop="toPath(path).join('.')"
|
|
127
|
+
:path="path"
|
|
128
|
+
@vue:mounted="onFormItemMounted(index, formItemProps)"
|
|
129
|
+
>
|
|
130
|
+
<template
|
|
131
|
+
v-for="(slot, name) in formItemSlots"
|
|
132
|
+
:key="name"
|
|
133
|
+
#[name]="scoped"
|
|
134
|
+
>
|
|
135
|
+
<SlotComponent
|
|
136
|
+
:path="path"
|
|
137
|
+
:component="slot"
|
|
138
|
+
v-bind="scoped"
|
|
139
|
+
/>
|
|
140
|
+
</template>
|
|
141
|
+
<template v-if="field.fields">
|
|
142
|
+
<BaseFormItem
|
|
143
|
+
:grid="(field as any).grid ?? grid"
|
|
144
|
+
:fields="field.fields"
|
|
145
|
+
:disabled="toValue(field.disabled)"
|
|
146
|
+
/>
|
|
147
|
+
</template>
|
|
148
|
+
<template v-else>
|
|
149
|
+
<BaseField
|
|
150
|
+
:ref="el => (componentRefs[index] = el)"
|
|
151
|
+
v-bind="componentProps"
|
|
152
|
+
:path="path"
|
|
153
|
+
@vue:mounted="onComponentMounted(index)"
|
|
154
|
+
/>
|
|
155
|
+
</template>
|
|
156
|
+
</FormItem>
|
|
157
|
+
</ContainerFragment>
|
|
158
|
+
</ContainerFragment>
|
|
159
|
+
</template>
|
|
160
|
+
</GroupedFieldAttrs>
|
|
161
|
+
</template>
|
|
162
|
+
</PathProvider>
|
|
163
|
+
</ContainerFragment>
|
|
164
|
+
</template>
|
|
165
|
+
|
|
166
|
+
<style scoped lang="less">
|
|
167
|
+
[class*='-form-item'] [class*='-form-item'] {
|
|
168
|
+
margin-bottom: 18px;
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
/**
|
|
3
|
+
* @component ContainerFragment
|
|
4
|
+
* @description 容器分片渲染组件。用于动态渲染表单行或表单项的外部包裹容器(例如 Grid 行、Col 列等布局容器)。
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
import type { ContainerComponent } from '../../types';
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
component?: ContainerComponent;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
defineProps<Props>();
|
|
15
|
+
|
|
16
|
+
defineOptions({ name: 'ContainerFragment', inheritAttrs: false });
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<component :is="component" v-if="component" v-bind="$attrs">
|
|
21
|
+
<slot />
|
|
22
|
+
</component>
|
|
23
|
+
<slot v-else />
|
|
24
|
+
</template>
|