前言:最近接了一个活,将一大堆数据通过左边树结构展示出来。对左边树可勾选后,右边展示左边树勾选过的内容,最终保存右边树入库。
效果图
- 节点开合
- 选择和未选择状态切换
- 左边树选择部分右边树展示
- 右边树点x则左边树取消选择
上代码:
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/angular.js/1.6.3/angular.min.js"></script>
<script type="text/javascript" src="js/angular-tree-control.js"></script>
<script src="./js/context-menu.js"></script>
<link rel="stylesheet" type="text/css" href="css/tree-control.css">
<link rel="stylesheet" type="text/css" href="css/tree-control-attribute.css">
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css">
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.slim.min.js"></script>
<script src="https://cdn.bootcss.com/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<style>
.height-500 {
height: 500px;
}
.margin-top-100 {
margin-top: 100px;
}
pre {
outline: 1px solid #ccc;
padding: 5px;
margin: 5px;
}
.string {
color: green;
}
.number {
color: darkorange;
}
.boolean {
color: blue;
}
.null {
color: magenta;
}
.key {
color: red;
}
</style>
</head>
<body>
<div class="container" ng-controller="myController">
<div class="row margin-top-100">
<div class="col-sm p-3 mb-2 bg-info text-white height-500">
<treecontrol class="tree-light"
tree-model="dataForTheTree"
options="treeOptions"
selected-nodes="selectedNodes">
<label ng-click="showSelected(node)">
<input type="checkbox"
ng-model="node.checked"
class="check-box" disabled>
{{node.id}}
</label>
</treecontrol>
</div>
<div class="col-sm p-3 mb-2 bg-dark text-white height-500">
<treecontrol class="tree-dark"
tree-model="selectedTree"
options="treeOptions1"
selected-nodes="selectedNodes">
<label>
<input type="checkbox"
ng-model="node.checked"
class="check-box" disabled>
<label>
{{node.id}}
</label>
<label ng-show="showClose(node)" ng-click="dirSelectedStatusNeedRemoveById(node.id)">
x
</label>
</label>
</treecontrol>
</div>
</div>
<div class="row">
<div class="col-sm">
<h1>左边树</h1>
<pre>
<div ng-bind-html='dataForTheTree| jsonFormat | showAsHtml'></div>
</pre>
</div>
<div class="col-sm">
<h1>右边树</h1>
<pre>
<div ng-bind-html='selectedTree| jsonFormat | showAsHtml'></div>
</pre>
</div>
<div class="col-sm">
<h1>已经选中的节点</h1>
<pre>
<div ng-bind-html='selectedNodes| jsonFormat | showAsHtml'></div>
</pre>
</div>
</div>
</div>
<script>
const app = angular.module('myApp', ['treeControl']);
app.controller('myController', function ($scope) {
let vm = $scope;
// 左边树选择的内容
vm.selectedNodes = [];
// 右边树展示内容
vm.selectedTree = [];
vm.treeOptions = {
nodeChildren: "children",
dirSelectable: true,
injectClasses: {
ul: "a1",
li: "a2",
liSelected: "a7",
iExpanded: "a3",
iCollapsed: "a4",
iLeaf: "a5",
label: "a6",
labelSelected: "a8"
},
multiSelection: true
};
vm.treeOptions1 = {
nodeChildren: "children",
dirSelectable: false,
injectClasses: {
ul: "a1",
li: "a2",
liSelected: "a7",
iExpanded: "a3",
iCollapsed: "a4",
iLeaf: "a5",
label: "a6",
labelSelected: "a8"
},
isSelectable: function () {
return false;
}
};
vm.dataForTheTree = [{
"id": "Joe",
"fid": "-1",
"checked": false,
"children": [
{"id": "Smith", "fid": "Joe", "checked": false, "children": []},
{
"id": "Gary",
"checked": false,
"fid": "Joe",
"children": [{
"id": "Jenifer",
"fid": "Gary",
"checked": false,
"children": [
{"id": "Dani", "checked": false, "fid": "Jenifer", "children": []},
{"id": "Max", "checked": false, "fid": "Jenifer", "children": []}
]
}]
}
]
},
{"id": "Albert", "checked": false, "fid": "-1", "children": []},
{"id": "Ron", "checked": false, "fid": "-1", "children": []}
];
// 将左边树选中的同步给右边的树,全量同步,效率略差于增量同步
$scope.$watch('dataForTheTree', function () {
if (!!$scope.dataForTheTree && $scope.dataForTheTree.length > 0) {
let treeDataRightTmp = JSON.parse(JSON.stringify($scope.dataForTheTree));
$scope.selectedTree = createObjectTree(treeDataRightTmp);
}
}, true);
// 切换节点的选中状态
function toggleSelect(node) {
node.checked = !node.checked;
vm.dataForTheTree.push();
}
vm.showSelected = function (node) {
toggleSelect(node);
selectFollowNodesIfDir(node);
dirSelectedStatusNeedRemove(vm.dataForTheTree);
};
vm.showClose = function (node) {
return node.children.length === 0;
};
// 判断父级别节点是否需要删除(一个父节点下没有任何被选中的节点则该节点需要被删除)
function dirSelectedStatusNeedRemove(tree) {
tree.forEach(n => {
if (isDir(n)) {
if (!dirHasSelectedNode(n)) {
n.checked = false;
removeFromSelectedNodes(n);
}
dirSelectedStatusNeedRemove(n.children);
}
})
}
vm.dirSelectedStatusNeedRemoveById = function (id) {
closeById(vm.dataForTheTree, id);
vm.dataForTheTree.push();
dirSelectedStatusNeedRemove(vm.dataForTheTree);
vm.dataForTheTree.push();
};
function closeById(tree, id) {
for (let i = 0; i < tree.length; i++) {
let node = tree[i];
if (node.id === id) {
node.checked = false;
removeFromSelectedNodes(node);
return;
}
if (isDir(node)) {
closeById(node.children, id);
}
}
}
// 查找节点下所有非父级别节点是否有被选中,一个也没有则返回false
function dirHasSelectedNode(node) {
let result = false;
let treeNode = node.children;
for (let i = 0; i < treeNode.length; i++) {
let childNode = treeNode[i];
// 如果非选中并且是父级别节点则迭代找该节点下的是否有勾选状态的节点
if (isDir(childNode)) {
if (dirHasSelectedNode(childNode)) {
result = true;
break
}
} else {
if (childNode.checked) {
result = true;
break
}
}
}
return result;
}
// 创建目标树
function createObjectTree(tree) {
for (let i = tree.length - 1; i >= 0; i--) {
let node = tree[i];
if (isDir(node)) {
// 先去删除根
createObjectTree(node.children);
// 没有根的,删除父级别元素
if (node.children.length === 0) {
tree.splice(i, 1);
}
} else {
// 删除根级别元素
if (!node.checked) {
tree.splice(i, 1);
}
}
}
return tree;
}
// 判断是否是父节点
function isDir(node) {
return !!node && !!node.children && node.children.length > 0;
}
// 选中父节点则勾选父节点下所有节点
function selectFollowNodesIfDir(node) {
if (isDir(node)) {
// 遍历dir下面的节点
node.children.forEach(n => {
// 节点状态和dir节点不一致,则把节点状态改成与dir节点状态一致
if (node.checked !== n.checked) {
n.checked = node.checked;
n.checked ? vm.selectedNodes.push(n) : removeFromSelectedNodes(n);
selectFollowNodesIfDir(n);
}
});
}
}
// 节点取消勾选情况下,从已选中数组中删除该节点
function removeFromSelectedNodes(node) {
let index = vm.selectedNodes.indexOf(node);
if (index !== -1) {
vm.selectedNodes.splice(index, 1);
}
}
});
app.filter('jsonFormat', function () {
return function (json) {
return syntaxHighlight(json);
}
});
app.filter("showAsHtml", function ($sce) {
return function (input) {
return $sce.trustAsHtml(input);
}
});
function syntaxHighlight(json) {
if (!json) {
return json;
}
if (typeof json !== 'string') {
json = JSON.stringify(json, undefined, 2);
}
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
let cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
</script>
</body>
</html>
参考文献:
[1]:https://github.com/wix/angular-tree-control
[2]: https://blog.csdn.net/weixin_38496860/article/details/81166907
[3]: https://www.runoob.com/angularjs/angularjs-tutorial.html