• 微信公众号菜单管理接口开发


    最近一直在看计算机视觉方面的论文,尽量抽时间复习下微服务吧。

    1、需求分析

    1.1 微信自定义菜单说明

      微信自定义菜单文档地址:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html

      微信自定义菜单注意事项:

      1. 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
      2. 一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“…”代替。
      3. 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

    1.2 硅谷课堂自定义菜单

      一级菜单:直播、课程、我的

      二级菜单:根据一级菜单动态设置二级菜单,直播(近期直播课程),课程(课程分类),我的(我的订单、我的课程、我的优惠券及关于我们)

      说明:

       1、二级菜单可以是网页类型,点击跳转H5页面

       2、二级菜单可以是消息类型,点击返回消息

    1.3 数据格式

      自定义菜单通过后台管理设置到数据库表,数据配置好后,通过微信接口推送菜单数据到微信平台。

      表结构如下:

    image-20220824211424381

    image-20220302094144684

    1.4 管理页面

      (1)页面功能“列表、添加、修改与删除”是对menu表的操作

      (2)页面功能“同步菜单与删除菜单”是对微信平台接口操作

    image-20220824211509442

    2、搭建菜单管理后端环境

    2.1、创建模块service_wechat

      (1)在service下创建子模块service_wechat

    image-20220302095309078

      (2)引入依赖

        <dependencies>
            <dependency>
                <groupId>com.github.binarywanggroupId>
                <artifactId>weixin-java-mpartifactId>
                <version>4.1.0version>
            dependency>
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.2、生成菜单相关代码

    image-20220824211854572

    2.3、创建启动类和配置文件

      (1)启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients(basePackages = "com.atguigu")
    @MapperScan("com.atguigu.ggkt.wechat.mapper")
    @ComponentScan(basePackages = "com.atguigu")
    public class ServiceWechatApplication {
        public static void main(String[] args) {
            SpringApplication.run(ServiceWechatApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20220824211931125

      (2)配置文件

    # 服务端口
    server.port=8305
    # 服务名
    spring.application.name=service-wechat
    
    # 环境设置:dev、test、prod
    spring.profiles.active=dev
    
    # mysql数据库连接
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/glkt_wechat?characterEncoding=utf-8&useSSL=false
    spring.datasource.username=root
    spring.datasource.password=root
    
    #返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
    
    #mybatis日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    mybatis-plus.mapper-locations=classpath:com/atguigu/ggkt/wechat/mapper/xml/*.xml
    
    # nacos服务地址
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    
    #公众号id和秘钥
    # 硅谷课堂微信公众平台appId
    wechat.mpAppId: 用你的
    # 硅谷课堂微信公众平台api秘钥
    wechat.mpAppSecret: 用你的
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    这里appId和密钥用你自己的,我也是用的别人的测试号,就不泄露了。

    2.4、配置网关

    #service-wechat模块配置
    #设置路由id
    spring.cloud.gateway.routes[4].id=service-wechat
    #设置路由的uri
    spring.cloud.gateway.routes[4].uri=lb://service-wechat
    #设置路由断言,代理servicerId为auth-service的/auth/路径
    spring.cloud.gateway.routes[4].predicates= Path=/*/wechat/**
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3、开发菜单管理接口

    3.1、编写MenuController

    @RestController
    @RequestMapping("/admin/wechat/menu")
    public class MenuController {
    
        @Autowired
        private MenuService menuService;
    
        //公众号菜单删除
        @DeleteMapping("removeMenu")
        public Result removeMenu(){
            menuService.removeMenu();
            return Result.ok(null);
        }
    
        //同步菜单方法
        @GetMapping("syncMenu")
        public Result createMenu(){
            menuService.syncMenu();
            return Result.ok(null);
        }
    
        //获取access_token
        @GetMapping("getAccessToken")
        public Result getAccessToken(){
            //拼接请求地址
            StringBuffer buffer = new StringBuffer();
            buffer.append("https://api.weixin.qq.com/cgi-bin/token");
            buffer.append("?grant_type=client_credential");
            buffer.append("&appid=%s");
            buffer.append("&secret=%s");
            //设置路径中的参数
            String url = String.format(buffer.toString(),
                    ConstantPropertiesUtil.ACCESS_KEY_ID,
                    ConstantPropertiesUtil.ACCESS_KEY_SECRET);
            try {
                //发送http请求
                String tokenString = HttpClientUtils.get(url);
                //获取access_token
                JSONObject jsonObject = JSONObject.parseObject(tokenString);
                String access_token = jsonObject.getString("access_token");
                //返回
                return Result.ok(access_token);
            } catch (Exception e) {
                e.printStackTrace();
                throw new GgktException(20001,"获取access_token失败");
            }
        }
    
    
        //获取所有菜单,按照一级和二级菜单封装
        @GetMapping("findMenuInfo")
        public Result findMenuInfo(){
            List<MenuVo> list=menuService.findMenuInfo();
            return Result.ok(list);
        }
    
        //获取所有一级菜单
        @GetMapping("findOneMenuInfo")
        public Result findOneMenuInfo(){
            List<Menu> list=menuService.findMenuOneInfo();
            return Result.ok(list);
        }
    
        @ApiOperation(value = "获取")
        @GetMapping("get/{id}")
        public Result get(@PathVariable Long id) {
            Menu menu = menuService.getById(id);
            return Result.ok(menu);
        }
    
        @ApiOperation(value = "新增")
        @PostMapping("save")
        public Result save(@RequestBody Menu menu) {
            menuService.save(menu);
            return Result.ok(null);
        }
    
        @ApiOperation(value = "修改")
        @PutMapping("update")
        public Result updateById(@RequestBody Menu menu) {
            menuService.updateById(menu);
            return Result.ok(null);
        }
    
        @ApiOperation(value = "删除")
        @DeleteMapping("remove/{id}")
        public Result remove(@PathVariable Long id) {
            menuService.removeById(id);
            return Result.ok(null);
        }
    
        @ApiOperation(value = "根据id列表删除")
        @DeleteMapping("batchRemove")
        public Result batchRemove(@RequestBody List<Long> idList) {
            menuService.removeByIds(idList);
            return Result.ok(null);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    3.2、编写Service

      (1)MenuService定义方法

    public interface MenuService extends IService<Menu> {
        //获取全部菜单
        List<MenuVo> findMenuInfo();
        //获取一级菜单
        List<Menu> findOneMenuInfo();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      (2)MenuServiceImpl实现方法

    @Slf4j
    @Service
    public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
    
        @Autowired
        private WxMpService wxMpService;
    
        //获取所有菜单,按照一级和二级菜单封装
        @Override
        public List<MenuVo> findMenuInfo() {
            //1、创建List集合,用于最终数据封装
            List<MenuVo> finalMenuList=new ArrayList<>();
    
            //2、查询出所有菜单数据(包含一级和二级)
            List<Menu> menuList = baseMapper.selectList(null);
    
            //3、从所有菜单数据中获取所有一级菜单数据(parent_id=0)
            List<Menu> oneMenuList = menuList.stream()
                    .filter(menu -> menu.getParentId() == 0)
                    .collect(Collectors.toList());
    
            //4、封装一级菜单数据,封装到最终数据list集合
            //遍历一级菜单list集合
            oneMenuList.forEach(oneMenu->{
                //Menu -->MenuVo
                MenuVo oneMenuVo = new MenuVo();
                BeanUtils.copyProperties(oneMenu,oneMenuVo);
    
                //5、封装二级菜单数据(判断一级菜单id和二级菜单的parent_id是否相同)
                //如果相同,把二级菜单数据放到一级菜单里面
                List<Menu> twoMenuList = menuList.stream()
                        .filter(menu -> menu.getParentId().equals(oneMenu.getId()))
                        .collect(Collectors.toList());
                //List->List
                List<MenuVo> children=new ArrayList<>();
                twoMenuList.forEach(twoMenu -> {
                    MenuVo twoMenuVo = new MenuVo();
                    BeanUtils.copyProperties(twoMenu,twoMenuVo);
                    children.add(twoMenuVo);
                });
                //把二级菜单数据放到一级菜单里面
                oneMenuVo.setChildren(children);
                //把oneMenuVo放到最终list集合
                finalMenuList.add(oneMenuVo);
            });
            //返回最终数据
            return finalMenuList;
        }
    
        //获取所有一级菜单
        @Override
        public List<Menu> findMenuOneInfo() {
            QueryWrapper<Menu> wrapper=new QueryWrapper<>();
            wrapper.eq("parent_id",0);
            List<Menu> list = baseMapper.selectList(wrapper);
            return list;
        }
    
        //同步公众号菜单方法
        //https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
        //https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
        @Override
        public void syncMenu() {
    
            //获取所有菜单数据
            List<MenuVo> menuVoList = this.findMenuInfo();
            //封装button里面的结构,数组格式
            JSONArray buttonList=new JSONArray();
            menuVoList.forEach(oneMenuVo -> {
                //json对象  一级菜单
                JSONObject one=new JSONObject();
                one.put("name",oneMenuVo.getName());
                //json数组   二级菜单
                JSONArray subButton=new JSONArray();
                oneMenuVo.getChildren().forEach(twoMenuVo->{
                    JSONObject view = new JSONObject();
                    view.put("type", twoMenuVo.getType());
                    if(twoMenuVo.getType().equals("view")) {
                        view.put("name", twoMenuVo.getName());
                        view.put("url", "http://ggkt2.vipgz1.91tunnel.com/#"
                                +twoMenuVo.getUrl());
                    } else {
                        view.put("name", twoMenuVo.getName());
                        view.put("key", twoMenuVo.getMeunKey());
                    }
                    subButton.add(view);
                });
                one.put("sub_button",subButton);
                buttonList.add(one);
            });
            //封装最外层的button部分
            JSONObject button=new JSONObject();
            button.put("button",buttonList);
    
            try {
                String menuId =
                        this.wxMpService.getMenuService().menuCreate(button.toJSONString());
                log.info("menuId:{}",menuId);
            } catch (WxErrorException e) {
                e.printStackTrace();
                throw new GgktException(20001,"公众号菜单同步失败");
            }
        }
    
        //公众号菜单删除
        @Override
        public void removeMenu() {
            try {
                wxMpService.getMenuService().menuDelete();
            } catch (WxErrorException e) {
                e.printStackTrace();
                throw new GgktException(20001,"公众号菜单删除失败");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    4、同步菜单(获取access_token

    4.1、文档查看

      (1)进行菜单同步时候,需要获取到公众号的access_token,通过access_token进行菜单同步

      接口文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

    image-20220824212351446

      (2)调用方式

    image-20220824212446803

    4.2、service_wechat添加配置

    # 硅谷课堂微信公众平台appId
    wechat.mpAppId: 用你的
    # 硅谷课堂微信公众平台api秘钥
    wechat.mpAppSecret: 用你的
    
    • 1
    • 2
    • 3
    • 4

    4.3、添加工具类

    @Component
    public class ConstantPropertiesUtil implements InitializingBean {
    
        @Value("${wechat.mpAppId}")
        private String appid;
    
        @Value("${wechat.mpAppSecret}")
        private String appsecret;
    
        public static String ACCESS_KEY_ID;
        public static String ACCESS_KEY_SECRET;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            ACCESS_KEY_ID = appid;
            ACCESS_KEY_SECRET = appsecret;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.4、复制HttpClient工具类

    image-20220824212555584

    /**
     * 常量类,读取配置文件application.properties中的配置
     */
    @Component
    public class ConstantPropertiesUtil implements InitializingBean {
    
        @Value("${wechat.mpAppId}")
        private String appid;
    
        @Value("${wechat.mpAppSecret}")
        private String appsecret;
    
        public static String ACCESS_KEY_ID;
        public static String ACCESS_KEY_SECRET;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            ACCESS_KEY_ID = appid;
            ACCESS_KEY_SECRET = appsecret;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4.5、添加Menucontroller方法

    //获取access_token
        @GetMapping("getAccessToken")
        public Result getAccessToken(){
            //拼接请求地址
            StringBuffer buffer = new StringBuffer();
            buffer.append("https://api.weixin.qq.com/cgi-bin/token");
            buffer.append("?grant_type=client_credential");
            buffer.append("&appid=%s");
            buffer.append("&secret=%s");
            //设置路径中的参数
            String url = String.format(buffer.toString(),
                    ConstantPropertiesUtil.ACCESS_KEY_ID,
                    ConstantPropertiesUtil.ACCESS_KEY_SECRET);
            try {
                //发送http请求
                String tokenString = HttpClientUtils.get(url);
                //获取access_token
                JSONObject jsonObject = JSONObject.parseObject(tokenString);
                String access_token = jsonObject.getString("access_token");
                //返回
                return Result.ok(access_token);
            } catch (Exception e) {
                e.printStackTrace();
                throw new GgktException(20001,"获取access_token失败");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    5、同步菜单(功能实现)

      接口文档:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html

      接口调用请求说明

      http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

      weixin-java-mp是封装好了的微信接口客户端,使用起来很方便,后续我们就使用weixin-java-mp处理微信平台接口。

    5.1、添加配置类

    @Configuration
    public class WeChatMpConfig {
    
        @Autowired
        private ConstantPropertiesUtil constantPropertiesUtil;
    
        @Bean
        public WxMpService wxMpService(){
            WxMpService wxMpService = new WxMpServiceImpl();
            wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
            return wxMpService;
        }
        @Bean
        public WxMpConfigStorage wxMpConfigStorage(){
            WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl();
            wxMpConfigStorage.setAppId(ConstantPropertiesUtil.ACCESS_KEY_ID);
            wxMpConfigStorage.setSecret(ConstantPropertiesUtil.ACCESS_KEY_SECRET);
            return wxMpConfigStorage;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    5.2、定义Service方法

      MenuService

    void syncMenu();
    
    • 1

    5.3、实现Service方法

      MenuServiceImpl

    //同步公众号菜单方法
        //https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
        //https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
        @Override
        public void syncMenu() {
    
            //获取所有菜单数据
            List<MenuVo> menuVoList = this.findMenuInfo();
            //封装button里面的结构,数组格式
            JSONArray buttonList=new JSONArray();
            menuVoList.forEach(oneMenuVo -> {
                //json对象  一级菜单
                JSONObject one=new JSONObject();
                one.put("name",oneMenuVo.getName());
                //json数组   二级菜单
                JSONArray subButton=new JSONArray();
                oneMenuVo.getChildren().forEach(twoMenuVo->{
                    JSONObject view = new JSONObject();
                    view.put("type", twoMenuVo.getType());
                    if(twoMenuVo.getType().equals("view")) {
                        view.put("name", twoMenuVo.getName());
                        view.put("url", "http://ggkt2.vipgz1.91tunnel.com/#"
                                +twoMenuVo.getUrl());
                    } else {
                        view.put("name", twoMenuVo.getName());
                        view.put("key", twoMenuVo.getMeunKey());
                    }
                    subButton.add(view);
                });
                one.put("sub_button",subButton);
                buttonList.add(one);
            });
            //封装最外层的button部分
            JSONObject button=new JSONObject();
            button.put("button",buttonList);
    
            try {
                String menuId =
                        this.wxMpService.getMenuService().menuCreate(button.toJSONString());
                log.info("menuId:{}",menuId);
            } catch (WxErrorException e) {
                e.printStackTrace();
                throw new GgktException(20001,"公众号菜单同步失败");
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    5.4、controller方法

    //同步菜单方法
        @GetMapping("syncMenu")
        public Result createMenu(){
            menuService.syncMenu();
            return Result.ok(null);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6、删除菜单

    6.1、service接口

    	 //公众号菜单删除
        void removeMenu();
    
    • 1
    • 2

    6.2、service接口实现

     //公众号菜单删除
        @Override
        public void removeMenu() {
            try {
                wxMpService.getMenuService().menuDelete();
            } catch (WxErrorException e) {
                e.printStackTrace();
                throw new GgktException(20001,"公众号菜单删除失败");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6.3、controller方法

     //公众号菜单删除
        @DeleteMapping("removeMenu")
        public Result removeMenu(){
            menuService.removeMenu();
            return Result.ok(null);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7、开发菜单管理前端

    7.1、添加路由

      (1)src -> router -> index.js添加路由

    {
        path: '/wechat',
        component: Layout,
        redirect: '/wechat/menu/list',
        name: 'Wechat',
        meta: {
          title: '菜单管理',
          icon: 'el-icon-refrigerator'
        },
        alwaysShow: true,
        children: [
          {
            path: 'menu/list',
            name: 'Menu',
            component: () => import('@/views/wechat/menu/list'),
            meta: { title: '菜单列表' }
          }
        ]
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    image-20220824213148521

    7.2、定义接口

      (1)src -> api -> wechat -> menu.js定义接口

    import request from '@/utils/request'
    
    const api_name = '/admin/wechat/menu'
    
    export default {
    
      findMenuInfo() {
        return request({
          url: `${api_name}/findMenuInfo`,
          method: `get`
        })
      },
    
      findOneMenuInfo() {
        return request({
          url: `${api_name}/findOneMenuInfo`,
          method: `get`
        })
      },
    
      save(menu) {
        return request({
          url: `${api_name}/save`,
          method: `post`,
          data: menu
        })
      },
    
      getById(id) {
        return request({
          url: `${api_name}/get/${id}`,
          method: `get`
        })
      },
    
      updateById(menu) {
        return request({
          url: `${api_name}/update`,
          method: `put`,
          data: menu
        })
      },
    
      syncMenu() {
        return request({
          url: `${api_name}/syncMenu`,
          method: `get`
        })
      },
    
      removeById(id) {
        return request({
          url: `${api_name}/remove/${id}`,
          method: 'delete'
        })
      },
    
      removeMenu() {
        return request({
          url: `${api_name}/removeMenu`,
          method: `delete`
        })
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    image-20220824213218572

    7.3、编写页面

      (1)创建views -> wechat -> menu -> list.vue

    <template>
      <div class="app-container">
    
        
        <el-card class="operate-container" shadow="never">
          <i class="el-icon-tickets" style="margin-top: 5px">i>
          <span style="margin-top: 5px">数据列表span>
          <el-button class="btn-add" size="mini" @click="remove" style="margin-left: 10px;">删除菜单el-button>
          <el-button class="btn-add" size="mini" @click="syncMenu">同步菜单el-button>
          <el-button class="btn-add" size="mini" @click="add">添 加el-button>
        el-card>
    
        <el-table
          :data="list"
          style="width: 100%;margin-bottom: 20px;"
          row-key="id"
          border
          default-expand-all
          :tree-props="{children: 'children'}">
    
          <el-table-column label="名称" prop="name" width="350">el-table-column>
          <el-table-column label="类型" width="100">
            <template slot-scope="scope">
              {{ scope.row.type == 'view' ? '链接' : scope.row.type == 'click' ? '事件' : '' }}
            template>
          el-table-column>
          <el-table-column label="菜单URL" prop="url" >el-table-column>
          <el-table-column label="菜单KEY" prop="meunKey"  width="130">el-table-column>
          <el-table-column label="排序号" prop="sort"  width="70">el-table-column>
          <el-table-column label="操作" width="170" align="center">
            <template slot-scope="scope">
              <el-button v-if="scope.row.parentId > 0" type="text" size="mini" @click="edit(scope.row.id)">修改el-button>
              <el-button v-if="scope.row.parentId > 0" type="text" size="mini" @click="removeDataById(scope.row.id)">删除el-button>
            template>
          el-table-column>
        el-table>
    
        <el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" >
          <el-form ref="flashPromotionForm" label-width="150px" size="small" style="padding-right: 40px;">
    
            <el-form-item label="选择一级菜单">
              <el-select
                v-model="menu.parentId"
                placeholder="请选择">
                <el-option
                  v-for="item in list"
                  :key="item.id"
                  :label="item.name"
                  :value="item.id"/>
              el-select>
            el-form-item>
            <el-form-item v-if="menu.parentId == 1" label="菜单名称">
              <el-select
                v-model="menu.name"
                placeholder="请选择"
                @change="liveCourseChanged">
                <el-option
                  v-for="item in liveCourseList"
                  :key="item.id"
                  :label="item.courseName"
                  :value="item"/>
              el-select>
            el-form-item>
            <el-form-item v-if="menu.parentId == 2" label="菜单名称">
              <el-select
                v-model="menu.name"
                placeholder="请选择"
                @change="subjectChanged">
                <el-option
                  v-for="item in subjectList"
                  :key="item.id"
                  :label="item.title"
                  :value="item"/>
              el-select>
            el-form-item>
            <el-form-item v-if="menu.parentId == 3" label="菜单名称">
              <el-input v-model="menu.name"/>
            el-form-item>
            <el-form-item label="菜单类型">
              <el-radio-group v-model="menu.type">
                <el-radio label="view">链接el-radio>
                <el-radio label="click">事件el-radio>
              el-radio-group>
            el-form-item>
            <el-form-item v-if="menu.type == 'view'" label="链接">
              <el-input v-model="menu.url"/>
            el-form-item>
            <el-form-item v-if="menu.type == 'click'" label="菜单KEY">
              <el-input v-model="menu.meunKey"/>
            el-form-item>
            <el-form-item label="排序">
              <el-input v-model="menu.sort"/>
            el-form-item>
          el-form>
          <span slot="footer" class="dialog-footer">
            <el-button @click="dialogVisible = false" size="small">取 消el-button>
            <el-button type="primary" @click="saveOrUpdate()" size="small">确 定el-button>
          span>
        el-dialog>
      div>
    template>
    <script>
    import menuApi from '@/api/wechat/menu'
    //import liveCourseApi from '@/api/live/liveCourse'
    import subjectApi from '@/api/vod/subject'
    const defaultForm = {
      id: null,
      parentId: 1,
      name: '',
      nameId: null,
      sort: 1,
      type: 'view',
      meunKey: '',
      url: ''
    }
    export default {
    
      // 定义数据
      data() {
        return {
          list: [],
    
          liveCourseList: [],
          subjectList: [],
    
          dialogVisible: false,
          menu: defaultForm,
          saveBtnDisabled: false
        }
      },
    
      // 当页面加载时获取数据
      created() {
        this.fetchData()
       // this.fetchLiveCourse()
        this.fetchSubject()
      },
    
      methods: {
        // 调用api层获取数据库中的数据
        fetchData() {
          console.log('加载列表')
          menuApi.findMenuInfo().then(response => {
            this.list = response.data
            console.log(this.list)
          })
        },
    
        // fetchLiveCourse() {
        //   liveCourseApi.findLatelyList().then(response => {
        //     this.liveCourseList = response.data
        //     this.liveCourseList.push({'id': 0, 'courseName': '全部列表'})
        //   })
        // },
    
        fetchSubject() {
          console.log('加载列表')
          subjectApi.getChildList(0).then(response => {
            this.subjectList = response.data
          })
        },
    
        syncMenu() {
          this.$confirm('你确定上传菜单吗, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            return menuApi.syncMenu();
          }).then((response) => {
            this.fetchData()
            this.$message.success(response.message)
          }).catch(error => {
            console.log('error', error)
            // 当取消时会进入catch语句:error = 'cancel'
            // 当后端服务抛出异常时:error = 'error'
            if (error === 'cancel') {
              this.$message.info('取消上传')
            }
          })
        },
    
        // 根据id删除数据
        removeDataById(id) {
          // debugger
          this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => { // promise
            // 点击确定,远程调用ajax
            return menuApi.removeById(id)
          }).then((response) => {
            this.fetchData(this.page)
            if (response.code) {
              this.$message({
                type: 'success',
                message: '删除成功!'
              })
            }
          }).catch(() => {
            this.$message({
              type: 'info',
              message: '已取消删除'
            })
          })
        },
    
        // -------------
        add(){
          this.dialogVisible = true
          this.menu = Object.assign({}, defaultForm)
        },
    
        edit(id) {
          this.dialogVisible = true
          this.fetchDataById(id)
        },
    
        fetchDataById(id) {
          menuApi.getById(id).then(response => {
            this.menu = response.data
          })
        },
    
        saveOrUpdate() {
          this.saveBtnDisabled = true // 防止表单重复提交
    
          if (!this.menu.id) {
            this.saveData()
          } else {
            this.updateData()
          }
        },
    
        // 新增
        saveData() {
          menuApi.save(this.menu).then(response => {
            if (response.code) {
              this.$message({
                type: 'success',
                message: response.message
              })
              this.dialogVisible = false;
              this.fetchData(this.page)
            }
          })
        },
    
        // 根据id更新记录
        updateData() {
          menuApi.updateById(this.menu).then(response => {
            if (response.code) {
              this.$message({
                type: 'success',
                message: response.message
              })
              this.dialogVisible = false;
              this.fetchData(this.page)
            }
          })
        },
    
        // 根据id查询记录
        fetchDataById(id) {
          menuApi.getById(id).then(response => {
            this.menu = response.data
          })
        },
    
        subjectChanged(item) {
          console.info(item)
          this.menu.name = item.title
          this.menu.url = '/course/' + item.id
        },
    
        liveCourseChanged(item) {
          console.info(item)
          this.menu.name = item.courseName
          if(item.id == 0) {
            this.menu.url = '/live'
          } else {
            this.menu.url = '/liveInfo/' + item.id
          }
    
        }
      }
    }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289

    image-20220824213316872

    8、公众号菜单功能测试

      后端微服务启动

    image-20220824213348696

      nacos查看服务是否注册成功。

    image-20220824213417589

      前端项目启动

      登录之后,点击公众号菜单管理->菜单列表

    image-20220824213524447

      点击同步菜单

    image-20220824213545449

      然后进入测试号中看是否同步成功。

    image-20220824213940785

    image-20220824213953087

    image-20220824214004087

    菜单同步到这里就基本做完了。

  • 相关阅读:
    Spring5之IOC容器的底层原理简单理解
    智能低压配电房解决方案
    全国大学生电子设计竞赛参赛分享
    QRunnable与外界互传对象
    Python:对程序做性能分析及计时统计
    继电器介绍及接线说明
    青出于“蓝”的合资第一新能源,“换壳”背后有门道
    山海鲸报表系统:数据洞察的利器
    xf86-video-intel源码分析6 —— intel_device.c和intel_driver.h(1)
    spring-retry使用介绍
  • 原文地址:https://blog.csdn.net/qq_43753724/article/details/126513342