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

前端历劫之路

共 13016字,需浏览 27分钟

 ·

2021-02-11 11:03

前言

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

项目一览

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

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

源码

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

<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

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

结语

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

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

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


  • 欢迎关注我的公众号前端历劫之路
  • 回复关键词电子书,即可获取12本前端热门电子书。
  • 回复关键词红宝书第4版,即可获取最新《JavaScript高级程序设计》(第四版)电子书。
  • 我创建了一个技术交流、文章分享群,群里有很多大厂的前端大佬,关注公众号后,点击下方菜单了解更多即可加我微信,期待你的加入。

浏览 27
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报