我用Vue.js与ElementUI搭建了一个无限级联层级表格组件

Jacquelyn38
• 阅读 1844

前言

今天,回老家了。第一件事就是回家把大屏安排上,写作的感觉太爽了,终于可以专心地写文章了。我们今天要做的项目是怎么样搭建一个无限级联层级表格组件,好了,多了不多说,赶快行动起来吧!

项目一览

到底是啥样子来?我们来看下。

我用Vue.js与ElementUI搭建了一个无限级联层级表格组件

正如你所看到的那样,这个组件涉及添加、删除、编辑功能,并且可以无限级嵌套。那么怎样实现的?我们来看下。

源码

直接给出源码,就是这么直接。

<template>
    <div class="container">
        <el-button
            type="primary"
            size="small"
            @click="handleCreate"
            icon="el-icon-circle-plus-outline"
            style="margin: 10px 0"
            >添加</el-button
        >
        <el-table
            :data="tableData"
            style="width: 100%; margin-bottom: 20px"
            border
            row-key="value"
            stripe
            size="medium"
            :tree-props="{ children: 'children' }"
        >
            <el-table-column prop="label" label="标签名称"> </el-table-column>
            <el-table-column prop="location" label="层级"> </el-table-column>
            <el-table-column label="操作" :align="alignDir" width="180">
                <template slot-scope="scope">
                    <el-button
                        type="text"
                        size="small"
                        @click="handleUpdate(scope.row)"
                        >编辑</el-button
                    >
                    <el-button
                        type="text"
                        size="small"
                        @click="deleteClick(scope.row)"
                        >删除</el-button
                    >
                </template>
            </el-table-column>
        </el-table>
        <el-dialog
            :title="textMap[dialogStatus]"
            :visible.sync="dialogFormVisible"
            width="30%"
        >
            <el-form
                ref="dataForm"
                :rules="rules"
                :model="temp"
                label-position="left"
                label-width="120px"
                style="margin-left: 50px"
            >
                <el-form-item
                    label="层级:"
                    prop="location"
                    v-if="dialogStatus !== 'update'"
                >
                    <el-select
                        v-model="temp.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 && dialogStatus !== 'update'"
                    label="子位置:"
                    prop="children"
                >
                    <el-cascader
                        size="small"
                        :key="isResouceShow"
                        v-model="temp.children"
                        placeholder="请选择子位置"
                        :label="'label'"
                        :value="'value'"
                        :options="tableData"
                        :props="{ checkStrictly: true }"
                        clearable
                        @change="getCasVal"
                    ></el-cascader>
                </el-form-item>
                <el-form-item label="标签名称:" prop="label">
                    <el-input
                        v-model="temp.label"
                        size="small"
                        autocomplete="off"
                        placeholder="请输入标签名称"
                    ></el-input>
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click="dialogFormVisible = false" size="small">
                    取消
                </el-button>
                <el-button
                    type="primary"
                    size="small"
                    @click="
                        dialogStatus === 'create' ? createData() : updateData()
                    "
                >
                    确认
                </el-button>
            </div>
        </el-dialog>
    </div>
</template>

<script>
export default {
    name: 'Tag',
    data() {
        return {
            alignDir: 'center',
            textMap: {
                update: '编辑',
                create: '添加',
            },
            dialogStatus: '',
            dialogFormVisible: false,
            temp: {},
            isResouceShow: 1,
            sonStatus: false,
            casArr: [],
            idx: '',
            childKey: [],
            rules: {
                location: [
                    {
                        required: true,
                        message: '请选择层级',
                        trigger: 'blur',
                    },
                ],
                label: [
                    { required: true, message: '请输入名称', trigger: 'blur' },
                ],
                children: [
                    {
                        required: true,
                        message: '请选择子位置',
                        trigger: 'blur',
                    },
                ],
            },
            locationData: [
                {
                    id: '1',
                    name: '顶',
                },
                {
                    id: '2',
                    name: '子',
                },
            ],
            tableData: [
                {
                    tagId: '1', // 标签id
                    label: '第0', // 标签名称
                    parent: '', // 父级名称
                    location: '1', // 层级
                    value: '0', // 标识位
                    children: [
                        {
                            tagId: '1', // 子标签id
                            childKey: ['0', '0'], // 子标识位
                            label: '第0-0',
                            parent: '第0',
                            location: '2',
                            value: '0-0',
                            children: [],
                        },
                        {
                            tagId: '2', // 子标签id
                            childKey: ['0', '1'],
                            label: '第0-1',
                            parent: '第0',
                            location: '2',
                            value: '0-1',
                            children: [],
                        },
                    ],
                },
            ]
        };
    },
    methods: {
        // 递归寻找同级
        findSameTable(arr, i, casArr) {
            if (i == casArr.length - 1) {
                return arr;
            } else {
                return this.findTable(
                    arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
                    (i += 1),
                    casArr
                );
            }
        },
        // 寻找父级
        findTable(arr, i, casArr) {
            if (i == casArr.length - 1) {
                let index = casArr[i].substr(casArr[i].length - 1, 1);
                return arr[index];
            } else {
                return this.findTable(
                    arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
                    (i += 1),
                    casArr
                );
            }
        },
        // 递归表格数据(添加)
        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)
                );
            }
        },
        // 递归表格数据(编辑)
        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.temp);
            } else {
                return this.findSd(
                    arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
                    (i += 1),
                    casArr
                );
            }
        },
        // 递归寻找同步名称
        findLable(arr, i, casArr) {
            if (i == casArr.length - 1) {
                let index = casArr[i].substr(casArr[i].length - 1, 1);
                return arr[index];
            } else {
                return this.findLable(
                    arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
                    (i += 1),
                    casArr
                );
            }
        },
        // 同步子名称
        useChildLable(arr) {
            if (arr !== []) {
                arr.forEach((item) => {
                    item.parent = this.temp.label;
                });
            }
        },
        // 递归表格数据(删除)
        findDel(arr, i, item) {
            let casArr = item.childKey;
            if (i == casArr.length - 2) {
                let index = casArr[i].substr(casArr[i].length - 1, 1);
                arr[index].children.forEach((it, ix, arrs) => {
                    if (it == item) {
                        return arrs.splice(ix, 1);
                    }
                });
            } else {
                return this.findDel(
                    arr[casArr[i].substr(casArr[i].length - 1, 1)].children,
                    (i += 1),
                    item
                );
            }
        },
      // 置空
        resetTemp() {
            this.temp = {};
        },
      // 打开添加
        handleCreate() {
            this.resetTemp();
            this.dialogFormVisible = true;
            this.dialogStatus = 'create';
            this.$nextTick(() => {
                this.$refs['dataForm'].clearValidate();
            });
        },
      // 添加
        createData() {
            this.$refs['dataForm'].validate((valid) => {
                if (valid) {
                    if (this.sonStatus == false) {
                        this.temp.value = String(this.tableData.length);
                        const obj = Object.assign({}, this.temp);
                        obj.children = [];
                        obj.parent = '';
                        this.tableData.push(obj);
                        this.$message({
                            type: 'success',
                            message: '添加成功',
                        });
                        this.dialogFormVisible = false;
                    } else {
                        let arr = this.find(this.tableData, 0);
                        this.temp.value =
                            String(this.casArr[this.casArr.length - 1]) +
                            '-' +
                            String(arr.length);
                        delete this.temp.children;

                        const obj = Object.assign({}, this.temp);
                        obj.children = [];
                        obj.childKey = [...this.casArr, String(arr.length)];
                        obj.parent = this.findTable(
                            this.tableData,
                            0,
                            this.casArr
                        ).label;
                        if (this.temp.location === '2') {
                            obj.location = String(
                                [...this.casArr, String(arr.length)].length
                            );
                        }
                        arr.push(obj);
                        this.$message({
                            type: 'success',
                            message: '添加成功',
                        });
                        this.dialogFormVisible = false;
                    }
                } else {
                    return false;
                }
            });
        },
      // 打开更新
        handleUpdate(row) {
            console.log(row);
            row.value.length != 1
                ? (this.sonStatus = true)
                : (this.sonStatus = false);
            this.temp = Object.assign({}, row); // copy obj
            if (row.childKey) {
                this.childKey = row.childKey;
                this.idx = row.childKey[row.childKey.length - 1];
            } else {
                this.idx = row.value;
            }
            console.log(this.idx);

            this.dialogStatus = 'update';
            this.dialogFormVisible = true;
            this.$nextTick(() => {
                this.$refs['dataForm'].clearValidate();
            });
        },
      // 更新
        updateData() {
            this.$refs['dataForm'].validate((valid) => {
                if (valid) {
                    if (this.temp.location === '1') {
                        console.log(this.temp);
                        this.tableData.splice(this.idx, 1, this.temp);
                        this.useChildLable(this.tableData[this.idx].children);
                        this.$message({
                            type: 'success',
                            message: '编辑成功',
                        });
                        this.dialogFormVisible = false;
                    } else {
                        this.findSd(this.tableData, 0, this.childKey);
                        this.useChildLable(
                            this.findLable(this.tableData, 0, this.childKey)
                                .children
                        );
                        this.$message({
                            type: 'success',
                            message: '编辑成功',
                        });
                        this.dialogFormVisible = false;
                    }
                } else {
                    return false;
                }
            });
        },
        // 删除父级节点
        deleteParent(item) {
            this.tableData.forEach((it, ix, arrs) => {
                if (it == item) {
                    return arrs.splice(ix, 1);
                }
            });
        },
        // 删除
        deleteClick(item) {
            this.$confirm(`此操作将删除该标签, 是否继续?`, '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning',
            })
                .then(() => {
                    if (item.children.length != 0) {
                        this.$message.warning({
                            message: '请删除子节点',
                            duration: 1000,
                        });
                    } else {
                        ++this.isResouceShow;
                        if (item.value.length == 1) {
                            this.deleteParent(item);
                            this.$message({
                                type: 'success',
                                message: '删除成功',
                            });
                        } else {
                            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;
        },
    },
};
</script>

代码可以直接拿来用,但是要注意事先要安装下ElementUI框架。无限层级的核心算法是递归算法,掌握了这一点,任何难题都可以解决。

下面,我们就这个项目来回顾下前端中的递归算法。

递归简而言之就是函数调用自己。递归算法中有两个条件:基线条件和递归条件。基线条件用于控制递归啥时候暂停,而递归条件是控制调用自己的方式。

最简单的一个例子是5的阶乘。


var func = function(i){
    if(i === 1){
        return 1;
    }else{
        return i*func(i-1);
    }

}
func(5);

这样就很简单的实现了一个递归算法,我们将上述例子拆解下。

// 递
5*func(4);
5*4*func(3);
5*4*3*func(2);
5*4*3*2*func(1);
// 归
5*4*3*2*1;
5*4*3*2;
5*4*6;
5*24;
120

递归其实可以理解成两个操作递与归。可以这样比喻,比如你在做一道数学题时,有一个知识点你不懂,你需要查资料。但是,通过查资料你发现这个知识点中你又有另一个不明白的知识点,你又开始继续查,直到你没有不懂的知识点,这样递的操作已经完成。然后,你把已经查过的这些知识点又从尾到头复习了一遍,这样归的操作已经完成。最后,你明白了最初那个知识点。

结语

最后,在知乎上看到一篇文章觉得比较好,分享下。

对于刚工作的年轻人,我觉得技术重要:你以后很可能会跳槽到不同业务方向的公司,这时原先公司的业务知识没用了,而技术是不分公司、国界的~ 可能中国国情特殊,还没形成德国或美国硅谷所谓的工程师文化环境吧;我一向认为:技术人优先锻炼技术,比业务熟悉程度我们能比得过产品、运营、领导吗?招你的是技术岗位,就先把技术弄好,技术才是我们技术人的安身立命之本~ 当然,业务也很重要,不了解业务瞎钻技术,就像个无头苍蝇,无法根据业务场景选择合适够用、省力的技术,做事就会事倍功半~

摘自--知乎大牛(刀剑红叶)

我用Vue.js与ElementUI搭建了一个无限级联层级表格组件

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

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

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

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

我用Vue.js与ElementUI搭建了一个无限级联层级表格组件

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
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
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Jacquelyn38 Jacquelyn38
3年前
手把手教你实现一个Vue无限级联树形表格(增删改)
前言平时我们可能在做项目时,会遇到一个业务逻辑。实现一个无限级联树形表格,什么叫做无限级联树形表格呢?就是下图所展示的内容,有一个祖元素,然后下面可能有很多子孙元素,你可以实现添加、编辑、删除这样几个功能。资源JavaScript框架:vue.jsUI框架:ElementUI源码这里需要重点说明的是,主要使用了递归的算法以及给数
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这