Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
H
hxyj-admin-fe
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
hxyj
hxyj-admin-fe
Commits
a2ef584a
Commit
a2ef584a
authored
Jun 13, 2025
by
tyyin lan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor(common component): ellipsis-tooltip-text
parent
2258326c
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
57 additions
and
73 deletions
+57
-73
ellipsis-tooltip-text.vue
src/components/ellipsis-tooltip-text.vue
+57
-73
No files found.
src/components/ellipsis-tooltip-text.vue
View file @
a2ef584a
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
onMounted
,
onUnmounted
,
watch
}
from
'vue'
import
{
computed
,
nextTick
,
onMounted
,
onUnmounted
,
ref
,
watch
}
from
'vue'
import
{
useTippy
}
from
'vue-tippy'
import
type
{
Placement
}
from
'tippy.js'
// 1. 定义 Props
interface
Props
{
interface
Props
{
// 控制最大显示行数
// 控制最大显示行数
maxLines
?:
number
maxLines
?:
number
// Tippy 弹窗的位置
// 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
ellipsisContainerRef
=
ref
<
HTMLDivElement
|
null
>
(
null
)
const
textContainerRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
isToolTipDisabled
=
ref
(
true
)
// 3. 使用 useTippy 创建 Tippy 实例
let
resizeObserver
:
ResizeObserver
|
null
=
null
// 我们将目标元素(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
if
(
isTruncated
)
{
const
tooltipContent
=
computed
(()
=>
{
// 如果文本被截断,启用 tippy
return
ellipsisContainerRef
.
value
?.
textContent
||
''
tippy
.
enable
()
})
}
else
{
// 否则,禁用 tippy
tippy
.
disable
()
}
}
// 5. 使用 ResizeObserver 监听元素尺寸变化
watch
(
// 这是最健壮的方式,当父容器宽度变化导致溢出状态改变时,能自动更新
()
=>
maxLines
,
let
resizeObserver
:
ResizeObserver
|
null
=
null
()
=>
{
nextTick
(()
=>
{
checkTruncation
()
})
},
)
onMounted
(()
=>
{
onMounted
(()
=>
{
const
element
=
text
ContainerRef
.
value
const
element
=
ellipsis
ContainerRef
.
value
if
(
element
)
{
if
(
element
)
{
// 首次挂载时检查一次
checkTruncation
()
checkTruncation
()
// 创建 ResizeObserver 实例
if
(
observerPattern
)
{
resizeObserver
=
new
ResizeObserver
(()
=>
{
resizeObserver
=
new
ResizeObserver
(()
=>
{
// 当元素尺寸变化时,重新检查
checkTruncation
()
checkTruncation
()
})
})
// 开始监听元素
resizeObserver
.
observe
(
element
)
resizeObserver
.
observe
(
element
)
}
}
}
})
})
// 6. 组件卸载时清理 Observer
onUnmounted
(()
=>
{
onUnmounted
(()
=>
{
if
(
resizeObserver
&&
text
ContainerRef
.
value
)
{
if
(
resizeObserver
&&
ellipsis
ContainerRef
.
value
)
{
resizeObserver
.
unobserve
(
text
ContainerRef
.
value
)
resizeObserver
.
unobserve
(
ellipsis
ContainerRef
.
value
)
}
}
resizeObserver
=
null
resizeObserver
=
null
})
})
// 7. 监听 maxLines 变化,以重新应用样式并检查
function
checkTruncation
()
{
// 虽然 CSS v-bind 是响应式的,但 DOM 高度变化需要重新检查
const
element
=
ellipsisContainerRef
.
value
watch
(
if
(
!
element
)
return
()
=>
maxLines
,
()
=>
{
// 核心判断逻辑:
// 使用 nextTick 确保 DOM 更新后再检查
// scrollHeight > clientHeight: 垂直方向内容溢出(多行截断)
// 在实践中,因为 CSS 变化会自动触发 ResizeObserver,这一步通常是可选的,
// scrollWidth > clientWidth: 水平方向内容溢出(单行截断)
// 但显式添加可以增加代码的健壮性。
const
isTruncated
=
element
.
scrollHeight
>
element
.
clientHeight
||
element
.
scrollWidth
>
element
.
clientWidth
checkTruncation
()
},
isToolTipDisabled
.
value
=
!
isTruncated
)
}
</
script
>
</
script
>
<
template
>
<
template
>
<div
ref=
"textContainerRef"
class=
"ellipsis-container"
>
<el-tooltip
:offset=
"6"
:content=
"tooltipContent"
:placement=
"placement"
:disabled=
"isToolTipDisabled"
>
<slot></slot>
<div
ref=
"ellipsisContainerRef"
class=
"ellipsis-container"
>
</div>
<slot></slot>
</div>
</el-tooltip>
</
template
>
</
template
>
<
style
lang=
"css"
scoped
>
<
style
lang=
"css"
scoped
>
.ellipsis-container
{
.ellipsis-container
{
/* 关键的 CSS,用于实现多行文本溢出省略 */
display
:
-webkit-box
;
display
:
-webkit-box
;
-webkit-box-orient
:
vertical
;
min-width
:
0
;
/* 要求2:最大宽度随父元素决定 */
width
:
100%
;
max-width
:
100%
;
/* 使用 v-bind 将 Vue 的 props 绑定到 CSS 变量 */
overflow
:
hidden
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
text-overflow
:
ellipsis
;
-webkit-line-clamp
:
v-bind
(
'maxLines'
);
-webkit-line-clamp
:
v-bind
(
'maxLines'
);
-webkit-box-orient
:
vertical
;
}
}
</
style
>
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment