最近两周完成了对公司某一产品的前端重构,本文记录重构的主要思路及相关的设计内容。
公司期望把某一管理类信息系统从项目代码中抽取、重构为一个可复用的产品。该系统的前端是基于 ExtJs 5 进行构造的,后端是基于 Asp.net MVC 提供的 REST 数据接口。同时,希望通过这次重构,不但能将其本身重构至可用于快速二次开发的产品,同时还要求该前端代码要保证相对的独立,使得同时可以接入 .NET 和 JAVA 两个不同的后端平台所提供的数据接口。
旧代码的问题
老系统的前端代码如下图所示:
在构造之初,并没有考虑太多的产品化工作,而主要还是为了快速实现项目中的需求。也并没有对前端代码进行一个较好的架构设计。这导致了一些问题:
重构目标
设计难点
首先,与原系统一致,界面框架主要还是采用 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();
}
}
}