使用vue递归组件实现上一个多级的树状结构
先上图
demo链接 欢迎访问
获取到的数据
{
"list": [
{"id": 1000,"name": 1000},
{"id": 1100,"name": 1100,"pid": 1000},
{"id": 1110,"name": 1110,"pid": 1100},
{"id": 1120,"name": 1120,"pid": 1100},
{"id": 1121,"name": 1121,"pid": 1120},
{"id": 1122,"name": 1122,"pid": 1120},
{"id": 1200,"name": 1200,"pid": 1000},
{"id": 1210,"name": 1210,"pid": 1200},
{"id": 1300,"name": 1300,"pid": 1000},
{"id": 2000,"name": 2000},
{"id": 2100,"name": 2100,"pid": 2000},
{"id": 2200,"name": 2200,"pid": 2000}
]
}
pid 表d示父节点的id
拿到数据后需要将其整理为如下结构
{
1000: {
id: 1000,
name: 1000,
branch: [1100,1200,1300],
state: 0, // 选中状态 0:branch未选中 1:branch部分选中 2:branch全选
open: false // 展开状态
},
1100: {
id: 1100,
name: 1100,
branch: [],
state: 0, // 选中状态 0:branch未选中 1:branch部分选中 2:branch全选
open: false // 展开状态
},
1110: {
id: 1110,
name: 1110,
checked: false // 选中状态
}
...
}
数据方法示例如下:
/**
* list: [] 获取到的list数据
* format: Function:(list[n])=> obj , 可选:格式化数据
* 例如: function (item) { return '分支'+item.name;}
**/
function treeInit(list, format) {
var allBranch = {};
var rootIds = []; // 根id的集合 即:没有pId的对象的id的集合
list.forEach(function (data) {
var id = data.id;
var pid = data.pid;
var leaf;
var pLeaf;
if (allBranch[id]) {
leaf = allBranch[id];
leaf.checked = false;
leaf.id = id;
} else {
leaf = {
checked: false,
id: id
};
allBranch[id] = leaf;
}
Object.assign(leaf, format ? format(data) : {});
if (pid) {
leaf.pid = pid;
pLeaf = allBranch[pid];
if (pLeaf) {
if (pLeaf.branch) {
pLeaf.branch.push(id);
} else {
pLeaf.branch = [id];
pLeaf.open = true;
pLeaf.state = 0;
}
} else {
allBranch[pid] = {
branch: [id],
open: true,
state: 0
};
}
} else {
rootIds.push(id);
}
});
return {
root: rootIds,
tree: allBranch,
};
}
模板
模板包含一个tree组件和用于递归的branch组件
// tree 模版
<ul class="list-unstyled tree">
<li is="treebranch" v-for="b in root" :key="b" :branch="tree[b]" :tree="tree" @check="check" @toggle="toggle"></li>
</ul>
// tree 接收的数据
['tree', 'root'] // tree:上面格式化过的 treeData root: 根节点的id数组 ( [1000,2000] )
// tree 的方法
check: function (branchId) {
var state = {}; // 需要更新的状态
var tree = this.tree;
checkSub(branchId, tree, state, !tree[branchId].checked); // 从当前的节点往下递归计算选中状态并将需要更新的状态存入state中
checkParent(tree[branchId].pid, tree, state); // 从当前的节点往上计算状态并将需要更新的状态存入state中
this.$emit('update', state);
},
toggle: function (branchId) {
var state = {}; // 需要更新的状态
addState(state, branchId, { open: !this.tree[branchId].open }); //将状态添加到state中
this.$emit('update', state);
}
// branch模板
<li>
<a class="tree-switcher rotate-animate mr5" :class="{open:branch.open,hold:!branch.branch}" @click="toggle(branch.id)"></a>
<a class="tree-state" @click="check(branch.id)">
<i class="box mr5" :class="{some:branch.state===1,all:branch.checked||branch.state===2}"></i>
<span>{{branch.name}}</span>
</a>
<ul class="tree-group list-unstyled pl20" v-if="branch.branch" v-show="branch.open">
<li is="treebranch" v-for="a in branch.branch" :key="a" :branch="tree[a]" :tree="tree" @check="check" @toggle="toggle"></li>
</ul>
</li>
// branch 接收的数据
['branch', 'tree'] // branch:当前branch的id tree:整个treeData
// branch 方法
// 选中/取消选中的操作处理
check: function (bId) {
this.$emit('check', bId);
},
// 折叠/展开的事件处理
toggle: function (bId) {
this.$emit('toggle', bId);
}
到这里已经实现了 tree组件了
调用
<tree :tree="treeData" :root="rootIds" @update="update"><tree>
// data
{
treeData: treeData,
rootIds: rootIds
}
// 方法
update: function (newState) {
var temp, i, j, k;
Object.keys(newState).forEach(function(item){
var newChange = newState[i];
var branch = this.treeData[i];
}, this);
for (i in newState) {
temp = this.treeData[i];
k = newState[i].state;
for (j in k) {
temp[j] = k[j];
}
}
}
以上为基本实现方案、其中调用了三个方法checkSub、checkParent和addState,实现了一个小型的通用数据更新对象state
// 从下至上的状态检查修改上一级的state值
function checkParent(pid, treeData, resObj) {
var parent = treeData[pid];
if (!parent) return;
var all = true;
var some = false;
var state;
parent.branch.forEach(function (item) {
var it = treeData[item];
var checked = getAttr(resObj, [item, 'state', 'checked'], it.checked);
var state = getAttr(resObj, [item, 'state', 'state'], it.state);
if (checked || state === 1) {
some = true;
}
if (!checked) {
all = false;
}
});
if (all) {
state = 2;
} else {
if (some) {
state = 1;
} else {
state = 0;
}
}
if (parent.state !== state) {
addState(resObj, pid, { state: state });
}
if (parent.checked !== all) {
addState(resObj, pid, { checked: all });
}
checkParent(parent.pid, treeData, resObj);
}
// 创建一个更新的对象
function addState(obj, key, state) {
var temp = obj[key];
if (!temp) {
temp = { state: {} };
obj[key] = temp;
}
for (var i in state) {
temp.state[i] = state[i];
}
}
// 递归上到下递归修改状态
function checkSub(id, tree, resObj, checked) {
var obj = tree[id];
var targetState = checked ? 2 : 0;
addState(resObj, id, { checked: checked });
if (obj.branch) {
addState(resObj, id, { state: targetState });
obj.branch.forEach(function (item) {
checkSub(item, tree, resObj, checked);
});
}
}
// 本人最常用的一个方法实现 用于获取不确定的对象的数据
function getAttr(obj, arr, defaultVal) {
if (typeof arr === 'string') {
arr = arr.split('.').filter(function (item) {
return item;
});
}
arr.every(function (item) {
return obj = obj[item];
});
if (obj === undefined) {
if (defaultVal === undefined) {
defaultVal = '';
}
return defaultVal;
}
return obj;
}
至此本tree-demo完毕。
才疏学浅某些方法的实现不甚完美,望见谅。