挑战一下我的软肋!表格行拖拽

背景

不少组件库都没有拖拽排序的功能,或者是需要充钱。这倒逼我们需要上第三方库。例如:vue有现成的拖拽库vuedraggable,但是这样的话表格的样式需要自己写。

类型优点缺点
拖拽三方库拖拽有动画效果表格样式需要自己写
拖拽自己写表格样式可以用现成的组件库拖拽功能需要自己改造,动画效果不好实现(对我而言)

效果

无动图
在这里插入图片描述

手写拖拽思路

  1. 把表格行设置成可拖拽的draggable = true,设置一些自定义数据data-
  2. 监听一些事件并取消默认行为,例如:ondrop需要取消默认行为,因为它默认不让别的东西放在自己上;ondragover需要取消默认行为,它默认不让别的元素拖到自己上方;ondragenter也是一样,默认不让别的元素进入。
  3. 在拖拽放下时,根据记录的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>

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值