列表内容和样式在外部自定义,话不多说,直接上代码
1. 用drag事件实现,唯一不好的是拖拽时的半透明框没法修改
<template>
<div class="list" ref="listRef" @dragstart="drag" @dragover="dragover" @dragend="dragend" @mouseout="out">
<div class="list-item" v-for="(item, index) in listData" :key="index" :orgIndex="index" :draggable="isDrag"
@click="click">
<slot :row="item"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'DragList',
props: {
listData: Array, // list绑定的数据
isDrag: Boolean, // 是否开启拖拽
callDrop: Function,
callClick: Function
},
data() {
return {
oldListInfo: [], // 拖拽前数据
listInfo: [], // 拖拽后数据
draging: null, // 被拖拽元素
target: null, // 目标元素
allow: false // 是否可拖拽,鼠标按下后才可拖拽
}
},
created() {
this.listInfo = this.listData;
},
methods: {
click(e) {
let listItem = this.getParentNode(e.target, "list-item");
if (!listItem) return;
let clickItemIndex = this.getIndex(listItem);
// call 自定义click,返回被点击数据
if (this.callClick && typeof (this.callClick) == 'function') {
this.callClick({"index": clickItemIndex, "item": this.listInfo[clickItemIndex]});
}
},
drag(e) {
this.draging = e.target;
this.allow = true;
this.oldListInfo = this.listInfo;
},
dragover(e) {
e.preventDefault();
if (!this.allow) return;
let listItem = this.getParentNode(e.target, "list-item");
if (!listItem) return;
this.target = listItem;
let targetTop = this.target.getBoundingClientRect().top;
let dragingTop = this.draging.getBoundingClientRect().top;
let dragingIndex = this.getIndex(this.draging);
let targetIndex = this.getIndex(this.target);
if (this.target.className === "list-item" && this.target !== this.draging) {
if (this.target) {
if (this.target.animated) {
return;
}
}
// 互换位置
if (dragingIndex < targetIndex) {
this.target.parentNode.insertBefore(this.draging, this.target.nextSibling);
} else {
this.target.parentNode.insertBefore(this.draging, this.target);
}
this.animate(targetTop, this.target);
this.animate(dragingTop, this.draging);
}
},
dragend(e) {
// 返回调整后的数据,调用外部自定义函数
this.listInfo = this.getNewData();
if (this.callDrop && typeof (this.callDrop) == 'function') {
this.callDrop({"oldListInfo": this.oldListInfo, "newListInfo": this.listInfo});
}
},
// 鼠标移出list
out(e) {
if (e.clientY < this.$refs.listRef.getBoundingClientRect().top || e.clientY > this.$refs.listRef
.getBoundingClientRect().bottom) {
this.allow = false;
}
},
/*
* 按类名查询父元素
* node:当前节点
* pclassname:父元素类名
*/
getParentNode(node, pclassname) {
let p = node;
if (p.className == pclassname) return p;
while (p.parentNode) {
if (p.parentNode.className == pclassname) {
return p.parentNode;
} else {
p = p.parentNode;
}
if (p.nodeName == "BODY") return 0;
}
},
/*
* 获取元素在父元素的索引
* node:当前节点
* pclassname:父元素类名
*/
getIndex(el) {
let domData = Array.from(this.$refs.listRef.childNodes);
return domData.findIndex(i => i.innerText == el.innerText);
},
// 动画效果
animate(startPos, dom) {
let offset = startPos - dom.getBoundingClientRect().top;
dom.style.transition = "none";
dom.style.transform = `translateY(${offset}px)`;
dom.offsetWidth;
dom.style.transition = "transform .3s";
dom.style.transform = "";
clearTimeout(dom.animated);
dom.animated = setTimeout(() => {
dom.style.transition = "";
dom.style.transform = "";
dom.animated = false;
}, 300)
},
// 获取拖拽后的数据
getNewData() {
let currentNodes = Array.from(this.$refs.listRef.childNodes);
let listArr = [];
currentNodes.map((i, index) => {
return listArr[index] = this.listData[i.getAttribute("orgIndex")];
});
return listArr;
}
}
}
</script>
<style scoped>
.list {
display: block;
}
.list-item {
width: 100%;
height: 100%;
user-select: none;
cursor: pointer;
}
</style>
2. mouse事件实现
<template>
<div class="list" ref="listRef1" @mousedown="drag" @mousemove='dragover' @mouseup='dragend' @mouseout="out">
<div class="list-item" v-for="(item, index) in listData" :key="index" :orgIndex="index"
@click="click">
<slot :row="item"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'DragList1',
props: {
listData: Array, // list绑定的数据
isDrag: Boolean, // 是否开启拖拽
callDrop: Function,
callClick: Function
},
data() {
return {
oldListInfo: [], // 拖拽前数据
listInfo: [], // 拖拽后数据
draging: null, // 被拖拽元素
target: null, // 目标元素
allow: false, // 是否可拖拽,鼠标按下后才可拖拽
isMove: false // 是否移动,区分click
}
},
created() {
this.listInfo = this.listData;
},
methods: {
click(e) {
// 如果是移动状态
if (this.isMove) return;
let listItem = this.getParentNode(e.target, "list-item");
if (!listItem) return;
let clickItemIndex = this.getIndex(listItem);
// call 自定义click,返回被点击数据
if (this.callClick && typeof (this.callClick) == 'function') {
this.callClick({"index": clickItemIndex, "item": this.listInfo[clickItemIndex]});
}
},
drag(e) {
this.isMove = false; // 鼠标按下未移动
// 如果未开启拖拽
if (!this.isDrag) return;
// 获取当前点击的item
let listItem = this.getParentNode(e.target, "list-item");
if (!listItem) return;
this.draging = listItem;
this.allow = true; // 允许拖拽
this.oldListInfo = this.listInfo; // 当前数据变为旧数据
},
dragover(e) {
// 未开启或者不允许时,不触发
if (!this.isDrag || !this.allow) return;
this.isMove = true; // 鼠标按下移动
// 获取移动到的item
let listItem = this.getParentNode(e.target, "list-item");
if (!listItem) return;
this.target = listItem;
// 获取两个位置的纵坐标
let targetTop = this.target.getBoundingClientRect().top;
let dragingTop = this.draging.getBoundingClientRect().top;
let dragingIndex = this.getIndex(this.draging);
let targetIndex = this.getIndex(this.target);
if (this.target.className === "list-item" && this.target !== this.draging) {
if (this.target) {
// 如果正在进行动画
if (this.target.animated) {
return;
}
}
if (dragingIndex < targetIndex) {
this.target.parentNode.insertBefore(this.draging, this.target.nextSibling);
} else {
this.target.parentNode.insertBefore(this.draging, this.target);
}
this.animate(dragingTop, this.draging);
this.animate(targetTop, this.target);
}
},
dragend(e) {
this.allow = false; // 重置状态
if (!this.isDrag || !this.isMove) return;
// 获取拖拽后数据
this.listInfo = this.getNewData();
// call自定义事件,返回新旧数据
if (this.callDrop && typeof (this.callDrop) == 'function') {
this.callDrop({"oldListInfo": this.oldListInfo, "newListInfo": this.listInfo});
}
this.isMove = false; // 重置状态
},
out(e) {
if (!this.allow) return;
e = e || window.event;
var obj = e.toElement || e.relatedTarget;
// 鼠标移出list范围
if (!this.$refs.listRef1.contains(obj)) {
this.allow = false;
this.listInfo = this.getNewData();
if (this.callDrop && typeof (this.callDrop) == 'function') {
this.callDrop({"oldListInfo": this.oldListInfo, "newListInfo": this.listInfo});
}
this.isMove = false;
}
},
/*
* 按类名查询父元素
* node:当前节点
* pclassname:父元素类名
*/
getParentNode(node, pclassname) {
let p = node;
while (p.parentNode) {
if (p.parentNode.className == pclassname) {
return p.parentNode;
} else {
p = p.parentNode;
}
if (p.nodeName == "BODY") return 0;
}
},
/*
* 获取元素在父元素的索引
* node:当前节点
* pclassname:父元素类名
*/
getIndex(el) {
let domData = Array.from(this.$refs.listRef1.childNodes);
return domData.findIndex(i => i.innerText == el.innerText);
},
// 动画效果
animate(startPos, dom) {
let offset = startPos - dom.getBoundingClientRect().top;
dom.style.transition = "none";
dom.style.transform = `translateY(${offset}px)`;
dom.offsetWidth;
dom.style.transition = "transform .3s";
dom.style.transform = "";
clearTimeout(dom.animated);
dom.animated = setTimeout(() => {
dom.style.transition = "";
dom.style.transform = "";
dom.animated = false;
}, 300)
},
// 获取拖拽后的数据
getNewData() {
let currentNodes = Array.from(this.$refs.listRef1.childNodes);
let listArr = [];
currentNodes.map((i, index) => {
return listArr[index] = this.listData[i.getAttribute("orgIndex")];
});
return listArr;
}
}
}
</script>
<style scoped>
.list {
display: block;
}
.list-item {
width: 100%;
height: 100%;
user-select: none;
cursor: pointer;
}
</style>