refactor: mp模块ts重构
This commit is contained in:
parent
ebbf47f509
commit
df3b381d6f
@ -3,13 +3,16 @@
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
||||
<WxAccountSelect @change="accountChanged" />
|
||||
<el-form class="-mb-15px" :model="queryParams" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- tab 切换 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="type" @tab-change="handleTabChange">
|
||||
<el-tabs v-model="msgType" @tab-change="handleTabChange">
|
||||
<!-- 操作工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
@ -18,26 +21,26 @@
|
||||
plain
|
||||
@click="handleAdd"
|
||||
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" />新增
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- tab 项 -->
|
||||
<el-tab-pane name="1">
|
||||
<el-tab-pane :name="MsgType.Follow">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:star-off" /> 关注时回复</span>
|
||||
<span><Icon icon="ep:star" /> 关注时回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="2">
|
||||
<el-tab-pane :name="MsgType.Message">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:chat-line-round" /> 消息回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="3">
|
||||
<el-tab-pane :name="MsgType.Keyword">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:news" /> 关键词回复</span>
|
||||
<span><Icon icon="fa:newspaper-o" /> 关键词回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
@ -47,10 +50,20 @@
|
||||
label="请求消息类型"
|
||||
align="center"
|
||||
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 label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
|
||||
<el-table-column
|
||||
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">
|
||||
<dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch" />
|
||||
</template>
|
||||
@ -64,7 +77,7 @@
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
|
||||
<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 v-else-if="scope.row.responseMessageType === 'image'">
|
||||
<a target="_blank" :href="scope.row.responseMediaUrl">
|
||||
@ -77,7 +90,11 @@
|
||||
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 v-else-if="scope.row.responseMessageType === 'news'">
|
||||
<WxNews :articles="scope.row.responseArticles" />
|
||||
@ -123,21 +140,21 @@
|
||||
</el-table>
|
||||
|
||||
<!-- 添加或修改自动回复的对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
|
||||
<el-select v-model="form.requestMessageType" placeholder="请选择">
|
||||
<el-dialog :title="title" v-model="showReplyFormDialog" width="800px" append-to-body>
|
||||
<el-form ref="formRef" :model="replyForm" :rules="rules" label-width="80px">
|
||||
<el-form-item label="消息类型" prop="requestMessageType" v-if="msgType === MsgType.Message">
|
||||
<el-select v-model="replyForm.requestMessageType" placeholder="请选择">
|
||||
<template v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value">
|
||||
<el-option
|
||||
v-if="requestMessageTypes.includes(dict.value)"
|
||||
v-if="RequestMessageTypes.includes(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
|
||||
<el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable>
|
||||
<el-form-item label="匹配类型" prop="requestMatch" v-if="msgType === MsgType.Keyword">
|
||||
<el-select v-model="replyForm.requestMatch" placeholder="请选择匹配类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in getDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
|
||||
:key="dict.value"
|
||||
@ -146,8 +163,8 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'">
|
||||
<el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable />
|
||||
<el-form-item label="关键词" prop="requestKeyword" v-if="msgType === MsgType.Keyword">
|
||||
<el-input v-model="replyForm.requestKeyword" placeholder="请输入内容" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="回复消息">
|
||||
<WxReplySelect :objData="objData" v-if="hackResetWxReplySelect" />
|
||||
@ -160,38 +177,47 @@
|
||||
</el-dialog>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup name="MpAutoReply">
|
||||
<script setup lang="ts" name="MpAutoReply">
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import 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 { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { TabPaneName } from 'element-plus'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// const queryFormRef = ref()
|
||||
const formRef = ref()
|
||||
|
||||
// tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
|
||||
const type = ref('3')
|
||||
// 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复)
|
||||
// 作为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 showSearch = ref(true)
|
||||
// 总条数
|
||||
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,
|
||||
pageSize: 10,
|
||||
accountId: undefined
|
||||
@ -200,12 +226,50 @@ const queryParams = reactive({
|
||||
// 弹出层标题
|
||||
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({
|
||||
type: 'text'
|
||||
const objData = ref<ObjData>({
|
||||
type: 'text',
|
||||
accountId: undefined
|
||||
})
|
||||
// 表单校验
|
||||
const rules = {
|
||||
@ -216,8 +280,8 @@ const rules = {
|
||||
// 重置 WxReplySelect 组件,解决无法清除的问题
|
||||
const hackResetWxReplySelect = ref(false)
|
||||
|
||||
const accountChanged = (accountId) => {
|
||||
queryParams.accountId = accountId
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
@ -227,7 +291,7 @@ const getList = async () => {
|
||||
try {
|
||||
const data = await MpAutoReplyApi.getAutoReplyPage({
|
||||
...queryParams,
|
||||
type: type.value
|
||||
type: msgType.value
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
@ -242,8 +306,8 @@ const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleTabChange = (tabName) => {
|
||||
type.value = tabName
|
||||
const handleTabChange = (tabName: TabPaneName) => {
|
||||
msgType.value = tabName as MsgType
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
@ -252,29 +316,29 @@ const handleAdd = () => {
|
||||
reset()
|
||||
resetEditor()
|
||||
// 打开表单,并设置初始化
|
||||
open.value = true
|
||||
title.value = '新增自动回复'
|
||||
objData.value = {
|
||||
type: 'text',
|
||||
accountId: queryParams.accountId
|
||||
}
|
||||
|
||||
title.value = '新增自动回复'
|
||||
showReplyFormDialog.value = true
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = (row) => {
|
||||
const handleUpdate = async (row: any) => {
|
||||
reset()
|
||||
resetEditor()
|
||||
console.log(row)
|
||||
|
||||
MpAutoReplyApi.getAutoReply(row.id).then((data) => {
|
||||
const data = await MpAutoReplyApi.getAutoReply(row.id)
|
||||
// 设置属性
|
||||
form.value = { ...data }
|
||||
delete form.value['responseMessageType']
|
||||
delete form.value['responseContent']
|
||||
delete form.value['responseMediaId']
|
||||
delete form.value['responseMediaUrl']
|
||||
delete form.value['responseDescription']
|
||||
delete form.value['responseArticles']
|
||||
replyForm.value = { ...data }
|
||||
delete replyForm.value['responseMessageType']
|
||||
delete replyForm.value['responseContent']
|
||||
delete replyForm.value['responseMediaId']
|
||||
delete replyForm.value['responseMediaUrl']
|
||||
delete replyForm.value['responseDescription']
|
||||
delete replyForm.value['responseArticles']
|
||||
objData.value = {
|
||||
type: data.responseMessageType,
|
||||
accountId: queryParams.accountId,
|
||||
@ -291,55 +355,48 @@ const handleUpdate = (row) => {
|
||||
}
|
||||
|
||||
// 打开表单
|
||||
open.value = true
|
||||
title.value = '修改自动回复'
|
||||
})
|
||||
showReplyFormDialog.value = true
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
formRef.value?.validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 处理回复消息
|
||||
const form = { ...form.value }
|
||||
form.responseMessageType = objData.value.type
|
||||
form.responseContent = objData.value.content
|
||||
form.responseMediaId = objData.value.mediaId
|
||||
form.responseMediaUrl = objData.value.url
|
||||
form.responseTitle = objData.value.title
|
||||
form.responseDescription = objData.value.description
|
||||
form.responseThumbMediaId = objData.value.thumbMediaId
|
||||
form.responseThumbMediaUrl = objData.value.thumbMediaUrl
|
||||
form.responseArticles = objData.value.articles
|
||||
form.responseMusicUrl = objData.value.musicUrl
|
||||
form.responseHqMusicUrl = objData.value.hqMusicUrl
|
||||
const submitForm: any = { ...replyForm.value }
|
||||
submitForm.responseMessageType = objData.value.type
|
||||
submitForm.responseContent = objData.value.content
|
||||
submitForm.responseMediaId = objData.value.mediaId
|
||||
submitForm.responseMediaUrl = objData.value.url
|
||||
submitForm.responseTitle = objData.value.title
|
||||
submitForm.responseDescription = objData.value.description
|
||||
submitForm.responseThumbMediaId = objData.value.thumbMediaId
|
||||
submitForm.responseThumbMediaUrl = objData.value.thumbMediaUrl
|
||||
submitForm.responseArticles = objData.value.articles
|
||||
submitForm.responseMusicUrl = objData.value.musicUrl
|
||||
submitForm.responseHqMusicUrl = objData.value.hqMusicUrl
|
||||
|
||||
if (form.value.id !== undefined) {
|
||||
MpAutoReplyApi.updateAutoReply(form).then(() => {
|
||||
if (replyForm.value.id !== undefined) {
|
||||
await MpAutoReplyApi.updateAutoReply(submitForm)
|
||||
message.success('修改成功')
|
||||
open.value = false
|
||||
getList()
|
||||
})
|
||||
} else {
|
||||
MpAutoReplyApi.createAutoReply(form).then(() => {
|
||||
await MpAutoReplyApi.createAutoReply(submitForm)
|
||||
message.success('新增成功')
|
||||
open.value = false
|
||||
getList()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
showReplyFormDialog.value = false
|
||||
getList()
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
replyForm.value = {
|
||||
id: undefined,
|
||||
accountId: queryParams.accountId,
|
||||
type: type.value,
|
||||
type: msgType.value,
|
||||
requestKeyword: undefined,
|
||||
requestMatch: type.value === '3' ? 1 : undefined,
|
||||
requestMatch: msgType.value === MsgType.Keyword ? 1 : undefined,
|
||||
requestMessageType: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
@ -347,7 +404,7 @@ const reset = () => {
|
||||
|
||||
// 取消按钮
|
||||
const cancel = () => {
|
||||
open.value = false
|
||||
showReplyFormDialog.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<el-select
|
||||
v-model="accountId"
|
||||
placeholder="请选择公众号"
|
||||
class="!w-240px"
|
||||
@change="accountChanged"
|
||||
>
|
||||
<el-select v-model="account.id" placeholder="请选择公众号" class="!w-240px" @change="onChanged">
|
||||
<el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
@ -12,11 +7,14 @@
|
||||
<script lang="ts" setup name="WxMpSelect">
|
||||
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 emit = defineEmits<{
|
||||
(e: 'change', id: number | undefined): void
|
||||
(e: 'change', id?: number, name?: string): void
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
@ -27,12 +25,12 @@ const handleQuery = async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
accountId.value = accountList.value[0].id
|
||||
emit('change', accountId.value)
|
||||
account.id = accountList.value[0].id
|
||||
emit('change', account.id, account.name)
|
||||
}
|
||||
}
|
||||
|
||||
const accountChanged = () => {
|
||||
emit('change', accountId.value)
|
||||
const onChanged = () => {
|
||||
emit('change', account.id, account.name)
|
||||
}
|
||||
</script>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
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 editorOptions from './quill-options'
|
||||
import { Editor } from '@/components/Editor'
|
||||
|
||||
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()
|
||||
|
||||
@ -30,21 +30,16 @@ const props = defineProps({
|
||||
const emit = defineEmits(['input'])
|
||||
|
||||
const myQuillEditorRef = ref()
|
||||
|
||||
const content = ref(props.value.replace(/data-src/g, 'src'))
|
||||
|
||||
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({
|
||||
type: 'image', // TODO 芋艿:试试要不要换成 thumb
|
||||
accountId: props.accountId
|
||||
})
|
||||
|
||||
const onEditorChange = () => {
|
||||
const onEditorChange = (text) => {
|
||||
//内容改变事件
|
||||
emit('input', content.value)
|
||||
emit('input', text)
|
||||
}
|
||||
|
||||
// 富文本图片上传前
|
||||
@ -98,104 +93,14 @@ const uploadError = () => {
|
||||
:on-error="uploadError"
|
||||
:before-upload="beforeUpload"
|
||||
/>
|
||||
<QuillEditor
|
||||
class="editor"
|
||||
v-model="content"
|
||||
<Editor
|
||||
editor-id="wxEditor"
|
||||
ref="quillEditorRef"
|
||||
:options="editorOptions"
|
||||
@change="onEditorChange($event)"
|
||||
:modelValue="content"
|
||||
@change="(editor) => onEditorChange(editor.getText())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
<style></style>
|
||||
|
@ -3,14 +3,22 @@
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
||||
<WxAccountSelect @change="accountChanged">
|
||||
<template #actions>
|
||||
<el-form
|
||||
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-item>
|
||||
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
|
||||
<Icon icon="ep:plus" />新增
|
||||
</el-button>
|
||||
</template>
|
||||
</WxAccountSelect>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
@ -58,10 +66,8 @@
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- TODO @Dhb52:迁移成独立路由 -->
|
||||
<div class="app-container">
|
||||
<!-- 添加或修改草稿对话框 -->
|
||||
<Teleport to="body">
|
||||
<el-dialog
|
||||
:title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
|
||||
width="80%"
|
||||
@ -133,7 +139,7 @@
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
@click="plusNews(item)"
|
||||
@click="plusNews"
|
||||
v-if="articlesAdd.length < 8 && operateMaterial === 'add'"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
@ -176,7 +182,7 @@
|
||||
/>
|
||||
<div class="thumb-but">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
@ -200,15 +206,13 @@
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<el-dialog title="选择图片" v-model="dialogImageVisible" width="80%">
|
||||
<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>
|
||||
</Teleport>
|
||||
</div>
|
||||
<el-input
|
||||
:rows="8"
|
||||
@ -234,38 +238,49 @@
|
||||
<el-button type="primary" @click="submitForm">提 交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="MpDraft">
|
||||
|
||||
<script setup lang="ts" name="MpDraft">
|
||||
import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/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 * as MpDraftApi from '@/api/mp/draft'
|
||||
import * as MpFreePublishApi from '@/api/mp/freePublish'
|
||||
import { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'
|
||||
// 可以用改本地数据模拟,避免API调用超限
|
||||
// import drafts from './mock'
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined
|
||||
})
|
||||
|
||||
// ========== 文件上传 ==========
|
||||
const materialSelectRef = ref()
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') // 上传永久素材的地址
|
||||
const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
|
||||
const fileList = ref([])
|
||||
const uploadData = reactive({
|
||||
const uploadUrl = BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
|
||||
const headers = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
||||
|
||||
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',
|
||||
accountId: 1
|
||||
})
|
||||
@ -273,34 +288,28 @@ const uploadData = reactive({
|
||||
// ========== 草稿新建 or 修改 ==========
|
||||
const dialogNewsVisible = ref(false)
|
||||
const addMaterialLoading = ref(false) // 添加草稿的 loading 标识
|
||||
const articlesAdd = ref([])
|
||||
const articlesAdd = ref<any[]>([])
|
||||
const isActiveAddNews = ref(0)
|
||||
const dialogImageVisible = ref(false)
|
||||
const operateMaterial = ref('add')
|
||||
const operateMaterial = ref<'add' | 'edit'>('add')
|
||||
const articlesMediaId = ref('')
|
||||
const hackResetEditor = ref(false)
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const accountChanged = (accountId) => {
|
||||
setAccountId(accountId)
|
||||
const onAccountChanged = (id?: number) => {
|
||||
setAccountId(id)
|
||||
getList()
|
||||
}
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
/** 设置账号编号 */
|
||||
const setAccountId = (accountId) => {
|
||||
queryParams.accountId = accountId
|
||||
uploadData.accountId = accountId
|
||||
const setAccountId = (id?: number) => {
|
||||
queryParams.accountId = id
|
||||
uploadData.accountId = id
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询草稿箱')
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const drafts = await MpDraftApi.getDraftPage(queryParams)
|
||||
@ -329,7 +338,7 @@ const handleAdd = () => {
|
||||
}
|
||||
|
||||
/** 更新按钮操作 */
|
||||
const handleUpdate = (item) => {
|
||||
const handleUpdate = (item: any) => {
|
||||
resetEditor()
|
||||
reset()
|
||||
articlesMediaId.value = item.mediaId
|
||||
@ -340,39 +349,30 @@ const handleUpdate = (item) => {
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
// TODO @Dhb52: 参考别的模块写法,改成 await 方式
|
||||
const submitForm = async () => {
|
||||
addMaterialLoading.value = true
|
||||
try {
|
||||
if (operateMaterial.value === 'add') {
|
||||
MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
|
||||
.then(() => {
|
||||
await MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
|
||||
message.notifySuccess('新增成功')
|
||||
dialogNewsVisible.value = false
|
||||
getList()
|
||||
})
|
||||
.finally(() => {
|
||||
addMaterialLoading.value = false
|
||||
})
|
||||
} else {
|
||||
MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
|
||||
.then(() => {
|
||||
await MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
|
||||
message.notifySuccess('更新成功')
|
||||
}
|
||||
} finally {
|
||||
dialogNewsVisible.value = false
|
||||
getList()
|
||||
})
|
||||
.finally(() => {
|
||||
addMaterialLoading.value = false
|
||||
})
|
||||
getList()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const dialogNewsClose = async (done) => {
|
||||
const dialogNewsClose = async (onDone: () => {}) => {
|
||||
try {
|
||||
await message.confirm('修改内容可能还未保存,确定关闭吗?')
|
||||
reset()
|
||||
resetEditor()
|
||||
done()
|
||||
onDone()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@ -391,7 +391,7 @@ const resetEditor = () => {
|
||||
}
|
||||
|
||||
// 将图文向下移动
|
||||
const downNews = (index) => {
|
||||
const downNews = (index: number) => {
|
||||
let temp = articlesAdd.value[index]
|
||||
articlesAdd.value[index] = articlesAdd.value[index + 1]
|
||||
articlesAdd.value[index + 1] = temp
|
||||
@ -407,13 +407,13 @@ const upNews = (index) => {
|
||||
}
|
||||
|
||||
// 选中指定 index 的图文
|
||||
const activeNews = (index) => {
|
||||
const activeNews = (index: number) => {
|
||||
resetEditor()
|
||||
isActiveAddNews.value = index
|
||||
}
|
||||
|
||||
// 删除指定 index 的图文
|
||||
const minusNews = async (index) => {
|
||||
const minusNews = async (index: number) => {
|
||||
try {
|
||||
await message.confirm('确定删除该图文吗?')
|
||||
articlesAdd.value.splice(index, 1)
|
||||
@ -446,20 +446,17 @@ const buildEmptyArticle = () => {
|
||||
}
|
||||
|
||||
// ======================== 文件上传 ========================
|
||||
const beforeThumbImageUpload = (file) => {
|
||||
const beforeThumbImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||
addMaterialLoading.value = true
|
||||
const isType =
|
||||
file.type === 'image/jpeg' ||
|
||||
file.type === 'image/png' ||
|
||||
file.type === 'image/gif' ||
|
||||
file.type === 'image/bmp' ||
|
||||
file.type === 'image/jpg'
|
||||
const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes(
|
||||
rawFile.type
|
||||
)
|
||||
if (!isType) {
|
||||
message.error('上传图片格式不对!')
|
||||
addMaterialLoading.value = false
|
||||
return false
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
const isLt = rawFile.size / 1024 / 1024 < 2
|
||||
if (!isLt) {
|
||||
message.error('上传图片大小不能超过 2M!')
|
||||
addMaterialLoading.value = false
|
||||
@ -469,7 +466,7 @@ const beforeThumbImageUpload = (file) => {
|
||||
return true
|
||||
}
|
||||
|
||||
const handleUploadSuccess = (response, file, fileList) => {
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||
addMaterialLoading.value = false
|
||||
if (response.code !== 0) {
|
||||
message.error('上传出错:' + response.msg)
|
||||
@ -485,7 +482,7 @@ const handleUploadSuccess = (response, file, fileList) => {
|
||||
}
|
||||
|
||||
// 选择 or 上传完素材,设置回草稿
|
||||
const selectMaterial = (item) => {
|
||||
const selectMaterial = (item: any) => {
|
||||
dialogImageVisible.value = false
|
||||
articlesAdd.value[isActiveAddNews.value].thumbMediaId = item.mediaId
|
||||
articlesAdd.value[isActiveAddNews.value].thumbUrl = item.url
|
||||
@ -494,14 +491,10 @@ const selectMaterial = (item) => {
|
||||
// 打开素材选择
|
||||
const openMaterial = () => {
|
||||
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 mediaId = item.mediaId
|
||||
const content =
|
||||
@ -510,19 +503,19 @@ const handlePublish = async (item) => {
|
||||
await message.confirm(content)
|
||||
await MpFreePublishApi.submitFreePublish(accountId, mediaId)
|
||||
message.notifySuccess('发布成功')
|
||||
await getList()
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (item) => {
|
||||
const handleDelete = async (item: any) => {
|
||||
const accountId = queryParams.accountId
|
||||
const mediaId = item.mediaId
|
||||
try {
|
||||
await message.confirm('此操作将永久删除该草稿, 是否继续?')
|
||||
await MpDraftApi.deleteDraft(accountId, mediaId)
|
||||
message.notifySuccess('删除成功')
|
||||
await getList()
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
|
@ -3,8 +3,17 @@
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
||||
<WxAccountSelect @change="(accountId) => accountChanged(accountId)" />
|
||||
<el-form
|
||||
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>
|
||||
|
||||
<!-- 列表 -->
|
||||
@ -39,26 +48,32 @@
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup name="MpFreePublish">
|
||||
<script lang="ts" setup name="MpFreePublish">
|
||||
import * as FreePublishApi from '@/api/mp/freePublish'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined
|
||||
})
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const accountChanged = (accountId) => {
|
||||
queryParams.accountId = accountId
|
||||
const onAccountChanged = (id: number | undefined) => {
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
@ -75,7 +90,7 @@ const getList = async () => {
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (item) => {
|
||||
const handleDelete = async (item: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm('删除后用户将无法访问此页面,确定删除?')
|
||||
|
@ -4,13 +4,13 @@
|
||||
<ContentWrap>
|
||||
<el-form class="-mb-15px" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="accountChange" />
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="type" @tab-change="handleTabChange">
|
||||
<el-tabs v-model="type" @tab-change="onTabChange">
|
||||
<!-- tab 1:图片 -->
|
||||
<el-tab-pane name="image">
|
||||
<template #label>
|
||||
@ -93,7 +93,7 @@
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
<el-table-column label="语音" align="center">
|
||||
<template #default="scope">
|
||||
<WxVoicePlayer :url="scope.row.url" />
|
||||
<WxVoicePlayer v-if="scope.row.url" :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@ -188,10 +188,8 @@
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<!-- <span class="dialog-footer"> -->
|
||||
<el-button @click="cancelVideo">取 消</el-button>
|
||||
<el-button type="primary" @click="submitVideo">提 交</el-button>
|
||||
<!-- </span> -->
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@ -203,7 +201,7 @@
|
||||
<el-table-column label="介绍" align="center" prop="introduction" />
|
||||
<el-table-column label="视频" align="center">
|
||||
<template #default="scope">
|
||||
<WxVideoPlayer :url="scope.row.url" />
|
||||
<WxVideoPlayer v-if="scope.row.url" :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@ -284,9 +282,9 @@ const type = ref<MatertialType>('image')
|
||||
// 遮罩层
|
||||
const loading = ref(false)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 数据列表
|
||||
const list = ref([])
|
||||
const list = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
// 查询参数
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
@ -319,8 +317,8 @@ const dialogVideoVisible = ref(false)
|
||||
const addMaterialLoading = ref(false)
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const accountChange = (accountId: number | undefined) => {
|
||||
queryParams.accountId = accountId
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
@ -346,7 +344,7 @@ const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleTabChange = (tabName: TabPaneName) => {
|
||||
const onTabChange = (tabName: TabPaneName) => {
|
||||
// 设置 type
|
||||
uploadData.type = tabName as MatertialType
|
||||
|
||||
@ -359,54 +357,49 @@ const handleTabChange = (tabName: TabPaneName) => {
|
||||
}
|
||||
|
||||
// ======================== 文件上传 ========================
|
||||
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||
const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes(
|
||||
rawFile.type
|
||||
)
|
||||
if (!isType) {
|
||||
message.error('上传图片格式不对!')
|
||||
const beforeUpload = (rawFile: UploadRawFile, category: 'image' | 'audio' | 'video'): boolean => {
|
||||
let allowTypes: string[] = []
|
||||
let maxSizeMB = 0
|
||||
let name = ''
|
||||
|
||||
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
|
||||
}
|
||||
const isLt = rawFile.size / 1024 / 1024 < 2
|
||||
if (!isLt) {
|
||||
message.error('上传图片大小不能超过 2M!')
|
||||
// 校验大小
|
||||
if (rawFile.size / 1024 / 1024 > maxSizeMB) {
|
||||
message.error(`上传${name}大小不能超过${maxSizeMB}M!`)
|
||||
return false
|
||||
}
|
||||
loading.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||
const isType = ['audio/mp3', 'audio/wma', 'audio/wav', 'audio/amr'].includes(file.type)
|
||||
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 beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||
beforeUpload(rawFile, 'image')
|
||||
|
||||
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||
const isType = rawFile.type === 'video/mp4'
|
||||
if (!isType) {
|
||||
message.error('上传视频格式不对!')
|
||||
return false
|
||||
}
|
||||
const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||
beforeUpload(rawFile, 'audio')
|
||||
|
||||
const isLt = rawFile.size / 1024 / 1024 < 10
|
||||
if (!isLt) {
|
||||
message.error('上传视频大小不能超过 10M!')
|
||||
return false
|
||||
}
|
||||
|
||||
addMaterialLoading.value = true
|
||||
return true
|
||||
}
|
||||
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||
beforeUpload(rawFile, 'video')
|
||||
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||
loading.value = false
|
||||
@ -441,6 +434,7 @@ const submitVideo = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 弹出 video 新建的表单
|
||||
const handleAddVideo = () => {
|
||||
resetVideo()
|
||||
dialogVideoVisible.value = true
|
||||
|
@ -2,8 +2,11 @@
|
||||
<doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
||||
<WxAccountSelect @change="accountChanged" />
|
||||
<el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
@ -12,7 +15,7 @@
|
||||
<!--左边配置菜单-->
|
||||
<div class="left">
|
||||
<div class="weixin-hd">
|
||||
<div class="weixin-title">{{ name }}</div>
|
||||
<div class="weixin-title">{{ accountName }}</div>
|
||||
</div>
|
||||
<div class="weixin-menu menu_main clearfix">
|
||||
<div class="menu_bottom" v-for="(item, i) of menuList" :key="i">
|
||||
@ -68,7 +71,7 @@
|
||||
<div v-if="showRightFlag" class="right">
|
||||
<div class="configure_page">
|
||||
<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" />
|
||||
</el-button>
|
||||
</div>
|
||||
@ -155,7 +158,7 @@
|
||||
<div v-else>
|
||||
<el-row justify="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" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
@ -185,24 +188,26 @@
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup name="MpMenu">
|
||||
import { handleTree } from '@/utils/tree'
|
||||
<script lang="ts" setup name="MpMenu">
|
||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||
import 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 { handleTree } from '@/utils/tree'
|
||||
import menuOptions from './menuOptions'
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
const loading = ref(true) // 遮罩层
|
||||
const accountId = ref(undefined) // 公众号Id
|
||||
const menuList = ref({ children: [] })
|
||||
const loading = ref(false) // 遮罩层
|
||||
const accountId = ref<number | undefined>()
|
||||
const accountName = ref<string | undefined>('')
|
||||
const menuList = ref<any>({ children: [] })
|
||||
|
||||
// ======================== 菜单操作 ========================
|
||||
const isActive = ref(-1) // 一级菜单点中样式
|
||||
const isSubMenuActive = ref(-1) // 一级菜单点中样式
|
||||
const isSubMenuActive = ref<string | number>(-1) // 一级菜单点中样式
|
||||
const isSubMenuFlag = ref(-1) // 二级菜单显示标志
|
||||
|
||||
// ======================== 菜单编辑 ========================
|
||||
@ -210,15 +215,16 @@ const showRightFlag = ref(false) // 右边配置显示默认详情还是配置
|
||||
const nameMaxLength = ref(0) // 菜单名称最大长度;1 级是 4 字符;2 级是 7 字符;
|
||||
const showConfigureContent = ref(true) // 是否展示配置内容;如果有子菜单,就不显示配置内容
|
||||
const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件
|
||||
const tempObj = ref({}) // 右边临时变量,作为中间值牵引关系
|
||||
const tempObj = ref<any>({}) // 右边临时变量,作为中间值牵引关系
|
||||
|
||||
// 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
|
||||
const tempSelfObj = ref({})
|
||||
const tempSelfObj = ref<any>({})
|
||||
const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const accountChanged = (id) => {
|
||||
const onAccountChanged = (id?: number, name?: string) => {
|
||||
accountId.value = id
|
||||
accountName.value = name
|
||||
getList()
|
||||
}
|
||||
|
||||
@ -241,10 +247,10 @@ const handleQuery = () => {
|
||||
}
|
||||
|
||||
// 将后端返回的 menuList,转换成前端的 menuList
|
||||
const convertMenuList = (list) => {
|
||||
const convertMenuList = (list: any[]) => {
|
||||
if (!list) return []
|
||||
|
||||
const menuList = []
|
||||
const result: any[] = []
|
||||
list.forEach((item) => {
|
||||
const menu = {
|
||||
...item
|
||||
@ -271,9 +277,9 @@ const convertMenuList = (list) => {
|
||||
hqMusicUrl: item.replyHqMusicUrl
|
||||
}
|
||||
}
|
||||
menuList.push(menu)
|
||||
result.push(menu)
|
||||
})
|
||||
return menuList
|
||||
return result
|
||||
}
|
||||
|
||||
// 重置表单,清空表单数据
|
||||
@ -286,7 +292,7 @@ const resetForm = () => {
|
||||
// 菜单编辑
|
||||
showRightFlag.value = false
|
||||
nameMaxLength.value = 0
|
||||
showConfigureContent.value = 0
|
||||
showConfigureContent.value = false
|
||||
hackResetWxReplySelect.value = false
|
||||
tempObj.value = {}
|
||||
tempSelfObj.value = {}
|
||||
@ -295,7 +301,7 @@ const resetForm = () => {
|
||||
|
||||
// ======================== 菜单操作 ========================
|
||||
// 一级菜单点击事件
|
||||
const menuClick = (i, item) => {
|
||||
const menuClick = (i: number, item: any) => {
|
||||
// 右侧的表单相关
|
||||
resetEditor()
|
||||
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()
|
||||
showRightFlag.value = true // 右边菜单
|
||||
console.log(subItem)
|
||||
tempObj.value = subItem // 将点击的数据放到临时变量,对象有引用作用
|
||||
tempSelfObj.value.grand = '2' // 表示二级菜单
|
||||
tempSelfObj.value.index = index // 表示一级菜单索引
|
||||
@ -331,7 +336,7 @@ const subMenuClick = (subItem, index, k) => {
|
||||
|
||||
// 添加横向一级菜单
|
||||
const addMenu = () => {
|
||||
const menuKeyLength = menuList.value.length
|
||||
const menuKeyLength: number = menuList.value.length
|
||||
const addButton = {
|
||||
name: '菜单名称',
|
||||
children: [],
|
||||
@ -342,10 +347,10 @@ const addMenu = () => {
|
||||
}
|
||||
}
|
||||
menuList.value[menuKeyLength] = addButton
|
||||
menuClick(menuKeyLength.value - 1, addButton)
|
||||
menuClick(menuKeyLength - 1, addButton)
|
||||
}
|
||||
// 添加横向二级菜单;item 表示要操作的父菜单
|
||||
const addSubMenu = (i, item) => {
|
||||
const addSubMenu = (i: number, item: any) => {
|
||||
// 清空父菜单的属性,因为它只需要 name 属性即可
|
||||
if (!item.children || item.children.length <= 0) {
|
||||
item.children = []
|
||||
@ -361,8 +366,8 @@ const addSubMenu = (i, item) => {
|
||||
showConfigureContent.value = false
|
||||
}
|
||||
|
||||
let subMenuKeyLength = item.children.length // 获取二级菜单key长度
|
||||
let addButton = {
|
||||
const subMenuKeyLength = item.children.length // 获取二级菜单key长度
|
||||
const addButton = {
|
||||
name: '子菜单名称',
|
||||
reply: {
|
||||
// 用于存储回复内容
|
||||
@ -399,7 +404,7 @@ const handleDeleteMenu = async () => {
|
||||
// ======================== 菜单编辑 ========================
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await message.confirm('确定要删除吗?')
|
||||
await message.confirm('确定要保存吗?')
|
||||
loading.value = true
|
||||
await MpMenuApi.saveMenu(accountId.value, convertMenuFormList())
|
||||
getList()
|
||||
@ -413,7 +418,6 @@ const handleSave = async () => {
|
||||
const resetEditor = () => {
|
||||
hackResetWxReplySelect.value = false // 销毁组件
|
||||
nextTick(() => {
|
||||
console.log('nextTick')
|
||||
hackResetWxReplySelect.value = true // 重建组件
|
||||
})
|
||||
}
|
||||
@ -432,9 +436,9 @@ const handleDelete = async () => {
|
||||
|
||||
// 将前端的 menuList,转换成后端接收的 menuList
|
||||
const convertMenuFormList = () => {
|
||||
const result = []
|
||||
const result: any[] = []
|
||||
menuList.value.forEach((item) => {
|
||||
let menu = convertMenuForm(item)
|
||||
const menu = convertMenuForm(item)
|
||||
result.push(menu)
|
||||
|
||||
// 处理子菜单
|
||||
@ -450,7 +454,7 @@ const convertMenuFormList = () => {
|
||||
}
|
||||
|
||||
// 将前端的 menu,转换成后端接收的 menu
|
||||
const convertMenuForm = (menu) => {
|
||||
const convertMenuForm = (menu: any) => {
|
||||
let result = {
|
||||
...menu,
|
||||
children: undefined, // 不处理子节点
|
||||
@ -473,11 +477,7 @@ const convertMenuForm = (menu) => {
|
||||
}
|
||||
|
||||
// ======================== 菜单编辑(素材选择) ========================
|
||||
const openMaterial = () => {
|
||||
dialogNewsVisible.value = true
|
||||
}
|
||||
|
||||
const selectMaterial = (item) => {
|
||||
const selectMaterial = (item: any) => {
|
||||
const articleId = item.articleId
|
||||
const articles = item.content.newsItem
|
||||
// 提示,针对多图文
|
||||
|
@ -19,24 +19,30 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as MpTagApi from '@/api/mp/tag'
|
||||
import { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formType = ref<'create' | 'update' | ''>('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
accountId: -1,
|
||||
name: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
const formRules: FormRules = {
|
||||
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
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
@ -55,11 +61,10 @@ const open = async (type: string, accountId: number, id?: number) => {
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
|
@ -3,17 +3,25 @@
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
||||
<WxAccountSelect @change="accountChanged">
|
||||
<template #actions>
|
||||
<el-form
|
||||
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-item>
|
||||
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']">
|
||||
<Icon icon="ep:refresh" class="mr-5px" /> 同步
|
||||
</el-button>
|
||||
</template>
|
||||
</WxAccountSelect>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
@ -63,26 +71,35 @@
|
||||
<TagForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<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 TagForm from './TagForm.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
const list = ref<any>([]) // 列表的数据
|
||||
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined
|
||||
})
|
||||
|
||||
const formRef = ref<InstanceType<typeof TagForm> | null>(null)
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const accountChanged = (accountId) => {
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.pageNo = 1
|
||||
queryParams.accountId = accountId
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
@ -99,9 +116,8 @@ const getList = async () => {
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
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 () => {
|
||||
try {
|
||||
await message.confirm('是否确认同步标签?')
|
||||
// @ts-ignore
|
||||
await MpTagApi.syncTag(queryParams.accountId)
|
||||
await MpTagApi.syncTag(queryParams.accountId as number)
|
||||
message.success('同步标签成功')
|
||||
await getList()
|
||||
} catch {}
|
||||
|
@ -3,14 +3,42 @@
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
||||
<WxAccountSelect @change="(accountId) => accountChanged(accountId)">
|
||||
<template #actions>
|
||||
<el-form
|
||||
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-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']">
|
||||
<Icon icon="ep:refresh" class="mr-5px" /> 同步
|
||||
</el-button>
|
||||
</template>
|
||||
</WxAccountSelect>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
@ -66,25 +94,37 @@
|
||||
<UserForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup name="MpUser">
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MpUserApi from '@/api/mp/user'
|
||||
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'
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
openid: string | null
|
||||
nickname: string | null
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: null,
|
||||
accountId: undefined,
|
||||
openid: 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 () => {
|
||||
@ -92,9 +132,9 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const accountChanged = (accountId) => {
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.pageNo = 1
|
||||
queryParams.accountId = accountId
|
||||
queryParams.accountId = id
|
||||
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) => {
|
||||
formRef.value.open(id)
|
||||
formRef.value?.open(id)
|
||||
}
|
||||
|
||||
/** 同步标签 */
|
||||
const handleSync = async () => {
|
||||
const accountId = queryParams.accountId
|
||||
try {
|
||||
await message.confirm('是否确认同步粉丝?')
|
||||
await MpUserApi.syncUser(accountId)
|
||||
await MpUserApi.syncUser(queryParams.accountId)
|
||||
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
|
||||
await getList()
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
|
152
src/views/pay/order/orderForm.vue
Normal file
152
src/views/pay/order/orderForm.vue
Normal file
@ -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>
|
154
src/views/pay/refund/refundForm.vue
Normal file
154
src/views/pay/refund/refundForm.vue
Normal file
@ -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
Block a user