first
This commit is contained in:
8
packages/transfer/index.js
Normal file
8
packages/transfer/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import Transfer from './src/main';
|
||||
|
||||
/* istanbul ignore next */
|
||||
Transfer.install = function(Vue) {
|
||||
Vue.component(Transfer.name, Transfer);
|
||||
};
|
||||
|
||||
export default Transfer;
|
231
packages/transfer/src/main.vue
Normal file
231
packages/transfer/src/main.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<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>
|
251
packages/transfer/src/transfer-panel.vue
Normal file
251
packages/transfer/src/transfer-panel.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<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>
|
Reference in New Issue
Block a user