我在本系列隨筆的開始,介紹了CRM系統一個重要的客戶分類的展示界面,其中包含了從字典中加載 分類、從已有數據中加載分類、以及分組列表中加載分類等方式的實現,以及可以動態對這些節點進行 配置,實現客戶分類的界面配置處理。本文主要從邏輯代碼實現的角度上解說以上功能的實現,介紹常 規字典模塊的動態加載、客戶省份城市的動態加載、客戶分組管理、客戶分類配置管理等模塊的具體實 現。
一般情況下,我們對客戶的分類都需要動態加載,對這個客戶分類的管理,包括下面幾種分類。
1、常規字典模塊的動態加載

以上節點是從字典模塊的數據裡面進行動態加載的,根據節點的不同,顯示的內容不同。
首先我們需要在數據庫裡面建立一個表,用來記錄需要顯示的大的分類節點,如客戶狀態、客戶類型 、客戶級別這些層次的節點,如下所示。

根據這個表的內容指引,我們在動態加載裡面的子節點。
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實現節點的加載。
加載大的樹節點完畢後,我們就從字典中獲取對應的字典項目屬性進行加載了,我們不管上面的樹節 點是集成,我們只需要知道,上面每一個節點都從數據庫獲取對應的項目進行綁定即可,從字典加載子 節點的代碼邏輯如下所示。
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、客戶省份、客戶城市的動態加載
除了從數據字典中加載的節點數據,還有一種如客戶省份、客戶城市,我們知道這些數據很大,我們 如果在樹列表裡面展示全國的城市,那麼肯定是不好的用戶體驗,想想要在全國幾百個城市找一個出來 可不容易。
於是,可以設計從已有客戶所在的省份、所在的城市,把他們動態加載出來,數據就少很多,友好很 多,界面效果圖如下所示。

剛才我們看到了,從數據字典中動態加載子節點的操作了,其實這個和上面的操作類似,只是獲取數 據源的地方不同而已,我們可以根據樹的節點(特殊節點)來對數據源進行不同的加載,具體如下代碼 所示。
/// <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();
}
.....................
樹的動態加載在很多地方都可以用到,例如下面的界面中,我對訂單的各種屬性狀態進行了分類,方 便操作。

3、客戶分組的管理
除了上面兩種,還有一種來自個人的客戶組別的數據表數據,我們從其中獲取到對應的客戶分組信息 ,然後在客戶分組節點中展示出來,選擇對應的個人分組就可以獲取對應的客戶。

上面的個人分組來自對客戶的個人分組表裡面,它的管理界面如下所示。

個人分組的子節點加載操作代碼如下所示,其中除了加載已有的客戶分組外,還增加兩個分組名稱, 如“未分組客戶”和“全部客戶”,方便操作。
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),用來僅僅獲取當前 用戶的分組信息的。
4、客戶分類的配置管理
從上面對客戶的分類,我們看到已經有很多大的類別了,每個類別展開還有好幾項,這樣就構成了一 個很大的樹,但是有時候有些客戶可能不一定對所有的分類節點都感興趣,如果能夠給客戶一個選擇配 置的機會,會顯得更加友好

上面我們提供了一個單獨的界面元素配置窗口給用戶進行自定義的樹節點配置,我們約定默認(在用 戶還沒有保存配置的時候)是把所有節點勾選上去,如果用戶選定並保存了,那麼以用戶配置的為准來 加載樹列表。
下面我們來看看具體如何實現這個操作的。
首先我們在用戶初始化樹的時候,把用戶的保存列表獲取到,並保存在一個局部變量裡面,方便對節 點進行判斷,如下代碼所示。
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開發框架》的主要功能概覽如下圖所示。

伍華聰 http://www.iqidi.com