@qin-ui/antd-vue-pro 2.5.3 → 2.6.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.
- package/AI-CONTEXT.md +30 -10
- package/api.json +23 -3
- package/es/antd-vue-pro.css +6 -6
- package/es/components/component-provider/components/index.vue.d.ts.map +1 -1
- package/es/components/component-provider/types/index.d.ts +1 -1
- package/es/components/component-provider/types/index.d.ts.map +1 -1
- package/es/components/form/components/BaseField/index.vue.d.ts.map +1 -1
- package/es/components/form/components/BaseField/utils/index.d.ts +1 -2
- package/es/components/form/components/BaseField/utils/index.d.ts.map +1 -1
- package/es/components/form/components/ProForm/index.vue.d.ts.map +1 -1
- package/es/components/table/components/ProTable.vue.d.ts +11 -3
- package/es/components/table/components/ProTable.vue.d.ts.map +1 -1
- package/es/components/table/components/TableEllipsisTooltip.vue.d.ts +19 -0
- package/es/components/table/components/TableEllipsisTooltip.vue.d.ts.map +1 -0
- package/es/components/table/hooks/useEllipsisTooltip.d.ts +45 -0
- package/es/components/table/hooks/useEllipsisTooltip.d.ts.map +1 -0
- package/es/components/table/hooks/useEllipsisTooltipColumns.d.ts +19 -0
- package/es/components/table/hooks/useEllipsisTooltipColumns.d.ts.map +1 -0
- package/es/components/table/index.d.ts +1 -1
- package/es/components/table/index.d.ts.map +1 -1
- package/es/components/table/types/index.d.ts +47 -2
- package/es/components/table/types/index.d.ts.map +1 -1
- package/es/index.js +544 -118
- package/es/index.js.map +1 -1
- package/es/shared/core/index.d.ts +1 -1
- package/es/shared/core/index.d.ts.map +1 -1
- package/es/shared/ui/index.d.ts +2 -1
- package/es/shared/ui/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/component-provider/components/index.vue +31 -8
- package/src/components/component-provider/types/index.ts +1 -0
- package/src/components/form/components/BaseField/index.vue +21 -12
- package/src/components/form/components/BaseField/utils/index.ts +5 -5
- package/src/components/form/components/ProForm/index.vue +9 -2
- package/src/components/table/components/ProTable.vue +198 -71
- package/src/components/table/components/TableEllipsisTooltip.vue +90 -0
- package/src/components/table/hooks/useEllipsisTooltip.ts +244 -0
- package/src/components/table/hooks/useEllipsisTooltipColumns.ts +154 -0
- package/src/components/table/index.ts +7 -1
- package/src/components/table/types/index.ts +63 -2
- package/src/shared/core/index.ts +2 -0
- package/src/shared/ui/index.ts +3 -0
|
@@ -46,11 +46,12 @@
|
|
|
46
46
|
* @param {boolean | Partial<Control>} [control] - 是否展示尺寸与列控制按钮;显式传入 columns 时列控入口不渲染
|
|
47
47
|
* @param {SearchFormConfig} [searchFormConfig] - 搜索栏查询字段表单配置
|
|
48
48
|
* @param {ContainerComponent | false} [tableContainer] - 表格区域包裹容器
|
|
49
|
+
* @param {boolean | EllipsisTooltipConfig} [ellipsisTooltip] - truthy 时全局启用所有列;对象同时作为 Tooltip 配置;列上 `showTooltip: false` 或 `ellipsis: false` 可退出
|
|
49
50
|
*
|
|
50
51
|
* @slot search-form - 搜索区域插槽,作用域参数 form / search / reset
|
|
51
52
|
* @slot button-bar - 表格头部按钮区插槽
|
|
52
53
|
* @slot toolbar - 工具栏插槽
|
|
53
|
-
* @slot default -
|
|
54
|
+
* @slot default - 透传给宿主表格的默认插槽;未显式绑定 `columns` 且表格实例无列时用于声明列,显式传入(含空数组)时以列配置为准
|
|
54
55
|
* @slot table - 表格区域插槽,作用域参数 columns / dataSource / pagination / loading / size / onChange
|
|
55
56
|
*/
|
|
56
57
|
import type {
|
|
@@ -58,14 +59,19 @@ import type {
|
|
|
58
59
|
TableProps,
|
|
59
60
|
ColumnType,
|
|
60
61
|
} from '../../../shared/ui';
|
|
61
|
-
import { Table as ATable, tableProps } from '../../../shared/ui';
|
|
62
|
+
import { Table as ATable, TableColumn, tableProps } from '../../../shared/ui';
|
|
62
63
|
import {
|
|
63
64
|
computed,
|
|
64
65
|
getCurrentInstance,
|
|
65
66
|
nextTick,
|
|
66
67
|
onMounted,
|
|
68
|
+
onUpdated,
|
|
67
69
|
inject,
|
|
70
|
+
ref,
|
|
71
|
+
shallowRef,
|
|
68
72
|
toValue,
|
|
73
|
+
triggerRef,
|
|
74
|
+
watch,
|
|
69
75
|
type Slot,
|
|
70
76
|
} from 'vue';
|
|
71
77
|
import { INJECT_CONFIG } from '../../component-provider/constants';
|
|
@@ -78,6 +84,7 @@ import { useLocale } from '../../../locale';
|
|
|
78
84
|
import type { Path } from '../../../shared/core';
|
|
79
85
|
import {
|
|
80
86
|
camelizeProperties,
|
|
87
|
+
callListeners,
|
|
81
88
|
getObject,
|
|
82
89
|
isObjectItem,
|
|
83
90
|
isPlainObject,
|
|
@@ -91,7 +98,15 @@ import DefaultSearchFormContainer from './DefaultSearchFormContainer.vue';
|
|
|
91
98
|
import DefaultTableContainer from './DefaultTableContainer.vue';
|
|
92
99
|
import SizeControl from './SizeControl.vue';
|
|
93
100
|
import ColumnControl from './ColumnControl.vue';
|
|
94
|
-
import
|
|
101
|
+
import TableEllipsisTooltip from './TableEllipsisTooltip.vue';
|
|
102
|
+
import { useEllipsisTooltip } from '../hooks/useEllipsisTooltip';
|
|
103
|
+
import { useEllipsisTooltipColumns } from '../hooks/useEllipsisTooltipColumns';
|
|
104
|
+
import type {
|
|
105
|
+
Column,
|
|
106
|
+
Columns,
|
|
107
|
+
ColumnControlConfig,
|
|
108
|
+
EllipsisTooltipConfig,
|
|
109
|
+
} from '../types';
|
|
95
110
|
|
|
96
111
|
defineOptions({ name: 'ProTable', inheritAttrs: false });
|
|
97
112
|
|
|
@@ -124,6 +139,8 @@ type Props = {
|
|
|
124
139
|
searchFormConfig?: SearchFormConfig;
|
|
125
140
|
/** 表格区域包裹容器 */
|
|
126
141
|
tableContainer?: ContainerComponent | false;
|
|
142
|
+
/** 表格级省略 Tooltip 配置;truthy 时全局启用 */
|
|
143
|
+
ellipsisTooltip?: boolean | EllipsisTooltipConfig;
|
|
127
144
|
} & /* @vue-ignore */ TableProps;
|
|
128
145
|
|
|
129
146
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -134,6 +151,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
134
151
|
control: undefined,
|
|
135
152
|
searchFormConfig: undefined,
|
|
136
153
|
tableContainer: undefined,
|
|
154
|
+
ellipsisTooltip: undefined,
|
|
137
155
|
});
|
|
138
156
|
|
|
139
157
|
const { t } = useLocale();
|
|
@@ -171,6 +189,8 @@ const searchFormProps = computed(() => {
|
|
|
171
189
|
|
|
172
190
|
const attrs: Record<string, any> = useReactiveAttrs();
|
|
173
191
|
|
|
192
|
+
const rootRef = ref<HTMLElement | null>(null);
|
|
193
|
+
|
|
174
194
|
const size = defineModel<TableProps['size']>('size');
|
|
175
195
|
const loading = defineModel<boolean>('loading', { default: false });
|
|
176
196
|
|
|
@@ -194,16 +214,21 @@ const _search = async () => {
|
|
|
194
214
|
if (props.search && attrs.onSearch) {
|
|
195
215
|
warnSearchConfig('duplicate');
|
|
196
216
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
217
|
+
const results = callListeners(search);
|
|
218
|
+
if (!results.length) return;
|
|
219
|
+
const promises = results.filter(isPromise);
|
|
220
|
+
if (!promises.length) {
|
|
200
221
|
warnSearchConfig('not-promise');
|
|
201
222
|
return;
|
|
202
223
|
}
|
|
203
224
|
const seq = ++searchSequence;
|
|
204
225
|
loading.value = true;
|
|
205
226
|
try {
|
|
206
|
-
await
|
|
227
|
+
const settled = await Promise.allSettled(promises);
|
|
228
|
+
const rejected = settled.find(item => item.status === 'rejected') as
|
|
229
|
+
| PromiseRejectedResult
|
|
230
|
+
| undefined;
|
|
231
|
+
if (rejected) throw rejected.reason;
|
|
207
232
|
} finally {
|
|
208
233
|
if (seq === searchSequence) {
|
|
209
234
|
loading.value = false;
|
|
@@ -238,9 +263,17 @@ type TableSlots = Omit<ComponentSlots<typeof ATable>, 'bodyCell'> &
|
|
|
238
263
|
column: Column<RecordType>;
|
|
239
264
|
}) => void;
|
|
240
265
|
}>;
|
|
241
|
-
type Slots = Omit<TableSlots & CustomSlots, 'default'
|
|
266
|
+
type Slots = Omit<TableSlots & CustomSlots, 'default'> & {
|
|
267
|
+
/** 透传宿主表格的默认插槽,用于以宿主列组件声明列 */
|
|
268
|
+
default?: Slot;
|
|
269
|
+
};
|
|
242
270
|
|
|
243
271
|
const slots = defineSlots<Slots>();
|
|
272
|
+
/* 默认插槽存在性随父级重渲染更新:运行期增删插槽后插槽列模式可感知 */
|
|
273
|
+
const slotsRef = shallowRef<Record<string, any>>(slots);
|
|
274
|
+
onUpdated(() => {
|
|
275
|
+
triggerRef(slotsRef);
|
|
276
|
+
});
|
|
244
277
|
const forwardedSlotNames = () =>
|
|
245
278
|
(Object.keys(slots) as Array<Extract<keyof Slots, string>>).filter(
|
|
246
279
|
name => !customSlotNames.includes(name as any)
|
|
@@ -318,25 +351,6 @@ const effectiveSize = computed<TableProps['size']>(() => {
|
|
|
318
351
|
return size.value ?? injectAttrs.value.size;
|
|
319
352
|
});
|
|
320
353
|
|
|
321
|
-
/*
|
|
322
|
-
* 下发给宿主表格的属性:Provider 注入为底(其不可注入键已在注入层剔除),调用方绑定与 v-model 透传值覆盖同键。
|
|
323
|
-
* size / loading 为声明模型,其 onUpdate:* 回写通道不进入 attrs,无需另行剔除
|
|
324
|
-
*/
|
|
325
|
-
const computedTableProps = computed<TableProps>(() => {
|
|
326
|
-
const merged = {
|
|
327
|
-
...injectAttrs.value,
|
|
328
|
-
...omitBy(
|
|
329
|
-
pick(camelAttrs.value, tableProperties),
|
|
330
|
-
value => value === undefined
|
|
331
|
-
),
|
|
332
|
-
} as Record<string, any>;
|
|
333
|
-
// onChange 由模板监听器收口,散落 attrs 中的同名监听器并入会形成重复回调
|
|
334
|
-
delete merged.onChange;
|
|
335
|
-
merged.size = effectiveSize.value;
|
|
336
|
-
merged.loading = undefined;
|
|
337
|
-
return merged as TableProps;
|
|
338
|
-
});
|
|
339
|
-
|
|
340
354
|
const computedTableContainer = computed(() => {
|
|
341
355
|
const container =
|
|
342
356
|
props.tableContainer ??
|
|
@@ -369,53 +383,125 @@ const indexColumn = computed<ColumnType>(() => ({
|
|
|
369
383
|
customRender: ({ index }) => getIndex(index),
|
|
370
384
|
}));
|
|
371
385
|
|
|
386
|
+
/** 插槽序号列的单元格渲染:按有效分页偏移行号 */
|
|
387
|
+
const slotIndexRender = ({ index }: { index: number }) => getIndex(index);
|
|
388
|
+
|
|
372
389
|
// 列来源优先级:显式绑定 > table 实例(useTable 创建的 columns);显式列原样透传,不补齐 key
|
|
373
|
-
const
|
|
374
|
-
|
|
390
|
+
const columnSource = computed<any[]>(() => {
|
|
391
|
+
return (resolveOwnedProp('columns') ??
|
|
375
392
|
toValue(props.table?.columns) ??
|
|
376
|
-
[]) as any;
|
|
377
|
-
const needIndex = props.addIndexColumn ?? injectProps.value.addIndexColumn;
|
|
378
|
-
return needIndex ? [indexColumn.value, ...cols] : cols;
|
|
393
|
+
[]) as any[];
|
|
379
394
|
});
|
|
380
395
|
|
|
396
|
+
/* 无列源且存在默认插槽时由宿主消费插槽列,序号列改由插槽内的列组件提供 */
|
|
397
|
+
const slotColumnsMode = computed(
|
|
398
|
+
() =>
|
|
399
|
+
!hasExplicitColumns.value &&
|
|
400
|
+
Boolean(slotsRef.value.default) &&
|
|
401
|
+
columnSource.value.length === 0
|
|
402
|
+
);
|
|
403
|
+
const indexEnabled = computed(() =>
|
|
404
|
+
Boolean(props.addIndexColumn ?? injectProps.value.addIndexColumn)
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const computedColumns = computed<Columns<RecordType>>(() => {
|
|
408
|
+
if (slotColumnsMode.value) return [];
|
|
409
|
+
return indexEnabled.value
|
|
410
|
+
? [indexColumn.value, ...columnSource.value]
|
|
411
|
+
: columnSource.value;
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const ellipsisTooltipValue = computed(
|
|
415
|
+
() => props.ellipsisTooltip ?? injectProps.value.ellipsisTooltip
|
|
416
|
+
);
|
|
417
|
+
const ellipsisTooltipConfig = computed<EllipsisTooltipConfig>(() =>
|
|
418
|
+
typeof ellipsisTooltipValue.value === 'object' && ellipsisTooltipValue.value
|
|
419
|
+
? ellipsisTooltipValue.value
|
|
420
|
+
: {}
|
|
421
|
+
);
|
|
422
|
+
const ellipsisTooltipGlobal = computed(() =>
|
|
423
|
+
Boolean(ellipsisTooltipValue.value)
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
const {
|
|
427
|
+
columns: visibleComputedColumns,
|
|
428
|
+
hasTooltip: ellipsisTooltipEnabled,
|
|
429
|
+
resolveOriginalColumn,
|
|
430
|
+
resolveSorterColumn,
|
|
431
|
+
} = useEllipsisTooltipColumns(
|
|
432
|
+
() => computedColumns.value,
|
|
433
|
+
() => ellipsisTooltipGlobal.value
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
/* 宿主列绑定:插槽列模式下不下发列配置,其余模式始终以列配置为准 */
|
|
437
|
+
const hostColumns = computed(() =>
|
|
438
|
+
slotColumnsMode.value ? undefined : visibleComputedColumns.value
|
|
439
|
+
);
|
|
440
|
+
|
|
381
441
|
/*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
442
|
+
* 下发给宿主表格的属性:Provider 注入为底(其不可注入键已在注入层剔除),调用方绑定与 v-model 透传值覆盖同键。
|
|
443
|
+
* size / loading 为声明模型,其 onUpdate:* 回写通道不进入 attrs,无需另行剔除
|
|
384
444
|
*/
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
ancestors.add(column);
|
|
403
|
-
const filtered = filterVisibleColumns(column.children, ancestors);
|
|
404
|
-
ancestors.delete(column);
|
|
405
|
-
if (filtered.changed) {
|
|
406
|
-
changed = true;
|
|
407
|
-
list.push({ ...column, children: filtered.list });
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
list.push(column);
|
|
445
|
+
const computedTableProps = computed<TableProps>(() => {
|
|
446
|
+
const merged = {
|
|
447
|
+
...injectAttrs.value,
|
|
448
|
+
...omitBy(
|
|
449
|
+
pick(camelAttrs.value, tableProperties),
|
|
450
|
+
value => value === undefined
|
|
451
|
+
),
|
|
452
|
+
} as Record<string, any>;
|
|
453
|
+
// onChange 由模板监听器收口,散落 attrs 中的同名监听器并入会形成重复回调
|
|
454
|
+
delete merged.onChange;
|
|
455
|
+
// resizeColumn 回传转换列时,用户回调仍操作原始列
|
|
456
|
+
const resizeColumn = merged.onResizeColumn;
|
|
457
|
+
if (typeof resizeColumn === 'function') {
|
|
458
|
+
merged.onResizeColumn = (width: number, column: any) =>
|
|
459
|
+
resizeColumn(width, resolveOriginalColumn(column));
|
|
412
460
|
}
|
|
413
|
-
|
|
414
|
-
|
|
461
|
+
merged.size = effectiveSize.value;
|
|
462
|
+
merged.loading = undefined;
|
|
463
|
+
return merged as TableProps;
|
|
464
|
+
});
|
|
415
465
|
|
|
416
|
-
const
|
|
417
|
-
|
|
466
|
+
const ellipsisTooltipContainer = computed(() => {
|
|
467
|
+
const getPopupContainer = ellipsisTooltipConfig.value.getPopupContainer;
|
|
468
|
+
if (!getPopupContainer) return undefined;
|
|
469
|
+
return () => {
|
|
470
|
+
if (typeof document === 'undefined') return undefined;
|
|
471
|
+
const root = rootRef.value ?? document.body;
|
|
472
|
+
return getPopupContainer(root) ?? document.body;
|
|
473
|
+
};
|
|
418
474
|
});
|
|
475
|
+
const ellipsisTooltipDelay = computed(() =>
|
|
476
|
+
Math.max(0, (ellipsisTooltipConfig.value.mouseEnterDelay ?? 0.2) * 1000)
|
|
477
|
+
);
|
|
478
|
+
const ellipsisTooltipLeaveDelay = computed(() =>
|
|
479
|
+
Math.max(0, (ellipsisTooltipConfig.value.mouseLeaveDelay ?? 0.1) * 1000)
|
|
480
|
+
);
|
|
481
|
+
const ellipsisTooltipProps = computed(() => {
|
|
482
|
+
const config = ellipsisTooltipConfig.value;
|
|
483
|
+
const {
|
|
484
|
+
getPopupContainer: _getPopupContainer,
|
|
485
|
+
mouseEnterDelay: _mouseEnterDelay,
|
|
486
|
+
mouseLeaveDelay: _mouseLeaveDelay,
|
|
487
|
+
...rest
|
|
488
|
+
} = config;
|
|
489
|
+
return rest;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const {
|
|
493
|
+
open: ellipsisTooltipOpen,
|
|
494
|
+
content: ellipsisTooltipContent,
|
|
495
|
+
anchorStyle: ellipsisTooltipAnchorStyle,
|
|
496
|
+
onMouseOver: onEllipsisMouseOver,
|
|
497
|
+
onMouseOut: onEllipsisMouseOut,
|
|
498
|
+
close: closeEllipsisTooltip,
|
|
499
|
+
} = useEllipsisTooltip(
|
|
500
|
+
rootRef,
|
|
501
|
+
ellipsisTooltipEnabled,
|
|
502
|
+
ellipsisTooltipDelay,
|
|
503
|
+
ellipsisTooltipLeaveDelay
|
|
504
|
+
);
|
|
419
505
|
|
|
420
506
|
// 数据源优先级:显式绑定 > table 实例(useTable 创建的 dataSource)
|
|
421
507
|
const computedDataSource = computed<RecordType[]>(() => {
|
|
@@ -424,6 +510,9 @@ const computedDataSource = computed<RecordType[]>(() => {
|
|
|
424
510
|
);
|
|
425
511
|
});
|
|
426
512
|
|
|
513
|
+
// 列或数据变化后旧锚点失效,关闭共享 Tooltip
|
|
514
|
+
watch([visibleComputedColumns, computedDataSource], closeEllipsisTooltip);
|
|
515
|
+
|
|
427
516
|
/*
|
|
428
517
|
* 分页优先级:显式绑定 > provider 全局配置 > 默认合成;
|
|
429
518
|
* provider 作为基础配置与 table.pageParam 合并,显式绑定的对象形态在其之上覆盖
|
|
@@ -479,9 +568,19 @@ const onTableChange: TableProps['onChange'] = (...args) => {
|
|
|
479
568
|
);
|
|
480
569
|
if (shadowed.length) warnPaginationShadow(shadowed);
|
|
481
570
|
}
|
|
482
|
-
// change 回放:模板监听器优先,未绑定时回退 Provider
|
|
483
|
-
|
|
484
|
-
|
|
571
|
+
// change 回放:模板监听器优先,未绑定时回退 Provider 全局配置;sorter.column 还原为原始列
|
|
572
|
+
const nextArgs = [...args] as any[];
|
|
573
|
+
const sorterArg = nextArgs[2];
|
|
574
|
+
if (Array.isArray(sorterArg)) {
|
|
575
|
+
nextArgs[2] = sorterArg.map(resolveSorterColumn);
|
|
576
|
+
} else if (sorterArg) {
|
|
577
|
+
nextArgs[2] = resolveSorterColumn(sorterArg);
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
callListeners(attrs.onChange ?? injectProps.value.onChange, ...nextArgs);
|
|
581
|
+
} finally {
|
|
582
|
+
_search();
|
|
583
|
+
}
|
|
485
584
|
};
|
|
486
585
|
|
|
487
586
|
const searchFormVisible = computed(() => {
|
|
@@ -541,7 +640,15 @@ defineExpose({
|
|
|
541
640
|
</script>
|
|
542
641
|
|
|
543
642
|
<template>
|
|
544
|
-
<div
|
|
643
|
+
<div
|
|
644
|
+
ref="rootRef"
|
|
645
|
+
class="pro-table"
|
|
646
|
+
data-pro-table-root
|
|
647
|
+
:class="attrs.class"
|
|
648
|
+
:style="attrs.style"
|
|
649
|
+
@mouseover="onEllipsisMouseOver"
|
|
650
|
+
@mouseout="onEllipsisMouseOut"
|
|
651
|
+
>
|
|
545
652
|
<ContainerFragment
|
|
546
653
|
v-if="searchFormVisible || $slots['search-form']"
|
|
547
654
|
:component="computedSearchFormConfig.container"
|
|
@@ -601,7 +708,7 @@ defineExpose({
|
|
|
601
708
|
class="pro-table_content"
|
|
602
709
|
v-bind="computedTableProps"
|
|
603
710
|
:loading="loading"
|
|
604
|
-
:columns="
|
|
711
|
+
:columns="hostColumns"
|
|
605
712
|
:data-source="computedDataSource"
|
|
606
713
|
:pagination="computedPagination"
|
|
607
714
|
@change="onTableChange"
|
|
@@ -611,8 +718,19 @@ defineExpose({
|
|
|
611
718
|
:key="name"
|
|
612
719
|
#[name]="scoped"
|
|
613
720
|
>
|
|
721
|
+
<template v-if="name === 'default'">
|
|
722
|
+
<TableColumn
|
|
723
|
+
v-if="slotColumnsMode && indexEnabled"
|
|
724
|
+
key="__INDEX__"
|
|
725
|
+
:title="t('table.index')"
|
|
726
|
+
:width="80"
|
|
727
|
+
data-index="__INDEX__"
|
|
728
|
+
:custom-render="slotIndexRender"
|
|
729
|
+
/>
|
|
730
|
+
<slot name="default" v-bind="scoped || {}" />
|
|
731
|
+
</template>
|
|
614
732
|
<slot
|
|
615
|
-
v-if="isSlotRenderable(name, scoped)"
|
|
733
|
+
v-else-if="isSlotRenderable(name, scoped)"
|
|
616
734
|
:name="name"
|
|
617
735
|
v-bind="scoped"
|
|
618
736
|
/>
|
|
@@ -620,6 +738,15 @@ defineExpose({
|
|
|
620
738
|
</ATable>
|
|
621
739
|
</slot>
|
|
622
740
|
</ContainerFragment>
|
|
741
|
+
|
|
742
|
+
<TableEllipsisTooltip
|
|
743
|
+
v-if="ellipsisTooltipEnabled"
|
|
744
|
+
:open="ellipsisTooltipOpen"
|
|
745
|
+
:content="ellipsisTooltipContent"
|
|
746
|
+
:anchor-style="ellipsisTooltipAnchorStyle"
|
|
747
|
+
:container="ellipsisTooltipContainer"
|
|
748
|
+
v-bind="ellipsisTooltipProps"
|
|
749
|
+
/>
|
|
623
750
|
</div>
|
|
624
751
|
</template>
|
|
625
752
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 表格级共享省略 Tooltip
|
|
4
|
+
*
|
|
5
|
+
* @component TableEllipsisTooltip
|
|
6
|
+
* @description 以固定定位锚点承载单个宿主 Tooltip,锚点位置由 ProTable 按悬停单元格更新;
|
|
7
|
+
* 整个表格只渲染一个 Tooltip 实例,实例数量不随单元格数量增长。
|
|
8
|
+
*/
|
|
9
|
+
import { nextTick, ref, useAttrs, watch, type Component } from 'vue';
|
|
10
|
+
import { Tooltip as ATooltip } from '../../../shared/ui';
|
|
11
|
+
import type { EllipsisTooltipAnchorStyle } from '../hooks/useEllipsisTooltip';
|
|
12
|
+
import type { EllipsisTooltipConfig } from '../types';
|
|
13
|
+
|
|
14
|
+
defineOptions({ name: 'TableEllipsisTooltip', inheritAttrs: false });
|
|
15
|
+
|
|
16
|
+
type HostTooltipProps = Omit<
|
|
17
|
+
EllipsisTooltipConfig,
|
|
18
|
+
'getPopupContainer' | 'mouseEnterDelay' | 'mouseLeaveDelay'
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
type Props = {
|
|
22
|
+
open?: boolean;
|
|
23
|
+
content?: string;
|
|
24
|
+
anchorStyle?: EllipsisTooltipAnchorStyle;
|
|
25
|
+
container?: () => HTMLElement | null | undefined;
|
|
26
|
+
} & /* @vue-ignore */ HostTooltipProps;
|
|
27
|
+
|
|
28
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
29
|
+
open: false,
|
|
30
|
+
content: '',
|
|
31
|
+
anchorStyle: () => ({
|
|
32
|
+
position: 'fixed',
|
|
33
|
+
pointerEvents: 'none',
|
|
34
|
+
opacity: 0,
|
|
35
|
+
}),
|
|
36
|
+
container: () => undefined,
|
|
37
|
+
});
|
|
38
|
+
const attrs = useAttrs() as HostTooltipProps;
|
|
39
|
+
const HostTooltip = ATooltip as unknown as Component;
|
|
40
|
+
|
|
41
|
+
type TooltipInstance = {
|
|
42
|
+
forceAlign?: () => void;
|
|
43
|
+
forcePopupAlign?: () => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const tooltipRef = ref<TooltipInstance | null>(null);
|
|
47
|
+
|
|
48
|
+
/** 锚点或内容变化后重算宿主浮层位置 */
|
|
49
|
+
const forceAlign = async () => {
|
|
50
|
+
if (!props.open) return;
|
|
51
|
+
await nextTick();
|
|
52
|
+
const instance = tooltipRef.value;
|
|
53
|
+
(instance?.forceAlign ?? instance?.forcePopupAlign)?.();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
watch(() => props.anchorStyle, forceAlign, { deep: true });
|
|
57
|
+
watch(
|
|
58
|
+
() => props.open,
|
|
59
|
+
value => {
|
|
60
|
+
if (value) forceAlign();
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const getContainerNode = (): HTMLElement | undefined => {
|
|
65
|
+
const container = props.container?.();
|
|
66
|
+
if (container) return container;
|
|
67
|
+
return typeof document !== 'undefined' ? document.body : undefined;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const getPopupContainer = () => getContainerNode() ?? document.body;
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<Teleport :to="getContainerNode() ?? 'body'">
|
|
75
|
+
<HostTooltip
|
|
76
|
+
v-bind="attrs"
|
|
77
|
+
ref="tooltipRef"
|
|
78
|
+
:open="open"
|
|
79
|
+
:title="content"
|
|
80
|
+
:trigger="[]"
|
|
81
|
+
:get-popup-container="getPopupContainer"
|
|
82
|
+
>
|
|
83
|
+
<span
|
|
84
|
+
class="pro-table_ellipsis-tooltip-anchor"
|
|
85
|
+
:style="anchorStyle"
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
/>
|
|
88
|
+
</HostTooltip>
|
|
89
|
+
</Teleport>
|
|
90
|
+
</template>
|