@qin-ui/element-plus-pro 1.3.0 → 1.3.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.
Files changed (49) hide show
  1. package/AI-CONTEXT.md +1 -1
  2. package/api.json +1 -1
  3. package/es/__tests__/cross-package.spec.d.ts +2 -0
  4. package/es/__tests__/cross-package.spec.d.ts.map +1 -0
  5. package/es/element-plus-pro.css +12 -12
  6. package/es/index.js +95 -245
  7. package/es/index.js.map +1 -1
  8. package/es/src/components/component-provider/components/index.vue.d.ts.map +1 -1
  9. package/es/src/components/component-provider/constants/index.d.ts +2 -2
  10. package/es/src/components/component-provider/constants/index.d.ts.map +1 -1
  11. package/es/src/components/form/components/BaseField/index.vue.d.ts.map +1 -1
  12. package/es/src/components/form/components/BaseField/utils/index.d.ts +1 -0
  13. package/es/src/components/form/components/BaseField/utils/index.d.ts.map +1 -1
  14. package/es/src/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -1
  15. package/es/src/components/form/components/ProForm/index.vue.d.ts.map +1 -1
  16. package/es/src/components/form/components/SlotComponent/index.vue.d.ts.map +1 -1
  17. package/es/src/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -1
  18. package/es/src/components/table/components/ColumnControl.vue.d.ts.map +1 -1
  19. package/es/src/components/table/components/ProTable.vue.d.ts +4 -1
  20. package/es/src/components/table/components/ProTable.vue.d.ts.map +1 -1
  21. package/es/src/components/table/components/SearchForm.vue.d.ts.map +1 -1
  22. package/es/src/components/table/components/TableColumnRenderer.vue.d.ts.map +1 -1
  23. package/es/src/components/table/types/index.d.ts +3 -0
  24. package/es/src/components/table/types/index.d.ts.map +1 -1
  25. package/es/src/locale/index.d.ts +1 -1
  26. package/es/src/locale/index.d.ts.map +1 -1
  27. package/es/src/shared/core/index.d.ts +1 -1
  28. package/es/src/shared/core/index.d.ts.map +1 -1
  29. package/es/src/shared/ui/index.d.ts.map +1 -1
  30. package/package.json +2 -2
  31. package/src/components/component-provider/components/index.vue +4 -3
  32. package/src/components/component-provider/constants/index.ts +4 -3
  33. package/src/components/form/components/BaseField/index.vue +3 -6
  34. package/src/components/form/components/BaseField/utils/index.ts +11 -0
  35. package/src/components/form/components/BaseFormItem/index.vue +2 -1
  36. package/src/components/form/components/ProForm/index.vue +12 -22
  37. package/src/components/form/components/SlotComponent/index.vue +9 -6
  38. package/src/components/form/hooks/useGroupFieldAttrs.ts +5 -2
  39. package/src/components/table/components/ColumnControl.vue +15 -16
  40. package/src/components/table/components/ProTable.vue +39 -36
  41. package/src/components/table/components/SearchForm.vue +4 -182
  42. package/src/components/table/components/TableColumnRenderer.vue +4 -2
  43. package/src/components/table/types/index.ts +3 -0
  44. package/src/locale/en-US.ts +1 -1
  45. package/src/locale/index.ts +1 -5
  46. package/src/locale/zh-CN.ts +1 -1
  47. package/src/locale/zh-TW.ts +1 -1
  48. package/src/shared/core/index.ts +11 -0
  49. package/src/shared/ui/index.ts +9 -4
@@ -83,6 +83,9 @@ defineOptions({
83
83
  inheritAttrs: false,
84
84
  });
85
85
 
86
+ const tableProperties = Object.keys(tableProps());
87
+ const paginationProperties = Object.keys(paginationProps());
88
+
86
89
  type Control = {
87
90
  sizeControl: boolean;
88
91
  columnControl: boolean | ColumnControlConfig<Path<RecordType>>;
@@ -137,9 +140,6 @@ const config = INJECT_CONFIG['pro-table'];
137
140
  const injectPropsRaw = inject(config.injectionKey, config.default);
138
141
  const injectProps = computed(() => toValue(injectPropsRaw) || {});
139
142
 
140
- const tableProperties = Object.keys(tableProps());
141
- const paginationProperties = Object.keys(paginationProps());
142
-
143
143
  const attrs = useAttrs() as Record<string, any>;
144
144
 
145
145
  const injectTableAttrs = computed(() => {
@@ -156,14 +156,17 @@ const attrsTableProps = computed(() => {
156
156
  const size = defineModel<TableProps['size']>('size');
157
157
  const loading = defineModel<boolean>('loading', { default: false });
158
158
 
159
- // 核心搜索方法:设置 loading 状态,调用外部 search 函数
159
+ // 递增序号做请求竞态守卫,仅最新一次请求可变更 loading
160
+ let searchSequence = 0;
160
161
  const _search = async () => {
162
+ const seq = ++searchSequence;
161
163
  const searchPromise = (props.search || attrs.onSearch)?.();
162
- if (isPromise(searchPromise)) {
163
- loading.value = true;
164
- try {
165
- await searchPromise;
166
- } finally {
164
+ if (!isPromise(searchPromise)) return;
165
+ loading.value = true;
166
+ try {
167
+ await searchPromise;
168
+ } finally {
169
+ if (seq === searchSequence) {
167
170
  loading.value = false;
168
171
  }
169
172
  }
@@ -177,29 +180,21 @@ const reset = () => {
177
180
  });
178
181
  };
179
182
 
183
+ const customSlotNames = [
184
+ 'search-form',
185
+ 'button-bar',
186
+ 'toolbar',
187
+ 'table',
188
+ ] as const;
189
+
180
190
  type CustomSlots = Readonly<
181
- Partial<Record<'search-form' | 'button-bar' | 'toolbar' | 'table', Slot>>
191
+ Partial<Record<(typeof customSlotNames)[number], Slot>>
182
192
  >;
183
-
184
193
  type Slots = CustomSlots & Readonly<Record<string, Slot>>;
185
194
 
186
195
  const slots = useSlots() as Slots;
187
- const searchFormSlot = slots['search-form'];
188
- const buttonBarSlot = slots['button-bar'];
189
- const toolbarSlot = slots['toolbar'];
190
-
191
- const tableSlots = computed(() => {
192
- const customNames = new Set([
193
- 'search-form',
194
- 'button-bar',
195
- 'toolbar',
196
- 'table',
197
- ]);
198
- return Object.fromEntries(
199
- Object.entries(slots).filter(([name]) => !customNames.has(name))
200
- ) as Record<string, Slot>;
201
- });
202
- const tableSlotNames = computed(() => Object.keys(tableSlots.value));
196
+ const forwardedSlotNames = () =>
197
+ Object.keys(slots).filter(name => !customSlotNames.includes(name as any));
203
198
 
204
199
  const searchPage1st = () => {
205
200
  props.table?.setPageParam?.({ current: 1 });
@@ -328,15 +323,14 @@ const onPaginationChange = (currentPage: number, pageSize: number) => {
328
323
  attrs.onChange?.(currentPage, pageSize);
329
324
  };
330
325
 
331
- const showSearch = computed(() => {
326
+ const searchFormVisible = computed(() => {
332
327
  const searchFields = props.table?.searchForm?.fields;
333
328
  const fieldList =
334
329
  (Array.isArray(searchFields) ? searchFields : searchFields?.value) ?? [];
335
330
  return !!(
336
- searchFormSlot ||
337
- ((computedSearchFormConfig.value as any)?.hidden !== true &&
338
- props.table?.searchForm &&
339
- fieldList.filter((item: any) => !item.hidden)?.length > 0)
331
+ (computedSearchFormConfig.value as any)?.hidden !== true &&
332
+ props.table?.searchForm &&
333
+ fieldList.filter((item: any) => !toValue(item.hidden))?.length > 0
340
334
  );
341
335
  });
342
336
 
@@ -372,12 +366,17 @@ onMounted(() => {
372
366
  _search();
373
367
  }
374
368
  });
369
+
370
+ defineExpose({
371
+ /** 手动触发一次查询(与内部搜索共用 loading 竞态守卫),便于外部刷新列表 */
372
+ search: _search,
373
+ });
375
374
  </script>
376
375
 
377
376
  <template>
378
377
  <div class="pro-table" :class="attrs.class" :style="attrs.style">
379
378
  <ContainerFragment
380
- v-if="showSearch"
379
+ v-if="searchFormVisible || $slots['search-form']"
381
380
  :component="computedSearchFormConfig.container"
382
381
  >
383
382
  <slot name="search-form">
@@ -393,10 +392,10 @@ onMounted(() => {
393
392
 
394
393
  <ContainerFragment :component="computedTableContainer">
395
394
  <div class="pro-table_header">
396
- <div v-if="buttonBarSlot" class="pro-table_header_button-bar">
395
+ <div v-if="$slots['button-bar']" class="pro-table_header_button-bar">
397
396
  <slot name="button-bar" />
398
397
  </div>
399
- <div v-if="toolbarSlot" class="pro-table_header_toolbar">
398
+ <div v-if="$slots['toolbar']" class="pro-table_header_toolbar">
400
399
  <slot name="toolbar" />
401
400
  </div>
402
401
  <div
@@ -428,7 +427,11 @@ onMounted(() => {
428
427
  :data="computedDataSource"
429
428
  >
430
429
  <TableColumnRenderer :columns="normalizedRawColumns" />
431
- <template v-for="name in tableSlotNames" :key="name" #[name]="scoped">
430
+ <template
431
+ v-for="name in forwardedSlotNames()"
432
+ :key="name"
433
+ #[name]="scoped"
434
+ >
432
435
  <slot :name="name" v-bind="scoped" />
433
436
  </template>
434
437
  </ElTable>
@@ -60,10 +60,6 @@ import { Space, Button, FormProps } from '../../../shared/ui';
60
60
  import {
61
61
  AllowedComponentProps,
62
62
  computed,
63
- ref,
64
- watch,
65
- watchEffect,
66
- onBeforeUnmount,
67
63
  type Component,
68
64
  type DefineComponent,
69
65
  } from 'vue';
@@ -71,6 +67,7 @@ import ProForm from '../../form';
71
67
  import { type Form } from '../../form';
72
68
  import DownOutlined from './icons/DownOutlined.vue';
73
69
  import { useLocale } from '../../../locale';
70
+ import { useSearchFormExpand } from '../../../shared/core';
74
71
 
75
72
  defineOptions({ name: 'SearchForm' });
76
73
 
@@ -93,185 +90,10 @@ type Emits = {
93
90
  };
94
91
  const emit = defineEmits<Emits>();
95
92
 
96
- const proFormHeight = ref<'unset' | number>('unset');
97
- const collapseHeight = ref(0);
98
- const rowHeight = ref(32);
99
-
100
- let resizeObserver: ResizeObserver | null = null;
101
- let intersectionObserver: IntersectionObserver | null = null;
102
-
103
- const cleanupObservers = () => {
104
- if (resizeObserver) {
105
- resizeObserver.disconnect();
106
- resizeObserver = null;
107
- }
108
- if (intersectionObserver) {
109
- intersectionObserver.disconnect();
110
- intersectionObserver = null;
111
- }
112
- };
113
-
114
- onBeforeUnmount(() => {
115
- cleanupObservers();
116
- });
117
-
118
- const computedExpand = computed(() => {
119
- if (!props.expand) return false;
120
- if (props.expand === true) return { minExpandRows: 2, expandStatus: false };
121
- return {
122
- minExpandRows: Math.max(Math.floor(props.expand.minExpandRows ?? 2), 1),
123
- expandStatus: props.expand.expandStatus ?? false,
124
- };
125
- });
126
-
127
- const showExpandToggle = ref(false);
128
-
129
- const expandStatus = ref(true);
130
-
131
- const updateHeights = () => {
132
- if (!props.expand || !props.form?.formRef?.value) return;
133
- const proFormEl = (props.form.formRef.value as any)?.$el;
134
- if (!proFormEl) return;
135
-
136
- const itemHeight = proFormEl
137
- ?.querySelector("[class*='-form-item']")
138
- ?.getBoundingClientRect?.()?.height;
139
- if (itemHeight && itemHeight > 0) {
140
- rowHeight.value = itemHeight;
141
- }
142
-
143
- // 使用 scrollHeight 测量自然展开高度,突破固定高度与 overflow: hidden 样式的自引用限制
144
- const naturalHeight =
145
- proFormEl.scrollHeight || proFormEl.getBoundingClientRect?.()?.height || 0;
146
- if (naturalHeight > 0) {
147
- proFormHeight.value = naturalHeight;
148
- }
149
- };
150
-
151
- // 手动展开/收起标记:置位后自动测量不再覆盖用户操作
152
- const isManualToggle = ref(false);
153
-
154
- const changeExpandStatus = () => {
155
- isManualToggle.value = true;
156
- expandStatus.value = !expandStatus.value;
157
- };
158
-
159
- const hasFieldValue = (val: any) =>
160
- val !== null &&
161
- val !== undefined &&
162
- val !== '' &&
163
- !(Array.isArray(val) && val.length === 0) &&
164
- !(typeof val === 'object' && Object.keys(val).length === 0);
165
-
166
- /*
167
- * 使用 IntersectionObserver 检测折叠状态下隐藏字段是否有值
168
- * 如果有值则自动展开,让用户看到已填写的搜索条件
169
- */
170
- const setInitExpandStatus = () => {
171
- if (isManualToggle.value) return;
172
- const initStatus = computedExpand.value
173
- ? computedExpand.value.expandStatus
174
- : false;
175
- expandStatus.value = initStatus;
176
- if (props.form?.formRef?.value && computedExpand.value) {
177
- const formEl = (props.form.formRef.value as any).$el;
178
- const formItemsEl = formEl?.querySelectorAll?.(
179
- "[class*='-form-item'] [path]"
180
- );
181
- if (!formItemsEl || formItemsEl.length === 0) return;
182
- if (intersectionObserver) {
183
- intersectionObserver.disconnect();
184
- }
185
- intersectionObserver = new IntersectionObserver(
186
- entries => {
187
- if (isManualToggle.value) return;
188
- expandStatus.value = entries.some(e => {
189
- // 如果隐藏字段(intersectionRatio === 0)有值,则自动展开
190
- if (e.intersectionRatio === 0) {
191
- const path = e.target.getAttribute('path');
192
- const searchFieldValue = path
193
- ? props.form?.getFormData?.(path)
194
- : undefined;
195
- return hasFieldValue(searchFieldValue);
196
- }
197
- return computedExpand.value
198
- ? computedExpand.value.expandStatus
199
- : false;
200
- });
201
- intersectionObserver?.disconnect();
202
- },
203
- { root: formEl }
204
- );
205
- formItemsEl.forEach((element: Element) => {
206
- intersectionObserver?.observe(element);
207
- });
208
- }
209
- };
210
-
211
- watch(
212
- [
213
- () => props.form?.fields.value?.filter((f: any) => !f.hidden)?.length,
214
- () => props.form?.formRef?.value,
215
- () => props.expand,
216
- ],
217
- () => {
218
- updateHeights();
219
- },
220
- { flush: 'post', immediate: true }
221
- );
222
-
223
- watch(
224
- () => props.form?.formRef?.value,
225
- val => {
226
- if (resizeObserver) {
227
- resizeObserver.disconnect();
228
- resizeObserver = null;
229
- }
230
- const formEl = (val as any)?.$el;
231
- if (formEl && typeof ResizeObserver !== 'undefined') {
232
- resizeObserver = new ResizeObserver(() => {
233
- updateHeights();
234
- });
235
- resizeObserver.observe(formEl);
236
- }
237
- },
238
- { immediate: true }
239
- );
240
-
241
- /*
242
- * 计算折叠高度和是否显示展开按钮
243
- * 折叠高度 = minExpandRows * 行高 + (minExpandRows - 1) * 行间距
244
- * 如果实际高度与折叠高度差异 > 1px,说明有字段溢出,显示展开按钮
245
- */
246
- watchEffect(
247
- () => {
248
- if (typeof proFormHeight.value !== 'number') return;
249
- if (props.layout === 'grid' && computedExpand.value) {
250
- const { minExpandRows } = computedExpand.value;
251
- collapseHeight.value = Math.min(
252
- minExpandRows * rowHeight.value + (minExpandRows - 1) * props.rowGap,
253
- +proFormHeight.value
254
- );
255
- showExpandToggle.value = +proFormHeight.value - collapseHeight.value > 1;
256
- if (showExpandToggle.value) {
257
- setInitExpandStatus();
258
- }
259
- } else {
260
- showExpandToggle.value = false;
261
- }
262
- },
263
- { flush: 'post' }
264
- );
93
+ const { showExpandToggle, expandStatus, changeExpandStatus, heightStyle } =
94
+ useSearchFormExpand(props);
265
95
 
266
96
  const layoutProps = computed(() => {
267
- const currentHeight = expandStatus.value
268
- ? proFormHeight.value
269
- : collapseHeight.value;
270
- const heightStyle =
271
- currentHeight !== 'unset' && currentHeight !== undefined
272
- ? `${currentHeight}px`
273
- : undefined;
274
-
275
97
  return props.layout === 'grid'
276
98
  ? {
277
99
  grid: {
@@ -282,7 +104,7 @@ const layoutProps = computed(() => {
282
104
  display: 'flex',
283
105
  overflow: 'hidden',
284
106
  columnGap: `${props.columnGap}px`,
285
- height: heightStyle,
107
+ height: heightStyle.value,
286
108
  },
287
109
  }
288
110
  : {
@@ -5,6 +5,7 @@
5
5
  >
6
6
  import { get, normalizePathKey } from '../../../shared/core';
7
7
  import { TableColumn } from '../../../shared/ui';
8
+ import { SlotComponent } from '../../form';
8
9
  import type { Column, Columns } from '../types';
9
10
  import { computed } from 'vue';
10
11
 
@@ -66,8 +67,9 @@ const getColumnProps = (column: Column<D>) => {
66
67
  v-bind="getColumnProps(column)"
67
68
  >
68
69
  <template #default="scope">
69
- <component
70
- :is="
70
+ <!-- 经 SlotComponent 渲染:支持组件、VNode 与文本等多种形态 -->
71
+ <SlotComponent
72
+ :component="
71
73
  column.render?.({
72
74
  ...scope,
73
75
  column,
@@ -80,6 +80,7 @@ export type Columns<D extends Data = Data> = Array<Column<D>>;
80
80
  * @example
81
81
  * ```ts
82
82
  * const config: ColumnControlConfig<'name' | 'age'> = {
83
+ * labels: { name: '姓名', age: '年龄' },
83
84
  * excludedKeys: [],
84
85
  * disabledKeys: ['name'],
85
86
  * disableCheckAll: false,
@@ -87,6 +88,8 @@ export type Columns<D extends Data = Data> = Array<Column<D>>;
87
88
  * ```
88
89
  */
89
90
  export type ColumnControlConfig<K extends string | number = string | number> = {
91
+ /** 列在下拉中的自定义显示名称映射(key 为列标识),用于定制展示文案或覆盖非字符串标题 */
92
+ labels?: Partial<Record<K, string>>;
90
93
  /** 不在下拉中出现的列 keys(始终展示,不参与勾选) */
91
94
  excludedKeys?: K[];
92
95
  /** 在下拉中显示但不可取消勾选的列 keys(始终展示,始终勾选) */
@@ -4,7 +4,7 @@
4
4
  * @description Extends the core language pack with element-plus specific
5
5
  * date/time picker placeholder texts.
6
6
  */
7
- import { enUS } from '@qin-ui/pro-components-core';
7
+ import { enUS } from '../shared/core';
8
8
 
9
9
  export default {
10
10
  ...enUS,
@@ -13,11 +13,7 @@ import {
13
13
  type MaybeRef,
14
14
  } from 'vue';
15
15
  import { useLocale as useElLocale, localeContextKey } from 'element-plus';
16
- import {
17
- format,
18
- resolveLocaleName,
19
- type ProLocaleName,
20
- } from '@qin-ui/pro-components-core';
16
+ import { format, resolveLocaleName, type ProLocaleName } from '../shared/core';
21
17
  import zhCN from './zh-CN';
22
18
  import zhTW from './zh-TW';
23
19
  import enUS from './en-US';
@@ -4,7 +4,7 @@
4
4
  * @description 在 core 通用语言包基础上,扩展 element-plus 特有的
5
5
  * 日期/时间组件占位符文案。
6
6
  */
7
- import { zhCN } from '@qin-ui/pro-components-core';
7
+ import { zhCN } from '../shared/core';
8
8
 
9
9
  export default {
10
10
  ...zhCN,
@@ -4,7 +4,7 @@
4
4
  * @description 在 core 通用語言包基礎上,擴展 element-plus 特有的
5
5
  * 日期/時間元件佔位符文案。
6
6
  */
7
- import { zhTW } from '@qin-ui/pro-components-core';
7
+ import { zhTW } from '../shared/core';
8
8
 
9
9
  export default {
10
10
  ...zhTW,
@@ -5,7 +5,9 @@
5
5
  export {
6
6
  // hooks
7
7
  useForm,
8
+ createFormProxy,
8
9
  useTable,
10
+ useSearchFormExpand,
9
11
  useFields,
10
12
  useFormData,
11
13
  useFormRef,
@@ -23,6 +25,15 @@ export {
23
25
  pick,
24
26
  isPlainObject,
25
27
  isPromise,
28
+ // locale
29
+ zhCN,
30
+ zhTW,
31
+ enUS,
32
+ format,
33
+ resolveLocaleName,
34
+ type ProLocale,
35
+ type ProLocaleName,
36
+ type ProLocaleKey,
26
37
  // types
27
38
  type Data,
28
39
  type Path,
@@ -79,10 +79,15 @@ export {
79
79
 
80
80
  export type TableProps = _TableProps<any>;
81
81
 
82
- export const tableProps = () => (Table as any).props || {};
83
- export const paginationProps = () => (Pagination as any).props || {};
84
- export const gridItemProps = () => (GridItem as any).props || {};
85
- export const formItemProps = () => (FormItem as any).props || {};
82
+ const _tableProps = (Table as any).props || {};
83
+ const _paginationProps = (Pagination as any).props || {};
84
+ const _gridItemProps = (GridItem as any).props || {};
85
+ const _formItemProps = (FormItem as any).props || {};
86
+
87
+ export const tableProps = () => _tableProps;
88
+ export const paginationProps = () => _paginationProps;
89
+ export const gridItemProps = () => _gridItemProps;
90
+ export const formItemProps = () => _formItemProps;
86
91
 
87
92
  const DisabledContextKey = Symbol.for('pro-form-disabled-context');
88
93