• ASP.NET MVC5+EF6+EasyUI 后台管理系统(74)-微信公众平台开发-自定义菜单


    系列目录

    引言

    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;
             1000px;
            line-height: 55px;
        }
    
            .menutable input[type="text"] {
                 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=" 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="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="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>
    var senparc = {};
    var maxSubMenuCount = 5;
    var menuState;
    senparc.menu = {
        token: '',
        init: function () {
            menuState = $('#menuState');
    
            $('#buttonDetails').hide();
            $('#menuEditor').hide();
    
            $("#buttonDetails_type").change(senparc.menu.typeChanged);
    
            $(':input[id^=menu_button]').click(function () {
                $('#buttonDetails').show(100);
                var idPrefix = $(this).attr('data-root')
                                ? ('menu_button' + $(this).attr('data-root'))
                                : ('menu_button' + $(this).attr('data-j') + '_sub_button' + $(this).attr('data-i'));
    
                var keyId = idPrefix + "_key";
                var nameId = idPrefix + "_name";
                var typeId = idPrefix + "_type";
                var urlId = idPrefix + "_url";
    
                var txtDetailsKey = $('#buttonDetails_key');
                var txtDetailsName = $('#buttonDetails_name');
                var ddlDetailsType = $('#buttonDetails_type');
                var txtDetailsUrl = $('#buttonDetails_url');
    
                var hiddenButtonKey = $('#' + keyId);
                var hiddenButtonType = $('#' + typeId);
                var hiddenButtonUrl = $('#' + urlId);
    
                txtDetailsKey.val(hiddenButtonKey.val());
                txtDetailsName.val($('#' + nameId).val());
                ddlDetailsType.val(hiddenButtonType.val());
                txtDetailsUrl.val(hiddenButtonUrl.val());
    
                senparc.menu.typeChanged();
    
                txtDetailsKey.unbind('blur').blur(function () {
                    hiddenButtonKey.val($(this).val());
                });
                ddlDetailsType.unbind('blur').blur(function () {
                    hiddenButtonType.val($(this).val());
                });
                txtDetailsUrl.unbind('blur').blur(function () {
                    hiddenButtonUrl.val($(this).val());
                });
            });
    
            $('#menuLogin_Submit').click(function () {
                $.getJSON('/WC/MenuSetting/GetToken?t=' + Math.random(), { appId: $('#menuLogin_AppId').val(), appSecret: $('#menuLogin_AppSecret').val() },
                    function (json) {
                        if (json.access_token) {
                            senparc.menu.setToken(json.access_token);
                        } else {
                            alert(json.error || '执行过程有错误,请检查!');
                        }
                    });
            });
    
            $('#menuLogin_SubmitOldToken').click(function () {
                senparc.menu.setToken($('#menuLogin_OldToken').val());
            });
    
            $('#btnGetMenu').click(function () {
                menuState.html('获取菜单中...');
                $.getJSON('/WC/MenuSetting/GetMenu?t=' + Math.random(), { token: senparc.menu.token }, function (json) {
                    if (json.menu) {
                        $(':input[id^=menu_button]:not([id$=_type])').val('');
                        $('#buttonDetails:input').val('');
    
                        var buttons = json.menu.button;
                        //此处i与j和页面中反转
                        for (var i = 0; i < buttons.length; i++) {
                            var button = buttons[i];
                            $('#menu_button' + i + '_name').val(button.name);
                            $('#menu_button' + i + '_key').val(button.key);
                            $('#menu_button' + i + '_type').val(button.type || 'click');
                            $('#menu_button' + i + '_url').val(button.url);
    
                            if (button.sub_button && button.sub_button.length > 0) {
                                //二级菜单
                                for (var j = 0; j < button.sub_button.length; j++) {
                                    var subButton = button.sub_button[j];
                                    var idPrefix = '#menu_button' + i + '_sub_button' + j;
                                    $(idPrefix + "_name").val(subButton.name);
                                    $(idPrefix + "_type").val(subButton.type || 'click');
                                    $(idPrefix + "_key").val(subButton.key);
                                    $(idPrefix + "_url").val(subButton.url);
                                }
                            } else {
                                //底部菜单
                                //...
                            }
                        }
    
                        //显示JSON
                        $('#txtReveiceJSON').html(JSON.stringify(json));
    
                        menuState.html('菜单获取已完成');
                    } else {
                        menuState.html(json.error || '执行过程有错误,请检查!');
                    }
                });
            });
    
            $('#btnDeleteMenu').click(function () {
                if (!confirm('确定要删除菜单吗?此操作无法撤销!')) {
                    return;
                }
    
                menuState.html('删除菜单中...');
                $.getJSON('/WC/MenuSetting/DeleteMenu?t=' + Math.random(), { token: senparc.menu.token }, function (json) {
                    if (json.Success) {
                        menuState.html('删除成功,如果是误删,并且界面上有最新的菜单状态,可以立即点击【更新到服务器】按钮。');
                    } else {
                        menuState.html(json.Message);
                    }
                });
            });
    
            $('#submitMenu').click(function () {
                if (!confirm('确定要提交吗?此操作无法撤销!')) {
                    return;
                }
    
                menuState.html('上传中...');
    
                $('#form_Menu').ajaxSubmit({
                    dataType: 'json',
                    success: function (json) {
                        if (json.Successed) {
                            menuState.html('上传成功');
                        } else {
                            menuState.html(json.Message);
                        }
                    }
                });
            });
        },
        typeChanged: function () {
            var val = $('#buttonDetails_type').val();
            if (val.toUpperCase() == 'VIEW') {
                $('#buttonDetails_key_area').slideUp(100);
                $('#buttonDetails_url_area').slideDown(100);
            } else {
                $('#buttonDetails_key_area').slideDown(100);
                $('#buttonDetails_url_area').slideUp(100);
            }
        },
        setToken: function (token) {
            senparc.menu.token = token;
            $('#tokenStr').val(token);
            $('#menuEditor').show();
            $('#menuLogin').hide();
        }
    };
    senparc.menu.js

    最后界面

    总结

    1.普通菜单只要关注了就可以查看

    2.个性化菜单是有查看条件,比如性别,那么微信所属人的性别对应才可以查看

       一般个性化菜单,适用于会员级别享有特殊权限

    示例代码下载:https://yunpan.cn/cM9ffkutawueD  访问密码 2f0d

     参考资料

      参考开源项目中的Senparc.Weixin SDK,

  • 相关阅读:
    ranger0.5.4-开源安装配置
    Spark Streaming 读取Kafka数据写入ES
    kettle与sqoop的比较
    spark常用算子
    eclipse快捷键
    hive相关操作
    我眼中如何成为一名合格PHP高级开发工程师
    laravel 路由404
    TP5.0 未定义变量
    公众号基本配置(token验证失败)|公众平台测试账号接口配置信息(token验证失败)
  • 原文地址:https://www.cnblogs.com/ymnets/p/5837650.html
Copyright © 2020-2023  润新知