<template> <div class="el-transfer-panel"> <p class="el-transfer-panel__header"> <el-checkbox v-model="allChecked" :indeterminate="isIndeterminate" @change="handleAllCheckedChange"> {{ title }} <span>{{ checkedSummary }}</span> </el-checkbox> </p> <div :class="['el-transfer-panel__body', hasFooter ? 'is-with-footer' : '']"> <el-input v-if="filterable" v-model="query" :placeholder="placeholder" class="el-transfer-panel__filter" size="small" @mouseenter.native="inputHover = true" @mouseleave.native="inputHover = false"> <i slot="prefix" :class="['el-input__icon', 'el-icon-' + inputIcon]" @click="clearQuery" /> </el-input> <el-checkbox-group v-infinite-scroll="loadMore" v-show="!hasNoMatch && data.length > 0" :infinite-scroll-distance="10" v-model="checked" :class="{ 'is-filterable': filterable }" class="el-transfer-panel__list"> <el-checkbox v-for="item in dataForShow" :label="item[keyProp]" :disabled="item[disabledProp]" :key="item[keyProp]" class="el-transfer-panel__item"> <option-content :option="item"/> </el-checkbox> </el-checkbox-group> <p v-show="hasNoMatch" class="el-transfer-panel__empty">{{ t('el.transfer.noMatch') }}</p> <p v-show="data.length === 0 && !hasNoMatch" class="el-transfer-panel__empty">{{ t('el.transfer.noData') }}</p> </div> <p v-if="hasFooter" class="el-transfer-panel__footer"> <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' let start = new Date().getTime() export default { name: '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> } } }, mixins: [Locale], componentName: 'ElTransferPanel', 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, dataForShow: [], pageNumber: 1, pageSize: 50 } }, computed: { filteredData() { start = new Date().getTime() const arr = 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 } }) this.dataForShow = arr.slice(0, this.pageSize) console.log('filteredData耗时', new Date().getTime() - start) return arr }, 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 } }, watch: { checked(val, oldVal) { start = new Date().getTime() this.updateAllChecked() const newObj = {} val.every((item) => { newObj[item] = true }) const oldObj = {} oldVal.every((item) => { oldObj[item] = true }) if (this.checkChangeByUser) { // const movedKeys = val.concat(oldVal) const movedKeys = this.dataForShow.concat(oldVal) .filter(v => newObj[v] || oldVal[v]) this.$emit('checked-change', val, movedKeys) } else { this.$emit('checked-change', val) this.checkChangeByUser = true } console.log('checked耗时', new Date().getTime() - start) }, data() { start = new Date().getTime() 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 console.log('data耗时', new Date().getTime() - start) }, checkableData() { this.updateAllChecked() }, defaultChecked: { immediate: true, handler(val, oldVal) { start = new Date().getTime() 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 console.log('defaultCheck耗时', new Date().getTime() - start) } } }, methods: { updateAllChecked() { const start = new Date().getTime() const checkObj = {} this.checked.forEach((item, index) => { checkObj[item] = true }) // 通过对象的k-v对应,n(1)的方式寻找数组中是否存在某元素 this.allChecked = this.checkableData.length > 0 && this.checked.length > 0 && this.checkableData.every((item) => checkObj[item[this.keyProp]]) // 上面被注释的源码是最耗时的,所有一直看耗时就可以了 console.log('updateAllCheckedEnd', new Date().getTime() - start) }, handleAllCheckedChange(value) { // debugger start = new Date().getTime() this.checked = value ? this.checkableData.map(item => item[this.keyProp]) : [] console.log('handleAllCheckedChange耗时', new Date().getTime() - start) }, loadMore() { console.log('1111') this.pageNumber++ this.dataForShow = this.filteredData.slice(0, this.pageSize * this.pageNumber) }, clearQuery() { if (this.inputIcon === 'circle-close') { this.query = '' } } } } </script>