背景
不少组件库都没有拖拽排序的功能,或者是需要充钱。这倒逼我们需要上第三方库。例如:vue
有现成的拖拽库vuedraggable
,但是这样的话表格的样式需要自己写。
类型 优点 缺点 拖拽三方库 拖拽有动画效果 表格样式需要自己写 拖拽自己写 表格样式可以用现成的组件库 拖拽功能需要自己改造,动画效果不好实现(对我而言)
效果
无动图
手写拖拽思路
- 把表格行设置成可拖拽的
draggable = true
,设置一些自定义数据data-
- 监听一些事件并取消默认行为,例如:
ondrop
需要取消默认行为,因为它默认不让别的东西放在自己上;ondragover
需要取消默认行为,它默认不让别的元素拖到自己上方;ondragenter
也是一样,默认不让别的元素进入。 - 在拖拽放下时,根据记录的
id
重新对data
的数据调整顺序,状态改变驱动视图更新,就成了。
源码
<template>
<n-card>
<table class="tb">
<thead>
<tr>
<th v-for="col in columns" :key="col.key">{{ col.title }}</th>
</tr>
</thead>
<draggable
:list="data"
handle=".move"
animation="300"
@start="onStart"
@end="onEnd"
tag="tbody"
item-key="name"
>
<template #item="{ element }">
<tr>
<td class="move" v-for="col in columns" :key="col.key">
<div v-if="col.key === 'operate'">
<n-button quaternary type="info"> 编辑 </n-button>
<n-button quaternary type="error"> 删除 </n-button>
</div>
<div v-else>{{ element[col.key] }}</div>
</td>
</tr>
</template>
</draggable>
</table>
<div>naive-ui</div>
<n-data-table
:columns="columns"
:data="data"
bordered
row-class-name="row-move"
:pagination="false"
:row-props="generateRowProps"
/>
</n-card>
</template>
<script setup lang="ts">
import type { DataTableBaseColumn } from "naive-ui";
import draggable from "vuedraggable";
import { Icon } from "@iconify/vue";
interface Song {
sort: number;
title: string;
type: string;
template: string;
}
const columns: DataTableBaseColumn[] = [
{
key: "move",
width: 50,
render: () => {
return h(
"div",
{
class: "row-move flex-center",
style: {
height: "30px",
},
},
[
h(Icon, {
icon: "mdi:sort",
style: {
fontSize: "20px",
},
}),
],
);
},
},
{
title: "排序",
key: "sort",
},
{
title: "栏目名称",
key: "title",
},
{
title: "栏目类型",
key: "type",
},
{
title: "模版",
key: "template",
},
{
title: "操作",
key: "operate",
},
];
const data = ref<Song[]>([
{ sort: 3, title: "Wonderwall", type: "image", template: "1" },
{ sort: 1, title: "Don't Look Back in Anger", type: "image", template: "2" },
{ sort: 2, title: "Champagne Supernova", type: "image", template: "3" },
]);
// 拖拽开始的事件
const onStart = () => {
console.log("开始拖拽");
};
// 拖拽结束的事件
const onEnd = () => {
console.log("结束拖拽");
};
function generateRowProps(rowData: Song, rowIndex: number) {
return {
"data-id": rowData.title,
draggable: true,
ondragstart: handleRowDragStart,
ondrop: handleRowDrop,
ondragover: handleRowDragOver,
ondragenter: handleRowDragEnter,
ondragleave: handleRowDragLeave,
};
}
// 记录拖拽的id
let dragId = "";
// 写一个函数找到tr
function findTr(e: HTMLElement) {
let el = e;
while (el && el.tagName !== "TR") {
el = el.parentElement!;
}
return el;
}
// table拖拽
function handleRowDragStart(e: DragEvent) {
nextTick(() => {
const tr = findTr(e.target as HTMLElement);
console.log("row开始拖拽", tr);
dragId = tr.getAttribute("data-id")!;
});
}
function handleRowDrop(e: Event) {
e.preventDefault();
nextTick(() => {
const tr = findTr(e.target as HTMLElement);
console.log("row结束拖拽", tr);
const dragEndId = tr.getAttribute("data-id")!;
// dragId和dragEndId不相等,需要插入到dragEndId位置,其它往后排
if (dragId !== dragEndId) {
const dragIndex = data.value.findIndex((item) => item.title === dragId);
const dragTargetIndex = data.value.findIndex(
(item) => item.title === dragEndId,
);
const [insertItem] = data.value.splice(dragIndex, 1);
data.value.splice(dragTargetIndex, 0, insertItem);
}
tr.classList.remove("hold");
});
}
function handleRowDragEnter(e: Event) {
e.preventDefault();
// console.log("row拖拽进入");
nextTick(() => {
const tr = findTr(e.target as HTMLElement);
tr.classList.add("hold");
});
}
function handleRowDragLeave(e: Event) {
// console.log("row拖拽离开");
nextTick(() => {
const tr = findTr(e.target as HTMLElement);
tr.classList.remove("hold");
});
}
function handleRowDragOver(e: Event) {
e.preventDefault();
// console.log("row拖拽中");
}
</script>
<style lang="scss" scoped>
.title {
padding: 3px;
font-size: 13px;
}
.itxst {
width: 600px;
}
.move {
cursor: move;
}
:deep(.row-move) {
cursor: move;
}
:deep(.n-data-table .n-data-table-tr.hold) {
td {
background-color: rgb(247 247 250);
}
}
table.tb {
width: 100%;
color: #333;
border: solid 1px #999;
font-size: 13px;
border-collapse: collapse;
min-width: 500px;
user-select: none;
th {
background: rgb(168 173 217);
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #999;
text-align: left;
&:nth-of-type(1) {
text-align: center;
}
}
td {
background: #d6c8c8;
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #999;
&:nth-of-type(1) {
text-align: center;
}
}
}
</style>