引言
1、如果不借用Senparc.Weixin SDK自定义菜单,编码起来,工作量是非常之大
2、但是借助SDK似乎一切都是简单得不要不要的
3、自定义菜单无需要建立数据库表
4、自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
5、一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。
6、创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。
测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
7、下载尾部代码,跑起来调试
自定义接口的类型
1、click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
2、view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
3、scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
5、pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
6、pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
7、pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
8、location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
9、media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
10、view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
总结出类型:
<select id="buttonDetails_type" class="dllButtonDetails"> <option value="click" selected="selected">点击事件(传回服务器)</option> <option value="view">访问网页(直接跳转)</option> <option value="location_select">弹出地理位置选择器</option> <option value="pic_photo_or_album">弹出拍照或者相册发图</option> <option value="pic_sysphoto">弹出系统拍照发图</option> <option value="pic_weixin">弹出微信相册发图器</option> <option value="scancode_push">扫码推事件</option> <option value="scancode_waitmsg">扫码推事件且弹出“消息接收中”提示框</option> </select>
接口的调用和类型
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN
借助Senparc.Weixin SDK
由于不需要数据库,所以只有控制器和前端
控制器:编辑菜单,删除菜单,获取菜单
[HttpPost] public ActionResult CreateMenu( GetMenuResultFull resultFull, MenuMatchRule menuMatchRule) { WC_OfficalAccountsModel model = account_BLL.GetCurrentAccount(); string token = model.AccessToken; var useAddCondidionalApi = menuMatchRule != null && !menuMatchRule.CheckAllNull(); var apiName = string.Format("使用接口:{0}。", (useAddCondidionalApi ? "个性化菜单接口" : "普通自定义菜单接口")); try { //重新整理按钮信息 WxJsonResult result = null; IButtonGroupBase buttonGroup = null; if (useAddCondidionalApi) { //个性化接口 buttonGroup = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetMenuFromJsonResult(resultFull, new ConditionalButtonGroup()).menu; var addConditionalButtonGroup = buttonGroup as ConditionalButtonGroup; addConditionalButtonGroup.matchrule = menuMatchRule; result = Senparc.Weixin.MP.CommonAPIs.CommonApi.CreateMenuConditional(token, addConditionalButtonGroup); apiName += string.Format("menuid:{0}。", (result as CreateMenuConditionalResult).menuid); } else { //普通接口 buttonGroup = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetMenuFromJsonResult(resultFull, new ButtonGroup()).menu; result = Senparc.Weixin.MP.CommonAPIs.CommonApi.CreateMenu(token, buttonGroup); } var json = new { Success = result.errmsg == "ok", Message = "菜单更新成功。" + apiName }; return Json(json); } catch (Exception ex) { var json = new { Success = false, Message = string.Format("更新失败:{0}。{1}", ex.Message, apiName) }; return Json(json); } } public ActionResult GetMenu() { WC_OfficalAccountsModel model = account_BLL.GetCurrentAccount(); string token = model.AccessToken; var result = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetMenu(token); if (result == null) { return Json(new { error = "菜单不存在或验证失败!" }, JsonRequestBehavior.AllowGet); } return Json(result, JsonRequestBehavior.AllowGet); } public ActionResult DeleteMenu() { try { WC_OfficalAccountsModel model = account_BLL.GetCurrentAccount(); string token = model.AccessToken; var result = Senparc.Weixin.MP.CommonAPIs.CommonApi.DeleteMenu(token); var json = new { Success = result.errmsg == "ok", Message = result.errmsg }; return Json(json, JsonRequestBehavior.AllowGet); } catch (Exception ex) { var json = new { Success = false, Message = ex.Message }; return Json(json, JsonRequestBehavior.AllowGet); } }
都用SDK来完成接口的调用
前端代码
<style> .menutable { border: 1px #ccc solid; text-align: center; width: 1000px; line-height: 55px; } .menutable input[type="text"] { width: 150px; } .menutable th { border: 1px #ccc solid; text-align: center; } .menutable td { border: 1px #ccc solid; } .float-left { float: right; } .menu-state { line-height:40px; } </style> <div class="mvctool"> @Html.ToolButton("btnGetMenu", "fa fa-pencil", "获取当前菜单", ref perm, "Edit", true) @Html.ToolButton("submitMenu", "fa fa-pencil", "更新到服务器", ref perm, "Edit", true) @Html.ToolButton("btnDeleteMenu", "fa fa-trash", "删除菜单", ref perm, "Delete", true) <div class="rightdiv color-green"> 当前操作公众号:<span id="CurrentOfficalAccount">@(account.OfficalName)</span> @{if (string.IsNullOrEmpty(account.AppId) || string.IsNullOrEmpty(account.AppSecret) || string.IsNullOrEmpty(account.AccessToken)) { <span class="color-red">当前公众号没有菜单功能</span> } } </div> </div> <form id="form_Menu" action="/WC/MenuSetting/CreateMenu" method="post"> <p class="displaynone"> 当前Token: <input id="tokenStr" name="token" class="control-input" style="width: 900px;" type="text" readonly="readonly" /><br /> </p> <p class="menu-state color-green"> 操作状态:<strong id="menuState">-</strong> </p> <table> <tr> <td> <table class="formtable menutable" style="width:650px;"> <thead> <tr> <th></th> <th>第一列</th> <th>第二列</th> <th>第三列</th> </tr> </thead> <tbody> @for (int i = 0; i < 6; i++) { var isRootMenu = i == 5; <tr id="@(isRootMenu ? "subMenuRow_" + i : "rootMenuRow")"> <td> @(isRootMenu ? "一级菜单按钮" : ("二级菜单No." + (i + 1))) </td> @for (int j = 0; j < 3; j++) { var namePrefix = isRootMenu ? string.Format("menu.button[{0}]", j) : string.Format("menu.button[{0}].sub_button[{1}]", j, i); var idPrefix = isRootMenu ? string.Format("menu_button{0}", j) : string.Format("menu_button{0}_sub_button{1}", j, i); <td> <input type="hidden" class="control-input" name="@(namePrefix).key" id="@(idPrefix)_key" /> <input type="hidden" class="control-input" name="@(namePrefix).type" id="@(idPrefix)_type" value="click" /> <input type="hidden" class="control-input" name="@(namePrefix).url" id="@(idPrefix)_url" /> <input type="text" class="control-input" name="@(namePrefix).name" id="@(idPrefix)_name" class="txtButton" data-i="@i" data-j="@j" @Html.Raw(isRootMenu ? string.Format(@"data-root=""{0}""", j) : "") /> </td> } </tr> } </tbody> </table> </td> <td style="width:500px"> <div id="buttonDetails"> <table class="formtable"> <tr> <th></th> <td> 按钮其他参数 </td> </tr> <tr> <th>Name:</th> <td><input type="text" id="buttonDetails_name" class="control-input txtButton" disabled="disabled" /></td> </tr> <tr> <th> Type: </th> <td> <select id="buttonDetails_type" class="dllButtonDetails"> <option value="click" selected="selected">点击事件(传回服务器)</option> <option value="view">访问网页(直接跳转)</option> <option value="location_select">弹出地理位置选择器</option> <option value="pic_photo_or_album">弹出拍照或者相册发图</option> <option value="pic_sysphoto">弹出系统拍照发图</option> <option value="pic_weixin">弹出微信相册发图器</option> <option value="scancode_push">扫码推事件</option> <option value="scancode_waitmsg">扫码推事件且弹出“消息接收中”提示框</option> </select> </td> </tr> <tr id="buttonDetails_key_area"> <th> Key: </th> <td><input id="buttonDetails_key" class="control-input txtButtonDetails" type="text" /></td> </tr> <tr id="buttonDetails_url_area"> <th> Url: </th> <td> <input id="buttonDetails_url" class="control-input txtButtonDetails" type="text" /> </td> </tr> <tr> <th></th> <td> 如果还有下级菜单请忽略Type和Key、Url。 </td> </tr> </table> </div> </td> </tr> </table> <div id="addConditionalArea"> <p><h3>个性化菜单设置</h3></p> <table> <tr> <th>group_id</th> <td> <input type="text" name="MenuMatchRule.group_id" placeholder="用户分组id,可通过用户分组管理接口获取" class="control-input" /> </td> </tr> <tr><th>sex</th><td><input type="text" name="MenuMatchRule.sex" placeholder="性别:男(1)女(2),不填则不做匹配" class="control-input" /></td></tr> <tr><th>country</th><td><input type="text" name="MenuMatchRule.country" placeholder="国家信息,是用户在微信中设置的地区,具体请参考地区信息表" class="control-input" /></td></tr> <tr><th>province</th><td><input type="text" name="MenuMatchRule.province" placeholder="省份信息,是用户在微信中设置的地区,具体请参考地区信息表" class="control-input" /></td></tr> <tr><th>city</th><td><input type="text" name="MenuMatchRule.city" placeholder="城市信息,是用户在微信中设置的地区,具体请参考地区信息表" class="control-input" /></td></tr> <tr><th>client_platform_type</th><td><input type="text" name="MenuMatchRule.client_platform_type" placeholder="客户端版本,当前只具体到系统型号:IOS(1), Android(2),Others(3),不填则不做匹配" class="control-input" /></td></tr> </table> <p><i>提示:如果所有字段都留空,则使用普通自定义菜单,如果任何一个条件有值,则使用个性化菜单接口</i></p> </div> <div class="clear"></div> </form> <div id="reveiveJSON" class="displaynone"> <p>接收菜单JSON:</p> <p><textarea id="txtReveiceJSON"></textarea></p> </div> <script src="@Url.Content("~/Scripts/WeChat/senparc.menu.js")"></script> <script> $(function () { senparc.menu.init(); }); </script>
最后界面
总结
1.普通菜单只要关注了就可以查看
2.个性化菜单是有查看条件,比如性别,那么微信所属人的性别对应才可以查看
一般个性化菜单,适用于会员级别享有特殊权限
示例代码下载:https://yunpan.cn/cM9ffkutawueD 访问密码 2f0d