失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 基于elementui源码实现自定义穿梭框transfer组件

基于elementui源码实现自定义穿梭框transfer组件

时间:2022-07-06 09:32:31

相关推荐

基于elementui源码实现自定义穿梭框transfer组件

需求:更改elementui中el-transfer样式, 实现单击左侧选项即加入右边选框,单击右侧选项即可回到左侧选框,中间左右来回按钮更改为左侧全选右侧全选,最终修改后效果图如下

下图源码可通过github下载:/applebring/elementui

本以为自己能够不看elementui源码实现这需求,在折腾了一天还没实现后,还是回归到其源码进行研究,自己通过纯vue实现主要遇到了以下问题:

若是不需要进行模糊查询可通过index进行增删改查实现穿梭框功能,但是若是加入模糊查询则问题百出,但进行filter过滤器过滤后,数组index发生了变化,通过index进行增删改查行不通,当然,本人在同事指点下换了思路,通过给每个选项一个固定的识别id进行对象查找和增删改,虽然增删改查实现了,但是全选却不知如何能实现,毕竟通过计算属性得到数组,当把数组清空时,无法让对象复制到右边选框~

因此,值此星期六日之际,对elementui代码进行研究,以解心中之虑

1、首先是上github拉取elementui源码/ElemeFE/element

2、本人在名为elementui源码文件夹下克隆github上element源码,在拉取后的element文件夹中packages文件目录下找到transfer文件夹,该文件即为el-transfer源码

3、将源码复制到自己项目中,通过组件方式将其引入,本人将main.vue复制在transfermain.vue文件夹下,将transfer-panel.vue复制在transferPanel.vue文件下,如

通过观察源代码,可以看出el-transfer实际上是将穿梭框分成两部分,左右边选择框分别为重复引入transferPanel组件,即tranfermain.vue实际上可分解为如下

因此,想要修改tranfer-panel样式可在transferPanel.vue里面更改,中间的按钮组在tansfermain.vue里更改,由上面可知,我们需要引入三个文件,内容分别如下:(以下文件为范例)

transfer.vue

<template><div><el-card class="box-card"><dir-transfer v-model="value2" filter-placeholder="请输入城市拼音":data="data2" :titles="titles"></dir-transfer></el-card></div></template><script>import DirTransfer from './transfermain.vue'export default {data() {const generateData2 = _ => {const data = [];const cities = ['上海', '北京', '广州', '深圳', '南京', '西安', '成都'];const pinyin = ['shanghai', 'beijing', 'guangzhou', 'shenzhen', 'nanjing', 'xian', 'chengdu'];cities.forEach((city, index) => {data.push({label: city,key: index,pinyin: pinyin[index]});});return data;};return {data2: generateData2(),value2: [],titles:['选择成员','选中成员'],//左右穿梭框的标题filterMethod(query, item) {return item.pinyin.indexOf(query) > -1;}};},components:{DirTransfer}};</script>

在该页面dir-transfer即与el-transfer一致,属性,方法,事件均与官方文档一致

transfermain.vue(本应该为源码,本处直接粘贴更改后的代码)

<template><div class="el-transfer"><transfer-panelv-bind="$props"ref="leftPanel":filterable="true":data="sourceData":title="titles[0] || t('el.transfer.titles.0')":default-checked="leftDefaultChecked"@checked-change="onSourceCheckedChange"><slot name="left-footer"></slot></transfer-panel><div class="el-transfer__buttons"><el-buttontype="primary":class="['el-transfer__button', hasButtonTexts ? 'is-with-texts' : '']"@click.native="allToLeft"><span>左全选</span><!--此处本人更改为自己所需按钮,按钮事件名字也从addToLeft改为allToLeft--></el-button><el-buttontype="primary":class="['el-transfer__button', hasButtonTexts ? 'is-with-texts' : '']"@click.native="allToRight"><span>右全选</span><!--此处本人更改为自己所需按钮,按钮事件名字也从addToRight改为allToRight--></el-button></div><transfer-panelv-bind="$props":filterable="false":isRight="true"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 './transferPanel.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: [],checkedleft:true,checkedright:false};},computed: {dataObj() {const key = this.props.key;return this.data.reduce((o, cur) => (o[cur[key]] = cur) && o, {});},sourceData() {console.log("this",this.data);return this.data.filter(item => this.value.indexOf(item[this.props.key]) === -1);},targetData() {return this.targetOrder === 'original'? this.data.filter(item => this.value.indexOf(item[this.props.key]) > -1): this.value.map(key => this.dataObj[key]);},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) {console.log("this leftCheck",this.leftChecked)this.leftChecked = val;if (movedKeys === undefined) return;this.$emit('left-check-change', val, movedKeys);this.addToRight();//在此处直接把选中项添加到右边框,实现单击操作,而不需通过按钮再次操作},onTargetCheckedChange(val, movedKeys) {this.rightChecked = val;if (movedKeys === undefined) return;this.$emit('right-check-change', val, movedKeys);this.addToLeft();//在此处直接把选中项添加到左边框,实现单击操作,而不需通过按钮再次操作},allToLeft(){this.$refs.leftPanel.handleAllCheckedChange(true);//直接出发全选事件,父组件调用子组件方法需要通过this.$refs.组件ref值.子组件方法名},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);},allToRight(){this.$refs.rightPanel.handleAllCheckedChange(true);//直接出发全选事件,父组件调用子组件方法需要通过this.$refs.组件ref值.子组件方法名},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>

该文件修改的主要难点在于如何通过按钮实现全选功能,摸索了半天,终于发现入口,通过调用handleAllCheckedChange(true),传入值为true即可完美实现,此处注意父组件调用子组件的方法需要通过this.$refs.组件ref值.子组件方法(参数)来调用

transferPanel.vue

<template><div class="el-transfer-panel" style="border:1px solid #409EFF"><p class="el-transfer-panel__header"><el-checkbox v-show="false" v-model="allChecked"@change="handleAllCheckedChange":indeterminate="isIndeterminate">{{ title }}<span>{{ checkedSummary }}</span></el-checkbox>{{ title }}</p><div :class="['el-transfer-panel__body', hasFooter ? 'is-with-footer' : '']"><el-inputclass="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-groupv-model="checked"v-show="!hasNoMatch && data.length > 0":class="{ 'is-filterable': filterable }"class="el-transfer-panel__list"><el-checkboxclass="el-transfer-panel__item":label="item[keyProp]":disabled="item[disabledProp]":key="item[keyProp]"v-for="item in filteredData"><option-content :option="item"></option-content><span v-if="isRight" class="icon-close" style="float:right;margin-right:10px;width:20px;margin-top:5px;line-height:20px;text-align:center;height:20px;background-color:#ddd;border-radius:50%;">×</span></el-checkbox><!--此处加了个判断是否为右边框给其加上相应的关闭按钮,isRight通过属性值进行传入--></el-checkbox-group><pclass="el-transfer-panel__empty"v-show="hasNoMatch">{{ t('el.transfer.noMatch') }}</p><pclass="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.$ponentName === '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,isRight: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);console.log("Check by User");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) {console.log("value",value);this.checked = value? this.checkableData.map(item => item[this.keyProp]): [];},clearQuery() {if (this.inputIcon === 'circle-close') {this.query = '';}}}};</script>

通过上面对elementui穿梭框的自定义样式,可类比其他组件的自定义修改

如果觉得《基于elementui源码实现自定义穿梭框transfer组件》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。