mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-11-04 20:28:45 +08:00 
			
		
		
		
	refactor: mp模块,一个大大的重构+fix
This commit is contained in:
		@@ -94,10 +94,10 @@
 | 
			
		||||
  </el-table>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
 | 
			
		||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
 | 
			
		||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play'
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
 | 
			
		||||
import WxMusic from '@/views/mp/components/wx-music'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news'
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
import { DICT_TYPE } from '@/utils/dict'
 | 
			
		||||
import { MsgType } from './types'
 | 
			
		||||
 
 | 
			
		||||
@@ -5,44 +5,3 @@ export enum MsgType {
 | 
			
		||||
  Message = 2,
 | 
			
		||||
  Keyword = 3
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ReplyType = 'text' | 'image' | 'voice' | 'video' | 'shortvideo' | 'location' | 'link'
 | 
			
		||||
 | 
			
		||||
export interface ReplyForm {
 | 
			
		||||
  // relation:
 | 
			
		||||
  id?: number
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  type?: MsgType
 | 
			
		||||
  // request:
 | 
			
		||||
  requestMessageType?: ReplyType
 | 
			
		||||
  requestMatch?: number
 | 
			
		||||
  requestKeyword?: string
 | 
			
		||||
  // response:
 | 
			
		||||
  responseMessageType?: ReplyType
 | 
			
		||||
  responseContent?: string
 | 
			
		||||
  responseMediaId?: number
 | 
			
		||||
  responseMediaUrl?: string
 | 
			
		||||
  responseTitle?: string
 | 
			
		||||
  responseDescription?: number
 | 
			
		||||
  responseThumbMediaId?: string
 | 
			
		||||
  responseThumbMediaUrl?: string
 | 
			
		||||
  responseArticles?: any[]
 | 
			
		||||
  responseMusicUrl?: string
 | 
			
		||||
  responseHqMusicUrl?: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO @Dhb52:ObjData 这个类名可以在看看,ObjData 有点通用
 | 
			
		||||
export interface ObjData {
 | 
			
		||||
  type: ReplyType
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  content?: string
 | 
			
		||||
  mediaId?: number
 | 
			
		||||
  url?: string
 | 
			
		||||
  title?: string
 | 
			
		||||
  description?: string
 | 
			
		||||
  thumbMediaId?: number
 | 
			
		||||
  thumbMediaUrl?: string
 | 
			
		||||
  articles?: any[]
 | 
			
		||||
  musicUrl?: string
 | 
			
		||||
  hqMusicUrl?: string
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,7 @@
 | 
			
		||||
          <el-input v-model="replyForm.requestKeyword" placeholder="请输入内容" clearable />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="回复消息">
 | 
			
		||||
          <WxReplySelect :objData="objData" />
 | 
			
		||||
          <WxReplySelect v-model="reply" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
      <template #footer>
 | 
			
		||||
@@ -93,14 +93,14 @@
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts" name="MpAutoReply">
 | 
			
		||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 | 
			
		||||
import WxReplySelect, { type Reply, ReplyType } from '@/views/mp/components/wx-reply'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select'
 | 
			
		||||
import * as MpAutoReplyApi from '@/api/mp/autoReply'
 | 
			
		||||
import { DICT_TYPE, getDictOptions, getIntDictOptions } from '@/utils/dict'
 | 
			
		||||
import { ContentWrap } from '@/components/ContentWrap'
 | 
			
		||||
import type { TabPaneName } from 'element-plus'
 | 
			
		||||
import type { FormInstance, TabPaneName } from 'element-plus'
 | 
			
		||||
import ReplyTable from './components/ReplyTable.vue'
 | 
			
		||||
import { MsgType, ReplyForm, ObjData } from './components/types'
 | 
			
		||||
import { MsgType } from './components/types'
 | 
			
		||||
const message = useMessage() // 消息
 | 
			
		||||
 | 
			
		||||
const msgType = ref<MsgType>(MsgType.Keyword) // 消息类型
 | 
			
		||||
@@ -108,26 +108,26 @@ const RequestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'l
 | 
			
		||||
const loading = ref(true) // 遮罩层
 | 
			
		||||
const total = ref(0) // 总条数
 | 
			
		||||
const list = ref<any[]>([]) // 自动回复列表
 | 
			
		||||
const formRef = ref() // 表单 ref
 | 
			
		||||
const formRef = ref<FormInstance | null>(null) // 表单 ref
 | 
			
		||||
// 查询参数
 | 
			
		||||
interface QueryParams {
 | 
			
		||||
  pageNo: number
 | 
			
		||||
  pageSize: number
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  accountId: number
 | 
			
		||||
}
 | 
			
		||||
const queryParams: QueryParams = reactive({
 | 
			
		||||
  pageNo: 1,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  accountId: undefined
 | 
			
		||||
  accountId: 0
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const dialogTitle = ref('') // 弹出层标题
 | 
			
		||||
const showFormDialog = ref(false) // 是否显示弹出层
 | 
			
		||||
const replyForm = ref<ReplyForm>({}) // 表单参数
 | 
			
		||||
const replyForm = ref<any>({}) // 表单参数
 | 
			
		||||
// 回复消息
 | 
			
		||||
const objData = ref<ObjData>({
 | 
			
		||||
  type: 'text',
 | 
			
		||||
  accountId: undefined
 | 
			
		||||
const reply = ref<Reply>({
 | 
			
		||||
  type: ReplyType.Text,
 | 
			
		||||
  accountId: 0
 | 
			
		||||
})
 | 
			
		||||
// 表单校验
 | 
			
		||||
const rules = {
 | 
			
		||||
@@ -136,8 +136,9 @@ const rules = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 侦听账号变化 */
 | 
			
		||||
const onAccountChanged = (id?: number) => {
 | 
			
		||||
const onAccountChanged = (id: number) => {
 | 
			
		||||
  queryParams.accountId = id
 | 
			
		||||
  reply.value.accountId = id
 | 
			
		||||
  getList()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -171,8 +172,8 @@ const onTabChange = (tabName: TabPaneName) => {
 | 
			
		||||
const onCreate = () => {
 | 
			
		||||
  reset()
 | 
			
		||||
  // 打开表单,并设置初始化
 | 
			
		||||
  objData.value = {
 | 
			
		||||
    type: 'text',
 | 
			
		||||
  reply.value = {
 | 
			
		||||
    type: ReplyType.Text,
 | 
			
		||||
    accountId: queryParams.accountId
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -193,7 +194,7 @@ const onUpdate = async (id: number) => {
 | 
			
		||||
  delete replyForm.value['responseMediaUrl']
 | 
			
		||||
  delete replyForm.value['responseDescription']
 | 
			
		||||
  delete replyForm.value['responseArticles']
 | 
			
		||||
  objData.value = {
 | 
			
		||||
  reply.value = {
 | 
			
		||||
    type: data.responseMessageType,
 | 
			
		||||
    accountId: queryParams.accountId,
 | 
			
		||||
    content: data.responseContent,
 | 
			
		||||
@@ -227,17 +228,17 @@ const onSubmit = async () => {
 | 
			
		||||
 | 
			
		||||
  // 处理回复消息
 | 
			
		||||
  const submitForm: any = { ...replyForm.value }
 | 
			
		||||
  submitForm.responseMessageType = objData.value.type
 | 
			
		||||
  submitForm.responseContent = objData.value.content
 | 
			
		||||
  submitForm.responseMediaId = objData.value.mediaId
 | 
			
		||||
  submitForm.responseMediaUrl = objData.value.url
 | 
			
		||||
  submitForm.responseTitle = objData.value.title
 | 
			
		||||
  submitForm.responseDescription = objData.value.description
 | 
			
		||||
  submitForm.responseThumbMediaId = objData.value.thumbMediaId
 | 
			
		||||
  submitForm.responseThumbMediaUrl = objData.value.thumbMediaUrl
 | 
			
		||||
  submitForm.responseArticles = objData.value.articles
 | 
			
		||||
  submitForm.responseMusicUrl = objData.value.musicUrl
 | 
			
		||||
  submitForm.responseHqMusicUrl = objData.value.hqMusicUrl
 | 
			
		||||
  submitForm.responseMessageType = reply.value.type
 | 
			
		||||
  submitForm.responseContent = reply.value.content
 | 
			
		||||
  submitForm.responseMediaId = reply.value.mediaId
 | 
			
		||||
  submitForm.responseMediaUrl = reply.value.url
 | 
			
		||||
  submitForm.responseTitle = reply.value.title
 | 
			
		||||
  submitForm.responseDescription = reply.value.description
 | 
			
		||||
  submitForm.responseThumbMediaId = reply.value.thumbMediaId
 | 
			
		||||
  submitForm.responseThumbMediaUrl = reply.value.thumbMediaUrl
 | 
			
		||||
  submitForm.responseArticles = reply.value.articles
 | 
			
		||||
  submitForm.responseMusicUrl = reply.value.musicUrl
 | 
			
		||||
  submitForm.responseHqMusicUrl = reply.value.hqMusicUrl
 | 
			
		||||
 | 
			
		||||
  if (replyForm.value.id !== undefined) {
 | 
			
		||||
    await MpAutoReplyApi.updateAutoReply(submitForm)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/views/mp/components/wx-account-select/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/views/mp/components/wx-account-select/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import WxAccountSelect from './main.vue'
 | 
			
		||||
 | 
			
		||||
export default WxAccountSelect
 | 
			
		||||
@@ -14,7 +14,7 @@ const account: MpAccountApi.AccountVO = reactive({
 | 
			
		||||
const accountList: Ref<MpAccountApi.AccountVO[]> = ref([])
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  (e: 'change', id?: number, name?: string): void
 | 
			
		||||
  (e: 'change', id: number, name: string): void
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const handleQuery = async () => {
 | 
			
		||||
@@ -22,15 +22,19 @@ const handleQuery = async () => {
 | 
			
		||||
  // 默认选中第一个
 | 
			
		||||
  if (accountList.value.length > 0) {
 | 
			
		||||
    account.id = accountList.value[0].id
 | 
			
		||||
    account.name = accountList.value[0].name
 | 
			
		||||
    emit('change', account.id, account.name)
 | 
			
		||||
    if (account.id) {
 | 
			
		||||
      account.name = accountList.value[0].name
 | 
			
		||||
      emit('change', account.id, account.name)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onChanged = (id?: number) => {
 | 
			
		||||
  const found = accountList.value.find((v) => v.id === id)
 | 
			
		||||
  account.name = found ? found.name : ''
 | 
			
		||||
  emit('change', account.id, account.name)
 | 
			
		||||
  if (account.id) {
 | 
			
		||||
    account.name = found ? found.name : ''
 | 
			
		||||
    emit('change', account.id, account.name)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 初始化 */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/views/mp/components/wx-location/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/views/mp/components/wx-location/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import WxLocation from './main.vue'
 | 
			
		||||
 | 
			
		||||
export default WxLocation
 | 
			
		||||
							
								
								
									
										6
									
								
								src/views/mp/components/wx-material-select/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/views/mp/components/wx-material-select/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import WxMaterialSelect from './main.vue'
 | 
			
		||||
import { NewsType, MaterialType } from './types'
 | 
			
		||||
 | 
			
		||||
export { NewsType, MaterialType }
 | 
			
		||||
 | 
			
		||||
export default WxMaterialSelect
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="pb-30px">
 | 
			
		||||
    <!-- 类型:image -->
 | 
			
		||||
    <div v-if="objData.type === 'image'">
 | 
			
		||||
    <div v-if="props.type === 'image'">
 | 
			
		||||
      <div class="waterfall" v-loading="loading">
 | 
			
		||||
        <div class="waterfall-item" v-for="item in list" :key="item.mediaId">
 | 
			
		||||
          <img class="material-img" :src="item.url" />
 | 
			
		||||
@@ -29,7 +29,7 @@
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- 类型:voice -->
 | 
			
		||||
    <div v-else-if="objData.type === 'voice'">
 | 
			
		||||
    <div v-else-if="props.type === 'voice'">
 | 
			
		||||
      <!-- 列表 -->
 | 
			
		||||
      <el-table v-loading="loading" :data="list">
 | 
			
		||||
        <el-table-column label="编号" align="center" prop="mediaId" />
 | 
			
		||||
@@ -64,7 +64,7 @@
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- 类型:video -->
 | 
			
		||||
    <div v-else-if="objData.type === 'video'">
 | 
			
		||||
    <div v-else-if="props.type === 'video'">
 | 
			
		||||
      <!-- 列表 -->
 | 
			
		||||
      <el-table v-loading="loading" :data="list">
 | 
			
		||||
        <el-table-column label="编号" align="center" prop="mediaId" />
 | 
			
		||||
@@ -106,7 +106,7 @@
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- 类型:news -->
 | 
			
		||||
    <div v-else-if="objData.type === 'news'">
 | 
			
		||||
    <div v-else-if="props.type === 'news'">
 | 
			
		||||
      <div class="waterfall" v-loading="loading">
 | 
			
		||||
        <div class="waterfall-item" v-for="item in list" :key="item.mediaId">
 | 
			
		||||
          <div v-if="item.content && item.content.newsItem">
 | 
			
		||||
@@ -132,25 +132,25 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup name="WxMaterialSelect">
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news/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 WxNews from '@/views/mp/components/wx-news'
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
 | 
			
		||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play'
 | 
			
		||||
import { NewsType } from './types'
 | 
			
		||||
import * as MpMaterialApi from '@/api/mp/material'
 | 
			
		||||
import * as MpFreePublishApi from '@/api/mp/freePublish'
 | 
			
		||||
import * as MpDraftApi from '@/api/mp/draft'
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  objData: {
 | 
			
		||||
    type: Object, // type - 类型;accountId - 公众号账号编号
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  newsType: {
 | 
			
		||||
    // 图文类型:1、已发布图文;2、草稿箱图文
 | 
			
		||||
    type: String as PropType<string>,
 | 
			
		||||
    default: '1'
 | 
			
		||||
const props = withDefaults(
 | 
			
		||||
  defineProps<{
 | 
			
		||||
    type: string
 | 
			
		||||
    accountId: number
 | 
			
		||||
    newsType?: NewsType
 | 
			
		||||
  }>(),
 | 
			
		||||
  {
 | 
			
		||||
    newsType: NewsType.Published
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['select-material'])
 | 
			
		||||
 | 
			
		||||
@@ -159,15 +159,13 @@ const loading = ref(false)
 | 
			
		||||
// 总条数
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
// 数据列表
 | 
			
		||||
const list = ref([])
 | 
			
		||||
const list = ref<any[]>([])
 | 
			
		||||
// 查询参数
 | 
			
		||||
const queryParams = reactive({
 | 
			
		||||
  pageNo: 1,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  accountId: props.objData.accountId
 | 
			
		||||
  accountId: props.accountId
 | 
			
		||||
})
 | 
			
		||||
const objDataRef = reactive(props.objData)
 | 
			
		||||
const newsTypeRef = ref(props.newsType)
 | 
			
		||||
 | 
			
		||||
const selectMaterialFun = (item) => {
 | 
			
		||||
  emit('select-material', item)
 | 
			
		||||
@@ -176,10 +174,10 @@ const selectMaterialFun = (item) => {
 | 
			
		||||
const getPage = async () => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  try {
 | 
			
		||||
    if (objDataRef.type === 'news' && newsTypeRef.value === '1') {
 | 
			
		||||
    if (props.type === 'news' && props.newsType === NewsType.Published) {
 | 
			
		||||
      // 【图文】+ 【已发布】
 | 
			
		||||
      await getFreePublishPageFun()
 | 
			
		||||
    } else if (objDataRef.type === 'news' && newsTypeRef.value === '2') {
 | 
			
		||||
    } else if (props.type === 'news' && props.newsType === NewsType.Draft) {
 | 
			
		||||
      // 【图文】+ 【草稿】
 | 
			
		||||
      await getDraftPageFun()
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -194,7 +192,7 @@ const getPage = async () => {
 | 
			
		||||
const getMaterialPageFun = async () => {
 | 
			
		||||
  const data = await MpMaterialApi.getMaterialPage({
 | 
			
		||||
    ...queryParams,
 | 
			
		||||
    type: objDataRef.type
 | 
			
		||||
    type: props.type
 | 
			
		||||
  })
 | 
			
		||||
  list.value = data.list
 | 
			
		||||
  total.value = data.total
 | 
			
		||||
@@ -202,9 +200,9 @@ const getMaterialPageFun = async () => {
 | 
			
		||||
 | 
			
		||||
const getFreePublishPageFun = async () => {
 | 
			
		||||
  const data = await MpFreePublishApi.getFreePublishPage(queryParams)
 | 
			
		||||
  data.list.forEach((item) => {
 | 
			
		||||
    const newsItem = item.content.newsItem
 | 
			
		||||
    newsItem.forEach((article) => {
 | 
			
		||||
  data.list.forEach((item: any) => {
 | 
			
		||||
    const articles = item.content.newsItem
 | 
			
		||||
    articles.forEach((article: any) => {
 | 
			
		||||
      article.picUrl = article.thumbUrl
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
@@ -214,9 +212,9 @@ const getFreePublishPageFun = async () => {
 | 
			
		||||
 | 
			
		||||
const getDraftPageFun = async () => {
 | 
			
		||||
  const data = await MpDraftApi.getDraftPage(queryParams)
 | 
			
		||||
  data.list.forEach((item) => {
 | 
			
		||||
    const newsItem = item.content.newsItem
 | 
			
		||||
    newsItem.forEach((article) => {
 | 
			
		||||
  data.list.forEach((draft: any) => {
 | 
			
		||||
    const articles = draft.content.newsItem
 | 
			
		||||
    articles.forEach((article: any) => {
 | 
			
		||||
      article.picUrl = article.thumbUrl
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								src/views/mp/components/wx-material-select/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/views/mp/components/wx-material-select/types.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
export enum NewsType {
 | 
			
		||||
  Draft = '2',
 | 
			
		||||
  Published = '1'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum MaterialType {
 | 
			
		||||
  Image = 'image',
 | 
			
		||||
  Voice = 'voice',
 | 
			
		||||
  Video = 'video',
 | 
			
		||||
  News = 'news'
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/views/mp/components/wx-msg/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/views/mp/components/wx-msg/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import WxMsg from './main.vue'
 | 
			
		||||
import { MsgType } from './types'
 | 
			
		||||
 | 
			
		||||
export { MsgType }
 | 
			
		||||
 | 
			
		||||
export default WxMsg
 | 
			
		||||
@@ -125,19 +125,19 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="msg-send" v-loading="sendLoading">
 | 
			
		||||
      <WxReplySelect ref="replySelectRef" :objData="objData" />
 | 
			
		||||
      <WxReplySelect ref="replySelectRef" v-model="reply" />
 | 
			
		||||
      <el-button type="success" class="send-but" @click="sendMsg">发送(S)</el-button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="WxMsg">
 | 
			
		||||
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 WxReplySelect from '@/views/mp/components/wx-reply'
 | 
			
		||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play'
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news'
 | 
			
		||||
import WxLocation from '@/views/mp/components/wx-location'
 | 
			
		||||
import WxMusic from '@/views/mp/components/wx-music'
 | 
			
		||||
import { getMessagePage, sendMessage } from '@/api/mp/message'
 | 
			
		||||
import { getUser } from '@/api/mp/user'
 | 
			
		||||
import { formatDate } from '@/utils/formatTime'
 | 
			
		||||
@@ -187,14 +187,14 @@ const mp: Mp = reactive({
 | 
			
		||||
 | 
			
		||||
// ========= 消息发送 =========
 | 
			
		||||
const sendLoading = ref(false) // 发送消息是否加载中
 | 
			
		||||
interface ObjData {
 | 
			
		||||
interface Reply {
 | 
			
		||||
  type: MsgType
 | 
			
		||||
  accountId: number | null
 | 
			
		||||
  articles: any[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 微信发送消息
 | 
			
		||||
const objData: ObjData = reactive({
 | 
			
		||||
const reply = ref<Reply>({
 | 
			
		||||
  type: MsgType.Text,
 | 
			
		||||
  accountId: null,
 | 
			
		||||
  articles: []
 | 
			
		||||
@@ -209,23 +209,23 @@ onMounted(async () => {
 | 
			
		||||
  user.avatar = user.avatar?.length > 0 ? data.avatar : user.avatar
 | 
			
		||||
  user.accountId = data.accountId
 | 
			
		||||
  queryParams.accountId = data.accountId
 | 
			
		||||
  objData.accountId = data.accountId
 | 
			
		||||
  reply.value.accountId = data.accountId
 | 
			
		||||
 | 
			
		||||
  refreshChange()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 执行发送
 | 
			
		||||
const sendMsg = async () => {
 | 
			
		||||
  if (!objData) {
 | 
			
		||||
  if (!reply) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  // 公众号限制:客服消息,公众号只允许发送一条
 | 
			
		||||
  if (objData.type === MsgType.News && objData.articles.length > 1) {
 | 
			
		||||
    objData.articles = [objData.articles[0]]
 | 
			
		||||
  if (reply.value.type === MsgType.News && reply.value.articles.length > 1) {
 | 
			
		||||
    reply.value.articles = [reply.value.articles[0]]
 | 
			
		||||
    message.success('图文消息条数限制在 1 条以内,已默认发送第一条')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const data = await sendMessage(Object.assign({ userId: props.userId }, { ...objData }))
 | 
			
		||||
  const data = await sendMessage({ userId: props.userId, ...reply.value })
 | 
			
		||||
  sendLoading.value = false
 | 
			
		||||
 | 
			
		||||
  list.value = [...list.value, ...[data]]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/views/mp/components/wx-music/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/views/mp/components/wx-music/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import WxMusic from './main.vue'
 | 
			
		||||
 | 
			
		||||
export default WxMusic
 | 
			
		||||
							
								
								
									
										3
									
								
								src/views/mp/components/wx-news/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/views/mp/components/wx-news/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import WxNews from './main.vue'
 | 
			
		||||
 | 
			
		||||
export default WxNews
 | 
			
		||||
@@ -39,12 +39,14 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" name="WxNews" setup>
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  articles: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    default: () => null
 | 
			
		||||
const props = withDefaults(
 | 
			
		||||
  defineProps<{
 | 
			
		||||
    articles: any[] | null
 | 
			
		||||
  }>(),
 | 
			
		||||
  {
 | 
			
		||||
    articles: null
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  articles: props.articles
 | 
			
		||||
@@ -53,9 +55,9 @@ defineExpose({
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.news-home {
 | 
			
		||||
  background-color: #ffffff;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  margin: auto;
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.news-main {
 | 
			
		||||
@@ -64,29 +66,29 @@ defineExpose({
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.news-content {
 | 
			
		||||
  background-color: #acadae;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background-color: #acadae;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.news-content-title {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  color: #ffffff;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  background-color: black;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  width: 98%;
 | 
			
		||||
  padding: 1%;
 | 
			
		||||
  opacity: 0.65;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  white-space: normal;
 | 
			
		||||
  background-color: black;
 | 
			
		||||
  opacity: 0.65;
 | 
			
		||||
  box-sizing: unset !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.news-main-item {
 | 
			
		||||
  background-color: #ffffff;
 | 
			
		||||
  padding: 5px 0;
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  border-top: 1px solid #eaeaea;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -96,17 +98,17 @@ defineExpose({
 | 
			
		||||
 | 
			
		||||
.news-content-item-title {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
  width: 70%;
 | 
			
		||||
  margin-left: 1%;
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
  white-space: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.news-content-item-img {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  width: 25%;
 | 
			
		||||
  background-color: #acadae;
 | 
			
		||||
  margin-right: 1%;
 | 
			
		||||
  background-color: #acadae;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.material-img {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,9 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-tab-pane name="image">
 | 
			
		||||
    <template #label>
 | 
			
		||||
      <el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row>
 | 
			
		||||
    </template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <!-- 情况一:已经选择好素材、或者上传好图片 -->
 | 
			
		||||
    <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>
 | 
			
		||||
    <div class="select-item" v-if="reply.url">
 | 
			
		||||
      <img class="material-img" :src="reply.url" />
 | 
			
		||||
      <p class="item-name" v-if="reply.name">{{ reply.name }}</p>
 | 
			
		||||
      <el-row class="ope-row" justify="center">
 | 
			
		||||
        <el-button type="danger" circle @click="onDelete">
 | 
			
		||||
          <Icon icon="ep:delete" />
 | 
			
		||||
@@ -27,7 +24,11 @@
 | 
			
		||||
          append-to-body
 | 
			
		||||
          destroy-on-close
 | 
			
		||||
        >
 | 
			
		||||
          <WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
 | 
			
		||||
          <WxMaterialSelect
 | 
			
		||||
            type="image"
 | 
			
		||||
            :account-id="reply.accountId"
 | 
			
		||||
            @select-material="selectMaterial"
 | 
			
		||||
          />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <!-- 文件上传 -->
 | 
			
		||||
@@ -51,27 +52,27 @@
 | 
			
		||||
        </el-upload>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </el-tab-pane>
 | 
			
		||||
  </div>
 | 
			
		||||
</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 WxMaterialSelect from '@/views/mp/components/wx-material-select'
 | 
			
		||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
import type { UploadRawFile } from 'element-plus'
 | 
			
		||||
import { getAccessToken } from '@/utils/auth'
 | 
			
		||||
import { ObjData } from './types'
 | 
			
		||||
import { Reply } 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
 | 
			
		||||
  modelValue: Reply
 | 
			
		||||
}>()
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  (e: 'update:modelValue', v: ObjData)
 | 
			
		||||
  (e: 'update:modelValue', v: Reply)
 | 
			
		||||
}>()
 | 
			
		||||
const objData = computed<ObjData>({
 | 
			
		||||
const reply = computed<Reply>({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (val) => emit('update:modelValue', val)
 | 
			
		||||
})
 | 
			
		||||
@@ -79,14 +80,13 @@ const objData = computed<ObjData>({
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
const fileList = ref([])
 | 
			
		||||
const uploadData = reactive({
 | 
			
		||||
  accountId: objData.value.accountId,
 | 
			
		||||
  accountId: reply.value.accountId,
 | 
			
		||||
  type: 'image',
 | 
			
		||||
  title: '',
 | 
			
		||||
  introduction: ''
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const beforeImageUpload = (rawFile: UploadRawFile) =>
 | 
			
		||||
  useBeforeUpload(MaterialType.Image, 2)(rawFile)
 | 
			
		||||
const beforeImageUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Image, 2)(rawFile)
 | 
			
		||||
 | 
			
		||||
const onUploadSuccess = (res: any) => {
 | 
			
		||||
  if (res.code !== 0) {
 | 
			
		||||
@@ -104,18 +104,18 @@ const onUploadSuccess = (res: any) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onDelete = () => {
 | 
			
		||||
  objData.value.mediaId = null
 | 
			
		||||
  objData.value.url = null
 | 
			
		||||
  objData.value.name = null
 | 
			
		||||
  reply.value.mediaId = null
 | 
			
		||||
  reply.value.url = null
 | 
			
		||||
  reply.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
 | 
			
		||||
  // reply.value.type = 'image'
 | 
			
		||||
  reply.value.mediaId = item.mediaId
 | 
			
		||||
  reply.value.url = item.url
 | 
			
		||||
  reply.value.name = item.name
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-tab-pane name="music">
 | 
			
		||||
    <template #label>
 | 
			
		||||
      <el-row align="middle"><Icon icon="ep:service" />音乐</el-row>
 | 
			
		||||
    </template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <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" />
 | 
			
		||||
              <img style="width: 100px" v-if="reply.thumbMediaUrl" :src="reply.thumbMediaUrl" />
 | 
			
		||||
              <icon v-else icon="ep:plus" />
 | 
			
		||||
            </el-row>
 | 
			
		||||
            <el-row align="middle" justify="center" style="margin-top: 2%">
 | 
			
		||||
@@ -42,30 +39,31 @@
 | 
			
		||||
          destroy-on-close
 | 
			
		||||
        >
 | 
			
		||||
          <WxMaterialSelect
 | 
			
		||||
            :objData="{ type: 'image', accountId: objData.accountId }"
 | 
			
		||||
            type="image"
 | 
			
		||||
            :account-id="reply.accountId"
 | 
			
		||||
            @select-material="selectMaterial"
 | 
			
		||||
          />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="18">
 | 
			
		||||
        <el-input v-model="objData.title" placeholder="请输入标题" />
 | 
			
		||||
        <el-input v-model="reply.title" placeholder="请输入标题" />
 | 
			
		||||
        <div style="margin: 20px 0"></div>
 | 
			
		||||
        <el-input v-model="objData.description" placeholder="请输入描述" />
 | 
			
		||||
        <el-input v-model="reply.description" placeholder="请输入描述" />
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
    <div style="margin: 20px 0"></div>
 | 
			
		||||
    <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" />
 | 
			
		||||
    <el-input v-model="reply.musicUrl" placeholder="请输入音乐链接" />
 | 
			
		||||
    <div style="margin: 20px 0"></div>
 | 
			
		||||
    <el-input v-model="objData.hqMusicUrl" placeholder="请输入高质量音乐链接" />
 | 
			
		||||
  </el-tab-pane>
 | 
			
		||||
    <el-input v-model="reply.hqMusicUrl" placeholder="请输入高质量音乐链接" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
 | 
			
		||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
 | 
			
		||||
import type { UploadRawFile } from 'element-plus'
 | 
			
		||||
import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
import { getAccessToken } from '@/utils/auth'
 | 
			
		||||
import { ObjData } from './types'
 | 
			
		||||
import { Reply } from './types'
 | 
			
		||||
 | 
			
		||||
const message = useMessage()
 | 
			
		||||
 | 
			
		||||
@@ -73,12 +71,12 @@ const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/u
 | 
			
		||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  modelValue: ObjData
 | 
			
		||||
  modelValue: Reply
 | 
			
		||||
}>()
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  (e: 'update:modelValue', v: ObjData)
 | 
			
		||||
  (e: 'update:modelValue', v: Reply)
 | 
			
		||||
}>()
 | 
			
		||||
const objData = computed<ObjData>({
 | 
			
		||||
const reply = computed<Reply>({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (val) => emit('update:modelValue', val)
 | 
			
		||||
})
 | 
			
		||||
@@ -86,14 +84,13 @@ const objData = computed<ObjData>({
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
const fileList = ref([])
 | 
			
		||||
const uploadData = reactive({
 | 
			
		||||
  accountId: objData.value.accountId,
 | 
			
		||||
  accountId: reply.value.accountId,
 | 
			
		||||
  type: 'thumb', // 音乐类型为thumb
 | 
			
		||||
  title: '',
 | 
			
		||||
  introduction: ''
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const beforeImageUpload = (rawFile: UploadRawFile) =>
 | 
			
		||||
  useBeforeUpload(MaterialType.Image, 2)(rawFile)
 | 
			
		||||
const beforeImageUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Image, 2)(rawFile)
 | 
			
		||||
 | 
			
		||||
const onUploadSuccess = (res: any) => {
 | 
			
		||||
  if (res.code !== 0) {
 | 
			
		||||
@@ -113,7 +110,7 @@ const onUploadSuccess = (res: any) => {
 | 
			
		||||
const selectMaterial = (item: any) => {
 | 
			
		||||
  showDialog.value = false
 | 
			
		||||
 | 
			
		||||
  objData.value.thumbMediaId = item.mediaId
 | 
			
		||||
  objData.value.thumbMediaUrl = item.url
 | 
			
		||||
  reply.value.thumbMediaId = item.mediaId
 | 
			
		||||
  reply.value.thumbMediaUrl = item.url
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-tab-pane name="news">
 | 
			
		||||
    <template #label>
 | 
			
		||||
      <el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row>
 | 
			
		||||
    </template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <div class="select-item" v-if="objData.articles?.length > 0">
 | 
			
		||||
        <WxNews :articles="objData.articles" />
 | 
			
		||||
      <div class="select-item" v-if="reply.articles && reply.articles.length > 0">
 | 
			
		||||
        <WxNews :articles="reply.articles" />
 | 
			
		||||
        <el-col class="ope-row">
 | 
			
		||||
          <el-button type="danger" circle @click="onDelete">
 | 
			
		||||
            <Icon icon="ep:delete" />
 | 
			
		||||
@@ -13,7 +10,7 @@
 | 
			
		||||
        </el-col>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 选择素材 -->
 | 
			
		||||
      <el-col :span="24" v-if="!objData.content">
 | 
			
		||||
      <el-col :span="24" v-if="!reply.content">
 | 
			
		||||
        <el-row style="text-align: center" align="middle">
 | 
			
		||||
          <el-col :span="24">
 | 
			
		||||
            <el-button type="success" @click="showDialog = true">
 | 
			
		||||
@@ -25,28 +22,29 @@
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-dialog title="选择图文" v-model="showDialog" width="90%" append-to-body destroy-on-close>
 | 
			
		||||
        <WxMaterialSelect
 | 
			
		||||
          :objData="objData"
 | 
			
		||||
          @select-material="selectMaterial"
 | 
			
		||||
          type="news"
 | 
			
		||||
          :account-id="reply.accountId"
 | 
			
		||||
          :newsType="newsType"
 | 
			
		||||
          @select-material="selectMaterial"
 | 
			
		||||
        />
 | 
			
		||||
      </el-dialog>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </el-tab-pane>
 | 
			
		||||
  </div>
 | 
			
		||||
</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'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news'
 | 
			
		||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
 | 
			
		||||
import { Reply, NewsType } from './types'
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  modelValue: ObjData
 | 
			
		||||
  modelValue: Reply
 | 
			
		||||
  newsType: NewsType
 | 
			
		||||
}>()
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  (e: 'update:modelValue', v: ObjData)
 | 
			
		||||
  (e: 'update:modelValue', v: Reply)
 | 
			
		||||
}>()
 | 
			
		||||
const objData = computed<ObjData>({
 | 
			
		||||
const reply = computed<Reply>({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (val) => emit('update:modelValue', val)
 | 
			
		||||
})
 | 
			
		||||
@@ -55,11 +53,11 @@ const showDialog = ref(false)
 | 
			
		||||
 | 
			
		||||
const selectMaterial = (item: any) => {
 | 
			
		||||
  showDialog.value = false
 | 
			
		||||
  objData.value.articles = item.content.newsItem
 | 
			
		||||
  reply.value.articles = item.content.newsItem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onDelete = () => {
 | 
			
		||||
  objData.value.articles = []
 | 
			
		||||
  reply.value.articles = []
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,10 @@
 | 
			
		||||
<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>
 | 
			
		||||
  <el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="content" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  modelValue: string | null
 | 
			
		||||
  modelValue?: string | null
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
@@ -17,7 +12,7 @@ const emit = defineEmits<{
 | 
			
		||||
  (e: 'input', v: string | null)
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const content = computed<string | null>({
 | 
			
		||||
const content = computed<string | null | undefined>({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (val: string | null) => {
 | 
			
		||||
    emit('update:modelValue', val)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-tab-pane name="video">
 | 
			
		||||
    <template #label>
 | 
			
		||||
      <el-row align="middle"><Icon icon="ep:share" /> 视频</el-row>
 | 
			
		||||
    </template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <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-input v-model="reply.title" class="input-margin-bottom" placeholder="请输入标题" />
 | 
			
		||||
      <el-input class="input-margin-bottom" v-model="reply.description" placeholder="请输入描述" />
 | 
			
		||||
      <el-row class="ope-row" justify="center">
 | 
			
		||||
        <WxVideoPlayer v-if="objData.url" :url="objData.url" />
 | 
			
		||||
        <WxVideoPlayer v-if="reply.url" :url="reply.url" />
 | 
			
		||||
      </el-row>
 | 
			
		||||
      <el-col>
 | 
			
		||||
        <el-row style="text-align: center" align="middle">
 | 
			
		||||
@@ -27,7 +20,11 @@
 | 
			
		||||
              append-to-body
 | 
			
		||||
              destroy-on-close
 | 
			
		||||
            >
 | 
			
		||||
              <WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
 | 
			
		||||
              <WxMaterialSelect
 | 
			
		||||
                type="video"
 | 
			
		||||
                :account-id="reply.accountId"
 | 
			
		||||
                @select-material="selectMaterial"
 | 
			
		||||
              />
 | 
			
		||||
            </el-dialog>
 | 
			
		||||
          </el-col>
 | 
			
		||||
          <!-- 文件上传 -->
 | 
			
		||||
@@ -48,16 +45,16 @@
 | 
			
		||||
        </el-row>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </el-tab-pane>
 | 
			
		||||
  </div>
 | 
			
		||||
</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 WxVideoPlayer from '@/views/mp/components/wx-video-play'
 | 
			
		||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
 | 
			
		||||
import type { UploadRawFile } from 'element-plus'
 | 
			
		||||
import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
import { getAccessToken } from '@/utils/auth'
 | 
			
		||||
import { ObjData } from './types'
 | 
			
		||||
import { Reply } from './types'
 | 
			
		||||
 | 
			
		||||
const message = useMessage()
 | 
			
		||||
 | 
			
		||||
@@ -65,12 +62,12 @@ const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/u
 | 
			
		||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() }
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  modelValue: ObjData
 | 
			
		||||
  modelValue: Reply
 | 
			
		||||
}>()
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  (e: 'update:modelValue', v: ObjData)
 | 
			
		||||
  (e: 'update:modelValue', v: Reply)
 | 
			
		||||
}>()
 | 
			
		||||
const objData = computed<ObjData>({
 | 
			
		||||
const reply = computed<Reply>({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (val) => emit('update:modelValue', val)
 | 
			
		||||
})
 | 
			
		||||
@@ -78,14 +75,13 @@ const objData = computed<ObjData>({
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
const fileList = ref([])
 | 
			
		||||
const uploadData = reactive({
 | 
			
		||||
  accountId: objData.value.accountId,
 | 
			
		||||
  accountId: reply.value.accountId,
 | 
			
		||||
  type: 'video',
 | 
			
		||||
  title: '',
 | 
			
		||||
  introduction: ''
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const beforeVideoUpload = (rawFile: UploadRawFile) =>
 | 
			
		||||
  useBeforeUpload(MaterialType.Video, 10)(rawFile)
 | 
			
		||||
const beforeVideoUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Video, 10)(rawFile)
 | 
			
		||||
 | 
			
		||||
const onUploadSuccess = (res: any) => {
 | 
			
		||||
  if (res.code !== 0) {
 | 
			
		||||
@@ -105,16 +101,16 @@ const onUploadSuccess = (res: any) => {
 | 
			
		||||
const selectMaterial = (item: any) => {
 | 
			
		||||
  showDialog.value = false
 | 
			
		||||
 | 
			
		||||
  objData.value.mediaId = item.mediaId
 | 
			
		||||
  objData.value.url = item.url
 | 
			
		||||
  objData.value.name = item.name
 | 
			
		||||
  reply.value.mediaId = item.mediaId
 | 
			
		||||
  reply.value.url = item.url
 | 
			
		||||
  reply.value.name = item.name
 | 
			
		||||
 | 
			
		||||
  // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction
 | 
			
		||||
  if (item.title) {
 | 
			
		||||
    objData.value.title = item.title || ''
 | 
			
		||||
    reply.value.title = item.title || ''
 | 
			
		||||
  }
 | 
			
		||||
  if (item.introduction) {
 | 
			
		||||
    objData.value.description = item.introduction || ''
 | 
			
		||||
    reply.value.description = item.introduction || ''
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,9 @@
 | 
			
		||||
<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>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div class="select-item2" v-if="reply.url">
 | 
			
		||||
      <p class="item-name">{{ reply.name }}</p>
 | 
			
		||||
      <el-row class="ope-row" justify="center">
 | 
			
		||||
        <WxVoicePlayer :url="objData.url" />
 | 
			
		||||
        <WxVoicePlayer :url="reply.url" />
 | 
			
		||||
      </el-row>
 | 
			
		||||
      <el-row class="ope-row" justify="center">
 | 
			
		||||
        <el-button type="danger" circle @click="onDelete"><Icon icon="ep:delete" /></el-button>
 | 
			
		||||
@@ -25,7 +22,11 @@
 | 
			
		||||
          append-to-body
 | 
			
		||||
          destroy-on-close
 | 
			
		||||
        >
 | 
			
		||||
          <WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
 | 
			
		||||
          <WxMaterialSelect
 | 
			
		||||
            type="voice"
 | 
			
		||||
            :account-id="reply.accountId"
 | 
			
		||||
            @select-material="selectMaterial"
 | 
			
		||||
          />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <!-- 文件上传 -->
 | 
			
		||||
@@ -49,27 +50,27 @@
 | 
			
		||||
        </el-upload>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </el-tab-pane>
 | 
			
		||||
  </div>
 | 
			
		||||
</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 WxMaterialSelect from '@/views/mp/components/wx-material-select'
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
 | 
			
		||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
import type { UploadRawFile } from 'element-plus'
 | 
			
		||||
import { getAccessToken } from '@/utils/auth'
 | 
			
		||||
import { ObjData } from './types'
 | 
			
		||||
import { Reply } 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
 | 
			
		||||
  modelValue: Reply
 | 
			
		||||
}>()
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  (e: 'update:modelValue', v: ObjData)
 | 
			
		||||
  (e: 'update:modelValue', v: Reply)
 | 
			
		||||
}>()
 | 
			
		||||
const objData = computed<ObjData>({
 | 
			
		||||
const reply = computed<Reply>({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (val) => emit('update:modelValue', val)
 | 
			
		||||
})
 | 
			
		||||
@@ -77,14 +78,13 @@ const objData = computed<ObjData>({
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
const fileList = ref([])
 | 
			
		||||
const uploadData = reactive({
 | 
			
		||||
  accountId: objData.value.accountId,
 | 
			
		||||
  accountId: reply.value.accountId,
 | 
			
		||||
  type: 'voice',
 | 
			
		||||
  title: '',
 | 
			
		||||
  introduction: ''
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const beforeVoiceUpload = (rawFile: UploadRawFile) =>
 | 
			
		||||
  useBeforeUpload(MaterialType.Voice, 10)(rawFile)
 | 
			
		||||
const beforeVoiceUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Voice, 10)(rawFile)
 | 
			
		||||
 | 
			
		||||
const onUploadSuccess = (res: any) => {
 | 
			
		||||
  if (res.code !== 0) {
 | 
			
		||||
@@ -102,18 +102,18 @@ const onUploadSuccess = (res: any) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onDelete = () => {
 | 
			
		||||
  objData.value.mediaId = null
 | 
			
		||||
  objData.value.url = null
 | 
			
		||||
  objData.value.name = null
 | 
			
		||||
  reply.value.mediaId = null
 | 
			
		||||
  reply.value.url = null
 | 
			
		||||
  reply.value.name = null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const selectMaterial = (item: ObjData) => {
 | 
			
		||||
const selectMaterial = (item: Reply) => {
 | 
			
		||||
  showDialog.value = false
 | 
			
		||||
 | 
			
		||||
  objData.value.type = 'voice'
 | 
			
		||||
  objData.value.mediaId = item.mediaId
 | 
			
		||||
  objData.value.url = item.url
 | 
			
		||||
  objData.value.name = item.name
 | 
			
		||||
  // reply.value.type = ReplyType.Voice
 | 
			
		||||
  reply.value.mediaId = item.mediaId
 | 
			
		||||
  reply.value.url = item.url
 | 
			
		||||
  reply.value.name = item.name
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,54 @@
 | 
			
		||||
type ReplyType = '' | 'news' | 'image' | 'voice' | 'video' | 'music' | 'text'
 | 
			
		||||
enum ReplyType {
 | 
			
		||||
  News = 'news',
 | 
			
		||||
  Image = 'image',
 | 
			
		||||
  Voice = 'voice',
 | 
			
		||||
  Video = 'video',
 | 
			
		||||
  Music = 'music',
 | 
			
		||||
  Text = 'text'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ObjData {
 | 
			
		||||
interface _Reply {
 | 
			
		||||
  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[]
 | 
			
		||||
  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[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Reply = _Reply //Partial<_Reply>
 | 
			
		||||
 | 
			
		||||
enum NewsType {
 | 
			
		||||
  Published = '1',
 | 
			
		||||
  Draft = '2'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { ObjData, NewsType }
 | 
			
		||||
/** 利用旧的reply[accountId, type]初始化新的Reply */
 | 
			
		||||
const createEmptyReply = (old: Reply | Ref<Reply>): Reply => {
 | 
			
		||||
  return {
 | 
			
		||||
    accountId: unref(old).accountId,
 | 
			
		||||
    type: unref(old).type,
 | 
			
		||||
    name: null,
 | 
			
		||||
    content: null,
 | 
			
		||||
    mediaId: null,
 | 
			
		||||
    url: null,
 | 
			
		||||
    title: null,
 | 
			
		||||
    description: null,
 | 
			
		||||
    thumbMediaId: null,
 | 
			
		||||
    thumbMediaUrl: null,
 | 
			
		||||
    musicUrl: null,
 | 
			
		||||
    hqMusicUrl: null,
 | 
			
		||||
    introduction: null,
 | 
			
		||||
    articles: []
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { Reply, NewsType, ReplyType, createEmptyReply }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								src/views/mp/components/wx-reply/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/views/mp/components/wx-reply/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { Reply, NewsType, ReplyType, createEmptyReply } from './components/types'
 | 
			
		||||
 | 
			
		||||
import WxReplySelect from './main.vue'
 | 
			
		||||
 | 
			
		||||
export type { Reply }
 | 
			
		||||
export { createEmptyReply, NewsType, ReplyType }
 | 
			
		||||
export default WxReplySelect
 | 
			
		||||
@@ -8,24 +8,59 @@
 | 
			
		||||
  ④ 支持发送【视频】消息时,支持新建视频
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <el-tabs type="border-card" v-model="objData.type" @tab-click="onTabClick">
 | 
			
		||||
  <el-tabs type="border-card" v-model="currentTab">
 | 
			
		||||
    <!-- 类型 1:文本 -->
 | 
			
		||||
    <TabText v-model="objData.content" />
 | 
			
		||||
    <el-tab-pane :name="ReplyType.Text">
 | 
			
		||||
      <template #label>
 | 
			
		||||
        <el-row align="middle"><Icon icon="ep:document" /> 文本</el-row>
 | 
			
		||||
      </template>
 | 
			
		||||
      <TabText v-model="reply.content" />
 | 
			
		||||
    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
    <!-- 类型 2:图片 -->
 | 
			
		||||
    <TabImage v-model="objData" />
 | 
			
		||||
    <el-tab-pane :name="ReplyType.Image">
 | 
			
		||||
      <template #label>
 | 
			
		||||
        <el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row>
 | 
			
		||||
      </template>
 | 
			
		||||
      <TabImage v-model="reply" />
 | 
			
		||||
    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
    <!-- 类型 3:语音 -->
 | 
			
		||||
    <TabVoice v-model="objData" />
 | 
			
		||||
    <el-tab-pane :name="ReplyType.Voice">
 | 
			
		||||
      <template #label>
 | 
			
		||||
        <el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row>
 | 
			
		||||
      </template>
 | 
			
		||||
      <TabVoice v-model="reply" />
 | 
			
		||||
    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
    <!-- 类型 4:视频 -->
 | 
			
		||||
    <TabVideo v-model="objData" />
 | 
			
		||||
    <el-tab-pane :name="ReplyType.Video">
 | 
			
		||||
      <template #label>
 | 
			
		||||
        <el-row align="middle"><Icon icon="ep:share" /> 视频</el-row>
 | 
			
		||||
      </template>
 | 
			
		||||
      <TabVideo v-model="reply" />
 | 
			
		||||
    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
    <!-- 类型 5:图文 -->
 | 
			
		||||
    <TabNews v-model="objData" :news-type="newsType" />
 | 
			
		||||
    <el-tab-pane :name="ReplyType.News">
 | 
			
		||||
      <template #label>
 | 
			
		||||
        <el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row>
 | 
			
		||||
      </template>
 | 
			
		||||
      <TabNews v-model="reply" :news-type="newsType" />
 | 
			
		||||
    </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
    <!-- 类型 6:音乐 -->
 | 
			
		||||
    <TabMusic v-model="objData" />
 | 
			
		||||
    <el-tab-pane :name="ReplyType.Music">
 | 
			
		||||
      <template #label>
 | 
			
		||||
        <el-row align="middle"><Icon icon="ep:service" />音乐</el-row>
 | 
			
		||||
      </template>
 | 
			
		||||
      <TabMusic v-model="reply" />
 | 
			
		||||
    </el-tab-pane>
 | 
			
		||||
  </el-tabs>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="WxReplySelect">
 | 
			
		||||
import { ObjData, NewsType } from './components/types'
 | 
			
		||||
import { Reply, NewsType, ReplyType, createEmptyReply } from './components/types'
 | 
			
		||||
import TabText from './components/TabText.vue'
 | 
			
		||||
import TabImage from './components/TabImage.vue'
 | 
			
		||||
import TabVoice from './components/TabVoice.vue'
 | 
			
		||||
@@ -34,30 +69,54 @@ import TabNews from './components/TabNews.vue'
 | 
			
		||||
import TabMusic from './components/TabMusic.vue'
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  objData: ObjData
 | 
			
		||||
  modelValue: Reply
 | 
			
		||||
  newsType?: NewsType
 | 
			
		||||
}
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  newsType: () => NewsType.Published
 | 
			
		||||
})
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  (e: 'update:modelValue', v: Reply)
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const objData = reactive(props.objData)
 | 
			
		||||
// TODO @Dhb52:Tab 切换的时候,应该表单还保留着;清除只有两个时机:1)发送成功后;2)关闭窗口后;我捉摸,是不是每个 TabXXX 组件,是个独立的 Form,然后有自己的对象,不粘在 objData 一起。这样最终就是 MusicMessageForm、ImageMessageForm
 | 
			
		||||
// const tempObj = new Map().set(objData.type, Object.assign({}, objData))
 | 
			
		||||
const reply = computed<Reply>({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (val) => emit('update:modelValue', val)
 | 
			
		||||
})
 | 
			
		||||
// 作为多个标签保存各自Reply的缓存
 | 
			
		||||
const objCache = new Map<ReplyType, Reply>()
 | 
			
		||||
// 采用独立的ref来保存当前tab,避免在watch标签变化,对reply进行赋值会产生了循环调用
 | 
			
		||||
const currentTab = ref<ReplyType>(props.modelValue.type || ReplyType.Text)
 | 
			
		||||
 | 
			
		||||
/** 切换消息类型的 tab */
 | 
			
		||||
const onTabClick = () => {
 | 
			
		||||
  clear()
 | 
			
		||||
}
 | 
			
		||||
watch(
 | 
			
		||||
  currentTab,
 | 
			
		||||
  (newTab, oldTab) => {
 | 
			
		||||
    // 第一次进入:oldTab 为 undefined
 | 
			
		||||
    // 判断 newTab 是因为 Reply 为 Partial
 | 
			
		||||
    if (oldTab === undefined || newTab === undefined) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
/** 清除除了`type`的字段 */
 | 
			
		||||
    objCache.set(oldTab, unref(reply))
 | 
			
		||||
 | 
			
		||||
    // 从缓存里面取出新tab内容,有则覆盖Reply,没有则创建空Reply
 | 
			
		||||
    const temp = objCache.get(newTab)
 | 
			
		||||
    if (temp) {
 | 
			
		||||
      reply.value = temp
 | 
			
		||||
    } else {
 | 
			
		||||
      let newData = createEmptyReply(reply)
 | 
			
		||||
      newData.type = newTab
 | 
			
		||||
      reply.value = newData
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/** 清除除了`type`, `accountId`的字段 */
 | 
			
		||||
const clear = () => {
 | 
			
		||||
  objData.content = ''
 | 
			
		||||
  objData.mediaId = ''
 | 
			
		||||
  objData.url = ''
 | 
			
		||||
  objData.title = ''
 | 
			
		||||
  objData.description = ''
 | 
			
		||||
  objData.articles = []
 | 
			
		||||
  reply.value = createEmptyReply(reply)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/views/mp/components/wx-video-play/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/views/mp/components/wx-video-play/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import WxVideoPlayer from './main.vue'
 | 
			
		||||
 | 
			
		||||
export default WxVideoPlayer
 | 
			
		||||
							
								
								
									
										3
									
								
								src/views/mp/components/wx-voice-play/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/views/mp/components/wx-voice-play/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import WxVoicePlayer from './main.vue'
 | 
			
		||||
 | 
			
		||||
export default WxVoicePlayer
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
 | 
			
		||||
      存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
 | 
			
		||||
    2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。
 | 
			
		||||
  ② 代码优化:将 props 中的 objData 调成为 data 中对应的属性,并补充相关注释
 | 
			
		||||
  ② 代码优化:将 props 中的 reply 调成为 data 中对应的属性,并补充相关注释
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="wx-voice-div" @click="playVoice">
 | 
			
		||||
@@ -89,8 +89,8 @@ const amrStop = () => {
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
  background-color: #eaeaea;
 | 
			
		||||
  border-radius: 10px;
 | 
			
		||||
  width: 40px;
 | 
			
		||||
  height: 40px;
 | 
			
		||||
  width: 120px;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,9 +27,7 @@
 | 
			
		||||
          :on-success="onUploadSuccess"
 | 
			
		||||
        >
 | 
			
		||||
          <template #trigger>
 | 
			
		||||
            <el-button size="small" type="primary" :loading="isUploading" disabled="isUploading">
 | 
			
		||||
              {{ isUploading ? '正在上传' : '本地上传' }}
 | 
			
		||||
            </el-button>
 | 
			
		||||
            <el-button size="small" type="primary">本地上传</el-button>
 | 
			
		||||
          </template>
 | 
			
		||||
          <el-button
 | 
			
		||||
            size="small"
 | 
			
		||||
@@ -52,7 +50,8 @@
 | 
			
		||||
        destroy-on-close
 | 
			
		||||
      >
 | 
			
		||||
        <WxMaterialSelect
 | 
			
		||||
          :objData="{ type: 'image', accountId: accountId }"
 | 
			
		||||
          type="image"
 | 
			
		||||
          :account-id="accountId"
 | 
			
		||||
          @select-material="onMaterialSelected"
 | 
			
		||||
        />
 | 
			
		||||
      </el-dialog>
 | 
			
		||||
@@ -61,13 +60,13 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
 | 
			
		||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
 | 
			
		||||
import { getAccessToken } from '@/utils/auth'
 | 
			
		||||
import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'
 | 
			
		||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
import { NewsItem } from './types'
 | 
			
		||||
const message = useMessage()
 | 
			
		||||
 | 
			
		||||
// const UPLOAD_URL = 'http://localhost:8000/upload/' // 上传永久素材的地址
 | 
			
		||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
 | 
			
		||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
 | 
			
		||||
 | 
			
		||||
@@ -93,14 +92,13 @@ const showImageDialog = ref(false)
 | 
			
		||||
 | 
			
		||||
const fileList = ref<UploadFiles>([])
 | 
			
		||||
interface UploadData {
 | 
			
		||||
  type: 'image' | 'video' | 'audio'
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  type: UploadType
 | 
			
		||||
  accountId: number | undefined
 | 
			
		||||
}
 | 
			
		||||
const uploadData: UploadData = reactive({
 | 
			
		||||
  type: 'image',
 | 
			
		||||
  type: UploadType.Image,
 | 
			
		||||
  accountId: accountId
 | 
			
		||||
})
 | 
			
		||||
const isUploading = ref(false)
 | 
			
		||||
 | 
			
		||||
/** 素材选择完成事件*/
 | 
			
		||||
const onMaterialSelected = (item: any) => {
 | 
			
		||||
@@ -109,22 +107,8 @@ const onMaterialSelected = (item: any) => {
 | 
			
		||||
  newsItem.value.thumbUrl = item.url
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onBeforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
 | 
			
		||||
  const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes(
 | 
			
		||||
    rawFile.type
 | 
			
		||||
  )
 | 
			
		||||
  if (!isType) {
 | 
			
		||||
    message.error('上传图片格式不对!')
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (rawFile.size / 1024 / 1024 > 2) {
 | 
			
		||||
    message.error('上传图片大小不能超过 2M!')
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
  // 校验通过
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
const onBeforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
 | 
			
		||||
  useBeforeUpload(UploadType.Image, 2)(rawFile)
 | 
			
		||||
 | 
			
		||||
const onUploadSuccess: UploadProps['onSuccess'] = (res: any) => {
 | 
			
		||||
  if (res.code !== 0) {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news'
 | 
			
		||||
 | 
			
		||||
import { Article } from './types'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,6 @@
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
 | 
			
		||||
  <!-- 添加或修改草稿对话框 -->
 | 
			
		||||
  <!-- TODO @Dhb52:是不是整个做成一个组件 -->
 | 
			
		||||
  <el-dialog
 | 
			
		||||
    :title="isCreating ? '新建图文' : '修改图文'"
 | 
			
		||||
    width="80%"
 | 
			
		||||
@@ -63,7 +62,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="MpDraft">
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select'
 | 
			
		||||
import * as MpDraftApi from '@/api/mp/draft'
 | 
			
		||||
import * as MpFreePublishApi from '@/api/mp/freePublish'
 | 
			
		||||
import {
 | 
			
		||||
@@ -77,7 +76,7 @@ import {
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息
 | 
			
		||||
 | 
			
		||||
const accountId = ref(0)
 | 
			
		||||
const accountId = ref<number>(0)
 | 
			
		||||
provide('accountId', accountId)
 | 
			
		||||
 | 
			
		||||
const loading = ref(true) // 列表的加载中
 | 
			
		||||
@@ -91,7 +90,7 @@ interface QueryParams {
 | 
			
		||||
const queryParams: QueryParams = reactive({
 | 
			
		||||
  pageNo: 1,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  accountId: accountId.value
 | 
			
		||||
  accountId: 0
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
interface UploadData {
 | 
			
		||||
@@ -100,7 +99,7 @@ interface UploadData {
 | 
			
		||||
}
 | 
			
		||||
const uploadData: UploadData = reactive({
 | 
			
		||||
  type: 'image',
 | 
			
		||||
  accountId: accountId.value
 | 
			
		||||
  accountId: 0
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// ========== 草稿新建 or 修改 ==========
 | 
			
		||||
 
 | 
			
		||||
@@ -50,8 +50,8 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup name="MpFreePublish">
 | 
			
		||||
import * as FreePublishApi from '@/api/mp/freePublish'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select'
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const { t } = useI18n() // 国际化
 | 
			
		||||
 | 
			
		||||
@@ -62,16 +62,16 @@ const list = ref<any[]>([]) // 列表的数据
 | 
			
		||||
interface QueryParams {
 | 
			
		||||
  pageNo: number
 | 
			
		||||
  pageSize: number
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  accountId: number
 | 
			
		||||
}
 | 
			
		||||
const queryParams: QueryParams = reactive({
 | 
			
		||||
  pageNo: 1,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  accountId: undefined
 | 
			
		||||
  accountId: 0
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/** 侦听公众号变化 **/
 | 
			
		||||
const onAccountChanged = (id: number | undefined) => {
 | 
			
		||||
const onAccountChanged = (id: number) => {
 | 
			
		||||
  queryParams.accountId = id
 | 
			
		||||
  getList()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,29 +2,29 @@ import type { UploadRawFile } from 'element-plus'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息
 | 
			
		||||
 | 
			
		||||
enum MaterialType {
 | 
			
		||||
enum UploadType {
 | 
			
		||||
  Image = 'image',
 | 
			
		||||
  Voice = 'voice',
 | 
			
		||||
  Video = 'video'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useBeforeUpload = (type: MaterialType, maxSizeMB: number) => {
 | 
			
		||||
const useBeforeUpload = (type: UploadType, maxSizeMB: number) => {
 | 
			
		||||
  const fn = (rawFile: UploadRawFile): boolean => {
 | 
			
		||||
    let allowTypes: string[] = []
 | 
			
		||||
    let name = ''
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case MaterialType.Image:
 | 
			
		||||
      case UploadType.Image:
 | 
			
		||||
        allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg']
 | 
			
		||||
        maxSizeMB = 2
 | 
			
		||||
        name = '图片'
 | 
			
		||||
        break
 | 
			
		||||
      case MaterialType.Voice:
 | 
			
		||||
      case UploadType.Voice:
 | 
			
		||||
        allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr']
 | 
			
		||||
        maxSizeMB = 2
 | 
			
		||||
        name = '语音'
 | 
			
		||||
        break
 | 
			
		||||
      case MaterialType.Video:
 | 
			
		||||
      case UploadType.Video:
 | 
			
		||||
        allowTypes = ['video/mp4']
 | 
			
		||||
        maxSizeMB = 10
 | 
			
		||||
        name = '视频'
 | 
			
		||||
@@ -47,4 +47,4 @@ const useBeforeUpload = (type: MaterialType, maxSizeMB: number) => {
 | 
			
		||||
  return fn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { MaterialType, useBeforeUpload }
 | 
			
		||||
export { UploadType, useBeforeUpload }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,14 +6,11 @@
 | 
			
		||||
    :limit="1"
 | 
			
		||||
    :file-list="fileList"
 | 
			
		||||
    :data="uploadData"
 | 
			
		||||
    :on-progress="(isUploading = true)"
 | 
			
		||||
    :on-error="onUploadError"
 | 
			
		||||
    :before-upload="onBeforeUpload"
 | 
			
		||||
    :on-success="onUploadSuccess"
 | 
			
		||||
  >
 | 
			
		||||
    <el-button type="primary" plain :loading="isUploading" :disabled="isUploading">
 | 
			
		||||
      {{ isUploading ? '正在上传' : '点击上传' }}
 | 
			
		||||
    </el-button>
 | 
			
		||||
    <el-button type="primary" plain> 点击上传 </el-button>
 | 
			
		||||
    <template #tip>
 | 
			
		||||
      <span class="el-upload__tip" style="margin-left: 5px">
 | 
			
		||||
        <slot></slot>
 | 
			
		||||
@@ -27,14 +24,14 @@ import {
 | 
			
		||||
  HEADERS,
 | 
			
		||||
  UPLOAD_URL,
 | 
			
		||||
  UploadData,
 | 
			
		||||
  MaterialType,
 | 
			
		||||
  UploadType,
 | 
			
		||||
  beforeImageUpload,
 | 
			
		||||
  beforeVoiceUpload
 | 
			
		||||
} from './upload'
 | 
			
		||||
 | 
			
		||||
const message = useMessage()
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{ type: MaterialType }>()
 | 
			
		||||
const props = defineProps<{ type: UploadType }>()
 | 
			
		||||
 | 
			
		||||
const fileList = ref<UploadUserFile[]>([])
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
@@ -42,14 +39,13 @@ const emit = defineEmits<{
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
const uploadData: UploadData = reactive({
 | 
			
		||||
  type: MaterialType.Image,
 | 
			
		||||
  type: UploadType.Image,
 | 
			
		||||
  title: '',
 | 
			
		||||
  introduction: ''
 | 
			
		||||
})
 | 
			
		||||
const isUploading = ref(false)
 | 
			
		||||
 | 
			
		||||
/** 上传前检查 */
 | 
			
		||||
const onBeforeUpload = props.type === MaterialType.Image ? beforeImageUpload : beforeVoiceUpload
 | 
			
		||||
const onBeforeUpload = props.type === UploadType.Image ? beforeImageUpload : beforeVoiceUpload
 | 
			
		||||
 | 
			
		||||
/** 上传成功处理 */
 | 
			
		||||
const onUploadSuccess: UploadProps['onSuccess'] = (res: any) => {
 | 
			
		||||
@@ -64,7 +60,6 @@ const onUploadSuccess: UploadProps['onSuccess'] = (res: any) => {
 | 
			
		||||
  uploadData.introduction = ''
 | 
			
		||||
 | 
			
		||||
  message.notifySuccess('上传成功')
 | 
			
		||||
  isUploading.value = false
 | 
			
		||||
  emit('uploaded')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-dialog title="新建视频" v-model="showDialog" width="600px" destroy-on-close>
 | 
			
		||||
  <el-dialog title="新建视频" v-model="showDialog" width="600px">
 | 
			
		||||
    <el-upload
 | 
			
		||||
      :action="UPLOAD_URL"
 | 
			
		||||
      :headers="HEADERS"
 | 
			
		||||
@@ -8,7 +8,6 @@
 | 
			
		||||
      :file-list="fileList"
 | 
			
		||||
      :data="uploadData"
 | 
			
		||||
      :before-upload="beforeVideoUpload"
 | 
			
		||||
      :on-progress="(isUploading = true)"
 | 
			
		||||
      :on-error="onUploadError"
 | 
			
		||||
      :on-success="onUploadSuccess"
 | 
			
		||||
      ref="uploadVideoRef"
 | 
			
		||||
@@ -18,12 +17,14 @@
 | 
			
		||||
      <template #trigger>
 | 
			
		||||
        <el-button type="primary" plain>选择视频</el-button>
 | 
			
		||||
      </template>
 | 
			
		||||
      <span class="el-upload__tip" style="margin-left: 10px"
 | 
			
		||||
        >格式支持 MP4,文件大小不超过 10MB</span
 | 
			
		||||
      >
 | 
			
		||||
      <template #tip>
 | 
			
		||||
        <span class="el-upload__tip" style="margin-left: 10px"
 | 
			
		||||
          >格式支持 MP4,文件大小不超过 10MB</span
 | 
			
		||||
        >
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-upload>
 | 
			
		||||
    <el-divider />
 | 
			
		||||
    <el-form :model="uploadData" :rules="uploadRules" ref="uploadFormRef" v-loading="isUploading">
 | 
			
		||||
    <el-form :model="uploadData" :rules="uploadRules" ref="uploadFormRef">
 | 
			
		||||
      <el-form-item label="标题" prop="title">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="uploadData.title"
 | 
			
		||||
@@ -41,9 +42,7 @@
 | 
			
		||||
    </el-form>
 | 
			
		||||
    <template #footer>
 | 
			
		||||
      <el-button @click="showDialog = false">取 消</el-button>
 | 
			
		||||
      <el-button type="primary" @click="submitVideo" :loading="isUploading" :disabled="isUploading"
 | 
			
		||||
        >提 交</el-button
 | 
			
		||||
      >
 | 
			
		||||
      <el-button type="primary" @click="submitVideo">提 交</el-button>
 | 
			
		||||
    </template>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -56,7 +55,7 @@ import type {
 | 
			
		||||
  UploadProps,
 | 
			
		||||
  UploadUserFile
 | 
			
		||||
} from 'element-plus'
 | 
			
		||||
import { HEADERS, UploadData, UPLOAD_URL, beforeVideoUpload, MaterialType } from './upload'
 | 
			
		||||
import { HEADERS, UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from './upload'
 | 
			
		||||
 | 
			
		||||
const message = useMessage()
 | 
			
		||||
 | 
			
		||||
@@ -85,18 +84,16 @@ const showDialog = computed<boolean>({
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const isUploading = ref(false)
 | 
			
		||||
 | 
			
		||||
const fileList = ref<UploadUserFile[]>([])
 | 
			
		||||
 | 
			
		||||
const uploadData: UploadData = reactive({
 | 
			
		||||
  type: MaterialType.Video,
 | 
			
		||||
  type: UploadType.Video,
 | 
			
		||||
  title: '',
 | 
			
		||||
  introduction: ''
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const uploadFormRef = ref<FormInstance>()
 | 
			
		||||
const uploadVideoRef = ref<UploadInstance>()
 | 
			
		||||
const uploadFormRef = ref<FormInstance | null>(null)
 | 
			
		||||
const uploadVideoRef = ref<UploadInstance | null>(null)
 | 
			
		||||
 | 
			
		||||
const submitVideo = () => {
 | 
			
		||||
  uploadFormRef.value?.validate((valid) => {
 | 
			
		||||
@@ -109,7 +106,6 @@ const submitVideo = () => {
 | 
			
		||||
 | 
			
		||||
/** 上传成功处理 */
 | 
			
		||||
const onUploadSuccess: UploadProps['onSuccess'] = (res: any) => {
 | 
			
		||||
  isUploading.value = false
 | 
			
		||||
  if (res.code !== 0) {
 | 
			
		||||
    message.error('上传出错:' + res.msg)
 | 
			
		||||
    return false
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
 | 
			
		||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play'
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,29 @@
 | 
			
		||||
import type { UploadProps, UploadRawFile } from 'element-plus'
 | 
			
		||||
import { getAccessToken } from '@/utils/auth'
 | 
			
		||||
import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
 | 
			
		||||
 | 
			
		||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 请求头
 | 
			
		||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址
 | 
			
		||||
 | 
			
		||||
interface UploadData {
 | 
			
		||||
  type: MaterialType
 | 
			
		||||
  type: UploadType
 | 
			
		||||
  title: string
 | 
			
		||||
  introduction: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
 | 
			
		||||
  useBeforeUpload(MaterialType.Image, 2)(rawFile)
 | 
			
		||||
  useBeforeUpload(UploadType.Image, 2)(rawFile)
 | 
			
		||||
 | 
			
		||||
const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
 | 
			
		||||
  useBeforeUpload(MaterialType.Voice, 2)(rawFile)
 | 
			
		||||
  useBeforeUpload(UploadType.Voice, 2)(rawFile)
 | 
			
		||||
 | 
			
		||||
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
 | 
			
		||||
  useBeforeUpload(MaterialType.Video, 10)(rawFile)
 | 
			
		||||
  useBeforeUpload(UploadType.Video, 10)(rawFile)
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  HEADERS,
 | 
			
		||||
  UPLOAD_URL,
 | 
			
		||||
  MaterialType,
 | 
			
		||||
  UploadType,
 | 
			
		||||
  UploadData,
 | 
			
		||||
  beforeImageUpload,
 | 
			
		||||
  beforeVoiceUpload,
 | 
			
		||||
 
 | 
			
		||||
@@ -12,13 +12,13 @@
 | 
			
		||||
  <ContentWrap>
 | 
			
		||||
    <el-tabs v-model="type" @tab-change="onTabChange">
 | 
			
		||||
      <!-- tab 1:图片  -->
 | 
			
		||||
      <el-tab-pane :name="MaterialType.Image">
 | 
			
		||||
      <el-tab-pane :name="UploadType.Image">
 | 
			
		||||
        <template #label>
 | 
			
		||||
          <span> <Icon icon="ep:picture" />图片 </span>
 | 
			
		||||
          <el-row align="middle"> <Icon icon="ep:picture" />图片 </el-row>
 | 
			
		||||
        </template>
 | 
			
		||||
        <UploadFile
 | 
			
		||||
          v-hasPermi="['mp:material:upload-permanent']"
 | 
			
		||||
          :type="MaterialType.Image"
 | 
			
		||||
          :type="UploadType.Image"
 | 
			
		||||
          @uploaded="getList"
 | 
			
		||||
        >
 | 
			
		||||
          支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M
 | 
			
		||||
@@ -35,13 +35,13 @@
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
      <!-- tab 2:语音  -->
 | 
			
		||||
      <el-tab-pane :name="MaterialType.Voice">
 | 
			
		||||
      <el-tab-pane :name="UploadType.Voice">
 | 
			
		||||
        <template #label>
 | 
			
		||||
          <span> <Icon icon="ep:microphone" />语音 </span>
 | 
			
		||||
          <el-row align="middle"> <Icon icon="ep:microphone" />语音 </el-row>
 | 
			
		||||
        </template>
 | 
			
		||||
        <UploadFile
 | 
			
		||||
          v-hasPermi="['mp:material:upload-permanent']"
 | 
			
		||||
          :type="MaterialType.Voice"
 | 
			
		||||
          :type="UploadType.Voice"
 | 
			
		||||
          @uploaded="getList"
 | 
			
		||||
        >
 | 
			
		||||
          格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
 | 
			
		||||
@@ -58,9 +58,9 @@
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
      <!-- tab 3:视频 -->
 | 
			
		||||
      <el-tab-pane :name="MaterialType.Video">
 | 
			
		||||
      <el-tab-pane :name="UploadType.Video">
 | 
			
		||||
        <template #label>
 | 
			
		||||
          <span> <Icon icon="ep:video-play" /> 视频 </span>
 | 
			
		||||
          <el-row align="middle"> <Icon icon="ep:video-play" /> 视频 </el-row>
 | 
			
		||||
        </template>
 | 
			
		||||
        <el-button
 | 
			
		||||
          v-hasPermi="['mp:material:upload-permanent']"
 | 
			
		||||
@@ -85,17 +85,17 @@
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup name="MpMaterial">
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select'
 | 
			
		||||
import ImageTable from './components/ImageTable.vue'
 | 
			
		||||
import VoiceTable from './components/VoiceTable.vue'
 | 
			
		||||
import VideoTable from './components/VideoTable.vue'
 | 
			
		||||
import UploadFile from './components/UploadFile.vue'
 | 
			
		||||
import UploadVideo from './components/UploadVideo.vue'
 | 
			
		||||
import { MaterialType } from './components/upload'
 | 
			
		||||
import { UploadType } from './components/upload'
 | 
			
		||||
import * as MpMaterialApi from '@/api/mp/material'
 | 
			
		||||
const message = useMessage() // 消息
 | 
			
		||||
 | 
			
		||||
const type = ref<MaterialType>(MaterialType.Image) // 素材类型
 | 
			
		||||
const type = ref<UploadType>(UploadType.Image) // 素材类型
 | 
			
		||||
const loading = ref(false) // 遮罩层
 | 
			
		||||
const list = ref<any[]>([]) // 总条数
 | 
			
		||||
const total = ref(0) // 数据列表
 | 
			
		||||
@@ -103,19 +103,19 @@ const total = ref(0) // 数据列表
 | 
			
		||||
interface QueryParams {
 | 
			
		||||
  pageNo: number
 | 
			
		||||
  pageSize: number
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  accountId: number
 | 
			
		||||
  permanent: boolean
 | 
			
		||||
}
 | 
			
		||||
const queryParams: QueryParams = reactive({
 | 
			
		||||
  pageNo: 1,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  accountId: undefined,
 | 
			
		||||
  accountId: 0,
 | 
			
		||||
  permanent: true
 | 
			
		||||
})
 | 
			
		||||
const showCreateVideo = ref(false) // 是否新建视频的弹窗
 | 
			
		||||
 | 
			
		||||
/** 侦听公众号变化 **/
 | 
			
		||||
const onAccountChanged = (id?: number) => {
 | 
			
		||||
const onAccountChanged = (id: number) => {
 | 
			
		||||
  queryParams.accountId = id
 | 
			
		||||
  getList()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,8 @@
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-dialog title="选择图文" v-model="showNewsDialog" width="80%" destroy-on-close>
 | 
			
		||||
              <WxMaterialSelect
 | 
			
		||||
                :objData="{ type: 'news', accountId: props.accountId }"
 | 
			
		||||
                type="news"
 | 
			
		||||
                :account-id="props.accountId"
 | 
			
		||||
                @select-material="selectMaterial"
 | 
			
		||||
              />
 | 
			
		||||
            </el-dialog>
 | 
			
		||||
@@ -104,7 +105,7 @@
 | 
			
		||||
          class="configur_content"
 | 
			
		||||
          v-if="menu.type === 'click' || menu.type === 'scancode_waitmsg'"
 | 
			
		||||
        >
 | 
			
		||||
          <WxReplySelect v-if="hackResetWxReplySelect" :objData="menu.reply" />
 | 
			
		||||
          <WxReplySelect v-if="hackResetWxReplySelect" v-model="menu.reply" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -112,15 +113,15 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
 | 
			
		||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
 | 
			
		||||
import WxReplySelect from '@/views/mp/components/wx-reply'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news'
 | 
			
		||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select'
 | 
			
		||||
import menuOptions from './menuOptions'
 | 
			
		||||
 | 
			
		||||
const message = useMessage()
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  accountId: number
 | 
			
		||||
  modelValue: any
 | 
			
		||||
  isParent: boolean
 | 
			
		||||
}>()
 | 
			
		||||
@@ -130,7 +131,6 @@ const emit = defineEmits<{
 | 
			
		||||
  (e: 'update:modelValue', v: any)
 | 
			
		||||
}>()
 | 
			
		||||
 | 
			
		||||
// TODO @Dhb52 输入的 table 切换时,表单应该保留
 | 
			
		||||
const menu = computed({
 | 
			
		||||
  get() {
 | 
			
		||||
    return props.modelValue
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup name="MpMenu">
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select'
 | 
			
		||||
import MenuEditor from './components/MenuEditor.vue'
 | 
			
		||||
import MenuPreviewer from './components/MenuPreviewer.vue'
 | 
			
		||||
import * as MpMenuApi from '@/api/mp/menu'
 | 
			
		||||
@@ -65,8 +65,8 @@ const MENU_NOT_SELECTED = '__MENU_NOT_SELECTED__'
 | 
			
		||||
 | 
			
		||||
// ======================== 列表查询 ========================
 | 
			
		||||
const loading = ref(false) // 遮罩层
 | 
			
		||||
const accountId = ref<number | undefined>()
 | 
			
		||||
const accountName = ref<string | undefined>('')
 | 
			
		||||
const accountId = ref<number>(0)
 | 
			
		||||
const accountName = ref<string>('')
 | 
			
		||||
const menuList = ref<Menu[]>([])
 | 
			
		||||
 | 
			
		||||
// ======================== 菜单操作 ========================
 | 
			
		||||
@@ -103,7 +103,7 @@ const tempSelfObj = ref<{
 | 
			
		||||
const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
 | 
			
		||||
 | 
			
		||||
/** 侦听公众号变化 **/
 | 
			
		||||
const onAccountChanged = (id?: number, name?: string) => {
 | 
			
		||||
const onAccountChanged = (id: number, name: string) => {
 | 
			
		||||
  accountId.value = id
 | 
			
		||||
  accountName.value = name
 | 
			
		||||
  getList()
 | 
			
		||||
 
 | 
			
		||||
@@ -122,11 +122,11 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/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 WxVideoPlayer from '@/views/mp/components/wx-video-play'
 | 
			
		||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play'
 | 
			
		||||
import WxLocation from '@/views/mp/components/wx-location'
 | 
			
		||||
import WxMusic from '@/views/mp/components/wx-music'
 | 
			
		||||
import WxNews from '@/views/mp/components/wx-news'
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
import { MsgType } from '@/views/mp/components/wx-msg/types'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -81,8 +81,8 @@
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts" name="MpMessage">
 | 
			
		||||
import * as MpMessageApi from '@/api/mp/message'
 | 
			
		||||
import WxMsg from '@/views/mp/components/wx-msg/main.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 | 
			
		||||
import WxMsg from '@/views/mp/components/wx-msg'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select'
 | 
			
		||||
import MessageTable from './MessageTable.vue'
 | 
			
		||||
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 | 
			
		||||
import { MsgType } from '@/views/mp/components/wx-msg/types'
 | 
			
		||||
@@ -96,17 +96,17 @@ const list = ref<any[]>([]) // 当前页的列表数据
 | 
			
		||||
interface QueryParams {
 | 
			
		||||
  pageNo: number
 | 
			
		||||
  pageSize: number
 | 
			
		||||
  openid: string | null
 | 
			
		||||
  accountId: number | null
 | 
			
		||||
  type: MsgType | null
 | 
			
		||||
  openid: string | undefined
 | 
			
		||||
  accountId: number
 | 
			
		||||
  type: MsgType | undefined
 | 
			
		||||
  createTime: string[] | []
 | 
			
		||||
}
 | 
			
		||||
const queryParams: QueryParams = reactive({
 | 
			
		||||
  pageNo: 1,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  openid: null,
 | 
			
		||||
  accountId: null,
 | 
			
		||||
  type: null,
 | 
			
		||||
  openid: undefined,
 | 
			
		||||
  accountId: 0,
 | 
			
		||||
  type: undefined,
 | 
			
		||||
  createTime: []
 | 
			
		||||
})
 | 
			
		||||
const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单
 | 
			
		||||
@@ -118,8 +118,8 @@ const messageBox = reactive({
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/** 侦听accountId */
 | 
			
		||||
const onAccountChanged = (id?: number) => {
 | 
			
		||||
  queryParams.accountId = id as number
 | 
			
		||||
const onAccountChanged = (id: number) => {
 | 
			
		||||
  queryParams.accountId = id
 | 
			
		||||
  handleQuery()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,22 @@
 | 
			
		||||
        <WxAccountSelect @change="onAccountChanged" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item>
 | 
			
		||||
        <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']">
 | 
			
		||||
        <el-button
 | 
			
		||||
          type="primary"
 | 
			
		||||
          plain
 | 
			
		||||
          @click="openForm('create')"
 | 
			
		||||
          v-hasPermi="['mp:tag:create']"
 | 
			
		||||
          :disabled="queryParams.accountId === 0"
 | 
			
		||||
        >
 | 
			
		||||
          <Icon icon="ep:plus" class="mr-5px" /> 新增
 | 
			
		||||
        </el-button>
 | 
			
		||||
        <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']">
 | 
			
		||||
        <el-button
 | 
			
		||||
          type="success"
 | 
			
		||||
          plain
 | 
			
		||||
          @click="handleSync"
 | 
			
		||||
          v-hasPermi="['mp:tag:sync']"
 | 
			
		||||
          :disabled="queryParams.accountId === 0"
 | 
			
		||||
        >
 | 
			
		||||
          <Icon icon="ep:refresh" class="mr-5px" /> 同步
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
@@ -74,28 +86,30 @@
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
import * as MpTagApi from '@/api/mp/tag'
 | 
			
		||||
import TagForm from './TagForm.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const { t } = useI18n() // 国际化
 | 
			
		||||
 | 
			
		||||
const loading = ref(true) // 列表的加载中
 | 
			
		||||
const total = ref(0) // 列表的总页数
 | 
			
		||||
const list = ref<any>([]) // 列表的数据
 | 
			
		||||
const list = ref<any[]>([]) // 列表的数据
 | 
			
		||||
 | 
			
		||||
interface QueryParams {
 | 
			
		||||
  pageNo: number
 | 
			
		||||
  pageSize: number
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  accountId: number
 | 
			
		||||
}
 | 
			
		||||
const queryParams: QueryParams = reactive({
 | 
			
		||||
  pageNo: 1,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  accountId: undefined
 | 
			
		||||
  accountId: 0
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const formRef = ref<InstanceType<typeof TagForm> | null>(null)
 | 
			
		||||
 | 
			
		||||
/** 侦听公众号变化 **/
 | 
			
		||||
const onAccountChanged = (id?: number) => {
 | 
			
		||||
const onAccountChanged = (id: number) => {
 | 
			
		||||
  queryParams.pageNo = 1
 | 
			
		||||
  queryParams.accountId = id
 | 
			
		||||
  getList()
 | 
			
		||||
@@ -114,8 +128,8 @@ const getList = async () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 添加/修改操作 */
 | 
			
		||||
const openForm = (type: string, id?: number) => {
 | 
			
		||||
  formRef.value?.open(type, queryParams.accountId as number, id)
 | 
			
		||||
const openForm = (type: 'create' | 'update', id?: number) => {
 | 
			
		||||
  formRef.value?.open(type, queryParams.accountId, id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 删除按钮操作 */
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,13 @@
 | 
			
		||||
      <el-form-item>
 | 
			
		||||
        <el-button @click="handleQuery"> <Icon icon="ep:search" />搜索 </el-button>
 | 
			
		||||
        <el-button @click="resetQuery"> <Icon icon="ep:refresh" />重置 </el-button>
 | 
			
		||||
        <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user:sync']">
 | 
			
		||||
        <el-button
 | 
			
		||||
          type="success"
 | 
			
		||||
          plain
 | 
			
		||||
          @click="handleSync"
 | 
			
		||||
          v-hasPermi="['mp:user:sync']"
 | 
			
		||||
          :disabled="queryParams.accountId === 0"
 | 
			
		||||
        >
 | 
			
		||||
          <Icon icon="ep:refresh" class="mr-5px" /> 同步
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
@@ -97,7 +103,7 @@
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
import * as MpUserApi from '@/api/mp/user'
 | 
			
		||||
import * as MpTagApi from '@/api/mp/tag'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
 | 
			
		||||
import WxAccountSelect from '@/views/mp/components/wx-account-select'
 | 
			
		||||
import type { FormInstance } from 'element-plus'
 | 
			
		||||
import UserForm from './UserForm.vue'
 | 
			
		||||
 | 
			
		||||
@@ -110,14 +116,14 @@ const list = ref<any[]>([]) // 列表的数据
 | 
			
		||||
interface QueryParams {
 | 
			
		||||
  pageNo: number
 | 
			
		||||
  pageSize: number
 | 
			
		||||
  accountId?: number
 | 
			
		||||
  accountId: number
 | 
			
		||||
  openid: string | null
 | 
			
		||||
  nickname: string | null
 | 
			
		||||
}
 | 
			
		||||
const queryParams: QueryParams = reactive({
 | 
			
		||||
  pageNo: 1,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  accountId: undefined,
 | 
			
		||||
  accountId: 0,
 | 
			
		||||
  openid: null,
 | 
			
		||||
  nickname: null
 | 
			
		||||
})
 | 
			
		||||
@@ -125,7 +131,7 @@ const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单
 | 
			
		||||
const tagList = ref<any[]>([]) // 公众号标签列表
 | 
			
		||||
 | 
			
		||||
/** 侦听公众号变化 **/
 | 
			
		||||
const onAccountChanged = (id?: number) => {
 | 
			
		||||
const onAccountChanged = (id: number) => {
 | 
			
		||||
  queryParams.pageNo = 1
 | 
			
		||||
  queryParams.accountId = id
 | 
			
		||||
  getList()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user