一個共通的viewModel搞定所有的編輯頁面
前言
我寫程式碼喜歡提取一些共通的東西出來,之前的一篇部落格中說了如何用一個共通的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 [ [email protected] ] **/ 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和簡潔的後臺程式碼做查詢頁面,所有的查詢頁面都要對應一個數據錄入的編輯及檢視明細的頁面,那麼今天我們就來實現這個頁面,同樣我們也要使用一個共通的viewModel完成前臺UI與JSON資料互動
近期出現了可綜合利用Shodan裝置搜尋引擎和Metasploit滲透測試工具的Python程式碼。該程式碼會用Shodan.io自動搜尋有漏洞的線上裝置,隨後使用Metasploit的漏洞利用資料庫劫持計算機和其他線上裝置。
近期出現了可綜合利用Shodan裝置搜尋引擎和Metasploit滲透測試工具的Python程式碼。該程式碼會用Shodan.io自動搜尋有漏洞的線上裝置,隨後使用Metasploit的漏洞利用資料庫劫持計算機和其他線上裝置。
只需點選執行,該指令碼就會爬取網際網路,尋找可以攻擊的脆弱主機(通常
用一個模型就能實現所有型別的風格轉換!一個名為Arbitrary Image Stylization in the Browser的專案最近火起來。
作者是日本小哥Reiichiro Nakano,他用TensorFlow.js在瀏覽器中構建了一個使用任意影象進行風格化的demo。
不像以前
先來兩個基礎技能:shell下命令列補全和萬用字元。
Tab 鍵具有檔名補全功能,單擊補全,雙擊列出檔案列表。同樣的功能使用與命令列補全。
萬用字元
* 匹配任意長度字串
? 匹配一個字串
[ ]
專案中,我們用得最多的元素就是列表了,在Android 中,實現列表用原生的RecyclerView就能滿足需求,關於RecyclerView 的基礎使用這裡不做過多的介紹,網上有太多的博文介紹了。本篇文章將介紹自己封裝的一個Adapter,幫你快速高效的新增一個列表(包 ted round isp mar head cell tex oct meta 單行文本
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<t eigrp 水平分割 ccna EIGRP水平分割1.1.1.所需設備下面是做這個實驗練習所需的設備:1) 三臺具有一個串行端口的Cisco路由器;2) Cisco IOS 10.0版或更高;3) 一臺運行了終端仿真程序的PC; 4) 三根Cisco DTE/DCE交叉電纜;5) 一根Cisco扁
網上各種跨域教程,各種實踐,各種問答,除了簡單的 jsonp 以外,很多說 CORS 的都是行不通的,老是缺那麼一兩個關鍵的配置。本文只想解決問題,所有的程式碼經過親自實踐。
本文解決跨域中的 get、post、data、cookie 等這些問題。
本文只
眾所周知,在C++,記憶體的管理是程式設計師的任務,包括物件的建立和回收(記憶體的申請和釋放),而在java中,我們可以通過以下四種方式建立物件(面試考點):
new關鍵字建立物件
clone方法克隆產生物件
反序列化獲得物件
通過反射建立物件
而
你在列印Excel表格的時候,有沒有出現過這種情況:本該一頁顯示的內容硬生生被分成兩頁列印。結果只能儘量調小字型、單元格大小,將表格縮成一頁?
其實不用這麼麻煩的,想將表格列印在一張紙上,無需折騰字型、單元格大小,只要一個簡單的設定,就能將Excel表格自動調整為一頁列印,快來學學吧,以後就再也
網上各種跨域教程,各種實踐,各種問答,除了簡單的 jsonp 以外,很多說 CORS 的都是行不通的,老是缺那麼一兩個關鍵的配置。本文只想解決問題,所有的程式碼經過親自實踐。
本文解決跨域中的 get、post、data、cookie 等這些問題。
本文只會說 g
今天要分享的是用一個二維碼成功實現微信防封的經驗,別不信,已經有不少人都用過了。
適合人群:擔心自己的微訊號、微信群被封的人群,比如做微商、代理、淘寶客的群主。
目標:幫助這部分人群避免被封號封群。
其實,這個防封的原理和過程很簡單,就是把風險轉移出去,不在微信裡涉及敏感話題內容。
搭建整套物聯網系統的方法有很多,最近四處搗鼓,使用python + 阿里雲搭建一套最簡單的物聯絡統,可以將微控制器上的資料通過阿里雲傳輸到PC端。
學習Python中有不明白推薦加入交流裙
網上各種跨域教程,各種實踐,各種問答,除了簡單的 jsonp 以外,很多說 CORS 的都是行不通的,老是缺那麼一兩個關鍵的配置。本文只想解決問題,所有的程式碼經過親自實踐。
本文解決跨域中的 get、post、data、cookie 等這些問題。
前言:之前博主分享過knockoutJS和BootstrapTable的一些基礎用法,都是寫基礎應用,根本談不上封裝,僅僅是避免了html控制元件的取值和賦值,遠遠沒有將MVVM的精妙展現出來。最近專案打算正式將ko用起來,於是乎對ko和bootstraptable做了一些封裝,在此分享出來供園友們參考。封裝
scrapyd官方文件
首先,我們來了解一下什麼是scrapyd
scrapyd是執行scrapy爬蟲的服務程式,它支援以http命令方式釋出、刪除、啟動、停止爬蟲程式。而且scrapyd可以同
第一步:
sqlmap基於Python,所以首先下載:
http://yunpan.cn/QiCBLZtGGTa7U 訪問密碼 c26e
第二步:
安裝Python,將sqlmap解壓到Python根目錄下;
第三步:
小試牛刀,檢視sqlmap版本:
python sqlmap/sqlmap.py -
新概念三 Lesson 14 A noble gangster 貴族歹徒
There was a time when the owners of shops and businesses in Chicago had to pay large sums ofmon
這篇文章主要分享unity中與editor外掛等相關的使用,比較基礎,不過如果都掌握了就可以擴充套件寫一些unity外掛了,平時開發中也會提升工作效率。
editor相關指令碼一定要放在Editor資料夾下,繼承monobehaviour的檔案不要放到Editor資料
相關推薦
一個共通的viewModel搞定所有的編輯頁面
一鍵黑客工具:一個Python指令碼搞定所有攻擊操作
今天給你介紹一款黑客神器!一個Python指令碼搞定所有攻擊!
一個模型搞定所有風格轉換,直接在瀏覽器實現(demo+程式碼)
Shell基本命令([記住]一個man搞定所有)
RecyclerView Adapter 優雅封裝,一個Adapter搞定所有列表
css搞定所有垂直居中問題
一個拓撲全搞定-- EIGRP水平分割
搞定所有的跨域請求問題 : jsonp & CORS
一篇文章徹底搞定所有GC面試問題
Excel表格太大,怎麼列印在一張紙上?一個鍵1秒搞定!
搞定所有的跨域請求問題 : jsonp & CORS
手把手教你用一個二維碼搞定微信防封,親測有效
Python搭建物聯網,一招搞定所有代理商
搞定所有的跨域請求問題 jsonp CORS
JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(三):兩個Viewmodel搞定增刪改查
scrapyd的API呼叫方法難記?一個Python指令碼輕鬆搞定
安全攻防之SQL注入(通過sqlmap搞定所有問題)
36篇精品文章搞定所有TOEIC單詞
開發unity外掛——一次搞定unity編輯器常用功能