feat: ellipsis text automatically displays tooltip based on ellipsis (#6244)

* feat: ellipsis text automatically displays tooltip based on ellipsis

* feat: ellipsis text automatically displays tooltip based on ellipsis

---------

Co-authored-by: sqchen <9110848@qq.com>
Co-authored-by: sqchen <chenshiqi@sshlx.com>
This commit is contained in:
panda7 2025-05-23 15:20:38 +08:00 committed by GitHub
parent 11b2b5bcc2
commit a2bdcd6e49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 110 additions and 2 deletions

View File

@ -26,6 +26,12 @@ outline: deep
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" /> <DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
## 自动显示 tooltip
通过`tooltip-when-ellipsis`设置,仅在文本长度超出导致省略号出现时才触发 tooltip。
<DemoPreview dir="demos/vben-ellipsis-text/auto-display" />
## API ## API
### Props ### Props
@ -37,6 +43,8 @@ outline: deep
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` | | maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` | | placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
| tooltip | 启用文本提示 | `boolean` | `true` | | tooltip | 启用文本提示 | `boolean` | `true` |
| tooltipWhenEllipsis | 内容超出,自动启用文本提示 | `boolean` | `false` |
| ellipsisThreshold | 设置 tooltipWhenEllipsis 后才生效,文本截断检测的像素差异阈值,越大则判断越严格,如果碰见异常情况可以自己设置阈值 | `number` | `3` |
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - | | tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
| tooltipColor | 提示文本的颜色 | `string` | - | | tooltipColor | 提示文本的颜色 | `string` | - |
| tooltipFontSize | 提示文本的大小 | `string` | - | | tooltipFontSize | 提示文本的大小 | `string` | - |

View File

@ -0,0 +1,16 @@
<script lang="ts" setup>
import { EllipsisText } from '@vben/common-ui';
const text = `
Vben Admin 是一个基于 Vue3.0Vite TypeScript 的后台解决方案目标是为开发中大型项目提供开箱即用的解决方案
`;
</script>
<template>
<EllipsisText :line="2" :tooltip-when-ellipsis="true">
{{ text }}
</EllipsisText>
<EllipsisText :line="3" :tooltip-when-ellipsis="true">
{{ text }}
</EllipsisText>
</template>

View File

@ -1,7 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import { computed, ref, watchEffect } from 'vue'; import {
computed,
onBeforeUnmount,
onMounted,
onUpdated,
ref,
watchEffect,
} from 'vue';
import { VbenTooltip } from '@vben-core/shadcn-ui'; import { VbenTooltip } from '@vben-core/shadcn-ui';
@ -33,6 +40,16 @@ interface Props {
* @default true * @default true
*/ */
tooltip?: boolean; tooltip?: boolean;
/**
* 是否只在文本被截断时显示提示框
* @default false
*/
tooltipWhenEllipsis?: boolean;
/**
* 文本截断检测的像素差异阈值越大则判断越严格
* @default 3
*/
ellipsisThreshold?: number;
/** /**
* 提示框背景颜色优先级高于 overlayStyle * 提示框背景颜色优先级高于 overlayStyle
*/ */
@ -62,12 +79,15 @@ const props = withDefaults(defineProps<Props>(), {
maxWidth: '100%', maxWidth: '100%',
placement: 'top', placement: 'top',
tooltip: true, tooltip: true,
tooltipWhenEllipsis: false,
ellipsisThreshold: 3,
tooltipBackgroundColor: '', tooltipBackgroundColor: '',
tooltipColor: '', tooltipColor: '',
tooltipFontSize: 14, tooltipFontSize: 14,
tooltipMaxWidth: undefined, tooltipMaxWidth: undefined,
tooltipOverlayStyle: () => ({ textAlign: 'justify' }), tooltipOverlayStyle: () => ({ textAlign: 'justify' }),
}); });
const emit = defineEmits<{ expandChange: [boolean] }>(); const emit = defineEmits<{ expandChange: [boolean] }>();
const textMaxWidth = computed(() => { const textMaxWidth = computed(() => {
@ -79,9 +99,67 @@ const textMaxWidth = computed(() => {
const ellipsis = ref(); const ellipsis = ref();
const isExpand = ref(false); const isExpand = ref(false);
const defaultTooltipMaxWidth = ref(); const defaultTooltipMaxWidth = ref();
const isEllipsis = ref(false);
const { width: eleWidth } = useElementSize(ellipsis); const { width: eleWidth } = useElementSize(ellipsis);
//
const checkEllipsis = () => {
if (!ellipsis.value || !props.tooltipWhenEllipsis) return;
const element = ellipsis.value;
const originalText = element.textContent || '';
const originalTrimmed = originalText.trim();
// false
if (!originalTrimmed) {
isEllipsis.value = false;
return;
}
const widthDiff = element.scrollWidth - element.clientWidth;
const heightDiff = element.scrollHeight - element.clientHeight;
// 使 tooltip
isEllipsis.value =
props.line === 1
? widthDiff > props.ellipsisThreshold
: heightDiff > props.ellipsisThreshold;
};
// 使 ResizeObserver
let resizeObserver: null | ResizeObserver = null;
onMounted(() => {
if (typeof ResizeObserver !== 'undefined' && props.tooltipWhenEllipsis) {
resizeObserver = new ResizeObserver(() => {
checkEllipsis();
});
if (ellipsis.value) {
resizeObserver.observe(ellipsis.value);
}
}
//
checkEllipsis();
});
// 使onUpdated
onUpdated(() => {
if (props.tooltipWhenEllipsis) {
checkEllipsis();
}
});
onBeforeUnmount(() => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
watchEffect( watchEffect(
() => { () => {
if (props.tooltip && eleWidth.value) { if (props.tooltip && eleWidth.value) {
@ -91,9 +169,13 @@ watchEffect(
}, },
{ flush: 'post' }, { flush: 'post' },
); );
function onExpand() { function onExpand() {
isExpand.value = !isExpand.value; isExpand.value = !isExpand.value;
emit('expandChange', isExpand.value); emit('expandChange', isExpand.value);
if (props.tooltipWhenEllipsis) {
checkEllipsis();
}
} }
function handleExpand() { function handleExpand() {
@ -110,7 +192,9 @@ function handleExpand() {
color: tooltipColor, color: tooltipColor,
backgroundColor: tooltipBackgroundColor, backgroundColor: tooltipBackgroundColor,
}" }"
:disabled="!props.tooltip || isExpand" :disabled="
!props.tooltip || isExpand || (props.tooltipWhenEllipsis && !isEllipsis)
"
:side="placement" :side="placement"
> >
<slot name="tooltip"> <slot name="tooltip">