index.vue 7.74 KB
<template>
  <el-select
    ref="select"
    :size="selectSize"
    :disabled="selectDisabled"
    :value-key="valueKey"
    :filterable="selectFilterable"
    :remote="remote"
    :clearable="selectClearable"
    :placeholder="placeholder"
    :remote-method="remoteMethod"
    :loading="loading"
    :multiple="multiple"
    v-model="model"
    v-on="bindEvents"
    v-bind="$attrs"
  >
    <slot v-if="$scopedSlots.default"></slot>
    <template v-else>
      <el-option v-for="item in optionsCurrent" :key="item.id" :label="labelFormat ? labelFormat(item) : item[labelKey]" :value="item[valueKey]" :disabled="item.disabled">
        <slot name="option" :item="item" :value="model"></slot>
      </el-option>
    </template>
    <slot name="empty" slot="empty"></slot>
    <template slot="prefix">
      <slot name="prefix">
        <i v-if="selectFilterable && !multiple && prefixIcon" :class="`el-input__icon ${prefixIcon}`"></i>
      </slot>
    </template>
  </el-select>
</template>

<script>
export default {
  name: 'Select',
  inject: {
    elForm: {
      default: '',
    },
    elFormItem: {
      default: '',
    },
  },
  props: {
    value: [String, Number, Boolean, Array],
    // 占位符
    placeholder: {
      type: String,
      default: '请选择',
    },
    // 选项数据源
    options: {
      type: Array,
      default: () => [],
    },
    // 选中Label格式化方法
    labelFormat: Function,
    labelKey: {
      type: String,
      default: 'label',
    },
    valueKey: {
      type: String,
      default: 'value',
    },
    prefixIcon: {
      type: String,
      default: 'el-icon-search',
    },
    size: String,
    multiple: Boolean,
    sequence: Boolean,
    disabled: Boolean,
    clearable: Boolean,
    filterable: Boolean,
    stringify: Boolean,
    separator: {
      type: String,
      default: ',',
    },
    // 自定义接口
    queryApi: Function,
    lazy: Boolean,
    update: Boolean,
    beforeQuery: {
      type: Function,
      default: () => true,
    },
  },
  data() {
    return {
      model: this.formatValue(this.value),
      optionsDataSource: this.fixOptions(this.options),
      optionsCurrent: this.fixOptions(this.options),
      loading: false,
      initing: false,
      loaded: false,
      suffixClass: null,
    };
  },
  created() {
    if (this.remote && !(this.lazy || this.update)) {
      this.initing = true;
      this.remoteMethod();
    }
  },
  watch: {
    value(val) {
      this.model = this.formatValue(val);
    },
    options(val) {
      if (val) {
        this.optionsCurrent = this.fixOptions(this.optionsDataSource);
      }
    },
    initing(val) {
      if (val) {
        this.showSuffixLoading();
      } else {
        this.hideSuffixLoading();
      }
    },
  },
  computed: {
    _elFormItemSize() {
      return (this.elFormItem || {}).elFormItemSize;
    },
    selectSize() {
      return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size;
    },
    selectDisabled() {
      return this.disabled || (this.elForm || {}).disabled;
    },
    selectFilterable() {
      return this.filterable || this.remote;
    },
    selectClearable() {
      return this.clearable || this.remote;
    },
    elSelect() {
      return this.$refs.select;
    },
    remote() {
      return !!this.queryApi;
    },
    // Hack绑定事件,即令el-select组件绑定事件与当前eagle-select组件相同,且扩展部分事件返回值
    bindEvents() {
      let _events = {};
      Object.keys(this.$listeners || {}).forEach(key => {
        // 通过change事件向上emit出当前选中项
        if (key === 'change') {
          // 给el-select绑定change事件,且emit到eagle-select的change事件并拓展
          _events[key] = value => {
            this.$emit(
              key,
              value,
              this.multiple
                ? this.optionsCurrent.reduce((result, item) => {
                    if (value.includes(item[this.valueKey])) {
                      result.push(item);
                    }
                    return result;
                  }, [])
                : this.optionsCurrent.find(item => item[this.valueKey] === value),
            );
          };
        } else if (key === 'input') {
          _events[key] = e => {
            let value = e;
            if (this.multiple && this.stringify && Array.isArray(value)) {
              this.$emit(key, value.join(this.separator));
            } else {
              if (this.multiple && this.sequence) {
                let sequenceValue = [];
                this.optionsCurrent.forEach(item => {
                  if (value.includes(item[this.valueKey])) {
                    sequenceValue.push(item[this.valueKey]);
                  }
                });
                value = sequenceValue;
              }
              this.$emit(key, value);
            }
          };
        } else {
          _events[key] = e => {
            this.$emit(key, e);
          };
        }
      });
      return {
        ..._events,
        'visible-change': show => {
          if (this.remote && show) {
            if (this.lazy) {
              if (!this.loaded) {
                this.remoteMethod();
              } else if (this.update) {
                this.remoteMethod();
              }
            } else if (this.update) {
              this.remoteMethod();
            }
          }
          _events['visible-change'] && _events['visible-change'](show);
        },
      };
    },
  },
  methods: {
    // 获取右侧图标上级节点
    getSuffixDom() {
      return this.$el.querySelector('.el-input__suffix-inner');
    },
    // 显示右侧加载图标
    showSuffixLoading() {
      const suffix = this.getSuffixDom();
      if (suffix) {
        this.suffixClass = suffix.children[0].className;
        suffix.children[0].className = 'el-select__caret el-input__icon el-icon-loading';
      }
    },
    // 隐藏右侧加载图标
    hideSuffixLoading() {
      if (this.suffixClass) {
        const suffix = this.getSuffixDom();
        suffix.children[0].className = this.suffixClass;
        this.suffixClass = null;
      }
    },
    // 格式化绑定值
    formatValue(value) {
      if (!this.multiple || !this.stringify) {
        return value;
      }
      if (!Array.isArray(value)) {
        return value ? value.split(this.separator) : [];
      } else {
        return value;
      }
    },
    // 修复当前数据源包含当前选中项
    fixOptions(list) {
      let hash = {};
      return [...this.options, ...list].reduce((result, item) => {
        let hashKey = `${item[this.valueKey]}` || '_empty';
        if (!hash[hashKey]) {
          // 如果当前元素的key值没有在hash对象里,则可放入最终结果数组
          hash[hashKey] = true; // 把当前元素key值添加到hash对象
          item[this.labelKey] && result.push(item); // 把当前元素放入结果数组
        }
        return result; // 返回结果数组
      }, []);
    },
    // 远程加载
    remoteMethod(val = '') {
      const searchText = val.trim();
      const isQueryValid = this.beforeQuery(searchText);
      if (isQueryValid) {
        this.loading = true;
        this.queryApi(searchText)
          .then(res => {
            const response = res || {};
            const options = this.fixOptions(response.result);
            this.optionsDataSource = options;
            this.optionsCurrent = options;
          })
          .finally(() => {
            this.loading = false;
            this.initing = false;
            this.loaded = true;
          });
      } else {
        this.loading = false;
        this.initing = false;
        this.loaded = true;
      }
    },
    focus() {
      this.elSelect.focus();
    },
    blur() {
      this.elSelect.blur();
    },
  },
};
</script>