Commit 174cadd3 authored by linhuandong's avatar linhuandong

添加tranfer-grp组件

parent 38ab08e0
......@@ -4,7 +4,7 @@
:::demo Transfer 的数据通过 `data` 属性传入。数据需要是一个对象数组,每个对象有以下属性:`key` 为数据的唯一性标识,`label` 为显示文本,`disabled` 表示该项数据是否禁止转移。目标列表中的数据项会同步到绑定至 `v-model` 的变量,值为数据项的 `key` 所组成的数组。当然,如果希望在初始状态时目标列表不为空,可以像本例一样为 `v-model` 绑定的变量赋予一个初始值。
```html
<template>
<el-transfer v-model="value" :data="data"></el-transfer>
<el-transfer-grp v-model="value" :data="data"></el-transfer-grp>
</template>
<script>
......@@ -16,7 +16,7 @@
data.push({
key: i,
label: `备选项 ${ i }`,
disabled: i % 4 === 0
// disabled: i % 4 === 0
});
}
return data;
......
import TransferGrp from './src/main';
/* istanbul ignore next */
TransferGrp.install = function(Vue) {
Vue.component(TransferGrp.name, TransferGrp);
};
export default TransferGrp;
<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">
<div>
<el-button type="primary" :class="['el-transfer__button', hasButtonTexts ? 'is-with-texts' : '']"
@click.native="moveUp" :disabled="upDisabled">
<i class="el-icon-arrow-up"></i>
</el-button>
</div>
<div>
<el-button type="primary" :class="['el-transfer__button', hasButtonTexts ? 'is-with-texts' : '']"
@click.native="moveDown" :disabled="downDisabled">
<i class="el-icon-arrow-down"></i>
</el-button>
</div>
<div>
<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>
</el-button>
</div>
<div>
<el-button type="primary" :class="['el-transfer__button', hasButtonTexts ? 'is-with-texts' : '']"
@click.native="addToRight" :disabled="leftChecked.length === 0">
<i class="el-icon-arrow-right"></i>
</el-button>
</div>
</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: 'ElTransferGrp',
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: [],
upDisabled: true,
downDisabled: true
};
},
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);
let arr = [];
this.value.forEach(o => {
arr.push(this.data.find(o2 => o2[this.props.key] === o));
});
return arr;
} 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;
this.upDisabled = true;
this.downDisabled = true;
if (movedKeys === undefined) return;
this.$emit('left-check-change', val, movedKeys);
},
onTargetCheckedChange(val, movedKeys, currentItem) {
this.rightChecked = val;
if (currentItem != null) {
let index = this.targetData.indexOf(currentItem);
this.upDisabled = index === 0;
this.downDisabled = index === this.targetData.length - 1;
}
if (movedKeys === undefined) return;
this.$emit('right-check-change', val, movedKeys);
},
moveUp() {
let currentItem = this.$refs.rightPanel.getCurrentItem();
let oldIndex = this.value.indexOf(currentItem[this.props.key]);
let newIndex = oldIndex - 1;
this.value.splice(newIndex, 0, this.value.splice(oldIndex, 1)[0]);
console.log(this.value);
},
moveDown() {
let currentItem = this.$refs.rightPanel.getCurrentItem();
let oldIndex = this.value.indexOf(currentItem[this.props.key]);
let newIndex = oldIndex + 1;
this.value.splice(newIndex, 0, this.value.splice(oldIndex, 1)[0]);
console.log(this.value);
},
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>
<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">
<div @click="clickOption(item)"
:style="{ 'background-color': item !== currentItem ? 'transparent' : '#94d2fb' }">
<option-content :option="item"></option-content>
</div>
</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,
currentItem: null
};
},
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, this.currentItem);
} else {
this.$emit('checked-change', val, undefined, this.currentItem);
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: {
getCurrentItem() {
return this.currentItem;
},
clickOption(item) {
this.currentItem = item;
},
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>
......@@ -64,6 +64,7 @@ import CollapseItem from '../packages/collapse-item/index.js';
import Cascader from '../packages/cascader/index.js';
import ColorPicker from '../packages/color-picker/index.js';
import Transfer from '../packages/transfer/index.js';
import GrpTransfer from '../packages/grp-transfer/index.js';
import Container from '../packages/container/index.js';
import Header from '../packages/header/index.js';
import Aside from '../packages/aside/index.js';
......@@ -152,6 +153,7 @@ const components = [
Cascader,
ColorPicker,
Transfer,
GrpTransfer,
Container,
Header,
Aside,
......@@ -210,7 +212,7 @@ if (typeof window !== 'undefined' && window.Vue) {
}
export default {
version: '2.15.7683',
version: '2.15.7684',
locale: locale.use,
i18n: locale.i18n,
install,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment