mp:menu 前端接入菜单的保存、清空操作

This commit is contained in:
YunaiV 2023-01-15 00:54:53 +08:00
parent c0b950257f
commit 2173cf0364
13 changed files with 190 additions and 139 deletions

View File

@ -52,6 +52,7 @@ public interface ErrorCodeConstants {
ErrorCode DRAFT_DELETE_FAIL = new ErrorCode(1006007002, "删除草稿失败,原因:{}");
// ========== 公众号菜单 1006008000============
ErrorCode MENU_NOT_EXISTS = new ErrorCode(1006008000, "菜单不存在");
ErrorCode MENU_SAVE_FAIL = new ErrorCode(1006008000, "创建菜单失败,原因:{}");
ErrorCode MENU_DELETE_FAIL = new ErrorCode(1006008001, "删除菜单失败,原因:{}");
}

View File

@ -6,24 +6,24 @@ tenant-id: {{adminTenentId}}
{
"accountId": "1",
"buttons": [
"menus": [
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
"menuKey":"V1001_TODAY_MUSIC"
},
{
"name":"搜索",
"type":"view",
"url":"http://www.soso.com/"
"url":"https://www.soso.com/"
},
{
"name": "父按钮",
"subButtons": [
"children": [
{
"type":"click",
"name":"归去来兮",
"key":"MUSIC"
"menuKey":"MUSIC"
},
{
"name":"不说",
@ -33,6 +33,17 @@ tenant-id: {{adminTenentId}}
}]
}
### 请求 /mp/menu/save 接口 => 成功(清空)
POST {{baseUrl}}/mp/menu/save
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"accountId": "1",
"menus": []
}
### 请求 /mp/menu/list 接口 => 成功
GET {{baseUrl}}/mp/menu/list?accountId=1
Authorization: Bearer {{token}}

View File

@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -32,28 +31,20 @@ public class MpMenuController {
@PostMapping("/save")
@ApiOperation("保存公众号菜单")
@PreAuthorize("@ss.hasPermission('mp:menu:save')")
public CommonResult<Long> saveMenu(@Valid @RequestBody MpMenuSaveReqVO createReqVO) {
return success(mpMenuService.saveMenu(createReqVO));
public CommonResult<Boolean> saveMenu(@Valid @RequestBody MpMenuSaveReqVO createReqVO) {
mpMenuService.saveMenu(createReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除公众号菜单")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true, example = "10", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:menu:delete')")
public CommonResult<Boolean> deleteMenu(@RequestParam("id") Long id) {
mpMenuService.deleteMenu(id);
public CommonResult<Boolean> deleteMenu(@RequestParam("accountId") Long accountId) {
mpMenuService.deleteMenuByAccountId(accountId);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得公众号菜单")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:menu:query')")
public CommonResult<MpMenuRespVO> getMenu(@RequestParam("id") Long id) {
MpMenuDO menu = mpMenuService.getMenu(id);
return success(MpMenuConvert.INSTANCE.convert(menu));
}
@GetMapping("/list")
@ApiOperation("获得公众号菜单列表")
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true, example = "10", dataTypeClass = Long.class)

View File

@ -1,12 +1,9 @@
package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import me.chanjar.weixin.common.api.WxConsts;
import javax.validation.constraints.NotNull;
import java.util.List;
// TODO 芋艿完善 swagger 注解
@ -17,17 +14,6 @@ import java.util.List;
@Data
public class MpMenuBaseVO {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
/**
* 微信公众号 appid
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
/**
* 菜单名称
*/
@ -42,10 +28,6 @@ public class MpMenuBaseVO {
* 父菜单编号
*/
private Long parentId;
/**
* 排序
*/
private Integer sort;
// ========== 按钮操作 ==========

View File

@ -1,11 +1,16 @@
package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
import lombok.*;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.*;
import io.swagger.annotations.*;
import javax.validation.constraints.NotNull;
import java.util.Date;
// TODO swagger 文档
@ApiModel("管理后台 - 微信菜单 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ -15,6 +20,17 @@ public class MpMenuRespVO extends MpMenuBaseVO {
@ApiModelProperty(value = "主键", required = true)
private Long id;
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
/**
* 微信公众号 appid
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;

View File

@ -1,19 +1,32 @@
package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
import lombok.*;
import io.swagger.annotations.*;
import me.chanjar.weixin.common.bean.menu.WxMenuButton;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@ApiModel("管理后台 - 微信菜单保存 Request VO")
// TODO 芋艿swagger 文档
@ApiModel("管理后台 - 公众号菜单保存 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpMenuSaveReqVO extends MpMenuBaseVO {
public class MpMenuSaveReqVO {
@NotNull(message = "按钮不能为空")
private List<WxMenuButton> buttons;
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@NotEmpty(message = "菜单不能为空")
@Valid
private List<Menu> menus;
@Data
public static class Menu extends MpMenuBaseVO {
private List<Menu> children;
}
}

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import me.chanjar.weixin.common.bean.menu.WxMenuButton;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@ -16,8 +17,6 @@ public interface MpMenuConvert {
MpMenuConvert INSTANCE = Mappers.getMapper(MpMenuConvert.class);
MpMenuDO convert(MpMenuSaveReqVO bean);
MpMenuRespVO convert(MpMenuDO bean);
List<MpMenuRespVO> convertList(List<MpMenuDO> list);
@ -27,11 +26,20 @@ public interface MpMenuConvert {
@Mapping(source = "menu.replyMessageType", target = "type"),
@Mapping(source = "menu.replyContent", target = "content"),
@Mapping(source = "menu.replyMediaId", target = "mediaId"),
@Mapping(source = "menu.replyMediaUrl", target = "mediaUrl"),
@Mapping(source = "menu.replyTitle", target = "title"),
@Mapping(source = "menu.replyDescription", target = "description"),
@Mapping(source = "menu.replyArticles", target = "articles"),
})
MpMessageSendOutReqBO convert(String openid, MpMenuDO menu);
List<WxMenuButton> convert(List<MpMenuSaveReqVO.Menu> list);
@Mappings({
@Mapping(source = "menuKey", target = "key"),
@Mapping(source = "children", target = "subButtons"),
})
WxMenuButton convert(MpMenuSaveReqVO.Menu bean);
MpMenuDO convert02(MpMenuSaveReqVO.Menu menu);
}

View File

@ -64,10 +64,6 @@ public class MpMenuDO extends BaseDO {
* 父菜单编号
*/
private Long parentId;
/**
* 排序
*/
private Integer sort;
// ========== 按钮操作 ==========

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.mp.dal.mysql.menu;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
import org.apache.ibatis.annotations.Mapper;
@ -18,4 +19,7 @@ public interface MpMenuMapper extends BaseMapperX<MpMenuDO> {
return selectList(MpMenuDO::getAccountId, accountId);
}
default void deleteByAccountId(Long accountId) {
delete(new LambdaQueryWrapperX<MpMenuDO>().eq(MpMenuDO::getAccountId, accountId));
}
}

View File

@ -18,24 +18,15 @@ public interface MpMenuService {
* 保存公众号菜单
*
* @param createReqVO 创建信息
* @return 编号
*/
Long saveMenu(@Valid MpMenuSaveReqVO createReqVO);
void saveMenu(@Valid MpMenuSaveReqVO createReqVO);
/**
* 删除公众号菜单
*
* @param id 编号
* @param accountId 公众号账号的编号
*/
void deleteMenu(Long id);
/**
* 获得公众号菜单
*
* @param id 编号
* @return 公众号菜单
*/
MpMenuDO getMenu(Long id);
void deleteMenuByAccountId(Long accountId);
/**
* 用户点击菜单按钮时回复对应的消息
@ -50,7 +41,7 @@ public interface MpMenuService {
/**
* 获得公众号菜单列表
*
* @param accountId 公众号编号
* @param accountId 公众号账号的编号
* @return 公众号菜单列表
*/
List<MpMenuDO> getMenuListByAccountId(Long accountId);

View File

@ -1,11 +1,14 @@
package cn.iocoder.yudao.module.mp.service.menu;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;
import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
import cn.iocoder.yudao.module.mp.dal.mysql.menu.MpMenuMapper;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import cn.iocoder.yudao.module.mp.service.message.MpMessageService;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import lombok.extern.slf4j.Slf4j;
@ -15,13 +18,15 @@ import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_NOT_EXISTS;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_DELETE_FAIL;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MENU_SAVE_FAIL;
/**
* 公众号菜单 Service 实现类
@ -35,6 +40,9 @@ public class MpMenuServiceImpl implements MpMenuService {
@Resource
private MpMessageService mpMessageService;
@Resource
@Lazy // 延迟加载避免循环引用报错
private MpAccountService mpAccountService;
@Resource
@Lazy // 延迟加载避免循环引用报错
@ -44,43 +52,64 @@ public class MpMenuServiceImpl implements MpMenuService {
private MpMenuMapper mpMenuMapper;
@Override
public Long saveMenu(MpMenuSaveReqVO createReqVO) {
String appId = "wx5b23ba7a5589ecbb";
// 插入
MpMenuDO menu = MpMenuConvert.INSTANCE.convert(createReqVO);
// mpMenuMapper.insert(menu);
@Transactional(rollbackFor = Exception.class)
public void saveMenu(MpMenuSaveReqVO createReqVO) {
MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId());
WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId());
// TODO 同步菜单
WxMpService mpService = mpServiceFactory.getRequiredMpService(appId);
// 第一步同步公众号
WxMenu wxMenu = new WxMenu();
wxMenu.setButtons(createReqVO.getButtons());
wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus()));
try {
mpService.getMenuService().menuCreate(wxMenu);
} catch (WxErrorException e) {
throw new RuntimeException(e);
throw exception(MENU_SAVE_FAIL, e.getError().getErrorMsg());
}
// 返回
return menu.getId();
// 第二步存储到数据库
mpMenuMapper.deleteByAccountId(createReqVO.getAccountId());
createReqVO.getMenus().forEach(menu -> {
// 先保存顶级菜单
MpMenuDO menuDO = createMenu(menu, null, account);
// 再保存子菜单
if (CollUtil.isEmpty(menu.getChildren())) {
return;
}
menu.getChildren().forEach(childMenu -> createMenu(childMenu, menuDO, account));
});
}
@Override
public void deleteMenu(Long id) {
// 校验存在
validateMenuExists(id);
// 删除
mpMenuMapper.deleteById(id);
public void deleteMenuByAccountId(Long accountId) {
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
// 第一步同步公众号
try {
mpService.getMenuService().menuDelete();
} catch (WxErrorException e) {
throw exception(MENU_DELETE_FAIL, e.getError().getErrorMsg());
}
private void validateMenuExists(Long id) {
if (mpMenuMapper.selectById(id) == null) {
throw exception(MENU_NOT_EXISTS);
}
// 第二步存储到数据库
mpMenuMapper.deleteByAccountId(accountId);
}
@Override
public MpMenuDO getMenu(Long id) {
return mpMenuMapper.selectById(id);
private MpMenuDO createMenu(MpMenuSaveReqVO.Menu wxMenu, MpMenuDO parentMenu, MpAccountDO account) {
MpMenuDO menu = CollUtil.isNotEmpty(wxMenu.getChildren())
? new MpMenuDO().setName(wxMenu.getName())
: MpMenuConvert.INSTANCE.convert02(wxMenu);
if (account != null) {
menu.setAccountId(account.getId()).setAppId(account.getAppId());
}
if (parentMenu != null) {
menu.setParentId(parentMenu.getId());
} else {
menu.setParentId(MpMenuDO.ID_ROOT);
}
if (StrUtil.isNotEmpty(wxMenu.getReplyMediaId())) {
throw new IllegalArgumentException("未实现");
}
mpMenuMapper.insert(menu);
return menu;
}
@Override

View File

@ -7,3 +7,23 @@ export function getMenuList(accountId) {
method: 'get',
})
}
// 保存公众号菜单
export function saveMenu(accountId, menus) {
return request({
url: '/mp/menu/save',
method: 'post',
data: {
accountId,
menus
}
})
}
// 删除公众号菜单
export function deleteMenu(accountId) {
return request({
url: '/mp/menu/delete?accountId=' + accountId,
method: 'delete',
})
}

View File

@ -63,8 +63,8 @@ SOFTWARE.
<div class="menu_bottom menu_addicon" v-if="this.menuList.length < 3" @click="addMenu"><i class="el-icon-plus"></i></div>
</div>
<div class="save_div">
<!--<el-button class="save_btn" type="warning" size="small" @click="saveFun">保存菜单</el-button>-->
<el-button class="save_btn" type="success" size="small" @click="handleSaveAndReleaseFun">保存并发布菜单</el-button>
<el-button class="save_btn" type="success" size="small" @click="handleSave">保存并发布菜单</el-button>
<el-button class="save_btn" type="danger" size="small" @click="handleDelete">清空菜单</el-button>
</div>
</div>
<!--右边配置-->
@ -141,7 +141,7 @@ SOFTWARE.
import WxReplySelect from '@/views/mp/components/wx-news/main.vue'
import WxNews from '@/views/mp/components/wx-news/main.vue';
import WxMaterialSelect from '@/views/mp/components/wx-news/main.vue'
import { getMenuList } from "@/api/mp/menu";
import {deleteMenu, getMenuList, saveMenu} from "@/api/mp/menu";
import { getSimpleAccounts } from "@/api/mp/account";
export default {
@ -164,11 +164,6 @@ export default {
children: [],
},
menu:{ //
button:[
]
},
// ======================== ========================
isActive: -1,//
isSubMenuActive: -1, //
@ -178,7 +173,7 @@ export default {
showRightFlag: false, //
nameMaxLength: 0, // 1 4 2 7
showConfigureContent: true, //
hackResetWxReplySelect: false, // WxReplySelect
tempObj:{}, //
tempSelfObj: { // tempObjmenu
@ -218,7 +213,6 @@ export default {
label: '选择地理位置'
}],
dialogNewsVisible: false,
hackResetWxReplySelect: false,
//
accounts: [],
@ -246,7 +240,6 @@ export default {
this.loading = false;
getMenuList(this.accountId).then(response => {
this.menuList = this.handleTree(response.data, "id");
console.log(this.menuList)
}).finally(() => {
this.loading = false;
})
@ -354,31 +347,16 @@ export default {
},
// ======================== ========================
handleSaveAndReleaseFun(){
this.$confirm('确定要保证并发布该菜单吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
handleSave() {
this.$modal.confirm('确定要保证并发布该菜单吗?').then(() => {
this.loading = true
saveAndRelease({
strWxMenu:this.menu
}).then(response => {
return saveMenu(this.accountId, this.menuList);
}).then(() => {
this.getList();
this.$modal.msgSuccess("发布成功");
}).catch(() => {}).finally(() => {
this.loading = false
if(response.code == 200){
this.$message({
showClose: true,
message: '发布成功',
type: 'success'
})
}else{
this.$message.error(response.data.msg)
}
}).catch(() => {
this.loading = false
})
}).catch(() => {
})
});
},
// Editor
resetEditor() {
@ -387,6 +365,17 @@ export default {
this.hackResetEditor = true //
})
},
handleDelete() {
this.$modal.confirm('确定要清空所有菜单吗?').then(() => {
this.loading = true
return deleteMenu(this.accountId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("清空成功");
}).catch(() => {}).finally(() => {
this.loading = false
});
},
// TODO
@ -431,7 +420,7 @@ div{
left:0px;
width: 300px;
height:64px;
background: transparent url(assets/menu_head.png) no-repeat 0 0;
background: transparent url("assets/menu_head.png") no-repeat 0 0;
background-position: 0 0;
background-size: 100%
}
@ -445,7 +434,7 @@ div{
left: 0px;
}
.weixin-menu{
background: transparent url(assets/menu_foot.png) no-repeat 0 0;
background: transparent url("assets/menu_foot.png") no-repeat 0 0;
padding-left: 43px;
font-size: 12px
}
@ -581,26 +570,26 @@ div{
<!--</style>-->
<!--素材样式-->
<style lang="scss" scoped>
.pagination{
.pagination {
text-align: right;
margin-right: 25px;
}
.select-item{
.select-item {
width: 280px;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.select-item2{
.select-item2 {
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.ope-row{
.ope-row {
padding-top: 10px;
text-align: center;
}
.item-name{
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow:ellipsis;