首页 > 代码库 > 使用asp.net mvc,boostrap及knockout.js开发微信自定义菜单编辑工具
使用asp.net mvc,boostrap及knockout.js开发微信自定义菜单编辑工具
前言
微信的接口调试工具可以编辑自定义菜单,不过是提交json格式数据创建菜单,非常的不方便还容易出错。网上的工具不好用,所以就自己写了一个。
正文
先用bootstrap排个页面框架出来,调用自定义菜单接口需要用到AccessToken,放个输入框输入AccessToken。也不排除想直接输入AppId和AppSecret来获取AccessToken的用户,所以还需要下拉菜单来选择是输入AccessToken还是直接获取AccessToken。为了兼顾微信企业号应用创建菜单还需要AgentId,CorpId,套件永久授权码,SuiteId,SuiteSecret,SuiteTicket,参数的输入框大致就是这些。
使用knockout定义好observables监控属性。并绑定到输入框上。
定义菜单展示及菜单编辑模块,排版为微信公众号菜单三个大菜单,每个大菜单下面可以配五个子菜单。大致思路如下,页面排版为六行三列,三个大菜单未配置满时在右侧显示增加菜单按钮,
每个父级菜单的子菜单未配置满时在上方显示增加菜单按钮。未配置满时以空白div占位。
定义个函数生成自定义长度数组
使用knockout定义好菜单监控属性,格式为
{ "button": [ { "name": "父级菜单1", "sub_button": [ { "type": "view", "name": "子菜单1", "url": "" } ] }, { "name": "父级菜单1", "sub_button": [ { "type": "view", "name": "子菜单2", "url": "" }, { "type": "view", "name": "子菜单1", "url": "" } ] } ] }
定义添加,编辑,删除菜单函数,定义添加编辑菜单时临时监控属性,定义当前编辑菜单索引的监控属性。
一个一个编辑菜单还不是很方便,所以还要定义菜单的 上 下 左 右 的移动,及复制粘贴功能。
1 function MenuFormValidate() { 2 $("#MenuForm").validate({ 3 rules: { 4 name: { 5 required: true 6 }, 7 value: { 8 required: false 9 } 10 }, 11 messages: { 12 name: { 13 required: "请输入名称" 14 }, 15 value: { 16 required: $("#txtMenuButtonValue").attr("placeholder") 17 } 18 } 19 }); 20 } 21
22 MenusReset:function () { 23 var menus = JSON.stringify(model.Menus()); 24 model.Menus(undefined); 25 model.Menus(JSON.parse(menus));//刷新菜单对象 26 MenuFormValidate();//重新绑定验证方法 27 },
1 MenuIndex: ko.observable(), //父级菜单索引 2 isEditMenu: ko.observable(false), //是否是编辑菜单 3 BottonIndex: ko.observable(-1), //编辑菜单的父级菜单索引 4 SubBottonIndex: ko.observable(-1), //编辑菜单的子菜单索引 5 Menu: ko.observable(),//编辑菜单时临时监控属性 6 CopyMenu: ko.observable(),//复制的菜单对象 7 Copy: function () { //复制 8 if (model.Menu() != undefined) { 9 var menu = JSON.stringify(model.Menu()); 10 model.CopyMenu(JSON.parse(menu)); 11 model.Menu(undefined); 12 } 13 }, 14 Paste: function () {//粘贴 15 if (model.CopyMenu() != undefined) { 16 var menu = JSON.parse(JSON.stringify(model.CopyMenu())); 17 if (model.SubBottonIndex() !== -1 && menu.sub_button != undefined || (!model.isEditMenu() && model.MenuIndex() != undefined)) { 18 delete menu.sub_button; 19 } 20 21 model.Menu(menu); 22 MenuFormValidate(); 23 } 24 }, 25 Up: function () {//向上移动 26 var bottonIndex = model.BottonIndex(); 27 var subBottonIndex = model.SubBottonIndex(); 28 var newSubBottonIndex = subBottonIndex - 1; 29 model.Menus().button[bottonIndex].sub_button[subBottonIndex] = model.Menus().button[bottonIndex].sub_button[newSubBottonIndex]; 30 model.Menus().button[bottonIndex].sub_button[newSubBottonIndex] = model.Menu(); 31 model.MenusReset(); 32 model.SubBottonIndex(newSubBottonIndex); 33 }, 34 Down: function () {//向下移动 35 var bottonIndex = model.BottonIndex(); 36 var subBottonIndex = model.SubBottonIndex(); 37 var newSubBottonIndex = subBottonIndex + 1; 38 model.Menus().button[bottonIndex].sub_button[subBottonIndex] = model.Menus().button[bottonIndex].sub_button[newSubBottonIndex]; 39 model.Menus().button[bottonIndex].sub_button[newSubBottonIndex] = model.Menu(); 40 model.MenusReset(); 41 model.SubBottonIndex(newSubBottonIndex); 42 }, 43 Left: function () {//向左移动 44 var bottonIndex = model.BottonIndex(); 45 var subBottonIndex = model.SubBottonIndex(); 46 47 if (subBottonIndex === -1) { 48 var newBottonIndex = bottonIndex - 1; 49 model.Menus().button[bottonIndex] = model.Menus().button[newBottonIndex]; 50 model.Menus().button[newBottonIndex] = model.Menu(); 51 model.MenusReset(); 52 model.BottonIndex(newBottonIndex); 53 } 54 }, 55 Right: function () {//向右移动 56 var bottonIndex = model.BottonIndex(); 57 var subBottonIndex = model.SubBottonIndex(); 58 59 if (subBottonIndex === -1) { 60 var newBottonIndex = bottonIndex + 1; 61 model.Menus().button[bottonIndex] = model.Menus().button[newBottonIndex]; 62 model.Menus().button[newBottonIndex] = model.Menu(); 63 model.MenusReset(); 64 model.BottonIndex(newBottonIndex); 65 } 66 }, 67 EditMenu: function (obj, bottonindex, subbottonindex) {//编辑菜单 68 model.BottonIndex(bottonindex); 69 model.SubBottonIndex(subbottonindex); 70 71 model.isEditMenu(true); 72 var data =http://www.mamicode.com/ JSON.stringify(obj); 73 model.Menu(JSON.parse(data)); 74 MenuFormValidate(); 75 }, 76 AddMenu: function (index) {//添加菜单 77 model.BottonIndex(-1); 78 model.SubBottonIndex(-1); 79 80 model.isEditMenu(false); 81 model.MenuIndex(index); 82 var menu = { type: "view", name: "", value: "" }; 83 model.Menu(menu); 84 MenuFormValidate(); 85 }, 86 DeleteMenu: function () {//删除菜单 87 $(model.Menus().button).each(function (index, item) { 88 if (index === model.BottonIndex() && model.SubBottonIndex() === -1) { 89 model.Menus().button.splice(index, 1); 90 } 91 92 if (item.sub_button instanceof Array) { 93 $(item.sub_button).each(function (index1) { 94 if (index === model.BottonIndex() && index1 === model.SubBottonIndex()) { 95 item.sub_button.splice(index1, 1); 96 } 97 }); 98 } 99 }); 100 model.Menu(undefined); 101 model.MenuIndex(undefined); 102 model.BottonIndex(-1); 103 model.SubBottonIndex(-1); 104 model.MenusReset(); 105 }, 106 CancelMenuSave: function () {//取消编辑,重置参数 107 model.Menu(undefined); 108 model.MenuIndex(undefined); 109 model.BottonIndex(-1); 110 model.SubBottonIndex(-1); 111 }, 112 MenuSave: function () {//保存编辑的菜单 113 if (!$("#MenuForm").data("validator").form()) { 114 return; 115 } 116 117 if (model.isEditMenu()) { 118 var menuIndex = model.BottonIndex(); 119 var subMenuIndex = model.SubBottonIndex(); 120 if (subMenuIndex === -1) { 121 model.Menus().button[menuIndex] = model.Menu(); 122 } else { 123 model.Menus().button[menuIndex].sub_button[subMenuIndex] = model.Menu(); 124 } 125 } else { 126 if (model.MenuIndex() != undefined) { 127 if (model.Menus().button[model.MenuIndex()].sub_button == undefined) { 128 model.Menus().button[model.MenuIndex()].sub_button = new Array(); 129 } 130 model.Menus().button[model.MenuIndex()].sub_button.unshift(model.Menu()); 131 } else { 132 model.Menus().button.push(model.Menu()); 133 } 134 } 135 136 model.Menu(undefined); 137 model.MenuIndex(undefined); 138 model.BottonIndex(-1); 139 model.SubBottonIndex(-1); 140 model.MenusReset(); 141 },
绑定好监控属性,生成菜单排版
1 <div class="panel-body" data-bind="with:Menus" id="divMenu" style="display: none;"> 2 <div style="height: 200px;" data-bind="foreach:newArray(3)"> 3 <div class="list-group col-xs-4 clearFill bn"> 4 <!--ko if:($parent.button.length>0 && $parent.button[$index()]!=undefined && $parent.button[$index()].sub_button!=undefined ) --> 5 <!--ko foreach:newArray((4-$parent.button[$index()].sub_button.length)) --> 6 <div class="list-group-item bn"></div> 7 <!--/ko--> 8 <!--ko if:$parent.button[$index()].sub_button.length<5 --> 9 <div class="list-group-item" data-bind="click:function (){$root.AddMenu($index())}"><i class="fa fa-plus"></i> 10 </div> 11 <!--/ko--> 12 <!--ko foreach:($parent.button[$index()].sub_button) --> 13 <div class="list-group-item" data-bind="text:name,attr:{‘bottonIndex‘:$parent.value,‘subbottonIndex‘:$index()},click:function (){$root.EditMenu($data,$parent.value,$index())}"></div> 14 <!--/ko--> 15 <!--/ko --> 16 <!--ko if: $parent.button[$index()]!=undefined && $parent.button[$index()].sub_button==undefined --> 17 <div class="list-group-item bn"></div> 18 <div class="list-group-item bn"></div> 19 <div class="list-group-item bn"></div> 20 <div class="list-group-item bn"></div> 21 <div class="list-group-item" data-bind="click:function (){$root.AddMenu($index())}"><i class="fa fa-plus"></i> 22 </div> 23 <!--/ko--> 24 <!--ko if: $parent.button[$index()]==undefined --> 25 <div class="list-group-item bn"></div> 26 <div class="list-group-item bn"></div> 27 <div class="list-group-item bn"></div> 28 <div class="list-group-item bn"></div> 29 <div class="list-group-item bn"></div> 30 <!--/ko--> 31 </div> 32 </div> 33 <!--ko foreach:button --> 34 <div class="col-xs-4 list-group-item list-group-item-danger" data-bind="text:name,attr:{‘bottonindex‘:$index()},click:function (){$root.EditMenu($data,$index(),-1)}"></div> 35 <!--/ko--> 36 <!--ko if:button.length < 3 --> 37 <div class="col-xs-4 list-group-item" data-bind="click:function (){$root.AddMenu();}"><i class="fa fa-plus"></i> 38 </div> 39 <!--/ko--> 40 <div class="clearfix"></div> 41 42 <div class="col-xs-12" style="border: 1px solid #EEEEEE; padding-top: 15px; margin-top: 15px;" data-bind="with:$root.Menu,visible:($root.Menu()!=undefined)"> 43 <form id="MenuForm" onsubmit="return false;"> 44 <div class="form-group col-xs-4"> 45 <input type="text" class="form-control" name="name" placeholder="请输入名称" data-bind="value:name"> 46 </div> 47 <div class="form-group col-xs-4"> 48 <select class="form-control" onchange="$(‘#txtMenuButtonValue‘) 49 .attr(‘placeholder‘, $(this).find(‘option:selected‘).attr(‘pl‘))" data-bind="value:type"> 50 <option value="view" pl="请输入Url">跳转URL</option> 51 <option value="click" pl="请输入Key">点击推事件</option> 52 <option value="scancode_push" pl="请输入Key">扫码推事件</option> 53 <option value="scancode_waitmsg" pl="请输入Key">扫码推事件且弹出“消息接收中”提示框</option> 54 <option value="pic_sysphoto" pl="请输入Key">弹出系统拍照发图</option> 55 <option value="pic_photo_or_album" pl="请输入Key">弹出拍照或者相册发图</option> 56 <option value="pic_weixin" pl="请输入Key"> 弹出微信相册发图器</option> 57 <option value="location_select" pl="请输入Key">弹出地理位置选择器</option> 58 </select> 59 </div> 60 <div class="form-group col-xs-8"> 61 <input type="text" id="txtMenuButtonValue" name="value" class="form-control" placeholder="请输入Url" data-bind="value:value"> 62 </div> 63 <div class="form-group col-xs-12"> 64 <button type="submit" class="btn btn-primary" data-bind="click:$root.MenuSave">确定</button> 65 <button type="submit" class="btn btn-danger" data-bind="visible:$root.isEditMenu,click:$root.DeleteMenu">删除</button> 66 <button type="button" class="btn btn-default" title="上移" data-bind="visible:$root.isEditMenu(),disable:!$root.IsUp(),click:$root.Up"><i class="fa fa-chevron-circle-up" aria-hidden="true"></i></button> 67 <button type="button" class="btn btn-default" title="下移" data-bind="visible:$root.isEditMenu(),disable:!$root.IsDown(),click:$root.Down"><i class="fa fa-chevron-circle-down" aria-hidden="true"></i></button> 68 <button type="button" class="btn btn-default" title="左移" data-bind="visible:$root.isEditMenu(),disable:!$root.IsLeft(),click:$root.Left"><i class="fa fa-chevron-circle-left" aria-hidden="true"></i></button> 69 <button type="button" class="btn btn-default" title="右移" data-bind="visible:$root.isEditMenu(),disable:!$root.IsRight(),click:$root.Right"><i class="fa fa-chevron-circle-right" aria-hidden="true"></i></button> 70 <button type="button" class="btn btn-default" title="复制菜单" data-bind="visible:$root.isEditMenu(),click:$root.Copy">复制</button> 71 <button type="button" class="btn btn-default" title="粘贴菜单" data-bind="click:$root.Paste">粘贴</button> 72 <button type="submit" class="btn btn-default" data-bind="click:$root.CancelMenuSave">关闭</button> 73 </div> 74 </form> 75 </div> 76 <div class="clearfix"></div> 77 </div>
最后增加菜单的查询函数及发布函数。因为编辑菜单方便,菜单对象和微信自定义菜单接口所需要的json格式不对应,所以在查询现有菜单和发布菜单时,需要对json数据进行一下格式变化。
1 , 2 EditMenus: function (isQuery) { 3 if (isQuery == undefined) { 4 var menu = {}; 5 menu.button = new Array(); 6 model.Menus(menu); 7 } else { 8 var appId = model.AppId(); 9 var appSecret = model.AppSecret(); 10 var accessToken = model.AccessToken(); 11 var type = model.Type(); 12 var tokenType = model.TokenType(); 13 var corpId = model.CorpId(); 14 var permanentCode = model.PermanentCode(); 15 var agentId = model.AgentId(); 16 var suiteId = model.SuiteId(); 17 var suiteSecret = model.SuiteSecret(); 18 var suiteTicket = model.SuiteTicket(); 19 20 if (type === "1" && tokenType === "2") { 21 if (appId == undefined || $.trim(appId).length === 0) { 22 alert("请输入AppId"); 23 return; 24 } 25 26 if (appSecret == undefined || $.trim(appSecret).length === 0) { 27 alert("请输入AppSecret"); 28 return; 29 } 30 } else if (type === "2" && tokenType === "2") { 31 if (corpId == undefined || $.trim(corpId).length === 0) { 32 alert("请输入CorpId"); 33 return; 34 } 35 if (permanentCode == undefined || $.trim(permanentCode).length === 0) { 36 alert("请输入永久授权码"); 37 return; 38 } 39 40 if (agentId == undefined || $.trim(agentId).length === 0) { 41 alert("请输入AgentId"); 42 return; 43 } 44 45 if (suiteId == undefined || $.trim(suiteId).length === 0) { 46 alert("请输入SuiteId"); 47 return; 48 } 49 50 if (suiteSecret == undefined || $.trim(suiteSecret).length === 0) { 51 alert("请输入SuiteSecret"); 52 return; 53 } 54 55 if (suiteTicket == undefined || $.trim(suiteTicket).length === 0) { 56 alert("请输入SuiteTicket"); 57 return; 58 } 59 } else if (tokenType === "1") { 60 if (accessToken == undefined || $.trim(accessToken).length === 0) { 61 alert("请输入AccessToken"); 62 return; 63 } 64 } 65 $("#btnQueryMenu").button("查询中..."); 66 $.ajax({ 67 url: "", 68 datatype: "JSON", 69 type: "POST", 70 async: true, 71 data: JSON.stringify({ 72 appId: appId, appSecret: appSecret, accessToken: accessToken, type: type, tokenType: tokenType, corpId: corpId, permanentCode: permanentCode, agentId: agentId, 73 suiteId: suiteId, suiteSecret: suiteSecret, suiteTicket: suiteTicket 74 }), 75 contentType: "application/json; charset=UTF-8", 76 success: function (obj) { 77 $("#btnQueryMenu").button("reset"); 78 if (obj.Success) { 79 var data =http://www.mamicode.com/ obj.Data; 80 var menus = JSON.parse(data).menu; 81 $(menus.button).each(function (index, item) { 82 if (item.type === "view") { 83 item.value =http://www.mamicode.com/ item.url; 84 delete item.url; 85 } else { 86 item.value =http://www.mamicode.com/ item.key; 87 delete item.key; 88 } 89 if (item.type == undefined) { 90 item.type = "view"; 91 item.valuehttp://www.mamicode.com/= ""; 92 } 93 94 if (item.sub_button instanceof Array) { 95 $(item.sub_button).each(function (index1, item2) { 96 if (item2.type === "view") { 97 item2.value =http://www.mamicode.com/ item2.url; 98 delete item2.url; 99 } else { 100 item2.value =http://www.mamicode.com/ item2.key; 101 delete item2.key; 102 } 103 }); 104 } 105 }); 106 107 model.Menu(undefined); 108 model.MenuIndex(undefined); 109 model.BottonIndex(-1); 110 model.SubBottonIndex(-1); 111 model.Menus(undefined); 112 model.Menus(menus); 113 } else { 114 alert(obj.Messages); 115 } 116 }, 117 error: function (xmlHttpRequest, textStatus, errorThrown) { 118 $("#btnQueryMenu").button("reset"); 119 console.error(errorThrown); 120 } 121 }); 122 } 123 }, 124 SaveMenus: function () { 125 var menus = JSON.parse(JSON.stringify(model.Menus())); 126 $(menus.button).each(function (index, item) { 127 if (item.type === "view") { 128 item.url = item.value; 129 delete item.value; 130 } else { 131 item.key = item.value; 132 delete item.value; 133 } 134 135 if (item.sub_button instanceof Array) { 136 $(item.sub_button).each(function (index1, item2) { 137 if (item2.type === "view") { 138 item2.url = item2.value; 139 delete item2.value; 140 } else { 141 item2.key = item2.value; 142 delete item2.value; 143 } 144 }); 145 146 if (item.sub_button.length > 0) { 147 delete item.key; 148 delete item.url; 149 delete item.type; 150 } else { 151 delete item.sub_button; 152 } 153 } 154 }); 155 156 console.log(JSON.stringify(menus)); 157 158 var appId = model.AppId(); 159 var appSecret = model.AppSecret(); 160 var accessToken = model.AccessToken(); 161 var type = model.Type(); 162 var tokenType = model.TokenType(); 163 var agentId = model.AgentId(); 164 var suiteId = model.SuiteId(); 165 var suiteSecret = model.SuiteSecret(); 166 var suiteTicket = model.SuiteTicket(); 167 168 if (type === "1" && tokenType === "2") { 169 if (appId == undefined || $.trim(appId).length === 0) { 170 alert("请输入AppId"); 171 return; 172 } 173 174 if (appSecret == undefined || $.trim(appSecret).length === 0) { 175 alert("请输入AppSecret"); 176 return; 177 } 178 } else if (type === "2" && tokenType === "2") { 179 if (agentId == undefined || $.trim(agentId).length === 0) { 180 alert("请输入AgentId"); 181 return; 182 } 183 184 if (suiteId == undefined || $.trim(suiteId).length === 0) { 185 alert("请输入SuiteId"); 186 return; 187 } 188 189 if (suiteSecret == undefined || $.trim(suiteSecret).length === 0) { 190 alert("请输入SuiteSecret"); 191 return; 192 } 193 194 if (suiteTicket == undefined || $.trim(suiteTicket).length === 0) { 195 alert("请输入SuiteTicket"); 196 return; 197 } 198 } else if (tokenType === "1") { 199 if (accessToken == undefined || $.trim(accessToken).length === 0) { 200 alert("请输入AccessToken"); 201 return; 202 } 203 } 204 205 $("#btnSubmitMenu").button("发布中..."); 206 $.ajax({ 207 url: "", 208 datatype: "JSON", 209 type: "POST", 210 async: true, 211 data: JSON.stringify({ 212 appId: appId, appSecret: appSecret, accessToken: accessToken, type: type, tokenType: tokenType, agentId: agentId, 213 suiteId: suiteId, suiteSecret: suiteSecret, suiteTicket: suiteTicket, menu: JSON.stringify(menus) 214 }), 215 contentType: "application/json; charset=UTF-8", 216 success: function (obj) { 217 $("#btnSubmitMenu").button("reset"); 218 if (obj.Success) { 219 alert("发布成功"); 220 } else { 221 alert(obj.Messages); 222 } 223 }, 224 error: function (xmlHttpRequest, textStatus, errorThrown) { 225 $("#btnSubmitMenu").button("reset"); 226 console.error(errorThrown); 227 } 228 }); 229 }
最终效果如下
在线体验可点击:在线体验Demo
后台代码就不贴了c#开发可用Senparc.Weixin库轻松调用微信接口
第一次写博客,质量不高,见谅见谅~
使用asp.net mvc,boostrap及knockout.js开发微信自定义菜单编辑工具