最近遇到一个需求,记录一下,前置条件是后端数据返回一个数组,类似于 ['itemName1', 'itemName2'....'itemNameN'] 这样,但是前端在表格渲染的时候需求如下:
需求是【根据屏幕大小显示n个item名称,显示不开的后边追加显示“等X(数量)项“并且鼠标悬浮时浮框显示所有item名称】
先放一下最终的效果,可以比较直观地理解:
然后~在开发的时候在表格里放了个组件⬇️,把截取操作的函数写在组件里
组件内代码⬇️
<template>
<div class="text-ellipsis-container">
<!-- 带有tooltip的版本 -->
<el-tooltip v-if="showTooltip" effect="dark" :content="tooltipContent" placement="top">
<div :id="ellipsisId" class="ellipsis-content">{{ ellipsisContent }}</div>
</el-tooltip>
<!-- 不带有tooltip的版本 -->
<div v-else :id="ellipsisId" class="ellipsis-content">{{ ellipsisContent }}</div>
</div>
</template>
<script>
import resize from './mixins/resize'
export default {
name: '',
components: {},
mixins: [resize],
props: {
// 要处理为省略形式的数据
data: {
type: Array,
default: () => []
},
// 拆分为字符串后的间隔符号
join: {
type: String,
default: '、'
},
// 是否显示tooltip
showTooltip: {
type: Boolean,
default: true
},
// 字号大小,用于后续计算
fontSize: {
type: Number,
default: 14
}
},
data() {
return {
ellipsisId: 'text_ellipsis_' + Math.random().toString(36).substring(2), // id
ellipsisContent: '' // 省略后的内容
}
},
computed: {
// tooltip显示的内容
tooltipContent() {
if (Array.isArray(this.data) && this.data.length > 0) {
return this.data.join(this.join)
} else {
return ''
}
}
},
watch: {
// 要处理为省略形式的数据
data(newVal, oldVal) {
if (newVal) {
// 按规则处理数据
this.handleEllipsisData()
}
}
},
mounted() {
// 按规则处理数据
this.handleEllipsisData()
},
methods: {
/**
* @description 按规则处理数据,返回需要的结果
*/
handleEllipsisData() {
if (Array.isArray(this.data) && this.data.length > 0) {
const textString = this.data.join(this.join) // 根据数组拼成的字符串
let textStringResult = '' // 处理后的用于显示的最终字符串
// 继续处理字符串, 根据容器宽度截取内容
this.$nextTick(() => {
const node = document.getElementById(this.ellipsisId) // 显示内容的容器
const nodeWidth = node.offsetWidth // 容器宽度
// 计算当前容器宽度下能显示多少字
let fit = textString.length // 初始化可容纳字数
const span = document.createElement('span')
span.style.whiteSpace = 'nowrap'
span.style.fontFamily = 'Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif'
span.style.fontSize = this.fontSize + 'px'
document.body.appendChild(span)
for (let i = 0; i <= fit; i++) {
span.innerHTML = textString.substr(0, i) + `等${this.data.length}项`
if (span.offsetWidth > nodeWidth) {
// 除后缀外可容纳字数
fit = i - 1
// 先截取能显示下的字符串内容
textStringResult = textString.substr(0, fit) + `等${this.data.length}项`
// 因为截取的字符串中可能存在不完整的项,所以还需要再处理一下(例如:权限一、权限二、权等x项 => 权限一、权限二等x项)
if (textStringResult.includes(this.join)) {
textStringResult = textStringResult.substring(0, textStringResult.lastIndexOf(this.join)) + `等${this.data.length}项`
} else {
// 如果第一项名字特别长,截取后连第一项都显示不全,就加入省略号
// 这里“fit - 2”是因为后面加了个省略号占用2个汉字字符宽度
textStringResult = textStringResult.substring(0, fit - 2) + `……等${this.data.length}项`
}
break
} else if (span.offsetWidth <= nodeWidth && i === fit) {
// 如果内容一直没超过容器宽度,且是最后一次循环的话
textStringResult = textString
}
}
document.body.removeChild(span)
this.ellipsisContent = textStringResult
})
} else if (Array.isArray(this.data) && this.data.length === 0) {
return ''
} else {
return JSON.parse(JSON.stringify(this.data))
}
}
}
}
</script>
<style lang="scss" scoped>
.text-ellipsis-container {
.ellipsis-content {
// width: 100%;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
}
}
</style>
最后一步是考虑浏览器宽度改变或者sideBar收缩时导致的表格区域宽度变化,要加一下对resize事件的监听,这个写在了mixins里⬇️,然后在自己写的这个组件里使用mixins就可以啦
import { debounce } from '@/utils/utils'
export default {
data() {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted() {
this.$_resizeHandler = debounce(() => {
this.$nextTick(() => {
// 按规则处理数据
this.handleEllipsisData()
})
}, 100)
this.$_initResizeEvent()
this.$_initSidebarResizeEvent()
},
beforeDestroy() {
this.$_destroyResizeEvent()
this.$_destroySidebarResizeEvent()
},
// to fixed bug when cached by keep-alive
// https://github.com/PanJiaChen/vue-element-admin/issues/2116
activated() {
this.$_initResizeEvent()
this.$_initSidebarResizeEvent()
},
deactivated() {
this.$_destroyResizeEvent()
this.$_destroySidebarResizeEvent()
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_initResizeEvent() {
window.addEventListener('resize', this.$_resizeHandler)
},
$_destroyResizeEvent() {
window.removeEventListener('resize', this.$_resizeHandler)
},
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
$_initSidebarResizeEvent() {
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
$_destroySidebarResizeEvent() {
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
}
}
}