使用vue-cli搭建项目后,发现一个特别好用的前端框架,由于本身要作单页面应用,因此通常前端框架不太好用(多是 本身用的不太熟悉吧),vuetify,能够了解一下。它里面有一个treeview组件,作的几乎已经很完美了,可是到如今为 止,有一点不足就是在它的 选中这一块,没有默认值,初始化的时候,都是没有选中状态,因此有些时候,就不能彻底 符合项目要求。折腾了半天,仍是决定在它的基础上去重写树形结构的 选中逻辑
<template> <v-card> <v-card-title class="indigo white--text headline"> User Directory </v-card-title> <v-row class="pa-4" justify="space-between" > <v-col cols="10"> <!-- :load-children="fetchUsers" :open.sync="open" :active.sync="active" --> <v-treeview :items="items" activatable item-key="id" open-on-click > <!-- <template v-slot:prepend="{ item, active }"> <v-icon v-if="!item.children">mdi-account</v-icon> </template> --> <template v-slot:prepend="{ item, open }"> <!-- false是全选 --> <v-icon v-if="item.selected == false" @click.stop="selectFn(item)"> { { 'mdi-shield-outline' }} </v-icon> <!-- 2是半选 --> <v-icon v-else-if="item.selected == '2'"> { { 'mdi-shield-half-full' }} </v-icon> <!-- true是不选 --> <v-icon v-else @click.stop="selectFn(item)"> { { 'mdi-shield-check' }} </v-icon> <!-- <v-icon v-if="!item.file"> { { open ? 'mdi-folder-open' : 'mdi-folder' }} </v-icon> <v-icon v-else> { { files[item.file] }} </v-icon> --> </template> </v-treeview> </v-col> <!-- 加一个高度为0,宽度为100%的有border:1px solid rgba(0, 0, 0, 0.12)的div vertical改变纵向 --> <v-divider vertical></v-divider> </v-row> </v-card> </template> <script> //递归循环遍历 //ob为一个对象 返回的也是一个对象或数组 //当bools为1 返回父节点 和 子节点与自身 二者组合的 数组 (默认) //当bools为2 返回父节点 对象类型 //当bools为3 返回子节点与自身 对象类型 function getLeafLoop(ob,id,bools=1){ var resultOb = [];//先定义个返回值 为空数组 for(let j=0;j<ob['children'].length;j++){ if(ob['children'][j].id != id){ resultOb = getLeafLoop(ob['children'][j],id) //必需要有中断的解决办法,否则整个循环会继续下去,结果会变成underfind if(resultOb){ break } }else{ if(bools==1){ //返回父节点 子节点和自身 resultOb = [ob, ob['children'][j]]; }else if(bools==2){ //返回父节点 resultOb = ob; }else{ //返回子节点和自身 resultOb = ob['children'][j]; } break; } }; //由于返回结果根据bools有三种状况,因此要排除掉 空数组的返回 //当没有合适的匹配时,返回的是 underfind,即没有返回 if((!Array.isArray(resultOb)) || resultOb.length > 0){ return resultOb; } } //itemsArr为一个数组, 默认状况下,传入的是全部渲染的数据 items //返回一对象或者数组, //找到与id值一致的节点 //当bools为1 返回父节点 和 子节点与自身 二者组合的 数组 (默认) //当bools为2 返回父节点 对象类型 //当bools为3 返回子节点与自身 对象类型 function getLeaf(itemsArr,id,bools=1){ var returnOb = []; for(let i=0;i<itemsArr.length;i++){ if(itemsArr[i].id != id){ returnOb = getLeafLoop(itemsArr[i],id,bools) if(returnOb){ //若是返回的不是underfind,则中止循环 break } }else{ //id为根节点时,返回自身 if(bools==1){ //特殊状况,若是点击的节点,是根节点,则 父节点和 子节点与自身 都是本身 returnOb = [itemsArr[i], itemsArr[i]]; }else{ returnOb = itemsArr[i]; } break;//结束循环 } }; //由于返回结果根据bools有三种状况,因此要排除掉 空数组的返回 //当没有合适的匹配时,返回的是 underfind,即没有返回 if((!Array.isArray(returnOb)) || returnOb.length > 0){ return returnOb; } } //修改节点下的全部selected 的状态; //属性的selected 为 判断标准 //arr为一个数组 function changeSelect(arr,bools){ for(let i=0; i<arr.length; i++){ arr[i].selected = bools; if(arr[i].children.length > 0){ changeSelect(arr[i].children,bools) } } } //返回父节点下的子节点的状态 //返回一个二维数组,第一项为选中的项, 第二项为没有被选中的项 //arr为一个数组 function leafSelectStatus(arr){ var seleTrue = []; var seleFalse = []; for(let i=0; i<arr.length; i++){ if(arr[i].selected){ //将 true 和 2 的状态 归为一组(若是有须要能够分开) seleTrue.push(arr[i].id)//这里设置返回的选中的节点 数据内容 }else{ seleFalse.push(arr[i].id)//这里设置返回的未选中的节点 数据内容 }; if(arr[i].children.length > 0){ //递归,继续查找子节点下的子节点 var resultOb = leafSelectStatus(arr[i].children); //拼接上 子节点下的子节点 返回的数据 seleTrue = seleTrue.concat(resultOb[0]); seleFalse = seleFalse.concat(resultOb[1]); } } return [seleTrue, seleFalse]//返回一个数组 } //修改爷爷辈的节点selected function changeGraFatherStatus(itemArr, id){ var resultob = getLeaf(itemArr,id); var sonStatus = leafSelectStatus(resultob[0].children); if(sonStatus[0].length>0 && sonStatus[1].length>0){ //此时parent变为 半选状态 resultob[0].selected = 2; console.log("有未选中和选中的"); //这时候须要继续向根节点 作进一步修改 if(resultob[0].id != resultob[1].id){ // this.selectFn(parent, true); changeGraFatherStatus(itemArr, resultob[0].id); } }else if(sonStatus[0].length == 0 && sonStatus[1].length > 0){ //此时parent应该变成未选中 resultob[0].selected = false; console.log("都是未选中的") //这时候须要继续向根节点 作进一步修改 if(resultob[0].id != resultob[1].id){ // this.selectFn(parent, true); changeGraFatherStatus(itemArr, resultob[0].id); } }else if(sonStatus[0].length > 0 && sonStatus[1].length == 0){ //此时parent应该变成选中 resultob[0].selected = true; console.log("都是选中的"); //这时候须要继续向根节点 作进一步修改 //若是该节点就是根节点,不在进行循环 if(resultob[0].id != resultob[1].id){ changeGraFatherStatus(itemArr, resultob[0].id); } }else{ //没有子节点 而且自身仍是根节点的时候会出现 console.log("我就是孤独的根号3"); } } export default { data:()=>({ items:[//模拟数据,这一部分数据,通常是url请求过来的 { id:'1', name: '第一层01', selected:false, children:[ { id:'11', selected:false, name: '第二层0101', children:[ { id:'11', selected:false, name: '第二层010101', children:[], } ], } ], }, { id:'2', name: '第一层02', selected:false, children:[ { id:'10', selected:false, name: '第二层0201', children:[], }, ], }, { id:'3', selected:false, name: '第一层03', children: [ { id:'4', selected:false, name: '第二层0301', children: [ { id:'5', selected:false, name: '第三层030101', children:[], }, { id:'9', selected:false, name: '第三层030102', children:[], }, ], }, { id:'6', selected:false, name: '第二层0302', children:[], }, { id:'7', selected:false, name: '第二层0303', children:[], }, ], }, { id:'8', selected:false, name: '第一层04', children:[], }, ], selectSet:new Set(),//定义一个存储 选中状态的 节点容器 }), watch:{ items:{ deep:true, handler:function(value){ for(var i=0; i< value.length;i++){ if(value[i].selected){ //将半选和全选的节点 添加数据到set中 this.selectSet.add(value[i].id) }else{ this.selectSet.delete(value[i].id)//将数据从set中删除 } } } } }, methods:{ //点击节点的时候 会触发该函数 selectFn(item){ var leafArr=[]; var parent=""; var son = ""; //改变自身的selected, 该事件是由点击事件形成,而不是循环事件 item.selected ? item.selected=false : item.selected=true; //获取父节点(包含父节点的对象) 和 子节点与自身(自身的对象) 的数组 leafArr = getLeaf(this.items,item.id); parent= leafArr[0]; //其实此处的 son(子节点与自身)和 selectFn(item)的item是一个对象 son = leafArr[1]; //修改子节点的全部select 将子节点中的 selected 都变为item.selected状态 changeSelect(son.children,item.selected); //获取父节点下的全部children中 选中、半选中状态 和 未选中 的节点数组 var sonStatus = leafSelectStatus(parent.children); //判断sonStatus中 选中、半选中状态 和 未选中 的节点的数量 if(sonStatus[0].length>0 && sonStatus[1].length>0){ //此时parent变为 半选状态 parent.selected = 2; console.log("有未选中和选中的"); //这时候须要继续向根节点 作进一步修改 if(parent.id != son.id){ // this.selectFn(parent, true); changeGraFatherStatus(this.items, parent.id); } }else if(sonStatus[0].length == 0 && sonStatus[1].length > 0){ //此时parent应该变成未选中 parent.selected = false; console.log("都是未选中的") //这时候须要继续向根节点 作进一步修改 if(parent.id != son.id){ // this.selectFn(parent, true); changeGraFatherStatus(this.items, parent.id); } }else if(sonStatus[0].length > 0 && sonStatus[1].length == 0){ //此时parent应该变成选中 parent.selected = true; console.log("都是选中的"); //这时候须要继续向根节点 作进一步修改 if(parent.id != son.id){ changeGraFatherStatus(this.items, parent.id); } }else{ //没有子节点 而且自身仍是根节点的时候会出现 console.log("我就是孤独的根号3"); } } } } </script> <style> </style>
最后的样子为点击 节点中的 选择框 会发生级联变换 并且 初始化页面的时候, 能够经过修改每一级的 selected的值,能够设置初始效果
样式是 vuetify 的 v-treeview的样式 ,本身只是修改了 选中逻辑, 没有用它的selectable,并且它不如ztree的
另外一个缘由是,经过url获取的数据,每每都是一条条的 数据, 没有进行 树形结构的分级, 还须要本身去 写逻辑(这部分代码尚未整理,下次补上),须要默认选中的时候,初始化数据,修改selected属性便可。以上没有进行异步处理,因此还须要改进,第一次写这些东西, 欢迎指正!javascript