最近碰到el-table做拖拽排序的需求,而且有好几个页面都用到相同的代码逻辑,就想着把这拖拽排序的代码给封装一下,多个地方都能复用。并且以高内聚的方式封装,其他项目要用的话,直接copy一个js文件过去就能用,方便快捷。
当然拖拽排序这个逻辑太复杂肯定无法手写,我们可以先安装sortablejs这个库,然后把初始化这个库的代码给封装一下:
/*
* @Description : el-table 拖拽排序
*/
import Sortable from 'sortablejs'
let oldIndex = null
let newIndex = null
const ROW_CLASS = 'sort-target-row'
export function initSortable(options = {}) {
appendInlineStylesheet()
const _this = this
const {
selector = '.el-table__body-wrapper tbody',
handle = '.handle-move',
draggable = '.el-table__row',
list = 'list'
} = options
// 1. 获取拖拽元素的父元素
// 因为使用的element的table 所以可直接获取tbody
const el = document.querySelector(selector)
// 2. 创建并配置相关参数
var sortable = new Sortable(el, {
// 只有按住手柄才能进行拖拽,故而设置此参数
handle, // css选择器的字符串 若设置该值,则表示只有按住拖动手柄才能使列表单元进行拖动
// 行拖拽,所以该值设置为行class
draggable, // 允许拖拽的项目类名
// 因为拖拽过程中不进行位置调整,所以就要记录拖拽行初始位置以及目标位置
// 拖拽中 回调函数
onMove(customEvent) {
// 禁止在拖拽过程中交换位置
// 可将初始位置及目标位置进行记录
oldIndex = customEvent.dragged.rowIndex // 初始位置
newIndex = customEvent.related.rowIndex // 目标位置
cleanClass(el)
// 给目标行添加高亮样式
el.childNodes[newIndex].classList.add(ROW_CLASS)
return false // 不进行交换
// 返回值有三个
// return false; — for cancel
// return -1; — insert before target
// return 1; — insert after target
},
// 拖拽结束,调整位置
onEnd() {
// 在拖拽结束后,获取初始及目标位置
const sortList = getValueByPath(_this, list)
const currRow = sortList.splice(oldIndex, 1)[0]
sortList.splice(newIndex, 0, currRow)
// 拖拽完成,初始位置及目标位置重置
newIndex = null
oldIndex = null
cleanClass(el)
}
})
console.log('sortable:', sortable)
}
// 清除非目标行的高亮样式
function cleanClass(el) {
for (const i of el.childNodes) {
i.classList?.remove(ROW_CLASS)
}
}
// 全局添加高亮样式
let initialized = false
function appendInlineStylesheet() {
if (initialized) return
const style = document.createElement('style')
style.type = 'text/css'
const cssRules = `
.${ROW_CLASS} {
background: oldlace!important;
box-shadow: inset 0px 0px 16px 0px rgba(210, 210, 210, 0.5);
}
.handle-move {
cursor: move;
}
.handle-move > i {
color: #555;
font-size: 16px;
transition: all .2s;
}
.handle-move:hover > i {
transform: scale(1.3);
}
`
style.appendChild(document.createTextNode(cssRules))
// 将新的<style>元素添加到<head>中
const head = document.head || document.getElementsByTagName('head')[0]
head.appendChild(style)
initialized = true
}
// 路径解析,例如传入'a.b.c'
function getValueByPath(obj, path) {
const keys = path.split('.')
let current = obj
for (const key of keys) {
if (current && Object.prototype.hasOwnProperty.call(current, key)) {
current = current[key]
} else {
return undefined // 当前层级未找到对应属性,返回undefined
}
}
return current
}
使用也很简单,只需要引入这个js,把需要改成拖拽的表格类名传入,把表格对应的数据传入,还要记得用call把当前vue组件实例传入,以便拖拽的时候,视图驱动修改表格数据:
// table-sort-demo.vue
import { initSortable } from './sort.js'
mounted() {
initSortable.call(this, { list: 'list', selector: '.le-table .el-table__body-wrapper tbody' })
}
实现效果如下: