From 1134921a0ab6364dec2366999f54d67d729cfc04 Mon Sep 17 00:00:00 2001
From: wuxiran <wuxiran@outlook.com>
Date: Sun, 26 Mar 2023 04:25:34 +0800
Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=BE=AE=E4=BF=A1=E7=BB=84=E4=BB=B6?=
 =?UTF-8?q?=E6=9B=B4=E6=96=B0vue3=EF=BC=8C=E9=83=A8=E5=88=86=E5=8A=9F?=
 =?UTF-8?q?=E8=83=BD=E5=8F=AF=E8=83=BD=E8=BF=98=E6=9C=89=E9=97=AE=E9=A2=98?=
 =?UTF-8?q?=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/utils/dict.ts                             |  23 +-
 src/utils/formatTime.ts                       |  57 +-
 src/views/mp/components/img.png               | Bin 0 -> 15404 bytes
 src/views/mp/components/wx-location/main.vue  |  72 ++
 src/views/mp/components/wx-msg/card.scss      | 101 +++
 src/views/mp/components/wx-msg/comment.scss   |  88 +++
 src/views/mp/components/wx-msg/main.vue       | 338 ++++++++++
 src/views/mp/components/wx-music/main.vue     |  60 ++
 src/views/mp/components/wx-news/main.vue      | 107 +++
 src/views/mp/components/wx-reply/main.vue     | 634 ++++++++++++++++++
 .../mp/components/wx-video-play/main.vue      | 117 ++++
 .../mp/components/wx-voice-play/main.vue      | 100 +++
 src/views/mp/freePublish/index.vue            | 394 ++++++++++-
 src/views/mp/message/index.vue                | 261 ++++++-
 14 files changed, 2348 insertions(+), 4 deletions(-)
 create mode 100644 src/views/mp/components/img.png
 create mode 100644 src/views/mp/components/wx-location/main.vue
 create mode 100644 src/views/mp/components/wx-msg/card.scss
 create mode 100644 src/views/mp/components/wx-msg/comment.scss
 create mode 100644 src/views/mp/components/wx-msg/main.vue
 create mode 100644 src/views/mp/components/wx-music/main.vue
 create mode 100644 src/views/mp/components/wx-news/main.vue
 create mode 100644 src/views/mp/components/wx-reply/main.vue
 create mode 100644 src/views/mp/components/wx-video-play/main.vue
 create mode 100644 src/views/mp/components/wx-voice-play/main.vue

diff --git a/src/utils/dict.ts b/src/utils/dict.ts
index 15e57ff2..05c70dad 100644
--- a/src/utils/dict.ts
+++ b/src/utils/dict.ts
@@ -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' // 消息类型
 }
diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts
index 2582beee..ec7f3744 100644
--- a/src/utils/formatTime.ts
+++ b/src/utils/formatTime.ts
@@ -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 当前传入的日期值
diff --git a/src/views/mp/components/img.png b/src/views/mp/components/img.png
new file mode 100644
index 0000000000000000000000000000000000000000..c25a6e762f3a84c5a7f05c6383de843937725fcf
GIT binary patch
literal 15404
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
D*J2vT

literal 0
HcmV?d00001

diff --git a/src/views/mp/components/wx-location/main.vue b/src/views/mp/components/wx-location/main.vue
new file mode 100644
index 00000000..c0d67e29
--- /dev/null
+++ b/src/views/mp/components/wx-location/main.vue
@@ -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>
diff --git a/src/views/mp/components/wx-msg/card.scss b/src/views/mp/components/wx-msg/card.scss
new file mode 100644
index 00000000..67ac9219
--- /dev/null
+++ b/src/views/mp/components/wx-msg/card.scss
@@ -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;
+}
diff --git a/src/views/mp/components/wx-msg/comment.scss b/src/views/mp/components/wx-msg/comment.scss
new file mode 100644
index 00000000..3f1341b2
--- /dev/null
+++ b/src/views/mp/components/wx-msg/comment.scss
@@ -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;
+  }
+}
diff --git a/src/views/mp/components/wx-msg/main.vue b/src/views/mp/components/wx-msg/main.vue
new file mode 100644
index 00000000..b514a73e
--- /dev/null
+++ b/src/views/mp/components/wx-msg/main.vue
@@ -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>
diff --git a/src/views/mp/components/wx-music/main.vue b/src/views/mp/components/wx-music/main.vue
new file mode 100644
index 00000000..52555f15
--- /dev/null
+++ b/src/views/mp/components/wx-music/main.vue
@@ -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>
diff --git a/src/views/mp/components/wx-news/main.vue b/src/views/mp/components/wx-news/main.vue
new file mode 100644
index 00000000..d08e2813
--- /dev/null
+++ b/src/views/mp/components/wx-news/main.vue
@@ -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>
diff --git a/src/views/mp/components/wx-reply/main.vue b/src/views/mp/components/wx-reply/main.vue
new file mode 100644
index 00000000..57a3cd84
--- /dev/null
+++ b/src/views/mp/components/wx-reply/main.vue
@@ -0,0 +1,634 @@
+<!--&lt;!&ndash;-->
+<!--  - Copyright (C) 2018-2019-->
+<!--  - All rights reserved, Designed By www.joolun.com-->
+<!--  芋道源码:-->
+<!--  ① 移除多余的 rep 为前缀的变量,让 message 消息更简单-->
+<!--  ② 代码优化,补充注释,提升阅读性-->
+<!--  ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入-->
+<!--  ④ 支持发送【视频】消息时,支持新建视频-->
+<!--&ndash;&gt;-->
+<!--<template>-->
+<!--  <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">-->
+<!--    &lt;!&ndash; 类型 1:文本 &ndash;&gt;-->
+<!--    <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>-->
+<!--    &lt;!&ndash; 类型 2:图片 &ndash;&gt;-->
+<!--    <el-tab-pane name="image">-->
+<!--      <span slot="label"><i class="el-icon-picture"></i> 图片</span>-->
+<!--      <el-row>-->
+<!--        &lt;!&ndash; 情况一:已经选择好素材、或者上传好图片 &ndash;&gt;-->
+<!--        <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>-->
+<!--        &lt;!&ndash; 情况二:未做完上述操作 &ndash;&gt;-->
+<!--        <div v-else>-->
+<!--          <el-row style="text-align: center">-->
+<!--            &lt;!&ndash; 选择素材 &ndash;&gt;-->
+<!--            <el-col :span="12" class="col-select">-->
+<!--              <el-button type="success" @click="openMaterial">-->
+<!--                素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
+<!--            &lt;!&ndash; 文件上传 &ndash;&gt;-->
+<!--            <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>-->
+<!--    &lt;!&ndash; 类型 3:语音 &ndash;&gt;-->
+<!--    <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">-->
+<!--            &lt;!&ndash; 选择素材 &ndash;&gt;-->
+<!--            <el-col :span="12" class="col-select">-->
+<!--              <el-button type="success" @click="openMaterial">-->
+<!--                素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
+<!--            &lt;!&ndash; 文件上传 &ndash;&gt;-->
+<!--            <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>-->
+<!--    &lt;!&ndash; 类型 4:视频 &ndash;&gt;-->
+<!--    <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">-->
+<!--          &lt;!&ndash; 选择素材 &ndash;&gt;-->
+<!--          <el-col :span="12">-->
+<!--            <el-button type="success" @click="openMaterial">-->
+<!--              素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
+<!--          &lt;!&ndash; 文件上传 &ndash;&gt;-->
+<!--          <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&#45;&#45;right"></i-->
+<!--              ></el-button>-->
+<!--            </el-upload>-->
+<!--          </el-col>-->
+<!--        </el-row>-->
+<!--      </el-row>-->
+<!--    </el-tab-pane>-->
+<!--    &lt;!&ndash; 类型 5:图文 &ndash;&gt;-->
+<!--    <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>-->
+<!--        &lt;!&ndash; 选择素材 &ndash;&gt;-->
+<!--        <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&#45;&#45;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>-->
+<!--    &lt;!&ndash; 类型 6:音乐 &ndash;&gt;-->
+<!--    <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>-->
diff --git a/src/views/mp/components/wx-video-play/main.vue b/src/views/mp/components/wx-video-play/main.vue
new file mode 100644
index 00000000..880d10f8
--- /dev/null
+++ b/src/views/mp/components/wx-video-play/main.vue
@@ -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>
diff --git a/src/views/mp/components/wx-voice-play/main.vue b/src/views/mp/components/wx-voice-play/main.vue
new file mode 100644
index 00000000..f98ac681
--- /dev/null
+++ b/src/views/mp/components/wx-voice-play/main.vue
@@ -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>
diff --git a/src/views/mp/freePublish/index.vue b/src/views/mp/freePublish/index.vue
index 497f72ec..1d9b331e 100644
--- a/src/views/mp/freePublish/index.vue
+++ b/src/views/mp/freePublish/index.vue
@@ -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>
diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue
index 497f72ec..34e64ebf 100644
--- a/src/views/mp/message/index.vue
+++ b/src/views/mp/message/index.vue
@@ -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>