Develop wechat custom menu editing tool using asp.net MVC, bootstrap and knockout.js (recommended)

Time:2021-10-10

preface

Wechat’s interface debugging tool can edit custom menus, but it is very inconvenient and error prone to submit JSON format data to create menus. Online tools are not easy to use, so I wrote one myself.

text

First, use bootstrap to arrange a page frame. To call the user-defined menu interface, you need to use accesstoken. Put an input box and enter accesstoken. Users who want to directly enter appid and appsecret to obtain accesstoken are not excluded, so the drop-down menu is also required to select whether to enter accesstoken or obtain accesstoken directly. In order to take into account the wechat enterprise application, the creation menu also needs AgentID, Corpid, suite permanent authorization code, suiteid, suitesecret and suiteticket. These are roughly the input boxes of parameters.

Use knockout to define the observables monitoring attribute. And bind to the input box.

   

Define menu display and menu editing module, typesetting as three menu of WeChat official account menu, and five sub menus under each big menu. The general idea is as follows. The page layout is six rows and three columns. When the three large menus are not fully configured, the add menu button is displayed on the right,

When the submenus of each parent menu are not fully configured, the add menu button is displayed above. When it is not configured to be full, it is occupied with a blank Div.

Define a function to generate a custom length array

Use knockout to define the menu monitoring attribute in the format

{
 "button": [
  {
   "Name": "parent menu 1",
   "sub_button": [
    {
     "type": "view",
     "Name": "submenu 1",
     "url": ""
    }
   ]
  },
  {
   "Name": "parent menu 1",
   "sub_button": [
    {
     "type": "view",
     "Name": "submenu 2",
     "url": ""
    },
    {
     "type": "view",
     "Name": "submenu 1",
     "url": ""
    }
   ]
  }
 ]
}

Define the add, edit and delete menu functions, define the temporary monitoring properties when adding and editing menus, and define the monitoring properties of the current editing menu index.

It’s not very convenient to edit menus one by one, so you have to define the up, down, left, right and copy paste functions of the menu.

function MenuFormValidate() {
   $("#MenuForm").validate({
    rules: {
     name: {
      required: true
     },
     value: {
      required: false
     }
    },
    messages: {
     name: {
      Required: "please enter a name"
     },
     value: {
      required: $("#txtMenuButtonValue").attr("placeholder")
     }
    }
   });
  }
          MenusReset:function () {
     var menus = JSON.stringify(model.Menus());
     model.Menus(undefined);
     model.Menus(JSON.parse(menus));// Refresh menu object
     MenuFormValidate();// Rebind validation method
    },
Menuindex: Ko. Observable(), // parent menu index
    Iseditmenu: Ko. Observable (false), // is it an edit menu
    Bottomindex: Ko. Observable (- 1), // edit the parent menu index of the menu
    Subbottomindex: ko.observable (- 1), // edit the sub menu index of the menu
    Menu: Ko. Observable(), // temporarily monitor properties when editing a menu
    Copymenu: Ko. Observable(), // copied menu object
    Copy: function() {// copy
     if (model.Menu() != undefined) {
      var menu = JSON.stringify(model.Menu());
      model.CopyMenu(JSON.parse(menu));
      model.Menu(undefined);
     }
    },
    Paste: function() {// paste
     if (model.CopyMenu() != undefined) {
      var menu = JSON.parse(JSON.stringify(model.CopyMenu()));
      if (model.SubBottonIndex() !== -1 && menu.sub_button != undefined || (!model.isEditMenu() && model.MenuIndex() != undefined)) {
       delete menu.sub_button;
      }
      model.Menu(menu);
      MenuFormValidate();
     }
    },
    Up: function() {// move up
     var bottonIndex = model.BottonIndex();
     var subBottonIndex = model.SubBottonIndex();
     var newSubBottonIndex = subBottonIndex - 1;
     model.Menus().button[bottonIndex].sub_button[subBottonIndex] = model.Menus().button[bottonIndex].sub_button[newSubBottonIndex];
     model.Menus().button[bottonIndex].sub_button[newSubBottonIndex] = model.Menu();
     model.MenusReset();
     model.SubBottonIndex(newSubBottonIndex);
    },
    Down: function() {// move down
     var bottonIndex = model.BottonIndex();
     var subBottonIndex = model.SubBottonIndex();
     var newSubBottonIndex = subBottonIndex + 1;
     model.Menus().button[bottonIndex].sub_button[subBottonIndex] = model.Menus().button[bottonIndex].sub_button[newSubBottonIndex];
     model.Menus().button[bottonIndex].sub_button[newSubBottonIndex] = model.Menu();
     model.MenusReset();
     model.SubBottonIndex(newSubBottonIndex);
    },
    Left: function() {// move left
     var bottonIndex = model.BottonIndex();
     var subBottonIndex = model.SubBottonIndex();
     if (subBottonIndex === -1) {
      var newBottonIndex = bottonIndex - 1;
      model.Menus().button[bottonIndex] = model.Menus().button[newBottonIndex];
      model.Menus().button[newBottonIndex] = model.Menu();
      model.MenusReset();
      model.BottonIndex(newBottonIndex);
     }
    },
    Right: function() {// move right
     var bottonIndex = model.BottonIndex();
     var subBottonIndex = model.SubBottonIndex();
     if (subBottonIndex === -1) {
      var newBottonIndex = bottonIndex + 1;
      model.Menus().button[bottonIndex] = model.Menus().button[newBottonIndex];
      model.Menus().button[newBottonIndex] = model.Menu();
      model.MenusReset();
      model.BottonIndex(newBottonIndex);
     }
    },
    Editmenu: function (obj, bottonindex, subbottonindex) {// Edit menu
     model.BottonIndex(bottonindex);
     model.SubBottonIndex(subbottonindex);
     model.isEditMenu(true);
     var data = JSON.stringify(obj);
     model.Menu(JSON.parse(data));
     MenuFormValidate();
    },
    Addmenu: function (index) {// add menu
     model.BottonIndex(-1);
     model.SubBottonIndex(-1);
     model.isEditMenu(false);
     model.MenuIndex(index);
     var menu = { type: "view", name: "", value: "" };
     model.Menu(menu);
     MenuFormValidate();
    },
    Deletemenu: function() {// delete menu
     $(model.Menus().button).each(function (index, item) {
      if (index === model.BottonIndex() && model.SubBottonIndex() === -1) {
       model.Menus().button.splice(index, 1);
      }
      if (item.sub_button instanceof Array) {
       $(item.sub_button).each(function (index1) {
        if (index === model.BottonIndex() && index1 === model.SubBottonIndex()) {
         item.sub_button.splice(index1, 1);
        }
       });
      }
     });
     model.Menu(undefined);
     model.MenuIndex(undefined);
     model.BottonIndex(-1);
     model.SubBottonIndex(-1);
     model.MenusReset();
    },
    Cancelmenusave: function() {// cancel editing and reset parameters
     model.Menu(undefined);
     model.MenuIndex(undefined);
     model.BottonIndex(-1);
     model.SubBottonIndex(-1);
    },
    Menusave: function() {// save the edited menu
     if (!$("#MenuForm").data("validator").form()) {
      return;
     }
     if (model.isEditMenu()) {
      var menuIndex = model.BottonIndex();
      var subMenuIndex = model.SubBottonIndex();
      if (subMenuIndex === -1) {
       model.Menus().button[menuIndex] = model.Menu();
      } else {
       model.Menus().button[menuIndex].sub_button[subMenuIndex] = model.Menu();
      }
     } else {
      if (model.MenuIndex() != undefined) {
       if (model.Menus().button[model.MenuIndex()].sub_button == undefined) {
        model.Menus().button[model.MenuIndex()].sub_button = new Array();
       }
       model.Menus().button[model.MenuIndex()].sub_button.unshift(model.Menu());
      } else {
       model.Menus().button.push(model.Menu());
      }
     }
     model.Menu(undefined);
     model.MenuIndex(undefined);
     model.BottonIndex(-1);
     model.SubBottonIndex(-1);
     model.MenusReset();
    },

Bind the monitoring properties to generate menu layout

<div data-bind="with:Menus" style="display: none;">
  <div style="height: 200px;" data-bind="foreach:newArray(3)">
   <div>
    <!--ko if:($parent.button.length>0 && $parent.button[$index()]!=undefined && $parent.button[$index()].sub_button!=undefined ) -->
    <!--ko foreach:newArray((4-$parent.button[$index()].sub_button.length)) -->
    <div></div>
    <!--/ko-->
    <!--ko if:$parent.button[$index()].sub_button.length<5 -->
    <div data-bind="click:function (){$root.AddMenu($index())}"><i></i>
    </div>
    <!--/ko-->
    <!--ko foreach:($parent.button[$index()].sub_button) -->
    <div data-bind="text:name,attr:{'bottonIndex':$parent.value,'subbottonIndex':$index()},click:function (){$root.EditMenu($data,$parent.value,$index())}"></div>
    <!--/ko-->
    <!--/ko -->
    <!--ko if: $parent.button[$index()]!=undefined && $parent.button[$index()].sub_button==undefined -->
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div data-bind="click:function (){$root.AddMenu($index())}"><i></i>
    </div>
    <!--/ko-->
    <!--ko if: $parent.button[$index()]==undefined -->
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <!--/ko-->
   </div>
  </div>
  <!--ko foreach:button -->
  <div data-bind="text:name,attr:{'bottonindex':$index()},click:function (){$root.EditMenu($data,$index(),-1)}"></div>
  <!--/ko-->
  <!--ko if:button.length < 3 -->
  <div data-bind="click:function (){$root.AddMenu();}"><i></i>
  </div>
  <!--/ko-->
  <div></div>
  <div style="border: 1px solid #EEEEEE; padding-top: 15px; margin-top: 15px;" data-bind="with:$root.Menu,visible:($root.Menu()!=undefined)">
   <form onsubmit="return false;">
    <div>
     < input type = "text" name = "name" placeholder = "please enter name" data bind = "value: name" >
    </div>
    <div>
     <select onchange="$('#txtMenuButtonValue')
 .attr('placeholder', $(this).find('option:selected').attr('pl'))" data-bind="value:type">
      < option value = "view" PL = "please enter URL" > jump URL < / option >
      < option value = "click" PL = "please enter key" > Click to push the event < / option >
      < option value = "scancode_push" PL = "please enter key" > code scanning push event < / option >
      < option value = "scancode_waitmsg" PL = "please enter key" > scan code to push the event, and the prompt box of "message receiving" pops up < / option >
      < option value = "pic_sysphoto" PL = "please enter key" > the system will pop up to take photos and send pictures < / option >
      < option value = "pic_photo_or_album" PL = "please enter key" > pop up to take photos or send pictures of photo albums < / option >
      < option value = "pic_weixin" PL = "please enter key" > the wechat photo album poster < / option > pops up
      < option value = "location_select" PL = "please enter key" > pop up the geographic location selector < / option >
     </select>
    </div>
    <div>
     < input type = "text" name = "value" placeholder = "please enter URL" data bind = "value: value" >
    </div>
    <div>
     < button type = "submit" data bind = "click: $root. Menusave" > OK < / button >
     < button type = "submit" data bind = "visible: $root.iseditmenu, click: $root. Deletemenu" > delete < / button >
     < button type = "button" title = "move up" data bind = "visible: $root. Iseditmenu(), disable: $! $root. Isup(), click: $root. Up" > < I aria hidden = "true" > < / I > < / button >
     < button type = "button" title = "move down" data bind = "visible: $root. Iseditmenu(), disable: $! $root. Isdown(), click: $root. Down" > < I aria hidden = "true" > < / I > < / button >
     < button type = "button" title = "move left" data bind = "visible: $root. Iseditmenu(), disable: $! $root. Isleft(), click: $root. Left" > < I aria hidden = "true" > < / I > < / button >
     < button type = "button" title = "move right" data bind = "visible: $root. Iseditmenu(), disable: $! $root. Isright(), click: $root. Right" > < I aria hidden = "true" > < / I > < / button >
     < button type = "button" title = "copy menu" data bind = "visible: $root. Iseditmenu(), click: $root. Copy" > copy < / button >
     < button type = "button" title = "paste menu" data bind = "click: $root. Paste" > Paste < / button >
     < button type = "submit" data bind = "click: $root. Cancelmenusave" > Close < / button >
    </div>
   </form>
  </div>
  <div></div>
 </div>

Finally, the query function and publishing function of the menu are added. Because it is convenient to edit the menu and the menu object does not correspond to the JSON format required by the wechat custom menu interface, it is necessary to change the JSON data format when querying the existing menu and publishing the menu.,

EditMenus: function (isQuery) {
     if (isQuery == undefined) {
      var menu = {};
      menu.button = new Array();
      model.Menus(menu);
     } else {
      var appId = model.AppId();
      var appSecret = model.AppSecret();
      var accessToken = model.AccessToken();
      var type = model.Type();
      var tokenType = model.TokenType();
      var corpId = model.CorpId();
      var permanentCode = model.PermanentCode();
      var agentId = model.AgentId();
      var suiteId = model.SuiteId();
      var suiteSecret = model.SuiteSecret();
      var suiteTicket = model.SuiteTicket();
      if (type === "1" && tokenType === "2") {
       if (appId == undefined || $.trim(appId).length === 0) {
        Alert ("please enter appid");
        return;
       }
       if (appSecret == undefined || $.trim(appSecret).length === 0) {
        Alert ("please enter appsecret");
        return;
       }
      } else if (type === "2" && tokenType === "2") {
       if (corpId == undefined || $.trim(corpId).length === 0) {
        Alert ("please enter Corpid");
        return;
       }
       if (permanentCode == undefined || $.trim(permanentCode).length === 0) {
        Alert ("please enter the permanent authorization code");
        return;
       }
       if (agentId == undefined || $.trim(agentId).length === 0) {
        Alert ("please enter AgentID");
        return;
       }
       if (suiteId == undefined || $.trim(suiteId).length === 0) {
        Alert ("please enter suiteid");
        return;
       }
       if (suiteSecret == undefined || $.trim(suiteSecret).length === 0) {
        Alert ("please enter suitesecret");
        return;
       }
       if (suiteTicket == undefined || $.trim(suiteTicket).length === 0) {
        Alert ("please enter suiteticket");
        return;
       }
      } else if (tokenType === "1") {
       if (accessToken == undefined || $.trim(accessToken).length === 0) {
        Alert ("please enter accesstoken");
        return;
       }
      }
      $("#btnquerymenu"). Button ("querying...);
      $.ajax({
       url: "",
       datatype: "JSON",
       type: "POST",
       async: true,
       data: JSON.stringify({
        appId: appId, appSecret: appSecret, accessToken: accessToken, type: type, tokenType: tokenType, corpId: corpId, permanentCode: permanentCode, agentId: agentId,
        suiteId: suiteId, suiteSecret: suiteSecret, suiteTicket: suiteTicket
       }),
       contentType: "application/json; charset=UTF-8",
       success: function (obj) {
        $("#btnQueryMenu").button("reset");
        if (obj.Success) {
         var data = obj.Data;
         var menus = JSON.parse(data).menu;
         $(menus.button).each(function (index, item) {
          if (item.type === "view") {
           item.value = item.url;
           delete item.url;
          } else {
           item.value = item.key;
           delete item.key;
          }
          if (item.type == undefined) {
           item.type = "view";
           item.value = "";
          }
          if (item.sub_button instanceof Array) {
           $(item.sub_button).each(function (index1, item2) {
            if (item2.type === "view") {
             item2.value = item2.url;
             delete item2.url;
            } else {
             item2.value = item2.key;
             delete item2.key;
            }
           });
          }
         });
         model.Menu(undefined);
         model.MenuIndex(undefined);
         model.BottonIndex(-1);
         model.SubBottonIndex(-1);
         model.Menus(undefined);
         model.Menus(menus);
        } else {
         alert(obj.Messages);
        }
       },
       error: function (xmlHttpRequest, textStatus, errorThrown) {
        $("#btnQueryMenu").button("reset");
        console.error(errorThrown);
       }
      });
     }
    },
    SaveMenus: function () {
     var menus = JSON.parse(JSON.stringify(model.Menus()));
     $(menus.button).each(function (index, item) {
      if (item.type === "view") {
       item.url = item.value;
       delete item.value;
      } else {
       item.key = item.value;
       delete item.value;
      }
      if (item.sub_button instanceof Array) {
       $(item.sub_button).each(function (index1, item2) {
        if (item2.type === "view") {
         item2.url = item2.value;
         delete item2.value;
        } else {
         item2.key = item2.value;
         delete item2.value;
        }
       });
       if (item.sub_button.length > 0) {
        delete item.key;
        delete item.url;
        delete item.type;
       } else {
        delete item.sub_button;
       }
      }
     });
     console.log(JSON.stringify(menus));
     var appId = model.AppId();
     var appSecret = model.AppSecret();
     var accessToken = model.AccessToken();
     var type = model.Type();
     var tokenType = model.TokenType();
     var agentId = model.AgentId();
     var suiteId = model.SuiteId();
     var suiteSecret = model.SuiteSecret();
     var suiteTicket = model.SuiteTicket();
     if (type === "1" && tokenType === "2") {
      if (appId == undefined || $.trim(appId).length === 0) {
       Alert ("please enter appid");
       return;
      }
      if (appSecret == undefined || $.trim(appSecret).length === 0) {
       Alert ("please enter appsecret");
       return;
      }
     } else if (type === "2" && tokenType === "2") {
      if (agentId == undefined || $.trim(agentId).length === 0) {
       Alert ("please enter AgentID");
       return;
      }
      if (suiteId == undefined || $.trim(suiteId).length === 0) {
       Alert ("please enter suiteid");
       return;
      }
      if (suiteSecret == undefined || $.trim(suiteSecret).length === 0) {
       Alert ("please enter suitesecret");
       return;
      }
      if (suiteTicket == undefined || $.trim(suiteTicket).length === 0) {
       Alert ("please enter suiteticket");
       return;
      }
     } else if (tokenType === "1") {
      if (accessToken == undefined || $.trim(accessToken).length === 0) {
       Alert ("please enter accesstoken");
       return;
      }
     }
     $("#btnsubmitmenu"). Button ("Publishing...);
     $.ajax({
      url: "",
      datatype: "JSON",
      type: "POST",
      async: true,
      data: JSON.stringify({
       appId: appId, appSecret: appSecret, accessToken: accessToken, type: type, tokenType: tokenType, agentId: agentId,
       suiteId: suiteId, suiteSecret: suiteSecret, suiteTicket: suiteTicket, menu: JSON.stringify(menus)
      }),
      contentType: "application/json; charset=UTF-8",
      success: function (obj) {
       $("#btnSubmitMenu").button("reset");
       if (obj.Success) {
        Alert ("published successfully");
       } else {
        alert(obj.Messages);
       }
      },
      error: function (xmlHttpRequest, textStatus, errorThrown) {
       $("#btnSubmitMenu").button("reset");
       console.error(errorThrown);
      }
     });
    }

The final effect is as follows

The above is what Xiaobian introduced to you. We use asp.net MVC, bootstrap and knockout.js to develop wechat custom menu editing tools. We hope to help you. If you have any questions, please leave me a message and Xiaobian will reply to you in time. Thank you very much for your support to the developeppaer website!