Commit a2ef584a authored by tyyin lan's avatar tyyin lan

refactor(common component): ellipsis-tooltip-text

parent 2258326c
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useTippy } from 'vue-tippy'
import type { Placement } from 'tippy.js'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
// 1. 定义 Props
interface Props {
// 控制最大显示行数
maxLines?: number
// Tippy 弹窗的位置
placement?: Placement
placement?:
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end'
observerPattern?: boolean
}
const { maxLines = 1, placement = 'right' } = defineProps<Props>()
const { maxLines = 1, placement = 'right', observerPattern = false } = defineProps<Props>()
// 2. 获取 DOM 元素的引用
const textContainerRef = ref<HTMLElement | null>(null)
const ellipsisContainerRef = ref<HTMLDivElement | null>(null)
const isToolTipDisabled = ref(true)
// 3. 使用 useTippy 创建 Tippy 实例
// 我们将目标元素(textContainerRef)和配置项传入
const tippy = useTippy(textContainerRef, {
// 将 props 中的 placement 响应式地传递给 tippy
placement: placement,
// 添加一个动画效果,使其过渡更平滑
// animation: 'scale',
animation: 'scale-subtle',
// Tippy 的内容将是元素的完整文本
// 我们在启用它时再设置 content,以确保获取到最新内容
content: () => textContainerRef.value?.textContent || '',
})
// 4. 核心逻辑:检测文本是否溢出
const checkTruncation = () => {
const element = textContainerRef.value
if (!element) return
// 关键判断:当元素的滚动高度大于其可见高度时,说明内容被截断了
const isTruncated = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth
let resizeObserver: ResizeObserver | null = null
if (isTruncated) {
// 如果文本被截断,启用 tippy
tippy.enable()
} else {
// 否则,禁用 tippy
tippy.disable()
}
}
const tooltipContent = computed(() => {
return ellipsisContainerRef.value?.textContent || ''
})
// 5. 使用 ResizeObserver 监听元素尺寸变化
// 这是最健壮的方式,当父容器宽度变化导致溢出状态改变时,能自动更新
let resizeObserver: ResizeObserver | null = null
watch(
() => maxLines,
() => {
nextTick(() => {
checkTruncation()
})
},
)
onMounted(() => {
const element = textContainerRef.value
const element = ellipsisContainerRef.value
if (element) {
// 首次挂载时检查一次
checkTruncation()
// 创建 ResizeObserver 实例
resizeObserver = new ResizeObserver(() => {
// 当元素尺寸变化时,重新检查
checkTruncation()
})
if (observerPattern) {
resizeObserver = new ResizeObserver(() => {
checkTruncation()
})
// 开始监听元素
resizeObserver.observe(element)
resizeObserver.observe(element)
}
}
})
// 6. 组件卸载时清理 Observer
onUnmounted(() => {
if (resizeObserver && textContainerRef.value) {
resizeObserver.unobserve(textContainerRef.value)
if (resizeObserver && ellipsisContainerRef.value) {
resizeObserver.unobserve(ellipsisContainerRef.value)
}
resizeObserver = null
})
// 7. 监听 maxLines 变化,以重新应用样式并检查
// 虽然 CSS v-bind 是响应式的,但 DOM 高度变化需要重新检查
watch(
() => maxLines,
() => {
// 使用 nextTick 确保 DOM 更新后再检查
// 在实践中,因为 CSS 变化会自动触发 ResizeObserver,这一步通常是可选的,
// 但显式添加可以增加代码的健壮性。
checkTruncation()
},
)
function checkTruncation() {
const element = ellipsisContainerRef.value
if (!element) return
// 核心判断逻辑:
// scrollHeight > clientHeight: 垂直方向内容溢出(多行截断)
// scrollWidth > clientWidth: 水平方向内容溢出(单行截断)
const isTruncated = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth
isToolTipDisabled.value = !isTruncated
}
</script>
<template>
<div ref="textContainerRef" class="ellipsis-container">
<slot></slot>
</div>
<el-tooltip :offset="6" :content="tooltipContent" :placement="placement" :disabled="isToolTipDisabled">
<div ref="ellipsisContainerRef" class="ellipsis-container">
<slot></slot>
</div>
</el-tooltip>
</template>
<style lang="css" scoped>
.ellipsis-container {
/* 关键的 CSS,用于实现多行文本溢出省略 */
display: -webkit-box;
-webkit-box-orient: vertical;
/* 要求2:最大宽度随父元素决定 */
width: 100%;
max-width: 100%;
/* 使用 v-bind 将 Vue 的 props 绑定到 CSS 变量 */
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: v-bind('maxLines');
-webkit-box-orient: vertical;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment