一個共通的viewModel搞定所有的編輯頁面-經典ERP錄入頁面(easyui + knockoutjs + mvc4.0)


前言

我寫代碼喜歡提取一些共通的東西出來,之前的一篇博客中說了如何用一個共通的viewModel和簡潔的后台代碼做查詢頁面,所有的查詢頁面都要對應一個數據錄入的編輯及查看明細的頁面,那么今天我們就來實現這個頁面,同樣我們也要使用一個共通的viewModel完成前台UI與JSON數據交互的處理,同樣以超簡潔的后台代碼來處理保存。

需求分析

我們先弄明白我們要做怎么樣一個編輯的頁面。
1、最上面有一個共通的工具欄,有保存、撤消、審核、打印、還有上一條、下一條、第一條、最后一條的數據滾動按鈕,還有一些其它按鈕放在下拉按鈕中。
image

2、我們這個頁面支持一個主表和從表一起保存,同一個事務,首先要有主表的錄入


image

3、其次我們還要從表的錄入grid,從表可以增刪改,我們新增設計成從庫中選擇添加,當然也很容易實現直接新增一行。
image

4、然后我們可能主表中有些字段不常用,我們放在第二個tab頁簽中,如果還有從表還可以再增加頁簽
image

5、還有一個需求就是,我保存時,只保存改動過的東西,比如主表只改了合同名稱,從表就修改了一行,那么我們處理應該要主表只更新一個字段,從表中只修改一條數據。如果沒有值被修改時,保存按鈕不響應。

技術實現

前端要實現
1、頁面布局
2、綁定控件
3、UI與JSON數據交互的viewModel

后台web api要實現
1、主表(增、改)及從表(增、刪、改)在一個事務中保存

好我們還是在我們的mms區域中做示例,還是選擇一個跟我上一篇一樣的[材料接收的業務]
image

上一篇中我們已經創建了材料接收的控件器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頁。

效果展示

我們來看看這個頁面:
從查詢頁面雙擊一行數據進入到這個頁面
image

數據驗證
image

選擇在庫存材料

我們在主表中修改幾個字段,從表中新增一條,刪除一條,修改一條
image

點擊保存
image

測試數據滾動 上一條 下一條 第一條 最后一條都沒問題 就不截圖了。

打印報表,我這里直接寫死了一個測試報表在viewModel中,可以寫成從后台傳過來的
image

這個報表很強大,可以直接在線編輯,點擊設計按鈕進入
image

還有一些不常用的其它按鈕我放在其它下拉按鈕中。
審核按鈕在查詢與編輯頁面中都有,審核之后,審核按鈕變成取消審核按鈕
image

后述

和上一篇一樣,我們有了共通的edit viewModel之后,就可以非常簡潔的代碼完成一個比較復雜的數據編輯頁面了,這個頁面是我參照ERP系統中的編輯頁面完成的。開發一個新的編輯頁面我們要做的只有:
1、前台頁面布局修改一下
2、可以繼承共通的viewModel,然后可能還要可以加上一些驗證及頁面中前台的一些處理。如果沒有特殊的東西可以直接用共通的viewModel即可。
3、后台對應一個獲取主表及數據滾動的數據、獲取從表數據、及保存的Web Api服務,利用框架都只有幾行代碼,很簡單。
這樣就完成了。

這個編輯頁面和我之前寫的查詢頁面都屬性於典型的業務頁面,基本上可以搞定企業信息化中的60%以上的頁面了。寫完這兩篇,大家對我的框架應該有一定了解了,接下來准備寫一些框架中的實現或者系統管理模塊。因為在這種開發模式下前台后都有一定的工作量,我可能會分前台后台分開來寫,謝謝大家的支持!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM