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{60px!important;}
.m-contest .z-w100{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 {    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; 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{588px;padding:20px;border:1px solid #eaeaea;border-radius:2px;line-height:30px;font-size:14px;}
.c-contest-tree-box .loading{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;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{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;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{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;}

原文地址:https://www.cnblogs.com/youzhuxiaoyao/p/5660633.html