1、微信组件更新vue3,部分功能可能还有问题。
							parent
							
								
									6f8499c4d0
								
							
						
					
					
						commit
						1134921a0a
					
				|  | @ -70,6 +70,23 @@ export const getDictObj = (dictType: string, value: any) => { | |||
|   }) | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 获得字典数据的文本展示 | ||||
|  * | ||||
|  * @param dictType 字典类型 | ||||
|  * @param value 字典数据的值 | ||||
|  */ | ||||
| export const getDictLabel = (dictType: string, value: any) => { | ||||
|   const dictOptions: DictDataType[] = getDictOptions(dictType) | ||||
|   const dictLabel = ref('') | ||||
|   dictOptions.forEach((dict: DictDataType) => { | ||||
|     if (dict.value === value) { | ||||
|       dictLabel.value = dict.label | ||||
|     } | ||||
|   }) | ||||
|   return dictLabel.value | ||||
| } | ||||
| 
 | ||||
| export enum DICT_TYPE { | ||||
|   USER_TYPE = 'user_type', | ||||
|   COMMON_STATUS = 'common_status', | ||||
|  | @ -123,5 +140,9 @@ export enum DICT_TYPE { | |||
|   PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态
 | ||||
|   PAY_ORDER_REFUND_STATUS = 'pay_order_refund_status', // 商户支付订单退款状态
 | ||||
|   PAY_REFUND_ORDER_STATUS = 'pay_refund_order_status', // 退款订单状态
 | ||||
|   PAY_REFUND_ORDER_TYPE = 'pay_refund_order_type' // 退款订单类别
 | ||||
|   PAY_REFUND_ORDER_TYPE = 'pay_refund_order_type', // 退款订单类别
 | ||||
| 
 | ||||
|   // ========== MP 模块 ==========
 | ||||
|   MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
 | ||||
|   MP_MESSAGE_TYPE = 'mp_message_type' // 消息类型
 | ||||
| } | ||||
|  |  | |||
|  | @ -11,10 +11,65 @@ import dayjs from 'dayjs' | |||
|  * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ" | ||||
|  * @returns 返回拼接后的时间字符串 | ||||
|  */ | ||||
| export function formatDate(date: Date, format: string): string { | ||||
| export function formatDate(date: Date, format?: string): string { | ||||
|   // 日期不存在,则返回空
 | ||||
|   if (!date) { | ||||
|     return '' | ||||
|   } | ||||
|   // 日期存在,则进行格式化
 | ||||
|   if (format === undefined) { | ||||
|     format = 'YYYY-MM-DD HH:mm:ss' | ||||
|   } | ||||
|   return dayjs(date).format(format) | ||||
| } | ||||
| 
 | ||||
| // TODO 芋艿:稍后去掉
 | ||||
| // 日期格式化
 | ||||
| export function parseTime(time: any, pattern?: string) { | ||||
|   if (arguments.length === 0 || !time) { | ||||
|     return null | ||||
|   } | ||||
|   const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' | ||||
|   let date | ||||
|   if (typeof time === 'object') { | ||||
|     date = time | ||||
|   } else { | ||||
|     if (typeof time === 'string' && /^[0-9]+$/.test(time)) { | ||||
|       time = parseInt(time) | ||||
|     } else if (typeof time === 'string') { | ||||
|       time = time | ||||
|         .replace(new RegExp(/-/gm), '/') | ||||
|         .replace('T', ' ') | ||||
|         .replace(new RegExp(/\.\d{3}/gm), '') | ||||
|     } | ||||
|     if (typeof time === 'number' && time.toString().length === 10) { | ||||
|       time = time * 1000 | ||||
|     } | ||||
|     date = new Date(time) | ||||
|   } | ||||
|   const formatObj = { | ||||
|     y: date.getFullYear(), | ||||
|     m: date.getMonth() + 1, | ||||
|     d: date.getDate(), | ||||
|     h: date.getHours(), | ||||
|     i: date.getMinutes(), | ||||
|     s: date.getSeconds(), | ||||
|     a: date.getDay() | ||||
|   } | ||||
|   const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { | ||||
|     let value = formatObj[key] | ||||
|     // Note: getDay() returns 0 on Sunday
 | ||||
|     if (key === 'a') { | ||||
|       return ['日', '一', '二', '三', '四', '五', '六'][value] | ||||
|     } | ||||
|     if (result.length > 0 && value < 10) { | ||||
|       value = '0' + value | ||||
|     } | ||||
|     return value || 0 | ||||
|   }) | ||||
|   return time_str | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 获取当前日期是第几周 | ||||
|  * @param dateTime 当前传入的日期值 | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 15 KiB | 
|  | @ -0,0 +1,72 @@ | |||
| <!-- | ||||
|   【微信消息 - 定位】 | ||||
| --> | ||||
| <template> | ||||
|   <div> | ||||
|     <el-link | ||||
|       type="primary" | ||||
|       target="_blank" | ||||
|       :href=" | ||||
|         'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=' + | ||||
|         locationY + | ||||
|         '&pointy=' + | ||||
|         locationX + | ||||
|         '&name=' + | ||||
|         label + | ||||
|         '&ref=yudao' | ||||
|       " | ||||
|     > | ||||
|       <el-col> | ||||
|         <el-row> | ||||
|           <img | ||||
|             :src=" | ||||
|               'https://apis.map.qq.com/ws/staticmap/v2/?zoom=10&markers=color:blue|label:A|' + | ||||
|               locationX + | ||||
|               ',' + | ||||
|               locationY + | ||||
|               '&key=' + | ||||
|               qqMapKey + | ||||
|               '&size=250*180' | ||||
|             " | ||||
|           /> | ||||
|         </el-row> | ||||
|         <el-row> | ||||
|           <el-icon><Location /></el-icon>{{ label }} | ||||
|         </el-row> | ||||
|       </el-col> | ||||
|     </el-link> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts" name="WxLocation"> | ||||
| import { Location } from '@element-plus/icons-vue' | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   locationX: { | ||||
|     required: true, | ||||
|     type: Number | ||||
|   }, | ||||
|   locationY: { | ||||
|     required: true, | ||||
|     type: Number | ||||
|   }, | ||||
|   label: { | ||||
|     // 地名 | ||||
|     required: true, | ||||
|     type: String | ||||
|   }, | ||||
|   qqMapKey: { | ||||
|     // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc | ||||
|     required: false, | ||||
|     type: String, | ||||
|     default: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E' // 需要自定义 | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| defineExpose({ | ||||
|   locationX: props.locationX, | ||||
|   locationY: props.locationY, | ||||
|   label: props.label, | ||||
|   qqMapKey: props.qqMapKey | ||||
| }) | ||||
| </script> | ||||
|  | @ -0,0 +1,101 @@ | |||
| .avue-card{ | ||||
|   &__item{ | ||||
|     margin-bottom: 16px; | ||||
|     border: 1px solid #e8e8e8; | ||||
|     background-color: #fff; | ||||
|     box-sizing: border-box; | ||||
|     color: rgba(0,0,0,.65); | ||||
|     font-size: 14px; | ||||
|     font-variant: tabular-nums; | ||||
|     line-height: 1.5; | ||||
|     list-style: none; | ||||
|     font-feature-settings: "tnum"; | ||||
|     cursor: pointer; | ||||
|     height:200px; | ||||
|     &:hover{ | ||||
|       border-color: rgba(0,0,0,.09); | ||||
|       box-shadow: 0 2px 8px rgba(0,0,0,.09); | ||||
|     } | ||||
|     &--add{ | ||||
|       border:1px dashed #000; | ||||
|       width: 100%; | ||||
|       color: rgba(0,0,0,.45); | ||||
|       background-color: #fff; | ||||
|       border-color: #d9d9d9; | ||||
|       border-radius: 2px; | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       font-size: 16px; | ||||
|       i{ | ||||
|         margin-right: 10px; | ||||
|       } | ||||
|       &:hover{ | ||||
|         color: #40a9ff; | ||||
|         background-color: #fff; | ||||
|         border-color: #40a9ff; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   &__body{ | ||||
|     display: flex; | ||||
|     padding: 24px; | ||||
|   } | ||||
|   &__detail{ | ||||
|     flex:1 | ||||
|   } | ||||
|   &__avatar{ | ||||
|     width: 48px; | ||||
|     height: 48px; | ||||
|     border-radius: 48px; | ||||
|     overflow: hidden; | ||||
|     margin-right: 12px; | ||||
|     img{ | ||||
|       width: 100%; | ||||
|       height: 100%; | ||||
|     } | ||||
|   } | ||||
|   &__title{ | ||||
|     color: rgba(0,0,0,.85); | ||||
|     margin-bottom: 12px; | ||||
|     font-size: 16px; | ||||
|     &:hover{ | ||||
|       color:#1890ff; | ||||
|     } | ||||
|   } | ||||
|   &__info{ | ||||
|     color: rgba(0,0,0,.45); | ||||
|     display: -webkit-box; | ||||
|     -webkit-box-orient: vertical; | ||||
|     -webkit-line-clamp: 3; | ||||
|     overflow: hidden; | ||||
|     height: 64px; | ||||
|   } | ||||
|   &__menu{ | ||||
|     display: flex; | ||||
|     justify-content:space-around; | ||||
|     height: 50px; | ||||
|     background: #f7f9fa; | ||||
|     color: rgba(0,0,0,.45); | ||||
|     text-align: center; | ||||
|     line-height: 50px; | ||||
|     &:hover{ | ||||
|       color:#1890ff; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** joolun 额外加的 */ | ||||
| .avue-comment__main { | ||||
|   flex: unset!important; | ||||
|   border-radius: 5px!important; | ||||
|   margin: 0 8px!important; | ||||
| } | ||||
| .avue-comment__header { | ||||
|   border-top-left-radius: 5px; | ||||
|   border-top-right-radius: 5px; | ||||
| } | ||||
| .avue-comment__body { | ||||
|   border-bottom-right-radius: 5px; | ||||
|   border-bottom-left-radius: 5px; | ||||
| } | ||||
|  | @ -0,0 +1,88 @@ | |||
| /* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss  */ | ||||
| .avue-comment{ | ||||
|   margin-bottom: 30px; | ||||
|   display: flex; | ||||
|   align-items: flex-start; | ||||
|   &--reverse{ | ||||
|     flex-direction:row-reverse; | ||||
|     .avue-comment__main{ | ||||
|       &:before,&:after{ | ||||
|         left: auto; | ||||
|         right: -8px; | ||||
|         border-width: 8px 0 8px 8px; | ||||
|       } | ||||
|       &:before{ | ||||
|         border-left-color: #dedede; | ||||
|       } | ||||
|       &:after{ | ||||
|         border-left-color: #f8f8f8; | ||||
|         margin-right: 1px; | ||||
|         margin-left: auto; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   &__avatar{ | ||||
|     width: 48px; | ||||
|     height: 48px; | ||||
|     border-radius: 50%; | ||||
|     border: 1px solid transparent; | ||||
|     box-sizing: border-box; | ||||
|     vertical-align: middle; | ||||
|   } | ||||
|   &__header{ | ||||
|     padding: 5px 15px; | ||||
|     background: #f8f8f8; | ||||
|     border-bottom: 1px solid #eee; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
|   } | ||||
|   &__author{ | ||||
|     font-weight: 700; | ||||
|     font-size: 14px; | ||||
|     color: #999; | ||||
|   } | ||||
|   &__main{ | ||||
|     flex:1; | ||||
|     margin: 0 20px; | ||||
|     position: relative; | ||||
|     border: 1px solid #dedede; | ||||
|     border-radius: 2px; | ||||
|     &:before,&:after{ | ||||
|       position: absolute; | ||||
|       top: 10px; | ||||
|       left: -8px; | ||||
|       right: 100%; | ||||
|       width: 0; | ||||
|       height: 0; | ||||
|       display: block; | ||||
|       content: " "; | ||||
|       border-color: transparent; | ||||
|       border-style: solid solid outset; | ||||
|       border-width: 8px 8px 8px 0; | ||||
|       pointer-events: none; | ||||
|     } | ||||
|     &:before { | ||||
|       border-right-color: #dedede; | ||||
|       z-index: 1; | ||||
|     } | ||||
|     &:after{ | ||||
|       border-right-color: #f8f8f8; | ||||
|       margin-left: 1px; | ||||
|       z-index: 2; | ||||
|     } | ||||
|   } | ||||
|   &__body{ | ||||
|     padding: 15px; | ||||
|     overflow: hidden; | ||||
|     background: #fff; | ||||
|     font-family: Segoe UI,Lucida Grande,Helvetica,Arial,Microsoft YaHei,FreeSans,Arimo,Droid Sans,wenquanyi micro hei,Hiragino Sans GB,Hiragino Sans GB W3,FontAwesome,sans-serif;color: #333; | ||||
|     font-size: 14px; | ||||
|   } | ||||
|   blockquote{ | ||||
|     margin:0; | ||||
|     font-family: Georgia,Times New Roman,Times,Kai,Kaiti SC,KaiTi,BiauKai,FontAwesome,serif; | ||||
|     padding: 1px 0 1px 15px; | ||||
|     border-left: 4px solid #ddd; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,338 @@ | |||
| <!-- | ||||
|   - Copyright (C) 2018-2019 | ||||
|   - All rights reserved, Designed By www.joolun.com | ||||
|   芋道源码: | ||||
|   ① 移除暂时用不到的 websocket | ||||
|   ② 代码优化,补充注释,提升阅读性 | ||||
| --> | ||||
| <template> | ||||
|   <div class="msg-main"> | ||||
|     <div class="msg-div" :id="'msg-div' + nowStr"> | ||||
|       <!-- 加载更多 --> | ||||
|       <div v-loading="loading"></div> | ||||
|       <div v-if="!loading"> | ||||
|         <div class="el-table__empty-block" v-if="loadMore" @click="loadingMore" | ||||
|           ><span class="el-table__empty-text">点击加载更多</span></div | ||||
|         > | ||||
|         <div class="el-table__empty-block" v-if="!loadMore" | ||||
|           ><span class="el-table__empty-text">没有更多了</span></div | ||||
|         > | ||||
|       </div> | ||||
|       <!-- 消息列表 --> | ||||
|       <div class="execution" v-for="item in list" :key="item.id"> | ||||
|         <div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''"> | ||||
|           <div class="avatar-div"> | ||||
|             <img | ||||
|               :src="item.sendFrom === 1 ? user.avatar : mp.avatar" | ||||
|               class="avue-comment__avatar" | ||||
|             /> | ||||
|             <div class="avue-comment__author">{{ | ||||
|               item.sendFrom === 1 ? user.nickname : mp.nickname | ||||
|             }}</div> | ||||
|           </div> | ||||
|           <div class="avue-comment__main"> | ||||
|             <div class="avue-comment__header"> | ||||
|               <div class="avue-comment__create_time">{{ parseTime(item.createTime) }}</div> | ||||
|             </div> | ||||
|             <div | ||||
|               class="avue-comment__body" | ||||
|               :style="item.sendFrom === 2 ? 'background: #6BED72;' : ''" | ||||
|             > | ||||
|               <!-- 【事件】区域 --> | ||||
|               <div v-if="item.type === 'event' && item.event === 'subscribe'"> | ||||
|                 <el-tag type="success" size="mini">关注</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'unsubscribe'"> | ||||
|                 <el-tag type="danger" size="mini">取消关注</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'CLICK'"> | ||||
|                 <el-tag size="mini">点击菜单</el-tag>【{{ item.eventKey }}】 | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'VIEW'"> | ||||
|                 <el-tag size="mini">点击菜单链接</el-tag>【{{ item.eventKey }}】 | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'"> | ||||
|                 <el-tag size="mini">扫码结果</el-tag>【{{ item.eventKey }}】 | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'scancode_push'"> | ||||
|                 <el-tag size="mini">扫码结果</el-tag>【{{ item.eventKey }}】 | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'"> | ||||
|                 <el-tag size="mini">系统拍照发图</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'"> | ||||
|                 <el-tag size="mini">拍照或者相册</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'pic_weixin'"> | ||||
|                 <el-tag size="mini">微信相册</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event' && item.event === 'location_select'"> | ||||
|                 <el-tag size="mini">选择地理位置</el-tag> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'event'"> | ||||
|                 <el-tag type="danger" size="mini">未知事件类型</el-tag> | ||||
|               </div> | ||||
|               <!-- 【消息】区域 --> | ||||
|               <div v-else-if="item.type === 'text'">{{ item.content }}</div> | ||||
|               <div v-else-if="item.type === 'voice'"> | ||||
|                 <wx-voice-player :url="item.mediaUrl" :content="item.recognition" /> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'image'"> | ||||
|                 <a target="_blank" :href="item.mediaUrl"> | ||||
|                   <img :src="item.mediaUrl" style="width: 100px" /> | ||||
|                 </a> | ||||
|               </div> | ||||
|               <div | ||||
|                 v-else-if="item.type === 'video' || item.type === 'shortvideo'" | ||||
|                 style="text-align: center" | ||||
|               > | ||||
|                 <wx-video-player :url="item.mediaUrl" /> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'link'" class="avue-card__detail"> | ||||
|                 <el-link type="success" :underline="false" target="_blank" :href="item.url"> | ||||
|                   <div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div> | ||||
|                 </el-link> | ||||
|                 <div class="avue-card__info" style="height: unset">{{ item.description }}</div> | ||||
|               </div> | ||||
|               <!-- TODO 芋艿:待完善 --> | ||||
|               <div v-else-if="item.type === 'location'"> | ||||
|                 <wx-location | ||||
|                   :label="item.label" | ||||
|                   :location-y="item.locationY" | ||||
|                   :location-x="item.locationX" | ||||
|                 /> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'news'" style="width: 300px"> | ||||
|                 <!-- TODO 芋艿:待测试;详情页也存在类似的情况 --> | ||||
|                 <wx-news :articles="item.articles" /> | ||||
|               </div> | ||||
|               <div v-else-if="item.type === 'music'"> | ||||
|                 <wx-music | ||||
|                   :title="item.title" | ||||
|                   :description="item.description" | ||||
|                   :thumb-media-url="item.thumbMediaUrl" | ||||
|                   :music-url="item.musicUrl" | ||||
|                   :hq-music-url="item.hqMusicUrl" | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="msg-send" v-loading="sendLoading"> | ||||
|       <wx-reply-select ref="replySelect" :objData="objData" /> | ||||
|       <el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { getMessagePage, sendMessage } from '@/api/mp/message' | ||||
| import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' | ||||
| import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' | ||||
| import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' | ||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||
| import WxLocation from '@/views/mp/components/wx-location/main.vue' | ||||
| import WxMusic from '@/views/mp/components/wx-music/main.vue' | ||||
| import { getUser } from '@/api/mp/mpuser' | ||||
| 
 | ||||
| export default { | ||||
|   name: 'WxMsg', | ||||
|   components: { | ||||
|     WxReplySelect, | ||||
|     WxVideoPlayer, | ||||
|     WxVoicePlayer, | ||||
|     WxNews, | ||||
|     WxLocation, | ||||
|     WxMusic | ||||
|   }, | ||||
|   props: { | ||||
|     userId: { | ||||
|       type: Number, | ||||
|       required: true | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       nowStr: new Date().getTime(), // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处 | ||||
|       loading: false, // 消息列表是否正在加载中 | ||||
|       loadMore: true, // 是否可以加载更多 | ||||
|       list: [], // 消息列表 | ||||
|       queryParams: { | ||||
|         pageNo: 1, // 当前页数 | ||||
|         pageSize: 14, // 每页显示多少条 | ||||
|         accountId: undefined | ||||
|       }, | ||||
|       user: { | ||||
|         // 由于微信不再提供昵称,直接使用“用户”展示 | ||||
|         nickname: '用户', | ||||
|         avatar: require('@/assets/images/profile.jpg'), | ||||
|         accountId: 0 // 公众号账号编号 | ||||
|       }, | ||||
|       mp: { | ||||
|         nickname: '公众号', | ||||
|         avatar: require('@/assets/images/wechat.png') | ||||
|       }, | ||||
| 
 | ||||
|       // ========= 消息发送 ========= | ||||
|       sendLoading: false, // 发送消息是否加载中 | ||||
|       objData: { | ||||
|         // 微信发送消息 | ||||
|         type: 'text' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     // 获得用户信息 | ||||
|     getUser(this.userId).then((response) => { | ||||
|       this.user.nickname = | ||||
|         response.data.nickname && response.data.nickname.length > 0 | ||||
|           ? response.data.nickname | ||||
|           : this.user.nickname | ||||
|       this.user.avatar = | ||||
|         response.data.avatar && this.user.avatar.length > 0 | ||||
|           ? response.data.avatar | ||||
|           : this.user.avatar | ||||
|       this.user.accountId = response.data.accountId | ||||
|       // 设置公众号账号编号 | ||||
|       this.queryParams.accountId = response.data.accountId | ||||
|       this.objData.accountId = response.data.accountId | ||||
| 
 | ||||
|       // 加载消息 | ||||
|       console.log(this.queryParams) | ||||
|       this.refreshChange() | ||||
|     }) | ||||
|   }, | ||||
|   methods: { | ||||
|     sendMsg() { | ||||
|       if (!this.objData) { | ||||
|         return | ||||
|       } | ||||
|       // 公众号限制:客服消息,公众号只允许发送一条 | ||||
|       if (this.objData.type === 'news' && this.objData.articles.length > 1) { | ||||
|         this.objData.articles = [this.objData.articles[0]] | ||||
|         this.$message({ | ||||
|           showClose: true, | ||||
|           message: '图文消息条数限制在 1 条以内,已默认发送第一条', | ||||
|           type: 'success' | ||||
|         }) | ||||
|       } | ||||
| 
 | ||||
|       // 执行发送 | ||||
|       this.sendLoading = true | ||||
|       sendMessage( | ||||
|         Object.assign( | ||||
|           { | ||||
|             userId: this.userId | ||||
|           }, | ||||
|           { | ||||
|             ...this.objData | ||||
|           } | ||||
|         ) | ||||
|       ) | ||||
|         .then((response) => { | ||||
|           this.sendLoading = false | ||||
|           // 添加到消息列表,并滚动 | ||||
|           this.list = [...this.list, ...[response.data]] | ||||
|           this.scrollToBottom() | ||||
|           // 重置 objData 状态 | ||||
|           this.$refs['replySelect'].deleteObj() // 重置,避免 tab 的数据未清理 | ||||
|         }) | ||||
|         .catch(() => { | ||||
|           this.sendLoading = false | ||||
|         }) | ||||
|     }, | ||||
|     loadingMore() { | ||||
|       this.queryParams.pageNo++ | ||||
|       this.getPage(this.queryParams) | ||||
|     }, | ||||
|     getPage(page, params) { | ||||
|       this.loading = true | ||||
|       getMessagePage( | ||||
|         Object.assign( | ||||
|           { | ||||
|             pageNo: page.pageNo, | ||||
|             pageSize: page.pageSize, | ||||
|             userId: this.userId, | ||||
|             accountId: page.accountId | ||||
|           }, | ||||
|           params | ||||
|         ) | ||||
|       ).then((response) => { | ||||
|         // 计算当前的滚动高度 | ||||
|         const msgDiv = document.getElementById('msg-div' + this.nowStr) | ||||
|         let scrollHeight = 0 | ||||
|         if (msgDiv) { | ||||
|           scrollHeight = msgDiv.scrollHeight | ||||
|         } | ||||
| 
 | ||||
|         // 处理数据 | ||||
|         const data = response.data.list.reverse() | ||||
|         this.list = [...data, ...this.list] | ||||
|         this.loading = false | ||||
|         if (data.length < this.queryParams.pageSize || data.length === 0) { | ||||
|           this.loadMore = false | ||||
|         } | ||||
|         this.queryParams.pageNo = page.pageNo | ||||
|         this.queryParams.pageSize = page.pageSize | ||||
| 
 | ||||
|         // 滚动到原来的位置 | ||||
|         if (this.queryParams.pageNo === 1) { | ||||
|           // 定位到消息底部 | ||||
|           this.scrollToBottom() | ||||
|         } else if (data.length !== 0) { | ||||
|           // 定位滚动条 | ||||
|           this.$nextTick(() => { | ||||
|             if (scrollHeight !== 0) { | ||||
|               msgDiv.scrollTop = | ||||
|                 document.getElementById('msg-div' + this.nowStr).scrollHeight - scrollHeight - 100 | ||||
|             } | ||||
|           }) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     /** | ||||
|      * 刷新回调 | ||||
|      */ | ||||
|     refreshChange() { | ||||
|       this.getPage(this.queryParams) | ||||
|     }, | ||||
|     /** 定位到消息底部 */ | ||||
|     scrollToBottom: function () { | ||||
|       this.$nextTick(() => { | ||||
|         let div = document.getElementById('msg-div' + this.nowStr) | ||||
|         div.scrollTop = div.scrollHeight | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| /* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc  */ | ||||
| @import './comment.scss'; | ||||
| @import './card.scss'; | ||||
| 
 | ||||
| .msg-main { | ||||
|   margin-top: -30px; | ||||
|   padding: 10px; | ||||
| } | ||||
| .msg-div { | ||||
|   height: 50vh; | ||||
|   overflow: auto; | ||||
|   background-color: #eaeaea; | ||||
|   margin-left: 10px; | ||||
|   margin-right: 10px; | ||||
| } | ||||
| .msg-send { | ||||
|   padding: 10px; | ||||
| } | ||||
| .avatar-div { | ||||
|   text-align: center; | ||||
|   width: 80px; | ||||
| } | ||||
| .send-but { | ||||
|   float: right; | ||||
|   margin-top: 8px !important; | ||||
| } | ||||
| </style> | ||||
|  | @ -0,0 +1,60 @@ | |||
| <!-- | ||||
|   【微信消息 - 音乐】 | ||||
| --> | ||||
| <template> | ||||
|   <div> | ||||
|     <el-link | ||||
|       type="success" | ||||
|       :underline="false" | ||||
|       target="_blank" | ||||
|       :href="hqMusicUrl ? hqMusicUrl : musicUrl" | ||||
|     > | ||||
|       <div | ||||
|         class="avue-card__body" | ||||
|         style="padding: 10px; background-color: #fff; border-radius: 5px" | ||||
|       > | ||||
|         <div class="avue-card__avatar"> | ||||
|           <img :src="thumbMediaUrl" alt="" /> | ||||
|         </div> | ||||
|         <div class="avue-card__detail"> | ||||
|           <div class="avue-card__title" style="margin-bottom: unset">{{ title }}</div> | ||||
|           <div class="avue-card__info" style="height: unset">{{ description }}</div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </el-link> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts" name="WxMusic"> | ||||
| const props = defineProps({ | ||||
|   title: { | ||||
|     required: false, | ||||
|     type: String | ||||
|   }, | ||||
|   description: { | ||||
|     required: false, | ||||
|     type: String | ||||
|   }, | ||||
|   musicUrl: { | ||||
|     required: false, | ||||
|     type: String | ||||
|   }, | ||||
|   hqMusicUrl: { | ||||
|     required: false, | ||||
|     type: String | ||||
|   }, | ||||
|   thumbMediaUrl: { | ||||
|     required: true, | ||||
|     type: String | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| defineExpose({ | ||||
|   musicUrl: props.musicUrl | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| /* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scc  */ | ||||
| @import '../wx-msg/card.scss'; | ||||
| </style> | ||||
|  | @ -0,0 +1,107 @@ | |||
| <!-- | ||||
|   - Copyright (C) 2018-2019 | ||||
|   - All rights reserved, Designed By www.joolun.com | ||||
|   【微信消息 - 图文】 | ||||
|   芋道源码: | ||||
|   ① 代码优化,补充注释,提升阅读性 | ||||
| --> | ||||
| <template> | ||||
|   <div class="news-home"> | ||||
|     <div v-for="(article, index) in articles" :key="index" class="news-div"> | ||||
|       <!-- 头条 --> | ||||
|       <a target="_blank" :href="article.url" v-if="index === 0"> | ||||
|         <div class="news-main"> | ||||
|           <div class="news-content"> | ||||
|             <el-image | ||||
|               class="material-img" | ||||
|               style="width: 100%; height: 120px" | ||||
|               :src="article.picUrl" | ||||
|             /> | ||||
|             <div class="news-content-title"> | ||||
|               <span>{{ article.title }}</span> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </a> | ||||
|       <!-- 二条/三条等等 --> | ||||
|       <a target="_blank" :href="article.url" v-else> | ||||
|         <div class="news-main-item"> | ||||
|           <div class="news-content-item"> | ||||
|             <div class="news-content-item-title">{{ article.title }}</div> | ||||
|             <div class="news-content-item-img"> | ||||
|               <img class="material-img" :src="article.picUrl" height="100%" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </a> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| const props = defineProps({ | ||||
|   articles: { | ||||
|     type: Array, | ||||
|     default: () => null | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| defineExpose({ | ||||
|   articles: props.articles | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .news-home { | ||||
|   background-color: #ffffff; | ||||
|   width: 100%; | ||||
|   margin: auto; | ||||
| } | ||||
| .news-main { | ||||
|   width: 100%; | ||||
|   margin: auto; | ||||
| } | ||||
| .news-content { | ||||
|   background-color: #acadae; | ||||
|   width: 100%; | ||||
|   position: relative; | ||||
| } | ||||
| .news-content-title { | ||||
|   display: inline-block; | ||||
|   font-size: 12px; | ||||
|   color: #ffffff; | ||||
|   position: absolute; | ||||
|   left: 0; | ||||
|   bottom: 0; | ||||
|   background-color: black; | ||||
|   width: 98%; | ||||
|   padding: 1%; | ||||
|   opacity: 0.65; | ||||
|   white-space: normal; | ||||
|   box-sizing: unset !important; | ||||
| } | ||||
| .news-main-item { | ||||
|   background-color: #ffffff; | ||||
|   padding: 5px 0; | ||||
|   border-top: 1px solid #eaeaea; | ||||
| } | ||||
| .news-content-item { | ||||
|   position: relative; | ||||
| } | ||||
| .news-content-item-title { | ||||
|   display: inline-block; | ||||
|   font-size: 10px; | ||||
|   width: 70%; | ||||
|   margin-left: 1%; | ||||
|   white-space: normal; | ||||
| } | ||||
| .news-content-item-img { | ||||
|   display: inline-block; | ||||
|   width: 25%; | ||||
|   background-color: #acadae; | ||||
|   margin-right: 1%; | ||||
| } | ||||
| .material-img { | ||||
|   width: 100%; | ||||
| } | ||||
| </style> | ||||
|  | @ -0,0 +1,634 @@ | |||
| <!--<!–--> | ||||
| <!--  - Copyright (C) 2018-2019--> | ||||
| <!--  - All rights reserved, Designed By www.joolun.com--> | ||||
| <!--  芋道源码:--> | ||||
| <!--  ① 移除多余的 rep 为前缀的变量,让 message 消息更简单--> | ||||
| <!--  ② 代码优化,补充注释,提升阅读性--> | ||||
| <!--  ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入--> | ||||
| <!--  ④ 支持发送【视频】消息时,支持新建视频--> | ||||
| <!--–>--> | ||||
| <!--<template>--> | ||||
| <!--  <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">--> | ||||
| <!--    <!– 类型 1:文本 –>--> | ||||
| <!--    <el-tab-pane name="text">--> | ||||
| <!--      <span slot="label"><i class="el-icon-document"></i> 文本</span>--> | ||||
| <!--      <el-input--> | ||||
| <!--        type="textarea"--> | ||||
| <!--        :rows="5"--> | ||||
| <!--        placeholder="请输入内容"--> | ||||
| <!--        v-model="objData.content"--> | ||||
| <!--        @input="inputContent"--> | ||||
| <!--      />--> | ||||
| <!--    </el-tab-pane>--> | ||||
| <!--    <!– 类型 2:图片 –>--> | ||||
| <!--    <el-tab-pane name="image">--> | ||||
| <!--      <span slot="label"><i class="el-icon-picture"></i> 图片</span>--> | ||||
| <!--      <el-row>--> | ||||
| <!--        <!– 情况一:已经选择好素材、或者上传好图片 –>--> | ||||
| <!--        <div class="select-item" v-if="objData.url">--> | ||||
| <!--          <img class="material-img" :src="objData.url" />--> | ||||
| <!--          <p class="item-name" v-if="objData.name">{{ objData.name }}</p>--> | ||||
| <!--          <el-row class="ope-row">--> | ||||
| <!--            <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> | ||||
| <!--          </el-row>--> | ||||
| <!--        </div>--> | ||||
| <!--        <!– 情况二:未做完上述操作 –>--> | ||||
| <!--        <div v-else>--> | ||||
| <!--          <el-row style="text-align: center">--> | ||||
| <!--            <!– 选择素材 –>--> | ||||
| <!--            <el-col :span="12" class="col-select">--> | ||||
| <!--              <el-button type="success" @click="openMaterial">--> | ||||
| <!--                素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> | ||||
| <!--              </el-button>--> | ||||
| <!--              <el-dialog--> | ||||
| <!--                title="选择图片"--> | ||||
| <!--                v-model:visible="dialogImageVisible"--> | ||||
| <!--                width="90%"--> | ||||
| <!--                append-to-body--> | ||||
| <!--              >--> | ||||
| <!--                <wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" />--> | ||||
| <!--              </el-dialog>--> | ||||
| <!--            </el-col>--> | ||||
| <!--            <!– 文件上传 –>--> | ||||
| <!--            <el-col :span="12" class="col-add">--> | ||||
| <!--              <el-upload--> | ||||
| <!--                :action="actionUrl"--> | ||||
| <!--                :headers="headers"--> | ||||
| <!--                multiple--> | ||||
| <!--                :limit="1"--> | ||||
| <!--                :file-list="fileList"--> | ||||
| <!--                :data="uploadData"--> | ||||
| <!--                :before-upload="beforeImageUpload"--> | ||||
| <!--                :on-success="handleUploadSuccess"--> | ||||
| <!--              >--> | ||||
| <!--                <el-button type="primary">上传图片</el-button>--> | ||||
| <!--                <div slot="tip" class="el-upload__tip"--> | ||||
| <!--                  >支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div--> | ||||
| <!--                >--> | ||||
| <!--              </el-upload>--> | ||||
| <!--            </el-col>--> | ||||
| <!--          </el-row>--> | ||||
| <!--        </div>--> | ||||
| <!--      </el-row>--> | ||||
| <!--    </el-tab-pane>--> | ||||
| <!--    <!– 类型 3:语音 –>--> | ||||
| <!--    <el-tab-pane name="voice">--> | ||||
| <!--      <span slot="label"><i class="el-icon-phone"></i> 语音</span>--> | ||||
| <!--      <el-row>--> | ||||
| <!--        <div class="select-item2" v-if="objData.url">--> | ||||
| <!--          <p class="item-name">{{ objData.name }}</p>--> | ||||
| <!--          <div class="item-infos">--> | ||||
| <!--            <wx-voice-player :url="objData.url" />--> | ||||
| <!--          </div>--> | ||||
| <!--          <el-row class="ope-row">--> | ||||
| <!--            <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> | ||||
| <!--          </el-row>--> | ||||
| <!--        </div>--> | ||||
| <!--        <div v-else>--> | ||||
| <!--          <el-row style="text-align: center">--> | ||||
| <!--            <!– 选择素材 –>--> | ||||
| <!--            <el-col :span="12" class="col-select">--> | ||||
| <!--              <el-button type="success" @click="openMaterial">--> | ||||
| <!--                素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> | ||||
| <!--              </el-button>--> | ||||
| <!--              <el-dialog--> | ||||
| <!--                title="选择语音"--> | ||||
| <!--                v-model:visible="dialogVoiceVisible"--> | ||||
| <!--                width="90%"--> | ||||
| <!--                append-to-body--> | ||||
| <!--              >--> | ||||
| <!--                <WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial" />--> | ||||
| <!--              </el-dialog>--> | ||||
| <!--            </el-col>--> | ||||
| <!--            <!– 文件上传 –>--> | ||||
| <!--            <el-col :span="12" class="col-add">--> | ||||
| <!--              <el-upload--> | ||||
| <!--                :action="actionUrl"--> | ||||
| <!--                :headers="headers"--> | ||||
| <!--                multiple--> | ||||
| <!--                :limit="1"--> | ||||
| <!--                :file-list="fileList"--> | ||||
| <!--                :data="uploadData"--> | ||||
| <!--                :before-upload="beforeVoiceUpload"--> | ||||
| <!--                :on-success="handleUploadSuccess"--> | ||||
| <!--              >--> | ||||
| <!--                <el-button type="primary">点击上传</el-button>--> | ||||
| <!--                <div slot="tip" class="el-upload__tip"--> | ||||
| <!--                  >格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s</div--> | ||||
| <!--                >--> | ||||
| <!--              </el-upload>--> | ||||
| <!--            </el-col>--> | ||||
| <!--          </el-row>--> | ||||
| <!--        </div>--> | ||||
| <!--      </el-row>--> | ||||
| <!--    </el-tab-pane>--> | ||||
| <!--    <!– 类型 4:视频 –>--> | ||||
| <!--    <el-tab-pane name="video">--> | ||||
| <!--      <span slot="label"><i class="el-icon-share"></i> 视频</span>--> | ||||
| <!--      <el-row>--> | ||||
| <!--        <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />--> | ||||
| <!--        <div style="margin: 20px 0"></div>--> | ||||
| <!--        <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />--> | ||||
| <!--        <div style="margin: 20px 0"></div>--> | ||||
| <!--        <div style="text-align: center">--> | ||||
| <!--          <wx-video-player v-if="objData.url" :url="objData.url" />--> | ||||
| <!--        </div>--> | ||||
| <!--        <div style="margin: 20px 0"></div>--> | ||||
| <!--        <el-row style="text-align: center">--> | ||||
| <!--          <!– 选择素材 –>--> | ||||
| <!--          <el-col :span="12">--> | ||||
| <!--            <el-button type="success" @click="openMaterial">--> | ||||
| <!--              素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> | ||||
| <!--            </el-button>--> | ||||
| <!--            <el-dialog--> | ||||
| <!--              title="选择视频"--> | ||||
| <!--              v-model:visible="dialogVideoVisible"--> | ||||
| <!--              width="90%"--> | ||||
| <!--              append-to-body--> | ||||
| <!--            >--> | ||||
| <!--              <wx-material-select :objData="objData" @selectMaterial="selectMaterial" />--> | ||||
| <!--            </el-dialog>--> | ||||
| <!--          </el-col>--> | ||||
| <!--          <!– 文件上传 –>--> | ||||
| <!--          <el-col :span="12">--> | ||||
| <!--            <el-upload--> | ||||
| <!--              :action="actionUrl"--> | ||||
| <!--              :headers="headers"--> | ||||
| <!--              multiple--> | ||||
| <!--              :limit="1"--> | ||||
| <!--              :file-list="fileList"--> | ||||
| <!--              :data="uploadData"--> | ||||
| <!--              :before-upload="beforeVideoUpload"--> | ||||
| <!--              :on-success="handleUploadSuccess"--> | ||||
| <!--            >--> | ||||
| <!--              <el-button type="primary"--> | ||||
| <!--                >新建视频<i class="el-icon-upload el-icon--right"></i--> | ||||
| <!--              ></el-button>--> | ||||
| <!--            </el-upload>--> | ||||
| <!--          </el-col>--> | ||||
| <!--        </el-row>--> | ||||
| <!--      </el-row>--> | ||||
| <!--    </el-tab-pane>--> | ||||
| <!--    <!– 类型 5:图文 –>--> | ||||
| <!--    <el-tab-pane name="news">--> | ||||
| <!--      <span slot="label"><i class="el-icon-news"></i> 图文</span>--> | ||||
| <!--      <el-row>--> | ||||
| <!--        <div class="select-item" v-if="objData.articles">--> | ||||
| <!--          <wx-news :articles="objData.articles" />--> | ||||
| <!--          <el-row class="ope-row">--> | ||||
| <!--            <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> | ||||
| <!--          </el-row>--> | ||||
| <!--        </div>--> | ||||
| <!--        <!– 选择素材 –>--> | ||||
| <!--        <div v-if="!objData.content">--> | ||||
| <!--          <el-row style="text-align: center">--> | ||||
| <!--            <el-col :span="24">--> | ||||
| <!--              <el-button type="success" @click="openMaterial"--> | ||||
| <!--                >{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文'--> | ||||
| <!--                }}<i class="el-icon-circle-check el-icon--right"></i--> | ||||
| <!--              ></el-button>--> | ||||
| <!--            </el-col>--> | ||||
| <!--          </el-row>--> | ||||
| <!--        </div>--> | ||||
| <!--        <el-dialog title="选择图文" v-model:visible="dialogNewsVisible" width="90%" append-to-body>--> | ||||
| <!--          <wx-material-select--> | ||||
| <!--            :objData="objData"--> | ||||
| <!--            @selectMaterial="selectMaterial"--> | ||||
| <!--            :newsType="newsType"--> | ||||
| <!--          />--> | ||||
| <!--        </el-dialog>--> | ||||
| <!--      </el-row>--> | ||||
| <!--    </el-tab-pane>--> | ||||
| <!--    <!– 类型 6:音乐 –>--> | ||||
| <!--    <el-tab-pane name="music">--> | ||||
| <!--      <span slot="label"><i class="el-icon-service"></i> 音乐</span>--> | ||||
| <!--      <el-row>--> | ||||
| <!--        <el-col :span="6">--> | ||||
| <!--          <div class="thumb-div">--> | ||||
| <!--            <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl" />--> | ||||
| <!--            <i v-else class="el-icon-plus avatar-uploader-icon"></i>--> | ||||
| <!--            <div class="thumb-but">--> | ||||
| <!--              <el-upload--> | ||||
| <!--                :action="actionUrl"--> | ||||
| <!--                :headers="headers"--> | ||||
| <!--                multiple--> | ||||
| <!--                :limit="1"--> | ||||
| <!--                :file-list="fileList"--> | ||||
| <!--                :data="uploadData"--> | ||||
| <!--                :before-upload="beforeThumbImageUpload"--> | ||||
| <!--                :on-success="handleUploadSuccess"--> | ||||
| <!--              >--> | ||||
| <!--                <el-button slot="trigger" size="mini" type="text">本地上传</el-button>--> | ||||
| <!--                <el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px"--> | ||||
| <!--                  >素材库选择</el-button--> | ||||
| <!--                >--> | ||||
| <!--              </el-upload>--> | ||||
| <!--            </div>--> | ||||
| <!--          </div>--> | ||||
| <!--          <el-dialog--> | ||||
| <!--            title="选择图片"--> | ||||
| <!--            v-model:visible="dialogThumbVisible"--> | ||||
| <!--            width="80%"--> | ||||
| <!--            append-to-body--> | ||||
| <!--          >--> | ||||
| <!--            <wx-material-select--> | ||||
| <!--              :objData="{ type: 'image', accountId: objData.accountId }"--> | ||||
| <!--              @selectMaterial="selectMaterial"--> | ||||
| <!--            />--> | ||||
| <!--          </el-dialog>--> | ||||
| <!--        </el-col>--> | ||||
| <!--        <el-col :span="18">--> | ||||
| <!--          <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />--> | ||||
| <!--          <div style="margin: 20px 0"></div>--> | ||||
| <!--          <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />--> | ||||
| <!--        </el-col>--> | ||||
| <!--      </el-row>--> | ||||
| <!--      <div style="margin: 20px 0"></div>--> | ||||
| <!--      <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" />--> | ||||
| <!--      <div style="margin: 20px 0"></div>--> | ||||
| <!--      <el-input--> | ||||
| <!--        v-model="objData.hqMusicUrl"--> | ||||
| <!--        placeholder="请输入高质量音乐链接"--> | ||||
| <!--        @input="inputContent"--> | ||||
| <!--      />--> | ||||
| <!--    </el-tab-pane>--> | ||||
| <!--  </el-tabs>--> | ||||
| <!--</template>--> | ||||
| 
 | ||||
| <!--<script>--> | ||||
| <!--import WxNews from '@/views/mp/components/wx-news/main.vue'--> | ||||
| <!--import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'--> | ||||
| <!--import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'--> | ||||
| <!--import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'--> | ||||
| 
 | ||||
| <!--import { getAccessToken } from '@/utils/auth'--> | ||||
| 
 | ||||
| <!--export default {--> | ||||
| <!--  name: 'WxReplySelect',--> | ||||
| <!--  components: {--> | ||||
| <!--    WxNews,--> | ||||
| <!--    WxMaterialSelect,--> | ||||
| <!--    WxVoicePlayer,--> | ||||
| <!--    WxVideoPlayer--> | ||||
| <!--  },--> | ||||
| <!--  props: {--> | ||||
| <!--    objData: {--> | ||||
| <!--      // 消息对象。--> | ||||
| <!--      type: Object, // 设置为 Object 的原因,方便属性的传递--> | ||||
| <!--      required: true--> | ||||
| <!--    },--> | ||||
| <!--    newsType: {--> | ||||
| <!--      // 图文类型:1、已发布图文;2、草稿箱图文--> | ||||
| <!--      type: String,--> | ||||
| <!--      default: '1'--> | ||||
| <!--    }--> | ||||
| <!--  },--> | ||||
| <!--  data() {--> | ||||
| <!--    return {--> | ||||
| <!--      tempPlayerObj: {--> | ||||
| <!--        type: '2'--> | ||||
| <!--      },--> | ||||
| 
 | ||||
| <!--      tempObj: new Map().set(--> | ||||
| <!--        // 临时缓存,切换消息类型的 tab 的时候,可以保存对应的数据;--> | ||||
| <!--        this.objData.type, // 消息类型--> | ||||
| <!--        Object.assign({}, this.objData)--> | ||||
| <!--      ), // 消息内容--> | ||||
| 
 | ||||
| <!--      // ========== 素材选择的弹窗,是否可见 ==========--> | ||||
| <!--      dialogNewsVisible: false, // 图文--> | ||||
| <!--      dialogImageVisible: false, // 图片--> | ||||
| <!--      dialogVoiceVisible: false, // 语音--> | ||||
| <!--      dialogVideoVisible: false, // 视频--> | ||||
| <!--      dialogThumbVisible: false, // 缩略图--> | ||||
| 
 | ||||
| <!--      // ========== 文件上传(图片、语音、视频) ==========--> | ||||
| <!--      fileList: [], // 文件列表--> | ||||
| <!--      uploadData: {--> | ||||
| <!--        accountId: undefined,--> | ||||
| <!--        type: this.objData.type,--> | ||||
| <!--        title: '',--> | ||||
| <!--        introduction: ''--> | ||||
| <!--      },--> | ||||
| <!--      actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary',--> | ||||
| <!--      headers: { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部--> | ||||
| <!--    }--> | ||||
| <!--  },--> | ||||
| <!--  methods: {--> | ||||
| <!--    beforeThumbImageUpload(file) {--> | ||||
| <!--      const isType =--> | ||||
| <!--        file.type === 'image/jpeg' ||--> | ||||
| <!--        file.type === 'image/png' ||--> | ||||
| <!--        file.type === 'image/gif' ||--> | ||||
| <!--        file.type === 'image/bmp' ||--> | ||||
| <!--        file.type === 'image/jpg'--> | ||||
| <!--      if (!isType) {--> | ||||
| <!--        this.$message.error('上传图片格式不对!')--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| <!--      const isLt = file.size / 1024 / 1024 < 2--> | ||||
| <!--      if (!isLt) {--> | ||||
| <!--        this.$message.error('上传图片大小不能超过 2M!')--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| <!--      this.uploadData.accountId = this.objData.accountId--> | ||||
| <!--      return true--> | ||||
| <!--    },--> | ||||
| <!--    beforeVoiceUpload(file) {--> | ||||
| <!--      // 校验格式--> | ||||
| <!--      const isType =--> | ||||
| <!--        file.type === 'audio/mp3' ||--> | ||||
| <!--        file.type === 'audio/mpeg' ||--> | ||||
| <!--        file.type === 'audio/wma' ||--> | ||||
| <!--        file.type === 'audio/wav' ||--> | ||||
| <!--        file.type === 'audio/amr'--> | ||||
| <!--      if (!isType) {--> | ||||
| <!--        this.$message.error('上传语音格式不对!' + file.type)--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| <!--      // 校验大小--> | ||||
| <!--      const isLt = file.size / 1024 / 1024 < 2--> | ||||
| <!--      if (!isLt) {--> | ||||
| <!--        this.$message.error('上传语音大小不能超过 2M!')--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| <!--      this.uploadData.accountId = this.objData.accountId--> | ||||
| <!--      return true--> | ||||
| <!--    },--> | ||||
| <!--    beforeImageUpload(file) {--> | ||||
| <!--      // 校验格式--> | ||||
| <!--      const isType =--> | ||||
| <!--        file.type === 'image/jpeg' ||--> | ||||
| <!--        file.type === 'image/png' ||--> | ||||
| <!--        file.type === 'image/gif' ||--> | ||||
| <!--        file.type === 'image/bmp' ||--> | ||||
| <!--        file.type === 'image/jpg'--> | ||||
| <!--      if (!isType) {--> | ||||
| <!--        this.$message.error('上传图片格式不对!')--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| <!--      // 校验大小--> | ||||
| <!--      const isLt = file.size / 1024 / 1024 < 2--> | ||||
| <!--      if (!isLt) {--> | ||||
| <!--        this.$message.error('上传图片大小不能超过 2M!')--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| <!--      this.uploadData.accountId = this.objData.accountId--> | ||||
| <!--      return true--> | ||||
| <!--    },--> | ||||
| <!--    beforeVideoUpload(file) {--> | ||||
| <!--      // 校验格式--> | ||||
| <!--      const isType = file.type === 'video/mp4'--> | ||||
| <!--      if (!isType) {--> | ||||
| <!--        this.$message.error('上传视频格式不对!')--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| <!--      // 校验大小--> | ||||
| <!--      const isLt = file.size / 1024 / 1024 < 10--> | ||||
| <!--      if (!isLt) {--> | ||||
| <!--        this.$message.error('上传视频大小不能超过 10M!')--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| <!--      this.uploadData.accountId = this.objData.accountId--> | ||||
| <!--      return true--> | ||||
| <!--    },--> | ||||
| <!--    handleUploadSuccess(response, file, fileList) {--> | ||||
| <!--      if (response.code !== 0) {--> | ||||
| <!--        this.$message.error('上传出错:' + response.msg)--> | ||||
| <!--        return false--> | ||||
| <!--      }--> | ||||
| 
 | ||||
| <!--      // 清空上传时的各种数据--> | ||||
| <!--      this.fileList = []--> | ||||
| <!--      this.uploadData.title = ''--> | ||||
| <!--      this.uploadData.introduction = ''--> | ||||
| 
 | ||||
| <!--      // 上传好的文件,本质是个素材,所以可以进行选中--> | ||||
| <!--      let item = response.data--> | ||||
| <!--      this.selectMaterial(item)--> | ||||
| <!--    },--> | ||||
| <!--    /**--> | ||||
| <!--     * 切换消息类型的 tab--> | ||||
| <!--     *--> | ||||
| <!--     * @param tab tab--> | ||||
| <!--     */--> | ||||
| <!--    handleClick(tab) {--> | ||||
| <!--      // 设置后续文件上传的文件类型--> | ||||
| <!--      this.uploadData.type = this.objData.type--> | ||||
| <!--      if (this.uploadData.type === 'music') {--> | ||||
| <!--        // 【音乐】上传的是缩略图--> | ||||
| <!--        this.uploadData.type = 'thumb'--> | ||||
| <!--      }--> | ||||
| 
 | ||||
| <!--      // 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData--> | ||||
| <!--      let tempObjItem = this.tempObj.get(this.objData.type)--> | ||||
| <!--      if (tempObjItem) {--> | ||||
| <!--        this.objData.content = tempObjItem.content ? tempObjItem.content : null--> | ||||
| <!--        this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null--> | ||||
| <!--        this.objData.url = tempObjItem.url ? tempObjItem.url : null--> | ||||
| <!--        this.objData.name = tempObjItem.url ? tempObjItem.name : null--> | ||||
| <!--        this.objData.title = tempObjItem.title ? tempObjItem.title : null--> | ||||
| <!--        this.objData.description = tempObjItem.description ? tempObjItem.description : null--> | ||||
| <!--        return--> | ||||
| <!--      }--> | ||||
| <!--      // 如果获取不到,需要把 objData 复原--> | ||||
| <!--      // 必须使用 $set 赋值,不然 input 无法输入内容--> | ||||
| <!--      this.$set(this.objData, 'content', '')--> | ||||
| <!--      this.$delete(this.objData, 'mediaId')--> | ||||
| <!--      this.$delete(this.objData, 'url')--> | ||||
| <!--      this.$set(this.objData, 'title', '')--> | ||||
| <!--      this.$set(this.objData, 'description', '')--> | ||||
| <!--    },--> | ||||
| <!--    /**--> | ||||
| <!--     * 选择素材,将设置设置到 objData 变量--> | ||||
| <!--     *--> | ||||
| <!--     * @param item 素材--> | ||||
| <!--     */--> | ||||
| <!--    selectMaterial(item) {--> | ||||
| <!--      // 选择好素材,所以隐藏弹窗--> | ||||
| <!--      this.closeMaterial()--> | ||||
| 
 | ||||
| <!--      // 创建 tempObjItem 对象,并设置对应的值--> | ||||
| <!--      let tempObjItem = {}--> | ||||
| <!--      tempObjItem.type = this.objData.type--> | ||||
| <!--      if (this.objData.type === 'news') {--> | ||||
| <!--        tempObjItem.articles = item.content.newsItem--> | ||||
| <!--        this.objData.articles = item.content.newsItem--> | ||||
| <!--      } else if (this.objData.type === 'music') {--> | ||||
| <!--        // 音乐需要特殊处理,因为选择的是图片的缩略图--> | ||||
| <!--        tempObjItem.thumbMediaId = item.mediaId--> | ||||
| <!--        this.objData.thumbMediaId = item.mediaId--> | ||||
| <!--        tempObjItem.thumbMediaUrl = item.url--> | ||||
| <!--        this.objData.thumbMediaUrl = item.url--> | ||||
| <!--        // title、introduction、musicUrl、hqMusicUrl:从 objData 到 tempObjItem,避免上传素材后,被覆盖掉--> | ||||
| <!--        tempObjItem.title = this.objData.title || ''--> | ||||
| <!--        tempObjItem.introduction = this.objData.introduction || ''--> | ||||
| <!--        tempObjItem.musicUrl = this.objData.musicUrl || ''--> | ||||
| <!--        tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || ''--> | ||||
| <!--      } else if (this.objData.type === 'image' || this.objData.type === 'voice') {--> | ||||
| <!--        tempObjItem.mediaId = item.mediaId--> | ||||
| <!--        this.objData.mediaId = item.mediaId--> | ||||
| <!--        tempObjItem.url = item.url--> | ||||
| <!--        this.objData.url = item.url--> | ||||
| <!--        tempObjItem.name = item.name--> | ||||
| <!--        this.objData.name = item.name--> | ||||
| <!--      } else if (this.objData.type === 'video') {--> | ||||
| <!--        tempObjItem.mediaId = item.mediaId--> | ||||
| <!--        this.objData.mediaId = item.mediaId--> | ||||
| <!--        tempObjItem.url = item.url--> | ||||
| <!--        this.objData.url = item.url--> | ||||
| <!--        tempObjItem.name = item.name--> | ||||
| <!--        this.objData.name = item.name--> | ||||
| <!--        // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction--> | ||||
| <!--        if (item.title) {--> | ||||
| <!--          this.objData.title = item.title || ''--> | ||||
| <!--          tempObjItem.title = item.title || ''--> | ||||
| <!--        }--> | ||||
| <!--        if (item.introduction) {--> | ||||
| <!--          this.objData.description = item.introduction || '' // 消息使用的是 description,素材使用的是 introduction,所以转换下--> | ||||
| <!--          tempObjItem.description = item.introduction || ''--> | ||||
| <!--        }--> | ||||
| <!--      } else if (this.objData.type === 'text') {--> | ||||
| <!--        this.objData.content = item.content || ''--> | ||||
| <!--      }--> | ||||
| <!--      // 最终设置到临时缓存--> | ||||
| <!--      this.tempObj.set(this.objData.type, tempObjItem)--> | ||||
| <!--    },--> | ||||
| <!--    openMaterial() {--> | ||||
| <!--      if (this.objData.type === 'news') {--> | ||||
| <!--        this.dialogNewsVisible = true--> | ||||
| <!--      } else if (this.objData.type === 'image') {--> | ||||
| <!--        this.dialogImageVisible = true--> | ||||
| <!--      } else if (this.objData.type === 'voice') {--> | ||||
| <!--        this.dialogVoiceVisible = true--> | ||||
| <!--      } else if (this.objData.type === 'video') {--> | ||||
| <!--        this.dialogVideoVisible = true--> | ||||
| <!--      } else if (this.objData.type === 'music') {--> | ||||
| <!--        this.dialogThumbVisible = true--> | ||||
| <!--      }--> | ||||
| <!--    },--> | ||||
| <!--    closeMaterial() {--> | ||||
| <!--      this.dialogNewsVisible = false--> | ||||
| <!--      this.dialogImageVisible = false--> | ||||
| <!--      this.dialogVoiceVisible = false--> | ||||
| <!--      this.dialogVideoVisible = false--> | ||||
| <!--      this.dialogThumbVisible = false--> | ||||
| <!--    },--> | ||||
| <!--    deleteObj() {--> | ||||
| <!--      if (this.objData.type === 'news') {--> | ||||
| <!--        this.$delete(this.objData, 'articles')--> | ||||
| <!--      } else if (this.objData.type === 'image') {--> | ||||
| <!--        this.objData.mediaId = null--> | ||||
| <!--        this.$delete(this.objData, 'url')--> | ||||
| <!--        this.objData.name = null--> | ||||
| <!--      } else if (this.objData.type === 'voice') {--> | ||||
| <!--        this.objData.mediaId = null--> | ||||
| <!--        this.$delete(this.objData, 'url')--> | ||||
| <!--        this.objData.name = null--> | ||||
| <!--      } else if (this.objData.type === 'video') {--> | ||||
| <!--        this.objData.mediaId = null--> | ||||
| <!--        this.$delete(this.objData, 'url')--> | ||||
| <!--        this.objData.name = null--> | ||||
| <!--        this.objData.title = null--> | ||||
| <!--        this.objData.description = null--> | ||||
| <!--      } else if (this.objData.type === 'music') {--> | ||||
| <!--        this.objData.thumbMediaId = null--> | ||||
| <!--        this.objData.thumbMediaUrl = null--> | ||||
| <!--        this.objData.title = null--> | ||||
| <!--        this.objData.description = null--> | ||||
| <!--        this.objData.musicUrl = null--> | ||||
| <!--        this.objData.hqMusicUrl = null--> | ||||
| <!--      } else if (this.objData.type === 'text') {--> | ||||
| <!--        this.objData.content = null--> | ||||
| <!--      }--> | ||||
| <!--      // 覆盖缓存--> | ||||
| <!--      this.tempObj.set(this.objData.type, Object.assign({}, this.objData))--> | ||||
| <!--    },--> | ||||
| <!--    /**--> | ||||
| <!--     * 输入时,缓存每次 objData 到 tempObj 中--> | ||||
| <!--     *--> | ||||
| <!--     * why?不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式--> | ||||
| <!--     */--> | ||||
| <!--    inputContent(str) {--> | ||||
| <!--      // 覆盖缓存--> | ||||
| <!--      this.tempObj.set(this.objData.type, Object.assign({}, this.objData))--> | ||||
| <!--    }--> | ||||
| <!--  }--> | ||||
| <!--}--> | ||||
| <!--</script>--> | ||||
| 
 | ||||
| <!--<style lang="scss" scoped>--> | ||||
| <!--.public-account-management {--> | ||||
| <!--  .el-input {--> | ||||
| <!--    width: 70%;--> | ||||
| <!--    margin-right: 2%;--> | ||||
| <!--  }--> | ||||
| <!--}--> | ||||
| <!--.pagination {--> | ||||
| <!--  text-align: right;--> | ||||
| <!--  margin-right: 25px;--> | ||||
| <!--}--> | ||||
| <!--.select-item {--> | ||||
| <!--  width: 280px;--> | ||||
| <!--  padding: 10px;--> | ||||
| <!--  margin: 0 auto 10px auto;--> | ||||
| <!--  border: 1px solid #eaeaea;--> | ||||
| <!--}--> | ||||
| <!--.select-item2 {--> | ||||
| <!--  padding: 10px;--> | ||||
| <!--  margin: 0 auto 10px auto;--> | ||||
| <!--  border: 1px solid #eaeaea;--> | ||||
| <!--}--> | ||||
| <!--.ope-row {--> | ||||
| <!--  padding-top: 10px;--> | ||||
| <!--  text-align: center;--> | ||||
| <!--}--> | ||||
| <!--.item-name {--> | ||||
| <!--  font-size: 12px;--> | ||||
| <!--  overflow: hidden;--> | ||||
| <!--  text-overflow: ellipsis;--> | ||||
| <!--  white-space: nowrap;--> | ||||
| <!--  text-align: center;--> | ||||
| <!--}--> | ||||
| <!--.el-form-item__content {--> | ||||
| <!--  line-height: unset !important;--> | ||||
| <!--}--> | ||||
| <!--.col-select {--> | ||||
| <!--  border: 1px solid rgb(234, 234, 234);--> | ||||
| <!--  padding: 50px 0px;--> | ||||
| <!--  height: 160px;--> | ||||
| <!--  width: 49.5%;--> | ||||
| <!--}--> | ||||
| <!--.col-select2 {--> | ||||
| <!--  border: 1px solid rgb(234, 234, 234);--> | ||||
| <!--  padding: 50px 0px;--> | ||||
| <!--  height: 160px;--> | ||||
| <!--}--> | ||||
| <!--.col-add {--> | ||||
| <!--  border: 1px solid rgb(234, 234, 234);--> | ||||
| <!--  padding: 50px 0px;--> | ||||
| <!--  height: 160px;--> | ||||
| <!--  width: 49.5%;--> | ||||
| <!--  float: right;--> | ||||
| <!--}--> | ||||
| <!--.avatar-uploader-icon {--> | ||||
| <!--  border: 1px solid #d9d9d9;--> | ||||
| <!--  font-size: 28px;--> | ||||
| <!--  color: #8c939d;--> | ||||
| <!--  width: 100px !important;--> | ||||
| <!--  height: 100px !important;--> | ||||
| <!--  line-height: 100px !important;--> | ||||
| <!--  text-align: center;--> | ||||
| <!--}--> | ||||
| <!--.material-img {--> | ||||
| <!--  width: 100%;--> | ||||
| <!--}--> | ||||
| <!--.thumb-div {--> | ||||
| <!--  display: inline-block;--> | ||||
| <!--  text-align: center;--> | ||||
| <!--}--> | ||||
| <!--.item-infos {--> | ||||
| <!--  width: 30%;--> | ||||
| <!--  margin: auto;--> | ||||
| <!--}--> | ||||
| <!--</style>--> | ||||
|  | @ -0,0 +1,117 @@ | |||
| <!-- | ||||
|   - Copyright (C) 2018-2019 | ||||
|   - All rights reserved, Designed By www.joolun.com | ||||
|   【微信消息 - 视频】 | ||||
|   芋道源码: | ||||
|   ① bug 修复: | ||||
|     1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容; | ||||
|       存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 | ||||
|     2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。 | ||||
|   ② 体验优化:弹窗关闭后,自动暂停视频的播放 | ||||
| --> | ||||
| <template> | ||||
|   <div> | ||||
|     <!-- 提示 --> | ||||
|     <div @click="playVideo()"> | ||||
|       <el-icon> | ||||
|         <VideoPlay /> | ||||
|       </el-icon> | ||||
|       <p>点击播放视频</p> | ||||
|     </div> | ||||
| 
 | ||||
|     <!-- 弹窗播放 --> | ||||
|     <el-dialog | ||||
|       title="视频播放" | ||||
|       v-model:visible="dialogVideo" | ||||
|       width="40%" | ||||
|       append-to-body | ||||
|       @close="closeDialog" | ||||
|     > | ||||
|       <video-player | ||||
|         v-if="playerOptions.sources[0].src" | ||||
|         class="video-player vjs-custom-skin" | ||||
|         ref="videoPlayerRef" | ||||
|         :playsinline="true" | ||||
|         :options="playerOptions" | ||||
|         @play="onPlayerPlay($event)" | ||||
|         @pause="onPlayerPause($event)" | ||||
|       /> | ||||
|     </el-dialog> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts" name="WxVideoPlayer"> | ||||
| // 引入 videoPlayer 相关组件。教程:https://juejin.cn/post/6923056942281654285 | ||||
| import { videoPlayer } from 'vue-video-player' | ||||
| import 'video.js/dist/video-js.css' | ||||
| import 'vue-video-player/src/custom-theme.css' | ||||
| import { VideoPlay } from '@element-plus/icons-vue' | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   url: { | ||||
|     // 视频地址,例如说:https://www.iocoder.cn/xxx.mp4 | ||||
|     type: String, | ||||
|     required: true | ||||
|   } | ||||
| }) | ||||
| const videoPlayerRef = ref() | ||||
| const dialogVideo = ref(false) | ||||
| const playerOptions = reactive({ | ||||
|   playbackRates: [0.5, 1.0, 1.5, 2.0], // 播放速度 | ||||
|   autoplay: false, // 如果 true,浏览器准备好时开始回放。 | ||||
|   muted: false, // 默认情况下将会消除任何音频。 | ||||
|   loop: false, // 导致视频一结束就重新开始。 | ||||
|   preload: 'auto', // 建议浏览器在 <video> 加载元素后是否应该开始下载视频数据。auto 浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) | ||||
|   language: 'zh-CN', | ||||
|   aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") | ||||
|   fluid: true, // 当true时,Video.js player 将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 | ||||
|   sources: [ | ||||
|     { | ||||
|       type: 'video/mp4', | ||||
|       src: '' // 你的视频地址(必填)【重要】 | ||||
|     } | ||||
|   ], | ||||
|   poster: '', // 你的封面地址 | ||||
|   width: document.documentElement.clientWidth, | ||||
|   notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖 Video.js 无法播放媒体源时显示的默认信息。 | ||||
|   controlBar: { | ||||
|     timeDivider: true, | ||||
|     durationDisplay: true, | ||||
|     remainingTimeDisplay: false, | ||||
|     fullscreenToggle: true //全屏按钮 | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| const playVideo = () => { | ||||
|   dialogVideo.value = true | ||||
|   playerOptions.sources[0].src = props.url | ||||
| } | ||||
| const closeDialog = () => { | ||||
|   // 暂停播放 | ||||
|   // videoPlayerRef.player.pause() | ||||
| } | ||||
| //   onPlayerPlay(player) {}, | ||||
| //   // // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
| //   // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars | ||||
| //   onPlayerPause(player) {} | ||||
| 
 | ||||
| // methods: { | ||||
| //   playVideo() { | ||||
| //     this.dialogVideo = true | ||||
| //     // 设置地址 | ||||
| //     this.playerOptions.sources[0]['src'] = this.url | ||||
| //   }, | ||||
| //   closeDialog() { | ||||
| //     // 暂停播放 | ||||
| //     this.$refs.videoPlayer.player.pause() | ||||
| //   }, | ||||
| // | ||||
| //   //todo player组件引入可能有问题 | ||||
| // | ||||
| //   // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars | ||||
| //   onPlayerPlay(player) {}, | ||||
| //   // // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
| //   // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars | ||||
| //   onPlayerPause(player) {} | ||||
| // } | ||||
| </script> | ||||
|  | @ -0,0 +1,100 @@ | |||
| <!-- | ||||
|   - Copyright (C) 2018-2019 | ||||
|   - All rights reserved, Designed By www.joolun.com | ||||
|   【微信消息 - 语音】 | ||||
|    芋道源码: | ||||
|   ① bug 修复: | ||||
|     1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容; | ||||
|       存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 | ||||
|     2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。 | ||||
|   ② 代码优化:将 props 中的 objData 调成为 data 中对应的属性,并补充相关注释 | ||||
| --> | ||||
| <template> | ||||
|   <div class="wx-voice-div" @click="playVoice"> | ||||
|     <el-icon | ||||
|       ><VideoPlay v-if="playing !== true" /> | ||||
|       <VideoPause v-if="playing === true" /> | ||||
|       <span class="amr-duration" v-if="duration">{{ duration }} 秒</span> | ||||
|     </el-icon> | ||||
|     <div v-if="content"> | ||||
|       <el-tag type="success" size="mini">语音识别</el-tag> | ||||
|       {{ content }} | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts" name="WxVoicePlayer"> | ||||
| // 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder | ||||
| 
 | ||||
| import BenzAMRRecorder from 'benz-amr-recorder' | ||||
| import { VideoPause, VideoPlay } from '@element-plus/icons-vue' | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   url: { | ||||
|     // 语音地址,例如说:https://www.iocoder.cn/xxx.amr | ||||
|     type: String, | ||||
|     required: true | ||||
|   }, | ||||
|   content: { | ||||
|     // 语音文本 | ||||
|     type: String, | ||||
|     required: false | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| const amr = ref() | ||||
| const playing = ref(false) | ||||
| const duration = ref() | ||||
| 
 | ||||
| const playVoice = () => { | ||||
|   // 情况一:未初始化,则创建 BenzAMRRecorder | ||||
|   debugger | ||||
|   console.log('进入' + amr.value) | ||||
|   if (amr.value === undefined) { | ||||
|     console.log('开始初始化') | ||||
|     amrInit() | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
|   if (amr.value.isPlaying()) { | ||||
|     amrStop() | ||||
|   } else { | ||||
|     amrPlay() | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const amrInit = () => { | ||||
|   amr.value = new BenzAMRRecorder() | ||||
|   console.log(amr.value) | ||||
|   console.log(props.url) | ||||
|   // 设置播放 | ||||
|   amr.value.initWithUrl(props.url).then(function () { | ||||
|     amrPlay() | ||||
|     duration.value = amr.value.getDuration() | ||||
|   }) | ||||
|   // 监听暂停 | ||||
|   amr.value.onEnded(function () { | ||||
|     playing.value = false | ||||
|   }) | ||||
| } | ||||
| const amrPlay = () => { | ||||
|   playing.value = true | ||||
|   amr.value.play() | ||||
| } | ||||
| const amrStop = () => { | ||||
|   playing.value = false | ||||
|   amr.value.stop() | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .wx-voice-div { | ||||
|   padding: 5px; | ||||
|   background-color: #eaeaea; | ||||
|   border-radius: 10px; | ||||
| } | ||||
| .amr-duration { | ||||
|   font-size: 11px; | ||||
|   margin-left: 5px; | ||||
| } | ||||
| </style> | ||||
|  | @ -1,3 +1,395 @@ | |||
| <template> | ||||
|   <span>开发中</span> | ||||
|   <content-wrap> | ||||
|     <doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" /> | ||||
| 
 | ||||
|     <!-- 搜索工作栏 --> | ||||
|     <el-form | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       size="small" | ||||
|       :inline="true" | ||||
|       v-show="showSearch" | ||||
|       label-width="68px" | ||||
|     > | ||||
|       <el-form-item label="公众号" prop="accountId"> | ||||
|         <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> | ||||
|           <el-option | ||||
|             v-for="item in accounts" | ||||
|             :key="parseInt(item.id)" | ||||
|             :label="item.name" | ||||
|             :value="parseInt(item.id)" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button> | ||||
|         <el-button :icon="Refresh" @click="resetQuery">重置</el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
| 
 | ||||
|     <!-- 列表 --> | ||||
|     <div class="waterfall" v-loading="loading"> | ||||
|       <div | ||||
|         v-show="item.content && item.content.newsItem" | ||||
|         class="waterfall-item" | ||||
|         v-for="item in list" | ||||
|         :key="item.articleId" | ||||
|       > | ||||
|         <wx-news :articles="item.content.newsItem" /> | ||||
|         <!-- 操作 --> | ||||
|         <el-row justify="center" class="ope-row"> | ||||
|           <el-button | ||||
|             type="danger" | ||||
|             :icon="Delete" | ||||
|             circle | ||||
|             @click="handleDelete(item)" | ||||
|             v-hasPermi="['mp:free-publish:delete']" | ||||
|           /> | ||||
|         </el-row> | ||||
|       </div> | ||||
|     </div> | ||||
|     <!-- 分页组件 --> | ||||
|     <pagination | ||||
|       v-show="total > 0" | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
|   </content-wrap> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts" name="freePublish"> | ||||
| import { getFreePublishPage, deleteFreePublish } from '@/api/mp/freePublish' | ||||
| import { getSimpleAccounts } from '@/api/mp/account' | ||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||
| import { Delete, Search, Refresh } from '@element-plus/icons-vue' | ||||
| 
 | ||||
| const message = useMessage() // 消息弹窗 | ||||
| 
 | ||||
| const queryParams = reactive({ | ||||
|   total: 0, // 总页数 | ||||
|   currentPage: 1, // 当前页数 | ||||
|   pageNo: 1, // 当前页数 | ||||
|   accountId: undefined, // 当前页数 | ||||
|   queryParamsSize: 10 // 每页显示多少条 | ||||
| }) | ||||
| const loading = ref(false) // 列表的加载中 | ||||
| const showSearch = ref(true) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数据 | ||||
| const accounts = ref([]) // 列表的数据 | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   // 如果没有选中公众号账号,则进行提示。 | ||||
|   if (!queryParams.accountId) { | ||||
|     message.error('未选中公众号,无法查询已发表图文') | ||||
|     return false | ||||
|   } | ||||
|   loading.value = true | ||||
|   getFreePublishPage(queryParams) | ||||
|     .then((data) => { | ||||
|       console.log(data) | ||||
|       // 将 thumbUrl 转成 picUrl,保证 wx-news 组件可以预览封面 | ||||
|       data.list.forEach((item) => { | ||||
|         console.log(item) | ||||
|         const newsItem = item.content.newsItem | ||||
|         newsItem.forEach((article) => { | ||||
|           article.picUrl = article.thumbUrl | ||||
|         }) | ||||
|       }) | ||||
|       list.value = data.list | ||||
|       total.value = data.total | ||||
|     }) | ||||
|     .finally(() => { | ||||
|       loading.value = false | ||||
|     }) | ||||
| } | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = async () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
| 
 | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = async () => { | ||||
|   queryFormRef.value.resetFields() | ||||
|   // 默认选中第一个 | ||||
|   if (accounts.value.length > 0) { | ||||
|     queryParams.accountId = accounts[0].id | ||||
|   } | ||||
|   handleQuery() | ||||
| } | ||||
| 
 | ||||
| /** 删除按钮操作 */ | ||||
| const handleDelete = async (item) => { | ||||
|   { | ||||
|     const articleId = item.articleId | ||||
|     const accountId = queryParams.accountId | ||||
|     message | ||||
|       .confirm('删除后用户将无法访问此页面,确定删除?') | ||||
|       .then(function () { | ||||
|         return deleteFreePublish(accountId, articleId) | ||||
|       }) | ||||
|       .then(() => { | ||||
|         getList() | ||||
|         message.success('删除成功') | ||||
|       }) | ||||
|       .catch(() => {}) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| onMounted(() => { | ||||
|   getSimpleAccounts().then((response) => { | ||||
|     accounts.value = response | ||||
|     // 默认选中第一个 | ||||
|     if (accounts.value.length > 0) { | ||||
|       queryParams.accountId = accounts.value[0]['id'] | ||||
|     } | ||||
|     // 加载数据 | ||||
|     getList() | ||||
|   }) | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .pagination { | ||||
|   float: right; | ||||
|   margin-right: 25px; | ||||
| } | ||||
| 
 | ||||
| .add_but { | ||||
|   padding: 10px; | ||||
| } | ||||
| 
 | ||||
| .ope-row { | ||||
|   margin-top: 5px; | ||||
|   text-align: center; | ||||
|   border-top: 1px solid #eaeaea; | ||||
|   padding-top: 5px; | ||||
| } | ||||
| 
 | ||||
| .item-name { | ||||
|   font-size: 12px; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   white-space: nowrap; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .el-upload__tip { | ||||
|   margin-left: 5px; | ||||
| } | ||||
| 
 | ||||
| /*新增图文*/ | ||||
| .left { | ||||
|   display: inline-block; | ||||
|   width: 35%; | ||||
|   vertical-align: top; | ||||
|   margin-top: 200px; | ||||
| } | ||||
| 
 | ||||
| .right { | ||||
|   display: inline-block; | ||||
|   width: 60%; | ||||
|   margin-top: -40px; | ||||
| } | ||||
| 
 | ||||
| .avatar-uploader { | ||||
|   width: 20%; | ||||
|   display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .avatar-uploader .el-upload { | ||||
|   border-radius: 6px; | ||||
|   cursor: pointer; | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
|   text-align: unset !important; | ||||
| } | ||||
| 
 | ||||
| .avatar-uploader .el-upload:hover { | ||||
|   border-color: #165dff; | ||||
| } | ||||
| 
 | ||||
| .avatar-uploader-icon { | ||||
|   border: 1px solid #d9d9d9; | ||||
|   font-size: 28px; | ||||
|   color: #8c939d; | ||||
|   width: 120px; | ||||
|   height: 120px; | ||||
|   line-height: 120px; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .avatar { | ||||
|   width: 230px; | ||||
|   height: 120px; | ||||
| } | ||||
| 
 | ||||
| .avatar1 { | ||||
|   width: 120px; | ||||
|   height: 120px; | ||||
| } | ||||
| 
 | ||||
| .digest { | ||||
|   width: 60%; | ||||
|   display: inline-block; | ||||
|   vertical-align: top; | ||||
| } | ||||
| 
 | ||||
| /*新增图文*/ | ||||
| /*瀑布流样式*/ | ||||
| .waterfall { | ||||
|   width: 100%; | ||||
|   column-gap: 10px; | ||||
|   column-count: 5; | ||||
|   margin: 0 auto; | ||||
| } | ||||
| 
 | ||||
| .waterfall-item { | ||||
|   padding: 10px; | ||||
|   margin-bottom: 10px; | ||||
|   break-inside: avoid; | ||||
|   border: 1px solid #eaeaea; | ||||
| } | ||||
| 
 | ||||
| p { | ||||
|   line-height: 30px; | ||||
| } | ||||
| 
 | ||||
| @media (min-width: 992px) and (max-width: 1300px) { | ||||
|   .waterfall { | ||||
|     column-count: 3; | ||||
|   } | ||||
|   p { | ||||
|     color: red; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (min-width: 768px) and (max-width: 991px) { | ||||
|   .waterfall { | ||||
|     column-count: 2; | ||||
|   } | ||||
|   p { | ||||
|     color: orange; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 767px) { | ||||
|   .waterfall { | ||||
|     column-count: 1; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /*瀑布流样式*/ | ||||
| .news-main { | ||||
|   background-color: #ffffff; | ||||
|   width: 100%; | ||||
|   margin: auto; | ||||
|   height: 120px; | ||||
| } | ||||
| 
 | ||||
| .news-content { | ||||
|   background-color: #acadae; | ||||
|   width: 100%; | ||||
|   height: 120px; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .news-content-title { | ||||
|   display: inline-block; | ||||
|   font-size: 15px; | ||||
|   color: #ffffff; | ||||
|   position: absolute; | ||||
|   left: 0px; | ||||
|   bottom: 0px; | ||||
|   background-color: black; | ||||
|   width: 98%; | ||||
|   padding: 1%; | ||||
|   opacity: 0.65; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   white-space: nowrap; | ||||
|   height: 25px; | ||||
| } | ||||
| 
 | ||||
| .news-main-item { | ||||
|   background-color: #ffffff; | ||||
|   padding: 5px 0px; | ||||
|   border-top: 1px solid #eaeaea; | ||||
|   width: 100%; | ||||
|   margin: auto; | ||||
| } | ||||
| 
 | ||||
| .news-content-item { | ||||
|   position: relative; | ||||
|   margin-left: -3px; | ||||
| } | ||||
| 
 | ||||
| .news-content-item-title { | ||||
|   display: inline-block; | ||||
|   font-size: 12px; | ||||
|   width: 70%; | ||||
| } | ||||
| 
 | ||||
| .news-content-item-img { | ||||
|   display: inline-block; | ||||
|   width: 25%; | ||||
|   background-color: #acadae; | ||||
| } | ||||
| 
 | ||||
| .input-tt { | ||||
|   padding: 5px; | ||||
| } | ||||
| 
 | ||||
| .activeAddNews { | ||||
|   border: 5px solid #2bb673; | ||||
| } | ||||
| 
 | ||||
| .news-main-plus { | ||||
|   width: 280px; | ||||
|   text-align: center; | ||||
|   margin: auto; | ||||
|   height: 50px; | ||||
| } | ||||
| 
 | ||||
| .icon-plus { | ||||
|   margin: 10px; | ||||
|   font-size: 25px; | ||||
| } | ||||
| 
 | ||||
| .select-item { | ||||
|   width: 60%; | ||||
|   padding: 10px; | ||||
|   margin: 0 auto 10px auto; | ||||
|   border: 1px solid #eaeaea; | ||||
| } | ||||
| 
 | ||||
| .father .child { | ||||
|   display: none; | ||||
|   text-align: center; | ||||
|   position: relative; | ||||
|   bottom: 25px; | ||||
| } | ||||
| 
 | ||||
| .father:hover .child { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| .thumb-div { | ||||
|   display: inline-block; | ||||
|   width: 30%; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .thumb-but { | ||||
|   margin: 5px; | ||||
| } | ||||
| 
 | ||||
| .material-img { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,3 +1,262 @@ | |||
| <template> | ||||
|   <span>开发中</span> | ||||
|   <ContentWrap> | ||||
|     <doc-alert title="公众号消息" url="https://doc.iocoder.cn/mp/message/" /> | ||||
| 
 | ||||
|     <!-- 搜索工作栏 --> | ||||
|     <el-form | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       size="small" | ||||
|       :inline="true" | ||||
|       v-show="showSearch" | ||||
|       label-width="68px" | ||||
|     > | ||||
|       <el-form-item label="公众号" prop="accountId"> | ||||
|         <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> | ||||
|           <el-option | ||||
|             v-for="item in accounts" | ||||
|             :key="parseInt(item.id)" | ||||
|             :label="item.name" | ||||
|             :value="parseInt(item.id)" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="消息类型" prop="type"> | ||||
|         <el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small"> | ||||
|           <el-option | ||||
|             v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" | ||||
|             :key="dict.value" | ||||
|             :label="dict.label" | ||||
|             :value="dict.value" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="用户标识" prop="openid"> | ||||
|         <el-input | ||||
|           v-model="queryParams.openid" | ||||
|           placeholder="请输入用户标识" | ||||
|           clearable | ||||
|           :v-on="handleQuery" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="创建时间" prop="createTime"> | ||||
|         <el-date-picker | ||||
|           v-model="queryParams.createTime" | ||||
|           style="width: 240px" | ||||
|           value-format="yyyy-MM-dd HH:mm:ss" | ||||
|           type="daterange" | ||||
|           range-separator="-" | ||||
|           start-placeholder="开始日期" | ||||
|           end-placeholder="结束日期" | ||||
|           :default-time="['00:00:00', '23:59:59']" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> | ||||
|         <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
| 
 | ||||
|     <!--todo 操作工具栏 --> | ||||
|     <!--    <el-row :gutter="10" class="mb8">--> | ||||
|     <!--      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />--> | ||||
|     <!--    </el-row>--> | ||||
| 
 | ||||
|     <!-- 列表 --> | ||||
|     <el-table v-loading="loading" :data="list"> | ||||
|       <el-table-column label="发送时间" align="center" prop="createTime" width="180"> | ||||
|         <template #default="scope"> | ||||
|           <span>{{ parseTime(scope.row.createTime) }}</span> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="消息类型" align="center" prop="type" width="80" /> | ||||
|       <el-table-column label="发送方" align="center" prop="sendFrom" width="80"> | ||||
|         <template #default="scope"> | ||||
|           <el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag> | ||||
|           <el-tag v-else type="info">公众号</el-tag> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="用户标识" align="center" prop="openid" width="300" /> | ||||
|       <el-table-column label="内容" prop="content"> | ||||
|         <template #default="scope"> | ||||
|           <!-- 【事件】区域 --> | ||||
|           <div v-if="scope.row.type === 'event' && scope.row.event === 'subscribe'"> | ||||
|             <el-tag type="success" size="mini">关注</el-tag> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'unsubscribe'"> | ||||
|             <el-tag type="danger" size="mini">取消关注</el-tag> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'CLICK'"> | ||||
|             <el-tag size="mini">点击菜单</el-tag>【{{ scope.row.eventKey }}】 | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'VIEW'"> | ||||
|             <el-tag size="mini">点击菜单链接</el-tag>【{{ scope.row.eventKey }}】 | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_waitmsg'"> | ||||
|             <el-tag size="mini">扫码结果</el-tag>【{{ scope.row.eventKey }}】 | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_push'"> | ||||
|             <el-tag size="mini">扫码结果</el-tag>【{{ scope.row.eventKey }}】 | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_sysphoto'"> | ||||
|             <el-tag size="mini">系统拍照发图</el-tag> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_photo_or_album'"> | ||||
|             <el-tag size="mini">拍照或者相册</el-tag> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_weixin'"> | ||||
|             <el-tag size="mini">微信相册</el-tag> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event' && scope.row.event === 'location_select'"> | ||||
|             <el-tag size="mini">选择地理位置</el-tag> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'event'"> | ||||
|             <el-tag type="danger" size="mini">未知事件类型</el-tag> | ||||
|           </div> | ||||
|           <!-- 【消息】区域 --> | ||||
|           <div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div> | ||||
|           <div v-else-if="scope.row.type === 'voice'"> | ||||
|             <wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" /> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'image'"> | ||||
|             <a target="_blank" :href="scope.row.mediaUrl"> | ||||
|               <img :src="scope.row.mediaUrl" style="width: 100px" /> | ||||
|             </a> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'video' || scope.row.type === 'shortvideo'"> | ||||
|             <wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" /> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'link'"> | ||||
|             <el-tag size="mini">链接</el-tag>: | ||||
|             <a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'location'"> | ||||
|             <wx-location | ||||
|               :label="scope.row.label" | ||||
|               :location-y="scope.row.locationY" | ||||
|               :location-x="scope.row.locationX" | ||||
|             /> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'music'"> | ||||
|             <wx-music | ||||
|               :title="scope.row.title" | ||||
|               :description="scope.row.description" | ||||
|               :thumb-media-url="scope.row.thumbMediaUrl" | ||||
|               :music-url="scope.row.musicUrl" | ||||
|               :hq-music-url="scope.row.hqMusicUrl" | ||||
|             /> | ||||
|           </div> | ||||
|           <div v-else-if="scope.row.type === 'news'"> | ||||
|             <wx-news :articles="scope.row.articles" /> | ||||
|           </div> | ||||
|           <div v-else> | ||||
|             <el-tag type="danger" size="mini">未知消息类型</el-tag> | ||||
|           </div> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> | ||||
|         <template #default="scope"> | ||||
|           <el-button | ||||
|             size="mini" | ||||
|             type="text" | ||||
|             icon="el-icon-edit" | ||||
|             @click="handleSend(scope.row)" | ||||
|             v-hasPermi="['mp:message:send']" | ||||
|             >消息 | ||||
|           </el-button> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|     <!-- 分页组件 --> | ||||
|     <pagination | ||||
|       v-show="total > 0" | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
| 
 | ||||
|     <!-- 发送消息的弹窗 --> | ||||
|     <el-dialog title="粉丝消息列表" v-model:visible="open" width="50%"> | ||||
|       <wx-msg :user-id="userId" v-if="open" /> | ||||
|     </el-dialog> | ||||
|   </ContentWrap> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts" name="MpMessage"> | ||||
| import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' | ||||
| import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' | ||||
| // import WxMsg from '@/views/mp/components/wx-msg/main.vue' | ||||
| import WxLocation from '@/views/mp/components/wx-location/main.vue' | ||||
| import WxMusic from '@/views/mp/components/wx-music/main.vue' | ||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||
| import { getMessagePage } from '@/api/mp/message' | ||||
| import { getSimpleAccounts } from '@/api/mp/account' | ||||
| import { DICT_TYPE, getDictOptions } from '@/utils/dict' | ||||
| import { parseTime } from '@/utils/formatTime' | ||||
| 
 | ||||
| // ========== CRUD 相关 ========== | ||||
| const loading = ref(false) // 遮罩层 | ||||
| const showSearch = ref(true) // 显示搜索条件 | ||||
| const total = ref(0) // 总条数 | ||||
| const list = ref([]) // 粉丝消息列表 | ||||
| const accounts = ref([]) // 公众号账号列表 | ||||
| const open = ref(false) // 是否显示弹出层 | ||||
| const userId = ref(0) // 操作的用户编号 | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
| 
 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   openid: null, | ||||
|   accountId: null, | ||||
|   type: null, | ||||
|   createTime: [] | ||||
| }) // 是否显示弹出层 | ||||
| 
 | ||||
| const getList = async () => { | ||||
|   // 如果没有选中公众号账号,则进行提示。 | ||||
|   if (!queryParams.accountId) { | ||||
|     message.error('未选中公众号,无法查询消息') | ||||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   loading.value = true | ||||
|   // 执行查询 | ||||
|   getMessagePage(queryParams).then((data) => { | ||||
|     console.log(data) | ||||
|     list.value = data.list | ||||
|     total.value = data.total | ||||
|     loading.value = false | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| const handleQuery = async () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
| const resetQuery = async () => { | ||||
|   queryFormRef.value.resetFields() | ||||
|   // 默认选中第一个 | ||||
|   if (accounts.value.length > 0) { | ||||
|     queryParams.accountId = accounts[0].id | ||||
|   } | ||||
|   handleQuery() | ||||
| } | ||||
| const handleSend = async (row) => { | ||||
|   userId.value = row.userId | ||||
|   open.value = true | ||||
| } | ||||
| onMounted(() => { | ||||
|   getSimpleAccounts().then((response) => { | ||||
|     accounts.value = response | ||||
|     // 默认选中第一个 | ||||
|     if (accounts.value.length > 0) { | ||||
|       queryParams.accountId = accounts.value[0]['id'] | ||||
|     } | ||||
|     // 加载数据 | ||||
|     getList() | ||||
|   }) | ||||
| }) | ||||
| </script> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 wuxiran
						wuxiran