zoukankan      html  css  js  c++  java
  • ExtJs十(ExtJs Mvc用户管理之二)

    前言

    为了防止意外情况,这里添加了try模块,在错误的时候会将错误信息作为Msg关键字的值返回。

    而在客户端脚本,目前是没有处理错误信息的代码的,因而在这里返回的错误信息,客户端是看不到。如果要处理这样的错误信息,就要在Store的Proxy中监听exception事件。因为exception事件的回调函数是一致的,因而可以统一到一个函数中处理,就不用复制再复制了。

    先切换到Index.cshtml,在Ext.ns下添加以下代码定义处理exception事件的回调:

                ExtMVCOne.ProxyException = function (proxy, response, opts) {
                    Ext.Msg.alert("错误信息", opts.error);
                }

    在exception事件的回调函数的第三个参数返回的是Operation对象,当success为false时,它会将Msg关键字的值复制到对象的error属性,因而直接调用该属性就可获得错误信息了。在这里要处理的意外情况其实还有很多,大家可根据API的说明完善该函数。

    接着,切换到Users的Store,在proxy定义内添加listeners配置项来监听exception事件,代码如下:

            listeners: {
                exception: ExtMVCOne.ProxyException
            }

    要测试exception事件是否能正常执行,在pagesize的定义代码前添加以下代码抛出一个异常:

    throw new Exception("发生错误了。");

    F5运行后到用户管理标签

     

     上一节中还有一个错误就是,CheckColumn的样式和图片没复制过来,造成最后一列的Checkbox显示不正确。在Ext JS包的examples\ux\css目录下打开CheckHeader.css文件,将文件里的全部样式定义复制到app.css中。然后修改将带背景图片的路径修改为“../images”。最后将image目录下的check.gif和uncheck.gif文件复制到scripts\app\resources\images目录下。

    用户管理添加修改删除重置密码

     首先切换到用户视图脚本文件,为Grid添加一个RowEditing插件,这不难,创建RowEditing的实例,并添加到plugins就行了,代码如下:

            me.rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
                autoCancel: false,
                saveBtnText: '保存',
                cancelBtnText: '取消',
                errorsText: '错误',
                dirtyText: "你要确认或取消更改"
            });
    
            me.plugins = [me.rowEditing];

    现在,要为用户名、电子邮件、角色和禁用列添加编辑控件了,这个只要定义好editor配置项就可以了,修改后的代码如下:

            me.columns = [
                { text: '用户名', dataIndex: 'Username', flex: 1,
                    editor: { allowBlank: false }
                },
                { text: '电子邮件', dataIndex: 'Email', flex: 1 ,
                    editor: { allowBlank: false, vtype: 'email' }
                },
                { text: '角色', dataIndex: 'Roles', flex: 1 ,
                    editor: {
                        xtype: 'combo', store: 'Roles', multiSelect: true, allowBlank: false, editable: false,
                        emptyText: "请选择角色", forceSelection: true
                    }
                },
                { xtype: "datecolumn", text: '创建时间', dataIndex: 'Created', format: "Y-m-d H:i:s",  150 },
                { xtype: "datecolumn", text: '最后登录时间', dataIndex: 'Created', format: "Y-m-d H:i:s",  150 },
                { xtype: 'checkcolumn', dataIndex: "IsApproved", text: "允许登录", winth: 150 ,
                    editor: { xtype: 'checkbox', cls: 'x-grid-checkheader-editor' }
                }
            ]

    代码中,用户名只是简单的不允许为空;电子邮件除了不允许为空外,还要符合电子邮件格式(vtype为email);角色则使用Combobox,数据来自之前定义的Roles Store;禁用列则使用了Checkbox控件,样式使用x-grid-checkheader-editor,这样好看点。

    接着在分页工具栏添加3个按钮,其中添加用户、删除用户使用图标显示,而重置密码则直接使用文字按钮。要在按钮显示图标,最好的方式使用样式,因而,先切换到app.css,添加以下样式:

    .user-add{
         background:url("../images/user-add-16.png")!important;
    }
    .user-delete{
         background:url("../images/user-delete-16.png")!important;
    }

    复制后,切换回用户视图脚本文件,在分页工具栏定义中加入items配置项来添加按钮,代码如下:

                items: [
                    '-',
                    { iconCls: "user-add", tooltip: '添加用户', id: "buttonUserAdd" },
                    { iconCls: "user-delete", tooltip: '删除用户', id: "buttonUserDelete", disabled: true },
                    '-',
                   { text: "重置密码", id: "buttonUserResetPassword", disabled: true }
                ]

    注意,按钮全部都添加了id,目的就是在控制器中使用id查找按钮。删除用户和重置密码默认状态为disabled状态,只有在Grid选择行后才会启用。

    然后可以F5运行,然后点击Grid数据行,效果如下

     现在,要在控制器完成各种视图操作了。切换到Users控制器,先添加一些引用来获取视图和按钮,代码如下:

        refs: [
            { ref: "UserPanel", selector: "#userPanel" },
            { ref: "UserView", selector: "#usersView" },
            { ref: "ButtonUserAdd", selector: "#buttonUserAdd" },
            { ref: "ButtonUserDelete", selector: "#buttonUserDelete" },
            { ref: "ButtonUserResetPassword", selector: "#buttonUserResetPassword" },
        ],

    这样,就可通过get方法获得各控件了。

    首先来完成选择一条记录后,启用删除和重置密码按钮。实现相当简单,利用引用,使用getUserView返回用户视图后,调用on方法绑定selectionchange事件就行了。当然也可以在control方法内定义,不过笔者感觉再用id获取一次对象,有点多余。在panel.add这句下加入以下代码:

    me.getUserView().on("selectionchange", me.onUserSelect, me);

    在onUserSelect方法内,利用get方法返回两个按钮后,调用对象的setDisabled方法设置其开启状态就可以了,代码如下:

        onUserSelect:function(model, rs){
            var me=this,
            length=rs.length;
           me.getButtonUserDelete().setDisabled(length==0);
           me.getButtonUserResetPassword().setDisabled(length == 0);
        },

    事件selectionchange会在第二个参数以数组形式返回所有选择的记录,只要数组的长度不为0,就启用按钮的,为0则禁用按钮。

    接着完成添加操作,在绑定selectionchange事件的代码下添加以下代码绑定click事件:

    me.getButtonUserAdd().on("click", me.onAddUser, me);

    在onAddUser方法内,要做的操作是先调用cancelEdit取消当前编辑操作,以避免在编辑过程中单击了添加按钮出现问题。接着在Store中添加一条记录。最后调用startEdit方法进入编辑状态。具体代码如下:

       onAddUser: function () {
           var me = this
           edit = me.getUserView().plugins[0],
           User = me.getUserModel();
           edit.cancelEdit();
           me.getUsersStore().insert(0, new User);
           edit.startEdit(0, 0);
       },

    代码中,getUserModel是定义models配置项时自动生成的方法,可返回模型。同理,getUsersStore方法也是自动生成的,用于返回Store。要注意plugins中的索引,因为当前示例只有一个插件,因而使用0就可以返回RowEditing实例了,如果有多个插件,要注意索引值。

    接下来要考虑怎么保存数据了,在RowEditing有一个Edit事件,它会在编辑完成后触发,非常适合用来进行数据保存操作。而且该事件会把事件冒泡到Grid,因而在Grid绑定该事件就行了,代码如下:

    me.getUserView().on("edit",me.onEditcomplete, me);

    在onEditcomplete方法内,调用Store的sync方法将数据同步到服务器就行了,代码如下:

       onEditcomplete: function (editor, e) {
           var me = this;
           me.getUsersStore().sync({
               success: function (e, opt) {
                   var me = this;
                   me.getUsersStore().commitChanges();
               },
               failure: function (e, opt) {
                   var me = this, msg = "";
                   me.getUsersStore().rejectChanges()
                   Ext.Msg.alert("错误", e.exceptions[0].error);
               },
               scope: me
           });
       }

    如果同步操作,服务器返回success为true,则调用commitChanges方法确认修改,如果失败,则调用rejectChanges方法取消修改,并显示错误信息。错误信息的处理与上文的处理差不多,只是返回的对象不同,自己根据需要做好定义就行了。

    还有考虑用户取消编辑时,要调用rejectChanges方法取消更改,这个通过监听canceledit事件就可以实现,代码如下:

    me.getUserView().on("canceledit", me.onCancelEdit, me);

    方法onCancelEdit的代码如下:

       onCancelEdit: function () {
           var me = this;
           me.getUsersStore().rejectChanges();
       }

    现在要在服务器端完成用户的添加功能了,这里要注意的是数据的提交方式。如果不清楚,可在页面单击添加按钮,然后单击保存按钮,在Firebug中就可以看到如图28所示的提交数据。 在服务器端的处理过程就是通过data提取数据,然后转换为JSON数组,从数组中把数据提取出来。对于添加操作,调用Membership的CreateUser方法添加用户就可以了,具体代码如下:

    public JObject Add()
            {
                bool success = false;
                string msg = "";
                JArray ja = null;
                try
                {
                    string data = Request["data"] ?? "";
                    if (string.IsNullOrEmpty(data))
                    {
                        msg = "错误的提交数据。";
                    }
                    else
                    {
                        ja = JArray.Parse(data);
                        if (ja.Count > 0)
                        {
                            JObject jo = (JObject)ja[0];
                            MembershipCreateStatus createStatus;
                            MembershipUser user = Membership.CreateUser((string)jo["Username"], "123456", (string)jo["Email"], null, null, (bool)jo["IsApproved"], out createStatus);
                            if (createStatus == MembershipCreateStatus.Success)
                            {
                                success = true;
                                string[] roles = ((JArray)jo["Roles"]).Values<string>().ToArray<string>();
                                jo["id"] = user.ProviderUserKey.ToString();
                                jo["Created"] = user.CreationDate.ToString("yyyy-MM-dd hh:mm:ss");
                                jo["LastLoginDate"] = user.LastLoginDate.ToString("yyyy-MM-dd hh:mm:ss");
                                Roles.AddUserToRoles(user.UserName, roles);
                            }
                            else
                            {
                                msg = MyFunction.ErrorCodeToString(createStatus);
                            }
                        }
                        else
                        {
                            msg = "错误的提交数据。";
                        }
                    }
                }
                catch (Exception e)
                {
                    msg = e.Message;
                }
                return MyFunction.WriteJObjectResult(success, 0, msg, ja);
            }

    还要在相应的MyFunction类中添加方法

            public static string ErrorCodeToString(MembershipCreateStatus createStatus)
            {
                // See http://go.microsoft.com/fwlink/?LinkID=177550 for
                // a full list of status codes.
                switch (createStatus)
                {
                    case MembershipCreateStatus.DuplicateUserName:
                        return "当前用户名已被注册,请使用其他用户名。";
    
                    case MembershipCreateStatus.DuplicateEmail:
                        return "当前电子邮件已被注册,请使用其他电子邮件。";
    
                    case MembershipCreateStatus.InvalidPassword:
                        return "密码错误,请输入正确的密码。";
    
                    case MembershipCreateStatus.InvalidEmail:
                        return "电子邮件地址错误,请重新输入。";
    
                    case MembershipCreateStatus.InvalidAnswer:
                        return "The password retrieval answer provided is invalid. Please check the value and try again.";
    
                    case MembershipCreateStatus.InvalidQuestion:
                        return "The password retrieval question provided is invalid. Please check the value and try again.";
    
                    case MembershipCreateStatus.InvalidUserName:
                        return "用户名错误,请输入正确的用户名。";
    
                    case MembershipCreateStatus.ProviderError:
                        return "系统错误,请联系管理员。";
    
                    case MembershipCreateStatus.UserRejected:
                        return "当前请求已被取消,请重新输入并再次尝试提交。如果还存在问题,请与管理员联系。";
    
                    default:
                        return "未知错误,请重新输入并再次尝试提交。如果问题依然存在,请与管理员联系。";
                }
            }

    编辑用户的操作过程与添加操作雷同,不过Membership不允许修改用户名,因为最好在编辑的时候,不允许用户修改用户名。这个需要在RowEditing进入编辑之前来控制编辑控件的状态,也就是在beforeedit事件中实现。先在Grid绑定beforeedit事件,代码如下:

    me.getUserView().on("beforeedit", me.onBeforeEdit, me);

    在onBeforeEdit方法内,要对用户名的编辑组件进行操作,因而要为它添加一个引用,那就要先为编辑控件添加一个id,在用户视图脚本文件内,为用户名的编辑控件添加一个id:

                { text: '用户名', dataIndex: 'Username', flex: 1,
                    editor: { allowBlank: false, id: "editorUserName" }
                },

    然后在控制器中引用:

    现在完成onBeforeEdit方法,代码如下:

       onBeforeEdit: function (editor, e) {
           var me = this;
           if (e.record.data.id) {
               me.getEditorUsername().setDisabled(true);
           } 
           else {
               me.getEditorUsername().setDisabled(false);
           }
       }

    编辑的数据都有id,而新增的数据id为null,因而通过判断id就可判断是编辑数据还是新增数据,从而可控制用户名是否允许编辑了。这也是为什么在添加数据后必须返回正确的id的一个重要原因。

    现在可完成服务器端的编辑操作代码了,具体代码如下:

    public JObject Edit()
            {
                bool success = false;
                string msg = "";
                JArray ja = null;
                try
                {
                    string data = Request["data"] ?? "";
                    if (string.IsNullOrEmpty(data))
                    {
                        msg = "错误的提交数据。";
                    }
                    else
                    {
                        ja = JArray.Parse(data);
                        if (ja.Count > 0)
                        {
                            JObject jo = (JObject)ja[0];
                            Guid id = Guid.Parse((string)jo["id"]);
                            MembershipUser user = Membership.GetUser(id);
                            if (user != null)
                            {
                                user.Email = (string)jo["Email"];
                                user.IsApproved = (bool)jo["IsApproved"];
                                Membership.UpdateUser(user);
                                string[] roles = ((JArray)jo["Roles"]).Values<string>().ToArray<string>();
                                string[] oldroles = Roles.GetRolesForUser(user.UserName);
                                string[] rolelist = new string[] { "普通用户", "系统管理员" };
                                foreach (var c in rolelist)
                                {
                                    if (roles.Contains(c))
                                    {
                                        if (!oldroles.Contains(c))
                                        {
                                            Roles.AddUserToRole(user.UserName, c);
                                        }
                                    }
                                    else
                                    {
                                        if (oldroles.Contains(c))
                                        {
                                            Roles.RemoveUserFromRole(user.UserName, c);
                                        }
                                    }
                                }
                                success = true;
                            }
                            else
                            {
                                msg = "要修改的用户不存在或已被删除。";
                            }
                        }
                        else
                        {
                            msg = "错误的提交数据。";
                        }
                    }
                }
                catch (Exception e)
                {
                    msg = e.Message;
                }
                return MyFunction.WriteJObjectResult(success, 0, msg, ja);
            }

     现在来完成删除功能。目前的Grid,一次只能选择一行,也就是说,一次只能删除一行,不太方便,因而要设置成使用复选框选择,并允许多选的。在用户视图脚本文件中,添加以下配置项实现这个:

        selType: "checkboxmodel",
        selModel: { checkOnly: false, mode: "MULTI" },

    打开浏览器刷新

    删除用户的方式有2种,一种是先使用remove方法在Store中删除记录,然后调用sync方法同步,一种是提取选择行的id,然后通过Ajax方式提交到服务器进行删除,确认后再在客户端刷新页面。第一种方式必须在proxy的api定义中定义destroy配置项,之前的代码中已经定义了,因而本示例将使用该方式。第二种方式如果也定义了destroy配置项,就千万别用remove删除Store的记录,不然在添加或编辑的时候,调用sync方法进行同步的时候会把删除记录的数据一起提交的。

    现在切换到Users控制器的脚本,为删除按钮绑定单击事件,代码如下:

    me.getButtonUserDelete().on("click", me.onDeleteUser, me);

    在onDeleteUser方法内,要先从Store的选择模型获取选择的记录。如果有选择记录,则先提示用户是否真的删除用户。确认后,调用remove方法删除记录,并调用sync方法同步数据,如果成功,调用commitChanges方法确认修改,否则调用rejectChanges方法取消修改。具体代码如下:

       onDeleteUser: function () {
           var me = this,
           sm = me.getUserView().getSelectionModel();
           if (sm.getCount() > 0) {
               var rs = sm.getSelection();
               content = ["确定删除以下用户?"];
               for (var i = 0; ln = rs.length, i < ln; i++) {
                   content.push(rs[i].data.Username);
               }
               Ext.Msg.confirm("删除用户", content.join("<br/>"), function (btn) {
                   if (btn == "yes") {
                       varme = this,
                       rs = me.getUserView().getSelectionModel().getSelection()
                       store = me.getUsersStore();
                       store.remove(rs);
                       store.sync({
                           success: function (e, opt) {
                               var me = this;
                               me.getUsersStore().commitChanges();
                           },
                           failure: function (e, opt) {
                               var me = this;
                               me.getUsersStore().rejectChanges()
                               Ext.Msg.alert("发生错误", e.exceptions[0].error);
                           },
                           scope: me
                       });
                   }
               }, me)
           } 
           else {
               Ext.Msg.alert('删除用户', '请选择要删除的用户。');
           }
       }

    代码中,使用了数组content来组合确认信息。当用户确认后,就调用remove方法,并调用sync方法。

    现在来完成服务器端代码。因为sync方法提交数据的方式是固定的,因而提取删除数据的方式与添加和编辑操作的一样,需要从data中提取数据,然后使用parse方法转换为JArray。余下的工作就是从JArray中提取出删除数据的JObject,通过id或Username去删除用户了,具体代码如下:

            public JObject Delete()
            {
                string msg = "";
                bool success = false;
                JArray ja = null;
                string data = Request["data"] ?? "";
                if (string.IsNullOrEmpty(data))
                {
                    msg = "错误的提交数据。";
                }
                try
                {
                    ja = JArray.Parse(data);
                    if (ja.Count > 0)
                    {
                        foreach (JObject jo in ja)
                        {
                            Membership.DeleteUser((string)jo["Username"]);
                        }
                        success = true;
                    }
                    else
                    {
                        msg = "错误的提交数据。";
                    }
                }
                catch (Exception e)
                {
                    msg = e.Message;
                }
                return MyFunction.WriteJObjectResult(success, 0, msg, ja);
            }

    这里要注意的是,和添加编辑操作一样,尽管success返回true,还是要把删除的数据返回。因为删除不需要改变原有的数据,因而直接将ja返回就行了。

    还有2个问题要自己考虑清楚。第1个是删除后是否提示用户已删除记录,如果需要,在sync方法内的回调函数success内加入提示信息就可以了。第2个问题是,因为删除数据后,Grid内的数据会减少,是否需要刷新页面?

    最后一个功能重置密码与删除用户差不多,也是从选择模型获取选择记录。不过,这次,不能用sync同步,只能通过提取id,然后使用Ajax方法提交数据了。先为重置密码按钮绑定单击事件,代码如下:

    me.getButtonUserResetPassword().on("click", me.onResetPassword, me);

    接着完成onResetPassword方法,代码如下:

       onResetPassword: function () {
           var me = this,
               rs = me.getUserView().getSelectionModel().getSelection();
           if (rs.length > 0) {
               var idList = [];
               for (var i = rs.length - 1; i >= 0; i--) {
                   idList.push(rs[i].data.id);
               }
               Ext.Ajax.request({
                   params: { id: idList },
                   url: '/Users/ResetPassword',
                   scripts: true,
                   scope: me,
                   success: function (response, opt) {
                       var obj = Ext.JSON.decode(response.responseText);
                       if (obj) {
                           if (obj.success) {
                               Ext.Msg.alert("提示信息", "重置密码成功");
                               return;
                           } else {
                               Ext.Msg.alert("错误", obj.Msg);
                           }
                       }
                   },
                   failure: function (response, options) {
                       Ext.Msg.alert("错误", "重置密码失败!<br>错误信息:" + response.responseText);
                   }
               });
           }
       }

    代码中,没有进行确认,如果需要,可自行添加。因为Ajax提交不是根据返回的success值来调用success方法或failure方法的,只要不是页面错误,都会执行回调函数success方法,因而要自己根据返回的数据,调用decode方法将数据转换为对象,然后根据success的值来做处理。

    下面,完成服务器端的ResetPassword方法。在这里,有个麻烦,就是ChangePassword方法需要使用旧密码才能修改新买,而ResetPassword方法会随机生成一个密码,都不能直接将密码设置为123456,因而,需要先用ResetPassword方法重置密码后,然后利用这个重置的密码,调用ChangePassword方法将密码修改为123456,具体代码如下:

            public JObject ResetPassword()
            {
                bool success = false;
                string msg = "";
                string idList = Request["id"] ?? "";
                try
                {
                    string[] ids = idList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var c in ids)
                    {
                        Guid id = Guid.Parse(c);
                        MembershipUser user = Membership.GetUser(id);
                        if (user != null)
                        {
                            string pwd = user.ResetPassword();
                            user.ChangePassword(pwd, "123456");
                        }
                    }
                    success = true;
                }
                catch (Exception e)
                {
                    msg = e.Message;
                }
                return MyFunction.WriteJObjectResult(success, 0, msg, null);
            }

    现在用户管理的基本功能(添加、删除、修改、重置密码)功能都实现了。

    示例代码下载链接https://files.cnblogs.com/aehyok/ExtJsUserViewTwo.zip

  • 相关阅读:
    智器SmartQ T7实体店试用体验
    BI笔记之SSAS库Process的几种方案
    PowerTip of the Day from powershell.com上周汇总(八)
    PowerTip of the Day2010071420100716 summary
    PowerTip of the Day from powershell.com上周汇总(十)
    PowerTip of the Day from powershell.com上周汇总(六)
    重新整理Cellset转Datatable
    自动加密web.config配置节批处理
    与DotNet数据对象结合的自定义数据对象设计 (二) 数据集合与DataTable
    在VS2003中以ClassLibrary工程的方式管理Web工程.
  • 原文地址:https://www.cnblogs.com/aehyok/p/3044152.html
Copyright © 2011-2022 走看看