彻底解决el-cascader的bug

2,360 阅读1分钟

1. 问题

距离上一次发布解决el-cascader的bug的文章已经过去半年了,今天我又被el-cascader恶心到了,这次就是上次遗留的问题。多选情况下,没有匹配上options不显示数据。

2. 解决过程

  • github查看没有相关issues github.com/ElemeFE/ele… 可能维护人员都忙着维护element plus去了,这里的issues已经不怎么处理了

  • 那我再去element plus看看有没有相关问题,如果新的已经解决了的话,可以参考一下怎么写的,但是我在issues上没有找到相关问题,不过element plus的el-cascader也会有这个bug

  • 我又去看了Ant Design Vue的cascader,他们确实解决了这个问题,但是在写法和el-cascade相差有点多就没有再深究下去

  • 只能按照上次的思路再去研究了,直接贴代码

单选的时候

computePresentText() {
  const { checkedValue, config } = this
  if (!this.isEmptyValue(checkedValue)) {
    const node = this.panel.getNodeByValue(checkedValue)
    if (node && (config.checkStrictly || node.isLeaf)) {
      this.presentText = node.getText(this.showAllLevels, this.separator)
      return
    } else {
      //再匹配不上的时候就把数据用,拼接起来
      this.presentText = checkedValue.join(',')
      return
    }
  }
  this.presentText = null
},

多选的时候

<template>
  <div
    ref="reference"
    v-clickoutside="() => toggleDropDownVisible(false)"
    :class="[
      'el-cascader',
      realSize && `el-cascader--${realSize}`,
      { 'is-disabled': isDisabled }
    ]"
    @mouseenter="inputHover = true"
    @mouseleave="inputHover = false"
    @click="() => toggleDropDownVisible(readonly ? undefined : true)"
    @keydown="handleKeyDown"
  >

    <el-input
      ref="input"
      v-model="multiple ? presentText : inputValue"
      :size="realSize"
      :placeholder="placeholder"
      :readonly="readonly"
      :disabled="isDisabled"
      :validate-event="false"
      :class="{ 'is-focus': dropDownVisible }"
      @focus="handleFocus"
      @blur="handleBlur"
      @input="handleInput"
    >
      <template slot="suffix">
        <i
          v-if="clearBtnVisible"
          key="clear"
          class="el-input__icon el-icon-circle-close"
          @click.stop="handleClear"
        />
        <i
          v-else
          key="arrow-down"
          :class="[
            'el-input__icon',
            'el-icon-arrow-down',
            dropDownVisible && 'is-reverse'
          ]"
          @click.stop="toggleDropDownVisible()"
        />
      </template>
    </el-input>

    <div v-if="multiple" class="el-cascader__tags" style="display: flex;align-items: center;">
      <el-tag
        v-for="tag in presentTags"
        :key="tag.key"
        type="info"
        :size="tagSize"
        :hit="tag.hitState"
        :closable="tag.closable"
        disable-transitions
        @close="deleteTag(tag)"
      >
        <span>{{ tag.text }}</span>
      </el-tag>
      <!-- 再用一个列表展示匹配不上的数据 -->
      <el-tag
        v-for="(tag,index) in noMatchValue"
        :key="tag.text"
        type="info"
        :size="tagSize"
        closable
        disable-transitions
        @close="deleteTagNoMatch(index)"
      >
        <span>{{ tag.text }}</span>
      </el-tag>
      <input
        v-if="filterable && !isDisabled"
        v-model.trim="inputValue"
        type="text"
        class="el-cascader__search-input"
        :placeholder="presentTags.length || noMatchValue.length ? '' : placeholder"
        @input="e => handleInput(inputValue, e)"
        @click.stop="toggleDropDownVisible(true)"
        @keydown.delete="handleDelete"
      >
    </div>

    <transition name="el-zoom-in-top" @after-leave="handleDropdownLeave">
      <div
        v-show="dropDownVisible"
        ref="popper"
        :class="['el-popper', 'el-cascader__dropdown', popperClass]"
      >
        <el-cascader-panel
          v-show="!filtering"
          ref="panel"
          v-model="checkedValue"
          :options="options"
          :props="config"
          :border="false"
          :render-label="$scopedSlots.default"
          @expand-change="handleExpandChange"
          @close="toggleDropDownVisible(false)"
        />
        <el-scrollbar
          v-if="filterable"
          v-show="filtering"
          ref="suggestionPanel"
          tag="ul"
          class="el-cascader__suggestion-panel"
          view-class="el-cascader__suggestion-list"
          @keydown.native="handleSuggestionKeyDown"
        >
          <template v-if="suggestions.length">
            <li
              v-for="(item, index) in suggestions"
              :key="item.uid"
              :class="[
                'el-cascader__suggestion-item',
                item.checked && 'is-checked'
              ]"
              :tabindex="-1"
              @click="handleSuggestionClick(index)"
            >
              <span>{{ item.text }}</span>
              <i v-if="item.checked" class="el-icon-check" />
            </li>
          </template>
          <slot v-else name="empty">
            <li class="el-cascader__empty-text">{{ t('el.cascader.noMatch') }}</li>
          </slot>
        </el-scrollbar>
      </div>
    </transition>
  </div>
</template>
<script>
import { Cascader } from 'element-ui'
export default {
  extends: Cascader,
  data() {
    return {
      noMatchValue: [],
    }
  },
  methods: {
    computePresentText() {
      const { checkedValue, config } = this
      if (!this.isEmptyValue(checkedValue)) {
        const node = this.panel.getNodeByValue(checkedValue)
        if (node && (config.checkStrictly || node.isLeaf)) {
          this.presentText = node.getText(this.showAllLevels, this.separator)
          return
        } else {
          this.presentText = checkedValue.join(',')
          return
        }
      }
      this.presentText = null
    },
    computePresentTags() {
      const { isDisabled, leafOnly, showAllLevels, separator, collapseTags, checkedValue } = this
      const checkedNodes = this.getCheckedNodes(leafOnly)
      const tags = []

      const genTag = node => ({
        node,
        key: node.uid,
        text: node.getText(showAllLevels, separator),
        hitState: false,
        closable: !isDisabled && !node.isDisabled,
      })

      if (checkedNodes.length) {
        const [first, ...rest] = checkedNodes
        const restCount = rest.length
        tags.push(genTag(first))

        if (restCount) {
          if (collapseTags) {
            tags.push({
              key: -1,
              text: `+ ${restCount}`,
              closable: false,
            })
          } else {
            rest.forEach(node => tags.push(genTag(node)))
          }
        }
      }

      this.checkedNodes = checkedNodes
      this.presentTags = tags

      // 在这里就是全部处理完的多选数据了
      // 将checkedNodes没有的数据,而checkedValue有的数据,也就是匹配不上的数据,放到noMatchValue中
      this.noMatchValue = []
      checkedValue.forEach(item => {
        if (this.checkedNodes.length) {
          let flag = false
          for (const tag of this.checkedNodes) {
            if (tag && tag.path && item.join('mm@mm') === tag.path.join('mm@mm')) {
              flag = true
            }
          }
          !flag && this.noMatchValue.push({
            text: item.join(','),
          })
        } else {
          this.noMatchValue.push({
            text: item.join(','),
          })
        }
      })
    },
    // 删除的时候把相应数据删掉就可以了,因为这里是匹配不上的数据,在我看来也就不需要在删除的时候发送事件出去,如果要加的话,可以参考el-cascader的源码加上去
    deleteTagNoMatch(index) {
      this.checkedValue.findIndex(item => item.join(',') === this.noMatchValue[index].text)
      this.checkedValue.splice(index, 1)
      this.noMatchValue.splice(index, 1)
    },
  },
}
</script>

到这里这个问题就解决了,展示出来的效果就是这样

image.png

3. 总结

数据匹配的方式比较笨,采用的是不怎么用到的字符串(mm@mm)合并匹配,如果有大佬有更好的方法,请不吝赐教

自己学习过程的总结,菜鸡一枚,有什么问题与错误,请不吝赐教