管理后台-前端-AngularJS

ng+bootstrap可以做出很漂亮的管理系统出来,https://wrapbootstrap.com/可以付费购买,下文会提供一个免费的,要讲解如何从0到1把ng前端结构搭出来是很漫长的教程,本文仅仅介绍一下这个免费后台模版的结构,然后重点讲解如何改写这个结构。
开始阅读之前,假设读者已经ng入门并且对于ui.router,bootstrap有一定了解。


下载链接http://pan.baidu.com/s/1o73ewfK
下载下来以后我们可以挂载到IIS里面看看这个模版的运行效果
这里写图片描述


1) 后台结构
这里写图片描述

其中我们需要关注的如下:

index.html: 入口
js: 存放所有业务逻辑代码
js/app.js:定义ng需要加载的模块
js/main.js:定义ng的全局配置信息
js/config.router.js:ng的路由器
tpl:存放所有页面模版
tpl/blocks:页面框架(头、尾、侧边栏…)
vendor:存放所有第三方模块


2) 改写结构,接管路由

结构简图如下
这里写图片描述
这里接管的原则是尽量不改动原始结构,首先在根目录创建我们自己的目录结构.

> mkdir admin
> mkdir admin\blocks
> copy js\main.js admin\

编写页面框架模版,我们也可以直接从tpl目录复制过来再做修改。简单起见,我们在模版里面直接使用了中文,这样会导致页面乱码,解决方法:html文件用utf-8编码格式存储。

<!--admin/blocks/aside.html-->
<div class="aside-wrap">
  <div class="navi-wrap">
    <!-- nav -->
    <nav ui-nav class="navi" ng-include="'admin/blocks/nav.html'"></nav>
    <!-- nav -->
  </div>
</div>
<!--admin/blocks/header.html-->
      <!-- navbar header -->
      <div class="navbar-header {{app.settings.navbarHeaderColor}}">
        <button class="pull-right visible-xs dk" ui-toggle-class="show" data-target=".navbar-collapse">
          <i class="glyphicon glyphicon-cog"></i>
        </button>
        <button class="pull-right visible-xs" ui-toggle-class="off-screen" data-target=".app-aside" ui-scroll="app">
          <i class="glyphicon glyphicon-align-justify"></i>
        </button>
        <!-- brand -->
        <a href="#/" class="navbar-brand text-lt">
          <i class="fa fa-btc"></i>
          <img src="img/logo.png" alt="." class="hide">
          <span class="hidden-folded m-l-xs">{{app.name}}</span>
        </a>
        <!-- / brand -->
      </div>
      <!-- / navbar header -->

      <!-- navbar collapse -->
      <div class="collapse pos-rlt navbar-collapse box-shadow {{app.settings.navbarCollapseColor}}">
        <!-- buttons -->
        <div class="nav navbar-nav hidden-xs">
          <a href class="btn no-shadow navbar-btn" ng-click="app.settings.asideFolded = !app.settings.asideFolded">
            <i class="fa {{app.settings.asideFolded ? 'fa-indent' : 'fa-dedent'}} fa-fw"></i>
          </a>
        </div>
        <!-- / buttons -->

        <!-- nabar right -->
        <ul class="nav navbar-nav navbar-right">
          <li class="hidden-xs">
            <a ui-fullscreen></a>
          </li>
          <li class="dropdown" dropdown>
            <a href class="dropdown-toggle clear" dropdown-toggle>
              <span class="thumb-sm avatar pull-right m-t-n-sm m-b-n-sm m-l-sm">
                <img src="img/a0.jpg" alt="...">
                <i class="on md b-white bottom"></i>
              </span>
              <span class="hidden-sm hidden-md"></span> <b class="caret"></b>
            </a>
            <!-- dropdown -->
            <ul class="dropdown-menu animated fadeInRight w">
              <li>
                <a href="#">Logout</a>
              </li>
            </ul>
            <!-- / dropdown -->
          </li>
        </ul>
        <!-- / navbar right -->

      </div>
      <!-- / navbar collapse -->
<!--admin/blocks/nav.html-->
<!-- list -->
<ul class="nav">
  <li class="hidden-folded padder m-t m-b-sm text-muted text-xs">
    <span translate="导航">导航</span>
  </li>  
</ul>
<!-- / list -->
<!--admin/app.html-->
  <!-- navbar -->
  <div data-ng-include=" 'admin/blocks/header.html' " class="app-header navbar">
  </div>
  <!-- / navbar -->

  <!-- menu -->
  <div data-ng-include=" 'admin/blocks/aside.html' " class="app-aside hidden-xs {{app.settings.asideColor}}">
  </div>
  <!-- / menu -->

  <!-- content -->
  <div class="app-content">
    <div ui-butterbar></div>
    <a href class="off-screen-toggle hide" ui-toggle-class="off-screen" data-target=".app-aside" ></a>
    <div ncy-breadcrumb></div>
    <div class="app-content-body fade-in-up" ui-view></div>
  </div>
  <!-- /content -->

  <!-- footer -->
  <div class="app-footer wrapper b-t bg-light">
    <span class="pull-right">{{app.version}} <a href ui-scroll="app" class="m-l-sm text-muted"><i class="fa fa-long-arrow-up"></i></a></span>
    &copy; 2016 Copyright.
  </div>
  <!-- / footer -->

  <div data-ng-include=" 'tpl/blocks/settings.html' " class="settings panel panel-default">
  </div>

给我们的首页创建一个空白模版admin/dashboard.html

<!--admin/dashboard.html-->

写我们新的路由

// admin/router.js
'use strict';

app
  .run(
      function ($rootScope,   $state,   $stateParams) {
          $rootScope.$state = $state;
          $rootScope.$stateParams = $stateParams;        
      }
  )
.config(
      function ($stateProvider,   $urlRouterProvider) {
          $urlRouterProvider
              .otherwise('/app/dashboard');
          $stateProvider
              .state('app', {
                  abstract: true,
                  url: '/app',
                  templateUrl: 'admin/app.html',
              })
              .state('app.dashboard', {
                  url: '/dashboard',
                  templateUrl: 'admin/dashboard.html',
              })
      }
  );

修改入口,注释或删除掉对原config.router.js、main.js的引用,换成我们的控制接管

<!--index.html-->
...
  <!--<script src="js/config.router.js"></script>-->
  <!--<script src="js/main.js"></script>-->
  <script src="admin/router.js"></script>
  <script src="admin/main.js"></script>
...

这里写图片描述
到此我们就得到了一套可自由扩展的前端框架


3) 接下来我们基于BasicAuth加入系统的用户验证功能。

这里写图片描述
这里我们采用按功能模块来建立子目录,区别于原模版框架(原框架是按文件类型区分子目录,例如脚本放在js/里面,模版放在tpl里面)。

首先启动Restful服务器,然后我们为服务器配置一个全局变量,代码里面的host需要修改成服务器真实地址

// admin/main.js
...
      $scope.app = {
        host: "http://172.17.9.92:8000",
        name: 'Angulr',
...

创建认证功能模块目录auth

> mkdir admin\auth\

编写controller(控制器)

// admin/auth/ctrl.js
app.controller('LoadingController',function($scope,$resource,$state){
    var $com = $resource($scope.app.host + "/auth/info/?");
    $com.get(function(){
        $state.go('app.dashboard');
    },function(){
        $state.go('auth.login');
    })  
});
app.controller('LoginController',function($scope,$state,$http,$resource,Base64){
    $scope.login = function(){
        $scope.authError = ""
        var authdata = Base64.encode($scope.user.username + ':' + $scope.user.password);
        $http.defaults.headers.common['Authorization'] = 'Basic ' + authdata;
        var $com = $resource($scope.app.host + "/auth/info/?");
        $com.get(function(){
            $state.go('app.dashboard');
        },function(){
            $scope.authError = "服务器登录错误"
        })
    }
});
app.factory('Base64',function(){
    var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
    return {
        encode: function (input) {
            var output = "";
            var chr1, chr2, chr3 = "";
            var enc1, enc2, enc3, enc4 = "";
            var i = 0;

            do {
                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);

                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;

                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                }

                output = output +
                    keyStr.charAt(enc1) +
                    keyStr.charAt(enc2) +
                    keyStr.charAt(enc3) +
                    keyStr.charAt(enc4);
                chr1 = chr2 = chr3 = "";
                enc1 = enc2 = enc3 = enc4 = "";
            } while (i < input.length);

            return output;
        },

        decode: function (input) {
            var output = "";
            var chr1, chr2, chr3 = "";
            var enc1, enc2, enc3, enc4 = "";
            var i = 0;

            // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
            var base64test = /[^A-Za-z0-9\+\/\=]/g;
            if (base64test.exec(input)) {
                window.alert("There were invalid base64 characters in the input text.\n" +
                    "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" +
                    "Expect errors in decoding.");
            }
            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

            do {
                enc1 = keyStr.indexOf(input.charAt(i++));
                enc2 = keyStr.indexOf(input.charAt(i++));
                enc3 = keyStr.indexOf(input.charAt(i++));
                enc4 = keyStr.indexOf(input.charAt(i++));

                chr1 = (enc1 << 2) | (enc2 >> 4);
                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                chr3 = ((enc3 & 3) << 6) | enc4;

                output = output + String.fromCharCode(chr1);

                if (enc3 != 64) {
                    output = output + String.fromCharCode(chr2);
                }
                if (enc4 != 64) {
                    output = output + String.fromCharCode(chr3);
                }

                chr1 = chr2 = chr3 = "";
                enc1 = enc2 = enc3 = enc4 = "";

            } while (i < input.length);

            return output;
        }
    };
})

编写template(模版)

<!--admin/auth/loading.html-->
<div class="form-group" ng-controller="LoadingController">
    <div class="col-md-12 text-center">
        <span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>&nbsp;&nbsp;loading...
    </div>
</div>
<!--admin/auth/login.html-->
<div class="container w-xxl w-auto-xs" ng-controller="LoginController">
  <a href class="navbar-brand block m-t">{{app.name}}</a>
  <div class="m-b-lg">
    <div class="wrapper text-center">
      <strong>Sign in to get in touch</strong>
    </div>
    <form name="form" class="form-validation">
      <div class="list-group list-group-sm">
        <div class="list-group-item">
          <input type="text" placeholder="username" class="form-control no-border" ng-model="user.username" required>
        </div>
        <div class="list-group-item">
           <input type="password" placeholder="password" class="form-control no-border" ng-model="user.password" required>
        </div>
      </div>
      <button type="submit" class="btn btn-lg btn-primary btn-block" ng-click="login()" ng-disabled='form.$invalid'>Log in</button>
      <div class="line line-dashed"></div>
      <div class="text-danger wrapper text-center" ng-show="authError">
          {{authError}}
      </div>
    </form>
  </div>
</div>

改写路由

// admin/router.js
...
          $urlRouterProvider
              .otherwise('/auth/loading');
          $stateProvider
              .state('auth',{
                  abstract: true,
                  url:'/auth',
                  template: '<div ui-view class="fade-in"></div>',
                  resolve: {
                      deps: ['$ocLazyLoad',
                        function( $ocLazyLoad ){
                          return $ocLazyLoad.load('admin/auth/ctrl.js');
                      }]
                  }
              })
              .state('auth.loading',{
                  url:'/loading',
                  templateUrl:'admin/auth/loading.html',
              })
              .state('auth.login',{
                  url:'/login',
                  templateUrl:'admin/auth/login.html',
              })

...

代码挺多,就不逐行解释了,最核心的就是:
用Base64加密[用户名:密码],在请求头加入 Authorization: Basic [加密串]

var authdata = Base64.encode(username + ":" + password);        $http.defaults.headers.common['Authorization'] = 'Basic ' + authdata;

改完以后重新访问,将实现流程图中的home->loading->login->dashboard。
事情还没完,我们再重新访问,又会重复这个流程,并不会自动登录,这是因为BasicAuth的特性是在每一次web请求的时候都需要加入Authorization请求头才行。所以我们还要做点工作:1.登录成功以后将authdata存在本地,2.给全局http请求统一加入这个请求头

// admin/auth/ctrl.js
...
app.controller('LoginController',function($scope,$state,$http,$resource,Base64,$localStorage)
...
        $com.get(function(){
            $localStorage.auth = authdata;
            $state.go('app.dashboard');
        },function(){
            $scope.authError = "服务器登录错误"
        })
...
// admin/router.js
app
.run(
    function ($rootScope,   $state,   $stateParams,$localStorage,$http) {
          $http.defaults.headers.common['Authorization'] = 'Basic ' + $localStorage.auth;
          $rootScope.$state = $state;
          $rootScope.$stateParams = $stateParams;
      }
  )
...

重新刷新首页,页面将实现自动登录了,但是事情还没完,进入系统以后,虽然每次Web请求我们都加入了BasicAuth的请求头,但是如果服务器端做了帐号修改,一样会产生401的错误,产生的结果就是客户端点什么操作都不会有反应,我们应该在全局来拦截401,引导客户端跳转到重新登录的界面:

// admin/router.js
...
app.config(function ($httpProvider) {
  $httpProvider.interceptors.push('AuthInterceptor');
})
app.factory('AuthInterceptor', function ($rootScope, $q,$location) {
  return {
    responseError: function (response) {
        if(response.status==401)
        {
            $location.url('/auth/login');
        }
      return $q.reject(response);
    }
  };
})

大功即将告成,还有最后一步,有了登录必然有登出,BasicAuth协议本身是没有登出概念的,我们这里做的登出,就是删除本地那个保存的authdata。

// admin/main.js
angular.module('app')
  .controller('AppCtrl', ['$scope', '$translate', '$localStorage', '$window', '$state','$http',
    function(              $scope,   $translate,   $localStorage,   $window ,$state,$http) {
...
    $scope.logout = function(){
        $localStorage.auth = null;
        $http.defaults.headers.common['Authorization'] = "Basic";
        $state.go("auth.login");
    }
    function isSmartDevice( $window ){...}
<!--admin/blocks/header.html-->
...
            <!-- dropdown -->
            <ul class="dropdown-menu animated fadeInRight w">
              <li>
                <a ng-click="logout()">Logout</a>
              </li>
            </ul>
            <!-- / dropdown -->

...

锦上添花,标准的后台系统一般会在页面右上角显示登录用户的帐号信息,我们定义的协议/auth/info/是会把这些信息带下来的,我们来补全一下这个功能:

// admin/auth/ctrl.js
app.controller('LoadingController',function($scope,$resource,$state,$localStorage){
    var $com = $resource($scope.app.host + "/auth/info/?");
    $com.get(function(data){//引入data
        $scope.session_user = $localStorage.user = data; //保存用户信息
        $state.go('app.dashboard');
    })  
});
app.controller('LoginController',function($scope,$state,$http,$resource,Base64,$localStorage){
    $scope.login = function(){
        $scope.authError = ""
        var authdata = Base64.encode($scope.user.username + ':' + $scope.user.password);
        $http.defaults.headers.common['Authorization'] = 'Basic ' + authdata;
        var $com = $resource($scope.app.host + "/auth/info/?");
        $com.get(function(data){//引入data
            $scope.session_user = $localStorage.user = data; //保存用户信息
            $localStorage.auth = authdata;
            $state.go('app.dashboard');
        },function(){
            $scope.authError = "服务器登录错误"
        })
    }
});
...
// admin/main.js
...
$scope.session_user = $localStorage.user;
$scope.logout = function(){...}
<!--admin/blocks/header.html-->
...
<span class="hidden-sm hidden-md">{{session_user.username}}</span> <b class="caret"></b>
...

4) CRUD

创建news目录

> mkdir admin\news\

增加news导航

<!-- admin/blocks/nav.html -->
<!-- list -->
<ul class="nav">
  <li class="hidden-folded padder m-t m-b-sm text-muted text-xs">
    <span translate="导航">导航</span>
  </li>  
  <li ui-sref-active="active">
    <a ui-sref="app.news.list">
      <i class="glyphicon glyphicon-book icon text-info-dker"></i>
      <span class="font-bold">新闻管理</span>
    </a>
  </li>
</ul>
<!-- / list -->

书写控制器

// admin/news/ctrl.js
'use strict';

app.controller('ListController', function($scope, $resource,$stateParams,$modal,$state) {
    //查询
    $scope.query = function(page,filter){
        var $com = $resource($scope.app.host + "/news/?page=:page&search=:filter",{page:'@page',filter:'@filter'});
        if(!page){
            page=1;
        }else{
            page=parseInt(page);
        }
        $com.get({page:page,filter:filter},function(data){
            //扩展分页数据,显示页签,最终效果为  < 1 2 3 4 5 >
            data.page_index = page;
            data.pages = [];    //页签表
            var N = 5;          //每次显示5个页签
            var s = Math.floor(page/N)*N;
            if(s==page)s-=N;
            s += 1;
            var e = Math.min(data.page_count,s+N-1)
            for(var i=s;i<=e;i++)
                data.pages.push(i)
            $scope.data = data;
            $scope.search_context = filter;
        });
    }
    //搜索跳转
    $scope.search = function(){
        $state.go('app.news.list',{search:$scope.search_context});
    }
    //全选
    var selected = false;
    $scope.selectAll = function(){
        selected = !selected;
        angular.forEach($scope.data.results,function(item){
            item.selected = selected;
        });
    }
    //自定义操作处理,其中1为删除所选记录
    $scope.exec = function(){
        if($scope.operate=="1"){
            var ids = [];
            angular.forEach($scope.data.results,function(item){
                if(item.selected){
                    ids.push(item.id);
                }
            });
            if(ids.length>0){
                //弹出删除确认
                var modalInstance = $modal.open({
                    templateUrl: 'admin/confirm.html',
                    controller: 'ConfirmController',
                    size:'sm',
                });
                modalInstance.result.then(function () {
                      var $com = $resource($scope.app.host + "/news/deletes/?");
                      $com.delete({'ids':ids.join(',')},function(){
                          $state.go('app.news.list');
                      });
                });
            }
        }
    }
    //根据url参数(分页、搜索关键字)查询数据
    $scope.query($stateParams.page,$stateParams.search);
});

app.controller('ConfirmController', ['$scope', '$modalInstance', function($scope, $modalInstance){
     $scope.ok = function () {
        $modalInstance.close();
    };
    $scope.cancel = function () {
        $modalInstance.dismiss('cancel');
  };
}]);

app.controller('DetailController', function($rootScope,$scope, $resource, $stateParams,$state) {
  $scope.edit_mode = !!$stateParams.id;
  if($scope.edit_mode){
      var $com = $resource($scope.app.host + "/news/:id/?",{id:'@id'});
      var resp = $com.get({id:$stateParams.id},function(data){
          $scope.data = resp;
      });
  }
  else{
      $scope.data = {};
  }
  $scope.submit = function(){
      if($scope.edit_mode){
          var $com = $resource($scope.app.host + "/news/:id/?",{id:'@id'},{
              'update': { method:'PUT' },
          });
          $com.update({id:$stateParams.id},$scope.data,function(data){
              $state.go($rootScope.previousState,$rootScope.previousStateParams);
          });
      }
      else{
          var $com = $resource($scope.app.host + "/news/?");
          $com.save($scope.data,function(data){
              $state.go('app.news.list');
          });
      }
  };
  $scope.delete = function(){
      var $com = $resource($scope.app.host + "/news/:id/?",{id:'@id'});
      $com.delete({id:$stateParams.id},function(){
          $state.go('app.news.list');
      })
  }
});

书写列表模版

<!-- admin/news/list.html -->
<div class="wrapper-md" ng-controller="ListController">
  <div class="panel panel-default">
    <div class="panel-heading">
    <ul class="nav nav-pills pull-right">
            <li style=" padding-top:4px; padding-right:4px"><button class="btn m-b-xs btn-sm btn-primary btn-addon" ui-sref="app.news.create()"><i class="fa fa-plus"></i>新增</button></li>
          </ul>
      新闻列表
    </div>
    <div class="row wrapper">
      <div class="col-sm-5 m-b-xs">
        <select class="input-sm form-control w-sm inline v-middle" ng-model="operate" ng-init="operate=0">
          <option value="0">---</option>
          <option value="1">删除所选记录</option>
        </select>
        <button class="btn btn-sm btn-default" ng-click="exec()">执行</button>                
      </div>
      <div class="col-sm-4">
      </div>
      <div class="col-sm-3">
        <div class="input-group">
          <input type="text" class="input-sm form-control" placeholder="Search" ng-model="search_context">
          <span class="input-group-btn">
            <button class="btn btn-sm btn-default" ng-click="search()" type="button">Go!</button>
          </span>
        </div>
      </div>
    </div>
    <div class="table-responsive" ng-if="data.total_count>0">
      <table class="table table-striped b-t b-light">
        <thead>
          <tr>
            <th style="width:20px;">
              <label class="i-checks m-b-none">
                <input type="checkbox" ng-click="selectAll()"><i></i>
              </label>
            </th>
            <th>标题</th>
            <th>创建时间</th>
            <th style="width:30px;"></th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="data in data.results">
            <td><label class="i-checks m-b-none"><input type="checkbox" ng-model="data.selected"><i></i></label></td>
            <td>{{data.title}}</td>
            <td>{{data.create_time|date:'yyyy-MM-dd HH:mm:ss Z'}}</td>
            <td>
              <a ui-sref="app.news.detail({id:data.id})" class="active"><i class="fa fa-edit"></i></a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <footer class="panel-footer">
      <div class="row">
        <div class="col-sm-8 text-left">
          <small class="text-muted inline m-t-sm m-b-sm">{{data.total_count}}条记录</small>
        </div>
        <div ng-if="data.page_count>1" class="col-sm-4 text-right text-center-xs">                
          <ul class="pagination pagination-sm m-t-none m-b-none">
            <li ng-class="{disabled:!data.previous}"><a ui-sref="app.news.list({page:data.page_index-1,search:search_context})"><i class="fa fa-chevron-left"></i></a></li>
            <li ng-repeat="page in data.pages" ng-class="{active:page==data.page_index}"><a ui-sref="app.news.list({page:page,search:search_context})">{{page}}</a></li>
            <li ng-class="{disabled:!data.next}"><a ui-sref="app.news.list({page:data.page_index+1,search:search_context})"><i class="fa fa-chevron-right"></i></a></li>
          </ul>
        </div>
      </div>
    </footer>
  </div>
</div>

书写详情、新增的模版,两者是可以复用一个模版的

<!-- admin/news/detail.html -->
<div ng-controller="DetailController">
    <div class="wrapper-md" >
        <div class="panel panel-default">
        <form class="form-horizontal ng-pristine ng-valid ng-valid-date ng-valid-required ng-valid-parse ng-valid-date-disabled" ng-submit="submit()">
            <div class="panel-body">
                <div class="form-group">
                  <label class="col-sm-2 control-label">标题</label>
                  <div class="col-sm-10">
                    <input type="text" class="form-control" ng-model="data.title" required>
                  </div>
                </div>
                <div class="line line-dashed b-b line-lg pull-in"></div>
                <div class="form-group">
                  <label class="col-sm-2 control-label">内容</label>
                  <div class="col-sm-10">
                    <textarea class="form-control" rows="6" ng-model="data.content"></textarea>
                  </div>
                </div>
            </div>
            <footer class="panel-footer text-right bg-light lter">
                    <input type="button" ng-click="delete()" ng-if="edit_mode" class="btn btn-danger pull-left" value="删除"/>
                      <input type="submit" class="btn btn-success" value="提交"/>
             </footer>
         </form>
      </div>
    </div>
</div>

书写删除确认框模版

<!-- admin/confirm.html -->
<div class="modal-header">
    <h3>确认删除?</h3>
</div>
<div class="modal-footer">                  
    <button class="btn btn-default" ng-click="cancel()">Cancel</button>
    <button class="btn btn-primary" ng-click="ok()">OK</button>
</div>

添加路由,注意run里面增加了事件监听,后文详细说

// admin/router.js
app
  .run(
      function (...) {
          ...       
          $rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams) {
            $rootScope.previousState = from;
            $rootScope.previousStateParams = fromParams;
          });
      }
  )
...
              .state('app.news', {
                  abstract: true,
                  url: '/news',
                  template: '<div ui-view class="fade-in"></div>',
                  resolve: {
                      deps: ['$ocLazyLoad',
                        function( $ocLazyLoad ){
                          return $ocLazyLoad.load('admin/news/ctrl.js');
                      }]
                  }
              })
              .state('app.news.list', {
                  url: '/list?page&search',
                  templateUrl: 'admin/news/list.html',
              })
              .state('app.news.detail', {
                  url: '/detail/{id}',
                  templateUrl: 'admin/news/detail.html',
              })
              .state('app.news.create', {
                  url: '/create',
                  templateUrl: 'admin/news/detail.html',
              })
...

成果如下
这里写图片描述
这里写图片描述

我们这里搜索和分页都是采用URL跳转的方式(#/news/list/?search=str&page=int),这样能保证刷新页面的时候能停留在之前的页面结果上,ng默认的页面跳转是不保留前一个页面状态的(链接和参数),如果我们跳转到第2页,编辑,再返回,是会回到第1页去,为了比较好的用户体验所以我们有了如下代码

监听全局页面跳转信号($statChangeSuccess),将参数保存下来

// admin/router.js
app.run(
    ...
    $rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams) {
            $rootScope.previousState = from;
            $rootScope.previousStateParams = fromParams;
          });
    ...
)

详情页编辑完成返回时读取参数跳转

// admin/news/ctrls.js
app.controller('DetailController',...){
    ...
    $com.update({id:$stateParams.id},$scope.data,function(data){
              $state.go($rootScope.previousState,$rootScope.previousStateParams);
          });
    ...
});

完整的后台少不了导航
这里写图片描述
我们这里选用github上面一个写得挺棒的ng导航插件angular-breadcrumb,求简,我们直接下载https://raw.github.com/ncuillery/angular-breadcrumb/master/dist/angular-breadcrumb.min.js,由于我们是给ng装插件,所以建议放到vendor/里面去,接下来的改动也是针对原模版框架。

注入JS

<!-- index.html -->
...
<script src="vendor/angular/angular-breadcrumb/angular-breadcrumb.min.js"></script>
...

引入模块

// js/app.js
angular.module('app', [
    ...
    'ncy-angular-breadcrumb',
]);

配置扩展,让导航能支持HTML(具体就是能显示回首页的图标)
这里写图片描述

// js/config.js
...
app.config(function($breadcrumbProvider) {
    $breadcrumbProvider.setOptions({
      templateUrl: 'tpl/blocks/breadcrumb.html'
    });
  });

扩展的导航模版,写法参考插件官方文档

<!-- tpl/blocks/breadcrumb.html -->
<ul class="breadcrumb bg-white b-a">
    <li ng-repeat="step in steps | limitTo:(steps.length-1)">
        <a href="{{step.ncyBreadcrumbLink}}" ng-bind-html="step.ncyBreadcrumbLabel"></a>
    </li>
    <li ng-repeat="step in steps | limitTo:-1" class="active">
        <span ng-bind-html="step.ncyBreadcrumbLabel"></span>
    </li>
</ul>

配置路由,加入导航

// admin/router.js
...
              .state('app.dashboard', {
                  ...
                  ncyBreadcrumb: {
                    label: '<i class="fa fa-home"></i> 首页'
                  }
              })
              ...
              .state('app.news.list', {
                  ...
                  ncyBreadcrumb: {
                    parent:'app.dashboard',
                    label: '新闻列表',
                  }
              })
              .state('app.news.detail', {
                  ...
                  ncyBreadcrumb: {
                    parent:'app.news.list',
                    label: '编辑',
                  }
               })
              .state('app.news.create', {
                  ...
                  ncyBreadcrumb: {
                    parent:'app.news.list',
                    label: '新增',
                  }
              })
...

这里写图片描述

完整源码下载及前后端技术浅析在这里

阅读更多
想对作者说点什么? 我来说一句

angularjs管理系统框架

2015年08月09日 3.52MB 下载

BeyondAdmin AngularJS 1.60

2016年04月15日 3.69MB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭