Browse Source

refactor: mp/WxReply重构,拆分组件

dhb52 2 years ago
parent
commit
f8a86cbc24

+ 5 - 8
src/views/mp/components/wx-msg/main.vue

@@ -126,7 +126,7 @@
     </div>
     <div class="msg-send" v-loading="sendLoading">
       <WxReplySelect ref="replySelectRef" :objData="objData" />
-      <el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button>
+      <el-button type="success" class="send-but" @click="sendMsg">发送(S)</el-button>
     </div>
   </ContentWrap>
 </template>
@@ -231,12 +231,8 @@ const sendMsg = async () => {
   list.value = [...list.value, ...[data]]
   scrollToBottom()
 
-  //ts检查的時候会判断这个组件可能是空的,所以需要进行断言。
-  //避免 tab 的数据未清理
-  const deleteObj = replySelectRef.value?.deleteObj
-  if (deleteObj) {
-    deleteObj()
-  }
+  // 发送后清空数据
+  replySelectRef.value?.clear()
 }
 
 const loadingMore = () => {
@@ -333,6 +329,7 @@ const scrollToBottom = () => {
 
 .send-but {
   float: right;
-  margin-top: 8px !important;
+  margin-top: 8px;
+  margin-bottom: 8px;
 }
 </style>

+ 172 - 0
src/views/mp/components/wx-reply/components/TabImage.vue

@@ -0,0 +1,172 @@
+<template>
+  <el-tab-pane name="image">
+    <template #label>
+      <el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row>
+    </template>
+    <!-- 情况一:已经选择好素材、或者上传好图片 -->
+    <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" justify="center">
+        <el-button type="danger" circle @click="onDelete">
+          <Icon icon="ep:delete" />
+        </el-button>
+      </el-row>
+    </div>
+    <!-- 情况二:未做完上述操作 -->
+    <el-row v-else style="text-align: center" align="middle">
+      <!-- 选择素材 -->
+      <el-col :span="12" class="col-select">
+        <el-button type="success" @click="showDialog = true">
+          素材库选择 <Icon icon="ep:circle-check" />
+        </el-button>
+        <el-dialog
+          title="选择图片"
+          v-model="showDialog"
+          width="90%"
+          append-to-body
+          destroy-on-close
+        >
+          <WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
+        </el-dialog>
+      </el-col>
+      <!-- 文件上传 -->
+      <el-col :span="12" class="col-add">
+        <el-upload
+          :action="UPLOAD_URL"
+          :headers="HEADERS"
+          multiple
+          :limit="1"
+          :file-list="fileList"
+          :data="uploadData"
+          :before-upload="beforeImageUpload"
+          :on-success="onUploadSuccess"
+        >
+          <el-button type="primary">上传图片</el-button>
+          <template #tip>
+            <span>
+              <div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
+            </span>
+          </template>
+        </el-upload>
+      </el-col>
+    </el-row>
+  </el-tab-pane>
+</template>
+
+<script setup lang="ts">
+import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
+import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
+import type { UploadRawFile } from 'element-plus'
+import { getAccessToken } from '@/utils/auth'
+import { ObjData } from './types'
+
+const message = useMessage()
+
+const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/upload-temporary'
+const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
+
+const props = defineProps<{
+  modelValue: ObjData
+}>()
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: ObjData)
+}>()
+const objData = computed<ObjData>({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+const showDialog = ref(false)
+const fileList = ref([])
+const uploadData = reactive({
+  accountId: objData.value.accountId,
+  type: 'image',
+  title: '',
+  introduction: ''
+})
+
+const beforeImageUpload = (rawFile: UploadRawFile) =>
+  useBeforeUpload(MaterialType.Image, 2)(rawFile)
+
+const onUploadSuccess = (res: any) => {
+  if (res.code !== 0) {
+    message.error('上传出错:' + res.msg)
+    return false
+  }
+
+  // 清空上传时的各种数据
+  fileList.value = []
+  uploadData.title = ''
+  uploadData.introduction = ''
+
+  // 上传好的文件,本质是个素材,所以可以进行选中
+  selectMaterial(res.data)
+}
+
+const onDelete = () => {
+  objData.value.mediaId = null
+  objData.value.url = null
+  objData.value.name = null
+}
+
+const selectMaterial = (item) => {
+  showDialog.value = false
+
+  objData.value.type = 'image'
+  objData.value.mediaId = item.mediaId
+  objData.value.url = item.url
+  objData.value.name = item.name
+}
+</script>
+
+<style lang="scss" scoped>
+.select-item {
+  width: 280px;
+  padding: 10px;
+  margin: 0 auto 10px auto;
+  border: 1px solid #eaeaea;
+
+  .material-img {
+    width: 100%;
+  }
+
+  .item-name {
+    font-size: 12px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    text-align: center;
+
+    .item-infos {
+      width: 30%;
+      margin: auto;
+    }
+
+    .ope-row {
+      padding-top: 10px;
+      text-align: center;
+    }
+  }
+
+  .col-select {
+    border: 1px solid rgb(234, 234, 234);
+    padding: 50px 0px;
+    height: 160px;
+    width: 49.5%;
+  }
+
+  .col-add {
+    border: 1px solid rgb(234, 234, 234);
+    padding: 50px 0px;
+    height: 160px;
+    width: 49.5%;
+    float: right;
+
+    .el-upload__tip {
+      line-height: 18px;
+      text-align: center;
+    }
+  }
+}
+</style>

+ 121 - 0
src/views/mp/components/wx-reply/components/TabMusic.vue

@@ -0,0 +1,121 @@
+<template>
+  <el-tab-pane name="music">
+    <template #label>
+      <el-row align="middle"><Icon icon="ep:service" />音乐</el-row>
+    </template>
+    <el-row align="middle" justify="center">
+      <el-col :span="6">
+        <el-row align="middle" justify="center" class="thumb-div">
+          <el-col :span="24">
+            <el-row align="middle" justify="center">
+              <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl" />
+              <icon v-else icon="ep:plus" />
+            </el-row>
+            <el-row align="middle" justify="center" style="margin-top: 2%">
+              <div class="thumb-but">
+                <el-upload
+                  :action="UPLOAD_URL"
+                  :headers="HEADERS"
+                  multiple
+                  :limit="1"
+                  :file-list="fileList"
+                  :data="uploadData"
+                  :before-upload="beforeImageUpload"
+                  :on-success="onUploadSuccess"
+                >
+                  <template #trigger>
+                    <el-button type="primary" link>本地上传</el-button>
+                  </template>
+                  <el-button type="primary" link @click="showDialog = true" style="margin-left: 5px"
+                    >素材库选择
+                  </el-button>
+                </el-upload>
+              </div>
+            </el-row>
+          </el-col>
+        </el-row>
+        <el-dialog
+          title="选择图片"
+          v-model="showDialog"
+          width="80%"
+          append-to-body
+          destroy-on-close
+        >
+          <WxMaterialSelect
+            :objData="{ type: 'image', accountId: objData.accountId }"
+            @select-material="selectMaterial"
+          />
+        </el-dialog>
+      </el-col>
+      <el-col :span="18">
+        <el-input v-model="objData.title" placeholder="请输入标题" />
+        <div style="margin: 20px 0"></div>
+        <el-input v-model="objData.description" placeholder="请输入描述" />
+      </el-col>
+    </el-row>
+    <div style="margin: 20px 0"></div>
+    <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" />
+    <div style="margin: 20px 0"></div>
+    <el-input v-model="objData.hqMusicUrl" placeholder="请输入高质量音乐链接" />
+  </el-tab-pane>
+</template>
+
+<script setup lang="ts">
+import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
+import type { UploadRawFile } from 'element-plus'
+import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
+import { getAccessToken } from '@/utils/auth'
+import { ObjData } from './types'
+
+const message = useMessage()
+
+const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/upload-temporary'
+const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
+
+const props = defineProps<{
+  modelValue: ObjData
+}>()
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: ObjData)
+}>()
+const objData = computed<ObjData>({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+const showDialog = ref(false)
+const fileList = ref([])
+const uploadData = reactive({
+  accountId: objData.value.accountId,
+  type: 'thumb', // 音乐类型为thumb
+  title: '',
+  introduction: ''
+})
+
+const beforeImageUpload = (rawFile: UploadRawFile) =>
+  useBeforeUpload(MaterialType.Image, 2)(rawFile)
+
+const onUploadSuccess = (res: any) => {
+  if (res.code !== 0) {
+    message.error('上传出错:' + res.msg)
+    return false
+  }
+
+  // 清空上传时的各种数据
+  fileList.value = []
+  uploadData.title = ''
+  uploadData.introduction = ''
+
+  // 上传好的文件,本质是个素材,所以可以进行选中
+  selectMaterial(res.data)
+}
+
+const selectMaterial = (item: any) => {
+  showDialog.value = false
+
+  objData.value.thumbMediaId = item.mediaId
+  objData.value.thumbMediaUrl = item.url
+}
+</script>
+
+<style scoped></style>

+ 78 - 0
src/views/mp/components/wx-reply/components/TabNews.vue

@@ -0,0 +1,78 @@
+<template>
+  <el-tab-pane name="news">
+    <template #label>
+      <el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row>
+    </template>
+    <el-row>
+      <div class="select-item" v-if="objData.articles?.length > 0">
+        <WxNews :articles="objData.articles" />
+        <el-col class="ope-row">
+          <el-button type="danger" circle @click="onDelete">
+            <Icon icon="ep:delete" />
+          </el-button>
+        </el-col>
+      </div>
+      <!-- 选择素材 -->
+      <el-col :span="24" v-if="!objData.content">
+        <el-row style="text-align: center" align="middle">
+          <el-col :span="24">
+            <el-button type="success" @click="showDialog = true">
+              {{ newsType === NewsType.Published ? '选择已发布图文' : '选择草稿箱图文' }}
+              <icon icon="ep:circle-check" />
+            </el-button>
+          </el-col>
+        </el-row>
+      </el-col>
+      <el-dialog title="选择图文" v-model="showDialog" width="90%" append-to-body destroy-on-close>
+        <WxMaterialSelect
+          :objData="objData"
+          @select-material="selectMaterial"
+          :newsType="newsType"
+        />
+      </el-dialog>
+    </el-row>
+  </el-tab-pane>
+</template>
+
+<script setup lang="ts">
+import WxNews from '@/views/mp/components/wx-news/main.vue'
+import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
+import { ObjData, NewsType } from './types'
+
+const props = defineProps<{
+  modelValue: ObjData
+  newsType: NewsType
+}>()
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: ObjData)
+}>()
+const objData = computed<ObjData>({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+const showDialog = ref(false)
+
+const selectMaterial = (item: any) => {
+  showDialog.value = false
+  objData.value.articles = item.content.newsItem
+}
+
+const onDelete = () => {
+  objData.value.articles = []
+}
+</script>
+
+<style lang="scss" scoped>
+.select-item {
+  width: 280px;
+  padding: 10px;
+  margin: 0 auto 10px auto;
+  border: 1px solid #eaeaea;
+
+  .ope-row {
+    padding-top: 10px;
+    text-align: center;
+  }
+}
+</style>

+ 29 - 0
src/views/mp/components/wx-reply/components/TabText.vue

@@ -0,0 +1,29 @@
+<template>
+  <el-tab-pane name="text">
+    <template #label>
+      <el-row align="middle"><Icon icon="ep:document" /> 文本</el-row>
+    </template>
+    <el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="content" />
+  </el-tab-pane>
+</template>
+
+<script setup lang="ts">
+const props = defineProps<{
+  modelValue: string | null
+}>()
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: string | null)
+  (e: 'input', v: string | null)
+}>()
+
+const content = computed<string | null>({
+  get: () => props.modelValue,
+  set: (val: string | null) => {
+    emit('update:modelValue', val)
+    emit('input', val)
+  }
+})
+</script>
+
+<style scoped></style>

+ 132 - 0
src/views/mp/components/wx-reply/components/TabVideo.vue

@@ -0,0 +1,132 @@
+<template>
+  <el-tab-pane name="video">
+    <template #label>
+      <el-row align="middle"><Icon icon="ep:share" /> 视频</el-row>
+    </template>
+    <el-row>
+      <el-input v-model="objData.title" class="input-margin-bottom" placeholder="请输入标题" />
+      <el-input
+        class="input-margin-bottom"
+        v-model="objData.description"
+        placeholder="请输入描述"
+      />
+      <el-row class="ope-row" justify="center">
+        <WxVideoPlayer v-if="objData.url" :url="objData.url" />
+      </el-row>
+      <el-col>
+        <el-row style="text-align: center" align="middle">
+          <!-- 选择素材 -->
+          <el-col :span="12">
+            <el-button type="success" @click="showDialog = true">
+              素材库选择 <Icon icon="ep:circle-check" />
+            </el-button>
+            <el-dialog
+              title="选择视频"
+              v-model="showDialog"
+              width="90%"
+              append-to-body
+              destroy-on-close
+            >
+              <WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
+            </el-dialog>
+          </el-col>
+          <!-- 文件上传 -->
+          <el-col :span="12">
+            <el-upload
+              :action="UPLOAD_URL"
+              :headers="HEADERS"
+              multiple
+              :limit="1"
+              :file-list="fileList"
+              :data="uploadData"
+              :before-upload="beforeVideoUpload"
+              :on-success="onUploadSuccess"
+            >
+              <el-button type="primary">新建视频 <Icon icon="ep:upload" /></el-button>
+            </el-upload>
+          </el-col>
+        </el-row>
+      </el-col>
+    </el-row>
+  </el-tab-pane>
+</template>
+
+<script setup lang="ts">
+import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
+import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
+import type { UploadRawFile } from 'element-plus'
+import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
+import { getAccessToken } from '@/utils/auth'
+import { ObjData } from './types'
+
+const message = useMessage()
+
+const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/upload-temporary'
+const HEADERS = { Authorization: 'Bearer ' + getAccessToken() }
+
+const props = defineProps<{
+  modelValue: ObjData
+}>()
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: ObjData)
+}>()
+const objData = computed<ObjData>({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+const showDialog = ref(false)
+const fileList = ref([])
+const uploadData = reactive({
+  accountId: objData.value.accountId,
+  type: 'video',
+  title: '',
+  introduction: ''
+})
+
+const beforeVideoUpload = (rawFile: UploadRawFile) =>
+  useBeforeUpload(MaterialType.Video, 10)(rawFile)
+
+const onUploadSuccess = (res: any) => {
+  if (res.code !== 0) {
+    message.error('上传出错:' + res.msg)
+    return false
+  }
+
+  // 清空上传时的各种数据
+  fileList.value = []
+  uploadData.title = ''
+  uploadData.introduction = ''
+
+  selectMaterial(res.data)
+}
+
+/** 选择素材后设置 */
+const selectMaterial = (item: any) => {
+  showDialog.value = false
+
+  objData.value.mediaId = item.mediaId
+  objData.value.url = item.url
+  objData.value.name = item.name
+
+  // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction
+  if (item.title) {
+    objData.value.title = item.title || ''
+  }
+  if (item.introduction) {
+    objData.value.description = item.introduction || ''
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.input-margin-bottom {
+  margin-bottom: 2%;
+}
+
+.ope-row {
+  width: 100%;
+  padding-top: 10px;
+  text-align: center;
+}
+</style>

+ 162 - 0
src/views/mp/components/wx-reply/components/TabVoice.vue

@@ -0,0 +1,162 @@
+<template>
+  <el-tab-pane name="voice">
+    <template #label>
+      <el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row>
+    </template>
+    <div class="select-item2" v-if="objData.url">
+      <p class="item-name">{{ objData.name }}</p>
+      <el-row class="ope-row" justify="center">
+        <WxVoicePlayer :url="objData.url" />
+      </el-row>
+      <el-row class="ope-row" justify="center">
+        <el-button type="danger" circle @click="onDelete"><Icon icon="ep:delete" /></el-button>
+      </el-row>
+    </div>
+    <el-row v-else style="text-align: center">
+      <!-- 选择素材 -->
+      <el-col :span="12" class="col-select">
+        <el-button type="success" @click="showDialog = true">
+          素材库选择<Icon icon="ep:circle-check" />
+        </el-button>
+        <el-dialog
+          title="选择语音"
+          v-model="showDialog"
+          width="90%"
+          append-to-body
+          destroy-on-close
+        >
+          <WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
+        </el-dialog>
+      </el-col>
+      <!-- 文件上传 -->
+      <el-col :span="12" class="col-add">
+        <el-upload
+          :action="UPLOAD_URL"
+          :headers="HEADERS"
+          multiple
+          :limit="1"
+          :file-list="fileList"
+          :data="uploadData"
+          :before-upload="beforeVoiceUpload"
+          :on-success="onUploadSuccess"
+        >
+          <el-button type="primary">点击上传</el-button>
+          <template #tip>
+            <div class="el-upload__tip">
+              格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
+            </div>
+          </template>
+        </el-upload>
+      </el-col>
+    </el-row>
+  </el-tab-pane>
+</template>
+
+<script setup lang="ts">
+import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
+import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
+import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
+import type { UploadRawFile } from 'element-plus'
+import { getAccessToken } from '@/utils/auth'
+import { ObjData } from './types'
+
+const message = useMessage()
+
+const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/upload-temporary'
+const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
+
+const props = defineProps<{
+  modelValue: ObjData
+}>()
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: ObjData)
+}>()
+const objData = computed<ObjData>({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+const showDialog = ref(false)
+const fileList = ref([])
+const uploadData = reactive({
+  accountId: objData.value.accountId,
+  type: 'voice',
+  title: '',
+  introduction: ''
+})
+
+const beforeVoiceUpload = (rawFile: UploadRawFile) =>
+  useBeforeUpload(MaterialType.Voice, 10)(rawFile)
+
+const onUploadSuccess = (res: any) => {
+  if (res.code !== 0) {
+    message.error('上传出错:' + res.msg)
+    return false
+  }
+
+  // 清空上传时的各种数据
+  fileList.value = []
+  uploadData.title = ''
+  uploadData.introduction = ''
+
+  // 上传好的文件,本质是个素材,所以可以进行选中
+  selectMaterial(res.data)
+}
+
+const onDelete = () => {
+  objData.value.mediaId = null
+  objData.value.url = null
+  objData.value.name = null
+}
+
+const selectMaterial = (item: ObjData) => {
+  showDialog.value = false
+
+  objData.value.type = 'voice'
+  objData.value.mediaId = item.mediaId
+  objData.value.url = item.url
+  objData.value.name = item.name
+}
+</script>
+
+<style lang="scss" scoped>
+.select-item2 {
+  padding: 10px;
+  margin: 0 auto 10px auto;
+  border: 1px solid #eaeaea;
+
+  .item-name {
+    font-size: 12px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    text-align: center;
+
+    .ope-row {
+      width: 100%;
+      padding-top: 10px;
+      text-align: center;
+    }
+  }
+
+  .col-select {
+    border: 1px solid rgb(234, 234, 234);
+    padding: 50px 0px;
+    height: 160px;
+    width: 49.5%;
+  }
+
+  .col-add {
+    border: 1px solid rgb(234, 234, 234);
+    padding: 50px 0px;
+    height: 160px;
+    width: 49.5%;
+    float: right;
+
+    .el-upload__tip {
+      line-height: 18px;
+      text-align: center;
+    }
+  }
+}
+</style>

+ 25 - 0
src/views/mp/components/wx-reply/components/types.ts

@@ -0,0 +1,25 @@
+type ReplyType = '' | 'news' | 'image' | 'voice' | 'video' | 'music' | 'text'
+
+interface ObjData {
+  accountId: number
+  type: ReplyType
+  name: string | null
+  content: string | null
+  mediaId: string | null
+  url: string | null
+  title: string | null
+  description: string | null
+  thumbMediaId: string | null
+  thumbMediaUrl: string | null
+  musicUrl: string | null
+  hqMusicUrl: string | null
+  introduction: string | null
+  articles: any[]
+}
+
+enum NewsType {
+  Published = '1',
+  Draft = '2'
+}
+
+export { ObjData, NewsType }

+ 32 - 488
src/views/mp/components/wx-reply/main.vue

@@ -8,519 +8,63 @@
   ④ 支持发送【视频】消息时,支持新建视频
 -->
 <template>
-  <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">
+  <el-tabs type="border-card" v-model="objData.type" @tab-click="onTabClick">
     <!-- 类型 1:文本 -->
-    <el-tab-pane name="text">
-      <template #label>
-        <el-row align="middle"><Icon icon="ep:document" /> 文本</el-row>
-      </template>
-      <el-input
-        type="textarea"
-        :rows="5"
-        placeholder="请输入内容"
-        v-model="objData.content"
-        @input="inputContent"
-      />
-    </el-tab-pane>
+    <TabText v-model="objData.content" />
     <!-- 类型 2:图片 -->
-    <el-tab-pane name="image">
-      <template #label>
-        <el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row>
-      </template>
-      <!-- 情况一:已经选择好素材、或者上传好图片 -->
-      <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" justify="center">
-          <el-button type="danger" circle @click="deleteObj">
-            <Icon icon="ep:delete" />
-          </el-button>
-        </el-row>
-      </div>
-      <!-- 情况二:未做完上述操作 -->
-      <el-row v-else style="text-align: center" align="middle">
-        <!-- 选择素材 -->
-        <el-col :span="12" class="col-select">
-          <el-button type="success" @click="openMaterial">
-            素材库选择 <Icon icon="ep:circle-check" />
-          </el-button>
-          <el-dialog title="选择图片" v-model="dialogState.image" width="90%" append-to-body>
-            <WxMaterialSelect :obj-data="objData" @select-material="selectMaterial" />
-          </el-dialog>
-        </el-col>
-        <!-- 文件上传 -->
-        <el-col :span="12" class="col-add">
-          <el-upload
-            :action="UPLOAD_URL"
-            :headers="HEADERS"
-            multiple
-            :limit="1"
-            :file-list="fileList"
-            :data="uploadData"
-            :before-upload="beforeImageUpload"
-            :on-success="onUploadSuccess"
-          >
-            <el-button type="primary">上传图片</el-button>
-            <template #tip>
-              <span>
-                <div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
-              </span>
-            </template>
-          </el-upload>
-        </el-col>
-      </el-row>
-    </el-tab-pane>
+    <TabImage v-model="objData" />
     <!-- 类型 3:语音 -->
-    <el-tab-pane name="voice">
-      <template #label>
-        <el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row>
-      </template>
-      <div class="select-item2" v-if="objData.url">
-        <p class="item-name">{{ objData.name }}</p>
-        <div class="item-infos">
-          <WxVoicePlayer :url="objData.url" />
-        </div>
-        <el-row class="ope-row" justify="center">
-          <el-button type="danger" circle @click="deleteObj"><Icon icon="ep:delete" /></el-button>
-        </el-row>
-      </div>
-      <el-row v-else style="text-align: center">
-        <!-- 选择素材 -->
-        <el-col :span="12" class="col-select">
-          <el-button type="success" @click="openMaterial">
-            素材库选择<Icon icon="ep:circle-check" />
-          </el-button>
-          <el-dialog title="选择语音" v-model="dialogState.voice" width="90%" append-to-body>
-            <WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
-          </el-dialog>
-        </el-col>
-        <!-- 文件上传 -->
-        <el-col :span="12" class="col-add">
-          <el-upload
-            :action="UPLOAD_URL"
-            :headers="HEADERS"
-            multiple
-            :limit="1"
-            :file-list="fileList"
-            :data="uploadData"
-            :before-upload="beforeVoiceUpload"
-            :on-success="onUploadSuccess"
-          >
-            <el-button type="primary">点击上传</el-button>
-            <template #tip>
-              <div class="el-upload__tip">
-                格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
-              </div>
-            </template>
-          </el-upload>
-        </el-col>
-      </el-row>
-    </el-tab-pane>
+    <TabVoice v-model="objData" />
     <!-- 类型 4:视频 -->
-    <el-tab-pane name="video">
-      <template #label>
-        <el-row align="middle"><Icon icon="ep:share" /> 视频</el-row>
-      </template>
-      <el-row>
-        <el-input
-          v-model="objData.title"
-          class="input-margin-bottom"
-          placeholder="请输入标题"
-          @input="inputContent"
-        />
-        <el-input
-          class="input-margin-bottom"
-          v-model="objData.description"
-          placeholder="请输入描述"
-          @input="inputContent"
-        />
-        <el-row style="text-align: center" justify="center">
-          <WxVideoPlayer v-if="objData.url" :url="objData.url" />
-        </el-row>
-        <el-col>
-          <el-row style="text-align: center" align="middle">
-            <!-- 选择素材 -->
-            <el-col :span="12">
-              <el-button type="success" @click="openMaterial">
-                素材库选择 <Icon icon="ep:circle-check" />
-              </el-button>
-              <el-dialog title="选择视频" v-model="dialogState.video" width="90%" append-to-body>
-                <WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
-              </el-dialog>
-            </el-col>
-            <!-- 文件上传 -->
-            <el-col :span="12">
-              <el-upload
-                :action="UPLOAD_URL"
-                :headers="HEADERS"
-                multiple
-                :limit="1"
-                :file-list="fileList"
-                :data="uploadData"
-                :before-upload="beforeVideoUpload"
-                :on-success="onUploadSuccess"
-              >
-                <el-button type="primary">新建视频 <Icon icon="ep:upload" /></el-button>
-              </el-upload>
-            </el-col>
-          </el-row>
-        </el-col>
-      </el-row>
-    </el-tab-pane>
+    <TabVideo v-model="objData" />
     <!-- 类型 5:图文 -->
-    <el-tab-pane name="news">
-      <template #label>
-        <el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row>
-      </template>
-      <el-row>
-        <div class="select-item" v-if="objData.articles?.length > 0">
-          <WxNews :articles="objData.articles" />
-          <el-col class="ope-row">
-            <el-button type="danger" circle @click="deleteObj">
-              <Icon icon="ep:delete" />
-            </el-button>
-          </el-col>
-        </div>
-        <!-- 选择素材 -->
-        <el-col :span="24" v-if="!objData.content">
-          <el-row style="text-align: center" align="middle">
-            <el-col :span="24">
-              <el-button type="success" @click="openMaterial">
-                {{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' }}
-                <icon icon="ep:circle-check" />
-              </el-button>
-            </el-col>
-          </el-row>
-        </el-col>
-        <el-dialog title="选择图文" v-model="dialogState.news" width="90%" append-to-body>
-          <WxMaterialSelect
-            :obj-data="objData"
-            @select-material="selectMaterial"
-            :newsType="newsType"
-          />
-        </el-dialog>
-      </el-row>
-    </el-tab-pane>
+    <TabNews v-model="objData" :news-type="newsType" />
     <!-- 类型 6:音乐 -->
-    <el-tab-pane name="music">
-      <template #label>
-        <el-row align="middle"><Icon icon="ep:service" />音乐</el-row>
-      </template>
-      <el-row align="middle" justify="center">
-        <el-col :span="6">
-          <el-row align="middle" justify="center" class="thumb-div">
-            <el-col :span="24">
-              <el-row align="middle" justify="center">
-                <img
-                  style="width: 100px"
-                  v-if="objData.thumbMediaUrl"
-                  :src="objData.thumbMediaUrl"
-                />
-                <icon v-else icon="ep:plus" />
-              </el-row>
-              <el-row align="middle" justify="center" style="margin-top: 2%">
-                <div class="thumb-but">
-                  <el-upload
-                    :action="UPLOAD_URL"
-                    :headers="HEADERS"
-                    multiple
-                    :limit="1"
-                    :file-list="fileList"
-                    :data="uploadData"
-                    :before-upload="beforeImageUpload"
-                    :on-success="onUploadSuccess"
-                  >
-                    <template #trigger>
-                      <el-button type="primary" link>本地上传</el-button>
-                    </template>
-                    <el-button type="primary" link @click="openMaterial" style="margin-left: 5px"
-                      >素材库选择
-                    </el-button>
-                  </el-upload>
-                </div>
-              </el-row>
-            </el-col>
-          </el-row>
-          <el-dialog title="选择图片" v-model="dialogState.thumb" width="80%" append-to-body>
-            <WxMaterialSelect
-              :objData="{ type: 'image', accountId: objData.accountId }"
-              @select-material="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>
+    <TabMusic v-model="objData" />
   </el-tabs>
 </template>
-<script setup lang="ts" name="WxReplySelect">
-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'
-import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
-import type { UploadRawFile } from 'element-plus'
 
-const props = defineProps({
-  objData: {
-    // 消息对象。
-    type: Object, // 设置为 Object 的原因,方便属性的传递
-    required: true
-  },
-  newsType: {
-    // 图文类型:1、已发布图文;2、草稿箱图文
-    type: String,
-    default: '1'
-  }
+<script setup lang="ts" name="WxReplySelect">
+import { ObjData, NewsType } from './components/types'
+import TabText from './components/TabText.vue'
+import TabImage from './components/TabImage.vue'
+import TabVoice from './components/TabVoice.vue'
+import TabVideo from './components/TabVideo.vue'
+import TabNews from './components/TabNews.vue'
+import TabMusic from './components/TabMusic.vue'
+
+interface Props {
+  objData: ObjData
+  newsType?: NewsType
+}
+const props = withDefaults(defineProps<Props>(), {
+  newsType: () => NewsType.Published
 })
 
 const objData = reactive(props.objData)
-const message = useMessage() // 消息弹窗
-const tempObj = new Map().set(objData.type, Object.assign({}, objData))
-
-// ========== 素材选择的弹窗,是否可见 ==========
-type DialogName = 'news' | 'image' | 'voice' | 'video' | 'thumb'
-type DialogState = {
-  [prop in DialogName]: boolean
-}
-const dialogState: DialogState = reactive({
-  news: false,
-  image: false,
-  voice: false,
-  video: false,
-  thumb: false
-})
-
-// ========== 文件上传(图片、语音、视频) ==========
-const fileList = ref([])
-const uploadData = reactive({
-  accountId: objData.accountId,
-  type: objData.type,
-  title: '',
-  introduction: ''
-})
+// const tempObj = new Map().set(objData.type, Object.assign({}, objData))
 
-const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/upload-temporary'
-const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
-
-const beforeImageUpload = (rawFile: UploadRawFile) =>
-  useBeforeUpload(MaterialType.Image, 2)(rawFile)
-const beforeVoiceUpload = (rawFile: UploadRawFile) =>
-  useBeforeUpload(MaterialType.Voice, 10)(rawFile)
-const beforeVideoUpload = (rawFile: UploadRawFile) =>
-  useBeforeUpload(MaterialType.Video, 10)(rawFile)
-
-const onUploadSuccess = (res: any) => {
-  if (res.code !== 0) {
-    message.error('上传出错:' + res.msg)
-    return false
-  }
-
-  // 清空上传时的各种数据
-  fileList.value = []
-  uploadData.title = ''
-  uploadData.introduction = ''
-
-  // 上传好的文件,本质是个素材,所以可以进行选中
-  selectMaterial(res.data)
+/** 切换消息类型的 tab */
+const onTabClick = () => {
+  clear()
 }
 
-/**
- * 切换消息类型的 tab
- *
- * @param tab tab  没用 暂时删了tab
- */
-const handleClick = () => {
-  // 设置后续文件上传的文件类型
-  uploadData.type = objData.type
-  if (uploadData.type === 'music') {
-    // 【音乐】上传的是缩略图
-    uploadData.type = 'thumb'
-  }
-
-  // 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData
-  let tempObjItem = tempObj.get(objData.type)
-  if (tempObjItem) {
-    objData.content = tempObjItem.content ? tempObjItem.content : null
-    objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null
-    objData.url = tempObjItem.url ? tempObjItem.url : null
-    objData.name = tempObjItem.url ? tempObjItem.name : null
-    objData.title = tempObjItem.title ? tempObjItem.title : null
-    objData.description = tempObjItem.description ? tempObjItem.description : null
-    return
-  }
-  // 如果获取不到,需要把 objData 复原
-  // 必须使用 $set 赋值,不然 input 无法输入内容
+/** 清除除了`type`的字段 */
+const clear = () => {
   objData.content = ''
   objData.mediaId = ''
   objData.url = ''
   objData.title = ''
   objData.description = ''
+  objData.articles = []
 }
 
-/**
- * 选择素材,将设置设置到 objData 变量
- *
- * @param item 素材
- */
-const selectMaterial = (item) => {
-  // 选择好素材,所以隐藏弹窗
-  closeMaterial()
-
-  // 创建 tempObjItem 对象,并设置对应的值
-  let tempObjItem = {
-    type: '',
-    articles: [],
-    thumbMediaId: '',
-    thumbMediaUrl: '',
-    introduction: '',
-    title: '',
-    musicUrl: '',
-    hqMusicUrl: '',
-    mediaId: '',
-    url: '',
-    name: '',
-    description: ''
-  }
-  tempObjItem.type = objData.type
-  if (objData.type === 'news') {
-    tempObjItem.articles = item.content.newsItem
-    objData.articles = item.content.newsItem
-  } else if (objData.type === 'music') {
-    // 音乐需要特殊处理,因为选择的是图片的缩略图
-    tempObjItem.thumbMediaId = item.mediaId
-    objData.thumbMediaId = item.mediaId
-    tempObjItem.thumbMediaUrl = item.url
-    objData.thumbMediaUrl = item.url
-    // title、introduction、musicUrl、hqMusicUrl:从 objData 到 tempObjItem,避免上传素材后,被覆盖掉
-    tempObjItem.title = objData.title || ''
-    tempObjItem.introduction = objData.introduction || ''
-    tempObjItem.musicUrl = objData.musicUrl || ''
-    tempObjItem.hqMusicUrl = objData.hqMusicUrl || ''
-  } else if (objData.type === 'image' || objData.type === 'voice') {
-    tempObjItem.mediaId = item.mediaId
-    objData.mediaId = item.mediaId
-    tempObjItem.url = item.url
-    objData.url = item.url
-    tempObjItem.name = item.name
-    objData.name = item.name
-  } else if (objData.type === 'video') {
-    tempObjItem.mediaId = item.mediaId
-    objData.mediaId = item.mediaId
-    tempObjItem.url = item.url
-    objData.url = item.url
-    tempObjItem.name = item.name
-    objData.name = item.name
-    // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction
-    if (item.title) {
-      objData.title = item.title || ''
-      tempObjItem.title = item.title || ''
-    }
-    if (item.introduction) {
-      objData.description = item.introduction || '' // 消息使用的是 description,素材使用的是 introduction,所以转换下
-      tempObjItem.description = item.introduction || ''
-    }
-  } else if (objData.type === 'text') {
-    objData.content = item.content || ''
-  }
-  // 最终设置到临时缓存
-  tempObj.set(objData.type, tempObjItem)
-}
-
-const openMaterial = () => {
-  // if (objData.type === 'news') {
-  //   dialogNewsVisible.value = true
-  // } else if (objData.type === 'image') {
-  //   dialogImageVisible.value = true
-  // } else if (objData.type === 'voice') {
-  //   dialogVoiceVisible.value = true
-  // } else if (objData.type === 'video') {
-  //   dialogVideoVisible.value = true
-  // } else if (objData.type === 'music') {
-  //   dialogThumbVisible.value = true
-  // }
-  dialogState[objData.type] = true
-}
-
-const closeMaterial = () => {
-  // dialogNewsVisible.value = false
-  // dialogImageVisible.value = false
-  // dialogVoiceVisible.value = false
-  // dialogVideoVisible.value = false
-  // dialogThumbVisible.value = false
-  for (const key of ['news', 'image', 'voice', 'video', 'thumb']) {
-    dialogState[key] = false
-  }
-}
-
-const deleteObj = () => {
-  if (objData.type === 'news') {
-    objData.articles = []
-  } else if (objData.type === 'image') {
-    objData.mediaId = null
-    objData.url = null
-    objData.name = null
-  } else if (objData.type === 'voice') {
-    objData.mediaId = null
-    objData.url = null
-    objData.name = null
-  } else if (objData.type === 'video') {
-    objData.mediaId = null
-    objData.url = null
-    objData.name = null
-    objData.title = null
-    objData.description = null
-  } else if (objData.type === 'music') {
-    objData.thumbMediaId = null
-    objData.thumbMediaUrl = null
-    objData.title = null
-    objData.description = null
-    objData.musicUrl = null
-    objData.hqMusicUrl = null
-  } else if (objData.type === 'text') {
-    objData.content = null
-  }
-  // 覆盖缓存
-  tempObj.set(objData.type, Object.assign({}, objData))
-}
-
-/**
- * 输入时,缓存每次 objData 到 tempObj 中
- *
- * why?不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式
- */
-const inputContent = () => {
-  // 覆盖缓存
-  tempObj.set(objData.type, Object.assign({}, objData))
-}
+defineExpose({
+  clear
+})
 </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;

+ 30 - 44
src/views/mp/components/wx-video-play/main.vue

@@ -14,26 +14,24 @@
   <div @click="playVideo()">
     <!-- 提示 -->
     <div>
-      <Icon icon="ep:video-play" class="mr-5px" />
-      <p>点击播放视频</p>
+      <Icon icon="ep:video-play" :size="32" class="mr-5px" />
+      <p class="text-sm">点击播放视频</p>
     </div>
 
     <!-- 弹窗播放 -->
-    <el-dialog v-model="dialogVideo" title="视频播放" width="40%" append-to-body>
-      <template #footer>
-        <video-player
-          v-if="dialogVideo"
-          class="video-player vjs-big-play-centered"
-          :src="url"
-          poster=""
-          crossorigin="anonymous"
-          playsinline
-          controls
-          :volume="0.6"
-          :height="320"
-          :playback-rates="[0.7, 1.0, 1.5, 2.0]"
-        />
-      </template>
+    <el-dialog v-model="dialogVideo" title="视频播放" append-to-body>
+      <video-player
+        v-if="dialogVideo"
+        class="video-player vjs-big-play-centered"
+        :src="props.url"
+        poster=""
+        crossorigin="anonymous"
+        playsinline
+        controls
+        :volume="0.6"
+        :width="800"
+        :playback-rates="[0.7, 1.0, 1.5, 2.0]"
+      />
       <!--     事件,暫時沒用
       @mounted="handleMounted"-->
       <!--        @ready="handleEvent($event)"-->
@@ -50,36 +48,24 @@
   </div>
 </template>
 
-<script lang="ts" name="WxVideoPlayer">
-//升级videojs6.0版本,重寫6.0版本
+<script setup lang="ts" name="WxVideoPlayer">
 import 'video.js/dist/video-js.css'
-import { defineComponent } from 'vue'
 import { VideoPlayer } from '@videojs-player/vue'
-import 'video.js/dist/video-js.css'
-
-export default defineComponent({
-  components: {
-    VideoPlayer
-  },
-  props: {
-    url: {
-      // 视频地址,例如说:https://vjs.zencdn.net/v/oceans.mp4
-      type: String,
-      required: true
-    }
-  },
-  setup() {
-    // const videoPlayerRef = ref(null)
-    const dialogVideo = ref(false)
-
-    const handleEvent = (log) => {
-      console.log('Basic player event', log)
-    }
-    const playVideo = () => {
-      dialogVideo.value = true
-    }
 
-    return { handleEvent, playVideo, dialogVideo }
+const props = defineProps({
+  url: {
+    type: String,
+    required: true
   }
 })
+
+const dialogVideo = ref(false)
+
+// const handleEvent = (log) => {
+//   console.log('Basic player event', log)
+// }
+
+const playVideo = () => {
+  dialogVideo.value = true
+}
 </script>

+ 8 - 3
src/views/mp/components/wx-voice-play/main.vue

@@ -12,8 +12,8 @@
 <template>
   <div class="wx-voice-div" @click="playVoice">
     <el-icon>
-      <Icon v-if="playing !== true" icon="ep:video-play" />
-      <Icon v-else icon="ep:video-pause" />
+      <Icon v-if="playing !== true" icon="ep:video-play" :size="32" />
+      <Icon v-else icon="ep:video-pause" :size="32" />
       <span class="amr-duration" v-if="duration">{{ duration }} 秒</span>
     </el-icon>
     <div v-if="content">
@@ -25,7 +25,6 @@
 
 <script setup lang="ts" name="WxVoicePlayer">
 // 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder
-
 import BenzAMRRecorder from 'benz-amr-recorder'
 
 const props = defineProps({
@@ -90,6 +89,12 @@ const amrStop = () => {
   padding: 5px;
   background-color: #eaeaea;
   border-radius: 10px;
+  width: 40px;
+  height: 40px;
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 .amr-duration {
   font-size: 11px;

+ 1 - 1
src/views/mp/menu/index.vue

@@ -333,7 +333,7 @@ div {
 }
 
 .public-account-management {
-  // width: 1200px;
+  width: 1200px;
   // min-width: 1200px;
   margin: 0 auto;