分享一個html+js+ashx+easyui+ado.net權限管理系統


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,切勿用於生產環境。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM