<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>