Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

时间:2021-08-09 08:55:14

我在本系列随笔的开始,介绍了CRM系统一个重要的客户分类的展示界面,其中包含了从字典中加载分类、从已有数据中加载分类、以及分组列表中加载分类等方式的实现,以及可以动态对这些节点进行配置,实现客户分类的界面配置处理。本文主要从逻辑代码实现的角度上解说以上功能的实现,介绍常规字典模块的动态加载、客户省份城市的动态加载、客户分组管理、客户分类配置管理等模块的具体实现。

一般情况下,我们对客户的分类都需要动态加载,对这个客户分类的管理,包括下面几种分类。

1、常规字典模块的动态加载

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现  Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

以上节点是从字典模块的数据里面进行动态加载的,根据节点的不同,显示的内容不同。

首先我们需要在数据库里面建立一个表,用来记录需要显示的大的分类节点,如客户状态、客户类型、客户级别这些层次的节点,如下所示。

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

根据这个表的内容指引,我们在动态加载里面的子节点。

            TreeNode topNode = new TreeNode("全部客户", , );
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, , );
AddSystemTree(nodeInfo.Children, subNode, );
this.treeView1.Nodes.Add(subNode);
}
}
this.treeView1.ExpandAll(); for (int i = ; i < this.treeView1.Nodes.Count; i++)
{
TreeNode node = this.treeView1.Nodes[i];
AddDictData(node, );
}

其中使用递归函数进行创建树节点,也就是树节点可以是多层级的。

        /// <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 + );
}
}
}

上面代码首先从一个SystemTree的业务对象里面加载列表信息,然后通过一个递归函数AddSystemTree实现节点的加载。

加载大的树节点完毕后,我们就从字典中获取对应的字典项目属性进行加载了,我们不管上面的树节点是集成,我们只需要知道,上面每一个节点都从数据库获取对应的项目进行绑定即可,从字典加载子节点的代码逻辑如下所示。

                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);
}
}

2、客户省份、客户城市的动态加载

除了从数据字典中加载的节点数据,还有一种如客户省份、客户城市,我们知道这些数据很大,我们如果在树列表里面展示全国的城市,那么肯定是不好的用户体验,想想要在全国几百个城市找一个出来可不容易。

于是,可以设计从已有客户所在的省份、所在的城市,把他们动态加载出来,数据就少很多,友好很多,界面效果图如下所示。

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现   Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

刚才我们看到了,从数据字典中动态加载子节点的操作了,其实这个和上面的操作类似,只是获取数据源的地方不同而已,我们可以根据树的节点(特殊节点)来对数据源进行不同的加载,具体如下代码所示。

        /// <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();
}
.....................

树的动态加载在很多地方都可以用到,例如下面的界面中,我对订单的各种属性状态进行了分类,方便操作。

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

3、客户分组的管理

除了上面两种,还有一种来自个人的客户组别的数据表数据,我们从其中获取到对应的客户分组信息,然后在客户分组节点中展示出来,选择对应的个人分组就可以获取对应的客户。

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

上面的个人分组来自对客户的个人分组表里面,它的管理界面如下所示。

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

个人分组的子节点加载操作代码如下所示,其中除了加载已有的客户分组外,还增加两个分组名称,如“未分组客户”和“全部客户”,方便操作。

            TreeNode myGroupNode = new TreeNode("个人分组", , );
List<CustomerGroupNodeInfo> groupList = BLLFactory<CustomerGroup>.Instance.GetTree(LoginUserInfo.Name);
AddCustomerGroupTree(groupList, myGroupNode, );
//添加一个未分类和全部客户的组别
myGroupNode.Nodes.Add(new TreeNode("未分组客户", , ));
myGroupNode.Nodes.Add(new TreeNode("全部客户", , )); 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("个人分组") >= )
{
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),用来仅仅获取当前用户的分组信息的。

4、客户分类的配置管理

从上面对客户的分类,我们看到已经有很多大的类别了,每个类别展开还有好几项,这样就构成了一个很大的树,但是有时候有些客户可能不一定对所有的分类节点都感兴趣,如果能够给客户一个选择配置的机会,会显得更加友好

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

上面我们提供了一个单独的界面元素配置窗口给用户进行自定义的树节点配置,我们约定默认(在用户还没有保存配置的时候)是把所有节点勾选上去,如果用户选定并保存了,那么以用户配置的为准来加载树列表。

下面我们来看看具体如何实现这个操作的。

首先我们在用户初始化树的时候,把用户的保存列表获取到,并保存在一个局部变量里面,方便对节点进行判断,如下代码所示。

        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 == || 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, , );
subNode.Tag = nodeInfo.ID;
subNode.Checked = ContainTree(nodeInfo.ID); AddSystemTree(nodeInfo.Children, subNode, );
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, , );
AddSystemTree(nodeInfo.Children, subNode, );
this.treeView1.Nodes.Add(subNode);
}
}

以上就是我的CRM系统模块里面的一些常用界面元素具体实现逻辑,希望对大家分析学习有帮助。

本CRM系统主要是基于我的《Winform开发框架》基础上进行的模块开发,其中整合了整个框架体系里面的权限管理模块、字典管理模块、Winform分页控件、公用类库、自动更新模块、附件管理模块、人员管理模块,以及后续可能需要整合的流程管理模块、邮件收发服务模块、信息通知模块等一系列内容,希望开发出一个高效、易用的客户管理系统,同时也希望藉此系统的开发实践,进一步改进我的代码生成工具,以及进一步完善Winform开发框架各模块的内容,达到新的一个高度。

Winform开发框架》的主要功能概览如下图所示。

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

我的该CRM系统系列的几篇随笔链接如下,供阅读。

Winform开发框架之客户关系管理系统(CRM)的开发总结系列1-界面功能展示

Winform开发框架之客户关系管理系统(CRM)的开发总结系列2-基于框架的开发过程

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现

Winform开发框架之客户关系管理系统(CRM)的开发总结系列4-Tab控件页面的动态加载

Winform开发框架之客户关系管理系统(CRM)的开发总结系列3-客户分类和配置管理实现的更多相关文章

  1. Winform开发框架之客户关系管理系统&lpar;CRM&rpar;的开发总结系列4-Tab控件页面的动态加载

    在前面介绍的几篇关于CRM系统的开发随笔中,里面都整合了多个页面的功能,包括多文档界面,以及客户相关信息的页面展示,这个模块就是利用DevExpress控件的XtraTabPage控件的动态加载实现的 ...

  2. Winform开发框架之客户关系管理系统&lpar;CRM&rpar;的开发总结系列2-基于框架的开发过程

    在上篇随笔<Winform开发框架之客户关系管理系统(CRM)的开发总结系列1-界面功能展示>中介绍了我的整个CRM系统的概貌,本篇继续本系列的文章,介绍如何基于我的<winform ...

  3. Winform开发框架之客户关系管理系统&lpar;CRM&rpar;的开发总结系列1-界面功能展示

    一直以来,都希望整合一个以客户为中心的平台,有两个方面的考虑:一是实现客户数据.客户关系.客户管理等方面的整合,以便更好利用好客户的相关资源,发挥最大的营销效益:二是整合目前我的开发框架的所有模块和技 ...

  4. Winform开发框架之客户关系管理系统&lpar;CRM&rpar;的报价单和销售单的处理

    在前面介绍了很多CRM相关的界面和实现思路的随笔文章,本篇继续介绍一下系统中用到的一些经验和技巧片段.本篇随笔主要介绍客户关系管理系统(CRM)的报价单和销售单的处理界面效果,使用列表内置的选择代替弹 ...

  5. 客户关系管理系统&lpar;CRM&rpar;的开发过程中使用到的开发工具总结

    开发<客户关系管理系统(CRM)>软件过程,也就是一个标准的Winform程序的开发过程,我们可以通过这个典型的软件开发过程来了解目前的开发思路.开发理念,以及一些必要的高效率手段.本篇随 ...

  6. Java高级项目实战02:客户关系管理系统CRM系统模块分析与介绍

    本文承接上一篇:Java高级项目实战之CRM系统01:CRM系统概念和分类.企业项目开发流程 先来CRM系统结构图: 每个模块作用介绍如下: 1.营销管理 营销机会管理:针对企业中客户的质询需求所建立 ...

  7. 客户关系管理系统CRM

    http://www.cnblogs.com/Michael2397/tag/SSH%E9%A1%B9%E7%9B%AE-CRM/   客户关系管理系统

  8. 客户关系管理系统-CRM源码

    QQ:2112326142   邮箱:jxsupport@qq.com 本公司开发的CRM源代码系统一份,附源代码,本公司产品唯一销售客服QQ号:2112326142  请联系此QQ号,以免给您的工作 ...

  9. Django CRM客户关系管理系统

    CRM需求分析 随着信息化时代带来的科技创新,CRM客户关系管理系统带来的效益在已经成为很多企业提高竞争优势的一分部,CRM客户关系管理系统将企业管理和客户关系管理集成到统一的平台,其系统功能主要体现 ...

随机推荐

  1. wp8 入门到精通 Gallery

    <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.Resources> ...

  2. 百度Ueditor编辑器的Html模式自动替换样式的解决方法

    百度的Ueditor编辑器出于安全性考虑,用户在html模式下粘贴进去的html文档会自动被去除样式和转义.虽然安全的,但是非常不方便. 做一下修改把这个功能去掉. 一.打开ueditor.all.j ...

  3. Android应用性能优化笔记&lpar;java代码优化&rpar;

    Java代码优化  缓存结果:  如果计算代价过高,最好把过去的结果缓存起来. 伪代码如下: result=cache.get(n);        //输入参数n作为键 if(result==nul ...

  4. CocoaPods 报错 &lbrack;&excl;&rsqb; Error installing JSONModel

    pod install p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #34bd26 } span.s1 { } ...

  5. 大数运算的算法设计和C&plus;&plus;实现

    1.背景 工作中遇到过需要进行极大数据的存储和运算的场景,当时使用Python解决了这个问题,在Python中,整数没有位数限制,使用起来很方便.但是当程序主体使用C/C++实现时,就比较麻烦.所以考 ...

  6. Spring Boot缓存应用实践

    缓存是最直接有效提升系统性能的手段之一.个人认为用好用对缓存是优秀程序员的必备基本素质. 本文结合实际开发经验,从简单概念原理和代码入手,一步一步搭建一个简单的二级缓存系统. 一.通用缓存接口 1.缓 ...

  7. g&lowbar;thread&lowbar;init

    NAME g_thread_init - 初始化线程系统 SYNOPSIS #include <glib.h> //in gthread.h void g_thread_init (GTh ...

  8. js 年份左右点击加减

    默认为今年 var date = new Date; $scope.year = date.getFullYear(); //年份减 $scope.yearPrev = function(){ $sc ...

  9. AtCoder Regular Contest 077 E - guruguru

    https://arc077.contest.atcoder.jp/tasks/arc077_c 有m个点围成一个圈,按顺时针编号为1到m,一开始可以固定一个位置x,每次操作可以往顺时针方向走一步或直 ...

  10. 《剑指offer》第十五题(二进制中1的个数)

    // 面试题:二进制中1的个数 // 题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数.例如 // 把9表示成二进制是1001,有2位是1.因此如果输入9,该函数输出2. #inclu ...