index.vue 7.36 KB
<style lang="scss">
.eagle-schema-transfer {
  display: flex;
  justify-content: space-between;
  box-sizing: border-box;
  &__left,
  &__right {
    width: 50%;
    flex: 1;
    border: 1px solid #ebeef5;
  }
  &__left {
    border-right: 0;
  }
  &__header {
    display: flex;
    background: #f5f7fa;
    padding: 8px 10px;
    font-size: 14px;
  }
  &__content {
    padding: 10px;
  }
}
</style>

<template>
  <div class="eagle-schema-transfer">
    <div class="eagle-schema-transfer__left">
      <div class="eagle-schema-transfer__header">
        <div class="eagle-schema-transfer__title">
          <slot name="title-left" v-bind="_slotScope">{{ titles[0] }}</slot>
        </div>
      </div>
      <div class="eagle-schema-transfer__content">
        <slot v-bind="_slotScope">
          <eagle-schema-page
            ref="schema-page"
            :size="transferSize"
            :value-filter="valueFilter"
            :value-table="valueTable"
            :schema="schemaLeft"
            :api-search="searchMethod"
            @update:value-filter="e => $emit('update:value-filter', e)"
            :auto="auto"
          >
            <template v-for="item in getSlotKeys('table-')" #[item.name]="slotScope">
              <slot :name="item.slot" v-bind="{ ..._slotScope, ...slotScope }"></slot>
            </template>
            <template #operation>
              <slot name="operation" v-bind="_slotScope">
                <el-table-column label="操作" width="80" align="center" fixed="right">
                  <template #default="{ row, $index }">
                    <div class="eagle-schema-page__table-operation">
                      <el-button type="text" :disabled="_rowDisabled(row, $index)" @click="onChoose(row)">选择</el-button>
                    </div>
                  </template>
                </el-table-column>
              </slot>
            </template>
          </eagle-schema-page>
        </slot>
      </div>
    </div>
    <div class="eagle-schema-transfer__right">
      <div class="eagle-schema-transfer__header">
        <div class="eagle-schema-transfer__title">
          <slot name="title-right">{{ titles[1] }}</slot>
        </div>
      </div>
      <div class="eagle-schema-transfer__content">
        <slot name="selected" v-bind="_slotScope">
          <eagle-schema-table ref="schema-table" :size="transferSize" :value="value" :schema="schemaRight" @input="onInput">
            <template v-for="item in getSlotKeys('selected-', true)" #[item.name]="slotScope">
              <slot :name="item.slot" v-bind="{ ..._slotScope, ...slotScope }"></slot>
            </template>
            <slot name="selected-operation" v-bind="_slotScope">
              <el-table-column label="操作" width="80" align="center" fixed="right">
                <template #default="{ row, $index }">
                  <div class="eagle-schema-page__table-operation">
                    <el-button type="text" :disabled="_rowDisabled(row, $index)" @click="onRemove(row, $index)">移除</el-button>
                  </div>
                </template>
              </el-table-column>
            </slot>
          </eagle-schema-table>
        </slot>
      </div>
    </div>
  </div>
</template>

<script>
import { cloneDeep, get } from '../utils';

export default {
  name: 'SchemaTransfer',
  props: {
    value: {
      type: Array,
      default() {
        return [];
      },
    },
    schema: {
      required: true,
      type: Object,
      default() {
        return {};
      },
    },
    titles: {
      type: Array,
      default() {
        return ['未选中', '已选中'];
      },
    },
    source: Array,
    valueKey: {
      type: String,
      default: 'id',
    },
    size: String,
    disabled: Boolean,
    auto: Boolean,
    rowDisabled: Function,
    apiSearch: Function,
    chooseFormatter: Function,
    valueFilter: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  inject: {
    elForm: {
      default: undefined,
    },
    elFormItem: {
      default: undefined,
    },
  },
  data() {
    return {
      dataSource: this.source || [],
    };
  },
  watch: {
    source(val) {
      this.dataSource = val || [];
    },
  },
  computed: {
    _elFormItemSize() {
      return (this.elFormItem || {}).elFormItemSize;
    },
    transferSize() {
      return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size;
    },
    transferDisabled() {
      return this.disabled || (this.elForm || {}).disabled;
    },
    schemaFilter() {
      return this.schema.filter || {};
    },
    schemaLeft() {
      return {
        ...this.schema,
        selection: false,
        action: false,
        filter: this.schema.filter
          ? {
              props: {
                span: 12,
                ...(this.schemaFilter.props || {}),
              },
              ...this.schemaFilter,
            }
          : false,
        pagination: !this.apiSearch ? false : get(this.schema, 'pagination'),
      };
    },
    schemaRight() {
      if (this.schema.selected) {
        return this.schema.selected;
      }
      return {
        props: {
          border: true,
          'highlight-current-row': true,
          ...(get(this.schema, 'table.props') || {}),
        },
        ...this.schema.table,
      };
    },
    valueKeys() {
      return this.value.map(item => item[this.valueKey]);
    },
    valueTable() {
      return this.deselect(this.dataSource || []);
    },
    slotKeys() {
      return Object.keys(this.$scopedSlots);
    },
    _slotScope() {
      const methods = ['deselect'];
      const defaultScope = {
        size: this.transferSize,
        disabled: this.transferDisabled,
        choose: this.onChoose,
        remove: this.onRemove,
        source: this.valueTable,
      };
      return [...methods].reduce((result, current) => {
        result[current] = this[current];
        return result;
      }, defaultScope);
    },
  },
  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;
      }, []);
    },
    _rowDisabled(row, index) {
      if (this.transferDisabled) {
        return true;
      }
      if (this.rowDisabled) {
        return this.rowDisabled({ row, index });
      }
      return false;
    },
    search() {
      if (this.$refs['schema-page']) {
        this.$refs['schema-page'].search();
      }
    },
    deselect(value) {
      return value.filter(item => !this.valueKeys.includes(item[this.valueKey]));
    },
    async onChoose(row) {
      let newRow = cloneDeep(row);
      if (this.chooseFormatter) {
        newRow = await this.chooseFormatter(newRow);
      }
      this.$emit('input', [...this.value, newRow]);
    },
    onRemove(row, index) {
      const newValue = cloneDeep(this.value || []);
      newValue.splice(index, 1);
      this.$emit('input', newValue);
    },
    onInput(value) {
      this.$emit('input', value);
    },
    searchMethod(e) {
      if (this.apiSearch) {
        return this.apiSearch(e).then(result => {
          this.dataSource = result[0] || [];
          return [this.valueTable, result[1]];
        });
      }
      return new Promise(resolve => {
        resolve([]);
      });
    },
  },
};
</script>