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 相关文章推荐
文本框根据输入内容自适应高度的代码
Oct 24 Javascript
js Array对象的扩展函数代码
Apr 24 Javascript
node.js中使用socket.io的方法
Dec 15 Javascript
javascript禁止超链接跳转的方法
Feb 02 Javascript
jQuery插件制作的实例教程
May 16 Javascript
vue.js todolist实现代码
Oct 29 Javascript
在vue中使用css modules替代scroped的方法
Mar 10 Javascript
vue打包的时候自动将px转成rem的操作方法
Jun 20 Javascript
微信小程序学习笔记之跳转页面、传递参数获得数据操作图文详解
Mar 28 Javascript
vue点击标签切换选中及互相排斥操作
Jul 17 Javascript
在vue中配置不同的代理同时访问不同的后台操作
Sep 11 Javascript
解决antd的Form组件setFieldsValue的警告问题
Oct 29 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 gzip压缩输出的实现方法
2013/04/27 PHP
Laravel构建即时应用的一种实现方法详解
2017/08/31 PHP
PHP7 echo和print语句实例用法
2019/02/15 PHP
laravel 解决paginate查询多个字段报错的问题
2019/10/22 PHP
php使用event扩展的io复用测试的示例
2020/10/20 PHP
PHP网站常见安全漏洞,及相应防范措施总结
2021/03/01 PHP
优化网页之快速的呈现我们的网页
2007/06/29 Javascript
javascript返回顶部效果(自写代码)
2013/01/06 Javascript
jQuery实现级联菜单效果(仿淘宝首页菜单动画)
2014/04/10 Javascript
js实现同一页面多个不同运动效果的方法
2015/04/10 Javascript
再谈Javascript中的基本类型和引用类型(推荐)
2016/07/01 Javascript
JavaScript中的对象继承关系
2016/08/01 Javascript
通过网页查看JS源码中汉字显示乱码的解决方法
2016/10/26 Javascript
基于bootstrap-datetimepicker.js不支持IE8的快速解决方法
2016/11/07 Javascript
angularJs使用$watch和$filter过滤器制作搜索筛选实例
2017/06/01 Javascript
Javascript实现的StopWatch功能示例
2017/06/13 Javascript
Vue2.0父组件与子组件之间的事件发射与接收实例代码
2017/09/19 Javascript
JavaScript的词法结构精华篇
2018/10/17 Javascript
jQuery实现的鼠标拖动浮层功能示例【拖动div等任何标签】
2018/12/29 jQuery
Vue2.x Todo之自定义指令实现自动聚焦的方法
2019/01/08 Javascript
Layer UI表格列日期格式化及取消自动填充日期的实现方法
2020/05/10 Javascript
[05:46]2018完美盛典-《同梦共竞》
2018/12/17 DOTA
Python匹配中文的正则表达式
2016/05/11 Python
Python爬虫的两套解析方法和四种爬虫实现过程
2018/07/20 Python
kafka-python批量发送数据的实例
2018/12/27 Python
Pytorch在NLP中的简单应用详解
2020/01/08 Python
python误差棒图errorbar()函数实例解析
2020/02/11 Python
python DES加密与解密及hex输出和bs64格式输出的实现代码
2020/04/13 Python
Python利用matplotlib绘制散点图的新手教程
2020/11/05 Python
CSS3 text-shadow实现文字阴影效果
2016/02/24 HTML / CSS
BCBG官网:BCBGMAXAZRIA
2017/12/29 全球购物
澳大利亚珍珠首饰购物网站:Vayo Pearls
2019/03/11 全球购物
2014村务公开实施方案
2014/02/25 职场文书
晨会主持词
2014/03/17 职场文书
销售主管竞聘书
2014/03/31 职场文书
CSS实现漂亮的时钟动画效果的实例代码
2021/03/30 HTML / CSS