效果预览

vue拖拽排序(原创组件)_vue.js

组件 drag.vue

<template>
<TransitionGroup name="group-list" tag="ul">
<li
v-for="(item, index) in list"
:key="item.name"
:draggable="item.draggable"
:class="[
'list-item',
{
'is-dragover':
index === dropIndex && item.draggable && config.exchange,
},
]"
@dragstart="onDragstart($event, index)"
@dragenter="onDragenter(index)"
@dragover.prevent="onDragover(index)"
@dragleave="onDragleave"
@dragend="onDragend"
@drop="onDrop"
>
<slot :item="item" />
</li>
</TransitionGroup>
</template>

<script>export default {
name: "Draggable",
props: {
list: {
type: Array,
default: () => [],
},
config: {
type: Object,
default: () => ({
name: "",
push: true,
pull: true,
exchange: true,
}),
},
},

data() {
return {
dragIndex: null,
dropIndex: null,
};
},

computed: {
isPush() {
const { dropIndex, dragIndex } = this;
return dropIndex !== null && dragIndex === null;
},

isExchange() {
const { dropIndex, dragIndex } = this;
return dragIndex !== null && dropIndex !== null;
},

pushCbName() {
const {
config: { name },
} = this;
return `${name}-push-callback`;
},
},

methods: {
onDragstart(e,) {
const {
list,
config: { name },
transferData,
} = this;

this.dragIndex = i;

if (name) {
transferData({ e, key: name, type: "set", data: list[i] });
} else {
throw new Error("缺少配置关联名name");
}

this.$emit("drag-start", i);
},

onDragenter(i) {
this.dropIndex = i;
this.$emit("drag-enter", i);
},

onDragover(i) {
const { dragIndex, dropIndex } = this;
if (i === dragIndex || i === dropIndex) return;
this.dropIndex = i;
this.$emit("drag-over", i);
},

onDragleave() {
this.dropIndex = null;
},

onDrop(e) {
const {
list,
dropIndex,
dragIndex,
config: { name, push: enablePush, exchange },
isPush,
isExchange,
pushCbName,
storage,
resetIndex,
transferData,
} = this;

if (dropIndex === dragIndex || !exchange) return;

if (isPush) {
if (!enablePush) {
resetIndex();
return;
}

if (name) {
list.splice(
dropIndex,
0,
transferData({ e, key: name, type: "get" })
);

storage("set", pushCbName, true);
} else {
resetIndex();
throw new Error("缺少配置关联属性name");
}
resetIndex();
return;
}

if (isExchange) {
const drapItem = list[dragIndex];
const dropItem = list[dropIndex];
list.splice(dropIndex, 1, drapItem);
list.splice(dragIndex, 1, dropItem);
}

resetIndex();
},

onDragend() {
const {
list,
dragIndex,
config: { pull: enablePull },
pushCbName,
storage,
resetIndex,
} = this;

if (enablePull) {
const isPushSuccess = storage("get", pushCbName);

if (isPushSuccess) {
list.splice(dragIndex, 1);
storage("remove", pushCbName);
}
}
resetIndex();
this.$emit("drag-end");
},

storage(type, key,) {
return {
get() {
return JSON.parse(localStorage.getItem(key));
},
set() {
localStorage.setItem(key, JSON.stringify(value));
},
remove() {
localStorage.removeItem(key);
},
}[type]();
},

resetIndex() {
this.dropIndex = null;
this.dragIndex = null;
},

transferData({ e, key, type, data } = {}) {
if (type === "get") {
return JSON.parse(e.dataTransfer.getData(`${key}-drag-key`));
}

if (type === "set") {
e.dataTransfer.setData(`${key}-drag-key`, JSON.stringify(data));
}
},
},
};</script>

<style scoped>.list-item {
list-style: none;
position: relative;
margin-bottom: 10px;
border-radius: 4px;
padding: 4px;
background-color: #fff;
cursor: move;
}

.list-item.is-dragover::before {
content: "";
position: absolute;
bottom: -4px;
left: 0;
width: 100%;
height: 4px;
background-color: #0c6bc9;
}

.list-item.is-dragover::after {
content: "";
position: absolute;
bottom: -8px;
left: -6px;
border: 3px solid #0c6bc9;
border-radius: 50%;
width: 6px;
height: 6px;
background-color: #fff;
}

.group-list-move {
transition: transform 0.8s;
}</style>

使用范例

index.vue

<template>
<div class="dragBox">
<Drag style="width:" :list="list1" :config="config1">
<template v-slot="{ item }">
<div class="item">
{{ item.name }}
</div>
</template>
</Drag>

<Drag style="width:" :list="list2" :config="config2">
<template v-slot="{ item }">
<div class="item">
{{ item.name }}
</div>
</template>
</Drag>
</div>
</template>

<script>import Drag from "./drag.vue";

export default {
components: {
Drag,
},

data() {
return {
list1: new Array(10).fill(0).map((_,) => ({
name: `列表1 - ${i + 1}`,
draggable: true,
})),

config1: {
name: "test",
push: true,
pull: true,
exchange: true,
},

list2: new Array(10).fill(0).map((_,) => ({
name: `列表2 - ${i + 1}`,
draggable: true,
})),

config2: {
name: "test",
push: true,
pull: true,
exchange: true,
},
};
},
};</script>

<style scoped>.dragBox {
display: flex;
justify-content: center;
}
.item {
border: 1px solid #ccc;
width: 200px;
height: 30px;
text-align: center;
}</style>

参数说明

list: 渲染列表
config: {
name: '', // 跨列表关联名,跨列表拖拽时必传
push: true, // 当前列表是否支持从其他列表push元素进来
pull: true, // 将当前列表的某个元素拖拽并添加到其他列表里,该元素是否从当前列表移除
exchange: true, // 当前列表元素之间是否支持交换位置
}