@qin-ui/antd-vue-pro 2.4.3 → 2.4.5
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 +4 -4
- package/README.md +4 -3
- package/api.json +2 -2
- package/es/antd-vue-pro.css +11 -11
- package/es/components/component-provider/types/index.d.ts +1 -1
- 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/BaseFormItem/index.vue.d.ts.map +1 -1
- package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -1
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts +1 -1
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -1
- package/es/components/form/types/index.d.ts +2 -2
- package/es/components/form/types/index.d.ts.map +1 -1
- package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -1
- package/es/components/table/components/ProTable.vue.d.ts.map +1 -1
- package/es/components/table/components/SearchForm.vue.d.ts +1 -1
- package/es/components/table/components/SearchForm.vue.d.ts.map +1 -1
- package/es/components/table/hooks/useTable.d.ts +1 -1
- package/es/index.js +321 -260
- package/es/index.js.map +1 -1
- package/es/shared/core/index.d.ts +1 -2
- package/es/shared/core/index.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/components/component-provider/types/index.ts +1 -1
- package/src/components/form/components/BaseField/index.vue +25 -9
- package/src/components/form/components/BaseField/utils/index.ts +1 -1
- package/src/components/form/components/BaseFormItem/index.vue +63 -8
- package/src/components/form/components/ProForm/index.vue +21 -10
- package/src/components/form/hooks/useGroupFieldAttrs.ts +9 -17
- package/src/components/form/types/index.ts +2 -8
- package/src/components/table/components/ColumnControl.vue +5 -3
- package/src/components/table/components/ProTable.vue +26 -11
- package/src/components/table/components/SearchForm.vue +3 -3
- package/src/components/table/hooks/useTable.ts +1 -1
- package/src/shared/core/index.ts +1 -2
- package/tsconfig.json +23 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 统一 re-export @qin-ui/pro-components-core,包内所有文件通过此文件引入 core
|
|
3
|
-
* 避免直接散列依赖 @qin-ui/pro-components-core,便于将来统一维护。
|
|
2
|
+
* 统一 re-export @qin-ui/pro-components-core,包内所有文件通过此文件引入 core 内容。
|
|
4
3
|
*/
|
|
5
4
|
export { useForm, createFormProxy, useTable, useSearchFormExpand, useFields, useFormData, useFormRef, InjectionFormKey, InjectionFormSlotsKey, getObject, get, set, normalizePathKey, toPath, camelizeProperties, cloneDeep, pick, isPlainObject, isPromise, zhCN, zhTW, enUS, format, resolveLocaleName, type ProLocale, type ProLocaleName, type ProLocaleKey, type Data, type Path, type Paths, type KeyExpandString, type ExtendWithAny, type DeepPartial, type PageParam, type Fields, type Form, type Table, type UseFieldsReturn, type UseFormDataReturn, type UseFormRefReturn, } from '@qin-ui/pro-components-core';
|
|
6
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/core/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAEL,OAAO,EACP,eAAe,EACf,QAAQ,EACR,mBAAmB,EACnB,SAAS,EACT,WAAW,EACX,UAAU,EAEV,gBAAgB,EAChB,qBAAqB,EAErB,SAAS,EACT,GAAG,EACH,GAAG,EACH,gBAAgB,EAChB,MAAM,EACN,kBAAkB,EAClB,SAAS,EACT,IAAI,EACJ,aAAa,EACb,SAAS,EAET,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,iBAAiB,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,YAAY,EAEjB,KAAK,IAAI,EACT,KAAK,IAAI,EACT,KAAK,KAAK,EACV,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,KAAK,EACV,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,6BAA6B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qin-ui/antd-vue-pro",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
4
4
|
"description": "二次封装antd vue组件",
|
|
5
5
|
"bin": {
|
|
6
6
|
"antd-vue-pro": "./bin/init-ai.mjs"
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"README.md",
|
|
24
24
|
"LICENSE",
|
|
25
25
|
"api.json",
|
|
26
|
-
"AI-CONTEXT.md"
|
|
26
|
+
"AI-CONTEXT.md",
|
|
27
|
+
"tsconfig.json"
|
|
27
28
|
],
|
|
28
29
|
"homepage": "https://dufan3715.github.io/pro-components/",
|
|
29
30
|
"repository": {
|
|
@@ -43,11 +44,11 @@
|
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"scroll-into-view-if-needed": "^3.1.0",
|
|
45
46
|
"vue-component-type-helpers": "^3.2.5",
|
|
46
|
-
"@qin-ui/pro-components-core": "^2.0.
|
|
47
|
+
"@qin-ui/pro-components-core": "^2.0.4"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"@vue/test-utils": "^2.4.11",
|
|
50
|
-
"ant-design-vue": "^4",
|
|
51
|
+
"ant-design-vue": "^4.2.6",
|
|
51
52
|
"happy-dom": "^20.11.6",
|
|
52
53
|
"vitest": "^4.1.11"
|
|
53
54
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GridItemProps, FormItemProps, TableProps } from '../../../shared/ui';
|
|
2
2
|
import { Base, Grid } from '../../form/types';
|
|
3
|
-
import { ProFormProps } from '../../form';
|
|
3
|
+
import { ProFormProps } from '../../form/components';
|
|
4
4
|
import { ProTableProps } from '../../table';
|
|
5
5
|
import { AllowedComponentProps } from 'vue';
|
|
6
6
|
|
|
@@ -42,7 +42,8 @@ import {
|
|
|
42
42
|
useInjectFormItemContext,
|
|
43
43
|
} from '../../../../shared/ui';
|
|
44
44
|
import type { Field } from '../../types';
|
|
45
|
-
import
|
|
45
|
+
import ContainerFragment from '../ContainerFragment/index.vue';
|
|
46
|
+
import SlotComponent from '../SlotComponent/index.vue';
|
|
46
47
|
import { componentMap, InjectionFormSlotsKey } from '../../constants';
|
|
47
48
|
import {
|
|
48
49
|
INJECT_COMPONENTS,
|
|
@@ -52,6 +53,10 @@ import { useForm } from '../../hooks/useForm';
|
|
|
52
53
|
import { getInitProps, warnUnknownComponent } from './utils';
|
|
53
54
|
import { useLocale } from '../../../../locale';
|
|
54
55
|
|
|
56
|
+
const cloneDeepIfObject = (val: unknown) =>
|
|
57
|
+
// 原始值直接传递,引用值克隆快照
|
|
58
|
+
typeof val === 'object' && val !== null ? cloneDeep(val) : val;
|
|
59
|
+
|
|
55
60
|
defineOptions({ name: 'BaseField', inheritAttrs: false });
|
|
56
61
|
|
|
57
62
|
type Props = {
|
|
@@ -76,7 +81,11 @@ const triggerFormItemChange = () => {
|
|
|
76
81
|
|
|
77
82
|
const attrs: any = useAttrs();
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
/**
|
|
85
|
+
* 全局默认配置注入:key = 组件名 + picker 修饰段,inject 须在 setup 同步期调用。
|
|
86
|
+
* 组件名与 `picker` 仅取 setup 期初始值——运行期变更不会切换 Provider 的默认值预设
|
|
87
|
+
* (`date-picker.month` 等子类型预设需在初始渲染前确定)。
|
|
88
|
+
*/
|
|
80
89
|
const pickerVal = attrs.picker;
|
|
81
90
|
const injectConfigKey =
|
|
82
91
|
typeof props.component === 'string'
|
|
@@ -117,12 +126,19 @@ const value = computed({
|
|
|
117
126
|
// 仅当 set 转换函数需要旧值参数时,才获取旧值上下文
|
|
118
127
|
const oldVal =
|
|
119
128
|
setFormatter.length > 1
|
|
120
|
-
?
|
|
129
|
+
? cloneDeepIfObject(form.getFormData?.(props.path))
|
|
121
130
|
: undefined;
|
|
122
131
|
newVal = setFormatter(val, oldVal);
|
|
123
132
|
}
|
|
124
133
|
form.setFormData?.(props.path, newVal);
|
|
125
134
|
triggerFormItemChange();
|
|
135
|
+
// 字段配置/attrs 传入的 onUpdate:* 监听器与内部写值组合回放,参数为组件原始 emit 值
|
|
136
|
+
const { onUpdateValue } = groupedAttrs.value;
|
|
137
|
+
if (typeof onUpdateValue === 'function') {
|
|
138
|
+
onUpdateValue(val);
|
|
139
|
+
} else if (Array.isArray(onUpdateValue)) {
|
|
140
|
+
onUpdateValue.forEach(listener => listener(val));
|
|
141
|
+
}
|
|
126
142
|
},
|
|
127
143
|
});
|
|
128
144
|
|
|
@@ -149,8 +165,7 @@ const groupedAttrs = computed(() => {
|
|
|
149
165
|
) as Required<Field>;
|
|
150
166
|
|
|
151
167
|
// prettier-ignore
|
|
152
|
-
|
|
153
|
-
const { valueFormatter, modelProp, slots, componentClass, componentStyle, componentContainer, [effectiveModelProp]: _modelVal, [`onUpdate:${effectiveModelProp}`]: _onUpdateModelVal, ...bindAttrs } = mergedProps as any;
|
|
168
|
+
const { valueFormatter, modelProp, slots, componentClass, componentStyle, componentContainer, [effectiveModelProp]: _modelVal, [`onUpdate:${effectiveModelProp}`]: onUpdateValue, ...bindAttrs } = mergedProps as any;
|
|
154
169
|
|
|
155
170
|
return {
|
|
156
171
|
attrs: bindAttrs,
|
|
@@ -158,12 +173,13 @@ const groupedAttrs = computed(() => {
|
|
|
158
173
|
componentContainer,
|
|
159
174
|
modelProp,
|
|
160
175
|
valueFormatter,
|
|
176
|
+
onUpdateValue,
|
|
161
177
|
};
|
|
162
178
|
});
|
|
163
179
|
|
|
164
|
-
// 从 ProForm 插槽注入的 slots
|
|
165
|
-
const
|
|
166
|
-
| Record<string, any>
|
|
180
|
+
// 从 ProForm 插槽注入的 slots 集合(优先级最高;shallowRef 形态,computed 内经 .value 读取建立依赖)
|
|
181
|
+
const injectedFormSlots = inject(InjectionFormSlotsKey, undefined) as
|
|
182
|
+
| { value: Record<string, any> | undefined }
|
|
167
183
|
| undefined;
|
|
168
184
|
|
|
169
185
|
// 从 ProComponentProvider 注入的自定义组件映射
|
|
@@ -174,7 +190,7 @@ const customComponents = inject(INJECT_COMPONENTS, {});
|
|
|
174
190
|
* ProForm 插槽 → 自定义组件映射 → 内置组件映射 → 原始 component 值
|
|
175
191
|
*/
|
|
176
192
|
const is = computed(() => {
|
|
177
|
-
const teleportSlot = props.path &&
|
|
193
|
+
const teleportSlot = props.path && injectedFormSlots?.value?.[props.path];
|
|
178
194
|
if (teleportSlot) return teleportSlot;
|
|
179
195
|
if (typeof props.component === 'string') {
|
|
180
196
|
const resolved =
|
|
@@ -33,10 +33,28 @@ import {
|
|
|
33
33
|
} from '../../../../shared/ui';
|
|
34
34
|
import { computed, inject, toRaw, toValue } from 'vue';
|
|
35
35
|
import type { Fields, Grid } from '../../types';
|
|
36
|
-
import
|
|
36
|
+
import BaseField from '../BaseField/index.vue';
|
|
37
|
+
import SlotComponent from '../SlotComponent/index.vue';
|
|
38
|
+
import ContainerFragment from '../ContainerFragment/index.vue';
|
|
37
39
|
import { useGroupFieldAttrs } from '../../hooks/useGroupFieldAttrs';
|
|
38
40
|
import { INJECT_CONFIG } from '../../../component-provider/constants';
|
|
39
41
|
|
|
42
|
+
/*
|
|
43
|
+
* 无 path 字段的稳定渲染 key:以字段原始对象为 key 分配自增 id,
|
|
44
|
+
* 字段插删/重排/过滤时保持 key 与对象绑定,不随数组位置漂移
|
|
45
|
+
*/
|
|
46
|
+
let fieldKeySeed = 0;
|
|
47
|
+
const fieldKeyMap = new WeakMap<object, number>();
|
|
48
|
+
const getFieldKey = (field: unknown): number => {
|
|
49
|
+
const raw = toRaw(field as object);
|
|
50
|
+
let key = fieldKeyMap.get(raw);
|
|
51
|
+
if (key === undefined) {
|
|
52
|
+
key = ++fieldKeySeed;
|
|
53
|
+
fieldKeyMap.set(raw, key);
|
|
54
|
+
}
|
|
55
|
+
return key;
|
|
56
|
+
};
|
|
57
|
+
|
|
40
58
|
defineOptions({ name: 'BaseFormItem', inheritAttrs: false });
|
|
41
59
|
|
|
42
60
|
type Props = {
|
|
@@ -55,6 +73,7 @@ const validFields = computed(() =>
|
|
|
55
73
|
(props.fields ?? []).filter(field => !toValue(field.hidden))
|
|
56
74
|
);
|
|
57
75
|
|
|
76
|
+
// 向下注入表单级禁用上下文,BaseField 经 useInjectDisabled 消费
|
|
58
77
|
useProviderDisabled(computed(() => props.disabled));
|
|
59
78
|
|
|
60
79
|
const config = INJECT_CONFIG['pro-form'];
|
|
@@ -79,15 +98,45 @@ const computedGridProps = computed<GridProps>(() => {
|
|
|
79
98
|
const formItemInstanceMap = new WeakMap<object, any>();
|
|
80
99
|
const componentInstanceMap = new WeakMap<object, any>();
|
|
81
100
|
|
|
101
|
+
/** 已告警过的字段对象(不可写场景去重) */
|
|
102
|
+
const warnedFrozenFields = new WeakSet<object>();
|
|
103
|
+
const defineFieldAccessors = (
|
|
104
|
+
raw: object,
|
|
105
|
+
accessors: Record<string, () => any>
|
|
106
|
+
) => {
|
|
107
|
+
try {
|
|
108
|
+
for (const [key, getter] of Object.entries(accessors)) {
|
|
109
|
+
// 不可枚举:不进入 Object.keys / spread,不随字段配置透传
|
|
110
|
+
Object.defineProperty(raw, key, {
|
|
111
|
+
value: getter,
|
|
112
|
+
enumerable: false,
|
|
113
|
+
configurable: true,
|
|
114
|
+
writable: true,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
if (!warnedFrozenFields.has(raw)) {
|
|
119
|
+
warnedFrozenFields.add(raw);
|
|
120
|
+
console.warn(
|
|
121
|
+
'[pro-components] 字段配置对象不可写(如被 Object.freeze),getFormItemRef / getComponentRef 等实例访问器挂载失败。'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
82
127
|
const setFormItemRef = (field: any, el: any, formItemProps?: any) => {
|
|
83
128
|
if (!field || typeof field !== 'object') return;
|
|
84
129
|
const raw = toRaw(field);
|
|
85
130
|
if (el) {
|
|
86
131
|
formItemInstanceMap.set(raw, el);
|
|
87
|
-
raw
|
|
88
|
-
|
|
132
|
+
defineFieldAccessors(raw, {
|
|
133
|
+
getFormItemRef: () => formItemInstanceMap.get(raw),
|
|
134
|
+
getFormItemComputedProps: () => formItemProps,
|
|
135
|
+
});
|
|
89
136
|
} else {
|
|
90
137
|
formItemInstanceMap.delete(raw);
|
|
138
|
+
delete raw.getFormItemRef;
|
|
139
|
+
delete raw.getFormItemComputedProps;
|
|
91
140
|
}
|
|
92
141
|
};
|
|
93
142
|
|
|
@@ -96,12 +145,15 @@ const setComponentRef = (field: any, el: any) => {
|
|
|
96
145
|
const raw = toRaw(field);
|
|
97
146
|
if (el) {
|
|
98
147
|
componentInstanceMap.set(raw, el);
|
|
99
|
-
raw
|
|
100
|
-
componentInstanceMap.get(raw)?.getComponentRef()
|
|
101
|
-
|
|
102
|
-
|
|
148
|
+
defineFieldAccessors(raw, {
|
|
149
|
+
getComponentRef: () => componentInstanceMap.get(raw)?.getComponentRef(),
|
|
150
|
+
getComponentComputedProps: () =>
|
|
151
|
+
componentInstanceMap.get(raw)?.getComponentComputedProps(),
|
|
152
|
+
});
|
|
103
153
|
} else {
|
|
104
154
|
componentInstanceMap.delete(raw);
|
|
155
|
+
delete raw.getComponentRef;
|
|
156
|
+
delete raw.getComponentComputedProps;
|
|
105
157
|
}
|
|
106
158
|
};
|
|
107
159
|
|
|
@@ -113,7 +165,10 @@ const groupedFields = useGroupFieldAttrs(validFields);
|
|
|
113
165
|
:component="enableGrid ? AGrid : undefined"
|
|
114
166
|
v-bind="computedGridProps"
|
|
115
167
|
>
|
|
116
|
-
<template
|
|
168
|
+
<template
|
|
169
|
+
v-for="item of groupedFields"
|
|
170
|
+
:key="item.path || getFieldKey(item.rawField)"
|
|
171
|
+
>
|
|
117
172
|
<ContainerFragment
|
|
118
173
|
:component="enableGrid ? AGridItem : undefined"
|
|
119
174
|
v-bind="item.gridItemProps"
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 1. 数据隔离:ProForm 本身不负责存储和管理表单状态,所有核心状态(formData, fields, 校验规则)
|
|
11
11
|
* 均交由底层的 `useForm`(@qin-ui/pro-components-core)驱动。
|
|
12
|
-
* 2. 上下文透传:通过 `provide`
|
|
13
|
-
* 3. 递归渲染:网格布局和具体字段的解析交由 `<BaseFormItem>`
|
|
12
|
+
* 2. 上下文透传:通过 `provide` 将核心状态下发给子组件。
|
|
13
|
+
* 3. 递归渲染:网格布局和具体字段的解析交由 `<BaseFormItem>` 递归完成。
|
|
14
14
|
*
|
|
15
15
|
* @public
|
|
16
16
|
*
|
|
@@ -47,13 +47,16 @@ import {
|
|
|
47
47
|
computed,
|
|
48
48
|
inject,
|
|
49
49
|
mergeProps,
|
|
50
|
+
onUpdated,
|
|
50
51
|
provide,
|
|
52
|
+
shallowRef,
|
|
51
53
|
toValue,
|
|
54
|
+
triggerRef,
|
|
52
55
|
useAttrs,
|
|
53
56
|
type Slot,
|
|
54
57
|
} from 'vue';
|
|
55
58
|
import { INJECT_CONFIG } from '../../../component-provider/constants';
|
|
56
|
-
import
|
|
59
|
+
import BaseFormItem from '../BaseFormItem/index.vue';
|
|
57
60
|
import { InjectionFormKey, InjectionFormSlotsKey } from '../../constants';
|
|
58
61
|
import type { Grid, VModelProps, PathProps } from '../../types';
|
|
59
62
|
import {
|
|
@@ -75,12 +78,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
75
78
|
|
|
76
79
|
/**
|
|
77
80
|
* 依赖注入中心:
|
|
78
|
-
*
|
|
81
|
+
* 未传 form 实例(含运行期置空)时惰性初始化内部表单实例;
|
|
79
82
|
* 将 Form 实例注入到 Vue Context 中,供深层子组件(如 BaseFormItem, BaseField, 自定义插槽)跨层级直接访问数据和方法。
|
|
80
83
|
*/
|
|
81
|
-
|
|
84
|
+
let fallbackForm: F | undefined;
|
|
82
85
|
const currentForm = computed(
|
|
83
|
-
() => props.form ?? (fallbackForm as unknown as F)
|
|
86
|
+
() => props.form ?? (fallbackForm ??= useForm() as unknown as F)
|
|
84
87
|
);
|
|
85
88
|
|
|
86
89
|
/**
|
|
@@ -98,9 +101,14 @@ const fields = computed(() => {
|
|
|
98
101
|
const formData = computed(() => currentForm.value?.formData);
|
|
99
102
|
const setFormRef = (el: any) => currentForm.value?.setFormRef?.(el);
|
|
100
103
|
|
|
104
|
+
/*
|
|
105
|
+
* formKey 子树重挂载契约:以 WeakMap 为每个 form 实例生成自增 key,绑定到原生 Form 的 :key
|
|
106
|
+
* :form 运行期替换为全新实例时强制整棵表单子树重新挂载,重置宿主校验状态与深层字段非受控状态
|
|
107
|
+
*/
|
|
101
108
|
let formInstanceCounter = 0;
|
|
102
109
|
const formInstanceKeyMap = new WeakMap<object, number>();
|
|
103
110
|
const getFormKey = (f: object) => {
|
|
111
|
+
if (f == null) return 0;
|
|
104
112
|
if (!formInstanceKeyMap.has(f)) {
|
|
105
113
|
formInstanceKeyMap.set(f, ++formInstanceCounter);
|
|
106
114
|
}
|
|
@@ -118,7 +126,6 @@ const attrs = useAttrs();
|
|
|
118
126
|
*/
|
|
119
127
|
const injectAttrs = computed(() => {
|
|
120
128
|
const raw = toValue(injectFormConfig);
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
122
129
|
const { grid: _injectGrid, ...rest } = raw || {};
|
|
123
130
|
return rest;
|
|
124
131
|
});
|
|
@@ -134,11 +141,15 @@ type FieldSlots = Record<ExtractPath<F>, Slot<FieldSlotProps>>;
|
|
|
134
141
|
|
|
135
142
|
const slots = defineSlots<Partial<FieldSlots & { default: Slot }>>();
|
|
136
143
|
|
|
137
|
-
|
|
144
|
+
/*
|
|
138
145
|
* 动态插槽映射中心 (Slot Teleporting):
|
|
139
|
-
*
|
|
146
|
+
* slots 以 shallowRef 注入,组件每次更新后触发通知,深层 BaseField 可感知父级运行期增删的具名插槽
|
|
140
147
|
*/
|
|
141
|
-
|
|
148
|
+
const formSlotsRef = shallowRef<Record<string, any>>(slots);
|
|
149
|
+
onUpdated(() => {
|
|
150
|
+
triggerRef(formSlotsRef);
|
|
151
|
+
});
|
|
152
|
+
provide(InjectionFormSlotsKey, formSlotsRef);
|
|
142
153
|
</script>
|
|
143
154
|
|
|
144
155
|
<template>
|
|
@@ -17,6 +17,7 @@ const gridItemPropKeys = Object.keys(gridItemProps());
|
|
|
17
17
|
// 模块级 Set:字段循环内 O(1) 命中,替代数组 includes 线性扫描
|
|
18
18
|
const gridItemPropKeySet = new Set<string>(gridItemPropKeys);
|
|
19
19
|
const formItemPropKeySet = new Set<string>(formItemPropKeys);
|
|
20
|
+
const formItemSlotKeySet = new Set<string>(FORM_ITEM_SLOT_KEYS);
|
|
20
21
|
|
|
21
22
|
export interface GroupedField {
|
|
22
23
|
path: string;
|
|
@@ -33,7 +34,7 @@ export interface GroupedField {
|
|
|
33
34
|
*
|
|
34
35
|
* @description
|
|
35
36
|
* 1. 响应式集成 ProComponentProvider 的 `pro-form-item` 全局变量配置;
|
|
36
|
-
* 2. 剥离 `extraProps
|
|
37
|
+
* 2. 剥离 `extraProps`(用户自定义字段标识袋):保留在字段原始对象上供业务层存取,渲染层经解构剥离,不落入 componentProps 透传。
|
|
37
38
|
*/
|
|
38
39
|
export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
39
40
|
const config = INJECT_CONFIG['pro-form-item'];
|
|
@@ -49,23 +50,11 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
|
49
50
|
|
|
50
51
|
if (isPlainObject(field)) {
|
|
51
52
|
// prettier-ignore
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
53
53
|
const { path, name, fields, formItemClass, formItemStyle, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, formItemContainer, ...rest } =
|
|
54
54
|
field as any;
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
style: injectStyle,
|
|
59
|
-
span: injectSpan,
|
|
60
|
-
xs,
|
|
61
|
-
sm,
|
|
62
|
-
md,
|
|
63
|
-
lg,
|
|
64
|
-
xl,
|
|
65
|
-
xxl,
|
|
66
|
-
formItemContainer: injectFormItemContainer,
|
|
67
|
-
...injectRest
|
|
68
|
-
} = rawInject;
|
|
56
|
+
// prettier-ignore
|
|
57
|
+
const { class: injectClassName, style: injectStyle, span: injectSpan, xs, sm, md, lg, xl, xxl, formItemContainer: injectFormItemContainer, ...injectRest } = rawInject;
|
|
69
58
|
|
|
70
59
|
// 字段显式 span 优先,否则回退注入配置的响应式断点
|
|
71
60
|
const initGridItemProps =
|
|
@@ -73,7 +62,10 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
|
73
62
|
? { span }
|
|
74
63
|
: { xs, sm, md, lg, xl, xxl, span: injectSpan };
|
|
75
64
|
|
|
76
|
-
|
|
65
|
+
// 分组字段默认整行(span 兜底 24),注入断点同样生效
|
|
66
|
+
gridItemProps = fields
|
|
67
|
+
? { ...initGridItemProps, span: span ?? 24 }
|
|
68
|
+
: initGridItemProps;
|
|
77
69
|
// 字段级容器优先,回退注入级配置
|
|
78
70
|
const container = formItemContainer ?? injectFormItemContainer;
|
|
79
71
|
formItemProps = mergeProps(
|
|
@@ -95,7 +87,7 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
|
95
87
|
|
|
96
88
|
Object.assign(componentProps, componentDataAttrs);
|
|
97
89
|
Object.keys(slots).forEach(k => {
|
|
98
|
-
if (
|
|
90
|
+
if (formItemSlotKeySet.has(k)) {
|
|
99
91
|
formItemSlots[k] = slots[k];
|
|
100
92
|
} else {
|
|
101
93
|
componentProps.slots[k] = slots[k];
|
|
@@ -267,10 +267,7 @@ type WithFields<D extends Data = Data> = WithRef<
|
|
|
267
267
|
/**
|
|
268
268
|
* @description 合并 FormItem 插槽与组件插槽
|
|
269
269
|
*/
|
|
270
|
-
export type MergeSlots<
|
|
271
|
-
T extends abstract new (...args: any) => any,
|
|
272
|
-
CS = ComponentSlots<T>,
|
|
273
|
-
> = Partial<{
|
|
270
|
+
export type MergeSlots<T, CS = ComponentSlots<T>> = Partial<{
|
|
274
271
|
[K in keyof FormItemSlots | keyof CS]: K extends keyof FormItemSlots
|
|
275
272
|
? FormItemSlots[K]
|
|
276
273
|
: K extends keyof CS
|
|
@@ -283,10 +280,7 @@ export type MergeSlots<
|
|
|
283
280
|
* @template T - Vue 组件类型
|
|
284
281
|
* @template D - 数据对象类型
|
|
285
282
|
*/
|
|
286
|
-
type WithComponent<
|
|
287
|
-
T extends abstract new (...args: any) => any,
|
|
288
|
-
D extends Data = Data,
|
|
289
|
-
> = Omit<
|
|
283
|
+
type WithComponent<T, D extends Data = Data> = Omit<
|
|
290
284
|
WithCommon<Partial<ComponentProps<T>>, D>,
|
|
291
285
|
keyof VNodeProps | 'slots'
|
|
292
286
|
> & {
|
|
@@ -55,7 +55,7 @@ const checkedColumnsOptions = computed<
|
|
|
55
55
|
const disabled =
|
|
56
56
|
props.columnControl?.disabledKeys?.some(dk => String(dk) === key) ??
|
|
57
57
|
false;
|
|
58
|
-
// labels 优先;title 为字符串时直接使用;vnode 等其他类型降级为 key
|
|
58
|
+
// labels 优先;title 为字符串时直接使用;vnode 等其他类型降级为 key
|
|
59
59
|
const label =
|
|
60
60
|
(props.columnControl?.labels?.[key] as string) ??
|
|
61
61
|
(typeof item.title === 'string' ? item.title : key);
|
|
@@ -80,10 +80,12 @@ const checkedColumnKeys = computed({
|
|
|
80
80
|
);
|
|
81
81
|
},
|
|
82
82
|
set(val) {
|
|
83
|
+
// 仅写 hidden 发生翻转的列
|
|
84
|
+
const checkedSet = new Set(val);
|
|
83
85
|
checkedColumnsOptions.value.forEach(item => {
|
|
84
|
-
if (item.disabled) return;
|
|
86
|
+
if (item.disabled || item.checked === checkedSet.has(item.value)) return;
|
|
85
87
|
props.table?.setColumn?.(item.value, {
|
|
86
|
-
hidden: !
|
|
88
|
+
hidden: !checkedSet.has(item.value),
|
|
87
89
|
});
|
|
88
90
|
});
|
|
89
91
|
},
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
type Slot,
|
|
50
50
|
} from 'vue';
|
|
51
51
|
import { INJECT_CONFIG } from '../../component-provider/constants';
|
|
52
|
-
import
|
|
52
|
+
import ContainerFragment from '../../form/components/ContainerFragment/index.vue';
|
|
53
53
|
import type { ContainerComponent } from '../../form/types';
|
|
54
54
|
import SearchForm from './SearchForm.vue';
|
|
55
55
|
import { SearchFormProps } from './SearchForm.vue';
|
|
@@ -64,12 +64,10 @@ import {
|
|
|
64
64
|
Path,
|
|
65
65
|
} from '../../../shared/core';
|
|
66
66
|
import { ComponentSlots } from 'vue-component-type-helpers';
|
|
67
|
-
import
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
ColumnControl,
|
|
72
|
-
} from '.';
|
|
67
|
+
import DefaultSearchFormContainer from './DefaultSearchFormContainer.vue';
|
|
68
|
+
import DefaultTableContainer from './DefaultTableContainer.vue';
|
|
69
|
+
import SizeControl from './SizeControl.vue';
|
|
70
|
+
import ColumnControl from './ColumnControl.vue';
|
|
73
71
|
import { Column, Columns, ColumnControlConfig } from '../types';
|
|
74
72
|
|
|
75
73
|
defineOptions({ name: 'ProTable', inheritAttrs: false });
|
|
@@ -147,10 +145,26 @@ const loading = defineModel<boolean>('loading', { default: false });
|
|
|
147
145
|
|
|
148
146
|
// 递增序号做请求竞态守卫,仅最新一次请求可变更 loading
|
|
149
147
|
let searchSequence = 0;
|
|
148
|
+
let warnedSearchConfig = false;
|
|
149
|
+
const warnSearchConfig = (reason: string) => {
|
|
150
|
+
if (warnedSearchConfig) return;
|
|
151
|
+
warnedSearchConfig = true;
|
|
152
|
+
console.warn(`[ProTable] ${reason}`);
|
|
153
|
+
};
|
|
150
154
|
const _search = async () => {
|
|
151
|
-
|
|
155
|
+
if (props.search && attrs.onSearch) {
|
|
156
|
+
warnSearchConfig(
|
|
157
|
+
'同时配置了 :search prop 与 @search 监听,@search 监听不会被执行,请仅保留其一。'
|
|
158
|
+
);
|
|
159
|
+
}
|
|
152
160
|
const searchPromise = (props.search || attrs.onSearch)?.();
|
|
153
|
-
if (!isPromise(searchPromise))
|
|
161
|
+
if (!isPromise(searchPromise)) {
|
|
162
|
+
warnSearchConfig(
|
|
163
|
+
'search 未返回 Promise,本次查询不参与 loading 竞态管理(在途请求的 loading 状态保持不变)。'
|
|
164
|
+
);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const seq = ++searchSequence;
|
|
154
168
|
loading.value = true;
|
|
155
169
|
try {
|
|
156
170
|
await searchPromise;
|
|
@@ -206,7 +220,7 @@ const columnRequiredSlotNames = new Set([
|
|
|
206
220
|
/**
|
|
207
221
|
* 判定插槽是否具备渲染条件(守卫过渡状态或空列下 column 为 undefined 的情况)
|
|
208
222
|
* 当列被动态隐藏或所有列均隐藏时,旧单元格销毁过渡帧或空占位中 column 可能为 undefined,
|
|
209
|
-
*
|
|
223
|
+
* 此时阻断依赖 column 的业务插槽调用;
|
|
210
224
|
* 非列依赖的表格级插槽(如 emptyText、footer、summary 等)始终正常渲染。
|
|
211
225
|
*/
|
|
212
226
|
const isSlotRenderable = (name: string, scoped: any) => {
|
|
@@ -306,7 +320,8 @@ const computedPagination = computed<PaginationProps | false>(() => {
|
|
|
306
320
|
|
|
307
321
|
const onTableChange: TableProps['onChange'] = (...args) => {
|
|
308
322
|
props.table?.setPageParam?.(args[0]);
|
|
309
|
-
|
|
323
|
+
// change 回放:模板监听器优先,未绑定时回退 Provider 全局配置
|
|
324
|
+
(attrs.onChange ?? injectProps.value.onChange)?.(...args);
|
|
310
325
|
_search();
|
|
311
326
|
};
|
|
312
327
|
|
|
@@ -46,7 +46,7 @@ export type SearchFormProps = {
|
|
|
46
46
|
*
|
|
47
47
|
* 1. 内部渲染 ProForm,使用 table.searchForm 管理搜索条件
|
|
48
48
|
* 2. 支持 grid(网格)和 inline(行内)两种布局
|
|
49
|
-
* 3.
|
|
49
|
+
* 3. 网格布局下支持展开/折叠(溢出检测与初值自动展开见下方机制说明)
|
|
50
50
|
* 4. 提供搜索、重置、展开/折叠按钮(支持自定义)
|
|
51
51
|
*
|
|
52
52
|
* ## 展开/折叠机制
|
|
@@ -63,8 +63,8 @@ import {
|
|
|
63
63
|
type Component,
|
|
64
64
|
type DefineComponent,
|
|
65
65
|
} from 'vue';
|
|
66
|
-
import ProForm from '../../form';
|
|
67
|
-
import { type Form } from '../../form';
|
|
66
|
+
import ProForm from '../../form/components/ProForm/index.vue';
|
|
67
|
+
import { type Form } from '../../form/hooks/useForm';
|
|
68
68
|
import DownOutlined from './icons/DownOutlined.vue';
|
|
69
69
|
import { useLocale } from '../../../locale';
|
|
70
70
|
import { useSearchFormExpand } from '../../../shared/core';
|
|
@@ -29,7 +29,7 @@ import { Fields } from '../../form/types';
|
|
|
29
29
|
* @property appendColumn - 在指定列后追加新列,传 undefined 在末尾追加
|
|
30
30
|
* @property prependColumn - 在指定列前插入新列,传 undefined 在开头插入
|
|
31
31
|
* @property setPageParam - 设置分页参数,支持部分更新和函数式更新
|
|
32
|
-
* @property resetQueryParams -
|
|
32
|
+
* @property resetQueryParams - 恢复分页与搜索条件到初始化时的快照(含自定义分页键,非固定回到第一页)
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
35
35
|
* ```ts
|
package/src/shared/core/index.ts
CHANGED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"jsx": "preserve",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"lib": ["ESNext", "ES2022", "DOM"],
|
|
13
|
+
"baseUrl": ".",
|
|
14
|
+
"types": ["ant-design-vue/typings/global"],
|
|
15
|
+
"declaration": true,
|
|
16
|
+
"declarationMap": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"noEmit": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["src", "__tests__"],
|
|
22
|
+
"exclude": ["node_modules", "es"]
|
|
23
|
+
}
|