index.vue 8.93 KB
<style lang="scss">
.eagle-schema-select {
  display: inline-flex;
}
.eagle-schema-select__popper {
  padding: 0 !important;
}
.eagle-schema-select__popper-content {
  padding: 4px;
  user-select: none;
}
</style>

<template>
  <el-popover
    class="eagle-schema-select"
    popper-class="eagle-schema-select__popper"
    v-model="visible"
    trigger="manual"
    placement="bottom-start"
    transition="el-zoom-in-top"
    @show="onTriggerShow"
  >
    <template #reference>
      <el-input
        ref="input"
        v-model="model"
        :size="selectSize"
        :disabled="selectDisabled"
        :prefix-icon="prefixIcon"
        :placeholder="selectPlaceholder"
        @input="debouncedOnInput"
        @focus="onInputFocus"
        @blur="onInputBlur"
        @mouseenter.native="inputHovering = true"
        @mouseleave.native="inputHovering = false"
      >
        <template #suffix>
          <i v-if="showClose" class="el-input__icon el-icon-circle-close" @click="onClear" @mouseenter="clearHovering = true" @mouseleave="clearHovering = false"></i>
          <i v-else class="el-input__icon"></i>
        </template>
      </el-input>
    </template>
    <div class="eagle-schema-select__popper-content" v-clickoutside="onClickoutside">
      <eagle-schema-page
        v-if="apiSearch"
        ref="schema"
        :value-table.sync="tableData"
        :value-filter="valueFilter"
        :loading.sync="loading"
        @update:value-filter="e => $emit('update:value-filter', e)"
        :schema="selectSchema"
        :size="selectSize"
        :auto="auto"
        :api-search="e => apiSearch(query, e)"
      >
        <template v-for="(item, index) in tableColumns" #[`table-cell-${item.prop}`]="{ value }">
          <cell-highlight v-if="highlight" :value="value" :keyword="query" :key="index"></cell-highlight>
          <template v-else>{{ value }}</template>
        </template>
        <template v-for="item in getSlotKeys('table-')" #[item.name]="slotScope">
          <slot :name="item.slot" v-bind="slotScope"></slot>
        </template>
      </eagle-schema-page>
      <eagle-schema-table v-else ref="table" :value="options" :schema="selectTableSchema" :size="selectSize">
        <template v-for="(item, index) in tableColumns" #[`cell-${item.prop}`]="{ value }">
          <cell-highlight v-if="highlight" :value="value" :keyword="query" :key="index"></cell-highlight>
          <template v-else>{{ value }}</template>
        </template>
        <template v-for="item in getSlotKeys('table-', true)" #[item.name]="slotScope">
          <slot :name="item.slot" v-bind="slotScope"></slot>
        </template>
      </eagle-schema-table>
    </div>
  </el-popover>
</template>

<script>
import debounce from 'throttle-debounce/debounce';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import { get } from '../utils';

export default {
  name: 'SchemaSelect',
  directives: { Clickoutside },
  components: {
    CellHighlight: {
      functional: true,
      render(h, context) {
        const props = context.props || {};
        const keyword = props.keyword;
        const value = props.value || '';
        if (!keyword) {
          return h('span', value);
        }
        const reg = new RegExp(`(${keyword})`, 'g');
        const result = `${value}`.replace(reg, '<font style="color: red;">$1</font>');
        return h('span', { domProps: { innerHTML: result } });
      },
    },
  },
  props: {
    value: String,
    schema: {
      required: true,
      type: Object,
      default() {
        return {};
      },
    },
    options: {
      type: Array,
      default: () => [],
    },
    clearable: {
      type: Boolean,
      default: true,
    },
    highlight: {
      type: Boolean,
      default: true,
    },
    disabled: Boolean,
    size: String,
    prefixIcon: {
      type: String,
      default: 'el-icon-search',
    },
    placeholder: String,
    labelKey: {
      type: String,
      default: 'label',
    },
    valueKey: {
      type: String,
      default: 'value',
    },
    allowCreate: Boolean,
    valueFilter: {
      type: Object,
      default() {
        return {};
      },
    },
    apiSearch: Function,
    lazy: Boolean,
    update: Boolean,
  },
  inject: {
    elForm: {
      default: undefined,
    },
    elFormItem: {
      default: undefined,
    },
  },
  data() {
    return {
      model: this.value || '',
      currentLabel: '',
      query: '',
      visible: false,
      inputHovering: false,
      tableData: [],
      loading: false,
      loaded: false,
      inFocus: false,
      clearHovering: false,
    };
  },
  created() {
    this.debouncedOnInput = debounce(300, () => {
      this.onInput();
    });
    this.model = this.selectedLabel;
  },
  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;
    },
    selectPlaceholder() {
      return this.selectedLabel || this.placeholder || '请选择';
    },
    selectedLabel() {
      if (this.value) {
        const item = [...this.tableData, ...this.options].find(item => item[this.valueKey] === this.value) || {};
        return item[this.labelKey] || this.currentLabel || this.value;
      }
      return '';
    },
    selectSchema() {
      return {
        filter: false,
        action: false,
        operation: false,
        pagination: false,
        selection: false,
        ...this.schema,
        table: {
          on: {
            'row-click': this.onTableRowClick,
          },
          ...(this.schema.table || {}),
        },
      };
    },
    selectTableSchema() {
      if (!this.schema) {
        return {};
      }
      return {
        on: {
          'row-click': this.onTableRowClick,
        },
        props: {
          'highlight-current-row': true,
          ...(get(this.schema, 'table.props') || {}),
        },
        items: [...(get(this.schema, 'table.items') || [])],
      };
    },
    showClose() {
      let hasValue = this.value !== undefined && this.value !== null && this.value !== '';
      let criteria = this.clearable && !this.selectDisabled && this.inputHovering && hasValue;
      return criteria;
    },
    tableColumns() {
      const tableSchema = this.schema.table;
      if (tableSchema) {
        return tableSchema.items;
      }
      return [];
    },
    auto() {
      if (this.lazy) {
        return false;
      }
      if (this.update) {
        return false;
      }
      return true;
    },
    slotKeys() {
      return Object.keys(this.$scopedSlots);
    },
  },
  watch: {
    value(val) {
      this.model = this.selectedLabel;
    },
    options() {
      this.model = this.selectedLabel;
    },
  },
  methods: {
    getSlotKeys(prefix, fixed) {
      return this.slotKeys.reduce((result, current) => {
        if (current.indexOf(prefix) === 0) {
          result.push({
            slot: current,
            name: fixed ? current.substring(prefix.length) : current,
          });
        }
        return result;
      }, []);
    },
    // 手动设置当前value对应的label
    setLabel(val) {
      this.currentLabel = val;
      this.model = this.selectedLabel;
    },
    // 输入查询
    onInput() {
      this.query = this.model;
      if (this.$refs.schema) {
        this.$refs.schema.search();
        if (this.allowCreate) {
          this.$emit('input', this.query);
        }
      }
    },
    // 显示弹框
    onTriggerShow() {
      if (this.lazy) {
        if (!this.loaded) {
          this.onInput();
          this.loaded = true;
        } else if (this.update) {
          this.onInput();
        }
      } else if (this.update) {
        this.onInput();
      }
    },
    onInputFocus() {
      if (!this.clearHovering) {
        this.visible = true;
        this.inFocus = true;
        this.query = this.model;
        if (!this.allowCreate) {
          this.model = '';
        }
      }
    },
    onInputBlur() {
      this.inFocus = false;
      this.$nextTick(() => {
        if (!this.visible) {
          this.query = '';
          if (!this.allowCreate) {
            this.model = this.selectedLabel;
          }
        }
      });
    },
    onClickoutside() {
      if (!this.inFocus) {
        this.visible = false;
        this.model = this.selectedLabel;
        this.query = this.selectedLabel;
      }
    },
    // 点击表格行
    onTableRowClick(row) {
      this.model = this.selectedLabel;
      this.$nextTick(() => {
        this.query = this.selectedLabel;
      });
      this.visible = false;
      this.$emit('input', row[this.valueKey]);
      this.$emit('change', row);
    },
    // 清空
    onClear() {
      this.query = '';
      this.model = '';
      this.clearHovering = false;
      this.$refs.input.blur();
      this.$emit('input', '');
      this.$emit('clear');
      this.$emit('change', '');
      this.onInput();
    },
  },
};
</script>