博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Element Transfer
阅读量:5944 次
发布时间:2019-06-19

本文共 9405 字,大约阅读时间需要 31 分钟。

效果图

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

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

复制代码

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 })          : { this.option[panel.labelProp] || this.option[panel.keyProp] };    } }复制代码

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

filteredData() {    // data 为 数据源, leftPanel 对应 sourceData    return 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

你可能感兴趣的文章
mysql开启binlog
查看>>
ctrl + z fg bg
查看>>
工作流引擎Oozie(一):workflow
查看>>
struct框架
查看>>
Deep Learning(深度学习)相关网站
查看>>
设置Eclipse编码方式
查看>>
分布式系统唯一ID生成方案汇总【转】
查看>>
并查集hdu1232
查看>>
oracle进行字符串拆分并组成数组
查看>>
100多个基础常用JS函数和语法集合大全
查看>>
Java8 lambda表达式10个示例
查看>>
innerHTML outerHTML innerText
查看>>
kafka安装教程
查看>>
go语言基础
查看>>
【Windows】字符串处理
查看>>
Spring(十八):Spring AOP(二):通知(前置、后置、返回、异常、环绕)
查看>>
CentOS使用chkconfig增加开机服务提示service xxx does not support chkconfig的问题解决
查看>>
微服务+:服务契约治理
查看>>
save
查看>>
Android DrawLayout + ListView 的使用(一)
查看>>