4. 自定义菜单基础:菜单结构、按钮类型(click/view)、创建菜单、查询菜单、删除菜单

好,咱们今天来聊聊自定义菜单。这是公众号开发里最基础、也最实用的功能之一。我记得刚接手第一个公众号项目时,客户上来就说:「我要一个菜单,点一下能跳官网,再点一下能弹出客服。」嗯,其实就是 click 和 view 的组合。

说白了,自定义菜单就是公众号底部的那个导航栏。你想想看,用户关注你之后,第一眼看到的就是这个菜单。它好不好用,直接决定了用户愿不愿意留下来。

4.1 菜单结构长什么样?

先看结构。微信的菜单是三级结构:

  • 一级菜单:最多 3 个,显示在底部
  • 二级菜单:每个一级菜单下最多 5 个
  • 三级菜单:抱歉,没有三级。微信只支持两级

我在项目中遇到过不少新手,一上来就设计三级菜单,结果发现微信根本不支持。嗯,这个坑我先帮你踩了。

菜单的 JSON 结构大概是这样:

{
  "button": [
    {
      "name": "关于我们",
      "sub_button": [
        {
          "type": "view",
          "name": "公司官网",
          "url": "https://example.com"
        },
        {
          "type": "click",
          "name": "联系我们",
          "key": "contact_us"
        }
      ]
    },
    {
      "type": "click",
      "name": "每日签到",
      "key": "daily_sign"
    },
    {
      "type": "view",
      "name": "在线商城",
      "url": "https://shop.example.com"
    }
  ]
}

你看,最外层是 button 数组,里面每个对象就是一个一级菜单。如果一级菜单下面有子菜单,就用 sub_button 数组包起来。

重要提醒:一级菜单如果没子菜单,可以直接写 type 和 key/url。如果有子菜单,一级菜单本身不能设置 type,它的点击行为由子菜单决定。

4.2 按钮类型:click 和 view

微信菜单的按钮类型有好几种,但最常用的就两个:clickview。我建议你先掌握这两个,其他的(比如扫码、拍照等)后面再学。

类型 名称 触发方式 典型场景
click 点击推事件 用户点击后,微信服务器会推送一个事件到你的后台 签到、客服、领取优惠券
view 跳转 URL 用户点击后,直接跳转到指定的网页 官网、商城、文章详情

click 类型:你需要定义一个 key 值。用户点击后,微信会把这个 key 通过事件推送给你。你的后台收到后,就可以根据 key 做不同的处理。

view 类型:你需要提供一个 url。用户点击后,微信内置浏览器直接打开这个链接。注意,这个 url 必须在公众号后台的「网页授权域名」或「JS 接口安全域名」里配置过,否则会报错。

我的习惯:click 的 key 值我一般用英文加下划线,比如 daily_signcontact_us。这样在代码里好识别,也不容易乱。

4.3 创建菜单

创建菜单的接口很简单,就是往微信服务器发一个 POST 请求。接口地址是:

POST https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

请求体就是上面那个 JSON 结构。我直接给你看代码,用 Python 的 requests 库实现:

import requests
import json

def create_menu(access_token, menu_data):
    url = f"https://api.weixin.qq.com/cgi-bin/menu/create?access_token={access_token}"
    headers = {"Content-Type": "application/json"}
    
    response = requests.post(url, data=json.dumps(menu_data, ensure_ascii=False), headers=headers)
    result = response.json()
    
    if result.get("errcode") == 0:
        print("菜单创建成功!")
    else:
        print(f"创建失败:{result.get('errmsg')}")
    
    return result

调用的时候,把上面那个菜单 JSON 传进去就行。返回的 errcode 为 0 表示成功。

我曾经踩过的坑:有一次我创建菜单一直返回 errcode: 40018,查了半天才发现是按钮名称长度超了。一级菜单名称不能超过 4 个汉字,二级菜单不能超过 7 个汉字。嗯,这个限制很坑,但你必须遵守。

4.4 查询菜单

菜单创建好了,怎么确认它是不是对的?用查询接口。这是个 GET 请求:

GET https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN

返回的数据就是当前菜单的完整 JSON。我一般会在创建菜单后立刻查一次,确认无误再告诉客户「搞定了」。

def get_menu(access_token):
    url = f"https://api.weixin.qq.com/cgi-bin/menu/get?access_token={access_token}"
    response = requests.get(url)
    result = response.json()
    
    if "menu" in result:
        print("当前菜单:")
        print(json.dumps(result["menu"], indent=2, ensure_ascii=False))
    else:
        print(f"查询失败:{result.get('errmsg')}")
    
    return result

你想想看,如果菜单没生效,用户那边看到的还是旧的,那多尴尬。所以每次更新菜单后,我都习惯查一下。

4.4 删除菜单

删除菜单更简单,一个 GET 请求搞定:

GET https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN

调用后,当前所有菜单都会被清空。嗯,这个操作不可逆,要小心。

def delete_menu(access_token):
    url = f"https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={access_token}"
    response = requests.get(url)
    result = response.json()
    
    if result.get("errcode") == 0:
        print("菜单已删除")
    else:
        print(f"删除失败:{result.get('errmsg')}")
    
    return result

特别注意:删除菜单后,公众号底部会变成空白。如果你正在开发测试,建议先别删,或者删完后立刻重新创建。我曾经在演示时不小心点了删除,结果全场尴尬了 30 秒。

4.5 实战建议

最后给你几个我个人的建议:

  1. 先设计,再编码:别急着写代码。先在纸上画一下菜单结构,确认好每个按钮的类型和 key/url。
  2. 测试环境用测试号:微信提供了测试公众号,可以随便折腾。等菜单结构稳定了,再部署到正式号。
  3. 菜单更新有延迟:微信客户端会缓存菜单,最长可能 24 小时才更新。你可以让用户取关再关注,或者等微信自动刷新。
  4. 善用 key 的命名规范:我习惯用 模块_功能 的格式,比如 sign_inhelp_feedback。这样后期维护时一眼就能看懂。

一个小技巧:如果你想让菜单立即生效,可以在微信公众平台后台的「开发-接口调试工具」里手动调用一次「获取菜单」接口。有时候能触发客户端刷新。

好了,自定义菜单的基础就这些。说白了就是三个接口:创建、查询、删除。再加上 click 和 view 两种按钮类型。你只要把 JSON 结构写对,access_token 拿到,剩下的就是调接口的事了。

下一章咱们聊聊菜单事件的处理——用户点了 click 按钮,你的后台怎么接收?怎么回复?嗯,那才是真正有意思的部分。