动态多级下拉菜单(原创)

时间:2022-02-23 11:08:02

 说明:此文章版权归作者所有,转载必注明出处,谢谢!

最近需要对系统进行调整,将原本的树形菜单调整为下拉菜单,下面简单介绍一下自己是如何实现的,由于本人水平有限,其中错误之处还请大家指正,谢谢!

 

环境: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

菜单模块的ActionMenuViewAction.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中添加下拉效果代码

首先添加jscssauthorizedMenu.action返回的页面jsp/framework/menu.jsp

Css如下:

<STYLE type=text/css>

TABLE.menu_table{

font-size: 9pt ;cursor:hand;BORDER-COLLAPSE: collapse

}

/*菜单表格的单元格*/

TABLE.menu_table TR,TD{

font-size: 9pt ;cursor:hand

}

/*收缩时的样式,分辨率在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是自定义的一个比较规则,按照菜单项menuItemOrderNum进行比较,(menuItemOrderNum对应于数据库中菜单的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