@qin-ui/antd-vue-pro 2.5.3 → 2.6.0
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/types/index.d.ts +1 -1
- package/es/components/component-provider/types/index.d.ts.map +1 -1
- package/es/components/table/components/ProTable.vue.d.ts +3 -1
- 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 +46 -2
- package/es/components/table/types/index.d.ts.map +1 -1
- package/es/index.js +438 -87
- package/es/index.js.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/types/index.ts +1 -0
- package/src/components/table/components/ProTable.vue +128 -56
- 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 +139 -0
- package/src/components/table/index.ts +7 -1
- package/src/components/table/types/index.ts +62 -2
- package/src/shared/ui/index.ts +2 -0
package/es/shared/ui/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { FormProps as _FormProps, Table, TableProps as _TableProps } from 'ant-design-vue';
|
|
2
2
|
import { ComponentProps } from 'vue-component-type-helpers';
|
|
3
3
|
import { Options } from 'scroll-into-view-if-needed';
|
|
4
|
-
export { theme, Form, FormItem, Row as Grid, Col as GridItem, Table, Space, MenuDivider, Dropdown, Menu, MenuItem, Checkbox, Button, Cascader, CheckboxGroup, DatePicker, Input, InputNumber, InputPassword, InputSearch, RadioGroup, Select, Slider, Switch, Textarea, TimePicker, Transfer, TreeSelect, RangePicker, type FormItemInstance, type FormInstance, type RowProps as GridProps, type ColProps as GridItemProps, type FormItemProps, type PaginationProps, } from 'ant-design-vue';
|
|
4
|
+
export { theme, Form, FormItem, Row as Grid, Col as GridItem, Table, Space, MenuDivider, Dropdown, Menu, MenuItem, Checkbox, Button, Cascader, CheckboxGroup, DatePicker, Input, InputNumber, InputPassword, InputSearch, RadioGroup, Select, Slider, Switch, Textarea, TimePicker, Transfer, TreeSelect, RangePicker, Tooltip, type FormItemInstance, type FormInstance, type RowProps as GridProps, type ColProps as GridItemProps, type FormItemProps, type PaginationProps, } from 'ant-design-vue';
|
|
5
5
|
export { type ColumnType, tableProps } from 'ant-design-vue/es/table';
|
|
6
|
+
export type { TooltipProps } from 'ant-design-vue';
|
|
6
7
|
export { type RangePickerProps } from 'ant-design-vue/es/date-picker';
|
|
7
8
|
export { useConfigContextInject } from 'ant-design-vue/es/config-provider/context';
|
|
8
9
|
export { useInjectDisabled, useProviderDisabled, } from 'ant-design-vue/es/config-provider/DisabledContext';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,IAAI,UAAU,EACvB,KAAK,EACL,UAAU,IAAI,WAAW,EAC1B,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EACL,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,GAAG,IAAI,IAAI,EACX,GAAG,IAAI,QAAQ,EACf,KAAK,EACL,KAAK,EACL,WAAW,EACX,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,aAAa,EACb,UAAU,EACV,KAAK,EACL,WAAW,EACX,aAAa,EACb,WAAW,EACX,UAAU,EACV,MAAM,EACN,MAAM,EACN,MAAM,EACN,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,UAAU,EACV,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,QAAQ,IAAI,SAAS,EAC1B,KAAK,QAAQ,IAAI,aAAa,EAC9B,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,KAAK,UAAU,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACL,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAEzE,oDAAoD;AACpD,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,GAAG;IAC/D,kBAAkB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CAC7C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,0BAA0B,CAAC,EAAE,cAAc,CACzC,OAAO,KAAK,CACb,CAAC,0BAA0B,CAAC,CAAC;CAC/B"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,IAAI,UAAU,EACvB,KAAK,EACL,UAAU,IAAI,WAAW,EAC1B,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EACL,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,GAAG,IAAI,IAAI,EACX,GAAG,IAAI,QAAQ,EACf,KAAK,EACL,KAAK,EACL,WAAW,EACX,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,aAAa,EACb,UAAU,EACV,KAAK,EACL,WAAW,EACX,aAAa,EACb,WAAW,EACX,UAAU,EACV,MAAM,EACN,MAAM,EACN,MAAM,EACN,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,UAAU,EACV,WAAW,EACX,OAAO,EACP,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,QAAQ,IAAI,SAAS,EAC1B,KAAK,QAAQ,IAAI,aAAa,EAC9B,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,KAAK,UAAU,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACtE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,mDAAmD,CAAC;AAC3D,OAAO,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACL,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAEzE,oDAAoD;AACpD,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,GAAG;IAC/D,kBAAkB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CAC7C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,0BAA0B,CAAC,EAAE,cAAc,CACzC,OAAO,KAAK,CACb,CAAC,0BAA0B,CAAC,CAAC;CAC/B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qin-ui/antd-vue-pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "二次封装antd vue组件",
|
|
5
5
|
"bin": {
|
|
6
6
|
"antd-vue-pro": "./bin/init-ai.mjs"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"scroll-into-view-if-needed": "^3.1.0",
|
|
46
46
|
"vue-component-type-helpers": "^3.2.5",
|
|
47
|
-
"@qin-ui/pro-components-core": "^2.1.
|
|
47
|
+
"@qin-ui/pro-components-core": "^2.1.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^24",
|
|
@@ -46,6 +46,7 @@
|
|
|
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 - 表格头部按钮区插槽
|
|
@@ -65,7 +66,9 @@ import {
|
|
|
65
66
|
nextTick,
|
|
66
67
|
onMounted,
|
|
67
68
|
inject,
|
|
69
|
+
ref,
|
|
68
70
|
toValue,
|
|
71
|
+
watch,
|
|
69
72
|
type Slot,
|
|
70
73
|
} from 'vue';
|
|
71
74
|
import { INJECT_CONFIG } from '../../component-provider/constants';
|
|
@@ -91,7 +94,15 @@ import DefaultSearchFormContainer from './DefaultSearchFormContainer.vue';
|
|
|
91
94
|
import DefaultTableContainer from './DefaultTableContainer.vue';
|
|
92
95
|
import SizeControl from './SizeControl.vue';
|
|
93
96
|
import ColumnControl from './ColumnControl.vue';
|
|
94
|
-
import
|
|
97
|
+
import TableEllipsisTooltip from './TableEllipsisTooltip.vue';
|
|
98
|
+
import { useEllipsisTooltip } from '../hooks/useEllipsisTooltip';
|
|
99
|
+
import { useEllipsisTooltipColumns } from '../hooks/useEllipsisTooltipColumns';
|
|
100
|
+
import type {
|
|
101
|
+
Column,
|
|
102
|
+
Columns,
|
|
103
|
+
ColumnControlConfig,
|
|
104
|
+
EllipsisTooltipConfig,
|
|
105
|
+
} from '../types';
|
|
95
106
|
|
|
96
107
|
defineOptions({ name: 'ProTable', inheritAttrs: false });
|
|
97
108
|
|
|
@@ -124,6 +135,8 @@ type Props = {
|
|
|
124
135
|
searchFormConfig?: SearchFormConfig;
|
|
125
136
|
/** 表格区域包裹容器 */
|
|
126
137
|
tableContainer?: ContainerComponent | false;
|
|
138
|
+
/** 表格级省略 Tooltip 配置;truthy 时全局启用 */
|
|
139
|
+
ellipsisTooltip?: boolean | EllipsisTooltipConfig;
|
|
127
140
|
} & /* @vue-ignore */ TableProps;
|
|
128
141
|
|
|
129
142
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -134,6 +147,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
134
147
|
control: undefined,
|
|
135
148
|
searchFormConfig: undefined,
|
|
136
149
|
tableContainer: undefined,
|
|
150
|
+
ellipsisTooltip: undefined,
|
|
137
151
|
});
|
|
138
152
|
|
|
139
153
|
const { t } = useLocale();
|
|
@@ -171,6 +185,8 @@ const searchFormProps = computed(() => {
|
|
|
171
185
|
|
|
172
186
|
const attrs: Record<string, any> = useReactiveAttrs();
|
|
173
187
|
|
|
188
|
+
const rootRef = ref<HTMLElement | null>(null);
|
|
189
|
+
|
|
174
190
|
const size = defineModel<TableProps['size']>('size');
|
|
175
191
|
const loading = defineModel<boolean>('loading', { default: false });
|
|
176
192
|
|
|
@@ -318,25 +334,6 @@ const effectiveSize = computed<TableProps['size']>(() => {
|
|
|
318
334
|
return size.value ?? injectAttrs.value.size;
|
|
319
335
|
});
|
|
320
336
|
|
|
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
337
|
const computedTableContainer = computed(() => {
|
|
341
338
|
const container =
|
|
342
339
|
props.tableContainer ??
|
|
@@ -378,45 +375,93 @@ const computedColumns = computed<Columns<RecordType>>(() => {
|
|
|
378
375
|
return needIndex ? [indexColumn.value, ...cols] : cols;
|
|
379
376
|
});
|
|
380
377
|
|
|
378
|
+
const ellipsisTooltipValue = computed(
|
|
379
|
+
() => props.ellipsisTooltip ?? injectProps.value.ellipsisTooltip
|
|
380
|
+
);
|
|
381
|
+
const ellipsisTooltipConfig = computed<EllipsisTooltipConfig>(() =>
|
|
382
|
+
typeof ellipsisTooltipValue.value === 'object' && ellipsisTooltipValue.value
|
|
383
|
+
? ellipsisTooltipValue.value
|
|
384
|
+
: {}
|
|
385
|
+
);
|
|
386
|
+
const ellipsisTooltipGlobal = computed(() =>
|
|
387
|
+
Boolean(ellipsisTooltipValue.value)
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const {
|
|
391
|
+
columns: visibleComputedColumns,
|
|
392
|
+
hasTooltip: ellipsisTooltipEnabled,
|
|
393
|
+
resolveOriginalColumn,
|
|
394
|
+
resolveSorterColumn,
|
|
395
|
+
} = useEllipsisTooltipColumns(
|
|
396
|
+
() => computedColumns.value,
|
|
397
|
+
() => ellipsisTooltipGlobal.value
|
|
398
|
+
);
|
|
399
|
+
|
|
381
400
|
/*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
401
|
+
* 下发给宿主表格的属性:Provider 注入为底(其不可注入键已在注入层剔除),调用方绑定与 v-model 透传值覆盖同键。
|
|
402
|
+
* size / loading 为声明模型,其 onUpdate:* 回写通道不进入 attrs,无需另行剔除
|
|
384
403
|
*/
|
|
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);
|
|
404
|
+
const computedTableProps = computed<TableProps>(() => {
|
|
405
|
+
const merged = {
|
|
406
|
+
...injectAttrs.value,
|
|
407
|
+
...omitBy(
|
|
408
|
+
pick(camelAttrs.value, tableProperties),
|
|
409
|
+
value => value === undefined
|
|
410
|
+
),
|
|
411
|
+
} as Record<string, any>;
|
|
412
|
+
// onChange 由模板监听器收口,散落 attrs 中的同名监听器并入会形成重复回调
|
|
413
|
+
delete merged.onChange;
|
|
414
|
+
// resizeColumn 回传转换列时,用户回调仍操作原始列
|
|
415
|
+
const resizeColumn = merged.onResizeColumn;
|
|
416
|
+
if (typeof resizeColumn === 'function') {
|
|
417
|
+
merged.onResizeColumn = (width: number, column: any) =>
|
|
418
|
+
resizeColumn(width, resolveOriginalColumn(column));
|
|
412
419
|
}
|
|
413
|
-
|
|
414
|
-
|
|
420
|
+
merged.size = effectiveSize.value;
|
|
421
|
+
merged.loading = undefined;
|
|
422
|
+
return merged as TableProps;
|
|
423
|
+
});
|
|
415
424
|
|
|
416
|
-
const
|
|
417
|
-
|
|
425
|
+
const ellipsisTooltipContainer = computed(() => {
|
|
426
|
+
const getPopupContainer = ellipsisTooltipConfig.value.getPopupContainer;
|
|
427
|
+
if (!getPopupContainer) return undefined;
|
|
428
|
+
return () => {
|
|
429
|
+
if (typeof document === 'undefined') return undefined;
|
|
430
|
+
const root = rootRef.value ?? document.body;
|
|
431
|
+
return getPopupContainer(root) ?? document.body;
|
|
432
|
+
};
|
|
433
|
+
});
|
|
434
|
+
const ellipsisTooltipDelay = computed(() =>
|
|
435
|
+
Math.max(0, (ellipsisTooltipConfig.value.mouseEnterDelay ?? 0.2) * 1000)
|
|
436
|
+
);
|
|
437
|
+
const ellipsisTooltipLeaveDelay = computed(() =>
|
|
438
|
+
Math.max(0, (ellipsisTooltipConfig.value.mouseLeaveDelay ?? 0.1) * 1000)
|
|
439
|
+
);
|
|
440
|
+
const ellipsisTooltipProps = computed(() => {
|
|
441
|
+
const config = ellipsisTooltipConfig.value;
|
|
442
|
+
const {
|
|
443
|
+
getPopupContainer: _getPopupContainer,
|
|
444
|
+
mouseEnterDelay: _mouseEnterDelay,
|
|
445
|
+
mouseLeaveDelay: _mouseLeaveDelay,
|
|
446
|
+
...rest
|
|
447
|
+
} = config;
|
|
448
|
+
return rest;
|
|
418
449
|
});
|
|
419
450
|
|
|
451
|
+
const {
|
|
452
|
+
open: ellipsisTooltipOpen,
|
|
453
|
+
content: ellipsisTooltipContent,
|
|
454
|
+
anchorStyle: ellipsisTooltipAnchorStyle,
|
|
455
|
+
onMouseOver: onEllipsisMouseOver,
|
|
456
|
+
onMouseOut: onEllipsisMouseOut,
|
|
457
|
+
close: closeEllipsisTooltip,
|
|
458
|
+
} = useEllipsisTooltip(
|
|
459
|
+
rootRef,
|
|
460
|
+
ellipsisTooltipEnabled,
|
|
461
|
+
ellipsisTooltipDelay,
|
|
462
|
+
ellipsisTooltipLeaveDelay
|
|
463
|
+
);
|
|
464
|
+
|
|
420
465
|
// 数据源优先级:显式绑定 > table 实例(useTable 创建的 dataSource)
|
|
421
466
|
const computedDataSource = computed<RecordType[]>(() => {
|
|
422
467
|
return (
|
|
@@ -424,6 +469,9 @@ const computedDataSource = computed<RecordType[]>(() => {
|
|
|
424
469
|
);
|
|
425
470
|
});
|
|
426
471
|
|
|
472
|
+
// 列或数据变化后旧锚点失效,关闭共享 Tooltip
|
|
473
|
+
watch([visibleComputedColumns, computedDataSource], closeEllipsisTooltip);
|
|
474
|
+
|
|
427
475
|
/*
|
|
428
476
|
* 分页优先级:显式绑定 > provider 全局配置 > 默认合成;
|
|
429
477
|
* provider 作为基础配置与 table.pageParam 合并,显式绑定的对象形态在其之上覆盖
|
|
@@ -479,8 +527,15 @@ const onTableChange: TableProps['onChange'] = (...args) => {
|
|
|
479
527
|
);
|
|
480
528
|
if (shadowed.length) warnPaginationShadow(shadowed);
|
|
481
529
|
}
|
|
482
|
-
// change 回放:模板监听器优先,未绑定时回退 Provider
|
|
483
|
-
|
|
530
|
+
// change 回放:模板监听器优先,未绑定时回退 Provider 全局配置;sorter.column 还原为原始列
|
|
531
|
+
const nextArgs = [...args] as any[];
|
|
532
|
+
const sorterArg = nextArgs[2];
|
|
533
|
+
if (Array.isArray(sorterArg)) {
|
|
534
|
+
nextArgs[2] = sorterArg.map(resolveSorterColumn);
|
|
535
|
+
} else if (sorterArg) {
|
|
536
|
+
nextArgs[2] = resolveSorterColumn(sorterArg);
|
|
537
|
+
}
|
|
538
|
+
(attrs.onChange ?? injectProps.value.onChange)?.(...nextArgs);
|
|
484
539
|
_search();
|
|
485
540
|
};
|
|
486
541
|
|
|
@@ -541,7 +596,15 @@ defineExpose({
|
|
|
541
596
|
</script>
|
|
542
597
|
|
|
543
598
|
<template>
|
|
544
|
-
<div
|
|
599
|
+
<div
|
|
600
|
+
ref="rootRef"
|
|
601
|
+
class="pro-table"
|
|
602
|
+
data-pro-table-root
|
|
603
|
+
:class="attrs.class"
|
|
604
|
+
:style="attrs.style"
|
|
605
|
+
@mouseover="onEllipsisMouseOver"
|
|
606
|
+
@mouseout="onEllipsisMouseOut"
|
|
607
|
+
>
|
|
545
608
|
<ContainerFragment
|
|
546
609
|
v-if="searchFormVisible || $slots['search-form']"
|
|
547
610
|
:component="computedSearchFormConfig.container"
|
|
@@ -620,6 +683,15 @@ defineExpose({
|
|
|
620
683
|
</ATable>
|
|
621
684
|
</slot>
|
|
622
685
|
</ContainerFragment>
|
|
686
|
+
|
|
687
|
+
<TableEllipsisTooltip
|
|
688
|
+
v-if="ellipsisTooltipEnabled"
|
|
689
|
+
:open="ellipsisTooltipOpen"
|
|
690
|
+
:content="ellipsisTooltipContent"
|
|
691
|
+
:anchor-style="ellipsisTooltipAnchorStyle"
|
|
692
|
+
:container="ellipsisTooltipContainer"
|
|
693
|
+
v-bind="ellipsisTooltipProps"
|
|
694
|
+
/>
|
|
623
695
|
</div>
|
|
624
696
|
</template>
|
|
625
697
|
|
|
@@ -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>
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
onBeforeUnmount,
|
|
3
|
+
onDeactivated,
|
|
4
|
+
onMounted,
|
|
5
|
+
ref,
|
|
6
|
+
watch,
|
|
7
|
+
type Ref,
|
|
8
|
+
} from 'vue';
|
|
9
|
+
|
|
10
|
+
/** 省略单元格标记属性,由列转换写入 body 单元格 */
|
|
11
|
+
export const ELLIPSIS_TOOLTIP_CELL_ATTR = 'data-pro-table-ellipsis';
|
|
12
|
+
|
|
13
|
+
/** ProTable 根节点归属标记 */
|
|
14
|
+
export const PRO_TABLE_ROOT_ATTR = 'data-pro-table-root';
|
|
15
|
+
|
|
16
|
+
/** 共享 Tooltip 锚点样式 */
|
|
17
|
+
export type EllipsisTooltipAnchorStyle = {
|
|
18
|
+
position: 'fixed';
|
|
19
|
+
pointerEvents: 'none';
|
|
20
|
+
opacity: number;
|
|
21
|
+
left?: string;
|
|
22
|
+
top?: string;
|
|
23
|
+
width?: string;
|
|
24
|
+
height?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** 共享 Tooltip 状态与事件处理器 */
|
|
28
|
+
export type EllipsisTooltipState = {
|
|
29
|
+
/** Tooltip 是否展示 */
|
|
30
|
+
open: Ref<boolean>;
|
|
31
|
+
/** Tooltip 文本 */
|
|
32
|
+
content: Ref<string>;
|
|
33
|
+
/** 固定定位锚点样式 */
|
|
34
|
+
anchorStyle: Ref<EllipsisTooltipAnchorStyle>;
|
|
35
|
+
/** 根节点 mouseover 处理器 */
|
|
36
|
+
onMouseOver: (event: MouseEvent) => void;
|
|
37
|
+
/** 根节点 mouseout 处理器 */
|
|
38
|
+
onMouseOut: (event: MouseEvent) => void;
|
|
39
|
+
/** 关闭 Tooltip */
|
|
40
|
+
close: () => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** 共享 Tooltip 锚点的基础样式:固定定位,不参与命中测试与绘制 */
|
|
44
|
+
const ANCHOR_BASE_STYLE: EllipsisTooltipAnchorStyle = {
|
|
45
|
+
position: 'fixed',
|
|
46
|
+
pointerEvents: 'none',
|
|
47
|
+
opacity: 0,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** 读取单元格可读文本,连续空白折叠为单个空格 */
|
|
51
|
+
const getCellText = (cell: HTMLElement) => {
|
|
52
|
+
const text = cell.innerText || cell.textContent || '';
|
|
53
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** 单元格测量结果:文本与是否横向溢出 */
|
|
57
|
+
type CellMeasurement = {
|
|
58
|
+
text: string;
|
|
59
|
+
overflow: boolean;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** 单次读取单元格文本并判断横向溢出 */
|
|
63
|
+
const measureCell = (cell: HTMLElement): CellMeasurement => {
|
|
64
|
+
const text = getCellText(cell);
|
|
65
|
+
return {
|
|
66
|
+
text,
|
|
67
|
+
overflow: Boolean(text) && cell.scrollWidth > cell.clientWidth,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 表格级共享省略 Tooltip
|
|
73
|
+
*
|
|
74
|
+
* @description
|
|
75
|
+
* 在 ProTable 根节点上以事件委托识别带标记的 body 单元格,维护单个 Tooltip 的显隐、文本与固定定位锚点;
|
|
76
|
+
* 仅在单元格文本横向溢出时触发;表格滚动、窗口尺寸变化、KeepAlive 停用时关闭,卸载时移除监听。
|
|
77
|
+
*
|
|
78
|
+
* @param rootRef - ProTable 根节点引用
|
|
79
|
+
* @param enabled - 是否启用共享 Tooltip;为 false 时不注册全局监听
|
|
80
|
+
* @param delay - 悬停延迟(ms),0 表示立即展示
|
|
81
|
+
* @param leaveDelay - 移出延迟(ms),0 表示立即关闭
|
|
82
|
+
* @returns Tooltip 状态与根节点事件处理器
|
|
83
|
+
*/
|
|
84
|
+
export function useEllipsisTooltip(
|
|
85
|
+
rootRef: Ref<HTMLElement | null | undefined>,
|
|
86
|
+
enabled: Ref<boolean>,
|
|
87
|
+
delay: Ref<number>,
|
|
88
|
+
leaveDelay: Ref<number>
|
|
89
|
+
): EllipsisTooltipState {
|
|
90
|
+
const open = ref(false);
|
|
91
|
+
const content = ref('');
|
|
92
|
+
const anchorStyle = ref<EllipsisTooltipAnchorStyle>({
|
|
93
|
+
...ANCHOR_BASE_STYLE,
|
|
94
|
+
});
|
|
95
|
+
let activeCell: HTMLElement | null = null;
|
|
96
|
+
let pendingCell: HTMLElement | null = null;
|
|
97
|
+
let openTimer: ReturnType<typeof setTimeout> | null = null;
|
|
98
|
+
let closeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
99
|
+
|
|
100
|
+
const clearOpenTimer = () => {
|
|
101
|
+
if (openTimer === null) return;
|
|
102
|
+
clearTimeout(openTimer);
|
|
103
|
+
openTimer = null;
|
|
104
|
+
pendingCell = null;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const clearCloseTimer = () => {
|
|
108
|
+
if (closeTimer === null) return;
|
|
109
|
+
clearTimeout(closeTimer);
|
|
110
|
+
closeTimer = null;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const close = () => {
|
|
114
|
+
clearOpenTimer();
|
|
115
|
+
clearCloseTimer();
|
|
116
|
+
open.value = false;
|
|
117
|
+
activeCell = null;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const getEventRoot = (node: Node | null) => {
|
|
121
|
+
const element = node instanceof Element ? node : node?.parentElement;
|
|
122
|
+
return element?.closest?.(`[${PRO_TABLE_ROOT_ATTR}]`) ?? null;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const getCellFromEvent = (event: MouseEvent) => {
|
|
126
|
+
const target = event.target as Element | null;
|
|
127
|
+
const cell = target?.closest?.(`[${ELLIPSIS_TOOLTIP_CELL_ATTR}]`);
|
|
128
|
+
if (!(cell instanceof HTMLElement)) return null;
|
|
129
|
+
const root = rootRef.value;
|
|
130
|
+
if (!root) return null;
|
|
131
|
+
if (cell.closest(`[${PRO_TABLE_ROOT_ATTR}]`) !== root) return null;
|
|
132
|
+
return cell;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const onMouseOver = (event: MouseEvent) => {
|
|
136
|
+
const cell = getCellFromEvent(event);
|
|
137
|
+
if (!cell) return;
|
|
138
|
+
clearCloseTimer();
|
|
139
|
+
if (cell === activeCell || cell === pendingCell) return;
|
|
140
|
+
close();
|
|
141
|
+
const measurement = measureCell(cell);
|
|
142
|
+
if (!measurement.overflow) return;
|
|
143
|
+
const rect = cell.getBoundingClientRect();
|
|
144
|
+
if (!rect.width || !rect.height) return;
|
|
145
|
+
content.value = measurement.text;
|
|
146
|
+
anchorStyle.value = {
|
|
147
|
+
...ANCHOR_BASE_STYLE,
|
|
148
|
+
left: `${rect.left}px`,
|
|
149
|
+
top: `${rect.top}px`,
|
|
150
|
+
width: `${rect.width}px`,
|
|
151
|
+
height: `${rect.height}px`,
|
|
152
|
+
};
|
|
153
|
+
pendingCell = cell;
|
|
154
|
+
openTimer = setTimeout(
|
|
155
|
+
() => {
|
|
156
|
+
openTimer = null;
|
|
157
|
+
pendingCell = null;
|
|
158
|
+
if (!cell.isConnected) return;
|
|
159
|
+
activeCell = cell;
|
|
160
|
+
open.value = true;
|
|
161
|
+
},
|
|
162
|
+
Math.max(0, delay.value)
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const onMouseOut = (event: MouseEvent) => {
|
|
167
|
+
if (!activeCell && !pendingCell) return;
|
|
168
|
+
|
|
169
|
+
const owner = activeCell ?? pendingCell;
|
|
170
|
+
const target = event.target as Node | null;
|
|
171
|
+
if (!owner || !target || !owner.contains(target)) return;
|
|
172
|
+
|
|
173
|
+
const related = event.relatedTarget as Node | null;
|
|
174
|
+
if (related && owner.contains(related)) {
|
|
175
|
+
const relatedRoot = getEventRoot(related);
|
|
176
|
+
if (!relatedRoot || relatedRoot === rootRef.value) return;
|
|
177
|
+
close();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (pendingCell) {
|
|
182
|
+
close();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
clearCloseTimer();
|
|
187
|
+
const delayMs = Math.max(0, leaveDelay.value);
|
|
188
|
+
if (delayMs === 0) {
|
|
189
|
+
close();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
closeTimer = setTimeout(() => {
|
|
193
|
+
closeTimer = null;
|
|
194
|
+
close();
|
|
195
|
+
}, delayMs);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const onViewportChange = () => close();
|
|
199
|
+
let listening = false;
|
|
200
|
+
|
|
201
|
+
const addViewportListeners = () => {
|
|
202
|
+
if (listening || typeof window === 'undefined') return;
|
|
203
|
+
listening = true;
|
|
204
|
+
window.addEventListener('scroll', onViewportChange, true);
|
|
205
|
+
window.addEventListener('resize', onViewportChange);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const removeViewportListeners = () => {
|
|
209
|
+
if (!listening) return;
|
|
210
|
+
listening = false;
|
|
211
|
+
window.removeEventListener('scroll', onViewportChange, true);
|
|
212
|
+
window.removeEventListener('resize', onViewportChange);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
onDeactivated(close);
|
|
216
|
+
|
|
217
|
+
onMounted(() => {
|
|
218
|
+
if (enabled.value) addViewportListeners();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
watch(enabled, value => {
|
|
222
|
+
if (value) {
|
|
223
|
+
addViewportListeners();
|
|
224
|
+
} else {
|
|
225
|
+
removeViewportListeners();
|
|
226
|
+
close();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
onBeforeUnmount(() => {
|
|
231
|
+
removeViewportListeners();
|
|
232
|
+
clearOpenTimer();
|
|
233
|
+
clearCloseTimer();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
open,
|
|
238
|
+
content,
|
|
239
|
+
anchorStyle,
|
|
240
|
+
onMouseOver,
|
|
241
|
+
onMouseOut,
|
|
242
|
+
close,
|
|
243
|
+
};
|
|
244
|
+
}
|