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;
        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>
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,


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM