封装vue基于element的select多选时启用鼠标悬停折叠文字以tooltip显示具体所选值

这篇具有很好参考价值的文章主要介绍了封装vue基于element的select多选时启用鼠标悬停折叠文字以tooltip显示具体所选值。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

相信很多公司的前端开发人员都会选择使用vue+element-ui的形式来开发公司的管理后台系统,基于element-ui很丰富的组件生态,我们可以很快速的开发管理后台系统的页面(管理后台系统的页面也不复杂,大多都是分页查询类需求和增删改查)。但一个前端框架有优点,就必然会有一些缺点或bug存在,element-ui框架也不例外,甚至elementui框架的缺点或bug还很多,这里就不一一列举了,相信使用它的我们都心知肚明。

今天,本篇文章就针对element-ui的一个组件——select选择器进行一些改进,以达到我们实际的项目开发中想要实现的一个效果,或者说完善该组件的一些功能。当然了,还是在select选择器的基础上改进,不会对它的源代码做任何修改。

那么,具体做什么改进呢?就是我们文章标题所说的“select选择器多选时启用鼠标悬停折叠文字以tooltip显示具体所选值”。如果你没有做过这样的需求,或者没有听说过这样的效果,那你听起来可能会觉得有点绕,不过没关系,来张效果图你就知道了:

封装vue基于element的select多选时启用鼠标悬停折叠文字以tooltip显示具体所选值
封装vue基于element的select多选时启用鼠标悬停折叠文字以tooltip显示具体所选值

这种效果呢,element plus已经实现了,但它实现的效果不太好看,而且我们公司用的也不是element plus,而是element2,element2的select选择器又没有这样的实现,所以我就参考网上的大神们的资料自己用js写了一个这样的效果:

select多选时启用鼠标悬停折叠文字以tooltip显示具体所选值collapseTagsTooltip.js:

export default {
  // 最多显示多少个tag
  props: {
    maxTagCount: {
      type: Number,
      default: 1
    },
  },
  data() {
    // 创建数字展示的tag
    let countDom = document.createElement("span")
    countDom.className = "jy-ui-collapse-tag"

    return {
      domSelectTags: null,
      domSelect: null,
      countDom,
      toolTip: null,
      toolTipArr: []
    };
  },
  watch: {
    '$attrs.value'(v) {
      this.afterChange(v)
    },
  },
  mounted() {
    this.domSelect = this.$refs.select.$el
    this.domSelectTags = this.domSelect.querySelector(".el-select__tags")

    this.domSelectTags && this.domSelect.querySelector(".el-select__tags > span").after(this.countDom)
  },
  methods: {
    querySelectorAll(txt) {
      const selectRefs = this.$refs.select
      if(!selectRefs) return []

      const select = selectRefs.$el

      if (!select) return []
      return select.querySelectorAll(txt)
    },
    // vue 获取元素距离浏览器视口左侧的距离
    offsetLeft(elements) {
      let left = elements.offsetLeft
      let parent = elements.offsetParent
      while (parent != null) {
        left += parent.offsetLeft
        parent = parent.offsetParent
      }
 
      return left
    },
    // vue 获取元素距离浏览器视口顶部的距离
    offsetTop(elements) {
      let top = elements.offsetTop
      let parent = elements.offsetParent
      while (parent != null) {
        top += parent.offsetTop
        parent = parent.offsetParent
      }

      return top
    },
    // 获取当前元素所在的模块的第一个有滚动条的父元素
    parentScroll(elements){
      let dom = null
      let parent = elements.parentNode
      let flag = true

      while (parent != null && flag) {
        const style = this.isDOM(parent) ? this.getStyle(parent, 'overflow-y') : ''
        if(style === 'auto' || style === 'scroll'){
          dom = parent
          flag = false
        }

        parent = parent.parentNode
      }

      return dom
    },
    // 根据className类获取祖先节点
    getParent(elements, className){
      let dom = null
      let parent = elements.parentNode
      let flag = true

      while (parent != null && flag) {
        const _className = this.isDOM(parent) ? parent.className : ''
        if(_className.indexOf(className) > -1){
          dom = parent
          flag = false
        }

        parent = parent.parentNode
      }

      return dom
    },
    async afterChange(value) {
      if (!this.domSelectTags) return

      await this.$nextTick()

      let awaitUntilNodeEqual = () => {
        let { length } = this.querySelectorAll(".el-tag")

        if (length != value.length) {
          requestAnimationFrame(awaitUntilNodeEqual)
          return;
        }

        if (length == 0) {
          this.countDom.style.display = "none"
          this.countDom.innerHTML = 0
        }

        this.handleInsideTags()
      };

      awaitUntilNodeEqual()
    },
    handleInsideTags() {
      // 处理内部节点
      let elTags = Array.from(this.querySelectorAll(".el-tag"))
      
      // toolTip的内容
      this.toolTipArr = []

      elTags.forEach((elTag, index) => {
        if (index >= this.maxTagCount) {
          elTag.style.display = "none"
          this.toolTipArr.push(elTag.innerText)
        } else {
          // 这里不用display = "inline-block",是因为display设置了inline-block后会导致用了align-items: center的样式会失效。
          // display:flex已经block化了。
          elTag.style.display = "flex"
        }
      });

      let elCount = elTags.length

      if (elCount > this.maxTagCount) {
        this.countDom.innerHTML = `+${elCount - this.maxTagCount}`
        this.countDom.style.display = "flex"
        this.countDom.style.alignItems = "center"
      } else {
        this.countDom.style.display = "none"
        this.countDom.innerHTML = 0
      }

      // 鼠标移入collapse-tags,即鼠标移入多选的数字标签时。
      this.countDom.onmouseenter = self => this.mouseenter(self)
      // 鼠标离开collapse-tags,即鼠标离开多选的数字标签时。
      this.countDom.onmouseleave = () => this.mouseleave()
    },
    mouseenter({ target }){
       // 微前端框架里需要被减去的宽度
       let subtractWidth = 0
       let subtractHeight = 0
       let subTop = 0
       if (window.__MICRO_APP_ENVIRONMENT__) { 
        //  let alideNode = document.querySelector('body').querySelector('.d2-theme-container-aside')
         subtractWidth = 0
         subtractHeight = 0
         subTop = 72
       }
      // 创建toolTip元素DOM
      this.toolTip = document.createElement("div")
      this.toolTip.className = "jy-ui-select-tooltip"
      // 创建toolTip中内容的显示元素DOM
      const toolTipContent = document.createElement("span")
      toolTipContent.className = "jy-ui-select-tooltip-content"
      toolTipContent.innerHTML = this.toolTipArr.join(',')
      // 创建toolTip显示时所需的三角形元素DOM
      const arrowBottom = document.createElement("span")
      arrowBottom.className = "jy-ui-select-tooltip-arrow"

      const arrowTop = document.createElement("span")
      arrowTop.className = 'jy-ui-select-tooltip-arrow jy-ui-select-tooltip-arrow-top'

      // 将toolTip中内容的显示元素DOM插入到toolTip元素中
      this.toolTip.appendChild(toolTipContent)
      // 将三角形元素插入到toolTip元素中
      this.toolTip.appendChild(arrowBottom)
      this.toolTip.appendChild(arrowTop)
      // 将toolTip元素插入到body中
      document.querySelector('body').appendChild(this.toolTip)

      // target.offsetLeft - 当前鼠标移入的数字元素距离浏览器视口左侧的距离
      const selectOffsetTop = this.offsetTop(this.domSelect)  // 当前select元素距离浏览时视口顶部的距离
      const targetOffsetLeft = this.offsetLeft(target)  // 当前鼠标移入的数字元素距离浏览器视口左侧的距离
      const slectOffsetHeight = this.domSelect.offsetHeight  // slectOffsetHeight - 当前select元素的高度
      const toolTipOffsetHeight = this.toolTip.offsetHeight  // 当前所要显示的toolTip的高度
      const toolTipOffsetWidth = this.toolTip.offsetWidth  // toolTipOffsetWidth - 当前所要显示的toolTip的宽度
      const targetOffsetWidth = target.offsetWidth  // targetOffsetWidth - 当前鼠标移入的数字元素的宽度

      // toolTip距离浏览器视口左侧的距离
      let leftPos = targetOffsetLeft + (targetOffsetWidth / 2) - (toolTipOffsetWidth / 2)
      // toolTip距离浏览器视口顶部的距离
      let topPos = selectOffsetTop - toolTipOffsetHeight - 5

      // 如果toolTip的上边被浏览器视口遮挡,则将toolTip放置在select的下边
      if(topPos - subTop <= 0){
        topPos = selectOffsetTop + slectOffsetHeight + 5
        arrowBottom.style.display = 'none'
        arrowTop.style.display = 'block'
      }else{
        arrowBottom.style.display = 'block'
        arrowTop.style.display = 'none'
      }

      // 如果toolTip的左边被浏览器视口遮挡,则将toolTip的left置为2
      if(leftPos <= 0){
        leftPos = 2
        let arrowLeftPos = targetOffsetLeft + (targetOffsetWidth / 2)
        arrowLeftPos = Math.floor(arrowLeftPos) + 'px'
        arrowBottom.style.left = arrowLeftPos
        arrowTop.style.left = arrowLeftPos
      }

      // window.pageYOffset: safari获取scrollTop的方法
      const bodyTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset
      const bodyHasScroll = this.getStyle(document.querySelector('body'), 'overflow') === 'hidden'
      const floorBodyTop = Math.floor(bodyTop)

      // 如果是被浏览器的滚动条滚到了视口的顶部,则将toolTip放置在select的下边。其中,bodyTop指的是网页滚动的距离
      if(!bodyHasScroll && (floorBodyTop + toolTipOffsetHeight + 10 + subTop >= selectOffsetTop)){
        topPos = selectOffsetTop + slectOffsetHeight + 5
        arrowBottom.style.display = 'none'
        arrowTop.style.display = 'block'
      }else{
        arrowBottom.style.display = 'block'
        arrowTop.style.display = 'none'
      }

      const parentScroll = this.parentScroll(this.domSelect)
      const parentScrollTop = parentScroll ? parentScroll.scrollTop : 0
      const floorParentScrollTop = Math.floor(parentScrollTop)

      // 弹框的高度缩小到一定程度时,也会出现滚动条,当这个滚动条滚动时,也会影响tooltip的位置。
      // 用this.getParent(this.domSelect, 'el-dialog__wrapper')来获取el-dialog__wrapper,是因为弹窗关闭后,
      // 会在body节点中依旧保留弹窗的节点dom,如果页面中打开的弹窗不止一个,此时要再获取el-dialog__wrapper的scrollTop就不知道要获取哪个弹窗的了。
      const dialogWrapper = this.getParent(this.domSelect, 'el-dialog__wrapper')
      const dialogWrapperScrollTop = dialogWrapper ? Math.floor(dialogWrapper.scrollTop) : 0

      // 如果当前元素的父元素存在滚动条,且body元素的滚动条不存在,说明select组件有可能是在弹窗中且弹窗内可能会发生滚动。
      if(parentScroll && bodyHasScroll){
        topPos = topPos - floorParentScrollTop + floorBodyTop - dialogWrapperScrollTop

        if(floorParentScrollTop + toolTipOffsetHeight + 10 + subTop + dialogWrapperScrollTop >= selectOffsetTop){
          topPos = selectOffsetTop + slectOffsetHeight - floorParentScrollTop - dialogWrapperScrollTop + floorBodyTop + 5
          arrowBottom.style.display = 'none'
          arrowTop.style.display = 'block'
        }else{
          arrowBottom.style.display = 'block'
          arrowTop.style.display = 'none'
        }
      }else if(parentScroll && !bodyHasScroll){
        // 如果当前元素的父元素存在滚动条,且body元素的滚动条存在,说明select组件可能只是在页面中且其所在模块的某个父元素可能会发生滚动,
        // 那么这个时候topPos的值其实已经包含了浏览器滚动条的滚动距离了,所以这里就不再加bodyTop了。
        topPos = topPos - floorParentScrollTop
        // 如果浏览器的滚动条和当前元素的某个父元素的滚动条发生了滚动且tooltip被顶部遮挡,则将toolTip放置在select的下边
        if(floorBodyTop + floorParentScrollTop + toolTipOffsetHeight + 10 + subTop >= selectOffsetTop){
          topPos = selectOffsetTop + slectOffsetHeight - floorParentScrollTop + 5
          arrowBottom.style.display = 'none'
          arrowTop.style.display = 'block'
        }else{
          arrowBottom.style.display = 'block'
          arrowTop.style.display = 'none'
        }
      }

      this.toolTip.style.display = 'block'
      this.toolTip.style.left = Math.floor(leftPos) - subtractWidth + 'px'
      this.toolTip.style.top = Math.floor(topPos) - subtractHeight + 'px'

      // 如果toolTip的右边被浏览器视口遮挡,则将toolTip的left置为initial,right置为2
      if(Math.floor(leftPos) + toolTipOffsetWidth >= document.body.offsetWidth){
        this.toolTip.style.left = 'initial'
        this.toolTip.style.right = '2px'

        let arrowRightPos = document.body.offsetWidth - targetOffsetLeft - (targetOffsetWidth / 2) - 18
        arrowRightPos = Math.floor(arrowRightPos) + 'px'

        arrowBottom.style.left = 'initial'
        arrowTop.style.left = 'initial'
        arrowBottom.style.right = arrowRightPos
        arrowTop.style.right = arrowRightPos
      }
    },
    mouseleave(){
      // 鼠标离开多选的数字标签时,删除插入到body中国的toolTip。
      document.querySelector('body').removeChild(this.toolTip)
    },
    // 原生js获取元素的样式
    getStyle(el, name){
      if(window.getComputedStyle){
        return String(getComputedStyle(el).getPropertyValue(name)).trim()
      }else{
        return el.currentStyle[name]
      }
    },
    // 判断当前节点是否是dom节点,如果不判断的话,在使用getComputedStyle时,
    // 会报Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.的错,因为getComputedStyle这个方法只能用在dom节点上。
    isDOM(el) {
      // 首先判断是否支持HTMLELement,如果支持,使用HTMLElement,如果不支持,通过判断DOM的特征,如果拥有这些特征说明就是ODM节点,特征使用的越多越准确
      return (typeof HTMLElement === 'function')
      ? (el instanceof HTMLElement)
      : (el && (typeof el === 'object') && (el.nodeType === 1) && (typeof el.nodeName === 'string'))
    }
  },
}

使用的时候,可能需要你基于element-ui单独封装一个select组件,然后你只需把这个文件引入到你封装的select组件中然后mixins一下就可以了。比如select.vue:

<template>
  <el-select v-model="select" v-bind="{clearable, ...$attrs, collapseTags: false, 'collapse-tags': false}>
    <el-option value="1" label="选项一" />
    <el-option value="2" label="选项二" />
    <el-option value="3" label="选项三" />
  </el-select>
</template>

<script>
import collapseTagsTooltip from './collapseTagsTooltip'

export default {
  mixins: [collapseTagsTooltip],
  data(){
    return {
      select: []
    }
  }
}
</script>

写到这里,其实已经实现了本文所说的效果,但有几个地方需要注意:

1、我们在封装select组件时,要把select组件的 collapse-tags 属性置为false,且即使用户在使用select组件时传入了 collapse-tags 属性也不让他传入的那个属性起作用,为什么呢?其实,仔细看我们的实现逻辑就会知道,collapse-tags 属性为false时会把select多选后的所有选项都放在select框内,这个时候我们就可以获取到除了必须要展示出来的那几个选项之外的其他所有选项(这些其他选中的选项的value就要用tooltip来展示的),然后再把这些选项置为 display: none,剩下的事情就是如何展示这些其他选项的问题了。试想一下,如果 collapse-tags 属性为true时会怎么样?element-ui的select选择器会把第一项展示在select框内,其余选中的节点的dom根本就不会出现在页面的源代码中,这个时候想获取除了选中的第一项的其他的选项,就比较麻烦了,也不是不可以实现,只是会很麻烦,因为你要用选中的key去循环匹配select下拉中与key一一对应的value。有人说这不就是一个双重循环嘛,是,循环一把拿到value没问题,可你有没有想过如果select的下拉数据源是通过远程搜索请求了接口获取的到呢?你要把每一次通过接口获取到的下拉数据源都保存到一个变量中,而且还得去重,去重之后再双重循环去获取到对应的value。有这么麻烦的步骤,直接将collapse-tags 属性置为false去获取剩下的那些选中的项,它不香吗?只是这样其实也有一个问题,比如接口一次性把两三千条下拉数据都吐给了前端,虽然前端可能只取了前100条展示了出来,可如果select组件有搜索的属性 filterable,那么用户就可以通过搜索选中一两千条数据,此时可能就会造成select组件卡顿,因为什么呢?就是因为把 collapse-tags 属性置为了false,导致select组件中其实渲染了一两千个选中的dom节点。不过一下子选中一两千条数据的场景不多,如果你介意这样的问题,那你就把 collapse-tags 属性置为true,然后像之前说的那样去获取需要tooltip展示的数据。

2、我们这里也实现了select中最多展示几个选中项后其余的选中项以tooltip的方式展示,在使用时传入 maxTagCount 属性即可。

3、我们这里实现的效果并不会像element-ui的tooltip文字提示那样可以上下左右、左上左下、右上右下等等展示,只会根据浏览器的视口来上下展示。

4、我们这里实现的效果也可以用在弹窗中,比如弹窗中有select的多选效果,tooltip也是可以正常展示的,并且tooltip的top值会随着弹窗的纵向滚动条的滚动而变化。

5、我们这里实现的效果并不是基于element-ui的tooltip,因为它的tooltip没法用在我们的实现中,我是自己用js实现了一个tooltip。

6、我们这里实现的效果与element-ui的tooltip还不一样的地方是element-ui的tooltip在鼠标离开后会把tooltip的dom节点留在页面的源代码中,而我实现的则是鼠标离开后就在页面的源代码中销毁了tooltip的dom节点。想留下节点,会很麻烦,所以就没有实现。

7、我们这里实现的效果是鼠标悬停在折叠文字上才出现tooltip,并不像网上其他人那样实现的是鼠标悬停在select上就展示tooltip,因为他们是直接在select的外面套了一个element-ui的 el-tooltip

7、我们这里也实现了select中最多展示几个选中项后其余的选中项以tooltip的方式展示,在使用时传入 maxTagCount 属性即可。文章来源地址https://www.toymoban.com/news/detail-458355.html

到了这里,关于封装vue基于element的select多选时启用鼠标悬停折叠文字以tooltip显示具体所选值的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • element-ui 下拉框选择器selete多选时,单行显示所选内容

    1.只需重写 el-select 原生样式 特别注意:重写原生样式时,去掉当前 style 的 scoped 或者可以通过该穿透去实现

    2024年02月12日
    浏览(43)
  • vue2+ant-design-vue a-select组件二次封装(支持单选/多选添加全选/分页(多选跨页选中)/自定义label)

    参数 说明 类型 默认值 v-model 绑定值 boolean / string / number/Array - mode 设置’multiple’\\\'tags’多选 (显示全选) String - optionSource 下拉数据源 Array - width select宽度(可以设置百分比或px) String 100% customLabel 是否自定义设置下拉label String - valueKey 传入的 option 数组中,要作为最终选择

    2024年02月08日
    浏览(50)
  • < element-Ui表格组件:表格多选功能回显勾选时因分页问题,导致无法勾选回显的全部数据 >

    在 Vue + elementUi 开发中,elementUI中表格在本身是自带多选功能的,但是在某些情况下,并不能完全适用,甚至可能产生bug。例如本次案例所遇Bug,情景如下: 本案例场景 :在表单中, 通过表单参数筛选某个明细表格数据 ,后端要求新增时可多选明细表格中的内容。但由于明

    2024年02月09日
    浏览(58)
  • uniapp select 多选选择器封装

    前言:作者想实现的功能类似一个uniapp选择器,但是可以选择多个值,同时又可以单选和全选,在uniapp  的UI框架去找,发现没有类似的,最后在uniapp 的插件市场找到了这个multiple-select   里面的功能比较全实现了单选全选并同时可以选择多个值,还可以禁用某一项数据,自

    2024年02月06日
    浏览(39)
  • element ui多选框(Checkbox 多选框、Select多选框)编辑时无法选中的解决办法

     在上面添加变更事件,然后变更事件中添加this.$forceUpdate();  强制渲染多选框的样式即可 注意: 多选框需要传数组,字符串无法正常渲染,因此表单初始化绑定的v-model需要初始化为空数组[],而编辑页面传值时如果是字符串,需要转数组:         重点就是: this.$forceUpda

    2024年02月05日
    浏览(44)
  • Element-UI el-select多选表单校验问题

    在使用 el-select 多选下拉菜单配置表单校验时, 如果form表单绑定的form对象对应属性值为空字符串或者null(其他未尝试),表单中的多选下拉框会立刻执行校验并弹出校验信息,代码如下: 正确方式如下: 将多选下拉框对应的属性值默认值设置未空数组即可

    2024年02月16日
    浏览(62)
  • element ui中select多选框change选择获取选项的所有字段信息

            在 Element UI 的 Select 组件中, 多选框 的选择变化( change )事件可以通过监听 change 事件来获取选项的所有字段信息。         当多选框选项发生改变时,会触发 change 事件,此时可以通过该事件的回调函数来获取选中的选项的所有字段信息。 示例: html代码: dat

    2024年02月06日
    浏览(44)
  • 关于Element-UI el-select多选表单校验问题

       在使用 el-select 多选下拉菜单配置表单校验时, 如果form表单绑定的form对象对应属性值为空字符串或者null(其他未尝试),表单中的多选下拉框会立刻执行校验并弹出校验信息,代码如下: 正确方式如下: 将多选下拉框对应的属性值默认值设置未空数组即可

    2024年02月11日
    浏览(54)
  • Element-UI el-select 多选菜单换行撑开

    问题描述:           Element-UI el-select 多选菜单换行撑开显示破坏整体样式  问题解决:         添加如下样式:          若出现滚动条样式不好看,可以更改样式,和elementui保持一致。        

    2024年02月16日
    浏览(47)
  • 基于vue+element 分页的封装

    分页也是我们在实际应用当中非常常见的存在,其实分页本身在element中做的就挺好的了,但是使用确实非常的多,所以还是有必要封装一下,主要是为了减少代码的冗余,以及提升开发的效率和降低后续维护的成本。 这是一段普通分页的示例 效果是这样的 在这当中用到了我

    2024年02月15日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包