@qin-ui/antd-vue-pro 2.3.1 → 2.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qin-ui/antd-vue-pro",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "二次封装antd vue组件",
5
5
  "bin": {
6
6
  "antd-vue-pro": "./bin/init-ai.mjs"
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "scroll-into-view-if-needed": "^3.1.0",
45
45
  "vue-component-type-helpers": "^3.2.5",
46
- "@qin-ui/pro-components-core": "^1.1.0"
46
+ "@qin-ui/pro-components-core": "^1.1.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "ant-design-vue": "^4"
@@ -42,7 +42,7 @@ import {
42
42
  } from '../../../../shared/ui';
43
43
  import type { Field } from '../../types';
44
44
  import { ContainerFragment, SlotComponent } from '..';
45
- import { componentMap, TeleportComponentNamePrefix } from '../../constants';
45
+ import { componentMap, InjectionFormSlotsKey } from '../../constants';
46
46
  import { INJECT_COMPONENTS } from '../../../component-provider/constants';
47
47
  import { useForm } from '../../hooks/useForm';
48
48
  import { getInitProps } from './utils';
@@ -141,21 +141,21 @@ const groupedAttrs = computed(() => {
141
141
  };
142
142
  });
143
143
 
144
- // 从 ProForm 插槽注入的 teleport 组件(优先级最高)
145
- const teleportComponent = inject(
146
- `${TeleportComponentNamePrefix}${path}`,
147
- undefined
148
- );
144
+ // 从 ProForm 插槽注入的 slots 集合(优先级最高)
145
+ const formSlots = inject(InjectionFormSlotsKey, undefined) as
146
+ | Record<string, any>
147
+ | undefined;
149
148
 
150
149
  // 从 ProComponentProvider 注入的自定义组件映射
151
150
  const customComponents = inject(INJECT_COMPONENTS, {});
152
151
 
153
152
  /*
154
153
  * 按优先级解析最终要渲染的组件:
155
- * teleport 插槽 → 自定义组件映射 → 内置组件映射 → 原始 component 值
154
+ * ProForm 插槽 → 自定义组件映射 → 内置组件映射 → 原始 component 值
156
155
  */
157
156
  const is = computed(() => {
158
- if (teleportComponent) return teleportComponent;
157
+ const teleportSlot = path && formSlots?.[path];
158
+ if (teleportSlot) return teleportSlot;
159
159
  if (typeof component === 'string') {
160
160
  return (
161
161
  (customComponents as any)[component] ||
@@ -43,10 +43,10 @@
43
43
  * ```
44
44
  */
45
45
  import { Form as AForm, FormProps as AFormProps } from '../../../../shared/ui';
46
- import { inject, mergeProps, provide, type Slot, watchEffect } from 'vue';
46
+ import { inject, mergeProps, provide, reactive, type Slot } from 'vue';
47
47
  import { INJECT_CONFIG } from '../../../component-provider/constants';
48
48
  import { BaseFormItem } from '..';
49
- import { InjectionFormKey, TeleportComponentNamePrefix } from '../../constants';
49
+ import { InjectionFormKey, InjectionFormSlotsKey } from '../../constants';
50
50
  import type { Grid, VModelProps, PathProps } from '../../types';
51
51
  import { Path, camelizeProperties } from '../../../../shared/core';
52
52
  import type { Form } from '../../hooks/useForm';
@@ -82,18 +82,12 @@ type FieldSlotProps = VModelProps &
82
82
  type FieldSlots = Record<ExtractPath<F>, Slot<FieldSlotProps>>;
83
83
 
84
84
  const slots = defineSlots<Partial<FieldSlots & { default: Slot }>>();
85
+
85
86
  /**
86
87
  * 动态插槽映射中心 (Slot Teleporting):
87
- * 目的:将 ProForm 上以 Field Path 命名的插槽,动态注册为全局可访问的 Context。
88
- * 实现:BaseField 组件在渲染对应字段时,会通过 inject 尝试获取对应的 Prefix+Path,
89
- * 如果存在,则将插槽内容渲染到深层的具体位置,从而突破了组件嵌套层级的限制。
88
+ * 在 setup 同步期直接提供响应式 slots 上下文容器,供深层 BaseField 按需动态获取插槽
90
89
  */
91
- watchEffect(() => {
92
- Object.keys(slots).forEach(name => {
93
- if (name === 'default') return;
94
- provide(`${TeleportComponentNamePrefix}${name}`, (slots as any)[name]);
95
- });
96
- });
90
+ provide(InjectionFormSlotsKey, reactive(slots) as any);
97
91
  </script>
98
92
 
99
93
  <template>
@@ -125,11 +125,9 @@ export const componentMap: BaseComponentMap = {
125
125
  'transfer': Transfer,
126
126
  }
127
127
 
128
- /**
129
- * Teleport 组件名称前缀
130
- * @description 用于通过插槽动态替换指定 path 的组件
131
- */
132
- export const TeleportComponentNamePrefix = 'TeleportComponent_';
133
-
134
128
  // inject keys
135
- export { InjectionFormKey, InjectionPathKey } from '../../../shared/core';
129
+ export {
130
+ InjectionFormKey,
131
+ InjectionPathKey,
132
+ InjectionFormSlotsKey,
133
+ } from '../../../shared/core';
@@ -70,6 +70,8 @@ export type Options = Array<Option>;
70
70
 
71
71
  export type Grid = boolean | (GridProps & {});
72
72
 
73
+ export type FormItemSlots = ComponentSlots<typeof FormItem>;
74
+
73
75
  type BaseCommon<D extends Data = Data> = {
74
76
  /**
75
77
  * @description 字段标识namePath, 同name
@@ -100,7 +102,7 @@ type BaseCommon<D extends Data = Data> = {
100
102
  * }
101
103
  * ```
102
104
  */
103
- slots?: Partial<ComponentSlots<typeof FormItem>>;
105
+ slots?: Partial<FormItemSlots>;
104
106
  /**
105
107
  * @description 字段formItem样式属性
106
108
  * @example { marginBottom: '8px', padding: '12px' }
@@ -238,6 +240,20 @@ type WithFields<D extends Data = Data> = WithRef<
238
240
  WithCommonBase & BaseWithFields<D>
239
241
  >;
240
242
 
243
+ /**
244
+ * @description 合并 FormItem 插槽与组件插槽
245
+ */
246
+ export type MergeSlots<
247
+ T extends abstract new (...args: any) => any,
248
+ CS = ComponentSlots<T>,
249
+ > = Partial<{
250
+ [K in keyof FormItemSlots | keyof CS]: K extends keyof FormItemSlots
251
+ ? FormItemSlots[K]
252
+ : K extends keyof CS
253
+ ? CS[K]
254
+ : SlotComponentType;
255
+ }>;
256
+
241
257
  /**
242
258
  * @description 自动从 Vue 组件提取 Props 和 Slots,并加上公共表单字段属性
243
259
  * @template T - Vue 组件类型
@@ -246,12 +262,12 @@ type WithFields<D extends Data = Data> = WithRef<
246
262
  type WithComponent<
247
263
  T extends abstract new (...args: any) => any,
248
264
  D extends Data = Data,
249
- > = WithCommon<
250
- { slots?: Partial<ComponentSlots<T>> } & Partial<
251
- Omit<ComponentProps<T>, keyof VNodeProps>
252
- >,
253
- D
254
- >;
265
+ > = Omit<
266
+ WithCommon<Partial<ComponentProps<T>>, D>,
267
+ keyof VNodeProps | 'slots'
268
+ > & {
269
+ slots?: MergeSlots<T>;
270
+ };
255
271
 
256
272
  /**
257
273
  * @description 字段配置类型,包含所有字段属性和响应式支持
@@ -40,8 +40,13 @@ const checkedColumnsOptions = computed<
40
40
  }>
41
41
  >(() =>
42
42
  columns.flatMap(item => {
43
- if (!item.key || !item.title) return [];
44
- const key = item.key as string;
43
+ const rawKey =
44
+ item.key ??
45
+ (Array.isArray(item.dataIndex)
46
+ ? item.dataIndex.join('.')
47
+ : item.dataIndex);
48
+ if (!rawKey || !item.title) return [];
49
+ const key = String(rawKey);
45
50
  if (columnControl?.excludedKeys?.some(ek => String(ek) === key)) return [];
46
51
  const disabled =
47
52
  columnControl?.disabledKeys?.some(dk => String(dk) === key) ?? false;
@@ -221,18 +221,7 @@ const computedColumns = computed(
221
221
  ...((addIndexColumn ?? injectProps.addIndexColumn)
222
222
  ? [indexColumn()]
223
223
  : []),
224
- ...(tableAttrs.value.columns ?? columns?.value ?? ([] as any)).flatMap(
225
- (item: ColumnType<RecordType>, index: number) => {
226
- if (item.key) return [{ ...item, key: item.key }];
227
- if (item.dataIndex) {
228
- const dataIndexKey = Array.isArray(item.dataIndex)
229
- ? item.dataIndex.join('.')
230
- : (item.dataIndex as string);
231
- return [{ ...item, key: dataIndexKey }];
232
- }
233
- return [{ ...item, key: index }];
234
- }
235
- ),
224
+ ...(tableAttrs.value.columns ?? columns?.value ?? []),
236
225
  ] as Columns<RecordType>
237
226
  );
238
227
 
@@ -301,7 +290,7 @@ onMounted(() => {
301
290
  </script>
302
291
 
303
292
  <template>
304
- <div class="pro-table" :class="$attrs.class" :style="$attrs.style">
293
+ <div class="pro-table" :class="attrs.class" :style="attrs.style">
305
294
  <ContainerFragment
306
295
  v-if="showSearch"
307
296
  :component="computedSearchFormConfig.container"
@@ -61,8 +61,10 @@ import {
61
61
  AllowedComponentProps,
62
62
  computed,
63
63
  ref,
64
+ nextTick,
64
65
  watch,
65
66
  watchEffect,
67
+ onBeforeUnmount,
66
68
  type Component,
67
69
  type DefineComponent,
68
70
  } from 'vue';
@@ -94,7 +96,25 @@ const emit = defineEmits<Emits>();
94
96
 
95
97
  const proFormHeight = ref<'unset' | number>('unset');
96
98
  const collapseHeight = ref(0);
97
- let rowHeight = 32;
99
+ const rowHeight = ref(32);
100
+
101
+ let resizeObserver: ResizeObserver | null = null;
102
+ let intersectionObserver: IntersectionObserver | null = null;
103
+
104
+ const cleanupObservers = () => {
105
+ if (resizeObserver) {
106
+ resizeObserver.disconnect();
107
+ resizeObserver = null;
108
+ }
109
+ if (intersectionObserver) {
110
+ intersectionObserver.disconnect();
111
+ intersectionObserver = null;
112
+ }
113
+ };
114
+
115
+ onBeforeUnmount(() => {
116
+ cleanupObservers();
117
+ });
98
118
 
99
119
  const computedExpand = computed(() => {
100
120
  if (!expand) return false;
@@ -109,11 +129,41 @@ const showExpandToggle = ref(false);
109
129
 
110
130
  const expandStatus = ref(true);
111
131
 
132
+ const { formRef, getFormData } = form;
133
+
134
+ const updateHeights = () => {
135
+ if (!expand || !formRef.value) return;
136
+ const proFormEl = (formRef.value as any)?.$el;
137
+ if (!proFormEl) return;
138
+
139
+ const itemHeight = proFormEl
140
+ ?.querySelector("[class*='-form-item']")
141
+ ?.getBoundingClientRect?.()?.height;
142
+ if (itemHeight && itemHeight > 0) {
143
+ rowHeight.value = itemHeight;
144
+ }
145
+
146
+ // 使用 scrollHeight 测量自然展开高度,突破固定高度与 overflow: hidden 样式的自引用限制
147
+ const naturalHeight =
148
+ proFormEl.scrollHeight || proFormEl.getBoundingClientRect?.()?.height || 0;
149
+ if (naturalHeight > 0) {
150
+ proFormHeight.value = naturalHeight;
151
+ }
152
+ };
153
+
112
154
  const changeExpandStatus = () => {
113
155
  expandStatus.value = !expandStatus.value;
156
+ nextTick(() => {
157
+ updateHeights();
158
+ });
114
159
  };
115
160
 
116
- const { formRef, getFormData } = form;
161
+ const hasFieldValue = (val: any) =>
162
+ val !== null &&
163
+ val !== undefined &&
164
+ val !== '' &&
165
+ !(Array.isArray(val) && val.length === 0) &&
166
+ !(typeof val === 'object' && Object.keys(val).length === 0);
117
167
 
118
168
  /*
119
169
  * 使用 IntersectionObserver 检测折叠状态下隐藏字段是否有值
@@ -123,26 +173,30 @@ const setInitExpandStatus = () => {
123
173
  expandStatus.value = false;
124
174
  if (formRef.value && expand) {
125
175
  const formEl = (formRef.value as any).$el;
126
- const formItemsEl = formEl.querySelectorAll(
176
+ const formItemsEl = formEl?.querySelectorAll?.(
127
177
  "[class*='-form-item'] > [path]"
128
178
  );
129
- const observer = new IntersectionObserver(
179
+ if (!formItemsEl || formItemsEl.length === 0) return;
180
+ if (intersectionObserver) {
181
+ intersectionObserver.disconnect();
182
+ }
183
+ intersectionObserver = new IntersectionObserver(
130
184
  entries => {
131
185
  expandStatus.value = entries.some(e => {
132
186
  // 如果隐藏字段(intersectionRatio === 0)有值,则自动展开
133
187
  if (e.intersectionRatio === 0) {
134
188
  const path = e.target.getAttribute('path');
135
189
  const searchFieldValue = path ? getFormData?.(path) : undefined;
136
- return ![null, undefined].includes(searchFieldValue);
190
+ return hasFieldValue(searchFieldValue);
137
191
  }
138
192
  return (expand as any).expandStatus;
139
193
  });
140
- observer.disconnect();
194
+ intersectionObserver?.disconnect();
141
195
  },
142
196
  { root: formEl }
143
197
  );
144
198
  formItemsEl.forEach((element: Element) => {
145
- observer.observe(element);
199
+ intersectionObserver?.observe(element);
146
200
  });
147
201
  }
148
202
  };
@@ -154,17 +208,29 @@ watch(
154
208
  () => expand,
155
209
  ],
156
210
  () => {
157
- if (!expand || !expandStatus.value || !formRef.value) return;
158
- const proFormEl = (formRef.value as any)?.$el;
159
- const { height = 0 } = proFormEl?.getBoundingClientRect?.() || {};
160
- proFormHeight.value = height;
161
- rowHeight = proFormEl
162
- ?.querySelector("[class*='-form-item']")
163
- ?.getBoundingClientRect?.()?.height;
211
+ updateHeights();
164
212
  },
165
213
  { flush: 'post', immediate: true }
166
214
  );
167
215
 
216
+ watch(
217
+ () => formRef.value,
218
+ val => {
219
+ if (resizeObserver) {
220
+ resizeObserver.disconnect();
221
+ resizeObserver = null;
222
+ }
223
+ const formEl = (val as any)?.$el;
224
+ if (formEl && typeof ResizeObserver !== 'undefined') {
225
+ resizeObserver = new ResizeObserver(() => {
226
+ updateHeights();
227
+ });
228
+ resizeObserver.observe(formEl);
229
+ }
230
+ },
231
+ { immediate: true }
232
+ );
233
+
168
234
  /*
169
235
  * 计算折叠高度和是否显示展开按钮
170
236
  * 折叠高度 = minExpandRows * 行高 + (minExpandRows - 1) * 行间距
@@ -176,7 +242,7 @@ watchEffect(
176
242
  if (layout === 'grid' && computedExpand.value) {
177
243
  const { minExpandRows } = computedExpand.value;
178
244
  collapseHeight.value = Math.min(
179
- minExpandRows * rowHeight + (minExpandRows - 1) * rowGap,
245
+ minExpandRows * rowHeight.value + (minExpandRows - 1) * rowGap,
180
246
  +proFormHeight.value
181
247
  );
182
248
  showExpandToggle.value = +proFormHeight.value - collapseHeight.value > 1;
@@ -12,6 +12,7 @@ export {
12
12
  // injection keys
13
13
  InjectionFormKey,
14
14
  InjectionPathKey,
15
+ InjectionFormSlotsKey,
15
16
  // utils
16
17
  getObject,
17
18
  toPath,