奇葩功能实现:级联选择框组件el-cascader实现同一级的二级只能单选,但是一级可以多选

这篇具有很好参考价值的文章主要介绍了奇葩功能实现:级联选择框组件el-cascader实现同一级的二级只能单选,但是一级可以多选。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:

其实也不能说这个功能奇葩,做项目碰到这种需求也算合理正常,只是确实没有能直接实现这一需求的现成组件。

el-cascader作为级联选择组件,并不能同时支持一级多选,二级单选的功能,只能要么是单选或者多选。

不过既然产品提了这个需求,皱着眉头也得上啊。

实现:

先说下思路,由于一般来讲级联选择框都只需要提交子级的选项,所以先明确emitPath需要设置为false。multiple设置为true。

有了这两个前提,那怎么做呢?

逻辑当然就是监听change事件,通过动态修改el-cascader组件绑定的值的数据,强行不让勾选同一父级下的二级选项。(但是我这边思路优化了下,每次勾选了同一个二级选项之后,取消之前已经勾选的二级选项,具体看后面的代码)

一开始以为很简单,但是做下来发现,坑啊,是真的坑。

直接上代码吧,代码里多加点注释就看的懂了:

先上html的代码:

        <el-cascader
            :key="cascaderKey"
            ref="cascaderRef"
            style="width: 100%"
            :options="groupOptions"
            :props="{
              value: 'value',
              label: 'label',
              multiple: true,
              emitPath: false,
              checkStrictly: true,
              children: 'children'
            }"
            class="tree-search"
            v-model="devGroupIds"
            @change="(val) => handleCascaderChange(val)"
            clearable
            filterable
            :show-all-levels="false"
            :collapse-tags="true"
          />

这个代码就不多解释了, 需要注明的是这里加了这个key,具体原因后面解释,因为这是这个组件最大的坑。

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug

 继续上js的代码,主要是handleCascaderChange方法:

    // 切换分组
    handleCascaderChange(valueArr) {
      // 判断当前选中的节点的父级节点是否同时存在其他已经勾选的节点
      if (valueArr.length > 1) {
        const newArr = valueArr.filter(t => !this.preSelectedGroupIds.includes(t))
        let indexInAll, indexInCurrent
        const newGroupId = newArr[0] || ''
        let checkedGroupInParentGroup = []
        let oldCheckedGroupIdInParentGroup = ''
        // 找到当前新勾选的分组id
        if (newGroupId) {
          let parentGroup = []
          parentGroup = this.groupOptions.find(group => group.children.some(t => t.value === newGroupId))
          // 通过当前分组id找到所属一级分组
          for (const group of parentGroup.children) {
            if (valueArr.includes(group.value)) {
              checkedGroupInParentGroup.push(group.value) // 找到该一级分组下勾选的二级分组,最多只有两个
            }
            if (checkedGroupInParentGroup.length > 1) {
              break
            }
          }
          // 当前一级分组如果存在多个已经勾选的二级分组
          if (checkedGroupInParentGroup.length > 1) {
            checkedGroupInParentGroup.forEach(t => {
              if (t !== newGroupId) {
                oldCheckedGroupIdInParentGroup = t // 找到一级分组下不是当前勾选分组的id,也就是之前勾选的二级分组id
              }
            })
            // 找到之前勾选的二级分组id在所有已经勾选的二级分组id中的序号和在当前一级分组下所有二级分组忠的序号
            indexInAll = valueArr.findIndex(t => t === oldCheckedGroupIdInParentGroup)
            // indexInCurrent = parentGroup.children.findIndex(t => t.value === oldCheckedGroupIdInParentGroup)
            // this.$nextTick(() => {
            //   let panelId = this.$refs.cascaderRef.panel.$refs.menu[1].$el.id  //其中menu[1]表示右侧的面板 menu[0]即为左侧的面板
            //   let liId = document.getElementById(panelId + '-' + indexInCurrent)
            //   // 之前勾选的二级分组id对应的元素,删除其被勾选的样式
            //   // liId.children[0].click()
            //   liId.children[0].classList.remove('is-checked')
            //   liId.children[0].children[0].classList.remove('is-checked')
            // })
            // 之前勾选的二级分组id需要从已勾选的分组id中移除
            valueArr.splice(indexInAll, 1)
            // 将当前选中的新分组id放到第一位,以便重新打开时,可以默认打开面板时打开当前分组
            const index = valueArr.indexOf(newGroupId)
            if (index !== -1) {
               valueArr.unshift(valueArr.splice(index, 1)[0])
            }
            this.devGroupIds = valueArr
            // 由于<el-cascader组件绑定的值修改之后,UI不会更新,这里只能使用动态key的方式,强行让组件再渲染一遍
            this.cascaderKey ++
            console.log('cascaderKey', this.cascaderKey)
            // cascaderKey修改后,组件会重新渲染,这里模拟重新渲染后默认展开数据面板
            this.$nextTick(() => {
              const cascader = this.$refs.cascaderRef
              const trigger = cascader.$el.querySelector(".el-cascader__tags")
              trigger.click()
            })
          }
        }
      }
      this.preSelectedGroupIds = JSON.parse(JSON.stringify(valueArr))
    },

这个代码直接拷贝过去就能用了,不谢。

下面来解释下:

定义preSelectedGroupIds这个全局变量,用来存上一次已经勾选够的选项id,所以preSelectedGroupIds的初始值需要获取devGroupIds的初始值,为了回显的时候也能正常使用。

接下来就是每次触发勾选事件后,通过与preSelectedGroupIds比对,就能找到当前勾选的选项是哪个。

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug

因为change事件的参数只有当前全部选项的选项,太坑了,正常逻辑肯定是需要再提供一个当前选择的选项数据的。

找到当前的勾选数据之后,再去找这个选项所属的父级分组。

找到父级分组后,再去找这个父级分组下已经勾选的二级分组,注意这个已经勾选的二级分组肯定是不会超过两条的,因为我们每次都会只保留一个二级分组,所以再勾选一个新的,最多就是勾选了两个。

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug

 找到之后,为了实现保留当前勾选的选项,所以要把之前勾选的选项也找到,这样才能取消之前选项的勾选:

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug

这样就找到了,然后就去修改组件绑定的数据,正常来讲就大功告成了。

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug 万万没想到,el-cascader组件在手动设置了绑定的选项值后,UI是不会更新的。即便使用this.$set也是没没用的。

再解决这个问题之前,我的思路是通过找到需要取消勾选的选项的dom元素,直接移除 is-checked class样式,这样就直接UI上操作dom,ui肯定就改变了。

想法很美好,确实也做到了。额,一点点。。。

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug

 确实有一点点效果,但是还有很多问题,如果重新打开面板,被取消的数据又勾上,原因很简单,之前修改的绑定的值是没有直接更新到UI上的,所以UI上还是认为那个被删除的选项同时被移除 is-checked样式的选择没有被删除。

怎么办呢?

只能上大招了,操作key。没有办法了,只能让组件重新渲染才能让el-cascader组件接受绑定的已经选择的选项已经被修改的事实。动用了key之后,“通过找到需要取消勾选的选项的dom元素,直接移除 is-checked class样式”,这个也就不再需要了(白想了这个一开始认为是天才的方案)。

但是这样,就有个问题,每次选择完之后,面板就会被关掉,因为组件重新渲染了。

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug

 本来到这里,已经算尽力了。

但是程序员的老毛病犯了,总这样不行,所以又加了最后一个逻辑:每次更新了key之后,模拟点击组件的事件,让面板默认打开:

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug

这还不够, 打开后面板会默认打开第一个选项所在的一级分组,所以为了模拟重新展示面板后,还是打开当前选择的选项的分组,需要再做一步:

el-cascader多选,技巧,前端,bug,vue.js,elementui,前端,javascript,bug

总算完美了。。。

但是其实还有一个小问题,那就是用户还是能看到面板闪了一下的,因为被关闭了又打开了一次。

但是本人已经不想折腾, 谁有本事谁来解决这个问题。希望有大神看到最后,能伸出援手,给出更完美的方案,谢谢!

鉴于确实有不少小伙伴碰到过这个奇葩需求,或者借鉴这个解决此类问题的思路,这边直接把整个封装好的组件的代码贴出来,需要的直接拿去用吧,记得点赞哦!文章来源地址https://www.toymoban.com/news/detail-568096.html

<template>
  <el-cascader
    :key="cascaderKey"
    ref="cascader"
    :placeholder="placeholder || $t('general.pleaseSelect')"
    :options="options"
    v-bind="$attrs"
    :clearable='clearable'
    filterable
    collapse-tags
    class="customCascader"
    v-on="$listeners"
    @expand-change='expandChange'
    @change="change"
    v-model="val"
  >
    <slot></slot>
  </el-cascader>
</template>
<script>
export default {
  name: 'CustomCascader',
  props: {
    options: {
      type: Array,
      default: () => []
    },
    value: {
      type: [Array, String],
      default: () => []
    },
    clearable: {
      type: Boolean,
      default: true
    },
    placeholder: {
      type: String,
      default: ''
    },
    // 是否需要处理二级选项单选
    secondGroupSingleCheck: {
      type: Boolean,
      default: false
    },
    expandChange: {
      type: Function,
      default: () => {}
    }
  },
  data() {
    return {
      val: null,
      cascaderKey: 0,
      preSelectedGroupIds: null
    }
  },
  methods: {
    change(valueArr) {
      if (!this.secondGroupSingleCheck) {
        this.$emit('input', valueArr)
      } else { // 二级选项只能单选
        // 判断当前选中的节点的父级节点是否同时存在其他已经勾选的节点
        if (valueArr.length > 1) {
          const newArr = valueArr.filter(t => !this.preSelectedGroupIds.includes(t))
          let indexInAll
          const newGroupId = newArr[0] || ''
          let checkedGroupInParentGroup = []
          let oldCheckedGroupIdInParentGroup = ''
          // 找到当前新勾选的分组id
          if (newGroupId) {
            let parentGroup = []
            parentGroup = this.options.find(group => group.children.some(t => t.value === newGroupId))
            // 通过当前分组id找到所属一级分组
            for (const group of parentGroup.children) {
              if (valueArr.includes(group.value)) {
                checkedGroupInParentGroup.push(group.value) // 找到该一级分组下勾选的二级分组,最多只有两个
              }
              if (checkedGroupInParentGroup.length > 1) {
                break
              }
            }
            // 当前一级分组如果存在多个已经勾选的二级分组
            if (checkedGroupInParentGroup.length > 1) {
              checkedGroupInParentGroup.forEach(t => {
                if (t !== newGroupId) {
                  oldCheckedGroupIdInParentGroup = t // 找到一级分组下不是当前勾选分组的id,也就是之前勾选的二级分组id
                }
              })
              // 找到之前勾选的二级分组id在所有已经勾选的二级分组id中的序号和在当前一级分组下所有二级分组忠的序号
              indexInAll = valueArr.findIndex(t => t === oldCheckedGroupIdInParentGroup)
              // 之前勾选的二级分组id需要从已勾选的分组id中移除
              valueArr.splice(indexInAll, 1)
              // 将当期选中的新分组id放到第一位,以便重新打开时,可以默认打开面板时打开当前分组
              const index = valueArr.indexOf(newGroupId)
              if (index !== -1) {
                valueArr.unshift(valueArr.splice(index, 1)[0])
              }
              // 由于<el-cascader组件绑定的值修改之后,UI不会更新,这里只能使用动态key的方式,强行让组件再渲染一遍
              this.cascaderKey ++
              // cascaderKey修改后,组件会重新渲染,这里模拟重新渲染后默认展开数据面板
              this.$nextTick(() => {
                const cascader = this.$refs.cascader
                const trigger = cascader.$el.querySelector(".el-cascader__tags")
                trigger.click()
              })
            }
          }
        }
      }
    }
  },
  watch: {
    value: {
      handler(val){
        this.val = val
        this.preSelectedGroupIds = JSON.parse(JSON.stringify(val))
      },
      immediate: true
    }
  }
}
</script>
<style lang="scss" scoped>
.customCascader ::v-deep.el-cascader__tags {
  flex-wrap: nowrap;
}
.customCascader ::v-deep .el-cascader__tags .el-tag {
  max-width: 50%;
}
.customCascader ::v-deep.el-cascader__search-input {
  min-width: 22px;
}
::v-deep.customCascader .el-input__inner {
  height: 32px !important;
}
::v-deep.customCascader .el-input__suffix {
  z-index: 100;
}
</style>

到了这里,关于奇葩功能实现:级联选择框组件el-cascader实现同一级的二级只能单选,但是一级可以多选的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • element-ui el-cascader级联选择器设置指定层级不能选中(示例代码)

    本文为转载原地址:https://www.136.la/shida/show-396330.html 有时候用element-ui el-cascader级联选择器添加分类时会遇到最多添加几级的限定.看了文档,只要给需要禁止选择的选项添加disabled属性就可以.但是使用一层一层循环遍历数据感觉很麻烦,自己写了个遍历的方法,纪录下,方便以后使

    2024年02月11日
    浏览(40)
  • element 的 el-cascader 组件获取级联选中label和value值

    1.  多选时  获取 cascader 级联选择器的 label 值         需要给 el-cascader 加 ref 用以获取值  获取后的样式 2. 单选时获取 cascader 级联选择器的值     

    2024年02月12日
    浏览(42)
  • el-cascader级联选择器加载远程数据、默认开始加载固定条、可以根据搜索加载远程数据。

    加载用户列表分页请求、默认请求20条数据。想添加远程搜索用户功能。原有的方法 filter-method 不能监听到输入清空数据的时候。这样搜索完无法返回默认的20条数据。 直接监听级联选择的 v-model 绑定的值是无法检测到用户自己输入的。 解决思路: el-cascader 没有提供监听用户

    2024年02月14日
    浏览(30)
  • Vue+element-ui的el-cascader实现动态添加删除级联地点输入框

    实现省市区三级地点级联选择,可联想; 包括始发地点、途径地点、终止地点,始发地点、终止地点均为一个,途径地点可以没有也可以是多个; 用户可以动态添加/删除途径地点。 使用级联选择器Cascader需要的树形数据,前端请求到后端获取省市区数据并处理为elementui官网

    2024年02月04日
    浏览(35)
  • Element UI 中使用el-cascader组件,可以选择任意一级的内容并取消单选框

    当加入checkStrictly后就会出现单选框的问题,修改样式即可;这里使用的less语法,需要有less依赖,加入样式后如果没有生效,可以放在App.vue中的样式下

    2024年02月04日
    浏览(42)
  • vue+element UI 使用el-cascader实现全选功能

    实现效果图     使用el-cascader代码片段 js代码 data数据设置: // 这里是处理成自己需要的数据格式, 需要把全选的这一选项过滤掉 // 原始选择的数据格式 [[\\\'全选\\\'], [1, 2], [1, 3], [1, 4],[5, 6], [5, 7, 8],5, 7, 9],[10]] //因为我自己需要的数据是“2,3,4,5”的格式,做了以下处理 注:本文是

    2024年02月12日
    浏览(42)
  • element ui 层级选择器el-cascader只能选最后一级多选

    在element ui 中el-cascader多选: 每个层级都可以选择,但并不是我需要的,我需要多选只能选最后一级,在网上找了很久都复杂的,最终自己选择用css样式对checkbox进行隐藏。 实现方法: 在css 中加入 关键点在于利用属性选择器,遇到属性是 aria-haspopup (表示点击的时候是否会

    2024年02月11日
    浏览(61)
  • Naive UI 组件使用体验之-级联选择 Cascader

    地址区域选择 安装依赖 npm install naive-ui -D 按需引入之-手动引入 使用 这里我们是要对收货地址进行一个增加操作。 我们这里只针对 所在地址 进行说明。 获取region 这里需要通过接口提前请求

    2024年02月11日
    浏览(26)
  • el-cascader 数据的回显

    allOptions里面包含了所有你要选择的值   options:[\\\'0\\\',\\\'2\\\',\\\'4\\\',\\\'6\\\'] 如果 multiple: true  则 options 是数组 如果没有该属性 则 options: 0 options 中的数据 在allOptions 中必须存在才可以查出

    2024年02月15日
    浏览(41)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包