@qin-ui/antd-vue-pro 2.4.4 → 2.5.0
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 +26 -2
- package/README.md +44 -37
- package/api.json +24 -9
- package/es/antd-vue-pro.css +15 -15
- package/es/components/component-provider/components/index.vue.d.ts +3 -3
- package/es/components/component-provider/components/index.vue.d.ts.map +1 -1
- package/es/components/component-provider/constants/index.d.ts +20 -4
- package/es/components/component-provider/constants/index.d.ts.map +1 -1
- package/es/components/component-provider/types/index.d.ts +8 -4
- package/es/components/component-provider/types/index.d.ts.map +1 -1
- package/es/components/form/components/BaseField/index.vue.d.ts +7 -1
- package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -1
- package/es/components/form/components/BaseField/utils/index.d.ts +18 -0
- package/es/components/form/components/BaseField/utils/index.d.ts.map +1 -1
- package/es/components/form/components/BaseFormItem/index.vue.d.ts +3 -0
- package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -1
- package/es/components/form/components/ContainerFragment/index.vue.d.ts +1 -0
- package/es/components/form/components/ContainerFragment/index.vue.d.ts.map +1 -1
- package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -1
- package/es/components/form/components/SlotComponent/index.vue.d.ts +1 -0
- package/es/components/form/components/SlotComponent/index.vue.d.ts.map +1 -1
- package/es/components/form/components/index.d.ts +4 -0
- package/es/components/form/components/index.d.ts.map +1 -1
- package/es/components/form/constants/index.d.ts.map +1 -1
- package/es/components/form/hooks/useFields.d.ts +2 -0
- package/es/components/form/hooks/useFields.d.ts.map +1 -1
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts +10 -0
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -1
- package/es/components/form/index.d.ts +2 -2
- package/es/components/form/index.d.ts.map +1 -1
- package/es/components/form/types/index.d.ts +75 -44
- package/es/components/form/types/index.d.ts.map +1 -1
- package/es/components/form/utils/fieldKey.d.ts +13 -0
- package/es/components/form/utils/fieldKey.d.ts.map +1 -0
- package/es/components/table/components/ColumnControl.vue.d.ts +3 -0
- package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -1
- package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +1 -1
- package/es/components/table/components/DefaultTableContainer.vue.d.ts.map +1 -1
- package/es/components/table/components/ProTable.vue.d.ts +8 -1
- package/es/components/table/components/ProTable.vue.d.ts.map +1 -1
- package/es/components/table/components/SearchForm.vue.d.ts +10 -2
- package/es/components/table/components/SearchForm.vue.d.ts.map +1 -1
- package/es/components/table/components/SizeControl.vue.d.ts +7 -2
- package/es/components/table/components/SizeControl.vue.d.ts.map +1 -1
- package/es/components/table/hooks/useTable.d.ts.map +1 -1
- package/es/components/table/index.d.ts +4 -1
- package/es/components/table/index.d.ts.map +1 -1
- package/es/components/table/types/index.d.ts +14 -6
- package/es/components/table/types/index.d.ts.map +1 -1
- package/es/index.d.ts +8 -4
- package/es/index.d.ts.map +1 -1
- package/es/index.js +528 -343
- package/es/index.js.map +1 -1
- package/es/locale/index.d.ts +2 -0
- package/es/locale/index.d.ts.map +1 -1
- package/es/shared/core/index.d.ts +1 -1
- package/es/shared/core/index.d.ts.map +1 -1
- package/es/shared/ui/index.d.ts +15 -3
- package/es/shared/ui/index.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/components/component-provider/components/index.vue +26 -12
- package/src/components/component-provider/constants/index.ts +33 -6
- package/src/components/component-provider/types/index.ts +17 -9
- package/src/components/form/components/BaseField/index.vue +48 -16
- package/src/components/form/components/BaseField/utils/index.ts +19 -1
- package/src/components/form/components/BaseFormItem/index.vue +65 -17
- package/src/components/form/components/ContainerFragment/index.vue +3 -1
- package/src/components/form/components/ProForm/index.vue +30 -18
- package/src/components/form/components/SlotComponent/index.vue +14 -4
- package/src/components/form/components/index.ts +8 -1
- package/src/components/form/constants/index.ts +3 -4
- package/src/components/form/hooks/useFields.ts +3 -1
- package/src/components/form/hooks/useGroupFieldAttrs.ts +34 -27
- package/src/components/form/index.ts +1 -3
- package/src/components/form/types/index.ts +79 -46
- package/src/components/form/utils/fieldKey.ts +27 -0
- package/src/components/table/components/ColumnControl.vue +21 -5
- package/src/components/table/components/DefaultSearchFormContainer.vue +5 -0
- package/src/components/table/components/DefaultTableContainer.vue +5 -0
- package/src/components/table/components/ProTable.vue +239 -66
- package/src/components/table/components/SearchForm.vue +58 -21
- package/src/components/table/components/SizeControl.vue +21 -1
- package/src/components/table/hooks/useTable.ts +1 -1
- package/src/components/table/index.ts +8 -3
- package/src/components/table/types/index.ts +15 -7
- package/src/index.ts +12 -5
- package/src/locale/index.ts +2 -0
- package/src/shared/core/index.ts +11 -0
- package/src/shared/ui/index.ts +22 -4
- package/tsconfig.json +22 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts" setup generic="T extends Table<any> = Table">
|
|
2
2
|
/**
|
|
3
|
+
* 配置驱动的表格组件
|
|
4
|
+
*
|
|
3
5
|
* @component ProTable
|
|
4
|
-
* @description 配置驱动的表格组件
|
|
5
6
|
*
|
|
6
7
|
* ## 架构概览
|
|
7
8
|
*
|
|
@@ -25,52 +26,67 @@
|
|
|
25
26
|
* └── 转发所有 table 插槽
|
|
26
27
|
* ```
|
|
27
28
|
*
|
|
28
|
-
* ##
|
|
29
|
+
* ## 搜索行为
|
|
30
|
+
*
|
|
31
|
+
* 1. 搜索:分页回到第一页后发起查询
|
|
32
|
+
* 2. 分页变化:分页参数同步后发起查询
|
|
33
|
+
* 3. 重置:查询参数恢复初始值后重新查询
|
|
34
|
+
*
|
|
35
|
+
* @template T - 表格实例类型(useTable 返回值)
|
|
29
36
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
37
|
+
* @param {T} [table] - 列表表格对象,来自 useTable
|
|
38
|
+
* @param {() => Promise<unknown>} [search] - 列表数据查询方法
|
|
39
|
+
* @param {boolean} [addIndexColumn] - 是否在首列插入序号列
|
|
40
|
+
* @param {boolean} [immediateSearch] - 挂载后是否立即触发一次查询
|
|
41
|
+
* @param {boolean | Partial<Control>} [control] - 是否展示尺寸与列控制按钮;显式传入 columns 时列控入口不渲染
|
|
42
|
+
* @param {SearchFormConfig} [searchFormConfig] - 搜索栏查询字段表单配置
|
|
43
|
+
* @param {ContainerComponent | false} [tableContainer] - 表格区域包裹容器
|
|
44
|
+
*
|
|
45
|
+
* @slot search-form - 搜索区域插槽,作用域参数 form / search / reset
|
|
46
|
+
* @slot button-bar - 表格头部按钮区插槽
|
|
47
|
+
* @slot toolbar - 工具栏插槽
|
|
48
|
+
* @slot default - 透传给宿主表格的默认插槽(可用于以宿主列组件声明列)
|
|
49
|
+
* @slot table - 表格区域插槽,作用域参数 columns / dataSource / pagination / loading / size / onChange
|
|
34
50
|
*/
|
|
35
|
-
import {
|
|
51
|
+
import type {
|
|
36
52
|
PaginationProps,
|
|
37
|
-
Table as ATable,
|
|
38
53
|
TableProps,
|
|
39
54
|
ColumnType,
|
|
40
|
-
tableProps,
|
|
41
55
|
} from '../../../shared/ui';
|
|
56
|
+
import { Table as ATable, tableProps } from '../../../shared/ui';
|
|
42
57
|
import {
|
|
43
58
|
computed,
|
|
59
|
+
getCurrentInstance,
|
|
44
60
|
nextTick,
|
|
45
|
-
useAttrs,
|
|
46
61
|
onMounted,
|
|
47
62
|
inject,
|
|
48
63
|
toValue,
|
|
49
64
|
type Slot,
|
|
50
65
|
} from 'vue';
|
|
51
66
|
import { INJECT_CONFIG } from '../../component-provider/constants';
|
|
52
|
-
import
|
|
67
|
+
import ContainerFragment from '../../form/components/ContainerFragment/index.vue';
|
|
53
68
|
import type { ContainerComponent } from '../../form/types';
|
|
54
69
|
import SearchForm from './SearchForm.vue';
|
|
55
|
-
import { SearchFormProps } from './SearchForm.vue';
|
|
70
|
+
import type { SearchFormProps } from './SearchForm.vue';
|
|
56
71
|
import type { Table } from '../hooks/useTable';
|
|
57
72
|
import { useLocale } from '../../../locale';
|
|
73
|
+
import type { Path } from '../../../shared/core';
|
|
58
74
|
import {
|
|
75
|
+
camelizeProperties,
|
|
59
76
|
getObject,
|
|
77
|
+
isObjectItem,
|
|
60
78
|
isPlainObject,
|
|
61
|
-
pick,
|
|
62
79
|
isPromise,
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
omitBy,
|
|
81
|
+
pick,
|
|
82
|
+
useReactiveAttrs,
|
|
65
83
|
} from '../../../shared/core';
|
|
66
|
-
import { ComponentSlots } from 'vue-component-type-helpers';
|
|
67
|
-
import
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} from '.';
|
|
73
|
-
import { Column, Columns, ColumnControlConfig } from '../types';
|
|
84
|
+
import type { ComponentSlots } from 'vue-component-type-helpers';
|
|
85
|
+
import DefaultSearchFormContainer from './DefaultSearchFormContainer.vue';
|
|
86
|
+
import DefaultTableContainer from './DefaultTableContainer.vue';
|
|
87
|
+
import SizeControl from './SizeControl.vue';
|
|
88
|
+
import ColumnControl from './ColumnControl.vue';
|
|
89
|
+
import type { Column, Columns, ColumnControlConfig } from '../types';
|
|
74
90
|
|
|
75
91
|
defineOptions({ name: 'ProTable', inheritAttrs: false });
|
|
76
92
|
|
|
@@ -89,19 +105,19 @@ type SearchFormConfig = Omit<SearchFormProps, 'form'> & {
|
|
|
89
105
|
};
|
|
90
106
|
|
|
91
107
|
type Props = {
|
|
92
|
-
|
|
108
|
+
/** 列表表格对象,来自 `useTable` */
|
|
93
109
|
table?: T;
|
|
94
|
-
|
|
110
|
+
/** 列表数据查询方法 */
|
|
95
111
|
search?: () => Promise<unknown>;
|
|
96
|
-
|
|
112
|
+
/** 是否在首列插入序号列 */
|
|
97
113
|
addIndexColumn?: boolean;
|
|
98
|
-
|
|
114
|
+
/** 挂载后是否立即触发一次查询 */
|
|
99
115
|
immediateSearch?: boolean;
|
|
100
|
-
|
|
116
|
+
/** 是否展示尺寸与列控制按钮;显式传入 columns 时列控入口不渲染 */
|
|
101
117
|
control?: boolean | Partial<Control>;
|
|
102
|
-
|
|
118
|
+
/** 搜索栏查询字段表单配置 */
|
|
103
119
|
searchFormConfig?: SearchFormConfig;
|
|
104
|
-
|
|
120
|
+
/** 表格区域包裹容器 */
|
|
105
121
|
tableContainer?: ContainerComponent | false;
|
|
106
122
|
} & /* @vue-ignore */ TableProps;
|
|
107
123
|
|
|
@@ -121,6 +137,7 @@ const config = INJECT_CONFIG['pro-table'];
|
|
|
121
137
|
const injectPropsRaw = inject(config.injectionKey, config.default);
|
|
122
138
|
const injectProps = computed(() => toValue(injectPropsRaw) || {});
|
|
123
139
|
|
|
140
|
+
/* Provider 注入的不可注入键(columns / dataSource / loading)已在注入层剔除,此处直接取用其注入属性 */
|
|
124
141
|
const injectAttrs = computed<TableProps>(
|
|
125
142
|
() => pick(injectProps.value, tableProperties) as TableProps
|
|
126
143
|
);
|
|
@@ -137,20 +154,48 @@ const computedSearchFormConfig = computed(() => {
|
|
|
137
154
|
};
|
|
138
155
|
});
|
|
139
156
|
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
157
|
+
/** SearchForm 属性面:容器与显隐由 ProTable 自身消费,不下发 SearchForm */
|
|
158
|
+
const searchFormProps = computed(() => {
|
|
159
|
+
const {
|
|
160
|
+
container: _container,
|
|
161
|
+
hidden: _hidden,
|
|
162
|
+
...rest
|
|
163
|
+
} = computedSearchFormConfig.value;
|
|
164
|
+
return rest;
|
|
143
165
|
});
|
|
144
166
|
|
|
167
|
+
const attrs: Record<string, any> = useReactiveAttrs();
|
|
168
|
+
|
|
145
169
|
const size = defineModel<TableProps['size']>('size');
|
|
146
170
|
const loading = defineModel<boolean>('loading', { default: false });
|
|
147
171
|
|
|
148
172
|
// 递增序号做请求竞态守卫,仅最新一次请求可变更 loading
|
|
149
173
|
let searchSequence = 0;
|
|
174
|
+
// 告警文案按原因分键,各原因独立去重
|
|
175
|
+
const SEARCH_WARN_MESSAGES = {
|
|
176
|
+
duplicate:
|
|
177
|
+
'同时配置了 :search prop 与 @search 监听,@search 监听不会被执行,请仅保留其一。',
|
|
178
|
+
'not-promise':
|
|
179
|
+
'search 未返回 Promise,本次查询不参与 loading 竞态管理(在途请求的 loading 状态保持不变)。',
|
|
180
|
+
} as const;
|
|
181
|
+
const warnedSearchReasons = new Set<keyof typeof SEARCH_WARN_MESSAGES>();
|
|
182
|
+
const warnSearchConfig = (reason: keyof typeof SEARCH_WARN_MESSAGES) => {
|
|
183
|
+
if (warnedSearchReasons.has(reason)) return;
|
|
184
|
+
warnedSearchReasons.add(reason);
|
|
185
|
+
console.warn(`[ProTable] ${SEARCH_WARN_MESSAGES[reason]}`);
|
|
186
|
+
};
|
|
150
187
|
const _search = async () => {
|
|
188
|
+
const search = props.search || attrs.onSearch;
|
|
189
|
+
if (props.search && attrs.onSearch) {
|
|
190
|
+
warnSearchConfig('duplicate');
|
|
191
|
+
}
|
|
192
|
+
if (!search) return;
|
|
193
|
+
const searchPromise = search();
|
|
194
|
+
if (!isPromise(searchPromise)) {
|
|
195
|
+
warnSearchConfig('not-promise');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
151
198
|
const seq = ++searchSequence;
|
|
152
|
-
const searchPromise = (props.search || attrs.onSearch)?.();
|
|
153
|
-
if (!isPromise(searchPromise)) return;
|
|
154
199
|
loading.value = true;
|
|
155
200
|
try {
|
|
156
201
|
await searchPromise;
|
|
@@ -204,10 +249,15 @@ const columnRequiredSlotNames = new Set([
|
|
|
204
249
|
]);
|
|
205
250
|
|
|
206
251
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
252
|
+
* 判定插槽在当前渲染状态下是否可渲染。
|
|
253
|
+
*
|
|
254
|
+
* @description
|
|
255
|
+
* 依赖列信息的插槽(`bodyCell` / `headerCell` / `customFilterDropdown` / `customFilterIcon`)仅在存在可见列且作用域中存在 `column` 时渲染;
|
|
256
|
+
* 非列依赖的表格级插槽(如 `emptyText`、`footer`、`summary` 等)始终渲染。
|
|
257
|
+
*
|
|
258
|
+
* @param name - 插槽名
|
|
259
|
+
* @param scoped - 插槽作用域参数
|
|
260
|
+
* @returns 该插槽是否参与渲染
|
|
211
261
|
*/
|
|
212
262
|
const isSlotRenderable = (name: string, scoped: any) => {
|
|
213
263
|
if (columnRequiredSlotNames.has(name)) {
|
|
@@ -221,13 +271,65 @@ const searchPage1st = () => {
|
|
|
221
271
|
_search();
|
|
222
272
|
};
|
|
223
273
|
|
|
274
|
+
const instance = getCurrentInstance();
|
|
275
|
+
|
|
276
|
+
/** 调用方是否显式绑定该 prop(含 kebab-case 与 v-model 写法) */
|
|
277
|
+
const isBoundProp = (name: string) => {
|
|
278
|
+
const vnodeProps = instance?.vnode.props || {};
|
|
279
|
+
const kebab = name.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`);
|
|
280
|
+
return (
|
|
281
|
+
name in vnodeProps ||
|
|
282
|
+
kebab in vnodeProps ||
|
|
283
|
+
`onUpdate:${name}` in vnodeProps
|
|
284
|
+
);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/** attrs 的驼峰视图:模板中以 kebab-case 写法的绑定键在此归一 */
|
|
288
|
+
const camelAttrs = computed<Record<string, any>>(() =>
|
|
289
|
+
camelizeProperties(attrs)
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
/*
|
|
293
|
+
* 组件自有键取值:`Props` 中声明的键取 prop,其余宿主键取 attrs(模板 kebab-case 绑定经归一后同键);
|
|
294
|
+
* 均以调用方显式绑定为唯一来源,未绑定时不取值(留给 Provider 注入兜底)
|
|
295
|
+
*/
|
|
296
|
+
const resolveOwnedProp = (name: string) => {
|
|
297
|
+
const attrValue = camelAttrs.value[name];
|
|
298
|
+
return isBoundProp(name)
|
|
299
|
+
? ((props as Record<string, any>)[name] ?? attrValue)
|
|
300
|
+
: attrValue;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/** 调用方是否显式传入 columns(prop 或 attrs 形态,值为 undefined 时视为未指定) */
|
|
304
|
+
const hasExplicitColumns = computed(
|
|
305
|
+
() => resolveOwnedProp('columns') !== undefined
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
/*
|
|
309
|
+
* 表格尺寸单一来源:本地模型(显式绑定或 SizeControl 回写)> Provider 注入;
|
|
310
|
+
* 本地模型为 undefined 时视为未指定,Provider 注入值继续生效
|
|
311
|
+
*/
|
|
312
|
+
const effectiveSize = computed<TableProps['size']>(() => {
|
|
313
|
+
return size.value ?? injectAttrs.value.size;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
/*
|
|
317
|
+
* 下发给宿主表格的属性:Provider 注入为底(其不可注入键已在注入层剔除),调用方绑定与 v-model 透传值覆盖同键。
|
|
318
|
+
* size / loading 为声明模型,其 onUpdate:* 回写通道不进入 attrs,无需另行剔除
|
|
319
|
+
*/
|
|
224
320
|
const computedTableProps = computed<TableProps>(() => {
|
|
225
|
-
|
|
321
|
+
const merged = {
|
|
226
322
|
...injectAttrs.value,
|
|
227
|
-
...
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
323
|
+
...omitBy(
|
|
324
|
+
pick(camelAttrs.value, tableProperties),
|
|
325
|
+
value => value === undefined
|
|
326
|
+
),
|
|
327
|
+
} as Record<string, any>;
|
|
328
|
+
// onChange 由模板监听器收口,散落 attrs 中的同名监听器并入会形成重复回调
|
|
329
|
+
delete merged.onChange;
|
|
330
|
+
merged.size = effectiveSize.value;
|
|
331
|
+
merged.loading = undefined;
|
|
332
|
+
return merged as TableProps;
|
|
231
333
|
});
|
|
232
334
|
|
|
233
335
|
const computedTableContainer = computed(() => {
|
|
@@ -241,15 +343,17 @@ const computedTableContainer = computed(() => {
|
|
|
241
343
|
type ReturnGenericParameterTypes<V> = V extends Table<any, infer R> ? R : never;
|
|
242
344
|
type RecordType = ReturnGenericParameterTypes<T>;
|
|
243
345
|
|
|
244
|
-
|
|
346
|
+
/*
|
|
347
|
+
* 序号列随有效分页偏移:分页关闭时退化为 index + 1;
|
|
348
|
+
* current / pageSize 取有效分页(显式 :pagination 或 Provider 注入),table.pageParam 仅作兜底
|
|
349
|
+
*/
|
|
245
350
|
const getIndex = (i: number) => {
|
|
351
|
+
const pagination = computedPagination.value;
|
|
352
|
+
if (pagination === false) return i + 1;
|
|
246
353
|
const pageParam = props.table?.pageParam;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return (current - 1) * pageSize + i + 1;
|
|
251
|
-
}
|
|
252
|
-
return i + 1;
|
|
354
|
+
const current = pagination.current ?? pageParam?.current ?? 1;
|
|
355
|
+
const pageSize = pagination.pageSize ?? pageParam?.pageSize ?? 10;
|
|
356
|
+
return (current - 1) * pageSize + i + 1;
|
|
253
357
|
};
|
|
254
358
|
|
|
255
359
|
const indexColumn = computed<ColumnType>(() => ({
|
|
@@ -260,26 +364,72 @@ const indexColumn = computed<ColumnType>(() => ({
|
|
|
260
364
|
customRender: ({ index }) => getIndex(index),
|
|
261
365
|
}));
|
|
262
366
|
|
|
367
|
+
// 列来源优先级:显式绑定 > table 实例(useTable 创建的 columns)
|
|
263
368
|
const computedColumns = computed<Columns<RecordType>>(() => {
|
|
264
|
-
const cols = (
|
|
369
|
+
const cols = (resolveOwnedProp('columns') ??
|
|
265
370
|
toValue(props.table?.columns) ??
|
|
266
371
|
[]) as any;
|
|
267
372
|
const needIndex = props.addIndexColumn ?? injectProps.value.addIndexColumn;
|
|
268
373
|
return needIndex ? [indexColumn.value, ...cols] : cols;
|
|
269
374
|
});
|
|
270
375
|
|
|
376
|
+
/*
|
|
377
|
+
* 递归过滤 hidden 列(含分组子列):仅对被过滤过子列的列生成浅拷贝,
|
|
378
|
+
* 未发生变动的列保持原对象引用(每次 columns 变化执行一次,不在渲染期递归)
|
|
379
|
+
*/
|
|
380
|
+
const filterVisibleColumns = (
|
|
381
|
+
columns: any[],
|
|
382
|
+
ancestors: Set<object> = new Set()
|
|
383
|
+
): { list: any[]; changed: boolean } => {
|
|
384
|
+
const list: any[] = [];
|
|
385
|
+
let changed = false;
|
|
386
|
+
for (const column of columns) {
|
|
387
|
+
if (!isObjectItem(column) || toValue((column as Column).hidden)) {
|
|
388
|
+
changed = true;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (Array.isArray(column.children) && column.children.length > 0) {
|
|
392
|
+
// 环状分组列不再深入,重复出现的祖先引用直接剔除
|
|
393
|
+
if (ancestors.has(column)) {
|
|
394
|
+
changed = true;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
ancestors.add(column);
|
|
398
|
+
const filtered = filterVisibleColumns(column.children, ancestors);
|
|
399
|
+
ancestors.delete(column);
|
|
400
|
+
if (filtered.changed) {
|
|
401
|
+
changed = true;
|
|
402
|
+
list.push({ ...column, children: filtered.list });
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
list.push(column);
|
|
407
|
+
}
|
|
408
|
+
return { list, changed };
|
|
409
|
+
};
|
|
410
|
+
|
|
271
411
|
const visibleComputedColumns = computed(() => {
|
|
272
|
-
return computedColumns.value
|
|
412
|
+
return filterVisibleColumns(computedColumns.value).list;
|
|
273
413
|
});
|
|
274
414
|
|
|
415
|
+
// 数据源优先级:显式绑定 > table 实例(useTable 创建的 dataSource)
|
|
275
416
|
const computedDataSource = computed<RecordType[]>(() => {
|
|
276
|
-
return
|
|
417
|
+
return (
|
|
418
|
+
resolveOwnedProp('dataSource') ?? toValue(props.table?.dataSource) ?? []
|
|
419
|
+
);
|
|
277
420
|
});
|
|
278
421
|
|
|
279
|
-
|
|
422
|
+
/*
|
|
423
|
+
* 分页优先级:显式绑定 > provider 全局配置 > 默认合成;
|
|
424
|
+
* provider 作为基础配置与 table.pageParam 合并,显式绑定的对象形态在其之上覆盖
|
|
425
|
+
*/
|
|
280
426
|
const computedPagination = computed<PaginationProps | false>(() => {
|
|
281
|
-
|
|
282
|
-
|
|
427
|
+
const attrsPagination = resolveOwnedProp('pagination') as
|
|
428
|
+
| PaginationProps
|
|
429
|
+
| false
|
|
430
|
+
| undefined;
|
|
431
|
+
if (attrsPagination === false) return false;
|
|
432
|
+
if (isPlainObject(attrsPagination)) {
|
|
283
433
|
const globalPagination =
|
|
284
434
|
injectAttrs.value.pagination === false
|
|
285
435
|
? {}
|
|
@@ -287,9 +437,9 @@ const computedPagination = computed<PaginationProps | false>(() => {
|
|
|
287
437
|
return {
|
|
288
438
|
...globalPagination,
|
|
289
439
|
...props.table?.pageParam,
|
|
290
|
-
...
|
|
440
|
+
...attrsPagination,
|
|
291
441
|
showTotal:
|
|
292
|
-
|
|
442
|
+
attrsPagination?.showTotal ??
|
|
293
443
|
globalPagination?.showTotal ??
|
|
294
444
|
((total: number) => t('pagination.total', { total })),
|
|
295
445
|
};
|
|
@@ -318,11 +468,15 @@ const searchFormVisible = computed(() => {
|
|
|
318
468
|
return !!(
|
|
319
469
|
computedSearchFormConfig.value?.hidden !== true &&
|
|
320
470
|
props.table?.searchForm &&
|
|
321
|
-
fieldList.filter(item => !toValue(item.hidden))
|
|
471
|
+
fieldList.filter(item => isObjectItem(item) && !toValue(item.hidden))
|
|
472
|
+
.length > 0
|
|
322
473
|
);
|
|
323
474
|
});
|
|
324
475
|
|
|
325
|
-
|
|
476
|
+
/*
|
|
477
|
+
* 控制项来源:显式控制配置 > Provider 全局配置
|
|
478
|
+
*/
|
|
479
|
+
const resolvedControl = computed<{
|
|
326
480
|
sizeControl: boolean;
|
|
327
481
|
columnControl: boolean | ColumnControlConfig<Path<RecordType>>;
|
|
328
482
|
}>(() => {
|
|
@@ -339,6 +493,13 @@ const computedControl = computed<{
|
|
|
339
493
|
};
|
|
340
494
|
});
|
|
341
495
|
|
|
496
|
+
/* 显式 columns 模式下渲染源不受 table.setColumn 影响,列控入口隐藏 */
|
|
497
|
+
const computedControl = computed(() =>
|
|
498
|
+
hasExplicitColumns.value && resolvedControl.value.columnControl
|
|
499
|
+
? { ...resolvedControl.value, columnControl: false }
|
|
500
|
+
: resolvedControl.value
|
|
501
|
+
);
|
|
502
|
+
|
|
342
503
|
const columnControlConfig = computed(() => {
|
|
343
504
|
const cc = computedControl.value.columnControl;
|
|
344
505
|
return typeof cc === 'object' ? cc : undefined;
|
|
@@ -362,11 +523,14 @@ defineExpose({
|
|
|
362
523
|
v-if="searchFormVisible || $slots['search-form']"
|
|
363
524
|
:component="computedSearchFormConfig.container"
|
|
364
525
|
>
|
|
365
|
-
<slot
|
|
526
|
+
<slot
|
|
527
|
+
name="search-form"
|
|
528
|
+
v-bind="{ form: props.table?.searchForm, search: searchPage1st, reset }"
|
|
529
|
+
>
|
|
366
530
|
<SearchForm
|
|
367
531
|
v-if="props.table?.searchForm"
|
|
368
532
|
:form="props.table.searchForm"
|
|
369
|
-
v-bind="
|
|
533
|
+
v-bind="searchFormProps"
|
|
370
534
|
@search="searchPage1st"
|
|
371
535
|
@reset="reset"
|
|
372
536
|
/>
|
|
@@ -385,7 +549,7 @@ defineExpose({
|
|
|
385
549
|
v-if="computedControl.sizeControl"
|
|
386
550
|
class="pro-table_header_size-control"
|
|
387
551
|
>
|
|
388
|
-
<SizeControl v-model:size="size" />
|
|
552
|
+
<SizeControl v-model:size="size" :effective-size="effectiveSize" />
|
|
389
553
|
</div>
|
|
390
554
|
<div
|
|
391
555
|
v-if="computedControl.columnControl"
|
|
@@ -399,11 +563,20 @@ defineExpose({
|
|
|
399
563
|
</div>
|
|
400
564
|
</div>
|
|
401
565
|
|
|
402
|
-
<slot
|
|
566
|
+
<slot
|
|
567
|
+
name="table"
|
|
568
|
+
v-bind="{
|
|
569
|
+
columns: visibleComputedColumns,
|
|
570
|
+
dataSource: computedDataSource,
|
|
571
|
+
pagination: computedPagination,
|
|
572
|
+
loading: loading,
|
|
573
|
+
size: effectiveSize,
|
|
574
|
+
onChange: onTableChange,
|
|
575
|
+
}"
|
|
576
|
+
>
|
|
403
577
|
<ATable
|
|
404
578
|
class="pro-table_content"
|
|
405
579
|
v-bind="computedTableProps"
|
|
406
|
-
:size="size"
|
|
407
580
|
:loading="loading"
|
|
408
581
|
:columns="visibleComputedColumns"
|
|
409
582
|
:data-source="computedDataSource"
|
|
@@ -21,17 +21,26 @@ type _FormProps = Pick<
|
|
|
21
21
|
type ButtonProps = { onClick: () => void };
|
|
22
22
|
type ExpandButtonProps = ButtonProps & { expandStatus: boolean };
|
|
23
23
|
|
|
24
|
+
/** ProTable 搜索表单的属性面:表单实例、布局、展开与按钮自定义 */
|
|
24
25
|
export type SearchFormProps = {
|
|
26
|
+
/** 搜索表单实例,由 table.searchForm 提供 */
|
|
25
27
|
form: Form;
|
|
28
|
+
/** 布局形态,默认 'grid' */
|
|
26
29
|
layout?: 'grid' | 'inline';
|
|
30
|
+
/** 展开配置,默认 true */
|
|
27
31
|
expand?: boolean | Expand;
|
|
32
|
+
/** 自定义搜索按钮,false 时不渲染 */
|
|
28
33
|
searchButton?: Component<ButtonProps> | DefineComponent<ButtonProps> | false;
|
|
34
|
+
/** 自定义重置按钮,false 时不渲染 */
|
|
29
35
|
resetButton?: Component<ButtonProps> | DefineComponent<ButtonProps> | false;
|
|
36
|
+
/** 自定义展开/折叠按钮,false 时不渲染 */
|
|
30
37
|
expandButton?:
|
|
31
38
|
| Component<ExpandButtonProps>
|
|
32
39
|
| DefineComponent<ExpandButtonProps>
|
|
33
40
|
| false;
|
|
41
|
+
/** 行间距,默认 16 */
|
|
34
42
|
rowGap?: number;
|
|
43
|
+
/** 列间距,默认 24 */
|
|
35
44
|
columnGap?: number;
|
|
36
45
|
} & /* @vue-ignore */ _FormProps &
|
|
37
46
|
AllowedComponentProps;
|
|
@@ -39,32 +48,42 @@ export type SearchFormProps = {
|
|
|
39
48
|
|
|
40
49
|
<script lang="ts" setup>
|
|
41
50
|
/**
|
|
51
|
+
* ProTable 的搜索表单子组件
|
|
52
|
+
*
|
|
42
53
|
* @component SearchForm
|
|
43
|
-
* @description ProTable 的搜索表单子组件
|
|
44
54
|
*
|
|
45
55
|
* ## 核心功能
|
|
46
56
|
*
|
|
47
57
|
* 1. 内部渲染 ProForm,使用 table.searchForm 管理搜索条件
|
|
48
58
|
* 2. 支持 grid(网格)和 inline(行内)两种布局
|
|
49
|
-
* 3.
|
|
59
|
+
* 3. 网格布局下支持展开/折叠
|
|
50
60
|
* 4. 提供搜索、重置、展开/折叠按钮(支持自定义)
|
|
51
61
|
*
|
|
52
|
-
* ##
|
|
62
|
+
* ## 展开/折叠行为
|
|
63
|
+
*
|
|
64
|
+
* 1. 内容高度超出折叠高度(由 minExpandRows 决定,默认 2 行)时显示展开/折叠按钮
|
|
65
|
+
* 2. 隐藏区域存在搜索值时自动展开
|
|
66
|
+
* 3. 用户手动切换后保持手动状态,不随重新测量回退
|
|
53
67
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
68
|
+
* @param {Form} form - 搜索表单实例,由 table.searchForm 提供
|
|
69
|
+
* @param {'grid' | 'inline'} [layout] - 布局形态,默认 'grid'
|
|
70
|
+
* @param {boolean | Expand} [expand] - 展开配置,默认 true
|
|
71
|
+
* @param {Component<ButtonProps> | DefineComponent<ButtonProps> | false} [searchButton] - 自定义搜索按钮,false 时不渲染
|
|
72
|
+
* @param {Component<ButtonProps> | DefineComponent<ButtonProps> | false} [resetButton] - 自定义重置按钮,false 时不渲染
|
|
73
|
+
* @param {Component<ExpandButtonProps> | DefineComponent<ExpandButtonProps> | false} [expandButton] - 自定义展开/折叠按钮,false 时不渲染
|
|
74
|
+
* @param {number} [rowGap] - 行间距,默认 16
|
|
75
|
+
* @param {number} [columnGap] - 列间距,默认 24
|
|
76
|
+
*
|
|
77
|
+
* @slot reset-button - 重置按钮插槽,作用域参数 onReset
|
|
78
|
+
* @slot search-button - 搜索按钮插槽,作用域参数 onSearch
|
|
79
|
+
* @slot expand-button - 展开/折叠按钮插槽,作用域参数 expandStatus / changeExpandStatus
|
|
58
80
|
*/
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
} from 'vue';
|
|
66
|
-
import ProForm from '../../form';
|
|
67
|
-
import { type Form } from '../../form';
|
|
81
|
+
import type { FormProps } from '../../../shared/ui';
|
|
82
|
+
import { Space, Button } from '../../../shared/ui';
|
|
83
|
+
import type { AllowedComponentProps } from 'vue';
|
|
84
|
+
import { computed, nextTick, type Component, type DefineComponent } from 'vue';
|
|
85
|
+
import ProForm from '../../form/components/ProForm/index.vue';
|
|
86
|
+
import { type Form } from '../../form/hooks/useForm';
|
|
68
87
|
import DownOutlined from './icons/DownOutlined.vue';
|
|
69
88
|
import { useLocale } from '../../../locale';
|
|
70
89
|
import { useSearchFormExpand } from '../../../shared/core';
|
|
@@ -81,7 +100,6 @@ const props = withDefaults(defineProps<SearchFormProps>(), {
|
|
|
81
100
|
expandButton: undefined,
|
|
82
101
|
rowGap: 16,
|
|
83
102
|
columnGap: 24,
|
|
84
|
-
form: undefined,
|
|
85
103
|
});
|
|
86
104
|
|
|
87
105
|
type Emits = {
|
|
@@ -117,9 +135,28 @@ const onReset = () => {
|
|
|
117
135
|
emit('reset');
|
|
118
136
|
};
|
|
119
137
|
|
|
120
|
-
|
|
138
|
+
// 同一 tick 内 click 与 submit 只放行首个触达者,下一 tick 释放标记
|
|
139
|
+
let searchTriggeredInTick = false;
|
|
140
|
+
|
|
141
|
+
const emitSearch = () => {
|
|
142
|
+
searchTriggeredInTick = true;
|
|
143
|
+
nextTick(() => {
|
|
144
|
+
searchTriggeredInTick = false;
|
|
145
|
+
});
|
|
121
146
|
emit('search');
|
|
122
147
|
};
|
|
148
|
+
|
|
149
|
+
// 默认查询按钮为 html-type=submit:点击与回车统一经表单 submit 事件触发
|
|
150
|
+
const onSearchSubmit = () => {
|
|
151
|
+
if (searchTriggeredInTick) return;
|
|
152
|
+
emitSearch();
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// 自定义查询按钮与 search-button 插槽经 click 触发
|
|
156
|
+
const onSearchClick = () => {
|
|
157
|
+
if (searchTriggeredInTick) return;
|
|
158
|
+
emitSearch();
|
|
159
|
+
};
|
|
123
160
|
</script>
|
|
124
161
|
|
|
125
162
|
<template>
|
|
@@ -127,6 +164,7 @@ const onSearch = () => {
|
|
|
127
164
|
v-bind="layoutProps"
|
|
128
165
|
:form="props.form"
|
|
129
166
|
class="pro-table_search-form transition"
|
|
167
|
+
@submit.prevent="onSearchSubmit"
|
|
130
168
|
>
|
|
131
169
|
<Space align="start" class="pro-table_search-form_button-group">
|
|
132
170
|
<slot name="reset-button" :on-reset="onReset">
|
|
@@ -144,19 +182,18 @@ const onSearch = () => {
|
|
|
144
182
|
>
|
|
145
183
|
</template>
|
|
146
184
|
</slot>
|
|
147
|
-
<slot name="search-button" :on-search="
|
|
185
|
+
<slot name="search-button" :on-search="onSearchClick">
|
|
148
186
|
<template v-if="props.searchButton !== false">
|
|
149
187
|
<component
|
|
150
188
|
:is="props.searchButton"
|
|
151
189
|
v-if="props.searchButton"
|
|
152
|
-
@click="
|
|
190
|
+
@click="onSearchClick"
|
|
153
191
|
/>
|
|
154
192
|
<Button
|
|
155
193
|
v-else
|
|
156
194
|
class="pro-table_search-form_search-button"
|
|
157
195
|
type="primary"
|
|
158
196
|
html-type="submit"
|
|
159
|
-
@click="onSearch"
|
|
160
197
|
>{{ t('table.query') }}
|
|
161
198
|
</Button>
|
|
162
199
|
</template>
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
+
/**
|
|
3
|
+
* 表格尺寸切换控件,以图标按钮 + 下拉菜单呈现
|
|
4
|
+
*
|
|
5
|
+
* @component SizeControl
|
|
6
|
+
*
|
|
7
|
+
* @param {Size} [size] - 表格尺寸(`v-model:size`),仅用于回写选中结果
|
|
8
|
+
* @param {Size} [effectiveSize] - 表格实际使用的尺寸,用于高亮选中项;未提供时依次回退 `size`、宿主 ConfigProvider 的 componentSize
|
|
9
|
+
*/
|
|
2
10
|
import {
|
|
3
11
|
Dropdown,
|
|
4
12
|
Menu,
|
|
@@ -17,10 +25,22 @@ const { t } = useLocale();
|
|
|
17
25
|
|
|
18
26
|
const configContext = useConfigContextInject();
|
|
19
27
|
|
|
28
|
+
const props = withDefaults(
|
|
29
|
+
defineProps<{
|
|
30
|
+
effectiveSize?: Size;
|
|
31
|
+
}>(),
|
|
32
|
+
{ effectiveSize: undefined }
|
|
33
|
+
);
|
|
34
|
+
|
|
20
35
|
const size = defineModel<Size>('size');
|
|
21
36
|
|
|
37
|
+
// 高亮项:表格有效尺寸 > 本地绑定 > 宿主 ConfigProvider 的 componentSize
|
|
22
38
|
const currentSize = computed(
|
|
23
|
-
() =>
|
|
39
|
+
() =>
|
|
40
|
+
props.effectiveSize ??
|
|
41
|
+
size.value ??
|
|
42
|
+
configContext.componentSize?.value ??
|
|
43
|
+
'large'
|
|
24
44
|
);
|
|
25
45
|
|
|
26
46
|
const onSizeChange = (val: any) => {
|