Element UI 表格拖动到左侧树


前言

研究表格拖动到树的目的:

随着互联网的不断发展,网页的便捷性操作也越来越重要,方便快捷的操作方式越来越受人青睐,本文就介绍了表格拖动到树的操作逻辑,方便用户修改表格数据的所属。


前期花费了大量时间去使用各种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.
在这里插入图片描述


总结

以上就是我的表格拖动到左侧树方案,有不同的见解或者疑问,欢迎留言。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
element ui 表格可以通过手指拖动来实现重新排序的功能。步骤如下: 1. 首先,需要找到表格的 DOM 元素,也就是要拖拽元素的父容器。可以通过以下代码找到表格的 tbody 元素: ```javascript const tbody = this.$refs.manufacturing.$el.querySelector(".el-table__body-wrapper > table > tbody"); ``` 这里的 `this.$refs.manufacturing` 是指引用的表格组件,通过 `$el` 属性获取到真实的 DOM 元素。 2. 接下来,需要使用 Sortable 库来创建可拖拽行的效果。可以使用以下代码: ```javascript Sortable.create(tbody, { onEnd({ newIndex, oldIndex }) { const currRow = _this.form.manufacturing.splice(oldIndex, 1)[0]; _this.form.manufacturing.splice(newIndex, 0, currRow); }, }); ``` 在 `onEnd` 回调函数中,可以通过 `newIndex` 和 `oldIndex` 获取到拖拽行的新位置和原位置。然后,可以使用数组的 `splice` 方法将当前行从原位置删除,并插入到新位置。 这样,通过以上步骤,就可以实现在 element ui 表格中使用手指拖动进行行的重新排序了。 请注意,以上代码示例中的 `this.$refs.manufacturing` 和 `_this.form.manufacturing` 是示例代码,具体的引用和数据需要根据实际情况进行修改。同时,还需要确保已经引入了 Sortable 库。 参考资料: element ui 表格动态列显示空白bug 修复方法 template文件应用 row-key填写唯一标识 id="dragTable"是为了通过document找到该父容器 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Vue2+elementui表格实现拖拽行功能](https://blog.csdn.net/weixin_45441470/article/details/128701619)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [element ui 表格动态列显示空白bug 修复方法](https://download.csdn.net/download/weixin_38563176/12952759)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [【VUEElementUI实现表格拖拽功能及行大图预览](https://blog.csdn.net/weixin_47375144/article/details/131301371)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值