效果图

这是 Element-UI 的 Transfer 组件,下面就配合源码看下具体实现。

Template

<template><div class="el-transfer"><transfer-panel// 将父组件的props一起传给自组件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"// 动态绑定disable状态:disabled="rightChecked.length === 0"><i class="el-icon-arrow-left"></i>// 按钮名称<span v-if="buttonTexts[0] !== undefined">{{ buttonTexts[0] }}</span></el-button><el-buttontype="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-panelv-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>
复制代码

整体上,可以划分为左中右三块,左右两个 TransferPanel 组件承载数据展示。中间两个 ElButton 是左右移动的操作按钮。结构清晰。

JS 部分

mixins: [Emitter, Locale, Migrating],
复制代码

mixins 部分混入了三个对象。Locale 是国际化的东西,Migrating 是组件迁移的一些提示信息。需要关注的是 Emitter 部分,代码如下:

// 寻找所有子组件,直到找到名为componentName的组件,调用其$emit方法
function broadcast(componentName, eventName, params) {this.$children.forEach(child => {var name = child.$options.componentName;if (name === componentName) {child.$emit.apply(child, [eventName].concat(params));} else {broadcast.apply(child, [componentName, eventName].concat([params]));}});
}
export default {// 事件定向传播methods: {// 寻找所有父组件,直到找到名为componentName的组件,调用其$emit方法dispatch(componentName, eventName, params) {var parent = this.$parent || this.$root;var name = parent.$options.componentName;while (parent && (!name || name !== componentName)) {parent = parent.$parent;if (parent) {name = parent.$options.componentName;}}if (parent) {parent.$emit.apply(parent, [eventName].concat(params));}},broadcast(componentName, eventName, params) {broadcast.call(this, componentName, eventName, params);}}
};
复制代码

提供了两个方法: dispatch, broadcast 做事件的定向传播。

属性 props

  • data ,Transfer 的数据源
// array[{ key, label, disabled }]
data: {
type: Array,
default() {return [];
}
}
复制代码

传入的数组,每一项需要有三个属性,key : 唯一标识,label : 展示内容,disabled : 是否可勾选,如果不想用这三个属性名,可以通过 props 属性设置别名。

  • props , 数据源的字段别名
props: {type: Object,default() {return {label: 'label',key: 'key',disabled: 'disabled'};}}
复制代码
  • titles , 允许自定义标题列表
// ['列表 1', '列表 2']
titles: {type: Array,default() {return [];}}
复制代码
  • buttonTexts , 自定义 el-button 文案
// ['到左边', '到右边']
buttonTexts: {type: Array,default() {return [];}}
复制代码
  • filterPlaceholder , 搜索框占位符
filterPlaceholder: {type: String,default: ''}
复制代码
  • filterMethod , 自定义搜索方法
filterMethod: Function
复制代码
  • leftDefaultChecked / rightDefaultChecked ,初始状态下左侧/右侧列表的已勾选项的 key 数组
leftDefaultChecked: {type: Array,default() {return [];}}
复制代码
  • renderContent , 自定义的数据渲染函数
renderContent: Function,
复制代码
  • value , 目标列表的 key 数组
value: {type: Array,default() {return [];}}
复制代码
  • format , 列表顶部勾选状态文案
// object{noChecked, hasChecked}format: {type: Object,default() {return {};}}
复制代码
  • filterable , 是否可搜索,默认为 false
filterable: Boolean
复制代码
  • targetOrder , 右侧列表元素的排序策略:若为 original,则保持与数据源相同的顺序;若为 push,则新加入的元素排在最后;若为 unshift,则新加入的元素排在最前
targetOrder: {type: String,default: 'original'}
复制代码

计算属性 computed

  • dataObj , data 数组转为对象
// [{key:1,label:'数据1',disabled:false}] => {1:{key:1,label:'数据1',disabled:false}
dataObj() {const key = this.props.key;return this.data.reduce((o, cur) => (o[cur[key]] = cur) && o, {});
}
复制代码
  • sourceData , leftPanel 数据源
// 筛选在 data 中 ,但是不在 value 中的数据
sourceData() {return this.data.filter(item => this.value.indexOf(item[this.props.key]) === -1);}
复制代码
  • targetData , rightPanel 数据源
targetData() {// 目标源排序顺序为 original,按照数据在 data 数组的先后顺序if (this.targetOrder === 'original') {return this.data.filter(item => this.value.indexOf(item[this.props.key]) > -1);// 否则按照 value 数组中 key 的先后顺序} else {return this.value.reduce((arr, cur) => {const val = this.dataObj[cur];if (val) {arr.push(val);}return arr;}, []);}}
复制代码
  • hasButtonTexts , 是否传入可用的按钮文案
// 当传入的 button-text 有两项的时候返回 true
hasButtonTexts() {return this.buttonTexts.length === 2;
}
复制代码

方法 methods

methods 中 leftPanel 和 rightPanel 的部分是对称的,所以只选取 rightPanel 部分展示:

// val : 当前选中项的 key 数组
// movedKeys: 选中状态发生变化的 key 数组
onTargetCheckedChange(val, movedKeys) {this.rightChecked = val;if (movedKeys === undefined) return;this.$emit('right-check-change', val, movedKeys);
},
addToLeft() {// rightPanel 中数据项的 key 数组let currentValue = this.value.slice();// 从 currentValue 中删除选中的项this.rightChecked.forEach(item => {const index = currentValue.indexOf(item);if (index > -1) {currentValue.splice(index, 1);}});// currentValue: 当前 rightPanel 中存在数据的 key 数组// rightChecked: 选中移动的数据项的 key 数组this.$emit('input', currentValue);this.$emit('change', currentValue, 'left', this.rightChecked);},
clearQuery(which) {// 清除 leftPanel 搜索栏的搜索条件if (which === 'left') {this.$refs.leftPanel.query = '';// 清除 rightPanel 搜索栏的搜索条件} else if (which === 'right') {this.$refs.rightPanel.query = '';}
}
复制代码

Transfer 组件部分就这些内容,主要是控制传入 TransferPanel 的 data ,以及向外发射 change ,check-change 事件。

ElTransferPanel

template

<template><div class="el-transfer-panel"><p class="el-transfer-panel__header">// 全选框<el-checkbox// 绑定值初始为 falsev-model="allChecked"// 勾选回调@change="handleAllCheckedChange"// 设置 indeterminate 状态,只负责样式控制:indeterminate="isIndeterminate">// 展示文本{{ title }}// 勾选总结文本<span>{{ checkedSummary }}</span></el-checkbox></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 事件@mouseenter.native="inputHover = true"// mouseleave 事件@mouseleave.native="inputHover = false"// 设置 filterable 才展示v-if="filterable">// prefix 插槽, 点击清除搜索栏条件<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"// 根据 filterable 动态绑定 class:class="{ 'is-filterable': filterable }"class="el-transfer-panel__list">// v-for 列表渲染,数据源为 filteredData<el-checkboxclass="el-transfer-panel__item"// 选中状态的值:label="item[keyProp]"// 是否禁用:disabled="item[disabledProp]":key="item[keyProp]"v-for="item in filteredData">// option-content 组件<option-content :option="item"></option-content></el-checkbox></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>// 底部插槽,当设置footer时展示<p class="el-transfer-panel__footer" v-if="hasFooter"><slot></slot></p></div>
</template>
复制代码

JS

引入的组件中,需要关注下 option-content ,它是 render 函数直接定义的

OptionContent: {props: {option: Object},render(h) {// 获取名为 ElTransferPanel 的父组件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);// 获取 transfer 组件const transfer = panel.$parent || panel;// 如果设置了自定义数据项渲染函数,则调用自定义的渲染函数// 如果没有定义 render-content 方法,则检查 Transfer 组件是否设置了 slot-scope 插槽内容// 如果设置了,则用 slot-scope 内容渲染// 否则用默认的 span 标签渲染// 意味着数据项的渲染可以通过 render-content 或者 slot-scoped 自定义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>;}}
复制代码

组件传入的 option 是 item ,item 来自 filteredData,

filteredData() {// data 为 数据源, leftPanel 对应 sourceDatareturn this.data.filter(item => {// 如果自定义了搜索方法,则调用自定义的方法if (typeof this.filterMethod === 'function') {return this.filterMethod(this.query, item);// 默认搜索规则是数据项的 label 中是否包含输入的条件 } else {const label = item[this.labelProp] || item[this.keyProp].toString();return label.toLowerCase().indexOf(this.query.toLowerCase()) > -1;}});
}
复制代码

watch

  // 选择项发生变化// val 当前选中的元素的 key 数组// oldVal 前一状态选中的元素的 key 数组checked(val, oldVal) {// 更新全新状态this.updateAllChecked();// 如果改变是用户点击造成的if (this.checkChangeByUser) {// 选中状态发生变化的元素的 key 数组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;}}
复制代码

Transferpanel 组件的 computed 比较简单,主要的 filteredData 在上面已经提过,下面看他的 methods

methods


// 更新全选状态
updateAllChecked() {// 所有可勾选数据项的 key 数组const checkableDataKeys = this.checkableData.map(item => item[this.keyProp]);// 所有可勾选的项都在已勾选数组中,则标记为全勾选状态this.allChecked = checkableDataKeys.length > 0 &&checkableDataKeys.every(item => this.checked.indexOf(item) > -1);
},
// 勾选全选框的回调
handleAllCheckedChange(value) {// 如果是选中,则将所有可勾选数据项的 key 放入 checked 数组// 如果是取消选中,则清空 checked 数组this.checked = value? this.checkableData.map(item => item[this.keyProp]): [];
},
// 清空搜索栏
clearQuery() {// 如果搜索栏输入了内容if (this.inputIcon === 'circle-close') {this.query = '';}
}复制代码

转载于:https://juejin.im/post/5ccc300b6fb9a0321855604e

Element Transfer相关推荐

  1. element筛选 ajax,vue使用element Transfer 穿梭框实现ajax请求数据和自定义查询

    ##vue使用element Transfer 穿梭框实现ajax请求数据和自定义查询 基于element Transfer 直接上代码 XXXXX 编辑XXX 编辑XXXX style=" ...

  2. 组件-Element—Transfer(穿梭框)

    组件-Element-Transfer(穿梭框) 组件-穿梭框 基础用法 <template><el-transfer v-model="value" :data ...

  3. Element transfer 两边数据(左右)的显示问题?

    本仙今天遇到这个穿梭框的问题 这个是我前几天刚换的(原来用的是iview的,换成了element ) 别问我为什么,用过iview的都知道 转入正题 问题:从后台获取的数据全部都显示在了我的左边框中 ...

  4. vue element Transfer 穿梭框 自定义数

    1.Transfer 穿梭框自定义数据时有两种方式: (1)使用"render-content 函数"方式 (2)使用" slot-scope="{ optio ...

  5. php穿梭框多选,多选穿梭框总结 (vue + element)

    示例 介绍 实现省市区三级多选联动,可任选一个省级.市级.区级,加入已选框,也可以在已选框中删除对应的区域. 选择对应仓库,自动勾选仓库对应的省,取消就反选 选择同样地区,选择省级或市级,若该对象下面 ...

  6. Linux usb 3. Host 详解

    文章目录 1. 简介 2. Usb Core 驱动设备模型 2.1 Usb Device Layer 2.1.1 device (struct usb_device) 2.1.2 driver (st ...

  7. ajax浏览器操作发生异常,解决IE浏览器缓存导致AJAX请求数据异常

    IE10浏览器会把AJAX请求的数据都缓存下来,然后每次想去刷新数据时发现数据都是一样的,于是导致数据显示异常. 解决方法: 在页面 标签里,加上以下声明: 保存后,刷新页面,重新访问即可. 说明:最 ...

  8. linux ehci ehci_urb_enqueue之qh_urb_transaction()分析 【史上最强大分析】

    以下文字会对linux usb hcd driver中的ehci_urb_enqueue函数做一些说明. 先把该函数罗列一下. /** non-error returns are a promise ...

  9. linux usb系统

    1.   简述: USB 出自豪门,一问世便有 IBM,Microsoft,compaq 等前呼后拥,不红实在是没有道理,以致于连三岁小毛孩都知道买游戏手柄要买 USB 的. USB 名气这么大,但 ...

最新文章

  1. Java单例模式个人总结(实例变量和类变量)
  2. 剑指offer:面试题22. 链表中倒数第k个节点
  3. python时间函数报错_python3中datetime库,time库以及pandas中的时间函数区别与详解...
  4. move语句java_Oracle中的move命令
  5. JNDI数据源的配置
  6. MFC中创建线程实例
  7. (小费马定理降幂)Sum
  8. jquery.pin 修改浮动的top元素
  9. java 运行时异常 处理_如何在Java中处理运行时异常?
  10. BricsCAD 22 for Mac(CAD建模软件)
  11. vscode格式化代码快捷键
  12. springboot2 oauth2 jwt认证服务器和资源服务器
  13. windows通过javaw启动spring boot项目jar命令,查看进程命令,关闭进程命令
  14. linux忘记密码,使用星号密码查看器,查看SSH工具记录的密码
  15. 还原html默认打开方式,怎么还原打开方式,详细教您Win10系统下如何还原程序默认打开方式...
  16. 全部口罩机3D图纸图档打包
  17. Jquery颜色选择插件使用
  18. 反斜杆e,Linux下五彩斑斓的命令行输出
  19. model.named_parameters()与model.parameters()
  20. 听诊器的基本构造及其特征

热门文章

  1. IDEA和Tomcat 的相关配置
  2. 拍婚纱照六个注意事项
  3. CsPbI3钙钛矿量子点 CsPbI3 QDs发射波长670±10nm
  4. 浏览器的 5 种 Observer,你用过几种?
  5. 投资理财-要有家国情怀
  6. Linux开启/关闭防火墙
  7. IOS 字符串转日期
  8. “新四化”:危机下的汽车产业转型路标
  9. linux 脚本 上网限制,Linux_Ubuntu下限制局域网网速教程,为了限制无线路由器上大家的 - phpStudy...
  10. 苹果加内存-macbook pro 升级内存