Element-UI Table组件上添加列拖拽效果实现方法


Posted in Javascript onApril 14, 2018

Element-UI 的 Table 组件很强大,但是我们的需求更强大...

简单粗暴的来一发效果图:

Element-UI Table组件上添加列拖拽效果实现方法

一、数据驱动

传统的拖动效果,都是基于通过 mousedown、mousemove、mouseup 事件来修改删减 dom 节点

但 Vue 是一个数据驱动的前端框架,开发时应尽量避免操作 dom

而且 Element-UI 的 Table 组件封装得很严谨,直接操作 dom 很容易产生不可预计的 bug

所以我的核心思路就是:通过一个数组渲染表头(列),然后修改这个数组的顺序,从而修改列表的列排序

template 部分:

<div class="w-table" :class="{'w-table_moving': dragState.dragging}">
 <el-table :data="data"
  :border="option.border"
  :height="option.height"
  :max-height="option.maxHeight"
  :style="{ width: parseInt(option.width)+'px' }"
  :header-cell-class-name="headerCellClassName"
 >
  <slot name="fixed"></slot>
  <el-table-column v-for="(col, index) in tableHeader" :key="index"
  :prop="col.prop"
  :label="col.label"
  :width="col.width"
  :min-width="col.minWidth"
  :type="col.type"
  :header-align="col.headerAlign"
  :column-key="index.toString()"
  :render-header="renderHeader"
  >
  </el-table-column>
 </el-table>
 </div>

上面的 data 是列表数据集合,option 是 Table 组件配置项,header 是表头数据集合,由父组件传入

props: {
 data: {
  default: function () {
  return []
  },
  type: Array
 },
 header: {
  default: function () {
  return []
  },
  type: Array
 },
 option: {
  default: function () {
  return {}
  },
  type: Object
 }
 }

配置项可以根据 Element-UI 的 api 自行删减

但有几个参数在组件内部被征用:

1. header-cell-class-name

绑定了一个函数,动态给表头单元格添加 class,从而实现拖动中的虚线效果。

2. column-key

绑定为 header 数组的 index,用于确定需要修改的 header 元素下标

3. render-header

表头渲染函数,用以添加自定义方法,以监听 mousemove 等相关事件 

二、记录拖动状态

拖动过程中需要记录几个关键参数:

data () {
 return {
  tableHeader: this.header,
  dragState: {
  start: -1, // 起始元素的 index
  end: -1, // 结束元素的 index
  move: -1, // 移动鼠标时所覆盖的元素 index
  dragging: false, // 是否正在拖动
  direction: undefined // 拖动方向
  }
 }
 }

另外父元素传入了一个表头数据 header,但拖动完成后会修改这个数据

在子组件中直接修改父元素的数据是不推荐的,所以这里初始化了一个 tableHeader 用于托管表头数据 header

但为了让 header 修改时,tableHeader 也能响应修改,就得添加一个监视器 watch

watch: {
 header (val, oldVal) {
  this.tableHeader = val
 }
 }

三、自定义表头

Element-UI 的 Table 组件为了实现【拖拽边框以修改列宽】的功能,没有将 mousemove、mouseup、mousedown 这三个事件暴露出来

所以需要自定义表头,并手动添加鼠标事件的处理函数,这就需要用到 renderHeader() 方法

renderHeader (createElement, {column}) {
  return createElement(
  'div', {
   'class': ['thead-cell'],
   on: {
   mousedown: ($event) => { this.handleMouseDown($event, column) },
   mouseup: ($event) => { this.handleMouseUp($event, column) },
   mousemove: ($event) => { this.handleMouseMove($event, column) }
   }
  }, [
   // 添加 <a> 用于显示表头 label
   createElement('a', column.label),
   // 添加一个空标签用于显示拖动动画
   createElement('span', {
   'class': ['virtual']
   })
  ])
 },

三个鼠标事件中,第一个参数是事件对象,第二个是表头对象

在对应的处理函数中,可以通过 column.columnKey 获取到对应的表头元素下标 index

空标签 <span class="virtual"> 用来显示拖动时的动画(虚线)

四、事件处理

按下鼠标时,记录下起始列。鼠标抬起时,记录下结束列。根据二者之差计算出拖动的方向。

然后根据起始列和结束列的位置,将表头数据重新排序,从而实现列的拖动

拖动过程的处理函数如下:

// 按下鼠标开始拖动
handleMouseDown (e, column) {
 this.dragState.dragging = true
 this.dragState.start = parseInt(column.columnKey)
 // 给拖动时的虚拟容器添加宽高
 let table = document.getElementsByClassName('w-table')[0]
 let virtual = document.getElementsByClassName('virtual')
 for (let item of virtual) {
 item.style.height = table.clientHeight - 1 + 'px'
 item.style.width = item.parentElement.parentElement.clientWidth + 'px'
 }
},

// 鼠标放开结束拖动
handleMouseUp (e, column) {
 this.dragState.end = parseInt(column.columnKey) // 记录起始列
 this.dragColumn(this.dragState)
 // 初始化拖动状态
 this.dragState = {
 start: -1,
 end: -1,
 move: -1,
 dragging: false,
 direction: undefined
 }
},

// 拖动中
handleMouseMove (e, column) {
 if (this.dragState.dragging) {
 let index = parseInt(column.columnKey) // 记录起始列
 if (index - this.dragState.start !== 0) {
  this.dragState.direction = index - this.dragState.start < 0 ? 'left' : 'right' // 判断拖动方向
  this.dragState.move = parseInt(column.columnKey)
 } else {
  this.dragState.direction = undefined
 }
 } else {
 return false
 }
},

// 拖动易位
dragColumn ({start, end, direction}) {
 let tempData = []
 let left = direction === 'left'
 let min = left ? end : start - 1
 let max = left ? start + 1 : end
 for (let i = 0; i < this.tableHeader.length; i++) {
 if (i === end) {
  tempData.push(this.tableHeader[start])
 } else if (i > min && i < max) {
  tempData.push(this.tableHeader[ left ? i - 1 : i + 1 ])
 } else {
  tempData.push(this.tableHeader[i])
 }
 }
 this.tableHeader = tempData
},

五、虚线效果

在拖动过程中,通过 mousemove 事件,改变当前列的表头状态

然后借助 headerCellClassName 动态修改其 class

headerCellClassName ({column, columnIndex}) {
 return (columnIndex - 1 === this.dragState.move ? `darg_active_${this.dragState.direction}` : '')
}

这个 class 会添加到表头单元格 <th> 上,通过这个 class 给上面的空标签 <span class="virtual"> 添加虚线即可

贴一下我自己写的完整样式(使用了 sass 作为编译工具):

<style lang="scss">
.w-table {
 .el-table th {
 padding: 0;
 .virtual{
  position: fixed;
  display: block;
  width: 0;
  height: 0;
  margin-left: -10px;
  z-index: 99;
  background: none;
  border: none;
 }
 &.darg_active_left {
  .virtual {
  border-left: 2px dotted #666;
  }
 }
 &.darg_active_right {
  .virtual {
  border-right: 2px dotted #666;
  }
 }
 }
 .thead-cell {
 padding: 0;
 display: inline-flex;
 flex-direction: column;
 align-items: left;
 cursor: pointer;
 overflow: initial;
 &:before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
 }
 }
 &.w-table_moving {
 .el-table th .thead-cell{
  cursor: move !important;
 }
 .el-table__fixed {
  cursor: not-allowed;
 }
 }
}

六、父组件调用

<template>
<div>
 <wTable :data="tableData" :header="tableHeader" :option="tableOption">
 <el-table-column slot="fixed"
  fixed
  prop="date"
  label="日期"
  width="150">
 </el-table-column>
 </wTable>
</div>
</template>

<script>
import wTable from '@/components/w-table.vue'
export default {
 name: 'Table',
 data () {
 return {
  tableOption: {
  border: true,
  maxHeight: 500
  },
  tableHeader: [{
  prop: 'name',
  label: '姓名',
  sortable: true,
  sortMethod: this.handleNameSort
  }, {
  prop: 'province',
  label: '省份',
  minWidth: '120'
  }, {
  prop: 'city',
  label: '市区',
  minWidth: '120'
  }, {
  prop: 'address',
  label: '地区',
  minWidth: '150'
  }, {
  prop: 'zip',
  label: '邮编',
  minWidth: '120'
  }],

  tableData: [{
  date: '2016-05-03',
  name: '王小虎',
  province: '上海',
  city: '普陀区',
  address: '上海市普陀区金沙江路 1518 弄',
  zip: 200333
  }, {
  date: '2016-05-02',
  name: '王小虎',
  province: '上海',
  city: '普陀区',
  address: '上海市普陀区金沙江路 1518 弄',
  zip: 200333
  }, {
  date: '2016-05-04',
  name: '王小虎',
  province: '上海',
  city: '普陀区',
  address: '上海市普陀区金沙江路 1518 弄',
  zip: 200333
  }, {
  date: '2016-05-01',
  name: '王小虎',
  province: '上海',
  city: '普陀区',
  address: '上海市普陀区金沙江路 1518 弄',
  zip: 200333
  }, {
  date: '2016-05-08',
  name: '王小虎',
  province: '上海',
  city: '普陀区',
  address: '上海市普陀区金沙江路 1518 弄',
  zip: 200333
  }, {
  date: '2016-05-06',
  name: '王小虎',
  province: '上海',
  city: '普陀区',
  address: '上海市普陀区金沙江路 1518 弄',
  zip: 200333
  }]
 }
 },
 methods: {
 handleNameSort () {
  console.log('handleNameSort')
 }
 },
 components: {
 wTable
 }
}
</script>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery 对象中的类数组操作
Apr 27 Javascript
jQuery选择器的工作原理和优化分析
Jul 25 Javascript
深入学习jQuery Validate表单验证(二)
Jan 18 Javascript
JS面试题---关于算法台阶的问题
Jul 26 Javascript
javascript入门之数组[新手必看]
Nov 21 Javascript
基于jQuery实现选项卡效果
Jan 04 Javascript
微信小程序 增、删、改、查操作实例详解
Jan 13 Javascript
JavaScript模板引擎实现原理实例详解
Dec 14 Javascript
浅谈Vue.js中如何实现自定义下拉菜单指令
Jan 06 Javascript
vuex 多模块时 模块内部的mutation和action的调用方式
Jul 24 Javascript
vue选项卡切换的实现案例
Apr 11 Vue.js
二维码条形码生成的JavaScript脚本库
Jul 07 Javascript
React中的render何时执行过程
Apr 13 #Javascript
全站最详细的Vuex教程
Apr 13 #Javascript
vue的常用组件操作方法应用分析
Apr 13 #Javascript
简单的vuex 的使用案例笔记
Apr 13 #Javascript
angular实现页面打印局部功能的思考与方法
Apr 13 #Javascript
vuex中的 mapState,mapGetters,mapActions,mapMutations 的使用
Apr 13 #Javascript
js捆绑TypeScript声明文件的方法教程
Apr 13 #Javascript
You might like
php中的curl_multi系列函数使用例子
2014/07/29 PHP
php用ini_get获取php.ini里变量值的方法
2015/03/04 PHP
yii2框架中使用下拉菜单的自动搜索yii-widget-select2实例分析
2016/01/09 PHP
PHP数据库操作Helper类完整实例
2016/05/11 PHP
PHP实现限制IP访问及提交次数的方法详解
2017/07/17 PHP
PHP 构造函数和析构函数原理与用法分析
2020/04/21 PHP
jquery的index方法实现tab效果
2011/02/16 Javascript
利用jQuery实现可以编辑的表格
2014/05/26 Javascript
理运用命名空间让js不产生冲突避免全局变量的泛滥
2014/06/15 Javascript
js简单抽奖代码
2015/01/16 Javascript
JavaScript判断对象是否为数组
2015/12/22 Javascript
jQuery实现漂亮实用的商品图片tips提示框效果(无图片箭头+阴影)
2016/04/16 Javascript
node.js利用redis数据库缓存数据的方法
2017/03/01 Javascript
基于bootstrap按钮式下拉菜单组件的搜索建议插件
2017/03/25 Javascript
vue使用Axios做ajax请求详解
2017/06/07 Javascript
详解vue-cli 脚手架项目-package.json
2017/07/04 Javascript
vue通过路由实现页面刷新的方法
2018/01/25 Javascript
vue-router 手势滑动触发返回功能
2018/09/30 Javascript
基于vue 动态菜单 刷新空白问题的解决
2020/08/06 Javascript
Python深入学习之对象的属性
2014/08/31 Python
利用 Monkey 命令操作屏幕快速滑动
2016/12/07 Python
Python多进程并发与多线程并发编程实例总结
2018/02/08 Python
Python要如何实现列表排序的几种方法
2020/02/21 Python
Python pandas如何向excel添加数据
2020/05/22 Python
pytorch cuda上tensor的定义 以及减少cpu的操作详解
2020/06/23 Python
python闭包与引用以及需要注意的陷阱
2020/09/18 Python
Html5页面上如何禁止手机虚拟键盘弹出
2020/03/19 HTML / CSS
Uber Eats台湾:寻找附近提供送餐服务的餐厅
2018/05/07 全球购物
adidas菲律宾官网:adidas PH
2020/02/07 全球购物
车间班组长的职责
2013/12/13 职场文书
企业军训感想
2014/02/07 职场文书
厂区绿化方案
2014/05/08 职场文书
房屋租赁授权委托书范本
2014/09/20 职场文书
催款函范本大全
2015/06/24 职场文书
Python预测分词的实现
2021/06/18 Python
Python数据结构之队列详解
2022/03/21 Python