http://www.cnblogs.com/xqin/archive/2013/06/06/3120887.html

前言

我写代码喜欢提取一些共通的东西出来,之前的一篇博客中说了如何用一个共通的viewModel和简洁的后台代码做查询页面,所有的查询页面都要对应一个数据录入的编辑及查看明细的页面,那么今天我们就来实现这个页面,同样我们也要使用一个共通的viewModel完成前台UI与JSON数据交互的处理,同样以超简洁的后台代码来处理保存。

需求分析

我们先弄明白我们要做怎么样一个编辑的页面。 
1、最上面有一个共通的工具栏,有保存、撤消、审核、打印、还有上一条、下一条、第一条、最后一条的数据滚动按钮,还有一些其它按钮放在下拉按钮中。 

2、我们这个页面支持一个主表和从表一起保存,同一个事务,首先要有主表的录入

3、其次我们还要从表的录入grid,从表可以增删改,我们新增设计成从库中选择添加,当然也很容易实现直接新增一行。 

4、然后我们可能主表中有些字段不常用,我们放在第二个tab页签中,如果还有从表还可以再增加页签 

5、还有一个需求就是,我保存时,只保存改动过的东西,比如主表只改了合同名称,从表就修改了一行,那么我们处理应该要主表只更新一个字段,从表中只修改一条数据。如果没有值被修改时,保存按钮不响应。

技术实现

前端要实现 
1、页面布局 
2、绑定控件 
3、UI与JSON数据交互的viewModel

后台web api要实现 
1、主表(增、改)及从表(增、删、改)在一个事务中保存

好我们还是在我们的mms区域中做示例,还是选择一个跟我上一篇一样的[材料接收的业务] 

上一篇中我们已经创建了材料接收的控件器RecieveController.cs,其中已经写了查询的页面Index及查询的api Get方法,现在我们先添加编辑的页面。 
在mvc controller中添加Edit Actioin

using System;
using System.Web.Mvc;
using Zephyr.Core;
using Zephyr.Models;
using Zephyr.Web.Areas.Mms.Common; namespace Zephyr.Areas.Mms.Controllers
{
public class ReceiveController : Controller
{
//查询页面
public ActionResult Index()
{
...
} //编辑页面
public ActionResult Edit(string id)
{
return View();
}
}
}

然后我们右击这个action,添加一个对应的view页面~/Views/Receive/Edit.cshtml

@{
ViewBag.Title = "Edit";
Layout = "~/Views/Shared/_Layout.cshtml";
} @section scripts{
<script src="~/Areas/Mms/ViewModels/mms.com.js"></script>
<script src="~/Areas/Mms/ViewModels/mms.viewModel.edit.js"></script>
<script type="text/javascript">
var viewModel = function (data) {
var self = this;
mms.viewModel.edit.apply(self, arguments); //继承mms.viewModel.edit
this.grid.OnAfterCreateEditor = function(editors){ //在grid行编辑开始时绑定金额=单价*数量的计算 及 加上数量的验证
mms.com.bindCalcTotalMoney(self, "Num", "UnitPrice", "Money", "TotalMoney")(editors);
$.fn.validatebox.defaults.rules.checkNum = {
validator:function(value,param){
return parseFloat(value) <= parseFloat(editors['CheckNum'].target.numberbox('getValue'));
},
message:'入库数量不能大于验收数量!'
};
};
};
var data = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));
ko.bindingViewModel(new viewModel(data));
</script>
} <div class="z-toolbar">
<a href="#" plain="true" class="easyui-linkbutton" icon="icon-save" title="保存" data-bind="click:saveClick">保存</a>
<a href="#" plain="true" class="easyui-linkbutton" icon="icon-undo" title="撤消" data-bind="click:rejectClick">撤消</a>
<a href="#" plain="true" class="easyui-linkbutton" icon="icon-user-accept" title="审核" data-bind="click:auditClick,easyuiLinkbutton:approveButton" >审核</a>
<a href="#" plain="true" class="easyui-linkbutton" icon="icon-printer" title="打印" data-bind="click:printClick">打印</a>
<div class="datagrid-btn-separator"></div>
<a href="#" class="easyui-splitbutton" data-options="menu:'#divother',iconCls:'icon-application_go'" title="其他">其他</a>
<div class="datagrid-btn-separator"></div>
<a href="#" plain="true" class="easyui-linkbutton" icon="icon-resultset_first" title="第一条" data-bind="click:firstClick,linkbuttonEnable:scrollKeys.firstEnable" ></a>
<a href="#" plain="true" class="easyui-linkbutton" icon="icon-resultset_previous" title="上一条" data-bind="click:previousClick,linkbuttonEnable:scrollKeys.previousEnable"></a>
<a href="#" plain="true" class="easyui-linkbutton" icon="icon-resultset_next" title="下一条" data-bind="click:nextClick,linkbuttonEnable:scrollKeys.nextEnable" ></a>
<a href="#" plain="true" class="easyui-linkbutton" icon="icon-resultset_last" title="最后一条" data-bind="click:lastClick,linkbuttonEnable:scrollKeys.lastEnable" ></a>
</div> <div id="divother" style="width:100px; display:none;">
<div data-options="iconCls:'icon-add'">新增</div>
<div data-options="iconCls:'icon-cross'">删除</div>
<div data-options="iconCls:'icon-arrow_refresh'">刷新</div>
</div> <div id="master" class="container_12" data-bind="inputwidth:0.9">
<div class="grid_1 lbl">单据编号</div>
<div class="grid_3 val"><input type="text" data-bind="value:form.BillNo,readOnly:true" class="z-txt readonly"/></div>
<div class="grid_1 lbl">单据日期</div>
<div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.BillDate" class="z-txt easyui-datebox" /></div>
<div class="grid_1 lbl">经办人</div>
<div class="grid_3 val "><input type="text" data-bind="value:form.DoPerson" class="z-txt easyui-validatebox" /></div> <div class="clear"></div> <div class="grid_1 lbl required">供应商</div>
<div class="grid_3 val"><input type="text" data-bind="lookupValue:form.SupplierCode" required="true" class="z-txt easyui-lookup" data-options="lookupType:'merchants',queryParams:{MerchantsProperty:'\'采购\''}"/></div>
<div class="grid_1 lbl required">库房</div>
<div class="grid_3 val"><input type="text" data-bind="comboboxValue:form.WarehouseCode,datasource:dataSource.warehouseItems" class="z-txt easyui-combobox" required="true" /></div>
<div class="grid_1 lbl required">收料日期 </div>
<div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.ReceiveDate" class="easyui-datebox z-txt" required="true" /></div> <div class="clear"></div> <div class="grid_1 lbl required">供应类型 </div>
<div class="grid_3 val"><input type="text" data-bind="comboboxValue:form.SupplyType,datasource:dataSource.supplyType" class="easyui-combobox z-txt" required="true" /></div>
<div class="grid_1 lbl required">付款方式</div>
<div class="grid_3 val"><input type="text" data-bind="comboboxValue:form.PayKind,datasource:dataSource.payKinds" class="z-txt easyui-combobox" required="true"/></div>
<div class="grid_1 lbl">合同名称 </div>
<div class="grid_3 val required"><input type="text" data-bind="value:form.ContractCode" class="z-txt" /></div> <div class="clear"></div> <div class="grid_1 lbl">原始票号</div>
<div class="grid_3 val"><input type="text" data-bind="value:form.OriginalNum" class="z-txt" /></div>
<div class="grid_1 lbl">金额</div>
<div class="grid_3 val"><input type="text" id="TotalMoney" name="TotalMoney" data-bind="numberboxValue:form.TotalMoney,readOnly:true" class="z-txt easyui-numberbox readonly" data-options="min: 0, precision: 2"/></div>
<div class="grid_1 lbl">备注</div>
<div class="grid_3 val"><input type="text" id="Remark" name="Remark" data-bind="value:form.Remark" class="z-txt" /></div> <div class="clear"></div>
</div> <div id="tt" class="easyui-tabs">
<div title="材料明细">
<table id="list" data-bind="datagrid:grid">
<thead>
<tr>
<th field="BillNo" hidden="true"></th>
<th field="RowId" hidden="true"></th>
<th field="MaterialCode" sortable="true" align="left" width="80" >材料编码 </th>
<th field="MaterialName" sortable="true" align="left" width="100" >材料名称 </th>
<th field="Model" sortable="true" align="left" width="100" >规格型号 </th>
<th field="Material" sortable="true" align="left" width="80" >材质 </th>
<th field="Unit" sortable="true" align="left" width="100" editor="{type: 'combobox', options:{data:data.dataSource.measureUnit}}">单位</th>
<th field="CheckNum" sortable="true" align="right" width="70" editor="{type: 'numberbox',options:{min: 0}}">验收数量</th>
<th field="Num" sortable="true" align="right" width="70" editor="{type: 'numberbox',options:{min: 0,validType:'checkNum'}}">入库数量</th>
<th field="UnitPrice" sortable="true" align="right" width="70" editor="{type: 'numberbox',options:{min: 0, precision: 2}}" formatter="com.formatMoney">入库单价</th>
<th field="Money" sortable="true" align="right" width="70" editor="{type: 'numberbox',options:{min: 0, precision: 2}}" formatter="com.formatMoney">金额</th>
<th field="Remark" sortable="true" align="left" width="200" editor="text">备注</th>
</tr>
</thead>
</table>
</div>
<div title="表单信息" class="hide" style="padding-top:2px;">
<div class="container_12" id="BillDetail" data-bind="inputwidth:0.9,autoheight:181">
<div class="clear"></div> <div class="grid_1 lbl">审批状态</div>
<div class="grid_3 val"><input type="text" data-bind="value:form.ApproveState,readOnly:true" class="z-txt readonly"/></div>
<div class="grid_1 lbl">审批意见</div>
<div class="grid_3 val"><input type="text" data-bind="value:form.ApproveRemark,readOnly:true" class="z-txt readonly"/></div>
<div class="grid_1 lbl">审批人 </div>
<div class="grid_3 val"><input type="text" data-bind="value:form.ApprovePerson,readOnly:true" class="z-txt readonly"/></div> <div class="clear"></div> <div class="grid_1 lbl">审批日期</div>
<div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.ApproveDate,dateboxReadOnly:true" class="z-txt easyui-datebox readonly"/></div>
<div class="grid_1 lbl">编制日期</div>
<div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.CreateDate,dateboxReadOnly:true" class="z-txt easyui-datebox readonly" /></div>
<div class="grid_1 lbl">编制人</div>
<div class="grid_3 val "><input type="text" data-bind="value:form.CreatePerson,readOnly:true" class="z-txt readonly" /></div> <div class="clear"></div> <div class="grid_1 lbl">修改日期</div>
<div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.UpdateDate,dateboxReadOnly:true" class="z-txt easyui-datebox readonly" /></div>
<div class="grid_1 lbl">修改人</div>
<div class="grid_3 val "><input type="text" data-bind="value:form.UpdatePerson,readOnly:true" class="z-txt readonly" /></div>
</div>
</div>
</div>

代码贴出来有换行变得很不整齐,没办法了,大家将就看吧。上面这估代码我还是要解释下,同样data-bind是knouckout的写法,easyui-xxx及data-optionis是easyui的写法。还有三段脚本,第一个是引入项目共通脚本,第二个是引入编辑页面共通的viewModel,第三个是继承共通的viewModel再加上本页面中的一些计算或验证之类并绑定viewModel到页面上。

我们再看看我们的编辑的共通viewModel,这段代码同样也就100行左右

/**
* 模块名:mms viewModel
* 程序名: mms.viewModel.edit.js
* Copyright(c) 2013-2015 liuhuisheng [ liuhuisheng.xm@gmail.com ]
**/
var mms = mms || {};
mms.viewModel = mms.viewModel || {}; mms.viewModel.edit = function (data) {
var self = this;
this.dataSource = data.dataSource; //下拉框的数据源
this.urls = data.urls; //api服务地址
this.resx = data.resx; //中文资源
this.scrollKeys = ko.mapping.fromJS(data.scrollKeys); //数据滚动按钮(上一条下一条)
this.form = ko.mapping.fromJS(data.form||data.defaultForm); //表单数据
this.setting = data.setting;
this.defaultRow = data.defaultRow; //默认grid行的值
this.defaultForm = data.defaultForm; //主表的默认值 this.grid = {
size: { w: 6, h: 177 },
pagination: false,
remoteSort: false,
url: ko.observable(self.urls.getdetail + self.scrollKeys.current())
};
this.gridEdit = new com.editGridViewModel(self.grid);
this.grid.onDblClickRow = self.gridEdit.begin;
this.grid.onClickRow = self.gridEdit.ended;
this.grid.toolbar = [{
text: '选择在库材料',
iconCls: 'icon-search',
handler: function () {
mms.com.selectMaterial(self, { _xml: 'mms.material_dict' });
}
}, '-', {
text: '删除材料',
iconCls: 'icon-remove',
handler: self.gridEdit.deleterow
}]; this.rejectClick = function () {
ko.mapping.fromJS(data.form, {}, self.form);
self.gridEdit.reject();
com.message('success', self.resx.rejected);
};
this.firstClick = function () {
self.scrollTo(self.scrollKeys.first());
};
this.previousClick = function () {
self.scrollTo(self.scrollKeys.previous());
};
this.nextClick = function () {
self.scrollTo(self.scrollKeys.next());
};
this.lastClick = function () {
self.scrollTo(self.scrollKeys.last());
};
this.scrollTo = function (id) {
if (id == self.scrollKeys.current()) return;
com.setLocationHashId(id);
com.ajax({
type: 'GET',
url: self.urls.getmaster + id,
success: function (d) {
ko.mapping.fromJS(d, {}, self);
ko.mapping.fromJS(d, {}, data);
}
});
self.grid.url(self.urls.getdetail + id);
self.grid.datagrid('loaded');
};
this.saveClick = function () {
self.gridEdit.ended(); //结束grid编辑状态
var post = { //传递到后台的数据
form: com.formChanges(self.form, data.form, self.setting.postFormKeys),
list: self.gridEdit.getChanges(self.setting.postListFields)
};
if ((self.gridEdit.ended() && com.formValidate()) && (post.form._changed || post.list._changed)) {
com.ajax({
url: self.urls.edit,
data: ko.toJSON(post),
success: function (d) {
com.message('success', self.resx.editSuccess);
ko.mapping.fromJS(post.form, {}, data.form); //更新旧值
self.gridEdit.accept();
}
});
}
};
this.auditClick = function () {
var updateArray = ['ApproveState', 'ApproveRemark'];
mms.com.auditDialog(this.form, function (d) {
com.ajax({
type: 'POST',
url: self.urls.audit + self.scrollKeys.current(),
data: JSON.stringify(d),
success: function () {
com.message('success', d.status == "passed" ? self.resx.auditPassed : self.resx.auditReject);
if (data.form)
for (var i in updateArray) data.form[updateArray[i]] = self.form[updateArray[i]]();
}
});
});
};
this.approveButton = {
iconCls: ko.computed(function () { return self.form.ApproveState() == "passed" ? "icon-user-reject" : "icon-user-accept"; }),
text: ko.computed(function () { return self.form.ApproveState() == "passed" ? "取消审核" : "审核"; })
};
this.printClick = function () {
com.openTab('打印报表', '/report?p1=0002&p2=2012-1-1', 'icon-printer_color');
};
};

这段代码利用了很多的ko的mapping组件去更新数据对象。需要了解下kouckoujs才比较好理解。 
这个viewModel中上面也定义了很多变量,我基本都有注释,接下来this.grid是我赋给明细表格的属性,除了这些,我data-bind=”datagrid:grid”绑定时还有给它默认的属性。这里面对明细grid的增删改操作的对象this.gridEdit = new com.editGridViewModel(self.grid); 利用到我的另一个共通的grid编辑的viewModel。这里面已经实现了对easyui datagrid的操作,我这里了给大家共享下

com.editGridViewModel = function (grid) {
var self = this;
this.begin = function (index, row) {
if (index== undefined || typeof index === 'object') {
row = grid.datagrid('getSelected');
index = grid.datagrid('getRowIndex', row);
}
self.editIndex = self.ended() ? index : self.editIndex;
grid.datagrid('selectRow', self.editIndex).datagrid('beginEdit', self.editIndex);
};
this.ended = function () {
if (self.editIndex == undefined) return true;
if (grid.datagrid('validateRow', self.editIndex)) {
grid.datagrid('endEdit', self.editIndex);
self.editIndex = undefined;
return true;
}
grid.datagrid('selectRow', self.editIndex);
return false;
};
this.addnew = function (rowData) {
if (self.ended()) {
if (Object.prototype.toString.call(rowData) != '[object Object]') rowData = {};
rowData = $.extend({_isnew:true},rowData);
grid.datagrid('appendRow', rowData);
self.editIndex = grid.datagrid('getRows').length - 1;
grid.datagrid('selectRow', self.editIndex);
self.begin(self.editIndex, rowData);
}
};
this.deleterow = function () {
var selectRow = grid.datagrid('getSelected');
if (selectRow) {
var selectIndex = grid.datagrid('getRowIndex', selectRow);
if (selectIndex == self.editIndex) {
grid.datagrid('cancelEdit', self.editIndex);
self.editIndex = undefined;
}
grid.datagrid('deleteRow', selectIndex);
}
};
this.reject = function () {
grid.datagrid('rejectChanges');
};
this.accept = function () {
grid.datagrid('acceptChanges');
var rows = grid.datagrid('getRows');
for (var i in rows) delete rows[i]._isnew;
};
this.getChanges = function (include, ignore) {
if (!include) include = [], ignore = true;
var deleted = utils.filterProperties(grid.datagrid('getChanges', "deleted"), include, ignore),
updated = utils.filterProperties(grid.datagrid('getChanges', "updated"), include, ignore),
inserted = utils.filterProperties(grid.datagrid('getChanges', "inserted"), include, ignore); var changes = { deleted: deleted, inserted: utils.minusArray(inserted, deleted), updated: utils.minusArray(updated, deleted) };
changes._changed = (changes.deleted.length + changes.updated.length + changes.inserted.length)>0; return changes;
};
this.isChangedAndValid = function () {
return self.ended() && self.getChanges()._changed;
};
};

grid的编辑实现之后接下来就是一些按钮的实现。这里主要说一下保存按钮 
this.saveClick = function () { … } 
这里面第一句话就是调用grid编辑的对象去结束行编辑,然后取得传到后台的数据,post={form:xxx,list:xxxx},form是指主表的数据,而且过滤掉了未改变的字段,而list是指我明细grid的编辑的数据结果,可以看com.editGridViewModel中的getChanges的方法,它的结构应该是list={deleted:xxx,inserted:xxx,updated:xxx}; 
然后我们还要判断主表输入验证是否通过,grid的输入验证是否通过,及它们是否有修改,满足条件才去ajax请求保存数据。

前端就说到这里,那么我们的viewModel还需要一些参数,我们还是从后台mvc controller中返回。回过头再编辑Edit的action方法,传递我们viewModel中需要的参数

public ActionResult Edit(string id)
{
var userName = MmsHelper.GetUserName();
var currentProject = MmsHelper.GetCurrentProject();
var data = new ReceiveApiController().GetEditMaster(id);
var codeService = new sys_codeService(); var model = new
{
form = data.form,
scrollKeys = data.scrollKeys,
urls = new {
getdetail = "/api/mms/receive/getdetail/", //获取明细数据api
            getmaster =  "/api/mms/receive/geteditmaster/",        //获取主表数据及数据滚动数据api
edit = "/api/mms/receive/edit/", //数据保存api
audit = "/api/mms/receive/audit/", //审核api
getrowid = "/api/mms/receive/getnewrowid/" //获取新的明细数据的主键(日语叫采番)
},
resx = new {
rejected = "已撤消修改!",
editSuccess = "保存成功!",
auditPassed ="单据已通过审核!",
auditReject = "单据已取消审核!"
},
dataSource = new{
measureUnit = codeService.GetMeasureUnitListByType(),
supplyType = codeService.GetValueTextListByType("SupplyType"),
payKinds = codeService.GetValueTextListByType("PayType"),
warehouseItems = new mms_warehouseService().GetWarehouseItems(currentProject)
},
defaultForm = new mms_receive().Extend(new { //定义主表数据的默认值
BillNo = id,
BillDate = DateTime.Now,
DoPerson = userName,
ReceiveDate = DateTime.Now,
SupplyType = codeService.GetDefaultCode("SupplyType"),
PayKind = codeService.GetDefaultCode("PayType"),
}),
defaultRow = new { //定义从表数据的默认值
CheckNum = 1,
Num = 1,
UnitPrice = 0,
Money = 0,
PrePay = 0
},
setting = new
{
postFormKeys = new string[] { "BillNo" }, //主表的主键
postListFields = new string[] { "BillNo", "RowId", //定义从表中哪些字段要传递到后台
"MaterialCode", "Unit", "CheckNum", "Num", "UnitPrice", "PrePay", "Money", "Remark" }
}
};
return View(model);
}

上面定义的这些数据,就是我们共通的viewModel中需要的数据,根据这些数据我们的viewModel就能创建出不同的实例了。 
接下来我们就开始实现Web Api服务,包括出现的urls当然的查询主表单条数据,查询明细数据,保存等。我们在ReceiveApiController中添加以下方法:

    public class ReceiveApiController : ApiController
{
// GET api/mms/send/geteditmaster 取得编辑页面中的主表数据及上一页下一页主键
public dynamic GetEditMaster(string id) {
var projectCode = MmsHelper.GetCookies("CurrentProject");
var masterService = new mms_receiveService();
return new{
form = masterService.GetModel(ParamQuery.Instance().AndWhere("BillNo", id)),
scrollKeys = masterService.ScrollKeys("BillNo", id, ParamQuery.Instance().AndWhere("ProjectCode", projectCode))
};
} // 地址:GET api/mms/send/getnewrowid 预取得新的明细表的行号
public string GetNewRowId(int id)
{
var service = new mms_receiveDetailService();
return service.GetNewKey("RowId", "maxplus",id);
} // 地址:GET api/mms/send/getdetail 功能:取得收料单明细信息
public dynamic GetDetail(string id)
{
var query = RequestWrapper
.InstanceFromRequest()
.SetRequestData("BillNo",id)
.LoadSettingXmlString(@"
<settings defaultOrderBy='MaterialCode'>
<select>
A.*, B.MaterialName,B.Model,B.Material
</select>
<from>
mms_receiveDetail A
left join mms_materialInfo B on B.MaterialCode = A.MaterialCode
</from>
<where>
<field name='BillNo' cp='equal'></field>
</where>
</settings>"); var pQuery = query.ToParamQuery();
var ReceiveService = new mms_receiveService();
var result = ReceiveService.GetDynamicListWithPaging(pQuery);
return result;
} // 地址:POST api/mms/send 功能:保存收料单数据
[System.Web.Http.HttpPost]
public void Edit(dynamic data)
{
var formWrapper = RequestWrapper.Instance().LoadSettingXmlString(@"
<settings>
<table>
mms_receive
</table>
<where>
<field name='BillNo' cp='equal'></field>
</where>
</settings>"); var listWrapper = RequestWrapper.Instance().LoadSettingXmlString(@"
<settings>
<columns ignore='MaterialName,Model,Material'></columns>
<table>
mms_receiveDetail
</table>
<where>
<field name='BillNo' cp='equal'></field>
<field name='RowId' cp='equal'></field>
</where>
</settings>"); var service = new mms_receiveService();
var result = service.Edit(formWrapper, listWrapper, data);
}
}

同样,这段代码也是利用我的框架中的RequestWrapper写出来的,我这里就不再解释RequestWrapper这个对象了,在我的上一篇博客中有解释过: 
一个共通的viewModel搞定所有的分页查询一览及数据导出:http://www.cnblogs.com/xqin/archive/2013/06/03/3114634.html

我这里只说一下最后一个保存的方法: 
传递到后台的data应该是这种结构 data={form:{a:’’,b:’’,…},list:{deleted: [{…},{…},…],inserted: [{},{},…],updated: [{},{},…]}}; 
我再定义两个RequestWrapper对象配置这个保存操作,告诉框架我这些数据应该如果去保存。然后把这个配置信息及我的数据传给框架的共通方法去处理。这样,上面的Edit方法就简单的完成了这个主从表一起保存的复杂的处理。

这样,我们的一个复杂的查看、编辑页面就完成了。可以跟上一个查询的页面连接在一起。在查询页面双击或编辑时会打开编辑页面的新的tab页。

效果展示

我们来看看这个页面: 
从查询页面双击一行数据进入到这个页面 

数据验证 

选择在库存材料

我们在主表中修改几个字段,从表中新增一条,删除一条,修改一条 

点击保存 

测试数据滚动 上一条 下一条 第一条 最后一条都没问题 就不截图了。

打印报表,我这里直接写死了一个测试报表在viewModel中,可以写成从后台传过来的 

这个报表很强大,可以直接在线编辑,点击设计按钮进入 

还有一些不常用的其它按钮我放在其它下拉按钮中。 
审核按钮在查询与编辑页面中都有,审核之后,审核按钮变成取消审核按钮 

后述

和上一篇一样,我们有了共通的edit viewModel之后,就可以非常简洁的代码完成一个比较复杂的数据编辑页面了,这个页面是我参照ERP系统中的编辑页面完成的。开发一个新的编辑页面我们要做的只有: 
1、前台页面布局修改一下 
2、可以继承共通的viewModel,然后可能还要可以加上一些验证及页面中前台的一些处理。如果没有特殊的东西可以直接用共通的viewModel即可。 
3、后台对应一个获取主表及数据滚动的数据、获取从表数据、及保存的Web Api服务,利用框架都只有几行代码,很简单。 
这样就完成了。

这个编辑页面和我之前写的查询页面都属性于典型的业务页面,基本上可以搞定企业信息化中的60%以上的页面了。写完这两篇,大家对我的框架应该有一定了解了,接下来准备写一些框架中的实现或者系统管理模块。因为在这种开发模式下前台后都有一定的工作量,我可能会分前台后台分开来写,谢谢大家的支持!

一个共通的viewModel搞定所有的编辑页面-经典ERP录入页面(easyui + knockoutjs + mvc4.0)的更多相关文章

  1. 一个共通的viewModel搞定所有的分页查询一览及数据导出(easyui + knockoutjs + mvc4.0)

    前言 大家看标题就明白了我想写什么了,在做企业信息化系统中可能大家写的最多的一种页面就是查询页面了.其实每个查询页面,除了条件不太一样,数据不太一样,其它的其实都差不多.所以我就想提取一些共通的东西出 ...

  2. JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(三):两个Viewmodel搞定增删改查

    前言:之前博主分享过knockoutJS和BootstrapTable的一些基础用法,都是写基础应用,根本谈不上封装,仅仅是避免了html控件的取值和赋值,远远没有将MVVM的精妙展现出来.最近项目打 ...

  3. 一个小工具帮你搞定实时监控Nginx服务器

    Linux运维工程师的首要职责就是保证业务7 x 24小时稳定的运行,监控Web服务器对于查看网站上发生的情况至关重要.关注最多的便是日志变动,查看实时日志文件变动大家第一反应应该是'tail -f ...

  4. 从0 开始手写一个 RPC 框架,轻松搞定!

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 来源:juejin.im/post/5c4481a4f265da613438aec3 之前在 RPC框架底层到底什么原理得知 ...

  5. 一个文件搞定Asp.net core 3.1动态页面转静态页面

    最近一个Asp.net core项目需要静态化页面,百度查找了一下,没有发现合适的.原因如下 配置麻烦. 类库引用了第三方类,修改起来麻烦. 有只支持MVC,不支持PageModel. 继承Actio ...

  6. Java 11 快要来了,编译 & 运行一个命令搞定!

    Java 11 马上要来了,原定于 9 月发布,还有不到 3 个月了,敬请期待更多新功能被加入到 11 当中,本文本讲的是 JEP 330 这个新特性. 化繁为简,一个命令编译运行源代码 看下面的代码 ...

  7. 【摸鱼神器】一次搞定 vue3的 路由 + 菜单 + tabs

    做一个管理后台,首先要设置路由,然后配置菜单(有时候还需要导航),再来一个动态tabs,最后加上权限判断. 这个是不是有点繁琐?尤其是路由的设置和菜单的配置,是不是很雷同?那么能不能简单一点呢?如果可 ...

  8. 如何让两个div在同一行显示?一个float搞定

    最近在学习div和css,遇到了一些问题也解决了很多以前以为很难搞定的问题.比如:如何让两个div显示在同一行呢?(不是用table表格,table对SE不太友好)其实,<div> 是一个 ...

  9. 3小时搞定一个简单的MIS系统案例Northwind,有视频、有源代码下载、有真相

    一.瞎扯框架.架构 楼主自从1998年从C语言.MASM.Foxbase开始学计算机开始接触这个行当16年以来,2001年干第一份与程序.软件.然后是各种屌的东西开始,差不多干了13年了,这13年来, ...

随机推荐

  1. prepareStatement createStatement

    preparedstatement具备很多优点,开发者可能通常都使用它,只有在完全是因为性能原因或者是在一行sql语句中没有变量的时候才使用通常的statement. preparedstatemen ...

  2. Android的Drawable缓存机制源码分析

    Android获取Drawable的方式一般是Resources.getDrawable(int),Framework会返回给你一个顶层抽象的Drawable对象.而在Framework中,系统使用了 ...

  3. 【转】【Asp.Net】asp.net服务器控件创建

    VS新建一个Web服务控件工程,会默认生成以下代码: namespace WebControlLibrary { [DefaultProperty("Text")] [Toolbo ...

  4. shell案例

    调用同目录下的ip.txt内容: 路径 [root@lanny ~]# pwd /root txt文件 [root@lanny ~]# cat ip.txt 10.1.1.1 10.1.1.2 10. ...

  5. C语言 共用体

    //共用体 union #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #includ ...

  6. DropDownList中显示无限级树形结构

    效果图: 数据库表: DirID:目录的ID,ParentID:目录的父路径ID,Name:目录的名字主要代码: using System;using System.Collections;using ...

  7. 用 eric6 与 PyQt5 实现python的极速GUI编程(系列03)---- Drawing(绘图)(2)-- 画点

    [概览] 本文实现如下的程序:(在窗体中绘画出[-100, 100]两个周期的正弦函数图像) 主要步骤如下: 1.在eric6中新建项目,新建窗体 2.(自动打开)进入PyQt5 Desinger,编 ...

  8. c#中的var优缺点和适用场景

    var是c# 3.0新加的特性,叫做隐式类型局部变量,大家都知道c#其实是一种强类型的语言,为什么会引入匿名类型呢? 我猜测是因为linq的原因吧,因为感觉var在linq中被大量使用.下面说下var ...

  9. 20145215《Java程序设计》第10周学习总结

    20145215<Java程序设计>第十周学习总结 教材学习内容总结 网络编程 网络概述 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据.程序员所作的事情就是把数据发送到指定 ...

  10. 网页音乐突破金币(RMB)下载限制

    我平时有时间会跳跳舞 跳舞肯定要有音乐呀 于是在网上找音乐 好不容易找到了一个网站,里面有很多很全的音乐 正准备下载呢,尼玛居然要金币! 在这里解释一下,金币你可以通过回复帖子或者发帖子得到,但是数量 ...