近花了不少时间在重构和进一步提炼我的Web开发框架上,力求在用户体验和界面设计方面,和Winform开发框架保持一致,而在Web上,我主要采用EasyUI的前端界面处理技术,走MVC的技术路线,在重构完善过程中,很多细节花费不少时间进行研究和提炼,一步步走过来,也积累了不少经验,本系列将主要介绍我在进一步完善我的Web框架基础上积累的经验进行分享,本随笔主要介绍使用如何使用Json实体类构建菜单数据,然后在主界面中进行使用。

菜单的界面效果如下所示,菜单分为一级菜单、二级菜单、三级菜单,他们各自在位置上是不同的定义,这个界面布局规定三级菜单就是最小的菜单节点了,也就是叶子节点。

要实现以上的菜单,需要把菜单定义成相关的Json数据,然后通过脚本把它们添加到界面里面去,如下数据和脚本就是定义相关的菜单数据的。

<scripttype="text/javascript">var_menus={"default":[{"menuid":"1","icon":"icon-computer","menuname":"权限管理","menus":[{"menuid":"13","menuname":"用户管理","icon":"icon-user","url":"/User/Index"},{"menuid":"14","menuname":"组织机构管理","icon":"icon-organ","url":"/OU/Index"},{"menuid":"15","menuname":"角色管理","icon":"icon-group-key","url":"/Role/Index"},{"menuid":"16","menuname":"功能管理","icon":"icon-key","url":"/Function/Index"},{"menuid":"17","menuname":"登陆日志","icon":"icon-view","url":"/LoginLog/Index"}]},{"menuid":"2","icon":"icon-user","menuname":"其他管理","menus":[{"menuid":"21","menuname":"修改密码","icon":"icon-lock","url":"javascript:ShowPasswordDialog()"}]}],"point":[{"menuid":"3","icon":"icon-computer","menuname":"事务中心","menus":[{"menuid":"33","menuname":"测试菜单1","icon":"icon-user","url":"../Commonpage/building.htm"},{"menuid":"34","menuname":"测试菜单2","icon":"icon-organ","url":"../Commonpage/building.htm"},{"menuid":"35","menuname":"测试菜单3","icon":"icon-group-key","url":"../Commonpage/building.htm"},{"menuid":"36","menuname":"测试菜单4","icon":"icon-key","url":"../Commonpage/building.htm"}]},{"menuid":"4","icon":"icon-user","menuname":"其他菜单","menus":[{"menuid":"41","menuname":"测试菜单5","icon":"icon-lock","url":"../Commonpage/building.htm"}]}]};functionshowSubMenu(url,title,menuCategory,defaultIcon){if(defaultIcon==null||defaultIcon==""){defaultIcon="icon-table";}addTab(title,url,"icon"+defaultIcon);Clearnav();if(menuCategory!=""){addNav(_menus[menuCategory]);}}</script>

从上面的菜单Json数据来看,它是一个字典的Json数据列表,在Web界面上,通过下面的代码可以展开上面Json定义的二级菜单。

<li><ahref="#"onclick="showSubMenu('/User/Index','用户管理','default')">权限管理</a></li>

虽然上面的定义的数据能够解决菜单的显示问题,但是对于我们需要动态控制的菜单,显然做不到,因此需要把上面的json数据,通过菜单控制器进行动态生成才可以,然后在脚本里面通过Jquery的方式获取Json数据,如下所示。

var_menus={};//同步获取$.ajax({type:'GET',url:'/Menu/GetMenuData?r='+Math.random(),async:false,//同步dataType:'json',success:function(json){_menus=json;},error:function(xhr,status,error){alert("操作失败");//xhr.responseText}});

上面的GetMenuData方法,通过后台的控制器进行动态生成的,它的代码如下所示

///<summary>///获取树形展示数据///</summary>///<returns></returns>publicActionResultGetMenuData(){stringjson=GetTreeJson("-1","","");json=json.Trim(',');returnContent(string.Format("[{0}]",json));}///<summary>///递归获取树形信息///</summary>///<returns></returns>privatestringGetTreeJson(stringPID,stringfolderIcon,stringleafIcon){stringcondition=string.Format("PID='{0}'",PID);List<MenuInfo>nodeList=BLLFactory<Menu>.Instance.Find(condition);StringBuildercontent=newStringBuilder();foreach(MenuInfomodelinnodeList){stringParentID=(model.PID=="-1"?"0":model.PID);stringsubMenu=this.GetTreeJson(model.ID,folderIcon,leafIcon);stringparentMenu=string.Format("{{\"id\":\"{0}\",\"pId\":\"{1}\",\"name\":\"{2}\"",model.ID,ParentID,model.Name);if(string.IsNullOrEmpty(subMenu)){if(!string.IsNullOrEmpty(leafIcon)){parentMenu+=string.Format(",\"icon\":\"{0}\"}},",leafIcon);}else{parentMenu+="},";}}else{if(!string.IsNullOrEmpty(folderIcon)){parentMenu+=string.Format(",\"icon\":\"{0}\"}},",folderIcon);}else{parentMenu+="},";}}content.AppendLine(parentMenu.Trim());content.AppendLine(subMenu.Trim());}returncontent.ToString().Trim();}

不过对于上面的代码,我觉得虽然能解决问题,能够正确生成相关的Json代码,但是感觉不够优雅,我不喜欢使用拼凑方法构建数据。

前面看了Menu的Json脚本,我说过他是一个字典类型的Json数据格式,那么我们是否可以通过字典和实体信息来承载,然后直接通过ToJson方法出来呢?答案是可以的。

///<summary>///获取菜单的树形展示数据///</summary>///<returns></returns>publicActionResultGetMenuData(){Dictionary<string,List<MenuData>>dict=newDictionary<string,List<MenuData>>();List<MenuInfo>list=BLLFactory<Menu>.Instance.GetTopMenu(MyConstants.SystemType);inti=0;foreach(MenuInfoinfoinlist){if(!HasFunction(info.FunctionId)){continue;}List<MenuData>treeList=newList<MenuData>();List<MenuNodeInfo>nodeList=BLLFactory<Menu>.Instance.GetTreeByID(info.ID);foreach(MenuNodeInfonodeInfoinnodeList){if(!HasFunction(nodeInfo.FunctionId)){continue;}MenuDatamenuData=newMenuData(nodeInfo.ID,nodeInfo.Name,string.IsNullOrEmpty(nodeInfo.WebIcon)?"icon-computer":nodeInfo.WebIcon);foreach(MenuNodeInfosubNodeInfoinnodeInfo.Children){if(!HasFunction(subNodeInfo.FunctionId)){continue;}stringicon=string.IsNullOrEmpty(subNodeInfo.WebIcon)?"icon-organ":subNodeInfo.WebIcon;menuData.menus.Add(newMenuData(subNodeInfo.ID,subNodeInfo.Name,icon,subNodeInfo.Url));}treeList.Add(menuData);}//添加到字典里面,如果是第一个,默认用default名称stringdictName=(i++==0)?"default":info.ID;dict.Add(dictName,treeList);}stringcontent=ToJson(dict);returnContent(content.Trim(','));}

上面的代码,通过MenuData的对象数据,来承载相关的菜单信息,然后把它添加到字典Dictionary<string, List<MenuData>> dict 里面就可以了,这样的代码,没有那么多拼凑出来的感觉,是不是很好看呢?把对象转换为Json数据,直接通过ToJson就可以解决了,很简单吧。

而菜单的权限控制,就是通过集合权限管理进行判断,父菜单如果没有权限,就直接跳过,不在继续生成下面的子菜单,权限判断的如下所示。

if(!HasFunction(info.FunctionId)){continue;}

当然,在界面上展开二级菜单的操作界面,也应该通过脚本动态进行生成的,这样才能做到所有的内容动态构建。

<ulclass="navigation">@Html.Raw(@ViewBag.HeaderScript)</ul>

上面使用ViewBag对象进行传递脚本内容到界面上,其实后台生成的操作,是一行HTML代码就是了,代码类似下面的内容。

<li><ahref="#"onclick="showSubMenu('/User/Index','用户管理','default')">权限管理</a></li>

最后出来的效果,就是博客开始介绍的界面截图,没有任何变化,但是代码我们已经经过了几步的优化整理,看起来很清爽,更能实现动态变化了。