需求分析
今天接到个需求,对列表数据进行上下移动,实现排序效果,并且移动数据时,要保持移动的数据在可视区域中。
- 选中行上下移动,可以操作dom(不推荐,毕竟vue是以数据驱动的),或者对数据进行操作
- 选中行保持在可视范围内(要让元素保持在可视范围内,有很多方法实现,我这种场景是设置容器的scrollTop,让容器滚动,以达到我们的目的,后面会解释)
第一步:基础代码
html代码:
<el-table
ref="deptSort"
:data="list"
@row-click="handleClick"
>
...
</el-table>
通过el-table
的row-click
方法获取选中的那一行:

第二步:移动数据的方式
需求分析1,有说到两种移动数据的方式:
方式一: 对dom进行操作实现上下移动,不是说不可以。既然我们用vue开发项目,能操作数据实现的,我们就尽量不操作dom,当然有些场景,不得不操作dom。
方式二: 操作数据。
首先获取选中行的下标
handleClick(row, col, event) {
let currIndex = ''
currIndex = this.list.findIndex(e => {
return e.exam_dept_id == row.exam_dept_id
})
this.$set(this.currIndex, 'currIndex', currIndex); //获取选中行的下标
this.$set(this.currEvent, 'currEvent', event);//保存点击的event,后面用到
}
通过选中行的下标向上移动数据
handleMoveUp() {
let currIndex = this.currIndex,
list = this.list
if (currIndex) {
let currRow = list[currIndex] //当前行
let temp = JSON.parse(JSON.stringify(list[currIndex - 1])) //上一行
this.$set(list, currIndex - 1, currRow)
this.$set(list, currIndex, temp)
this.currIndex = currIndex - 1 //此处是为了保存选中行移动后的下标
} else if (currIndex == 0) {
this.$message({
message: '排序第一的无法上移',
type: 'error'
})
} else {
this.$message({
message: '请选中后操作',
type: 'error'
})
}
}
向下移动类似操作。
第三步:让移动的数据保持在可视区域中
首先,选中行保持在可视区域中,不是让他脱离文档流,固定在头部,而是在移动时,若超出可视范围了,整个列表上移或者下移,所以不能用position:fixed 或者 position:absolute
然后,如何知道选中行是否在科室范围呢?
- 数据上移后,判断选中行dom的顶部是否高于可视区域dom的顶部:通过选中行的
offsetTop
和滚动容器的scrollTop
进行对比,若offsetTop
<scrollTop
,表示选中行已离开可视区域 - 数据下移后,判断选中行dom的底部值
currTrFloor
是否低于可视区域dom的底部值wrapFloor
:若currTrFloor
>wrapFloor
,表示选中行已离开可视区域
代码如下:
scrollTable(type) { // type为移动方式
let currIndex = this.currIndex, // 当前选中tr的下标
tableDom = this.currEvent.target.parentNode.parentNode.parentNode, //当前滚动tr的父元素table DOM元素
currTrDom = tableDom.querySelector('.current-row'), //当前滚动的tr DOM元素
wrapDom = tableDom.parentNode.parentNode, //当前列表容器的DOM元素
wrapScrollTop = wrapDom.scrollTop, //容器滚动高度
trOffsetTop = currTrDom.offsetTop//当前tr距离顶部距离
if (type == 'up') { // 上移
if (wrapScrollTop > trOffsetTop) { //tr顶部超出可视区域顶部
wrapDom.scrollTop = trOffsetTop
}
} else { // 下移
let currTrFloor = trOffsetTop + currTrDom.offsetHeight,//当前tr底部值
wrapFloor = wrapScrollTop + wrapDom.clientHeight//可视区域底部值
if (currTrFloor > wrapFloor) {//tr底部超出可视区域底部
wrapDom.scrollTop = wrapScrollTop + (currTrFloor - wrapFloor)
}
}
}
到此,基本的逻辑代码已经实现,将第三步代码添加到第二步代码中,代码如下:
//数据移动后,判断是否选中行在可视区域中
setTimeout(() => {
this.scrollTable('up')
},20)
上面的setTimeout是为了数据移动后,确保判断逻辑在页面重新渲染之后执行,作用与vue的$nextTick
相同,但是$nextTick
偶尔有坑,所以不用。20ms后执行,是因为数据改变,触发视图更新的时间大概为17ms,经过测试所得。
既然加了定时器,习惯性的会考虑节流或者防抖,这个场景适用节流,最终js代码如下:
// 选中当前行
handleClick(row, col, event) {
let currIndex = ''
currIndex = this.list.findIndex(e => {
return e.exam_dept_id == row.exam_dept_id
})
this.currIndex = currIndex //保存选中行的下标
this.$set(this.currEvent, 'currEvent', event);//保存点击的event,后面用到
},
//向上移动
handleMoveUp() {
if (this.isScrolling) { //节流
return
}
this.isScrolling = true
let currIndex = this.currIndex,
list = this.list
if (currIndex) {
let currRow = list[currIndex] //当前行
let temp = JSON.parse(JSON.stringify(list[currIndex - 1])) //上一行
this.$set(list, currIndex - 1, currRow)
this.$set(list, currIndex, temp)
this.currIndex = currIndex - 1 //此处是为了保存选中行移动后的下标
//数据移动后,判断选中行是否在可视区域中
setTimeout(() => {
this.scrollTable('up')
},20)
} else if (currIndex == 0) {
this.isScrolling = false
this.$message({
message: '排序第一的无法上移',
type: 'error'
})
} else {
this.isScrolling = false
this.$message({
message: '请选中后操作',
type: 'error'
})
}
},
// 判断选中行是否在可视区域中
scrollTable(type) { // type为移动方式
let currIndex = this.currIndex, // 当前选中tr的下标
tableDom = this.currEvent.target.parentNode.parentNode.parentNode, //当前滚动tr的父元素table DOM元素
currTrDom = tableDom.querySelector('.current-row'), //当前滚动的tr DOM元素
wrapDom = tableDom.parentNode.parentNode, //当前列表容器的DOM元素
wrapScrollTop = wrapDom.scrollTop, //容器滚动高度
trOffsetTop = currTrDom.offsetTop//当前tr距离顶部距离
if (type == 'up') { // 上移
if (wrapScrollTop > trOffsetTop) { //tr顶部超出可视区域顶部
wrapDom.scrollTop = trOffsetTop
}
} else { // 下移
let currTrFloor = trOffsetTop + currTrDom.offsetHeight,//当前tr底部值
wrapFloor = wrapScrollTop + wrapDom.clientHeight//可视区域底部值
if (currTrFloor > wrapFloor) {//tr底部超出可视区域底部
wrapDom.scrollTop = wrapScrollTop + (currTrFloor - wrapFloor)
}
}
this.isScrolling = false
}
以上是我实现此功能的全部代码,有不足之处,欢迎指出,谢谢