@qin-ui/element-plus-pro 1.3.4 → 1.3.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/api.json +1 -1
- package/es/components/component-provider/types/index.d.ts +2 -2
- 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.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/components/TableColumnRenderer.vue.d.ts.map +1 -1
- package/es/element-plus-pro.css +12 -12
- package/es/index.js +336 -274
- package/es/index.js.map +1 -1
- package/package.json +4 -3
- package/src/components/component-provider/types/index.ts +2 -2
- package/src/components/form/components/BaseField/index.vue +11 -7
- package/src/components/form/components/BaseFormItem/index.vue +58 -8
- package/src/components/form/components/ProForm/index.vue +17 -10
- package/src/components/form/hooks/useGroupFieldAttrs.ts +6 -14
- package/src/components/table/components/ColumnControl.vue +4 -2
- package/src/components/table/components/ProTable.vue +24 -16
- package/src/components/table/components/SearchForm.vue +4 -3
- package/src/components/table/components/TableColumnRenderer.vue +22 -9
- package/tsconfig.json +23 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qin-ui/element-plus-pro",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "基于 element-plus 的二次封装组件",
|
|
5
5
|
"bin": {
|
|
6
6
|
"element-plus-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": {
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"vue-component-type-helpers": "^3.2.5",
|
|
42
|
-
"@qin-ui/pro-components-core": "^2.0.
|
|
43
|
+
"@qin-ui/pro-components-core": "^2.0.4"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@vue/test-utils": "^2.4.11",
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
} from '../../../shared/ui';
|
|
8
8
|
import { ComponentName, GetComponentType } from '../../form/constants';
|
|
9
9
|
import { Base, Grid, ContainerComponent } from '../../form/types';
|
|
10
|
-
import { ProFormProps } from '../../form';
|
|
10
|
+
import { ProFormProps } from '../../form/components';
|
|
11
11
|
import type { SearchFormProps } from '../../table/components/SearchForm.vue';
|
|
12
12
|
import { AllowedComponentProps } from 'vue';
|
|
13
13
|
import type { ComponentProps } from 'vue-component-type-helpers';
|
|
14
|
-
import { ColumnControlConfig } from '
|
|
14
|
+
import { ColumnControlConfig } from '../../table/types';
|
|
15
15
|
|
|
16
16
|
type PP<T extends Record<string, any>> = Partial<T & AllowedComponentProps>;
|
|
17
17
|
|
|
@@ -39,7 +39,8 @@ import {
|
|
|
39
39
|
import { cloneDeep } from '../../../../shared/core';
|
|
40
40
|
import { useDisabledContext } from '../../../../shared/ui';
|
|
41
41
|
import type { Field } from '../../types';
|
|
42
|
-
import
|
|
42
|
+
import ContainerFragment from '../ContainerFragment/index.vue';
|
|
43
|
+
import SlotComponent from '../SlotComponent/index.vue';
|
|
43
44
|
import { componentMap, InjectionFormSlotsKey } from '../../constants';
|
|
44
45
|
import {
|
|
45
46
|
INJECT_COMPONENTS,
|
|
@@ -49,6 +50,10 @@ import { useForm } from '../../hooks/useForm';
|
|
|
49
50
|
import { getInitProps, warnUnknownComponent } from './utils';
|
|
50
51
|
import { useLocale } from '../../../../locale';
|
|
51
52
|
|
|
53
|
+
const cloneDeepIfObject = (val: unknown) =>
|
|
54
|
+
// 原始值直接传递,引用值克隆快照
|
|
55
|
+
typeof val === 'object' && val !== null ? cloneDeep(val) : val;
|
|
56
|
+
|
|
52
57
|
defineOptions({ name: 'BaseField', inheritAttrs: false });
|
|
53
58
|
|
|
54
59
|
type Props = {
|
|
@@ -113,7 +118,7 @@ const value = computed({
|
|
|
113
118
|
// 仅当 set 转换函数需要旧值参数时,才获取旧值上下文
|
|
114
119
|
const oldVal =
|
|
115
120
|
setFormatter.length > 1
|
|
116
|
-
?
|
|
121
|
+
? cloneDeepIfObject(form.getFormData?.(props.path))
|
|
117
122
|
: undefined;
|
|
118
123
|
newVal = setFormatter(val, oldVal);
|
|
119
124
|
}
|
|
@@ -158,7 +163,6 @@ const groupedAttrs = computed(() => {
|
|
|
158
163
|
) as Required<Field>;
|
|
159
164
|
|
|
160
165
|
// prettier-ignore
|
|
161
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
162
166
|
const { valueFormatter, modelProp, slots, componentClass, componentStyle, componentContainer, [modelPropName]: _modelVal, [`onUpdate:${modelPropName}`]: onUpdateValue, ...bindAttrs } = mergedProps as any;
|
|
163
167
|
|
|
164
168
|
return {
|
|
@@ -171,9 +175,9 @@ const groupedAttrs = computed(() => {
|
|
|
171
175
|
};
|
|
172
176
|
});
|
|
173
177
|
|
|
174
|
-
// 从 ProForm 插槽注入的 slots
|
|
175
|
-
const
|
|
176
|
-
| Record<string, any>
|
|
178
|
+
// 从 ProForm 插槽注入的 slots 集合(优先级最高;shallowRef 形态,computed 内经 .value 读取建立依赖)
|
|
179
|
+
const injectedFormSlots = inject(InjectionFormSlotsKey, undefined) as
|
|
180
|
+
| { value: Record<string, any> | undefined }
|
|
177
181
|
| undefined;
|
|
178
182
|
|
|
179
183
|
// 从 ProComponentProvider 注入的自定义组件映射
|
|
@@ -184,7 +188,7 @@ const customComponents = inject(INJECT_COMPONENTS, {});
|
|
|
184
188
|
* ProForm 插槽 → 自定义组件映射 → 内置组件映射 → 原始 component 值
|
|
185
189
|
*/
|
|
186
190
|
const is = computed(() => {
|
|
187
|
-
const teleportSlot = props.path &&
|
|
191
|
+
const teleportSlot = props.path && injectedFormSlots?.value?.[props.path];
|
|
188
192
|
if (teleportSlot) return teleportSlot;
|
|
189
193
|
if (typeof props.component === 'string') {
|
|
190
194
|
const resolved =
|
|
@@ -31,10 +31,28 @@ import {
|
|
|
31
31
|
} from '../../../../shared/ui';
|
|
32
32
|
import { computed, inject, toRaw, toValue } from 'vue';
|
|
33
33
|
import type { Fields, Grid } from '../../types';
|
|
34
|
-
import
|
|
34
|
+
import BaseField from '../BaseField/index.vue';
|
|
35
|
+
import SlotComponent from '../SlotComponent/index.vue';
|
|
36
|
+
import ContainerFragment from '../ContainerFragment/index.vue';
|
|
35
37
|
import { useGroupFieldAttrs } from '../../hooks/useGroupFieldAttrs';
|
|
36
38
|
import { INJECT_CONFIG } from '../../../component-provider/constants';
|
|
37
39
|
|
|
40
|
+
/*
|
|
41
|
+
* 无 path 字段的稳定渲染 key:以字段原始对象为 key 分配自增 id,
|
|
42
|
+
* 字段插删/重排/过滤时保持 key 与对象绑定,不随数组位置漂移
|
|
43
|
+
*/
|
|
44
|
+
let fieldKeySeed = 0;
|
|
45
|
+
const fieldKeyMap = new WeakMap<object, number>();
|
|
46
|
+
const getFieldKey = (field: unknown): number => {
|
|
47
|
+
const raw = toRaw(field as object);
|
|
48
|
+
let key = fieldKeyMap.get(raw);
|
|
49
|
+
if (key === undefined) {
|
|
50
|
+
key = ++fieldKeySeed;
|
|
51
|
+
fieldKeyMap.set(raw, key);
|
|
52
|
+
}
|
|
53
|
+
return key;
|
|
54
|
+
};
|
|
55
|
+
|
|
38
56
|
defineOptions({ name: 'BaseFormItem', inheritAttrs: false });
|
|
39
57
|
|
|
40
58
|
type Props = {
|
|
@@ -73,13 +91,41 @@ const computedGridProps = computed<GridProps>(() => {
|
|
|
73
91
|
const formItemInstanceMap = new WeakMap<object, any>();
|
|
74
92
|
const componentInstanceMap = new WeakMap<object, any>();
|
|
75
93
|
|
|
94
|
+
/** 已告警过的字段对象(不可写场景去重) */
|
|
95
|
+
const warnedFrozenFields = new WeakSet<object>();
|
|
96
|
+
const defineFieldAccessors = (
|
|
97
|
+
raw: object,
|
|
98
|
+
accessors: Record<string, () => any>
|
|
99
|
+
) => {
|
|
100
|
+
try {
|
|
101
|
+
for (const [key, getter] of Object.entries(accessors)) {
|
|
102
|
+
// 不可枚举:不进入 Object.keys / spread,不随字段配置透传
|
|
103
|
+
Object.defineProperty(raw, key, {
|
|
104
|
+
value: getter,
|
|
105
|
+
enumerable: false,
|
|
106
|
+
configurable: true,
|
|
107
|
+
writable: true,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
if (!warnedFrozenFields.has(raw)) {
|
|
112
|
+
warnedFrozenFields.add(raw);
|
|
113
|
+
console.warn(
|
|
114
|
+
'[pro-components] 字段配置对象不可写(如被 Object.freeze),getFormItemRef / getComponentRef 等实例访问器挂载失败。'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
76
120
|
const setFormItemRef = (field: any, el: any, formItemProps?: any) => {
|
|
77
121
|
if (!field || typeof field !== 'object') return;
|
|
78
122
|
const raw = toRaw(field);
|
|
79
123
|
if (el) {
|
|
80
124
|
formItemInstanceMap.set(raw, el);
|
|
81
|
-
raw
|
|
82
|
-
|
|
125
|
+
defineFieldAccessors(raw, {
|
|
126
|
+
getFormItemRef: () => formItemInstanceMap.get(raw),
|
|
127
|
+
getFormItemComputedProps: () => formItemProps,
|
|
128
|
+
});
|
|
83
129
|
} else {
|
|
84
130
|
formItemInstanceMap.delete(raw);
|
|
85
131
|
delete raw.getFormItemRef;
|
|
@@ -92,10 +138,11 @@ const setComponentRef = (field: any, el: any) => {
|
|
|
92
138
|
const raw = toRaw(field);
|
|
93
139
|
if (el) {
|
|
94
140
|
componentInstanceMap.set(raw, el);
|
|
95
|
-
raw
|
|
96
|
-
componentInstanceMap.get(raw)?.getComponentRef()
|
|
97
|
-
|
|
98
|
-
|
|
141
|
+
defineFieldAccessors(raw, {
|
|
142
|
+
getComponentRef: () => componentInstanceMap.get(raw)?.getComponentRef(),
|
|
143
|
+
getComponentComputedProps: () =>
|
|
144
|
+
componentInstanceMap.get(raw)?.getComponentComputedProps(),
|
|
145
|
+
});
|
|
99
146
|
} else {
|
|
100
147
|
componentInstanceMap.delete(raw);
|
|
101
148
|
delete raw.getComponentRef;
|
|
@@ -112,7 +159,10 @@ const groupedFields = useGroupFieldAttrs(validFields);
|
|
|
112
159
|
v-bind="computedGridProps"
|
|
113
160
|
style="width: 100%"
|
|
114
161
|
>
|
|
115
|
-
<template
|
|
162
|
+
<template
|
|
163
|
+
v-for="item of groupedFields"
|
|
164
|
+
:key="item.path || getFieldKey(item.rawField)"
|
|
165
|
+
>
|
|
116
166
|
<ContainerFragment
|
|
117
167
|
:component="enableGrid ? ElGridItem : undefined"
|
|
118
168
|
v-bind="item.gridItemProps"
|
|
@@ -29,16 +29,19 @@ import {
|
|
|
29
29
|
FormProps as ElFormProps,
|
|
30
30
|
} from '../../../../shared/ui';
|
|
31
31
|
import {
|
|
32
|
+
computed,
|
|
32
33
|
inject,
|
|
33
34
|
mergeProps,
|
|
35
|
+
onUpdated,
|
|
34
36
|
provide,
|
|
37
|
+
shallowRef,
|
|
35
38
|
toValue,
|
|
36
|
-
|
|
37
|
-
computed,
|
|
39
|
+
triggerRef,
|
|
38
40
|
useAttrs,
|
|
41
|
+
type Slot,
|
|
39
42
|
} from 'vue';
|
|
40
43
|
import { INJECT_CONFIG } from '../../../component-provider/constants';
|
|
41
|
-
import
|
|
44
|
+
import BaseFormItem from '../BaseFormItem/index.vue';
|
|
42
45
|
import { InjectionFormKey, InjectionFormSlotsKey } from '../../constants';
|
|
43
46
|
import type { Grid, VModelProps, PathProps } from '../../types';
|
|
44
47
|
import {
|
|
@@ -63,12 +66,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
63
66
|
|
|
64
67
|
/**
|
|
65
68
|
* 依赖注入中心:
|
|
66
|
-
*
|
|
69
|
+
* 未传 form 实例(含运行期置空)时惰性初始化内部表单实例;
|
|
67
70
|
* 将 Form 实例注入到 Vue Context 中,供深层子组件跨层级直接访问数据和方法。
|
|
68
71
|
*/
|
|
69
|
-
|
|
72
|
+
let fallbackForm: F | undefined;
|
|
70
73
|
const currentForm = computed(
|
|
71
|
-
() => props.form ?? (fallbackForm as unknown as F)
|
|
74
|
+
() => props.form ?? (fallbackForm ??= useForm() as unknown as F)
|
|
72
75
|
);
|
|
73
76
|
|
|
74
77
|
/**
|
|
@@ -93,6 +96,7 @@ const setFormRef = (el: any) => currentForm.value?.setFormRef?.(el);
|
|
|
93
96
|
let formInstanceCounter = 0;
|
|
94
97
|
const formInstanceKeyMap = new WeakMap<object, number>();
|
|
95
98
|
const getFormKey = (f: object) => {
|
|
99
|
+
if (f == null) return 0;
|
|
96
100
|
if (!formInstanceKeyMap.has(f)) {
|
|
97
101
|
formInstanceKeyMap.set(f, ++formInstanceCounter);
|
|
98
102
|
}
|
|
@@ -109,7 +113,6 @@ const attrs = useAttrs();
|
|
|
109
113
|
*/
|
|
110
114
|
const injectAttrs = computed(() => {
|
|
111
115
|
const raw = toValue(injectFormConfig);
|
|
112
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
113
116
|
const { grid: _injectGrid, ...rest } = raw || {};
|
|
114
117
|
return rest;
|
|
115
118
|
});
|
|
@@ -125,11 +128,15 @@ type FieldSlots = Record<ExtractPath<F>, Slot<FieldSlotProps>>;
|
|
|
125
128
|
|
|
126
129
|
const slots = defineSlots<Partial<FieldSlots & { default: Slot }>>();
|
|
127
130
|
|
|
128
|
-
|
|
131
|
+
/*
|
|
129
132
|
* 动态插槽映射中心 (Slot Teleporting):
|
|
130
|
-
*
|
|
133
|
+
* slots 以 shallowRef 注入,组件每次更新后触发通知,深层 BaseField 可感知父级运行期增删的具名插槽
|
|
131
134
|
*/
|
|
132
|
-
|
|
135
|
+
const formSlotsRef = shallowRef<Record<string, any>>(slots);
|
|
136
|
+
onUpdated(() => {
|
|
137
|
+
triggerRef(formSlotsRef);
|
|
138
|
+
});
|
|
139
|
+
provide(InjectionFormSlotsKey, formSlotsRef);
|
|
133
140
|
</script>
|
|
134
141
|
|
|
135
142
|
<template>
|
|
@@ -46,22 +46,11 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
|
46
46
|
|
|
47
47
|
if (isPlainObject(field)) {
|
|
48
48
|
// prettier-ignore
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
50
49
|
const { path, name, fields, formItemClass, formItemStyle, formItemContainer, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, ...rest } =
|
|
51
50
|
field as any;
|
|
52
51
|
|
|
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;
|
|
52
|
+
// prettier-ignore
|
|
53
|
+
const { class: injectClassName, style: injectStyle, span: injectSpan, formItemContainer: injectFormItemContainer, xs, sm, md, lg, xl, ...injectRest } = rawInjectProps;
|
|
65
54
|
|
|
66
55
|
// 字段显式 span 优先,否则回退注入配置的响应式断点
|
|
67
56
|
const initGridItemProps =
|
|
@@ -69,7 +58,10 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
|
|
|
69
58
|
? { span }
|
|
70
59
|
: { xs, sm, md, lg, xl, span: injectSpan };
|
|
71
60
|
|
|
72
|
-
|
|
61
|
+
// 分组字段默认整行(span 兜底 24),注入断点同样生效
|
|
62
|
+
gridItemProps = fields
|
|
63
|
+
? { ...initGridItemProps, span: span ?? 24 }
|
|
64
|
+
: initGridItemProps;
|
|
73
65
|
formItemProps = mergeProps(
|
|
74
66
|
injectRest as any,
|
|
75
67
|
{ class: injectClassName, style: injectStyle },
|
|
@@ -91,10 +91,12 @@ const checkedColumnKeys = computed({
|
|
|
91
91
|
);
|
|
92
92
|
},
|
|
93
93
|
set(val: string[]) {
|
|
94
|
+
// 仅写 hidden 发生翻转的列
|
|
95
|
+
const checkedSet = new Set(val);
|
|
94
96
|
checkedColumnsOptions.value.forEach(item => {
|
|
95
|
-
if (item.disabled) return;
|
|
97
|
+
if (item.disabled || item.checked === checkedSet.has(item.value)) return;
|
|
96
98
|
props.table?.setColumn(item.value as any, {
|
|
97
|
-
hidden: !
|
|
99
|
+
hidden: !checkedSet.has(item.value),
|
|
98
100
|
});
|
|
99
101
|
});
|
|
100
102
|
},
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
useSlots,
|
|
54
54
|
} from 'vue';
|
|
55
55
|
import { INJECT_CONFIG } from '../../component-provider/constants';
|
|
56
|
-
import
|
|
56
|
+
import ContainerFragment from '../../form/components/ContainerFragment/index.vue';
|
|
57
57
|
import type { ContainerComponent } from '../../form/types';
|
|
58
58
|
import SearchForm from './SearchForm.vue';
|
|
59
59
|
import type { SearchFormProps } from './SearchForm.vue';
|
|
@@ -67,12 +67,10 @@ import {
|
|
|
67
67
|
isPlainObject,
|
|
68
68
|
Path,
|
|
69
69
|
} from '../../../shared/core';
|
|
70
|
-
import
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ColumnControl,
|
|
75
|
-
} from '.';
|
|
70
|
+
import DefaultSearchFormContainer from './DefaultSearchFormContainer.vue';
|
|
71
|
+
import DefaultTableContainer from './DefaultTableContainer.vue';
|
|
72
|
+
import SizeControl from './SizeControl.vue';
|
|
73
|
+
import ColumnControl from './ColumnControl.vue';
|
|
76
74
|
import type { Columns, ColumnControlConfig } from '../types';
|
|
77
75
|
import TableColumnRenderer from './TableColumnRenderer.vue';
|
|
78
76
|
|
|
@@ -201,10 +199,26 @@ const loading = defineModel<boolean>('loading', { default: false });
|
|
|
201
199
|
|
|
202
200
|
// 递增序号做请求竞态守卫,仅最新一次请求可变更 loading
|
|
203
201
|
let searchSequence = 0;
|
|
202
|
+
let warnedSearchConfig = false;
|
|
203
|
+
const warnSearchConfig = (reason: string) => {
|
|
204
|
+
if (warnedSearchConfig) return;
|
|
205
|
+
warnedSearchConfig = true;
|
|
206
|
+
console.warn(`[ProTable] ${reason}`);
|
|
207
|
+
};
|
|
204
208
|
const _search = async () => {
|
|
205
|
-
|
|
209
|
+
if (props.search && attrs.onSearch) {
|
|
210
|
+
warnSearchConfig(
|
|
211
|
+
'同时配置了 :search prop 与 @search 监听,@search 监听不会被执行,请仅保留其一。'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
206
214
|
const searchPromise = (props.search || attrs.onSearch)?.();
|
|
207
|
-
if (!isPromise(searchPromise))
|
|
215
|
+
if (!isPromise(searchPromise)) {
|
|
216
|
+
warnSearchConfig(
|
|
217
|
+
'search 未返回 Promise,本次查询不参与 loading 竞态管理(在途请求的 loading 状态保持不变)。'
|
|
218
|
+
);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const seq = ++searchSequence;
|
|
208
222
|
loading.value = true;
|
|
209
223
|
try {
|
|
210
224
|
await searchPromise;
|
|
@@ -294,13 +308,7 @@ const normalizedRawColumns = computed(() => {
|
|
|
294
308
|
|
|
295
309
|
const computedDataSource = computed<any[]>(() => {
|
|
296
310
|
const attrsData = attrsTableProps.value.data as any[] | undefined;
|
|
297
|
-
return (
|
|
298
|
-
props.data ??
|
|
299
|
-
attrsData ??
|
|
300
|
-
toValue((props.table as any)?.dataSource) ??
|
|
301
|
-
toValue(props.table?.data) ??
|
|
302
|
-
[]
|
|
303
|
-
);
|
|
311
|
+
return props.data ?? attrsData ?? toValue(props.table?.data) ?? [];
|
|
304
312
|
});
|
|
305
313
|
|
|
306
314
|
const computedTableProps = computed<TableProps>(() => {
|
|
@@ -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';
|
|
@@ -122,6 +122,7 @@ const onReset = () => {
|
|
|
122
122
|
emit('reset');
|
|
123
123
|
};
|
|
124
124
|
|
|
125
|
+
// 查询按钮为 native-type=submit:点击与回车统一经表单 submit 事件触发
|
|
125
126
|
const onSearch = () => {
|
|
126
127
|
emit('search');
|
|
127
128
|
};
|
|
@@ -132,6 +133,7 @@ const onSearch = () => {
|
|
|
132
133
|
v-bind="layoutProps"
|
|
133
134
|
:form="props.form"
|
|
134
135
|
class="pro-table_search-form transition"
|
|
136
|
+
@submit.prevent="onSearch"
|
|
135
137
|
>
|
|
136
138
|
<Space alignment="start" class="pro-table_search-form_button-group">
|
|
137
139
|
<slot name="reset-button" :on-reset="onReset">
|
|
@@ -162,7 +164,6 @@ const onSearch = () => {
|
|
|
162
164
|
class="pro-table_search-form_search-button"
|
|
163
165
|
type="primary"
|
|
164
166
|
native-type="submit"
|
|
165
|
-
@click="onSearch"
|
|
166
167
|
>
|
|
167
168
|
{{ t('table.query') }}
|
|
168
169
|
</Button>
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
>
|
|
6
6
|
import { get, normalizePathKey } from '../../../shared/core';
|
|
7
7
|
import { TableColumn } from '../../../shared/ui';
|
|
8
|
-
import
|
|
8
|
+
import SlotComponent from '../../form/components/SlotComponent/index.vue';
|
|
9
9
|
import type { Column, Columns } from '../types';
|
|
10
|
-
import { computed } from 'vue';
|
|
10
|
+
import { computed, toRaw } from 'vue';
|
|
11
11
|
|
|
12
12
|
defineOptions({ name: 'TableColumnRenderer' });
|
|
13
13
|
|
|
@@ -18,9 +18,26 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
18
18
|
columns: () => [],
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/*
|
|
22
|
+
* 无 key/prop 列的稳定渲染 key:以列原始对象为 key 分配自增 id,
|
|
23
|
+
* 列插删/重排/过滤时保持 key 与对象绑定,不随数组位置漂移
|
|
24
|
+
*/
|
|
25
|
+
let columnKeySeed = 0;
|
|
26
|
+
const columnKeyMap = new WeakMap<object, number>();
|
|
27
|
+
const getStableColumnKey = (column: unknown): number => {
|
|
28
|
+
const raw = toRaw(column as object);
|
|
29
|
+
let key = columnKeyMap.get(raw);
|
|
30
|
+
if (key === undefined) {
|
|
31
|
+
key = ++columnKeySeed;
|
|
32
|
+
columnKeyMap.set(raw, key);
|
|
33
|
+
}
|
|
34
|
+
return key;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getColumnKey = (column: Column<D>) => {
|
|
22
38
|
const rawKey = column.key ?? column.prop;
|
|
23
|
-
if (rawKey === undefined || rawKey === null)
|
|
39
|
+
if (rawKey === undefined || rawKey === null)
|
|
40
|
+
return getStableColumnKey(column);
|
|
24
41
|
return normalizePathKey(rawKey);
|
|
25
42
|
};
|
|
26
43
|
|
|
@@ -35,7 +52,6 @@ const normalizedColumns = computed(() => {
|
|
|
35
52
|
});
|
|
36
53
|
|
|
37
54
|
const getColumnProps = (column: Column<D>) => {
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
39
55
|
const { hidden, render, key, children, ...rest } = column;
|
|
40
56
|
const prop = column.prop ? normalizePathKey(column.prop) : undefined;
|
|
41
57
|
const columnKey = key ? normalizePathKey(key) : prop;
|
|
@@ -49,10 +65,7 @@ const getColumnProps = (column: Column<D>) => {
|
|
|
49
65
|
</script>
|
|
50
66
|
|
|
51
67
|
<template>
|
|
52
|
-
<template
|
|
53
|
-
v-for="(column, index) in normalizedColumns"
|
|
54
|
-
:key="getColumnKey(column, index)"
|
|
55
|
-
>
|
|
68
|
+
<template v-for="column in normalizedColumns" :key="getColumnKey(column)">
|
|
56
69
|
<!-- 分组列(递归子列) -->
|
|
57
70
|
<TableColumn
|
|
58
71
|
v-if="column.children && column.children.length > 0"
|
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": ["element-plus/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
|
+
}
|