我在本系列随笔的开始,介绍了CRM系统一个重要的客户分类的展现界面,其中包含了从字典中加载分类、从已有数据中加载分类、以及分组列表中加载分类等方式的实现,以及能够动态对这些节点进行配置,实现客户分类的界面配置处理。本文主要从逻辑代码实现的角度上解说以上功能的实现,介绍常规字典模块的动态加载、客户省份城市的动态加载、客户分组管理、客户分类配置管理等模块的具体实现。html
通常状况下,咱们对客户的分类都须要动态加载,对这个客户分类的管理,包括下面几种分类。node
以上节点是从字典模块的数据里面进行动态加载的,根据节点的不一样,显示的内容不一样。数据库
首先咱们须要在数据库里面创建一个表,用来记录须要显示的大的分类节点,如客户状态、客户类型、客户级别这些层次的节点,以下所示。框架
根据这个表的内容指引,咱们在动态加载里面的子节点。函数
TreeNode topNode = new TreeNode("所有客户", 0, 0); this.treeView1.Nodes.Add(topNode); List<SystemTreeNodeInfo> propList = BLLFactory<SystemTree>.Instance.GetTree("客户属性分类"); foreach (SystemTreeNodeInfo nodeInfo in propList) { if (ContainTree(nodeInfo.ID)) { TreeNode subNode = new TreeNode(nodeInfo.TreeName, 1, 1); AddSystemTree(nodeInfo.Children, subNode, 2); this.treeView1.Nodes.Add(subNode); } } this.treeView1.ExpandAll(); for (int i = 0; i < this.treeView1.Nodes.Count; i++) { TreeNode node = this.treeView1.Nodes[i]; AddDictData(node, 3); }
其中使用递归函数进行建立树节点,也就是树节点能够是多层级的。工具
/// <summary> /// 从系统树形表里面获取数据,绑定客户属性分类和客户状态分类 /// </summary> private void AddSystemTree(List<SystemTreeNodeInfo> nodeList, TreeNode treeNode, int i) { foreach (SystemTreeNodeInfo nodeInfo in nodeList) { if (ContainTree(nodeInfo.ID)) { TreeNode subNode = new TreeNode(nodeInfo.TreeName, i, i); subNode.Tag = nodeInfo.SpecialTag;//用来作必定的标识 treeNode.Nodes.Add(subNode); AddSystemTree(nodeInfo.Children, subNode, i + 1); } } }
上面代码首先从一个SystemTree的业务对象里面加载列表信息,而后经过一个递归函数AddSystemTree实现节点的加载。post
加载大的树节点完毕后,咱们就从字典中获取对应的字典项目属性进行加载了,咱们无论上面的树节点是集成,咱们只须要知道,上面每个节点都从数据库获取对应的项目进行绑定便可,从字典加载子节点的代码逻辑以下所示。学习
List<DictDataInfo> dict = BLLFactory<DictData>.Instance.FindByDictType(treeNode.Text); foreach (DictDataInfo info in dict) { if (ContainTree(info.ID)) { TreeNode subNode = new TreeNode(info.Name, i, i); if (treeNode.Tag != null) { subNode.Tag = string.Format("{0}='{1}' ", treeNode.Tag, info.Value); } treeNode.Nodes.Add(subNode); } }
除了从数据字典中加载的节点数据,还有一种如客户省份、客户城市,咱们知道这些数据很大,咱们若是在树列表里面展现全国的城市,那么确定是很差的用户体验,想一想要在全国几百个城市找一个出来可不容易。this
因而,能够设计从已有客户所在的省份、所在的城市,把他们动态加载出来,数据就少不少,友好不少,界面效果图以下所示。spa
刚才咱们看到了,从数据字典中动态加载子节点的操做了,其实这个和上面的操做相似,只是获取数据源的地方不一样而已,咱们能够根据树的节点(特殊节点)来对数据源进行不一样的加载,具体以下代码所示。
/// <summary> /// 从数据库获取对应字典数据,并绑定到相关节点上 /// </summary> private void AddDictData(TreeNode treeNode, int i) { string nodeText = treeNode.Text; if (nodeText == "客户省份") { List<string> provinceList = BLLFactory<Customer>.Instance.GetCustomersProvince(); foreach (string province in provinceList) { TreeNode subNode = new TreeNode(province, i, i); if (treeNode.Tag != null) { subNode.Tag = string.Format("{0}='{1}' ", treeNode.Tag, province); } treeNode.Nodes.Add(subNode); } } else if (nodeText == "客户城市") { List<string> cityList = BLLFactory<Customer>.Instance.GetCustomersCity(); foreach (string city in cityList) { TreeNode subNode = new TreeNode(city, i, i); if (treeNode.Tag != null) { subNode.Tag = string.Format("{0}='{1}' ", treeNode.Tag, city); } treeNode.Nodes.Add(subNode); } }
经过预先在节点里面定义一些属性,咱们就能构建一个能够查询出正确数据的过滤语句了,而后在树的AfterSelect事件里面实现对条件语句的查询便可。
string treeConditionSql = ""; private void treeView1_AfterSelect(object sender, TreeViewEventArgs e) { if (e.Node != null) { //须要清空查询输入条件 this.customGridLookUpEdit1.EditValue = null; if (e.Node.Tag != null && !string.IsNullOrEmpty(e.Node.Tag.ToString())) { treeConditionSql = e.Node.Tag.ToString(); BindData(); } .....................
树的动态加载在不少地方均可以用到,例以下面的界面中,我对订单的各类属性状态进行了分类,方便操做。
除了上面两种,还有一种来自我的的客户组别的数据表数据,咱们从其中获取到对应的客户分组信息,而后在客户分组节点中展现出来,选择对应的我的分组就能够获取对应的客户。
上面的我的分组来自对客户的我的分组表里面,它的管理界面以下所示。
我的分组的子节点加载操做代码以下所示,其中除了加载已有的客户分组外,还增长两个分组名称,如“未分组客户”和“所有客户”,方便操做。
TreeNode myGroupNode = new TreeNode("我的分组", 4, 4); List<CustomerGroupNodeInfo> groupList = BLLFactory<CustomerGroup>.Instance.GetTree(LoginUserInfo.Name); AddCustomerGroupTree(groupList, myGroupNode, 3); //添加一个未分类和所有客户的组别 myGroupNode.Nodes.Add(new TreeNode("未分组客户", 3, 3)); myGroupNode.Nodes.Add(new TreeNode("所有客户", 3, 3)); this.treeView1.Nodes.Add(myGroupNode); myGroupNode.ExpandAll();
/// <summary> /// 获取客户分组并绑定 /// </summary> private void AddCustomerGroupTree(List<CustomerGroupNodeInfo> nodeList, TreeNode treeNode, int i) { foreach (CustomerGroupNodeInfo nodeInfo in nodeList) { if (ContainTree(nodeInfo.ID)) { TreeNode subNode = new TreeNode(nodeInfo.Name, i, i); treeNode.Nodes.Add(subNode); AddCustomerGroupTree(nodeInfo.Children, subNode, i); } } }
而后在AfterSelect事件中处理便可实现对应数据的查询操做了。
else if (e.Node.FullPath.IndexOf("我的分组") >= 0) { if (e.Node.Text == "所有客户") { treeConditionSql = ""; BindData(); } else if (e.Node.Text == "未分组客户") { isUserGroupName = true; BindDataWithGroup(null); } else { isUserGroupName = true; BindDataWithGroup(e.Node.Text); } }
private void BindDataWithGroup(string groupName) { //entity this.winGridViewPager1.DisplayColumns = displayColumns; this.winGridViewPager1.ColumnNameAlias = BLLFactory<Customer>.Instance.GetColumnNameAlias();//字段列显示名称转义 List<CustomerInfo> list = BLLFactory<Customer>.Instance.FindByGroupName(LoginUserInfo.Name, groupName, this.winGridViewPager1.PagerInfo); this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<CustomerInfo>(list); this.winGridViewPager1.PrintTitle = "客户信息列表"; }
上面的代码中用到了当前用户的登录名称做为一个标识(LoginUserInfo.Name),用来仅仅获取当前用户的分组信息的。
从上面对客户的分类,咱们看到已经有不少大的类别了,每一个类别展开还有好几项,这样就构成了一个很大的树,可是有时候有些客户可能不必定对全部的分类节点都感兴趣,若是可以给客户一个选择配置的机会,会显得更加友好
上面咱们提供了一个单独的界面元素配置窗口给用户进行自定义的树节点配置,咱们约定默认(在用户尚未保存配置的时候)是把全部节点勾选上去,若是用户选定并保存了,那么以用户配置的为准来加载树列表。
下面咱们来看看具体如何实现这个操做的。
首先咱们在用户初始化树的时候,把用户的保存列表获取到,并保存在一个局部变量里面,方便对节点进行判断,以下代码所示。
private void InitTree() { userTreeList = BLLFactory<UserTreeSetting>.Instance.GetTreeSetting(treeCategory, LoginUserInfo.ID.ToString());
而后咱们编写一个函数,用来判断是否须要勾选上去。刚才说到,默认若是没有保存,则须要勾选上去。
/// <summary> /// 若是列表为空或包含指定ID,则认为包含 /// </summary> /// <param name="id">树ID节点</param> /// <returns></returns> private bool ContainTree(string id) { bool result = false; if (userTreeList == null || userTreeList.Count == 0 || userTreeList.Contains(id)) { result = true; } return result; }
而后咱们添加每一个树节点的时候,使用这个函数判断是否勾选上去便可,注意每一个节点的Tag使用了一个GUID做为记录,方便保存。
List<SystemTreeNodeInfo> propList = BLLFactory<SystemTree>.Instance.GetTree("客户属性分类"); foreach (SystemTreeNodeInfo nodeInfo in propList) { TreeNode subNode = new TreeNode(nodeInfo.TreeName, 1, 1); subNode.Tag = nodeInfo.ID; subNode.Checked = ContainTree(nodeInfo.ID); AddSystemTree(nodeInfo.Children, subNode, 2); this.treeView1.Nodes.Add(subNode); } this.treeView1.ExpandAll();
最后,保存节点的时候,咱们遍历每一个节点的Tag的GUID内容,而后把它保存到用户配置表里面便可。
private void btnOK_Click(object sender, EventArgs e) { List<string> nodeIdList = new List<string>(); foreach (TreeNode node in this.treeView1.Nodes) { if (node.Checked && node.Tag != null && !string.IsNullOrEmpty(node.Tag.ToString())) { nodeIdList.Add(node.Tag.ToString()); } nodeIdList.AddRange(GetNodeIdList(node)); } bool result = BLLFactory<UserTreeSetting>.Instance.SaveTreeSetting(treeCategory, LoginUserInfo.ID.ToString(), nodeIdList); if (result) { ProcessDataSaved(null, null); MessageDxUtil.ShowTips("保存成功"); } this.Close(); }
经过以上这些操做,咱们就能在配置界面中,显示用户的选择节点,而后能够保存用户的选择内容到一个单独的配置表里面,在正式的树列表中,咱们用一样的方法来判断用户是否勾选了对应的节点,若是没有勾选,那么咱们不要建立这个节点便可,以下面的代码所示。
List<SystemTreeNodeInfo> propList = BLLFactory<SystemTree>.Instance.GetTree("客户属性分类"); foreach (SystemTreeNodeInfo nodeInfo in propList) { if (ContainTree(nodeInfo.ID)) { TreeNode subNode = new TreeNode(nodeInfo.TreeName, 1, 1); AddSystemTree(nodeInfo.Children, subNode, 2); this.treeView1.Nodes.Add(subNode); } }
以上就是个人CRM系统模块里面的一些经常使用界面元素具体实现逻辑,但愿对你们分析学习有帮助。
本CRM系统主要是基于个人《Winform开发框架》基础上进行的模块开发,其中整合了整个框架体系里面的权限管理模块、字典管理模块、Winform分页控件、公用类库、自动更新模块、附件管理模块、人员管理模块,以及后续可能须要整合的流程管理模块、邮件收发服务模块、信息通知模块等一系列内容,但愿开发出一个高效、易用的客户管理系统,同时也但愿藉此系统的开发实践,进一步改进个人代码生成工具,以及进一步完善Winform开发框架各模块的内容,达到新的一个高度。
《Winform开发框架》的主要功能概览以下图所示。
个人该CRM系统系列的几篇随笔连接以下,供阅读。
Winform开发框架之客户关系管理系统(CRM)的开发总结系列1-界面功能展现
Winform开发框架之客户关系管理系统(CRM)的开发总结系列2-基于框架的开发过程