EasyUI、權限管理 這是個都快被搞爛了的組合,但是easyui的確好用,權限管理在項目中的確實用。一直以來博客園里也不少朋友分享過,但是感覺好的要不沒源碼,要不就是過度設計寫的太復雜看不懂,也懶得去看懂,還有一些不是在推廣自己的代碼生成器就是在賣權限組件,看着漂亮的UI和完善的功能就是沒源碼學習,真是惱人。
前段時間公司項目階段性結束了,就抽空把權限控制的部分抽取出來寫了個html+js+ashx+ado.net的權限管理系統分享給一些初學者,這個權限系統demo沒有MVC、沒有ORM、數據庫表都沒設外鍵關系、級聯刪除等,所有需要級聯操作的地方都是事務提交。界面上的所有操作基本都是jquery發ajax請求ashx處理,ashx處理后輸出json前台接收處理並配合easyui的組件響應給用戶。基本沒什么門檻,比較適合初學者。先看項目結構圖:
項目結構基本就是模仿PetShop的,簡單的7層,接口里定義方法,不同數據庫不同實現,工廠負責創建訪問數據庫的對象,具體訪問哪個數據庫寫在配置文件,都是老東西了也沒什么說的。Model、BLL、SQLServerDAL等類庫里的類都跟數據庫表名保持一致,新建類庫的時候修改了默認的命名空間(右鍵類庫 - 屬性 - 應用程序 - 程序集名稱&默認命名空間),調用的時候用解決方案名.類庫名.類名,這是我的個人習慣。具體查看源碼
接下來簡單分享下代碼和貼圖演示,懶的聽我啰嗦的直接跳轉到文章結尾下載源碼。戳我
一、關於登陸
登陸就是用框架提供的FormsAuthentication類來做的,基本就是寫cookie了,用戶登錄成功就加密下票證寫到cookie里,簡單的SetAuthCookie方法有點太簡單了,只能寫用戶名到cookie里。我一般用FormsAuthenticationTicket類來做,可以把整個用戶對象(userData)都寫到cookie里。如果只把用戶名寫到cookie里,這樣用戶在別的瀏覽器登錄然后執行修改密碼操作,過來之前登陸過的瀏覽器,雖然改了密碼,但還是可以繼續保持登陸狀態(博客園就是),這顯然不符合常理。我的是把用戶名和密碼都保存到cookie里,然后用戶每次訪問都取出用戶名和密碼去數據庫驗證,如果找不到記錄就干掉cookie。說到這肯定有人疑惑,把用戶密碼寫到cookie里會不會不安全,用戶密碼本身就是不可逆的md5,寫入cookie之前也再次進行了加密,我個人相信是比較安全的,且只有你自己看到cookie,如果擔心有人抓包,可以把登陸功能部署到https上(個人想法,歡迎拍磚)。
簡單來說,我的登陸邏輯:用戶訪問登陸頁面就ajax請求后台驗證cookie,只有用戶名和密碼匹配上(用戶沒修改密碼)、狀態IsAble可用(管理員沒在后台禁用此用戶)等等的情況下直接跳到首頁,其他都干掉cookie。這樣做的好處就是管理員可以很方便的控制一個用戶的狀態,就算他保存了cookie,因為服務端每次都有驗證IsAble字段,管理員也可以很方便的禁用這個用戶。還有不影響登陸的情況:比如用戶修改了自己的姓名等情況也得重寫cookie,否則從cookie里取出來的用戶名顯示到歡迎區域就不准確了,這里用FormsAuthenticationTicket就完美了,userData參數可以存很多東西。
首次訪問登陸頁面判斷是否登陸和用戶點擊登陸按鈕的示例代碼:
case "iflogin": //System.Threading.Thread.Sleep(5000); if (context.Request.IsAuthenticated) { FormsIdentity id = (FormsIdentity)context.User.Identity; FormsAuthenticationTicket tickets = id.Ticket; //獲取票證里序列化的用戶對象(反序列化) ZGZY.Model.User userCheck = new JavaScriptSerializer().Deserialize<ZGZY.Model.User>(tickets.UserData); //執行登錄操作 ZGZY.Model.User userReLogin = new ZGZY.BLL.User().UserLogin(userCheck.UserId, userCheck.UserPwd); if (userReLogin == null) { FormsAuthentication.SignOut(); context.Response.Write("{\"msg\":\"用戶名或密碼錯誤!\",\"success\":false}"); } else if (!userReLogin.IsAble) { FormsAuthentication.SignOut(); context.Response.Write("{\"msg\":\"用戶已被禁用!\",\"success\":false}"); } else { //記錄登錄日志 ZGZY.Model.LoginLog loginInfo = new Model.LoginLog(); loginInfo.UserIp = context.Request.UserHostAddress; loginInfo.City = context.Request.Params["city"] ?? "未知"; //訪問者所處城市 loginInfo.UserName = context.User.Identity.Name; loginInfo.Success = true; new ZGZY.BLL.LoginLog().WriteLoginLog(loginInfo); context.Response.Write("{\"msg\":\"已登錄過,正在跳轉!\",\"success\":true}"); } } else context.Response.Write("{\"msg\":\"nocookie\",\"success\":false}"); break; case "login": //System.Threading.Thread.Sleep(5000); string userIp = context.Request.UserHostAddress; string city = context.Request.Params["city"] ?? "未知"; string remember = context.Request.Params["remember"] ?? ""; //記住密碼天數 string name = context.Request.Params["loginName"]; string pwd = ZGZY.Common.Md5.GetMD5String(context.Request.Params["loginPwd"]); //md5加密 DateTime? lastLoginTime; if (new ZGZY.BLL.LoginLog().CheckLogin(userIp, out lastLoginTime) != null) { DateTime dtNextLogin = Convert.ToDateTime(lastLoginTime); context.Response.Write("{\"msg\":\"密碼錯誤次數達到5次,請在" + dtNextLogin.AddMinutes(30).ToShortTimeString() + "之后再登陸!\",\"success\":false}"); } else { ZGZY.Model.LoginLog loginInfo = new Model.LoginLog(); loginInfo.UserName = name; loginInfo.UserIp = userIp; loginInfo.City = city; ZGZY.Model.User currentUser = new ZGZY.BLL.User().UserLogin(name, pwd); if (currentUser == null) { context.Response.Write("{\"msg\":\"用戶名或密碼錯誤!\",\"success\":false}"); loginInfo.Success = false; new ZGZY.BLL.LoginLog().WriteLoginLog(loginInfo); } else if (currentUser.IsAble == false) { context.Response.Write("{\"msg\":\"用戶已被禁用!\",\"success\":false}"); loginInfo.Success = false; new ZGZY.BLL.LoginLog().WriteLoginLog(loginInfo); } else { //記錄登錄日志 loginInfo.Success = true; new ZGZY.BLL.LoginLog().WriteLoginLog(loginInfo); context.Response.Write("{\"msg\":\"登錄成功!\",\"success\":true}"); DateTime dateCookieExpires; //cookie有效期 switch (remember) { case "notremember": dateCookieExpires = new DateTime(9999, 12, 31); //默認時間 break; case "oneday": dateCookieExpires = DateTime.Now.AddDays(1); break; case "sevenday": dateCookieExpires = DateTime.Now.AddDays(7); break; case "onemouth": dateCookieExpires = DateTime.Now.AddDays(30); break; case "oneyear": dateCookieExpires = DateTime.Now.AddDays(365); break; default: dateCookieExpires = new DateTime(9999, 12, 31); break; } FormsAuthenticationTicket ticket = new FormsAuthenticationTicket ( 2, currentUser.UserId, DateTime.Now, dateCookieExpires, false, new JavaScriptSerializer().Serialize(currentUser) //序列化當前用戶對象 ); string encTicket = FormsAuthentication.Encrypt(ticket); HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); if (dateCookieExpires != new DateTime(9999, 12, 31)) //不是默認時間才設置過期時間,否則會話cookie cookie.Expires = dateCookieExpires; context.Response.Cookies.Add(cookie); } } break;
博客園的做法是訪問登陸頁面就把cookie干掉(如果有),我個人還是覺得有cookie再訪問登陸頁面就跳轉到首頁比較好。你可以訪問博客園的登陸頁面試試(慎點,會干掉你的cookie)
另外登陸功能還調用了sina的api獲取用戶登錄城市、同一個ip連續5次輸錯密碼就30分鍾之內不讓登陸,詳細請自己查看源碼。
二、權限控制
權限控制基本就是用戶擁有角色(可以多角色)、角色擁有菜單不同按鈕的權限(瀏覽、增加、修改、刪除等)。這樣基本做到了單用戶多角色,界面上的操作按鈕根據用戶擁有的權限顯示或者不顯示。先添加一個用戶,默認密碼123:
"已經改密"如果不勾選上,那么下次這個用戶登錄就會彈框讓他修改密碼(這個功能是跟添加用戶默認密碼是123相互呼應的)。直接用添加的用戶登錄會什么都沒有,因為此用戶沒有任何菜單權限:
左側的目錄樹是EasyUI的Tree組件,打開頁面的時候ajax取出當前用戶擁有的菜單權限然后展示出來,不同用戶看到的菜單是不一樣的。后台操作基本就是一個連表查詢,DataTable取出來然后遍歷構建這個Tree:
/// <summary> /// 根據用戶主鍵id查詢用戶可以訪問的菜單 /// </summary> public DataTable GetUserMenu(int id) { StringBuilder strSql = new StringBuilder(); strSql.Append("select distinct(m.Name) menuname,m.Id menuid,m.Icon icon,u.Id userid,u.UserId username,m.ParentId menuparentid,m.Sort menusort,m.LinkAddress linkaddress from tbUser u"); strSql.Append(" join tbUserRole ur on u.Id=ur.UserId"); strSql.Append(" join tbRoleMenuButton rmb on ur.RoleId=rmb.RoleId"); strSql.Append(" join tbMenu m on rmb.MenuId=m.Id"); strSql.Append(" where u.Id=@Id order by m.ParentId,m.Sort"); return ZGZY.Common.SqlHelper.GetDataTable(ZGZY.Common.SqlHelper.connStr, CommandType.Text, strSql.ToString(), new SqlParameter("@Id", id)); }
重新登陸下管理員賬戶添加一個瀏覽角色:
給角色授權:
角色授權使的也是EasyUI的Tree組件,初始化的時候就連表查出了這個角色已經有的按鈕權限,輸出json綁定Tree的時候已經有的按鈕權限加了:“checked”:true 就自動完成勾選了(當然,菜單和按鈕的對應事先已經在數據庫里對應上了。so,“按鈕管理”里就不會有“角色設置”)。連表查詢:
/// <summary> /// 根據角色id獲取此角色可以訪問的菜單和菜單下的按鈕(編輯角色-菜單使用) /// </summary> public DataTable GetAllMenu(int roleId) { StringBuilder sqlStr = new StringBuilder(); sqlStr.Append("select m.Id menuid,m.Name menuname,m.ParentId parentid,m.Icon menuicon,mb.ButtonId buttonid,b.Name buttonname,b.Icon buttonicon,rmb.RoleId roleid,case when isnull(rmb.ButtonId , 0) =
0 then 'false' else 'true' end checked"); sqlStr.Append(" from tbMenu m"); sqlStr.Append(" left join tbMenuButton mb on m.Id=mb.MenuId"); sqlStr.Append(" left join tbButton b on mb.ButtonId=b.Id"); sqlStr.Append(" left join tbRoleMenuButton rmb on(mb.MenuId=rmb.MenuId and mb.ButtonId=rmb.ButtonId and rmb.RoleId = @RoleId)"); sqlStr.Append(" order by m.ParentId,m.Sort,b.Sort"); return ZGZY.Common.SqlHelper.GetDataTable(ZGZY.Common.SqlHelper.connStr, CommandType.Text, sqlStr.ToString(), new SqlParameter("@RoleId", roleId)); }
拼接json:
/// <summary> /// 根據角色id獲取此角色可以訪問的菜單和菜單下的按鈕(編輯角色-菜單使用) /// </summary> public string GetAllMenu(int roleId) { DataTable dt = dal.GetAllMenu(roleId); StringBuilder sb = new StringBuilder(); sb.Append("["); DataRow[] rows = dt.Select("parentid = 0"); if (rows.Length > 0) { DataView dataView = new DataView(dt); DataTable dtDistinct = dataView.ToTable(true, new string[] { "menuname", "menuid", "parentid" }); //distinct取不重復的子節點 for (int i = 0; i < rows.Length; i++) { sb.Append("{\"id\":\"" + rows[i]["menuid"].ToString() + "\",\"text\":\"" + rows[i]["menuname"].ToString() + "\",\"children\":["); DataRow[] r_list = dtDistinct.Select(string.Format("parentid = {0}", rows[i]["menuid"])); //取子節點 if (r_list.Length > 0) //根節點下有子節點 { for (int j = 0; j < r_list.Length; j++) { sb.Append("{\"id\":\"" + r_list[j]["menuid"].ToString() + "\",\"text\":\"" + r_list[j]["menuname"].ToString() + "\",\"children\":["); DataRow[] r_listButton = dt.Select(string.Format("menuid = {0}", r_list[j]["menuid"])); //子子節點 if (r_listButton.Length > 0) //有子子節點就遍歷進去 { for (int k = 0; k < r_listButton.Length; k++) { sb.Append("{\"id\":\"" + roleId + "\",\"text\":\"" + r_listButton[k]["buttonname"].ToString() + "\",\"checked\":" + r_listButton[k]["checked"].ToString() + ",\"attributes\":{\"menuid\":\"" + r_listButton[k]["menuid"].ToString() + "\",\"buttonid\":\"" + r_listButton[k]["buttonid"].ToString() + "\"}},"); } sb.Remove(sb.Length - 1, 1); sb.Append("]},"); } else { sb.Append("]},"); //跟上面if條件之外的字符串拼上 } } sb.Remove(sb.Length - 1, 1); sb.Append("]},"); } else //根節點下沒有子節點 { sb.Append("]},"); //跟上面if條件之外的字符串拼上 } } sb.Remove(sb.Length - 1, 1); sb.Append("]"); } else { sb.Append("]"); } return sb.ToString(); }
勾選好按鈕后點擊授權就是用ajax把菜單id和按鈕id帶到后台跟角色對應上插入角色菜單按鈕id表中即可,這種角色授權的做法有很多種,對應上就好了:
上圖中:7、3、4等都是菜單id、1是按鈕id(1是瀏覽權限)。最后給用戶角色:
當然這里是可以多角色的,這里演示只給上瀏覽權限。重新登陸下就有菜單顯示了,但是沒有按鈕權限也不會顯示增加/編輯/刪除等任何按鈕:
實現要點:用過EasyUI的都知道,這種展示的圖標肯定用的是datagrid組件,datagrid組件帶toolbar,就是頂部的工具欄區域。EasyUI組件的組合非常靈活,要做的就是請求的時候根據用戶的id查出其權限決定顯示或者不顯示操作按鈕即可:
$(function () { $.ajax({ //請求當前用戶可以操作的按鈕 url: "ashx/bg_button.ashx?menucode=user&pagename=ui_user", type: "post", data: { "action": "getbutton" }, dataType: "json", timeout: 5000, success: function (data) { if (data.success) { var toolbar = getToolBar(data); //common.js if (data.browser) { //判斷是否有瀏覽權限 $("#ui_user_dg").datagrid({ //初始化datagrid url: "ashx/bg_user.ashx?action=search", striped: true, rownumbers: true, pagination: true, pageSize: 20, idField: 'Id', //這個idField必須指定為輸出的id,輸出的是Id就必須是Id,不能小寫 sortName: 'AddDate', sortOrder: 'desc', pageList: [20, 40, 60, 80, 100], frozenColumns: [[ { field: 'ck', checkbox: true }, { width: 100, title: '登錄名', field: 'UserId', sortable: true, formatter: function (value, row, index) { return value.length > 8 ? '<span title="' + value + '">' + value + '</span>' : value; } }, { width: 80, title: '姓名', field: 'UserName', sortable: true, formatter: function (value, row, index) { return value.length > 5 ? '<span title="' + value + '">' + value + '</span>' : value; } }]], columns: [[ { field: 'UserRole', title: '角色', width: 180, formatter: function (value, row, index) { return value.length > 12 ? '<span title="' + value + '">' + value + '</span>' : value; } }, { field: 'UserDepartment', title: '部門', width: 150, formatter: function (value, row, index) { return value.length > 10 ? '<span title="' + value + '">' + value + '</span>' : value; } }, { field: 'IsAble', title: '啟用', sortable: true, width: 60, align: 'center', formatter: function (value, row, index) { return value ? '<img src="themes/icon/chk_checked.gif" alt="已啟用" title="用戶已啟用" />' : '<img src="themes/icon/chk_unchecked.gif" alt="未啟用" title="用戶未啟用" />'; } }, { field: 'IfChangePwd', title: '改密', sortable: true, width: 60, align: 'center', formatter: function (value, row, index) { return value ? '<img src="themes/icon/chk_checked.gif" alt="已改密" title="用戶已改密" />' : '<img src="themes/icon/chk_unchecked.gif" alt="未改密" title="用戶未改密" />'; } }, { field: 'AddDate', title: '添加時間', sortable: true, width: 130 }, { field: 'Description', title: '簡介', sortable: true, width: 260, formatter: function (value, row, index) { return value.length > 15 ? '<span title="' + value + '">' + value + '</span>' : value; } } ]], toolbar: toolbar.length == 0 ? null : toolbar, //請求回來的當前用戶可以操作的按鈕 onDblClickRow: function (rowIndex, rowData) { //雙擊行彈框編輯 //被編輯列高亮,其他列去除高亮 $("#ui_user_dg").datagrid('clearSelections').datagrid('clearChecked').datagrid('checkRow', rowIndex); ui_user_edit(); } }); } else { $("#ui_user_layout").layout("remove", "east"); //如果沒有瀏覽權限就移除搜索 $.show_warning("提示", "無權限,請聯系管理員!"); } } else { $.show_warning("錯誤", data.result); } }, error: function (XMLHttpRequest, textStatus, errorThrown) { if (textStatus == "timeout") { $.show_warning("提示", "請求超時,請刷新當前頁重試!"); } else { $.show_warning("錯誤", textStatus + ":" + errorThrown); } } })
頁面加載的時候先去請求當前登陸用戶擁有的按鈕權限,有就展示在toolBar上,沒有就什么都不顯示。這就是動態綁定EasyUI datagrid/treeGrid系列組件的toolBar:
toolbar: toolbar.length == 0 ? null : toolbar, //請求回來的當前用戶可以操作的按鈕
因為EasyUI官方並沒有提供toolBar的動態添加和刪除方法,當時還發了幾封郵件給他們了,最后回復也不理想。沒辦法,官方不提供只能自己寫toolBar的動態綁定了,這里其實走了一些彎路(直接輸出數組實現toolbar初始化、頁面加載的時候在toolbar區域追加html等等都不行),最后群里的風騎士幫我寫了段js搞定了,大家可以自己試着動態綁定下toolBar試試,我這只是一個簡單的方法。getToolBar函數:
/** * @author 風騎士 * @requires jQuery,EasyUI * 初始化 datagrid toolbar */ getToolBar = function (data) { if (data.toolbar != undefined && data.toolbar != '') { var toolbar = []; $.each(data.toolbar, function (index, row) { var handler = row.handler; row.handler = function () { eval(handler); }; toolbar.push(row); }); return toolbar; } else { return []; } }
輸出的toolbar是這樣的(我用這個工具格式化JSON):
"toolbar": [ { "text": "添加", "iconCls": "icon-add", "handler": "ui_user_add();" }, { "text": "修改", "iconCls": "icon-application_edit", "handler": "ui_user_edit();" }, { "text": "刪除", "iconCls": "icon-delete", "handler": "ui_user_delete();" }, { "text": "角色設置", "iconCls": "icon-user_key", "handler": "ui_user_role();" }, { "text": "部門設置", "iconCls": "icon-group", "handler": "ui_user_department();" } ],
這是EasyUI官方提供DataGrid組件ToolBar的綁定實例,拼成這樣不能實現動態綁定,我很苦惱:
$('#dg').datagrid({ toolbar: [{ iconCls: 'icon-edit', handler: function(){alert('edit')} },'-',{ iconCls: 'icon-help', handler: function(){alert('help')} }] });
輸出的handler不帶function就可以了,出來的時候eval綁定下就好了,其實就是展示按鈕,並為操作按鈕綁定js方法。當然這些按鈕對應的js是實現寫好在頁面里的,如果你不點按鈕而是console調出這些添加、修改框,其實也是操作不了的,后台有權限驗證:
firebug都自動為我們提示頁面有的函數了,不點按鈕調出添加用戶操作框試試:
點擊“添加”:
后台操作日志也記錄了:
不僅按鈕的權限是及時監控的,就連瀏覽權限也是即時的,先登錄管理員賬號:
登陸后再開一個標簽頁,訪問首頁或者登陸頁,這個時候都會定位到首頁,cookie里的用戶是管理員。在新標簽頁里退出管理員用戶登陸一個沒有“用戶管理”頁面瀏覽權限的用戶,再回到之前第一個標簽頁刷新“用戶管理”,會出現:
右上角雖然顯示的用戶是管理員,但是cookie里的用戶已經變成了重新登陸的用戶了,刷新標簽頁會重新請求,然后通過輸出的json里的"browser": true 來判斷當前用戶是否有瀏覽權限,這東西用mvc的過濾器來控制就非常簡單了,后續會有分享我的MVC權限系統。
部門管理功能基本一樣,用的是EasyUI的treeGrid組件做的,有興趣的自己下載源碼看看吧。
菜單和按鈕的CRUD我都沒寫,這兩項我自己倒是更喜歡直接到庫里手動添加,有興趣的朋友可以補上代碼跟員工、角色、部門的CRUD一模一樣。
三、EasyUI使用的注意事項
第一、使用dialog一定要記得destroy,否則一直存在頁面的html里。這個系統的所有彈框添加、編輯、授權等都是用的EasyUI的dialog組件,dialog初始化引用的是一個html,大概是這樣:
$('#dd').dialog({ title: 'My Dialog', width: 400, height: 200, closed: false, cache: false, href: 'get_content.html', //使用頁面來展示內容 modal: true });
這樣做的好處是不用事先把這些編輯的html元素埋在頁面的html里。因為總體的頁面只有一個,所有的員工管理/部門管理/角色管理等都是以tab的形式展示的,如果所有頁面的所有增刪改都埋在頁面里,那么就首頁就特別臃腫了。寫成單獨的頁面調用的時候去取,用完調用dialog的destroy方法銷毀dialog框,否則一直存在頁面的html里,用完怎么找到這個dialog框呢,初始化的時候給dialog框一個id值即可:
function ui_user_add() { $("<div/>").dialog({ id: "ui_user_add_dialog", href: "html/ui_user_add.html", title: "添加用戶", height: 350, width: 460, modal: true, buttons: [{ id: "ui_user_add_btn", text: '添 加', handler: function () { $("#ui_user_useraddform").form("submit", { url: "ashx/bg_user.ashx", onSubmit: function (param) { $('#ui_user_add_btn').linkbutton('disable'); //點擊就禁用按鈕,防止連擊 param.action = 'add'; if ($(this).form('validate')) return true; else { $('#ui_user_add_btn').linkbutton('enable'); //恢復按鈕 return false; } }, success: function (data) { var dataJson = eval('(' + data + ')'); //轉成json格式 if (dataJson.success) { $("#ui_user_add_dialog").dialog('destroy'); //銷毀dialog對象 $.show_warning("提示", dataJson.msg); $("#ui_user_dg").datagrid("reload").datagrid('clearSelections').datagrid('clearChecked'); } else { $('#ui_user_add_btn').linkbutton('enable'); //恢復按鈕 $.show_warning("提示", dataJson.msg); } } }); } }], onLoad: function () { $("#ui_user_userid_add").focus(); }, onClose: function () { $("#ui_user_add_dialog").dialog('destroy'); //銷毀dialog對象 } }); }
這是一個標准的添加函數,dialog在用戶點擊確定或者點dialog的叉叉都會destroy掉;
第二、其次需要主要的就是函數的命名。建議用頁面的名字加方法名字,如果員工/部門/角色的添加函數都叫add(),那么同時打開多個標簽頁再點其中的添加就會出錯,畢竟所有標簽頁都在一個頁面,上面的用戶添加函數名字就是ui_user_add();
第三、最后就是按鈕禁用。由於用戶的網速或者服務端數據庫響應速度的問題,避免用戶多次提交表單就是用戶點了“提交”就禁用“提交”按鈕,響應回來了再開啟“提交”按鈕。就比如登陸功能,用戶輸入用戶名和密碼之后點“登陸”,你不禁用“登陸”按鈕,網速問題登陸比較慢,他可能連續點幾次“登陸”,這個時候你如果記錄了登陸日志那一次登陸會有多個登陸日志。
日常使用的各大網站也都是這么做的:百度的登陸頁面加載的時候登陸框是沒有的,它取回來結果之后再給你顯示登陸框,如果已經登陸直接被帶到了首頁:
支付寶的是你輸入用戶名和密碼之后點“登陸”,密碼框會被清空,同時登陸按鈕變成“正在進入...”,這個時候你無論如何也連擊不了:
上網的時候多留心其實會有很多發現,我上網的時候一般都開着firebug看各種請求。
四、參考文章
以下是對我寫這個小的權限demo有影響的資料(排名有先后):
1.jQuery EasyUI EasyUI官網,EasyUI的學習主要都是在這;
2.發一個easyui+ajax+ashx權限管理框架 這個作者沒寫完,我主要是借鑒了他的思想;
3.SyPro 這個很強大,有java版、php版,就是沒.net版;
4.郝冠軍的PetShop講解視頻 這個demo的項目結構完全從郝冠軍視頻里學的;
5..Net項目分層與文件夾結構大全(最佳架子獎,吐槽獎,陰溝翻船獎揭曉) 看看高人的架子,不能老局限於UI+BLL+DAL
五、源碼下載
感謝閱讀,源碼請點我
如果覺得還可以,請不吝給我點個“贊”,謝謝!
-----------------------------------------------------------------
2014.04.15更新:
有不少園友都反饋了一個問題,IE9以上版本登錄或者增加、編輯等提交表單時提示保存json文件,再要不就是點擊了確定按鈕dialog框也不銷毀等等。我的公司同事也跟我說這個問題了,公司自用的后台系統我也懶得去解決這個問題了。建議大家用火狐瀏覽器(Chrome也可以),這個demo我全程開發和調試都是火狐瀏覽器,基本上firebug不會報一個錯,IE所有版本基本都會有這樣或那樣的問題。
2014.04.18更新:
由於demo里沒寫菜單和按鈕的CRUD,這里補充如何在數據庫里添加菜單和按鈕:
添加菜單:到tbMenu表里添加一條記錄,是父級菜單ParentId就寫0,不是就找到父級菜單的Id填成當前添加子菜單的ParentId,Code隨便填,之后的html請求按鈕時跟這個Code保持一致即可,LinkAddress就是點擊菜單打開的html頁面,Icon到css里找個圖標,Sort是排序,控制菜單的順序的;
添加按鈕:先到tbButton表里添加一條記錄,跟添加菜單一樣,Code、Icon、Sort等等,然后到tbMenuButton里添加一條記錄,表示哪個菜單有這個按鈕,這個是通過主鍵Id關聯起來的。至此界面上授權的時候就可以看到新的菜單和按鈕了。但是給角色授權並給用戶賦予角色后,用戶登錄上來還是看不到新添加的按鈕。這時得到項目的Common類庫里找到ToolBarHelper類,向switch結構里添加按鈕,這里是通過Code關聯的,添加后就會輸出新的按鈕了,界面上也就能看到了,之后這個按鈕實現的功能再html頁面里寫js方法就可以了。
學習demo,切勿用於生產環境。