In Element UI cascading selector multi-selection mode, only one child can be selected under the same parent level

This requirement is a combination of multiple selection and single selection. Single selection of child nodes under the same parent level can also select multiple nodes under different parent levels. Here we take two levels as an example to implement a cascading selector that can only select at most one child level under the same parent level in the multi-selection mode.

1. Hide the CheckBox at the parent node

In the multi-selection mode, you can select all the children by checking the parent with one click, and there may be multiple children under each parent, or there may be only one. At first, I thought about classifying and discussing according to the number of selected ones. , but the discussion is cumbersome, so I finally decided to hide the checkbox of the parent directly, preventing users from directly checking the parent, which reduces a lot of unnecessary troubles.

.hide {
  .el-cascader-menu:first-of-type {
    .el-cascader-node {
      .el-checkbox {
        display: none;
      }
    }
  }
}

One thing to note here is that the hide class name must use the popper-class in the cascade selector to add a custom floating layer class name.

 2. Filter and restrict the selected items, first find the newly added item, and compare it with the value before the new addition to see if there is a parent that belongs to the same parent as the newly added item, if it exists, delete it before The item that already exists, leave the newly added one.

The solution here took me a lot of time, because at first I thought about directly manipulating the binding value of the cascading selector and deleting the unnecessary item, but a problem arose. The bound value does achieve the effect I want (that is, delete the item that existed before, leaving the newly added one), but the part on the right side of the cascading panel (that is, the child node part) will automatically Refresh and jump to the sub-level panel under the first parent level. The user experience is poor. Try to solve it, but it can't be solved. . .

Finally, choose to implement it from another angle. If there is an item that belongs to the same parent as the newly added item, the click event will be triggered directly through js, and the item that already existed before will be unchecked.

Once you have an idea, you can implement it step by step.

First, find this newly added item. Since the cascading selector in the component library document does not provide a relevant method to obtain the latest selected item, you can only get all the values ​​​​that have been selected, and the cascading selection The new items added by the device are not directly pushed to the end of the bound value, but are added in the order in which the option values ​​are displayed on the page, so they can only be compared and searched manually, and a preValue array needs to be defined to store the last value.

let newIndex;
let i = 0, j = 0;
while (i < val.length && j < this.preValue.length) {
  if (val[i][0] === this.preValue[j][0] && val[i][1] === this.preValue[j][1]) {
    i++;
    j++;
  } else {
    //添加在中间的情况
    newIndex = i;
    break;
  }
}
//添加在末尾的情况
if (j === this.preValue.length) {
  newIndex = i;
}

After finding the new item, compare whether it has added too many new items before that to other children under the same parent.

let delIndex = val.findIndex((item, index) => index !== newIndex && item[0] === val[newIndex][0]);

If it exists, then you need to trigger the corresponding click event to cancel the check of this item.

Let's first observe the html structure. The id value of each li tag under the right panel is actually the id value of the right panel plus: '-'+index, so we only need to get the id value of the right panel to know the corresponding The id value of the option, and then trigger the click event of the checkbox in the child tag of the li tag.

 The id of the right panel can be obtained through the reference of the cascade selector. In addition, the index of the unchecked option needs to be obtained by comparing it with the option value of the cascade selector.

let cancelIndex;
for (let i = 0; i < this.options.length; i++) {
  if (this.options[i].value === val[delIndex][0]) {
    for (let j = 0; j < this.options[i].children.length; j++) {
      if (this.options[i].children[j].value === val[delIndex][1]) {
        cancelIndex = j;
        break;
      }
    }
    break;
  }
}

Get the id value.

this.$nextTick(() => {
    let panelId = this.$refs.cascade.panel.$refs.menu[1].$el.id;  //其中menu[1]表示右侧的面板 menu[0]即为左侧的面板
    let liId = document.getElementById(panelId + '-' + cancelIndex);
    liId.children[0].click();
  })

This requirement has been basically fulfilled here, which is actually not difficult, but because of problems in the realization of my own ideas (direct value deletion) at the beginning, it took a lot of time to think about solving them, but I did not solve them. Finally, I discussed with my colleagues to change them. A kind of thinking will be realized soon, so it is very important to be flexible~

3. Complete code

<template>
  <div class='test'>
    <el-cascader
      class='cascader'
      :options='options'
      :props='props'
      clearable
      :popper-class="'hide'"
      @change='handleChange'
      ref='cascade'
    ></el-cascader>
  </div>
</template>

<script>
export default {
  name: 'Cascader',
  data() {
    return {
      props: { multiple: true, expandTrigger: 'click' },
      options: [{
        value: 1,
        label: '杭州',
        children: [{
          value: 3,
          label: '西湖'
        }, {
          value: 4,
          label: '钱塘'
        }, {
          value: 7,
          label: '上城'
        }]
      }, {
        value: 2,
        label: '成都',
        children: [{
          value: 5,
          label: '青羊'
        }, {
          value: 6,
          label: '武侯'
        }]
      }],
      preValue:[]
    }
  },
  methods:{
    handleChange(val){
      if (this.preValue.length > 0 && val.length > this.preValue.length) {
        let newIndex;
        let i = 0, j = 0;
        while (i < val.length && j < this.preValue.length) {
          if (val[i][0] === this.preValue[j][0] && val[i][1] === this.preValue[j][1]) {
            i++;
            j++;
          } else {
            //添加在中间的情况
            newIndex = i;
            break;
          }
        }
        //添加在末尾的情况
        if (j === this.preValue.length) {
          newIndex = i;
        }

        let delIndex = val.findIndex((item, index) => index !== newIndex && item[0] === val[newIndex][0]);
        if (delIndex >= 0) {
          // 取消选择的节点
          let cancelIndex;
          for (let i = 0; i < this.options.length; i++) {
            if (this.options[i].value === val[delIndex][0]) {
              for (let j = 0; j < this.options[i].children.length; j++) {
                if (this.options[i].children[j].value === val[delIndex][1]) {
                  cancelIndex = j;
                  break;
                }
              }
              break;
            }
          }
          this.$nextTick(() => {
            let panelId = this.$refs.cascade.panel.$refs.menu[1].$el.id;  //其中menu[1]表示右侧的面板 menu[0]即为左侧的面板
            let liId = document.getElementById(panelId + '-' + cancelIndex);
            liId.children[0].click();
          })
          val[delIndex] = '';
          val = val.filter(item => item !== '');
        }
      }
      this.preValue = val;
    }
  }
};
</script>

<style lang='less'>
.test {
  text-align: center;
  margin-top: 200px;

  .title {
    display: block;
    margin-bottom: 20px;
  }

  .cascader {
    .el-input__inner {
      width: 362px;
    }

    .el-cascader__tags {
      display: flex;
      flex-wrap: nowrap;
      overflow-y: overlay;
      margin-left: 2px;
    }

    .el-cascader__tags::-webkit-scrollbar {
      width: 0;
      height: 3px;
    }

    /*定义滚动条轨道 内阴影+圆角*/

    .el-cascader__tags::-webkit-scrollbar-track {
      background-color: rgba(186, 203, 227, 0.3);
    }

    /*定义滑块 内阴影+圆角*/

    .el-cascader__tags::-webkit-scrollbar-thumb {
      background-color: #B3C2D7;
    }
  }
}

.hide {
  .el-cascader-menu:first-of-type {
    .el-cascader-node {
      .el-checkbox {
        display: none;
      }
    }
  }
}
</style>

If you have a better solution or optimization, welcome to discuss in the comment area~

Guess you like

Origin blog.csdn.net/qq_44748911/article/details/129158847