B/S模式树形菜单制作

要设计可扩展性高的树形菜单栏和权限控制是肯定离不开数据库的

1.数据库设计
(1)表设计

模块表:
	CREATE TABLE TreeMenu
	(
		ModuleID INT PRIMARY KEY,	--功能模块ID,同时作为权限控制
		ParentID INT,				--父级模块ID,0为功能菜单
		Title VARCHAR(40),			--功能模块名称
		Link VARCHAR(100),			--功能模块链接地址
		OrderNO INT,				--各模块显示顺序
		IsVisible BIT,				--指定模块是否可见,1可见,0不可见
	)
角色权限表:
	CREATE TABLE RolePower
	(
		RoleID INT,					--角色ID,每个登录用户那相对应有一个角色ID
		ModuleID INT,				--角色可拥有的模块ID,对应TreeMenu的ModuleID
	)
用户表:
	CREATE TABLE Users
	(
		UserID INT PRIMARY KEY,				--用户ID
		Username VARCHAR(20) UNIQUE,		--登录用户名,需要保证唯一
		Password VARCHAR(20),				--登录密码
		RoleID INT,							--用户所属角色
	)

(2)插入数据:

INSERT INTO Users(UserID,Username,Password,RoleID) VALUES(1,'admin','123456',1)
INSERT INTO Users(UserID,Username,Password,RoleID) VALUES(2,'testAdmin','123456',2)

INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(1,0,'功能模块1','',1,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(2,0,'功能模块2','',2,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(3,0,'功能模块3','',3,1)

INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(101,1,'功能菜单1_1','/Module/Module1_1.aspx',1,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(102,1,'功能菜单1_2','/Module/Module1_2.aspx',2,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(201,2,'功能菜单2_1','/Module/Module2_1.aspx',1,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(202,2,'功能菜单2_2','/Module/Module2_2.aspx',2,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(203,2,'功能菜单2_3','/Module/Module2_3.aspx',3,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(301,3,'功能菜单3_1','/Module/Module3_1.aspx',1,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(302,3,'功能菜单3_2','/Module/Module3_2.aspx',2,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(303,3,'功能菜单3_3','/Module/Module3_3.aspx',3,1)
INSERT INTO TreeMenu(ModuleID,ParentID,Title,Link,OrderNO,IsVisible) 
VALUES(304,3,'功能菜单3_4','/Module/Module3_4.aspx',4,1)

INSERT INTO RolePower(RoleID,ModuleID) VALUES(2,101)
INSERT INTO RolePower(RoleID,ModuleID) VALUES(2,102)
INSERT INTO RolePower(RoleID,ModuleID) VALUES(2,201)
INSERT INTO RolePower(RoleID,ModuleID) VALUES(2,301)

(3)存储过程设计

CREATE PROCEDURE spGetTreeMenuAndSub
	@UserID INT	 		--用户ID参数,要用来获取角色
AS
BEGIN
	DECLARE @RoleID INT
	SELECT @RoleID=RoleID FROM Users WHERE [email protected];

	IF @RoleID=1				--角色ID为1为超级用户,提供所有功能
	BEGIN
		SELECT ModuleID,ParentID,Title,Link
		FROM TreeMenu WHERE IsVisible=1 AND ParentID=0 ORDER BY OrderNO ASC

		SELECT ModuleID,ParentID,Title,Link
		FROM TreeMenu WHERE IsVisible=1 AND ParentID<>0 ORDER BY ParentID,OrderNO ASC
	END
	ELSE IF @RoleID<>1		--角色ID不为1则根据角色配置的权限给定模块子功能
	BEGIN
		SELECT ModuleID,ParentID,Title,Link
		FROM TreeMenu WHERE IsVisible=1 AND ParentID=0 ORDER BY OrderNO ASC

		SELECT ModuleID,ParentID,Title,Link FROM TreeMenu WHERE ModuleID IN(
		SELECT ModuleID FROM RolePower WHERE [email protected])
		AND IsVisible=1 ORDER BY ParentID,OrderNO ASC
	END
END

(4)存储过程测试
EXEC spGetTreeMenuAndSub 1
B/S模式树形菜单制作
EXEC spGetTreeMenuAndSub 2
B/S模式树形菜单制作
2.登录设计
登录就不做赘述,之前的博客也做过相关的解释,这里登录只做简单的处理,然后将用户ID放入Session中,这一步很重要
登录前端页面Login.aspx

<form id="form1" runat="server">
        <table>
            <tr>
                <td>账号:</td>
                <td><asp:TextBox ID="txtLoginName" runat="server" /></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><asp:TextBox ID="txtLoginPass" runat="server" TextMode="Password"/></td>
            </tr>
            <tr>
                <td><asp:Button ID="btn1" Text="登录" OnClick="Unnamed_Click" runat="server"/></td>
            </tr>
        </table>
</form>

登录页面后台Login.aspx.cs

protected void Page_Load(object sender, EventArgs e)
{

}
protected void Unnamed_Click(object sender, EventArgs e)
{
    string username = txtLoginName.Text;
    string password = txtLoginPass.Text;

    if (username == "admin" && password == "123456")
    {
        HttpContext.Current.Session["UserID"] = 1;
        Response.Redirect("TreeMenu.aspx");
    }
    if (username == "testAdmin" && password == "123456")
    {
        HttpContext.Current.Session["UserID"] = 2;
        Response.Redirect("TreeMenu.aspx");
    }
    else
    {
        Response.Write("<script type='text/javascript'>alert('账号或密码错误')</script>");
    }
}

3.树形菜单设计
前台设计TreeMenu.aspx

<!--OnItemDataBound表示当该控件绑定数据项后触发事件-->
<asp:Repeater runat="server" ID="rpt1" OnItemDataBound="Unnamed_ItemDataBound">
    <ItemTemplate>
    	<!--只要出现在ItemTemplate下的标签,会跟着DataTable的遍历一起输出-->
        <table style="width:100%;border:0px;">
            <tr>
                <td style="height:30px;" onclick="ShowHide(Sub_<%#Eval("ModuleID") %>)"><%# Eval("Title") %></td>
            </tr>
            <tr>
                <!--第一项默认需要显示-->
                <td id="Sub_<%#Eval("ModuleID")%>" <%#Convert.ToInt32(Eval("ModuleID"))==1?"":"style=\"display:none;\"" %>>
                    <table>
                        <asp:Repeater runat="server" ID="SubRpt1">
                            <ItemTemplate>
                                <tr>
                                    <td style="width:313px;height:25px;text-align:right"></td>
                                    <td id="<%#Eval("Link") %>" style="width:400px;height:25px;text-align:right"><%#Eval("Title") %></td>
                                </tr>
                            </ItemTemplate>
                        </asp:Repeater>
                    </table>
                </td>
            </tr>
        </table>
    </ItemTemplate>
</asp:Repeater>
<script type="text/javascript">
    //控制子项的显示隐藏
    function ShowHide(obj) {
        //传入的obj为其子项的ID
        var sty = obj.style;
        sty.display == "none" ? sty.display = "block" : sty.display = "none";
    }
</script>

后台设计TreeMenu.aspx.cs

DataSet ds=new DataSet();
protected override void OnInit(EventArgs e)
{
    int UserID = Convert.ToInt32(HttpContext.Current.Session["UserID"]);
    //绑定菜单项
    SqlConnection sqlConnection = new SqlConnection("Server=.;user=sa;pwd=sa;database=DBTest");
    SqlCommand sqlCommand = new SqlCommand("spGetTreeMenuAndSub", sqlConnection);
    sqlCommand.CommandType = CommandType.StoredProcedure;
    sqlCommand.Parameters.Add(new SqlParameter("@UserID", UserID));
    SqlDataAdapter sda = new SqlDataAdapter(sqlCommand);
    sda.Fill(ds);
    rpt1.DataSource=ds.Tables[0];
    rpt1.DataBind();
}

protected void Unnamed_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    //绑定菜单子项
    DataTable dt = ds.Tables[1];
    DataTable dtNew=dt.Clone();
    //确定绑定项,每次触发Repeater控件时,会把当前的Repeater作为参数传过来,可以通过该参数获取一些重要信息
    int moduleID = Convert.ToInt32(DataBinder.Eval(e.Item.DataItem, "ModuleID"));
    //根据已知的项确定其子项的行
    DataRow[] drArray = dt.Select("ParentID=" + moduleID);
    //将筛选出的行进行处理
    for (int i = 0; i < drArray.Length; i++)
    {
        DataRow drNew = dtNew.NewRow();
        drNew.ItemArray = drArray[i].ItemArray;
        dtNew.Rows.Add(drNew);
    }
    /*
    这里需要注意的是,嵌套在Repeater控件里的Repeater是不能直接获取的,需要通过传入该方法的RepeaterItemEventArgs对象去寻找指定ID的Repeater,然后在进行绑定数据源的操作
    */
    Repeater SubRpt1=(Repeater)e.Item.FindControl("SubRpt1");
    SubRpt1.DataSource = dtNew;
    SubRpt1.DataBind();
}

前端展示:
首先登录admin看一下登录后由后台生成的html

 <table style="width:100%;border:0px;">
        <tr>
            <td style="height:30px;" onclick="ShowHide(Sub_1)">功能模块1</td>
        </tr>
        <tr>
            <td id="Sub_1" >
                <table>
                    <tr>
                        <td style="width:313px;height:25px;text-align:right"></td>
                        <td id="/Module/Module1_1.aspx" style="width:400px;height:25px;text-align:right">功能菜单1_1</td>
                    </tr>
                    <tr>
                        <td style="width:313px;height:25px;text-align:right"></td>
                        <td id="/Module/Module1_2.aspx" style="width:400px;height:25px;text-align:right">功能菜单1_2</td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
    <table style="width:100%;border:0px;">
        <tr>
            <td style="height:30px;" onclick="ShowHide(Sub_2)">功能模块2</td>
        </tr>
        <tr>
            <td id="Sub_2" style="display:none;">
                <table>
                    <tr>
                        <td style="width:313px;height:25px;text-align:right"></td>
                        <td id="/Module/Module2_1.aspx" style="width:400px;height:25px;text-align:right">功能菜单2_1</td>
                    </tr>
                    <tr>
                        <td style="width:313px;height:25px;text-align:right"></td>
                        <td id="/Module/Module2_2.aspx" style="width:400px;height:25px;text-align:right">功能菜单2_2</td>
                    </tr>
                    <tr>
                        <td style="width:313px;height:25px;text-align:right"></td>
                        <td id="/Module/Module2_3.aspx" style="width:400px;height:25px;text-align:right">功能菜单2_3</td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
    <table style="width:100%;border:0px;">
        <tr>
            <td style="height:30px;" onclick="ShowHide(Sub_3)">功能模块3</td>
        </tr>
        <tr>
            <td id="Sub_3" style="display:none;">
    	        <table>
    	        	<tr>
    	                <td style="width:313px;height:25px;text-align:right"></td>
    	                <td id="/Module/Module3_1.aspx" style="width:400px;height:25px;text-align:right">功能菜单3_1</td>
    	            </tr>
    	            <tr>
    	                <td style="width:313px;height:25px;text-align:right"></td>
    	                <td id="/Module/Module3_2.aspx" style="width:400px;height:25px;text-align:right">功能菜单3_2</td>
    	            </tr>
    	            <tr>
    	                <td style="width:313px;height:25px;text-align:right"></td>
    	                <td id="/Module/Module3_3.aspx" style="width:400px;height:25px;text-align:right">功能菜单3_3</td>
    	            </tr>
    	            <tr>
    	                <td style="width:313px;height:25px;text-align:right"></td>
    	                <td id="/Module/Module3_4.aspx" style="width:400px;height:25px;text-align:right">功能菜单3_4</td>
    	            </tr>
    	        </table>
        	</td>
        </tr>
    </table>

Link列的数据显示在了td标签的id属性中,这里就不做后续点击功能模块URL跳转的事情。可以看到,第一个功能默认显示,其他功能模块的子模块数据也都一并取了出来进行隐藏,最后通过点击事件调用javascript脚本进行动态显示。

B/S模式树形菜单制作

B/S模式树形菜单制作然后更换为testAdmin再看看
B/S模式树形菜单制作

像这种树形菜单一般都是嵌入到某个页面进行展示的,所以我们需要更改一下相关逻辑:
创建一个新的页面Index.aspx

<iframe src="TreeMenu.aspx" frameborder="0" style="width:200px;height:500px;visibility: inherit"></iframe>

更改登录重定向
Response.Redirect(“Index.aspx”);
页面展示:
B/S模式树形菜单制作

这样,一个高可扩展的树形菜单完成。但数据库表的配置需要严格按照现有的模式进行crud,这种配置表一般是不建议提供给用户去修改,菜单功能的调整全权由DBA负责