先来看看实现效果:
起因:
该项目是一个脱敏产品,简单地说就是把数据库或者其他大量数据中的敏感数据筛选或处理。而此处是选择文件部分,它由elementUI的卡片组件和zTree的树形图插件组合而成。
至于为什么不使用elementUI的树形图组件呢?诚然elementUI的树形图比zTree好看多了,不过elemntUI的树形图功能太少了,获取想要的数据很困难,NNwanzi最开始是使用的elementUI来完成这一功能,但浪费了不少时间最后决定用的zTree。
环境搭建:
除了elementUI,vue的基本环境外,还需要一个zTree的环境,这里主要讲zTree的环境
1.官方下载地址:zTree_v3: jQuery 树插件 zTree v3 系列 (gitee.com)
官方API:API 文档 [zTree -- jQuery 树插件] (treejs.cn)
2.导入项目
这里没有用到脚手架,直接git下载资源,使用代码导入到index
<script src="js/plugin/zTree_3.5.48/jquery.ztree.all.js" type="text/javascript"></script>
<script src="js/plugin/zTree_3.5.48/jquery.ztree.exhide.js" type="text/javascript"></script>
自定义cardTree组件源码:
(要想实现全部穿梭树效果还需调用cardTree和按钮一起组合完成)
<!--cardTree组件,页面效果为卡片中显示了zTree的树形结构,主要用于导出文件页面,敏感元新增第3步的文件选择页面。-->
<!--通过外部按钮调用此组件内的各种方法操控zTree树,具体请看"由父页面调用"-->
<style scoped>
.tree {
height: 400px;
}
.tree .el-scrollbar__wrap {
overflow: scroll;
width: 110%;
height: 106%;
}
</style>
<template>
<div>
<el-card>
<div slot="header">
<span>列表共{{ total }}个子节点</span>
<el-popover
placement="bottom"
width="400"
trigger="click"
@hide="getMainData"
v-if="url !== ''">
<el-input type="textarea" v-model="tableRegex" maxlength="100"></el-input>
<span style="color: red">
弹出框关闭时自动筛选<br>
通配符为*<br>
输入条件$TDMP.table1,精准查询出TDMP下的table1表(table1可以加*号,适用下面两条)<br>
输入条件CUST*,会查出CUST_001,CUST_002等结果<br>
输入条件aim*CUST*,会查出aimCUST_001,aimpulsCUST等结果<br>
带通配符为忽略大小写模糊查询,不带通配符为匹配大小写精准查询<br>
</span>
<el-button style="float: right; padding: 3px 0" type="text" slot="reference">高级检索</el-button>
</el-popover>
</div>
<el-input
placeholder="输入关键字进行过滤"
v-model="filtration">
</el-input>
<el-scrollbar class="tree">
<!-- ztree渲染位置-->
<ul :id="randomId" class="ztree"></ul>
</el-scrollbar>
</el-card>
</div>
</template>
<script>
module.exports = {
name: "export",
components: {},
data() {
return {
//初始化时的随机值,用于ZTree的id(因为敏感元新增页面第二步一个页面用了两次cardTree,导致id重复)
randomId: "",
//tree的配置
defaultProps: {
children: 'children',
label: 'label',
isLeaf: (data, node) => {
if (data.children === undefined) {
return true;
}
}
},
//打开的路径id数组(因为tree是风琴式的,所以路径只有一条)
unfold: [],
//过滤树的关键字
filtration: "",
//页面主数据,卡片中的树
//高级检索输入的条件
tableRegex: "",
//已选项列表个数
total: 0,
//保存tree中获取到的json对象的属性名
treeJsonKey: [],
//zTree的对象
treeObj: {}
}
},
watch: {
filtration(val) {
//显示出所有隐藏的文件
var nodes = this.treeObj.getNodesByParam("isHidden", true);
this.treeObj.showNodes(nodes);
//过滤逻辑
let hiddenNodes = this.treeObj.getNodesByFilter(this.filter); // 查找节点集合
this.treeObj.hideNodes(hiddenNodes);
}
},
methods: {
//zTree自定义筛选,返回模糊查询 label 中不包含this.filtration的节点
filter(node) {
return (node.level == 2 && node.label.indexOf(this.filtration) === -1);
},
/**
* 由父页面调用
**/
//处理提交,参数:是否提存返回值
submit(refine) {
let nodes = this.treeObj.getSelectedNodes();
//这里会使用循环获取nodes中所有项的父路径,其实只用获取第一个的就行了,因为选中的时候规定只能选中同一路径下的节点
//若要优化请兼顾其他使用位置,他们的nodes不一定在同一路径下
let selected = this.getPathNodes(nodes);
if (refine){
selected = this.refineSubmit(selected);
}
//返回选中项
return selected;
},
//由父页面调用,获取增量。只获取通过addTree()增加的节点的tree数组,参数:是否提存返回值
getIncrement(refine){
//原理:在addTree()中会将所有叶子节点都改为选中状态,但选中状态是不显示到页面上的。这里获取所有选中状态即为增量
let checkedNodes = this.treeObj.getCheckedNodes();
let checked = this.getPathNodes(checkedNodes);
if (refine){
checked = this.refineSubmit(checked);
}
return checked
},
//由父页面调用,删除参数项
removeTree(nodes){
nodes.forEach((node)=>{
//第一个label相同的节点
let nodesByParam = this.treeObj.getNodesByParam("label", node.label)[0];
if(!node.isParent){
//如果不是一个父节点,则删除
this.treeObj.removeNode(nodesByParam);
}else {
//如果不是叶子节点则递归子节点
//需要添加的不存在的节点
let childs = [];
// zTree中若只有一个子对象,将会展现为对象,而不是数组,此时会导致递归失败
if (Array.isArray(node.children)) {
childs = node.children
} else {
childs.push(node.children);
}
//删除子节点中的叶子节点
this.removeTree(childs);
// 删除子节点中的叶子节点后检查其本身是否变为了叶子节点
this.removeParentNode(nodesByParam);
}
});
},
//由父页面调用,删除选中项
removeSelected() {
let selectedNodes = this.treeObj.getSelectedNodes();
let parentNode = {};
if (selectedNodes.length > 0) {
parentNode = selectedNodes[0].getParentNode();
}
for (let i=selectedNodes.length - 1; i >= 0; i--) {
this.treeObj.removeNode(selectedNodes[i]);
}
//判断如果删除后父节点为叶子节点则同样删除。
this.removeParentNode(parentNode);
//更新总数
let nodes = this.treeObj.getNodes();//获取 zTree 的全部节点数据
if (nodes !== undefined) {
this.total = this.getAllChildrenNodes(nodes);
} else {
this.total = 0;
}
},
//可由父页面调用,向treeJson添加值
addTree(tree) {
debugger
this.addChildren(null, tree)
//显示出所有隐藏的文件
let hiddenNodes = this.treeObj.getNodesByParam("isHidden", true);
this.treeObj.showNodes(hiddenNodes);
this.filtration = "";
//更新总数
let nodes = this.treeObj.getNodes();//获取 zTree 的全部节点数据
this.total = this.getAllChildrenNodes(nodes);
},
//由父页面调用,获取所有树,而不是选中树,参数:是否提存返回值
getTree(refine) {
let nodes = this.treeObj.getNodes();
if (refine){
nodes = this.refineSubmit(nodes);
}
return nodes
},
//由父页面调用,获取所有的叶子节点
getLeafNodes(){
let nodes = this.treeObj.getNodesByParam("isParent", false, null);
return nodes
},
/**
* 数据处理
*/
//给一个节点对象,获取所有叶子节点的个数
getAllChildrenNodes(treeNode) {//给定一个节点对象
let result = 0;
for (let i = 0; i < treeNode.length; i++) {
if (treeNode[i].isParent) {//如果是父
let childrenNodes = treeNode[i].children;
result += this.getAllChildrenNodes(childrenNodes);
} else {
result++
}
}
this.total = result;
return result;
},
//对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
//获取页面主数据(树)
getMainData() {
this.params.tableRegex = this.tableRegex;
this.$httpUtils.get(this.url, this.params, true)
.then(res => {
debugger
//排空处理
let treeJson = [];
if (this.$stringUtils.isEmpty(res.treeJson)){
treeJson = res.treeJson;
}else {
treeJson = JSON.parse(res.treeJson);
for (const treeJsonKey in treeJson[0]) {
this.treeJsonKey.push(treeJsonKey)
}
}
this.total = res.totalCount;
this.renderTree(treeJson);
})
},
//若该节点不为父节点则删除它,若参数节点的父节点删除后参数节点的爷爷节点将不再是父节点,则同样删除。
removeParentNode(node) {
if (node === null) {
//该节点为空,说明已经删除到了根节点,递归出口
return
}
if (!node.isParent) {
let parentNode = node.getParentNode();
this.treeObj.removeNode(node);
//递归删除父节点
this.removeParentNode(parentNode);
}
},
//递归添加children
addChildren(parent, children) {
for (const treeIndex in children) {
let child = children[treeIndex];
let node = this.treeObj.getNodeByParam("id", child.id, parent);
if (node) {
if (!child.isParent) {
//如果找到了这个节点,并且这个节点是叶子节点
continue;
}
//需要添加的不存在的节点
let childs = [];
// zTree中若只有一个子对象,将会展现为对象,而不是数组,此时会导致递归失败
if (Array.isArray(child.children)) {
childs = child.children
} else {
childs.push(child.children);
}
// 如果有该节点,则将该节点作为父节点,其子节点作为children继续轮询添加
this.addChildren(node, childs)
} else {
if(this.incrementFlag){
//设置节点的所有叶子节点为选中状态(该值即为增量)
this.setLeafChecked(child);
}
//若不存在该节点,则向父节点添加该节点
this.treeObj.addNodes(parent, child, false);
}
}
},
//设置节点的所有叶子节点为选中状态
setLeafChecked(node){
if(!node.isParent){
//如果是叶子节点则设置选中
node.checked = true;
}else {
//如果不是叶子节点则递归子节点
//需要添加的不存在的节点
let childs = [];
// zTree中若只有一个子对象,将会展现为对象,而不是数组,此时会导致递归失败
if (Array.isArray(node.children)) {
childs = node.children
} else {
childs.push(node.children);
}
for (const childIndex in childs) {
this.setLeafChecked(childs[childIndex]);
}
}
},
//将nodes用树形结构分类,返回一个tree数组
getPathNodes(nodes){
let map = new Map();
let tree = [];
//向map中添加数据key:parentTId,value:[node]
nodes.forEach((node)=>{
let parentTId = node.parentTId;
if (parentTId === null) {
tree.push(node)
return
}
//向map中添加数据key:parentTId,value:[node]
if(map.has(parentTId)){
let value = map.get(parentTId);
value.push(node);
}else {
let nodes = [];
nodes.push(node);
map.set(parentTId,nodes);
}
})
//将map转化为带父路径的tree
for (let [key,value] of map.entries()) {
//获取路径arr
let pathNode = this.treeObj.getNodeByTId(key);
let oldPath = pathNode.getPath();
let pathArr = {};
$.extend(true,pathArr,oldPath);
//根据路径arr,再按路径添加父节点
let length = oldPath.length;
pathArr[length-1].children = value;
for (let i = length - 2; i >= 0; i--) {
pathArr[i].children = [pathArr[i + 1]]
}
tree.push(pathArr[0])
}
return tree
},
//提纯cardTree选中的数据,原数据中存在zTree添加的其他属性
refineSubmit(nodes){
let tree = [];
for (const nodesIndex in nodes) {
let node =nodes[nodesIndex];
let refineNode = {};
refineNode.id = node.id;
refineNode.label = node.label;
if(node.children !== undefined && node.children.length !== 0){
// zTree中若只有一个子对象,将会展现为对象,而不是数组,此时会导致递归失败
let childs = [];
if(Array.isArray(node.children)){
childs = node.children
}else {
childs.push(node.children);
}
refineNode.children = this.refineSubmit(childs);
}
tree.push(refineNode)
}
return tree;
},
/**
* ZTree
*
*/
/**
* 渲染数据
*/
renderTree(zNodes) {
// zTree 的参数配置,深入使用请参考 API 文档(setting 配置详解)
let setting = {
edit: {
enable: false,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: false,
isMove: true,
next: false
}
},
data: {
simpleData: {
enable: true
},
key: {
name: "label"
}
},
check:{
chkboxType: {"Y": "ps", "N": "ps"},
},
view: {
filter: true, //是否启动过滤
expandLevel: 0, //展开层级
showFilterChildren: true, //是否显示过滤数据孩子节点
showFilterParent: true, //是否显示过滤数据父节点
showLine: false
},
callback: {
beforeDrag: function (treeId, treeNodes) {
for (var i = 0, l = treeNodes.length; i < l; i++) {
if (treeNodes[i].drag === false) {
return false;
}
}
return true;
},
beforeDrop: function (treeId, treeNodes, targetNode, moveType) {
var tmpTreeNodes = treeNodes;
for (var i = 0, l = tmpTreeNodes.length; i < l; i++) {
var pnode = tmpTreeNodes[i].getParentNode();
if (null != pnode) {
var newPnode = {
'id': pnode.id,
'pId': pnode.pId,
'name': pnode.name,
'open': pnode.open
};
treeNodes.push(newPnode);
}
}
return true;
},
onClick: function (event, treeId, treeNode, clickFlag) { //支持shift键
var preClickedNode = window.preClickedNode;
window.preClickedNode = treeNode;
event = window.event || event;//兼容IE
if (event.ctrlKey && preClickedNode != null) { // ctrl键禁止跨层选择
var zTreeObject = $.fn.zTree.getZTreeObj(treeId);
if (treeNode.level == '1') {
this.cancelParentNode(zTreeObject, treeNode); //递归取消根节点
this.cancelChildrenNode(zTreeObject, treeNode); //递归取消子节点
} else if (treeNode.level == '0') {
this.cancelChildrenNode(zTreeObject, treeNode); //递归取消子节点
} else {
this.cancelParentNode(zTreeObject, treeNode); //递归取消父节点
}
}
if (!event.shiftKey || !preClickedNode) return;// shift键
if (preClickedNode.getParentNode() != treeNode.getParentNode()) {
preClickedNode = null;
return;
}
var obj = jQuery.fn.zTree.getZTreeObj(treeId);
obj.selectNode(preClickedNode, true, true);
var firstNode = obj.getNodeIndex(preClickedNode);
var lastNode = obj.getNodeIndex(treeNode);
var count = lastNode - firstNode;
var nodeNew = preClickedNode;
if (count > 0) {
for (var i = 1; i < count; i++) {
nodeNew = nodeNew.getNextNode();
if(nodeNew.isHidden)continue;//用于排除隐藏项
obj.selectNode(nodeNew, true, true);
}
} else {
for (var j = 1; j < (-count); j++) {
nodeNew = nodeNew.getPreNode();
if(nodeNew.isHidden)continue;//用于排除隐藏项
obj.selectNode(nodeNew, true, true);
}
}
window.preClickedNode = null;
}
}
};
this.$nextTick(() => {
// zTree 的数据属性,深入使用请参考 API 文档(zTreeNode 节点数据详解)
$.fn.zTree.init($("#" + this.randomId), setting, zNodes);
this.treeObj = $.fn.zTree.getZTreeObj(this.randomId);
//更新总数
let nodes = this.treeObj.getNodes();//获取 zTree 的全部节点数据
this.total = this.getAllChildrenNodes(nodes);
//调用父组件设置状态,标识该页面已经渲染完毕
this.$emit('set-state');
})
},
/**
* Z-tree
* @param zTreeObject
* @param treeNode
*/
cancelParentNode(zTreeObject, treeNode) {
if (treeNode.getParentNode() != null) {
var node = treeNode.getParentNode();
zTreeObject.cancelSelectedNode(node);
this.cancelParentNode(zTreeObject, node);
}
},
/**
* Z-Tree
* @param zTreeObject
* @param treeNode
*/
cancelChildrenNode(zTreeObject, treeNode) {
if (treeNode.children != null) {
for (var i = 0; i < treeNode.children.length; i++) {
zTreeObject.cancelSelectedNode(treeNode.children[i]);
this.cancelChildrenNode(zTreeObject, treeNode.children[i]);
}
}
}
},
created() {
//解决同一个页面多次使用该cardTree时id重复
this.randomId = "mainTree" + Math.floor(Math.random() * 100000);
if (this.url !== "") {
this.getMainData()
} else {
// 否则使用mainData
this.renderTree(this.mainData);
}
},
/**
* <!--
卡片树,用于导出,选择文件等功能
使用方法:父页面点击按钮获得此页面的返回值:
*/
props: {
//url用于获取树,如果不传入该值,并且不会显示高级检索,因为高级检索是使用url时的筛选
url: {
default: "",
type: String,
request: false
},
//通常与url同时出现,他用于使用url时需传的其他参数
params: {
default: function () {
return {}
},
type: Object,
request: false
},
//mainData是树数据,他的格式同elementUI树型组件
//这里this.mainData,如果不传输url,则会使用undefined创建树。
mainData: {
default: function () {
return undefined
},
type: Array,
request: false
},
//删除旗帜,若为true则在树返回时会删除选中节点
deleteFlag: {
default: false,
type: Boolean,
request: false
},
//增量旗帜,该参数决定了在调用add向树中添加数据时是否记载增量
incrementFlag:{
default: false,
type: Boolean,
request: false
}
}
}
</script>
<style scoped>
</style>
cardTree使用注意要点:
调用传参:
url:用于获取zTree的数据,该数据需要准寻一定的格式,具体格式请参照zTree官网
params:当我们发送请求时通常会携带一些参数
mainData:若不希望通过url向zTree中传递数据,也可以使用mainData向树中填充数据,当然这也必须准寻zTree官方的格式
incrementFlag:增量旗帜,该值能让树记录其中的增量,以便于之后获取增量
组件使用:
因为该组件由其他页面调用处理,所以需要符合一定的规则才能正常调用。
其他页面调用cardTree组件:
两个cardTree组件和两个按钮共同实现该博客一开始的那种穿梭树的效果,当然你也可以只调用一次cardTree用于实现文件导出功能的选择文件和文件夹。
<!--调用cardTree组件,拼接成树形穿梭框-->
<template>
<div v-loading="loading" element-loading-text="加载中...">
<el-row :gutter="20" style="margin-bottom: 20px">
<el-col :span="10">
<card-tree ref="unselectedTree" v-cloak :url="url" :params="params" :delete-flag="true" @set-state="unSelectedState = true"></card-tree>
</el-col>
<el-col :span="4" style="padding-top: 250px;padding-left: 5%">
<el-button size="small" type="primary" @click="cardTreeSubmit('selectedTree')"><</el-button>
<el-button size="small" type="primary" @click="cardTreeSubmit('unselectedTree')">></el-button>
</el-col>
<el-col :span="10">
<card-tree ref="selectedTree" v-cloak :main-data="selectedData" :delete-flag="true" :increment-flag="incrementFlag" @set-state="selectedState = true"></card-tree>
</el-col>
</el-row>
<div style="height: 30px">
<div style="float: right">
<el-button @click="last" v-if="flag === 0">上一步</el-button>
<el-button type="primary" @click="next">下一步</el-button>
</div>
</div>
</div>
</template>
<script>
module.exports = {
name: "two",
watch: {
flag(newValue, oldValue) {
//根据传入的flag决定是否记录zTree增量
this.incrementFlag = (newValue === 0) ? false : true
},
//当selectedState和unSelectedState两棵树都渲染完毕时结束"加载中..."的显示
unSelectedState(newValue){
if(newValue && this.selectedState){
let tree = this.$refs.selectedTree.getTree(false);
this.$refs.unselectedTree.removeTree(tree);
this.loading = false;
}
},
selectedState(newValue){
if(newValue && this.unSelectedState){
let tree = this.$refs.selectedTree.getTree(false);
this.$refs.unselectedTree.removeTree(tree);
this.loading = false;
}
}
},
components: {
'card-tree': 'url:components/static_menu/cardTree.vue',
},
data() {
return {
//传入cardTree的url,获取渲染ztree的json
url: "",
//作为获取json请求的参数
params:{
id:""
},
//增量flag,该值控制ztree是否记录增量
incrementFlag: false,
//树状态,当树渲染完毕后将会给他们赋值true,标识页面渲染完毕
selectedState: false,
unSelectedState: false,
//当表格未加载成功时显示加载动画
loading: true
};
},
methods: {
//由父页面调用,该方法调用 selectedTree选中树,获取选中树的增量
getIncrement(refine){
return this.$refs.selectedTree.getIncrement(refine);
},
//调用父页面调用,
next() {
let selectedTree = this.$refs.selectedTree.getTree(false);
if(selectedTree === undefined || selectedTree.length === 0){
//获取到所有数据后它不为叶子节点,说明有选中文件
this.$message.error('请选择文件');
return
}
//将选中数据提交给父页面处理
this.$emit('update:selectedData',selectedTree)
this.$emit('get-active', true);
},
//处理上一步按钮
last() {
this.$emit('get-active', false);
},
/**
* 数据处理相关
*/
//控制 >< 按钮,将参数中的数据传到另一个中
cardTreeSubmit(originalTree) {
let submit = this.$refs[originalTree].submit(false);
let laterTree = (originalTree === 'unselectedTree') ? 'selectedTree' : 'unselectedTree';
// 把选中项添加到另一棵树中,并删除原树中的选中项
this.$refs[laterTree].addTree(submit);
this.$refs[originalTree].removeSelected();
},
},
created(){
// 给url赋值,该url将会用于树中查询zTree渲染数据的json
switch (this.dataOne.scanType){
case "db":
this.url = "source/db/tableTree";
break;
case "file":
this.url = "source/file/fileList";
break;
case "hadoop":
this.url = "source/hadoop/tableTree";
break;
default:
this.$message.error("暂时只支持db、file、hadoop数据源")
}
//树获取数据请求中的参数
this.params.id = this.dataOne.dataSourceId;
},
//接收来自父级的对象
props:{
//因为该页面用于修改范围和新增两处,而修改范围时不允许上一步,flag用作区分,当修改范围时传值
flag:{
type: Number,
required: false
},
//选中的所有数据
selectedData:{
type: Array,
required: false
},
//横幅中需要显示第一步中的数据
dataOne:{
type: Object,
required: true
}
}
}
</script>
<style scoped>
</style>
源码解析:
cardTree ==> DOM部分:
由elementUI的card组件组成外框和卡片头,zTree渲染进卡片体中,并使用elementUI的<el-scrollbar>滚动条组件对zTree包裹(scrollbar组件不能在官网看到,若需要可以直接搜索使用方法或查看源码)
cardTree ==> JS部分:
js部分分为多条方法线,每个方法线对应操作zTree数据,(注:以下API统统表示zTreeAPI,其余API会著名)
一、页面加载初始化zTree
1.vue.created()进入页面时生成一个TreeId,该id用于如果一个页面中使用了多个cardTree组件时id重复的问题
2.根据传递进入页面的url或者mainData进入renderTree()方法初始化zTree组件,初始化组件时定义了shift,ctrl功能选中文件时使用,可查询zTreeAPI了解方法。
3.初始化组件结束后会调用setState表示已初始化完毕,这个值可以用于父组件用作elementUI的v-loading
二、自定义筛选功能
1.监听卡片中的输入内容,若值改变则调用API筛选所有 "isHidden=true" 的节点,显示出这些节点,
2.调用API.getNodesByFilter,模糊查询node.level中不包含输入值的节点并隐藏(赛选规则由this.filter返回)
三、返回选中项
submit()方法可由父页面调用,参数:Boole 是否去除zTree添加的属性(提纯)
this.$refs[originalTree].submit(false);
1.调用API.getSelectedNodes()获取选中节点
2.进入getPathNodes(选中节点)补充其所有父节点
循环检测父节点id
若没有父节点id直接装进tree数组返回
若有父节点id则装入map<父节点id,[选中的节点数组]>中之后再次校验父节点的父节点
循环父节点map
$.extend(true,arr1[],arr2[])深度合并一次API.getPath()获取到的路径,修改时防止修改到源数据
循环替换API.getPath()获取到的父路径,将选中的节点修改到第[length-1]个,再循环替换最终得到顶级父节点下所有节点都是已选中节点
注意:
这种方式只能运用于3级节点或以下,若有3级节点及以上的情况需要map去重或者递归循环
3.根据传值是否去除zTree赋予的属性(提纯)
循环提取nodes中的id,label,children属性到tree数组中并返回,若循环项存在children,则轮询提取。
四、接收节点
addTree(tree)可由父页面调用,用于该树接收节点
1.进入addChildren(null,tree)递归向树中添加节点,参数:parent 目标节点;children 需要添加的节点
循环children 调用API.getNodeByParam("id", child.id, parent);在parent下查找本次循环的节点
若找到了这个节点并且该节点是叶子节点则直接退出本次循环,若不是叶子节点则调用addChildren(该节点,该节点的子节点)进入递归
若没有找到本次循环的节点则根据传入的incrementFlag是否将本次循环的节点设置为增量,然后再调用API.addNodes()向zTree中添加该节点
增量其实就是递归将该节点及其子节点的checked设置为true,该属性表示勾选状态,但在配置zTree时为配置多选框显示,所以在页面上没有表示出来,但可以通过API.getCheckedNodes()获取所有勾选的节点(即增量)
2.更新总数
调用API.getNodes()获取zTree的全部节点
循环递归求总数,并赋值给this.total显示在页面上
五、返回增量
增量其实就是在新增数据时利用了未配置显示的zTree多选框,此处只需要调用API.getChechedNodes()获取选中多选框的节点再和返回选中项一样添加父路径和提纯
六、删除选中项
1.调用API.getSelectedNodes()获取所有选中项
2.调用API.getParentNode()获取父节点 -- 注:此处是shift/ctrl选中节点,不能跨越父节点选择,所以只需要任意取一个节点获取父节点就可以。
3.循环调用API.removeNode()删除选中节点
4.判断若删除完选中节点后父节点变为叶子节点则同样删除
此处递归调用如果删除完选中节点后 父节点任然为父节点,则删除并递归判断爷爷节点
七、删除参数项
1.循环参数节点,第一轮循环循环的是顶级父节点
2.调用API.getNodesByParam("label", node.label)[0]; 根据label查找zTree中的第0个节点
3.判断找到的节点若不是一个父节点则直接删除
若是一个父节点,则递归调用删除其子节点
递归结束判断其本身是否变为了一个叶子节点,若变为了叶子节点则删除
八、获取整棵树,而不是选中节点
1.调用API.getNodes(); 获取所有节点
2.根据方法参数决定是否调用this.refineSubmit()提纯去除zTree添加的属性
3.返回节点
九、获取所有叶子节点
1.调用API.getNodesByParam("isParent", false, null); 筛选isParent属性为false的节点
2.返回节点
cardTree ==> 使用部分:
一、dom部分
1.使用v-loading做页面未加载完毕时的覆盖"加载中..."字样。使用过程中若树太大会加载较慢,需要给用户反馈
2.使用<el-row>准备两个引用card-tree的标签和两个切换的按钮
3.<card-tree>属性说明:
ref:同html-ref。
url 和 params:cardTree获取树数据的url和请求体携带的数据
main-data:树数据,支持不通过url去后端查询数据,可传递该数据直接向树中添加
increment-flag:增量标识,若传递true则该树会在通过addTree()新增节点时记录增量
@set-state:cardTree初始化结束后会通过this.$emit("set-state")调用此方法,用于标识加载结束
注:这些值通过vue-props传递进入cardTree
二、js部分
1.vue-components:标记cardTree的位置,以便于页面上使用<cardTree>标签显示出cardTree
2.vue-watch:
监听flag,因为其他业务过程中需要一个变化的 增量flag用于标识cardTree是否记录增量,所以监听了flag,正常情况可不监听flag。
unSelectedState 和 selectedState 主要用于监听两颗树是否加载完毕,加载完毕时取消页面的“加载中...”覆盖层
3.vue-methods:
getIncrement(refine): 可由其他页面调用该方法获取selectedTree(该页面第二个Tree的名称,意为选中树,另一颗为未选中树)中的增量数据
next(): 下一页按钮实现,获取selectedTree中的所有节点数据,跳转
cardTreeSubmit(originalTree): 统一了两个按钮,该方法用于将参数中的选中节点传输到另一个
通过this.$refs[originalTree].submit(false);调用cardTree中的选中节点
通过this.$refs[laterTree].addTree(submit);调用另一颗cardTree将选中节点新增进另一颗cardTree
通过this.$refs[originalTree].removeSelected();删除选中节点,完成数据转移
4.vue-created 和 vue-props:
此处和cardTree无关,用于增加本页面复用性的区别代码,