@qin-ui/element-plus-pro 1.3.4 → 1.4.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 +41 -30
- package/api.json +69 -38
- package/es/components/component-provider/components/index.vue.d.ts +10 -3
- package/es/components/component-provider/components/index.vue.d.ts.map +1 -1
- package/es/components/component-provider/constants/index.d.ts +22 -4
- package/es/components/component-provider/constants/index.d.ts.map +1 -1
- package/es/components/component-provider/types/index.d.ts +17 -3
- 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/BaseField/utils/index.d.ts +15 -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 -1
- package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -1
- 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.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 +8 -2
- package/es/components/form/constants/index.d.ts.map +1 -1
- package/es/components/form/hooks/useFields.d.ts.map +1 -1
- package/es/components/form/hooks/useForm.d.ts.map +1 -1
- package/es/components/form/hooks/useGroupFieldAttrs.d.ts +9 -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 +65 -50
- 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 +1 -1
- package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -1
- package/es/components/table/components/ProTable.vue.d.ts +15 -1
- package/es/components/table/components/ProTable.vue.d.ts.map +1 -1
- package/es/components/table/components/SearchForm.vue.d.ts +14 -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/components/TableColumnRenderer.vue.d.ts +14 -14
- package/es/components/table/components/TableColumnRenderer.vue.d.ts.map +1 -1
- package/es/components/table/hooks/index.d.ts +2 -0
- package/es/components/table/hooks/index.d.ts.map +1 -0
- package/es/components/table/{useTable.d.ts → hooks/useTable.d.ts} +3 -3
- package/es/components/table/hooks/useTable.d.ts.map +1 -0
- package/es/components/table/index.d.ts +6 -2
- package/es/components/table/index.d.ts.map +1 -1
- package/es/components/table/types/index.d.ts +16 -12
- package/es/components/table/types/index.d.ts.map +1 -1
- package/es/element-plus-pro.css +14 -14
- package/es/index.d.ts +9 -6
- package/es/index.d.ts.map +1 -1
- package/es/index.js +577 -432
- package/es/index.js.map +1 -1
- package/es/locale/index.d.ts +2 -1
- 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 +20 -2
- package/es/shared/ui/index.d.ts.map +1 -1
- package/package.json +8 -5
- package/src/components/component-provider/components/index.vue +48 -21
- package/src/components/component-provider/constants/index.ts +34 -6
- package/src/components/component-provider/types/index.ts +21 -7
- package/src/components/form/components/BaseField/index.vue +32 -15
- package/src/components/form/components/BaseField/utils/index.ts +16 -1
- package/src/components/form/components/BaseFormItem/index.vue +61 -15
- package/src/components/form/components/ContainerFragment/index.vue +3 -1
- package/src/components/form/components/ProForm/index.vue +54 -27
- package/src/components/form/components/SlotComponent/index.vue +4 -2
- package/src/components/form/components/index.ts +8 -1
- package/src/components/form/constants/index.ts +9 -2
- package/src/components/form/hooks/useFields.ts +5 -2
- package/src/components/form/hooks/useForm.ts +8 -1
- package/src/components/form/hooks/useGroupFieldAttrs.ts +29 -30
- package/src/components/form/index.ts +1 -3
- package/src/components/form/types/index.ts +71 -52
- package/src/components/form/utils/fieldKey.ts +27 -0
- package/src/components/table/components/ColumnControl.vue +20 -8
- package/src/components/table/components/ProTable.vue +194 -96
- package/src/components/table/components/SearchForm.vue +62 -23
- package/src/components/table/components/SizeControl.vue +23 -1
- package/src/components/table/components/TableColumnRenderer.vue +93 -61
- package/src/components/table/hooks/index.ts +1 -0
- package/src/components/table/{useTable.ts → hooks/useTable.ts} +6 -4
- package/src/components/table/index.ts +11 -2
- package/src/components/table/types/index.ts +17 -13
- package/src/index.ts +13 -7
- package/src/locale/index.ts +4 -2
- package/src/shared/core/index.ts +11 -0
- package/src/shared/ui/index.ts +29 -5
- package/tsconfig.json +22 -0
- package/es/components/table/useTable.d.ts.map +0 -1
|
@@ -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,12 +26,27 @@
|
|
|
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 default - 保留给列渲染器,传入内容不参与渲染,自定义区域请使用下列具名插槽
|
|
46
|
+
* @slot search-form - 搜索区域插槽(无作用域)
|
|
47
|
+
* @slot button-bar - 表格头部按钮区插槽
|
|
48
|
+
* @slot toolbar - 工具栏插槽
|
|
49
|
+
* @slot table - 表格区域插槽(无作用域)
|
|
34
50
|
*/
|
|
35
51
|
import {
|
|
36
52
|
Pagination as ElPagination,
|
|
@@ -43,9 +59,13 @@ import {
|
|
|
43
59
|
} from '../../../shared/ui';
|
|
44
60
|
import { ElLoading } from 'element-plus';
|
|
45
61
|
import {
|
|
62
|
+
Comment,
|
|
63
|
+
Fragment,
|
|
64
|
+
Text,
|
|
46
65
|
computed,
|
|
66
|
+
effect,
|
|
67
|
+
stop,
|
|
47
68
|
nextTick,
|
|
48
|
-
useAttrs,
|
|
49
69
|
onMounted,
|
|
50
70
|
inject,
|
|
51
71
|
toValue,
|
|
@@ -53,26 +73,27 @@ import {
|
|
|
53
73
|
useSlots,
|
|
54
74
|
} from 'vue';
|
|
55
75
|
import { INJECT_CONFIG } from '../../component-provider/constants';
|
|
56
|
-
import
|
|
76
|
+
import ContainerFragment from '../../form/components/ContainerFragment/index.vue';
|
|
57
77
|
import type { ContainerComponent } from '../../form/types';
|
|
58
78
|
import SearchForm from './SearchForm.vue';
|
|
59
79
|
import type { SearchFormProps } from './SearchForm.vue';
|
|
60
|
-
import type { Table } from '../
|
|
80
|
+
import type { Table } from '../hooks';
|
|
61
81
|
import { useLocale } from '../../../locale';
|
|
82
|
+
import type { Path } from '../../../shared/core';
|
|
62
83
|
import {
|
|
63
84
|
camelizeProperties,
|
|
64
85
|
getObject,
|
|
65
|
-
|
|
66
|
-
isPromise,
|
|
86
|
+
isObjectItem,
|
|
67
87
|
isPlainObject,
|
|
68
|
-
|
|
88
|
+
isPromise,
|
|
89
|
+
omitBy,
|
|
90
|
+
pick,
|
|
91
|
+
useReactiveAttrs,
|
|
69
92
|
} from '../../../shared/core';
|
|
70
|
-
import
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ColumnControl,
|
|
75
|
-
} from '.';
|
|
93
|
+
import DefaultSearchFormContainer from './DefaultSearchFormContainer.vue';
|
|
94
|
+
import DefaultTableContainer from './DefaultTableContainer.vue';
|
|
95
|
+
import SizeControl from './SizeControl.vue';
|
|
96
|
+
import ColumnControl from './ColumnControl.vue';
|
|
76
97
|
import type { Columns, ColumnControlConfig } from '../types';
|
|
77
98
|
import TableColumnRenderer from './TableColumnRenderer.vue';
|
|
78
99
|
|
|
@@ -88,14 +109,17 @@ const tableProperties = Object.keys(tableProps());
|
|
|
88
109
|
const paginationProperties = Object.keys(paginationProps());
|
|
89
110
|
|
|
90
111
|
type Control = {
|
|
112
|
+
/** 是否展示表格尺寸控制 */
|
|
91
113
|
sizeControl: boolean;
|
|
114
|
+
/** 是否展示列控制,传对象时作为列控面板配置 */
|
|
92
115
|
columnControl: boolean | ColumnControlConfig<Path<RecordType>>;
|
|
93
116
|
};
|
|
94
117
|
|
|
118
|
+
/** 搜索表单配置:SearchFormProps 去掉 form,并加上搜索区显隐与容器 */
|
|
95
119
|
type SearchFormConfig = Omit<SearchFormProps, 'form'> & {
|
|
96
|
-
|
|
120
|
+
/** 是否隐藏搜索表单 */
|
|
97
121
|
hidden?: boolean;
|
|
98
|
-
|
|
122
|
+
/** 搜索区域包裹容器 */
|
|
99
123
|
container?: ContainerComponent | false;
|
|
100
124
|
};
|
|
101
125
|
|
|
@@ -103,25 +127,31 @@ type ReturnTableRecord<V> = V extends Table<any, infer R> ? R : any;
|
|
|
103
127
|
type RecordType = ReturnTableRecord<T>;
|
|
104
128
|
|
|
105
129
|
type Props = {
|
|
106
|
-
|
|
130
|
+
/** 列表表格对象,来自 useTable */
|
|
107
131
|
table?: T;
|
|
108
|
-
|
|
132
|
+
/** 列表数据查询方法 */
|
|
109
133
|
search?: () => Promise<unknown>;
|
|
110
|
-
|
|
134
|
+
/** 是否在首列插入序号列 */
|
|
111
135
|
addIndexColumn?: boolean;
|
|
112
|
-
|
|
136
|
+
/** 挂载后是否立即触发一次查询 */
|
|
113
137
|
immediateSearch?: boolean;
|
|
114
|
-
|
|
138
|
+
/** 是否展示尺寸与列控制按钮,支持分别控制;显式传入 columns 时列控入口不渲染 */
|
|
115
139
|
control?: boolean | Partial<Control>;
|
|
116
|
-
|
|
140
|
+
/** 搜索栏查询字段表单配置 */
|
|
117
141
|
searchFormConfig?: SearchFormConfig;
|
|
118
|
-
|
|
142
|
+
/** 表格区域包裹容器 */
|
|
119
143
|
tableContainer?: ContainerComponent | false;
|
|
144
|
+
/** 直接传入列配置(覆盖 :table 实例的 columns) */
|
|
120
145
|
columns?: Columns<RecordType>;
|
|
146
|
+
/** 直接传入数据源(覆盖 :table 实例的 data) */
|
|
121
147
|
data?: RecordType[];
|
|
148
|
+
/** 分页配置,传 false 禁用 */
|
|
122
149
|
pagination?: false | Partial<PaginationProps>;
|
|
123
150
|
} & /* @vue-ignore */ Omit<TableProps, 'data'>;
|
|
124
151
|
|
|
152
|
+
/*
|
|
153
|
+
* 组件自身仅触发模型事件;宿主表格事件不入运行时 emits,监听入口保留在 attrs 中透传至 ElTable
|
|
154
|
+
*/
|
|
125
155
|
type Emits = {} & /* @vue-ignore */ TableEmits<RecordType>;
|
|
126
156
|
|
|
127
157
|
defineEmits<Emits>();
|
|
@@ -145,34 +175,23 @@ const config = INJECT_CONFIG['pro-table'];
|
|
|
145
175
|
const injectPropsRaw = inject(config.injectionKey, config.default);
|
|
146
176
|
const injectProps = computed(() => toValue(injectPropsRaw) || {});
|
|
147
177
|
|
|
148
|
-
const attrs =
|
|
178
|
+
const attrs = useReactiveAttrs() as Record<string, any>;
|
|
149
179
|
|
|
150
|
-
|
|
180
|
+
// 组件自身消费的 attrs 键:class / style 绑定根节点,onSearch / onChange 由搜索与分页流程消费
|
|
181
|
+
const PRO_TABLE_CONSUMED_ATTR_KEYS = new Set([
|
|
151
182
|
'class',
|
|
152
183
|
'style',
|
|
153
184
|
'onSearch',
|
|
154
185
|
'onChange',
|
|
155
|
-
'onUpdate:size',
|
|
156
|
-
'onUpdate:loading',
|
|
157
|
-
'data',
|
|
158
|
-
'columns',
|
|
159
|
-
'size',
|
|
160
|
-
'pagination',
|
|
161
186
|
]);
|
|
162
187
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
'searchFormConfig',
|
|
166
|
-
'control',
|
|
167
|
-
'immediateSearch',
|
|
168
|
-
'addIndexColumn',
|
|
169
|
-
'tableContainer',
|
|
170
|
-
]);
|
|
188
|
+
// 组件自身消费的 props 键:对应值按 prop / provider 单一来源派生,provider 注入不再透传这些键
|
|
189
|
+
const PRO_TABLE_PROP_KEYS = new Set(Object.keys(props));
|
|
171
190
|
|
|
172
191
|
const injectTableAttrs = computed(() => {
|
|
173
192
|
const tableConfig: Record<string, any> = {};
|
|
174
193
|
for (const [key, value] of Object.entries(injectProps.value)) {
|
|
175
|
-
if (!
|
|
194
|
+
if (!PRO_TABLE_PROP_KEYS.has(key)) {
|
|
176
195
|
tableConfig[key] = value;
|
|
177
196
|
}
|
|
178
197
|
}
|
|
@@ -180,20 +199,20 @@ const injectTableAttrs = computed(() => {
|
|
|
180
199
|
});
|
|
181
200
|
|
|
182
201
|
const attrsTableProps = computed(() => {
|
|
183
|
-
return
|
|
184
|
-
camelizeProperties(attrs),
|
|
185
|
-
|
|
202
|
+
return omitBy(
|
|
203
|
+
pick(camelizeProperties(attrs), tableProperties),
|
|
204
|
+
value => value === undefined
|
|
186
205
|
) as Partial<TableProps>;
|
|
187
206
|
});
|
|
188
207
|
|
|
189
208
|
const restTableAttrs = computed(() => {
|
|
190
209
|
const rest: Record<string, any> = {};
|
|
191
210
|
for (const [key, value] of Object.entries(attrs)) {
|
|
192
|
-
if (!
|
|
211
|
+
if (!PRO_TABLE_CONSUMED_ATTR_KEYS.has(key)) {
|
|
193
212
|
rest[key] = value;
|
|
194
213
|
}
|
|
195
214
|
}
|
|
196
|
-
return rest;
|
|
215
|
+
return omitBy(rest, value => value === undefined);
|
|
197
216
|
});
|
|
198
217
|
|
|
199
218
|
const size = defineModel<TableProps['size']>('size');
|
|
@@ -201,10 +220,31 @@ const loading = defineModel<boolean>('loading', { default: false });
|
|
|
201
220
|
|
|
202
221
|
// 递增序号做请求竞态守卫,仅最新一次请求可变更 loading
|
|
203
222
|
let searchSequence = 0;
|
|
223
|
+
// 告警文案按原因分键,各原因独立去重
|
|
224
|
+
const SEARCH_WARN_MESSAGES = {
|
|
225
|
+
duplicate:
|
|
226
|
+
'同时配置了 :search prop 与 @search 监听,@search 监听不会被执行,请仅保留其一。',
|
|
227
|
+
'not-promise':
|
|
228
|
+
'search 未返回 Promise,本次查询不参与 loading 竞态管理(在途请求的 loading 状态保持不变)。',
|
|
229
|
+
} as const;
|
|
230
|
+
const warnedSearchReasons = new Set<keyof typeof SEARCH_WARN_MESSAGES>();
|
|
231
|
+
const warnSearchConfig = (reason: keyof typeof SEARCH_WARN_MESSAGES) => {
|
|
232
|
+
if (warnedSearchReasons.has(reason)) return;
|
|
233
|
+
warnedSearchReasons.add(reason);
|
|
234
|
+
console.warn(`[ProTable] ${SEARCH_WARN_MESSAGES[reason]}`);
|
|
235
|
+
};
|
|
204
236
|
const _search = async () => {
|
|
237
|
+
const search = props.search || attrs.onSearch;
|
|
238
|
+
if (props.search && attrs.onSearch) {
|
|
239
|
+
warnSearchConfig('duplicate');
|
|
240
|
+
}
|
|
241
|
+
if (!search) return;
|
|
242
|
+
const searchPromise = search();
|
|
243
|
+
if (!isPromise(searchPromise)) {
|
|
244
|
+
warnSearchConfig('not-promise');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
205
247
|
const seq = ++searchSequence;
|
|
206
|
-
const searchPromise = (props.search || attrs.onSearch)?.();
|
|
207
|
-
if (!isPromise(searchPromise)) return;
|
|
208
248
|
loading.value = true;
|
|
209
249
|
try {
|
|
210
250
|
await searchPromise;
|
|
@@ -236,8 +276,57 @@ type CustomSlots = Readonly<
|
|
|
236
276
|
type Slots = CustomSlots & Readonly<Record<string, Slot>>;
|
|
237
277
|
|
|
238
278
|
const slots = useSlots() as Slots;
|
|
239
|
-
|
|
240
|
-
|
|
279
|
+
|
|
280
|
+
/** 插槽产出是否含可渲染内容:注释节点与空白文本不计 */
|
|
281
|
+
const hasRenderableContent = (nodes: unknown): boolean => {
|
|
282
|
+
if (nodes === null || nodes === undefined || nodes === false) return false;
|
|
283
|
+
if (typeof nodes === 'string') return nodes.trim() !== '';
|
|
284
|
+
if (typeof nodes === 'number') return true;
|
|
285
|
+
if (Array.isArray(nodes)) return nodes.some(hasRenderableContent);
|
|
286
|
+
const vnode = nodes as { type?: unknown; children?: unknown };
|
|
287
|
+
if (vnode.type === Comment) return false;
|
|
288
|
+
if (vnode.type === Text) return String(vnode.children ?? '').trim() !== '';
|
|
289
|
+
if (vnode.type === Fragment) return hasRenderableContent(vnode.children);
|
|
290
|
+
return true;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
/*
|
|
294
|
+
* 求值一次 default 插槽产出,用于判定是否有可渲染内容(空插槽 / 包装组件透传不提示)。
|
|
295
|
+
* 求值由一次性 effect 执行并立即停止,插槽内的响应式读取不进入 ProTable 的渲染依赖。
|
|
296
|
+
*/
|
|
297
|
+
const evaluateSlotContent = (): boolean => {
|
|
298
|
+
let hasContent = false;
|
|
299
|
+
const runner = effect(() => {
|
|
300
|
+
try {
|
|
301
|
+
hasContent = hasRenderableContent(slots.default?.());
|
|
302
|
+
} catch {
|
|
303
|
+
// 产出无法求值时按有内容处理,照常提示 default 插槽被保留
|
|
304
|
+
hasContent = true;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
stop(runner);
|
|
308
|
+
return hasContent;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// default 插槽保留给列渲染器,同名用户插槽不透传宿主表格
|
|
312
|
+
let defaultSlotChecked = false;
|
|
313
|
+
const checkDefaultSlot = () => {
|
|
314
|
+
if (defaultSlotChecked) return;
|
|
315
|
+
if (!Object.prototype.hasOwnProperty.call(slots, 'default')) return;
|
|
316
|
+
defaultSlotChecked = true;
|
|
317
|
+
if (evaluateSlotContent()) {
|
|
318
|
+
console.warn(
|
|
319
|
+
'[pro-components] ProTable 的 default 插槽保留用于列渲染,已忽略传入的 default 插槽内容;自定义区域请使用 search-form / button-bar / toolbar / table 具名插槽。'
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const forwardedSlotNames = () => {
|
|
325
|
+
checkDefaultSlot();
|
|
326
|
+
return Object.keys(slots).filter(
|
|
327
|
+
name => name !== 'default' && !customSlotNames.includes(name as any)
|
|
328
|
+
);
|
|
329
|
+
};
|
|
241
330
|
|
|
242
331
|
const searchPage1st = () => {
|
|
243
332
|
props.table?.setPageParam?.({ current: 1 });
|
|
@@ -256,6 +345,16 @@ const computedSearchFormConfig = computed(() => {
|
|
|
256
345
|
};
|
|
257
346
|
});
|
|
258
347
|
|
|
348
|
+
/** SearchForm 属性面:容器与显隐由 ProTable 自身消费,不下发 SearchForm */
|
|
349
|
+
const searchFormProps = computed(() => {
|
|
350
|
+
const {
|
|
351
|
+
container: _container,
|
|
352
|
+
hidden: _hidden,
|
|
353
|
+
...rest
|
|
354
|
+
} = computedSearchFormConfig.value;
|
|
355
|
+
return rest;
|
|
356
|
+
});
|
|
357
|
+
|
|
259
358
|
const computedTableContainer = computed(() => {
|
|
260
359
|
const container =
|
|
261
360
|
props.tableContainer ??
|
|
@@ -264,19 +363,26 @@ const computedTableContainer = computed(() => {
|
|
|
264
363
|
return container ? container : undefined;
|
|
265
364
|
});
|
|
266
365
|
|
|
366
|
+
/*
|
|
367
|
+
* 序号列随有效分页偏移:分页关闭时退化为 index + 1;
|
|
368
|
+
* current / pageSize 取有效分页(显式 :pagination 或 Provider 注入),table.pageParam 仅作兜底
|
|
369
|
+
*/
|
|
267
370
|
const getIndex = (i: number) => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return i + 1;
|
|
371
|
+
const pagination = computedPagination.value;
|
|
372
|
+
if (pagination === false) return i + 1;
|
|
373
|
+
const pageParam = props.table?.pageParam;
|
|
374
|
+
const current = pagination.currentPage ?? pageParam?.current ?? 1;
|
|
375
|
+
const pageSize = pagination.pageSize ?? pageParam?.pageSize ?? 10;
|
|
376
|
+
return (current - 1) * pageSize + i + 1;
|
|
274
377
|
};
|
|
275
378
|
|
|
379
|
+
/** 调用方是否显式传入 columns(值为 undefined 时视为未指定) */
|
|
380
|
+
const hasExplicitColumns = computed(() => props.columns !== undefined);
|
|
381
|
+
|
|
276
382
|
const normalizedRawColumns = computed(() => {
|
|
277
|
-
const source = (
|
|
278
|
-
toValue(props.table?.columns) ??
|
|
279
|
-
|
|
383
|
+
const source = (
|
|
384
|
+
(props.columns ?? toValue(props.table?.columns) ?? []) as any[]
|
|
385
|
+
).filter(isObjectItem);
|
|
280
386
|
if (props.addIndexColumn ?? injectProps.value.addIndexColumn) {
|
|
281
387
|
return [
|
|
282
388
|
{
|
|
@@ -293,41 +399,25 @@ const normalizedRawColumns = computed(() => {
|
|
|
293
399
|
});
|
|
294
400
|
|
|
295
401
|
const computedDataSource = computed<any[]>(() => {
|
|
296
|
-
|
|
297
|
-
return (
|
|
298
|
-
props.data ??
|
|
299
|
-
attrsData ??
|
|
300
|
-
toValue((props.table as any)?.dataSource) ??
|
|
301
|
-
toValue(props.table?.data) ??
|
|
302
|
-
[]
|
|
303
|
-
);
|
|
402
|
+
return props.data ?? toValue(props.table?.data) ?? [];
|
|
304
403
|
});
|
|
305
404
|
|
|
306
405
|
const computedTableProps = computed<TableProps>(() => {
|
|
307
|
-
|
|
406
|
+
return {
|
|
308
407
|
...injectTableAttrs.value,
|
|
309
408
|
...restTableAttrs.value,
|
|
310
409
|
...attrsTableProps.value,
|
|
311
410
|
} as TableProps;
|
|
312
|
-
delete (merged as any).data;
|
|
313
|
-
delete (merged as any).columns;
|
|
314
|
-
delete (merged as any).size;
|
|
315
|
-
delete (merged as any).pagination;
|
|
316
|
-
return merged;
|
|
317
411
|
});
|
|
318
412
|
|
|
319
|
-
|
|
413
|
+
/*
|
|
414
|
+
* 分页来源:分页开关与展示配置取 props.pagination 显式配置 > provider 全局配置 > 默认合成;
|
|
415
|
+
* currentPage / pageSize / total 以 table.pageParam 为实时状态,显式配置仅在 pageParam 缺省时兜底
|
|
416
|
+
*/
|
|
320
417
|
const computedPagination = computed<false | Partial<PaginationProps>>(() => {
|
|
321
|
-
|
|
322
|
-
if (props.pagination
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
const hasUserPagination =
|
|
326
|
-
isPlainObject(props.pagination) || isPlainObject(attrsPagination);
|
|
327
|
-
if (hasUserPagination) {
|
|
328
|
-
const pageConfig = (props.pagination ??
|
|
329
|
-
attrsPagination ??
|
|
330
|
-
{}) as Partial<PaginationProps>;
|
|
418
|
+
if (props.pagination === false) return false;
|
|
419
|
+
if (isPlainObject(props.pagination)) {
|
|
420
|
+
const pageConfig = props.pagination as Partial<PaginationProps>;
|
|
331
421
|
const globalPagination =
|
|
332
422
|
(injectProps.value.pagination as any) === false
|
|
333
423
|
? {}
|
|
@@ -378,11 +468,15 @@ const searchFormVisible = computed(() => {
|
|
|
378
468
|
return !!(
|
|
379
469
|
(computedSearchFormConfig.value as any)?.hidden !== true &&
|
|
380
470
|
props.table?.searchForm &&
|
|
381
|
-
fieldList.filter((item: any) => !toValue(item.hidden))
|
|
471
|
+
fieldList.filter((item: any) => isObjectItem(item) && !toValue(item.hidden))
|
|
472
|
+
.length > 0
|
|
382
473
|
);
|
|
383
474
|
});
|
|
384
475
|
|
|
385
|
-
|
|
476
|
+
/*
|
|
477
|
+
* 控制项来源:显式控制配置 > Provider 全局配置
|
|
478
|
+
*/
|
|
479
|
+
const resolvedControl = computed<{
|
|
386
480
|
sizeControl: boolean;
|
|
387
481
|
columnControl: boolean | ColumnControlConfig<Path<RecordType>>;
|
|
388
482
|
}>(() => {
|
|
@@ -396,17 +490,21 @@ const computedControl = computed<{
|
|
|
396
490
|
return { sizeControl: !!sizeControl, columnControl };
|
|
397
491
|
});
|
|
398
492
|
|
|
493
|
+
/* 显式 columns 模式下渲染源不受 table.setColumn 影响,列控入口隐藏 */
|
|
494
|
+
const computedControl = computed(() =>
|
|
495
|
+
hasExplicitColumns.value && resolvedControl.value.columnControl
|
|
496
|
+
? { ...resolvedControl.value, columnControl: false }
|
|
497
|
+
: resolvedControl.value
|
|
498
|
+
);
|
|
499
|
+
|
|
399
500
|
const columnControlConfig = computed(() => {
|
|
400
501
|
const cc = computedControl.value.columnControl;
|
|
401
502
|
return typeof cc === 'object' ? cc : undefined;
|
|
402
503
|
});
|
|
403
504
|
|
|
505
|
+
// 尺寸来源:size 模型 > provider 全局配置
|
|
404
506
|
const resolvedSize = computed(() => {
|
|
405
|
-
return
|
|
406
|
-
size.value ??
|
|
407
|
-
(attrsTableProps.value.size as any) ??
|
|
408
|
-
injectTableAttrs.value.size
|
|
409
|
-
);
|
|
507
|
+
return size.value ?? injectProps.value.size;
|
|
410
508
|
});
|
|
411
509
|
|
|
412
510
|
onMounted(() => {
|
|
@@ -431,7 +529,7 @@ defineExpose({
|
|
|
431
529
|
<SearchForm
|
|
432
530
|
v-if="props.table?.searchForm"
|
|
433
531
|
:form="props.table.searchForm"
|
|
434
|
-
v-bind="
|
|
532
|
+
v-bind="searchFormProps"
|
|
435
533
|
@search="searchPage1st"
|
|
436
534
|
@reset="reset"
|
|
437
535
|
/>
|
|
@@ -450,7 +548,7 @@ defineExpose({
|
|
|
450
548
|
v-if="computedControl.sizeControl"
|
|
451
549
|
class="pro-table_header_size-control"
|
|
452
550
|
>
|
|
453
|
-
<SizeControl v-model:size="size" />
|
|
551
|
+
<SizeControl v-model:size="size" :effective-size="resolvedSize" />
|
|
454
552
|
</div>
|
|
455
553
|
<div
|
|
456
554
|
v-if="computedControl.columnControl"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
type Expand = {
|
|
3
|
-
|
|
3
|
+
/** 网格布局时默认展开行数量 */
|
|
4
4
|
minExpandRows?: number;
|
|
5
|
-
|
|
5
|
+
/** 网格布局时展开状态 */
|
|
6
6
|
expandStatus?: boolean;
|
|
7
7
|
};
|
|
8
8
|
|
|
@@ -21,17 +21,28 @@ type _FormProps = Pick<
|
|
|
21
21
|
type ButtonProps = { onClick: () => void };
|
|
22
22
|
type ExpandButtonProps = ButtonProps & { expandStatus: boolean };
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 搜索表单属性面,由 ProTable 经 `searchFormConfig` 下发
|
|
26
|
+
*/
|
|
24
27
|
export type SearchFormProps = {
|
|
28
|
+
/** 搜索表单实例,由 table.searchForm 提供 */
|
|
25
29
|
form: Form;
|
|
30
|
+
/** 布局形态,默认 'grid' */
|
|
26
31
|
layout?: 'grid' | 'inline';
|
|
32
|
+
/** 展开配置,默认 true */
|
|
27
33
|
expand?: boolean | Expand;
|
|
34
|
+
/** 自定义搜索按钮,false 时不渲染 */
|
|
28
35
|
searchButton?: Component<ButtonProps> | DefineComponent<ButtonProps> | false;
|
|
36
|
+
/** 自定义重置按钮,false 时不渲染 */
|
|
29
37
|
resetButton?: Component<ButtonProps> | DefineComponent<ButtonProps> | false;
|
|
38
|
+
/** 自定义展开/折叠按钮,false 时不渲染 */
|
|
30
39
|
expandButton?:
|
|
31
40
|
| Component<ExpandButtonProps>
|
|
32
41
|
| DefineComponent<ExpandButtonProps>
|
|
33
42
|
| false;
|
|
43
|
+
/** 行间距,默认 16 */
|
|
34
44
|
rowGap?: number;
|
|
45
|
+
/** 列间距,默认 24 */
|
|
35
46
|
columnGap?: number;
|
|
36
47
|
} & /* @vue-ignore */ _FormProps &
|
|
37
48
|
AllowedComponentProps;
|
|
@@ -39,32 +50,42 @@ export type SearchFormProps = {
|
|
|
39
50
|
|
|
40
51
|
<script lang="ts" setup>
|
|
41
52
|
/**
|
|
53
|
+
* ProTable 的搜索表单子组件
|
|
54
|
+
*
|
|
42
55
|
* @component SearchForm
|
|
43
|
-
* @description ProTable 的搜索表单子组件
|
|
44
56
|
*
|
|
45
57
|
* ## 核心功能
|
|
46
58
|
*
|
|
47
59
|
* 1. 内部渲染 ProForm,使用 table.searchForm 管理搜索条件
|
|
48
60
|
* 2. 支持 grid(网格)和 inline(行内)两种布局
|
|
49
|
-
* 3.
|
|
61
|
+
* 3. 网格布局下支持展开/折叠
|
|
50
62
|
* 4. 提供搜索、重置、展开/折叠按钮(支持自定义)
|
|
51
63
|
*
|
|
52
|
-
* ##
|
|
64
|
+
* ## 展开/折叠行为
|
|
65
|
+
*
|
|
66
|
+
* 1. 内容高度超出折叠高度(由 minExpandRows 决定,默认 2 行)时显示展开/折叠按钮
|
|
67
|
+
* 2. 隐藏区域存在搜索值时自动展开
|
|
68
|
+
* 3. 用户手动切换后保持手动状态,不随重新测量回退
|
|
69
|
+
*
|
|
70
|
+
* @param {Form} form - 搜索表单实例,由 table.searchForm 提供
|
|
71
|
+
* @param {'grid' | 'inline'} [layout] - 布局形态,默认 'grid'
|
|
72
|
+
* @param {boolean | Expand} [expand] - 展开配置,默认 true
|
|
73
|
+
* @param {Component<ButtonProps> | DefineComponent<ButtonProps> | false} [searchButton] - 自定义搜索按钮,false 时不渲染
|
|
74
|
+
* @param {Component<ButtonProps> | DefineComponent<ButtonProps> | false} [resetButton] - 自定义重置按钮,false 时不渲染
|
|
75
|
+
* @param {Component<ExpandButtonProps> | DefineComponent<ExpandButtonProps> | false} [expandButton] - 自定义展开/折叠按钮,false 时不渲染
|
|
76
|
+
* @param {number} [rowGap] - 行间距,默认 16
|
|
77
|
+
* @param {number} [columnGap] - 列间距,默认 24
|
|
53
78
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* 4. 使用 IntersectionObserver 检测隐藏字段是否有值,有值则自动展开
|
|
79
|
+
* @slot reset-button - 重置按钮插槽,作用域参数 onReset
|
|
80
|
+
* @slot search-button - 搜索按钮插槽,作用域参数 onSearch
|
|
81
|
+
* @slot expand-button - 展开/折叠按钮插槽,作用域参数 expandStatus / changeExpandStatus
|
|
58
82
|
*/
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
} from 'vue';
|
|
66
|
-
import ProForm from '../../form';
|
|
67
|
-
import { type Form } from '../../form';
|
|
83
|
+
import type { FormProps } from '../../../shared/ui';
|
|
84
|
+
import { Space, Button } from '../../../shared/ui';
|
|
85
|
+
import type { AllowedComponentProps } from 'vue';
|
|
86
|
+
import { computed, nextTick, type Component, type DefineComponent } from 'vue';
|
|
87
|
+
import ProForm from '../../form/components/ProForm/index.vue';
|
|
88
|
+
import { type Form } from '../../form/hooks/useForm';
|
|
68
89
|
import DownOutlined from './icons/DownOutlined.vue';
|
|
69
90
|
import { useLocale } from '../../../locale';
|
|
70
91
|
import { useSearchFormExpand } from '../../../shared/core';
|
|
@@ -81,7 +102,6 @@ const props = withDefaults(defineProps<SearchFormProps>(), {
|
|
|
81
102
|
expandButton: undefined,
|
|
82
103
|
rowGap: 16,
|
|
83
104
|
columnGap: 24,
|
|
84
|
-
form: undefined,
|
|
85
105
|
});
|
|
86
106
|
|
|
87
107
|
type Emits = {
|
|
@@ -122,9 +142,28 @@ const onReset = () => {
|
|
|
122
142
|
emit('reset');
|
|
123
143
|
};
|
|
124
144
|
|
|
125
|
-
|
|
145
|
+
// 同一 tick 内 click 与 submit 只放行首个触达者,下一 tick 释放标记
|
|
146
|
+
let searchTriggeredInTick = false;
|
|
147
|
+
|
|
148
|
+
const emitSearch = () => {
|
|
149
|
+
searchTriggeredInTick = true;
|
|
150
|
+
nextTick(() => {
|
|
151
|
+
searchTriggeredInTick = false;
|
|
152
|
+
});
|
|
126
153
|
emit('search');
|
|
127
154
|
};
|
|
155
|
+
|
|
156
|
+
// 默认查询按钮为 native-type=submit:点击与回车统一经表单 submit 事件触发
|
|
157
|
+
const onSearchSubmit = () => {
|
|
158
|
+
if (searchTriggeredInTick) return;
|
|
159
|
+
emitSearch();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// 自定义查询按钮与 search-button 插槽经 click 触发
|
|
163
|
+
const onSearchClick = () => {
|
|
164
|
+
if (searchTriggeredInTick) return;
|
|
165
|
+
emitSearch();
|
|
166
|
+
};
|
|
128
167
|
</script>
|
|
129
168
|
|
|
130
169
|
<template>
|
|
@@ -132,6 +171,7 @@ const onSearch = () => {
|
|
|
132
171
|
v-bind="layoutProps"
|
|
133
172
|
:form="props.form"
|
|
134
173
|
class="pro-table_search-form transition"
|
|
174
|
+
@submit.prevent="onSearchSubmit"
|
|
135
175
|
>
|
|
136
176
|
<Space alignment="start" class="pro-table_search-form_button-group">
|
|
137
177
|
<slot name="reset-button" :on-reset="onReset">
|
|
@@ -150,19 +190,18 @@ const onSearch = () => {
|
|
|
150
190
|
</Button>
|
|
151
191
|
</template>
|
|
152
192
|
</slot>
|
|
153
|
-
<slot name="search-button" :on-search="
|
|
193
|
+
<slot name="search-button" :on-search="onSearchClick">
|
|
154
194
|
<template v-if="props.searchButton !== false">
|
|
155
195
|
<component
|
|
156
196
|
:is="props.searchButton"
|
|
157
197
|
v-if="props.searchButton"
|
|
158
|
-
@click="
|
|
198
|
+
@click="onSearchClick"
|
|
159
199
|
/>
|
|
160
200
|
<Button
|
|
161
201
|
v-else
|
|
162
202
|
class="pro-table_search-form_search-button"
|
|
163
203
|
type="primary"
|
|
164
204
|
native-type="submit"
|
|
165
|
-
@click="onSearch"
|
|
166
205
|
>
|
|
167
206
|
{{ t('table.query') }}
|
|
168
207
|
</Button>
|
|
@@ -1,9 +1,18 @@
|
|
|
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 的尺寸
|
|
9
|
+
*/
|
|
2
10
|
import {
|
|
3
11
|
Dropdown,
|
|
4
12
|
DropdownMenu,
|
|
5
13
|
DropdownItem,
|
|
6
14
|
Button,
|
|
15
|
+
useSize,
|
|
7
16
|
type TableProps,
|
|
8
17
|
} from '../../../shared/ui';
|
|
9
18
|
import ColumnHeightOutlined from './icons/ColumnHeightOutlined.vue';
|
|
@@ -14,9 +23,22 @@ type Size = TableProps['size'];
|
|
|
14
23
|
|
|
15
24
|
const { t } = useLocale();
|
|
16
25
|
|
|
26
|
+
const props = withDefaults(
|
|
27
|
+
defineProps<{
|
|
28
|
+
effectiveSize?: Size;
|
|
29
|
+
}>(),
|
|
30
|
+
{ effectiveSize: undefined }
|
|
31
|
+
);
|
|
32
|
+
|
|
17
33
|
const size = defineModel<Size>('size');
|
|
18
34
|
|
|
19
|
-
|
|
35
|
+
// 宿主 ConfigProvider / 表单上下文的尺寸兜底
|
|
36
|
+
const hostSize = useSize();
|
|
37
|
+
|
|
38
|
+
// 高亮项:表格有效尺寸 > 本地绑定 > 宿主 ConfigProvider 尺寸 > 默认
|
|
39
|
+
const currentSize = computed(
|
|
40
|
+
() => (props.effectiveSize ?? size.value ?? hostSize.value) || 'default'
|
|
41
|
+
);
|
|
20
42
|
|
|
21
43
|
const items = computed<Array<{ label: string; key: NonNullable<Size> }>>(() => [
|
|
22
44
|
{ label: t('table.sizeDefault'), key: 'default' },
|