后台管理框架之六 :View页面设计
2014-09-29 14:50
597 查看
在前面技术选型章节中,已经提到了页面采用Razor(CShtml) + Jquery +EasyUI + js。在整个项目中,页面布局采用Html + EasyUI(个人喜欢避免使用JS代码生成EasyUI);而编辑类页面采用Razor(CShtml)
,其中验证框架采用MVC4 自带的jquery.validate.unobtrusive.js。
JQuery
、Easyui 两个Js框架优点突出,我就不再一一描述了。这里再说说这段时间Razor给我的感觉:
1.
Razor语法页面结构较Html更为简捷、清晰;
2.
Razor语法对页面的操作做了很多封装,可以减少代码工作量;
3.
Razor语法可充分利用后台代码,可以提供更为灵活、简捷的页面处理;
4.
Razor语法提供了很多的Helper类,如@Html , @Model
,这些类与后台视图模型的显示、验证可以实现完全、紧密的绑定,一来使得减少较多的验证代码,二来可以提供前、后台数据验证的一致性。我认为这一点在“编辑类页面”是非常有用的,这也是我在页面模型中不使用jquery.validate.js验证框架和EasyUI validateBox,而是直接使用Mvc4自带的jquery.validate.unobtrusive.js验证框架的原因(虽然这个框架仍然是基于jquery.validate的)。
一、页面部局
整个项目前台页面布局采用EasyUI,这个基于JQuery
的开源JS框架非常好用,目前最新版本1.4,符合Html5规范,通过NuGet可发直接下载并加入到项目中。整个项目抓图如下:
整个项目布局部分代码如下(可参照EasyUI官网布局示例源代码):
二、视图模型
视图模型是View的一部分,主要是对前后台数据交互的封装,如后台发往前台的编辑类数据、前台发往后台的查询结构数据等。使用视图模型的好处:
1. 方便对前后台数据交互进行管理。Mvc可以根据传递的QueryParams参数值,自动构建参数模型,自动生成参数对象,不用再像Asp.net那样自行封装数据,减少很多工作量。
2. 能对数据进行验证性规则定义,配合Mvc4验证框架和提供的一些Helper类,高效紧密结合,可以有效保证数据前后台一致性、有效性;
3. 实现与实体数据模型的分离,避免实体数据模型直接暴露在前台。
这些视图模型类似于实体模型,主要是单一的数据承载类,其设计可以根据实际页面模型进行定义,无需过多关注。
三、JS设计
Js语言是一个强大的、面向对象的、弱类型的编程语言,弱类型导致她非常灵活,也非常容易出错,因为在编辑时解析器不检查语法,直到运行时才能发现问题。在View中,JS承担着View的前台页面逻辑处理,通常的处理方式是独立将JS代码存放在一个JS文件中,然后在页面中进行引用。这个项目也不可避免,总体来说,一切为了项目结构清晰、易读为主。
在原来的项目中,我们的JS一般是针对性的进行编写,比如说,页面上有个Button,点击后应弹出一个对话框,一般的写法是:
<input id=”btn011” type=”button”onclick=”btnClick” value=”点击” />
......
function btnClick(){
alert(“btn011被点击”);
}
然后在一个页面Js中,可能有N个function定义,其实这也没有什么错。但是在EasyUI、ExtJs这类JS
_UI框架下可能会碰到“别扭”的地方:很多的页面基本上都是一个列表页面,再配合一个编辑页面,选中某条数据后再点击相关的按钮(如增、删、改、查)进行操作,基本很多操作类似,代码相似度极高,复制、粘贴大法将会产生大量的冗余代码,而且极有可能出现相同名称的函数。同名函数在单页面显示时没有影响,但是在UI
布局框架下,这些单页面都会作为同一个Document对象的内容进行加载,那么同名函数就会产生非常大的影响,函数被调用的永远是最后定义的那一个,很多时候会出现莫名其妙的所谓BUG。这是一个极其别扭的事,调试找错是极其费时费事的事情。
一种解决办法是尽量使用匿名函数和匿名对象,但是匿名函数会导致代码可读性差、控制性差等问题,而且也会存在大量的冗余代码。所以在这个项目上,对冗余代码进行了一定程度的封装,另外针对列表页面和编辑页面做了抽象定义,个人感觉效果还不错,冗余减少,功能结构更清晰,以下是这部分的JS代码:
在页面具体操作时,可以直接定义一个baseGridHandler
的对象,然后将相关按照的操作指定到这个对象的相关方法,或者直接调用这个对象的init方法,自动为datagrid增加按钮;当然也可以在此对象里定义一些事件,如编辑完成后应执行的函数,然后由定义的对象去实现,就可以进行各项功能的扩展;还有一种办法是,直接重定这个对象的方法,改变整个窗体的处理逻辑也行。
整个项目View处理没有什么特别的地方,基本上想说的也就这么多,下一步将介绍业务逻辑层的设计思路。
,其中验证框架采用MVC4 自带的jquery.validate.unobtrusive.js。
JQuery
、Easyui 两个Js框架优点突出,我就不再一一描述了。这里再说说这段时间Razor给我的感觉:
1.
Razor语法页面结构较Html更为简捷、清晰;
2.
Razor语法对页面的操作做了很多封装,可以减少代码工作量;
3.
Razor语法可充分利用后台代码,可以提供更为灵活、简捷的页面处理;
4.
Razor语法提供了很多的Helper类,如@Html , @Model
,这些类与后台视图模型的显示、验证可以实现完全、紧密的绑定,一来使得减少较多的验证代码,二来可以提供前、后台数据验证的一致性。我认为这一点在“编辑类页面”是非常有用的,这也是我在页面模型中不使用jquery.validate.js验证框架和EasyUI validateBox,而是直接使用Mvc4自带的jquery.validate.unobtrusive.js验证框架的原因(虽然这个框架仍然是基于jquery.validate的)。
一、页面部局
整个项目前台页面布局采用EasyUI,这个基于JQuery
的开源JS框架非常好用,目前最新版本1.4,符合Html5规范,通过NuGet可发直接下载并加入到项目中。整个项目抓图如下:
整个项目布局部分代码如下(可参照EasyUI官网布局示例源代码):
<body class="easyui-layout"> <div id="north" data-options="region:'north',split:false,border:false" style="height: 60px; overflow: hidden; background: #D2E0F2; line-height: 20px; color: #fff; font-family: Verdana, 微软雅黑,黑体;"> @Html.Partial( "_MainTopLayoutPartial" ) </div> <div id="south" data-options="region:'south',split:false" style="height:30px; background: #D2E0F2; "> @Html.Partial( "_MainBottomLayoutPartial" ) </div> @*<div id="right" data-options="region:'east',split:true,collapse:true" title="East" style="width:200px;"> <ul class="easyui-tree" data-options="url:'./data/tree_data1.json',method:'get',animate:true,dnd:true"></ul> </div>*@ <div id="left" data-options="region:'west',split:true" title="导航菜单" style="width:180px;"> @Html.Partial( "_MainLeftLayoutPartial" ) </div> <div id="mainTitle" data-options="region:'center',title:'操作区',iconCls:'icon-ok',noheader:true"> @Html.Partial( "_MainCenterLayoutPartial" ) </div> </body>
二、视图模型
视图模型是View的一部分,主要是对前后台数据交互的封装,如后台发往前台的编辑类数据、前台发往后台的查询结构数据等。使用视图模型的好处:
1. 方便对前后台数据交互进行管理。Mvc可以根据传递的QueryParams参数值,自动构建参数模型,自动生成参数对象,不用再像Asp.net那样自行封装数据,减少很多工作量。
2. 能对数据进行验证性规则定义,配合Mvc4验证框架和提供的一些Helper类,高效紧密结合,可以有效保证数据前后台一致性、有效性;
3. 实现与实体数据模型的分离,避免实体数据模型直接暴露在前台。
这些视图模型类似于实体模型,主要是单一的数据承载类,其设计可以根据实际页面模型进行定义,无需过多关注。
三、JS设计
Js语言是一个强大的、面向对象的、弱类型的编程语言,弱类型导致她非常灵活,也非常容易出错,因为在编辑时解析器不检查语法,直到运行时才能发现问题。在View中,JS承担着View的前台页面逻辑处理,通常的处理方式是独立将JS代码存放在一个JS文件中,然后在页面中进行引用。这个项目也不可避免,总体来说,一切为了项目结构清晰、易读为主。
在原来的项目中,我们的JS一般是针对性的进行编写,比如说,页面上有个Button,点击后应弹出一个对话框,一般的写法是:
<input id=”btn011” type=”button”onclick=”btnClick” value=”点击” />
......
function btnClick(){
alert(“btn011被点击”);
}
然后在一个页面Js中,可能有N个function定义,其实这也没有什么错。但是在EasyUI、ExtJs这类JS
_UI框架下可能会碰到“别扭”的地方:很多的页面基本上都是一个列表页面,再配合一个编辑页面,选中某条数据后再点击相关的按钮(如增、删、改、查)进行操作,基本很多操作类似,代码相似度极高,复制、粘贴大法将会产生大量的冗余代码,而且极有可能出现相同名称的函数。同名函数在单页面显示时没有影响,但是在UI
布局框架下,这些单页面都会作为同一个Document对象的内容进行加载,那么同名函数就会产生非常大的影响,函数被调用的永远是最后定义的那一个,很多时候会出现莫名其妙的所谓BUG。这是一个极其别扭的事,调试找错是极其费时费事的事情。
一种解决办法是尽量使用匿名函数和匿名对象,但是匿名函数会导致代码可读性差、控制性差等问题,而且也会存在大量的冗余代码。所以在这个项目上,对冗余代码进行了一定程度的封装,另外针对列表页面和编辑页面做了抽象定义,个人感觉效果还不错,冗余减少,功能结构更清晰,以下是这部分的JS代码:
<span style="font-size:12px;">function options( tar, href, fn ) { options.prototype.formTag = tar; options.prototype.actionHref = href; options.prototype.handler = fn; }; /* 定义基本表格操作处理类 参数说明: tableId : string , 当前表格对应的html Id idField : string, 当前表格数据的Id主键 参数说明: 两个参数必须提供,否则将抛出异常 */ function baseGridHandler( tableId, field ) { if ( ( tableId == undefined || tableId == "" ) ) // || ( field == undefined || field == "" ) throw "表格参数必须提供!" var owner = this; var selectRowValueTag = "SelectRowValue"; var DatagridValue = "DatagridValue"; if ( tableId.substr( 0, 1 ) != "#" ) tableId = "#" + tableId; /* 表格对应的Html Table Id值 */ var tableTagID = tableId; var idField = field; if ( idField == undefined ) idField = $( tableTagID ).datagrid( "options" ).idField; if ( idField == null ) throw "表格必须提供IdField参数!"; /* 存储表格对应数据 数据格式:{ total : 0 , rows : [] } 默 认值:{ total : 0 , rows : [] } */ baseGridHandler.prototype.datas = { total: 0, rows: [] }; /* 定义子编辑框尺寸 默 认值: { width: 550, height: 450 }; */ baseGridHandler.prototype.borderSize = { width: 550, height: 450 }; baseGridHandler.prototype.toolbarOptions = { showRefreshButton: true, showAddButton: true, showEditButton: true, showDeleteButton: true, showClearButton: true }; /* 定义表格列表刷新操作配置内容 参数格式:{ actionHref : "",showInToolbar : true} 参数说明: actionHref : List请求路径地址 showInToolbar : true 默 认值: null; */ baseGridHandler.prototype.listOptions = null; /* 定义表格编辑操作配置内容 参数格式:{ actionHref : "" , actionViewHref : "" , formTag : "" , showInToolbar:true} 参数说明: actionViewHref : Edit页面的请求路径地址 formTag : Edit编辑Dialog的form Id值 actionHref : Edit页面的请求路径地址 showInToolbar : true 默 认值: null; */ baseGridHandler.prototype.editOptions = null; /* 定义表格删除操作配置内容 参数格式:{ actionHref : "" , showInToolbar : true } 参数说明: actionView : 删除页面的请求路径地址 actionHref : Edit页面的请求路径地址 默 认值: null; */ baseGridHandler.prototype.deleteOptions = null; /* 定义表格执行刷新操作之后应调用函数 参数格式:function(){} 默 认值: null; */ baseGridHandler.prototype.afterRefresh = null; baseGridHandler.prototype.afterRowSelected = null; showMessage = function ( msg ) { $.messager.show( { title: "提示", msg: msg, timeout: 3000 } ); } /* 根据指定参数,执行表格数据刷新操作 参数格式:{Id : value},根据实体业务要求组合参数,如{Id : 1 , Name : 'san'} 默 认值:null */ baseGridHandler.prototype.doRefresh = function ( params ) { $( tableTagID ).datagrid( "reload", params ); owner.onDataRowSelected( -1, null ); //if ( owner.listOptions != null ) { // $.ajaxRequest( { // url: owner.listOptions.actionHref, // data: params, // onSuccessHandler: function ( result ) { // var data; // if ( result.data == undefined ) // data = result; // else // data = resutl.data; // owner.doFillData( data ); // if ( owner.afterRefresh != null ) // owner.call( afterRefresh, data ); // } // } ); //} }; /* 根据指定参数,执行表格数据编辑操作 参数格式:{Id : value},根据实体业务要求组合参数,如{Id : 1 , Name : 'san'} 默 认值:{Id : -1} 表格新增数据 */ baseGridHandler.prototype.doEdit = function ( params ) { if ( owner.editOptions != null ) { if ( params == undefined ) params = { Id: -1 }; $.showDialog( { width: owner.borderSize.width, height: owner.borderSize.height, href: owner.editOptions.actionViewHref, params: params, onClosedHandler: owner.doRefresh, okHandler: owner.submitEditForm } ); } }; /* 根据指定参数,执行表格数据删除操作 参数格式:{Id : value},根据实体业务要求组合参数,如{Id : 1 , Name : 'san'} */ baseGridHandler.prototype.doDelete = function ( params ) { if ( owner.deleteOptions == null ) throw "删除操作配置不能为空!"; if ( params == undefined ) throw "删除操作需要提供参数!"; var url = owner.deleteOptions.actionHref; $.messager.confirm( '确认对话框', '确定删除选择的数据?', function ( r ) { if ( r ) { $.ajaxRequest( { url: url, data: param, onSuccessHandler: owner.doRefresh } ); }; } ); }; /* 使用数据参数刷新表格内容 参数格式:{total:0,row:[]} */ baseGridHandler.prototype.doFillData = function ( data ) { owner.datas = data; $( tableTagID ).datagrid( "loadData", data ); }; /* 使用指定数据参数设置表格属性 参数格式:{},具体格式参照Easyui datagrid options对象 */ baseGridHandler.prototype.setGridOptions = function ( options ) { if ( options == null || options == undefined ) return; $( tableTagID ).datagrid( options ); } /* 获取表格属性 参数格式:无 返回 值:datagrid Options对象 */ baseGridHandler.prototype.getGridOptions = function () { return $( tableTagID ).datagrid( "options" ); } /* 使用指定数据参数设置表格属性 参数格式:value,可对存储的任何对象对象 返回 值:无 */ baseGridHandler.prototype.setGridData = function ( value ) { $( tableTagID ).data( DatagridValue, value ); } /* 获取表格属性 参数格式:无 返回 值:任意对象 */ baseGridHandler.prototype.getGridData = function () { return $( tableTagID ).data( DatagridValue ); } /* 根据指定条件执行搜索操作,搜索后自动填充当前表格 目前暂未实现 */ baseGridHandler.prototype.doSearch = function () { }; /* 执行当前表格数据导出操作 目前暂未实现 */ baseGridHandler.prototype.doExport = function () { }; /* 提交表格数据对应实体编辑框 此方法配置增加或修改的showDialog使用,在此dialog中的确定按钮点击时调用 参数格式:{cancle:true} ,如果将此参数值设置为true,可以取消Dialog对话框关闭 */ baseGridHandler.prototype.submitEditForm = function ( e ) { if ( owner.editOptions == null ) throw "编辑区配置不能为空!"; if ( $( owner.editOptions.formTag ).valid() ) { $.ajaxRequest( { url: owner.editOptions.actionHref, data: $( owner.editOptions.formTag ).serialize()// 你的formid } ); e.cancle = false; } else e.cancle = true; }; /* 获取当前表格选中行的主键值 */ baseGridHandler.prototype.selectedValue = function () { return $( tableTagID ).data( selectRowValueTag ); }; /* 获取当前表格选中行的数据 返回结果为选中的行对象,可以使用对象.属性获取相应数值,如var id = row.Id; */ baseGridHandler.prototype.selectedRow = function () { return $( tableTagID ).datagrid( "getSelected" ); }; /* 当前表格选择行被修改时引发,单行选择时被触发 参数格式:rowIndex , rowData 参数说明 : rowIndex : 所选择的行索引 rowData : 所选择的行数据 */ baseGridHandler.prototype.onDataRowSelected = function ( rowIndex, rowData ) { if ( 0 > rowIndex ) { if ( owner.toolbarOptions.showEditButton == true ) { $( '#' + edit_toolbar_button.id ).linkbutton( "disable" ); }; if ( owner.toolbarOptions.showDeleteButton == true ) { $( '#' + delete_toolbar_button.id ).linkbutton( "disable" ); }; if ( owner.toolbarOptions.showClearButton == true ) { $( '#' + clearSelected_toolbar_button.id ).linkbutton( "disable" ); }; $( tableTagID ).datagrid( "unselectAll" ); $( tableTagID ).removeData( selectRowValueTag ); //移除blah return; }; var code = $( tableTagID ).data( selectRowValueTag ); if ( code != rowData[idField] ) { $( tableTagID ).data( selectRowValueTag, rowData[idField] ); if ( owner.toolbarOptions.showEditButton == true ) { $( '#' + edit_toolbar_button.id ).linkbutton( "enable" ); }; if ( owner.toolbarOptions.showDeleteButton == true ) { $( '#' + delete_toolbar_button.id ).linkbutton( "enable" ); }; if ( owner.toolbarOptions.showClearButton == true ) { $( '#' + clearSelected_toolbar_button.id ).linkbutton( "enable" ); }; if ( owner.afterRowSelected != null ) owner.afterRowSelected( rowIndex, rowData ); } }; /* 定义刷新按钮被点击执行的操作 直接会调用doRefresh方法 */ baseGridHandler.prototype.onRefreshClicked = function () { owner.doRefresh(); showMessage( "刷新获取数据完成!" ); }; baseGridHandler.prototype.onClearSelectedClicked = function () { owner.onDataRowSelected( -1, null ); } /* 定义默认新增操作 */ baseGridHandler.prototype.onCreateClicked = function () { owner.doEdit(); }; /* 定义默认编辑操作 */ baseGridHandler.prototype.onEditClicked = function () { var value = owner.selectedValue(); if ( value == undefined || value == null ) { showMessage( "请选择对应数据后,再执行修改操作!" ); return; } var params = {}; params[idField] = value; //owner.doEdit( appUtils.builderParamsToJson( [{ "name": idField, "value": value }] ) ); owner.doEdit( params ); }; /* 定义默认编辑操作 */ baseGridHandler.prototype.onDeleteClicked = function () { var value = owner.selectedValue(); if ( value == undefined || value == null ) { showMessage( "请选择对应数据后,再执行删除操作!" ); return; } var params = {}; params[idField] = value; owner.doDelete( params ); }; /* 定义传递给grid获取数据的loader对象 特别提示:默认loader直接返回true,不做任何处理,子类对象应重写当前方法 参数格式:param,successCallback,errorCallback 参数说明: param:json对象 successCallback:数据加载成功时回调函数 errorCallback:数据加载失败时回调函数 参数格式: param:参数对象传递给远程服务器。 successCallback:fn(data):当检索数据成功的时候会调用该回调函数。 errorCallback: fn()当检索数据失败的时候会调用该回调函数。 返回结果: 返回false可以放弃本次请求动作。 */ baseGridHandler.prototype.dataLoader = function ( param, successCallback, errorCallback ) { var options = $( tableTagID ).datagrid( "options" ); $.ajaxRequest( { url: options.url, data: param, onSuccessHandler: successCallback, onErrorHandler: errorCallback } ); return true; }; /* 定义表格工具栏 */ var refresh_toolbar_button = null; var create_toolbar_button = null; var edit_toolbar_button = null; var delete_toolbar_button = null; var clearSelected_toolbar_button = null; baseGridHandler.prototype.toolbar = []; _builderToolbar = function () { if ( owner.toolbarOptions.showRefreshButton == true ) { refresh_toolbar_button = { id: appUtils.randomId(), text: '刷新', iconCls: 'icon-reload', handler: owner.onRefreshClicked }; owner.toolbar.push( refresh_toolbar_button, '-' ); }; if ( owner.toolbarOptions.showAddButton == true ) { create_toolbar_button = { id: appUtils.randomId(), text: '添加', iconCls: 'icon-add', handler: owner.onCreateClicked }; owner.toolbar.push( create_toolbar_button ); }; if ( owner.toolbarOptions.showEditButton == true ) { edit_toolbar_button = { id: appUtils.randomId(), text: '修改', iconCls: 'icon-edit', disabled: true, handler: owner.onEditClicked }; owner.toolbar.push( edit_toolbar_button ); }; if ( owner.toolbarOptions.showDeleteButton == true ) { delete_toolbar_button = { id: appUtils.randomId(), text: '删除', iconCls: 'icon-cut', disabled: true, handler: owner.onDeleteClicked }; owner.toolbar.push( delete_toolbar_button, '-' ); }; if ( owner.toolbarOptions.showClearButton == true ) { clearSelected_toolbar_button = { id: appUtils.randomId(), text: '清除选择', iconCls: 'icon-remove', disabled: true, handler: owner.onClearSelectedClicked }; owner.toolbar.push( clearSelected_toolbar_button, '-' ); }; $( tableTagID ).datagrid( { toolbar: owner.toolbar } ); }; _initDatagridProperty = function () { $( tableTagID ).datagrid( { onSelect: owner.onDataRowSelected, loader: owner.dataLoader } ); }; _doFillData = function ( result ) { baseGridHandler.prototype.doFillData( resut.data ); }; /* 执行对象初始化 */ baseGridHandler.prototype.init = function () { _builderToolbar(); _initDatagridProperty(); }; }; </span>
在页面具体操作时,可以直接定义一个baseGridHandler
的对象,然后将相关按照的操作指定到这个对象的相关方法,或者直接调用这个对象的init方法,自动为datagrid增加按钮;当然也可以在此对象里定义一些事件,如编辑完成后应执行的函数,然后由定义的对象去实现,就可以进行各项功能的扩展;还有一种办法是,直接重定这个对象的方法,改变整个窗体的处理逻辑也行。
整个项目View处理没有什么特别的地方,基本上想说的也就这么多,下一步将介绍业务逻辑层的设计思路。
相关文章推荐
- 基于ssm框架的个人博客(3)--easyui后台管理页面设计
- 分享27款后台管理页面设计 DIV+CSS+JS
- 最为纯粹简单的后台管理页面框架
- 基于YIi的三栏frameset框架后台管理页面的实现
- ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统之前端页面框架构建源码分享
- [网页设计] 27款后台管理页面设计 DIV+CSS+JS
- [网页设计] 27款后台管理页面设计 DIV+CSS+JS
- 仿wordpress管理后台设计的后台管理框架
- 后台管理框架之五 :数据仓储设计
- 贴出使用dojo做的经典后台管理页面,这里只是个框架
- 27款后台管理页面设计 DIV+CSS+JS
- 27款后台管理页面设计 DIV+CSS+JS
- 后台管理框架之四 :数据模型设计
- 一步一步实现web程序信息管理系统之二----后台框架实现跳转登陆页面
- ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统之前端页面框架构建源码分享
- 后台管理框架之七 :业务逻辑设计
- 借用 疯狂秀才 的页面,修改了一下自然框架后台管理的页面。
- 基于YIi的三栏frameset框架后台管理页面的实现
- 借用 疯狂秀才 的页面,修改了一下自然框架后台管理的页面。
- 自己设计开发的通用后台管理系统开发框架(struts2+hibernate+spring+easyui)