实现效果
两行…浮窗提示,多行提示要考虑到低版本火狐以及ie浏览的器的兼容性问题,因此需要引用clamp.js。多行提示封装的是全局组件可保持统一,文件路径components文件夹下。
兼容ie浏览器实现方式
一、clamp.js
export default {
name: 'vue-clamp',
props: {
tag: {
type: String,
default: 'div'
},
autoresize: {
type: Boolean,
default: false
},
maxLines: Number,
maxHeight: [String, Number],
ellipsis: {
type: String,
default: '…'
},
location: {
type: String,
default: 'end',
validator (value) {
return ['start', 'middle', 'end'].indexOf(value) !== -1
}
},
expanded: Boolean
},
data () {
return {
offset: null,
text: this.getText(),
localExpanded: !!this.expanded
}
},
computed: {
clampedText () {
if (this.location === 'start') {
return this.ellipsis + (this.text.slice(0, this.offset) || '').trim()
} else if (this.location === 'middle') {
const split = Math.floor(this.offset / 2)
return (this.text.slice(0, split) || '').trim() + this.ellipsis + (this.text.slice(-split) || '').trim()
}
return (this.text.slice(0, this.offset) || '').trim() + this.ellipsis
},
isClamped () {
if (!this.text) {
return false
}
return this.offset !== this.text.length
},
realText () {
return this.isClamped ? this.clampedText : this.text
},
realMaxHeight () {
if (this.localExpanded) {
return null
}
const { maxHeight } = this
if (!maxHeight) {
return null
}
return typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight
}
},
watch: {
expanded (val) {
this.localExpanded = val
},
localExpanded (val) {
if (val) {
this.clampAt(this.text.length)
} else {
this.update()
}
if (this.expanded !== val) {
this.$emit('update:expanded', val)
}
},
isClamped: {
handler (val) {
this.$nextTick(() => this.$emit('clampchange', val))
},
immediate: true
}
},
mounted () {
this.init()
this.$watch(
(vm) => [vm.maxLines, vm.maxHeight, vm.ellipsis, vm.isClamped].join(),
this.update
)
this.$watch((vm) => [vm.tag, vm.text, vm.autoresize].join(), this.init)
},
updated () {
this.text = this.getText()
this.applyChange()
},
beforeDestroy () {
this.cleanUp()
},
methods: {
init () {
const contents = this.$slots.default
if (!contents) {
return
}
this.offset = this.text.length
this.cleanUp()
if (this.autoresize) {
setTimeout(() => {
this.update()
window.addEventListener("resize", this.update) }, 10)
this.unregisterResizeCallback = () => {
window.removeEventListener("resize",this.update);
}
}
this.update()
},
update () {
if (this.localExpanded) {
return
}
this.applyChange()
if (this.isOverflow() || this.isClamped) {
this.search()
}
},
expand () {
this.localExpanded = true
},
collapse () {
this.localExpanded = false
},
toggle () {
this.localExpanded = !this.localExpanded
},
getLines () {
return Object.keys(
Array.prototype.slice.call(this.$refs.content.getClientRects()).reduce(
(prev, { top, bottom }) => {
const key = `${top}/${bottom}`
if (!prev[key]) {
prev[key] = true
}
return prev
},
{}
)
).length
},
isOverflow () {
if (!this.maxLines && !this.maxHeight) {
return false
}
if (this.maxLines) {
if (this.getLines() > this.maxLines) {
return true
}
}
if (this.maxHeight) {
if (this.$el.scrollHeight > this.$el.offsetHeight) {
return true
}
}
return false
},
getText () {
// Look for the first non-empty text node
const [content] = (this.$slots.default || []).filter(
(node) => !node.tag && !node.isComment
)
return content ? content.text : ''
},
moveEdge (steps) {
this.clampAt(this.offset + steps)
},
clampAt (offset) {
this.offset = offset
this.applyChange()
},
applyChange () {
this.$refs.text.textContent = this.realText
},
stepToFit () {
this.fill()
this.clamp()
},
fill () {
while (
(!this.isOverflow() || this.getLines() < 2) &&
this.offset < this.text.length
) {
this.moveEdge(1)
}
},
clamp () {
while (this.isOverflow() && this.getLines() > 1 && this.offset > 0) {
this.moveEdge(-1)
}
},
search (...range) {
const [from = 0, to = this.offset] = range
if (to - from <= 3) {
this.stepToFit()
return
}
const target = Math.floor((to + from) / 2)
this.clampAt(target)
if (this.isOverflow()) {
this.search(from, target)
} else {
this.search(target, to)
}
},
cleanUp () {
if (this.unregisterResizeCallback) {
this.unregisterResizeCallback()
}
}
},
render (h) {
const contents = [
h(
'span',
this.$isServer
? {}
: {
ref: 'text',
attrs: {
'aria-label': this.text.trim()
}
},
this.$isServer ? this.text : this.realText
)
]
const { expand, collapse, toggle } = this
const scope = {
expand,
collapse,
toggle,
clamped: this.isClamped,
expanded: this.localExpanded
}
const before = this.$scopedSlots.before
? this.$scopedSlots.before(scope)
: this.$slots.before
if (before) {
contents.unshift(...(Array.isArray(before) ? before : [before]))
}
const after = this.$scopedSlots.after
? this.$scopedSlots.after(scope)
: this.$slots.after
if (after) {
contents.push(...(Array.isArray(after) ? after : [after]))
}
const lines = [
h(
'span',
{
style: {
boxShadow: 'transparent 0 0'
},
ref: 'content'
},
contents
)
]
return h(
this.tag,
{
style: {
maxHeight: this.realMaxHeight,
overflow: 'hidden'
}
},
lines
)
}
}
二、在封装的tooltip组件中引用。
<template>
<div ref="content" class="text-tooltip" :style="boxStyle">
<el-tooltip v-if="content || content === 0" class="item" effect="dark" :disabled="!isShowTooltip" :content="contentText" :placement="placement">
<div class="text-content">
<v-clamp ref="spanClamp" :style="pStyle" autoresize :max-lines="2" @clampchange="clampchange">{{ content }}</v-clamp>
<!--<span v-else ref="span" :style="pStyle" class="text-value" v-html="content" />-->
</div>
<div slot="content" :style="tooltipView" class="mult-text-tooltip-popper" v-html="contentText" />
</el-tooltip>
<!--<span v-if="isShowTooltip" :style="{lineHeight: lineHeight + 'px', background: backgroundColor, bottom: paddingHeight + 'px'}" class="over-i">...</span>-->
</div>
</template>
<script>
import VClamp from './components/Clamp'
export default {
name: "MultiLineTooltip",
components: {
VClamp
},
props: {
content: {
type: [Number, String],
default: ""
},
line: {
type: Number,
default: 2
},
lineHeight: {
type: Number,
default: 24
},
paddingHeight: {
type: Number,
default: 8
},
backgroundColor: {
type: String,
default: ""
},
placement: {
type: String,
default: "top-start"
}
},
data() {
return {
contentText: '',
isShowTooltip: false,
boxStyle: {
paddingTop: this.paddingHeight + "px",
paddingBottom: this.paddingHeight + "px"
},
styleObject: {
maxHeight: this.lineHeight * this.line + "px"
},
pStyle: {
lineHeight: this.lineHeight + "px"
},
tooltipView: {
width: '400px'
},
lineNum: 2,
lineHeightNum: 80
}
},
watch: {
content() {
this.$nextTick(() => {
// this.detectionHeight()
})
}
},
mounted() {
this.detectionHeight()
},
methods: {
clampchange($event) {
this.isShowTooltip = $event
if ($event) {
this.detectionHeight()
}
},
detectionHeight() {
if (!this.content && this.content !== 0) {
return
}
this.contentText = this.$refs["spanClamp"].text
// this.isShowTooltip = this.$refs["spanClamp"].isClamped
const contentWidth = this.$refs["content"].offsetWidth
this.tooltipView.width = contentWidth + 'px'
}
}
}
</script>
<style lang="scss" scoped>
.text-tooltip {
display: flex;
position: relative;
}
.text-content {
overflow: hidden;
}
.over-i {
content: "...";
position: absolute;
bottom: 0;
right: 0;
padding-left: 20px;
user-select: none;
pointer-events: none;
/*background: linear-gradient(to right, transparent, #fff 55%);*/
}
.text-value {
word-break: break-all;
overflow: hidden;
white-space: initial;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
</style>
三、全局注册组件
import MultiLineTooltip from '@/components/MultiLineTooltip'
Vue.component('MultiLineTooltip', MultiLineTooltip)
四、在局部模块中使用
<table-comment
:table_getdata.sync="table"
class="table_comment"
@handleSizeChange="handleSizeChange"
@handleCurrentChange="handleCurrentChange"
>
<template v-slot:title="slotProps">
<div @click="handleDetail(slotProps.row)">
//使用全局封装的提示组件
<multi-line-tooltip
:content="slotProps.row.title"
class="title-view"
>
<!-- <span class="data-title" >{{ slotProps.row.innnerTitle }}</span> -->
</multi-line-tooltip>
</div>
</template>
</table-comment>
二、不兼容ie浏览器多行提示
- 实现多行…效果
.text-value { word-break: break-all; overflow: hidden; white-space: initial; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
2.去掉clamp其他是实现方式一样