公司在做一个OA系统, 包括移动端(从微信企业号进入OA系统),电脑端。
电脑端还是用的传统的easyui做界面,asp.net mvc作为服务端。这个技术已经很成熟了配合权限框架很快就能开发出来。但是手机端之前团队也没有什么经验,我之前做过一个jqureymobile的项目,但是这个框架实在是太卡了,直接pass掉。恰好之前有自学一点点angularjs 所以我就推荐手机端的项目使用angularjs来开发,一来团队可以学习一些新技术跟上潮流,二来单页程序很适合我们目前的这个小项目,三是我们以后打算用phonegap直接打包webapp。好吧优点这么多那就开工吧(我们用的是1.5版本,2.0国内还比较少)。
由于团队之前没有接触过这个框架,所以大概花了3天时间来学习(正常开发任务还是有,相对减少),可想而知以angularjs学习曲线我们的成果不会好到哪里去,项目不等人,
angularjs确实很不错给前端带来很多新的概念:mvc 模块化 单页 依赖注入 指令 等。首先我们遇到的问题是怎么团队开发,不可能大家都读写一个js文件吧,于是采用第三方的oclazyload.js来动态加载js和模板,路由采用angular-ui-router,其次我们需要一款手机端的ui来配合angularjs,我们选用了淘宝的sui。一切准备好了,那就开始吧!
进入正题,我的第一个angularjs的指令设计,需求如下:
1.OA填完单之后,需要选择审核人。(这个比较次了,正规的OA是不需要自己选择审核人的,但是我们是简单版的,况且大名鼎鼎的阿里巴巴 钉钉OA也是这么设计的)。
2.做成通用组件。
需求就这些了,那么直接上结果看看:
1.主页面
2.审批页面
3.用车申请
4.选人指令
以上顺便介绍了一下我的OA系统,那么下边再看看最终做出来的选审核人的指令:
1.
2.
3.
5.
好吧这个功能基本是钉钉的翻版。那么我如何来设计这个指令的呢,首先,弹出框这些ui都是来自于sui的ui插件,我做的事情就是用angularjs的指令来封装ui和一些逻辑,最终达到的通用的目标。上代码:
/* *用于选择部门及用户 *刘功葵 2016-4-9 * */ var departUser = angular.module("ktfOA"); //第一个指令,弹出部门和人员选择框 departUser.directive("departpopup", function ($http) { return { restrict: 'AECM',//<!-- departpopup --> //replace: true, transclude: true, scope: { pop: '@',//部门弹出框的id,为了让同一个页面多吃使用指令时,不出现重复ID peng: '@',//人员弹出框id selects: '=', //通过=绑定最终的选择结果,并传递到外层的controller users: '=', dowork: "&" //在选择用户时执行的方法 }, templateUrl: '/Areas/Phone/Directive/DepartUser/DepartUser.html', controller: function ($scope) { this.open = function () { $.popup('#' + $scope.pop); } }, link: function (scope) { scope.pop = scope.pop || "pop"; scope.peng = scope.peng || "peng"; scope.$emit('ngPageFinished'); //$emit 给父scope返回一个值 父scope可以监控这个值 然后执行一些代码 相当于事件传播 $http.post("/Phone/BusinessTravel/GetApiDepartList").success(function (msg) { scope.departList = msg; }) /*选择部门事件*/ scope.selectDepart = function (departId) { $.popup('#' + scope.peng); scope.formJson = { 'departId': departId } $http.post("/Phone/BusinessTravel/GetAipAdminUserList", scope.formJson).success(function (msg) { scope.adminUserList = msg; }) } scope.selects = []; /*选择用户事件*/ scope.selectUser = function () { scope.noPic = true; if (this.itemUsr.AbsoluteImgSrc == "" || this.itemUsr.AbsoluteImgSrc == undefined) { this.itemUsr.AbsoluteImgSrc = "/Images/UserHeadPic/default.jpg"; } for (var i = 0; i < scope.selects.length; i++) { if (scope.selects[i].UserNo == this.itemUsr.UserNo) { $.alert('用户已选过,不能重复!'); return; } } scope.selects.push(this.itemUsr); scope.users += this.itemUsr.UserNo + ","; $.closeModal('#' + scope.peng); $.closeModal('#' + scope.pop); if (scope.dowork != undefined) { scope.dowork(); } } } }; }) //第二个指令,依赖于第一个指令,用于显示最终的选择结果。 departUser.directive("userselect", function ($http) { return { restrict: 'AECM',//<!-- departpopup --> replace: true, require: '^?departpopup', //此处是指依赖上面一个指令 scope: { selects: "=", users: "=" }, template: ' <div> <div ng-click="delUser()" style="float:left;position:relative" ng-repeat="user in selects"><img style="border-radius:50%; height:56px;width:56px; overflow:hidden;" src="{{user.AbsoluteImgSrc}}" /><span style="position:absolute;left:7px;top:19px;font-size:small;color:white;text-align:center;width:40px;height:20px;" >{{user.UserName}}</span><span style="float:right;margin-top:8px;">...</span></div> <div style="float:left;margin-top:8px;"><a ng-click="Open()" class="open-about"><i class="fa fa-plus-circle fa-2x" style="color:#bab7b7"></i></a></div></div>', link: function (scope, element, attrs, departpopupController) { //$(".datetime-pickerStart").datetimePicker({}); //$(".datetime-pickerEnd").datetimePicker({}); scope.Open = function () { departpopupController.open(); } //删除选择的用户 scope.delUser = function () { var userNo = this.user.UserNo; for (var i = 0; i < scope.selects.length; i++) { if (scope.selects[i].UserNo == userNo) { scope.selects.splice(i, 1); break; } } scope.users = ""; for (var i = 0; i < scope.selects.length; i++) { scope.users += scope.selects[i].UserNo + ","; } } } }; })
<div> <div class="popup" id="{{pop}}"> <header class="bar bar-nav"> <a class="icon icon-left pull-left close-popup"></a> </header> <div class="bar bar-header-secondary" style="position:relative"> <div class="searchbar"> <a ng-click="abc()" class="searchbar-cancel">取消</a> <div class="search-input"> <label class="icon icon-search" for="search"></label> <input type="search" id='search' placeholder='输入关键字...' /> </div> </div> </div> <div class="list-block contacts-block"> <div class="list-group"> <ul> <li ng-repeat="item in departList track by $index" class="open-services" ng-click="selectDepart(item.DepartID)" departid="{{item.DepartID}}"> <div class="item-content item-link"> <div class="item-inner"> <div class="item-title"> <span style="color:#c6c4c4">{{item.TreeText}}</span> {{item.DepartName}} </div> </div> </div> </li> </ul> </div> </div> </div> <div class="popup" id="{{peng}}"> <header class="bar bar-nav"> <a class="icon icon-left pull-left close-popup"></a> </header> <div class="bar bar-header-secondary" style="position:relative"> <div class="searchbar"> <a class="searchbar-cancel">取消</a> <div class="search-input"> <label class="icon icon-search" for="search"></label> <input type="search" id='search' placeholder='输入关键字...' /> </div> </div> </div> <div class="list-block contacts-block"> <div class="list-group"> <ul ng-repeat="itemUsr in adminUserList track by $index"> <li ng-click="selectUser()" userno="{{itemUsr.UserNo}}" username="{{itemUsr.UserName}}"> <div class="item-content"> <div class="item-inner"> <div class="item-title"> <span style="color:#c6c4c4">{{itemUsr.UserNo}}</span> {{itemUsr.UserName}} </div> </div> </div> </li> </ul> </div> </div> </div> <div ng-transclude></div> </div> DepartUser.html3.在车辆申请页面的使用
<!-- /************************************************************************/ /* * 创建人:向高 * 创建日期:2016-04-01 * 文件描述:车辆申请表单页面 */ /************************************************************************/ --> <div ng-controller="CarApply"> <header class="bar bar-nav"> <a class="button button-link button-nav pull-left" ui-sref="Index"> <span class="icon icon-left"></span> 返回 </a> <h1 class='title'>用车申请</h1> </header> <departpopup selects="selects" users="users" pop="pop" peng="peng1"> <div class="content" style="top:0.9rem"> <div class="list-block"> <ng-form name="CarForm" novalidate> <!--ng-submit="save(CarForm.$valid)"--> <ul> <!-- Text inputs --> <li> <div class="item-content"> <div class="item-media"><i class="icon icon-form-name"></i></div> <div class="item-inner"> <div class="item-title label inputSize">用车事由</div> <div class="item-input"> <textarea required name="AskReason" ng-model='Apply.AskReason' style="font-size:14px;" placeholder="请输入(必填)"></textarea> <!--<input type="text">--> </div> </div> </div> </li> <li> <div class="item-content"> <div class="item-media"><i class="icon icon-form-email"></i></div> <div class="item-inner"> <div class="item-title label inputSize">始发地点</div> <div class="item-input"> <input type="text" required name="LevelPlace" ng-model="Apply.LevelPlace" style="font-size:14px;" placeholder="请输入(必填)"> </div> </div> </div> </li> <li> <div class="item-content"> <div class="item-media"><i class="icon icon-form-gender"></i></div> <div class="item-inner"> <div class="item-title label inputSize">返回地点</div> <div class="item-input"> <input type="text" required name="EndPlace" ng-model="Apply.EndPlace" style="font-size:14px;" placeholder="请输入(必填)"> </div> </div> </div> </li> <li> <div class="item-content"> <div class="item-media"><i class="icon icon-form-gender"></i></div> <div class="item-inner"> <div class="item-title label inputSize">用车时间</div> <div class="item-input"> <input type="text" name="LeaveTimeFrom" required ng-model="Apply.LeaveTimeFrom" id="datetime-pickerStart" style="font-size:14px;" placeholder="请输入(必填)"> </div> </div> </div> </li> <li> <div class="item-content"> <div class="item-media"><i class="icon icon-form-gender"></i></div> <div class="item-inner"> <div class="item-title label inputSize">返程时间</div> <div class="item-input"> <input type="text" name="LeaveTimeTo" required ng-model="Apply.LeaveTimeTo" id="datetime-pickerEnd" style="font-size:14px;" placeholder="请输入(必填)"> </div> </div> </div> </li> <li ng-repeat="cars in cars" on-finish-render-filters> <div class="item-content" style="top:0.7rem;padding-left:20px;background:#f3f2f2;line-height:55px;font-size:14px;height:40px"> <div style="width:85px;float:left"> <span>车辆明细({{$index+1}})</span></div> <div style="width:70px;float:right;padding-right:15px;" ng-if="$index+1>1"><span><a href="javascript:void(0)" ng-click="deleteCar()">删除</a></span></div> </div> <!--<div class="content-block-title xingcheng" style="margin-left:30px;font-size:small">车辆明细({{$index+1}})</div>--> <div class="item-content"> <div class="item-media"><i class="icon icon-form-gender"></i></div> <div class="item-inner"> <div class="item-title label inputSize">车辆选择</div> <div class="item-input"> <input type="text" class='picker' required name="BusInfo" ng-model="cars.BusInfo" style="font-size:14px;" placeholder="请选择(必选)"> </div> </div> </div> <div class="item-content"> <div class="item-media"><i class="icon icon-form-gender"></i></div> <div class="item-inner"> <div class="item-title label inputSize">数量</div> <div class="item-input"> <input type="text" ng-change="getCarNum()" required name="AskNum" ng-model="cars.AskNum" required style="font-size:14px;" placeholder="请输入(必填)"> </div> </div> </div> <div class="item-content"> <div class="item-media"><i class="icon icon-form-gender"></i></div> <div class="item-inner"> <div class="item-title label inputSize">其他要求</div> <div class="item-input"> <input type="text" name="Remark" ng-model="cars.Remark" style="font-size:14px;" placeholder="请输入"> </div> </div> </div> </li> <li><div class="content-block-title xingcheng" style="margin-left:30px;font-size:small">如需多种车型,请点击“添加车辆”</div></li> <li style="text-align:center;"><a style="cursor:pointer" ng-click="AddMore()"><i class="fa fa-plus" style="color:blue"></i>添加车辆</a></li> <li><div class="item-title label inputSize" style="margin-left:30px;">总数量(辆):{{carnum}}</div></li> <li> <div class="item-content"> <div class="item-media"><i class="icon icon-form-gender"></i></div> <div class="item-inner"> <div class="item-title label inputSize">备注</div> <div class="item-input"> <textarea name="Remark" ng-model='Apply.Remark' style="font-size:14px;" placeholder="请输入"></textarea> </div> </div> </div> </li> <li style="padding-left:30px; height:110px;"> <div><span>审批人</span><span style="font-size:small;color:#999999">(点击头像可删除)</span></div> <userselect selects="selects" users="users"></userselect> </li> </ul> </ng-form> </div> <div class="content-block"> <div class="row"> <div class="col-50"><a href="#" class="button button-big button-fill button-danger">取消</a></div> <div class="col-50"><a ng-class="{true:'button button-big button-fill button-success',false:'button button-big button-fill button-success disabled'}[CarForm.$valid]" ng-click="save(CarForm.$valid)" >提交</a></div> <!--<a ng-class="{true:'button button-big button-fill button-success',false:'button button-big button-fill button-success disabled'}[CarForm.$valid]" ng-click="save(CarForm.$valid)" class="button button-big button-fill button-success">提交</a>--> </div> </div> </div> </departpopup> </div> CarApply.html
//var CarApplyModule = angular.module("CarApplyModule", ['ngAnimate']); /************************************************************************/ /* * 创建人:向高 * 创建日期:2016-04-01 * 文件描述:车辆申请控制器 */ /************************************************************************/ var CarApplyModule = angular.module("ktfOA"); CarApplyModule.controller("CarApply", function ($scope, $http, CalcService, $state, $stateParams) { var selectCars = []; //保存根据用车时间 获取可选车辆 $scope.cars = [{ BusInfo: "", AskNum: 1, Remark: "" }]; //用于初始化repeater $scope.carnum = parseInt($scope.cars[0].AskNum); $scope.users = "";//审核人 $scope.selects = []; //选人控件 传回来的选择用户集合 //$scope.selectCars = []; //保存根据用车时间 获取可选车辆 $scope.Apply = { AskReason: "", LevelPlace: "", EndPlace: "", LeaveTimeFrom: "", LeaveTimeTo: "", Remark: "", Detail: $scope.cars, users: "" }; $scope.getCarNum = function () { var num = 0; for (var i = 0; i < $scope.cars.length; i++) { if ($scope.cars[i].AskNum != undefined) { num += parseInt($scope.cars[i].AskNum); } } $scope.carnum = num; } //提交表单数据 $scope.save = function () { if ($scope.selects.length <= 0) { $.alert("请选择审核人!"); return; } //获取审核人 var users = ""; for (var i = 0; i < $scope.selects.length; i++) { users += $scope.selects[i].UserNo + ","; } $scope.Apply.users = users; $http({ method: 'POST', url: '/phone/CarApply/SaveCarApply', data: JSON.stringify($scope.Apply) }). success(function (response) { if (response.status == 2) { $.alert(response.msg); } else if (response.status == 1) { $.alert(response.msg, function () { $state.go("AuditHome"); }); } }); } //添加车辆 $scope.AddMore = function () { $scope.cars.push({ BusInfo: "", AskNum: "", Remark: "" }); } //删除车 $scope.deleteCar = function () { var index = this.$index; $scope.cars.splice(index,1); } //选取用车时间时 获取车辆信息 $scope.GetCars = function () { if ($scope.Apply.LeaveTimeFrom != "") { $http({ method: 'POST', url: '/phone/CarApply/GetCars', data: { Stime: $scope.Apply.LeaveTimeFrom } }).success(function (data) { //var selects = []; if (selectCars.length > 0) { selectCars.splice(0, selectCars.length); } for (var i = 0; i < data.length; i++) { selectCars.push(data[i].BusName + data[i].BusType + "(" + data[i].BusSiteCount + "座)" + " 可用数量" + data[i].CarNum); } $(".picker").picker("setValue", selectCars); }) } } $scope.$on('ngRepeatFinished', function (ngRepeatFinishedEvent) { //初始化sui的选择器控件 $(".picker").picker({ toolbarTemplate: '<header class="bar bar-nav">\<button class="button button-link pull-left">按钮</button>\<button class="button button-link pull-right close-picker">确定</button>\<h1 class="title">车辆信息</h1>\</header>', cols: [ { textAlign: 'center', values: selectCars //values: ['奥迪A6L(5座)', '丰田商务车(7座)', '丰田卡罗拉(5座)', '大众帕萨特(5座)', '宇通大巴(47座)', '宇通中巴(24座)', 'iPad 2', 'iPad Retina', 'iPad Air', 'iPad mini', 'iPad mini 2', 'iPad mini 3'] } ] }); }); $scope.$on('ngPageFinished', function (ngPageFinishedEvent) { $("#datetime-pickerStart").datetimePicker({ onClose: function () { $scope.GetCars(); } }); $("#datetime-pickerEnd").datetimePicker({}); }) $scope.getNum = function () { var num = 0; for (var i = 0; i < $scope.cars.length; i++) { num += $scope.cars[i].AskNum; } return num; } }) CarApplyModule.directive('onFinishRenderFilters', function ($timeout) { return { restrict: 'A', //匹配模式:A(属性方式)E(元素)M(注释 <!-- directive:xx -->)C(class方式 class = xx),angular 默认使用A link: function (scope, element, attr) { if (scope.$last === true) { $timeout(function () { scope.$emit('ngRepeatFinished'); }); } } }; }); CarApplyController
简单的使用方式:
<departpopup selects="selects" users="users" pop="pop" peng="peng1">
<userselect selects="selects" users="users"></userselect>
</departpopup>
注意两个指令是嵌套的关系。selects和users等就是外部controller和指令之间的绑定,用于传递数据。
以上就是我的第一个angularjs指令。下边谈谈遇到的坑:
首先我只用一个指令来搞定,但是不行,因为sui必须将弹出框放在body元素里边,不能放在li元素,否则就弹不出来。我想到这是样式的问题,于是手动来更改sui弹出框的定位样式,但是这不是一个好办法,电脑上还可以,但是手机上会出现兼容问题。最后我想到可以用两个指令来写这个插件,外层的<departpopup>负责弹出框(放在body标签里边),内部的<userselect>用于显示最终的结果。这会有涉及两个指令之间的数据传递,和相互调用。
大概就这些,我也是刚开始学习,路过的请不惜赐教。感谢!