@qin-ui/element-plus-pro 1.4.1 → 1.4.3

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 (32) hide show
  1. package/api.json +2 -2
  2. package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -1
  3. package/es/components/form/components/BaseField/utils/index.d.ts.map +1 -1
  4. package/es/components/form/components/BaseFormItem/index.vue.d.ts +60 -9
  5. package/es/components/form/components/BaseFormItem/index.vue.d.ts.map +1 -1
  6. package/es/components/form/components/BaseFormItems/index.vue.d.ts +13 -0
  7. package/es/components/form/components/BaseFormItems/index.vue.d.ts.map +1 -0
  8. package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -1
  9. package/es/components/form/components/index.d.ts +6 -6
  10. package/es/components/form/components/index.d.ts.map +1 -1
  11. package/es/components/form/hooks/useGroupFieldAttrs.d.ts +10 -2
  12. package/es/components/form/hooks/useGroupFieldAttrs.d.ts.map +1 -1
  13. package/es/components/form/utils/disabled.d.ts +28 -0
  14. package/es/components/form/utils/disabled.d.ts.map +1 -0
  15. package/es/components/form/utils/fieldKey.d.ts +12 -0
  16. package/es/components/form/utils/fieldKey.d.ts.map +1 -1
  17. package/es/components/table/components/ProTable.vue.d.ts +1 -1
  18. package/es/components/table/components/ProTable.vue.d.ts.map +1 -1
  19. package/es/element-plus-pro.css +8 -7
  20. package/es/index.js +301 -153
  21. package/es/index.js.map +1 -1
  22. package/package.json +2 -2
  23. package/src/components/form/components/BaseField/index.vue +6 -8
  24. package/src/components/form/components/BaseField/utils/index.ts +3 -0
  25. package/src/components/form/components/BaseFormItem/index.vue +145 -175
  26. package/src/components/form/components/BaseFormItems/index.vue +183 -0
  27. package/src/components/form/components/ProForm/index.vue +18 -5
  28. package/src/components/form/components/index.ts +6 -6
  29. package/src/components/form/hooks/useGroupFieldAttrs.ts +104 -79
  30. package/src/components/form/utils/disabled.ts +37 -0
  31. package/src/components/form/utils/fieldKey.ts +64 -0
  32. package/src/components/table/components/ProTable.vue +15 -1
@@ -3,12 +3,12 @@ import type {
3
3
  ComponentExposed,
4
4
  } from 'vue-component-type-helpers';
5
5
  import ProForm from './ProForm/index.vue';
6
- import BaseFormItem from './BaseFormItem/index.vue';
6
+ import BaseFormItems from './BaseFormItems/index.vue';
7
7
 
8
8
  export { default as BaseField } from './BaseField/index.vue';
9
9
  export { default as ContainerFragment } from './ContainerFragment/index.vue';
10
10
  export { default as SlotComponent } from './SlotComponent/index.vue';
11
- export { ProForm, BaseFormItem };
11
+ export { ProForm, BaseFormItems };
12
12
 
13
13
  // instance
14
14
  /** ProForm 组件实例 */
@@ -16,7 +16,7 @@ export type ProFormInstance = ComponentExposed<typeof ProForm>;
16
16
  /** ProForm 组件属性 */
17
17
  export type ProFormProps = ComponentProps<typeof ProForm>;
18
18
 
19
- /** BaseFormItem 组件实例(字段级 FormItem) */
20
- export type ProFormItemInstance = ComponentExposed<typeof BaseFormItem>;
21
- /** BaseFormItem 组件属性(字段级 FormItem) */
22
- export type ProFormItemProps = ComponentProps<typeof BaseFormItem>;
19
+ /** BaseFormItems 组件实例(字段级 FormItem) */
20
+ export type ProFormItemsInstance = ComponentExposed<typeof BaseFormItems>;
21
+ /** BaseFormItems 组件属性(字段级 FormItem) */
22
+ export type ProFormItemsProps = ComponentProps<typeof BaseFormItems>;
@@ -1,5 +1,11 @@
1
- import { computed, inject, mergeProps, toValue, type ComputedRef } from 'vue';
2
- import { getFieldKey } from '../utils/fieldKey';
1
+ import {
2
+ computed,
3
+ inject,
4
+ mergeProps,
5
+ toValue,
6
+ type ComputedRef,
7
+ type MaybeRefOrGetter,
8
+ } from 'vue';
3
9
  import {
4
10
  formItemProps,
5
11
  gridItemProps,
@@ -26,9 +32,87 @@ export interface GroupedField {
26
32
  componentProps: Record<string, any>;
27
33
  container?: Base['formItemContainer'];
28
34
  rawField: Field;
29
- renderKey: string | number;
30
35
  }
31
36
 
37
+ /** 将单个字段配置拆分为 grid / formItem / component / slots 分组 */
38
+ const groupField = (
39
+ field: Field,
40
+ rawInjectProps: Record<string, any>
41
+ ): GroupedField => {
42
+ let gridItemProps: Record<string, any> = {};
43
+ let formItemProps: Record<string, any> = {};
44
+ const componentProps: Record<string, any> = { slots: {} };
45
+ const formItemSlots: Record<string, any> = {};
46
+
47
+ if (isPlainObject(field)) {
48
+ // prettier-ignore
49
+ const { path: _path, prop: _prop, fields, formItemClass, formItemStyle, formItemContainer, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, ...rest } =
50
+ field as any;
51
+
52
+ // prettier-ignore
53
+ const { class: injectClassName, style: injectStyle, span: injectSpan, formItemContainer: injectFormItemContainer, xs, sm, md, lg, xl, ...injectRest } = rawInjectProps;
54
+
55
+ /*
56
+ * 字段显式指定 span 时整体接管栅格,不再叠加注入的响应式断点;
57
+ * 未指定 span 时使用注入的断点配置
58
+ */
59
+ const initGridItemProps =
60
+ span || span === 0 ? { span } : { xs, sm, md, lg, xl, span: injectSpan };
61
+
62
+ // 分组字段未指定 span 时整行渲染(span 兜底 24),注入的响应式断点保持生效
63
+ gridItemProps = fields
64
+ ? { ...initGridItemProps, span: span ?? 24 }
65
+ : initGridItemProps;
66
+ formItemProps = mergeProps(
67
+ injectRest as any,
68
+ { class: injectClassName, style: injectStyle },
69
+ { class: toValue(formItemClass), style: toValue(formItemStyle) },
70
+ formItemDataAttrs
71
+ );
72
+ // 字段级容器优先,回退注入级配置
73
+ const container = formItemContainer ?? injectFormItemContainer;
74
+
75
+ Object.keys(rest).forEach(k => {
76
+ if (gridItemPropKeySet.has(k)) {
77
+ gridItemProps[k] = rest[k];
78
+ } else if (formItemPropKeySet.has(k)) {
79
+ (formItemProps as any)[k] = rest[k];
80
+ } else {
81
+ componentProps[k] = rest[k];
82
+ }
83
+ });
84
+
85
+ Object.assign(componentProps, componentDataAttrs);
86
+ Object.keys(slots).forEach(k => {
87
+ if (formItemSlotKeySet.has(k)) {
88
+ formItemSlots[k] = slots[k];
89
+ } else {
90
+ componentProps.slots[k] = slots[k];
91
+ }
92
+ });
93
+
94
+ // 字段标识经统一解析(prop 优先,path 兜底);name 作为控件自身属性经 rest 透传
95
+ return {
96
+ path: resolveFieldIdentity(field, ELEMENT_FIELD_IDENTITY_KEYS),
97
+ gridItemProps: gridItemProps as GridItemProps,
98
+ formItemProps: formItemProps as any,
99
+ formItemSlots,
100
+ componentProps,
101
+ container,
102
+ rawField: field,
103
+ };
104
+ }
105
+
106
+ return {
107
+ path: resolveFieldIdentity(field, ELEMENT_FIELD_IDENTITY_KEYS),
108
+ gridItemProps: gridItemProps as GridItemProps,
109
+ formItemProps: formItemProps as any,
110
+ formItemSlots,
111
+ componentProps,
112
+ rawField: field,
113
+ };
114
+ };
115
+
32
116
  /**
33
117
  * 将 fields 配置数组逐个拆分为 grid / formItem / component / slots 分组。
34
118
  *
@@ -45,82 +129,23 @@ export function useGroupFieldAttrs(validFields: ComputedRef<Field[]>) {
45
129
 
46
130
  return computed<GroupedField[]>(() => {
47
131
  const rawInjectProps = toValue(injectFormItemProps) || {};
48
- return validFields.value.map(field => {
49
- let gridItemProps: Record<string, any> = {};
50
- let formItemProps: Record<string, any> = {};
51
- const componentProps: Record<string, any> = { slots: {} };
52
- const formItemSlots: Record<string, any> = {};
53
-
54
- if (isPlainObject(field)) {
55
- // prettier-ignore
56
- const { path: _path, prop: _prop, fields, formItemClass, formItemStyle, formItemContainer, hidden, span, getFormItemRef, getComponentRef, getFormItemComputedProps, getComponentComputedProps, slots = {}, formItemDataAttrs = {}, componentDataAttrs = {}, extraProps, ...rest } =
57
- field as any;
58
-
59
- // prettier-ignore
60
- const { class: injectClassName, style: injectStyle, span: injectSpan, formItemContainer: injectFormItemContainer, xs, sm, md, lg, xl, ...injectRest } = rawInjectProps;
61
-
62
- /*
63
- * 字段显式指定 span 时整体接管栅格,不再叠加注入的响应式断点;
64
- * 未指定 span 时使用注入的断点配置
65
- */
66
- const initGridItemProps =
67
- span || span === 0
68
- ? { span }
69
- : { xs, sm, md, lg, xl, span: injectSpan };
70
-
71
- // 分组字段未指定 span 时整行渲染(span 兜底 24),注入的响应式断点保持生效
72
- gridItemProps = fields
73
- ? { ...initGridItemProps, span: span ?? 24 }
74
- : initGridItemProps;
75
- formItemProps = mergeProps(
76
- injectRest as any,
77
- { class: injectClassName, style: injectStyle },
78
- { class: toValue(formItemClass), style: toValue(formItemStyle) },
79
- formItemDataAttrs
80
- );
81
- const container = formItemContainer ?? injectFormItemContainer;
82
-
83
- Object.keys(rest).forEach(k => {
84
- if (gridItemPropKeySet.has(k)) {
85
- gridItemProps[k] = rest[k];
86
- } else if (formItemPropKeySet.has(k)) {
87
- (formItemProps as any)[k] = rest[k];
88
- } else {
89
- componentProps[k] = rest[k];
90
- }
91
- });
92
-
93
- Object.assign(componentProps, componentDataAttrs);
94
- Object.keys(slots).forEach(k => {
95
- if (formItemSlotKeySet.has(k)) {
96
- formItemSlots[k] = slots[k];
97
- } else {
98
- componentProps.slots[k] = slots[k];
99
- }
100
- });
132
+ return validFields.value.map(field => groupField(field, rawInjectProps));
133
+ });
134
+ }
101
135
 
102
- // 字段标识经统一解析(prop 优先,path 兜底);name 作为控件自身属性经 rest 透传
103
- return {
104
- path: resolveFieldIdentity(field, ELEMENT_FIELD_IDENTITY_KEYS),
105
- gridItemProps: gridItemProps as GridItemProps,
106
- formItemProps: formItemProps as any,
107
- formItemSlots,
108
- componentProps,
109
- container,
110
- rawField: field,
111
- renderKey: getFieldKey(field),
112
- };
113
- }
136
+ /**
137
+ * 将单个字段配置拆分为 grid / formItem / component / slots 分组
138
+ *
139
+ * @description 拆分规则与 `useGroupFieldAttrs` 一致,结果依赖该字段自身配置与 Provider 的 `pro-form-item` 默认值。
140
+ *
141
+ * @param {MaybeRefOrGetter<Field>} field - 待拆分的字段配置
142
+ * @returns {ComputedRef<GroupedField>} 单字段渲染分组
143
+ */
144
+ export function useGroupedField(field: MaybeRefOrGetter<Field>) {
145
+ const config = INJECT_CONFIG['pro-form-item'];
146
+ const injectFormItemProps = inject(config.injectionKey, config.default);
114
147
 
115
- return {
116
- path: resolveFieldIdentity(field, ELEMENT_FIELD_IDENTITY_KEYS),
117
- gridItemProps: gridItemProps as GridItemProps,
118
- formItemProps: formItemProps as any,
119
- formItemSlots,
120
- componentProps,
121
- rawField: field,
122
- renderKey: getFieldKey(field),
123
- };
124
- });
125
- });
148
+ return computed<GroupedField>(() =>
149
+ groupField(toValue(field), toValue(injectFormItemProps) || {})
150
+ );
126
151
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 归一化禁用值
3
+ *
4
+ * @description 字符串形态与宿主 Boolean prop 一致:`''` 与 `disabled` 为 true,其余取真值
5
+ * @param value - 原始禁用值
6
+ * @returns 归一化后的布尔值
7
+ */
8
+ export const normalizeDisabled = (value: unknown) =>
9
+ value === '' || value === 'disabled' ? true : Boolean(value);
10
+
11
+ /**
12
+ * 解析显式声明的禁用值
13
+ *
14
+ * @description `undefined` / `null` 为未声明,返回 `undefined`;其余返回归一化结果
15
+ * @param value - 原始禁用值
16
+ * @returns 显式禁用值;未声明时为 `undefined`
17
+ */
18
+ export const resolveDisabled = (value: unknown): boolean | undefined =>
19
+ value === undefined || value === null ? undefined : normalizeDisabled(value);
20
+
21
+ /**
22
+ * 解析控件最终生效的禁用状态
23
+ *
24
+ * @description 字段显式声明(含 `false`)优先;未声明时继承外层禁用,外层为真即禁用;
25
+ * 外层未禁用时回退 Provider 注入的组件级默认值
26
+ * @param explicit - 字段显式声明的禁用值
27
+ * @param inherited - 外层禁用上下文取值
28
+ * @param fallback - Provider 注入的组件级默认禁用值
29
+ * @returns 最终生效的禁用状态
30
+ */
31
+ export const resolveEffectiveDisabled = (
32
+ explicit: unknown,
33
+ inherited: unknown,
34
+ fallback: unknown
35
+ ): boolean =>
36
+ resolveDisabled(explicit) ??
37
+ (normalizeDisabled(inherited) || normalizeDisabled(fallback));
@@ -1,4 +1,6 @@
1
1
  import { toRaw } from 'vue';
2
+ import { resolveFieldIdentity, toPath } from '../../../shared/core';
3
+ import { ELEMENT_FIELD_IDENTITY_KEYS } from '../constants';
2
4
 
3
5
  const fieldKeyMap = new WeakMap<object, number>();
4
6
  let fieldKeySeed = 0;
@@ -25,3 +27,65 @@ export const getFieldKey = (field: unknown): string | number => {
25
27
  }
26
28
  return key;
27
29
  };
30
+
31
+ /** 数据行对象身份编号 */
32
+ const rowKeyMap = new WeakMap<object, number>();
33
+ let rowKeySeed = 0;
34
+
35
+ const getRowKey = (row: object) => {
36
+ const raw = toRaw(row);
37
+ let key = rowKeyMap.get(raw);
38
+ if (key === undefined) {
39
+ key = ++rowKeySeed;
40
+ rowKeyMap.set(raw, key);
41
+ }
42
+ return key;
43
+ };
44
+
45
+ /** 数组下标段判定:仅十进制非负整数段按数组下标处理 */
46
+ const isIndexSegment = (segment: string) => /^(?:0|[1-9]\d*)$/.test(segment);
47
+
48
+ /**
49
+ * 字段的数据行渲染 key
50
+ *
51
+ * @description 沿字段路径逐段解析原始表单数据,取路径经过的对象数组行元素身份与行内剩余路径组成 key;
52
+ * 同一数据行对象在数组增删 / 重排后恒得同一 key。路径不经过对象数组行、行元素为原始值或数据缺失时,
53
+ * 回退到 {@link getFieldKey} 的字段对象身份。同 key 的去重由调用方按兄弟列表收敛。
54
+ *
55
+ * @param field - 字段配置项
56
+ * @param rawFormData - 原始表单数据(非响应式对象)
57
+ * @returns 数据行 key;无法定位数据行时返回字段对象身份 key
58
+ */
59
+ export const getFieldDataKey = (
60
+ field: unknown,
61
+ rawFormData: unknown
62
+ ): string | number => {
63
+ const path = resolveFieldIdentity(field, ELEMENT_FIELD_IDENTITY_KEYS);
64
+ if (!path || rawFormData === null || rawFormData === undefined) {
65
+ return getFieldKey(field);
66
+ }
67
+
68
+ const segments = toPath(path);
69
+ let node: any = rawFormData;
70
+ const rowIds: number[] = [];
71
+ let lastRowIndex = -1;
72
+
73
+ for (let i = 0; i < segments.length; i++) {
74
+ if (Array.isArray(node)) {
75
+ const segment = segments[i];
76
+ if (!isIndexSegment(segment)) break;
77
+ const element = node[Number(segment)];
78
+ if (element === null || typeof element !== 'object') break;
79
+ rowIds.push(getRowKey(toRaw(element) as object));
80
+ lastRowIndex = i;
81
+ node = element;
82
+ continue;
83
+ }
84
+ if (node === null || typeof node !== 'object') break;
85
+ node = node[segments[i]];
86
+ }
87
+
88
+ if (lastRowIndex === -1) return getFieldKey(field);
89
+ const rest = segments.slice(lastRowIndex + 1).join('.');
90
+ return `data:${rowIds.join('/')}:${rest}`;
91
+ };
@@ -32,6 +32,11 @@
32
32
  * 2. 分页变化:分页参数同步后发起查询
33
33
  * 3. 重置:查询参数恢复初始值后重新查询
34
34
  *
35
+ * ## 列来源
36
+ *
37
+ * 显式 `:columns` 优先于 `:table` 实例的 `columns`;显式列按原样透传给宿主,不补齐 `key`,
38
+ * 需由调用方提供 `key` / `dataIndex` / `prop`。显式模式下 `setColumn` 与列控入口均不生效。
39
+ *
35
40
  * @template T - 表格实例类型(useTable 返回值)
36
41
  *
37
42
  * @param {T} [table] - 列表表格对象,来自 useTable
@@ -141,7 +146,7 @@ type Props = {
141
146
  searchFormConfig?: SearchFormConfig;
142
147
  /** 表格区域包裹容器 */
143
148
  tableContainer?: ContainerComponent | false;
144
- /** 直接传入列配置(覆盖 :table 实例的 columns) */
149
+ /** 直接传入列配置(覆盖 :table 实例的 columns);列需自备 key / dataIndex / prop,显式模式不参与 setColumn 与列控 */
145
150
  columns?: Columns<RecordType>;
146
151
  /** 直接传入数据源(覆盖 :table 实例的 data) */
147
152
  data?: RecordType[];
@@ -379,6 +384,7 @@ const getIndex = (i: number) => {
379
384
  /** 调用方是否显式传入 columns(值为 undefined 时视为未指定) */
380
385
  const hasExplicitColumns = computed(() => props.columns !== undefined);
381
386
 
387
+ /** 显式列原样透传(key 由调用方提供);table 列经 core 规范化 */
382
388
  const normalizedRawColumns = computed(() => {
383
389
  const source = (
384
390
  (props.columns ?? toValue(props.table?.columns) ?? []) as any[]
@@ -445,15 +451,23 @@ const computedPagination = computed<false | Partial<PaginationProps>>(() => {
445
451
 
446
452
  const showPagination = computed(() => computedPagination.value !== false);
447
453
 
454
+ // pageSize 变更窗口:窗口内忽略宿主级联的 current-change
455
+ let sizeChangeWindow = false;
456
+
448
457
  const onCurrentChange = (current: number) => {
458
+ if (sizeChangeWindow) return;
449
459
  props.table?.setPageParam?.({ current });
450
460
  _search();
451
461
  };
452
462
 
453
463
  // 改变 pageSize 时重置回第 1 页并重新查询
454
464
  const onSizeChange = (pageSize: number) => {
465
+ sizeChangeWindow = true;
455
466
  props.table?.setPageParam?.({ pageSize, current: 1 });
456
467
  _search();
468
+ nextTick(() => {
469
+ sizeChangeWindow = false;
470
+ });
457
471
  };
458
472
 
459
473
  const onPaginationChange = (currentPage: number, pageSize: number) => {