在上篇随笔《Winform开发框架之客户关系管理系统(CRM)的开发总结系列1-界面功能展示》中介绍了我的整个CRM系统的概貌,本篇继续本系列的文章,介绍如何基于我的《winform开发框架》的基础上进行CRM系统模块的开发工作,希望对大家在系统模块开发有所启示或者帮助。
在我整个开发框架的体系结构中,我都希望开发的业务模块尽可能重用,因此遵循这个要求,所有的模块除了一些基础模块外,尽可能和其他业务模块没有任何耦合关系,同时也可以动态对模块进行加载使用,和我在《Winform开发框架之插件化应用框架实现》的思想一样,各个模块之间可以动态组合起来,实现更多的业务整合。
本客户关系管理系统,也是基于这个目的和基础上进行模块开发,在整个项目模块开发过程中,将会利用到整个Winform开发框架的相关组件模块,包括基础界面模块、程序启动模块、权限管理模块、字典管理模块、分页控件、公用类库、附件管理等公用模块。
整个CRM系统的界面效果如下所示。
首先我们来看看CRM系统主要项目工程的布局和说明。
设计好CRM的相关数据库表后,利用C#代码生成工具Database2Sharp生成框架各层的代码,模块开始开发的时候,可以一次性把所有业务表的代码一次性生成,然后在整个新的解决方案(.sln)上进行递增式完善即可,如果是后续模块的开发,则需要增量把生成的代码,复制到相关的框架目录即可,整理后的业务逻辑层代码结构如下所示
这个时候,我们生成了界面层以下的所有分层代码,整个代码生成后,一次性即可编译通过,界面层我们另外建立一个Winform项目工程WHC.CRM.UIDx ,然后添加相关的界面引用程序集(如DevExpress的相关界面程序集)。处理完这些后,我们又可以利用C#代码生成工具Database2Sharp来实现界面的快速开发工作了,代码生成工具生成界面的操作界面如下所示,具体生成界面的操作可以参考随笔《利用代码生成工具Database2Sharp设计数据编辑界面》进行了解。
最后得到类似项目目录结构的CRM系统界面模块工程。
由于整个CRM系统包含很多界面元素,因此以上模块的界面部分只是其中一部分,如果内容较多,可以建立目录进行分类管理,这样会更加清晰。
利用C#代码生成工具Database2Sharp,可以快速生成所需要的框架界面代码,包括集成各种已有模块的界面基类、导入导出模块支持、高级查询能功能模块,各种实体类对应关系等内容,这些如果利用手工操作,效率非常低下,而且容易出错。即使利用一些代码生成工具,如果没有和现成的界面模块进行很好的整合,也需要花费大量的时间进行整理,下面通过几个界面代码的展示进行大致的了解。
1)列表显示界面的集成和分页整合
2)字典模块的整合处理(通过扩展类方法实现)
3)导入导出模块的整合
private string moduleName = "客户合同信息"; /// <summary> /// 导入Excel的操作 /// </summary> private void btnImport_Click(object sender, EventArgs e) { string templateFile = string.Format("{0}-模板.xls", moduleName); FrmImportExcelData dlg = new FrmImportExcelData(); dlg.SetTemplate(templateFile, System.IO.Path.Combine(Application.StartupPath, templateFile)); dlg.OnDataSave += new FrmImportExcelData.SaveDataHandler(ExcelData_OnDataSave); dlg.OnRefreshData += new EventHandler(ExcelData_OnRefreshData); dlg.ShowDialog(); } void ExcelData_OnRefreshData(object sender, EventArgs e) { BindData(); } bool ExcelData_OnDataSave(DataRow dr) { string customerName = dr["客户名称"].ToString(); if (string.IsNullOrEmpty(customerName)) return false; CustomerInfo customerInfo = BLLFactory<Customer>.Instance.FindByName(customerName); if (customerInfo == null) { throw new ArgumentException(string.Format("客户名称【{0}】不存在,记录已跳过", customerName)); } bool success = false; bool converted = false; DateTime dtDefault = Convert.ToDateTime("1900-01-01"); DateTime dt; ContractInfo info = new ContractInfo(); info.Customer_ID = customerInfo.ID;//客户ID info.HandNo = dr["合同编号"].ToString(); info.ExpenditureType = dr["收支类型"].ToString(); info.ContractType = dr["合同类型"].ToString(); info.ContractName = dr["合同名称"].ToString(); info.ContractMoney = dr["合同金额"].ToString().ToDecimal(); converted = DateTime.TryParse(dr["签约日期"].ToString(), out dt); if (converted && dt > dtDefault) { info.SignDate = dt; } ........................................ success = BLLFactory<Contract>.Instance.Insert(info); return success; } /// <summary> /// 导出Excel的操作 /// </summary> private void btnExport_Click(object sender, EventArgs e) { string file = FileDialogHelper.SaveExcel(string.Format("{0}.xls", moduleName)); if (!string.IsNullOrEmpty(file)) { string where = GetConditionSql(); List<ContractInfo> list = BLLFactory<Contract>.Instance.Find(where); DataTable dtNew = DataTableHelper.CreateTable("序号|int,客户名称,合同编号,收支类型,合同类型,合同名称,合同金额,公司签约人,客户签约人,签约日期,签约地点,乙方名称,合同开始日期,合同结束日期,结算情况,合同状态,关联项目,联系人,联系人电话,联系人手机,合同内容,备注说明,经办人"); DataRow dr; int j = 1; for (int i = 0; i < list.Count; i++) { dr = dtNew.NewRow(); dr["序号"] = j++; dr["客户名称"] = BLLFactory<Customer>.Instance.GetCustomerName(list[i].Customer_ID);//转义为客户名称 dr["合同编号"] = list[i].HandNo; dr["收支类型"] = list[i].ExpenditureType; dr["合同类型"] = list[i].ContractType; ...................................... dr["经办人"] = list[i].Operator; dtNew.Rows.Add(dr); } try { string error = ""; AsposeExcelTools.DataTableToExcel2(dtNew, file, out error); if (!string.IsNullOrEmpty(error)) { MessageDxUtil.ShowError(string.Format("导出Excel出现错误:{0}", error)); } else { if (MessageDxUtil.ShowYesNoAndTips("导出成功,是否打开文件?") == System.Windows.Forms.DialogResult.Yes) { System.Diagnostics.Process.Start(file); } } } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } }
4)高级查询模块的整合
private FrmAdvanceSearch dlg; private void btnAdvanceSearch_Click(object sender, EventArgs e) { if (dlg == null) { dlg = new FrmAdvanceSearch(); dlg.FieldTypeTable = BLLFactory<Contract>.Instance.GetFieldTypeList(); dlg.ColumnNameAlias = BLLFactory<Contract>.Instance.GetColumnNameAlias(); dlg.DisplayColumns = "Customer_ID,HandNo,ExpenditureType,ContractType,ContractName,ContractMoney,CompanySigner,CustomerSigner,SignDate,SignLocation,PartyBName,StartDate,EndDate,Settlement,Status,RelatedItems,Contact,ContactPhone,ContactMobile,Content,Note,Operator"; #region 下拉列表数据 //dlg.AddColumnListItem("UserType", Portal.gc.GetDictData("人员类型"));//字典列表 //dlg.AddColumnListItem("Sex", "男,女");//固定列表 //dlg.AddColumnListItem("Credit", BLLFactory<Contract>.Instance.GetFieldList("Credit"));//动态列表 #endregion dlg.ConditionChanged += new FrmAdvanceSearch.ConditionChangedEventHandler(dlg_ConditionChanged); } dlg.ShowDialog(); } void dlg_ConditionChanged(SearchCondition condition) { advanceCondition = condition; BindData(); }
5)编辑界面的基类继承