友情提醒:内容有点多
总体思路
thinkphp通过RESTful方式提供数据给angular,前端(包括模板页面)全部由angular来接管。
示例
实现一个用户管理模块,走通增删改查4个操作,通过该示例,演示如何在thinkphp中使用angularjs。
一. 准备工作
1. 加载所需的js和css文件
angular.min.js angular 核心库文件
angular-ui-router.min.js angular 路由插件
angular-resource.min.js 负责与服务端restful交互的插件
layer 弹窗插件,该插件依赖于jquery-1.10.1.min.js和jquery-ui.min.js两个库文件以及一个jquery-ui.min.css样式表
bootstrap.min.css bootstrap 核心样式表
文件结构如下:
2. 引导页
准备一个angular引导页,angular通过该引导页开启一个angular应用,后续所有的操作,都是基于该引导页进行的。
友情提醒:
这里的引导页,其实就是我们应用的默认页面,具体到thinkphp中,指的就是DEFAULT_MODULE/DEFAULT_CONTROLLER/DEFAULT_ACTION对应的模板文件。第一次访问应用时,thinkphp控制器会定向到该页面,之后的模板页面,全部由angular接管,跟thinkphp的模板引擎就没有半点关系了。
3. 应用首页
在引导页中通过<div ui-view="main"></div>包含我们的应用首页。便签的含义后面会有解释。
注意:这一步不是必须的,因为你也可以把应用首页的内容,全部放在引导页中,但是这样做就不够友好了。
4. 搭建RESTFul环境
为了能够使用$resource,我们必须让服务端按照RESTful的方式来工作,否则,$resource就无法发挥其作用了。
thinkphp中内置了对RESTful的支持,使用方式也很简单,就是让某个Controller继承自RestController,然后按照一定的规则来编写资源方法即可。但是它有一个缺点,thinkphp内置的RESTful对资源的访问方式不够友好,其访问资源的URL结构如下:
/模块名称/控制器名称/资源名称
基于此,我们考虑使用thinkphp的路由功能,来实现对RESTful的支持,使用thinkphp的路由功能时,控制器是没有必要继承RestController的,但为了尊重tp的劳动成果,同时,为了兼顾友好的资源访问方式,最终,我使用的是RestController+路由两者结合的方式。
补充:
资源方法的命名规则为:资源名称_REST类型
REST类型有如下几种:get,post,put,delete
5. 创建数据库表
创建用户表,并初始化一些数据
DROP TABLE IF EXISTS `an_user`; CREATE TABLE `an_user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL COMMENT '用户id', `user_name` varchar(100) NOT NULL COMMENT '用户名称', `email` varchar(255) DEFAULT NULL COMMENT '邮箱地址', `tel` varchar(255) DEFAULT NULL COMMENT '手机号码', `weixin` varchar(255) DEFAULT NULL COMMENT '微信号', `qq` varchar(255) DEFAULT NULL COMMENT 'qq号码', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表'; INSERT INTO `an_user` VALUES ('1', '1', 'demo1', 'demo1@qq.com', '13100000000', 'weixin_test1', '123456'); INSERT INTO `an_user` VALUES ('2', '2', 'demo2', 'demo2@qq.com', '13100000001', 'weixin_test2', '123456'); INSERT INTO `an_user` VALUES ('3', '3', 'demo3', 'demo3@qq.com', '13100000002', 'weixin_test3', '123456'); INSERT INTO `an_user` VALUES ('4', '4', 'demo4', 'demo4@qq.com', '13100000003', 'weixin_test4', '123456'); INSERT INTO `an_user` VALUES ('5', '5', 'demo5', 'demo5@qq.com', '13100000004', 'weixin_test5', '123456'); INSERT INTO `an_user` VALUES ('6', '6', 'demo6', 'demo6@qq.com', '13100000005', 'weixin_test6', '123456'); INSERT INTO `an_user` VALUES ('7', '7', 'demo7', 'demo7@qq.com', '13100000006', 'weixin_test7', '123456'); INSERT INTO `an_user` VALUES ('8', '8', 'demo8', 'demo8@qq.com', '13100000007', 'weixin_test8', '123456'); INSERT INTO `an_user` VALUES ('9', '9', 'demo9', 'demo9@qq.com', '13100000008', 'weixin_test9', '123456');二. 开始吧
1. 创建引导页
前面说过,引导页其实就是我们应用的默认页面,在我的项目中,默认页面的文件路径为:
/tpl/Admin/Index/index.html
在该文件中,加入如下内容:
<!DOCTYPE html> <html ng-app="antp"> <head> <meta charset="utf-8" /> <title></title> <meta content="width=device-width, initial-scale=1.0" name="viewport" /> <meta content="" name="author" /> <link href="/public/css/bootstrap.min.css" rel="stylesheet" type="text/css" media="all"> <link href="/public/css/jquery-ui.min.css" rel="stylesheet" type="text/css" media="all"> <script type="text/javascript" src="/public/js/angular.min.js"></script> <script type="text/javascript" src="/public/js/angular-resource.min.js"></script> <script type="text/javascript" src="/public/js/angular-ui-router.min.js"></script> <script type="text/javascript" src="/public/js/jquery-1.10.1.min.js"></script> <script type="text/javascript" src="/public/js/jquery-ui.min.js"></script> <script type="text/javascript" src="/public/js/layer/layer.js"></script> <script type="text/javascript" src="/config.js"></script> <script type="text/javascript" src="/app.js"></script> <link rel="shortcut icon" href="favicon.ico" /> </head> <body> <div class="container-fluid"> <div class="row clearfix"> <div class="col-md-12" style="float: none;display: block;margin-left: auto;margin-right: auto;"> <div ui-view="main"></div> </div> </div> </div> </body> </html>
页面中加载的js和css,除了config.js和app.js,其它的在前面都已经作了说明,这两个后面也会说到。
注意html标签中的ng-app指令,该指令的内容,就是angular要开启的应用名称。
2. 开启应用
之前的index.html文件中,通过ng-app指令定义了一个应用名称,定义完之后,我们还需要开启它,开启的方式也很简单,我们需要创建一个js文件,名称为app.js,在里面先加入如下内容:
var app = angular.module("antp",["ui.router","ngResource"]);
其中,module的第二个参数,是angular的一些依赖包。
3. 配置路由
使用ui.router的路由功能,通过config预先配置好访问url、该url对应的视图模板以及控制器等信息,同样是在app.js文件中,继续加入如下内容:
app.config(function($stateProvider, $urlRouterProvider, $locationProvider) { //启用HTML5模式的路由,该模式下会去除URL中的#号 //$locationProvider.html5Mode(true); //默认页面,所有请求不到的资源,都会转向到这个URL $urlRouterProvider.otherwise("/index"); $stateProvider.state("user", { url: "/user", views: { main: { templateUrl: "tpl/Admin/User/index.html", controller: "UserCtroller" } } }).state("index", { url: "/index", views: { main: { templateUrl: "tpl/Admin/Index/main.html", controller: "MainCtroller" } } }).state("user-add", { url: "/user/add", views: { main: { templateUrl: "tpl/Admin/User/add.html", controller: "UserFormCtroller" } } }).state("user-edit", { url: "/user/edit/:user_id", views: { main: { templateUrl: "tpl/Admin/User/add.html", controller: "UserFormCtroller" } } }); });
还记得之前引导页中的ui-view="main"吗?ui-view的名字,就是state中配置的views下面的属性名称。也许你会说,config里面配置了那么多的main,它是如何知道找的是哪个main?仔细看下代码就知道了。
注意1:控制器controller我们并没有手动写在某个页面的标签上,而是统一配置在了config里。
注意2:每个视图模板必须要对应一个控制器,并且这个控制器必须要被创建,否则,该视图将无法展示。
注意3:不要把此处的控制器和thinkphp里面的控制器搞混,事实上,他们两者之间没有任何关系。
4. 引入每个模块下的js
这里为了便于代码管理与维护,我在每个模块下面创建了一个js文件,每个模块的js完成特定的功能,这些js同样需要在app.js中被引入。
document.write('<script type="text/javascript" src="/tpl/Admin/Index/main.js"></script>'); document.write('<script type="text/javascript" src="/tpl/Admin/User/user.js"></script>');
5. 浏览器访问
在浏览器地址栏中,输入http://local.antp,将会看到如下效果:
仔细观察下地址栏,我们输入的是http://local.antp,但是却自动变成了http://local.antp/#/index,知道为什么吗?
首页,引导页中的<div ui-view="main"></div>,表示我需要路由到main这个视图,但是,我们输入的地址http://local.antp中,并没有告诉angular到底是哪个main,发生这种情况的时候,angular就会定向到默认的视图,默认的视图由$urlRouterProvider.otherwise("/index");这一句来指定。
由于/index指向的是tpl/Admin/Index/main.html,所以此处就会把main.html对应的内容展示出来。
main.html文件内容:
<div ng-include="'tpl/Admin/Public/header.html'"></div> <blockquote> <p> 我是默认页面,所有请求不到的资源,都会到我这里来... </p> </blockquote>
其中,通过ng-include指令,引入了一个公共导航页面,ng-include指令中双引号内的单引号不可少,内容如下:
<div style="margin-top:20px;"> <nav class="navbar navbar-default" role="navigation"> <div class="navbar-header"> <a class="navbar-brand" href="#">Brand</a> </div> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"> <a ui-sref="user">用户管理</a> </li> </ul> </div> </nav> </div>
6. 用户列表
点击导航中的【用户管理】,即可跳转到用户列表页面,如下:
该列表对应的视图文件为tpl/Admin/User/index.html,内容如下:
<div ng-include="'tpl/Admin/Public/header.html'"></div> <button type="button" class="btn btn-primary" ng-click="addAction()">新增</button> <table class="table table-bordered table-striped" style="margin-top:15px;"> <thead> <tr> <th>用户名</th> <th>邮箱</th> <th>手机号</th> <th>微信</th> <th>QQ</th> <th>操作</th> </tr> </thead> <tbody> <tr ng-repeat="user in data.user"> <td> <a ui-sref="user-edit({user_id:user.user_id})" ng-bind="user.user_name"></a> </td> <td ng-bind="user.email"></td> <td ng-bind="user.tel"></td> <td ng-bind="user.weixin"></td> <td ng-bind="user.qq"></td> <td> <button type="button" class="btn btn-link"> <a ui-sref="user-edit({user_id:user.user_id})">修改</a> </button> <button type="button" class="btn btn-link" ng-click="deleteAction(user.user_id)">删除</button> </td> </tr> </tbody> </table>
为了能够从后端拿到数据,我们需要创建一个$resource资源,创建方式如下:
//通过factory创建一个service,该service通过$resource返回了一个资源对象 //$resource负责与支持restful的服务端进行数据交互 app.factory("UserService", function($resource) { return $resource(globalConfig.API.URL + "users/:id", { id: "@id" }, { //query方法要求服务端返回的数据格式为数组,如果返回的是非数组格式,需要在transformResponse函数中作转换处理 query: { method: "GET", isArray: true, transformResponse: function(data) { return JSON.parse(data); } }, update: { method: "PUT" } }); });
这里,创建了一个名字为UserService的资源,然后,我们还需要创建一个控制器,将UserService资源注入进去,代码如下:
//用户列表Ctroller app.controller('UserCtroller', function($scope, $state, UserService) { $scope.data = {}; //获取用户列表 UserService.query().$promise.then( function(data){ //将查询结果赋值给data.user,模板中可以对data.user变量进行遍历 $scope.data.user = data; }, function(error) { console.log("An error occurred", error); } ); $scope.addAction = function() { $state.go("user-add"); }; $scope.deleteAction = function(user_id){ layer.confirm("确定要删除该用户吗", { btn: ['确定','取消'] }, function(index){ layer.close(index); UserService.remove({id:user_id}).$promise.then( function(res){ if(res.status){ $state.go("user",null,{ reload:true }); }else{ } }, function(error) { console.log("An error occurred", error); } ); }); } });
通过UserService的query方法,获取用户列表信息。
7. 新增用户
点击列表上的【新增】,将会通过$state.go("user-add");跳转到新增页面:
该页面对应的视图文件为tpl/Admin/User/add.html,内容如下:
<div ng-include="'tpl/Admin/Public/header.html'"></div> <form class="form-horizontal" role="form" name="userForm"> <div class="form-group"> <label class="col-sm-3 control-label">用户名</label> <div class="col-sm-4"> <input type="text" class="form-control" ng-model="user.user_name"/> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">邮箱</label> <div class="col-sm-4"> <input type="email" class="form-control" ng-model="user.email"/> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">手机号</label> <div class="col-sm-4"> <input type="tel" class="form-control" ng-model="user.tel"/> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">微信号</label> <div class="col-sm-4"> <input type="text" class="form-control" ng-model="user.weixin"/> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">QQ</label> <div class="col-sm-4"> <input type="text" class="form-control" ng-model="user.qq"/> </div> </div> <div class="col-sm-offset-3 col-sm-4"> <button type="submit" class="btn btn-primary" ng-click="submitAction(userForm)"> 保存 </button> <button type="button" class="btn btn-default" ng-click="cancelAction()"> 返回 </button> </div> </form>
该页面中用到了angular的ng-model指令,ng-model指令用于数据的双向绑定,注意下表单中类似于user.user_name的东西,首先,controller中会通过点号前面的user获取表单数据,表单数据就是点号后面若干个类似于user_name的对应的内容的组合。
js代码:
//新增和修改用户Ctroller app.controller('UserFormCtroller', function($scope, $state, $stateParams, UserService) { if($stateParams.user_id){ var user_id = $stateParams.user_id; var param = {id:user_id}; //获取指定用户 UserService.get(param).$promise.then( function(res) { $scope.user = res; }, function(error) { console.log("An error occurred", error); } ); } $scope.submitAction = function(userForm) { if(!userForm.$valid){ return false; } if($stateParams.user_id){ //更新用户信息 UserService.update(param,$scope.user).$promise.then( function(res) { if(res.status){ $state.go("user"); }else{ } }, function(error) { console.log("An error occured", error); } ); }else{ //新增 UserService.save($scope.user).$promise.then( function(res) { if(res.status){ $state.go("user"); }else{ } }, function(error) { console.log("An error occured", error); } ); } }; $scope.cancelAction = function() { $state.go("user"); }; });
通过UserService的save方法来新增一个用户,而update方法则负责用户信息的修改。
注意:在修改用户的信息之前,我们需要通过UserService的get方法把用户已有的信息填充到页面上。
8. 修改用户
点击列表上的【修改】链接,进入到用户修改页面:
通过列表中的ui-sref="user-edit({user_id:user.user_id})"这一句,即可实现页面的跳转,同时,带上了user_id这个参数,可以在控制器中注入$stateParams来获取此处的参数,代码在前面已经贴过了。
9. 删除用户
点击列表上的【删除】,将会弹出一个删除确认的提示,如图:
点击【确定】后,即可通过UserService的remove方法来删除该用户。
注意:删除一个资源,也可以使用delete方法,但由于delete在有些浏览器中被当成了关键字,所以使用上需要写成UserService["delete"]这种格式。
10. thinkphp路由配置
'URL_ROUTER_ON' => true
'URL_ROUTE_RULES' => array( array('api/users/:id','Admin/User/user_get','',array('method'=>'get')), //注意:列表记录对应的路由必须要放在单条记录路由的后面,否则无法获取单条记录 array('api/users','Admin/User/user_get','',array('method'=>'get')), array('api/users','Admin/User/user_post','',array('method'=>'post')), array('api/users/:id','Admin/User/user_put','',array('method'=>'put')), array('api/users/:id','Admin/User/user_delete','',array('method'=>'delete')), )
11. 后端代码
//查询用户列表或单条记录 public function user_get(){ $id = I('id'); if ($id) { $where = array('user_id' => $id); $users = DBUtil::queryRow($this->userModel,$where); }else{ $users = DBUtil::queryList($this->userModel); } $this->response($users,'json'); } //新增用户 public function user_post(){ $user_id = DBUtil::getMaxKey($this->userModel, 'user_id'); //angular默认post过来的数据类型为Content-Type:application/json; charset=utf-8 $userData = json_decode(file_get_contents('php://input'),true); $userData['user_id'] = $user_id; $result = DBUtil::add($this->userModel, $userData); $this->response(array('status'=>$result),'json'); } //修改用户 public function user_put(){ $user_id = $_REQUEST['id']; //angular默认post过来的数据类型为Content-Type:application/json; charset=utf-8 $userData = json_decode(file_get_contents('php://input'),true); $result = DBUtil::save($this->userModel, array('user_id' => $user_id), $userData); if ($result !== false) { $result = true; } $this->response(array('status'=>$result),'json'); } //删除用户 public function user_delete(){ $user_id = $_REQUEST['id']; $result = DBUtil::delete($this->userModel, array('user_id' => $user_id)); $this->response(array('status'=>$result),'json'); }三. 去除URL中的#号
默认情况下,angular通过URL中的#号来识别资源路径类别,凡是带#号的资源,统一由angular调配,而不带#号的资源,由服务端调配,如果需要去除#号,可以参考以下步骤:
1. 启用html5模式
首页需要在config中注入$locationProvider,然后通过$locationProvider.html5Mode(true);来启用HTML5模式的路由。
2. 加入base标签
在引导页中加入<base href="/">
3. 后端thinkphp配置
在控制器中增加一个_empty方法,在该方法中,跳转到首页。
至此,该演示示例就算完成了,如需完整代码,请访问如下网址下载即可:https://github.com/fyl123/antp,或联系QQ:2529854689