angular 嵌套实现树结构 ng-repeat ng-include
效果图
ang.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="angular.min.js"></script>
<script src="ang.js"></script>
<link rel="stylesheet" href="css/contest/style.css">
</head>
<body ng-app="myApp" ng-controller="TreeController">
<div class="c-group-r" style="background:#fff;">
<div class="c-contest-tree-box fix">
<div class="tree">
<ul>
<li ng-repeat="data in tree" ng-include="'tree_item_renderer.html'"></li>
</ul>
<div class="loading" ng-show="loadingSwitch">正在加载请稍等</div>
</div>
<div class="tree">
<ul>
<li ng-show="data.isChecked==1||data.isChecked==2" ng-repeat="data in tree" ng-include="'tree_item_renderer2.html'"></li>
</ul>
<div class="loading" ng-show="loadingSwitch">正在加载请稍等</div>
</div>
</div>
</div>
</body>
</html>
====================================================================================
<script id="tree_item_renderer.html">
<div>
<div ng-click="switchOper(data)" ng-class="{'c-contest-menueTri-close':!data.isOpen,'c-contest-menueTri-open':data.isOpen}" ng-show="!data.isLeaf"></div>
<div class="c-contest-menueTri-none" ng-show="data.isLeaf"><i class="c-contest-menueTri-point" ng-show="data.isChecked=='none'"></i></div>
<div ng-click="change(data)" ng-class="{'c-contest-checkbox':data.isChecked==0,'c-contest-checkbox-check':data.isChecked==1,'c-contest-checkbox-have':data.isChecked==2}"><span></span></div>
<span>{{data.name}}</span>
</div>
<ul ng-show="data.isOpen">
<li ng-repeat="data in data.child" ng-include="'tree_item_renderer.html'"></li>
</ul>
<script>
====================================================================================
<script id="tree_item_renderer2.html">
<div>
<div ng-click="switchOper(data)" ng-class="{'c-contest-menueTri-close':!data.isOpen,'c-contest-menueTri-open':data.isOpen}" ng-show="!data.isLeaf"></div>
<div class="c-contest-menueTri-none" ng-show="data.isLeaf"><i class="c-contest-menueTri-point" ng-show="data.isChecked=='none'"></i></div>
<div ng-click="change(data)" ng-class="{'c-contest-checkbox':data.isChecked==0,'c-contest-checkbox-check':data.isChecked==1,'c-contest-checkbox-have':data.isChecked==2}"><span></span></div>
<span>{{data.name}}</span>
</div>
<ul ng-show="data.isOpen">
<li ng-show="data.isChecked==1||data.isChecked==2" ng-repeat="data in data.child" ng-include="'tree_item_renderer2.html'"></li>
</ul>
</script>
=====================================================================================
<script id="ang.js">
angular.module("myApp", []).
controller("TreeController", ['$scope', function($scope) {
var gtree = [
{
"id": "2",
"name": "a1",
"isOpen": true,
"type": 1,
"child": [
{
"id": "3",
"name": "a2",
"isOpen": true,
"type": 1,
"child": [
{
"id": "4",
"name": "小王",
"isChecked": 1,
"isOpen": true,
"type": 2
},
{
"id": "5",
"name": "么么",
"isChecked": 0,
"isOpen": true,
"type": 2
},
{
"id": "6",
"name": "你爹",
"isChecked": 0,
"isOpen": true,
"type": 2
}
],
"child_nums": 3,
"isChecked": 2
}
],
"child_nums": 1,
"isChecked": 2
},
{
"id": "9",
"name": "b1",
"isOpen": true,
"type": 1,
"child": [
{
"id": "8",
"name": "b2",
"isOpen": true,
"type": 1,
"child": [
{
"id": "7",
"name": "ZXY",
"isChecked": 1,
"isOpen": true,
"type": 2
},
{
"id": "13",
"name": "丽丽",
"isChecked": 1,
"isOpen": true,
"type": 2
},
{
"id": "12",
"name": "啦啦啦",
"isChecked": 1,
"isOpen": true,
"type": 2
}
],
"child_nums": 3,
"isChecked": 1
}
],
"child_nums": 1,
"isChecked": 1
},
{
"id": "14",
"name": "c1",
"isOpen": true,
"type": 1,
"child": {
"isChecked": 0
},
"child_nums": 0
},
{
"id": "69473",
"name": "分组",
"isOpen": true,
"type": 1,
"child": [
{
"id": "15",
"name": "分组1",
"isOpen": true,
"type": 1,
"child": [
{
"id": "23",
"name": "测试柯",
"isChecked": 0,
"isOpen": true,
"type": 1,
"child": [
{
"id": "16",
"name": "测试柯",
"isChecked": 0,
"isOpen": true,
"type": 2
}
]
},
{
"id": "24",
"name": "美眉",
"isChecked": 0,
"isOpen": true,
"type": 1
}
],
"child_nums": 2,
"isChecked": 0
},
{
"id": "26",
"name": "分组2",
"isOpen": true,
"type": 1,
"child": {
"isChecked": 0
},
"child_nums": 0
}
],
"child_nums": 2,
"isChecked": 0
}
];
tree = [{'name' : "全部", "isRoot":true, "isOpen":true,"type" : 1,"child":gtree,"isChecked":"1","pathes":[]}];
$scope.tree = tree;
makeTree($scope.tree,1);
console.log(tree)
//工具函数
/**
* 展开关闭
* @param {object} data 当前节点数据
*/
$scope.switchOper = function(data) {
if(data.isOpen){
data.isOpen=false;
}else{
data.isOpen=true;
}
};
/**
* 改变选择状态
* @param {object} data 当前节点数据
*/
$scope.change = function(data,type){
var istotal = 0;
if(data.isChecked ==0){//非选择状态则全选(遇到有空分组则部分选择)
if(data.blankChild){
data.isChecked = 2;
}else{
data.isChecked = 1;
}
selectAll(data,1);
}else{//全选状态或者部分全选则取消选择
data.isChecked = 0;
selectAll(data,0);
}
// console.log(data)
checkParent(data);
}
/**
* makeTree 加工树数据
* @param {object} tree 原始树结构
* @param {number} type 获取类型
*/
function makeTree(tree,type){
var len = tree.length,
clen = 0,
temp = null,
path = '';
for(var i=0;i<len;i++){
if(tree[i].child instanceof Array){
tree[i].parent ? path=tree[i].parent.path + '["child"]' + '['+i+']' : path='['+i+']';
tree[i].path = path ;
temp = tree[i].child;
clen = temp.length;
if(clen == 0){
tree[i].isLeaf = true;
if(tree[i].type == 1){
tree[i].isChecked = "none";//无人员分组
if(tree[i].parent)
tree[i].parent.blankChild = true;//是否含有空分组
}
}
for(var j=0; j<clen; j++){
temp[j].parent = tree[i];
// temp[j].path = temp[j].parent.path + 'child';
if(temp[j].isChecked==undefined){
temp[j].isChecked =0;
}
if(temp[j].isChecked == 2 && temp[j].parent.isRoot){//当子节点存在中间状态并且其父节点是根节点的时候
temp[j].parent.isChecked = 2;
}
}
makeTree(temp,type);
}else{
tree[i].parent ? path=tree[i].parent.path + '["child"]' + '['+i+']' : path='['+i+']';
tree[i].path = path ;
if(type == 1){
console.log(tree[0].pathes)
$scope.tree[0].pathes.push(tree[i].path);
}else if(type == 2){
//如果是多棵树的时候绑定多个scope值用type区分
}
tree[i].child = [];
tree[i].isLeaf = true;
if(tree[i].type == 1){
tree[i].isChecked = "none";//无人员分组
if(tree[i].parent)
tree[i].parent.blankChild = true;//是否含有空分组
}
}
}
}
// 选择父节点
function checkParent(data){
if(!data.parent) return;
var len = data.parent.child.length,
total = 0,
have = 0,
uncheck = 0;
if(data.isChecked == 2){
data.parent.isChecked = 2;
}else{
for(var i=0;i<len;i++){
if(data.parent.child[i].isChecked == 1){
total++;
}else if(data.parent.child[i].isChecked == 2){
have++;
}
}
if(total == len){
data.parent.isChecked = 1;
}else if(total > 0){
data.parent.isChecked = 2;
}else if(total == 0){
if(have>0){
data.parent.isChecked = 2;
}else{
data.parent.isChecked = 0;
}
}
}
checkParent(data.parent);
}
/**
* 选择所有子集
*/
function selectAll(data, ischeck){
for(var i=0;i<data.child.length;i++){
if(data.child[i].isChecked!="none"){
if(data.child[i].blankChild && ischeck){
data.child[i].isChecked = 2;
}else{
data.child[i].isChecked = ischeck;
}
}
if(data.child[i].child.length>0){
selectAll(data.child[i], ischeck);
}
}
// console.log(data.nodes)
}
/**
* getSelctPersons 遍历所有人员挑出被选则的
* @param {string} rootKey 对象的名字
* @param {array} pathes 人员路径数组
* @return {array} persons 被选择的人员数组
*/
function getSelctPersons(rootKey, pathes){
var len = pathes.length,
temp,
persons = [];
for(var i=0;i<len;i++){
temp = eval(rootKey+pathes[i]);
if(temp.isChecked == 1){
persons.push({
id : temp.id,
name : temp.name,
department : temp.parent.id
});
}
}
return persons;
}
// getSelctPersons('$scope["tree"]', $scope.tree[0].pathes);//获取所有选择的人员
/**
* [removeParent 删除树中的递归访问 parent字段]
* @param {object} tree 处理过的树结构数据
*/
function removeParent(tree){
var len = tree.length;
for(var i=0;i<len;i++){
if(tree[i].parent){
tree[i].parent = null;
}
removeParent(tree[i].child);
}
}
}]);
<script>
=======================================css=============================================
@charset "utf-8";
.m-contest-footer {
padding: 20px 0;
height:70px;
text-align: right;
border-top: 1px solid #e8edf1;
}
.m-contest textarea{resize:none;}
.m-contest em,.m-contest i,.m-contest dfn{font-style:normal;}
.m-contest .z-w60{width:60px!important;}
.m-contest .z-w100{width:100px!important;}
.m-contest .z-inline-block input,.m-contest .z-inline-block select{display:inline-block;}
.c-contest-bar{line-height:30px;color:#666;}
.c-contest-bar .u-radio{padding-top:2px;}
@charset "utf-8";
/* html,body */
html { font-size:1em;-webkit-tap-highlight-color:rgba(0,0,0,0); -webkit-tap-highlight:rgba(0,0,0,0);-webkit-text-size-adjust:none;}
body { font-size:0.75em;}
label { cursor:pointer}
a:link, a:visited { text-decoration:none}
input,button,select,textarea{outline:none} textarea{resize:none;}
a, abbr, acronym, address, applet, article, aside, audio, b, blockquote, big, body, center, canvas, caption, cite, code, command, datalist, dd, del, details, dfn, dl, div, dt, em, embed, fieldset, figcaption, figure, font, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, html, i, iframe, img, ins, kbd, keygen, label, legend, li, meter, nav, menu, object, ol, output, p, pre, progress, q, s, samp, section, small, span, source, strike, strong, sub, sup, table, tbody, tfoot, thead, th, tr, tdvideo, tt,
u, ul, var { margin:0; padding:0}
article, aside, footer, header, hgroup, nav, section, figure, figcaption { display: block}
h1, h2, h3, h4, h5, h6, th, td, table, input, button, select, textarea, sub{ font-size:1em}
body, input, button, select, textarea, sub{ font-family:Arial, sans-serif}
em, cite, address, optgroup { font-style:normal}
kbd, samp, code { font-family:monospace}
img, input, button, select, textarea { vertical-align:middle}
ul, ol { list-style:none}
img, fieldset { border:0}
abbr, acronym { cursor:help; border-bottom:1px dotted black;border:0;font-variant:normal;}
table { width:100%; border-spacing:0; border:0}
table th, table td { border:0}
legend, hr { overflow:hidden; position:absolute; top:0; left:0}
legend, hr, caption { visibility:hidden; font-size:0; width:0; height:0; line-height:0}
input, select, textarea{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-o-box-sizing:border-box;-ms-box-sizing:border-box;-webkit-appearance:button;-moz-appearance:button;-o-appearance:button;-ms-appearance:button;border-radius:0px;}
sup{vertical-align:text-top;}
sub{vertical-align:text-bottom;}
input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;*font-size:100%;}
:focus{outline:0;}
a{text-decoration:none;outline:none;}
textarea{resize:none;}
i{font-style:initial;}
div, h1, h2, p, table, tr, td{word-wrap:break-word;word-break:break-all;}
.fix{display:inline-block;}
.fix{display:block;}
.fix:after{content:"";display:block;height:0px; clear:both;visibility:hidden;}
div, h1, h2, p, table, tr, td{word-wrap:break-word;word-break:break-all;}
.wbn{word-break:keep-all;white-space:nowrap;}
.wby{word-break:break-all;word-wrap:break-word;white-space:normal;}
input::-webkit-input-placeholder, textarea::-webkit-input-placeholder {color: #999;}
input:-moz-placeholder, textarea:-moz-placeholder {color: #999;}
input::-moz-placeholder, textarea::-moz-placeholder {color: #999;}
input:-ms-input-placeholder, textarea:-ms-input-placeholder {color: #999;}
html,body{min-height:100%;font-family:"microsoft yahei";}
input::-ms-clear, ::-ms-reveal{display:none;}
/*树结构*/
.c-contest-tree-box{width:588px;padding:20px;border:1px solid #eaeaea;border-radius:2px;line-height:30px;font-size:14px;}
.c-contest-tree-box .loading{width:100px;height:100px;padding-top:66px;background:url(../../images/contest/loading-img.png) no-repeat 50% 50%;position:absolute;top:80px;left:70px;}
.c-contest-tree-box .tree div{outline:none;}
.c-contest-tree-box .tree:nth-child(1){margin-right:36px;}
.c-contest-tree-box .tree{float:left;width:254px;height:328px;box-shadow:1px 0 1px rgba(0,0,0,0.1);border:1px solid #d8dde6;border-radius:4px;overflow-y:scroll;position:relative;}
.c-contest-menueTri-close,.c-contest-menueTri-open,.c-contest-menueTri-none{width:20px;height:20px;margin-top:5px;float:left;}
.c-contest-menueTri-close{background:url(../../images/contest/tri-close.png) no-repeat 50% 50%;}
.c-contest-menueTri-open{background:url(../../images/contest/tri-open.png) no-repeat 50% 50%;}
.c-contest-menueTri-point{display:block;width:100%;height:100%;background:url(../../images/contest/tri-point.png) no-repeat 50% 50%;}
.c-contest-menueTri.isleaf{background:none;}
.c-contest-checkbox,.c-contest-checkbox-check,.c-contest-checkbox-have{width:16px;height:16px;border-radius:2px;float:left;margin:7px 5px 0 0;}
.c-contest-checkbox{background:url(../../images/contest/nocheck.jpg) no-repeat 0 0;}
.c-contest-checkbox-check{background:url(../../images/contest/check.jpg) no-repeat 0 0;}
.c-contest-checkbox-have{background:url(../../images/contest/have.jpg) no-repeat 0 0;}
.c-contest-tree-box ul{background:#fff;}
.c-contest-tree-box ul ul{padding-left:12px;}
.c-contest-tree-box ul li > div{margin-bottom:4px;}