说明:此文章版权归作者所有,转载必注明出处,谢谢!
最近需要对系统进行调整,将原本的树形菜单调整为下拉菜单,下面简单介绍一下自己是如何实现的,由于本人水平有限,其中错误之处还请大家指正,谢谢!
环境:tomcat5.5/weblogic9.2+oracle10g+jdk1.5.0
1 设计思想
在用户登陆之后,根据其权限和角色得到他所能看到的菜单,然后在前台按照js构造下拉菜单的方式添加结点,构造多级菜单。
2 所用技术
Webwork,spring,javascript,css
3 后台的实现
DAO层:
接口ResourceDao.java
核心方法List<MenuItem> getAllSortedMenu()
实现类ResourceDaoImp.java
public List<MenuItem> getAllSortedMenu() {
String getAllSortedMenu_SQL = "select t.* from pw_core_app_res_tree t where t.resource_type='menu' start with pid='-1' connect by prior oid = pid order siblings by t.order_no";
return jt.query(getAllSortedMenu_SQL, resourceMapper);
}
Service层:
接口AuthControlManager.java
核心方法List<MenuItem> getAuthorizedMenus(Collection<Long> roleIds);
实现类AuthControlManagerImp.java
public List<MenuItem> getAuthorizedMenus(Collection<Long> roleIds) {
List<MenuItem> allSortedMenus= resourceDao.getAllSortedMenu();
List<MenuItem> authorizedResMenus=getAuthorizedResMenus(allSortedMenus,roleIds);
return authorizedResMenus;
}
其中getAuthorizedResMenus方法是根据用户的角色对菜单进行筛选,得到的结果是该用户可以看到的菜单列表
Web层(Action)
菜单模块的Action为MenuViewAction.java
成员变量:private AuthControlManager authControlManager;
Private
List<MenuItem> authorizedMenuList=newArrayList<MenuItem>(18);
方法:
public String initAuthorizedMenu() {
authorizedMenuList = authControlManager.getAuthorizedMenus(getCurrentPrincipal().getRoleIds());
return SUCCESS;
}
Xwork.xml中添加action
<action name="authorizedMenu"
class="com.rich.framework.auth.web.MenuViewAction" method="initAuthorizedMenu">
<result>jsp/framework/menu.jsp</result>
</action>
4.前台的实现
4.1 静态下拉菜单的实现(js代码及Demo见附件)
主要采用js实现,部分效果用css实现,实现原理是:按照顺序(在后台返回的菜单列表中已经实现)调用方法AddMenuItem(id, parentId, url, description, img)添加菜单项,在AddMenuItem方法中为每一个菜单项构建TMenu对象,并存入menu数组,为每个结点创建了一个层(用于显示下拉菜单),并计算出菜单的个数,以及每个菜单的层数及拥有孩子结点的菜单的孩子数目。接着,在遍历结点的过程中动态设置一些js效果(下拉,字体变色,隐藏下拉框等)。
4.2 jsp中添加下拉效果代码
首先添加js及css到authorizedMenu.action返回的页面jsp/framework/menu.jsp中
Css如下:
<STYLE type=text/css>
TABLE.menu_table{
font-size:
}
/*菜单表格的单元格*/
TABLE.menu_table TR,TD{
font-size:
}
/*收缩时的样式,分辨率在1024*768时测试有效,若分辨率变化,调整top值即可*/
.shousuo{
position:absolute;
top:2%;
right:0px;
}
/*展开时的样式,分辨率在1024*768时测试有效,若分辨率变化,调整top值即可*/
.zhankai{
position:absolute;
top:15%;
right:0px;
}
</STYLE>
这里css主要是控制鼠标移到菜单上时呈手状,以及控制点击菜单右边箭头伸缩菜单上面部分
然后需要对后台传来的列表进行遍历,调用AddMenuItem方法添加菜单结点
在需要插入下拉菜单的地方添加以下代码
<table width=100% background="<%=path %>/img/menu_bg.jpg">
<tr>
<td>
<SCRIPT LANGUAGE="JavaScript">
<!--
<ww:iterator value="authorizedMenuList">
<ww:if test="%{pid==-1}">
AddMenuItem (<ww:property value="id"/>, 0, "<ww:property value="url"/>", "<ww:property value="name"/>", "")
</ww:if>
<ww:else>
AddMenuItem (<ww:property value="id"/>, <ww:property value="pid"/>, "<ww:property value="url"/>", "<ww:property value="name"/>", "")
</ww:else>
</ww:iterator>
//-->
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript">
<!--
DrawMenu()
//-->
</SCRIPT>
</td>
</tr>
</table>
这样就完成了下拉菜单!
但是这样设计会遇到一点问题
5 设计中遇到的问题以及解决方案
问题1:页面在显示的时候提示脚本错误
解决:原因可能是下面2个之一。
一是从后台传来的菜单列表没有按顺序排好,这里的顺序是父亲结点在前,紧跟的是其孩子结点,有点类似数据结构中树的先根遍历。如果顺序不对,必然会导致js错误,采用的方法是在利用一下SQL中的connect by prior进行查询
select t.* from pw_core_app_res_tree t where t.resource_type='menu' start with pid='-1' connect by prior oid = pid
二是从后台传来的菜单列表确实已经按顺序排好了,但是因为权限设置不当等原因,导致某个用户对父亲结点资源没有权限,但是却拥有其子结点的权限,所以添加的时候也会出现js错误。
对此有几种解决办法,可以对数据库中出现该问题的用户的权限重新进行设置,但是如果系统中用户比较多的话,最好写代码测试一下,看具体是哪些用户出现此类问题,要不然一个一个去做的话会比较麻烦。除此之外,可以用程序的办法解决,这种可以说是一个偷懒的办法,治标不治本,思想是对于那些不是主菜单的子菜单进行检查,如果发现哪个子菜单没有父亲结点,那么就将其父亲结点添加进来,因为正确情况下非主菜单结点都会拥有父亲结点的。这里,要取得父亲结点,就必须取得整个菜单资源,然后放到缓存中,需要时,就从里面取得,因此在
ResourceDao.java及其实现类ResourceDaoImp.java中应该添加方法getAllMenu(),以取得所有的菜单资源,然后修改AuthControlManagerImp.java,代码如下:
public List<MenuItem> getAuthorizedMenus(Collection<Long> roleIds) {
List<MenuItem> allMenus = resourceDao.getAllMenu(); List<MenuItem> allSortedMenus= resourceDao.getAllSortedMenu();
List<MenuItem> authorizedResMenus=getAuthorizedResMenus(allSortedMenus,roleIds);
dealAuthorizedMenus(authorizedResMenus,allMenus);
return authorizedResMenus;
}
其中dealAuthorizedMenus是对之前取得的菜单列表进行处理,如果出现断层,则补上缺少的部分,否则,不作处理。
在dealAuthorizedMenus(authorizedResMenus,allMenus)过程中可能遇到的另一个问题是对list边遍历边插值导致的同步问题,会报错Concurrent Modification Exception,所以我们应尽量避免在遍历list的同时进行插值,这里我们采用的解决方法是将要添加的父亲结点放入另一个列表List<MenuItem> otherMenu = new LinkedList<MenuItem>()中,然后在对authorizedResMenus遍历完了之后,用addAll方法将otherMenu添加到authorizedResMenus中
注意到这里添加了父亲结点之后顺序又打乱了,因此必须对新列表进行重新排序,这里我们可以利用Collections.sort(authorizedMenus, menuItemComparator);方法进行排序,
menuItemComparator是自定义的一个比较规则,按照菜单项menuItem的OrderNum进行比较,(menuItem的OrderNum对应于数据库中菜单的order_no,用于排序同级菜单)
问题2 主菜单出现顺序不对,如“首页“没有出现在第一个位置
解决方法:
按照order_no对同级菜单进行排序,SQL如下:
select t.* from pw_core_app_res_tree t where t.resource_type='menu' start with pid='-1' connect by prior oid = pid order siblings by t.order_no