@qin-ui/element-plus-pro 1.0.8 → 1.1.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 +49 -37
- package/LICENSE +9 -0
- package/api.json +44 -27
- package/bin/init-ai.mjs +0 -0
- package/es/component-provider/{index-Bmx9Q9Q-.js → index-BGGIWSkO.js} +2 -1
- package/es/component-provider/index-BGGIWSkO.js.map +1 -0
- package/es/component-provider/index.js +2 -1
- package/es/component-provider/index.js.map +1 -0
- package/es/components/component-provider/components/index.vue.d.ts +27 -0
- package/es/components/component-provider/components/index.vue.d.ts.map +1 -0
- package/es/components/component-provider/constants/index.d.ts +16 -0
- package/es/components/component-provider/constants/index.d.ts.map +1 -0
- package/es/components/component-provider/index.d.ts +4 -0
- package/es/components/component-provider/index.d.ts.map +1 -0
- package/es/components/component-provider/types/index.d.ts +35 -0
- package/es/components/component-provider/types/index.d.ts.map +1 -0
- package/es/components/form/components/BaseField/index.vue.d.ts +13 -0
- package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -0
- package/es/components/form/components/BaseField/utils/index.d.ts +3 -0
- package/es/components/form/components/BaseField/utils/index.d.ts.map +1 -0
- package/es/components/form/components/BaseFormItem/index.vue.d.ts +9 -0
- package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -0
- package/es/components/form/components/ContainerFragment/index.vue.d.ts +23 -0
- package/es/components/form/components/ContainerFragment/index.vue.d.ts.map +1 -0
- package/es/components/form/components/GroupedFieldAttrs/index.vue.d.ts +31 -0
- package/es/components/form/components/GroupedFieldAttrs/index.vue.d.ts.map +1 -0
- package/es/components/form/components/PathProvider/index.vue.d.ts +24 -0
- package/es/components/form/components/PathProvider/index.vue.d.ts.map +1 -0
- package/es/components/form/components/ProForm/index.vue.d.ts +36 -0
- package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -0
- package/es/components/form/components/SlotComponent/index.vue.d.ts +7 -0
- package/es/components/form/components/SlotComponent/index.vue.d.ts.map +1 -0
- package/es/components/form/components/index.d.ts +12 -0
- package/es/components/form/components/index.d.ts.map +1 -0
- package/es/components/form/constants/index.d.ts +67 -0
- package/es/components/form/constants/index.d.ts.map +1 -0
- package/es/components/form/hooks/index.d.ts +5 -0
- package/es/components/form/hooks/index.d.ts.map +1 -0
- package/es/components/form/hooks/useFields.d.ts +57 -0
- package/es/components/form/hooks/useFields.d.ts.map +1 -0
- package/es/components/form/hooks/useForm.d.ts +93 -0
- package/es/components/form/hooks/useForm.d.ts.map +1 -0
- package/es/components/form/hooks/useFormRef.d.ts +30 -0
- package/es/components/form/hooks/useFormRef.d.ts.map +1 -0
- package/es/components/form/index.d.ts +7 -0
- package/es/components/form/index.d.ts.map +1 -0
- package/es/components/form/types/index.d.ts +234 -0
- package/es/components/form/types/index.d.ts.map +1 -0
- package/es/components/table/components/ColumnControl.vue.d.ts +9 -0
- package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -0
- package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts +18 -0
- package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +1 -0
- package/es/components/table/components/DefaultTableContainer.vue.d.ts +18 -0
- package/es/components/table/components/DefaultTableContainer.vue.d.ts.map +1 -0
- package/es/components/table/components/ProTable.vue.d.ts +47 -0
- package/es/components/table/components/ProTable.vue.d.ts.map +1 -0
- package/es/components/table/components/SearchForm.vue.d.ts +48 -0
- package/es/components/table/components/SearchForm.vue.d.ts.map +1 -0
- package/es/components/table/components/SizeControl.vue.d.ts +12 -0
- package/es/components/table/components/SizeControl.vue.d.ts.map +1 -0
- package/es/components/table/components/TableColumnRenderer.vue.d.ts +17 -0
- package/es/components/table/components/TableColumnRenderer.vue.d.ts.map +1 -0
- package/es/components/table/components/icons/ColumnHeightOutlined.vue.d.ts +3 -0
- package/es/components/table/components/icons/ColumnHeightOutlined.vue.d.ts.map +1 -0
- package/es/components/table/components/icons/DownOutlined.vue.d.ts +3 -0
- package/es/components/table/components/icons/DownOutlined.vue.d.ts.map +1 -0
- package/es/components/table/components/icons/FilterOutlined.vue.d.ts +3 -0
- package/es/components/table/components/icons/FilterOutlined.vue.d.ts.map +1 -0
- package/es/components/table/components/index.d.ts +5 -0
- package/es/components/table/components/index.d.ts.map +1 -0
- package/es/components/table/index.d.ts +33 -0
- package/es/components/table/index.d.ts.map +1 -0
- package/es/components/table/types/index.d.ts +67 -0
- package/es/components/table/types/index.d.ts.map +1 -0
- package/es/components/table/useTable.d.ts +111 -0
- package/es/components/table/useTable.d.ts.map +1 -0
- package/es/element-plus-pro.css +10 -10
- package/es/form/{index-D8btGgqa.js → index-oI6LGSpA.js} +13 -7
- package/es/form/index-oI6LGSpA.js.map +1 -0
- package/es/form/index.js +4 -3
- package/es/form/index.js.map +1 -0
- package/es/index.d.ts +76 -1472
- package/es/index.d.ts.map +1 -0
- package/es/index.js +14 -23
- package/es/index.js.map +1 -0
- package/es/shared/core/index.d.ts +6 -0
- package/es/shared/core/index.d.ts.map +1 -0
- package/es/shared/ui/index.d.ts +12 -0
- package/es/shared/ui/index.d.ts.map +1 -0
- package/es/table/index.js +10 -10
- package/es/table/index.js.map +1 -0
- package/package.json +10 -11
- package/src/components/component-provider/components/index.vue +67 -0
- package/src/components/component-provider/constants/index.ts +209 -0
- package/src/components/component-provider/index.ts +6 -0
- package/src/components/component-provider/types/index.ts +64 -0
- package/src/components/form/components/BaseField/index.vue +181 -0
- package/src/components/form/components/BaseField/utils/index.ts +15 -0
- package/src/components/form/components/BaseFormItem/index.vue +170 -0
- package/src/components/form/components/ContainerFragment/index.vue +24 -0
- package/src/components/form/components/GroupedFieldAttrs/index.vue +115 -0
- package/src/components/form/components/PathProvider/index.vue +19 -0
- package/src/components/form/components/ProForm/index.vue +106 -0
- package/src/components/form/components/SlotComponent/index.vue +26 -0
- package/src/components/form/components/index.ts +15 -0
- package/src/components/form/constants/index.ts +116 -0
- package/src/components/form/hooks/index.ts +5 -0
- package/src/components/form/hooks/useFields.ts +67 -0
- package/src/components/form/hooks/useForm.ts +110 -0
- package/src/components/form/hooks/useFormRef.ts +35 -0
- package/src/components/form/index.ts +29 -0
- package/src/components/form/types/index.ts +300 -0
- package/src/components/table/components/ColumnControl.vue +136 -0
- package/src/components/table/components/DefaultSearchFormContainer.vue +14 -0
- package/src/components/table/components/DefaultTableContainer.vue +14 -0
- package/src/components/table/components/ProTable.vue +420 -0
- package/src/components/table/components/SearchForm.vue +302 -0
- package/src/components/table/components/SizeControl.vue +55 -0
- package/src/components/table/components/TableColumnRenderer.vue +71 -0
- package/src/components/table/components/icons/ColumnHeightOutlined.vue +4 -0
- package/src/components/table/components/icons/DownOutlined.vue +4 -0
- package/src/components/table/components/icons/FilterOutlined.vue +4 -0
- package/src/components/table/components/index.ts +4 -0
- package/src/components/table/index.ts +38 -0
- package/src/components/table/types/index.ts +77 -0
- package/src/components/table/useTable.ts +144 -0
- package/src/index.ts +81 -0
- package/src/shared/core/index.ts +38 -0
- package/src/shared/ui/index.ts +106 -0
- package/es/core/index-C0Pbv44C.js +0 -360
- package/es/vendor/utils/lodash-es-p6jau26B.js +0 -1120
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
<script lang="ts" setup generic="T extends Table<any> = Table">
|
|
2
|
+
/**
|
|
3
|
+
* @component ProTable
|
|
4
|
+
* @description 配置驱动的表格组件
|
|
5
|
+
*
|
|
6
|
+
* ## 架构概览
|
|
7
|
+
*
|
|
8
|
+
* ProTable 是表格系统的顶层容器,负责:
|
|
9
|
+
* 1. 接收 useTable() 创建的表格实例,驱动列、数据源、分页的渲染
|
|
10
|
+
* 2. 内部集成 SearchForm 搜索区域(基于 ProForm)
|
|
11
|
+
* 3. 支持列动态控制(显示/隐藏)和尺寸控制
|
|
12
|
+
* 4. 将分页变化和搜索事件串联到数据查询流程
|
|
13
|
+
*
|
|
14
|
+
* ## 数据流
|
|
15
|
+
*
|
|
16
|
+
* ```
|
|
17
|
+
* useTable({ columns, dataSource, searchFields, ... })
|
|
18
|
+
* ↓ 传入 :table prop
|
|
19
|
+
* ProTable
|
|
20
|
+
* ├── SearchForm (使用 table.searchForm 管理搜索条件)
|
|
21
|
+
* │ └── ProForm
|
|
22
|
+
* ├── SizeControl (表格尺寸切换)
|
|
23
|
+
* ├── ColumnControl (列显示/隐藏)
|
|
24
|
+
* └── ElTable (element-plus Table)
|
|
25
|
+
* └── 转发所有 table 插槽
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ## 搜索流程
|
|
29
|
+
*
|
|
30
|
+
* 1. 用户点击搜索 → SearchForm @search → searchPage1st()
|
|
31
|
+
* 2. searchPage1st 重置分页到第一页,触发 _search()
|
|
32
|
+
* 3. 分页变化 → onTableChange → setPageParam + _search()
|
|
33
|
+
* 4. 重置 → reset() → resetQueryParams + _search()
|
|
34
|
+
*/
|
|
35
|
+
import {
|
|
36
|
+
Pagination as ElPagination,
|
|
37
|
+
type PaginationProps,
|
|
38
|
+
Table as ElTable,
|
|
39
|
+
type TableProps,
|
|
40
|
+
tableProps,
|
|
41
|
+
paginationProps,
|
|
42
|
+
} from '../../../shared/ui';
|
|
43
|
+
import {
|
|
44
|
+
computed,
|
|
45
|
+
nextTick,
|
|
46
|
+
useAttrs,
|
|
47
|
+
onMounted,
|
|
48
|
+
inject,
|
|
49
|
+
type Slot,
|
|
50
|
+
useSlots,
|
|
51
|
+
} from 'vue';
|
|
52
|
+
import { INJECT_CONFIG } from '../../component-provider/constants';
|
|
53
|
+
import { ContainerFragment } from '../../form';
|
|
54
|
+
import type { ContainerComponent } from '../../form/types';
|
|
55
|
+
import SearchForm from './SearchForm.vue';
|
|
56
|
+
import type { SearchFormProps } from './SearchForm.vue';
|
|
57
|
+
import type { Table } from '../useTable';
|
|
58
|
+
import { camelizeProperties, getObject, pick } from '../../../shared/core';
|
|
59
|
+
import {
|
|
60
|
+
DefaultSearchFormContainer,
|
|
61
|
+
DefaultTableContainer,
|
|
62
|
+
SizeControl,
|
|
63
|
+
ColumnControl,
|
|
64
|
+
} from '.';
|
|
65
|
+
import type { Columns } from '../types';
|
|
66
|
+
import TableColumnRenderer from './TableColumnRenderer.vue';
|
|
67
|
+
|
|
68
|
+
defineOptions({ name: 'ProTable', inheritAttrs: false });
|
|
69
|
+
|
|
70
|
+
type Control = { sizeControl: boolean; columnControl: boolean };
|
|
71
|
+
|
|
72
|
+
type SearchFormConfig = Omit<SearchFormProps, 'form'> & {
|
|
73
|
+
// 是否隐藏搜索表单
|
|
74
|
+
hidden?: boolean;
|
|
75
|
+
// 搜索区域包裹容器
|
|
76
|
+
container?: ContainerComponent | false;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
type ReturnTableRecord<V> = V extends Table<any, infer R> ? R : any;
|
|
80
|
+
type RecordType = ReturnTableRecord<T>;
|
|
81
|
+
|
|
82
|
+
type Props = {
|
|
83
|
+
// 列表表格对象,returns from useTable
|
|
84
|
+
table?: T;
|
|
85
|
+
// 列表数据查询方法
|
|
86
|
+
search?: () => Promise<unknown>;
|
|
87
|
+
// 是否在首列插入index列
|
|
88
|
+
addIndexColumn?: boolean;
|
|
89
|
+
// onMounted 时立即触发一次 search 事件
|
|
90
|
+
immediateSearch?: boolean;
|
|
91
|
+
// 是否展示表格 size 和 column 控制按钮
|
|
92
|
+
control?: boolean | Partial<Control>;
|
|
93
|
+
// 搜索栏查询字段表单配置
|
|
94
|
+
searchFormConfig?: SearchFormConfig;
|
|
95
|
+
// 表格区域包裹容器
|
|
96
|
+
tableContainer?: ContainerComponent | false;
|
|
97
|
+
columns?: Columns<RecordType>;
|
|
98
|
+
data?: RecordType[];
|
|
99
|
+
pagination?: false | Partial<PaginationProps>;
|
|
100
|
+
} & Omit<TableProps, 'data'>;
|
|
101
|
+
|
|
102
|
+
const {
|
|
103
|
+
table = undefined,
|
|
104
|
+
search = undefined,
|
|
105
|
+
addIndexColumn = undefined,
|
|
106
|
+
control = undefined,
|
|
107
|
+
tableContainer = undefined,
|
|
108
|
+
immediateSearch = undefined,
|
|
109
|
+
searchFormConfig = undefined,
|
|
110
|
+
columns = undefined,
|
|
111
|
+
data = undefined,
|
|
112
|
+
pagination = undefined,
|
|
113
|
+
} = defineProps<Props>();
|
|
114
|
+
|
|
115
|
+
const config = INJECT_CONFIG['pro-table'];
|
|
116
|
+
const injectProps = inject(config.injectionKey, config.default) as any;
|
|
117
|
+
|
|
118
|
+
const tableProperties = Object.keys(tableProps());
|
|
119
|
+
const paginationProperties = Object.keys(paginationProps());
|
|
120
|
+
|
|
121
|
+
const attrs = useAttrs() as Record<string, any>;
|
|
122
|
+
|
|
123
|
+
const injectTableAttrs = computed(() => {
|
|
124
|
+
return pick(injectProps, tableProperties) as Partial<TableProps>;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const attrsTableProps = computed(() => {
|
|
128
|
+
return pick(
|
|
129
|
+
camelizeProperties(attrs),
|
|
130
|
+
tableProperties
|
|
131
|
+
) as Partial<TableProps>;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const {
|
|
135
|
+
columns: tableColumns,
|
|
136
|
+
data: tableData,
|
|
137
|
+
searchForm,
|
|
138
|
+
pageParam,
|
|
139
|
+
setPageParam,
|
|
140
|
+
} = table || {};
|
|
141
|
+
|
|
142
|
+
const size = defineModel<TableProps['size']>('size');
|
|
143
|
+
const loading = defineModel<boolean>('loading');
|
|
144
|
+
|
|
145
|
+
// 核心搜索方法:设置 loading 状态,调用外部 search 函数
|
|
146
|
+
const _search = async () => {
|
|
147
|
+
try {
|
|
148
|
+
loading.value = true;
|
|
149
|
+
await search?.();
|
|
150
|
+
} finally {
|
|
151
|
+
loading.value = false;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// 重置:恢复查询参数到初始值,重新搜索
|
|
156
|
+
const reset = () => {
|
|
157
|
+
table?.resetQueryParams();
|
|
158
|
+
nextTick(() => {
|
|
159
|
+
_search();
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
type CustomSlots = Readonly<
|
|
164
|
+
Partial<Record<'search-form' | 'button-bar' | 'toolbar' | 'table', Slot>>
|
|
165
|
+
>;
|
|
166
|
+
|
|
167
|
+
type Slots = CustomSlots & Readonly<Record<string, Slot>>;
|
|
168
|
+
|
|
169
|
+
const slots = useSlots() as Slots;
|
|
170
|
+
const searchFormSlot = slots['search-form'];
|
|
171
|
+
const buttonBarSlot = slots['button-bar'];
|
|
172
|
+
const toolbarSlot = slots['toolbar'];
|
|
173
|
+
|
|
174
|
+
const tableSlots = computed(() => {
|
|
175
|
+
const customNames = new Set([
|
|
176
|
+
'search-form',
|
|
177
|
+
'button-bar',
|
|
178
|
+
'toolbar',
|
|
179
|
+
'table',
|
|
180
|
+
]);
|
|
181
|
+
return Object.fromEntries(
|
|
182
|
+
Object.entries(slots).filter(([name]) => !customNames.has(name))
|
|
183
|
+
) as Record<string, Slot>;
|
|
184
|
+
});
|
|
185
|
+
const tableSlotNames = computed(() => Object.keys(tableSlots.value));
|
|
186
|
+
|
|
187
|
+
const searchPage1st = () => {
|
|
188
|
+
setPageParam?.({ current: 1 });
|
|
189
|
+
_search();
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const computedSearchFormConfig = computed(() => {
|
|
193
|
+
const container =
|
|
194
|
+
searchFormConfig?.container ??
|
|
195
|
+
injectProps.searchFormConfig?.container ??
|
|
196
|
+
DefaultSearchFormContainer;
|
|
197
|
+
return {
|
|
198
|
+
...injectProps.searchFormConfig,
|
|
199
|
+
...getObject(searchFormConfig),
|
|
200
|
+
container: container ? container : undefined,
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const computedTableContainer = computed(() => {
|
|
205
|
+
const container =
|
|
206
|
+
tableContainer ?? injectProps.tableContainer ?? DefaultTableContainer;
|
|
207
|
+
return container ? container : undefined;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const normalizedRawColumns = computed(() => {
|
|
211
|
+
const source = (columns ?? tableColumns?.value ?? []) as any[];
|
|
212
|
+
|
|
213
|
+
const normalized = source.map((item, index) => {
|
|
214
|
+
const current: any = { ...item };
|
|
215
|
+
if (current.label === undefined && current.title !== undefined) {
|
|
216
|
+
current.label = current.title;
|
|
217
|
+
}
|
|
218
|
+
if (current.prop === undefined && current.dataIndex !== undefined) {
|
|
219
|
+
current.prop = Array.isArray(current.dataIndex)
|
|
220
|
+
? current.dataIndex.join('.')
|
|
221
|
+
: current.dataIndex;
|
|
222
|
+
}
|
|
223
|
+
if (current.key === undefined) {
|
|
224
|
+
current.key =
|
|
225
|
+
current.prop ??
|
|
226
|
+
(typeof current.columnKey === 'string'
|
|
227
|
+
? current.columnKey
|
|
228
|
+
: undefined) ??
|
|
229
|
+
`col-${index}`;
|
|
230
|
+
}
|
|
231
|
+
return current;
|
|
232
|
+
});
|
|
233
|
+
if (addIndexColumn ?? injectProps.addIndexColumn) {
|
|
234
|
+
return [
|
|
235
|
+
{
|
|
236
|
+
key: '_index',
|
|
237
|
+
type: 'index',
|
|
238
|
+
label: '序号',
|
|
239
|
+
width: 80,
|
|
240
|
+
},
|
|
241
|
+
...normalized,
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
return normalized;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const computedDataSource = computed<any[]>(() => {
|
|
248
|
+
const attrsData = attrsTableProps.value.data as any[] | undefined;
|
|
249
|
+
return data ?? attrsData ?? tableData?.value ?? [];
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const computedTableProps = computed<TableProps>(() => {
|
|
253
|
+
const merged = {
|
|
254
|
+
...injectTableAttrs.value,
|
|
255
|
+
...attrsTableProps.value,
|
|
256
|
+
} as TableProps;
|
|
257
|
+
delete (merged as any).data;
|
|
258
|
+
return merged;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const computedPagination = computed<false | Partial<PaginationProps>>(() => {
|
|
262
|
+
if (pagination === false) return false;
|
|
263
|
+
const pageConfig = pagination ?? {};
|
|
264
|
+
return {
|
|
265
|
+
...injectProps.pagination,
|
|
266
|
+
...pick(pageConfig as Record<string, any>, paginationProperties),
|
|
267
|
+
currentPage: (pageConfig as any).currentPage ?? pageParam?.current ?? 1,
|
|
268
|
+
pageSize: (pageConfig as any).pageSize ?? pageParam?.pageSize ?? 10,
|
|
269
|
+
total: (pageConfig as any).total ?? pageParam?.total ?? 0,
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const showPagination = computed(() => computedPagination.value !== false);
|
|
274
|
+
|
|
275
|
+
const onCurrentChange = (current: number) => {
|
|
276
|
+
setPageParam?.({ current });
|
|
277
|
+
_search();
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const onSizeChange = (pageSize: number) => {
|
|
281
|
+
setPageParam?.({ pageSize, current: 1 });
|
|
282
|
+
_search();
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const onPaginationChange = (currentPage: number, pageSize: number) => {
|
|
286
|
+
attrs.onChange?.(currentPage, pageSize);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const showSearch = computed(() => {
|
|
290
|
+
return !!(
|
|
291
|
+
searchFormSlot ||
|
|
292
|
+
(computedSearchFormConfig.value?.hidden !== true &&
|
|
293
|
+
searchForm &&
|
|
294
|
+
searchForm.fields.value.filter(item => !item.hidden)?.length > 0)
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const computedControl = computed<Control>(() => {
|
|
299
|
+
const mergedControl = control ?? injectProps.control;
|
|
300
|
+
const sizeControl = (mergedControl as Control)?.sizeControl ?? mergedControl;
|
|
301
|
+
const columnControl =
|
|
302
|
+
(mergedControl as Control)?.columnControl ?? mergedControl;
|
|
303
|
+
return { sizeControl: !!sizeControl, columnControl: !!columnControl };
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const resolvedSize = computed(() => {
|
|
307
|
+
return (
|
|
308
|
+
size.value ??
|
|
309
|
+
(attrsTableProps.value.size as any) ??
|
|
310
|
+
injectTableAttrs.value.size
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
onMounted(() => {
|
|
315
|
+
if (immediateSearch ?? injectProps.immediateSearch) {
|
|
316
|
+
_search();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
</script>
|
|
320
|
+
|
|
321
|
+
<template>
|
|
322
|
+
<div
|
|
323
|
+
class="pro-table"
|
|
324
|
+
:class="($attrs as any).class"
|
|
325
|
+
:style="($attrs as any).style"
|
|
326
|
+
>
|
|
327
|
+
<ContainerFragment
|
|
328
|
+
v-if="showSearch"
|
|
329
|
+
:component="computedSearchFormConfig.container"
|
|
330
|
+
>
|
|
331
|
+
<slot name="search-form">
|
|
332
|
+
<SearchForm
|
|
333
|
+
v-if="searchForm"
|
|
334
|
+
:form="searchForm"
|
|
335
|
+
v-bind="computedSearchFormConfig"
|
|
336
|
+
@search="searchPage1st"
|
|
337
|
+
@reset="reset"
|
|
338
|
+
/>
|
|
339
|
+
</slot>
|
|
340
|
+
</ContainerFragment>
|
|
341
|
+
|
|
342
|
+
<ContainerFragment :component="computedTableContainer">
|
|
343
|
+
<div class="pro-table_header">
|
|
344
|
+
<div v-if="buttonBarSlot" class="pro-table_header_button-bar">
|
|
345
|
+
<slot name="button-bar" />
|
|
346
|
+
</div>
|
|
347
|
+
<div v-if="toolbarSlot" class="pro-table_header_toolbar">
|
|
348
|
+
<slot name="toolbar" />
|
|
349
|
+
</div>
|
|
350
|
+
<div
|
|
351
|
+
v-if="computedControl.sizeControl"
|
|
352
|
+
class="pro-table_header_size-control"
|
|
353
|
+
>
|
|
354
|
+
<SizeControl v-model:size="size" />
|
|
355
|
+
</div>
|
|
356
|
+
<div
|
|
357
|
+
v-if="computedControl.columnControl"
|
|
358
|
+
class="pro-table_header_column-control"
|
|
359
|
+
>
|
|
360
|
+
<ColumnControl :columns="normalizedRawColumns" :table="table" />
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<slot name="table">
|
|
365
|
+
<ElTable
|
|
366
|
+
v-loading="loading"
|
|
367
|
+
class="pro-table_content"
|
|
368
|
+
v-bind="computedTableProps"
|
|
369
|
+
:size="resolvedSize"
|
|
370
|
+
:data="computedDataSource"
|
|
371
|
+
>
|
|
372
|
+
<TableColumnRenderer :columns="normalizedRawColumns" />
|
|
373
|
+
<template v-for="name in tableSlotNames" :key="name" #[name]="scoped">
|
|
374
|
+
<slot :name="name" v-bind="scoped" />
|
|
375
|
+
</template>
|
|
376
|
+
</ElTable>
|
|
377
|
+
</slot>
|
|
378
|
+
|
|
379
|
+
<ElPagination
|
|
380
|
+
v-if="showPagination"
|
|
381
|
+
class="pro-table_pagination"
|
|
382
|
+
v-bind="computedPagination"
|
|
383
|
+
@current-change="onCurrentChange"
|
|
384
|
+
@size-change="onSizeChange"
|
|
385
|
+
@change="onPaginationChange"
|
|
386
|
+
/>
|
|
387
|
+
</ContainerFragment>
|
|
388
|
+
</div>
|
|
389
|
+
</template>
|
|
390
|
+
|
|
391
|
+
<style scoped lang="less">
|
|
392
|
+
.pro-table {
|
|
393
|
+
&_header {
|
|
394
|
+
display: flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
justify-content: flex-end;
|
|
397
|
+
|
|
398
|
+
&:empty {
|
|
399
|
+
display: none;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
& + &_content {
|
|
403
|
+
margin-top: 16px;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
&_button-bar {
|
|
407
|
+
flex: 1;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
&_toolbar {
|
|
411
|
+
margin-left: 12px;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
&_pagination {
|
|
416
|
+
justify-content: flex-end;
|
|
417
|
+
padding: 16px 0;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
</style>
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
type Expand = {
|
|
3
|
+
// 网格布局时默认展开行数量
|
|
4
|
+
minExpandRows?: number;
|
|
5
|
+
// 网格布局时展开状态
|
|
6
|
+
expandStatus?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type _FormProps = Pick<
|
|
10
|
+
Partial<FormProps>,
|
|
11
|
+
| 'disabled'
|
|
12
|
+
| 'labelPosition'
|
|
13
|
+
| 'labelWidth'
|
|
14
|
+
| 'inline'
|
|
15
|
+
| 'inlineMessage'
|
|
16
|
+
| 'statusIcon'
|
|
17
|
+
| 'showMessage'
|
|
18
|
+
| 'size'
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
type ButtonProps = { onClick: () => void };
|
|
22
|
+
type ExpandButtonProps = ButtonProps & { expandStatus: boolean };
|
|
23
|
+
|
|
24
|
+
export type SearchFormProps = {
|
|
25
|
+
form: Form;
|
|
26
|
+
layout?: 'grid' | 'inline';
|
|
27
|
+
expand?: boolean | Expand;
|
|
28
|
+
searchButton?: Component<ButtonProps> | DefineComponent<ButtonProps> | false;
|
|
29
|
+
resetButton?: Component<ButtonProps> | DefineComponent<ButtonProps> | false;
|
|
30
|
+
expandButton?:
|
|
31
|
+
| Component<ExpandButtonProps>
|
|
32
|
+
| DefineComponent<ExpandButtonProps>
|
|
33
|
+
| false;
|
|
34
|
+
rowGap?: number;
|
|
35
|
+
columnGap?: number;
|
|
36
|
+
} & /* @vue-ignore */ _FormProps &
|
|
37
|
+
AllowedComponentProps;
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<script lang="ts" setup>
|
|
41
|
+
/**
|
|
42
|
+
* @component SearchForm
|
|
43
|
+
* @description ProTable 的搜索表单子组件
|
|
44
|
+
*
|
|
45
|
+
* ## 核心功能
|
|
46
|
+
*
|
|
47
|
+
* 1. 内部渲染 ProForm,使用 table.searchForm 管理搜索条件
|
|
48
|
+
* 2. 支持 grid(网格)和 inline(行内)两种布局
|
|
49
|
+
* 3. 网格布局下支持展开/折叠:通过 IntersectionObserver 自动检测哪些字段溢出
|
|
50
|
+
* 4. 提供搜索、重置、展开/折叠按钮(支持自定义)
|
|
51
|
+
*
|
|
52
|
+
* ## 展开/折叠机制
|
|
53
|
+
*
|
|
54
|
+
* 1. 首次渲染后,测量 ProForm 的实际高度
|
|
55
|
+
* 2. 根据 minExpandRows 计算折叠高度(默认 2 行)
|
|
56
|
+
* 3. 如果实际高度 > 折叠高度,显示展开/折叠按钮
|
|
57
|
+
* 4. 使用 IntersectionObserver 检测隐藏字段是否有值,有值则自动展开
|
|
58
|
+
*/
|
|
59
|
+
import { Space, Button, FormProps } from '../../../shared/ui';
|
|
60
|
+
import {
|
|
61
|
+
AllowedComponentProps,
|
|
62
|
+
computed,
|
|
63
|
+
ref,
|
|
64
|
+
watch,
|
|
65
|
+
watchEffect,
|
|
66
|
+
type Component,
|
|
67
|
+
type DefineComponent,
|
|
68
|
+
} from 'vue';
|
|
69
|
+
import ProForm from '../../form';
|
|
70
|
+
import { type Form } from '../../form';
|
|
71
|
+
import DownOutlined from './icons/DownOutlined.vue';
|
|
72
|
+
|
|
73
|
+
defineOptions({ name: 'SearchForm' });
|
|
74
|
+
|
|
75
|
+
const {
|
|
76
|
+
layout = 'grid',
|
|
77
|
+
expand = true,
|
|
78
|
+
searchButton = undefined,
|
|
79
|
+
resetButton = undefined,
|
|
80
|
+
expandButton = undefined,
|
|
81
|
+
rowGap = 16,
|
|
82
|
+
columnGap = 24,
|
|
83
|
+
form,
|
|
84
|
+
} = defineProps<SearchFormProps>();
|
|
85
|
+
|
|
86
|
+
type Emits = {
|
|
87
|
+
(e: 'search'): void;
|
|
88
|
+
(e: 'reset'): void;
|
|
89
|
+
};
|
|
90
|
+
const emit = defineEmits<Emits>();
|
|
91
|
+
|
|
92
|
+
const proFormHeight = ref<'unset' | number>('unset');
|
|
93
|
+
const collapseHeight = ref(0);
|
|
94
|
+
let rowHeight = 32;
|
|
95
|
+
|
|
96
|
+
const computedExpand = computed(() => {
|
|
97
|
+
if (!expand) return false;
|
|
98
|
+
if (expand === true) return { minExpandRows: 2, expandStatus: false };
|
|
99
|
+
return {
|
|
100
|
+
minExpandRows: Math.max(Math.floor(expand.minExpandRows ?? 2), 1),
|
|
101
|
+
expandStatus: expand.expandStatus ?? false,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const showExpandToggle = ref(false);
|
|
106
|
+
|
|
107
|
+
const expandStatus = ref(true);
|
|
108
|
+
|
|
109
|
+
const changeExpandStatus = () => {
|
|
110
|
+
expandStatus.value = !expandStatus.value;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const { formRef, getFormData } = form;
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* 使用 IntersectionObserver 检测折叠状态下隐藏字段是否有值
|
|
117
|
+
* 如果有值则自动展开,让用户看到已填写的搜索条件
|
|
118
|
+
*/
|
|
119
|
+
const setInitExpandStatus = () => {
|
|
120
|
+
expandStatus.value = false;
|
|
121
|
+
if (formRef.value && expand) {
|
|
122
|
+
const formEl = (formRef.value as any).$el;
|
|
123
|
+
const formItemsEl = formEl.querySelectorAll(
|
|
124
|
+
"[class*='-form-item'] > [path]"
|
|
125
|
+
);
|
|
126
|
+
const observer = new IntersectionObserver(
|
|
127
|
+
entries => {
|
|
128
|
+
expandStatus.value = entries.some(e => {
|
|
129
|
+
// 如果隐藏字段(intersectionRatio === 0)有值,则自动展开
|
|
130
|
+
if (e.intersectionRatio === 0) {
|
|
131
|
+
const path = e.target.getAttribute('path');
|
|
132
|
+
const searchFieldValue = path ? getFormData?.(path) : undefined;
|
|
133
|
+
return ![null, undefined].includes(searchFieldValue);
|
|
134
|
+
}
|
|
135
|
+
return (expand as any).expandStatus;
|
|
136
|
+
});
|
|
137
|
+
observer.disconnect();
|
|
138
|
+
},
|
|
139
|
+
{ root: formEl }
|
|
140
|
+
);
|
|
141
|
+
formItemsEl.forEach((element: Element) => {
|
|
142
|
+
observer.observe(element);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
watch(
|
|
148
|
+
[
|
|
149
|
+
() => form?.fields.value?.filter((f: any) => !f.hidden)?.length,
|
|
150
|
+
() => formRef.value,
|
|
151
|
+
() => expand,
|
|
152
|
+
],
|
|
153
|
+
() => {
|
|
154
|
+
if (!expand || !expandStatus.value || !formRef.value) return;
|
|
155
|
+
const proFormEl = (formRef.value as any)?.$el;
|
|
156
|
+
const { height = 0 } = proFormEl?.getBoundingClientRect?.() || {};
|
|
157
|
+
proFormHeight.value = height;
|
|
158
|
+
rowHeight = proFormEl
|
|
159
|
+
?.querySelector("[class*='-form-item']")
|
|
160
|
+
?.getBoundingClientRect?.()?.height;
|
|
161
|
+
},
|
|
162
|
+
{ flush: 'post', immediate: true }
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* 计算折叠高度和是否显示展开按钮
|
|
167
|
+
* 折叠高度 = minExpandRows * 行高 + (minExpandRows - 1) * 行间距
|
|
168
|
+
* 如果实际高度与折叠高度差异 > 1px,说明有字段溢出,显示展开按钮
|
|
169
|
+
*/
|
|
170
|
+
watchEffect(
|
|
171
|
+
() => {
|
|
172
|
+
if (typeof proFormHeight.value !== 'number') return;
|
|
173
|
+
if (layout === 'grid' && computedExpand.value) {
|
|
174
|
+
const { minExpandRows } = computedExpand.value;
|
|
175
|
+
collapseHeight.value = Math.min(
|
|
176
|
+
minExpandRows * rowHeight + (minExpandRows - 1) * rowGap,
|
|
177
|
+
+proFormHeight.value
|
|
178
|
+
);
|
|
179
|
+
showExpandToggle.value = +proFormHeight.value - collapseHeight.value > 1;
|
|
180
|
+
if (showExpandToggle.value) {
|
|
181
|
+
setInitExpandStatus();
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
showExpandToggle.value = false;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
{ flush: 'post' }
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const layoutProps = computed(() =>
|
|
191
|
+
layout === 'grid'
|
|
192
|
+
? {
|
|
193
|
+
grid: {
|
|
194
|
+
gutter: columnGap,
|
|
195
|
+
style: { rowGap: `${rowGap}px` },
|
|
196
|
+
},
|
|
197
|
+
style: {
|
|
198
|
+
display: 'flex',
|
|
199
|
+
overflow: 'hidden',
|
|
200
|
+
columnGap: `${columnGap}px`,
|
|
201
|
+
height: `${expandStatus.value ? proFormHeight.value : collapseHeight.value}px`,
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
: {
|
|
205
|
+
inline: true,
|
|
206
|
+
style: {
|
|
207
|
+
display: 'flex',
|
|
208
|
+
flexWrap: 'wrap',
|
|
209
|
+
gap: `${rowGap}px ${columnGap}px`,
|
|
210
|
+
},
|
|
211
|
+
grid: false,
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const onReset = () => {
|
|
216
|
+
emit('reset');
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const onSearch = () => {
|
|
220
|
+
emit('search');
|
|
221
|
+
};
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<template>
|
|
225
|
+
<ProForm
|
|
226
|
+
v-bind="layoutProps"
|
|
227
|
+
:form="form"
|
|
228
|
+
class="pro-table_search-form transition"
|
|
229
|
+
@keyup.enter="onSearch"
|
|
230
|
+
>
|
|
231
|
+
<Space alignment="start" class="pro-table_search-form_button-group">
|
|
232
|
+
<slot name="reset-button" @click="onReset">
|
|
233
|
+
<template v-if="resetButton !== false">
|
|
234
|
+
<component :is="resetButton" v-if="resetButton" @click="onReset" />
|
|
235
|
+
<Button
|
|
236
|
+
v-else
|
|
237
|
+
class="pro-table_search-form_reset-button"
|
|
238
|
+
@click="onReset"
|
|
239
|
+
>重置</Button
|
|
240
|
+
>
|
|
241
|
+
</template>
|
|
242
|
+
</slot>
|
|
243
|
+
<slot name="search-button" @click="onSearch">
|
|
244
|
+
<template v-if="searchButton !== false">
|
|
245
|
+
<component :is="searchButton" v-if="searchButton" @click="onSearch" />
|
|
246
|
+
<Button
|
|
247
|
+
v-else
|
|
248
|
+
class="pro-table_search-form_search-button"
|
|
249
|
+
type="primary"
|
|
250
|
+
native-type="submit"
|
|
251
|
+
@click="onSearch"
|
|
252
|
+
>
|
|
253
|
+
查询
|
|
254
|
+
</Button>
|
|
255
|
+
</template>
|
|
256
|
+
</slot>
|
|
257
|
+
<slot name="expand-button" @click="changeExpandStatus">
|
|
258
|
+
<template v-if="expandButton !== false && showExpandToggle">
|
|
259
|
+
<component
|
|
260
|
+
:is="expandButton"
|
|
261
|
+
v-if="expandButton"
|
|
262
|
+
:expand-status="expandStatus"
|
|
263
|
+
@click="changeExpandStatus"
|
|
264
|
+
/>
|
|
265
|
+
<Button
|
|
266
|
+
v-else
|
|
267
|
+
text
|
|
268
|
+
class="pro-table_search-form_expand-toggle-button"
|
|
269
|
+
@click="changeExpandStatus"
|
|
270
|
+
>
|
|
271
|
+
{{ expandStatus ? '收起' : '展开' }}
|
|
272
|
+
<DownOutlined
|
|
273
|
+
class="transition"
|
|
274
|
+
style="margin-left: 4px"
|
|
275
|
+
:style="{ transform: `rotate(${expandStatus ? -180 : 0}deg)` }"
|
|
276
|
+
/>
|
|
277
|
+
</Button>
|
|
278
|
+
</template>
|
|
279
|
+
</slot>
|
|
280
|
+
</Space>
|
|
281
|
+
</ProForm>
|
|
282
|
+
</template>
|
|
283
|
+
|
|
284
|
+
<style scoped lang="less">
|
|
285
|
+
.pro-table_search-form {
|
|
286
|
+
:deep {
|
|
287
|
+
[class*='-form-item'] {
|
|
288
|
+
margin: 0;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
&_expand-toggle-button {
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
padding: 0;
|
|
295
|
+
padding-left: 4px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.transition {
|
|
299
|
+
transition: all 0.25s;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
</style>
|