在 Element-UI 的 Table 组件上添加列拖拽效果
在 Element-UI 的 Table 組件上添加列拖拽效果
一、數(shù)據(jù)驅(qū)動
傳統(tǒng)的拖動效果,都是基于通過 mousedown、mousemove、mouseup 事件來修改刪減 dom 節(jié)點
但 Vue 是一個數(shù)據(jù)驅(qū)動的前端框架,開發(fā)時應(yīng)盡量避免操作 dom
而且 Element-UI 的 Table 組件封裝得很嚴(yán)謹(jǐn),直接操作 dom 很容易產(chǎn)生不可預(yù)計的 bug
所以我的核心思路就是:通過一個數(shù)組渲染表頭(列),然后修改這個數(shù)組的順序,從而修改列表的列排序
template部分
<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' }":cell-class-name="cellClassName":header-cell-class-name="headerCellClassName"><slot name="fixed"></slot><el-table-columnv-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> </template>下面的 data 是列表數(shù)據(jù)集合,option 是 Table 組件配置項,header 是表頭數(shù)據(jù)集合,由父組件傳入
props: {data: {default: function () {return []},type: Array},header: {default: function () {return []},type: Array},option: {default: function () {return {}},type: Object}}配置項可以根據(jù) Element-UI 的 api 自行刪減
但有幾個參數(shù)在組件內(nèi)部被征用:
1. header-cell-class-name
綁定了一個函數(shù),動態(tài)給表頭單元格添加 class,從而實現(xiàn)拖動中的虛線效果。
2. cell-class-name
同上。用一個函數(shù)給表頭以外的所有單元格添加 class,用于區(qū)分選中列。
3. column-key
綁定為 header 數(shù)組的 index,用于確定需要修改的 header 元素下標(biāo)
4. render-header
表頭渲染函數(shù),用以添加自定義方法,以監(jiān)聽 mousemove 等相關(guān)事件
二、記錄拖動狀態(tài)
拖動過程中需要記錄幾個關(guān)鍵參數(shù):
data () {return {tableHeader: this.header,dragState: {start: -9, // 起始元素的 indexend: -9, // 移動鼠標(biāo)時所覆蓋的元素 indexdragging: false, // 是否正在拖動direction: undefined // 拖動方向}}}另外父元素傳入了一個表頭數(shù)據(jù) header,但拖動完成后會修改這個數(shù)據(jù)
在子組件中直接修改父元素的數(shù)據(jù)是不推薦的,所以這里初始化了一個 tableHeader 用于托管表頭數(shù)據(jù) header
但為了讓 header 修改時,tableHeader 也能響應(yīng)修改,就得添加一個監(jiān)視器 watch
watch: {header (val, oldVal) {this.tableHeader = val}}三、自定義表頭
Element-UI 的 Table 組件為了實現(xiàn)【拖拽邊框以修改列寬】的功能,沒有將 mousemove、mouseup、mousedown 這三個事件暴露出來
所以需要自定義表頭,并手動添加鼠標(biāo)事件的處理函數(shù),這就需要用到 renderHeader() 方法
renderHeader (createElement, {column}) {return createElement('div', {'class': ['thead-cell'],on: {mousedown: ($event) => { this.handleMouseDown($event, column) },mousemove: ($event) => { this.handleMouseMove($event, column) }}}, [// 添加 <a> 用于顯示表頭 labelcreateElement('a', column.label),// 添加一個空標(biāo)簽用于顯示拖動動畫createElement('span', {'class': ['virtual']})])},三個鼠標(biāo)事件中,第一個參數(shù)是事件對象,第二個是表頭對象
在對應(yīng)的處理函數(shù)中,可以通過 column.columnKey 獲取到對應(yīng)的表頭元素下標(biāo) index
空標(biāo)簽 用來顯示拖動時的動畫(虛線)
四、事件處理
按下鼠標(biāo)時,記錄下起始列。鼠標(biāo)抬起時,記錄下結(jié)束列。根據(jù)二者之差計算出拖動的方向。
然后根據(jù)起始列和結(jié)束列的位置,將表頭數(shù)據(jù)重新排序,從而實現(xiàn)列的拖動
拖動過程的處理函數(shù)如下:
// 按下鼠標(biāo)開始拖動 handleMouseDown (e, column) {this.dragState.dragging = truethis.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'}document.addEventListener('mouseup', this.handleMouseUp); },// 鼠標(biāo)放開結(jié)束拖動 handleMouseUp () {this.dragColumn(this.dragState)// 初始化拖動狀態(tài)this.dragState = {start: -9,end: -9,dragging: false,direction: undefined}document.removeEventListener('mouseup', this.handleMouseUp); },// 拖動中 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.end = 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 - 1let max = left ? start + 1 : endfor (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 },五、動態(tài)樣式
在拖動過程中,通過 mousemove 事件,改變當(dāng)前列的表頭狀態(tài)
然后借助 headerCellClassName, cellClassName 動態(tài)修改單元格 class
這里的 darg_active 用來給上面的空標(biāo)簽 添加虛線,darg_start 用于實現(xiàn)選中效果
貼一下我自己寫的完整樣式(使用了 sass 作為編譯工具):
<style lang="scss"> .w-table {.el-table .darg_start {background-color: #f3f3f3; }.el-table th {padding: 0;.virtual{position: fixed;display: block;width: 0;height: 0;margin-left: -10px;background: none;border: none;}&.darg_active_left {.virtual {border-left: 2px dotted #666;z-index: 99;}}&.darg_active_right {.virtual {border-right: 2px dotted #666;z-index: 99;}}}.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 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' }":cell-class-name="cellClassName":header-cell-class-name="headerCellClassName"><slot name="fixed"></slot><el-table-columnv-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> </template><script> export default {data() {return {tableHeader: this.header,dragState: {start: -9, // 起始元素的 indexend: -9, // 移動鼠標(biāo)時所覆蓋的元素 indexdragging: false, // 是否正在拖動direction: undefined // 拖動方向}};},props: {data: {default: function () {return [];},type: Array},header: {default: function () {return [];},type: Array},option: {default: function () {return {};},type: Object}},watch: {header(val, oldVal) {this.tableHeader = val;}},methods: {renderHeader(createElement, { column }) {return createElement('div',{class: ['thead-cell'],on: {mousedown: ($event) => {this.handleMouseDown($event, column);},mousemove: ($event) => {this.handleMouseMove($event, column);}}},[// 添加 <a> 用于顯示表頭 labelcreateElement('a', column.label),// 添加一個空標(biāo)簽用于顯示拖動動畫createElement('span', {class: ['virtual']})]);},// 按下鼠標(biāo)開始拖動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';}document.addEventListener('mouseup', this.handleMouseUp);},// 鼠標(biāo)放開結(jié)束拖動handleMouseUp() {this.dragColumn(this.dragState);// 初始化拖動狀態(tài)this.dragState = {start: -9,end: -9,dragging: false,direction: undefined};document.removeEventListener('mouseup', this.handleMouseUp);},// 拖動中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.end = 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;},headerCellClassName({ column, columnIndex }) {let active = columnIndex - 1 === this.dragState.end ? `darg_active_${this.dragState.direction}` : '';let start = columnIndex - 1 === this.dragState.start ? `darg_start` : '';return `${active} ${start}`;},cellClassName({ column, columnIndex }) {return columnIndex - 1 === this.dragState.start ? `darg_start` : '';}} }; </script> <style lang="scss"> .w-table {.el-table .darg_start {background-color: #f3f3f3;}.el-table th {padding: 0;.virtual {position: fixed;display: block;width: 0;height: 0;margin-left: -10px;background: none;border: none;}&.darg_active_left {.virtual {border-left: 2px dotted #666;z-index: 99;}}&.darg_active_right {.virtual {border-right: 2px dotted #666;z-index: 99;}}}.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;}} }六:父組件調(diào)用
<template> <div><wTable :data="tableData" :header="tableHeader" :option="tableOption"><el-table-column slot="fixed"fixedprop="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: '市區(qū)',minWidth: '120'}, {prop: 'address',label: '地區(qū)',minWidth: '150'}, {prop: 'zip',label: '郵編',minWidth: '120'}],tableData: [{date: '2016-05-03',name: '王小虎',province: '上海',city: '普陀區(qū)',address: '上海市普陀區(qū)金沙江路 1518 弄',zip: 200333}, {date: '2016-05-02',name: '王小虎',province: '上海',city: '普陀區(qū)',address: '上海市普陀區(qū)金沙江路 1518 弄',zip: 200333}, {date: '2016-05-04',name: '王小虎',province: '上海',city: '普陀區(qū)',address: '上海市普陀區(qū)金沙江路 1518 弄',zip: 200333}, {date: '2016-05-01',name: '王小虎',province: '上海',city: '普陀區(qū)',address: '上海市普陀區(qū)金沙江路 1518 弄',zip: 200333}, {date: '2016-05-08',name: '王小虎',province: '上海',city: '普陀區(qū)',address: '上海市普陀區(qū)金沙江路 1518 弄',zip: 200333}, {date: '2016-05-06',name: '王小虎',province: '上海',city: '普陀區(qū)',address: '上海市普陀區(qū)金沙江路 1518 弄',zip: 200333}]}},methods: {handleNameSort () {console.log('handleNameSort')}},components: {wTable} } </script>總結(jié)
以上是生活随笔為你收集整理的在 Element-UI 的 Table 组件上添加列拖拽效果的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信小程序(购物车)--在wxml中设置
- 下一篇: 微信小程序点击页面tab栏切换