我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(二)菜单导航

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               
一、前言
 

上篇博客中已经总体的说了一下权限系统的思路和表结构设计,那接下来我们就要进入正文了,先从菜单导航这个功能开始。

 
二、实现
 

这个页面基本不用什么需求分析了,大家都很明白,不过在这个页面要多维护一个东西,那就是定义页面中有哪些按钮,这个用弹出窗口做。
我们技术分析一下:
1、直在grid中在线编辑,使用easyui的treegrid控件可实现。
2、行编辑时选择父节点,使用easyui中的combotree控件,数据源直接在treegrid中取。
3、选择图标,这个没有控件可用,自己代码实现
4、弹出设置按钮窗口,使用easyui的window或dialog控件
5、按钮库管理窗口,使用easyui的datagrid控件。
6、前台交互逻辑使用ko,后台数据接口采用web api
经上面分析,技术上没有什么问题,唯一就是选择图标这个要自己实现比较麻烦点。

 

1、当然先从mvc控制器开始吧。创建MenuController.cs 里面只有一个index方法的空的mvc控件器,里面什么都不用写。

 
  
public class MenuController : Controller{    public ActionResult Index()    {        return View();    }}
 
 


2、接下来再创建对应的视图,前台razor页面代码如下,看完了我再给大家解释

 
  
@{    ViewBag.Title = "title";    Layout = "~/Views/Shared/_Layout.cshtml";}@section scripts{    @Scripts.Render("~/Resource/Sys/Menu.js")    <script type="text/javascript">        using(['lookup', 'validatebox', 'combotree', 'numberspinner'], easyuifix.datagrid_editor_extend);        var formatterParent = function (value, row) { return row.ParentName };        var formatterButton = function (value, row) { return (row.URL&&row.URL!='#')?'<a href="#" οnclick="setButton(\'' 
+ row.MenuCode + '\')"><span class="icon icon-set2">&nbsp;</span>[设置按钮]</a>':''; };        ko.bindingViewModel(new viewModel());    </script>}    <div class="z-toolbar">        <a href="#" plain="true" class="easyui-linkbutton" icon="icon-arrow_refresh" title="刷新" data-bind="click:refreshClick">刷新</a>        <a href="#" plain="true" class="easyui-linkbutton" icon="icon-add"           title="新增" data-bind="click:addClick"    >新增</a>        <a href="#" plain="true" class="easyui-linkbutton" icon="icon-edit"          title="编辑" data-bind="click:editClick"   >编辑</a>        <a href="#" plain="true" class="easyui-linkbutton" icon="icon-cross"         title="删除" data-bind="click:deleteClick" >删除</a>        <a href="#" plain="true" class="easyui-linkbutton" icon="icon-save"          title="保存" data-bind="click:saveClick"   >保存</a>    </div>        <table id="gridlist" data-bind="treegrid:grid">        <thead>              <tr>                  <th field="_id"  hidden="true"></th>                  <th field="MenuName"    align="left"   width="150" editor="{type:'validatebox',options:{required: true }}">菜单名称  </th>                  <th field="MenuCode"    align="left"   width="80"  editor="{type:'validatebox',options:{required: true }}">编码      </th>                  <th field="ParentCode"  align="left"   width="150" editor="combotree" formatter="formatterParent"         >上级菜单  </th>                <th field="IconClass"   align="left"   width="180" editor="{type:'lookup'}"                               >图标      </th>                 <th field="URL"         align="left"   width="180" editor="text"                                          >链接地址  </th>                 <th field="IsVisible"   align="center" width="60"  editor="{type: 'checkbox',options: {on: true,off: false}}" formatter="com.formatCheckbox" >是否可见</th>                 <th field="IsEnable"    align="center" width="60"  editor="{type: 'checkbox',options: {on: true,off: false}}" formatter="com.formatCheckbox" >是否启用</th>                 <th field="MenuSeq"     align="right"  width="50"  editor="text"                                          >排序      </th>                 <th field="Button"      align="center" width="100"                     formatter="formatterButton"        >页面按钮  </th>             </tr>                                    </thead>          </table> <script type="text/html" id="button-template">    <div style="margin:5px;height:320px;overflow:auto;">        <style type="text/css">            .listview{ margin:0 !important;}            .listview li{width:100px !important;background-color:#ECECFF !important;float:left;margin:3px;overflow:hidden;}            .listview span{ font-size:14px !important;height:auto !important; white-space: nowrap;}            .listview .icon:before{content:"" !important}        </style>        <div style="border-bottom:1px solid #CCC; margin-bottom:5px;">            <span class="icon32 icon-settings32" style="padding-left:48px;font-weight:bold; font-size:14px;color:#666;">请选择页面按钮</span>         </div>         <div class="metrouicss">            <label class="input-control checkbox" style="margin-top:6px;margin-left:3px;">                <input type="checkbox" data-bind="checked:checkAll"><span class="helper">全选</span>            </label>            <button class="image-button standart fg-color-white" style="float:right" data-bind="click:manageClick">            <i class="icon-grid-view bg-color-green"></i>                管理按钮库            </button>            <ul class="listview" data-bind="foreach: buttons" style="clear:both">                <li data-bind="click:$parent.buttonClick,css:{selected:Selected()>0}"><span class="icon" data-bind="text:ButtonName,css:ButtonIcon"></span></li>            </ul>        </div>     </div>    <div style="text-align:center;">        <a class="easyui-linkbutton" data-options="iconCls:'icon-ok'" data-bind="click:confirmClick" href="javascript:void(0)"  >确定</a>          <a class="easyui-linkbutton" data-options="iconCls:'icon-cancel'" data-bind="click:cancelClick" href="javascript:void(0)">取消</a>     </div></script><script type="text/html" id="manage-template">    <style type="text/css">        .datagrid-wrap{border-width:0 0 1px 0;}        .datagrid-toolbar{background-color:#E0ECFF !important}    </style>    <table data-bind="datagrid:grid">        <thead>            <th field="ButtonCode"    align="left" editor="{type:'validatebox',options:{required:true}}"    width="80"  >编码   </th>              <th field="ButtonName"    align="left" editor="{type:'validatebox',options:{required:true}}"    width="70"  >名称   </th>              <th field="ButtonIcon"    align="left" editor="{type:'validatebox',options:{required:true}}"    width="120" >图标   </th>             <th field="ButtonSeq"     align="left" editor="text"    width="50"  >排序   </th>             <th field="Description"   align="left" editor="text"    width="200" >备注说明   </th>         </thead>    </table>        <div style="text-align:center;margin:5px;">        <a class="easyui-linkbutton" data-options="iconCls:'icon-ok'" data-bind="click:confirmClick" href="javascript:void(0)"  >确定</a>          <a class="easyui-linkbutton" data-options="iconCls:'icon-cancel'" data-bind="click:cancelClick" href="javascript:void(0)">取消</a>     </div></script>
 
 

这一段基本上都是html,大家都懂,要解释的可能会有以下几点:
@Scripts.Render("~/Resource/Sys/Menu.js") 这个是利用了mvc4下面的bundle技术对js文件进行压缩捆绑。
using(['lookup', 'validatebox', 'combotree', 'numberspinner'], easyuifix.datagrid_editor_extend);  这句话的意思是这个页面中动态载入这些组件,(我没有默认加载所有的easyui控件,都是使用时动态加载的)  
<th field="_id"  hidden="true"></th> 这句是表示我有一个隐藏的_id字段,因为我的这个功能是可以修改主键的,所以不能把主键做为更新条件,只有建一个隐藏字段来当做更新条件才能实现。
<script type="text/html" id="xxxx-template"> 这个应该用的很多的利用script的type=text/html写的模板,弹出窗口时使用

 

3、前端viewModel

 
  
/*** 模块名:mms viewModel* 程序名: menu.js* Copyright(c) 2013-2015 liuhuisheng [ liuhuisheng.xm@gmail.com ] **/
  
 function viewModel() {    var self = this;    this.grid = {        size: { w: 4, h: 40 },        url: '/api/sys/menu/getall',        idField: '_id',        queryParams: ko.observable(),        treeField: 'MenuName',        loadFilter: function (d) {            d = utils.copyProperty(d.rows || d, ["MenuCode", "IconClass"], ["_id", "iconCls"], false);            return utils.toTreeData(d, '_id', 'ParentCode', "children");        }     };    this.refreshClick = function () {        window.location.reload();    };    this.addClick = function () {        if (self.grid.onClickRow()) {            var row = { _id: utils.uuid(),MenuCode:'',MenuName:''};            self.grid.treegrid('append', { parent: '', data: [row] });            self.grid.treegrid('select', row._id);            self.grid.$element().data("datagrid").insertedRows.push(row);            self.editClick();        }    };    this.editClick = function () {        var row = self.grid.treegrid('getSelected');        if (row) {            self.grid.treegrid('beginEdit', row._id);            self.edit_id = row._id;            var eds = self.grid.treegrid('getEditors', row._id);            var edt = function (field) { return $.grep(eds, function (n) { return n.field == field })[0]; };            var treeData = JSON.parse(JSON.stringify(self.grid.treegrid('getData')).replace(/_id/g, "id").replace(/MenuName/g, "text"));            treeData.unshift({ "id": 0, "text": "" });            edt("ParentCode").target.combotree('loadData', treeData);            self.afterCreateEditors(edt);        }    };    this.afterCreateEditors = function (editors) {        var iconInput = editors("IconClass").target;        var onShowPanel = function () {            iconInput.lookup('hidePanel');            com.dialog({                title: "&nbsp;选择图标",                iconCls: 'icon-node_tree',                width: 700,                height: 500,                url: "/Resource/page/icon.html",                viewModel: function (w) {                    w.find('#iconlist').css("padding", "5px");                    w.find('#iconlist li').attr('style', 'float:left;border:1px solid #fff; line-height:20px; margin-right:4px;width:16px;cursor:pointer')                     .click(function () {                         iconInput.lookup('setValue',$(this).find('span').attr('class').split(" ")[1]);                         w.dialog('close');                     }).hover(function () {                         $(this).css({ 'border': '1px solid red' });                     }, function () {                         $(this).css({ 'border': '1px solid #fff' });                     });                }            });        };        iconInput.lookup({ customShowPanel: true, onShowPanel: onShowPanel, editable: true });        iconInput.lookup('resize', iconInput.parent().width());        iconInput.lookup('textbox').unbind();    };    this.grid.OnBeforeDestroyEditor = function (editors, row) {        row.ParentName = editors['ParentCode'].target.combotree('getText');        row.IconClass = editors["IconClass"].target.lookup('textbox').val();    };    this.deleteClick = function () {        var row = self.grid.treegrid('getSelected');        if (row) {            self.grid.$element().treegrid('remove', row._id);            self.grid.$element().data("datagrid").deletedRows.push(row);        }    };    this.grid.onDblClickRow = self.editClick;    this.grid.onClickRow = function () {        var edit_id = self.edit_id;        if (!!edit_id) {            if (self.grid.treegrid('validateRow', edit_id)) { //通过验证                self.grid.treegrid('endEdit', edit_id);              self.edit_id = undefined;            }            else { //未通过验证                self.grid.treegrid('select', edit_id);              return false;            }        }        return true;    };    this.saveClick = function () {        var post = {};        post.list = new com.editTreeGridViewModel(self.grid)
                    .getChanges(['_id', 'MenuName', 'MenuCode', 'ParentCode', 'IconClass', 'URL', 'IsVisible', 'IsEnable', 'MenuSeq']);        if (self.grid.onClickRow() && post.list._changed) {            com.ajax({                url: '/api/sys/menu/edit',                data: ko.toJSON(post),                success: function (d) {                    com.message('success', '保存成功!');                    self.grid.treegrid('acceptChanges');                    self.grid.queryParams({});                }            });        }    };}var setButton = function (MenuCode) {    com.dialog({        title: "设置按钮",        width: 555,        height: 400,        html: "#button-template",        viewModel: function (w) {            var self = this;            com.loadCss('/Resource/css/metro/css/modern.css', parent.document);            this.buttons = ko.observableArray();            this.refresh = function () {                com.ajax({                    url: '/api/sys/menu/getmenubuttons/' + MenuCode,                    type: 'GET',                    async: false,                    success: function (d) {                        self.buttons(ko.mapping.fromJS(d)());                    }                });            };            this.refresh();            this.checkAll = ko.observable(false);            this.checkAll.subscribe(function (value) {                $.each(self.buttons(), function () {                    this.Selected(value ? 1 : 0);                });            });            this.buttonClick = function (row) {                row.Selected(row.Selected() ? 0 : 1);            };            this.confirmClick = function () {                var data = utils.filterProperties($.grep(self.buttons(), function (row) {                    return row.Selected() > 0;                }), ['ButtonCode']);                com.ajax({                    url: '/api/sys/menu/editmenubuttons/' + MenuCode,                    data: ko.toJSON(data),                    success: function (d) {                        com.message('success', '保存成功!');                        self.cancelClick();                    }                });            };            this.manageClick = function () {                com.dialog({                    title: "管理按钮库",                    width: 600,                    height: 410,                    html: "#manage-template",                    viewModel: function (w_sub) {                        var that = this;                        this.grid = {                            width: 586,                            height: 340,                            pagination: false,                            pageSize: 10,                            url: "/api/sys/menu/getbuttons",                            queryParams: ko.observable()                        };                        this.cancelClick = function () {                            w_sub.dialog('close');                        };                        this.gridEdit = new com.editGridViewModel(this.grid);                        this.grid.OnAfterCreateEditor = function (editors, row) {                            if (!row._isnew) com.readOnlyHandler('input')(editors["ButtonCode"].target, true);                        };                        this.grid.onClickRow = that.gridEdit.ended;                        this.grid.onDblClickRow = that.gridEdit.begin;                        this.grid.toolbar = [                            { text: '新增', iconCls: 'icon-add1', handler: function () { that.gridEdit.addnew(); } }, '-',                            { text: '编辑', iconCls: 'icon-edit', handler: that.gridEdit.begin }, '-',                            { text: '删除', iconCls: 'icon-cross', handler: that.gridEdit.deleterow }                        ];                        this.confirmClick = function () {                            if (!that.gridEdit.isChangedAndValid()) return;                            var list = that.gridEdit.getChanges(['ButtonCode', 'ButtonName','ButtonIcon', 'ButtonSeq', 'Description']);                            com.ajax({                                url: '/api/sys/menu/editbutton',                                data: ko.toJSON({ list: list }),                                success: function (d) {                                    that.cancelClick();                                    self.refresh();                                    com.message('success', '保存成功!');                                }                            });                        };                    }                });                            };            this.cancelClick = function () {                w.dialog('close');            };        }    });};
 
 

viewModel的写法大家要先了解下knockout,再回过头来看这个,各个按钮对应的事件属性就不说了,
this.grid={}这里的grid是绑定到菜单树上的,this.grid即treegrid的属性
在实现弹出图标页面采,利用lookup控件,把lookup的弹出页面换掉,事件也unbind绑,即可以最少的代码实现这个功能
保存功能就不说了,一看就懂的。

 

接下来弹出设置按钮,调用我封装的com.dialog方法实现,然后写弹出窗口的viewModel即可。在弹出窗口时动态加载了它所需要的css,在这里处理中使用了ko中的observableArray的功能,对按钮数据进行监视,全选功能是使用subscribe函数实现的。这里引入了ko大家明显可以感觉到代码优雅了很多,而且以后也好维护。

 

弹出管理按钮库就更简单了,直接使用我封装好的com.editGridViewModel就实现了对datagrid的增删除改查了

 

4、后台web Api控制器

 

前台使用到的ajax请求中的方法包括:
1 获取treegrid中数据:/api/sys/menu/getall
2 保存菜单数据:/api/sys/menu/edit
3 获取菜单中的按钮:/api/sys/menu/getmenubuttons/menucode
4 保存菜单中的按钮:/api/sys/menu/editmenubuttons/menucode
5 获取按钮列表:/api/sys/menu/getbuttons
6 保存按钮的增删除改的操作改动:/api/sys/menu/editbutton
直接看webapi的代码

 
  
    public class MenuApiController : ApiController    {        // GET api/menu        public IEnumerable<dynamic> Get()        {            var UserCode = this.User.Identity.Name;            return new sys_menuService().GetUserMenu(UserCode);        }        // GET api/menu        public dynamic GetEnabled(string id)        {            var result = new sys_menuService().GetEnabledMenusAndButtons(id);            return result;        }        // GET api/menu        public IEnumerable<dynamic> GetAll()        {            var MenuService = new sys_menuService();            var pQuery = ParamQuery.Instance().Select("A.*,B.MenuName as ParentName")                .From(@"sys_menu A left join sys_menu B on B.MenuCode = A.ParentCode")                .OrderBy("A.MenuSeq,A.MenuCode");            var result = MenuService.GetDynamicList(pQuery);            return result;        }        // 地址:POST api/mms/send        [System.Web.Http.HttpPost]        public void Edit(dynamic data)        {            var listWrapper = RequestWrapper.Instance().LoadSettingXmlString(@"<settings>    <table>        sys_menu    </table>    <where>        <field name='MenuCode' cp='equal' variable='_Id'></field>    </where></settings>");            var service = new sys_menuService();            var result = service.Edit(null, listWrapper, data);        }        public IEnumerable<dynamic> GetMenuButtons(string id)        {            return new sys_menuService().GetMenuButtons(id);        }        public IEnumerable<dynamic> GetButtons()        {            var pQuery = ParamQuery.Instance().OrderBy("ButtonSeq");            return new sys_buttonService().GetModelList(pQuery);        }        [System.Web.Http.HttpPost]        public void EditMenuButtons(string id, dynamic data)        {            var service = new sys_menuService();            service.SaveMenuButtons(id, data as JToken);        }        [System.Web.Http.HttpPost]        public void EditButton(dynamic data)        {            var listWrapper = RequestWrapper.Instance().LoadSettingXmlString(@"<settings>    <table>sys_button</table>    <where>        <field name='ButtonCode' cp='equal'></field>    </where></settings>");            var service = new sys_buttonService();            var result = service.Edit(null, listWrapper, data);        }    }
 
 

webapi中要指定请求类型,比如GetEnable这个方法,默认是Get方法,其它请求是访问不到的。这些代码也基本是采用我的zephyr.net框架实现的,实现代码非常简洁,大家后台应该都有自己的一套我就不多说了。

 
三、效果图
 

我本来不想再上图的,但是想下大家看完了还得到第一篇中看看这个页面应该长的怎么样,麻烦,还是每篇都上两张图方便大家。
image

 

image

 

image

 

image

 

 

 
四、后述
 

上一篇博客受到很多博友的关注,首先非常感谢大家的支持!我已经在群公告中开放了demo地址。
如果你觉得不错就帮我再【推荐】一下吧,你的支持才是我能坚持写完这个系列文章的动力。
技术交流QQ群:328510073,欢迎大家来交流。

 

 

 

系列博客链接:

 

我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(一)

 

 


<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
这里写图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值