@qin-ui/element-plus-pro 1.3.5 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI-CONTEXT.md +27 -3
- package/README.md +42 -31
- 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 +15 -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/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 +13 -1
- 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 +307 -227
- 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 +5 -4
- 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 +25 -12
- package/src/components/form/components/BaseField/utils/index.ts +16 -1
- package/src/components/form/components/BaseFormItem/index.vue +25 -32
- package/src/components/form/components/ContainerFragment/index.vue +3 -1
- package/src/components/form/components/ProForm/index.vue +41 -21
- 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 +24 -17
- 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 +16 -6
- package/src/components/table/components/ProTable.vue +183 -93
- package/src/components/table/components/SearchForm.vue +60 -22
- package/src/components/table/components/SizeControl.vue +23 -1
- package/src/components/table/components/TableColumnRenderer.vue +82 -63
- 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 +0 -1
- 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,
|
|
@@ -57,15 +77,18 @@ 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
93
|
import DefaultSearchFormContainer from './DefaultSearchFormContainer.vue';
|
|
71
94
|
import DefaultTableContainer from './DefaultTableContainer.vue';
|
|
@@ -86,14 +109,17 @@ const tableProperties = Object.keys(tableProps());
|
|
|
86
109
|
const paginationProperties = Object.keys(paginationProps());
|
|
87
110
|
|
|
88
111
|
type Control = {
|
|
112
|
+
/** 是否展示表格尺寸控制 */
|
|
89
113
|
sizeControl: boolean;
|
|
114
|
+
/** 是否展示列控制,传对象时作为列控面板配置 */
|
|
90
115
|
columnControl: boolean | ColumnControlConfig<Path<RecordType>>;
|
|
91
116
|
};
|
|
92
117
|
|
|
118
|
+
/** 搜索表单配置:SearchFormProps 去掉 form,并加上搜索区显隐与容器 */
|
|
93
119
|
type SearchFormConfig = Omit<SearchFormProps, 'form'> & {
|
|
94
|
-
|
|
120
|
+
/** 是否隐藏搜索表单 */
|
|
95
121
|
hidden?: boolean;
|
|
96
|
-
|
|
122
|
+
/** 搜索区域包裹容器 */
|
|
97
123
|
container?: ContainerComponent | false;
|
|
98
124
|
};
|
|
99
125
|
|
|
@@ -101,25 +127,31 @@ type ReturnTableRecord<V> = V extends Table<any, infer R> ? R : any;
|
|
|
101
127
|
type RecordType = ReturnTableRecord<T>;
|
|
102
128
|
|
|
103
129
|
type Props = {
|
|
104
|
-
|
|
130
|
+
/** 列表表格对象,来自 useTable */
|
|
105
131
|
table?: T;
|
|
106
|
-
|
|
132
|
+
/** 列表数据查询方法 */
|
|
107
133
|
search?: () => Promise<unknown>;
|
|
108
|
-
|
|
134
|
+
/** 是否在首列插入序号列 */
|
|
109
135
|
addIndexColumn?: boolean;
|
|
110
|
-
|
|
136
|
+
/** 挂载后是否立即触发一次查询 */
|
|
111
137
|
immediateSearch?: boolean;
|
|
112
|
-
|
|
138
|
+
/** 是否展示尺寸与列控制按钮,支持分别控制;显式传入 columns 时列控入口不渲染 */
|
|
113
139
|
control?: boolean | Partial<Control>;
|
|
114
|
-
|
|
140
|
+
/** 搜索栏查询字段表单配置 */
|
|
115
141
|
searchFormConfig?: SearchFormConfig;
|
|
116
|
-
|
|
142
|
+
/** 表格区域包裹容器 */
|
|
117
143
|
tableContainer?: ContainerComponent | false;
|
|
144
|
+
/** 直接传入列配置(覆盖 :table 实例的 columns) */
|
|
118
145
|
columns?: Columns<RecordType>;
|
|
146
|
+
/** 直接传入数据源(覆盖 :table 实例的 data) */
|
|
119
147
|
data?: RecordType[];
|
|
148
|
+
/** 分页配置,传 false 禁用 */
|
|
120
149
|
pagination?: false | Partial<PaginationProps>;
|
|
121
150
|
} & /* @vue-ignore */ Omit<TableProps, 'data'>;
|
|
122
151
|
|
|
152
|
+
/*
|
|
153
|
+
* 组件自身仅触发模型事件;宿主表格事件不入运行时 emits,监听入口保留在 attrs 中透传至 ElTable
|
|
154
|
+
*/
|
|
123
155
|
type Emits = {} & /* @vue-ignore */ TableEmits<RecordType>;
|
|
124
156
|
|
|
125
157
|
defineEmits<Emits>();
|
|
@@ -143,34 +175,23 @@ const config = INJECT_CONFIG['pro-table'];
|
|
|
143
175
|
const injectPropsRaw = inject(config.injectionKey, config.default);
|
|
144
176
|
const injectProps = computed(() => toValue(injectPropsRaw) || {});
|
|
145
177
|
|
|
146
|
-
const attrs =
|
|
178
|
+
const attrs = useReactiveAttrs() as Record<string, any>;
|
|
147
179
|
|
|
148
|
-
|
|
180
|
+
// 组件自身消费的 attrs 键:class / style 绑定根节点,onSearch / onChange 由搜索与分页流程消费
|
|
181
|
+
const PRO_TABLE_CONSUMED_ATTR_KEYS = new Set([
|
|
149
182
|
'class',
|
|
150
183
|
'style',
|
|
151
184
|
'onSearch',
|
|
152
185
|
'onChange',
|
|
153
|
-
'onUpdate:size',
|
|
154
|
-
'onUpdate:loading',
|
|
155
|
-
'data',
|
|
156
|
-
'columns',
|
|
157
|
-
'size',
|
|
158
|
-
'pagination',
|
|
159
186
|
]);
|
|
160
187
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
'searchFormConfig',
|
|
164
|
-
'control',
|
|
165
|
-
'immediateSearch',
|
|
166
|
-
'addIndexColumn',
|
|
167
|
-
'tableContainer',
|
|
168
|
-
]);
|
|
188
|
+
// 组件自身消费的 props 键:对应值按 prop / provider 单一来源派生,provider 注入不再透传这些键
|
|
189
|
+
const PRO_TABLE_PROP_KEYS = new Set(Object.keys(props));
|
|
169
190
|
|
|
170
191
|
const injectTableAttrs = computed(() => {
|
|
171
192
|
const tableConfig: Record<string, any> = {};
|
|
172
193
|
for (const [key, value] of Object.entries(injectProps.value)) {
|
|
173
|
-
if (!
|
|
194
|
+
if (!PRO_TABLE_PROP_KEYS.has(key)) {
|
|
174
195
|
tableConfig[key] = value;
|
|
175
196
|
}
|
|
176
197
|
}
|
|
@@ -178,20 +199,20 @@ const injectTableAttrs = computed(() => {
|
|
|
178
199
|
});
|
|
179
200
|
|
|
180
201
|
const attrsTableProps = computed(() => {
|
|
181
|
-
return
|
|
182
|
-
camelizeProperties(attrs),
|
|
183
|
-
|
|
202
|
+
return omitBy(
|
|
203
|
+
pick(camelizeProperties(attrs), tableProperties),
|
|
204
|
+
value => value === undefined
|
|
184
205
|
) as Partial<TableProps>;
|
|
185
206
|
});
|
|
186
207
|
|
|
187
208
|
const restTableAttrs = computed(() => {
|
|
188
209
|
const rest: Record<string, any> = {};
|
|
189
210
|
for (const [key, value] of Object.entries(attrs)) {
|
|
190
|
-
if (!
|
|
211
|
+
if (!PRO_TABLE_CONSUMED_ATTR_KEYS.has(key)) {
|
|
191
212
|
rest[key] = value;
|
|
192
213
|
}
|
|
193
214
|
}
|
|
194
|
-
return rest;
|
|
215
|
+
return omitBy(rest, value => value === undefined);
|
|
195
216
|
});
|
|
196
217
|
|
|
197
218
|
const size = defineModel<TableProps['size']>('size');
|
|
@@ -199,23 +220,28 @@ const loading = defineModel<boolean>('loading', { default: false });
|
|
|
199
220
|
|
|
200
221
|
// 递增序号做请求竞态守卫,仅最新一次请求可变更 loading
|
|
201
222
|
let searchSequence = 0;
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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]}`);
|
|
207
235
|
};
|
|
208
236
|
const _search = async () => {
|
|
237
|
+
const search = props.search || attrs.onSearch;
|
|
209
238
|
if (props.search && attrs.onSearch) {
|
|
210
|
-
warnSearchConfig(
|
|
211
|
-
'同时配置了 :search prop 与 @search 监听,@search 监听不会被执行,请仅保留其一。'
|
|
212
|
-
);
|
|
239
|
+
warnSearchConfig('duplicate');
|
|
213
240
|
}
|
|
214
|
-
|
|
241
|
+
if (!search) return;
|
|
242
|
+
const searchPromise = search();
|
|
215
243
|
if (!isPromise(searchPromise)) {
|
|
216
|
-
warnSearchConfig(
|
|
217
|
-
'search 未返回 Promise,本次查询不参与 loading 竞态管理(在途请求的 loading 状态保持不变)。'
|
|
218
|
-
);
|
|
244
|
+
warnSearchConfig('not-promise');
|
|
219
245
|
return;
|
|
220
246
|
}
|
|
221
247
|
const seq = ++searchSequence;
|
|
@@ -250,8 +276,57 @@ type CustomSlots = Readonly<
|
|
|
250
276
|
type Slots = CustomSlots & Readonly<Record<string, Slot>>;
|
|
251
277
|
|
|
252
278
|
const slots = useSlots() as Slots;
|
|
253
|
-
|
|
254
|
-
|
|
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
|
+
};
|
|
255
330
|
|
|
256
331
|
const searchPage1st = () => {
|
|
257
332
|
props.table?.setPageParam?.({ current: 1 });
|
|
@@ -270,6 +345,16 @@ const computedSearchFormConfig = computed(() => {
|
|
|
270
345
|
};
|
|
271
346
|
});
|
|
272
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
|
+
|
|
273
358
|
const computedTableContainer = computed(() => {
|
|
274
359
|
const container =
|
|
275
360
|
props.tableContainer ??
|
|
@@ -278,19 +363,26 @@ const computedTableContainer = computed(() => {
|
|
|
278
363
|
return container ? container : undefined;
|
|
279
364
|
});
|
|
280
365
|
|
|
366
|
+
/*
|
|
367
|
+
* 序号列随有效分页偏移:分页关闭时退化为 index + 1;
|
|
368
|
+
* current / pageSize 取有效分页(显式 :pagination 或 Provider 注入),table.pageParam 仅作兜底
|
|
369
|
+
*/
|
|
281
370
|
const getIndex = (i: number) => {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
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;
|
|
288
377
|
};
|
|
289
378
|
|
|
379
|
+
/** 调用方是否显式传入 columns(值为 undefined 时视为未指定) */
|
|
380
|
+
const hasExplicitColumns = computed(() => props.columns !== undefined);
|
|
381
|
+
|
|
290
382
|
const normalizedRawColumns = computed(() => {
|
|
291
|
-
const source = (
|
|
292
|
-
toValue(props.table?.columns) ??
|
|
293
|
-
|
|
383
|
+
const source = (
|
|
384
|
+
(props.columns ?? toValue(props.table?.columns) ?? []) as any[]
|
|
385
|
+
).filter(isObjectItem);
|
|
294
386
|
if (props.addIndexColumn ?? injectProps.value.addIndexColumn) {
|
|
295
387
|
return [
|
|
296
388
|
{
|
|
@@ -307,35 +399,25 @@ const normalizedRawColumns = computed(() => {
|
|
|
307
399
|
});
|
|
308
400
|
|
|
309
401
|
const computedDataSource = computed<any[]>(() => {
|
|
310
|
-
|
|
311
|
-
return props.data ?? attrsData ?? toValue(props.table?.data) ?? [];
|
|
402
|
+
return props.data ?? toValue(props.table?.data) ?? [];
|
|
312
403
|
});
|
|
313
404
|
|
|
314
405
|
const computedTableProps = computed<TableProps>(() => {
|
|
315
|
-
|
|
406
|
+
return {
|
|
316
407
|
...injectTableAttrs.value,
|
|
317
408
|
...restTableAttrs.value,
|
|
318
409
|
...attrsTableProps.value,
|
|
319
410
|
} as TableProps;
|
|
320
|
-
delete (merged as any).data;
|
|
321
|
-
delete (merged as any).columns;
|
|
322
|
-
delete (merged as any).size;
|
|
323
|
-
delete (merged as any).pagination;
|
|
324
|
-
return merged;
|
|
325
411
|
});
|
|
326
412
|
|
|
327
|
-
|
|
413
|
+
/*
|
|
414
|
+
* 分页来源:分页开关与展示配置取 props.pagination 显式配置 > provider 全局配置 > 默认合成;
|
|
415
|
+
* currentPage / pageSize / total 以 table.pageParam 为实时状态,显式配置仅在 pageParam 缺省时兜底
|
|
416
|
+
*/
|
|
328
417
|
const computedPagination = computed<false | Partial<PaginationProps>>(() => {
|
|
329
|
-
|
|
330
|
-
if (props.pagination
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
const hasUserPagination =
|
|
334
|
-
isPlainObject(props.pagination) || isPlainObject(attrsPagination);
|
|
335
|
-
if (hasUserPagination) {
|
|
336
|
-
const pageConfig = (props.pagination ??
|
|
337
|
-
attrsPagination ??
|
|
338
|
-
{}) as Partial<PaginationProps>;
|
|
418
|
+
if (props.pagination === false) return false;
|
|
419
|
+
if (isPlainObject(props.pagination)) {
|
|
420
|
+
const pageConfig = props.pagination as Partial<PaginationProps>;
|
|
339
421
|
const globalPagination =
|
|
340
422
|
(injectProps.value.pagination as any) === false
|
|
341
423
|
? {}
|
|
@@ -386,11 +468,15 @@ const searchFormVisible = computed(() => {
|
|
|
386
468
|
return !!(
|
|
387
469
|
(computedSearchFormConfig.value as any)?.hidden !== true &&
|
|
388
470
|
props.table?.searchForm &&
|
|
389
|
-
fieldList.filter((item: any) => !toValue(item.hidden))
|
|
471
|
+
fieldList.filter((item: any) => isObjectItem(item) && !toValue(item.hidden))
|
|
472
|
+
.length > 0
|
|
390
473
|
);
|
|
391
474
|
});
|
|
392
475
|
|
|
393
|
-
|
|
476
|
+
/*
|
|
477
|
+
* 控制项来源:显式控制配置 > Provider 全局配置
|
|
478
|
+
*/
|
|
479
|
+
const resolvedControl = computed<{
|
|
394
480
|
sizeControl: boolean;
|
|
395
481
|
columnControl: boolean | ColumnControlConfig<Path<RecordType>>;
|
|
396
482
|
}>(() => {
|
|
@@ -404,17 +490,21 @@ const computedControl = computed<{
|
|
|
404
490
|
return { sizeControl: !!sizeControl, columnControl };
|
|
405
491
|
});
|
|
406
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
|
+
|
|
407
500
|
const columnControlConfig = computed(() => {
|
|
408
501
|
const cc = computedControl.value.columnControl;
|
|
409
502
|
return typeof cc === 'object' ? cc : undefined;
|
|
410
503
|
});
|
|
411
504
|
|
|
505
|
+
// 尺寸来源:size 模型 > provider 全局配置
|
|
412
506
|
const resolvedSize = computed(() => {
|
|
413
|
-
return
|
|
414
|
-
size.value ??
|
|
415
|
-
(attrsTableProps.value.size as any) ??
|
|
416
|
-
injectTableAttrs.value.size
|
|
417
|
-
);
|
|
507
|
+
return size.value ?? injectProps.value.size;
|
|
418
508
|
});
|
|
419
509
|
|
|
420
510
|
onMounted(() => {
|
|
@@ -439,7 +529,7 @@ defineExpose({
|
|
|
439
529
|
<SearchForm
|
|
440
530
|
v-if="props.table?.searchForm"
|
|
441
531
|
:form="props.table.searchForm"
|
|
442
|
-
v-bind="
|
|
532
|
+
v-bind="searchFormProps"
|
|
443
533
|
@search="searchPage1st"
|
|
444
534
|
@reset="reset"
|
|
445
535
|
/>
|
|
@@ -458,7 +548,7 @@ defineExpose({
|
|
|
458
548
|
v-if="computedControl.sizeControl"
|
|
459
549
|
class="pro-table_header_size-control"
|
|
460
550
|
>
|
|
461
|
-
<SizeControl v-model:size="size" />
|
|
551
|
+
<SizeControl v-model:size="size" :effective-size="resolvedSize" />
|
|
462
552
|
</div>
|
|
463
553
|
<div
|
|
464
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,30 +50,40 @@ 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
|
-
type Component,
|
|
64
|
-
type DefineComponent,
|
|
65
|
-
} from 'vue';
|
|
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';
|
|
66
87
|
import ProForm from '../../form/components/ProForm/index.vue';
|
|
67
88
|
import { type Form } from '../../form/hooks/useForm';
|
|
68
89
|
import DownOutlined from './icons/DownOutlined.vue';
|
|
@@ -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,10 +142,28 @@ const onReset = () => {
|
|
|
122
142
|
emit('reset');
|
|
123
143
|
};
|
|
124
144
|
|
|
125
|
-
//
|
|
126
|
-
|
|
145
|
+
// 同一 tick 内 click 与 submit 只放行首个触达者,下一 tick 释放标记
|
|
146
|
+
let searchTriggeredInTick = false;
|
|
147
|
+
|
|
148
|
+
const emitSearch = () => {
|
|
149
|
+
searchTriggeredInTick = true;
|
|
150
|
+
nextTick(() => {
|
|
151
|
+
searchTriggeredInTick = false;
|
|
152
|
+
});
|
|
127
153
|
emit('search');
|
|
128
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
|
+
};
|
|
129
167
|
</script>
|
|
130
168
|
|
|
131
169
|
<template>
|
|
@@ -133,7 +171,7 @@ const onSearch = () => {
|
|
|
133
171
|
v-bind="layoutProps"
|
|
134
172
|
:form="props.form"
|
|
135
173
|
class="pro-table_search-form transition"
|
|
136
|
-
@submit.prevent="
|
|
174
|
+
@submit.prevent="onSearchSubmit"
|
|
137
175
|
>
|
|
138
176
|
<Space alignment="start" class="pro-table_search-form_button-group">
|
|
139
177
|
<slot name="reset-button" :on-reset="onReset">
|
|
@@ -152,12 +190,12 @@ const onSearch = () => {
|
|
|
152
190
|
</Button>
|
|
153
191
|
</template>
|
|
154
192
|
</slot>
|
|
155
|
-
<slot name="search-button" :on-search="
|
|
193
|
+
<slot name="search-button" :on-search="onSearchClick">
|
|
156
194
|
<template v-if="props.searchButton !== false">
|
|
157
195
|
<component
|
|
158
196
|
:is="props.searchButton"
|
|
159
197
|
v-if="props.searchButton"
|
|
160
|
-
@click="
|
|
198
|
+
@click="onSearchClick"
|
|
161
199
|
/>
|
|
162
200
|
<Button
|
|
163
201
|
v-else
|
|
@@ -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' },
|