<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Layui 表格拖拽</title>
<script src="https://cdn.jsdelivr.net/npm/layui@2.9.21/dist/layui.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layui@2.9.21/dist/css/layui.min.css">
<!-- 确保在 layui 之后引入插件 -->
<script src="./layui-table-drag.js"></script>
</head>
<body>
<div style="padding: 20px;">
<table id="demoTable" lay-filter="demoTable"></table>
</div>
<script>
layui.use(['table'], function(){
var table = layui.table;
// 生成模拟数据
function generateData(num) {
var data = [];
var cities = ['北京', '上海', '广州', '深圳', '成都', '杭州'];
for (var i = 0; i < num; i++) {
data.push({
id: i + 1,
username: '用户' + (i + 1),
age: Math.floor(Math.random() * 30) + 20, // 随机年龄 20-50
city: cities[Math.floor(Math.random() * cities.length)],
score: Math.floor(Math.random() * 100) + 50 // 随机分数 50-100
});
}
return data;
}
// 渲染表格
table.render({
elem: '#demoTable',
id: 'testTable', // 一定要设置id
data: generateData(20), // 生成1000条数据
cols: [[
{field: 'id', title: 'ID', width: 80, fixed: 'left'},
{field: 'username', title: '用户名', width: 120, fixed: 'left'},
{field: 'age', title: '年龄', width: 120},
{field: 'city', title: '城市', width: 120},
{field: 'score', title: '语文', width: 120},
{field: 'score', title: '数学', width: 120},
{field: 'score', title: '英语', width: 120},
{field: 'score', title: '总分', width: 120}
]],
done: function() {
// 在表格渲染完成后初始化拖拽
setTimeout(() => {
new LayuiTableDrag({
elem: 'testTable', // 表格元素ID
id: 'testTable', // 表格ID
table: table, // table实例
done: function(data) { // 拖拽完成的回调
console.log('数据更新了:', data);
}
});
}, 100);
}
});
});
</script>
</body>
</html>
关键 layui-table-drag.js文件
/**
* LayuiTableDrag 插件
* author: webrs - lingfeng
* v1.0.0
*/
(function (window) {
"use strict";
class LayuiTableDrag {
constructor(config = {}) {
// 必要参数检查
if (!config.elem || !config.table || !config.id) {
console.error("LayuiTableDrag: elem、table、id 是必须的配置项");
return;
}
this.config = Object.assign(
{
trigger: "row", // 触发方式: row=整行触发, cell=第一列触发
triggerClass: "", // 自定义触发元素的类名
done: null, // 拖拽完成的回调函数
},
config
);
this.table = this.config.table;
this.tableId = this.config.id;
this.elem =
typeof config.elem === "string"
? config.elem.charAt(0) === "#"
? config.elem
: "#" + config.elem
: "#" + config.elem.attr("id");
this.tableView = `div[lay-table-id="${this.tableId}"]`;
this.waitForTable();
}
waitForTable() {
const timeoutLimit = 3000;
const startTime = Date.now();
const checkTable = () => {
const tableView = document.querySelector(this.tableView);
if (tableView) {
this.init();
} else if (Date.now() - startTime < timeoutLimit) {
setTimeout(checkTable, 50);
} else {
console.error("LayuiTableDrag: 表格加载超时");
}
};
checkTable();
}
init() {
const tableView = document.querySelector(this.tableView);
if (!tableView) {
console.error("LayuiTableDrag: 未找到指定的表格视图元素");
return;
}
this.injectStyle();
this.initObserver();
this.bindEvents();
}
injectStyle() {
if (!document.getElementById("layui-table-drag-style")) {
const style = document.createElement("style");
style.id = "layui-table-drag-style";
style.textContent = `
.layui-table-drag-trigger {
cursor: move;
}
.layui-table-drag-dragging {
opacity: 0.6;
background: #e6f7ff !important;
}
.layui-table-drag-target {
border-top: 2px dashed #1E9FFF !important;
}
${this.tableView} .layui-table-body tr {
cursor: move;
}
`;
document.head.appendChild(style);
}
}
initObserver() {
if (this.observer) return;
this.observer = new MutationObserver(() => {
this.bindEvents();
});
const tables = document.querySelectorAll(
`${this.tableView} .layui-table-body table`
);
if (tables.length > 0) {
tables.forEach((table) => {
this.observer.observe(table, {
childList: true,
subtree: true,
});
});
} else {
console.warn("LayuiTableDrag: 未找到表格体元素,无法初始化观察者");
}
}
getAllTableBodies() {
const mainTable = document.querySelector(
`${this.tableView} .layui-table-main.layui-table-body table`
);
const leftTable = document.querySelector(
`${this.tableView} .layui-table-fixed-l .layui-table-body table`
);
const rightTable = document.querySelector(
`${this.tableView} .layui-table-fixed-r .layui-table-body table`
);
return [mainTable, leftTable, rightTable].filter((el) => el);
}
bindEvents() {
const mainBody = document.querySelector(
`${this.tableView} .layui-table-main.layui-table-body`
);
const leftBody = document.querySelector(
`${this.tableView} .layui-table-fixed-l .layui-table-body`
);
const rightBody = document.querySelector(
`${this.tableView} .layui-table-fixed-r .layui-table-body`
);
const bodies = [mainBody, leftBody, rightBody].filter((el) => el);
bodies.forEach((body) => {
const rows = body.getElementsByTagName("tr");
Array.from(rows).forEach((row) => {
row.removeEventListener("dragstart", this.handleDragStart);
row.removeEventListener("dragend", this.handleDragEnd);
row.removeEventListener("dragover", this.handleDragOver);
row.removeEventListener("dragleave", this.handleDragLeave);
row.removeEventListener("drop", this.handleDrop);
this.bindRowEvents(row);
});
});
if (mainBody && leftBody) {
mainBody.addEventListener("scroll", () => {
leftBody.scrollTop = mainBody.scrollTop;
});
}
if (mainBody && rightBody) {
mainBody.addEventListener("scroll", () => {
rightBody.scrollTop = mainBody.scrollTop;
});
}
}
bindRowEvents(row) {
row.setAttribute("draggable", "true");
row.addEventListener("dragstart", (e) => this.handleDragStart(e, row));
row.addEventListener("dragend", (e) => this.handleDragEnd(e, row));
row.addEventListener("dragover", (e) => this.handleDragOver(e, row));
row.addEventListener("dragleave", (e) => this.handleDragLeave(e, row));
row.addEventListener("drop", (e) => this.handleDrop(e, row));
}
handleDragStart(e, row) {
e.stopPropagation();
const index = this.getRowIndex(row);
e.dataTransfer.setData("text/plain", index);
e.dataTransfer.effectAllowed = "move";
this.addClassToRow(index, "layui-table-drag-dragging");
const dragImage = row.cloneNode(true);
dragImage.style.opacity = "0.5";
document.body.appendChild(dragImage);
e.dataTransfer.setDragImage(dragImage, 0, 0);
setTimeout(() => document.body.removeChild(dragImage), 0);
}
handleDragEnd(e, row) {
e.stopPropagation();
const index = this.getRowIndex(row);
this.removeClassFromRow(index, "layui-table-drag-dragging");
this.removeClassFromAllRows("layui-table-drag-target");
this.bindRowEvents(row);
}
handleDragOver(e, row) {
e.preventDefault();
e.stopPropagation();
const index = this.getRowIndex(row);
this.removeClassFromAllRows("layui-table-drag-target");
this.addClassToRow(index, "layui-table-drag-target");
}
handleDragLeave(e, row) {
e.preventDefault();
e.stopPropagation();
const index = this.getRowIndex(row);
this.removeClassFromRow(index, "layui-table-drag-target");
}
handleDrop(e, row) {
e.preventDefault();
e.stopPropagation();
const sourceIndex = parseInt(e.dataTransfer.getData("text/plain"));
const targetIndex = this.getRowIndex(row);
if (sourceIndex === targetIndex) return;
this.moveData(sourceIndex, targetIndex);
this.removeClassFromAllRows("layui-table-drag-dragging");
this.removeClassFromAllRows("layui-table-drag-target");
}
getRowIndex(row) {
return Array.from(row.parentElement.children).indexOf(row);
}
addClassToRow(index, className) {
this.getAllTableBodies().forEach((body) => {
const row = body.rows[index];
if (row) row.classList.add(className);
});
}
removeClassFromRow(index, className) {
this.getAllTableBodies().forEach((body) => {
const row = body.rows[index];
if (row) row.classList.remove(className);
});
}
removeClassFromAllRows(className) {
document.querySelectorAll(`${this.elem} .${className}`).forEach((el) => {
el.classList.remove(className);
});
}
moveData(sourceIndex, targetIndex) {
const data = this.table.cache[this.tableId];
if (!data || !Array.isArray(data)) return;
const [movedItem] = data.splice(sourceIndex, 1);
data.splice(targetIndex, 0, movedItem);
this.table.reload(this.tableId, {
data: data,
done: () => {
this.bindEvents();
if (typeof this.config.done === "function") {
this.config.done(data);
}
},
});
}
destroy() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
const style = document.getElementById("layui-table-drag-style");
if (style) style.remove();
this.getAllTableBodies().forEach((table) => {
Array.from(table.rows).forEach((row) => {
row.removeAttribute("draggable");
});
});
}
}
// 注册到 layui
if (typeof layui !== "undefined") {
layui.define(["table"], function (exports) {
exports("tableDrag", LayuiTableDrag);
});
}
// 同时支持全局访问
window.LayuiTableDrag = LayuiTableDrag;
})(window);