當前我們項目的界面是這樣的:
簡單來說,看不出一點管理系統的樣子,到Axure中文網找了一下,管理類網站的原型應該是下面這個樣子的:
照著擼一個唄,管理系統界面一般由頂部導航&狀態欄、左側導航欄、右側內容區域三個部分組成,也就是這樣的:
我們網站內容較少所以頂部無需導航欄,也就是頂部只需保留左側標題和右側狀態部分,接著再填充一下界面,頂部左側加入系統名稱和logo,頂部右側加入用戶名,左側導航欄填充入導航項,右側內容欄根據左側選中的導航項顯示對應的內容:
填充完布局之后好像變得能看了一些,那我們就根據效果圖來完成這個界面。
借助框架能夠快速實現整體樣式,選用最通用的樣式框架Bootstrap3來協助完成界面,但是如果想要引入Bootstrap3控件的話需要引入jQuery,這是我們不想要的。
這時候我們可以考慮使用和Angular項目的相性最佳的angular-ui-bootstrap來取代Bootstrap3的控件,angular-ui-bootstrap是AngularUI團隊在Bootstrap基礎上用AngularJS實現的一組UI控件,在達到和Bootstrap控件相同效果的情況下還無需引入jQuery簡直棒。
于是我們簡單選定了Bootstrap + angular-ui-bootstrap組合來加速界面開發。
參考angular-ui-bootstrap文檔-Dependencies,了解到其版本的選擇與Angular的版本有對應關系,不過我們用的是AngularJS1.6.10版本所以可以直接安裝最新的angular-ui-bootstrap:
yarn add angular-ui-bootstrap --save
由于上面的文檔還提到angular-ui-bootstrap需要Angular-animate、Angular-touch、Bootstrap CSS,直接安裝yarn安裝順便更新一下angular到1.7.0:
yarn add angular --save yarn add angular-animate --save yarn add angular-touch --save yarn add bootstrap@3 --save
在app.js中加入引用并為'pokemon-app'模塊加入依賴(暫時不加入Bootstrap3樣式):
import ngAnimate from 'angular-animate'; import ngTouch from 'angular-touch'; import uibootstrap from 'angular-ui-bootstrap'; ... angular.module('pokemon-app', [ ... ngAnimate, ngTouch, ngUIBootstrap ... ])
在index.tpl.html中加入一段文檔中的測試代碼:
<h4>Single toggle</h4> <pre>{{singleModel}}</pre> <button type="button" class="btn btn-primary" ng-model="singleModel" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0"> Single Toggle </button>
接著在app.js中的AppController中加入:
$scope.singleModel = 1;
結果如下:
中間多出了一個button并且可以通過點擊修改數字,這表示angular-ui-bootstrap已經安裝成功了~
接下來我們為項目加入Bootstrap.css,CSS可以通過Webpack打包然后在項目入口文件app.js中加載,這里我們要用到css-loader、style-loader、file-loader(加載字體,如果沒有這個loader字體會加載失敗):
yarn add css-loader style-loader file-loader --save-dev
修改webpack.config.js的module如下:
module: { rules: [{ test: /\.html$/, loader: 'raw-loader' }, { // 負責css模塊加載 test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(woff|woff2|eot|ttf|svg)$/, use: ['file-loader'] }] },
app.js中引入
import 'bootstrap/dist/css/bootstrap.min.css';
現在查看自動重載之后的頁面,你會發現熟悉的Bootstrap頁面樣式終于出現了:
首先去掉上面添加的angular-ui-bootstrap測試代碼,然后開始界面開發:
頂部欄使用navbar樣式編寫,去掉原來的h1標簽然后左邊填充icon和系統名右邊填充用戶名,編寫代碼如下(圖源來自神奇寶貝百科,承諾不用于商業用途):
<nav class="navbar navbar-inverse navbar-fixed-top"> <p class="container-fluid"> <p class="navbar-header"> <a href="#" class="navbar-brand"> <img width="20" height="20" src="http://s1.52poke.wiki/wiki/5/5e/Bag_%E7%B2%BE%E7%81%B5%E7%90%83_Sprite.png"> </a> <a href="#" class="navbar-brand">口袋妖怪管理系統v0.0.1</a> </p> <p id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Nodreame</a></li> </ul> </p> </p> </nav>
效果如下:
為了防止鏈接丟失導致圖片加載失敗,把圖片下載下來放在本地assert/img文件夾下,命名為spriteball-common.png。按照Webpack模塊化規則,圖片也應該作為一個模塊來加載,于是參考文檔url-loader,在webpack.config.js文件的module中加入:
{ test: /\.(png|svg|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192, fallback: 'file-loader' } } ] }
用url-loader作為小于8192byte圖片的加載器,如果符合條件圖片鏈接將會轉為一個DataURL,如果超過改限制,將會默認使用file-loader作為圖片的加載器,修改后重新編譯通過.
現在繼續修改index.tpl.html中圖片位置的a標簽,加入id="icon"并屏蔽原來圖片:
<a href="#" class="navbar-brand" id="icon"> <!-- <img width="20" height="20" src="http://s1.52poke.wiki/wiki/5/5e/Bag_%E7%B2%BE%E7%81%B5%E7%90%83_Sprite.png" alt=""> --> <!-- <img src="../assert/img/spriteball-common.png" alt=""> --> </a>
在app.js中引入圖片,并通過DOM操作把圖片插入頁面:
import icon from '../assert/img/spriteball-common.png' ... function AppController ($scope) { // $scope.singleModel = 1; var sysIcon = new Image(); sysIcon.src = icon; sysIcon.width = 20; sysIcon.height = 20; document.getElementById('icon').appendChild(sysIcon); }
重新編譯,在瀏覽器元素檢測中看到圖片已成功插入頁面并以DataURL形式被引用:
頂部欄基本編寫完成~
頂部欄完成之后,左右將分成兩部分,這里的頁面布局劃分Bootstrap3似乎沒有提供響應的樣式,不過在Bootstrap的官網樣例中我們找到了類似的Dashboard,他提供了一個現成的dashboard.css我們可以直接用起來,將dashboard.css放到assert/css文件夾下,并在app.js中引用:
import '../assert/css/dashboard.css'
然后開始跟隨Demo簡單布局:
<p class="container-fluid"> <p class="row"> <p class="col-sm-3 col-md-2 sidebar">sidebar</p> <p class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">main</p> </p> </p>
很簡單就完成了頁面布局劃分(這里左側導航欄在小于768px時將自動隱藏):
然后繼續編寫左側導航欄:
<p class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li><a href="/#!/pokemons">圖鑒</a></li> <li><a href="/#!/skills">技能</a></li> <li><a href="/#!/hagberrys">樹果</a></li> <li><a href="/#!/props">道具</a></li> <li><a href="/#!/games">游戲</a></li> </ul> </p>
簡單的左側導航欄已經基本完成,并且點擊能夠看到內容切換,現在我們將被部分遮蔽的內容移動到右側內容區域:
<p class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <p ng-view></p> </p>
簡單移動完成頁面:
基礎頁面完成之后,看看內容樣式還是比較丑,跟隨dashboard例子和Bootstrap修改其樣式,對圖鑒頁(原來的口袋妖怪詳情頁)進行修改:
<h1 class="page-header">圖鑒</h1> <p class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>NO.</th> <th>名稱</th> <th>數量</th> <th>重量</th> <th>總計</th> <th>操作</th> </tr> </thead> <tbody> <tr ng-repeat="pokemon in pokemons"> <td>{{pokemon.no}}</td> <td><a href="/#!/pokemon/{{pokemon.no}}">{{pokemon.name}}</a></td> <td><input type="text" ng-model="pokemon.count"></td> <td>{{pokemon.weight}}</td> <td>{{pokemon.weight * pokemon.count}}</td> <td><button class="btn btn-xs btn-primary" ng-click="remove($index)">刪除</button></td> </tr> </tbody> </table> </p>
<p> <a href="/#!/pokemons"> <span class="glyphicon glyphicon-arrow-left"></span>返回圖鑒列表 </a> </p> <h2 class="sub-header"><b>{{pokemon.name}}</b></h2> <img ng-src="{{pokemon.img}}" width="200" height="200"> <p><b>編號: </b>No.{{pokemon.no}}</p> <p><b>體重: </b>{{pokemon.weight}}</p> <p><b>屬性: </b>{{pokemon.property}}</p> <p><b>種類: </b>{{pokemon.type}}</p> <p> <b>特性: </b> <ul> <li><b>普通特性: </b>{{pokemon.character.common}}</li> <li><b>隱藏特性: </b>{{pokemon.character.conceal}}</li> </ul> </p> <p ng-show="pokemon.forms"> <b style="float: left;">其他形象:</b><br/> <p ng-repeat="form in pokemon.forms" style="float: left;"> <img ng-src="{{form.src}}"> <p style="text-align: center;">{{form.name}}</p> </p> <p style="clear: both;"></p> </p>
上面代碼中,pm-list修改了標題和表格樣式,pm-detail修改了返回樣式并稍微修繕了布局,修改結果如下:
其他界面也進行類似的修改,結果如下:
至此基本網站布局已完成。
現在網站布局和樣式得到了優化,但是一些細節暫時還沒處理好,列出一些比較直觀能看到的不足:
左側導航欄交互缺乏選中感
刪除按鈕沒有二次確認容易導致誤刪
現在我們就來完善這些細節。
dashboard.css已經幫我們寫好了選中左側導航欄某項之后變藍底白字的樣式,只需要簡單在選中項的<li>元素上加上class="active"就行,但是如果用DOM操作來做這個交互就很繁瑣,所以考慮通過監聽頁面當前url來改變<li>元素的class,監聽頁面url當然是使用AngularJS的$location服務,該服務中有一個廣播$locationChangeSuccess,在每次url改變之后都會廣播事件,這里我們用它來修改全局對象nowUrl,我們在app.js中加入run:
.run(['$rootScope', '$location', function ($rootScope, $location) { $rootScope.$on('$locationChangeSuccess', function () { $rootScope.nowUrl = $location.url(); console.log('nowUrl:', $rootScope.nowUrl); // console.log('$route,routes.null.redirectTo:', $route.routes.null.redirectTo); }); }])
監聽頁面切換的日志結果如下:
ke'yi看到獲取到的nowUrl都是http://localhost:8080/#!
后面的部分,那么了解到這點之后我們就可以嘗試在index.tpl.html中借助ng-class指令來完成"根據當前url選中對應導航項"的操作了,修改index.tpl.html中<ul>元素部分如下:
<ul class="nav nav-sidebar"> <li ng-class="{'/pokemons': 'active'}[nowUrl]"><a href="/#!/pokemons">圖鑒</a></li> <li ng-class="{'/skills': 'active'}[nowUrl]"><a href="/#!/skills">技能</a></li> <li ng-class="{'/hagberrys': 'active'}[nowUrl]"><a href="/#!/hagberrys">樹果</a></li> <li ng-class="{'/props': 'active'}[nowUrl]"><a href="/#!/props">道具</a></li> <li ng-class="{'/games': 'active'}[nowUrl]"><a href="/#!/games">游戲</a></li> </ul>
為了達到二次確認刪除的效果,我們可以使用angular-ui-bootstrap提供的模態框Modal,參考Modal.
首先我們在src目錄下新建文件夾common來存放通用的html模板,新建文件deleteDialog.tpl.html作為模態框的模板文件:
<p class="modal-header"> <h3 class="modal-title" id="modal-title">{{modalTitle}}</h3> </p> <p class="modal-body" id="modal-body"> {{modalBody}} </p> <p class="modal-footer"> <button class="btn btn-danger" type="button" ng-click="ok()">刪除</button> <button class="btn btn-default" type="button" ng-click="cancel()">取消</button> </p>
接下來就可以編寫觸發模態框的邏輯了,模仿文檔修改pokemon.js中PMListController如下:
PMListController.$inject = ['$scope', '$uibModal']; function PMListController ($scope, $uibModal) { $scope.pokemons = pokemons; console.log($scope.pokemons); $scope.remove = function (index) { console.log('index:', index); var modalInstance = $uibModal.open({ animation: true, ariaLabelledBy: 'modal-title', ariaDescribedBy: 'modal-body', template: delDiage, controller: 'DeleteInstanceController', resolve: { pokemon: function () { return $scope.pokemons[index]; } } }); modalInstance.result.then(function (content) { console.log('Delete!', content); $scope.pokemons.splice(index, 1); }, function (content) { console.log('Cancel!', content); }); }; }
上面我們做了兩處修改:
1. 為PMListController加入了依賴$uibModal,用以調用模態框; 2. 修改remove方法,使用$uibModal.open()創建模態框實例,并用實例編寫模態框關閉的promise,關閉時選擇close或cancel將觸發不同事件。
完成了模態框觸發邏輯編寫之后,我們開始編寫模態框的邏輯:
DeleteInstanceController.$inject = ['$scope', '$uibModalInstance', 'pokemon']; function DeleteInstanceController ($scope, $uibModalInstance, pokemon) { // console.log('thisIndex:', thisIndex); console.log('pokemon:', pokemon); $scope.modalTitle = '刪除'; $scope.modalBody = '是否刪除' + pokemon.name + '的數據'; $scope.ok = function () { console.log('delete!'); $uibModalInstance.close(pokemon); }; $scope.cancel = function () { console.log('cancel!'); $uibModalInstance.dismiss('cancel'); }; }
這里加入了$uibModalInstance和pokemon依賴,$uibModalInstance代表當前模態框對象,pokemon是$uibModal.open()配置中resolve傳遞過來的數據。在該controller中完成模態框的內容編寫以及兩個button觸發的事件。接下來我們把這個controller加入module:
export default angular.module('pokemon-app.pokemon', [ngRoute]) .config(['$routeProvider', function ($routeProvider) { $routeProvider .when('/pokemons', { template: pmlist, controller: 'PMListController' }) .when ('/pokemon/:no', { template: pmdetail, controller: 'PMDetailController' }) }]) .controller('PMListController', PMListController) .controller('PMDetailController', PMDetailController) .controller('DeleteInstanceController', DeleteInstanceController) .name;
倒數第二行就是新加入module的controller,pm-list.html不用作任何修改,保存等待自動編譯重載。
至此,系統的操作體驗升級已經基本完成,為了系統體驗同步,我們需要把模態框的效果也應用到其他的界面上。
相信看了本文案例你已經掌握了方法,更多精彩請關注Gxl網其它相關文章!
推薦閱讀:
JS中的JSON和Math使用案例分析
react實現選中li高亮步驟詳解
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com