最近兩週完成了對公司某一產品的前端重構,本文記錄重構的主要思路及相關的設計內容。html
公司指望把某一管理類信息系統從項目代碼中抽取、重構爲一個可複用的產品。該系統的前端是基於 ExtJs 5 進行構造的,後端是基於 Asp.net MVC 提供的 REST 數據接口。同時,但願經過此次重構,不但能將其自己重構至可用於快速二次開發的產品,同時還要求該前端代碼要保證相對的獨立,使得同時能夠接入 .NET 和 JAVA 兩個不一樣的後端平臺所提供的數據接口。前端
舊代碼的問題編程
老系統的前端代碼以下圖所示:後端
在構造之初,並無考慮太多的產品化工做,而主要仍是爲了快速實現項目中的需求。也並無對前端代碼進行一個較好的架構設計。這致使了一些問題:緩存
重構目標架構
設計難點app
首先,與原系統一致,界面框架主要仍是採用 EXTJS 5。不一樣的是,這裏的 MVC 須要自行從新設計,Controller、View 都須要從新創建新的基類。因爲視圖控件仍是採用 EXTJS 中的控件,因此這個 MVC 框架中的 View 實際上是圖中的 ViewBuilder,其職責爲建立 EXTJS 中的控件。全部構造界面相關的代碼,都將編寫在 ViewBuilder 中。框架
其次,Controller 與 ViewBuilder 之間獨立開以後,還須要創建哪些關聯?模塊化
實現學習
目前已經實現了第一個版本。
過程當中其實還解決了以前項目中總是出現的 Ext 控件 Id 重複的問題:經過定義新的 cId 來替換 Id,並提供相應的經過 cId 查詢對應控件的方法。這樣,就算有重複的 cId 的控件,也不會有什麼問題了。
另外,完成後的框架,雖然帶來了諸多好處,可是開發者的第一感受仍是複雜了許多。以前全都堆在一個文件中的代碼,如今要分爲控制器、視圖,並且還須要基於統一的底層框架來實現,框架中的 Api 還須要慢慢熟悉,學習門檻高了很多。
PS-----------------------------------------
附上基於該 MVC 框架的某模塊的最終部分 TS 代碼:
HolidayViewBuilder.ts:
module DBI.modules.holiday { /** * 假日頁面的視圖。 */ export class HolidayViewBuilder extends ViewBuilder { buildView(): View { return this.buildGrid({ cId: 'grid', region: 'center', store: this.buildStore(), tbar: this.buildToolbar({ items: [ DBI.Workflow.createStatusComboBox({ model: this.modelName }), { cId: 'btnSearch', text: "查詢", operationName: 'Search' }, { cId: 'btnAdd', text: '添加', operationName: 'Add' }, { cId: 'btnEdit', text: '修改', operationName: 'Edit' }, { cId: 'btnDelete', text: '刪除', operationName: 'Delete' }, { cId: 'btnSubmitWF', text: '提交審批', operationName: 'SubmitWF' } ] }), columns: [ { text: "ID", width: 60, dataIndex: 'Id', hidden: true, align: "center" }, { xtype: "rownumberer", text: "序號", width: 50, align: "center" }, { text: "開始時間", width: 150, dataIndex: 'StartDate', sortable: true, align: 'center', renderer: function (value) { return Ext.util.Format.date(value, 'Y-m-d'); } }, { text: "結束時間", width: 150, dataIndex: 'EndDate', sortable: true, align: 'center', renderer: function (value) { return Ext.util.Format.date(value, 'Y-m-d'); } }, { text: "節假日名稱", width: 150, dataIndex: 'HolidayName', sortable: true, align: 'center' }, { text: "狀態", width: 150, dataIndex: 'WF_ApprovalStatus', sortable: true, align: 'center' }, { text: "審覈緣由", width: 180, dataIndex: 'WF_ApprovalReason', sortable: true, align: 'center' }, //{ text: "生效時間", width: 135, dataIndex: 'WF_EffectiveTime', sortable: true, align: 'center' }, { text: "最後更新時間", width: 150, dataIndex: 'UpdatedTime', sortable: true, align: 'center', renderer: function (value) { return Ext.util.Format.date(value, 'Y-m-d H:i:s'); } }, { text: "生效時間", width: 150, dataIndex: 'WF_EffectiveTime', sortable: true, align: 'center', renderer: function (value) { return Ext.util.Format.date(value, 'Y-m-d'); } } ] }); } } }
HolidayController.ts
module DBI.modules.holiday { /** * 假日模塊的控制器 */ export class HolidayController extends ViewController { viewBuilder = new HolidayViewBuilder(); modelName = "DBI.Holiday"; moduleTitle = "節假日管理"; store: Ext.data.IStore; grid: Ext.grid.IGridPanel; formWindow: Ext.IWindow; formPanel: Ext.IFormPanel; form: Ext.form.IBasic; init() { super.init(); this.grid = this.view; this.store = this.grid.store; this.control(this.view, { btnSearch: { click: this.onBtnSearchClick }, btnAdd: { click: this.onBtnAddClick }, btnEdit: { click: this.onBtnEditClick }, btnDelete: { click: this.onBtnDeleteClick }, btnSubmitWF: { click: this.onBtnSubmitWFClick } }); this.reloadData(); } onBtnAddClick() { this.showFormWindow(); this.formWindow.setTitle("添加節假日"); this.form.url = urls.Holiday.InsertHoliday; } /** * 打開提交申請的窗體 */ onBtnSubmitWFClick() { if (DBI.Workflow.canSubmitApply({ grid: this.grid })) { var applyController = new wf.CommonApplyWinController(); applyController.modelName = this.modelName; applyController.viewModel = { flowCode: "WF_HOLIDAY", windowTitle: "假日審批流程", columns: HolidayApporvalViewBuilder.buildApprovingGridColumns(), dataSource: new wf.ApplyWinDataSource(this.grid) }; applyController.init(); applyController.showWindow(); } } showFormWindow() { this.formWindow = this.viewBuilder.buildFormWindow(); this.formPanel = this.formWindow.getChild("form"); this.form = this.formPanel.getForm(); this.control(this.formWindow, { btnSubmit: { click: this.submitForm }, btnClose: { click: () => { this.formWindow.close(); } } }); this.formWindow.show(); } submitForm() { var form = this.form; if (!form.isValid()) return; var startDate = form.findField('StartDate').getValue(); var endDate = form.findField('EndDate').getValue(); if (startDate > endDate) { Ext.MessageBox.alert('提示', "開始時間不能大於結束時間"); return; } //提交數據到服務端。 form.submit({ success: () => { Ext.MessageBox.alert('提示', "提交成功!"); this.formWindow.close(); this.store.reload(); }, failure: () => { Ext.MessageBox.alert('提示', "提交失敗!"); this.formWindow.close(); this.store.reload(); } }); } reloadData() { var filter = DBI.Workflow.createStatusFilter(); this.store.proxy.url = DBI.OData.createUrl({ model: this.modelName, filter: filter }); this.store.load(); } } }