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

开发 前端
今天要做的项目是怎么样搭建一个无限级联层级表格组件,好了,多了不多说,赶快行动起来吧!

[[381545]]

 前言

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

项目一览

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


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

源码

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

  1. <template> 
  2.     <div class="container"
  3.         <el-button 
  4.             type="primary" 
  5.             size="small" 
  6.             @click="handleCreate" 
  7.             icon="el-icon-circle-plus-outline" 
  8.             style="margin: 10px 0" 
  9.             >添加</el-button 
  10.         > 
  11.         <el-table 
  12.             :data="tableData" 
  13.             style="width: 100%; margin-bottom: 20px" 
  14.             border 
  15.             row-key="value" 
  16.             stripe 
  17.             size="medium" 
  18.             :tree-props="{ children: 'children' }" 
  19.         > 
  20.             <el-table-column prop="label" label="标签名称"> </el-table-column
  21.             <el-table-column prop="location" label="层级"> </el-table-column
  22.             <el-table-column label="操作" :align="alignDir" width="180"
  23.                 <template slot-scope="scope"
  24.                     <el-button 
  25.                         type="text" 
  26.                         size="small" 
  27.                         @click="handleUpdate(scope.row)" 
  28.                         >编辑</el-button 
  29.                     > 
  30.                     <el-button 
  31.                         type="text" 
  32.                         size="small" 
  33.                         @click="deleteClick(scope.row)" 
  34.                         >删除</el-button 
  35.                     > 
  36.                 </template> 
  37.             </el-table-column
  38.         </el-table
  39.         <el-dialog 
  40.             :title="textMap[dialogStatus]" 
  41.             :visible.sync="dialogFormVisible" 
  42.             width="30%" 
  43.         > 
  44.             <el-form 
  45.                 ref="dataForm" 
  46.                 :rules="rules" 
  47.                 :model="temp" 
  48.                 label-position="left" 
  49.                 label-width="120px" 
  50.                 style="margin-left: 50px" 
  51.             > 
  52.                 <el-form-item 
  53.                     label="层级:" 
  54.                     prop="location" 
  55.                     v-if="dialogStatus !== 'update'" 
  56.                 > 
  57.                     <el-select 
  58.                         v-model="temp.location" 
  59.                         placeholder="请选择层级" 
  60.                         @change="locationChange" 
  61.                         size="small" 
  62.                     > 
  63.                         <el-option 
  64.                             v-for="item in locationData" 
  65.                             :key="item.id" 
  66.                             :label="item.name" 
  67.                             :value="item.id" 
  68.                         /> 
  69.                     </el-select
  70.                 </el-form-item> 
  71.                 <el-form-item 
  72.                     v-if="sonStatus && dialogStatus !== 'update'" 
  73.                     label="子位置:" 
  74.                     prop="children" 
  75.                 > 
  76.                     <el-cascader 
  77.                         size="small" 
  78.                         :key="isResouceShow" 
  79.                         v-model="temp.children" 
  80.                         placeholder="请选择子位置" 
  81.                         :label="'label'" 
  82.                         :value="'value'" 
  83.                         :options="tableData" 
  84.                         :props="{ checkStrictly: true }" 
  85.                         clearable 
  86.                         @change="getCasVal" 
  87.                     ></el-cascader> 
  88.                 </el-form-item> 
  89.                 <el-form-item label="标签名称:" prop="label"
  90.                     <el-input 
  91.                         v-model="temp.label" 
  92.                         size="small" 
  93.                         autocomplete="off" 
  94.                         placeholder="请输入标签名称" 
  95.                     ></el-input> 
  96.                 </el-form-item> 
  97.             </el-form> 
  98.             <div slot="footer" class="dialog-footer"
  99.                 <el-button @click="dialogFormVisible = false" size="small"
  100.                     取消 
  101.                 </el-button> 
  102.                 <el-button 
  103.                     type="primary" 
  104.                     size="small" 
  105.                     @click=" 
  106.                         dialogStatus === 'create' ? createData() : updateData() 
  107.                     " 
  108.                 > 
  109.                     确认 
  110.                 </el-button> 
  111.             </div> 
  112.         </el-dialog> 
  113.     </div> 
  114. </template> 
  115.  
  116. <script> 
  117. export default { 
  118.     name'Tag'
  119.     data() { 
  120.         return { 
  121.             alignDir: 'center'
  122.             textMap: { 
  123.                 update'编辑'
  124.                 create'添加'
  125.             }, 
  126.             dialogStatus: ''
  127.             dialogFormVisible: false
  128.             temp: {}, 
  129.             isResouceShow: 1, 
  130.             sonStatus: false
  131.             casArr: [], 
  132.             idx: ''
  133.             childKey: [], 
  134.             rules: { 
  135.                 location: [ 
  136.                     { 
  137.                         required: true
  138.                         message: '请选择层级'
  139.                         trigger'blur'
  140.                     }, 
  141.                 ], 
  142.                 label: [ 
  143.                     { required: true, message: '请输入名称'trigger'blur' }, 
  144.                 ], 
  145.                 children: [ 
  146.                     { 
  147.                         required: true
  148.                         message: '请选择子位置'
  149.                         trigger'blur'
  150.                     }, 
  151.                 ], 
  152.             }, 
  153.             locationData: [ 
  154.                 { 
  155.                     id: '1'
  156.                     name'顶'
  157.                 }, 
  158.                 { 
  159.                     id: '2'
  160.                     name'子'
  161.                 }, 
  162.             ], 
  163.             tableData: [ 
  164.                 { 
  165.                     tagId: '1', // 标签id 
  166.                     label: '第0', // 标签名称 
  167.                     parent: '', // 父级名称 
  168.                     location: '1', // 层级 
  169.                     value: '0', // 标识位 
  170.                     children: [ 
  171.                         { 
  172.                             tagId: '1', // 子标签id 
  173.                             childKey: ['0''0'], // 子标识位 
  174.                             label: '第0-0'
  175.                             parent: '第0'
  176.                             location: '2'
  177.                             value: '0-0'
  178.                             children: [], 
  179.                         }, 
  180.                         { 
  181.                             tagId: '2', // 子标签id 
  182.                             childKey: ['0''1'], 
  183.                             label: '第0-1'
  184.                             parent: '第0'
  185.                             location: '2'
  186.                             value: '0-1'
  187.                             children: [], 
  188.                         }, 
  189.                     ], 
  190.                 }, 
  191.             ] 
  192.         }; 
  193.     }, 
  194.     methods: { 
  195.         // 递归寻找同级 
  196.         findSameTable(arr, i, casArr) { 
  197.             if (i == casArr.length - 1) { 
  198.                 return arr; 
  199.             } else { 
  200.                 return this.findTable( 
  201.                     arr[casArr[i].substr(casArr[i].length - 1, 1)].children, 
  202.                     (i += 1), 
  203.                     casArr 
  204.                 ); 
  205.             } 
  206.         }, 
  207.         // 寻找父级 
  208.         findTable(arr, i, casArr) { 
  209.             if (i == casArr.length - 1) { 
  210.                 let index = casArr[i].substr(casArr[i].length - 1, 1); 
  211.                 return arr[index]; 
  212.             } else { 
  213.                 return this.findTable( 
  214.                     arr[casArr[i].substr(casArr[i].length - 1, 1)].children, 
  215.                     (i += 1), 
  216.                     casArr 
  217.                 ); 
  218.             } 
  219.         }, 
  220.         // 递归表格数据(添加) 
  221.         find(arr, i) { 
  222.             if (i == this.casArr.length - 1) { 
  223.                 return arr[this.casArr[i].substr(this.casArr[i].length - 1, 1)] 
  224.                     .children; 
  225.             } else { 
  226.                 return this.find( 
  227.                     arr[this.casArr[i].substr(this.casArr[i].length - 1, 1)] 
  228.                         .children, 
  229.                     (i += 1) 
  230.                 ); 
  231.             } 
  232.         }, 
  233.         // 递归表格数据(编辑) 
  234.         findSd(arr, i, casArr) { 
  235.             if (i == casArr.length - 1) { 
  236.                 let index = casArr[i].substr(casArr[i].length - 1, 1); 
  237.                 return arr.splice(index, 1, this.temp); 
  238.             } else { 
  239.                 return this.findSd( 
  240.                     arr[casArr[i].substr(casArr[i].length - 1, 1)].children, 
  241.                     (i += 1), 
  242.                     casArr 
  243.                 ); 
  244.             } 
  245.         }, 
  246.         // 递归寻找同步名称 
  247.         findLable(arr, i, casArr) { 
  248.             if (i == casArr.length - 1) { 
  249.                 let index = casArr[i].substr(casArr[i].length - 1, 1); 
  250.                 return arr[index]; 
  251.             } else { 
  252.                 return this.findLable( 
  253.                     arr[casArr[i].substr(casArr[i].length - 1, 1)].children, 
  254.                     (i += 1), 
  255.                     casArr 
  256.                 ); 
  257.             } 
  258.         }, 
  259.         // 同步子名称 
  260.         useChildLable(arr) { 
  261.             if (arr !== []) { 
  262.                 arr.forEach((item) => { 
  263.                     item.parent = this.temp.label; 
  264.                 }); 
  265.             } 
  266.         }, 
  267.         // 递归表格数据(删除) 
  268.         findDel(arr, i, item) { 
  269.             let casArr = item.childKey; 
  270.             if (i == casArr.length - 2) { 
  271.                 let index = casArr[i].substr(casArr[i].length - 1, 1); 
  272.                 arr[index].children.forEach((it, ix, arrs) => { 
  273.                     if (it == item) { 
  274.                         return arrs.splice(ix, 1); 
  275.                     } 
  276.                 }); 
  277.             } else { 
  278.                 return this.findDel( 
  279.                     arr[casArr[i].substr(casArr[i].length - 1, 1)].children, 
  280.                     (i += 1), 
  281.                     item 
  282.                 ); 
  283.             } 
  284.         }, 
  285.       // 置空 
  286.         resetTemp() { 
  287.             this.temp = {}; 
  288.         }, 
  289.       // 打开添加 
  290.         handleCreate() { 
  291.             this.resetTemp(); 
  292.             this.dialogFormVisible = true
  293.             this.dialogStatus = 'create'
  294.             this.$nextTick(() => { 
  295.                 this.$refs['dataForm'].clearValidate(); 
  296.             }); 
  297.         }, 
  298.       // 添加 
  299.         createData() { 
  300.             this.$refs['dataForm'].validate((valid) => { 
  301.                 if (valid) { 
  302.                     if (this.sonStatus == false) { 
  303.                         this.temp.value = String(this.tableData.length); 
  304.                         const obj = Object.assign({}, this.temp); 
  305.                         obj.children = []; 
  306.                         obj.parent = ''
  307.                         this.tableData.push(obj); 
  308.                         this.$message({ 
  309.                             type: 'success'
  310.                             message: '添加成功'
  311.                         }); 
  312.                         this.dialogFormVisible = false
  313.                     } else { 
  314.                         let arr = this.find(this.tableData, 0); 
  315.                         this.temp.value = 
  316.                             String(this.casArr[this.casArr.length - 1]) + 
  317.                             '-' + 
  318.                             String(arr.length); 
  319.                         delete this.temp.children; 
  320.  
  321.                         const obj = Object.assign({}, this.temp); 
  322.                         obj.children = []; 
  323.                         obj.childKey = [...this.casArr, String(arr.length)]; 
  324.                         obj.parent = this.findTable( 
  325.                             this.tableData, 
  326.                             0, 
  327.                             this.casArr 
  328.                         ).label; 
  329.                         if (this.temp.location === '2') { 
  330.                             obj.location = String( 
  331.                                 [...this.casArr, String(arr.length)].length 
  332.                             ); 
  333.                         } 
  334.                         arr.push(obj); 
  335.                         this.$message({ 
  336.                             type: 'success'
  337.                             message: '添加成功'
  338.                         }); 
  339.                         this.dialogFormVisible = false
  340.                     } 
  341.                 } else { 
  342.                     return false
  343.                 } 
  344.             }); 
  345.         }, 
  346.       // 打开更新 
  347.         handleUpdate(row) { 
  348.             console.log(row); 
  349.             row.value.length != 1 
  350.                 ? (this.sonStatus = true
  351.                 : (this.sonStatus = false); 
  352.             this.temp = Object.assign({}, row); // copy obj 
  353.             if (row.childKey) { 
  354.                 this.childKey = row.childKey; 
  355.                 this.idx = row.childKey[row.childKey.length - 1]; 
  356.             } else { 
  357.                 this.idx = row.value; 
  358.             } 
  359.             console.log(this.idx); 
  360.  
  361.             this.dialogStatus = 'update'
  362.             this.dialogFormVisible = true
  363.             this.$nextTick(() => { 
  364.                 this.$refs['dataForm'].clearValidate(); 
  365.             }); 
  366.         }, 
  367.       // 更新 
  368.         updateData() { 
  369.             this.$refs['dataForm'].validate((valid) => { 
  370.                 if (valid) { 
  371.                     if (this.temp.location === '1') { 
  372.                         console.log(this.temp); 
  373.                         this.tableData.splice(this.idx, 1, this.temp); 
  374.                         this.useChildLable(this.tableData[this.idx].children); 
  375.                         this.$message({ 
  376.                             type: 'success'
  377.                             message: '编辑成功'
  378.                         }); 
  379.                         this.dialogFormVisible = false
  380.                     } else { 
  381.                         this.findSd(this.tableData, 0, this.childKey); 
  382.                         this.useChildLable( 
  383.                             this.findLable(this.tableData, 0, this.childKey) 
  384.                                 .children 
  385.                         ); 
  386.                         this.$message({ 
  387.                             type: 'success'
  388.                             message: '编辑成功'
  389.                         }); 
  390.                         this.dialogFormVisible = false
  391.                     } 
  392.                 } else { 
  393.                     return false
  394.                 } 
  395.             }); 
  396.         }, 
  397.         // 删除父级节点 
  398.         deleteParent(item) { 
  399.             this.tableData.forEach((it, ix, arrs) => { 
  400.                 if (it == item) { 
  401.                     return arrs.splice(ix, 1); 
  402.                 } 
  403.             }); 
  404.         }, 
  405.         // 删除 
  406.         deleteClick(item) { 
  407.             this.$confirm(`此操作将删除该标签, 是否继续?`, '提示', { 
  408.                 confirmButtonText: '确定'
  409.                 cancelButtonText: '取消'
  410.                 type: 'warning'
  411.             }) 
  412.                 .then(() => { 
  413.                     if (item.children.length != 0) { 
  414.                         this.$message.warning({ 
  415.                             message: '请删除子节点'
  416.                             duration: 1000, 
  417.                         }); 
  418.                     } else { 
  419.                         ++this.isResouceShow; 
  420.                         if (item.value.length == 1) { 
  421.                             this.deleteParent(item); 
  422.                             this.$message({ 
  423.                                 type: 'success'
  424.                                 message: '删除成功'
  425.                             }); 
  426.                         } else { 
  427.                             this.findDel(this.tableData, 0, item); 
  428.                             this.$message({ 
  429.                                 type: 'success'
  430.                                 message: '删除成功'
  431.                             }); 
  432.                         } 
  433.                     } 
  434.                 }) 
  435.                 .catch((err) => { 
  436.                     console.log(err); 
  437.                     this.$message({ 
  438.                         type: 'info'
  439.                         message: '已取消删除'
  440.                     }); 
  441.                 }); 
  442.         }, 
  443.         // 是否显示次位置 
  444.         locationChange(v) { 
  445.             if (v == 2) { 
  446.                 this.sonStatus = true
  447.             } else { 
  448.                 this.sonStatus = false
  449.             } 
  450.         }, 
  451.         // 获取次位置 
  452.         getCasVal(v) { 
  453.             this.casArr = v; 
  454.         }, 
  455.     }, 
  456. }; 
  457. </script> 

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

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

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

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

  1. var func = function(i){ 
  2.     if(i === 1){ 
  3.         return 1; 
  4.     }else
  5.         return i*func(i-1); 
  6.     } 
  7.  
  8. func(5); 

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

  1. // 递 
  2. 5*func(4); 
  3. 5*4*func(3); 
  4. 5*4*3*func(2); 
  5. 5*4*3*2*func(1); 
  6. // 归 
  7. 5*4*3*2*1; 
  8. 5*4*3*2; 
  9. 5*4*6; 
  10. 5*24; 
  11. 120 

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

 

责任编辑:姜华 来源: 前端历劫之路
相关推荐

2020-12-02 12:29:24

Vue无限级联树形

2018-01-31 15:45:07

前端Vue.js组件

2022-02-10 10:48:23

JavaScriptVue.js数据

2023-01-08 21:05:45

数据预警模型

2023-07-14 12:02:29

2022-04-26 05:55:06

Vue.js异步组件

2022-09-20 11:00:14

Vue3滚动组件

2017-07-11 18:00:21

vue.js数据组件

2020-10-27 08:07:17

Vue.js

2022-04-05 16:44:59

系统Vue.js响应式

2022-04-25 07:36:21

组件数据函数

2021-02-20 07:02:24

Vue.js组件开发技术

2023-10-12 12:43:16

组件Vue

2020-09-16 06:12:30

Vue.js 3.0Suspense组件前端

2022-04-09 17:53:56

Vue.js分支切换嵌套的effect

2019-10-15 09:05:07

域插槽组件前端

2020-05-12 14:20:47

GitHub 系统微软

2020-06-01 14:02:25

Vue.js框架模板

2020-06-02 14:00:53

Vue.js组件Web开发

2020-04-07 09:43:17

vue.js进度组件开发
点赞
收藏

51CTO技术栈公众号