refactor: mp模块ts重构
							parent
							
								
									ebbf47f509
								
							
						
					
					
						commit
						df3b381d6f
					
				|  | @ -3,13 +3,16 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- 搜索工作栏 --> |   <!-- 搜索工作栏 --> | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect  --> |     <el-form class="-mb-15px" :model="queryParams" :inline="true" label-width="68px"> | ||||||
|     <WxAccountSelect @change="accountChanged" /> |       <el-form-item label="公众号" prop="accountId"> | ||||||
|  |         <WxMpSelect @change="onAccountChanged" /> | ||||||
|  |       </el-form-item> | ||||||
|  |     </el-form> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| 
 | 
 | ||||||
|   <!-- tab 切换 --> |   <!-- tab 切换 --> | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <el-tabs v-model="type" @tab-change="handleTabChange"> |     <el-tabs v-model="msgType" @tab-change="handleTabChange"> | ||||||
|       <!-- 操作工具栏 --> |       <!-- 操作工具栏 --> | ||||||
|       <el-row :gutter="10" class="mb8"> |       <el-row :gutter="10" class="mb8"> | ||||||
|         <el-col :span="1.5"> |         <el-col :span="1.5"> | ||||||
|  | @ -18,26 +21,26 @@ | ||||||
|             plain |             plain | ||||||
|             @click="handleAdd" |             @click="handleAdd" | ||||||
|             v-hasPermi="['mp:auto-reply:create']" |             v-hasPermi="['mp:auto-reply:create']" | ||||||
|             v-if="type !== '1' || list.length <= 0" |             v-if="msgType !== MsgType.Follow || list.length <= 0" | ||||||
|           > |           > | ||||||
|             <Icon icon="ep:plus" />新增 |             <Icon icon="ep:plus" />新增 | ||||||
|           </el-button> |           </el-button> | ||||||
|         </el-col> |         </el-col> | ||||||
|       </el-row> |       </el-row> | ||||||
|       <!-- tab 项 --> |       <!-- tab 项 --> | ||||||
|       <el-tab-pane name="1"> |       <el-tab-pane :name="MsgType.Follow"> | ||||||
|         <template #label> |         <template #label> | ||||||
|           <span><Icon icon="ep:star-off" /> 关注时回复</span> |           <span><Icon icon="ep:star" /> 关注时回复</span> | ||||||
|         </template> |         </template> | ||||||
|       </el-tab-pane> |       </el-tab-pane> | ||||||
|       <el-tab-pane name="2"> |       <el-tab-pane :name="MsgType.Message"> | ||||||
|         <template #label> |         <template #label> | ||||||
|           <span><Icon icon="ep:chat-line-round" /> 消息回复</span> |           <span><Icon icon="ep:chat-line-round" /> 消息回复</span> | ||||||
|         </template> |         </template> | ||||||
|       </el-tab-pane> |       </el-tab-pane> | ||||||
|       <el-tab-pane name="3"> |       <el-tab-pane :name="MsgType.Keyword"> | ||||||
|         <template #label> |         <template #label> | ||||||
|           <span><Icon icon="ep:news" /> 关键词回复</span> |           <span><Icon icon="fa:newspaper-o" /> 关键词回复</span> | ||||||
|         </template> |         </template> | ||||||
|       </el-tab-pane> |       </el-tab-pane> | ||||||
|     </el-tabs> |     </el-tabs> | ||||||
|  | @ -47,10 +50,20 @@ | ||||||
|         label="请求消息类型" |         label="请求消息类型" | ||||||
|         align="center" |         align="center" | ||||||
|         prop="requestMessageType" |         prop="requestMessageType" | ||||||
|         v-if="type === '2'" |         v-if="msgType === MsgType.Message" | ||||||
|       /> |       /> | ||||||
|       <el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" /> |       <el-table-column | ||||||
|       <el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'"> |         label="关键词" | ||||||
|  |         align="center" | ||||||
|  |         prop="requestKeyword" | ||||||
|  |         v-if="msgType === MsgType.Keyword" | ||||||
|  |       /> | ||||||
|  |       <el-table-column | ||||||
|  |         label="匹配类型" | ||||||
|  |         align="center" | ||||||
|  |         prop="requestMatch" | ||||||
|  |         v-if="msgType === MsgType.Keyword" | ||||||
|  |       > | ||||||
|         <template #default="scope"> |         <template #default="scope"> | ||||||
|           <dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch" /> |           <dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch" /> | ||||||
|         </template> |         </template> | ||||||
|  | @ -64,7 +77,7 @@ | ||||||
|         <template #default="scope"> |         <template #default="scope"> | ||||||
|           <div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div> |           <div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div> | ||||||
|           <div v-else-if="scope.row.responseMessageType === 'voice'"> |           <div v-else-if="scope.row.responseMessageType === 'voice'"> | ||||||
|             <WxVoicePlayer :url="scope.row.responseMediaUrl" /> |             <WxVoicePlayer v-if="scope.row.responseMediaUrl" :url="scope.row.responseMediaUrl" /> | ||||||
|           </div> |           </div> | ||||||
|           <div v-else-if="scope.row.responseMessageType === 'image'"> |           <div v-else-if="scope.row.responseMessageType === 'image'"> | ||||||
|             <a target="_blank" :href="scope.row.responseMediaUrl"> |             <a target="_blank" :href="scope.row.responseMediaUrl"> | ||||||
|  | @ -77,7 +90,11 @@ | ||||||
|               scope.row.responseMessageType === 'shortvideo' |               scope.row.responseMessageType === 'shortvideo' | ||||||
|             " |             " | ||||||
|           > |           > | ||||||
|             <WxVideoPlayer :url="scope.row.responseMediaUrl" style="margin-top: 10px" /> |             <WxVideoPlayer | ||||||
|  |               v-if="scope.row.responseMediaUrl" | ||||||
|  |               :url="scope.row.responseMediaUrl" | ||||||
|  |               style="margin-top: 10px" | ||||||
|  |             /> | ||||||
|           </div> |           </div> | ||||||
|           <div v-else-if="scope.row.responseMessageType === 'news'"> |           <div v-else-if="scope.row.responseMessageType === 'news'"> | ||||||
|             <WxNews :articles="scope.row.responseArticles" /> |             <WxNews :articles="scope.row.responseArticles" /> | ||||||
|  | @ -123,21 +140,21 @@ | ||||||
|     </el-table> |     </el-table> | ||||||
| 
 | 
 | ||||||
|     <!-- 添加或修改自动回复的对话框 --> |     <!-- 添加或修改自动回复的对话框 --> | ||||||
|     <el-dialog :title="title" v-model="open" width="800px" append-to-body> |     <el-dialog :title="title" v-model="showReplyFormDialog" width="800px" append-to-body> | ||||||
|       <el-form ref="formRef" :model="form" :rules="rules" label-width="80px"> |       <el-form ref="formRef" :model="replyForm" :rules="rules" label-width="80px"> | ||||||
|         <el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'"> |         <el-form-item label="消息类型" prop="requestMessageType" v-if="msgType === MsgType.Message"> | ||||||
|           <el-select v-model="form.requestMessageType" placeholder="请选择"> |           <el-select v-model="replyForm.requestMessageType" placeholder="请选择"> | ||||||
|             <template v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value"> |             <template v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value"> | ||||||
|               <el-option |               <el-option | ||||||
|                 v-if="requestMessageTypes.includes(dict.value)" |                 v-if="RequestMessageTypes.includes(dict.value)" | ||||||
|                 :label="dict.label" |                 :label="dict.label" | ||||||
|                 :value="dict.value" |                 :value="dict.value" | ||||||
|               /> |               /> | ||||||
|             </template> |             </template> | ||||||
|           </el-select> |           </el-select> | ||||||
|         </el-form-item> |         </el-form-item> | ||||||
|         <el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'"> |         <el-form-item label="匹配类型" prop="requestMatch" v-if="msgType === MsgType.Keyword"> | ||||||
|           <el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable> |           <el-select v-model="replyForm.requestMatch" placeholder="请选择匹配类型" clearable> | ||||||
|             <el-option |             <el-option | ||||||
|               v-for="dict in getDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)" |               v-for="dict in getDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)" | ||||||
|               :key="dict.value" |               :key="dict.value" | ||||||
|  | @ -146,8 +163,8 @@ | ||||||
|             /> |             /> | ||||||
|           </el-select> |           </el-select> | ||||||
|         </el-form-item> |         </el-form-item> | ||||||
|         <el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'"> |         <el-form-item label="关键词" prop="requestKeyword" v-if="msgType === MsgType.Keyword"> | ||||||
|           <el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable /> |           <el-input v-model="replyForm.requestKeyword" placeholder="请输入内容" clearable /> | ||||||
|         </el-form-item> |         </el-form-item> | ||||||
|         <el-form-item label="回复消息"> |         <el-form-item label="回复消息"> | ||||||
|           <WxReplySelect :objData="objData" v-if="hackResetWxReplySelect" /> |           <WxReplySelect :objData="objData" v-if="hackResetWxReplySelect" /> | ||||||
|  | @ -160,38 +177,47 @@ | ||||||
|     </el-dialog> |     </el-dialog> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| </template> | </template> | ||||||
| <script setup name="MpAutoReply"> | <script setup lang="ts" name="MpAutoReply"> | ||||||
| import WxVideoPlayer from '@/views/mp/components/wx-video-play/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 WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' | ||||||
| import WxMusic from '@/views/mp/components/wx-music/main.vue' | import WxMusic from '@/views/mp/components/wx-music/main.vue' | ||||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||||
| import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' | import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' | ||||||
| import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' | import WxMpSelect from '@/views/mp/components/WxMpSelect.vue' | ||||||
| import * as MpAutoReplyApi from '@/api/mp/autoReply' | import * as MpAutoReplyApi from '@/api/mp/autoReply' | ||||||
| 
 |  | ||||||
| import { DICT_TYPE, getDictOptions } from '@/utils/dict' | import { DICT_TYPE, getDictOptions } from '@/utils/dict' | ||||||
| import { dateFormatter } from '@/utils/formatTime' | import { dateFormatter } from '@/utils/formatTime' | ||||||
| import { ContentWrap } from '@/components/ContentWrap' | import { ContentWrap } from '@/components/ContentWrap' | ||||||
|  | import { TabPaneName } from 'element-plus' | ||||||
| 
 | 
 | ||||||
| const message = useMessage() | const message = useMessage() | ||||||
| 
 | 
 | ||||||
| // const queryFormRef = ref() |  | ||||||
| const formRef = ref() | const formRef = ref() | ||||||
| 
 | 
 | ||||||
| // tab 类型(1、关注时回复;2、消息回复;3、关键词回复) | // 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复) | ||||||
| const type = ref('3') | // 作为tab.name | ||||||
|  | enum MsgType { | ||||||
|  |   Follow = 1, | ||||||
|  |   Message = 2, | ||||||
|  |   Keyword = 3 | ||||||
|  | } | ||||||
|  | const msgType = ref<MsgType>(MsgType.Keyword) | ||||||
| // 允许选择的请求消息类型 | // 允许选择的请求消息类型 | ||||||
| const requestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'] | const RequestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'] | ||||||
| // 遮罩层 | // 遮罩层 | ||||||
| const loading = ref(true) | const loading = ref(true) | ||||||
| // 显示搜索条件 |  | ||||||
| // const showSearch = ref(true) |  | ||||||
| // 总条数 | // 总条数 | ||||||
| const total = ref(0) | const total = ref(0) | ||||||
| // 自动回复列表 | // 自动回复列表 | ||||||
| const list = ref([]) | const list = ref<any[]>([]) | ||||||
|  | 
 | ||||||
| // 查询参数 | // 查询参数 | ||||||
| const queryParams = reactive({ | interface QueryParams { | ||||||
|  |   pageNo: number | ||||||
|  |   pageSize: number | ||||||
|  |   accountId?: number | ||||||
|  | } | ||||||
|  | const queryParams: QueryParams = reactive({ | ||||||
|   pageNo: 1, |   pageNo: 1, | ||||||
|   pageSize: 10, |   pageSize: 10, | ||||||
|   accountId: undefined |   accountId: undefined | ||||||
|  | @ -200,12 +226,50 @@ const queryParams = reactive({ | ||||||
| // 弹出层标题 | // 弹出层标题 | ||||||
| const title = ref('') | const title = ref('') | ||||||
| // 是否显示弹出层 | // 是否显示弹出层 | ||||||
| const open = ref(false) | const showReplyFormDialog = ref(false) | ||||||
| // 表单参数 | // 表单参数 | ||||||
| const form = ref({}) | type ReplyType = 'text' | 'image' | 'voice' | 'video' | 'shortvideo' | 'location' | 'link' | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  | const replyForm = ref<ReplyForm>({}) | ||||||
| // 回复消息 | // 回复消息 | ||||||
| const objData = ref({ | const objData = ref<ObjData>({ | ||||||
|   type: 'text' |   type: 'text', | ||||||
|  |   accountId: undefined | ||||||
| }) | }) | ||||||
| // 表单校验 | // 表单校验 | ||||||
| const rules = { | const rules = { | ||||||
|  | @ -216,8 +280,8 @@ const rules = { | ||||||
| // 重置 WxReplySelect 组件,解决无法清除的问题 | // 重置 WxReplySelect 组件,解决无法清除的问题 | ||||||
| const hackResetWxReplySelect = ref(false) | const hackResetWxReplySelect = ref(false) | ||||||
| 
 | 
 | ||||||
| const accountChanged = (accountId) => { | const onAccountChanged = (id?: number) => { | ||||||
|   queryParams.accountId = accountId |   queryParams.accountId = id | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -227,7 +291,7 @@ const getList = async () => { | ||||||
|   try { |   try { | ||||||
|     const data = await MpAutoReplyApi.getAutoReplyPage({ |     const data = await MpAutoReplyApi.getAutoReplyPage({ | ||||||
|       ...queryParams, |       ...queryParams, | ||||||
|       type: type.value |       type: msgType.value | ||||||
|     }) |     }) | ||||||
|     list.value = data.list |     list.value = data.list | ||||||
|     total.value = data.total |     total.value = data.total | ||||||
|  | @ -242,8 +306,8 @@ const handleQuery = () => { | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const handleTabChange = (tabName) => { | const handleTabChange = (tabName: TabPaneName) => { | ||||||
|   type.value = tabName |   msgType.value = tabName as MsgType | ||||||
|   handleQuery() |   handleQuery() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -252,94 +316,87 @@ const handleAdd = () => { | ||||||
|   reset() |   reset() | ||||||
|   resetEditor() |   resetEditor() | ||||||
|   // 打开表单,并设置初始化 |   // 打开表单,并设置初始化 | ||||||
|   open.value = true |  | ||||||
|   title.value = '新增自动回复' |  | ||||||
|   objData.value = { |   objData.value = { | ||||||
|     type: 'text', |     type: 'text', | ||||||
|     accountId: queryParams.accountId |     accountId: queryParams.accountId | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   title.value = '新增自动回复' | ||||||
|  |   showReplyFormDialog.value = true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 修改按钮操作 */ | /** 修改按钮操作 */ | ||||||
| const handleUpdate = (row) => { | const handleUpdate = async (row: any) => { | ||||||
|   reset() |   reset() | ||||||
|   resetEditor() |   resetEditor() | ||||||
|   console.log(row) |  | ||||||
| 
 | 
 | ||||||
|   MpAutoReplyApi.getAutoReply(row.id).then((data) => { |   const data = await MpAutoReplyApi.getAutoReply(row.id) | ||||||
|     // 设置属性 |   // 设置属性 | ||||||
|     form.value = { ...data } |   replyForm.value = { ...data } | ||||||
|     delete form.value['responseMessageType'] |   delete replyForm.value['responseMessageType'] | ||||||
|     delete form.value['responseContent'] |   delete replyForm.value['responseContent'] | ||||||
|     delete form.value['responseMediaId'] |   delete replyForm.value['responseMediaId'] | ||||||
|     delete form.value['responseMediaUrl'] |   delete replyForm.value['responseMediaUrl'] | ||||||
|     delete form.value['responseDescription'] |   delete replyForm.value['responseDescription'] | ||||||
|     delete form.value['responseArticles'] |   delete replyForm.value['responseArticles'] | ||||||
|     objData.value = { |   objData.value = { | ||||||
|       type: data.responseMessageType, |     type: data.responseMessageType, | ||||||
|       accountId: queryParams.accountId, |     accountId: queryParams.accountId, | ||||||
|       content: data.responseContent, |     content: data.responseContent, | ||||||
|       mediaId: data.responseMediaId, |     mediaId: data.responseMediaId, | ||||||
|       url: data.responseMediaUrl, |     url: data.responseMediaUrl, | ||||||
|       title: data.responseTitle, |     title: data.responseTitle, | ||||||
|       description: data.responseDescription, |     description: data.responseDescription, | ||||||
|       thumbMediaId: data.responseThumbMediaId, |     thumbMediaId: data.responseThumbMediaId, | ||||||
|       thumbMediaUrl: data.responseThumbMediaUrl, |     thumbMediaUrl: data.responseThumbMediaUrl, | ||||||
|       articles: data.responseArticles, |     articles: data.responseArticles, | ||||||
|       musicUrl: data.responseMusicUrl, |     musicUrl: data.responseMusicUrl, | ||||||
|       hqMusicUrl: data.responseHqMusicUrl |     hqMusicUrl: data.responseHqMusicUrl | ||||||
|     } |   } | ||||||
| 
 | 
 | ||||||
|     // 打开表单 |   // 打开表单 | ||||||
|     open.value = true |   title.value = '修改自动回复' | ||||||
|     title.value = '修改自动回复' |   showReplyFormDialog.value = true | ||||||
|   }) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const handleSubmit = () => { | const handleSubmit = async () => { | ||||||
|   formRef.value?.validate((valid) => { |   const valid = await formRef.value?.validate() | ||||||
|     if (!valid) { |   if (!valid) return | ||||||
|       return |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // 处理回复消息 |   // 处理回复消息 | ||||||
|     const form = { ...form.value } |   const submitForm: any = { ...replyForm.value } | ||||||
|     form.responseMessageType = objData.value.type |   submitForm.responseMessageType = objData.value.type | ||||||
|     form.responseContent = objData.value.content |   submitForm.responseContent = objData.value.content | ||||||
|     form.responseMediaId = objData.value.mediaId |   submitForm.responseMediaId = objData.value.mediaId | ||||||
|     form.responseMediaUrl = objData.value.url |   submitForm.responseMediaUrl = objData.value.url | ||||||
|     form.responseTitle = objData.value.title |   submitForm.responseTitle = objData.value.title | ||||||
|     form.responseDescription = objData.value.description |   submitForm.responseDescription = objData.value.description | ||||||
|     form.responseThumbMediaId = objData.value.thumbMediaId |   submitForm.responseThumbMediaId = objData.value.thumbMediaId | ||||||
|     form.responseThumbMediaUrl = objData.value.thumbMediaUrl |   submitForm.responseThumbMediaUrl = objData.value.thumbMediaUrl | ||||||
|     form.responseArticles = objData.value.articles |   submitForm.responseArticles = objData.value.articles | ||||||
|     form.responseMusicUrl = objData.value.musicUrl |   submitForm.responseMusicUrl = objData.value.musicUrl | ||||||
|     form.responseHqMusicUrl = objData.value.hqMusicUrl |   submitForm.responseHqMusicUrl = objData.value.hqMusicUrl | ||||||
| 
 | 
 | ||||||
|     if (form.value.id !== undefined) { |   if (replyForm.value.id !== undefined) { | ||||||
|       MpAutoReplyApi.updateAutoReply(form).then(() => { |     await MpAutoReplyApi.updateAutoReply(submitForm) | ||||||
|         message.success('修改成功') |     message.success('修改成功') | ||||||
|         open.value = false |   } else { | ||||||
|         getList() |     await MpAutoReplyApi.createAutoReply(submitForm) | ||||||
|       }) |     message.success('新增成功') | ||||||
|     } else { |   } | ||||||
|       MpAutoReplyApi.createAutoReply(form).then(() => { | 
 | ||||||
|         message.success('新增成功') |   showReplyFormDialog.value = false | ||||||
|         open.value = false |   getList() | ||||||
|         getList() |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 表单重置 | // 表单重置 | ||||||
| const reset = () => { | const reset = () => { | ||||||
|   form.value = { |   replyForm.value = { | ||||||
|     id: undefined, |     id: undefined, | ||||||
|     accountId: queryParams.accountId, |     accountId: queryParams.accountId, | ||||||
|     type: type.value, |     type: msgType.value, | ||||||
|     requestKeyword: undefined, |     requestKeyword: undefined, | ||||||
|     requestMatch: type.value === '3' ? 1 : undefined, |     requestMatch: msgType.value === MsgType.Keyword ? 1 : undefined, | ||||||
|     requestMessageType: undefined |     requestMessageType: undefined | ||||||
|   } |   } | ||||||
|   formRef.value?.resetFields() |   formRef.value?.resetFields() | ||||||
|  | @ -347,7 +404,7 @@ const reset = () => { | ||||||
| 
 | 
 | ||||||
| // 取消按钮 | // 取消按钮 | ||||||
| const cancel = () => { | const cancel = () => { | ||||||
|   open.value = false |   showReplyFormDialog.value = false | ||||||
|   reset() |   reset() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,10 +1,5 @@ | ||||||
| <template> | <template> | ||||||
|   <el-select |   <el-select v-model="account.id" placeholder="请选择公众号" class="!w-240px" @change="onChanged"> | ||||||
|     v-model="accountId" |  | ||||||
|     placeholder="请选择公众号" |  | ||||||
|     class="!w-240px" |  | ||||||
|     @change="accountChanged" |  | ||||||
|   > |  | ||||||
|     <el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /> |     <el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /> | ||||||
|   </el-select> |   </el-select> | ||||||
| </template> | </template> | ||||||
|  | @ -12,11 +7,14 @@ | ||||||
| <script lang="ts" setup name="WxMpSelect"> | <script lang="ts" setup name="WxMpSelect"> | ||||||
| import * as MpAccountApi from '@/api/mp/account' | import * as MpAccountApi from '@/api/mp/account' | ||||||
| 
 | 
 | ||||||
| const accountId: Ref<number | undefined> = ref() | const account: MpAccountApi.AccountVO = reactive({ | ||||||
|  |   id: undefined, | ||||||
|  |   name: '' | ||||||
|  | }) | ||||||
| const accountList: Ref<MpAccountApi.AccountVO[]> = ref([]) | const accountList: Ref<MpAccountApi.AccountVO[]> = ref([]) | ||||||
| 
 | 
 | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
|   (e: 'change', id: number | undefined): void |   (e: 'change', id?: number, name?: string): void | ||||||
| }>() | }>() | ||||||
| 
 | 
 | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
|  | @ -27,12 +25,12 @@ const handleQuery = async () => { | ||||||
|   accountList.value = await MpAccountApi.getSimpleAccountList() |   accountList.value = await MpAccountApi.getSimpleAccountList() | ||||||
|   // 默认选中第一个 |   // 默认选中第一个 | ||||||
|   if (accountList.value.length > 0) { |   if (accountList.value.length > 0) { | ||||||
|     accountId.value = accountList.value[0].id |     account.id = accountList.value[0].id | ||||||
|     emit('change', accountId.value) |     emit('change', account.id, account.name) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const accountChanged = () => { | const onChanged = () => { | ||||||
|   emit('change', accountId.value) |   emit('change', account.id, account.name) | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| <script setup> | <script setup> | ||||||
| import { ref, reactive } from 'vue' | import { ref, reactive } from 'vue' | ||||||
| import { QuillEditor } from '@vueup/vue-quill' |  | ||||||
| import '@vueup/vue-quill/dist/vue-quill.snow.css' |  | ||||||
| import { getAccessToken } from '@/utils/auth' | import { getAccessToken } from '@/utils/auth' | ||||||
| import editorOptions from './quill-options' | import { Editor } from '@/components/Editor' | ||||||
| 
 | 
 | ||||||
| const BASE_URL = import.meta.env.VITE_BASE_URL | const BASE_URL = import.meta.env.VITE_BASE_URL | ||||||
|  | const actionUrl = BASE_URL + '/admin-api/mp/material/upload-news-image' // 这里写你要上传的图片服务器地址 | ||||||
|  | const headers = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部 | ||||||
| 
 | 
 | ||||||
| const message = useMessage() | const message = useMessage() | ||||||
| 
 | 
 | ||||||
|  | @ -30,21 +30,16 @@ const props = defineProps({ | ||||||
| const emit = defineEmits(['input']) | const emit = defineEmits(['input']) | ||||||
| 
 | 
 | ||||||
| const myQuillEditorRef = ref() | const myQuillEditorRef = ref() | ||||||
| 
 |  | ||||||
| const content = ref(props.value.replace(/data-src/g, 'src')) | const content = ref(props.value.replace(/data-src/g, 'src')) | ||||||
| 
 |  | ||||||
| const loading = ref(false) // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示 | const loading = ref(false) // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示 | ||||||
| 
 |  | ||||||
| const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-news-image') // 这里写你要上传的图片服务器地址 |  | ||||||
| const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部 |  | ||||||
| const uploadData = reactive({ | const uploadData = reactive({ | ||||||
|   type: 'image', // TODO 芋艿:试试要不要换成 thumb |   type: 'image', // TODO 芋艿:试试要不要换成 thumb | ||||||
|   accountId: props.accountId |   accountId: props.accountId | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| const onEditorChange = () => { | const onEditorChange = (text) => { | ||||||
|   //内容改变事件 |   //内容改变事件 | ||||||
|   emit('input', content.value) |   emit('input', text) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 富文本图片上传前 | // 富文本图片上传前 | ||||||
|  | @ -98,104 +93,14 @@ const uploadError = () => { | ||||||
|         :on-error="uploadError" |         :on-error="uploadError" | ||||||
|         :before-upload="beforeUpload" |         :before-upload="beforeUpload" | ||||||
|       /> |       /> | ||||||
|       <QuillEditor |       <Editor | ||||||
|         class="editor" |         editor-id="wxEditor" | ||||||
|         v-model="content" |  | ||||||
|         ref="quillEditorRef" |         ref="quillEditorRef" | ||||||
|         :options="editorOptions" |         :modelValue="content" | ||||||
|         @change="onEditorChange($event)" |         @change="(editor) => onEditorChange(editor.getText())" | ||||||
|       /> |       /> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style> | <style></style> | ||||||
| .editor { |  | ||||||
|   line-height: normal !important; |  | ||||||
|   height: 500px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-tooltip[data-mode='link']::before { |  | ||||||
|   content: '请输入链接地址:'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-tooltip.ql-editing a.ql-action::after { |  | ||||||
|   border-right: 0; |  | ||||||
|   content: '保存'; |  | ||||||
|   padding-right: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-tooltip[data-mode='video']::before { |  | ||||||
|   content: '请输入视频地址:'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-size .ql-picker-label::before, |  | ||||||
| .ql-snow .ql-picker.ql-size .ql-picker-item::before { |  | ||||||
|   content: '14px'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before, |  | ||||||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before { |  | ||||||
|   content: '10px'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before, |  | ||||||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before { |  | ||||||
|   content: '18px'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before, |  | ||||||
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before { |  | ||||||
|   content: '32px'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-label::before, |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-item::before { |  | ||||||
|   content: '文本'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before, |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before { |  | ||||||
|   content: '标题1'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before, |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before { |  | ||||||
|   content: '标题2'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before, |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before { |  | ||||||
|   content: '标题3'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before, |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before { |  | ||||||
|   content: '标题4'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before, |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before { |  | ||||||
|   content: '标题5'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before, |  | ||||||
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before { |  | ||||||
|   content: '标题6'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-font .ql-picker-label::before, |  | ||||||
| .ql-snow .ql-picker.ql-font .ql-picker-item::before { |  | ||||||
|   content: '标准字体'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before, |  | ||||||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before { |  | ||||||
|   content: '衬线字体'; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before, |  | ||||||
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before { |  | ||||||
|   content: '等宽字体'; |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
|  | @ -3,14 +3,22 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- 搜索工作栏 --> |   <!-- 搜索工作栏 --> | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect  --> |     <el-form | ||||||
|     <WxAccountSelect @change="accountChanged"> |       class="-mb-15px" | ||||||
|       <template #actions> |       :model="queryParams" | ||||||
|  |       ref="queryFormRef" | ||||||
|  |       :inline="true" | ||||||
|  |       label-width="68px" | ||||||
|  |     > | ||||||
|  |       <el-form-item label="公众号" prop="accountId"> | ||||||
|  |         <WxMpSelect @change="onAccountChanged" /> | ||||||
|  |       </el-form-item> | ||||||
|  |       <el-form-item> | ||||||
|         <el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']"> |         <el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']"> | ||||||
|           <Icon icon="ep:plus" />新增 |           <Icon icon="ep:plus" />新增 | ||||||
|         </el-button> |         </el-button> | ||||||
|       </template> |       </el-form-item> | ||||||
|     </WxAccountSelect> |     </el-form> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| 
 | 
 | ||||||
|   <!-- 列表 --> |   <!-- 列表 --> | ||||||
|  | @ -58,214 +66,221 @@ | ||||||
|     /> |     /> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| 
 | 
 | ||||||
|   <!-- TODO @Dhb52:迁移成独立路由 --> |  | ||||||
|   <div class="app-container"> |   <div class="app-container"> | ||||||
|     <!-- 添加或修改草稿对话框 --> |     <!-- 添加或修改草稿对话框 --> | ||||||
|     <Teleport to="body"> |     <el-dialog | ||||||
|       <el-dialog |       :title="operateMaterial === 'add' ? '新建图文' : '修改图文'" | ||||||
|         :title="operateMaterial === 'add' ? '新建图文' : '修改图文'" |       width="80%" | ||||||
|         width="80%" |       top="20px" | ||||||
|         top="20px" |       v-model="dialogNewsVisible" | ||||||
|         v-model="dialogNewsVisible" |       :before-close="dialogNewsClose" | ||||||
|         :before-close="dialogNewsClose" |       :close-on-click-modal="false" | ||||||
|         :close-on-click-modal="false" |     > | ||||||
|       > |       <div class="left"> | ||||||
|         <div class="left"> |         <div class="select-item"> | ||||||
|           <div class="select-item"> |           <div v-for="(news, index) in articlesAdd" :key="news.id"> | ||||||
|             <div v-for="(news, index) in articlesAdd" :key="news.id"> |             <div | ||||||
|               <div |               class="news-main father" | ||||||
|                 class="news-main father" |               v-if="index === 0" | ||||||
|                 v-if="index === 0" |               :class="{ activeAddNews: isActiveAddNews === index }" | ||||||
|                 :class="{ activeAddNews: isActiveAddNews === index }" |               @click="activeNews(index)" | ||||||
|                 @click="activeNews(index)" |             > | ||||||
|               > |               <div class="news-content"> | ||||||
|                 <div class="news-content"> |                 <img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl" /> | ||||||
|                   <img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl" /> |                 <div class="news-content-title">{{ news.title }}</div> | ||||||
|                   <div class="news-content-title">{{ news.title }}</div> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="child" v-if="articlesAdd.length > 1"> |  | ||||||
|                   <el-button size="small" @click="downNews(index)" |  | ||||||
|                     ><Icon icon="ep:sort-down" />下移</el-button |  | ||||||
|                   > |  | ||||||
|                   <el-button v-if="operateMaterial === 'add'" size="small" @click="minusNews(index)" |  | ||||||
|                     ><Icon icon="ep:delete" />删除 |  | ||||||
|                   </el-button> |  | ||||||
|                 </div> |  | ||||||
|               </div> |               </div> | ||||||
|               <div |               <div class="child" v-if="articlesAdd.length > 1"> | ||||||
|                 class="news-main-item father" |                 <el-button size="small" @click="downNews(index)" | ||||||
|                 v-if="index > 0" |                   ><Icon icon="ep:sort-down" />下移</el-button | ||||||
|                 :class="{ activeAddNews: isActiveAddNews === index }" |  | ||||||
|                 @click="activeNews(index)" |  | ||||||
|               > |  | ||||||
|                 <div class="news-content-item"> |  | ||||||
|                   <div class="news-content-item-title">{{ news.title }}</div> |  | ||||||
|                   <div class="news-content-item-img"> |  | ||||||
|                     <img |  | ||||||
|                       class="material-img" |  | ||||||
|                       v-if="news.thumbUrl" |  | ||||||
|                       :src="news.thumbUrl" |  | ||||||
|                       height="100%" |  | ||||||
|                     /> |  | ||||||
|                   </div> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="child"> |  | ||||||
|                   <el-button |  | ||||||
|                     v-if="articlesAdd.length > index + 1" |  | ||||||
|                     size="small" |  | ||||||
|                     @click="downNews(index)" |  | ||||||
|                     ><Icon icon="ep:sort-down" />下移 |  | ||||||
|                   </el-button> |  | ||||||
|                   <el-button size="small" @click="upNews(index)" |  | ||||||
|                     ><Icon icon="ep:sort-up" />上移</el-button |  | ||||||
|                   > |  | ||||||
|                   <el-button |  | ||||||
|                     v-if="operateMaterial === 'add'" |  | ||||||
|                     type="danger" |  | ||||||
|                     size="small" |  | ||||||
|                     @click="minusNews(index)" |  | ||||||
|                     ><Icon icon="ep:delete" />删除 |  | ||||||
|                   </el-button> |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|             <el-row justify="center" class="ope-row"> |  | ||||||
|               <el-button |  | ||||||
|                 type="primary" |  | ||||||
|                 circle |  | ||||||
|                 @click="plusNews(item)" |  | ||||||
|                 v-if="articlesAdd.length < 8 && operateMaterial === 'add'" |  | ||||||
|               > |  | ||||||
|                 <Icon icon="ep:plus" /> |  | ||||||
|               </el-button> |  | ||||||
|             </el-row> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0"> |  | ||||||
|           <br /> |  | ||||||
|           <br /> |  | ||||||
|           <br /> |  | ||||||
|           <br /> |  | ||||||
|           <!-- 标题、作者、原文地址 --> |  | ||||||
|           <el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" /> |  | ||||||
|           <el-input |  | ||||||
|             v-model="articlesAdd[isActiveAddNews].author" |  | ||||||
|             placeholder="请输入作者" |  | ||||||
|             style="margin-top: 5px" |  | ||||||
|           /> |  | ||||||
|           <el-input |  | ||||||
|             v-model="articlesAdd[isActiveAddNews].contentSourceUrl" |  | ||||||
|             placeholder="请输入原文地址" |  | ||||||
|             style="margin-top: 5px" |  | ||||||
|           /> |  | ||||||
|           <!-- 封面和摘要 --> |  | ||||||
|           <div class="input-tt">封面和摘要:</div> |  | ||||||
|           <div> |  | ||||||
|             <div class="thumb-div"> |  | ||||||
|               <img |  | ||||||
|                 class="material-img" |  | ||||||
|                 v-if="articlesAdd[isActiveAddNews].thumbUrl" |  | ||||||
|                 :src="articlesAdd[isActiveAddNews].thumbUrl" |  | ||||||
|                 :class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'" |  | ||||||
|               /> |  | ||||||
|               <Icon |  | ||||||
|                 v-else |  | ||||||
|                 icon="ep:plus" |  | ||||||
|                 class="avatar-uploader-icon" |  | ||||||
|                 :class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'" |  | ||||||
|               /> |  | ||||||
|               <div class="thumb-but"> |  | ||||||
|                 <el-upload |  | ||||||
|                   :action="actionUrl" |  | ||||||
|                   :headers="headers" |  | ||||||
|                   multiple |  | ||||||
|                   :limit="1" |  | ||||||
|                   :file-list="fileList" |  | ||||||
|                   :data="uploadData" |  | ||||||
|                   :before-upload="beforeThumbImageUpload" |  | ||||||
|                   :on-success="handleUploadSuccess" |  | ||||||
|                 > |                 > | ||||||
|                   <template #trigger> |                 <el-button v-if="operateMaterial === 'add'" size="small" @click="minusNews(index)" | ||||||
|                     <el-button size="small" type="primary">本地上传</el-button> |                   ><Icon icon="ep:delete" />删除 | ||||||
|                   </template> |                 </el-button> | ||||||
|                   <el-button |               </div> | ||||||
|                     size="small" |             </div> | ||||||
|                     type="primary" |             <div | ||||||
|                     @click="openMaterial" |               class="news-main-item father" | ||||||
|                     style="margin-left: 5px" |               v-if="index > 0" | ||||||
|                     >素材库选择</el-button |               :class="{ activeAddNews: isActiveAddNews === index }" | ||||||
|                   > |               @click="activeNews(index)" | ||||||
|                   <template #tip> |             > | ||||||
|                     <div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div> |               <div class="news-content-item"> | ||||||
|                   </template> |                 <div class="news-content-item-title">{{ news.title }}</div> | ||||||
|                 </el-upload> |                 <div class="news-content-item-img"> | ||||||
|  |                   <img | ||||||
|  |                     class="material-img" | ||||||
|  |                     v-if="news.thumbUrl" | ||||||
|  |                     :src="news.thumbUrl" | ||||||
|  |                     height="100%" | ||||||
|  |                   /> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |               <div class="child"> | ||||||
|  |                 <el-button | ||||||
|  |                   v-if="articlesAdd.length > index + 1" | ||||||
|  |                   size="small" | ||||||
|  |                   @click="downNews(index)" | ||||||
|  |                   ><Icon icon="ep:sort-down" />下移 | ||||||
|  |                 </el-button> | ||||||
|  |                 <el-button size="small" @click="upNews(index)" | ||||||
|  |                   ><Icon icon="ep:sort-up" />上移</el-button | ||||||
|  |                 > | ||||||
|  |                 <el-button | ||||||
|  |                   v-if="operateMaterial === 'add'" | ||||||
|  |                   type="danger" | ||||||
|  |                   size="small" | ||||||
|  |                   @click="minusNews(index)" | ||||||
|  |                   ><Icon icon="ep:delete" />删除 | ||||||
|  |                 </el-button> | ||||||
|               </div> |               </div> | ||||||
|               <Teleport to="body"> |  | ||||||
|                 <el-dialog title="选择图片" v-model="dialogImageVisible" width="80%"> |  | ||||||
|                   <WxMaterialSelect |  | ||||||
|                     ref="materialSelectRef" |  | ||||||
|                     :objData="{ type: 'image', accountId: queryParams.accountId }" |  | ||||||
|                     @select-material="selectMaterial" |  | ||||||
|                   /> |  | ||||||
|                 </el-dialog> |  | ||||||
|               </Teleport> |  | ||||||
|             </div> |             </div> | ||||||
|             <el-input |  | ||||||
|               :rows="8" |  | ||||||
|               type="textarea" |  | ||||||
|               v-model="articlesAdd[isActiveAddNews].digest" |  | ||||||
|               placeholder="请输入摘要" |  | ||||||
|               class="digest" |  | ||||||
|               maxlength="120" |  | ||||||
|               style="float: right" |  | ||||||
|             /> |  | ||||||
|           </div> |           </div> | ||||||
|           <!--富文本编辑器组件--> |           <el-row justify="center" class="ope-row"> | ||||||
|           <el-row> |             <el-button | ||||||
|             <WxEditor |               type="primary" | ||||||
|               v-model="articlesAdd[isActiveAddNews].content" |               circle | ||||||
|               :account-id="uploadData.accountId" |               @click="plusNews" | ||||||
|               v-if="hackResetEditor" |               v-if="articlesAdd.length < 8 && operateMaterial === 'add'" | ||||||
|             /> |             > | ||||||
|  |               <Icon icon="ep:plus" /> | ||||||
|  |             </el-button> | ||||||
|           </el-row> |           </el-row> | ||||||
|         </div> |         </div> | ||||||
|         <template #footer> |       </div> | ||||||
|           <el-button @click="dialogNewsVisible = false">取 消</el-button> |       <div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0"> | ||||||
|           <el-button type="primary" @click="submitForm">提 交</el-button> |         <br /> | ||||||
|         </template> |         <br /> | ||||||
|       </el-dialog> |         <br /> | ||||||
|     </Teleport> |         <br /> | ||||||
|  |         <!-- 标题、作者、原文地址 --> | ||||||
|  |         <el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" /> | ||||||
|  |         <el-input | ||||||
|  |           v-model="articlesAdd[isActiveAddNews].author" | ||||||
|  |           placeholder="请输入作者" | ||||||
|  |           style="margin-top: 5px" | ||||||
|  |         /> | ||||||
|  |         <el-input | ||||||
|  |           v-model="articlesAdd[isActiveAddNews].contentSourceUrl" | ||||||
|  |           placeholder="请输入原文地址" | ||||||
|  |           style="margin-top: 5px" | ||||||
|  |         /> | ||||||
|  |         <!-- 封面和摘要 --> | ||||||
|  |         <div class="input-tt">封面和摘要:</div> | ||||||
|  |         <div> | ||||||
|  |           <div class="thumb-div"> | ||||||
|  |             <img | ||||||
|  |               class="material-img" | ||||||
|  |               v-if="articlesAdd[isActiveAddNews].thumbUrl" | ||||||
|  |               :src="articlesAdd[isActiveAddNews].thumbUrl" | ||||||
|  |               :class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'" | ||||||
|  |             /> | ||||||
|  |             <Icon | ||||||
|  |               v-else | ||||||
|  |               icon="ep:plus" | ||||||
|  |               class="avatar-uploader-icon" | ||||||
|  |               :class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'" | ||||||
|  |             /> | ||||||
|  |             <div class="thumb-but"> | ||||||
|  |               <el-upload | ||||||
|  |                 :action="uploadUrl" | ||||||
|  |                 :headers="headers" | ||||||
|  |                 multiple | ||||||
|  |                 :limit="1" | ||||||
|  |                 :file-list="fileList" | ||||||
|  |                 :data="uploadData" | ||||||
|  |                 :before-upload="beforeThumbImageUpload" | ||||||
|  |                 :on-success="handleUploadSuccess" | ||||||
|  |               > | ||||||
|  |                 <template #trigger> | ||||||
|  |                   <el-button size="small" type="primary">本地上传</el-button> | ||||||
|  |                 </template> | ||||||
|  |                 <el-button | ||||||
|  |                   size="small" | ||||||
|  |                   type="primary" | ||||||
|  |                   @click="openMaterial" | ||||||
|  |                   style="margin-left: 5px" | ||||||
|  |                   >素材库选择</el-button | ||||||
|  |                 > | ||||||
|  |                 <template #tip> | ||||||
|  |                   <div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div> | ||||||
|  |                 </template> | ||||||
|  |               </el-upload> | ||||||
|  |             </div> | ||||||
|  |             <el-dialog title="选择图片" v-model="dialogImageVisible" width="80%" append-to-body> | ||||||
|  |               <WxMaterialSelect | ||||||
|  |                 ref="materialSelectRef" | ||||||
|  |                 :objData="{ type: 'image', accountId: queryParams.accountId }" | ||||||
|  |                 @select-material="selectMaterial" | ||||||
|  |               /> | ||||||
|  |             </el-dialog> | ||||||
|  |           </div> | ||||||
|  |           <el-input | ||||||
|  |             :rows="8" | ||||||
|  |             type="textarea" | ||||||
|  |             v-model="articlesAdd[isActiveAddNews].digest" | ||||||
|  |             placeholder="请输入摘要" | ||||||
|  |             class="digest" | ||||||
|  |             maxlength="120" | ||||||
|  |             style="float: right" | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |         <!--富文本编辑器组件--> | ||||||
|  |         <el-row> | ||||||
|  |           <WxEditor | ||||||
|  |             v-model="articlesAdd[isActiveAddNews].content" | ||||||
|  |             :account-id="uploadData.accountId" | ||||||
|  |             v-if="hackResetEditor" | ||||||
|  |           /> | ||||||
|  |         </el-row> | ||||||
|  |       </div> | ||||||
|  |       <template #footer> | ||||||
|  |         <el-button @click="dialogNewsVisible = false">取 消</el-button> | ||||||
|  |         <el-button type="primary" @click="submitForm">提 交</el-button> | ||||||
|  |       </template> | ||||||
|  |     </el-dialog> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| <script setup name="MpDraft"> | 
 | ||||||
|  | <script setup lang="ts" name="MpDraft"> | ||||||
| import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue' | import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue' | ||||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||||
| import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' | import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' | ||||||
| import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' | import WxMpSelect from '@/views/mp/components/WxMpSelect.vue' | ||||||
| import { getAccessToken } from '@/utils/auth' | import { getAccessToken } from '@/utils/auth' | ||||||
| import * as MpDraftApi from '@/api/mp/draft' | import * as MpDraftApi from '@/api/mp/draft' | ||||||
| import * as MpFreePublishApi from '@/api/mp/freePublish' | import * as MpFreePublishApi from '@/api/mp/freePublish' | ||||||
|  | import { UploadFiles, UploadProps, UploadRawFile } from 'element-plus' | ||||||
| // 可以用改本地数据模拟,避免API调用超限 | // 可以用改本地数据模拟,避免API调用超限 | ||||||
| // import drafts from './mock' | // import drafts from './mock' | ||||||
| 
 | 
 | ||||||
| const message = useMessage() // 消息 | const message = useMessage() // 消息 | ||||||
| 
 | 
 | ||||||
| const loading = ref(true) // 列表的加载中 | const loading = ref(true) // 列表的加载中 | ||||||
|  | const list = ref<any[]>([]) // 列表的数据 | ||||||
| const total = ref(0) // 列表的总页数 | const total = ref(0) // 列表的总页数 | ||||||
| const list = ref([]) // 列表的数据 | interface QueryParams { | ||||||
| const queryParams = reactive({ |   pageNo: number | ||||||
|  |   pageSize: number | ||||||
|  |   accountId?: number | ||||||
|  | } | ||||||
|  | const queryParams: QueryParams = reactive({ | ||||||
|   pageNo: 1, |   pageNo: 1, | ||||||
|   pageSize: 10, |   pageSize: 10, | ||||||
|   accountId: undefined |   accountId: undefined | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| // ========== 文件上传 ========== | // ========== 文件上传 ========== | ||||||
| const materialSelectRef = ref() |  | ||||||
| const BASE_URL = import.meta.env.VITE_BASE_URL | const BASE_URL = import.meta.env.VITE_BASE_URL | ||||||
| const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') // 上传永久素材的地址 | const uploadUrl = BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址 | ||||||
| const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部 | const headers = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部 | ||||||
| const fileList = ref([]) | 
 | ||||||
| const uploadData = reactive({ | const materialSelectRef = ref<InstanceType<typeof WxMaterialSelect> | null>(null) | ||||||
|  | const fileList = ref<UploadFiles>([]) | ||||||
|  | interface UploadData { | ||||||
|  |   type: 'image' | 'video' | 'audio' | ||||||
|  |   accountId?: number | ||||||
|  | } | ||||||
|  | const uploadData: UploadData = reactive({ | ||||||
|   type: 'image', |   type: 'image', | ||||||
|   accountId: 1 |   accountId: 1 | ||||||
| }) | }) | ||||||
|  | @ -273,34 +288,28 @@ const uploadData = reactive({ | ||||||
| // ========== 草稿新建 or 修改 ========== | // ========== 草稿新建 or 修改 ========== | ||||||
| const dialogNewsVisible = ref(false) | const dialogNewsVisible = ref(false) | ||||||
| const addMaterialLoading = ref(false) // 添加草稿的 loading 标识 | const addMaterialLoading = ref(false) // 添加草稿的 loading 标识 | ||||||
| const articlesAdd = ref([]) | const articlesAdd = ref<any[]>([]) | ||||||
| const isActiveAddNews = ref(0) | const isActiveAddNews = ref(0) | ||||||
| const dialogImageVisible = ref(false) | const dialogImageVisible = ref(false) | ||||||
| const operateMaterial = ref('add') | const operateMaterial = ref<'add' | 'edit'>('add') | ||||||
| const articlesMediaId = ref('') | const articlesMediaId = ref('') | ||||||
| const hackResetEditor = ref(false) | const hackResetEditor = ref(false) | ||||||
| 
 | 
 | ||||||
| /** 侦听公众号变化 **/ | /** 侦听公众号变化 **/ | ||||||
| const accountChanged = (accountId) => { | const onAccountChanged = (id?: number) => { | ||||||
|   setAccountId(accountId) |   setAccountId(id) | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ======================== 列表查询 ======================== | // ======================== 列表查询 ======================== | ||||||
| /** 设置账号编号 */ | /** 设置账号编号 */ | ||||||
| const setAccountId = (accountId) => { | const setAccountId = (id?: number) => { | ||||||
|   queryParams.accountId = accountId |   queryParams.accountId = id | ||||||
|   uploadData.accountId = accountId |   uploadData.accountId = id | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 查询列表 */ | /** 查询列表 */ | ||||||
| const getList = async () => { | const getList = async () => { | ||||||
|   // 如果没有选中公众号账号,则进行提示。 |  | ||||||
|   if (!queryParams.accountId) { |  | ||||||
|     message.error('未选中公众号,无法查询草稿箱') |  | ||||||
|     return false |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   loading.value = true |   loading.value = true | ||||||
|   try { |   try { | ||||||
|     const drafts = await MpDraftApi.getDraftPage(queryParams) |     const drafts = await MpDraftApi.getDraftPage(queryParams) | ||||||
|  | @ -329,7 +338,7 @@ const handleAdd = () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 更新按钮操作 */ | /** 更新按钮操作 */ | ||||||
| const handleUpdate = (item) => { | const handleUpdate = (item: any) => { | ||||||
|   resetEditor() |   resetEditor() | ||||||
|   reset() |   reset() | ||||||
|   articlesMediaId.value = item.mediaId |   articlesMediaId.value = item.mediaId | ||||||
|  | @ -340,39 +349,30 @@ const handleUpdate = (item) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 提交按钮 */ | /** 提交按钮 */ | ||||||
| const submitForm = () => { | const submitForm = async () => { | ||||||
|   // TODO @Dhb52: 参考别的模块写法,改成 await 方式 |  | ||||||
|   addMaterialLoading.value = true |   addMaterialLoading.value = true | ||||||
|   if (operateMaterial.value === 'add') { |   try { | ||||||
|     MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value) |     if (operateMaterial.value === 'add') { | ||||||
|       .then(() => { |       await MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value) | ||||||
|         message.notifySuccess('新增成功') |       message.notifySuccess('新增成功') | ||||||
|         dialogNewsVisible.value = false |     } else { | ||||||
|         getList() |       await MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value) | ||||||
|       }) |       message.notifySuccess('更新成功') | ||||||
|       .finally(() => { |     } | ||||||
|         addMaterialLoading.value = false |   } finally { | ||||||
|       }) |     dialogNewsVisible.value = false | ||||||
|   } else { |     addMaterialLoading.value = false | ||||||
|     MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value) |     getList() | ||||||
|       .then(() => { |  | ||||||
|         message.notifySuccess('更新成功') |  | ||||||
|         dialogNewsVisible.value = false |  | ||||||
|         getList() |  | ||||||
|       }) |  | ||||||
|       .finally(() => { |  | ||||||
|         addMaterialLoading.value = false |  | ||||||
|       }) |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 关闭弹窗 | // 关闭弹窗 | ||||||
| const dialogNewsClose = async (done) => { | const dialogNewsClose = async (onDone: () => {}) => { | ||||||
|   try { |   try { | ||||||
|     await message.confirm('修改内容可能还未保存,确定关闭吗?') |     await message.confirm('修改内容可能还未保存,确定关闭吗?') | ||||||
|     reset() |     reset() | ||||||
|     resetEditor() |     resetEditor() | ||||||
|     done() |     onDone() | ||||||
|   } catch {} |   } catch {} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -391,7 +391,7 @@ const resetEditor = () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 将图文向下移动 | // 将图文向下移动 | ||||||
| const downNews = (index) => { | const downNews = (index: number) => { | ||||||
|   let temp = articlesAdd.value[index] |   let temp = articlesAdd.value[index] | ||||||
|   articlesAdd.value[index] = articlesAdd.value[index + 1] |   articlesAdd.value[index] = articlesAdd.value[index + 1] | ||||||
|   articlesAdd.value[index + 1] = temp |   articlesAdd.value[index + 1] = temp | ||||||
|  | @ -407,13 +407,13 @@ const upNews = (index) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 选中指定 index 的图文 | // 选中指定 index 的图文 | ||||||
| const activeNews = (index) => { | const activeNews = (index: number) => { | ||||||
|   resetEditor() |   resetEditor() | ||||||
|   isActiveAddNews.value = index |   isActiveAddNews.value = index | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 删除指定 index 的图文 | // 删除指定 index 的图文 | ||||||
| const minusNews = async (index) => { | const minusNews = async (index: number) => { | ||||||
|   try { |   try { | ||||||
|     await message.confirm('确定删除该图文吗?') |     await message.confirm('确定删除该图文吗?') | ||||||
|     articlesAdd.value.splice(index, 1) |     articlesAdd.value.splice(index, 1) | ||||||
|  | @ -446,20 +446,17 @@ const buildEmptyArticle = () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ======================== 文件上传 ======================== | // ======================== 文件上传 ======================== | ||||||
| const beforeThumbImageUpload = (file) => { | const beforeThumbImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => { | ||||||
|   addMaterialLoading.value = true |   addMaterialLoading.value = true | ||||||
|   const isType = |   const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes( | ||||||
|     file.type === 'image/jpeg' || |     rawFile.type | ||||||
|     file.type === 'image/png' || |   ) | ||||||
|     file.type === 'image/gif' || |  | ||||||
|     file.type === 'image/bmp' || |  | ||||||
|     file.type === 'image/jpg' |  | ||||||
|   if (!isType) { |   if (!isType) { | ||||||
|     message.error('上传图片格式不对!') |     message.error('上传图片格式不对!') | ||||||
|     addMaterialLoading.value = false |     addMaterialLoading.value = false | ||||||
|     return false |     return false | ||||||
|   } |   } | ||||||
|   const isLt = file.size / 1024 / 1024 < 2 |   const isLt = rawFile.size / 1024 / 1024 < 2 | ||||||
|   if (!isLt) { |   if (!isLt) { | ||||||
|     message.error('上传图片大小不能超过 2M!') |     message.error('上传图片大小不能超过 2M!') | ||||||
|     addMaterialLoading.value = false |     addMaterialLoading.value = false | ||||||
|  | @ -469,7 +466,7 @@ const beforeThumbImageUpload = (file) => { | ||||||
|   return true |   return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const handleUploadSuccess = (response, file, fileList) => { | const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => { | ||||||
|   addMaterialLoading.value = false |   addMaterialLoading.value = false | ||||||
|   if (response.code !== 0) { |   if (response.code !== 0) { | ||||||
|     message.error('上传出错:' + response.msg) |     message.error('上传出错:' + response.msg) | ||||||
|  | @ -485,7 +482,7 @@ const handleUploadSuccess = (response, file, fileList) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 选择 or 上传完素材,设置回草稿 | // 选择 or 上传完素材,设置回草稿 | ||||||
| const selectMaterial = (item) => { | const selectMaterial = (item: any) => { | ||||||
|   dialogImageVisible.value = false |   dialogImageVisible.value = false | ||||||
|   articlesAdd.value[isActiveAddNews.value].thumbMediaId = item.mediaId |   articlesAdd.value[isActiveAddNews.value].thumbMediaId = item.mediaId | ||||||
|   articlesAdd.value[isActiveAddNews.value].thumbUrl = item.url |   articlesAdd.value[isActiveAddNews.value].thumbUrl = item.url | ||||||
|  | @ -494,14 +491,10 @@ const selectMaterial = (item) => { | ||||||
| // 打开素材选择 | // 打开素材选择 | ||||||
| const openMaterial = () => { | const openMaterial = () => { | ||||||
|   dialogImageVisible.value = true |   dialogImageVisible.value = true | ||||||
|   try { |  | ||||||
|     materialSelectRef.value.queryParams.accountId = queryParams.accountId // 强制设置下 accountId,避免二次查询不对 |  | ||||||
|     materialSelectRef.value.handleQuery() // 刷新列表,失败也无所谓 |  | ||||||
|   } catch (e) {} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ======================== 草稿箱发布 ======================== | // ======================== 草稿箱发布 ======================== | ||||||
| const handlePublish = async (item) => { | const handlePublish = async (item: any) => { | ||||||
|   const accountId = queryParams.accountId |   const accountId = queryParams.accountId | ||||||
|   const mediaId = item.mediaId |   const mediaId = item.mediaId | ||||||
|   const content = |   const content = | ||||||
|  | @ -510,19 +503,19 @@ const handlePublish = async (item) => { | ||||||
|     await message.confirm(content) |     await message.confirm(content) | ||||||
|     await MpFreePublishApi.submitFreePublish(accountId, mediaId) |     await MpFreePublishApi.submitFreePublish(accountId, mediaId) | ||||||
|     message.notifySuccess('发布成功') |     message.notifySuccess('发布成功') | ||||||
|     await getList() |     getList() | ||||||
|   } catch {} |   } catch {} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 删除按钮操作 */ | /** 删除按钮操作 */ | ||||||
| const handleDelete = async (item) => { | const handleDelete = async (item: any) => { | ||||||
|   const accountId = queryParams.accountId |   const accountId = queryParams.accountId | ||||||
|   const mediaId = item.mediaId |   const mediaId = item.mediaId | ||||||
|   try { |   try { | ||||||
|     await message.confirm('此操作将永久删除该草稿, 是否继续?') |     await message.confirm('此操作将永久删除该草稿, 是否继续?') | ||||||
|     await MpDraftApi.deleteDraft(accountId, mediaId) |     await MpDraftApi.deleteDraft(accountId, mediaId) | ||||||
|     message.notifySuccess('删除成功') |     message.notifySuccess('删除成功') | ||||||
|     await getList() |     getList() | ||||||
|   } catch {} |   } catch {} | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -3,8 +3,17 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- 搜索工作栏 --> |   <!-- 搜索工作栏 --> | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect  --> |     <el-form | ||||||
|     <WxAccountSelect @change="(accountId) => accountChanged(accountId)" /> |       class="-mb-15px" | ||||||
|  |       :model="queryParams" | ||||||
|  |       ref="queryFormRef" | ||||||
|  |       :inline="true" | ||||||
|  |       label-width="68px" | ||||||
|  |     > | ||||||
|  |       <el-form-item label="公众号" prop="accountId"> | ||||||
|  |         <WxMpSelect @change="onAccountChanged" /> | ||||||
|  |       </el-form-item> | ||||||
|  |     </el-form> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| 
 | 
 | ||||||
|   <!-- 列表 --> |   <!-- 列表 --> | ||||||
|  | @ -39,26 +48,32 @@ | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script setup name="MpFreePublish"> | <script lang="ts" setup name="MpFreePublish"> | ||||||
| import * as FreePublishApi from '@/api/mp/freePublish' | import * as FreePublishApi from '@/api/mp/freePublish' | ||||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||||
| import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' | import WxMpSelect from '@/views/mp/components/WxMpSelect.vue' | ||||||
| 
 | 
 | ||||||
| const message = useMessage() // 消息弹窗 | const message = useMessage() // 消息弹窗 | ||||||
| const { t } = useI18n() // 国际化 | const { t } = useI18n() // 国际化 | ||||||
| 
 | 
 | ||||||
| const loading = ref(true) // 列表的加载中 | const loading = ref(true) // 列表的加载中 | ||||||
| const total = ref(0) // 列表的总页数 | const total = ref(0) // 列表的总页数 | ||||||
| const list = ref([]) // 列表的数据 | const list = ref<any[]>([]) // 列表的数据 | ||||||
| const queryParams = reactive({ | 
 | ||||||
|  | interface QueryParams { | ||||||
|  |   pageNo: number | ||||||
|  |   pageSize: number | ||||||
|  |   accountId?: number | ||||||
|  | } | ||||||
|  | const queryParams: QueryParams = reactive({ | ||||||
|   pageNo: 1, |   pageNo: 1, | ||||||
|   pageSize: 10, |   pageSize: 10, | ||||||
|   accountId: undefined |   accountId: undefined | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| /** 侦听公众号变化 **/ | /** 侦听公众号变化 **/ | ||||||
| const accountChanged = (accountId) => { | const onAccountChanged = (id: number | undefined) => { | ||||||
|   queryParams.accountId = accountId |   queryParams.accountId = id | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -75,7 +90,7 @@ const getList = async () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 删除按钮操作 */ | /** 删除按钮操作 */ | ||||||
| const handleDelete = async (item) => { | const handleDelete = async (item: any) => { | ||||||
|   try { |   try { | ||||||
|     // 删除的二次确认 |     // 删除的二次确认 | ||||||
|     await message.delConfirm('删除后用户将无法访问此页面,确定删除?') |     await message.delConfirm('删除后用户将无法访问此页面,确定删除?') | ||||||
|  |  | ||||||
|  | @ -4,13 +4,13 @@ | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <el-form class="-mb-15px" :inline="true" label-width="68px"> |     <el-form class="-mb-15px" :inline="true" label-width="68px"> | ||||||
|       <el-form-item label="公众号" prop="accountId"> |       <el-form-item label="公众号" prop="accountId"> | ||||||
|         <WxMpSelect @change="accountChange" /> |         <WxMpSelect @change="onAccountChanged" /> | ||||||
|       </el-form-item> |       </el-form-item> | ||||||
|     </el-form> |     </el-form> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| 
 | 
 | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <el-tabs v-model="type" @tab-change="handleTabChange"> |     <el-tabs v-model="type" @tab-change="onTabChange"> | ||||||
|       <!-- tab 1:图片  --> |       <!-- tab 1:图片  --> | ||||||
|       <el-tab-pane name="image"> |       <el-tab-pane name="image"> | ||||||
|         <template #label> |         <template #label> | ||||||
|  | @ -93,7 +93,7 @@ | ||||||
|           <el-table-column label="文件名" align="center" prop="name" /> |           <el-table-column label="文件名" align="center" prop="name" /> | ||||||
|           <el-table-column label="语音" align="center"> |           <el-table-column label="语音" align="center"> | ||||||
|             <template #default="scope"> |             <template #default="scope"> | ||||||
|               <WxVoicePlayer :url="scope.row.url" /> |               <WxVoicePlayer v-if="scope.row.url" :url="scope.row.url" /> | ||||||
|             </template> |             </template> | ||||||
|           </el-table-column> |           </el-table-column> | ||||||
|           <el-table-column |           <el-table-column | ||||||
|  | @ -188,10 +188,8 @@ | ||||||
|             </el-row> |             </el-row> | ||||||
|           </el-form> |           </el-form> | ||||||
|           <template #footer> |           <template #footer> | ||||||
|             <!-- <span class="dialog-footer"> --> |  | ||||||
|             <el-button @click="cancelVideo">取 消</el-button> |             <el-button @click="cancelVideo">取 消</el-button> | ||||||
|             <el-button type="primary" @click="submitVideo">提 交</el-button> |             <el-button type="primary" @click="submitVideo">提 交</el-button> | ||||||
|             <!-- </span> --> |  | ||||||
|           </template> |           </template> | ||||||
|         </el-dialog> |         </el-dialog> | ||||||
| 
 | 
 | ||||||
|  | @ -203,7 +201,7 @@ | ||||||
|           <el-table-column label="介绍" align="center" prop="introduction" /> |           <el-table-column label="介绍" align="center" prop="introduction" /> | ||||||
|           <el-table-column label="视频" align="center"> |           <el-table-column label="视频" align="center"> | ||||||
|             <template #default="scope"> |             <template #default="scope"> | ||||||
|               <WxVideoPlayer :url="scope.row.url" /> |               <WxVideoPlayer v-if="scope.row.url" :url="scope.row.url" /> | ||||||
|             </template> |             </template> | ||||||
|           </el-table-column> |           </el-table-column> | ||||||
|           <el-table-column |           <el-table-column | ||||||
|  | @ -284,9 +282,9 @@ const type = ref<MatertialType>('image') | ||||||
| // 遮罩层 | // 遮罩层 | ||||||
| const loading = ref(false) | const loading = ref(false) | ||||||
| // 总条数 | // 总条数 | ||||||
| const total = ref(0) |  | ||||||
| // 数据列表 | // 数据列表 | ||||||
| const list = ref([]) | const list = ref<any[]>([]) | ||||||
|  | const total = ref(0) | ||||||
| // 查询参数 | // 查询参数 | ||||||
| interface QueryParams { | interface QueryParams { | ||||||
|   pageNo: number |   pageNo: number | ||||||
|  | @ -319,8 +317,8 @@ const dialogVideoVisible = ref(false) | ||||||
| const addMaterialLoading = ref(false) | const addMaterialLoading = ref(false) | ||||||
| 
 | 
 | ||||||
| /** 侦听公众号变化 **/ | /** 侦听公众号变化 **/ | ||||||
| const accountChange = (accountId: number | undefined) => { | const onAccountChanged = (id?: number) => { | ||||||
|   queryParams.accountId = accountId |   queryParams.accountId = id | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -346,7 +344,7 @@ const handleQuery = () => { | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const handleTabChange = (tabName: TabPaneName) => { | const onTabChange = (tabName: TabPaneName) => { | ||||||
|   // 设置 type |   // 设置 type | ||||||
|   uploadData.type = tabName as MatertialType |   uploadData.type = tabName as MatertialType | ||||||
| 
 | 
 | ||||||
|  | @ -359,54 +357,49 @@ const handleTabChange = (tabName: TabPaneName) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ======================== 文件上传 ======================== | // ======================== 文件上传 ======================== | ||||||
| const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => { | const beforeUpload = (rawFile: UploadRawFile, category: 'image' | 'audio' | 'video'): boolean => { | ||||||
|   const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes( |   let allowTypes: string[] = [] | ||||||
|     rawFile.type |   let maxSizeMB = 0 | ||||||
|   ) |   let name = '' | ||||||
|   if (!isType) { | 
 | ||||||
|     message.error('上传图片格式不对!') |   switch (category) { | ||||||
|  |     case 'image': | ||||||
|  |       allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'] | ||||||
|  |       maxSizeMB = 2 | ||||||
|  |       name = '图片' | ||||||
|  |       break | ||||||
|  |     case 'audio': | ||||||
|  |       allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr'] | ||||||
|  |       maxSizeMB = 2 | ||||||
|  |       name = '图片' | ||||||
|  |       break | ||||||
|  |     case 'video': | ||||||
|  |       allowTypes = ['video/mp4'] | ||||||
|  |       maxSizeMB = 10 | ||||||
|  |       name = '视频' | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!allowTypes.includes(rawFile.type)) { | ||||||
|  |     message.error(`上传${name}格式不对!`) | ||||||
|     return false |     return false | ||||||
|   } |   } | ||||||
|   const isLt = rawFile.size / 1024 / 1024 < 2 |   // 校验大小 | ||||||
|   if (!isLt) { |   if (rawFile.size / 1024 / 1024 > maxSizeMB) { | ||||||
|     message.error('上传图片大小不能超过 2M!') |     message.error(`上传${name}大小不能超过${maxSizeMB}M!`) | ||||||
|     return false |     return false | ||||||
|   } |   } | ||||||
|   loading.value = true |   loading.value = true | ||||||
|   return true |   return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => { | const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => | ||||||
|   const isType = ['audio/mp3', 'audio/wma', 'audio/wav', 'audio/amr'].includes(file.type) |   beforeUpload(rawFile, 'image') | ||||||
|   const isLt = rawFile.size / 1024 / 1024 < 2 |  | ||||||
|   if (!isType) { |  | ||||||
|     message.error('上传语音格式不对!') |  | ||||||
|     return false |  | ||||||
|   } |  | ||||||
|   if (!isLt) { |  | ||||||
|     message.error('上传语音大小不能超过 2M!') |  | ||||||
|     return false |  | ||||||
|   } |  | ||||||
|   loading.value = true |  | ||||||
|   return true |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => { | const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => | ||||||
|   const isType = rawFile.type === 'video/mp4' |   beforeUpload(rawFile, 'audio') | ||||||
|   if (!isType) { |  | ||||||
|     message.error('上传视频格式不对!') |  | ||||||
|     return false |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   const isLt = rawFile.size / 1024 / 1024 < 10 | const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => | ||||||
|   if (!isLt) { |   beforeUpload(rawFile, 'video') | ||||||
|     message.error('上传视频大小不能超过 10M!') |  | ||||||
|     return false |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   addMaterialLoading.value = true |  | ||||||
|   return true |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => { | const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => { | ||||||
|   loading.value = false |   loading.value = false | ||||||
|  | @ -441,6 +434,7 @@ const submitVideo = () => { | ||||||
|   }) |   }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // 弹出 video 新建的表单 | ||||||
| const handleAddVideo = () => { | const handleAddVideo = () => { | ||||||
|   resetVideo() |   resetVideo() | ||||||
|   dialogVideoVisible.value = true |   dialogVideoVisible.value = true | ||||||
|  |  | ||||||
|  | @ -2,8 +2,11 @@ | ||||||
|   <doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" /> |   <doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" /> | ||||||
|   <!-- 搜索工作栏 --> |   <!-- 搜索工作栏 --> | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect  --> |     <el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px"> | ||||||
|     <WxAccountSelect @change="accountChanged" /> |       <el-form-item label="公众号" prop="accountId"> | ||||||
|  |         <WxMpSelect @change="onAccountChanged" /> | ||||||
|  |       </el-form-item> | ||||||
|  |     </el-form> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| 
 | 
 | ||||||
|   <!-- 列表 --> |   <!-- 列表 --> | ||||||
|  | @ -12,7 +15,7 @@ | ||||||
|       <!--左边配置菜单--> |       <!--左边配置菜单--> | ||||||
|       <div class="left"> |       <div class="left"> | ||||||
|         <div class="weixin-hd"> |         <div class="weixin-hd"> | ||||||
|           <div class="weixin-title">{{ name }}</div> |           <div class="weixin-title">{{ accountName }}</div> | ||||||
|         </div> |         </div> | ||||||
|         <div class="weixin-menu menu_main clearfix"> |         <div class="weixin-menu menu_main clearfix"> | ||||||
|           <div class="menu_bottom" v-for="(item, i) of menuList" :key="i"> |           <div class="menu_bottom" v-for="(item, i) of menuList" :key="i"> | ||||||
|  | @ -68,7 +71,7 @@ | ||||||
|       <div v-if="showRightFlag" class="right"> |       <div v-if="showRightFlag" class="right"> | ||||||
|         <div class="configure_page"> |         <div class="configure_page"> | ||||||
|           <div class="delete_btn"> |           <div class="delete_btn"> | ||||||
|             <el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)"> |             <el-button size="small" type="danger" @click="handleDeleteMenu"> | ||||||
|               删除当前菜单<Icon icon="ep:delete" /> |               删除当前菜单<Icon icon="ep:delete" /> | ||||||
|             </el-button> |             </el-button> | ||||||
|           </div> |           </div> | ||||||
|  | @ -155,7 +158,7 @@ | ||||||
|                 <div v-else> |                 <div v-else> | ||||||
|                   <el-row justify="center"> |                   <el-row justify="center"> | ||||||
|                     <el-col :span="24" style="text-align: center"> |                     <el-col :span="24" style="text-align: center"> | ||||||
|                       <el-button type="success" @click="openMaterial"> |                       <el-button type="success" @click="dialogNewsVisible = true"> | ||||||
|                         素材库选择<Icon icon="ep:circle-check" /> |                         素材库选择<Icon icon="ep:circle-check" /> | ||||||
|                       </el-button> |                       </el-button> | ||||||
|                     </el-col> |                     </el-col> | ||||||
|  | @ -185,24 +188,26 @@ | ||||||
|     </div> |     </div> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| </template> | </template> | ||||||
| <script setup name="MpMenu"> | <script lang="ts" setup name="MpMenu"> | ||||||
| import { handleTree } from '@/utils/tree' |  | ||||||
| import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' | import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' | ||||||
| import WxNews from '@/views/mp/components/wx-news/main.vue' | import WxNews from '@/views/mp/components/wx-news/main.vue' | ||||||
| import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' | import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' | ||||||
| import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' | import WxMpSelect from '@/views/mp/components/WxMpSelect.vue' | ||||||
| import * as MpMenuApi from '@/api/mp/menu' | import * as MpMenuApi from '@/api/mp/menu' | ||||||
|  | import { handleTree } from '@/utils/tree' | ||||||
| import menuOptions from './menuOptions' | import menuOptions from './menuOptions' | ||||||
|  | 
 | ||||||
| const message = useMessage() // 消息 | const message = useMessage() // 消息 | ||||||
| 
 | 
 | ||||||
| // ======================== 列表查询 ======================== | // ======================== 列表查询 ======================== | ||||||
| const loading = ref(true) // 遮罩层 | const loading = ref(false) // 遮罩层 | ||||||
| const accountId = ref(undefined) // 公众号Id | const accountId = ref<number | undefined>() | ||||||
| const menuList = ref({ children: [] }) | const accountName = ref<string | undefined>('') | ||||||
|  | const menuList = ref<any>({ children: [] }) | ||||||
| 
 | 
 | ||||||
| // ======================== 菜单操作 ======================== | // ======================== 菜单操作 ======================== | ||||||
| const isActive = ref(-1) // 一级菜单点中样式 | const isActive = ref(-1) // 一级菜单点中样式 | ||||||
| const isSubMenuActive = ref(-1) // 一级菜单点中样式 | const isSubMenuActive = ref<string | number>(-1) // 一级菜单点中样式 | ||||||
| const isSubMenuFlag = ref(-1) // 二级菜单显示标志 | const isSubMenuFlag = ref(-1) // 二级菜单显示标志 | ||||||
| 
 | 
 | ||||||
| // ======================== 菜单编辑 ======================== | // ======================== 菜单编辑 ======================== | ||||||
|  | @ -210,15 +215,16 @@ const showRightFlag = ref(false) // 右边配置显示默认详情还是配置 | ||||||
| const nameMaxLength = ref(0) // 菜单名称最大长度;1 级是 4 字符;2 级是 7 字符; | const nameMaxLength = ref(0) // 菜单名称最大长度;1 级是 4 字符;2 级是 7 字符; | ||||||
| const showConfigureContent = ref(true) // 是否展示配置内容;如果有子菜单,就不显示配置内容 | const showConfigureContent = ref(true) // 是否展示配置内容;如果有子菜单,就不显示配置内容 | ||||||
| const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件 | const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件 | ||||||
| const tempObj = ref({}) // 右边临时变量,作为中间值牵引关系 | const tempObj = ref<any>({}) // 右边临时变量,作为中间值牵引关系 | ||||||
| 
 | 
 | ||||||
| // 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数 | // 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数 | ||||||
| const tempSelfObj = ref({}) | const tempSelfObj = ref<any>({}) | ||||||
| const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗 | const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗 | ||||||
| 
 | 
 | ||||||
| /** 侦听公众号变化 **/ | /** 侦听公众号变化 **/ | ||||||
| const accountChanged = (id) => { | const onAccountChanged = (id?: number, name?: string) => { | ||||||
|   accountId.value = id |   accountId.value = id | ||||||
|  |   accountName.value = name | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -241,10 +247,10 @@ const handleQuery = () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 将后端返回的 menuList,转换成前端的 menuList | // 将后端返回的 menuList,转换成前端的 menuList | ||||||
| const convertMenuList = (list) => { | const convertMenuList = (list: any[]) => { | ||||||
|   if (!list) return [] |   if (!list) return [] | ||||||
| 
 | 
 | ||||||
|   const menuList = [] |   const result: any[] = [] | ||||||
|   list.forEach((item) => { |   list.forEach((item) => { | ||||||
|     const menu = { |     const menu = { | ||||||
|       ...item |       ...item | ||||||
|  | @ -271,9 +277,9 @@ const convertMenuList = (list) => { | ||||||
|         hqMusicUrl: item.replyHqMusicUrl |         hqMusicUrl: item.replyHqMusicUrl | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     menuList.push(menu) |     result.push(menu) | ||||||
|   }) |   }) | ||||||
|   return menuList |   return result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 重置表单,清空表单数据 | // 重置表单,清空表单数据 | ||||||
|  | @ -286,7 +292,7 @@ const resetForm = () => { | ||||||
|   // 菜单编辑 |   // 菜单编辑 | ||||||
|   showRightFlag.value = false |   showRightFlag.value = false | ||||||
|   nameMaxLength.value = 0 |   nameMaxLength.value = 0 | ||||||
|   showConfigureContent.value = 0 |   showConfigureContent.value = false | ||||||
|   hackResetWxReplySelect.value = false |   hackResetWxReplySelect.value = false | ||||||
|   tempObj.value = {} |   tempObj.value = {} | ||||||
|   tempSelfObj.value = {} |   tempSelfObj.value = {} | ||||||
|  | @ -295,7 +301,7 @@ const resetForm = () => { | ||||||
| 
 | 
 | ||||||
| // ======================== 菜单操作 ======================== | // ======================== 菜单操作 ======================== | ||||||
| // 一级菜单点击事件 | // 一级菜单点击事件 | ||||||
| const menuClick = (i, item) => { | const menuClick = (i: number, item: any) => { | ||||||
|   // 右侧的表单相关 |   // 右侧的表单相关 | ||||||
|   resetEditor() |   resetEditor() | ||||||
|   showRightFlag.value = true // 右边菜单 |   showRightFlag.value = true // 右边菜单 | ||||||
|  | @ -312,11 +318,10 @@ const menuClick = (i, item) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 二级菜单点击事件 | // 二级菜单点击事件 | ||||||
| const subMenuClick = (subItem, index, k) => { | const subMenuClick = (subItem: any, index: number, k: number) => { | ||||||
|   // 右侧的表单相关 |   // 右侧的表单相关 | ||||||
|   resetEditor() |   resetEditor() | ||||||
|   showRightFlag.value = true // 右边菜单 |   showRightFlag.value = true // 右边菜单 | ||||||
|   console.log(subItem) |  | ||||||
|   tempObj.value = subItem // 将点击的数据放到临时变量,对象有引用作用 |   tempObj.value = subItem // 将点击的数据放到临时变量,对象有引用作用 | ||||||
|   tempSelfObj.value.grand = '2' // 表示二级菜单 |   tempSelfObj.value.grand = '2' // 表示二级菜单 | ||||||
|   tempSelfObj.value.index = index // 表示一级菜单索引 |   tempSelfObj.value.index = index // 表示一级菜单索引 | ||||||
|  | @ -331,7 +336,7 @@ const subMenuClick = (subItem, index, k) => { | ||||||
| 
 | 
 | ||||||
| // 添加横向一级菜单 | // 添加横向一级菜单 | ||||||
| const addMenu = () => { | const addMenu = () => { | ||||||
|   const menuKeyLength = menuList.value.length |   const menuKeyLength: number = menuList.value.length | ||||||
|   const addButton = { |   const addButton = { | ||||||
|     name: '菜单名称', |     name: '菜单名称', | ||||||
|     children: [], |     children: [], | ||||||
|  | @ -342,10 +347,10 @@ const addMenu = () => { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   menuList.value[menuKeyLength] = addButton |   menuList.value[menuKeyLength] = addButton | ||||||
|   menuClick(menuKeyLength.value - 1, addButton) |   menuClick(menuKeyLength - 1, addButton) | ||||||
| } | } | ||||||
| // 添加横向二级菜单;item 表示要操作的父菜单 | // 添加横向二级菜单;item 表示要操作的父菜单 | ||||||
| const addSubMenu = (i, item) => { | const addSubMenu = (i: number, item: any) => { | ||||||
|   // 清空父菜单的属性,因为它只需要 name 属性即可 |   // 清空父菜单的属性,因为它只需要 name 属性即可 | ||||||
|   if (!item.children || item.children.length <= 0) { |   if (!item.children || item.children.length <= 0) { | ||||||
|     item.children = [] |     item.children = [] | ||||||
|  | @ -361,8 +366,8 @@ const addSubMenu = (i, item) => { | ||||||
|     showConfigureContent.value = false |     showConfigureContent.value = false | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   let subMenuKeyLength = item.children.length // 获取二级菜单key长度 |   const subMenuKeyLength = item.children.length // 获取二级菜单key长度 | ||||||
|   let addButton = { |   const addButton = { | ||||||
|     name: '子菜单名称', |     name: '子菜单名称', | ||||||
|     reply: { |     reply: { | ||||||
|       // 用于存储回复内容 |       // 用于存储回复内容 | ||||||
|  | @ -399,7 +404,7 @@ const handleDeleteMenu = async () => { | ||||||
| // ======================== 菜单编辑 ======================== | // ======================== 菜单编辑 ======================== | ||||||
| const handleSave = async () => { | const handleSave = async () => { | ||||||
|   try { |   try { | ||||||
|     await message.confirm('确定要删除吗?') |     await message.confirm('确定要保存吗?') | ||||||
|     loading.value = true |     loading.value = true | ||||||
|     await MpMenuApi.saveMenu(accountId.value, convertMenuFormList()) |     await MpMenuApi.saveMenu(accountId.value, convertMenuFormList()) | ||||||
|     getList() |     getList() | ||||||
|  | @ -413,7 +418,6 @@ const handleSave = async () => { | ||||||
| const resetEditor = () => { | const resetEditor = () => { | ||||||
|   hackResetWxReplySelect.value = false // 销毁组件 |   hackResetWxReplySelect.value = false // 销毁组件 | ||||||
|   nextTick(() => { |   nextTick(() => { | ||||||
|     console.log('nextTick') |  | ||||||
|     hackResetWxReplySelect.value = true // 重建组件 |     hackResetWxReplySelect.value = true // 重建组件 | ||||||
|   }) |   }) | ||||||
| } | } | ||||||
|  | @ -432,9 +436,9 @@ const handleDelete = async () => { | ||||||
| 
 | 
 | ||||||
| // 将前端的 menuList,转换成后端接收的 menuList | // 将前端的 menuList,转换成后端接收的 menuList | ||||||
| const convertMenuFormList = () => { | const convertMenuFormList = () => { | ||||||
|   const result = [] |   const result: any[] = [] | ||||||
|   menuList.value.forEach((item) => { |   menuList.value.forEach((item) => { | ||||||
|     let menu = convertMenuForm(item) |     const menu = convertMenuForm(item) | ||||||
|     result.push(menu) |     result.push(menu) | ||||||
| 
 | 
 | ||||||
|     // 处理子菜单 |     // 处理子菜单 | ||||||
|  | @ -450,7 +454,7 @@ const convertMenuFormList = () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 将前端的 menu,转换成后端接收的 menu | // 将前端的 menu,转换成后端接收的 menu | ||||||
| const convertMenuForm = (menu) => { | const convertMenuForm = (menu: any) => { | ||||||
|   let result = { |   let result = { | ||||||
|     ...menu, |     ...menu, | ||||||
|     children: undefined, // 不处理子节点 |     children: undefined, // 不处理子节点 | ||||||
|  | @ -473,11 +477,7 @@ const convertMenuForm = (menu) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ======================== 菜单编辑(素材选择) ======================== | // ======================== 菜单编辑(素材选择) ======================== | ||||||
| const openMaterial = () => { | const selectMaterial = (item: any) => { | ||||||
|   dialogNewsVisible.value = true |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const selectMaterial = (item) => { |  | ||||||
|   const articleId = item.articleId |   const articleId = item.articleId | ||||||
|   const articles = item.content.newsItem |   const articles = item.content.newsItem | ||||||
|   // 提示,针对多图文 |   // 提示,针对多图文 | ||||||
|  |  | ||||||
|  | @ -19,24 +19,30 @@ | ||||||
| </template> | </template> | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import * as MpTagApi from '@/api/mp/tag' | import * as MpTagApi from '@/api/mp/tag' | ||||||
|  | import { FormInstance, FormRules } from 'element-plus' | ||||||
|  | 
 | ||||||
| const { t } = useI18n() // 国际化 | const { t } = useI18n() // 国际化 | ||||||
| const message = useMessage() // 消息弹窗 | const message = useMessage() // 消息弹窗 | ||||||
| 
 | 
 | ||||||
| const dialogVisible = ref(false) // 弹窗的是否展示 | const dialogVisible = ref(false) // 弹窗的是否展示 | ||||||
| const dialogTitle = ref('') // 弹窗的标题 | const dialogTitle = ref('') // 弹窗的标题 | ||||||
| const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 | const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 | ||||||
| const formType = ref('') // 表单的类型:create - 新增;update - 修改 | const formType = ref<'create' | 'update' | ''>('') // 表单的类型:create - 新增;update - 修改 | ||||||
| const formData = ref({ | const formData = ref({ | ||||||
|   accountId: -1, |   accountId: -1, | ||||||
|   name: '' |   name: '' | ||||||
| }) | }) | ||||||
| const formRules = reactive({ | const formRules: FormRules = { | ||||||
|   name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }] |   name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }] | ||||||
| }) | } | ||||||
| const formRef = ref() // 表单 Ref | const formRef = ref<FormInstance | null>(null) // 表单 Ref | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits<{ | ||||||
|  |   (e: 'success'): void | ||||||
|  | }>() | ||||||
| 
 | 
 | ||||||
| /** 打开弹窗 */ | /** 打开弹窗 */ | ||||||
| const open = async (type: string, accountId: number, id?: number) => { | const open = async (type: 'create' | 'update', accountId: number, id?: number) => { | ||||||
|   dialogVisible.value = true |   dialogVisible.value = true | ||||||
|   dialogTitle.value = t('action.' + type) |   dialogTitle.value = t('action.' + type) | ||||||
|   formType.value = type |   formType.value = type | ||||||
|  | @ -55,11 +61,10 @@ const open = async (type: string, accountId: number, id?: number) => { | ||||||
| defineExpose({ open }) // 提供 open 方法,用于打开弹窗 | defineExpose({ open }) // 提供 open 方法,用于打开弹窗 | ||||||
| 
 | 
 | ||||||
| /** 提交表单 */ | /** 提交表单 */ | ||||||
| const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |  | ||||||
| const submitForm = async () => { | const submitForm = async () => { | ||||||
|   // 校验表单 |   // 校验表单 | ||||||
|   if (!formRef) return |   if (!formRef) return | ||||||
|   const valid = await formRef.value.validate() |   const valid = await formRef.value?.validate() | ||||||
|   if (!valid) return |   if (!valid) return | ||||||
|   // 提交请求 |   // 提交请求 | ||||||
|   formLoading.value = true |   formLoading.value = true | ||||||
|  |  | ||||||
|  | @ -3,17 +3,25 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- 搜索工作栏 --> |   <!-- 搜索工作栏 --> | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect  --> |     <el-form | ||||||
|     <WxAccountSelect @change="accountChanged"> |       class="-mb-15px" | ||||||
|       <template #actions> |       :model="queryParams" | ||||||
|  |       ref="queryFormRef" | ||||||
|  |       :inline="true" | ||||||
|  |       label-width="68px" | ||||||
|  |     > | ||||||
|  |       <el-form-item label="公众号" prop="accountId"> | ||||||
|  |         <WxMpSelect @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']"> | ||||||
|           <Icon icon="ep:plus" class="mr-5px" /> 新增 |           <Icon icon="ep:plus" class="mr-5px" /> 新增 | ||||||
|         </el-button> |         </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']"> | ||||||
|           <Icon icon="ep:refresh" class="mr-5px" /> 同步 |           <Icon icon="ep:refresh" class="mr-5px" /> 同步 | ||||||
|         </el-button> |         </el-button> | ||||||
|       </template> |       </el-form-item> | ||||||
|     </WxAccountSelect> |     </el-form> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| 
 | 
 | ||||||
|   <!-- 列表 --> |   <!-- 列表 --> | ||||||
|  | @ -63,26 +71,35 @@ | ||||||
|   <TagForm ref="formRef" @success="getList" /> |   <TagForm ref="formRef" @success="getList" /> | ||||||
| </template> | </template> | ||||||
| <script setup lang="ts" name="MpTag"> | <script setup lang="ts" name="MpTag"> | ||||||
| import { dateFormatter } from '@/utils/formatTime' |  | ||||||
| import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' |  | ||||||
| import * as MpTagApi from '@/api/mp/tag' | import * as MpTagApi from '@/api/mp/tag' | ||||||
| import TagForm from './TagForm.vue' | import TagForm from './TagForm.vue' | ||||||
|  | import WxMpSelect from '@/views/mp/components/WxMpSelect.vue' | ||||||
|  | import { dateFormatter } from '@/utils/formatTime' | ||||||
|  | 
 | ||||||
| const message = useMessage() // 消息弹窗 | const message = useMessage() // 消息弹窗 | ||||||
| const { t } = useI18n() // 国际化 | const { t } = useI18n() // 国际化 | ||||||
| 
 | 
 | ||||||
| const loading = ref(true) // 列表的加载中 | const loading = ref(true) // 列表的加载中 | ||||||
| const total = ref(0) // 列表的总页数 | const total = ref(0) // 列表的总页数 | ||||||
| const list = ref([]) // 列表的数据 | const list = ref<any>([]) // 列表的数据 | ||||||
| const queryParams = reactive({ | 
 | ||||||
|  | interface QueryParams { | ||||||
|  |   pageNo: number | ||||||
|  |   pageSize: number | ||||||
|  |   accountId?: number | ||||||
|  | } | ||||||
|  | const queryParams: QueryParams = reactive({ | ||||||
|   pageNo: 1, |   pageNo: 1, | ||||||
|   pageSize: 10, |   pageSize: 10, | ||||||
|   accountId: undefined |   accountId: undefined | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | const formRef = ref<InstanceType<typeof TagForm> | null>(null) | ||||||
|  | 
 | ||||||
| /** 侦听公众号变化 **/ | /** 侦听公众号变化 **/ | ||||||
| const accountChanged = (accountId) => { | const onAccountChanged = (id?: number) => { | ||||||
|   queryParams.pageNo = 1 |   queryParams.pageNo = 1 | ||||||
|   queryParams.accountId = accountId |   queryParams.accountId = id | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -99,9 +116,8 @@ const getList = async () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 添加/修改操作 */ | /** 添加/修改操作 */ | ||||||
| const formRef = ref() |  | ||||||
| const openForm = (type: string, id?: number) => { | const openForm = (type: string, id?: number) => { | ||||||
|   formRef.value.open(type, queryParams.accountId, id) |   formRef.value?.open(type, queryParams.accountId as number, id) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 删除按钮操作 */ | /** 删除按钮操作 */ | ||||||
|  | @ -121,8 +137,7 @@ const handleDelete = async (id: number) => { | ||||||
| const handleSync = async () => { | const handleSync = async () => { | ||||||
|   try { |   try { | ||||||
|     await message.confirm('是否确认同步标签?') |     await message.confirm('是否确认同步标签?') | ||||||
|     // @ts-ignore |     await MpTagApi.syncTag(queryParams.accountId as number) | ||||||
|     await MpTagApi.syncTag(queryParams.accountId) |  | ||||||
|     message.success('同步标签成功') |     message.success('同步标签成功') | ||||||
|     await getList() |     await getList() | ||||||
|   } catch {} |   } catch {} | ||||||
|  |  | ||||||
|  | @ -3,14 +3,42 @@ | ||||||
| 
 | 
 | ||||||
|   <!-- 搜索工作栏 --> |   <!-- 搜索工作栏 --> | ||||||
|   <ContentWrap> |   <ContentWrap> | ||||||
|     <!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect  --> |     <el-form | ||||||
|     <WxAccountSelect @change="(accountId) => accountChanged(accountId)"> |       class="-mb-15px" | ||||||
|       <template #actions> |       :model="queryParams" | ||||||
|  |       ref="queryFormRef" | ||||||
|  |       :inline="true" | ||||||
|  |       label-width="68px" | ||||||
|  |     > | ||||||
|  |       <el-form-item label="公众号" prop="accountId"> | ||||||
|  |         <WxMpSelect @change="onAccountChanged" /> | ||||||
|  |       </el-form-item> | ||||||
|  |       <el-form-item label="用户标识" prop="openid"> | ||||||
|  |         <el-input | ||||||
|  |           v-model="queryParams.openid" | ||||||
|  |           placeholder="请输入用户标识" | ||||||
|  |           clearable | ||||||
|  |           @keyup.enter="handleQuery" | ||||||
|  |           class="!w-240px" | ||||||
|  |         /> | ||||||
|  |       </el-form-item> | ||||||
|  |       <el-form-item label="昵称" prop="nickname"> | ||||||
|  |         <el-input | ||||||
|  |           v-model="queryParams.nickname" | ||||||
|  |           placeholder="请输入昵称" | ||||||
|  |           clearable | ||||||
|  |           @keyup.enter="handleQuery" | ||||||
|  |           class="!w-240px" | ||||||
|  |         /> | ||||||
|  |       </el-form-item> | ||||||
|  |       <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']"> | ||||||
|           <Icon icon="ep:refresh" class="mr-5px" /> 同步 |           <Icon icon="ep:refresh" class="mr-5px" /> 同步 | ||||||
|         </el-button> |         </el-button> | ||||||
|       </template> |       </el-form-item> | ||||||
|     </WxAccountSelect> |     </el-form> | ||||||
|   </ContentWrap> |   </ContentWrap> | ||||||
| 
 | 
 | ||||||
|   <!-- 列表 --> |   <!-- 列表 --> | ||||||
|  | @ -66,25 +94,37 @@ | ||||||
|   <UserForm ref="formRef" @success="getList" /> |   <UserForm ref="formRef" @success="getList" /> | ||||||
| </template> | </template> | ||||||
| <script lang="ts" setup name="MpUser"> | <script lang="ts" setup name="MpUser"> | ||||||
| import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' |  | ||||||
| import { dateFormatter } from '@/utils/formatTime' | import { dateFormatter } from '@/utils/formatTime' | ||||||
| import * as MpUserApi from '@/api/mp/user' | import * as MpUserApi from '@/api/mp/user' | ||||||
| import * as MpTagApi from '@/api/mp/tag' | import * as MpTagApi from '@/api/mp/tag' | ||||||
|  | import WxMpSelect from '@/views/mp/components/WxMpSelect.vue' | ||||||
|  | import { FormInstance } from 'element-plus' | ||||||
| import UserForm from './UserForm.vue' | import UserForm from './UserForm.vue' | ||||||
| 
 | 
 | ||||||
| const message = useMessage() // 消息 | const message = useMessage() // 消息 | ||||||
| 
 | 
 | ||||||
| const loading = ref(true) // 列表的加载中 | const loading = ref(true) // 列表的加载中 | ||||||
| const total = ref(0) // 列表的总页数 | const total = ref(0) // 列表的总页数 | ||||||
| const list = ref([]) // 列表的数据 | const list = ref<any[]>([]) // 列表的数据 | ||||||
| const queryParams = reactive({ | 
 | ||||||
|  | interface QueryParams { | ||||||
|  |   pageNo: number | ||||||
|  |   pageSize: number | ||||||
|  |   accountId?: number | ||||||
|  |   openid: string | null | ||||||
|  |   nickname: string | null | ||||||
|  | } | ||||||
|  | const queryParams: QueryParams = reactive({ | ||||||
|   pageNo: 1, |   pageNo: 1, | ||||||
|   pageSize: 10, |   pageSize: 10, | ||||||
|   accountId: null, |   accountId: undefined, | ||||||
|   openid: null, |   openid: null, | ||||||
|   nickname: null |   nickname: null | ||||||
| }) | }) | ||||||
| const tagList = ref([]) // 公众号标签列表 | 
 | ||||||
|  | const tagList = ref<any[]>([]) // 公众号标签列表 | ||||||
|  | const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单 | ||||||
|  | const formRef = ref<InstanceType<typeof UserForm> | null>(null) | ||||||
| 
 | 
 | ||||||
| /** 初始化 */ | /** 初始化 */ | ||||||
| onMounted(async () => { | onMounted(async () => { | ||||||
|  | @ -92,9 +132,9 @@ onMounted(async () => { | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| /** 侦听公众号变化 **/ | /** 侦听公众号变化 **/ | ||||||
| const accountChanged = (accountId) => { | const onAccountChanged = (id?: number) => { | ||||||
|   queryParams.pageNo = 1 |   queryParams.pageNo = 1 | ||||||
|   queryParams.accountId = accountId |   queryParams.accountId = id | ||||||
|   getList() |   getList() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -110,20 +150,32 @@ const getList = async () => { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** 搜索按钮操作 */ | ||||||
|  | const handleQuery = () => { | ||||||
|  |   queryParams.pageNo = 1 | ||||||
|  |   getList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 重置按钮操作 */ | ||||||
|  | const resetQuery = () => { | ||||||
|  |   const accountId = queryParams.accountId | ||||||
|  |   queryFormRef.value?.resetFields() | ||||||
|  |   queryParams.accountId = accountId | ||||||
|  |   handleQuery() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** 添加/修改操作 */ | /** 添加/修改操作 */ | ||||||
| const formRef = ref() |  | ||||||
| const openForm = (id: number) => { | const openForm = (id: number) => { | ||||||
|   formRef.value.open(id) |   formRef.value?.open(id) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 同步标签 */ | /** 同步标签 */ | ||||||
| const handleSync = async () => { | const handleSync = async () => { | ||||||
|   const accountId = queryParams.accountId |  | ||||||
|   try { |   try { | ||||||
|     await message.confirm('是否确认同步粉丝?') |     await message.confirm('是否确认同步粉丝?') | ||||||
|     await MpUserApi.syncUser(accountId) |     await MpUserApi.syncUser(queryParams.accountId) | ||||||
|     message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询') |     message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询') | ||||||
|     await getList() |     getList() | ||||||
|   } catch {} |   } catch {} | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,152 @@ | ||||||
|  | <template> | ||||||
|  |   <Dialog :title="dialogTitle" v-model="dialogVisible" width="50%"> | ||||||
|  |     <el-descriptions :column="2" label-class-name="desc-label"> | ||||||
|  |       <el-descriptions-item label="商户名称">{{ orderDetail.merchantName }}</el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="应用名称">{{ orderDetail.appName }}</el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="商品名称">{{ orderDetail.subject }}</el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |     <el-divider /> | ||||||
|  |     <el-descriptions :column="2" label-class-name="desc-label"> | ||||||
|  |       <el-descriptions-item label="商户订单号"> | ||||||
|  |         <el-tag size="small">{{ orderDetail.merchantOrderId }}</el-tag> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="渠道订单号"> | ||||||
|  |         <el-tag class="tag-purple" size="small">{{ orderDetail.channelOrderNo }}</el-tag> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="支付订单号"> | ||||||
|  |         <el-tag v-if="orderDetail.payOrderExtension.no !== ''" class="tag-pink" size="small"> | ||||||
|  |           {{ orderDetail.payOrderExtension.no }} | ||||||
|  |         </el-tag> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="金额"> | ||||||
|  |         <el-tag type="success" size="small">{{ parseFloat(orderDetail.amount / 100, 2) }}</el-tag> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="手续费"> | ||||||
|  |         <el-tag type="warning" size="small" | ||||||
|  |           >{{ parseFloat(orderDetail.channelFeeAmount / 100, 2) }} | ||||||
|  |         </el-tag> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="手续费比例"> | ||||||
|  |         {{ parseFloat(orderDetail.channelFeeRate / 100, 2) }}% | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="支付状态"> | ||||||
|  |         <dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="orderDetail.status" /> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="回调状态"> | ||||||
|  |         <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="orderDetail.notifyStatus" /> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="回调地址">{{ orderDetail.notifyUrl }}</el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="创建时间" :formatter="dateFormatter"> | ||||||
|  |         {{ formatDate(orderDetail.createTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="支付时间" :formatter="dateFormatter"> | ||||||
|  |         {{ formatDate(orderDetail.successTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="失效时间" :formatter="dateFormatter"> | ||||||
|  |         {{ formatDate(orderDetail.expireTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="通知时间" :formatter="dateFormatter"> | ||||||
|  |         {{ formatDate(orderDetail.notifyTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |     <el-divider /> | ||||||
|  |     <el-descriptions :column="2" label-class-name="desc-label"> | ||||||
|  |       <el-descriptions-item label="支付渠道" | ||||||
|  |         >{{ orderDetail.channelCodeName }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="支付IP">{{ orderDetail.userIp }}</el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款状态"> | ||||||
|  |         <dict-tag :type="DICT_TYPE.PAY_ORDER_REFUND_STATUS" :value="orderDetail.refundStatus" /> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款次数">{{ orderDetail.refundTimes }}</el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款金额"> | ||||||
|  |         <el-tag type="warning"> | ||||||
|  |           {{ parseFloat(orderDetail.refundAmount / 100, 2) }} | ||||||
|  |         </el-tag> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |     <el-divider /> | ||||||
|  |     <el-descriptions :column="1" label-class-name="desc-label" direction="vertical" border> | ||||||
|  |       <el-descriptions-item label="商品描述"> | ||||||
|  |         {{ orderDetail.body }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="支付通道异步回调内容"> | ||||||
|  |         {{ orderDetail.payOrderExtension.channelNotifyData }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |   </Dialog> | ||||||
|  | </template> | ||||||
|  | <script setup lang="ts" name="orderForm"> | ||||||
|  | import { DICT_TYPE } from '@/utils/dict' | ||||||
|  | import * as OrderApi from '@/api/pay/order' | ||||||
|  | import { dateFormatter, formatDate } from '@/utils/formatTime' | ||||||
|  | 
 | ||||||
|  | const { t } = useI18n() // 国际化 | ||||||
|  | // const message = useMessage() // 消息弹窗 | ||||||
|  | const dialogVisible = ref(false) // 弹窗的是否展示 | ||||||
|  | const dialogTitle = ref('订单详情') // 弹窗的标题 | ||||||
|  | const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 | ||||||
|  | const defaultOrderDetail = { | ||||||
|  |   merchantName: '', | ||||||
|  |   appName: '', | ||||||
|  |   channelCodeName: '', | ||||||
|  |   subject: '', | ||||||
|  |   merchantOrderId: null, | ||||||
|  |   channelOrderNo: '', | ||||||
|  |   body: '', | ||||||
|  |   amount: null, | ||||||
|  |   channelFeeRate: null, | ||||||
|  |   channelFeeAmount: null, | ||||||
|  |   userIp: '', | ||||||
|  |   status: null, | ||||||
|  |   notifyUrl: '', | ||||||
|  |   notifyStatus: null, | ||||||
|  |   refundStatus: null, | ||||||
|  |   refundTimes: '', | ||||||
|  |   refundAmount: null, | ||||||
|  |   createTime: '', | ||||||
|  |   successTime: '', | ||||||
|  |   notifyTime: '', | ||||||
|  |   expireTime: '', | ||||||
|  |   payOrderExtension: { | ||||||
|  |     channelNotifyData: '', | ||||||
|  |     no: '' | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | const orderDetail = ref(JSON.parse(JSON.stringify(defaultOrderDetail))) | ||||||
|  | 
 | ||||||
|  | /** 打开弹窗 */ | ||||||
|  | const open = async (id?: number) => { | ||||||
|  |   dialogVisible.value = true | ||||||
|  |   dialogTitle.value = t('action.preview') | ||||||
|  |   // 修改时,设置数据 | ||||||
|  |   if (id) { | ||||||
|  |     formLoading.value = true | ||||||
|  |     try { | ||||||
|  |       orderDetail.value = await OrderApi.getOrderDetail(id) | ||||||
|  |       if (orderDetail.value.payOrderExtension === null) { | ||||||
|  |         orderDetail.value.payOrderExtension = Object.assign( | ||||||
|  |           defaultOrderDetail.payOrderExtension, | ||||||
|  |           {} | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |     } finally { | ||||||
|  |       formLoading.value = false | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | defineExpose({ open }) // 提供 open 方法,用于打开弹窗 | ||||||
|  | </script> | ||||||
|  | <style> | ||||||
|  | .tag-purple { | ||||||
|  |   color: #722ed1; | ||||||
|  |   background: #f9f0ff; | ||||||
|  |   border-color: #d3adf7; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tag-pink { | ||||||
|  |   color: #eb2f96; | ||||||
|  |   background: #fff0f6; | ||||||
|  |   border-color: #ffadd2; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,154 @@ | ||||||
|  | <template> | ||||||
|  |   <Dialog :title="dialogTitle" v-model="dialogVisible" width="50%"> | ||||||
|  |     <el-descriptions :column="2" label-class-name="desc-label"> | ||||||
|  |       <el-descriptions-item label="商户名称">{{ refundDetail.merchantName }}</el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="应用名称">{{ refundDetail.appName }}</el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="商品名称">{{ refundDetail.subject }}</el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |     <el-divider /> | ||||||
|  |     <el-descriptions :column="2" label-class-name="desc-label"> | ||||||
|  |       <el-descriptions-item label="商户退款单号"> | ||||||
|  |         <el-tag size="small">{{ refundDetail.merchantRefundNo }}</el-tag> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="商户订单号" | ||||||
|  |         >{{ refundDetail.merchantOrderId }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="交易订单号">{{ refundDetail.tradeNo }}</el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |     <el-divider /> | ||||||
|  |     <el-descriptions :column="2" label-class-name="desc-label"> | ||||||
|  |       <el-descriptions-item label="支付金额"> | ||||||
|  |         {{ parseFloat(refundDetail.payAmount / 100).toFixed(2) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款金额" size="small"> | ||||||
|  |         <el-tag class="tag-purple" size="small"> | ||||||
|  |           {{ parseFloat(refundDetail.refundAmount / 100).toFixed(2) }} | ||||||
|  |         </el-tag> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款类型"> | ||||||
|  |         <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="refundDetail.type" /> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款状态"> | ||||||
|  |         <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="refundDetail.status" /> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="创建时间" | ||||||
|  |         >{{ formatDate(refundDetail.createTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款成功时间" | ||||||
|  |         >{{ formatDate(refundDetail.successTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款失效时间" | ||||||
|  |         >{{ formatDate(refundDetail.expireTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="更新时间" | ||||||
|  |         >{{ formatDate(refundDetail.updateTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |     <el-divider /> | ||||||
|  |     <el-descriptions :column="2" label-class-name="desc-label"> | ||||||
|  |       <el-descriptions-item label="支付渠道"> | ||||||
|  |         {{ refundDetail.channelCodeName }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="支付IP" size="small"> | ||||||
|  |         {{ refundDetail.userIp }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="回调地址">{{ refundDetail.notifyUrl }}</el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="回调状态"> | ||||||
|  |         <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="refundDetail.notifyStatus" /> | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="回调时间" | ||||||
|  |         >{{ formatDate(refundDetail.notifyTime) }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |     <el-divider /> | ||||||
|  |     <el-descriptions :column="2" label-class-name="desc-label"> | ||||||
|  |       <el-descriptions-item label="渠道订单号" | ||||||
|  |         >{{ refundDetail.channelOrderNo }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="渠道退款单号" | ||||||
|  |         >{{ refundDetail.channelRefundNo }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="渠道错误码" | ||||||
|  |         >{{ refundDetail.channelErrorCode }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="渠道错误码描述" | ||||||
|  |         >{{ refundDetail.channelErrorMsg }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |     <br /> | ||||||
|  |     <el-descriptions :column="1" label-class-name="desc-label" direction="vertical" border> | ||||||
|  |       <el-descriptions-item label="渠道额外参数" | ||||||
|  |         >{{ refundDetail.channelExtras }} | ||||||
|  |       </el-descriptions-item> | ||||||
|  |       <el-descriptions-item label="退款原因">{{ refundDetail.reason }}</el-descriptions-item> | ||||||
|  |     </el-descriptions> | ||||||
|  |   </Dialog> | ||||||
|  | </template> | ||||||
|  | <script setup lang="ts" name="refundForm"> | ||||||
|  | import { DICT_TYPE } from '@/utils/dict' | ||||||
|  | import * as RefundApi from '@/api/pay/refund' | ||||||
|  | import { formatDate } from '@/utils/formatTime' | ||||||
|  | 
 | ||||||
|  | const { t } = useI18n() // 国际化 | ||||||
|  | // const message = useMessage() // 消息弹窗 | ||||||
|  | const dialogVisible = ref(false) // 弹窗的是否展示 | ||||||
|  | const dialogTitle = ref('退款订单详情') // 弹窗的标题 | ||||||
|  | const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 | ||||||
|  | const defaultrefundDetail = { | ||||||
|  |   id: null, | ||||||
|  |   appId: null, | ||||||
|  |   appName: '', | ||||||
|  |   channelCode: '', | ||||||
|  |   channelCodeName: '', | ||||||
|  |   channelErrorCode: '', | ||||||
|  |   channelErrorMsg: '', | ||||||
|  |   channelExtras: '', | ||||||
|  |   channelId: null, | ||||||
|  |   channelOrderNo: '', | ||||||
|  |   channelRefundNo: '', | ||||||
|  |   createTime: null, | ||||||
|  |   expireTime: null, | ||||||
|  |   merchantId: null, | ||||||
|  |   merchantName: '', | ||||||
|  |   merchantOrderId: '', | ||||||
|  |   merchantRefundNo: '', | ||||||
|  |   notifyStatus: null, | ||||||
|  |   notifyTime: null, | ||||||
|  |   notifyUrl: '', | ||||||
|  |   orderId: null, | ||||||
|  |   payAmount: null, | ||||||
|  |   reason: '', | ||||||
|  |   refundAmount: null, | ||||||
|  |   status: null, | ||||||
|  |   subject: '', | ||||||
|  |   successTime: null, | ||||||
|  |   tradeNo: '', | ||||||
|  |   type: null, | ||||||
|  |   userIp: '' | ||||||
|  | } | ||||||
|  | const refundDetail = ref(JSON.parse(JSON.stringify(defaultrefundDetail))) | ||||||
|  | 
 | ||||||
|  | /** 打开弹窗 */ | ||||||
|  | const open = async (id?: number) => { | ||||||
|  |   dialogVisible.value = true | ||||||
|  |   dialogTitle.value = t('action.preview') | ||||||
|  |   // 修改时,设置数据 | ||||||
|  |   if (id) { | ||||||
|  |     formLoading.value = true | ||||||
|  |     try { | ||||||
|  |       refundDetail.value = await RefundApi.getRefundApi(id) | ||||||
|  |     } finally { | ||||||
|  |       formLoading.value = false | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | defineExpose({ open }) // 提供 open 方法,用于打开弹窗 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  | .tag-purple { | ||||||
|  |   color: #722ed1; | ||||||
|  |   background: #f9f0ff; | ||||||
|  |   border-color: #d3adf7; | ||||||
|  | } | ||||||
|  | </style> | ||||||
		Loading…
	
		Reference in New Issue
	
	 dhb52
						dhb52