一、前言
半年前左右折腾了一个前后端分离的架子,这几天才想起来翻出来分享给大家。关于前后端分离这个话题大家也谈了很久了,希望我这个实践能对大家有点点帮助,演示和源码都贴在后面。
二、技术架构
这两年angularjs和reactjs算是比较火的项目了,而我选择angularjs并不是因为它火,而是因它的模块化、双向数据绑定、注入、指令等都是非常适合架构较复杂的前端应用,而且文档是相当的全,碰到问题基本上可以在网上都找到答案。所以前端基本思路就以angularjs为主、代码模块化,通过requirejs实现动态加载,ui选择dhtmlx为主配合少量bootstrap3使用。前端项目dhtmlx_web: 开发工具 Sublime Text 前端框架angularjs 模块加载requirejs 前端UI dhtmlx + bt3 包管理 bower 构建工具 gruntjs 服务架设 http_server.js 浏览器支持IE8+ 实际是为了支持IE8我做了很多的努力,因为angluarjs 1.3已经不再支持IE8了,而我使用的angularjs是1.3.9 引入的一些其它类库或插件就不列出来了,太多了
服务端主要是提供restful数据服务,所以.net下毫无疑问选择asp.net webapi来实现了。 后端项目dhtmlx_webapi: 开发工具 VS2012 数据服务 Asp.net WebApi 跨域实现 CORS 依赖注入 Autofac 日志组件 Log4net 数据库已改为MS Access (为了方便大家可以直接运行)
三、前端介绍
1、基本说明 项目主要分了三个文件夹assets存放引用类库及插件,app中则是项目文件,build中存放构建后的文件,先让大家看几个实现的页面,再介绍代码吧
这是查询页面,查询条件、分页、排序都可用
这是虚拟分页的实现,也实现了过滤行,我自己也是挺喜欢这种风格的。
编辑页面
2、程序入口 以上的几个页面都比较典型,如果大家右键查看源码的话只能看到:
<!DOCTYPE html>
<html>
<head>
<title>权限管理系统</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="build/app.png" rel="shortcut icon" type="image/x-icon" />
<link href="build/app.css" rel="stylesheet" />
<!--[if lte IE 8]><script src="build/ie.js"></script><![endif]-->
</head>
<body ng-controller="myController">
<div id="my_header" my-header ></div>
<div id="my_layout" my-layout ></div>
<div id="my_footer" my-footer ></div>
<div id="my_setting" my-setting ></div>
<div id="my_chat" my-chat ></div>
<script src="build/app.js"></script>
</body>
</html>
那我们就从这里开始讲起,实际上我设计的这个前端也可以看做是单页面应用SPA,只有一个页面也就是index.html点左边菜单栏打开的新tab实际只是加载了一个ng的controller渲染出来的一个层而已,当然为了实用也支持输入一个页面地址。 当然这个页面是build之后的结果,build之前的index.src.html不忍直视,主要是因为不想引入所有的dhtmlx类库,只是选择性引入很多文件没有用一个dhtml.js替代
<!DOCTYPE html>
<html>
<head>
<title>权限管理系统</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link href="app/img/logo/chitu-32.png" rel="shortcut icon" type="image/x-icon" />
<link href="assets/css/default/icons.css" rel="stylesheet" />
<link href="assets/css/default/scaffolding.css" rel="stylesheet" />
<link href="assets/css/default/helpers.css" rel="stylesheet" />
<link href="assets/vendors/buttons/css/buttons.min.css" rel="stylesheet" />
<link href="assets/lib/dhtmlx/v412_std/skins/default/dhtmlx.css" rel="stylesheet" />
<link href="app/css/fix.css" rel="stylesheet" />
<link href="app/css/app.css" rel="stylesheet" />
<!--[if lte IE 8]>
<script src="assets/lib/ie/html5shiv/html5shiv.min.js"></script>
<script src="assets/lib/ie/es5-shim/es5-shim.min.js"></script>
<script src="assets/lib/ie/json/json3.min.js"></script>
<script src="assets/lib/ie/respond/respond.min.js"></script>
<script src="assets/lib/ie/ieupdate/ieupdate.js"></script>
<![endif]-->
</head>
<body ng-controller="myController">
<div id="my_header" my-header ></div>
<div id="my_layout" my-layout ></div>
<div id="my_footer" my-Footer ></div>
<div id="my_setting" my-setting ></div>
<div id="my_chat" my-chat ></div>
<script src="assets/lib/jquery/jquery-1.11.2.min.js"></script>
<script src="assets/lib/jquery/jquery-ui.js"></script>
<script src="assets/lib/jquery/jquery.cookie.js"></script>
<script src="assets/vendors/jquery-pulsate/jquery.pulsate.custom.js"></script>
<script src="assets/vendors/jquery-slimscroll/jquery.slimscroll.min.js"></script>
<script src="assets/vendors/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/vendors/buttons/js/buttons.js"></script>
<script src="assets/lib/angularjs/1.3.9/ie8/angular.js"></script>
<!--<script src="assets/lib/dhtmlx/v403_pro/codebase/dhtmlx.js"></script>
<script src="assets/lib/dhtmlx/v412_std/sources/dhtmlxTabbar/codebase/dhtmlxtabbar.js"></script>
<script src="assets/lib/dhtmlx/dhtmlx.custom.js"></script>-->
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxCommon/codebase/dhtmlxcommon.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxCommon/codebase/dhtmlxcore.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxCommon/codebase/dhtmlxcontainer.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxLayout/codebase/dhtmlxlayout.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxAccordion/codebase/dhtmlxaccordion.js"></script>
<script src="assets/lib/dhtmlx/v412_std/sources/dhtmlxTabbar/codebase/dhtmlxtabbar.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxTree/codebase/dhtmlxtree.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxTree/codebase/ext/dhtmlxtree_json.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxToolbar/codebase/dhtmlxtoolbar.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxMenu/codebase/dhtmlxmenu.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/dhtmlxgrid.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_drag.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_export.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_filter.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_nxml.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_selection.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_srnd.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_validation.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_tree.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_link.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_grid.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_dhxcalendar.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_cntr.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_acheck.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_context.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_start.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_data.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_fast.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_filter_ext.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_form.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_group.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_hextra.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_hmenu.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_json.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_markers.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_math.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_mcol.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_over.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_pgn.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_post.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_rowspan.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_splt.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_ssc.js"></script>
<script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_undo.js"></script>
<script src="assets/lib/dhtmlx/dhtmlx.custom.js"></script>
<script src="assets/lib/requirejs/require.min.js"></script>
<script src="app/main.js"></script>
</body>
</html>
不过从过里可以看出,我们的入口文件是main.js
!function () {
//config requirejs
require.config({
baseUrl: './app/js/',
paths: {
assets: '../../assets/',
css: '../../assets/lib/requirejs/css',
text: '../../assets/lib/requirejs/text',
views: '../views',
config: 'config/global',
'angular-resource': '../../assets/lib/angularjs/1.3.9/angular-resource'
},
shim: {},
urlArgs: 'v=201502100127&r='+Math.random()
});
//init main
require(['app',
'config',
'angular-resource',
'service/index',
'directive/index',
'controller/index'],
function (app, config) {
app.init();
}
);
}();
在main.js中,我们配置requriejs并且启动主程序,启动时载入了 app、config、angular-resource、service/index、directive/index、controller/index这六个模块,那我们就看app模块,其它不再分析了,大家自己去看代码吧
这里加载的app位于app/js/下的app.js文件
define(function (require) {
'use strict';
var app = angular.module('chituApp', ['ngResource']);
app.init = function () {
angular.bootstrap(document, ['chituApp']);
};
app.config(function ($controllerProvider, $provide, $compileProvider, $resourceProvider) {
// Save the older references.
app._controller = app.controller;
app._service = app.service;
app._factory = app.factory;
app._value = app.value;
app._directive = app.directive;
// Provider-based controller.
app.controller = function (name, constructor) {
$controllerProvider.register(name, constructor);
return (this);
};
// Provider-based service.
app.service = function (name, constructor) {
$provide.service(name, constructor);
return (this);
};
// Provider-based factory.
app.factory = function (name, factory) {
$provide.factory(name, factory);
return (this);
};
// Provider-based value.
app.value = function (name, value) {
$provide.value(name, value);
return (this);
};
// Provider-based directive.
app.directive = function (name, factory) {
$compileProvider.directive(name, factory);
return (this);
};
// $resource settings
$resourceProvider.defaults.stripTrailingSlashes = false;
});
app.run(function (){
//run some code here ...
});
return app;
});
3、与后台restful api交互
数据服务我准备都放在service文件夹下,比如菜单的数据服务在service/index,目前是静态数据。不过项目所有的ajax访问都是由ngResource实现的,实际对$http的封装,$resource可以方便的与resultful接口接合,我们可以大大简化操作,我是比较推荐它,一个简单示例:
//定义
app.factory('api', ['$resource', function ($resource) {
return $resource(url,{query:{..},update:{..},remove:{..},get:{..},insert:{..}});
}]);
app.controller('test',['api', function (api) {
//查询
api.query(params, function(data){
var list = data;
});
//获取并更新
api.get({id:1}, function(data){
data.name = 'new name';
data.update();
});
//新增
api.insert(data);
//删除
api.remove({id:1});
}]);
4、页面组件化
页面组件化思路基本就是依赖ng的指令,主页面上的各部分基本都是通过指令directive/index去渲染的,包括myHeader、myFooter、myLayout、mySetting、myChat五个指令分别实现各部分。一些通用的控件比如dhtmlxgrid、dhtmlxtoolbar我都写成了指令的方式,觉得以后常用的控件都可以用这种方式实现,方便而且还可以提高代码重用性。
define(['app'], function (app) {
app.directive('dhtmlxgrid', function ($resource) {
return {
restrict: 'A',
replace: true,
scope: {
fields: '@',
header1: '@',
header2: '@',
colwidth: '@',
colalign: '@',
coltype: '@',
colsorting: '@',
pagingsetting: '@',
autoheight: '=',
url: '@',
params:'@'
},
link: function (scope, element, attrs) {
scope.uid = app.genStr(12);
element.attr("id", "dhx_grid_" + scope.uid);
element.css({ "width": "100%", "border-width": "1px 0 0 0"});
scope.grid = new dhtmlXGridObject(element.attr("id"));
scope.header1 && scope.grid.setHeader(scope.header1);
scope.header2 && scope.grid.attachHeader(scope.header2);
scope.fields && scope.grid.setFields(scope.fields);
scope.colwidth && scope.grid.setInitWidths(scope.colwidth)
scope.colalign && scope.grid.setColAlign(scope.colalign)
scope.coltype && scope.grid.setColTypes(scope.coltype);
scope.colsorting && scope.grid.setColSorting(scope.colsorting);
scope.grid.entBox.onselectstart = function () { return true; };
if (scope.pagingsetting) {
var pagingArr = scope.pagingsetting.split(",");
var pageSize = parseInt(pagingArr[0]);
var pagesInGrp = parseInt(pagingArr[1]);
var pagingArea = document.createElement("div");
pagingArea.id = "pagingArea_" + scope.uid;
pagingArea.style.borderWidth = "1px 0 0 0";
var recinfoArea = document.createElement("div");
recinfoArea.id = "recinfoArea_" + scope.uid;
element.after(pagingArea);
element.after(recinfoArea);
scope.grid.enablePaging(true, pageSize, pagesInGrp, pagingArea.id, true, recinfoArea.id);
scope.grid.setPagingSkin("toolbar", "dhx_skyblue");
scope.grid.i18n.paging = {
results: "结果",
records: "显示",
to: "-",
page: "页",
perpage: "行每页",
first: "首页",
previous: "上一页",
found: "找到数据",
next: "下一页",
last: "末页",
of: " 的 ",
notfound: "查询无数据"
};
}
scope.grid.setImagePath(app.getProjectRoot("assets/lib/dhtmlx/v403_pro/skins/skyblue/imgs/"));
scope.grid.init();
if (scope.autoheight) {
var resizeGrid = function () {
element.height(element.parent().parent().height() - scope.autoheight);
scope.grid.setSizes();
};
$(window).resize(resizeGrid);
resizeGrid();
}
//scope.grid.enableSmartRendering(true);
if (scope.url) {
var url = app.getApiUrl(scope.url);
var param = scope.$parent[scope.params] || {};
var api = $resource(url, {}, { query: { method: 'GET', isArray: false } });
scope.grid.setQuery(api.query, param);
}
//保存grid到父作用域中
attrs.dhtmlxgrid && (scope.$parent[attrs.dhtmlxgrid] = scope.grid);
}
};
});
app.directive('dhtmlxtoolbar', function () {
return {
restrict: 'A',
replace: false,
scope: {
iconspath: '@',
items:'@'
},
link: function (scope, element, attrs) {
scope.uid = app.genStr(12);
element.attr("id", "dhx_toolbar_" + scope.uid);
element.css({ "border-width": "0 0 1px 0" });
scope.toolbar = new dhtmlXToolbarObject(element.attr("id"));
scope.toolbar.setIconsPath(app.getProjectRoot(scope.iconspath));
var items = eval("(" + scope.items + ")");
//scope.toolbar.loadStruct(items);
var index = 1;
var eventmap = {};
for (var i in items) {
var item = items[i];
if (item.action)
eventmap[item.id] = item.action;
if (item.type == 'button') {
scope.toolbar.addButton(item.id, index++, item.text, item.img, item.imgdis);
item.enabled == false && scope.toolbar.disableItem(item.id);
}
else if (item.type == 'separator') {
scope.toolbar.addSeparator(index++);
}
}
scope.toolbar.attachEvent("onClick", function (id) {
var name = eventmap[id];
if (name && scope.$parent[name] && angular.isFunction(scope.$parent[name]))
scope.$parent[name].call(this);
});
attrs.dhtmlxtoolbar && (scope.$parent[attrs.dhtmlxtoolbar] = scope.toolbar); }
}
});
});
5、前端文件分类(文件夹介绍)
再来说说js文件的分类,实际上是根据angularjs的特点,把所有的js分为以下几个文件夹,都是angular的概念我可能没法一下子跟大家解释清楚,请大家自己去了解。
app/js
│ ├─config 配置
├─constant 常量 ├─controller 控 |
请发表评论