天天看点

业务系统设计之一:系统菜单设计

      首先看看上面的数据库设计结构,其中ID为自动编号的主键字段,Code为菜单编码(也称业务编码),Name为菜单名称, Url为单击菜单所导航的路径,ImageUrl为菜单上显示的图片地址,ShortCut为快捷键,Order为菜单项显示顺序,Grade为菜单级限,EndGrade为是否为末级菜单(既最后一菜单项),SupMenuCode为当前菜单项的父级菜单编码(对应于Code字段),完整的还应该包括是否受权限控制等字段,这里不一一列出。

      上面的数据库设计其实很简单,需要主要的字段有Code菜单编码(也称业务编码),在业务系统里通常设计为每一菜单项都可以认为是一个业务操作,这里的Code非常重要,业务系统的后续工作都是从这里开始,通常与权限设计结合使用得最多。

      系统菜单应该是多层次结构的,比如常见的Windows菜单,业务系统里的菜单设计同样如此,通过SupMenuCode字段来标记该菜单项属于那一项菜单的子菜单,如果没有父级菜单(系统顶层菜单)则标记为0。

      既然菜单有层次结构,那就一定有层次标识,通过Grade来标记;EndGrade则表示当前菜单项在该层次级别上的菜单项上是否为末级。

      分析清楚了需求和菜单设计方案,下面我们着手代码开发,下图为项目解决方案截图:

      如上图,Menu.cs里封装了数据库查询功能,App_Data下存放的Access数据库,RadControls为RadMenu控件所需要的相关文件,Default.aspx则为菜单UI界面。

      首先需要配置数据库连串,如下:

1 private static string connectionString = "Provider=Microsoft.jet.OLEDB.4.0;data Source=" 

2     + HttpContext.Current.Server.MapPath(@"App_Data\MenuDB.mdb");

      接着我们需要一个执行SQL的方法,如下代码块:

 1 private  DataTable ExecuteQuery(string cmdText)

 2 {

 3     using (OleDbConnection conn = new OleDbConnection(connectionString))

 4     {

 5         using (OleDbDataAdapter oda = new OleDbDataAdapter(cmdText, conn))

 6         {

 7             DataSet ds = new DataSet();

 8             oda.Fill(ds);

 9             return ds.Tables[0];

10         }

11     }

12 }

      最后我们还需要写两个方法,一个是查询顶层菜单的方法,一个则是实现查询菜单下的子菜单方法(做法有很多种,我个人建议把所有的数据查询出然后在操作内存数据,这里为了方便理解我故采用多次查询数据库的方式来实现)。

 1 public DataTable GetMenu()

 3     string sql = "select * from menu where grade=0";

 4     return ExecuteQuery(sql);

 5 }

 6 

 7 public DataTable GetMenuBySupCode(string supMenuCode)

 8 {

 9     string sql = "select * from menu where supmenucode='" + supMenuCode + "'";

10     return ExecuteQuery(sql);

11 }

      OK,准备工作做好后,现在就是需要到UI层上去做菜单的展现工作了。菜单的展现我通过RadContrls的RadMenu控件来实现,此控件相对ASP.NET的标准Menu控件功能强大。要使用RadMenu则需要注册控件的引用,如果你是通过RadContrls控件库安装包安装的则会自动注册到VS工具箱,和标准控件使用方法一样。

1 <%@ Register Assembly="RadMenu.Net2" Namespace="Telerik.WebControls" TagPrefix="rad" %>

1     <%--菜单开始--%>

2     <rad:RadMenu ID="SystemMenu" runat="server" Skin="Vista">

3     </rad:RadMenu>

4     <%--菜单结束--%>

      上面准备好了顶层菜单的查询方法,直接通过该方法查询出顶层菜单项对菜单进行初始化:

 1 protected void Page_Load(object sender, EventArgs e)

 3     if (!IsPostBack)

 5         InitMenu();

 6     }

 7 }

 8 

 9 Menu menu = new Menu();

10 private void InitMenu()

11 {

12     //查询出菜单配置信息

13     DataTable dtMenu = menu.GetMenu();

14 

15     for (int i = 0; i < dtMenu.Rows.Count; i++)

16     {

17         //Rad菜单项

18         RadMenuItem item = new RadMenuItem();

19 

20         item.ID = dtMenu.Rows[i]["Code"].ToString();

21         item.Text = dtMenu.Rows[i]["Name"].ToString();

22         item.Value = dtMenu.Rows[i]["Url"].ToString();

23         item.AccessKey = dtMenu.Rows[i]["ShortCut"].ToString();

24 

25         this.SystemMenu.Items.Add(item);

26 

27         //菜建子菜单

28         InitSubMenu(item, dtMenu.Rows[i]["Code"].ToString());

29     }

30 }

      我们设计为多层次菜单结构,那么在初始化顶层菜单的时候就应该判断该菜单项是否有子菜单,如果有则也初始化子菜单,如果是多级层次的结构,通过递归算法来完成多层次的初始化。如下代码块:

 1 

 2 private void InitSubMenu(RadMenuItem item, string supMenuCode)

 3 {

 4     //根据父菜单编码查询出子菜单配置项

 5     DataTable dtMenu = menu.GetMenuBySupCode(supMenuCode);

 7     if (dtMenu != null)

 8     {

 9         foreach (DataRow dataRow in dtMenu.Rows)

10         {

11             RadMenuItem subItem = new RadMenuItem();

12 

13             subItem.ID = dataRow["ID"].ToString();

14             subItem.Text = dataRow["Name"].ToString();

15             subItem.Value = dataRow["Url"].ToString();

16             subItem.AccessKey = dataRow["ShortCut"].ToString();

17 

18             item.Items.Add(subItem);

20             if (Convert.ToInt32(dataRow["EndGrade"]) != 1)

21             {

22                 InitSubMenu(subItem, Convert.ToString(dataRow["Code"]));

23             }

24         }

25     }

26 }

      如上就完成了系统的菜单设计,实际项目开发中的设计与这样的设计几乎都大同小异。主要是了解设计思想,或许看完了本文你还是没有明白为什么要这样设计,如果是这样那请你关注下一篇文章,下一篇文章中就用到了菜单中的业务编码,应该可以给一个满意的答案。

本文转自 beniao 51CTO博客,原文链接:http://blog.51cto.com/beniao/202205,如需转载请自行联系原作者