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

开发 前端
实现一个无限级联树形表格,什么叫做无限级联树形表格呢?就是下图所展示的内容,有一个祖元素,然后下面可能有很多子孙元素,你可以实现添加、编辑、删除这样几个功能。

[[355275]]

本文转载自微信公众号「 前端历劫之路」,作者maomin9761。转载本文请联系 前端历劫之路公众号。

前言

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

资源

  • JavaScript框架:vue.js
  • UI框架:Element UI

源码

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

  1. <template> 
  2.     <div class="container"
  3.         <div class="btn-r"
  4.             <el-button 
  5.                 type="primary" 
  6.                 size="small" 
  7.                 @click="addView = true" 
  8.                 icon="el-icon-circle-plus-outline" 
  9.                 class="add" 
  10.                 >添加</el-button 
  11.             > 
  12.         </div> 
  13.         <el-table 
  14.             :data="tableData" 
  15.             style="width: 100%; margin-bottom: 20px" 
  16.             row-key="value" 
  17.             border 
  18.             default-expand-all 
  19.             size="medium" 
  20.             :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" 
  21.         > 
  22.             <el-table-column prop="label" label="名称" sortable> 
  23.             </el-table-column
  24.             <el-table-column label="操作" align="center" width="180"
  25.                 <template slot-scope="scope"
  26.                     <el-button 
  27.                         type="text" 
  28.                         size="small" 
  29.                         @click="handleClick(scope.row, scope.$index)" 
  30.                         >编辑</el-button 
  31.                     > 
  32.                     <el-button 
  33.                         type="text" 
  34.                         size="small" 
  35.                         @click="deleteClick(scope.row, scope.$index)" 
  36.                         >删除</el-button 
  37.                     > 
  38.                 </template> 
  39.             </el-table-column
  40.         </el-table
  41.  
  42.         <!-- 添加窗口 --> 
  43.         <el-dialog 
  44.             title="添加" 
  45.             :visible.sync="addView" 
  46.             :close-on-click-modal="false" 
  47.             width="30%" 
  48.             @close="closeView" 
  49.         > 
  50.             <el-form :model="form" ref="form" :rules="rules"
  51.                 <el-form-item 
  52.                     label="位置" 
  53.                     :label-width="formLabelWidth" 
  54.                     prop="location" 
  55.                 > 
  56.                     <el-select 
  57.                         v-model="form.location" 
  58.                         placeholder="请选择位置" 
  59.                         @change="locationChange" 
  60.                         size="small" 
  61.                     > 
  62.                         <el-option 
  63.                             v-for="item in locationData" 
  64.                             :key="item.id" 
  65.                             :label="item.name" 
  66.                             :value="item.id" 
  67.                         /> 
  68.                     </el-select
  69.                 </el-form-item> 
  70.                 <el-form-item 
  71.                     v-if="sonStatus" 
  72.                     label="子位置" 
  73.                     :label-width="formLabelWidth" 
  74.                     prop="childArr" 
  75.                 > 
  76.                     <el-cascader 
  77.                         size="small" 
  78.                         :key="isResouceShow" 
  79.                         v-model="form.childArr" 
  80.                         placeholder="请选择子位置" 
  81.                         :label="'name'" 
  82.                         :value="'id'" 
  83.                         :options="tableData" 
  84.                         :props="{ checkStrictly: true }" 
  85.                         clearable 
  86.                         @change="getCasVal" 
  87.                     ></el-cascader> 
  88.                 </el-form-item> 
  89.                 <el-form-item 
  90.                     label="名称" 
  91.                     :label-width="formLabelWidth" 
  92.                     prop="label" 
  93.                 > 
  94.                     <el-input 
  95.                         v-model="form.label" 
  96.                         size="small" 
  97.                         autocomplete="off" 
  98.                         placeholder="请输入名称" 
  99.                     ></el-input> 
  100.                 </el-form-item> 
  101.             </el-form> 
  102.             <span slot="footer" class="dialog-footer"
  103.                 <el-button @click="addView = false" size="small" 
  104.                     >取 消</el-button 
  105.                 > 
  106.                 <el-button type="primary" @click="okAdd('form')" size="small" 
  107.                     >确 定</el-button 
  108.                 > 
  109.             </span> 
  110.         </el-dialog> 
  111.  
  112.         <!-- 编辑窗口 --> 
  113.         <el-dialog 
  114.             title="编辑" 
  115.             :visible.sync="editView" 
  116.             :close-on-click-modal="false" 
  117.             width="30%" 
  118.         > 
  119.             <el-form :model="data" ref="data" :rules="rules"
  120.                 <el-form-item 
  121.                     label="位置" 
  122.                     :label-width="formLabelWidth" 
  123.                     prop="location" 
  124.                 > 
  125.                     <el-select 
  126.                         v-model="data.location" 
  127.                         placeholder="请选择位置" 
  128.                         size="small" 
  129.                         @change="locationChange" 
  130.                     > 
  131.                         <el-option 
  132.                             v-for="item in locationData" 
  133.                             :key="item.id" 
  134.                             :label="item.name" 
  135.                             :value="item.id" 
  136.                         /> 
  137.                     </el-select
  138.                 </el-form-item> 
  139.                 <el-form-item 
  140.                     v-if="sonStatus" 
  141.                     label="子位置" 
  142.                     :label-width="formLabelWidth" 
  143.                     prop="childArr" 
  144.                 > 
  145.                     <el-cascader 
  146.                         :key="isResouceShow" 
  147.                         v-model="data.childArr" 
  148.                         placeholder="请选择子位置" 
  149.                         size="small" 
  150.                         :label="'name'" 
  151.                         :value="'id'" 
  152.                         :options="tableData" 
  153.                         :props="{ checkStrictly: true }" 
  154.                         clearable 
  155.                         @change="getCasVal" 
  156.                     ></el-cascader> 
  157.                 </el-form-item> 
  158.                 <el-form-item 
  159.                     label="名称" 
  160.                     :label-width="formLabelWidth" 
  161.                     prop="label" 
  162.                 > 
  163.                     <el-input 
  164.                         v-model="data.label" 
  165.                         autocomplete="off" 
  166.                         placeholder="请输入名称" 
  167.                         size="small" 
  168.                     ></el-input> 
  169.                 </el-form-item> 
  170.             </el-form> 
  171.             <span slot="footer" class="dialog-footer"
  172.                 <el-button @click="editView = false" size="small" 
  173.                     >取 消</el-button 
  174.                 > 
  175.                 <el-button type="primary" @click="okEdit('data')" size="small" 
  176.                     >确 定</el-button 
  177.                 > 
  178.             </span> 
  179.         </el-dialog> 
  180.     </div> 
  181. </template> 
  182.  
  183. <script> 
  184. export default { 
  185.     name'Tag'
  186.     data() { 
  187.         return { 
  188.             location: ''
  189.             isResouceShow: 1, 
  190.             addView: false
  191.             sonStatus: false
  192.             editView: false
  193.             casArr: [], 
  194.             childArr: [], 
  195.             form: {}, 
  196.             data: {}, 
  197.             idx: ''
  198.             childkey: [], 
  199.             formLabelWidth: '80px'
  200.             rules: { 
  201.                 label: [ 
  202.                     { required: true, message: '请输入名称'trigger'blur' } 
  203.                 ] 
  204.             }, 
  205.             locationData: [ 
  206.                 { 
  207.                     id: 1, 
  208.                     name'顶' 
  209.                 }, 
  210.                 { 
  211.                     id: 2, 
  212.                     name'子' 
  213.                 } 
  214.             ], 
  215.             tableData: [] 
  216.         }; 
  217.     }, 
  218.     methods: { 
  219.         // 监听关闭窗口 
  220.         closeView() { 
  221.             this.$refs['form'].resetFields(); // 关闭窗口,清空填写的内容 
  222.         }, 
  223.         // 打开编辑 
  224.         handleClick(item, index) { 
  225.             item.value.length != 1 
  226.                 ? (this.sonStatus = true
  227.                 : (this.sonStatus = false); 
  228.             this.editView = true
  229.             const obj = Object.assign({}, item); 
  230.             this.childkey = item.childkey; 
  231.             this.casArr = item.childArr; 
  232.             this.idx = index
  233.             this.data = obj; 
  234.         }, 
  235.         // 递归表格数据(编辑) 
  236.         findSd(arr, i, casArr) { 
  237.             if (i == casArr.length - 1) { 
  238.                 let index = casArr[i].substr(casArr[i].length - 1, 1); 
  239.                 return arr.splice(index, 1, this.data); 
  240.             } else { 
  241.                 return this.findSd( 
  242.                     arr[casArr[i].substr(casArr[i].length - 1, 1)].children, 
  243.                     (i += 1), 
  244.                     casArr 
  245.                 ); 
  246.             } 
  247.         }, 
  248.         // 确定编辑 
  249.         okEdit(data) { 
  250.             this.$refs[data].validate(valid => { 
  251.                 if (valid) { 
  252.                     if (this.data.value.length == 1) { 
  253.                         this.tableData.splice(this.idx, 1, this.data); 
  254.                         this.$message({ 
  255.                             type: 'success'
  256.                             message: '编辑成功' 
  257.                         }); 
  258.                         this.editView = false
  259.                     } else { 
  260.                         this.findSd(this.tableData, 0, this.childkey); 
  261.                         this.$message({ 
  262.                             type: 'success'
  263.                             message: '编辑成功' 
  264.                         }); 
  265.                         this.editView = false
  266.                     } 
  267.                 } else { 
  268.                     return false
  269.                 } 
  270.             }); 
  271.         }, 
  272.         // 递归表格数据(删除) 
  273.         findDel(arr, i, item) { 
  274.             let casArr = item.childkey; 
  275.             if (i == casArr.length - 1) { 
  276.                 let index = casArr[i].substr(casArr[i].length - 1, 1); 
  277.                 return arr.splice(index, 1); 
  278.             } else { 
  279.                 return this.findDel( 
  280.                     arr[casArr[i].substr(casArr[i].length - 1, 1)].children, 
  281.                     (i += 1), 
  282.                     item 
  283.                 ); 
  284.             } 
  285.         }, 
  286.         // 删除 
  287.         deleteClick(item) { 
  288.             this.$confirm(`此操作将删除该项, 是否继续?`, '提示', { 
  289.                 confirmButtonText: '确定'
  290.                 cancelButtonText: '取消'
  291.                 type: 'warning' 
  292.             }) 
  293.                 .then(() => { 
  294.                     if (item.children.length != 0) { 
  295.                         this.$message.warning({ 
  296.                             message: '请删除子节点'
  297.                             duration: 1000 
  298.                         }); 
  299.                     } else { 
  300.                         this.casArr = item.childArr; 
  301.                         ++this.isResouceShow; // 给级联控件绑定一个key,防止报错。 
  302.                         if (item.value.length == 1) { // 删除的是顶节点 
  303.                             console.log(1); 
  304.                             this.tableData.splice(item.value, 1); 
  305.                             this.$message({ 
  306.                                 type: 'success'
  307.                                 message: '删除成功' 
  308.                             }); 
  309.                         } else { // 删除的是子节点 
  310.                             console.log(2); 
  311.                             this.findDel(this.tableData, 0, item); 
  312.                             this.$message({ 
  313.                                 type: 'success'
  314.                                 message: '删除成功' 
  315.                             }); 
  316.                         } 
  317.                     } 
  318.                 }) 
  319.                 .catch(err => { 
  320.                     console.log(err); 
  321.                     this.$message({ 
  322.                         type: 'info'
  323.                         message: '已取消删除' 
  324.                     }); 
  325.                 }); 
  326.         }, 
  327.         // 是否显示次位置 
  328.         locationChange(v) { 
  329.             if (v == 2) { 
  330.                 this.sonStatus = true
  331.             } else { 
  332.                 this.sonStatus = false
  333.             } 
  334.         }, 
  335.         // 获取次位置 
  336.         getCasVal(v) { 
  337.             this.casArr = v; 
  338.             this.form.childArr = v; 
  339.         }, 
  340.         // 递归表格数据(添加) 
  341.         find(arr, i) { 
  342.             if (i == this.casArr.length - 1) { 
  343.                 return arr[this.casArr[i].substr(this.casArr[i].length - 1, 1)] 
  344.                     .children; 
  345.             } else { 
  346.                 return this.find( 
  347.                     arr[this.casArr[i].substr(this.casArr[i].length - 1, 1)] 
  348.                         .children, 
  349.                     (i += 1) 
  350.                 ); 
  351.             } 
  352.         }, 
  353.         // 确定添加 
  354.         okAdd(form) { 
  355.             this.$refs[form].validate(valid => { 
  356.                 if (valid) { 
  357.                     if (this.sonStatus == false) { 
  358.                         this.form.value = String(this.tableData.length); 
  359.                         const obj = Object.assign({}, this.form); 
  360.                         obj.children = []; 
  361.                         obj.childArr = []; 
  362.                         this.tableData.push(obj); 
  363.                         this.$message({ 
  364.                             type: 'success'
  365.                             message: '添加成功' 
  366.                         }); 
  367.                         this.addView = false
  368.                     } else { 
  369.                         let arr = this.find(this.tableData, 0); 
  370.                         this.childArr = [...this.casArr, String(arr.length)]; 
  371.                         this.form.value = 
  372.                             String(this.casArr[this.casArr.length - 1]) + 
  373.                             String(arr.length); 
  374.                         delete this.form.children; 
  375.                         const obj = Object.assign({}, this.form); 
  376.                         obj.children = []; 
  377.                         obj.childkey = [...this.casArr, String(arr.length)]; 
  378.                         arr.push(obj); 
  379.                         this.$message({ 
  380.                             type: 'success'
  381.                             message: '添加成功' 
  382.                         }); 
  383.                         this.addView = false
  384.                     } 
  385.                 } else { 
  386.                     return false
  387.                 } 
  388.             }); 
  389.         } 
  390.     } 
  391. }; 
  392. </script> 
  393.  
  394. <style lang="scss" scoped> 
  395. ::v-deep .el-form-item__content { 
  396.     width: 203px; 
  397. </style>

 

责任编辑:武晓燕 来源: 前端历劫之路
相关推荐

2022-06-28 15:29:56

Python编程语言计时器

2020-12-15 08:58:07

Vue编辑器vue-cli

2022-09-22 12:38:46

antd form组件代码

2021-06-22 10:43:03

Webpack loader plugin

2023-04-26 12:46:43

DockerSpringKubernetes

2019-08-26 09:25:23

RedisJavaLinux

2021-11-10 11:40:42

数据加解密算法

2011-01-06 10:39:25

.NET程序打包

2009-11-09 14:57:37

WCF上传文件

2016-11-01 09:46:04

2022-08-26 08:01:38

DashWebJavaScrip

2021-07-14 09:00:00

JavaFX开发应用

2011-01-10 14:41:26

2011-05-03 15:59:00

黑盒打印机

2022-03-29 18:26:31

优化表格数据

2018-11-22 09:17:21

消息推送系统

2020-05-09 09:59:52

Python数据土星

2023-03-22 09:00:38

2019-10-29 15:46:07

区块链区块链技术

2011-04-21 10:32:44

MySQL双机同步
点赞
收藏

51CTO技术栈公众号