手把手教你实现一个Vue无限级联树形表格(增删改)

Jacquelyn38
• 阅读 1355

前言

平时我们可能在做项目时,会遇到一个业务逻辑。实现一个无限级联树形表格,什么叫做无限级联树形表格呢?就是下图所展示的内容,有一个祖元素,然后下面可能有很多子孙元素,你可以实现添加、编辑、删除这样几个功能。

手把手教你实现一个Vue无限级联树形表格(增删改)

资源

  • JavaScript框架:vue.js

  • UI框架:Element UI

源码

这里需要重点说明的是,主要使用了递归的算法以及给数据标识的重要性。详细说明可以在源码中查看注释,也可以通过删改代码融会贯通。

<template>  
    <div class="container">  
        <div class="btn-r">  
            <el-button  
                type="primary"  
                size="small"  
                @click="addView = true"  
                icon="el-icon-circle-plus-outline"  
                class="add"  
                >添加</el-button  
            >  
        </div>  
        <el-table  
            :data="tableData"  
            style="width: 100%; margin-bottom: 20px"  
            row-key="value"  
            border  
            default-expand-all  
            size="medium"  
            :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"  
        >  
            <el-table-column prop="label" label="名称" sortable>  
            </el-table-column>  
            <el-table-column label="操作" align="center" width="180">  
                <template slot-scope="scope">  
                    <el-button  
                        type="text"  
                        size="small"  
                        @click="handleClick(scope.row, scope.$index)"  
                        >编辑</el-button  
                    >  
                    <el-button  
                        type="text"  
                        size="small"  
                        @click="deleteClick(scope.row, scope.$index)"  
                        >删除</el-button  
                    >  
                </template>  
            </el-table-column>  
        </el-table>  

        <!-- 添加窗口 -->  
        <el-dialog  
            title="添加"  
            :visible.sync="addView"  
            :close-on-click-modal="false"  
            width="30%"  
            @close="closeView"  
        >  
            <el-form :model="form" ref="form" :rules="rules">  
                <el-form-item  
                    label="位置"  
                    :label-width="formLabelWidth"  
                    prop="location"  
                >  
                    <el-select  
                        v-model="form.location"  
                        placeholder="请选择位置"  
                        @change="locationChange"  
                        size="small"  
                    >  
                        <el-option  
                            v-for="item in locationData"  
                            :key="item.id"  
                            :label="item.name"  
                            :value="item.id"  
                        />  
                    </el-select>  
                </el-form-item>  
                <el-form-item  
                    v-if="sonStatus"  
                    label="子位置"  
                    :label-width="formLabelWidth"  
                    prop="childArr"  
                >  
                    <el-cascader  
                        size="small"  
                        :key="isResouceShow"  
                        v-model="form.childArr"  
                        placeholder="请选择子位置"  
                        :label="'name'"  
                        :value="'id'"  
                        :options="tableData"  
                        :props="{ checkStrictly: true }"  
                        clearable  
                        @change="getCasVal"  
                    ></el-cascader>  
                </el-form-item>  
                <el-form-item  
                    label="名称"  
                    :label-width="formLabelWidth"  
                    prop="label"  
                >  
                    <el-input  
                        v-model="form.label"  
                        size="small"  
                        autocomplete="off"  
                        placeholder="请输入名称"  
                    ></el-input>  
                </el-form-item>  
            </el-form>  
            <span slot="footer" class="dialog-footer">  
                <el-button @click="addView = false" size="small"  
                    >取 消</el-button  
                >  
                <el-button type="primary" @click="okAdd('form')" size="small"  
                    >确 定</el-button  
                >  
            </span>  
        </el-dialog>  

        <!-- 编辑窗口 -->  
        <el-dialog  
            title="编辑"  
            :visible.sync="editView"  
            :close-on-click-modal="false"  
            width="30%"  
        >  
            <el-form :model="data" ref="data" :rules="rules">  
                <el-form-item  
                    label="位置"  
                    :label-width="formLabelWidth"  
                    prop="location"  
                >  
                    <el-select  
                        v-model="data.location"  
                        placeholder="请选择位置"  
                        size="small"  
                        @change="locationChange"  
                    >  
                        <el-option  
                            v-for="item in locationData"  
                            :key="item.id"  
                            :label="item.name"  
                            :value="item.id"  
                        />  
                    </el-select>  
                </el-form-item>  
                <el-form-item  
                    v-if="sonStatus"  
                    label="子位置"  
                    :label-width="formLabelWidth"  
                    prop="childArr"  
                >  
                    <el-cascader  
                        :key="isResouceShow"  
                        v-model="data.childArr"  
                        placeholder="请选择子位置"  
                        size="small"  
                        :label="'name'"  
                        :value="'id'"  
                        :options="tableData"  
                        :props="{ checkStrictly: true }"  
                        clearable  
                        @change="getCasVal"  
                    ></el-cascader>  
                </el-form-item>  
                <el-form-item  
                    label="名称"  
                    :label-width="formLabelWidth"  
                    prop="label"  
                >  
                    <el-input  
                        v-model="data.label"  
                        autocomplete="off"  
                        placeholder="请输入名称"  
                        size="small"  
                    ></el-input>  
                </el-form-item>  
            </el-form>  
            <span slot="footer" class="dialog-footer">  
                <el-button @click="editView = false" size="small"  
                    >取 消</el-button  
                >  
                <el-button type="primary" @click="okEdit('data')" size="small"  
                    >确 定</el-button  
                >  
            </span>  
        </el-dialog>  
    </div>  
</template>  

<script>  
export default {  
    name: 'Tag',  
    data() {  
        return {  
            location: '',  
            isResouceShow: 1,  
            addView: false,  
            sonStatus: false,  
            editView: false,  
            casArr: [],  
            childArr: [],  
            form: {},  
            data: {},  
            idx: '',  
            childkey: [],  
            formLabelWidth: '80px',  
            rules: {  
                label: [  
                    { required: true, message: '请输入名称', trigger: 'blur' }  
                ]  
            },  
            locationData: [  
                {  
                    id: 1,  
                    name: '顶'  
                },  
                {  
                    id: 2,  
                    name: '子'  
                }  
            ],  
            tableData: []  
        };  
    },  
    methods: {  
        // 监听关闭窗口  
        closeView() {  
            this.$refs['form'].resetFields(); // 关闭窗口,清空填写的内容  
        },  
        // 打开编辑  
        handleClick(item, index) {  
            item.value.length != 1  
                ? (this.sonStatus = true)  
                : (this.sonStatus = false);  
            this.editView = true;  
            const obj = Object.assign({}, item);  
            this.childkey = item.childkey;  
            this.casArr = item.childArr;  
            this.idx = index;  
            this.data = obj;  
        },  
        // 递归表格数据(编辑)  
        findSd(arr, i, casArr) {  
            if (i == casArr.length - 1) {  
                let index = casArr[i].substr(casArr[i].length - 1, 1);  
                return arr.splice(index, 1, this.data);  
            } else {  
                return this.findSd(  
                    arr[casArr[i].substr(casArr[i].length - 1, 1)].children,  
                    (i += 1),  
                    casArr  
                );  
            }  
        },  
        // 确定编辑  
        okEdit(data) {  
            this.$refs[data].validate(valid => {  
                if (valid) {  
                    if (this.data.value.length == 1) {  
                        this.tableData.splice(this.idx, 1, this.data);  
                        this.$message({  
                            type: 'success',  
                            message: '编辑成功'  
                        });  
                        this.editView = false;  
                    } else {  
                        this.findSd(this.tableData, 0, this.childkey);  
                        this.$message({  
                            type: 'success',  
                            message: '编辑成功'  
                        });  
                        this.editView = false;  
                    }  
                } else {  
                    return false;  
                }  
            });  
        },  
        // 递归表格数据(删除)  
        findDel(arr, i, item) {  
            let casArr = item.childkey;  
            if (i == casArr.length - 1) {  
                let index = casArr[i].substr(casArr[i].length - 1, 1);  
                return arr.splice(index, 1);  
            } else {  
                return this.findDel(  
                    arr[casArr[i].substr(casArr[i].length - 1, 1)].children,  
                    (i += 1),  
                    item  
                );  
            }  
        },  
        // 删除  
        deleteClick(item) {  
            this.$confirm(`此操作将删除该项, 是否继续?`, '提示', {  
                confirmButtonText: '确定',  
                cancelButtonText: '取消',  
                type: 'warning'  
            })  
                .then(() => {  
                    if (item.children.length != 0) {  
                        this.$message.warning({  
                            message: '请删除子节点',  
                            duration: 1000  
                        });  
                    } else {  
                        this.casArr = item.childArr;  
                        ++this.isResouceShow; // 给级联控件绑定一个key,防止报错。  
                        if (item.value.length == 1) { // 删除的是顶节点  
                            console.log(1);  
                            this.tableData.splice(item.value, 1);  
                            this.$message({  
                                type: 'success',  
                                message: '删除成功'  
                            });  
                        } else { // 删除的是子节点  
                            console.log(2);  
                            this.findDel(this.tableData, 0, item);  
                            this.$message({  
                                type: 'success',  
                                message: '删除成功'  
                            });  
                        }  
                    }  
                })  
                .catch(err => {  
                    console.log(err);  
                    this.$message({  
                        type: 'info',  
                        message: '已取消删除'  
                    });  
                });  
        },  
        // 是否显示次位置  
        locationChange(v) {  
            if (v == 2) {  
                this.sonStatus = true;  
            } else {  
                this.sonStatus = false;  
            }  
        },  
        // 获取次位置  
        getCasVal(v) {  
            this.casArr = v;  
            this.form.childArr = v;  
        },  
        // 递归表格数据(添加)  
        find(arr, i) {  
            if (i == this.casArr.length - 1) {  
                return arr[this.casArr[i].substr(this.casArr[i].length - 1, 1)]  
                    .children;  
            } else {  
                return this.find(  
                    arr[this.casArr[i].substr(this.casArr[i].length - 1, 1)]  
                        .children,  
                    (i += 1)  
                );  
            }  
        },  
        // 确定添加  
        okAdd(form) {  
            this.$refs[form].validate(valid => {  
                if (valid) {  
                    if (this.sonStatus == false) {  
                        this.form.value = String(this.tableData.length);  
                        const obj = Object.assign({}, this.form);  
                        obj.children = [];  
                        obj.childArr = [];  
                        this.tableData.push(obj);  
                        this.$message({  
                            type: 'success',  
                            message: '添加成功'  
                        });  
                        this.addView = false;  
                    } else {  
                        let arr = this.find(this.tableData, 0);  
                        this.childArr = [...this.casArr, String(arr.length)];  
                        this.form.value =  
                            String(this.casArr[this.casArr.length - 1]) +  
                            String(arr.length);  
                        delete this.form.children;  
                        const obj = Object.assign({}, this.form);  
                        obj.children = [];  
                        obj.childkey = [...this.casArr, String(arr.length)];  
                        arr.push(obj);  
                        this.$message({  
                            type: 'success',  
                            message: '添加成功'  
                        });  
                        this.addView = false;  
                    }  
                } else {  
                    return false;  
                }  
            });  
        }  
    }  
};  
</script>  

<style lang="scss" scoped>  
::v-deep .el-form-item__content {  
    width: 203px;  
}  
</style>  

结语

  • 欢迎关注我的公众号前端历劫之路

  • 回复关键词电子书,即可获取12本前端热门电子书。

  • 回复关键词红宝书第4版,即可获取最新《JavaScript高级程序设计》(第四版)电子书。

  • 关注公众号后,点击下方菜单即可加我微信,我拉拢了很多IT大佬,创建了一个技术交流、文章分享群,期待你的加入。

  • 作者:Vam的金豆之路

  • 微信公众号:前端历劫之路

手把手教你实现一个Vue无限级联树形表格(增删改)

- END -

手把手教你实现一个Vue无限级联树形表格(增删改)

本文转转自微信公众号前端历劫之路原创https://mp.weixin.qq.com/s/uUmzBeCq3PL1PZyYQDlFBw,如有侵权,请联系删除。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
翼
3年前
js 数组 转为树形结构
需要转换为树形的数组vardata{"orderById":null,"platformCommissionProportion":1,"name":"添加剂","pid":13,"id":26
Jacquelyn38 Jacquelyn38
2年前
我用Vue.js与ElementUI搭建了一个无限级联层级表格组件
前言今天,回老家了。第一件事就是回家把大屏安排上,写作的感觉太爽了,终于可以专心地写文章了。我们今天要做的项目是怎么样搭建一个无限级联层级表格组件,好了,多了不多说,赶快行动起来吧!项目一览到底是啥样子来?我们来看下。正如你所看到的那样,这个组件涉及添加、删除、编辑功能,并且可以无限级嵌套。那么怎样实现的?我们来看下。源码直接给出源码,就是这么直接。<tem
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这