Vue自定义Tag标签

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',
    }),
  }"
/>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值