这段时间没事,做了一个动态菜单的实现。有很多的地方想的不是很全,做的不够完善。欢迎大神们拍砖和指点!
话不多说,进入正题:
设计了一个数据库表:
在设计的时候每一个菜单的子菜单最多能添加 99 个,子菜单编号根据父菜单编号拼接上同级子菜单的个数。
这里因为要用到Easyui,一些子段是按照easyui属性设计的。
EF使用的是数据库优先方式创建。
贴上代码:
1.HTML代码:
1 <style type="text/css"> 2 header { 3 height:60px; 4 } 5 aside{ 6 float:left; 7 width:200px; 8 } 9 article{ 10 margin-left:204px 11 } 12 </style> 13 <body> 14 <header> 15 EasyUI treegrid 实现动态菜单 16 </header> 17 18 <aside> 19 @*树形菜单*@ 20 <ul id="tt"></ul> 21 </aside> 22 <article> 23 24 @*treegrid数据表格*@ 25 26 <table id="treegrid" style="width: 600px; height: 400px"></table> 27 28 @*treegrid的右键菜单*@ 29 <div id="tree_menu" class="easyui-menu" style="width: 120px;"> 30 <div> 31 <span>数据操作</span> 32 <div style="width: 120px;"> 33 <div id="add" data-options="iconCls:''icon-add0''">添加节点</div> 34 <div id="edit" data-options="iconCls:''icon-edit0''">修改节点</div> 35 <div id="delete" data-options="iconCls:''icon-delete0''">删除节点</div> 36 </div> 37 </div> 38 <div id="ref" data-options="iconCls:''icon-new0''">刷 新</div> 39 <div class="menu-sep"></div> 40 <div>Exit</div> 41 </div> 42 @*添加,修改表单(弹窗模式)*@ 43 <div class ="treegrid_dialog" style="display: none;"> 44 <form id="treegrid_form"> 45 @*菜单主键*@ 46 <input type="hidden" name ="MId" /> 47 @*父菜单编号*@ 48 <input type="hidden" name ="pid" id="menuPid" /> 49 @*菜单编号*@ 50 <input type="hidden" name ="id" /> 51 <table> 52 <tr> 53 <td>菜单名称</td><td><input class="easyui-validatebox" name="text" data-options="required:true" /></td> 54 </tr> 55 <tr> 56 <td>菜单图标</td><td><input id="icon" name="iconCls" editable="false"/></td> 57 </tr> 58 <tr> 59 <td>菜单路径</td><td><input class="easyui-validatebox" name="url" data-options="required:true" /></td> 60 </tr> 61 <tr> 62 <td>菜单是否选中</td> 63 <td> 64 <input id="yes" type="radio" name="checked" value="1" /><label for="yes">是</label> 65 <input id="no" type="radio" name="checked" value="0" /><label for="no">否</label> 66 </td> 67 </tr> 68 </table> 69 </form> 70 </div> 71 </article> 72 </body>
2.JavaScript代码:
1 var treegrid; 2 var treegrid_dialog; 3 var iconData; 4 $(function () { 5 //绑定菜单tree数据 6 $(''#tt'').tree({ 7 url: ''/System/GetTree'' 8 }); 9 10 iconData = [{ 11 value: '''', 12 text: ''默认'' 13 }, { 14 value: ''icon-add'', 15 text: ''icon-add'' 16 }, { 17 value: ''icon-edit'', 18 text: ''icon-edit'' 19 }, { 20 value: ''icon-remove'', 21 text: ''icon-remove'' 22 }, { 23 value: ''icon-save'', 24 text: ''icon-save'' 25 }, { 26 value: ''icon-cut'', 27 text: ''icon-cut'' 28 }, { 29 value: ''icon-ok'', 30 text: ''icon-ok'' 31 }, { 32 value: ''icon-no'', 33 text: ''icon-no'' 34 }, { 35 value: ''icon-cancel'', 36 text: ''icon-cancel'' 37 }, { 38 value: ''icon-reload'', 39 text: ''icon-reload'' 40 }, { 41 value: ''icon-search'', 42 text: ''icon-search'' 43 }, { 44 value: ''icon-print'', 45 text: ''icon-print'' 46 }, { 47 value: ''icon-help'', 48 text: ''icon-help'' 49 }, { 50 value: ''icon-undo'', 51 text: ''icon-undo'' 52 }, { 53 value: ''icon-redo'', 54 text: ''icon-redo'' 55 }, { 56 value: ''icon-back'', 57 text: ''icon-back'' 58 }, { 59 value: ''icon-sum'', 60 text: ''icon-sum'' 61 }, { 62 value: ''icon-tip'', 63 text: ''icon-tip'' 64 }]; 65 66 treegrid= $(''#treegrid'').treegrid({ 67 url: ''/System/GetTree'', 68 idField: ''id'', 69 treeField: ''text'', 70 columns: [[ 71 { title: ''菜单名称'', field: ''text'', width: 180 }, 72 { 73 title: ''菜单图标'', field: ''iconCls'', width: 100, 74 formatter: function (value) { 75 if (value!=null) { 76 return ''<span class="'' + value+ ''"></span>''; 77 } 78 } 79 }, 80 { title: ''菜单路径'', field: ''url'', width: 100 }, 81 { title: ''是否选中'', field: ''checked'', width: 100 } 82 ]], 83 onContextMenu: function (e, row) { 84 e.preventDefault(); 85 $(this).treegrid(''unselectAll''); 86 $(this).treegrid(''select'', row.id); 87 $(''#tree_menu'').menu(''show'', { 88 left: e.pageX, 89 top: e.pageY 90 }); 91 } 92 }); 93 //刷新treegrid数据 94 $("#ref").click(function () { 95 treegrid.treegrid(''reload''); 96 }); 97 //添加节点 98 $("#add").click(function () { 99 //取得选中节点 100 var node = treegrid.treegrid(''getSelected''); 101 //清空表单数据 102 $("#treegrid_form").form(''clear''); 103 $("#no").attr("checked", "true");//添加节点--设置表单(菜单是否选中)--默认选择‘否’ 104 $("#menuPid").val(node.id); //设置将要添加节点 的 父节点编号 105 $("#icon").combobox({ 106 //url: ''../../Content/data/icondata.json'', 107 //valueField: ''id'', 108 //textField: ''text'' 109 data: iconData 110 }); 111 treegrid_dialog = $(".treegrid_dialog").show().dialog({ 112 modal: true, 113 title: ''添加 '' + node.text + '' 节点的子节点'', 114 closed: false, 115 width: 400, 116 height: 250, 117 cache: false, 118 collapsible: true, 119 maximizable: true, 120 resizable: true, 121 buttons: [{ 122 text: ''保 存'', 123 plain:true, 124 iconCls: ''icon-save'', 125 handler: function () { 126 //数据验证 通过与否 127 var validate = $("#treegrid_form").form(''validate''); 128 if (validate == false) { 129 return false; 130 } 131 132 $.ajax({ 133 url: ''/System/AddMenu'', 134 data: $("#treegrid_form").serialize(), 135 type: ''POST'', 136 cache: false, 137 dataType: ''json'', 138 success: function (data) { 139 if (data && data.Success) { 140 $.messager.show({ title: ''提示'', msg: '''' + data.Msg + '''', timeout: 5000, showType: ''slide'' }); 141 treegrid_dialog.dialog(''close''); 142 treegrid.treegrid(''reload''); 143 } 144 else { 145 $.messager.show({ title: ''提示'', msg: '''' + data.Msg + '''', timeout: 5000, showType: ''slide'' }); 146 treegrid_dialog.dialog(''close''); 147 treegrid.treegrid(''reload''); 148 } 149 } 150 }); 151 } 152 }, { 153 text: ''取 消'', 154 iconCls: ''icon-redo'', 155 plain: true, 156 handler: function () { 157 treegrid_dialog.dialog(''close''); 158 } 159 }] 160 }); 161 }); 162 //编辑节点 163 $("#edit").click(function () { 164 //取得选中节点 165 var node = treegrid.treegrid(''getSelected''); 166 //清空表单数据 167 $("#treegrid_form").form(''clear''); 168 treegrid_dialog = $(".treegrid_dialog").show().dialog({ 169 modal: true, 170 title: ''编辑 '' + node.text + '' 节点'', 171 closed: false, 172 width: 400, 173 height: 250, 174 cache: false, 175 collapsible: true, 176 maximizable: true, 177 resizable: true, 178 buttons: [{ 179 text: ''保 存'', 180 plain:true, 181 iconCls: ''icon-save'', 182 handler: function () { 183 //数据验证 通过与否 184 var validate = $("#treegrid_form").form(''validate''); 185 if (validate == false) { 186 return false; 187 } 188 $.ajax({ 189 url: ''/System/EditMenu'', 190 data: $("#treegrid_form").serialize(), 191 type: ''POST'', 192 cache: false, 193 dataType: ''json'', 194 success: function (data) { 195 if (data && data.Success) { 196 $.messager.show({ title: ''提示'', msg: '''' + data.Msg + '''', timeout: 5000, showType: ''slide'' }); 197 treegrid_dialog.dialog(''close''); 198 treegrid.treegrid(''reload''); 199 } 200 else { 201 $.messager.show({ title: ''提示'', msg: '''' + data.Msg + '''', timeout: 5000, showType: ''slide'' }); 202 treegrid_dialog.dialog(''close''); 203 treegrid.treegrid(''reload''); 204 } 205 } 206 }); 207 } 208 }, { 209 text: ''取 消'', 210 iconCls: ''icon-redo'', 211 plain: true, 212 handler: function () { 213 treegrid_dialog.dialog(''close''); 214 } 215 }] 216 }); 217 //绑定编辑窗口表单数据 218 $("#treegrid_form").form(''load'', node); 219 }); 220 //删除节点 221 $("#delete").click(function () { 222 //取得选中节点 223 var node = treegrid.treegrid(''getSelected''); 224 $.post(''/System/DeleteMenu'', { MId: node.MId }, function (data) { 225 if (data && data.Success) { 226 $.messager.show({ title: ''提示'', msg: '''' + data.Msg + '''', timeout: 5000, showType: ''slide'' }); 227 treegrid.treegrid(''reload''); 228 } 229 else { 230 $.messager.show({ title: ''提示'', msg: '''' + data.Msg + '''', timeout: 5000, showType: ''slide'' }); 231 treegrid.treegrid(''reload''); 232 } 233 }); 234 }); 235 });
3.后台Controller方法
1 private SYS_UserLimitEntities db = new SYS_UserLimitEntities(); 2 3 public ActionResult Index() 4 { 5 return View(); 6 } 7 8 #region 取得树形菜单 + ActionResult GetTree(string id) 9 /// <summary> 10 /// 取得树形菜单 11 /// </summary> 12 /// <param name="id">用户点击tree时,向后台访问父菜单编号</param> 13 /// <returns></returns> 14 [HttpPost] 15 public ActionResult GetTree(string id) 16 { 17 try 18 { 19 List<Menu> list = null; 20 //判断是否执行点击tree时,按需加载要扩展的菜单 21 if (id != null) 22 { 23 list = db.Menu.Where(a => a.pid == id).ToList(); 24 } 25 else 26 { 27 list = db.Menu.Where(a => a.pid == "000").ToList();//取得根菜单 28 } 29 return Json(list);//返回easyui展示数据的json数据 30 } 31 catch (Exception) 32 { 33 throw; 34 } 35 } 36 #endregion 37 38 #region 添加菜单操作 + ActionResult AddMenu(Menu menu) 39 /// <summary> 40 /// 添加菜单操作 41 /// </summary> 42 /// <param name="menu">收集到的Menu菜单各个属性的值</param> 43 /// <returns></returns> 44 [HttpPost] 45 public ActionResult AddMenu(Menu menu) 46 { 47 Messages m = new Messages(); 48 try 49 { 50 string newid = null;//将要添加菜单的编号 51 //判断将要添加子菜单的父菜单状态是否为父菜单状态/否则修改 52 Menu me = db.Menu.Single(a => a.id == menu.pid); 53 if (me.state == "open") 54 { 55 me.state = "closed"; 56 } 57 //取得将要添加子菜单同级菜单-----并倒序排列 58 var mlist = db.Menu.Where(a => a.pid == menu.pid).OrderByDescending(a => a.MId).ToList(); 59 //判断与将要添加的子菜单的同级菜单数目是否为0 60 if (mlist.Count > 0) 61 { 62 //取得同级菜单编号最大的的最后 两位 以便计算出将要添加菜单编号 63 string id = mlist[0].id; 64 int i = Convert.ToInt32(id.Substring(id.Length - 2, 2)) + 1; 65 //限定每一个子菜单的数目不得超过99个 66 if (i == 100) 67 { 68 m.Msg = "该菜单的子菜单已经达到最大数目了,你可以选择创建其他菜单选项"; 69 return Json(m); 70 } 71 //判断子菜单编号后两位是否为‘01’两位形式不是则补填为两位 72 newid = i > 9 ? mlist[0].pid + i.ToString() : mlist[0].pid + "0" + i.ToString(); 73 } 74 else 75 { 76 newid = menu.pid + "01";//没有同级菜单时候就默认添加菜单的编号为 父菜单编号+‘01’ 77 } 78 //修改将要添加菜单的菜单编号 79 menu.id = newid; 80 menu.state = "open"; 81 //写入数据库 82 db.Menu.Add(menu); 83 84 db.SaveChanges(); 85 m.Msg = "成功的添加["+menu.text+"]的菜单节点信息!"; 86 m.Success = true; 87 return Json(m); 88 } 89 catch (Exception ex) 90 { 91 m.Msg = "添加失败!" + ex.Message; 92 return Json(m); 93 } 94 } 95 #endregion 96 97 #region 编辑菜单操作 + ActionResult EditMenu(Menu menu) 98 /// <summary> 99 /// 编辑菜单操作 100 /// </summary> 101 /// <param name="menu">收集表单Menu数据</param> 102 /// <returns></returns> 103 [HttpPost] 104 public ActionResult EditMenu(Menu menu) 105 { 106 Messages m = new Messages(); 107 try 108 { 109 //将要修改的菜单添加到EF容器 110 DbEntityEntry<Menu> entry = db.Entry<Menu>(menu); 111 //设置包装类对象状态为unchanged 112 entry.State = System.Data.EntityState.Unchanged; 113 //设置改变的属性 114 entry.Property(a => a.text).IsModified = true; 115 entry.Property(a => a.url).IsModified = true; 116 entry.Property(a => a.@checked).IsModified = true; 117 entry.Property(a => a.iconCls).IsModified = true; 118 //提交数据库 119 db.SaveChanges(); 120 m.Msg = "节点["+menu.text+"]的信息修改成功!"; 121 m.Success = true; 122 return Json(m); 123 } 124 catch (Exception ex) 125 { 126 m.Msg = "修改失败!" + ex.Message; 127 return Json(m); 128 } 129 } 130 #endregion 131 132 #region 删除菜单操作 + ActionResult DeleteMenu(int MId) 133 /// <summary> 134 /// 删除菜单操作 135 /// </summary> 136 /// <param name="MId">将要删除的菜单编号</param> 137 /// <returns></returns> 138 [HttpPost] 139 public ActionResult DeleteMenu(int MId) 140 { 141 Messages m = new Messages(); 142 try 143 { 144 //取得将要删除的实体对象 145 Menu menu = db.Menu.Single(a => a.MId == MId); 146 // 删除项有--子菜单时 147 /* 148 * 从选中的节点开始,将其节点以及其一下节点都删除 149 * --无论从那个节点开始删除都能自如的将其子菜单以及子子菜单删除,以此类推 150 */ 151 List<Menu> mlist = db.Menu.Where(a => a.id.IndexOf(menu.id)>=0).ToList(); 152 if (mlist.Count>0) 153 { 154 foreach (var item in mlist) 155 { 156 db.Menu.Remove(item); 157 } 158 } 159 db.Menu.Remove(menu); 160 db.SaveChanges(); 161 m.Msg = "节点已经被成功删除!"; 162 m.Success = true; 163 return Json(m); 164 } 165 catch (Exception ex) 166 { 167 m.Msg = "删除失败!" + ex.Message; 168 return Json(m); 169 } 170 } 171 #endregion
效果图如下: