瀏覽代碼

fix: 加入Element UI的Transfer组件源码(以备修改)

furffico 1 年之前
父節點
當前提交
28bec8758b
共有 2 個文件被更改,包括 443 次插入0 次删除
  1. 215 0
      src/components/transfer-box/index.vue
  2. 228 0
      src/components/transfer-box/transfer-panel.vue

+ 215 - 0
src/components/transfer-box/index.vue

@@ -0,0 +1,215 @@
+<template>
+  <div class="el-transfer">
+    <transfer-panel v-bind="$props" ref="leftPanel" :data="sourceData" :title="titles[0] || t('el.transfer.titles.0')"
+      :default-checked="leftDefaultChecked" :placeholder="filterPlaceholder || t('el.transfer.filterPlaceholder')"
+      @checked-change="onSourceCheckedChange">
+      <slot name="left-footer"></slot>
+    </transfer-panel>
+    <div class="el-transfer__buttons">
+      <el-button type="primary" :class="['el-transfer__button', hasButtonTexts ? 'is-with-texts' : '']"
+        @click.native="addToLeft" :disabled="rightChecked.length === 0">
+        <i class="el-icon-arrow-left"></i>
+        <span v-if="buttonTexts[0] !== undefined">{{ buttonTexts[0] }}</span>
+      </el-button>
+      <el-button type="primary" :class="['el-transfer__button', hasButtonTexts ? 'is-with-texts' : '']"
+        @click.native="addToRight" :disabled="leftChecked.length === 0">
+        <span v-if="buttonTexts[1] !== undefined">{{ buttonTexts[1] }}</span>
+        <i class="el-icon-arrow-right"></i>
+      </el-button>
+    </div>
+    <transfer-panel v-bind="$props" ref="rightPanel" :data="targetData" :title="titles[1] || t('el.transfer.titles.1')"
+      :default-checked="rightDefaultChecked" :placeholder="filterPlaceholder || t('el.transfer.filterPlaceholder')"
+      @checked-change="onTargetCheckedChange">
+      <slot name="right-footer"></slot>
+    </transfer-panel>
+  </div>
+</template>
+
+<script>
+import ElButton from 'element-ui/packages/button';
+import Emitter from 'element-ui/src/mixins/emitter';
+import Locale from 'element-ui/src/mixins/locale';
+import TransferPanel from './transfer-panel.vue';
+import Migrating from 'element-ui/src/mixins/migrating';
+
+export default {
+  name: 'ElTransfer',
+
+  mixins: [Emitter, Locale, Migrating],
+
+  components: {
+    TransferPanel,
+    ElButton
+  },
+
+  props: {
+    data: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    titles: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    buttonTexts: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    filterPlaceholder: {
+      type: String,
+      default: ''
+    },
+    filterMethod: Function,
+    leftDefaultChecked: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    rightDefaultChecked: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    renderContent: Function,
+    value: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    format: {
+      type: Object,
+      default() {
+        return {};
+      }
+    },
+    filterable: Boolean,
+    props: {
+      type: Object,
+      default() {
+        return {
+          label: 'label',
+          key: 'key',
+          disabled: 'disabled'
+        };
+      }
+    },
+    targetOrder: {
+      type: String,
+      default: 'original'
+    }
+  },
+
+  data() {
+    return {
+      leftChecked: [],
+      rightChecked: []
+    };
+  },
+
+  computed: {
+    dataObj() {
+      const key = this.props.key;
+      return this.data.reduce((o, cur) => (o[cur[key]] = cur) && o, {});
+    },
+
+    sourceData() {
+      return this.data.filter(item => this.value.indexOf(item[this.props.key]) === -1);
+    },
+
+    targetData() {
+      if (this.targetOrder === 'original') {
+        return this.data.filter(item => this.value.indexOf(item[this.props.key]) > -1);
+      } else {
+        return this.value.reduce((arr, cur) => {
+          const val = this.dataObj[cur];
+          if (val) {
+            arr.push(val);
+          }
+          return arr;
+        }, []);
+      }
+    },
+
+    hasButtonTexts() {
+      return this.buttonTexts.length === 2;
+    }
+  },
+
+  watch: {
+    value(val) {
+      this.dispatch('ElFormItem', 'el.form.change', val);
+    }
+  },
+
+  methods: {
+    getMigratingConfig() {
+      return {
+        props: {
+          'footer-format': 'footer-format is renamed to format.'
+        }
+      };
+    },
+
+    onSourceCheckedChange(val, movedKeys) {
+      this.leftChecked = val;
+      if (movedKeys === undefined) return;
+      this.$emit('left-check-change', val, movedKeys);
+    },
+
+    onTargetCheckedChange(val, movedKeys) {
+      this.rightChecked = val;
+      if (movedKeys === undefined) return;
+      this.$emit('right-check-change', val, movedKeys);
+    },
+
+    addToLeft() {
+      let currentValue = this.value.slice();
+      this.rightChecked.forEach(item => {
+        const index = currentValue.indexOf(item);
+        if (index > -1) {
+          currentValue.splice(index, 1);
+        }
+      });
+      this.$emit('input', currentValue);
+      this.$emit('change', currentValue, 'left', this.rightChecked);
+    },
+
+    addToRight() {
+      let currentValue = this.value.slice();
+      const itemsToBeMoved = [];
+      const key = this.props.key;
+      this.data.forEach(item => {
+        const itemKey = item[key];
+        if (
+          this.leftChecked.indexOf(itemKey) > -1 &&
+          this.value.indexOf(itemKey) === -1
+        ) {
+          itemsToBeMoved.push(itemKey);
+        }
+      });
+      currentValue = this.targetOrder === 'unshift'
+        ? itemsToBeMoved.concat(currentValue)
+        : currentValue.concat(itemsToBeMoved);
+      this.$emit('input', currentValue);
+      this.$emit('change', currentValue, 'right', this.leftChecked);
+    },
+
+    clearQuery(which) {
+      if (which === 'left') {
+        this.$refs.leftPanel.query = '';
+      } else if (which === 'right') {
+        this.$refs.rightPanel.query = '';
+      }
+    }
+  }
+};
+</script>

+ 228 - 0
src/components/transfer-box/transfer-panel.vue

@@ -0,0 +1,228 @@
+<template>
+  <div class="el-transfer-panel">
+    <p class="el-transfer-panel__header">
+      <el-checkbox v-model="allChecked" @change="handleAllCheckedChange" :indeterminate="isIndeterminate">
+        {{ title }}
+        <span>{{ checkedSummary }}</span>
+      </el-checkbox>
+    </p>
+
+    <div :class="['el-transfer-panel__body', hasFooter ? 'is-with-footer' : '']">
+      <el-input class="el-transfer-panel__filter" v-model="query" size="small" :placeholder="placeholder"
+        @mouseenter.native="inputHover = true" @mouseleave.native="inputHover = false" v-if="filterable">
+        <i slot="prefix" :class="['el-input__icon', 'el-icon-' + inputIcon]" @click="clearQuery"></i>
+      </el-input>
+      <el-checkbox-group v-model="checked" v-show="!hasNoMatch && data.length > 0"
+        :class="{ 'is-filterable': filterable }" class="el-transfer-panel__list">
+        <el-checkbox class="el-transfer-panel__item" :label="item[keyProp]" :disabled="item[disabledProp]"
+          :key="item[keyProp]" v-for="item in filteredData">
+          <option-content :option="item"></option-content>
+        </el-checkbox>
+      </el-checkbox-group>
+      <p class="el-transfer-panel__empty" v-show="hasNoMatch">{{ t('el.transfer.noMatch') }}</p>
+      <p class="el-transfer-panel__empty" v-show="data.length === 0 && !hasNoMatch">{{ t('el.transfer.noData') }}</p>
+    </div>
+    <p class="el-transfer-panel__footer" v-if="hasFooter">
+      <slot></slot>
+    </p>
+  </div>
+</template>
+
+<script>
+import ElCheckboxGroup from 'element-ui/packages/checkbox-group';
+import ElCheckbox from 'element-ui/packages/checkbox';
+import ElInput from 'element-ui/packages/input';
+import Locale from 'element-ui/src/mixins/locale';
+
+export default {
+  mixins: [Locale],
+
+  name: 'ElTransferPanel',
+
+  componentName: 'ElTransferPanel',
+
+  components: {
+    ElCheckboxGroup,
+    ElCheckbox,
+    ElInput,
+    OptionContent: {
+      props: {
+        option: Object
+      },
+      render(h) {
+        const getParent = vm => {
+          if (vm.$options.componentName === 'ElTransferPanel') {
+            return vm;
+          } else if (vm.$parent) {
+            return getParent(vm.$parent);
+          } else {
+            return vm;
+          }
+        };
+        const panel = getParent(this);
+        const transfer = panel.$parent || panel;
+        return panel.renderContent
+          ? panel.renderContent(h, this.option)
+          : transfer.$scopedSlots.default
+            ? transfer.$scopedSlots.default({ option: this.option })
+            : <span>{this.option[panel.labelProp] || this.option[panel.keyProp]}</span>;
+      }
+    }
+  },
+
+  props: {
+    data: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    renderContent: Function,
+    placeholder: String,
+    title: String,
+    filterable: Boolean,
+    format: Object,
+    filterMethod: Function,
+    defaultChecked: Array,
+    props: Object
+  },
+
+  data() {
+    return {
+      checked: [],
+      allChecked: false,
+      query: '',
+      inputHover: false,
+      checkChangeByUser: true
+    };
+  },
+
+  watch: {
+    checked(val, oldVal) {
+      this.updateAllChecked();
+      if (this.checkChangeByUser) {
+        const movedKeys = val.concat(oldVal)
+          .filter(v => val.indexOf(v) === -1 || oldVal.indexOf(v) === -1);
+        this.$emit('checked-change', val, movedKeys);
+      } else {
+        this.$emit('checked-change', val);
+        this.checkChangeByUser = true;
+      }
+    },
+
+    data() {
+      const checked = [];
+      const filteredDataKeys = this.filteredData.map(item => item[this.keyProp]);
+      this.checked.forEach(item => {
+        if (filteredDataKeys.indexOf(item) > -1) {
+          checked.push(item);
+        }
+      });
+      this.checkChangeByUser = false;
+      this.checked = checked;
+    },
+
+    checkableData() {
+      this.updateAllChecked();
+    },
+
+    defaultChecked: {
+      immediate: true,
+      handler(val, oldVal) {
+        if (oldVal && val.length === oldVal.length &&
+          val.every(item => oldVal.indexOf(item) > -1)) return;
+        const checked = [];
+        const checkableDataKeys = this.checkableData.map(item => item[this.keyProp]);
+        val.forEach(item => {
+          if (checkableDataKeys.indexOf(item) > -1) {
+            checked.push(item);
+          }
+        });
+        this.checkChangeByUser = false;
+        this.checked = checked;
+      }
+    }
+  },
+
+  computed: {
+    filteredData() {
+      return this.data.filter(item => {
+        if (typeof this.filterMethod === 'function') {
+          return this.filterMethod(this.query, item);
+        } else {
+          const label = item[this.labelProp] || item[this.keyProp].toString();
+          return label.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
+        }
+      });
+    },
+
+    checkableData() {
+      return this.filteredData.filter(item => !item[this.disabledProp]);
+    },
+
+    checkedSummary() {
+      const checkedLength = this.checked.length;
+      const dataLength = this.data.length;
+      const { noChecked, hasChecked } = this.format;
+      if (noChecked && hasChecked) {
+        return checkedLength > 0
+          ? hasChecked.replace(/\${checked}/g, checkedLength).replace(/\${total}/g, dataLength)
+          : noChecked.replace(/\${total}/g, dataLength);
+      } else {
+        return `${checkedLength}/${dataLength}`;
+      }
+    },
+
+    isIndeterminate() {
+      const checkedLength = this.checked.length;
+      return checkedLength > 0 && checkedLength < this.checkableData.length;
+    },
+
+    hasNoMatch() {
+      return this.query.length > 0 && this.filteredData.length === 0;
+    },
+
+    inputIcon() {
+      return this.query.length > 0 && this.inputHover
+        ? 'circle-close'
+        : 'search';
+    },
+
+    labelProp() {
+      return this.props.label || 'label';
+    },
+
+    keyProp() {
+      return this.props.key || 'key';
+    },
+
+    disabledProp() {
+      return this.props.disabled || 'disabled';
+    },
+
+    hasFooter() {
+      return !!this.$slots.default;
+    }
+  },
+
+  methods: {
+    updateAllChecked() {
+      const checkableDataKeys = this.checkableData.map(item => item[this.keyProp]);
+      this.allChecked = checkableDataKeys.length > 0 &&
+        checkableDataKeys.every(item => this.checked.indexOf(item) > -1);
+    },
+
+    handleAllCheckedChange(value) {
+      this.checked = value
+        ? this.checkableData.map(item => item[this.keyProp])
+        : [];
+    },
+
+    clearQuery() {
+      if (this.inputIcon === 'circle-close') {
+        this.query = '';
+      }
+    }
+  }
+};
+</script>