@qin-ui/element-plus-pro 1.2.4 → 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 (179) hide show
  1. package/AI-CONTEXT.md +1 -1
  2. package/README.md +5 -5
  3. package/api.json +1 -25
  4. package/es/__tests__/BaseField.spec.d.ts +2 -0
  5. package/es/__tests__/BaseField.spec.d.ts.map +1 -0
  6. package/es/__tests__/BaseFormItem.spec.d.ts +2 -0
  7. package/es/__tests__/BaseFormItem.spec.d.ts.map +1 -0
  8. package/es/__tests__/ProForm.spec.d.ts +2 -0
  9. package/es/__tests__/ProForm.spec.d.ts.map +1 -0
  10. package/es/__tests__/ProTable.spec.d.ts +2 -0
  11. package/es/__tests__/ProTable.spec.d.ts.map +1 -0
  12. package/es/__tests__/SearchForm.spec.d.ts +2 -0
  13. package/es/__tests__/SearchForm.spec.d.ts.map +1 -0
  14. package/es/__tests__/controls.spec.d.ts +2 -0
  15. package/es/__tests__/controls.spec.d.ts.map +1 -0
  16. package/es/__tests__/cross-package.spec.d.ts +2 -0
  17. package/es/__tests__/cross-package.spec.d.ts.map +1 -0
  18. package/es/__tests__/helpers/fixtures.d.ts +15 -0
  19. package/es/__tests__/helpers/fixtures.d.ts.map +1 -0
  20. package/es/__tests__/helpers/mount.d.ts +38 -0
  21. package/es/__tests__/helpers/mount.d.ts.map +1 -0
  22. package/es/__tests__/hooks-locale.spec.d.ts +2 -0
  23. package/es/__tests__/hooks-locale.spec.d.ts.map +1 -0
  24. package/es/__tests__/index.spec.d.ts +2 -0
  25. package/es/__tests__/index.spec.d.ts.map +1 -0
  26. package/es/__tests__/integration.spec.d.ts +2 -0
  27. package/es/__tests__/integration.spec.d.ts.map +1 -0
  28. package/es/__tests__/setup.d.ts +14 -0
  29. package/es/__tests__/setup.d.ts.map +1 -0
  30. package/es/element-plus-pro.css +14 -14
  31. package/es/index.js +425 -434
  32. package/es/index.js.map +1 -1
  33. package/es/{components → src/components}/component-provider/components/index.vue.d.ts +5 -1
  34. package/es/src/components/component-provider/components/index.vue.d.ts.map +1 -0
  35. package/es/{components → src/components}/component-provider/constants/index.d.ts +2 -2
  36. package/es/src/components/component-provider/constants/index.d.ts.map +1 -0
  37. package/es/src/components/component-provider/index.d.ts.map +1 -0
  38. package/es/{components → src/components}/component-provider/types/index.d.ts +9 -5
  39. package/es/src/components/component-provider/types/index.d.ts.map +1 -0
  40. package/es/{components → src/components}/form/components/BaseField/index.vue.d.ts +4 -1
  41. package/es/src/components/form/components/BaseField/index.vue.d.ts.map +1 -0
  42. package/es/src/components/form/components/BaseField/utils/index.d.ts +6 -0
  43. package/es/src/components/form/components/BaseField/utils/index.d.ts.map +1 -0
  44. package/es/{components → src/components}/form/components/BaseFormItem/index.vue.d.ts +4 -2
  45. package/es/src/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -0
  46. package/es/src/components/form/components/ContainerFragment/index.vue.d.ts.map +1 -0
  47. package/es/src/components/form/components/ProForm/index.vue.d.ts.map +1 -0
  48. package/es/src/components/form/components/SlotComponent/index.vue.d.ts.map +1 -0
  49. package/es/src/components/form/components/index.d.ts.map +1 -0
  50. package/es/src/components/form/constants/index.d.ts.map +1 -0
  51. package/es/src/components/form/hooks/index.d.ts.map +1 -0
  52. package/es/src/components/form/hooks/useFields.d.ts.map +1 -0
  53. package/es/src/components/form/hooks/useForm.d.ts.map +1 -0
  54. package/es/src/components/form/hooks/useFormRef.d.ts.map +1 -0
  55. package/es/src/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -0
  56. package/es/src/components/form/index.d.ts.map +1 -0
  57. package/es/{components → src/components}/form/types/index.d.ts +26 -2
  58. package/es/src/components/form/types/index.d.ts.map +1 -0
  59. package/es/{components → src/components}/table/components/ColumnControl.vue.d.ts +4 -1
  60. package/es/src/components/table/components/ColumnControl.vue.d.ts.map +1 -0
  61. package/es/src/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +1 -0
  62. package/es/src/components/table/components/DefaultTableContainer.vue.d.ts.map +1 -0
  63. package/es/{components → src/components}/table/components/ProTable.vue.d.ts +4 -1
  64. package/es/src/components/table/components/ProTable.vue.d.ts.map +1 -0
  65. package/es/{components → src/components}/table/components/SearchForm.vue.d.ts +14 -4
  66. package/es/src/components/table/components/SearchForm.vue.d.ts.map +1 -0
  67. package/es/src/components/table/components/SizeControl.vue.d.ts.map +1 -0
  68. package/es/src/components/table/components/TableColumnRenderer.vue.d.ts.map +1 -0
  69. package/es/src/components/table/components/icons/ColumnHeightOutlined.vue.d.ts.map +1 -0
  70. package/es/src/components/table/components/icons/DownOutlined.vue.d.ts.map +1 -0
  71. package/es/src/components/table/components/icons/FilterOutlined.vue.d.ts.map +1 -0
  72. package/es/src/components/table/components/index.d.ts.map +1 -0
  73. package/es/src/components/table/index.d.ts.map +1 -0
  74. package/es/{components → src/components}/table/types/index.d.ts +3 -0
  75. package/es/src/components/table/types/index.d.ts.map +1 -0
  76. package/es/src/components/table/useTable.d.ts.map +1 -0
  77. package/es/{index.d.ts → src/index.d.ts} +1 -1
  78. package/es/src/index.d.ts.map +1 -0
  79. package/es/src/locale/en-US.d.ts.map +1 -0
  80. package/es/{locale → src/locale}/index.d.ts +1 -1
  81. package/es/src/locale/index.d.ts.map +1 -0
  82. package/es/src/locale/zh-CN.d.ts.map +1 -0
  83. package/es/src/locale/zh-TW.d.ts.map +1 -0
  84. package/es/src/shared/core/index.d.ts +6 -0
  85. package/es/src/shared/core/index.d.ts.map +1 -0
  86. package/es/{shared → src/shared}/ui/index.d.ts +2 -0
  87. package/es/src/shared/ui/index.d.ts.map +1 -0
  88. package/package.json +8 -3
  89. package/src/components/component-provider/components/index.vue +41 -11
  90. package/src/components/component-provider/constants/index.ts +6 -5
  91. package/src/components/component-provider/types/index.ts +9 -5
  92. package/src/components/form/components/BaseField/index.vue +67 -38
  93. package/src/components/form/components/BaseField/utils/index.ts +17 -11
  94. package/src/components/form/components/BaseFormItem/index.vue +48 -41
  95. package/src/components/form/components/ProForm/index.vue +64 -20
  96. package/src/components/form/components/SlotComponent/index.vue +9 -6
  97. package/src/components/form/hooks/useGroupFieldAttrs.ts +13 -6
  98. package/src/components/form/types/index.ts +26 -2
  99. package/src/components/table/components/ColumnControl.vue +31 -38
  100. package/src/components/table/components/ProTable.vue +142 -84
  101. package/src/components/table/components/SearchForm.vue +49 -202
  102. package/src/components/table/components/SizeControl.vue +2 -2
  103. package/src/components/table/components/TableColumnRenderer.vue +16 -11
  104. package/src/components/table/types/index.ts +3 -0
  105. package/src/components/table/useTable.ts +12 -7
  106. package/src/index.ts +1 -1
  107. package/src/locale/en-US.ts +1 -1
  108. package/src/locale/index.ts +7 -7
  109. package/src/locale/zh-CN.ts +1 -1
  110. package/src/locale/zh-TW.ts +1 -1
  111. package/src/shared/core/index.ts +15 -2
  112. package/src/shared/ui/index.ts +20 -9
  113. package/es/components/component-provider/components/index.vue.d.ts.map +0 -1
  114. package/es/components/component-provider/constants/index.d.ts.map +0 -1
  115. package/es/components/component-provider/index.d.ts.map +0 -1
  116. package/es/components/component-provider/types/index.d.ts.map +0 -1
  117. package/es/components/form/components/BaseField/index.vue.d.ts.map +0 -1
  118. package/es/components/form/components/BaseField/utils/index.d.ts +0 -5
  119. package/es/components/form/components/BaseField/utils/index.d.ts.map +0 -1
  120. package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +0 -1
  121. package/es/components/form/components/ContainerFragment/index.vue.d.ts.map +0 -1
  122. package/es/components/form/components/ProForm/index.vue.d.ts.map +0 -1
  123. package/es/components/form/components/SlotComponent/index.vue.d.ts.map +0 -1
  124. package/es/components/form/components/index.d.ts.map +0 -1
  125. package/es/components/form/constants/index.d.ts.map +0 -1
  126. package/es/components/form/hooks/index.d.ts.map +0 -1
  127. package/es/components/form/hooks/useFields.d.ts.map +0 -1
  128. package/es/components/form/hooks/useForm.d.ts.map +0 -1
  129. package/es/components/form/hooks/useFormRef.d.ts.map +0 -1
  130. package/es/components/form/hooks/useGroupFieldAttrs.d.ts.map +0 -1
  131. package/es/components/form/index.d.ts.map +0 -1
  132. package/es/components/form/types/index.d.ts.map +0 -1
  133. package/es/components/table/components/ColumnControl.vue.d.ts.map +0 -1
  134. package/es/components/table/components/DefaultSearchFormContainer.vue.d.ts.map +0 -1
  135. package/es/components/table/components/DefaultTableContainer.vue.d.ts.map +0 -1
  136. package/es/components/table/components/ProTable.vue.d.ts.map +0 -1
  137. package/es/components/table/components/SearchForm.vue.d.ts.map +0 -1
  138. package/es/components/table/components/SizeControl.vue.d.ts.map +0 -1
  139. package/es/components/table/components/TableColumnRenderer.vue.d.ts.map +0 -1
  140. package/es/components/table/components/icons/ColumnHeightOutlined.vue.d.ts.map +0 -1
  141. package/es/components/table/components/icons/DownOutlined.vue.d.ts.map +0 -1
  142. package/es/components/table/components/icons/FilterOutlined.vue.d.ts.map +0 -1
  143. package/es/components/table/components/index.d.ts.map +0 -1
  144. package/es/components/table/index.d.ts.map +0 -1
  145. package/es/components/table/types/index.d.ts.map +0 -1
  146. package/es/components/table/useTable.d.ts.map +0 -1
  147. package/es/index.d.ts.map +0 -1
  148. package/es/locale/en-US.d.ts.map +0 -1
  149. package/es/locale/index.d.ts.map +0 -1
  150. package/es/locale/zh-CN.d.ts.map +0 -1
  151. package/es/locale/zh-TW.d.ts.map +0 -1
  152. package/es/shared/core/index.d.ts +0 -6
  153. package/es/shared/core/index.d.ts.map +0 -1
  154. package/es/shared/ui/index.d.ts.map +0 -1
  155. /package/es/{components → src/components}/component-provider/index.d.ts +0 -0
  156. /package/es/{components → src/components}/form/components/ContainerFragment/index.vue.d.ts +0 -0
  157. /package/es/{components → src/components}/form/components/ProForm/index.vue.d.ts +0 -0
  158. /package/es/{components → src/components}/form/components/SlotComponent/index.vue.d.ts +0 -0
  159. /package/es/{components → src/components}/form/components/index.d.ts +0 -0
  160. /package/es/{components → src/components}/form/constants/index.d.ts +0 -0
  161. /package/es/{components → src/components}/form/hooks/index.d.ts +0 -0
  162. /package/es/{components → src/components}/form/hooks/useFields.d.ts +0 -0
  163. /package/es/{components → src/components}/form/hooks/useForm.d.ts +0 -0
  164. /package/es/{components → src/components}/form/hooks/useFormRef.d.ts +0 -0
  165. /package/es/{components → src/components}/form/hooks/useGroupFieldAttrs.d.ts +0 -0
  166. /package/es/{components → src/components}/form/index.d.ts +0 -0
  167. /package/es/{components → src/components}/table/components/DefaultSearchFormContainer.vue.d.ts +0 -0
  168. /package/es/{components → src/components}/table/components/DefaultTableContainer.vue.d.ts +0 -0
  169. /package/es/{components → src/components}/table/components/SizeControl.vue.d.ts +0 -0
  170. /package/es/{components → src/components}/table/components/TableColumnRenderer.vue.d.ts +0 -0
  171. /package/es/{components → src/components}/table/components/icons/ColumnHeightOutlined.vue.d.ts +0 -0
  172. /package/es/{components → src/components}/table/components/icons/DownOutlined.vue.d.ts +0 -0
  173. /package/es/{components → src/components}/table/components/icons/FilterOutlined.vue.d.ts +0 -0
  174. /package/es/{components → src/components}/table/components/index.d.ts +0 -0
  175. /package/es/{components → src/components}/table/index.d.ts +0 -0
  176. /package/es/{components → src/components}/table/useTable.d.ts +0 -0
  177. /package/es/{locale → src/locale}/en-US.d.ts +0 -0
  178. /package/es/{locale → src/locale}/zh-CN.d.ts +0 -0
  179. /package/es/{locale → src/locale}/zh-TW.d.ts +0 -0
@@ -10,8 +10,13 @@ import { INJECT_CONFIG } from '../../component-provider/constants';
10
10
  import { FORM_ITEM_SLOT_KEYS } from '../constants';
11
11
  import type { Base, Field } from '../types';
12
12
 
13
- const formItemPropKeys = Object.keys(formItemProps());
13
+ const formItemPropKeys = Object.keys(formItemProps()).filter(
14
+ k => k !== 'disabled'
15
+ );
14
16
  const gridItemPropKeys = Object.keys(gridItemProps());
17
+ // 模块级 Set:字段循环内 O(1) 命中,替代数组 includes 线性扫描
18
+ const gridItemPropKeySet = new Set<string>(gridItemPropKeys);
19
+ const formItemPropKeySet = new Set<string>(formItemPropKeys);
15
20
 
16
21
  export interface GroupedField {
17
22
  path: string;
@@ -31,6 +36,7 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
31
36
  const injectFormItemProps = inject(config.injectionKey, config.default);
32
37
 
33
38
  return computed<GroupedField[]>(() => {
39
+ const rawInjectProps = toValue(injectFormItemProps) || {};
34
40
  return validFields.value.map(field => {
35
41
  let gridItemProps: Record<string, any> = {};
36
42
  let formItemProps: Record<string, any> = {};
@@ -54,8 +60,9 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
54
60
  lg,
55
61
  xl,
56
62
  ...injectRest
57
- } = injectFormItemProps;
63
+ } = rawInjectProps;
58
64
 
65
+ // 字段显式 span 优先,否则回退注入配置的响应式断点
59
66
  const initGridItemProps =
60
67
  span || span === 0
61
68
  ? { span }
@@ -71,9 +78,9 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
71
78
  const container = formItemContainer ?? injectFormItemContainer;
72
79
 
73
80
  Object.keys(rest).forEach(k => {
74
- if (gridItemPropKeys.includes(k)) {
81
+ if (gridItemPropKeySet.has(k)) {
75
82
  gridItemProps[k] = rest[k];
76
- } else if (formItemPropKeys.includes(k)) {
83
+ } else if (formItemPropKeySet.has(k)) {
77
84
  (formItemProps as any)[k] = rest[k];
78
85
  } else {
79
86
  componentProps[k] = rest[k];
@@ -89,7 +96,7 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
89
96
  }
90
97
  });
91
98
 
92
- const raw = (toValue(path) ?? toValue(name)) as
99
+ const raw = (toValue(name) ?? toValue(path)) as
93
100
  | string
94
101
  | number[]
95
102
  | undefined;
@@ -105,7 +112,7 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
105
112
  }
106
113
 
107
114
  const raw =
108
- toValue((field as any)?.path) ?? toValue((field as any)?.name);
115
+ toValue((field as any)?.name) ?? toValue((field as any)?.path);
109
116
  return {
110
117
  path: Array.isArray(raw) ? raw.join('.') : (raw as string) || '',
111
118
  gridItemProps: gridItemProps as GridItemProps,
@@ -30,9 +30,33 @@ export type VModelProps<T = any> = {
30
30
  'onUpdate:modelValue'?: (val: T) => void;
31
31
  };
32
32
 
33
+ /**
34
+ * 字段值格式化/转换处理器
35
+ *
36
+ * @description 支持函数形态或包含 get/set 方法的对象形态。
37
+ *
38
+ * ### 行为与契约说明
39
+ * 1. **旧值参数按需注入**:底层基于函数的形参声明个数(`fn.length > 1`)决定是否读取旧值。若转换逻辑需要使用旧值,请显式声明两个形参 `(val, oldVal) => ...`(请勿使用默认参数或 `...args` 剩余参数,否则 `fn.length` 无法正确识别);
40
+ * 2. **只读快照副本**:传入的 `oldVal` 为只读快照副本,不得依赖原地修改写回;仅当 formatter 声明双形参时才克隆旧值,单参转换零开销。
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // 1. 单参数转换(常见):无需旧值,零多余开销
45
+ * valueFormatter: (val) => val?.trim()
46
+ *
47
+ * // 2. 双参数转换:需要基于旧值做对比或回退
48
+ * valueFormatter: (val, oldVal) => (isValid(val) ? val : oldVal)
49
+ *
50
+ * // 3. 对象形态:分别定义读(展示)与写(存储)转换
51
+ * valueFormatter: {
52
+ * get: (val) => (val ? dayjs(val).format('YYYY-MM-DD') : ''),
53
+ * set: (val, oldVal) => (val ? dayjs(val).valueOf() : undefined),
54
+ * }
55
+ * ```
56
+ */
33
57
  export type ValueFormatter =
34
- | { (val: any, oldVal: any): any }
35
- | { get?: (val: any) => any; set?: (val: any, oldVal: any) => any };
58
+ | { (val: any, oldVal?: any): any }
59
+ | { get?: (val: any) => any; set?: (val: any, oldVal?: any) => any };
36
60
 
37
61
  /**
38
62
  * @description 自定义组件
@@ -6,33 +6,32 @@ import {
6
6
  DropdownItem,
7
7
  Checkbox,
8
8
  } from '../../../shared/ui';
9
- import { computed, onMounted, ref, watch } from 'vue';
9
+ import { computed, ref, watch } from 'vue';
10
10
  import type { Column, Columns, ColumnControlConfig } from '../types';
11
11
  import { type Table } from '../useTable';
12
12
  import FilterOutlined from './icons/FilterOutlined.vue';
13
13
  import { useLocale } from '../../../locale';
14
+ import { normalizePathKey } from '../../../shared/core';
14
15
 
15
16
  type Props = {
16
17
  columns: Columns;
17
18
  table?: Table;
18
19
  columnControl?: ColumnControlConfig;
19
20
  };
20
- const {
21
- columns,
22
- table = undefined,
23
- columnControl = undefined,
24
- } = defineProps<Props>();
21
+ const props = withDefaults(defineProps<Props>(), {
22
+ table: undefined,
23
+ columnControl: undefined,
24
+ });
25
25
 
26
26
  const { t } = useLocale();
27
27
 
28
- const open = ref(false);
29
28
  const checkAll = ref(false);
30
29
  const indeterminate = ref(false);
31
30
 
32
31
  const getColumnKey = (column: Column, index: number) => {
33
- const key = column.key ?? column.prop;
34
- if (key === undefined || key === null) return `__col_${index}`;
35
- return Array.isArray(key) ? key.join('.') : String(key);
32
+ const rawKey = column.key ?? column.prop;
33
+ if (rawKey === undefined || rawKey === null) return `__col_${index}`;
34
+ return normalizePathKey(rawKey);
36
35
  };
37
36
 
38
37
  const checkedColumnsOptions = computed<
@@ -44,21 +43,27 @@ const checkedColumnsOptions = computed<
44
43
  column: Column;
45
44
  }>
46
45
  >(() =>
47
- columns
46
+ props.columns
48
47
  .map((item, index) => {
49
48
  const key = getColumnKey(item, index);
50
- const excluded = columnControl?.excludedKeys?.some(
49
+ const excluded = props.columnControl?.excludedKeys?.some(
51
50
  ek => String(ek) === key
52
51
  );
53
- const disabled = columnControl?.disabledKeys?.some(
52
+ const disabled = props.columnControl?.disabledKeys?.some(
54
53
  dk => String(dk) === key
55
54
  );
56
- const label = String(
57
- item.label ??
58
- (item as any).title ??
59
- item.prop ??
60
- t('table.columnFallback', { index: index + 1 })
61
- );
55
+ const rawTitle = item.label ?? (item as any).title;
56
+ const titleLabel = typeof rawTitle === 'string' ? rawTitle : undefined;
57
+ const propLabel =
58
+ item.prop !== undefined && item.prop !== null
59
+ ? String(item.prop)
60
+ : undefined;
61
+ const label =
62
+ (props.columnControl?.labels?.[key] as string) ??
63
+ titleLabel ??
64
+ propLabel ??
65
+ key ??
66
+ t('table.columnFallback', { index: index + 1 });
62
67
  return {
63
68
  label,
64
69
  value: key,
@@ -85,7 +90,7 @@ const checkedColumnKeys = computed({
85
90
  set(val: string[]) {
86
91
  checkedColumnsOptions.value.forEach(item => {
87
92
  if (item.disabled) return;
88
- table?.setColumn(item.value as any, {
93
+ props.table?.setColumn(item.value as any, {
89
94
  hidden: !val.includes(item.value),
90
95
  });
91
96
  });
@@ -97,7 +102,6 @@ const onCheckAllChange = (value: any) => {
97
102
  checkedColumnKeys.value = checked
98
103
  ? controllableOptions.value.map(item => item.value)
99
104
  : [];
100
- indeterminate.value = false;
101
105
  };
102
106
 
103
107
  const toggleColumnsItem = (val: string) => {
@@ -128,38 +132,27 @@ watch(
128
132
  { immediate: true }
129
133
  );
130
134
 
131
- // 强制 disabledKeys 列始终展示
132
- onMounted(() => {
133
- columnControl?.disabledKeys?.forEach(key => {
134
- table?.setColumn(String(key), { hidden: false });
135
- });
136
- });
137
-
135
+ // disabledKeys 指定的列始终强制展示
138
136
  watch(
139
- () => columnControl?.disabledKeys,
137
+ () => props.columnControl?.disabledKeys,
140
138
  keys => {
141
139
  keys?.forEach(key => {
142
- table?.setColumn(String(key), { hidden: false });
140
+ props.table?.setColumn(String(key), { hidden: false });
143
141
  });
144
142
  },
145
- { deep: true }
143
+ { immediate: true, deep: true }
146
144
  );
147
145
  </script>
148
146
 
149
147
  <template>
150
- <Dropdown
151
- trigger="click"
152
- :hide-on-click="false"
153
- :visible="open"
154
- @visible-change="val => (open = val)"
155
- >
148
+ <Dropdown trigger="click" :hide-on-click="false">
156
149
  <Button text class="pro-table_column-control_button">
157
150
  <FilterOutlined />
158
151
  </Button>
159
152
  <template #dropdown>
160
153
  <DropdownMenu>
161
154
  <DropdownItem
162
- v-if="!columnControl?.disableCheckAll"
155
+ v-if="!props.columnControl?.disableCheckAll"
163
156
  :disabled="true"
164
157
  :divided="false"
165
158
  >
@@ -40,12 +40,14 @@ import {
40
40
  tableProps,
41
41
  paginationProps,
42
42
  } from '../../../shared/ui';
43
+ import { ElLoading } from 'element-plus';
43
44
  import {
44
45
  computed,
45
46
  nextTick,
46
47
  useAttrs,
47
48
  onMounted,
48
49
  inject,
50
+ toValue,
49
51
  type Slot,
50
52
  useSlots,
51
53
  } from 'vue';
@@ -60,6 +62,8 @@ import {
60
62
  camelizeProperties,
61
63
  getObject,
62
64
  pick,
65
+ isPromise,
66
+ isPlainObject,
63
67
  Path,
64
68
  } from '../../../shared/core';
65
69
  import {
@@ -71,7 +75,16 @@ import {
71
75
  import type { Columns, ColumnControlConfig } from '../types';
72
76
  import TableColumnRenderer from './TableColumnRenderer.vue';
73
77
 
74
- defineOptions({ name: 'ProTable', inheritAttrs: false });
78
+ defineOptions({
79
+ name: 'ProTable',
80
+ directives: {
81
+ loading: ElLoading.directive,
82
+ },
83
+ inheritAttrs: false,
84
+ });
85
+
86
+ const tableProperties = Object.keys(tableProps());
87
+ const paginationProperties = Object.keys(paginationProps());
75
88
 
76
89
  type Control = {
77
90
  sizeControl: boolean;
@@ -108,31 +121,29 @@ type Props = {
108
121
  pagination?: false | Partial<PaginationProps>;
109
122
  } & Omit<TableProps, 'data'>;
110
123
 
111
- const {
112
- table = undefined,
113
- search = undefined,
114
- addIndexColumn = undefined,
115
- control = undefined,
116
- tableContainer = undefined,
117
- immediateSearch = undefined,
118
- searchFormConfig = undefined,
119
- columns = undefined,
120
- data = undefined,
121
- pagination = undefined,
122
- } = defineProps<Props>();
124
+ const props = withDefaults(defineProps<Props>(), {
125
+ table: undefined,
126
+ search: undefined,
127
+ addIndexColumn: undefined,
128
+ immediateSearch: undefined,
129
+ control: undefined,
130
+ searchFormConfig: undefined,
131
+ tableContainer: undefined,
132
+ columns: undefined,
133
+ data: undefined,
134
+ pagination: undefined,
135
+ });
123
136
 
124
137
  const { t } = useLocale();
125
138
 
126
139
  const config = INJECT_CONFIG['pro-table'];
127
- const injectProps = inject(config.injectionKey, config.default) as any;
128
-
129
- const tableProperties = Object.keys(tableProps());
130
- const paginationProperties = Object.keys(paginationProps());
140
+ const injectPropsRaw = inject(config.injectionKey, config.default);
141
+ const injectProps = computed(() => toValue(injectPropsRaw) || {});
131
142
 
132
143
  const attrs = useAttrs() as Record<string, any>;
133
144
 
134
145
  const injectTableAttrs = computed(() => {
135
- return pick(injectProps, tableProperties) as Partial<TableProps>;
146
+ return pick(injectProps.value, tableProperties) as Partial<TableProps>;
136
147
  });
137
148
 
138
149
  const attrsTableProps = computed(() => {
@@ -142,91 +153,95 @@ const attrsTableProps = computed(() => {
142
153
  ) as Partial<TableProps>;
143
154
  });
144
155
 
145
- const {
146
- columns: tableColumns,
147
- data: tableData,
148
- searchForm,
149
- pageParam,
150
- setPageParam,
151
- } = table || {};
152
-
153
156
  const size = defineModel<TableProps['size']>('size');
154
- const loading = defineModel<boolean>('loading');
157
+ const loading = defineModel<boolean>('loading', { default: false });
155
158
 
156
- // 核心搜索方法:设置 loading 状态,调用外部 search 函数
159
+ // 递增序号做请求竞态守卫,仅最新一次请求可变更 loading
160
+ let searchSequence = 0;
157
161
  const _search = async () => {
162
+ const seq = ++searchSequence;
163
+ const searchPromise = (props.search || attrs.onSearch)?.();
164
+ if (!isPromise(searchPromise)) return;
165
+ loading.value = true;
158
166
  try {
159
- loading.value = true;
160
- await search?.();
167
+ await searchPromise;
161
168
  } finally {
162
- loading.value = false;
169
+ if (seq === searchSequence) {
170
+ loading.value = false;
171
+ }
163
172
  }
164
173
  };
165
174
 
166
175
  // 重置:恢复查询参数到初始值,重新搜索
167
176
  const reset = () => {
168
- table?.resetQueryParams();
177
+ props.table?.resetQueryParams?.();
169
178
  nextTick(() => {
170
179
  _search();
171
180
  });
172
181
  };
173
182
 
183
+ const customSlotNames = [
184
+ 'search-form',
185
+ 'button-bar',
186
+ 'toolbar',
187
+ 'table',
188
+ ] as const;
189
+
174
190
  type CustomSlots = Readonly<
175
- Partial<Record<'search-form' | 'button-bar' | 'toolbar' | 'table', Slot>>
191
+ Partial<Record<(typeof customSlotNames)[number], Slot>>
176
192
  >;
177
-
178
193
  type Slots = CustomSlots & Readonly<Record<string, Slot>>;
179
194
 
180
195
  const slots = useSlots() as Slots;
181
- const searchFormSlot = slots['search-form'];
182
- const buttonBarSlot = slots['button-bar'];
183
- const toolbarSlot = slots['toolbar'];
184
-
185
- const tableSlots = computed(() => {
186
- const customNames = new Set([
187
- 'search-form',
188
- 'button-bar',
189
- 'toolbar',
190
- 'table',
191
- ]);
192
- return Object.fromEntries(
193
- Object.entries(slots).filter(([name]) => !customNames.has(name))
194
- ) as Record<string, Slot>;
195
- });
196
- const tableSlotNames = computed(() => Object.keys(tableSlots.value));
196
+ const forwardedSlotNames = () =>
197
+ Object.keys(slots).filter(name => !customSlotNames.includes(name as any));
197
198
 
198
199
  const searchPage1st = () => {
199
- setPageParam?.({ current: 1 });
200
+ props.table?.setPageParam?.({ current: 1 });
200
201
  _search();
201
202
  };
202
203
 
203
204
  const computedSearchFormConfig = computed(() => {
204
205
  const container =
205
- searchFormConfig?.container ??
206
- injectProps.searchFormConfig?.container ??
206
+ props.searchFormConfig?.container ??
207
+ (injectProps.value.searchFormConfig as any)?.container ??
207
208
  DefaultSearchFormContainer;
208
209
  return {
209
- ...injectProps.searchFormConfig,
210
- ...getObject(searchFormConfig),
210
+ ...injectProps.value.searchFormConfig,
211
+ ...getObject(props.searchFormConfig),
211
212
  container: container ? container : undefined,
212
213
  };
213
214
  });
214
215
 
215
216
  const computedTableContainer = computed(() => {
216
217
  const container =
217
- tableContainer ?? injectProps.tableContainer ?? DefaultTableContainer;
218
+ props.tableContainer ??
219
+ injectProps.value.tableContainer ??
220
+ DefaultTableContainer;
218
221
  return container ? container : undefined;
219
222
  });
220
223
 
224
+ const getIndex = (i: number) => {
225
+ if (props.table?.pageParam && computedPagination.value !== false) {
226
+ const current = props.table.pageParam.current ?? 1;
227
+ const pageSize = props.table.pageParam.pageSize ?? 10;
228
+ return (current - 1) * pageSize + i + 1;
229
+ }
230
+ return i + 1;
231
+ };
232
+
221
233
  const normalizedRawColumns = computed(() => {
222
- const source = (columns ?? tableColumns?.value ?? []) as any[];
223
- if (addIndexColumn ?? injectProps.addIndexColumn) {
234
+ const source = (props.columns ??
235
+ toValue(props.table?.columns) ??
236
+ []) as any[];
237
+ if (props.addIndexColumn ?? injectProps.value.addIndexColumn) {
224
238
  return [
225
239
  {
226
240
  key: '_index',
227
241
  type: 'index',
228
242
  label: t('table.index'),
229
243
  width: 80,
244
+ index: getIndex,
230
245
  },
231
246
  ...source,
232
247
  ];
@@ -236,7 +251,13 @@ const normalizedRawColumns = computed(() => {
236
251
 
237
252
  const computedDataSource = computed<any[]>(() => {
238
253
  const attrsData = attrsTableProps.value.data as any[] | undefined;
239
- return data ?? attrsData ?? tableData?.value ?? [];
254
+ return (
255
+ props.data ??
256
+ attrsData ??
257
+ toValue((props.table as any)?.dataSource) ??
258
+ toValue(props.table?.data) ??
259
+ []
260
+ );
240
261
  });
241
262
 
242
263
  const computedTableProps = computed<TableProps>(() => {
@@ -248,40 +269,68 @@ const computedTableProps = computed<TableProps>(() => {
248
269
  return merged;
249
270
  });
250
271
 
272
+ // 分页优先级:tableAttrs 显式配置 > provider 全局配置 > 默认合成
251
273
  const computedPagination = computed<false | Partial<PaginationProps>>(() => {
252
- if (pagination === false) return false;
253
- const pageConfig = pagination ?? {};
274
+ const attrsPagination = (attrsTableProps.value as any).pagination;
275
+ if (props.pagination === false || attrsPagination === false) {
276
+ return false;
277
+ }
278
+ const hasUserPagination =
279
+ isPlainObject(props.pagination) || isPlainObject(attrsPagination);
280
+ if (hasUserPagination) {
281
+ const pageConfig = (props.pagination ??
282
+ attrsPagination ??
283
+ {}) as Partial<PaginationProps>;
284
+ const globalPagination =
285
+ (injectProps.value.pagination as any) === false
286
+ ? {}
287
+ : (injectProps.value.pagination as Partial<PaginationProps>) || {};
288
+ const pageParam = props.table?.pageParam;
289
+ return {
290
+ ...globalPagination,
291
+ ...pick(pageConfig as Record<string, any>, paginationProperties),
292
+ currentPage: pageParam?.current ?? (pageConfig as any).currentPage ?? 1,
293
+ pageSize: pageParam?.pageSize ?? (pageConfig as any).pageSize ?? 10,
294
+ total: pageParam?.total ?? (pageConfig as any).total ?? 0,
295
+ };
296
+ }
297
+
298
+ if ((injectProps.value.pagination as any) === false) return false;
299
+
300
+ const pageParam = props.table?.pageParam;
254
301
  return {
255
- ...injectProps.pagination,
256
- ...pick(pageConfig as Record<string, any>, paginationProperties),
257
- currentPage: (pageConfig as any).currentPage ?? pageParam?.current ?? 1,
258
- pageSize: (pageConfig as any).pageSize ?? pageParam?.pageSize ?? 10,
259
- total: (pageConfig as any).total ?? pageParam?.total ?? 0,
302
+ ...(injectProps.value.pagination as Partial<PaginationProps>),
303
+ currentPage: pageParam?.current ?? 1,
304
+ pageSize: pageParam?.pageSize ?? 10,
305
+ total: pageParam?.total ?? 0,
260
306
  };
261
307
  });
262
308
 
263
309
  const showPagination = computed(() => computedPagination.value !== false);
264
310
 
265
311
  const onCurrentChange = (current: number) => {
266
- setPageParam?.({ current });
312
+ props.table?.setPageParam?.({ current });
267
313
  _search();
268
314
  };
269
315
 
270
316
  const onSizeChange = (pageSize: number) => {
271
- setPageParam?.({ pageSize, current: 1 });
317
+ props.table?.setPageParam?.({ pageSize, current: 1 });
272
318
  _search();
273
319
  };
274
320
 
275
321
  const onPaginationChange = (currentPage: number, pageSize: number) => {
322
+ // 透传 el-pagination change 回调
276
323
  attrs.onChange?.(currentPage, pageSize);
277
324
  };
278
325
 
279
- const showSearch = computed(() => {
326
+ const searchFormVisible = computed(() => {
327
+ const searchFields = props.table?.searchForm?.fields;
328
+ const fieldList =
329
+ (Array.isArray(searchFields) ? searchFields : searchFields?.value) ?? [];
280
330
  return !!(
281
- searchFormSlot ||
282
- (computedSearchFormConfig.value?.hidden !== true &&
283
- searchForm &&
284
- searchForm.fields.value.filter(item => !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
285
334
  );
286
335
  });
287
336
 
@@ -289,13 +338,13 @@ const computedControl = computed<{
289
338
  sizeControl: boolean;
290
339
  columnControl: boolean | ColumnControlConfig<Path<RecordType>>;
291
340
  }>(() => {
292
- const mergedControl = control ?? injectProps.control;
341
+ const mergedControl = props.control ?? injectProps.value.control;
293
342
  if (typeof mergedControl === 'boolean') {
294
343
  return { sizeControl: mergedControl, columnControl: mergedControl };
295
344
  }
296
345
  const c = mergedControl as Control;
297
- const sizeControl = c.sizeControl ?? true;
298
- const columnControl = c.columnControl ?? true;
346
+ const sizeControl = c?.sizeControl ?? false;
347
+ const columnControl = c?.columnControl ?? false;
299
348
  return { sizeControl: !!sizeControl, columnControl };
300
349
  });
301
350
 
@@ -313,22 +362,27 @@ const resolvedSize = computed(() => {
313
362
  });
314
363
 
315
364
  onMounted(() => {
316
- if (immediateSearch ?? injectProps.immediateSearch) {
365
+ if (props.immediateSearch ?? injectProps.value.immediateSearch) {
317
366
  _search();
318
367
  }
319
368
  });
369
+
370
+ defineExpose({
371
+ /** 手动触发一次查询(与内部搜索共用 loading 竞态守卫),便于外部刷新列表 */
372
+ search: _search,
373
+ });
320
374
  </script>
321
375
 
322
376
  <template>
323
377
  <div class="pro-table" :class="attrs.class" :style="attrs.style">
324
378
  <ContainerFragment
325
- v-if="showSearch"
379
+ v-if="searchFormVisible || $slots['search-form']"
326
380
  :component="computedSearchFormConfig.container"
327
381
  >
328
382
  <slot name="search-form">
329
383
  <SearchForm
330
- v-if="searchForm"
331
- :form="searchForm"
384
+ v-if="props.table?.searchForm"
385
+ :form="props.table.searchForm"
332
386
  v-bind="computedSearchFormConfig"
333
387
  @search="searchPage1st"
334
388
  @reset="reset"
@@ -338,10 +392,10 @@ onMounted(() => {
338
392
 
339
393
  <ContainerFragment :component="computedTableContainer">
340
394
  <div class="pro-table_header">
341
- <div v-if="buttonBarSlot" class="pro-table_header_button-bar">
395
+ <div v-if="$slots['button-bar']" class="pro-table_header_button-bar">
342
396
  <slot name="button-bar" />
343
397
  </div>
344
- <div v-if="toolbarSlot" class="pro-table_header_toolbar">
398
+ <div v-if="$slots['toolbar']" class="pro-table_header_toolbar">
345
399
  <slot name="toolbar" />
346
400
  </div>
347
401
  <div
@@ -358,7 +412,7 @@ onMounted(() => {
358
412
  :columns="
359
413
  normalizedRawColumns.filter(item => item.key !== '_index')
360
414
  "
361
- :table="table"
415
+ :table="props.table"
362
416
  :column-control="columnControlConfig"
363
417
  />
364
418
  </div>
@@ -373,7 +427,11 @@ onMounted(() => {
373
427
  :data="computedDataSource"
374
428
  >
375
429
  <TableColumnRenderer :columns="normalizedRawColumns" />
376
- <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
+ >
377
435
  <slot :name="name" v-bind="scoped" />
378
436
  </template>
379
437
  </ElTable>