<template>
  <el-select
    class="zee__select"
    :disabled="disabled"
    :value-key="valueKey"
    :filterable="filterable"
    :remote="remote"
    :reserve-keyword="reserveKeyword"
    :clearable="clearable"
    :placeholder="placeholder"
    :remote-method="remoteMethod"
    :loading="loading"
    :size="size"
    :multiple="multiple"
    v-model="model"
    v-on="bindEvents"
    v-bind="selectProps"
  >
    <el-option v-for="item in optionsCurrent" :key="item.id" :label="labelFormat ? labelFormat(item) : item[labelKey]" :value="raw ? item : item[valueKey]" :disabled="item.disabled">
      <slot :item="item" :value="model"></slot>
    </el-option>
    <slot name="empty" slot="empty"></slot>
    <template slot="prefix">
      <i v-if="initing" class="el-icon-loading"></i>
      <slot v-else name="prefix"></slot>
    </template>
  </el-select>
</template>

<script>
export default {
  name: 'Select',
  props: {
    value: [String, Number, Boolean, Object, Array],
    // 占位符
    placeholder: {
      type: String,
      default: '请选择',
    },
    // 选项数组
    options: {
      type: Array,
      default: () => [],
    },
    // 选中Label格式化方法
    labelFormat: Function,
    labelKey: {
      type: String,
      default: 'name',
    },
    valueKey: {
      type: String,
      default: 'code',
    },
    searchKey: {
      type: String,
      default: 'query',
    },
    size: {
      type: String,
      default: 'mini',
    },
    multiple: Boolean,
    disabled: Boolean,
    clearable: {
      type: Boolean,
      default: true,
    },
    filterable: {
      type: Boolean,
      default: true,
    },
    reserveKeyword: {
      type: Boolean,
      default: true,
    },
    selectProps: Object,
    // 保持原值，即绑定值为对象
    raw: Boolean,
    // 远程搜索URL
    url: String,
    // HTTP请求库
    http: Function,
    // 自定义接口
    queryApi: Function,
    // 触发远程搜索的字段长度
    triggerSize: {
      type: Number,
      default: 0,
    },
    auto: Boolean,
    update: Boolean,
  },
  data() {
    return {
      model: this.value,
      optionsDataSource: this.fixOptions(this.options),
      optionsCurrent: this.fixOptions(this.options),
      loading: false,
      initing: false,
    };
  },
  created() {
    if (this.remote && this.auto) {
      this.initing = true;
      this.remoteMethod();
    }
  },
  watch: {
    value(val) {
      this.model = val;
    },
  },
  computed: {
    request() {
      return this.http || this.zHttp;
    },
    remote() {
      return Boolean(this.queryApi || (this.url && (this.http || this.zHttp)));
    },
    bindEvents() {
      let _events = {};
      Object.keys(this.$listeners || {}).forEach(key => {
        // 非绑定对象的情况下，通过change事件向上emit出当前选中项
        if (key === 'change' && !this.raw) {
          _events[key] = value => {
            this.$emit(
              key,
              value,
              this.optionsCurrent.reduce((result, item) => {
                if (value.includes(item[this.valueKey])) {
                  result.push(item);
                }
                return result;
              }, []),
            );
          };
        } else {
          _events[key] = e => {
            this.$emit(key, e);
          };
        }
      });
      return {
        ..._events,
        'visible-change': show => {
          if (this.remote && this.update && show) {
            this.remoteMethod();
          }
          _events['visible-change'] && _events['visible-change'](show);
        },
      };
    },
  },
  methods: {
    // 修复当前数据源包含当前选中项
    fixOptions(list) {
      if (!this.value || (this.value instanceof Array && this.value.length === 0) || (this.value instanceof Object && Object.keys(this.value).length === 0)) {
        return list;
      }
      if (this.raw) {
        // 绑定为对象值时修复默认项
        let fixOptions = [];
        let defaultOptions = [];
        if (this.multiple) {
          fixOptions = [...this.value];
          this.options.forEach(item => {
            if (this.value.find(i => i[this.valueKey] === item[this.valueKey])) {
              defaultOptions.push(item);
            }
          });
        } else {
          fixOptions = [this.value];
          this.options.forEach(item => {
            if (item[this.valueKey] === this.value[this.valueKey]) {
              defaultOptions.push(item);
            }
          });
        }
        let hash = {};
        const options = [...fixOptions, ...defaultOptions, ...list].reduce((result, item) => {
          if (!hash[item[this.valueKey]]) {
            // 如果当前元素的key值没有在hash对象里，则可放入最终结果数组
            hash[item[this.valueKey]] = true; // 把当前元素key值添加到hash对象
            result.push(item); // 把当前元素放入结果数组
          }
          return result; // 返回结果数组
        }, []);
        return options;
      } else {
        // 绑定为非对象值时修复默认项
        let defaultOptions = [];
        if (this.multiple) {
          this.options.forEach(item => {
            if (this.value.find(i => i === item[this.valueKey])) {
              defaultOptions.push(item);
            }
          });
        } else {
          const targetOption = this.options.find(item => item[this.valueKey] === this.value);
          if (targetOption) {
            defaultOptions = [targetOption];
          }
        }
        let hash = {};
        const options = [...defaultOptions, ...list].reduce((result, item) => {
          if (!hash[item[this.valueKey]]) {
            // 如果当前元素的key值没有在hash对象里，则可放入最终结果数组
            hash[item[this.valueKey]] = true; // 把当前元素key值添加到hash对象
            result.push(item); // 把当前元素放入结果数组
          }
          return result; // 返回结果数组
        }, []);
        return options;
      }
    },
    // 远程加载
    remoteMethod(val = '') {
      const searchText = val.trim();
      if (searchText.length >= this.triggerSize) {
        this.loading = true;
        let requestPrimise;
        if (this.queryApi) {
          requestPrimise = this.queryApi(searchText);
        } else {
          requestPrimise = this.request({
            url: this.url,
            method: 'get',
            params: searchText ? { [this.searchKey]: searchText } : {},
          });
        }
        requestPrimise
          .then(list => {
            const options = this.fixOptions(list);
            this.optionsDataSource = options;
            this.optionsCurrent = options;
            // 获取到选项数据源时刷新绑定显示值
            this.model = undefined;
            this.$nextTick(() => {
              this.model = this.value;
            });
          })
          .finally(() => {
            this.loading = false;
            this.initing = false;
          });
      } else {
        this.optionsCurrent = this.optionsDataSource.filter(item => {
          return item[this.labelKey].includes(val);
        });
      }
    },
  },
};
</script>

<style lang="scss">
.zee__select {
  .el-input__prefix {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    .el-icon-loading {
      font-size: 16px;
    }
  }
}
</style>
