再很多时候我们的需求是这样的

对,就是像个select,但是它的下拉列表里面不是常规的option,列表里面的数据可能有层级结构,你是不是立马想到el-tree?是的。下面我们就来实现一个类似el-select和el-tree的结合体。

但是我们实际上用的el-input和el-tree来实现的哦

接着来看实现代码吧。

<template>
  <section class="selectTree" :class="inline ? 'inline': ''">
    <div class="el-select selectTree-select"
    >
      <el -input v-model.trim="selectedLabel" readonly
                @click.native="showList = true">
          <span class="el-input__suffix" slot="suffix">
            <span class="el-input__suffix-inner">
              <i class="el-select__caret el-input__icon el-icon-arrow-up"
                 :class="{'is-reverse': showList}"></i>
            </span>
          </span>
      </el>
    </div>
    </section><section class="selectTree-section" v-show="showList" :style="{top: topOffset + 'px'}">
      <div class="transparent-div" @click="showList = false" :style="{height: (inputHeight + 4) + 'px' }"></div>
      <div class="select-div" ref="list">
        <div class="inputResult">
          <el -input class="input-result"
                    prefix-icon="el-icon-search"
                    :placeholder="$t('lang.input_keyword_filter')"
                    v-model="inputText">
          </el>
        </div>
        <el -tree
                :data="data"
                show-checkbox
                :props="treeProps"
                :node-key="valueProp"
                ref="tree"
                :filter-node-method="filterNode"
                :default-checked-keys="selected"
                highlight-current>
        </el>
        <div class="btns">
          <el -button size="mini" type="text" @click="showList = false">{{$t('lang.cancel')}}</el>
          <el -button size="mini" type="primary" @click="handleConfirm">{{$t('lang._save')}}</el>
        </div>
      </div>
    </section>

</template>

<script>
  export default {
    name: 'selectTree',
    props: {
      value: {
        type: Array,
        default() {
          return [];
        }
      },
      visible: {
        type: Boolean,
        default: false,
      },
      data: {
        type: Array,
        default() {
          return [];
        }
      },
      valueProp: { // 选择标志属性
        type: String,
        default: 'id',
      },
      labelProp: { // 选择显示值
        type: String,
        default: 'name',
      },
      itemLabel: { // 选择项的描述
        type: String,
      },
      inputHeight: { // 输入框的高度
        type: Number,
        default: 0,
      },
      topOffset: { // 列表的垂直偏移量
        type: Number,
        default: 0,
      },
      treeProps: { // el-tree的props
        type: Object,
        default() {
          return {
            label: 'name',
            children: 'children'
          };
        }
      },
      containerRef: { // 部分容器,比如el-popover上面的点击事件document捕获不到,需要单独处理
        // type: Object
      },
      inline: {
        type: Boolean,
        default: false,
      },

    },
    data() {
      return {
        showList: this.visible,
        inputText: '',
        selected: this.value,
        dom: '',
      };
    },
    computed: {
      dataItems() {
        return this.commonscript.flattenArr([])(this.data, this.valueProp);
      },
      selectedArr() {
        return this.dataItems.filter(item => this.selected.includes(item[this.valueProp]));
      },
      selectedValue() {
        return this.selectedArr.map(item => item[this.valueProp]);
      },
      selectedLabel() {
        const selectedValue = this.selectedArr.map(item => item[this.valueProp]);
        const selectedLabel = this.selectedArr.map(item => item[this.labelProp]);
        // this.selected = selectedValue; // 修正选中的,因为传进来的selected的部分选项可能已经不在列表里面了
        // this.$emit('input', selectedValue);
        return this.selectedArr.length
          ? `${this.$t('lang.selected')} ${this.selectedArr.length} ${this.itemLabel || this.$t('lang.item')},${selectedLabel.join(' / ')}`
          : '';
      },
      treeSelected() {
        return this.$refs.tree.getCheckedNodes().filter(item => !item.children).map(item => item[this.valueProp]);
      }
    },
    mounted() {
      document.addEventListener('click', this.handleDocumentClick);
    },
    destroyed() {
      document.removeEventListener('click', this.handleDocumentClick);
    },
    methods: {
      filterNode(value, data) {
        if (!value) return true;
        const {label} = this.treeProps;
        return data[label].indexOf(value) !== -1;
      },
      handleConfirm() {
        this.showList = false;
        this.$emit('input', this.treeSelected);
      },
      handleDocumentClick(e) {
        const list = this.$refs.list;
        if (!this.$el ||
          this.$el.contains(e.target) ||
          !list ||
          list.contains(e.target)) return;
        this.showList = false;
      },
    },
    watch: {
      inputText(val) {
        this.$refs.tree.filter(val);
      },
      value: {
        immediate: true,
        handler(val) {
          this.selected = val || [];
        },
      },
      containerRef(value, oldValue) {
        if (oldValue) {
          const oldDom = oldValue.$el || oldValue;
          oldDom.removeEventListener('click', this.handleDocumentClick);
        }
        if (value) {
          this.dom = value.$el || value;
          this.dom.addEventListener('click', this.handleDocumentClick);
        }
      },
    },
  }
</script>

大家可能看到这里的containerRef是个什么东东,完全多此一举嘛,是的。只是在某些特定的嗯情况,比如再el-popover里面,document是无法获取点击事件的,造成的影响就是,我们在点击其它地方时无法取消下拉列表了,这个也是偶然发现的,所以额外加上了这个功能。

分类: Vue

发表评论