1、需求:项目中有好几处用到标签组,有单行、多行、单行显示不下省略(鼠标放上去提示)等等需求,为了方便使用提取为组件。
2、在项目的components文件下新建文件夹TagList,文件下三个文件(index.vue、tag-atom.less、TagEllipsis.vue)。
①index.vue对应的代码:
<template>
<div
ref="tagListRef"
class="tag-list row-main"
:class="{ 'no-wrap': !isMulLines }"
:style="styleByLines()"
>
<!-- 最多展示maxLength -->
<tag-ellipsis
v-for="(tag, idx) in tagList.slice(0, maxCount)"
:key="idx"
:text="tagProps.text && tagProps.text(tag)"
:num="tagProps.num && tagProps.num(tag)"
:numStyle="tagProps.numStyle && tagProps.numStyle(tag)"
:tagStyle="tagProps.tagStyle && tagProps.tagStyle(tag)"
@click.native="$emit('tag-click', idx)"
/>
<!-- 多出maxCount个则悬浮显示 -->
<template v-if="tagList.length > maxCount">
<el-tooltip
popper-class="more-tooltip"
enterable
>
<!-- 悬浮content -->
<ul slot="content">
<li
v-for="(tag, idx) in tagList.slice(maxCount)"
:key="idx"
>
<tag-ellipsis
:text="tagProps.text && tagProps.text(tag)"
:num="tagProps.num && tagProps.num(tag)"
:numStyle="tagProps.numStyle && tagProps.numStyle(tag)"
:tagStyle="tagProps.tagStyle && tagProps.tagStyle(tag)"
@click="$emit('tag-click')"
/>
</li>
</ul>
<!-- 本体 显示剩余多出的的个数 -->
<span class="more-hover">
<span class="tag-count">
+
{{ tagList.length - maxCount }}
</span>
</span>
</el-tooltip>
</template>
</div>
</template>
<script>
import TagEllipsis from "./TagEllipsis"
export default {
components: {
TagEllipsis,
},
props: {
tagList: {
type: Array,
default: () => [],
},
tagProps: {
type: Object,
default: () => {},
},
// 响应防抖 默认不防抖 大于0时显示效果不佳
debounce: {
type: Number,
default: 0,
},
// 限制行数
lines: {
type: Number,
default: null,
},
},
data () {
return {
maxCount: Infinity,
}
},
computed: {
isMulLines () {
return this.lines && this.lines >= 2
},
},
created () {
this.$nextTick(() => this.setObserver(true))
},
beforeDestroy () {
this.setObserver(false)
},
methods: {
// # style
styleByLines () {
return this.isMulLines
? // 多行
{
display: "-webkit-box",
overflow: "hidden",
"-webkit-box-orient": "vertical",
"-webkit-line-clamp": this.lines,
"white-space": "break-spaces",
}
: // 单行
{
"white-space": "nowrap",
}
},
// # main
onResize (target, entries) {
// 重置maxCount
this.maxCount = Infinity
this.$forceUpdate()
// 容器 宽度 高度
const wrapperWidth = Math.ceil(entries[0].contentRect.width)
const wrapperHeight = Math.ceil(entries[0].contentRect.height)
// 修正maxCount 使+N出现
this.$nextTick(() => {
const overflowNodeIndex = Array.from(target.childNodes).findIndex(
// 条件为:元素右边距(即元素的左侧偏移+元素的宽)溢出父容器
tagNode =>
tagNode.offsetLeft + tagNode.clientWidth > wrapperWidth ||
tagNode.offsetTop + tagNode.clientHeight > wrapperHeight,
)
if (overflowNodeIndex !== -1) {
this.maxCount = overflowNodeIndex
this.$forceUpdate()
}
})
},
// # utils
debounceFunc (fn, wait) {
var timer = null
return () => {
if (timer !== null) clearTimeout(timer)
timer = setTimeout(fn, wait)
}
},
// # init
setObserver (isAdd) {
// dom
const target = this.$refs.tagListRef
// 定义监听器
const resizeObserver = new ResizeObserver(
!this.debounce
? entries => this.onResize(target, entries)
: entries =>
this.debounceFunc(
() => this.onResize(target, entries),
this.debounce,
)(),
)
// 创建 or 销毁
isAdd ? resizeObserver.observe(target) : resizeObserver.disconnect()
},
},
}
</script>
<style lang="less" scoped>
@import "./tag-atom.less";
</style>
<style lang="less">
@space: 6px;
// 悬浮 内容
body .more-tooltip {
box-shadow: 0 4px 10px 0 rgba(107, 119, 158, 0.3);
border-radius: 2px;
&.el-tooltip__popper.is-light {
width: 200px;
padding: 10px;
background: #ffffff;
}
ul li {
&:not(:last-of-type) {
margin-bottom: @space;
}
.tag {
margin: 0;
border-radius: 2px;
.tag-count {
font-weight: 500;
}
}
}
}
</style>
②tag-atom.less对应的代码:
@space: 6px;
.tag-space {
margin: 0 @space @space 0;
}
.tag-space-no-wrap {
margin-right: @space;
}
// 省略号
.text-ellipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
// # 单个标签
.tag-ellipsis {
display: inline-block;
height: 22px;
padding: 0 8px;
border-radius: 2px;
line-height: 22px;
background: rgba(19, 19, 20, 0.6);
color: #6b779e;
cursor: pointer;
&--num {
font-weight: 700;
}
}
// # 标签列表
.tag-list.row-main {
position: relative;
min-height: 24px;
margin-right: 12px;
width: 100%;
// height: 100%;
// 多行
.tag-ellipsis {
.tag-space();
}
// 不换行
&.no-wrap {
min-height: 32px;
.tag-ellipsis {
.tag-space-no-wrap();
}
}
}
③TagEllipsis.vue对应的代码:
<template>
<!-- 限制max最大字数时 -->
<span
v-if="max !== false"
class="tag-ellipsis"
:title="text.length > max ? completeStr : ''"
:style="tagStyle"
>
<!-- 单个标签最多max个字 -->
<span>{{
`${text.slice(0, max)}${text.length > max ? "..." : ""}${
hasNumber ? ":" : ""
}`
}}</span>
<span
class="tag-ellipsis--num"
:style="numStyle"
>{{ num }}</span>
</span>
<!-- 无限制时 -->
<span
v-else
class="tag-ellipsis"
:style="tagStyle"
>
{{ completeStr }}
</span>
</template>
<script>
export default {
props: {
text: {
type: [String, Number],
default: "",
},
num: {
type: [String, Number],
default: "",
},
numStyle: {
type: [Object],
default: () => {},
},
tagStyle: {
type: [Object],
default: () => {},
},
max: {
type: [Number, Boolean],
default: 10,
},
primary: {
type: String,
default: "#2080F7",
},
},
computed: {
hasNumber () {
return ![undefined, null, ""].includes(this.num)
},
completeStr () {
return this.text + (this.num ? ":" + this.num : "")
},
},
}
</script>
<style lang="less" scoped>
@import "./tag-atom.less";
</style>
3、在vue文件中适用:
import TagList from "@/components/TagList"
<tag-list
style="min-height: 24px !important;"
:tagList="item.userLabels.split(',')"
:lines="1"
:tagProps="{
text: tag => tag,
tagStyle: () => ({
color: '#FF5959',
background: '#fff2f2',
}),
}"
/>