mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-22 07:11:52 +08:00
mp:完善【菜单】的回复功能
This commit is contained in:
parent
141e4e4c8b
commit
0499226c3d
@ -1,12 +1,18 @@
|
|||||||
package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
|
package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import me.chanjar.weixin.common.api.WxConsts;
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
import org.hibernate.validator.constraints.URL;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
// TODO 芋艿:完善 swagger 注解
|
import static cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信菜单 Base VO,提供给添加、修改、详细的子 VO 使用
|
* 微信菜单 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||||
@ -38,70 +44,51 @@ public class MpMenuBaseVO {
|
|||||||
*/
|
*/
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
/**
|
@ApiModelProperty(value = "网页链接", example = "https://www.iocoder.cn/")
|
||||||
* 网页链接
|
@NotEmpty(message = "网页链接不能为空", groups = {ViewButtonGroup.class, MiniProgramButtonGroup.class})
|
||||||
*
|
@URL(message = "网页链接必须是 URL 格式")
|
||||||
* 用户点击菜单可打开链接,不超过 1024 字节
|
|
||||||
*
|
|
||||||
* 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM
|
|
||||||
*/
|
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
/**
|
@ApiModelProperty(value = "小程序的 appId", example = "wx1234567890")
|
||||||
* 小程序的 appId
|
@NotEmpty(message = "小程序的 appId 不能为空", groups = MiniProgramButtonGroup.class)
|
||||||
*
|
|
||||||
* 类型为 {@link WxConsts.MenuButtonType} 的 MINIPROGRAM
|
|
||||||
*/
|
|
||||||
private String miniProgramAppId;
|
private String miniProgramAppId;
|
||||||
/**
|
|
||||||
* 小程序的页面路径
|
@ApiModelProperty(value = "小程序的页面路径", example = "pages/index/index")
|
||||||
*
|
@NotEmpty(message = "小程序的页面路径不能为空", groups = MiniProgramButtonGroup.class)
|
||||||
* 类型为 {@link WxConsts.MenuButtonType} 的 MINIPROGRAM
|
|
||||||
*/
|
|
||||||
private String miniProgramPagePath;
|
private String miniProgramPagePath;
|
||||||
|
|
||||||
// ========== 消息内容 ==========
|
// ========== 消息内容 ==========
|
||||||
|
|
||||||
/**
|
@ApiModelProperty(value = "消息类型", example = "text",
|
||||||
* 消息类型
|
notes = "枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC")
|
||||||
*
|
@NotEmpty(message = "消息类型不能为空", groups = {ClickButtonGroup.class, ScanCodeWaitMsgButtonGroup.class})
|
||||||
* 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG
|
|
||||||
*
|
|
||||||
* 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS
|
|
||||||
*/
|
|
||||||
private String replyMessageType;
|
private String replyMessageType;
|
||||||
|
|
||||||
/**
|
@ApiModelProperty(value = "回复的消息内容", example = "欢迎关注")
|
||||||
* 回复的消息内容
|
@NotEmpty(message = "回复的消息内容不能为空", groups = {TextMessageGroup.class})
|
||||||
*
|
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
|
|
||||||
*/
|
|
||||||
private String replyContent;
|
private String replyContent;
|
||||||
|
|
||||||
/**
|
@ApiModelProperty(value = "回复的媒体 id", example = "123456")
|
||||||
* 回复的媒体 id
|
@NotEmpty(message = "回复的消息 mediaId 不能为空",
|
||||||
*
|
groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
|
|
||||||
*/
|
|
||||||
private String replyMediaId;
|
private String replyMediaId;
|
||||||
/**
|
@ApiModelProperty(value = "回复的媒体 URL", example = "https://www.iocoder.cn/xxx.jpg")
|
||||||
* 回复的媒体 URL
|
@NotEmpty(message = "回复的消息 mediaId 不能为空",
|
||||||
*
|
groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
|
|
||||||
*/
|
|
||||||
private String replyMediaUrl;
|
private String replyMediaUrl;
|
||||||
|
|
||||||
/**
|
@ApiModelProperty(value = "缩略图的媒体 id", example = "123456")
|
||||||
* 回复的标题
|
@NotEmpty(message = "回复的消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class})
|
||||||
*
|
private String replyThumbMediaId;
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
|
@ApiModelProperty(value = "缩略图的媒体 URL",example = "https://www.iocoder.cn/xxx.jpg")
|
||||||
*/
|
@NotEmpty(message = "回复的消息 thumbMedia 地址不能为空", groups = {MusicMessageGroup.class})
|
||||||
|
private String replyThumbMediaUrl;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "回复的标题", example = "视频标题")
|
||||||
|
@NotEmpty(message = "回复的消息标题不能为空", groups = VideoMessageGroup.class)
|
||||||
private String replyTitle;
|
private String replyTitle;
|
||||||
/**
|
@ApiModelProperty(value = "回复的描述", example = "视频描述")
|
||||||
* 回复的描述
|
@NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class)
|
||||||
*
|
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
|
|
||||||
*/
|
|
||||||
private String replyDescription;
|
private String replyDescription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,6 +96,17 @@ public class MpMenuBaseVO {
|
|||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
||||||
*/
|
*/
|
||||||
|
@NotNull(message = "回复的图文消息不能为空", groups = NewsMessageGroup.class)
|
||||||
|
@Valid
|
||||||
private List<MpMessageDO.Article> replyArticles;
|
private List<MpMessageDO.Article> replyArticles;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/xxx.mp3")
|
||||||
|
@NotEmpty(message = "回复的音乐链接不能为空", groups = MusicMessageGroup.class)
|
||||||
|
@URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class)
|
||||||
|
private String replyMusicUrl;
|
||||||
|
@ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3")
|
||||||
|
@NotEmpty(message = "回复的高质量音乐链接不能为空", groups = MusicMessageGroup.class)
|
||||||
|
@URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class)
|
||||||
|
private String replyHqMusicUrl;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,28 +26,28 @@ public class MpMessageSendReqVO {
|
|||||||
public String type;
|
public String type;
|
||||||
|
|
||||||
@ApiModelProperty(value = "消息内容", required = true, example = "你好呀")
|
@ApiModelProperty(value = "消息内容", required = true, example = "你好呀")
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = TextGroup.class)
|
@NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class)
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@ApiModelProperty(value = "媒体 ID", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
|
@ApiModelProperty(value = "媒体 ID", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
|
@NotEmpty(message = "消息内容不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
|
||||||
private String mediaId;
|
private String mediaId;
|
||||||
|
|
||||||
@ApiModelProperty(value = "标题", required = true, example = "没有标题")
|
@ApiModelProperty(value = "标题", required = true, example = "没有标题")
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
|
@NotEmpty(message = "消息内容不能为空", groups = VideoMessageGroup.class)
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
@ApiModelProperty(value = "描述", required = true, example = "你猜")
|
@ApiModelProperty(value = "描述", required = true, example = "你猜")
|
||||||
@NotEmpty(message = "消息描述不能为空", groups = VideoGroup.class)
|
@NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class)
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@ApiModelProperty(value = "缩略图的媒体 id", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
|
@ApiModelProperty(value = "缩略图的媒体 id", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
|
||||||
@NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicGroup.class)
|
@NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicMessageGroup.class)
|
||||||
private String thumbMediaId;
|
private String thumbMediaId;
|
||||||
|
|
||||||
@ApiModelProperty(value = "图文消息", required = true)
|
@ApiModelProperty(value = "图文消息", required = true)
|
||||||
@Valid
|
@Valid
|
||||||
@NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
|
@NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class)
|
||||||
private List<MpMessageDO.Article> articles;
|
private List<MpMessageDO.Article> articles;
|
||||||
|
|
||||||
@ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时")
|
@ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时")
|
||||||
|
@ -26,9 +26,12 @@ public interface MpMenuConvert {
|
|||||||
@Mapping(source = "menu.replyMessageType", target = "type"),
|
@Mapping(source = "menu.replyMessageType", target = "type"),
|
||||||
@Mapping(source = "menu.replyContent", target = "content"),
|
@Mapping(source = "menu.replyContent", target = "content"),
|
||||||
@Mapping(source = "menu.replyMediaId", target = "mediaId"),
|
@Mapping(source = "menu.replyMediaId", target = "mediaId"),
|
||||||
|
@Mapping(source = "menu.replyThumbMediaId", target = "thumbMediaId"),
|
||||||
@Mapping(source = "menu.replyTitle", target = "title"),
|
@Mapping(source = "menu.replyTitle", target = "title"),
|
||||||
@Mapping(source = "menu.replyDescription", target = "description"),
|
@Mapping(source = "menu.replyDescription", target = "description"),
|
||||||
@Mapping(source = "menu.replyArticles", target = "articles"),
|
@Mapping(source = "menu.replyArticles", target = "articles"),
|
||||||
|
@Mapping(source = "menu.replyMusicUrl", target = "musicUrl"),
|
||||||
|
@Mapping(source = "menu.replyHqMusicUrl", target = "hqMusicUrl"),
|
||||||
})
|
})
|
||||||
MpMessageSendOutReqBO convert(String openid, MpMenuDO menu);
|
MpMessageSendOutReqBO convert(String openid, MpMenuDO menu);
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ public interface MpAutoReplyConvert {
|
|||||||
@Mapping(source = "reply.responseMessageType", target = "type"),
|
@Mapping(source = "reply.responseMessageType", target = "type"),
|
||||||
@Mapping(source = "reply.responseContent", target = "content"),
|
@Mapping(source = "reply.responseContent", target = "content"),
|
||||||
@Mapping(source = "reply.responseMediaId", target = "mediaId"),
|
@Mapping(source = "reply.responseMediaId", target = "mediaId"),
|
||||||
@Mapping(source = "reply.responseMediaUrl", target = "mediaUrl"),
|
|
||||||
@Mapping(source = "reply.responseTitle", target = "title"),
|
@Mapping(source = "reply.responseTitle", target = "title"),
|
||||||
@Mapping(source = "reply.responseDescription", target = "description"),
|
@Mapping(source = "reply.responseDescription", target = "description"),
|
||||||
@Mapping(source = "reply.responseArticles", target = "articles"),
|
@Mapping(source = "reply.responseArticles", target = "articles"),
|
||||||
|
@ -57,10 +57,14 @@ public interface MpMessageConvert {
|
|||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.IMAGE: // 2. 图片
|
case WxConsts.XmlMsgType.IMAGE: // 2. 图片
|
||||||
case WxConsts.XmlMsgType.VOICE: // 3. 语音
|
case WxConsts.XmlMsgType.VOICE: // 3. 语音
|
||||||
message.setMediaId(sendReqBO.getMediaId()).setMediaUrl(sendReqBO.getMediaUrl());
|
message.setMediaId(sendReqBO.getMediaId())
|
||||||
|
// .setMediaUrl(sendReqBO.getMediaUrl()) TODO 芋艿:去 url
|
||||||
|
;
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.VIDEO: // 4. 视频
|
case WxConsts.XmlMsgType.VIDEO: // 4. 视频
|
||||||
message.setMediaId(sendReqBO.getMediaId()).setMediaUrl(sendReqBO.getMediaUrl())
|
message.setMediaId(sendReqBO.getMediaId())
|
||||||
|
// .setMediaUrl(sendReqBO.getMediaUrl()) TODO 芋艿:去 url
|
||||||
|
|
||||||
.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription());
|
.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription());
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.NEWS: // 5. 图文
|
case WxConsts.XmlMsgType.NEWS: // 5. 图文
|
||||||
@ -69,7 +73,7 @@ public interface MpMessageConvert {
|
|||||||
message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription())
|
message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription())
|
||||||
.setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl())
|
.setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl())
|
||||||
.setThumbMediaId(sendReqBO.getThumbMediaId());
|
.setThumbMediaId(sendReqBO.getThumbMediaId());
|
||||||
// .setThumbMediaUrl(sendReqBO.getThumbMediaUrl()); TODO 芋艿:url 待确定
|
// .setThumbMediaUrl(sendReqBO.getThumbMediaUrl()); TODO 芋艿:去 url
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
|
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
|
||||||
|
@ -103,7 +103,7 @@ public class MpMenuDO extends BaseDO {
|
|||||||
*
|
*
|
||||||
* 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG
|
* 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG
|
||||||
*
|
*
|
||||||
* 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS
|
* 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC
|
||||||
*/
|
*/
|
||||||
private String replyMessageType;
|
private String replyMessageType;
|
||||||
|
|
||||||
@ -140,6 +140,19 @@ public class MpMenuDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private String replyDescription;
|
private String replyDescription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
|
||||||
|
*
|
||||||
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
|
||||||
|
*/
|
||||||
|
private String replyThumbMediaId;
|
||||||
|
/**
|
||||||
|
* 缩略图的媒体 URL
|
||||||
|
*
|
||||||
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
|
||||||
|
*/
|
||||||
|
private String replyThumbMediaUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回复的图文消息数组
|
* 回复的图文消息数组
|
||||||
*
|
*
|
||||||
@ -148,4 +161,19 @@ public class MpMenuDO extends BaseDO {
|
|||||||
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
|
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
|
||||||
private List<MpMessageDO.Article> replyArticles;
|
private List<MpMessageDO.Article> replyArticles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复的音乐链接
|
||||||
|
*
|
||||||
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
||||||
|
*/
|
||||||
|
private String replyMusicUrl;
|
||||||
|
/**
|
||||||
|
* 回复的高质量音乐链接
|
||||||
|
*
|
||||||
|
* WIFI 环境优先使用该链接播放音乐
|
||||||
|
*
|
||||||
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
||||||
|
*/
|
||||||
|
private String replyHqMusicUrl;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -124,13 +124,13 @@ public class MpMessageDO extends BaseDO {
|
|||||||
/**
|
/**
|
||||||
* 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
|
* 缩略图的媒体 id,通过素材管理中的接口上传多媒体文件,得到的 id
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
|
||||||
*/
|
*/
|
||||||
private String thumbMediaId;
|
private String thumbMediaId;
|
||||||
/**
|
/**
|
||||||
* 缩略图的媒体 URL
|
* 缩略图的媒体 URL
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO
|
||||||
*/
|
*/
|
||||||
private String thumbMediaUrl;
|
private String thumbMediaUrl;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.mp.framework.mp.core.util;
|
package cn.iocoder.yudao.module.mp.framework.mp.core.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.chanjar.weixin.common.api.WxConsts;
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
@ -14,36 +15,6 @@ import javax.validation.Validator;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class MpUtils {
|
public class MpUtils {
|
||||||
|
|
||||||
/**
|
|
||||||
* Text 类型的消息,参数校验 Group
|
|
||||||
*/
|
|
||||||
public interface TextGroup {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image 类型的消息,参数校验 Group
|
|
||||||
*/
|
|
||||||
public interface ImageGroup {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Voice 类型的消息,参数校验 Group
|
|
||||||
*/
|
|
||||||
public interface VoiceGroup {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Video 类型的消息,参数校验 Group
|
|
||||||
*/
|
|
||||||
public interface VideoGroup {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* News 类型的消息,参数校验 Group
|
|
||||||
*/
|
|
||||||
public interface NewsGroup {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Music 类型的消息,参数校验 Group
|
|
||||||
*/
|
|
||||||
public interface MusicGroup {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验消息的格式是否符合要求
|
* 校验消息的格式是否符合要求
|
||||||
*
|
*
|
||||||
@ -55,22 +26,22 @@ public class MpUtils {
|
|||||||
Class<?> group;
|
Class<?> group;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WxConsts.XmlMsgType.TEXT:
|
case WxConsts.XmlMsgType.TEXT:
|
||||||
group = TextGroup.class;
|
group = TextMessageGroup.class;
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.IMAGE:
|
case WxConsts.XmlMsgType.IMAGE:
|
||||||
group = ImageGroup.class;
|
group = ImageMessageGroup.class;
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.VOICE:
|
case WxConsts.XmlMsgType.VOICE:
|
||||||
group = VoiceGroup.class;
|
group = VoiceMessageGroup.class;
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.VIDEO:
|
case WxConsts.XmlMsgType.VIDEO:
|
||||||
group = VideoGroup.class;
|
group = VideoMessageGroup.class;
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.NEWS:
|
case WxConsts.XmlMsgType.NEWS:
|
||||||
group = NewsGroup.class;
|
group = NewsMessageGroup.class;
|
||||||
break;
|
break;
|
||||||
case WxConsts.XmlMsgType.MUSIC:
|
case WxConsts.XmlMsgType.MUSIC:
|
||||||
group = MusicGroup.class;
|
group = MusicMessageGroup.class;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("[validateMessage][未知的消息类型({})]", message);
|
log.error("[validateMessage][未知的消息类型({})]", message);
|
||||||
@ -80,6 +51,35 @@ public class MpUtils {
|
|||||||
ValidationUtils.validate(validator, message, group);
|
ValidationUtils.validate(validator, message, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateButton(Validator validator, String type, String messageType, Object button) {
|
||||||
|
if (StrUtil.isBlank(type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获得对应的校验 group
|
||||||
|
Class<?> group;
|
||||||
|
switch (type) {
|
||||||
|
case WxConsts.MenuButtonType.CLICK:
|
||||||
|
group = ClickButtonGroup.class;
|
||||||
|
validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式
|
||||||
|
break;
|
||||||
|
case WxConsts.MenuButtonType.VIEW:
|
||||||
|
group = ViewButtonGroup.class;
|
||||||
|
break;
|
||||||
|
case WxConsts.MenuButtonType.MINIPROGRAM:
|
||||||
|
group = MiniProgramButtonGroup.class;
|
||||||
|
break;
|
||||||
|
case WxConsts.MenuButtonType.SCANCODE_WAITMSG:
|
||||||
|
group = ScanCodeWaitMsgButtonGroup.class;
|
||||||
|
validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.error("[validateButton][未知的按钮({})]", button);
|
||||||
|
throw new IllegalArgumentException("不支持的按钮类型:" + type);
|
||||||
|
}
|
||||||
|
// 执行校验
|
||||||
|
ValidationUtils.validate(validator, button, group);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据消息类型,获得对应的媒体文件类型
|
* 根据消息类型,获得对应的媒体文件类型
|
||||||
*
|
*
|
||||||
@ -101,4 +101,53 @@ public class MpUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text 类型的消息,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface TextMessageGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image 类型的消息,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface ImageMessageGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Voice 类型的消息,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface VoiceMessageGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Video 类型的消息,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface VideoMessageGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* News 类型的消息,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface NewsMessageGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Music 类型的消息,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface MusicMessageGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click 类型的按钮,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface ClickButtonGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View 类型的按钮,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface ViewButtonGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MiniProgram 类型的按钮,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface MiniProgramButtonGroup {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SCANCODE_WAITMSG 类型的按钮,参数校验 Group
|
||||||
|
*/
|
||||||
|
public interface ScanCodeWaitMsgButtonGroup {}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ 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.dataobject.menu.MpMenuDO;
|
||||||
import cn.iocoder.yudao.module.mp.dal.mysql.menu.MpMenuMapper;
|
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.framework.mp.core.MpServiceFactory;
|
||||||
|
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
|
||||||
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
|
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.MpMessageService;
|
||||||
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
|
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
|
||||||
@ -22,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import javax.validation.Validator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
@ -48,6 +50,9 @@ public class MpMenuServiceImpl implements MpMenuService {
|
|||||||
@Lazy // 延迟加载,避免循环引用报错
|
@Lazy // 延迟加载,避免循环引用报错
|
||||||
private MpServiceFactory mpServiceFactory;
|
private MpServiceFactory mpServiceFactory;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private Validator validator;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private MpMenuMapper mpMenuMapper;
|
private MpMenuMapper mpMenuMapper;
|
||||||
|
|
||||||
@ -57,6 +62,9 @@ public class MpMenuServiceImpl implements MpMenuService {
|
|||||||
MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId());
|
MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId());
|
||||||
WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId());
|
WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId());
|
||||||
|
|
||||||
|
// 参数校验
|
||||||
|
createReqVO.getMenus().forEach(this::validateMenu);
|
||||||
|
|
||||||
// 第一步,同步公众号
|
// 第一步,同步公众号
|
||||||
WxMenu wxMenu = new WxMenu();
|
WxMenu wxMenu = new WxMenu();
|
||||||
wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus()));
|
wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus()));
|
||||||
@ -79,6 +87,49 @@ public class MpMenuServiceImpl implements MpMenuService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验菜单的格式是否正确
|
||||||
|
*
|
||||||
|
* @param menu 菜单
|
||||||
|
*/
|
||||||
|
private void validateMenu(MpMenuSaveReqVO.Menu menu) {
|
||||||
|
MpUtils.validateButton(validator, menu.getType(), menu.getReplyMessageType(), menu);
|
||||||
|
// 子菜单
|
||||||
|
if (CollUtil.isEmpty(menu.getChildren())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
menu.getChildren().forEach(this::validateMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建菜单,并存储到数据库
|
||||||
|
*
|
||||||
|
* @param wxMenu 菜单信息
|
||||||
|
* @param parentMenu 父菜单
|
||||||
|
* @param account 公众号账号
|
||||||
|
* @return 创建后的菜单
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入到数据库
|
||||||
|
mpMenuMapper.insert(menu);
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteMenuByAccountId(Long accountId) {
|
public void deleteMenuByAccountId(Long accountId) {
|
||||||
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
|
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
|
||||||
@ -93,25 +144,6 @@ public class MpMenuServiceImpl implements MpMenuService {
|
|||||||
mpMenuMapper.deleteByAccountId(accountId);
|
mpMenuMapper.deleteByAccountId(accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public WxMpXmlOutMessage reply(String appId, String key, String openid) {
|
public WxMpXmlOutMessage reply(String appId, String key, String openid) {
|
||||||
// 第一步,获得菜单
|
// 第一步,获得菜单
|
||||||
|
@ -76,8 +76,8 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser());
|
Assert.notNull(user, "公众号粉丝({}/{}) 不存在", appId, wxMessage.getFromUser());
|
||||||
|
|
||||||
// 记录消息
|
// 记录消息
|
||||||
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user)
|
||||||
message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
|
.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
|
||||||
downloadMessageMedia(message);
|
downloadMessageMedia(message);
|
||||||
mpMessageMapper.insert(message);
|
mpMessageMapper.insert(message);
|
||||||
}
|
}
|
||||||
@ -94,9 +94,9 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
Assert.notNull(user, "公众号粉丝({}/{}) 不存在", sendReqBO.getAppId(), sendReqBO.getOpenid());
|
Assert.notNull(user, "公众号粉丝({}/{}) 不存在", sendReqBO.getAppId(), sendReqBO.getOpenid());
|
||||||
|
|
||||||
// 记录消息
|
// 记录消息
|
||||||
MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user);
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user).
|
||||||
message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
|
setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
|
||||||
// TODO 芋艿:downloadMessageMedia
|
downloadMessageMedia(message);
|
||||||
mpMessageMapper.insert(message);
|
mpMessageMapper.insert(message);
|
||||||
|
|
||||||
// 转换返回 WxMpXmlOutMessage 对象
|
// 转换返回 WxMpXmlOutMessage 对象
|
||||||
@ -122,8 +122,8 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录消息
|
// 记录消息
|
||||||
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user)
|
||||||
message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
|
.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
|
||||||
downloadMessageMedia(message);
|
downloadMessageMedia(message);
|
||||||
mpMessageMapper.insert(message);
|
mpMessageMapper.insert(message);
|
||||||
return message;
|
return message;
|
||||||
|
@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
|
|||||||
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
|
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import me.chanjar.weixin.common.api.WxConsts;
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
import org.hibernate.validator.constraints.URL;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
@ -45,7 +46,7 @@ public class MpMessageSendOutReqBO {
|
|||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = TextGroup.class)
|
@NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class)
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,39 +54,38 @@ public class MpMessageSendOutReqBO {
|
|||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
|
@NotEmpty(message = "消息 mediaId 不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
|
||||||
private String mediaId;
|
private String mediaId;
|
||||||
// TODO 芋艿:考虑去掉
|
// // TODO 芋艿:考虑去掉
|
||||||
/**
|
// /**
|
||||||
* 媒体 URL
|
// * 媒体 URL
|
||||||
*
|
// *
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
|
// * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO
|
||||||
*/
|
// */
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = {ImageGroup.class, VoiceGroup.class, VideoGroup.class})
|
// @NotEmpty(message = "消息内容不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
|
||||||
private String mediaUrl;
|
// private String mediaUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缩略图的媒体 id
|
* 缩略图的媒体 id
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = {MusicGroup.class})
|
@NotEmpty(message = "消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class})
|
||||||
private String thumbMediaId;
|
private String thumbMediaId;
|
||||||
// TODO 芋艿:考虑去掉
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标题
|
* 标题
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
|
@NotEmpty(message = "消息标题不能为空", groups = VideoMessageGroup.class)
|
||||||
private String title;
|
private String title;
|
||||||
/**
|
/**
|
||||||
* 描述
|
* 描述
|
||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "消息内容不能为空", groups = VideoGroup.class)
|
@NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class)
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,7 +94,7 @@ public class MpMessageSendOutReqBO {
|
|||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS
|
||||||
*/
|
*/
|
||||||
@Valid
|
@Valid
|
||||||
@NotNull(message = "图文消息不能为空", groups = NewsGroup.class)
|
@NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class)
|
||||||
private List<MpMessageDO.Article> articles;
|
private List<MpMessageDO.Article> articles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,6 +102,8 @@ public class MpMessageSendOutReqBO {
|
|||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
||||||
*/
|
*/
|
||||||
|
@NotEmpty(message = "音乐链接不能为空", groups = MusicMessageGroup.class)
|
||||||
|
@URL(message = "高质量音乐链接格式不正确", groups = MusicMessageGroup.class)
|
||||||
private String musicUrl;
|
private String musicUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,6 +111,8 @@ public class MpMessageSendOutReqBO {
|
|||||||
*
|
*
|
||||||
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
* 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC
|
||||||
*/
|
*/
|
||||||
|
@NotEmpty(message = "高质量音乐链接不能为空", groups = MusicMessageGroup.class)
|
||||||
|
@URL(message = "高质量音乐链接格式不正确", groups = MusicMessageGroup.class)
|
||||||
private String hqMusicUrl;
|
private String hqMusicUrl;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,9 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
芋道源码:
|
||||||
|
① less 切到 scss,减少对 less 和 less-loader 的依赖
|
||||||
|
②
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
@ -75,60 +78,64 @@ SOFTWARE.
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>菜单名称:</span>
|
<span>菜单名称:</span>
|
||||||
<el-input class="input_width" v-model="tempObj.name" placeholder="请输入菜单名称" :maxlength="nameMaxLength"
|
<el-input class="input_width" v-model="tempObj.name" placeholder="请输入菜单名称" :maxlength="nameMaxLength" clearable />
|
||||||
clearable />
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showConfigureContent">
|
<div v-if="showConfigureContent">
|
||||||
<div class="menu_content">
|
<div class="menu_content">
|
||||||
<span>菜单内容:</span>
|
<span>菜单标识:</span>
|
||||||
<el-select v-model="tempObj.type" clearable placeholder="请选择" class="menu_option">
|
<el-input class="input_width" v-model="tempObj.menuKey" placeholder="请输入菜单 KEY" clearable />
|
||||||
<el-option v-for="item in menuOptions" :label="item.label" :value="item.value" :key="item.value" />
|
</div>
|
||||||
</el-select>
|
<div class="menu_content">
|
||||||
|
<span>菜单内容:</span>
|
||||||
|
<el-select v-model="tempObj.type" clearable placeholder="请选择" class="menu_option">
|
||||||
|
<el-option v-for="item in menuOptions" :label="item.label" :value="item.value" :key="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="configur_content" v-if="tempObj.type === 'view'">
|
||||||
|
<span>跳转链接:</span>
|
||||||
|
<el-input class="input_width" v-model="tempObj.url" placeholder="请输入链接" clearable />
|
||||||
|
</div>
|
||||||
|
<div class="configur_content" v-if="tempObj.type === 'miniprogram'">
|
||||||
|
<div class="applet">
|
||||||
|
<span>小程序的 appid :</span>
|
||||||
|
<el-input class="input_width" v-model="tempObj.miniProgramAppId" placeholder="请输入小程序的appid" clearable />
|
||||||
</div>
|
</div>
|
||||||
<div class="configur_content" v-if="tempObj.type === 'view'">
|
<div class="applet">
|
||||||
<span>跳转链接:</span>
|
<span>小程序的页面路径:</span>
|
||||||
<el-input class="input_width" v-model="tempObj.url" placeholder="请输入链接" clearable />
|
<el-input class="input_width" v-model="tempObj.miniProgramPagePath"
|
||||||
|
placeholder="请输入小程序的页面路径,如:pages/index" clearable />
|
||||||
</div>
|
</div>
|
||||||
<div class="configur_content" v-if="tempObj.type === 'miniprogram'">
|
<div class="applet">
|
||||||
<div class="applet">
|
<span>小程序的备用网页:</span>
|
||||||
<span>小程序的appid:</span>
|
<el-input class="input_width" v-model="tempObj.url" placeholder="不支持小程序的老版本客户端将打开本网页" clearable />
|
||||||
<el-input class="input_width" v-model="tempObj.appid" placeholder="请输入小程序的appid" clearable></el-input>
|
|
||||||
</div>
|
|
||||||
<div class="applet">
|
|
||||||
<span>小程序的页面路径:</span>
|
|
||||||
<el-input class="input_width" v-model="tempObj.pagepath" placeholder="请输入小程序的页面路径,如:pages/index" clearable></el-input>
|
|
||||||
</div>
|
|
||||||
<div class="applet">
|
|
||||||
<span>备用网页:</span>
|
|
||||||
<el-input class="input_width" v-model="tempObj.url" placeholder="不支持小程序的老版本客户端将打开本网页" clearable></el-input>
|
|
||||||
</div>
|
|
||||||
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟!</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="configur_content" v-if="tempObj.type === 'article_view_limited'">
|
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟!</p>
|
||||||
<el-row>
|
</div>
|
||||||
<div class="select-item" v-if="tempObj && tempObj.content && tempObj.content.articles">
|
<div class="configur_content" v-if="tempObj.type === 'article_view_limited'">
|
||||||
<WxNews :objData="tempObj.content.articles"></WxNews>
|
<el-row>
|
||||||
<el-row class="ope-row">
|
<div class="select-item" v-if="tempObj && tempObj.content && tempObj.content.articles">
|
||||||
<el-button type="danger" icon="el-icon-delete" circle @click="deleteTempObj"></el-button>
|
<WxNews :objData="tempObj.content.articles"></WxNews>
|
||||||
</el-row>
|
<el-row class="ope-row">
|
||||||
</div>
|
<el-button type="danger" icon="el-icon-delete" circle @click="deleteTempObj"></el-button>
|
||||||
<div v-if="!tempObj.content || !tempObj.content.articles">
|
</el-row>
|
||||||
<el-row>
|
</div>
|
||||||
<el-col :span="24" style="text-align: center">
|
<div v-if="!tempObj.content || !tempObj.content.articles">
|
||||||
<el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
|
<el-row>
|
||||||
</el-col>
|
<el-col :span="24" style="text-align: center">
|
||||||
</el-row>
|
<el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
|
||||||
</div>
|
</el-col>
|
||||||
<el-dialog title="选择图文" :visible.sync="dialogNewsVisible" width="90%">
|
</el-row>
|
||||||
<WxMaterialSelect :objData="{repType:'news'}" @selectMaterial="selectMaterial"></WxMaterialSelect>
|
</div>
|
||||||
</el-dialog>
|
<el-dialog title="选择图文" :visible.sync="dialogNewsVisible" width="90%">
|
||||||
</el-row>
|
<WxMaterialSelect :objData="{repType:'news'}" @selectMaterial="selectMaterial"></WxMaterialSelect>
|
||||||
</div>
|
</el-dialog>
|
||||||
<div class="configur_content" v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'">
|
</el-row>
|
||||||
<WxReplySelect :objData="tempObj" v-if="hackResetWxReplySelect"></WxReplySelect>
|
</div>
|
||||||
</div>
|
<div class="configur_content" v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'">
|
||||||
</div>
|
<wx-reply-select :objData="tempObj.reply" v-if="hackResetWxReplySelect" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 一进页面就显示的默认页面,当点击左边按钮的时候,就不显示了-->
|
<!-- 一进页面就显示的默认页面,当点击左边按钮的时候,就不显示了-->
|
||||||
<div v-else class="right">
|
<div v-else class="right">
|
||||||
@ -139,10 +146,10 @@ SOFTWARE.
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import WxReplySelect from '@/views/mp/components/wx-news/main.vue'
|
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||||
import WxNews 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 WxMaterialSelect from '@/views/mp/components/wx-news/main.vue'
|
||||||
import {deleteMenu, getMenuList, saveMenu} from "@/api/mp/menu";
|
import { deleteMenu, getMenuList, saveMenu } from "@/api/mp/menu";
|
||||||
import { getSimpleAccounts } from "@/api/mp/account";
|
import { getSimpleAccounts } from "@/api/mp/account";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -177,7 +184,7 @@ export default {
|
|||||||
showConfigureContent: true, // 是否展示配置内容;如果有子菜单,就不显示配置内容
|
showConfigureContent: true, // 是否展示配置内容;如果有子菜单,就不显示配置内容
|
||||||
hackResetWxReplySelect: false, // 重置 WxReplySelect 组件
|
hackResetWxReplySelect: false, // 重置 WxReplySelect 组件
|
||||||
|
|
||||||
tempObj:{}, // 右边临时变量,作为中间值牵引关系
|
tempObj: {}, // 右边临时变量,作为中间值牵引关系
|
||||||
tempSelfObj: { // 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
|
tempSelfObj: { // 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
|
||||||
},
|
},
|
||||||
visible2: false, //素材内容 "选择素材"按钮弹框显示隐藏
|
visible2: false, //素材内容 "选择素材"按钮弹框显示隐藏
|
||||||
@ -240,6 +247,7 @@ export default {
|
|||||||
getList() {
|
getList() {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
getMenuList(this.accountId).then(response => {
|
getMenuList(this.accountId).then(response => {
|
||||||
|
response.data = this.convertMenuList(response.data);
|
||||||
this.menuList = this.handleTree(response.data, "id");
|
this.menuList = this.handleTree(response.data, "id");
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@ -262,6 +270,39 @@ export default {
|
|||||||
}
|
}
|
||||||
this.handleQuery()
|
this.handleQuery()
|
||||||
},
|
},
|
||||||
|
// 将后端返回的 menuList,转换成前端的 menuList
|
||||||
|
convertMenuList(list) {
|
||||||
|
const menuList = [];
|
||||||
|
list.forEach(item => {
|
||||||
|
const menu = {
|
||||||
|
...item,
|
||||||
|
};
|
||||||
|
if (item.type === 'click' || item.type === 'scancode_waitmsg') {
|
||||||
|
this.$delete(menu, 'replyMessageType');
|
||||||
|
this.$delete(menu, 'replyContent');
|
||||||
|
this.$delete(menu, 'replyMediaId');
|
||||||
|
this.$delete(menu, 'replyMediaUrl');
|
||||||
|
this.$delete(menu, 'replyDescription');
|
||||||
|
this.$delete(menu, 'replyArticles');
|
||||||
|
menu.reply = {
|
||||||
|
type: item.replyMessageType,
|
||||||
|
accountId: item.accountId,
|
||||||
|
content: item.replyContent,
|
||||||
|
mediaId: item.replyMediaId,
|
||||||
|
url: item.replyMediaUrl,
|
||||||
|
title: item.replyTitle,
|
||||||
|
description: item.replyDescription,
|
||||||
|
thumbMediaId: item.replyThumbMediaId,
|
||||||
|
thumbMediaUrl: item.replyThumbMediaUrl,
|
||||||
|
articles: item.replyArticles,
|
||||||
|
musicUrl: item.replyMusicUrl,
|
||||||
|
hqMusicUrl: item.replyHqMusicUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menuList.push(menu);
|
||||||
|
});
|
||||||
|
return menuList;
|
||||||
|
},
|
||||||
|
|
||||||
// ======================== 菜单操作 ========================
|
// ======================== 菜单操作 ========================
|
||||||
// 一级菜单点击事件
|
// 一级菜单点击事件
|
||||||
@ -285,7 +326,7 @@ export default {
|
|||||||
// 右侧的表单相关
|
// 右侧的表单相关
|
||||||
this.resetEditor();
|
this.resetEditor();
|
||||||
this.showRightFlag = true; // 右边菜单
|
this.showRightFlag = true; // 右边菜单
|
||||||
this.tempObj = subItem;//将点击的数据放到临时变量,对象有引用作用
|
this.tempObj = subItem; // 将点击的数据放到临时变量,对象有引用作用
|
||||||
this.tempSelfObj.grand = "2"; // 表示二级菜单
|
this.tempSelfObj.grand = "2"; // 表示二级菜单
|
||||||
this.tempSelfObj.index = index; // 表示一级菜单索引
|
this.tempSelfObj.index = index; // 表示一级菜单索引
|
||||||
this.tempSelfObj.secondIndex = k; // 表示二级菜单索引
|
this.tempSelfObj.secondIndex = k; // 表示二级菜单索引
|
||||||
@ -301,19 +342,27 @@ export default {
|
|||||||
const menuKeyLength = this.menuList.length;
|
const menuKeyLength = this.menuList.length;
|
||||||
const addButton = {
|
const addButton = {
|
||||||
name: "菜单名称",
|
name: "菜单名称",
|
||||||
children: []
|
children: [],
|
||||||
|
reply: { // 用于存储回复内容
|
||||||
|
'type': 'text',
|
||||||
|
'accountId': this.accountId // 保证组件里,可以使用到对应的公众号
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.$set(this.menuList, menuKeyLength, addButton)
|
this.$set(this.menuList, menuKeyLength, addButton)
|
||||||
this.menuClick(this.menuKeyLength - 1, addButton)
|
this.menuClick(this.menuKeyLength - 1, addButton)
|
||||||
},
|
},
|
||||||
// 添加横向二级菜单;item 表示要操作的父菜单
|
// 添加横向二级菜单;item 表示要操作的父菜单
|
||||||
addSubMenu(i, item) {
|
addSubMenu(i, item) {
|
||||||
if (!item.children || item.children.length <= 0){
|
// 清空父菜单的属性,因为它只需要 name 属性即可
|
||||||
|
if (!item.children || item.children.length <= 0) {
|
||||||
this.$set( item, 'children',[])
|
this.$set( item, 'children',[])
|
||||||
// TODO 芋艿:需要搞的属性弄下
|
|
||||||
this.$delete( item, 'type')
|
this.$delete( item, 'type')
|
||||||
this.$delete( item, 'pagepath')
|
this.$delete( item, 'miniProgramAppId')
|
||||||
|
this.$delete( item, 'miniProgramPagePath')
|
||||||
this.$delete( item, 'url')
|
this.$delete( item, 'url')
|
||||||
|
this.$delete( item, 'reply')
|
||||||
|
// TODO 芋艿:需要搞的属性弄下
|
||||||
|
|
||||||
this.$delete( item, 'key')
|
this.$delete( item, 'key')
|
||||||
this.$delete( item, 'article_id')
|
this.$delete( item, 'article_id')
|
||||||
this.$delete( item, 'textContent')
|
this.$delete( item, 'textContent')
|
||||||
@ -322,7 +371,11 @@ export default {
|
|||||||
|
|
||||||
let subMenuKeyLength = item.children.length; // 获取二级菜单key长度
|
let subMenuKeyLength = item.children.length; // 获取二级菜单key长度
|
||||||
let addButton = {
|
let addButton = {
|
||||||
name: "子菜单名称"
|
name: "子菜单名称",
|
||||||
|
reply: { // 用于存储回复内容
|
||||||
|
'type': 'text',
|
||||||
|
'accountId': this.accountId // 保证组件里,可以使用到对应的公众号
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.$set(item.children, subMenuKeyLength, addButton);
|
this.$set(item.children, subMenuKeyLength, addButton);
|
||||||
this.subMenuClick(item.children[subMenuKeyLength], i, subMenuKeyLength)
|
this.subMenuClick(item.children[subMenuKeyLength], i, subMenuKeyLength)
|
||||||
@ -352,19 +405,19 @@ export default {
|
|||||||
handleSave() {
|
handleSave() {
|
||||||
this.$modal.confirm('确定要保证并发布该菜单吗?').then(() => {
|
this.$modal.confirm('确定要保证并发布该菜单吗?').then(() => {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
return saveMenu(this.accountId, this.menuList);
|
return saveMenu(this.accountId, this.convertMenuFormList());
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.getList();
|
this.getList();
|
||||||
this.$modal.msgSuccess("发布成功");
|
this.$modal.msgSuccess("发布成功");
|
||||||
}).catch(() => {}).finally(() => {
|
}).finally(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 表单 Editor 重置
|
// 表单 Editor 重置
|
||||||
resetEditor() {
|
resetEditor() {
|
||||||
this.hackResetEditor = false // 销毁组件
|
this.hackResetWxReplySelect = false // 销毁组件
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.hackResetEditor = true // 重建组件
|
this.hackResetWxReplySelect = true // 重建组件
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleDelete() {
|
handleDelete() {
|
||||||
@ -378,6 +431,45 @@ export default {
|
|||||||
this.loading = false
|
this.loading = false
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// 将前端的 menuList,转换成后端接收的 menuList
|
||||||
|
convertMenuFormList() {
|
||||||
|
const menuList = [];
|
||||||
|
this.menuList.forEach(item => {
|
||||||
|
let menu = this.convertMenuForm(item);
|
||||||
|
menuList.push(menu);
|
||||||
|
// 处理子菜单
|
||||||
|
if (!item.children || item.children.length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item.children = [];
|
||||||
|
item.children.forEach(subItem => {
|
||||||
|
menu.children.push(this.convertMenuForm(subItem))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return menuList;
|
||||||
|
},
|
||||||
|
// 将前端的 menu,转换成后端接收的 menu
|
||||||
|
convertMenuForm(menu) {
|
||||||
|
let result = {
|
||||||
|
...menu,
|
||||||
|
children: undefined, // 不处理子节点
|
||||||
|
reply: undefined, // 稍后复制
|
||||||
|
}
|
||||||
|
if (menu.type === 'click' || menu.type === 'scancode_waitmsg') {
|
||||||
|
result.replyMessageType = menu.reply.type;
|
||||||
|
result.replyContent = menu.reply.content;
|
||||||
|
result.replyMediaId = menu.reply.mediaId;
|
||||||
|
result.replyMediaUrl = menu.reply.url;
|
||||||
|
result.replyTitle = menu.reply.title;
|
||||||
|
result.replyDescription = menu.reply.description;
|
||||||
|
result.replyThumbMediaId = menu.reply.thumbMediaId;
|
||||||
|
result.replyThumbMediaUrl = menu.reply.thumbMediaUrl;
|
||||||
|
result.replyArticles = menu.reply.articles;
|
||||||
|
result.replyMusicUrl = menu.reply.musicUrl;
|
||||||
|
result.replyHqMusicUrl = menu.reply.hqMusicUrl;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
// TODO 芋艿:未归类
|
// TODO 芋艿:未归类
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user