前言
研究表格拖动到树的目的:
随着互联网的不断发展,网页的便捷性操作也越来越重要,方便快捷的操作方式越来越受人青睐,本文就介绍了表格拖动到树的操作逻辑,方便用户修改表格数据的所属。
前期花费了大量时间去使用各种VUE拖动插件(vuedraggable、sortablejs之类),但都无法满足我的需求,最后还是原生JS比较靠谱,才有了今天这篇文章。
一、HTML5拖放事件
在拖放的过程中会触发以下事件:
- 在拖动目标上触发事件 (源元素):
ondragstart
- 用户开始拖动元素时触发ondrag
- 元素正在拖动时触发ondragend
- 用户完成元素拖动后触发
- 释放目标时触发的事件:
ondragenter
- 当被鼠标拖动的对象进入其容器范围内时触发此事件ondragover
- 当某被拖动的对象在另一对象容器范围内拖动时触发此事件ondragleave
- 当被鼠标拖动的对象离开其容器范围内时触发此事件ondrop
- 在一个拖动过程中,释放鼠标键时触发此事件
二、简单拖动示例
1.复制代码
代码如下(示例):
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>拖动示例</title>
<style>
.droptarget {
float: left;
width: 100px;
height: 35px;
margin: 15px;
padding: 10px;
border: 1px solid #aaaaaa;
}
</style>
</head>
<body>
<p>在两个矩形框中来回拖动 p 元素:</p>
<div class="droptarget">
<p draggable="true" id="dragtarget">拖动我!</p>
</div>
<div class="droptarget"></div>
<p style="clear:both;"><strong>注意:</strong>Internet Explorer 8 及更早 IE 版本或 Safari 5.1 及更早版本的浏览器不支持 drag 事件。</p>
<p id="demo"></p>
<script>
/* 拖动时触发*/
document.addEventListener("dragstart", function(event) {
//dataTransfer.setData()方法设置数据类型和拖动的数据
event.dataTransfer.setData("Text", event.target.id);
// 拖动 p 元素时输出一些文本
document.getElementById("demo").innerHTML = "开始拖动 p 元素.";
//修改拖动元素的透明度
event.target.style.opacity = "0.4";
});
//在拖动p元素的同时,改变输出文本的颜色
document.addEventListener("drag", function(event) {
document.getElementById("demo").style.color = "red";
});
// 当拖完p元素输出一些文本元素和重置透明度
document.addEventListener("dragend", function(event) {
document.getElementById("demo").innerHTML = "完成 p 元素的拖动";
event.target.style.opacity = "1";
});
/* 拖动完成后触发 */
// 当p元素完成拖动进入droptarget,改变div的边框样式
document.addEventListener("dragenter", function(event) {
if ( event.target.className == "droptarget" ) {
event.target.style.border = "3px dotted red";
}
});
// 默认情况下,数据/元素不能在其他元素中被拖放。对于drop我们必须防止元素的默认处理
document.addEventListener("dragover", function(event) {
event.preventDefault();
});
// 当可拖放的p元素离开droptarget,重置div的边框样式
document.addEventListener("dragleave", function(event) {
if ( event.target.className == "droptarget" ) {
event.target.style.border = "";
}
});
/*对于drop,防止浏览器的默认处理数据(在drop中链接是默认打开)
复位输出文本的颜色和DIV的边框颜色
利用dataTransfer.getData()方法获得拖放数据
拖拖的数据元素id(“drag1”)
拖拽元素附加到drop元素*/
document.addEventListener("drop", function(event) {
event.preventDefault();
if ( event.target.className == "droptarget" ) {
document.getElementById("demo").style.color = "";
event.target.style.border = "";
var data = event.dataTransfer.getData("Text");
event.target.appendChild(document.getElementById(data));
}
});
</script>
</body>
</html>
2.运行代码
可以轻松的在两个矩形框中来回拖动 p 元素
三、表格拖动到左侧树示例
1.复制代码
代码如下(示例):
<template>
<div class="app-container">
<!--用户数据-->
<div class="main-wrap">
<div id="queryBox" class="search-wrap">
<el-form id="queryForm" ref="queryForm" size="mini" :model="queryParams" :inline="true">
<div class="query-wrap">
<el-form-item label="搜索" prop="search">
<el-input v-model="queryParams.search" placeholder="请输入" clearable size="mini"
style="width: 140px" @keyup.enter.native="handleQuery" />
</el-form-item>
<div style="display: inline-block">
<el-form-item>
<div class="search-btn">
<div v-preventReClick class="btn-wrap2" @click="handleQuery()">
搜索
</div>
</div>
</el-form-item>
<el-form-item>
<div class="search-btn">
<div v-preventReClick class="btn-wrap3" @click="handleReset()">
重置
</div>
</div>
</el-form-item>
</div>
</div>
</el-form>
<div v-show="showOther" class="other-bg" @click="showOther = false" />
<div class="right-btn">
<el-button type="text" icon="el-icon-refresh" @click="resetQuery">刷新</el-button>
<svg-icon v-preventReClick class="magnify" icon-class="magnify" @click="zoomControl()" />
</div>
</div>
<div class="content-wrap">
<div class="tree-wrap">
<el-input size="mini" placeholder="输入关键字进行过滤" v-model="filterText" clearable></el-input>
<div class="tree-choose" :style="{ height: chooseHeight }">
<el-tree ref="tree" :data="treeList" node-key="id" :props="defaultProps" accordion
@node-click="labelChoose" :filter-node-method="filterNode">
<span class="custom-tree-node" :data-id="data.id" slot-scope="{ node, data }">
<span class="label" :class="{ 'choose-item': chooseId == data.id }">{{
node.label }}</span>
</span>
</el-tree>
</div>
</div>
<div class="content-right">
<el-table v-loading="loading" border size="mini" :height="height" row-key="id" id="dragTable"
:data="dataList" row-class-name="drag-handle">
<el-table-column label="用户编号" align="center" prop="id" width="100" />
<el-table-column label="用户名称" align="center" prop="username" :show-overflow-tooltip="true" />
<el-table-column label="用户账号" align="center" prop="account" :show-overflow-tooltip="true" />
<el-table-column label="分组" align="center" prop="group.name" :show-overflow-tooltip="true" />
<el-table-column label="角色" align="center" prop="role.name" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" align="center" prop="phone" />
<div v-if="!loading" slot="empty" class="empty">
<img :src="emptyImg">
</div>
</el-table>
<!-- </draggable> -->
<element-Pagination v-show="total > 0" :total="total" :page.sync="queryParams.page"
:limit.sync="queryParams.perPage" @pagination="getList()" />
</div>
</div>
</div>
</div>
</template>
<script>
import emptyImg from '@/assets/image/noInfo.png';
export default {
name: 'UserList',
// components: { draggable },
data() {
return {
// 表格动态高度
height: undefined,
// 表格数据为空时默认图片
emptyImg: emptyImg,
// 遮罩层
loading: true,
// 总条数
total: 0,
// 用户表格数据
dataList: null,
// 是否显示弹出层
open: false,
chooseHeight: '',
// 表单参数
form: {},
// 查询参数
queryParams: {
page: 1,
perPage: 12,
search: undefined,
status: undefined,
group_id: undefined,
role_id: undefined
},
showOther: false,
defaultProps: {
children: 'children',
label: 'label'
},
filterText: undefined,
chooseId: undefined,
treeList: [{
label: '一级 1',
id: 1,
children: [{
label: '二级 1-1',
id: 11,
children: [{
id: 111,
label: '三级 1-1-1'
}]
}]
}, {
label: '一级 2',
id: 2,
children: [{
id: 21,
label: '二级 2-1',
children: [{
id: 211,
label: '三级 2-1-1'
}]
}, {
label: '二级 2-2',
id: 22,
children: [{
id: 221,
label: '三级 2-2-1'
}]
}]
}, {
label: '一级 3',
id: 3,
children: [{
id: 31,
label: '二级 3-1',
children: [{
id: 311,
label: '三级 3-1-1'
}]
}, {
id: 32,
label: '二级 3-2',
children: [{
id: 321,
label: '三级 3-2-1'
}]
}]
}]
};
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
}
},
created() {
const name = this.$route.name + 'PageSize';
const pageSize = localStorage.getItem(name);
if (pageSize) {
this.queryParams.perPage = pageSize * 1;
}
this.getList();
},
mounted() {
const height = document.getElementById('queryBox').clientHeight;
const val = 40 + height * 1;
this.height = 'calc((100vh - 1rem) - ' + val + 'px)';
this.chooseHeight = 'calc((100vh - 1.2rem) - ' + val + 'px)';
},
methods: {
// 缩放
zoomControl() {
// this.$store.dispatch('app/toggleSideBar')
this.$store.dispatch('app/toggleHideleft');
},
// 树节点筛选
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
labelChoose(row) {
this.chooseId = row.id;
this.getList();
},
// 行拖拽
openTableDrop() {
const tbody = document.querySelector('#dragTable tbody');
if (tbody) {
const dataList = this.dataList;
const tr = tbody.getElementsByTagName('tr');
for (let index = 0; index < tr.length; index++) {
tr[index].draggable = true;
tr[index].ondragstart = function(e) {
console.log(index);
const treeNodes = document.getElementsByClassName('tree-choose')[0].getElementsByClassName('el-tree-node__content');
for (let index2 = 0; index2 < treeNodes.length; index2++) {
treeNodes[index2].draggable = true;
treeNodes[index2].ondragstart = function(e) {
console.log(e);
};
treeNodes[index2].ondragover = function(e) {
e.preventDefault();
};
// 拖动结果
treeNodes[index2].ondrop = function() {
console.log('拖动的表格数据是:');
console.log(dataList[index]);
const treeId = treeNodes[index2].getElementsByClassName('custom-tree-node')[0].dataset.id;
console.log('拖动到的树ID是:' + treeId);
};
}
};
tr[index].ondragover = function(e) {
e.preventDefault();
};
tr[index].ondrop = function(e) {
console.log(e);
};
}
}
},
/** 查询用户列表 */
getList() {
this.loading = true;
const dataList = [{
id: 717, created_at: '2023-04-19 10:21:27',
status: 1,
statusVal: '正常',
username: 'admin',
phone: '13357118896',
account: 'admin',
role: {
id: 1,
name: '超级管理员'
},
group: { id: 1, name: '默认分组' }
}, {
id: 718, created_at: '2023-04-19 13:21:27',
status: 1,
statusVal: '正常',
username: '张三',
phone: '13757118896',
account: 'zhangsan',
role: {
id: 2,
name: '管理员'
},
group: { id: 2, name: '分组2' }
}];
this.dataList = dataList;
this.total = 2;
this.loading = false;
this.$nextTick(() => {
// 表格可拖拽
this.openTableDrop();
});
},
/** 搜索按钮操作 */
handleQuery() {
this.showOther = false;
this.queryParams.page = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.handleQuery();
},
// 重置按钮
handleReset() {
this.resetForm('queryForm');
}
}
};
</script>
<style lang="scss" scoped>
@import '@/assets/styles/mixin.scss';
.content-wrap {
display: flex;
.tree-wrap {
background: #fff;
width: 200px;
padding: 0.1rem;
border: 0.01rem solid #d3d7e1;
margin: 0 0.1rem 0.01rem 0.01rem;
.tree-choose {
overflow: hidden auto;
@include scrollBarNone;
font-size: 13px;
::v-deep .is-leaf.el-tree-node__expand-icon {
display: none;
}
.label {
padding-left: 0.05rem;
}
.site-item {
display: block;
max-width: calc(1.8rem - 60px);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.choose-item {
color: rgb(28, 103, 218);
}
}
}
.content-right {
width: calc(100% - 200px);
}
}
</style>
2.运行代码
代码运行后如下,拖动表格行到左侧树后,控制台会打印出拖动表格行的数据以及拖动到树的id.
总结
以上就是我的表格拖动到左侧树方案,有不同的见解或者疑问,欢迎留言。