@qin-ui/antdv-next-pro 1.1.14 → 1.2.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.
Files changed (129) hide show
  1. package/AI-CONTEXT.md +281 -23
  2. package/LICENSE +9 -0
  3. package/README.md +75 -58
  4. package/api.json +70 -28
  5. package/bin/init-ai.mjs +404 -49
  6. package/es/antdv-next-pro.css +9 -9
  7. package/es/component-provider/{index-Daiw7IxY.js → index-Tesc9NuK.js} +2 -1
  8. package/es/component-provider/index-Tesc9NuK.js.map +1 -0
  9. package/es/component-provider/index.js +2 -1
  10. package/es/component-provider/index.js.map +1 -0
  11. package/es/components/component-provider/components/index.vue.d.ts +27 -0
  12. package/es/components/component-provider/components/index.vue.d.ts.map +1 -0
  13. package/es/components/component-provider/constants/index.d.ts +16 -0
  14. package/es/components/component-provider/constants/index.d.ts.map +1 -0
  15. package/es/components/component-provider/index.d.ts +4 -0
  16. package/es/components/component-provider/index.d.ts.map +1 -0
  17. package/es/components/component-provider/types/index.d.ts +26 -0
  18. package/es/components/component-provider/types/index.d.ts.map +1 -0
  19. package/es/components/form/components/BaseField/index.vue.d.ts +13 -0
  20. package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -0
  21. package/es/components/form/components/BaseField/utils/index.d.ts +3 -0
  22. package/es/components/form/components/BaseField/utils/index.d.ts.map +1 -0
  23. package/es/components/form/components/BaseFormItem/index.vue.d.ts +9 -0
  24. package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -0
  25. package/es/components/form/components/ContainerFragment/index.vue.d.ts +23 -0
  26. package/es/components/form/components/ContainerFragment/index.vue.d.ts.map +1 -0
  27. package/es/components/form/components/GroupedFieldAttrs/index.vue.d.ts +31 -0
  28. package/es/components/form/components/GroupedFieldAttrs/index.vue.d.ts.map +1 -0
  29. package/es/components/form/components/PathProvider/index.vue.d.ts +24 -0
  30. package/es/components/form/components/PathProvider/index.vue.d.ts.map +1 -0
  31. package/es/components/form/components/ProForm/index.vue.d.ts +36 -0
  32. package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -0
  33. package/es/components/form/components/SlotComponent/index.vue.d.ts +7 -0
  34. package/es/components/form/components/SlotComponent/index.vue.d.ts.map +1 -0
  35. package/es/components/form/components/index.d.ts +12 -0
  36. package/es/components/form/components/index.d.ts.map +1 -0
  37. package/es/components/form/constants/index.d.ts +81 -0
  38. package/es/components/form/constants/index.d.ts.map +1 -0
  39. package/es/components/form/hooks/index.d.ts +5 -0
  40. package/es/components/form/hooks/index.d.ts.map +1 -0
  41. package/es/components/form/hooks/useFields.d.ts +57 -0
  42. package/es/components/form/hooks/useFields.d.ts.map +1 -0
  43. package/es/components/form/hooks/useForm.d.ts +93 -0
  44. package/es/components/form/hooks/useForm.d.ts.map +1 -0
  45. package/es/components/form/hooks/useFormRef.d.ts +30 -0
  46. package/es/components/form/hooks/useFormRef.d.ts.map +1 -0
  47. package/es/components/form/index.d.ts +7 -0
  48. package/es/components/form/index.d.ts.map +1 -0
  49. package/es/components/form/types/index.d.ts +251 -0
  50. package/es/components/form/types/index.d.ts.map +1 -0
  51. package/es/components/table/components/ColumnControl.vue.d.ts +9 -0
  52. package/es/components/table/components/ColumnControl.vue.d.ts.map +1 -0
  53. package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts +18 -0
  54. package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +1 -0
  55. package/es/components/table/components/DefaultTableContainer.vue.d.ts +18 -0
  56. package/es/components/table/components/DefaultTableContainer.vue.d.ts.map +1 -0
  57. package/es/components/table/components/ProTable.vue.d.ts +118 -0
  58. package/es/components/table/components/ProTable.vue.d.ts.map +1 -0
  59. package/es/components/table/components/SearchForm.vue.d.ts +48 -0
  60. package/es/components/table/components/SearchForm.vue.d.ts.map +1 -0
  61. package/es/components/table/components/SizeControl.vue.d.ts +12 -0
  62. package/es/components/table/components/SizeControl.vue.d.ts.map +1 -0
  63. package/es/components/table/components/icons/ColumnHeightOutlined.vue.d.ts +3 -0
  64. package/es/components/table/components/icons/ColumnHeightOutlined.vue.d.ts.map +1 -0
  65. package/es/components/table/components/icons/DownOutlined.vue.d.ts +3 -0
  66. package/es/components/table/components/icons/DownOutlined.vue.d.ts.map +1 -0
  67. package/es/components/table/components/icons/FilterOutlined.vue.d.ts +3 -0
  68. package/es/components/table/components/icons/FilterOutlined.vue.d.ts.map +1 -0
  69. package/es/components/table/components/index.d.ts +5 -0
  70. package/es/components/table/components/index.d.ts.map +1 -0
  71. package/es/components/table/index.d.ts +8 -0
  72. package/es/components/table/index.d.ts.map +1 -0
  73. package/es/components/table/types/index.d.ts +48 -0
  74. package/es/components/table/types/index.d.ts.map +1 -0
  75. package/es/components/table/useTable.d.ts +111 -0
  76. package/es/components/table/useTable.d.ts.map +1 -0
  77. package/es/form/{index-7clzY8ZD.js → index-CcP5OaUd.js} +12 -6
  78. package/es/form/index-CcP5OaUd.js.map +1 -0
  79. package/es/form/index.js +4 -3
  80. package/es/form/index.js.map +1 -0
  81. package/es/index.d.ts +76 -1522
  82. package/es/index.d.ts.map +1 -0
  83. package/es/index.js +14 -23
  84. package/es/index.js.map +1 -0
  85. package/es/shared/core/index.d.ts +6 -0
  86. package/es/shared/core/index.d.ts.map +1 -0
  87. package/es/shared/ui/index.d.ts +9 -0
  88. package/es/shared/ui/index.d.ts.map +1 -0
  89. package/es/table/index.js +11 -9
  90. package/es/table/index.js.map +1 -0
  91. package/package.json +10 -11
  92. package/src/components/component-provider/components/index.vue +67 -0
  93. package/src/components/component-provider/constants/index.ts +177 -0
  94. package/src/components/component-provider/index.ts +6 -0
  95. package/src/components/component-provider/types/index.ts +59 -0
  96. package/src/components/form/components/BaseField/index.vue +181 -0
  97. package/src/components/form/components/BaseField/utils/index.ts +16 -0
  98. package/src/components/form/components/BaseFormItem/index.vue +158 -0
  99. package/src/components/form/components/ContainerFragment/index.vue +24 -0
  100. package/src/components/form/components/GroupedFieldAttrs/index.vue +119 -0
  101. package/src/components/form/components/PathProvider/index.vue +19 -0
  102. package/src/components/form/components/ProForm/index.vue +104 -0
  103. package/src/components/form/components/SlotComponent/index.vue +26 -0
  104. package/src/components/form/components/index.ts +15 -0
  105. package/src/components/form/constants/index.ts +140 -0
  106. package/src/components/form/hooks/index.ts +5 -0
  107. package/src/components/form/hooks/useFields.ts +67 -0
  108. package/src/components/form/hooks/useForm.ts +110 -0
  109. package/src/components/form/hooks/useFormRef.ts +35 -0
  110. package/src/components/form/index.ts +29 -0
  111. package/src/components/form/types/index.ts +317 -0
  112. package/src/components/table/components/ColumnControl.vue +142 -0
  113. package/src/components/table/components/DefaultSearchFormContainer.vue +22 -0
  114. package/src/components/table/components/DefaultTableContainer.vue +19 -0
  115. package/src/components/table/components/ProTable.vue +372 -0
  116. package/src/components/table/components/SearchForm.vue +296 -0
  117. package/src/components/table/components/SizeControl.vue +54 -0
  118. package/src/components/table/components/icons/ColumnHeightOutlined.vue +4 -0
  119. package/src/components/table/components/icons/DownOutlined.vue +4 -0
  120. package/src/components/table/components/icons/FilterOutlined.vue +4 -0
  121. package/src/components/table/components/index.ts +4 -0
  122. package/src/components/table/index.ts +14 -0
  123. package/src/components/table/types/index.ts +52 -0
  124. package/src/components/table/useTable.ts +129 -0
  125. package/src/index.ts +81 -0
  126. package/src/shared/core/index.ts +37 -0
  127. package/src/shared/ui/index.ts +55 -0
  128. package/es/core/index-ghYuQ3Sy.js +0 -360
  129. package/es/vendor/utils/lodash-es-p6jau26B.js +0 -1120
@@ -0,0 +1,372 @@
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
+ * └── ATable (antdv-next 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
+
36
+ import {
37
+ PaginationProps,
38
+ Table as ATable,
39
+ TableProps,
40
+ ColumnType,
41
+ tableProps,
42
+ } from '../../../shared/ui';
43
+ import {
44
+ computed,
45
+ nextTick,
46
+ useAttrs,
47
+ onMounted,
48
+ inject,
49
+ type Slot,
50
+ } from 'vue';
51
+ import { INJECT_CONFIG } from '../../component-provider/constants';
52
+ import { ContainerFragment } from '../../form';
53
+ import type { ContainerComponent } from '../../form/types';
54
+ import SearchForm from './SearchForm.vue';
55
+ import { SearchFormProps } from './SearchForm.vue';
56
+ import type { Table } from '../useTable';
57
+ import { camelizeProperties, getObject, pick } from '../../../shared/core';
58
+ import { ComponentSlots } from 'vue-component-type-helpers';
59
+ import {
60
+ DefaultSearchFormContainer,
61
+ DefaultTableContainer,
62
+ SizeControl,
63
+ ColumnControl,
64
+ } from '.';
65
+ import { Column, Columns } from '../types';
66
+
67
+ defineOptions({ name: 'ProTable', inheritAttrs: false });
68
+
69
+ type Control = { sizeControl: boolean; columnControl: boolean };
70
+
71
+ type SearchFormConfig = Omit<SearchFormProps, 'form'> & {
72
+ // 是否隐藏搜索表单
73
+ hidden?: boolean;
74
+ // 搜索区域包裹容器
75
+ container?: ContainerComponent | false;
76
+ };
77
+
78
+ type Props = {
79
+ // 列表表格对象,returns from useTable
80
+ table?: T;
81
+ // 列表数据查询方法
82
+ search?: () => Promise<unknown>;
83
+ // 是否在首列插入index列
84
+ addIndexColumn?: boolean;
85
+ // onMounted 时立即触发一次search事件
86
+ immediateSearch?: boolean;
87
+ // 是否展示表格 size 和 column 控制按钮
88
+ control?: boolean | Partial<Control>;
89
+ // 搜索栏查询字段表单配置
90
+ searchFormConfig?: SearchFormConfig;
91
+ // 表格区域包裹容器
92
+ tableContainer?: ContainerComponent | false;
93
+ } & /* @vue-ignore */ TableProps;
94
+
95
+ const {
96
+ table = undefined,
97
+ search = undefined,
98
+ addIndexColumn = undefined,
99
+ control = undefined,
100
+ tableContainer = undefined,
101
+ immediateSearch = undefined,
102
+ searchFormConfig = undefined,
103
+ } = defineProps<Props>();
104
+
105
+ // 从 ProComponentProvider 获取全局配置,与 props 合并
106
+ const config = INJECT_CONFIG['pro-table'];
107
+ const injectProps = inject(config.injectionKey, config.default);
108
+
109
+ const tableProperties = Object.keys(tableProps());
110
+
111
+ const injectAttrs = pick(injectProps, tableProperties) as TableProps;
112
+
113
+ const computedSearchFormConfig = computed(() => {
114
+ const container =
115
+ searchFormConfig?.container ??
116
+ injectProps.searchFormConfig?.container ??
117
+ DefaultSearchFormContainer;
118
+ return {
119
+ ...injectProps.searchFormConfig,
120
+ ...getObject(searchFormConfig),
121
+ container: container ? container : undefined,
122
+ };
123
+ });
124
+
125
+ const attrs: Record<string, any> = useAttrs();
126
+ const tableAttrs = computed<TableProps>(() => {
127
+ return pick(camelizeProperties(attrs), tableProperties);
128
+ });
129
+
130
+ const { columns, dataSource, searchForm, pageParam, setPageParam } =
131
+ table || {};
132
+
133
+ const size = defineModel<TableProps['size']>('size');
134
+ const loading = defineModel<boolean>('loading');
135
+
136
+ // 核心搜索方法:设置 loading 状态,调用外部 search 函数
137
+ const _search = async () => {
138
+ try {
139
+ loading.value = true;
140
+ await search?.();
141
+ } finally {
142
+ loading.value = false;
143
+ }
144
+ };
145
+
146
+ // 重置:恢复查询参数到初始值,重新搜索
147
+ const reset = () => {
148
+ table?.resetQueryParams();
149
+ nextTick(() => {
150
+ _search();
151
+ });
152
+ };
153
+
154
+ type CustomSlots = Readonly<
155
+ Partial<Record<'search-form' | 'button-bar' | 'toolbar' | 'table', Slot>>
156
+ >;
157
+ type TableSlots = Omit<ComponentSlots<typeof ATable>, 'bodyCell'> &
158
+ Readonly<{
159
+ bodyCell?: (props: {
160
+ text: any;
161
+ value: any;
162
+ record: RecordType;
163
+ index: number;
164
+ column: Column<RecordType>;
165
+ }) => void;
166
+ }>;
167
+ type Slots = Omit<TableSlots & CustomSlots, 'default'>;
168
+
169
+ const slots = defineSlots<Slots>();
170
+ const {
171
+ 'search-form': searchFormSlot,
172
+ 'button-bar': buttonBarSlot,
173
+ toolbar: toolbarSlot,
174
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
175
+ table: tableSlot,
176
+ ...tableSlots
177
+ } = slots;
178
+
179
+ const searchPage1st = () => {
180
+ setPageParam?.({ current: 1 });
181
+ _search();
182
+ };
183
+
184
+ const computedTableProps = computed<TableProps>(() => {
185
+ return {
186
+ ...injectAttrs,
187
+ ...tableAttrs.value,
188
+ pagination: undefined,
189
+ onChange: undefined,
190
+ };
191
+ });
192
+
193
+ const computedTableContainer = computed(() => {
194
+ const container =
195
+ tableContainer ?? injectProps.tableContainer ?? DefaultTableContainer;
196
+ return container ? container : undefined;
197
+ });
198
+
199
+ type ReturnGenericParameterTypes<V> = V extends Table<any, infer R> ? R : never;
200
+ type RecordType = ReturnGenericParameterTypes<T>;
201
+
202
+ const indexColumn: ColumnType = {
203
+ title: '序号',
204
+ dataIndex: '_index',
205
+ width: 80,
206
+ render: (_, __, index) => index + 1,
207
+ };
208
+
209
+ const computedColumns = computed(
210
+ () =>
211
+ [
212
+ ...((addIndexColumn ?? injectProps.addIndexColumn) ? [indexColumn] : []),
213
+ ...(tableAttrs.value.columns ?? columns?.value ?? []).flatMap(
214
+ (item: any, index) => {
215
+ if (item.key) return [{ ...item, key: item.key }];
216
+ if (item.dataIndex) {
217
+ const dataIndexKey = Array.isArray(item.dataIndex)
218
+ ? item.dataIndex.join('.')
219
+ : (item.dataIndex as string);
220
+ return [{ ...item, key: dataIndexKey }];
221
+ }
222
+ return [{ ...item, key: index }];
223
+ }
224
+ ),
225
+ ] as Columns<RecordType>
226
+ );
227
+
228
+ const visibleComputedColumns = computed(() => {
229
+ return computedColumns.value.filter(item => !(item as Column).hidden);
230
+ });
231
+
232
+ const computedDataSource = computed<RecordType[]>(() => {
233
+ return (tableAttrs.value.dataSource as any[]) ?? dataSource?.value;
234
+ });
235
+
236
+ const computedPagination = computed<PaginationProps | false>(() => {
237
+ if (tableAttrs.value.pagination === false) return false;
238
+ return {
239
+ ...injectAttrs.pagination,
240
+ ...pageParam,
241
+ ...tableAttrs.value.pagination,
242
+ };
243
+ });
244
+
245
+ const onTableChange: any = (...args: any[]) => {
246
+ setPageParam?.(args[0]);
247
+ attrs.onChange?.(...args);
248
+ _search();
249
+ };
250
+
251
+ const showSearch = computed(() => {
252
+ return !!(
253
+ searchFormSlot ||
254
+ (computedSearchFormConfig.value?.hidden !== true &&
255
+ searchForm &&
256
+ searchForm.fields.value.filter(item => !item.hidden)?.length > 0)
257
+ );
258
+ });
259
+
260
+ const computedControl = computed<Control>(() => {
261
+ const mergedControl = control ?? injectProps.control;
262
+ const sizeControl = (mergedControl as Control)?.sizeControl ?? mergedControl;
263
+ const columnControl =
264
+ (mergedControl as Control)?.columnControl ?? mergedControl;
265
+ return { sizeControl, columnControl };
266
+ });
267
+
268
+ onMounted(() => {
269
+ if (immediateSearch ?? injectProps.immediateSearch) {
270
+ _search();
271
+ }
272
+ });
273
+ </script>
274
+
275
+ <template>
276
+ <div
277
+ class="pro-table"
278
+ :class="($attrs as any).class"
279
+ :style="($attrs as any).style"
280
+ >
281
+ <ContainerFragment
282
+ v-if="showSearch"
283
+ :component="computedSearchFormConfig.container"
284
+ >
285
+ <slot name="search-form">
286
+ <SearchForm
287
+ v-if="searchForm"
288
+ :form="searchForm"
289
+ v-bind="computedSearchFormConfig"
290
+ @search="searchPage1st"
291
+ @reset="reset"
292
+ />
293
+ </slot>
294
+ </ContainerFragment>
295
+
296
+ <ContainerFragment :component="computedTableContainer">
297
+ <div class="pro-table_header">
298
+ <div v-if="buttonBarSlot" class="pro-table_header_button-bar">
299
+ <slot name="button-bar" />
300
+ </div>
301
+ <div v-if="toolbarSlot" class="pro-table_header_toolbar">
302
+ <slot name="toolbar" />
303
+ </div>
304
+ <div
305
+ v-if="computedControl.sizeControl"
306
+ class="pro-table_header_size-control"
307
+ >
308
+ <SizeControl v-model:size="size" />
309
+ </div>
310
+ <div
311
+ v-if="computedControl.columnControl"
312
+ class="pro-table_header_column-control"
313
+ >
314
+ <ColumnControl :columns="computedColumns" :table="table" />
315
+ </div>
316
+ </div>
317
+
318
+ <slot name="table">
319
+ <ATable
320
+ class="pro-table_content"
321
+ v-bind="computedTableProps"
322
+ :size="size"
323
+ :loading="loading"
324
+ :columns="visibleComputedColumns"
325
+ :data-source="computedDataSource"
326
+ :pagination="computedPagination"
327
+ @change="onTableChange"
328
+ >
329
+ <template
330
+ v-for="(_, name) of tableSlots"
331
+ :key="name"
332
+ #[name]="scoped"
333
+ >
334
+ <slot :name="name" v-bind="scoped" />
335
+ </template>
336
+ </ATable>
337
+ </slot>
338
+ </ContainerFragment>
339
+ </div>
340
+ </template>
341
+
342
+ <style scoped lang="less">
343
+ .pro-table {
344
+ &_header {
345
+ display: flex;
346
+ align-items: center;
347
+ justify-content: flex-end;
348
+
349
+ &:empty {
350
+ display: none;
351
+ }
352
+
353
+ & + &_content {
354
+ margin-top: 16px;
355
+ }
356
+
357
+ &_button-bar {
358
+ flex: 1;
359
+ }
360
+
361
+ &_toolbar {
362
+ margin-left: 12px;
363
+ }
364
+ }
365
+
366
+ :deep([class*='-pagination']) {
367
+ [class*='-pagination-total-text'] {
368
+ flex: 1;
369
+ }
370
+ }
371
+ }
372
+ </style>
@@ -0,0 +1,296 @@
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
+ | 'colon'
12
+ | 'disabled'
13
+ | 'labelAlign'
14
+ | 'labelCol'
15
+ | 'labelWrap'
16
+ | 'name'
17
+ | 'wrapperCol'
18
+ >;
19
+
20
+ type ButtonProps = { onClick: () => void };
21
+ type ExpandButtonProps = ButtonProps & { expandStatus: boolean };
22
+
23
+ export type SearchFormProps = {
24
+ form: Form;
25
+ layout?: 'grid' | 'inline';
26
+ expand?: boolean | Expand;
27
+ searchButton?: Component<ButtonProps> | DefineComponent<ButtonProps> | false;
28
+ resetButton?: Component<ButtonProps> | DefineComponent<ButtonProps> | false;
29
+ expandButton?:
30
+ | Component<ExpandButtonProps>
31
+ | DefineComponent<ExpandButtonProps>
32
+ | false;
33
+ rowGap?: number;
34
+ columnGap?: number;
35
+ } & /* @vue-ignore */ _FormProps &
36
+ AllowedComponentProps;
37
+ </script>
38
+
39
+ <script lang="ts" setup>
40
+ /**
41
+ * @component SearchForm
42
+ * @description ProTable 的搜索表单子组件
43
+ *
44
+ * ## 核心功能
45
+ *
46
+ * 1. 内部渲染 ProForm,使用 table.searchForm 管理搜索条件
47
+ * 2. 支持 grid(网格)和 inline(行内)两种布局
48
+ * 3. 网格布局下支持展开/折叠:通过 IntersectionObserver 自动检测哪些字段溢出
49
+ * 4. 提供搜索、重置、展开/折叠按钮(支持自定义)
50
+ *
51
+ * ## 展开/折叠机制
52
+ *
53
+ * 1. 首次渲染后,测量 ProForm 的实际高度
54
+ * 2. 根据 minExpandRows 计算折叠高度(默认 2 行)
55
+ * 3. 如果实际高度 > 折叠高度,显示展开/折叠按钮
56
+ * 4. 使用 IntersectionObserver 检测隐藏字段是否有值,有值则自动展开
57
+ */
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
+ // 如果隐藏字段(intersectionRatio === 0)有值,则自动展开
129
+ expandStatus.value = entries.some(e => {
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 => !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, rowGap] as [number, number],
195
+ style: { flex: 1, marginRight: '12px' },
196
+ },
197
+ style: {
198
+ display: 'flex',
199
+ overflow: 'hidden',
200
+ height: `${expandStatus.value ? proFormHeight.value : collapseHeight.value}px`,
201
+ },
202
+ }
203
+ : {
204
+ layout: 'inline' as any,
205
+ style: { gap: `${rowGap}px ${columnGap}px` },
206
+ grid: false,
207
+ }
208
+ );
209
+
210
+ const onReset = () => {
211
+ emit('reset');
212
+ };
213
+
214
+ const onSearch = () => {
215
+ emit('search');
216
+ };
217
+ </script>
218
+
219
+ <template>
220
+ <ProForm
221
+ v-bind="layoutProps"
222
+ :form="form"
223
+ class="pro-table_search-form transition"
224
+ >
225
+ <Space align="start" class="pro-table_search-form_button-group">
226
+ <slot name="reset-button" @click="onReset">
227
+ <template v-if="resetButton !== false">
228
+ <component :is="resetButton" v-if="resetButton" @click="onReset" />
229
+ <Button
230
+ v-else
231
+ class="pro-table_search-form_reset-button"
232
+ @click="onReset"
233
+ >重置</Button
234
+ >
235
+ </template>
236
+ </slot>
237
+ <slot name="search-button" @click="onSearch">
238
+ <template v-if="searchButton !== false">
239
+ <component :is="searchButton" v-if="searchButton" @click="onSearch" />
240
+ <Button
241
+ v-else
242
+ class="pro-table_search-form_search-button"
243
+ type="primary"
244
+ html-type="submit"
245
+ @click="onSearch"
246
+ >
247
+ 查询
248
+ </Button>
249
+ </template>
250
+ </slot>
251
+ <slot name="expand-button" @click="changeExpandStatus">
252
+ <template v-if="expandButton !== false && showExpandToggle">
253
+ <component
254
+ :is="expandButton"
255
+ v-if="expandButton"
256
+ :expand-status="expandStatus"
257
+ @click="changeExpandStatus"
258
+ />
259
+ <Button
260
+ v-else
261
+ type="link"
262
+ class="pro-table_search-form_expand-toggle-button"
263
+ @click="changeExpandStatus"
264
+ >
265
+ {{ expandStatus ? '收起' : '展开' }}
266
+ <DownOutlined
267
+ class="transition"
268
+ style="margin-left: 4px"
269
+ :style="{ transform: `rotate(${expandStatus ? -180 : 0}deg)` }"
270
+ />
271
+ </Button>
272
+ </template>
273
+ </slot>
274
+ </Space>
275
+ </ProForm>
276
+ </template>
277
+
278
+ <style scoped lang="less">
279
+ .pro-table_search-form {
280
+ :deep {
281
+ [class*='-form-item'] {
282
+ margin: 0;
283
+ }
284
+ }
285
+ &_expand-toggle-button {
286
+ display: flex;
287
+ align-items: center;
288
+ padding: 0;
289
+ padding-left: 4px;
290
+ }
291
+
292
+ .transition {
293
+ transition: all 0.25s;
294
+ }
295
+ }
296
+ </style>
@@ -0,0 +1,54 @@
1
+ <script lang="ts" setup>
2
+ import {
3
+ Dropdown,
4
+ Menu,
5
+ Button,
6
+ type TableProps,
7
+ useConfig,
8
+ } from '../../../shared/ui';
9
+ import ColumnHeightOutlined from './icons/ColumnHeightOutlined.vue';
10
+
11
+ type Size = TableProps['size'];
12
+
13
+ const config = useConfig();
14
+
15
+ const size = defineModel<Size>('size');
16
+
17
+ size.value = size.value ?? config.componentSize?.value ?? 'large';
18
+
19
+ const items = [
20
+ { label: '默认', key: 'large' },
21
+ { label: '中等', key: 'middle' },
22
+ { label: '紧凑', key: 'small' },
23
+ ];
24
+ </script>
25
+
26
+ <template>
27
+ <Dropdown arrow placement="bottomRight">
28
+ <Button type="text" class="pro-table_size-control_button">
29
+ <ColumnHeightOutlined />
30
+ </Button>
31
+ <template #popupRender>
32
+ <Menu
33
+ :selected-keys="[size as string]"
34
+ style="min-width: 80px"
35
+ :items="items"
36
+ @click="({ key }) => (size = key as any)"
37
+ >
38
+ </Menu>
39
+ </template>
40
+ </Dropdown>
41
+ </template>
42
+
43
+ <style scoped lang="less">
44
+ .pro-table_size-control_button {
45
+ display: flex;
46
+ align-items: center;
47
+ padding-right: 12px;
48
+ padding-left: 12px;
49
+
50
+ :deep(svg) {
51
+ transform: scale(1.3);
52
+ }
53
+ }
54
+ </style>