要设计可扩展性高的树形菜单栏和权限控制是肯定离不开数据库的
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 UserID=@UserID;
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 RoleID=@RoleID)
AND IsVisible=1 ORDER BY ParentID,OrderNO ASC
END
END
(4)存储过程测试
EXEC spGetTreeMenuAndSub 1
EXEC spGetTreeMenuAndSub 2
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脚本进行动态显示。
然后更换为testAdmin再看看
像这种树形菜单一般都是嵌入到某个页面进行展示的,所以我们需要更改一下相关逻辑:
创建一个新的页面Index.aspx
<iframe src="TreeMenu.aspx" frameborder="0" style="width:200px;height:500px;visibility: inherit"></iframe>
更改登录重定向
Response.Redirect(“Index.aspx”);
页面展示:
这样,一个高可扩展的树形菜单完成。但数据库表的配置需要严格按照现有的模式进行crud,这种配置表一般是不建议提供给用户去修改,菜单功能的调整全权由DBA负责