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

资源
-
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> 184export 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} 398</style> 399 400
结语
欢迎关注我的公众号前端历劫之路
回复关键词电子书,即可获取12本前端热门电子书。
回复关键词红宝书第4版,即可获取最新《JavaScript高级程序设计》(第四版)电子书。
关注公众号后,点击下方菜单即可加我微信,我拉拢了很多IT大佬,创建了一个技术交流、文章分享群,期待你的加入。
作者:Vam的金豆之路
微信公众号:前端历劫之路
- END -

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

