From ab52f8b18fe9060a93088851ec794390ddfe85bc Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Thu, 20 Apr 2023 14:17:21 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E8=8F=9C=E5=8D=95=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=9A=E6=B7=BB=E5=8A=A0=E5=88=B7=E6=96=B0=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=8C=89=E9=92=AE=EF=BC=8C=E6=8F=90=E9=AB=98?= =?UTF-8?q?=E6=98=93=E7=94=A8=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/menu/index.vue | 93 +++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 323126c2..95e71b5c 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -5,27 +5,27 @@ <!-- 搜索工作栏 --> <ContentWrap> <el-form - class="-mb-15px" - :model="queryParams" ref="queryFormRef" :inline="true" + :model="queryParams" + class="-mb-15px" label-width="68px" > <el-form-item label="菜单名称" prop="name"> <el-input v-model="queryParams.name" - placeholder="请输入菜单名称" - clearable - @keyup.enter="handleQuery" class="!w-240px" + clearable + placeholder="请输入菜单名称" + @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="状态" prop="status"> <el-select v-model="queryParams.status" - placeholder="请选择菜单状态" - clearable class="!w-240px" + clearable + placeholder="请选择菜单状态" > <el-option v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" @@ -36,18 +36,30 @@ </el-select> </el-form-item> <el-form-item> - <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> - <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button - type="primary" - plain - @click="openForm('create')" - v-hasPermi="['system:menu:create']" - > - <Icon icon="ep:plus" class="mr-5px" /> 新增 + <el-button @click="handleQuery"> + <Icon class="mr-5px" icon="ep:search" /> + 搜索 </el-button> - <el-button type="danger" plain @click="toggleExpandAll"> - <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠 + <el-button @click="resetQuery"> + <Icon class="mr-5px" icon="ep:refresh" /> + 重置 + </el-button> + <el-button + v-hasPermi="['system:menu:create']" + plain + type="primary" + @click="openForm('create')" + > + <Icon class="mr-5px" icon="ep:plus" /> + 新增 + </el-button> + <el-button plain type="danger" @click="toggleExpandAll"> + <Icon class="mr-5px" icon="ep:sort" /> + 展开/折叠 + </el-button> + <el-button plain @click="refreshMenu"> + <Icon class="mr-5px" icon="ep:refresh" /> + 刷新菜单缓存 </el-button> </el-form-item> </el-form> @@ -56,50 +68,50 @@ <!-- 列表 --> <ContentWrap> <el-table + v-if="refreshTable" v-loading="loading" :data="list" - row-key="id" - v-if="refreshTable" :default-expand-all="isExpandAll" + row-key="id" > - <el-table-column prop="name" label="菜单名称" :show-overflow-tooltip="true" width="250" /> - <el-table-column prop="icon" label="图标" align="center" width="100"> + <el-table-column :show-overflow-tooltip="true" label="菜单名称" prop="name" width="250" /> + <el-table-column align="center" label="图标" prop="icon" width="100"> <template #default="scope"> <Icon :icon="scope.row.icon" /> </template> </el-table-column> - <el-table-column prop="sort" label="排序" width="60" /> - <el-table-column prop="permission" label="权限标识" :show-overflow-tooltip="true" /> - <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true" /> - <el-table-column prop="componentName" label="组件名称" :show-overflow-tooltip="true" /> - <el-table-column prop="status" label="状态" width="80"> + <el-table-column label="排序" prop="sort" width="60" /> + <el-table-column :show-overflow-tooltip="true" label="权限标识" prop="permission" /> + <el-table-column :show-overflow-tooltip="true" label="组件路径" prop="component" /> + <el-table-column :show-overflow-tooltip="true" label="组件名称" prop="componentName" /> + <el-table-column label="状态" prop="status" width="80"> <template #default="scope"> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> </template> </el-table-column> - <el-table-column label="操作" align="center"> + <el-table-column align="center" label="操作"> <template #default="scope"> <el-button + v-hasPermi="['system:menu:update']" link type="primary" @click="openForm('update', scope.row.id)" - v-hasPermi="['system:menu:update']" > 修改 </el-button> <el-button + v-hasPermi="['system:menu:create']" link type="primary" @click="openForm('create', undefined, scope.row.id)" - v-hasPermi="['system:menu:create']" > 新增 </el-button> <el-button + v-hasPermi="['system:menu:delete']" link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['system:menu:delete']" > 删除 </el-button> @@ -111,11 +123,14 @@ <!-- 表单弹窗:添加/修改 --> <MenuForm ref="formRef" @success="getList" /> </template> -<script setup lang="ts" name="SystemMenu"> +<script lang="ts" name="SystemMenu" setup> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { handleTree } from '@/utils/tree' import * as MenuApi from '@/api/system/menu' import MenuForm from './MenuForm.vue' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' + +const { wsCache } = useCache() const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -165,7 +180,19 @@ const toggleExpandAll = () => { refreshTable.value = true }) } - +/** 刷新菜单缓存按钮操作 */ +const refreshMenu = () => { + ElMessageBox.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存', { + confirmButtonText: t('common.ok'), + cancelButtonText: t('common.cancel'), + type: 'warning' + }).then(() => { + // 清空,从而触发刷新 + wsCache.delete(CACHE_KEY.ROLE_ROUTERS) + // 刷新浏览器 + location.reload() + }) +} /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { From b45b85984ce55f09e80d0b3a4ba7e788ad1c8742 Mon Sep 17 00:00:00 2001 From: dhb52 <dhb52@126.com> Date: Fri, 21 Apr 2023 20:22:11 +0800 Subject: [PATCH 2/8] =?UTF-8?q?refactor:=20mp=E6=A8=A1=E5=9D=97=EF=BC=8C?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=A4=A7=E5=A4=A7=E7=9A=84=E9=87=8D=E6=9E=84?= =?UTF-8?q?+fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mp/autoReply/components/ReplyTable.vue | 8 +- src/views/mp/autoReply/components/types.ts | 41 ------- src/views/mp/autoReply/index.vue | 55 ++++----- .../mp/components/wx-account-select/index.ts | 3 + .../mp/components/wx-account-select/main.vue | 14 ++- src/views/mp/components/wx-location/index.ts | 3 + .../mp/components/wx-material-select/index.ts | 6 + .../mp/components/wx-material-select/main.vue | 58 +++++----- .../mp/components/wx-material-select/types.ts | 11 ++ src/views/mp/components/wx-msg/index.ts | 6 + src/views/mp/components/wx-msg/main.vue | 28 ++--- src/views/mp/components/wx-music/index.ts | 3 + src/views/mp/components/wx-news/index.ts | 3 + src/views/mp/components/wx-news/main.vue | 36 +++--- .../wx-reply/components/TabImage.vue | 50 ++++----- .../wx-reply/components/TabMusic.vue | 41 ++++--- .../wx-reply/components/TabNews.vue | 34 +++--- .../wx-reply/components/TabText.vue | 11 +- .../wx-reply/components/TabVideo.vue | 52 ++++----- .../wx-reply/components/TabVoice.vue | 54 ++++----- .../components/wx-reply/components/types.ts | 59 +++++++--- src/views/mp/components/wx-reply/index.ts | 7 ++ src/views/mp/components/wx-reply/main.vue | 105 ++++++++++++++---- .../mp/components/wx-video-play/index.ts | 3 + .../mp/components/wx-voice-play/index.ts | 3 + .../mp/components/wx-voice-play/main.vue | 6 +- src/views/mp/draft/components/CoverSelect.vue | 36 ++---- src/views/mp/draft/components/DraftTable.vue | 2 +- src/views/mp/draft/index.vue | 9 +- src/views/mp/freePublish/index.vue | 10 +- src/views/mp/hooks/useUpload.ts | 12 +- .../mp/material/components/UploadFile.vue | 15 +-- .../mp/material/components/UploadVideo.vue | 28 ++--- .../mp/material/components/VideoTable.vue | 2 +- .../mp/material/components/VoiceTable.vue | 2 +- src/views/mp/material/components/upload.ts | 12 +- src/views/mp/material/index.vue | 28 ++--- src/views/mp/menu/components/MenuEditor.vue | 14 +-- src/views/mp/menu/index.vue | 8 +- src/views/mp/message/MessageTable.vue | 10 +- src/views/mp/message/index.vue | 20 ++-- src/views/mp/tag/index.vue | 32 ++++-- src/views/mp/user/index.vue | 16 ++- 43 files changed, 518 insertions(+), 438 deletions(-) create mode 100644 src/views/mp/components/wx-account-select/index.ts create mode 100644 src/views/mp/components/wx-location/index.ts create mode 100644 src/views/mp/components/wx-material-select/index.ts create mode 100644 src/views/mp/components/wx-material-select/types.ts create mode 100644 src/views/mp/components/wx-msg/index.ts create mode 100644 src/views/mp/components/wx-music/index.ts create mode 100644 src/views/mp/components/wx-news/index.ts create mode 100644 src/views/mp/components/wx-reply/index.ts create mode 100644 src/views/mp/components/wx-video-play/index.ts create mode 100644 src/views/mp/components/wx-voice-play/index.ts diff --git a/src/views/mp/autoReply/components/ReplyTable.vue b/src/views/mp/autoReply/components/ReplyTable.vue index 0b739cef..e3e53905 100644 --- a/src/views/mp/autoReply/components/ReplyTable.vue +++ b/src/views/mp/autoReply/components/ReplyTable.vue @@ -94,10 +94,10 @@ </el-table> </template> <script setup lang="ts"> -import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' -import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' -import WxMusic from '@/views/mp/components/wx-music/main.vue' -import WxNews from '@/views/mp/components/wx-news/main.vue' +import WxVideoPlayer from '@/views/mp/components/wx-video-play' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play' +import WxMusic from '@/views/mp/components/wx-music' +import WxNews from '@/views/mp/components/wx-news' import { dateFormatter } from '@/utils/formatTime' import { DICT_TYPE } from '@/utils/dict' import { MsgType } from './types' diff --git a/src/views/mp/autoReply/components/types.ts b/src/views/mp/autoReply/components/types.ts index 0d78fd85..68bc5c94 100644 --- a/src/views/mp/autoReply/components/types.ts +++ b/src/views/mp/autoReply/components/types.ts @@ -5,44 +5,3 @@ export enum MsgType { Message = 2, Keyword = 3 } - -type ReplyType = 'text' | 'image' | 'voice' | 'video' | 'shortvideo' | 'location' | 'link' - -export interface ReplyForm { - // relation: - id?: number - accountId?: number - type?: MsgType - // request: - requestMessageType?: ReplyType - requestMatch?: number - requestKeyword?: string - // response: - responseMessageType?: ReplyType - responseContent?: string - responseMediaId?: number - responseMediaUrl?: string - responseTitle?: string - responseDescription?: number - responseThumbMediaId?: string - responseThumbMediaUrl?: string - responseArticles?: any[] - responseMusicUrl?: string - responseHqMusicUrl?: string -} - -// TODO @Dhb52:ObjData 这个类名可以在看看,ObjData 有点通用 -export interface ObjData { - type: ReplyType - accountId?: number - content?: string - mediaId?: number - url?: string - title?: string - description?: string - thumbMediaId?: number - thumbMediaUrl?: string - articles?: any[] - musicUrl?: string - hqMusicUrl?: string -} diff --git a/src/views/mp/autoReply/index.vue b/src/views/mp/autoReply/index.vue index 2d9b492d..20a1e683 100644 --- a/src/views/mp/autoReply/index.vue +++ b/src/views/mp/autoReply/index.vue @@ -82,7 +82,7 @@ <el-input v-model="replyForm.requestKeyword" placeholder="请输入内容" clearable /> </el-form-item> <el-form-item label="回复消息"> - <WxReplySelect :objData="objData" /> + <WxReplySelect v-model="reply" /> </el-form-item> </el-form> <template #footer> @@ -93,14 +93,14 @@ </ContentWrap> </template> <script setup lang="ts" name="MpAutoReply"> -import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' -import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' +import WxReplySelect, { type Reply, ReplyType } from '@/views/mp/components/wx-reply' +import WxAccountSelect from '@/views/mp/components/wx-account-select' import * as MpAutoReplyApi from '@/api/mp/autoReply' import { DICT_TYPE, getDictOptions, getIntDictOptions } from '@/utils/dict' import { ContentWrap } from '@/components/ContentWrap' -import type { TabPaneName } from 'element-plus' +import type { FormInstance, TabPaneName } from 'element-plus' import ReplyTable from './components/ReplyTable.vue' -import { MsgType, ReplyForm, ObjData } from './components/types' +import { MsgType } from './components/types' const message = useMessage() // 消息 const msgType = ref<MsgType>(MsgType.Keyword) // 消息类型 @@ -108,26 +108,26 @@ const RequestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'l const loading = ref(true) // 遮罩层 const total = ref(0) // 总条数 const list = ref<any[]>([]) // 自动回复列表 -const formRef = ref() // 表单 ref +const formRef = ref<FormInstance | null>(null) // 表单 ref // 查询参数 interface QueryParams { pageNo: number pageSize: number - accountId?: number + accountId: number } const queryParams: QueryParams = reactive({ pageNo: 1, pageSize: 10, - accountId: undefined + accountId: 0 }) const dialogTitle = ref('') // 弹出层标题 const showFormDialog = ref(false) // 是否显示弹出层 -const replyForm = ref<ReplyForm>({}) // 表单参数 +const replyForm = ref<any>({}) // 表单参数 // 回复消息 -const objData = ref<ObjData>({ - type: 'text', - accountId: undefined +const reply = ref<Reply>({ + type: ReplyType.Text, + accountId: 0 }) // 表单校验 const rules = { @@ -136,8 +136,9 @@ const rules = { } /** 侦听账号变化 */ -const onAccountChanged = (id?: number) => { +const onAccountChanged = (id: number) => { queryParams.accountId = id + reply.value.accountId = id getList() } @@ -171,8 +172,8 @@ const onTabChange = (tabName: TabPaneName) => { const onCreate = () => { reset() // 打开表单,并设置初始化 - objData.value = { - type: 'text', + reply.value = { + type: ReplyType.Text, accountId: queryParams.accountId } @@ -193,7 +194,7 @@ const onUpdate = async (id: number) => { delete replyForm.value['responseMediaUrl'] delete replyForm.value['responseDescription'] delete replyForm.value['responseArticles'] - objData.value = { + reply.value = { type: data.responseMessageType, accountId: queryParams.accountId, content: data.responseContent, @@ -227,17 +228,17 @@ const onSubmit = async () => { // 处理回复消息 const submitForm: any = { ...replyForm.value } - submitForm.responseMessageType = objData.value.type - submitForm.responseContent = objData.value.content - submitForm.responseMediaId = objData.value.mediaId - submitForm.responseMediaUrl = objData.value.url - submitForm.responseTitle = objData.value.title - submitForm.responseDescription = objData.value.description - submitForm.responseThumbMediaId = objData.value.thumbMediaId - submitForm.responseThumbMediaUrl = objData.value.thumbMediaUrl - submitForm.responseArticles = objData.value.articles - submitForm.responseMusicUrl = objData.value.musicUrl - submitForm.responseHqMusicUrl = objData.value.hqMusicUrl + submitForm.responseMessageType = reply.value.type + submitForm.responseContent = reply.value.content + submitForm.responseMediaId = reply.value.mediaId + submitForm.responseMediaUrl = reply.value.url + submitForm.responseTitle = reply.value.title + submitForm.responseDescription = reply.value.description + submitForm.responseThumbMediaId = reply.value.thumbMediaId + submitForm.responseThumbMediaUrl = reply.value.thumbMediaUrl + submitForm.responseArticles = reply.value.articles + submitForm.responseMusicUrl = reply.value.musicUrl + submitForm.responseHqMusicUrl = reply.value.hqMusicUrl if (replyForm.value.id !== undefined) { await MpAutoReplyApi.updateAutoReply(submitForm) diff --git a/src/views/mp/components/wx-account-select/index.ts b/src/views/mp/components/wx-account-select/index.ts new file mode 100644 index 00000000..97556b2f --- /dev/null +++ b/src/views/mp/components/wx-account-select/index.ts @@ -0,0 +1,3 @@ +import WxAccountSelect from './main.vue' + +export default WxAccountSelect diff --git a/src/views/mp/components/wx-account-select/main.vue b/src/views/mp/components/wx-account-select/main.vue index 5359b366..8dbad499 100644 --- a/src/views/mp/components/wx-account-select/main.vue +++ b/src/views/mp/components/wx-account-select/main.vue @@ -14,7 +14,7 @@ const account: MpAccountApi.AccountVO = reactive({ const accountList: Ref<MpAccountApi.AccountVO[]> = ref([]) const emit = defineEmits<{ - (e: 'change', id?: number, name?: string): void + (e: 'change', id: number, name: string): void }>() const handleQuery = async () => { @@ -22,15 +22,19 @@ const handleQuery = async () => { // 默认选中第一个 if (accountList.value.length > 0) { account.id = accountList.value[0].id - account.name = accountList.value[0].name - emit('change', account.id, account.name) + if (account.id) { + account.name = accountList.value[0].name + emit('change', account.id, account.name) + } } } const onChanged = (id?: number) => { const found = accountList.value.find((v) => v.id === id) - account.name = found ? found.name : '' - emit('change', account.id, account.name) + if (account.id) { + account.name = found ? found.name : '' + emit('change', account.id, account.name) + } } /** 初始化 */ diff --git a/src/views/mp/components/wx-location/index.ts b/src/views/mp/components/wx-location/index.ts new file mode 100644 index 00000000..14ba8644 --- /dev/null +++ b/src/views/mp/components/wx-location/index.ts @@ -0,0 +1,3 @@ +import WxLocation from './main.vue' + +export default WxLocation diff --git a/src/views/mp/components/wx-material-select/index.ts b/src/views/mp/components/wx-material-select/index.ts new file mode 100644 index 00000000..eeda31d5 --- /dev/null +++ b/src/views/mp/components/wx-material-select/index.ts @@ -0,0 +1,6 @@ +import WxMaterialSelect from './main.vue' +import { NewsType, MaterialType } from './types' + +export { NewsType, MaterialType } + +export default WxMaterialSelect diff --git a/src/views/mp/components/wx-material-select/main.vue b/src/views/mp/components/wx-material-select/main.vue index d67ffb1f..e711c022 100644 --- a/src/views/mp/components/wx-material-select/main.vue +++ b/src/views/mp/components/wx-material-select/main.vue @@ -7,7 +7,7 @@ <template> <div class="pb-30px"> <!-- 类型:image --> - <div v-if="objData.type === 'image'"> + <div v-if="props.type === 'image'"> <div class="waterfall" v-loading="loading"> <div class="waterfall-item" v-for="item in list" :key="item.mediaId"> <img class="material-img" :src="item.url" /> @@ -29,7 +29,7 @@ /> </div> <!-- 类型:voice --> - <div v-else-if="objData.type === 'voice'"> + <div v-else-if="props.type === 'voice'"> <!-- 列表 --> <el-table v-loading="loading" :data="list"> <el-table-column label="编号" align="center" prop="mediaId" /> @@ -64,7 +64,7 @@ /> </div> <!-- 类型:video --> - <div v-else-if="objData.type === 'video'"> + <div v-else-if="props.type === 'video'"> <!-- 列表 --> <el-table v-loading="loading" :data="list"> <el-table-column label="编号" align="center" prop="mediaId" /> @@ -106,7 +106,7 @@ /> </div> <!-- 类型:news --> - <div v-else-if="objData.type === 'news'"> + <div v-else-if="props.type === 'news'"> <div class="waterfall" v-loading="loading"> <div class="waterfall-item" v-for="item in list" :key="item.mediaId"> <div v-if="item.content && item.content.newsItem"> @@ -132,25 +132,25 @@ </template> <script lang="ts" setup name="WxMaterialSelect"> -import WxNews from '@/views/mp/components/wx-news/main.vue' -import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' -import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' +import WxNews from '@/views/mp/components/wx-news' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play' +import WxVideoPlayer from '@/views/mp/components/wx-video-play' +import { NewsType } from './types' import * as MpMaterialApi from '@/api/mp/material' import * as MpFreePublishApi from '@/api/mp/freePublish' import * as MpDraftApi from '@/api/mp/draft' import { dateFormatter } from '@/utils/formatTime' -const props = defineProps({ - objData: { - type: Object, // type - 类型;accountId - 公众号账号编号 - required: true - }, - newsType: { - // 图文类型:1、已发布图文;2、草稿箱图文 - type: String as PropType<string>, - default: '1' +const props = withDefaults( + defineProps<{ + type: string + accountId: number + newsType?: NewsType + }>(), + { + newsType: NewsType.Published } -}) +) const emit = defineEmits(['select-material']) @@ -159,15 +159,13 @@ const loading = ref(false) // 总条数 const total = ref(0) // 数据列表 -const list = ref([]) +const list = ref<any[]>([]) // 查询参数 const queryParams = reactive({ pageNo: 1, pageSize: 10, - accountId: props.objData.accountId + accountId: props.accountId }) -const objDataRef = reactive(props.objData) -const newsTypeRef = ref(props.newsType) const selectMaterialFun = (item) => { emit('select-material', item) @@ -176,10 +174,10 @@ const selectMaterialFun = (item) => { const getPage = async () => { loading.value = true try { - if (objDataRef.type === 'news' && newsTypeRef.value === '1') { + if (props.type === 'news' && props.newsType === NewsType.Published) { // 【图文】+ 【已发布】 await getFreePublishPageFun() - } else if (objDataRef.type === 'news' && newsTypeRef.value === '2') { + } else if (props.type === 'news' && props.newsType === NewsType.Draft) { // 【图文】+ 【草稿】 await getDraftPageFun() } else { @@ -194,7 +192,7 @@ const getPage = async () => { const getMaterialPageFun = async () => { const data = await MpMaterialApi.getMaterialPage({ ...queryParams, - type: objDataRef.type + type: props.type }) list.value = data.list total.value = data.total @@ -202,9 +200,9 @@ const getMaterialPageFun = async () => { const getFreePublishPageFun = async () => { const data = await MpFreePublishApi.getFreePublishPage(queryParams) - data.list.forEach((item) => { - const newsItem = item.content.newsItem - newsItem.forEach((article) => { + data.list.forEach((item: any) => { + const articles = item.content.newsItem + articles.forEach((article: any) => { article.picUrl = article.thumbUrl }) }) @@ -214,9 +212,9 @@ const getFreePublishPageFun = async () => { const getDraftPageFun = async () => { const data = await MpDraftApi.getDraftPage(queryParams) - data.list.forEach((item) => { - const newsItem = item.content.newsItem - newsItem.forEach((article) => { + data.list.forEach((draft: any) => { + const articles = draft.content.newsItem + articles.forEach((article: any) => { article.picUrl = article.thumbUrl }) }) diff --git a/src/views/mp/components/wx-material-select/types.ts b/src/views/mp/components/wx-material-select/types.ts new file mode 100644 index 00000000..d4add1d5 --- /dev/null +++ b/src/views/mp/components/wx-material-select/types.ts @@ -0,0 +1,11 @@ +export enum NewsType { + Draft = '2', + Published = '1' +} + +export enum MaterialType { + Image = 'image', + Voice = 'voice', + Video = 'video', + News = 'news' +} diff --git a/src/views/mp/components/wx-msg/index.ts b/src/views/mp/components/wx-msg/index.ts new file mode 100644 index 00000000..fd9eddd7 --- /dev/null +++ b/src/views/mp/components/wx-msg/index.ts @@ -0,0 +1,6 @@ +import WxMsg from './main.vue' +import { MsgType } from './types' + +export { MsgType } + +export default WxMsg diff --git a/src/views/mp/components/wx-msg/main.vue b/src/views/mp/components/wx-msg/main.vue index c7354628..19763245 100644 --- a/src/views/mp/components/wx-msg/main.vue +++ b/src/views/mp/components/wx-msg/main.vue @@ -125,19 +125,19 @@ </div> </div> <div class="msg-send" v-loading="sendLoading"> - <WxReplySelect ref="replySelectRef" :objData="objData" /> + <WxReplySelect ref="replySelectRef" v-model="reply" /> <el-button type="success" class="send-but" @click="sendMsg">发送(S)</el-button> </div> </ContentWrap> </template> <script setup lang="ts" name="WxMsg"> -import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' -import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' -import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' -import WxNews from '@/views/mp/components/wx-news/main.vue' -import WxLocation from '@/views/mp/components/wx-location/main.vue' -import WxMusic from '@/views/mp/components/wx-music/main.vue' +import WxReplySelect from '@/views/mp/components/wx-reply' +import WxVideoPlayer from '@/views/mp/components/wx-video-play' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play' +import WxNews from '@/views/mp/components/wx-news' +import WxLocation from '@/views/mp/components/wx-location' +import WxMusic from '@/views/mp/components/wx-music' import { getMessagePage, sendMessage } from '@/api/mp/message' import { getUser } from '@/api/mp/user' import { formatDate } from '@/utils/formatTime' @@ -187,14 +187,14 @@ const mp: Mp = reactive({ // ========= 消息发送 ========= const sendLoading = ref(false) // 发送消息是否加载中 -interface ObjData { +interface Reply { type: MsgType accountId: number | null articles: any[] } // 微信发送消息 -const objData: ObjData = reactive({ +const reply = ref<Reply>({ type: MsgType.Text, accountId: null, articles: [] @@ -209,23 +209,23 @@ onMounted(async () => { user.avatar = user.avatar?.length > 0 ? data.avatar : user.avatar user.accountId = data.accountId queryParams.accountId = data.accountId - objData.accountId = data.accountId + reply.value.accountId = data.accountId refreshChange() }) // 执行发送 const sendMsg = async () => { - if (!objData) { + if (!reply) { return } // 公众号限制:客服消息,公众号只允许发送一条 - if (objData.type === MsgType.News && objData.articles.length > 1) { - objData.articles = [objData.articles[0]] + if (reply.value.type === MsgType.News && reply.value.articles.length > 1) { + reply.value.articles = [reply.value.articles[0]] message.success('图文消息条数限制在 1 条以内,已默认发送第一条') } - const data = await sendMessage(Object.assign({ userId: props.userId }, { ...objData })) + const data = await sendMessage({ userId: props.userId, ...reply.value }) sendLoading.value = false list.value = [...list.value, ...[data]] diff --git a/src/views/mp/components/wx-music/index.ts b/src/views/mp/components/wx-music/index.ts new file mode 100644 index 00000000..c4211261 --- /dev/null +++ b/src/views/mp/components/wx-music/index.ts @@ -0,0 +1,3 @@ +import WxMusic from './main.vue' + +export default WxMusic diff --git a/src/views/mp/components/wx-news/index.ts b/src/views/mp/components/wx-news/index.ts new file mode 100644 index 00000000..e68f4d5d --- /dev/null +++ b/src/views/mp/components/wx-news/index.ts @@ -0,0 +1,3 @@ +import WxNews from './main.vue' + +export default WxNews diff --git a/src/views/mp/components/wx-news/main.vue b/src/views/mp/components/wx-news/main.vue index 326b674c..779a446f 100644 --- a/src/views/mp/components/wx-news/main.vue +++ b/src/views/mp/components/wx-news/main.vue @@ -39,12 +39,14 @@ </template> <script lang="ts" name="WxNews" setup> -const props = defineProps({ - articles: { - type: Array, - default: () => null +const props = withDefaults( + defineProps<{ + articles: any[] | null + }>(), + { + articles: null } -}) +) defineExpose({ articles: props.articles @@ -53,9 +55,9 @@ defineExpose({ <style lang="scss" scoped> .news-home { - background-color: #ffffff; width: 100%; margin: auto; + background-color: #fff; } .news-main { @@ -64,29 +66,29 @@ defineExpose({ } .news-content { - background-color: #acadae; - width: 100%; position: relative; + width: 100%; + background-color: #acadae; } .news-content-title { - display: inline-block; - font-size: 12px; - color: #ffffff; position: absolute; - left: 0; bottom: 0; - background-color: black; + left: 0; + display: inline-block; width: 98%; padding: 1%; - opacity: 0.65; + font-size: 12px; + color: #fff; white-space: normal; + background-color: black; + opacity: 0.65; box-sizing: unset !important; } .news-main-item { - background-color: #ffffff; padding: 5px 0; + background-color: #fff; border-top: 1px solid #eaeaea; } @@ -96,17 +98,17 @@ defineExpose({ .news-content-item-title { display: inline-block; - font-size: 10px; width: 70%; margin-left: 1%; + font-size: 10px; white-space: normal; } .news-content-item-img { display: inline-block; width: 25%; - background-color: #acadae; margin-right: 1%; + background-color: #acadae; } .material-img { diff --git a/src/views/mp/components/wx-reply/components/TabImage.vue b/src/views/mp/components/wx-reply/components/TabImage.vue index eecc24cb..1a82c3aa 100644 --- a/src/views/mp/components/wx-reply/components/TabImage.vue +++ b/src/views/mp/components/wx-reply/components/TabImage.vue @@ -1,12 +1,9 @@ <template> - <el-tab-pane name="image"> - <template #label> - <el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row> - </template> + <div> <!-- 情况一:已经选择好素材、或者上传好图片 --> - <div class="select-item" v-if="objData.url"> - <img class="material-img" :src="objData.url" /> - <p class="item-name" v-if="objData.name">{{ objData.name }}</p> + <div class="select-item" v-if="reply.url"> + <img class="material-img" :src="reply.url" /> + <p class="item-name" v-if="reply.name">{{ reply.name }}</p> <el-row class="ope-row" justify="center"> <el-button type="danger" circle @click="onDelete"> <Icon icon="ep:delete" /> @@ -27,7 +24,11 @@ append-to-body destroy-on-close > - <WxMaterialSelect :objData="objData" @select-material="selectMaterial" /> + <WxMaterialSelect + type="image" + :account-id="reply.accountId" + @select-material="selectMaterial" + /> </el-dialog> </el-col> <!-- 文件上传 --> @@ -51,27 +52,27 @@ </el-upload> </el-col> </el-row> - </el-tab-pane> + </div> </template> <script setup lang="ts"> -import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' -import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload' +import WxMaterialSelect from '@/views/mp/components/wx-material-select' +import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import type { UploadRawFile } from 'element-plus' import { getAccessToken } from '@/utils/auth' -import { ObjData } from './types' +import { Reply } from './types' const message = useMessage() const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/upload-temporary' const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部 const props = defineProps<{ - modelValue: ObjData + modelValue: Reply }>() const emit = defineEmits<{ - (e: 'update:modelValue', v: ObjData) + (e: 'update:modelValue', v: Reply) }>() -const objData = computed<ObjData>({ +const reply = computed<Reply>({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) @@ -79,14 +80,13 @@ const objData = computed<ObjData>({ const showDialog = ref(false) const fileList = ref([]) const uploadData = reactive({ - accountId: objData.value.accountId, + accountId: reply.value.accountId, type: 'image', title: '', introduction: '' }) -const beforeImageUpload = (rawFile: UploadRawFile) => - useBeforeUpload(MaterialType.Image, 2)(rawFile) +const beforeImageUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Image, 2)(rawFile) const onUploadSuccess = (res: any) => { if (res.code !== 0) { @@ -104,18 +104,18 @@ const onUploadSuccess = (res: any) => { } const onDelete = () => { - objData.value.mediaId = null - objData.value.url = null - objData.value.name = null + reply.value.mediaId = null + reply.value.url = null + reply.value.name = null } const selectMaterial = (item) => { showDialog.value = false - objData.value.type = 'image' - objData.value.mediaId = item.mediaId - objData.value.url = item.url - objData.value.name = item.name + // reply.value.type = 'image' + reply.value.mediaId = item.mediaId + reply.value.url = item.url + reply.value.name = item.name } </script> diff --git a/src/views/mp/components/wx-reply/components/TabMusic.vue b/src/views/mp/components/wx-reply/components/TabMusic.vue index 2c3b04e5..b19fb592 100644 --- a/src/views/mp/components/wx-reply/components/TabMusic.vue +++ b/src/views/mp/components/wx-reply/components/TabMusic.vue @@ -1,14 +1,11 @@ <template> - <el-tab-pane name="music"> - <template #label> - <el-row align="middle"><Icon icon="ep:service" />音乐</el-row> - </template> + <div> <el-row align="middle" justify="center"> <el-col :span="6"> <el-row align="middle" justify="center" class="thumb-div"> <el-col :span="24"> <el-row align="middle" justify="center"> - <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl" /> + <img style="width: 100px" v-if="reply.thumbMediaUrl" :src="reply.thumbMediaUrl" /> <icon v-else icon="ep:plus" /> </el-row> <el-row align="middle" justify="center" style="margin-top: 2%"> @@ -42,30 +39,31 @@ destroy-on-close > <WxMaterialSelect - :objData="{ type: 'image', accountId: objData.accountId }" + type="image" + :account-id="reply.accountId" @select-material="selectMaterial" /> </el-dialog> </el-col> <el-col :span="18"> - <el-input v-model="objData.title" placeholder="请输入标题" /> + <el-input v-model="reply.title" placeholder="请输入标题" /> <div style="margin: 20px 0"></div> - <el-input v-model="objData.description" placeholder="请输入描述" /> + <el-input v-model="reply.description" placeholder="请输入描述" /> </el-col> </el-row> <div style="margin: 20px 0"></div> - <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" /> + <el-input v-model="reply.musicUrl" placeholder="请输入音乐链接" /> <div style="margin: 20px 0"></div> - <el-input v-model="objData.hqMusicUrl" placeholder="请输入高质量音乐链接" /> - </el-tab-pane> + <el-input v-model="reply.hqMusicUrl" placeholder="请输入高质量音乐链接" /> + </div> </template> <script setup lang="ts"> -import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' +import WxMaterialSelect from '@/views/mp/components/wx-material-select' import type { UploadRawFile } from 'element-plus' -import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload' +import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { getAccessToken } from '@/utils/auth' -import { ObjData } from './types' +import { Reply } from './types' const message = useMessage() @@ -73,12 +71,12 @@ const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/u const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部 const props = defineProps<{ - modelValue: ObjData + modelValue: Reply }>() const emit = defineEmits<{ - (e: 'update:modelValue', v: ObjData) + (e: 'update:modelValue', v: Reply) }>() -const objData = computed<ObjData>({ +const reply = computed<Reply>({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) @@ -86,14 +84,13 @@ const objData = computed<ObjData>({ const showDialog = ref(false) const fileList = ref([]) const uploadData = reactive({ - accountId: objData.value.accountId, + accountId: reply.value.accountId, type: 'thumb', // 音乐类型为thumb title: '', introduction: '' }) -const beforeImageUpload = (rawFile: UploadRawFile) => - useBeforeUpload(MaterialType.Image, 2)(rawFile) +const beforeImageUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Image, 2)(rawFile) const onUploadSuccess = (res: any) => { if (res.code !== 0) { @@ -113,7 +110,7 @@ const onUploadSuccess = (res: any) => { const selectMaterial = (item: any) => { showDialog.value = false - objData.value.thumbMediaId = item.mediaId - objData.value.thumbMediaUrl = item.url + reply.value.thumbMediaId = item.mediaId + reply.value.thumbMediaUrl = item.url } </script> diff --git a/src/views/mp/components/wx-reply/components/TabNews.vue b/src/views/mp/components/wx-reply/components/TabNews.vue index bb9272e7..88a82a53 100644 --- a/src/views/mp/components/wx-reply/components/TabNews.vue +++ b/src/views/mp/components/wx-reply/components/TabNews.vue @@ -1,11 +1,8 @@ <template> - <el-tab-pane name="news"> - <template #label> - <el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row> - </template> + <div> <el-row> - <div class="select-item" v-if="objData.articles?.length > 0"> - <WxNews :articles="objData.articles" /> + <div class="select-item" v-if="reply.articles && reply.articles.length > 0"> + <WxNews :articles="reply.articles" /> <el-col class="ope-row"> <el-button type="danger" circle @click="onDelete"> <Icon icon="ep:delete" /> @@ -13,7 +10,7 @@ </el-col> </div> <!-- 选择素材 --> - <el-col :span="24" v-if="!objData.content"> + <el-col :span="24" v-if="!reply.content"> <el-row style="text-align: center" align="middle"> <el-col :span="24"> <el-button type="success" @click="showDialog = true"> @@ -25,28 +22,29 @@ </el-col> <el-dialog title="选择图文" v-model="showDialog" width="90%" append-to-body destroy-on-close> <WxMaterialSelect - :objData="objData" - @select-material="selectMaterial" + type="news" + :account-id="reply.accountId" :newsType="newsType" + @select-material="selectMaterial" /> </el-dialog> </el-row> - </el-tab-pane> + </div> </template> <script setup lang="ts"> -import WxNews from '@/views/mp/components/wx-news/main.vue' -import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' -import { ObjData, NewsType } from './types' +import WxNews from '@/views/mp/components/wx-news' +import WxMaterialSelect from '@/views/mp/components/wx-material-select' +import { Reply, NewsType } from './types' const props = defineProps<{ - modelValue: ObjData + modelValue: Reply newsType: NewsType }>() const emit = defineEmits<{ - (e: 'update:modelValue', v: ObjData) + (e: 'update:modelValue', v: Reply) }>() -const objData = computed<ObjData>({ +const reply = computed<Reply>({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) @@ -55,11 +53,11 @@ const showDialog = ref(false) const selectMaterial = (item: any) => { showDialog.value = false - objData.value.articles = item.content.newsItem + reply.value.articles = item.content.newsItem } const onDelete = () => { - objData.value.articles = [] + reply.value.articles = [] } </script> diff --git a/src/views/mp/components/wx-reply/components/TabText.vue b/src/views/mp/components/wx-reply/components/TabText.vue index bd7b0187..aac698e8 100644 --- a/src/views/mp/components/wx-reply/components/TabText.vue +++ b/src/views/mp/components/wx-reply/components/TabText.vue @@ -1,15 +1,10 @@ <template> - <el-tab-pane name="text"> - <template #label> - <el-row align="middle"><Icon icon="ep:document" /> 文本</el-row> - </template> - <el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="content" /> - </el-tab-pane> + <el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="content" /> </template> <script setup lang="ts"> const props = defineProps<{ - modelValue: string | null + modelValue?: string | null }>() const emit = defineEmits<{ @@ -17,7 +12,7 @@ const emit = defineEmits<{ (e: 'input', v: string | null) }>() -const content = computed<string | null>({ +const content = computed<string | null | undefined>({ get: () => props.modelValue, set: (val: string | null) => { emit('update:modelValue', val) diff --git a/src/views/mp/components/wx-reply/components/TabVideo.vue b/src/views/mp/components/wx-reply/components/TabVideo.vue index c924bc2a..52553521 100644 --- a/src/views/mp/components/wx-reply/components/TabVideo.vue +++ b/src/views/mp/components/wx-reply/components/TabVideo.vue @@ -1,17 +1,10 @@ <template> - <el-tab-pane name="video"> - <template #label> - <el-row align="middle"><Icon icon="ep:share" /> 视频</el-row> - </template> + <div> <el-row> - <el-input v-model="objData.title" class="input-margin-bottom" placeholder="请输入标题" /> - <el-input - class="input-margin-bottom" - v-model="objData.description" - placeholder="请输入描述" - /> + <el-input v-model="reply.title" class="input-margin-bottom" placeholder="请输入标题" /> + <el-input class="input-margin-bottom" v-model="reply.description" placeholder="请输入描述" /> <el-row class="ope-row" justify="center"> - <WxVideoPlayer v-if="objData.url" :url="objData.url" /> + <WxVideoPlayer v-if="reply.url" :url="reply.url" /> </el-row> <el-col> <el-row style="text-align: center" align="middle"> @@ -27,7 +20,11 @@ append-to-body destroy-on-close > - <WxMaterialSelect :objData="objData" @select-material="selectMaterial" /> + <WxMaterialSelect + type="video" + :account-id="reply.accountId" + @select-material="selectMaterial" + /> </el-dialog> </el-col> <!-- 文件上传 --> @@ -48,16 +45,16 @@ </el-row> </el-col> </el-row> - </el-tab-pane> + </div> </template> <script setup lang="ts"> -import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' -import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' +import WxVideoPlayer from '@/views/mp/components/wx-video-play' +import WxMaterialSelect from '@/views/mp/components/wx-material-select' import type { UploadRawFile } from 'element-plus' -import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload' +import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { getAccessToken } from '@/utils/auth' -import { ObjData } from './types' +import { Reply } from './types' const message = useMessage() @@ -65,12 +62,12 @@ const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/u const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } const props = defineProps<{ - modelValue: ObjData + modelValue: Reply }>() const emit = defineEmits<{ - (e: 'update:modelValue', v: ObjData) + (e: 'update:modelValue', v: Reply) }>() -const objData = computed<ObjData>({ +const reply = computed<Reply>({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) @@ -78,14 +75,13 @@ const objData = computed<ObjData>({ const showDialog = ref(false) const fileList = ref([]) const uploadData = reactive({ - accountId: objData.value.accountId, + accountId: reply.value.accountId, type: 'video', title: '', introduction: '' }) -const beforeVideoUpload = (rawFile: UploadRawFile) => - useBeforeUpload(MaterialType.Video, 10)(rawFile) +const beforeVideoUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Video, 10)(rawFile) const onUploadSuccess = (res: any) => { if (res.code !== 0) { @@ -105,16 +101,16 @@ const onUploadSuccess = (res: any) => { const selectMaterial = (item: any) => { showDialog.value = false - objData.value.mediaId = item.mediaId - objData.value.url = item.url - objData.value.name = item.name + reply.value.mediaId = item.mediaId + reply.value.url = item.url + reply.value.name = item.name // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction if (item.title) { - objData.value.title = item.title || '' + reply.value.title = item.title || '' } if (item.introduction) { - objData.value.description = item.introduction || '' + reply.value.description = item.introduction || '' } } </script> diff --git a/src/views/mp/components/wx-reply/components/TabVoice.vue b/src/views/mp/components/wx-reply/components/TabVoice.vue index 6d40a052..c4868cf8 100644 --- a/src/views/mp/components/wx-reply/components/TabVoice.vue +++ b/src/views/mp/components/wx-reply/components/TabVoice.vue @@ -1,12 +1,9 @@ <template> - <el-tab-pane name="voice"> - <template #label> - <el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row> - </template> - <div class="select-item2" v-if="objData.url"> - <p class="item-name">{{ objData.name }}</p> + <div> + <div class="select-item2" v-if="reply.url"> + <p class="item-name">{{ reply.name }}</p> <el-row class="ope-row" justify="center"> - <WxVoicePlayer :url="objData.url" /> + <WxVoicePlayer :url="reply.url" /> </el-row> <el-row class="ope-row" justify="center"> <el-button type="danger" circle @click="onDelete"><Icon icon="ep:delete" /></el-button> @@ -25,7 +22,11 @@ append-to-body destroy-on-close > - <WxMaterialSelect :objData="objData" @select-material="selectMaterial" /> + <WxMaterialSelect + type="voice" + :account-id="reply.accountId" + @select-material="selectMaterial" + /> </el-dialog> </el-col> <!-- 文件上传 --> @@ -49,27 +50,27 @@ </el-upload> </el-col> </el-row> - </el-tab-pane> + </div> </template> <script setup lang="ts"> -import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' -import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' -import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload' +import WxMaterialSelect from '@/views/mp/components/wx-material-select' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play' +import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import type { UploadRawFile } from 'element-plus' import { getAccessToken } from '@/utils/auth' -import { ObjData } from './types' +import { Reply } from './types' const message = useMessage() const UPLOAD_URL = import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/upload-temporary' const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部 const props = defineProps<{ - modelValue: ObjData + modelValue: Reply }>() const emit = defineEmits<{ - (e: 'update:modelValue', v: ObjData) + (e: 'update:modelValue', v: Reply) }>() -const objData = computed<ObjData>({ +const reply = computed<Reply>({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) @@ -77,14 +78,13 @@ const objData = computed<ObjData>({ const showDialog = ref(false) const fileList = ref([]) const uploadData = reactive({ - accountId: objData.value.accountId, + accountId: reply.value.accountId, type: 'voice', title: '', introduction: '' }) -const beforeVoiceUpload = (rawFile: UploadRawFile) => - useBeforeUpload(MaterialType.Voice, 10)(rawFile) +const beforeVoiceUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Voice, 10)(rawFile) const onUploadSuccess = (res: any) => { if (res.code !== 0) { @@ -102,18 +102,18 @@ const onUploadSuccess = (res: any) => { } const onDelete = () => { - objData.value.mediaId = null - objData.value.url = null - objData.value.name = null + reply.value.mediaId = null + reply.value.url = null + reply.value.name = null } -const selectMaterial = (item: ObjData) => { +const selectMaterial = (item: Reply) => { showDialog.value = false - objData.value.type = 'voice' - objData.value.mediaId = item.mediaId - objData.value.url = item.url - objData.value.name = item.name + // reply.value.type = ReplyType.Voice + reply.value.mediaId = item.mediaId + reply.value.url = item.url + reply.value.name = item.name } </script> diff --git a/src/views/mp/components/wx-reply/components/types.ts b/src/views/mp/components/wx-reply/components/types.ts index d5273334..3e07d6e5 100644 --- a/src/views/mp/components/wx-reply/components/types.ts +++ b/src/views/mp/components/wx-reply/components/types.ts @@ -1,25 +1,54 @@ -type ReplyType = '' | 'news' | 'image' | 'voice' | 'video' | 'music' | 'text' +enum ReplyType { + News = 'news', + Image = 'image', + Voice = 'voice', + Video = 'video', + Music = 'music', + Text = 'text' +} -interface ObjData { +interface _Reply { accountId: number type: ReplyType - name: string | null - content: string | null - mediaId: string | null - url: string | null - title: string | null - description: string | null - thumbMediaId: string | null - thumbMediaUrl: string | null - musicUrl: string | null - hqMusicUrl: string | null - introduction: string | null - articles: any[] + name?: string | null + content?: string | null + mediaId?: string | null + url?: string | null + title?: string | null + description?: string | null + thumbMediaId?: string | null + thumbMediaUrl?: string | null + musicUrl?: string | null + hqMusicUrl?: string | null + introduction?: string | null + articles?: any[] } +type Reply = _Reply //Partial<_Reply> + enum NewsType { Published = '1', Draft = '2' } -export { ObjData, NewsType } +/** 利用旧的reply[accountId, type]初始化新的Reply */ +const createEmptyReply = (old: Reply | Ref<Reply>): Reply => { + return { + accountId: unref(old).accountId, + type: unref(old).type, + name: null, + content: null, + mediaId: null, + url: null, + title: null, + description: null, + thumbMediaId: null, + thumbMediaUrl: null, + musicUrl: null, + hqMusicUrl: null, + introduction: null, + articles: [] + } +} + +export { Reply, NewsType, ReplyType, createEmptyReply } diff --git a/src/views/mp/components/wx-reply/index.ts b/src/views/mp/components/wx-reply/index.ts new file mode 100644 index 00000000..d1da217e --- /dev/null +++ b/src/views/mp/components/wx-reply/index.ts @@ -0,0 +1,7 @@ +import { Reply, NewsType, ReplyType, createEmptyReply } from './components/types' + +import WxReplySelect from './main.vue' + +export type { Reply } +export { createEmptyReply, NewsType, ReplyType } +export default WxReplySelect diff --git a/src/views/mp/components/wx-reply/main.vue b/src/views/mp/components/wx-reply/main.vue index b00e4345..32a31222 100644 --- a/src/views/mp/components/wx-reply/main.vue +++ b/src/views/mp/components/wx-reply/main.vue @@ -8,24 +8,59 @@ ④ 支持发送【视频】消息时,支持新建视频 --> <template> - <el-tabs type="border-card" v-model="objData.type" @tab-click="onTabClick"> + <el-tabs type="border-card" v-model="currentTab"> <!-- 类型 1:文本 --> - <TabText v-model="objData.content" /> + <el-tab-pane :name="ReplyType.Text"> + <template #label> + <el-row align="middle"><Icon icon="ep:document" /> 文本</el-row> + </template> + <TabText v-model="reply.content" /> + </el-tab-pane> + <!-- 类型 2:图片 --> - <TabImage v-model="objData" /> + <el-tab-pane :name="ReplyType.Image"> + <template #label> + <el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row> + </template> + <TabImage v-model="reply" /> + </el-tab-pane> + <!-- 类型 3:语音 --> - <TabVoice v-model="objData" /> + <el-tab-pane :name="ReplyType.Voice"> + <template #label> + <el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row> + </template> + <TabVoice v-model="reply" /> + </el-tab-pane> + <!-- 类型 4:视频 --> - <TabVideo v-model="objData" /> + <el-tab-pane :name="ReplyType.Video"> + <template #label> + <el-row align="middle"><Icon icon="ep:share" /> 视频</el-row> + </template> + <TabVideo v-model="reply" /> + </el-tab-pane> + <!-- 类型 5:图文 --> - <TabNews v-model="objData" :news-type="newsType" /> + <el-tab-pane :name="ReplyType.News"> + <template #label> + <el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row> + </template> + <TabNews v-model="reply" :news-type="newsType" /> + </el-tab-pane> + <!-- 类型 6:音乐 --> - <TabMusic v-model="objData" /> + <el-tab-pane :name="ReplyType.Music"> + <template #label> + <el-row align="middle"><Icon icon="ep:service" />音乐</el-row> + </template> + <TabMusic v-model="reply" /> + </el-tab-pane> </el-tabs> </template> <script setup lang="ts" name="WxReplySelect"> -import { ObjData, NewsType } from './components/types' +import { Reply, NewsType, ReplyType, createEmptyReply } from './components/types' import TabText from './components/TabText.vue' import TabImage from './components/TabImage.vue' import TabVoice from './components/TabVoice.vue' @@ -34,30 +69,54 @@ import TabNews from './components/TabNews.vue' import TabMusic from './components/TabMusic.vue' interface Props { - objData: ObjData + modelValue: Reply newsType?: NewsType } const props = withDefaults(defineProps<Props>(), { newsType: () => NewsType.Published }) +const emit = defineEmits<{ + (e: 'update:modelValue', v: Reply) +}>() -const objData = reactive(props.objData) -// TODO @Dhb52:Tab 切换的时候,应该表单还保留着;清除只有两个时机:1)发送成功后;2)关闭窗口后;我捉摸,是不是每个 TabXXX 组件,是个独立的 Form,然后有自己的对象,不粘在 objData 一起。这样最终就是 MusicMessageForm、ImageMessageForm -// const tempObj = new Map().set(objData.type, Object.assign({}, objData)) +const reply = computed<Reply>({ + get: () => props.modelValue, + set: (val) => emit('update:modelValue', val) +}) +// 作为多个标签保存各自Reply的缓存 +const objCache = new Map<ReplyType, Reply>() +// 采用独立的ref来保存当前tab,避免在watch标签变化,对reply进行赋值会产生了循环调用 +const currentTab = ref<ReplyType>(props.modelValue.type || ReplyType.Text) -/** 切换消息类型的 tab */ -const onTabClick = () => { - clear() -} +watch( + currentTab, + (newTab, oldTab) => { + // 第一次进入:oldTab 为 undefined + // 判断 newTab 是因为 Reply 为 Partial + if (oldTab === undefined || newTab === undefined) { + return + } -/** 清除除了`type`的字段 */ + objCache.set(oldTab, unref(reply)) + + // 从缓存里面取出新tab内容,有则覆盖Reply,没有则创建空Reply + const temp = objCache.get(newTab) + if (temp) { + reply.value = temp + } else { + let newData = createEmptyReply(reply) + newData.type = newTab + reply.value = newData + } + }, + { + immediate: true + } +) + +/** 清除除了`type`, `accountId`的字段 */ const clear = () => { - objData.content = '' - objData.mediaId = '' - objData.url = '' - objData.title = '' - objData.description = '' - objData.articles = [] + reply.value = createEmptyReply(reply) } defineExpose({ diff --git a/src/views/mp/components/wx-video-play/index.ts b/src/views/mp/components/wx-video-play/index.ts new file mode 100644 index 00000000..91e00efa --- /dev/null +++ b/src/views/mp/components/wx-video-play/index.ts @@ -0,0 +1,3 @@ +import WxVideoPlayer from './main.vue' + +export default WxVideoPlayer diff --git a/src/views/mp/components/wx-voice-play/index.ts b/src/views/mp/components/wx-voice-play/index.ts new file mode 100644 index 00000000..9eb78e02 --- /dev/null +++ b/src/views/mp/components/wx-voice-play/index.ts @@ -0,0 +1,3 @@ +import WxVoicePlayer from './main.vue' + +export default WxVoicePlayer diff --git a/src/views/mp/components/wx-voice-play/main.vue b/src/views/mp/components/wx-voice-play/main.vue index 3ce970c1..169593d8 100644 --- a/src/views/mp/components/wx-voice-play/main.vue +++ b/src/views/mp/components/wx-voice-play/main.vue @@ -7,7 +7,7 @@ 1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容; 存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。 - ② 代码优化:将 props 中的 objData 调成为 data 中对应的属性,并补充相关注释 + ② 代码优化:将 props 中的 reply 调成为 data 中对应的属性,并补充相关注释 --> <template> <div class="wx-voice-div" @click="playVoice"> @@ -89,8 +89,8 @@ const amrStop = () => { padding: 5px; background-color: #eaeaea; border-radius: 10px; - width: 40px; - height: 40px; + width: 120px; + height: 50px; display: flex; justify-content: center; diff --git a/src/views/mp/draft/components/CoverSelect.vue b/src/views/mp/draft/components/CoverSelect.vue index ae2b6591..944b7d96 100644 --- a/src/views/mp/draft/components/CoverSelect.vue +++ b/src/views/mp/draft/components/CoverSelect.vue @@ -27,9 +27,7 @@ :on-success="onUploadSuccess" > <template #trigger> - <el-button size="small" type="primary" :loading="isUploading" disabled="isUploading"> - {{ isUploading ? '正在上传' : '本地上传' }} - </el-button> + <el-button size="small" type="primary">本地上传</el-button> </template> <el-button size="small" @@ -52,7 +50,8 @@ destroy-on-close > <WxMaterialSelect - :objData="{ type: 'image', accountId: accountId }" + type="image" + :account-id="accountId" @select-material="onMaterialSelected" /> </el-dialog> @@ -61,13 +60,13 @@ </template> <script setup lang="ts"> -import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' +import WxMaterialSelect from '@/views/mp/components/wx-material-select' import { getAccessToken } from '@/utils/auth' import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus' +import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' import { NewsItem } from './types' const message = useMessage() -// const UPLOAD_URL = 'http://localhost:8000/upload/' // 上传永久素材的地址 const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址 const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部 @@ -93,14 +92,13 @@ const showImageDialog = ref(false) const fileList = ref<UploadFiles>([]) interface UploadData { - type: 'image' | 'video' | 'audio' - accountId?: number + type: UploadType + accountId: number | undefined } const uploadData: UploadData = reactive({ - type: 'image', + type: UploadType.Image, accountId: accountId }) -const isUploading = ref(false) /** 素材选择完成事件*/ const onMaterialSelected = (item: any) => { @@ -109,22 +107,8 @@ const onMaterialSelected = (item: any) => { newsItem.value.thumbUrl = item.url } -const onBeforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => { - const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes( - rawFile.type - ) - if (!isType) { - message.error('上传图片格式不对!') - return false - } - - if (rawFile.size / 1024 / 1024 > 2) { - message.error('上传图片大小不能超过 2M!') - return false - } - // 校验通过 - return true -} +const onBeforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => + useBeforeUpload(UploadType.Image, 2)(rawFile) const onUploadSuccess: UploadProps['onSuccess'] = (res: any) => { if (res.code !== 0) { diff --git a/src/views/mp/draft/components/DraftTable.vue b/src/views/mp/draft/components/DraftTable.vue index 63cee31f..62a18528 100644 --- a/src/views/mp/draft/components/DraftTable.vue +++ b/src/views/mp/draft/components/DraftTable.vue @@ -36,7 +36,7 @@ </template> <script setup lang="ts"> -import WxNews from '@/views/mp/components/wx-news/main.vue' +import WxNews from '@/views/mp/components/wx-news' import { Article } from './types' diff --git a/src/views/mp/draft/index.vue b/src/views/mp/draft/index.vue index 7de992cd..d8e771a0 100644 --- a/src/views/mp/draft/index.vue +++ b/src/views/mp/draft/index.vue @@ -46,7 +46,6 @@ </ContentWrap> <!-- 添加或修改草稿对话框 --> - <!-- TODO @Dhb52:是不是整个做成一个组件 --> <el-dialog :title="isCreating ? '新建图文' : '修改图文'" width="80%" @@ -63,7 +62,7 @@ </template> <script setup lang="ts" name="MpDraft"> -import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' +import WxAccountSelect from '@/views/mp/components/wx-account-select' import * as MpDraftApi from '@/api/mp/draft' import * as MpFreePublishApi from '@/api/mp/freePublish' import { @@ -77,7 +76,7 @@ import { const message = useMessage() // 消息 -const accountId = ref(0) +const accountId = ref<number>(0) provide('accountId', accountId) const loading = ref(true) // 列表的加载中 @@ -91,7 +90,7 @@ interface QueryParams { const queryParams: QueryParams = reactive({ pageNo: 1, pageSize: 10, - accountId: accountId.value + accountId: 0 }) interface UploadData { @@ -100,7 +99,7 @@ interface UploadData { } const uploadData: UploadData = reactive({ type: 'image', - accountId: accountId.value + accountId: 0 }) // ========== 草稿新建 or 修改 ========== diff --git a/src/views/mp/freePublish/index.vue b/src/views/mp/freePublish/index.vue index 04edb705..2fda5100 100644 --- a/src/views/mp/freePublish/index.vue +++ b/src/views/mp/freePublish/index.vue @@ -50,8 +50,8 @@ <script lang="ts" setup name="MpFreePublish"> import * as FreePublishApi from '@/api/mp/freePublish' -import WxNews from '@/views/mp/components/wx-news/main.vue' -import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' +import WxNews from '@/views/mp/components/wx-news' +import WxAccountSelect from '@/views/mp/components/wx-account-select' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -62,16 +62,16 @@ const list = ref<any[]>([]) // 列表的数据 interface QueryParams { pageNo: number pageSize: number - accountId?: number + accountId: number } const queryParams: QueryParams = reactive({ pageNo: 1, pageSize: 10, - accountId: undefined + accountId: 0 }) /** 侦听公众号变化 **/ -const onAccountChanged = (id: number | undefined) => { +const onAccountChanged = (id: number) => { queryParams.accountId = id getList() } diff --git a/src/views/mp/hooks/useUpload.ts b/src/views/mp/hooks/useUpload.ts index b2d32d71..b0e70531 100644 --- a/src/views/mp/hooks/useUpload.ts +++ b/src/views/mp/hooks/useUpload.ts @@ -2,29 +2,29 @@ import type { UploadRawFile } from 'element-plus' const message = useMessage() // 消息 -enum MaterialType { +enum UploadType { Image = 'image', Voice = 'voice', Video = 'video' } -const useBeforeUpload = (type: MaterialType, maxSizeMB: number) => { +const useBeforeUpload = (type: UploadType, maxSizeMB: number) => { const fn = (rawFile: UploadRawFile): boolean => { let allowTypes: string[] = [] let name = '' switch (type) { - case MaterialType.Image: + case UploadType.Image: allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'] maxSizeMB = 2 name = '图片' break - case MaterialType.Voice: + case UploadType.Voice: allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr'] maxSizeMB = 2 name = '语音' break - case MaterialType.Video: + case UploadType.Video: allowTypes = ['video/mp4'] maxSizeMB = 10 name = '视频' @@ -47,4 +47,4 @@ const useBeforeUpload = (type: MaterialType, maxSizeMB: number) => { return fn } -export { MaterialType, useBeforeUpload } +export { UploadType, useBeforeUpload } diff --git a/src/views/mp/material/components/UploadFile.vue b/src/views/mp/material/components/UploadFile.vue index be7e323b..f58084bb 100644 --- a/src/views/mp/material/components/UploadFile.vue +++ b/src/views/mp/material/components/UploadFile.vue @@ -6,14 +6,11 @@ :limit="1" :file-list="fileList" :data="uploadData" - :on-progress="(isUploading = true)" :on-error="onUploadError" :before-upload="onBeforeUpload" :on-success="onUploadSuccess" > - <el-button type="primary" plain :loading="isUploading" :disabled="isUploading"> - {{ isUploading ? '正在上传' : '点击上传' }} - </el-button> + <el-button type="primary" plain> 点击上传 </el-button> <template #tip> <span class="el-upload__tip" style="margin-left: 5px"> <slot></slot> @@ -27,14 +24,14 @@ import { HEADERS, UPLOAD_URL, UploadData, - MaterialType, + UploadType, beforeImageUpload, beforeVoiceUpload } from './upload' const message = useMessage() -const props = defineProps<{ type: MaterialType }>() +const props = defineProps<{ type: UploadType }>() const fileList = ref<UploadUserFile[]>([]) const emit = defineEmits<{ @@ -42,14 +39,13 @@ const emit = defineEmits<{ }>() const uploadData: UploadData = reactive({ - type: MaterialType.Image, + type: UploadType.Image, title: '', introduction: '' }) -const isUploading = ref(false) /** 上传前检查 */ -const onBeforeUpload = props.type === MaterialType.Image ? beforeImageUpload : beforeVoiceUpload +const onBeforeUpload = props.type === UploadType.Image ? beforeImageUpload : beforeVoiceUpload /** 上传成功处理 */ const onUploadSuccess: UploadProps['onSuccess'] = (res: any) => { @@ -64,7 +60,6 @@ const onUploadSuccess: UploadProps['onSuccess'] = (res: any) => { uploadData.introduction = '' message.notifySuccess('上传成功') - isUploading.value = false emit('uploaded') } diff --git a/src/views/mp/material/components/UploadVideo.vue b/src/views/mp/material/components/UploadVideo.vue index 8e374767..9d2fd861 100644 --- a/src/views/mp/material/components/UploadVideo.vue +++ b/src/views/mp/material/components/UploadVideo.vue @@ -1,5 +1,5 @@ <template> - <el-dialog title="新建视频" v-model="showDialog" width="600px" destroy-on-close> + <el-dialog title="新建视频" v-model="showDialog" width="600px"> <el-upload :action="UPLOAD_URL" :headers="HEADERS" @@ -8,7 +8,6 @@ :file-list="fileList" :data="uploadData" :before-upload="beforeVideoUpload" - :on-progress="(isUploading = true)" :on-error="onUploadError" :on-success="onUploadSuccess" ref="uploadVideoRef" @@ -18,12 +17,14 @@ <template #trigger> <el-button type="primary" plain>选择视频</el-button> </template> - <span class="el-upload__tip" style="margin-left: 10px" - >格式支持 MP4,文件大小不超过 10MB</span - > + <template #tip> + <span class="el-upload__tip" style="margin-left: 10px" + >格式支持 MP4,文件大小不超过 10MB</span + > + </template> </el-upload> <el-divider /> - <el-form :model="uploadData" :rules="uploadRules" ref="uploadFormRef" v-loading="isUploading"> + <el-form :model="uploadData" :rules="uploadRules" ref="uploadFormRef"> <el-form-item label="标题" prop="title"> <el-input v-model="uploadData.title" @@ -41,9 +42,7 @@ </el-form> <template #footer> <el-button @click="showDialog = false">取 消</el-button> - <el-button type="primary" @click="submitVideo" :loading="isUploading" :disabled="isUploading" - >提 交</el-button - > + <el-button type="primary" @click="submitVideo">提 交</el-button> </template> </el-dialog> </template> @@ -56,7 +55,7 @@ import type { UploadProps, UploadUserFile } from 'element-plus' -import { HEADERS, UploadData, UPLOAD_URL, beforeVideoUpload, MaterialType } from './upload' +import { HEADERS, UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from './upload' const message = useMessage() @@ -85,18 +84,16 @@ const showDialog = computed<boolean>({ } }) -const isUploading = ref(false) - const fileList = ref<UploadUserFile[]>([]) const uploadData: UploadData = reactive({ - type: MaterialType.Video, + type: UploadType.Video, title: '', introduction: '' }) -const uploadFormRef = ref<FormInstance>() -const uploadVideoRef = ref<UploadInstance>() +const uploadFormRef = ref<FormInstance | null>(null) +const uploadVideoRef = ref<UploadInstance | null>(null) const submitVideo = () => { uploadFormRef.value?.validate((valid) => { @@ -109,7 +106,6 @@ const submitVideo = () => { /** 上传成功处理 */ const onUploadSuccess: UploadProps['onSuccess'] = (res: any) => { - isUploading.value = false if (res.code !== 0) { message.error('上传出错:' + res.msg) return false diff --git a/src/views/mp/material/components/VideoTable.vue b/src/views/mp/material/components/VideoTable.vue index 81472959..b1e14dd5 100644 --- a/src/views/mp/material/components/VideoTable.vue +++ b/src/views/mp/material/components/VideoTable.vue @@ -39,7 +39,7 @@ </template> <script setup lang="ts"> -import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' +import WxVideoPlayer from '@/views/mp/components/wx-video-play' import { dateFormatter } from '@/utils/formatTime' const props = defineProps<{ diff --git a/src/views/mp/material/components/VoiceTable.vue b/src/views/mp/material/components/VoiceTable.vue index 6f37e1a0..4ae5174b 100644 --- a/src/views/mp/material/components/VoiceTable.vue +++ b/src/views/mp/material/components/VoiceTable.vue @@ -37,7 +37,7 @@ </template> <script setup lang="ts"> -import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play' import { dateFormatter } from '@/utils/formatTime' const props = defineProps<{ diff --git a/src/views/mp/material/components/upload.ts b/src/views/mp/material/components/upload.ts index dc52a9eb..7158ab12 100644 --- a/src/views/mp/material/components/upload.ts +++ b/src/views/mp/material/components/upload.ts @@ -1,29 +1,29 @@ import type { UploadProps, UploadRawFile } from 'element-plus' import { getAccessToken } from '@/utils/auth' -import { MaterialType, useBeforeUpload } from '@/views/mp/hooks/useUpload' +import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload' const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 请求头 const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址 interface UploadData { - type: MaterialType + type: UploadType title: string introduction: string } const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => - useBeforeUpload(MaterialType.Image, 2)(rawFile) + useBeforeUpload(UploadType.Image, 2)(rawFile) const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => - useBeforeUpload(MaterialType.Voice, 2)(rawFile) + useBeforeUpload(UploadType.Voice, 2)(rawFile) const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => - useBeforeUpload(MaterialType.Video, 10)(rawFile) + useBeforeUpload(UploadType.Video, 10)(rawFile) export { HEADERS, UPLOAD_URL, - MaterialType, + UploadType, UploadData, beforeImageUpload, beforeVoiceUpload, diff --git a/src/views/mp/material/index.vue b/src/views/mp/material/index.vue index 4d8d3707..0e2a87d6 100644 --- a/src/views/mp/material/index.vue +++ b/src/views/mp/material/index.vue @@ -12,13 +12,13 @@ <ContentWrap> <el-tabs v-model="type" @tab-change="onTabChange"> <!-- tab 1:图片 --> - <el-tab-pane :name="MaterialType.Image"> + <el-tab-pane :name="UploadType.Image"> <template #label> - <span> <Icon icon="ep:picture" />图片 </span> + <el-row align="middle"> <Icon icon="ep:picture" />图片 </el-row> </template> <UploadFile v-hasPermi="['mp:material:upload-permanent']" - :type="MaterialType.Image" + :type="UploadType.Image" @uploaded="getList" > 支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M @@ -35,13 +35,13 @@ </el-tab-pane> <!-- tab 2:语音 --> - <el-tab-pane :name="MaterialType.Voice"> + <el-tab-pane :name="UploadType.Voice"> <template #label> - <span> <Icon icon="ep:microphone" />语音 </span> + <el-row align="middle"> <Icon icon="ep:microphone" />语音 </el-row> </template> <UploadFile v-hasPermi="['mp:material:upload-permanent']" - :type="MaterialType.Voice" + :type="UploadType.Voice" @uploaded="getList" > 格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s @@ -58,9 +58,9 @@ </el-tab-pane> <!-- tab 3:视频 --> - <el-tab-pane :name="MaterialType.Video"> + <el-tab-pane :name="UploadType.Video"> <template #label> - <span> <Icon icon="ep:video-play" /> 视频 </span> + <el-row align="middle"> <Icon icon="ep:video-play" /> 视频 </el-row> </template> <el-button v-hasPermi="['mp:material:upload-permanent']" @@ -85,17 +85,17 @@ </ContentWrap> </template> <script lang="ts" setup name="MpMaterial"> -import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' +import WxAccountSelect from '@/views/mp/components/wx-account-select' import ImageTable from './components/ImageTable.vue' import VoiceTable from './components/VoiceTable.vue' import VideoTable from './components/VideoTable.vue' import UploadFile from './components/UploadFile.vue' import UploadVideo from './components/UploadVideo.vue' -import { MaterialType } from './components/upload' +import { UploadType } from './components/upload' import * as MpMaterialApi from '@/api/mp/material' const message = useMessage() // 消息 -const type = ref<MaterialType>(MaterialType.Image) // 素材类型 +const type = ref<UploadType>(UploadType.Image) // 素材类型 const loading = ref(false) // 遮罩层 const list = ref<any[]>([]) // 总条数 const total = ref(0) // 数据列表 @@ -103,19 +103,19 @@ const total = ref(0) // 数据列表 interface QueryParams { pageNo: number pageSize: number - accountId?: number + accountId: number permanent: boolean } const queryParams: QueryParams = reactive({ pageNo: 1, pageSize: 10, - accountId: undefined, + accountId: 0, permanent: true }) const showCreateVideo = ref(false) // 是否新建视频的弹窗 /** 侦听公众号变化 **/ -const onAccountChanged = (id?: number) => { +const onAccountChanged = (id: number) => { queryParams.accountId = id getList() } diff --git a/src/views/mp/menu/components/MenuEditor.vue b/src/views/mp/menu/components/MenuEditor.vue index 0bd27f19..684d66f6 100644 --- a/src/views/mp/menu/components/MenuEditor.vue +++ b/src/views/mp/menu/components/MenuEditor.vue @@ -94,7 +94,8 @@ </div> <el-dialog title="选择图文" v-model="showNewsDialog" width="80%" destroy-on-close> <WxMaterialSelect - :objData="{ type: 'news', accountId: props.accountId }" + type="news" + :account-id="props.accountId" @select-material="selectMaterial" /> </el-dialog> @@ -104,7 +105,7 @@ class="configur_content" v-if="menu.type === 'click' || menu.type === 'scancode_waitmsg'" > - <WxReplySelect v-if="hackResetWxReplySelect" :objData="menu.reply" /> + <WxReplySelect v-if="hackResetWxReplySelect" v-model="menu.reply" /> </div> </div> </div> @@ -112,15 +113,15 @@ </template> <script setup lang="ts"> -import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' -import WxNews from '@/views/mp/components/wx-news/main.vue' -import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' +import WxReplySelect from '@/views/mp/components/wx-reply' +import WxNews from '@/views/mp/components/wx-news' +import WxMaterialSelect from '@/views/mp/components/wx-material-select' import menuOptions from './menuOptions' const message = useMessage() const props = defineProps<{ - accountId?: number + accountId: number modelValue: any isParent: boolean }>() @@ -130,7 +131,6 @@ const emit = defineEmits<{ (e: 'update:modelValue', v: any) }>() -// TODO @Dhb52 输入的 table 切换时,表单应该保留 const menu = computed({ get() { return props.modelValue diff --git a/src/views/mp/menu/index.vue b/src/views/mp/menu/index.vue index c053a47c..c8ed0835 100644 --- a/src/views/mp/menu/index.vue +++ b/src/views/mp/menu/index.vue @@ -53,7 +53,7 @@ </template> <script lang="ts" setup name="MpMenu"> -import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' +import WxAccountSelect from '@/views/mp/components/wx-account-select' import MenuEditor from './components/MenuEditor.vue' import MenuPreviewer from './components/MenuPreviewer.vue' import * as MpMenuApi from '@/api/mp/menu' @@ -65,8 +65,8 @@ const MENU_NOT_SELECTED = '__MENU_NOT_SELECTED__' // ======================== 列表查询 ======================== const loading = ref(false) // 遮罩层 -const accountId = ref<number | undefined>() -const accountName = ref<string | undefined>('') +const accountId = ref<number>(0) +const accountName = ref<string>('') const menuList = ref<Menu[]>([]) // ======================== 菜单操作 ======================== @@ -103,7 +103,7 @@ const tempSelfObj = ref<{ const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗 /** 侦听公众号变化 **/ -const onAccountChanged = (id?: number, name?: string) => { +const onAccountChanged = (id: number, name: string) => { accountId.value = id accountName.value = name getList() diff --git a/src/views/mp/message/MessageTable.vue b/src/views/mp/message/MessageTable.vue index 23eb9aae..fc5e55ff 100644 --- a/src/views/mp/message/MessageTable.vue +++ b/src/views/mp/message/MessageTable.vue @@ -122,11 +122,11 @@ </template> <script setup lang="ts"> -import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' -import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' -import WxLocation from '@/views/mp/components/wx-location/main.vue' -import WxMusic from '@/views/mp/components/wx-music/main.vue' -import WxNews from '@/views/mp/components/wx-news/main.vue' +import WxVideoPlayer from '@/views/mp/components/wx-video-play' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play' +import WxLocation from '@/views/mp/components/wx-location' +import WxMusic from '@/views/mp/components/wx-music' +import WxNews from '@/views/mp/components/wx-news' import { dateFormatter } from '@/utils/formatTime' import { MsgType } from '@/views/mp/components/wx-msg/types' diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index c115813c..85048f38 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -81,8 +81,8 @@ </template> <script setup lang="ts" name="MpMessage"> import * as MpMessageApi from '@/api/mp/message' -import WxMsg from '@/views/mp/components/wx-msg/main.vue' -import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' +import WxMsg from '@/views/mp/components/wx-msg' +import WxAccountSelect from '@/views/mp/components/wx-account-select' import MessageTable from './MessageTable.vue' import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' import { MsgType } from '@/views/mp/components/wx-msg/types' @@ -96,17 +96,17 @@ const list = ref<any[]>([]) // 当前页的列表数据 interface QueryParams { pageNo: number pageSize: number - openid: string | null - accountId: number | null - type: MsgType | null + openid: string | undefined + accountId: number + type: MsgType | undefined createTime: string[] | [] } const queryParams: QueryParams = reactive({ pageNo: 1, pageSize: 10, - openid: null, - accountId: null, - type: null, + openid: undefined, + accountId: 0, + type: undefined, createTime: [] }) const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单 @@ -118,8 +118,8 @@ const messageBox = reactive({ }) /** 侦听accountId */ -const onAccountChanged = (id?: number) => { - queryParams.accountId = id as number +const onAccountChanged = (id: number) => { + queryParams.accountId = id handleQuery() } diff --git a/src/views/mp/tag/index.vue b/src/views/mp/tag/index.vue index 6689529d..a92d9127 100644 --- a/src/views/mp/tag/index.vue +++ b/src/views/mp/tag/index.vue @@ -14,10 +14,22 @@ <WxAccountSelect @change="onAccountChanged" /> </el-form-item> <el-form-item> - <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']"> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['mp:tag:create']" + :disabled="queryParams.accountId === 0" + > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> - <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']"> + <el-button + type="success" + plain + @click="handleSync" + v-hasPermi="['mp:tag:sync']" + :disabled="queryParams.accountId === 0" + > <Icon icon="ep:refresh" class="mr-5px" /> 同步 </el-button> </el-form-item> @@ -74,28 +86,30 @@ import { dateFormatter } from '@/utils/formatTime' import * as MpTagApi from '@/api/mp/tag' import TagForm from './TagForm.vue' -import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' +import WxAccountSelect from '@/views/mp/components/wx-account-select' + const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 -const list = ref<any>([]) // 列表的数据 +const list = ref<any[]>([]) // 列表的数据 interface QueryParams { pageNo: number pageSize: number - accountId?: number + accountId: number } const queryParams: QueryParams = reactive({ pageNo: 1, pageSize: 10, - accountId: undefined + accountId: 0 }) + const formRef = ref<InstanceType<typeof TagForm> | null>(null) /** 侦听公众号变化 **/ -const onAccountChanged = (id?: number) => { +const onAccountChanged = (id: number) => { queryParams.pageNo = 1 queryParams.accountId = id getList() @@ -114,8 +128,8 @@ const getList = async () => { } /** 添加/修改操作 */ -const openForm = (type: string, id?: number) => { - formRef.value?.open(type, queryParams.accountId as number, id) +const openForm = (type: 'create' | 'update', id?: number) => { + formRef.value?.open(type, queryParams.accountId, id) } /** 删除按钮操作 */ diff --git a/src/views/mp/user/index.vue b/src/views/mp/user/index.vue index 8a54d38e..03e58a7f 100644 --- a/src/views/mp/user/index.vue +++ b/src/views/mp/user/index.vue @@ -34,7 +34,13 @@ <el-form-item> <el-button @click="handleQuery"> <Icon icon="ep:search" />搜索 </el-button> <el-button @click="resetQuery"> <Icon icon="ep:refresh" />重置 </el-button> - <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user:sync']"> + <el-button + type="success" + plain + @click="handleSync" + v-hasPermi="['mp:user:sync']" + :disabled="queryParams.accountId === 0" + > <Icon icon="ep:refresh" class="mr-5px" /> 同步 </el-button> </el-form-item> @@ -97,7 +103,7 @@ import { dateFormatter } from '@/utils/formatTime' import * as MpUserApi from '@/api/mp/user' import * as MpTagApi from '@/api/mp/tag' -import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue' +import WxAccountSelect from '@/views/mp/components/wx-account-select' import type { FormInstance } from 'element-plus' import UserForm from './UserForm.vue' @@ -110,14 +116,14 @@ const list = ref<any[]>([]) // 列表的数据 interface QueryParams { pageNo: number pageSize: number - accountId?: number + accountId: number openid: string | null nickname: string | null } const queryParams: QueryParams = reactive({ pageNo: 1, pageSize: 10, - accountId: undefined, + accountId: 0, openid: null, nickname: null }) @@ -125,7 +131,7 @@ const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单 const tagList = ref<any[]>([]) // 公众号标签列表 /** 侦听公众号变化 **/ -const onAccountChanged = (id?: number) => { +const onAccountChanged = (id: number) => { queryParams.pageNo = 1 queryParams.accountId = id getList() From 4925a66cc5becb1877ee46a9a64e4913513feeed Mon Sep 17 00:00:00 2001 From: dhb52 <dhb52@126.com> Date: Fri, 21 Apr 2023 23:19:04 +0800 Subject: [PATCH 3/8] =?UTF-8?q?style:=20mp=E6=A8=A1=E5=9D=97stylelint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mp/account/index.vue | 2 +- .../mp/components/wx-material-select/main.vue | 45 +++---- src/views/mp/components/wx-msg/card.scss | 79 +++++++----- src/views/mp/components/wx-msg/comment.scss | 58 ++++++--- src/views/mp/components/wx-music/main.vue | 2 +- .../wx-reply/components/TabImage.vue | 20 +-- .../wx-reply/components/TabNews.vue | 2 +- .../wx-reply/components/TabVoice.vue | 20 +-- src/views/mp/components/wx-reply/main.vue | 32 ++--- .../mp/components/wx-voice-play/main.vue | 10 +- src/views/mp/freePublish/index.vue | 121 +++++++++--------- .../mp/material/components/ImageTable.vue | 48 +++---- src/views/mp/menu/index.vue | 1 - 13 files changed, 236 insertions(+), 204 deletions(-) diff --git a/src/views/mp/account/index.vue b/src/views/mp/account/index.vue index d890f44f..f993b8e3 100644 --- a/src/views/mp/account/index.vue +++ b/src/views/mp/account/index.vue @@ -46,7 +46,7 @@ v-if="scope.row.qrCodeUrl" :src="scope.row.qrCodeUrl" alt="二维码" - style="height: 100px; display: inline-block" + style="display: inline-block; height: 100px" /> <el-button link diff --git a/src/views/mp/components/wx-material-select/main.vue b/src/views/mp/components/wx-material-select/main.vue index e711c022..8f2792dd 100644 --- a/src/views/mp/components/wx-material-select/main.vue +++ b/src/views/mp/components/wx-material-select/main.vue @@ -227,29 +227,6 @@ onMounted(async () => { }) </script> <style lang="scss" scoped> -/*瀑布流样式*/ -.waterfall { - width: 100%; - column-gap: 10px; - column-count: 5; - margin: 0 auto; -} - -.waterfall-item { - padding: 10px; - margin-bottom: 10px; - break-inside: avoid; - border: 1px solid #eaeaea; -} - -.material-img { - width: 100%; -} - -p { - line-height: 30px; -} - @media (min-width: 992px) and (max-width: 1300px) { .waterfall { column-count: 3; @@ -276,5 +253,25 @@ p { } } -/*瀑布流样式*/ +.waterfall { + width: 100%; + column-gap: 10px; + column-count: 5; + margin: 0 auto; +} + +.waterfall-item { + padding: 10px; + margin-bottom: 10px; + break-inside: avoid; + border: 1px solid #eaeaea; +} + +.material-img { + width: 100%; +} + +p { + line-height: 30px; +} </style> diff --git a/src/views/mp/components/wx-msg/card.scss b/src/views/mp/components/wx-msg/card.scss index 67ac9219..7fbbe802 100644 --- a/src/views/mp/components/wx-msg/card.scss +++ b/src/views/mp/components/wx-msg/card.scss @@ -1,25 +1,27 @@ -.avue-card{ - &__item{ +.avue-card { + &__item { margin-bottom: 16px; border: 1px solid #e8e8e8; background-color: #fff; box-sizing: border-box; - color: rgba(0,0,0,.65); + color: rgba(0, 0, 0, 0.65); font-size: 14px; font-variant: tabular-nums; line-height: 1.5; list-style: none; - font-feature-settings: "tnum"; + font-feature-settings: 'tnum'; cursor: pointer; - height:200px; - &:hover{ - border-color: rgba(0,0,0,.09); - box-shadow: 0 2px 8px rgba(0,0,0,.09); + height: 200px; + + &:hover { + border-color: rgba(0, 0, 0, 0.09); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09); } - &--add{ - border:1px dashed #000; + + &--add { + border: 1px dashed #000; width: 100%; - color: rgba(0,0,0,.45); + color: rgba(0, 0, 0, 0.45); background-color: #fff; border-color: #d9d9d9; border-radius: 2px; @@ -27,74 +29,87 @@ align-items: center; justify-content: center; font-size: 16px; - i{ + + i { margin-right: 10px; } - &:hover{ + + &:hover { color: #40a9ff; background-color: #fff; border-color: #40a9ff; } } } - &__body{ + + &__body { display: flex; padding: 24px; } - &__detail{ - flex:1 + + &__detail { + flex: 1; } - &__avatar{ + + &__avatar { width: 48px; height: 48px; border-radius: 48px; overflow: hidden; margin-right: 12px; - img{ + + img { width: 100%; height: 100%; } } - &__title{ - color: rgba(0,0,0,.85); + + &__title { + color: rgba(0, 0, 0, 0.85); margin-bottom: 12px; font-size: 16px; - &:hover{ - color:#1890ff; + + &:hover { + color: #1890ff; } } - &__info{ - color: rgba(0,0,0,.45); + + &__info { + color: rgba(0, 0, 0, 0.45); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; height: 64px; } - &__menu{ + + &__menu { display: flex; - justify-content:space-around; + justify-content: space-around; height: 50px; background: #f7f9fa; - color: rgba(0,0,0,.45); + color: rgba(0, 0, 0, 0.45); text-align: center; line-height: 50px; - &:hover{ - color:#1890ff; + + &:hover { + color: #1890ff; } } } /** joolun 额外加的 */ .avue-comment__main { - flex: unset!important; - border-radius: 5px!important; - margin: 0 8px!important; + flex: unset !important; + border-radius: 5px !important; + margin: 0 8px !important; } + .avue-comment__header { border-top-left-radius: 5px; border-top-right-radius: 5px; } + .avue-comment__body { border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; diff --git a/src/views/mp/components/wx-msg/comment.scss b/src/views/mp/components/wx-msg/comment.scss index 3f1341b2..aaeaccaf 100644 --- a/src/views/mp/components/wx-msg/comment.scss +++ b/src/views/mp/components/wx-msg/comment.scss @@ -1,27 +1,33 @@ /* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */ -.avue-comment{ +.avue-comment { margin-bottom: 30px; display: flex; align-items: flex-start; - &--reverse{ - flex-direction:row-reverse; - .avue-comment__main{ - &:before,&:after{ + + &--reverse { + flex-direction: row-reverse; + + .avue-comment__main { + &:before, + &:after { left: auto; right: -8px; border-width: 8px 0 8px 8px; } - &:before{ + + &:before { border-left-color: #dedede; } - &:after{ + + &:after { border-left-color: #f8f8f8; margin-right: 1px; margin-left: auto; } } } - &__avatar{ + + &__avatar { width: 48px; height: 48px; border-radius: 50%; @@ -29,7 +35,8 @@ box-sizing: border-box; vertical-align: middle; } - &__header{ + + &__header { padding: 5px 15px; background: #f8f8f8; border-bottom: 1px solid #eee; @@ -37,18 +44,22 @@ align-items: center; justify-content: space-between; } - &__author{ + + &__author { font-weight: 700; font-size: 14px; color: #999; } - &__main{ - flex:1; + + &__main { + flex: 1; margin: 0 20px; position: relative; border: 1px solid #dedede; border-radius: 2px; - &:before,&:after{ + + &:before, + &:after { position: absolute; top: 10px; left: -8px; @@ -56,32 +67,39 @@ width: 0; height: 0; display: block; - content: " "; + content: ' '; border-color: transparent; border-style: solid solid outset; border-width: 8px 8px 8px 0; pointer-events: none; } + &:before { border-right-color: #dedede; z-index: 1; } - &:after{ + + &:after { border-right-color: #f8f8f8; margin-left: 1px; z-index: 2; } } - &__body{ + + &__body { padding: 15px; overflow: hidden; background: #fff; - font-family: Segoe UI,Lucida Grande,Helvetica,Arial,Microsoft YaHei,FreeSans,Arimo,Droid Sans,wenquanyi micro hei,Hiragino Sans GB,Hiragino Sans GB W3,FontAwesome,sans-serif;color: #333; + font-family: Segoe UI, Lucida Grande, Helvetica, Arial, Microsoft YaHei, FreeSans, Arimo, + Droid Sans, wenquanyi micro hei, Hiragino Sans GB, Hiragino Sans GB W3, FontAwesome, + sans-serif; + color: #333; font-size: 14px; } - blockquote{ - margin:0; - font-family: Georgia,Times New Roman,Times,Kai,Kaiti SC,KaiTi,BiauKai,FontAwesome,serif; + + blockquote { + margin: 0; + font-family: Georgia, Times New Roman, Times, Kai, Kaiti SC, KaiTi, BiauKai, FontAwesome, serif; padding: 1px 0 1px 15px; border-left: 4px solid #ddd; } diff --git a/src/views/mp/components/wx-music/main.vue b/src/views/mp/components/wx-music/main.vue index 52555f15..70f4c58b 100644 --- a/src/views/mp/components/wx-music/main.vue +++ b/src/views/mp/components/wx-music/main.vue @@ -56,5 +56,5 @@ defineExpose({ <style lang="scss" scoped> /* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scc */ -@import '../wx-msg/card.scss'; +@import url('../wx-msg/card.scss'); </style> diff --git a/src/views/mp/components/wx-reply/components/TabImage.vue b/src/views/mp/components/wx-reply/components/TabImage.vue index 1a82c3aa..71950e04 100644 --- a/src/views/mp/components/wx-reply/components/TabImage.vue +++ b/src/views/mp/components/wx-reply/components/TabImage.vue @@ -123,7 +123,7 @@ const selectMaterial = (item) => { .select-item { width: 280px; padding: 10px; - margin: 0 auto 10px auto; + margin: 0 auto 10px; border: 1px solid #eaeaea; .material-img { @@ -131,11 +131,11 @@ const selectMaterial = (item) => { } .item-name { - font-size: 12px; overflow: hidden; + font-size: 12px; + text-align: center; text-overflow: ellipsis; white-space: nowrap; - text-align: center; .item-infos { width: 30%; @@ -149,18 +149,18 @@ const selectMaterial = (item) => { } .col-select { - border: 1px solid rgb(234, 234, 234); - padding: 50px 0px; - height: 160px; width: 49.5%; + height: 160px; + padding: 50px 0; + border: 1px solid rgb(234 234 234); } .col-add { - border: 1px solid rgb(234, 234, 234); - padding: 50px 0px; - height: 160px; - width: 49.5%; float: right; + width: 49.5%; + height: 160px; + padding: 50px 0; + border: 1px solid rgb(234 234 234); .el-upload__tip { line-height: 18px; diff --git a/src/views/mp/components/wx-reply/components/TabNews.vue b/src/views/mp/components/wx-reply/components/TabNews.vue index 88a82a53..ecb5026d 100644 --- a/src/views/mp/components/wx-reply/components/TabNews.vue +++ b/src/views/mp/components/wx-reply/components/TabNews.vue @@ -65,7 +65,7 @@ const onDelete = () => { .select-item { width: 280px; padding: 10px; - margin: 0 auto 10px auto; + margin: 0 auto 10px; border: 1px solid #eaeaea; .ope-row { diff --git a/src/views/mp/components/wx-reply/components/TabVoice.vue b/src/views/mp/components/wx-reply/components/TabVoice.vue index c4868cf8..5c9f64a7 100644 --- a/src/views/mp/components/wx-reply/components/TabVoice.vue +++ b/src/views/mp/components/wx-reply/components/TabVoice.vue @@ -120,15 +120,15 @@ const selectMaterial = (item: Reply) => { <style lang="scss" scoped> .select-item2 { padding: 10px; - margin: 0 auto 10px auto; + margin: 0 auto 10px; border: 1px solid #eaeaea; .item-name { - font-size: 12px; overflow: hidden; + font-size: 12px; + text-align: center; text-overflow: ellipsis; white-space: nowrap; - text-align: center; .ope-row { width: 100%; @@ -138,18 +138,18 @@ const selectMaterial = (item: Reply) => { } .col-select { - border: 1px solid rgb(234, 234, 234); - padding: 50px 0px; - height: 160px; width: 49.5%; + height: 160px; + padding: 50px 0; + border: 1px solid rgb(234 234 234); } .col-add { - border: 1px solid rgb(234, 234, 234); - padding: 50px 0px; - height: 160px; - width: 49.5%; float: right; + width: 49.5%; + height: 160px; + padding: 50px 0; + border: 1px solid rgb(234 234 234); .el-upload__tip { line-height: 18px; diff --git a/src/views/mp/components/wx-reply/main.vue b/src/views/mp/components/wx-reply/main.vue index 32a31222..1ef9dde0 100644 --- a/src/views/mp/components/wx-reply/main.vue +++ b/src/views/mp/components/wx-reply/main.vue @@ -128,13 +128,13 @@ defineExpose({ .select-item { width: 280px; padding: 10px; - margin: 0 auto 10px auto; + margin: 0 auto 10px; border: 1px solid #eaeaea; } .select-item2 { padding: 10px; - margin: 0 auto 10px auto; + margin: 0 auto 10px; border: 1px solid #eaeaea; } @@ -148,11 +148,11 @@ defineExpose({ } .item-name { - font-size: 12px; overflow: hidden; + font-size: 12px; + text-align: center; text-overflow: ellipsis; white-space: nowrap; - text-align: center; } .el-form-item__content { @@ -160,34 +160,34 @@ defineExpose({ } .col-select { - border: 1px solid rgb(234, 234, 234); - padding: 50px 0px; - height: 160px; width: 49.5%; + height: 160px; + padding: 50px 0; + border: 1px solid rgb(234 234 234); } .col-select2 { - border: 1px solid rgb(234, 234, 234); - padding: 50px 0px; height: 160px; + padding: 50px 0; + border: 1px solid rgb(234 234 234); } .col-add { - border: 1px solid rgb(234, 234, 234); - padding: 50px 0px; - height: 160px; - width: 49.5%; float: right; + width: 49.5%; + height: 160px; + padding: 50px 0; + border: 1px solid rgb(234 234 234); } .avatar-uploader-icon { - border: 1px solid #d9d9d9; - font-size: 28px; - color: #8c939d; width: 100px !important; height: 100px !important; + font-size: 28px; line-height: 100px !important; + color: #8c939d; text-align: center; + border: 1px solid #d9d9d9; } .material-img { diff --git a/src/views/mp/components/wx-voice-play/main.vue b/src/views/mp/components/wx-voice-play/main.vue index 169593d8..3cfec418 100644 --- a/src/views/mp/components/wx-voice-play/main.vue +++ b/src/views/mp/components/wx-voice-play/main.vue @@ -86,18 +86,18 @@ const amrStop = () => { </script> <style lang="scss" scoped> .wx-voice-div { + display: flex; + width: 120px; + height: 50px; padding: 5px; background-color: #eaeaea; border-radius: 10px; - width: 120px; - height: 50px; - - display: flex; justify-content: center; align-items: center; } + .amr-duration { - font-size: 11px; margin-left: 5px; + font-size: 11px; } </style> diff --git a/src/views/mp/freePublish/index.vue b/src/views/mp/freePublish/index.vue index 2fda5100..08a202c2 100644 --- a/src/views/mp/freePublish/index.vue +++ b/src/views/mp/freePublish/index.vue @@ -102,19 +102,45 @@ const handleDelete = async (item: any) => { } </script> <style lang="scss" scoped> +@media (min-width: 992px) and (max-width: 1300px) { + .waterfall { + column-count: 3; + } + + p { + color: red; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .waterfall { + column-count: 2; + } + + p { + color: orange; + } +} + +@media (max-width: 767px) { + .waterfall { + column-count: 1; + } +} + .ope-row { + padding-top: 5px; margin-top: 5px; text-align: center; border-top: 1px solid #eaeaea; - padding-top: 5px; } .item-name { - font-size: 12px; overflow: hidden; + font-size: 12px; + text-align: center; text-overflow: ellipsis; white-space: nowrap; - text-align: center; } .el-upload__tip { @@ -125,8 +151,8 @@ const handleDelete = async (item: any) => { .left { display: inline-block; width: 35%; - vertical-align: top; margin-top: 200px; + vertical-align: top; } .right { @@ -136,16 +162,16 @@ const handleDelete = async (item: any) => { } .avatar-uploader { - width: 20%; display: inline-block; + width: 20%; } .avatar-uploader .el-upload { - border-radius: 6px; - cursor: pointer; position: relative; overflow: hidden; text-align: unset !important; + cursor: pointer; + border-radius: 6px; } .avatar-uploader .el-upload:hover { @@ -153,13 +179,13 @@ const handleDelete = async (item: any) => { } .avatar-uploader-icon { - border: 1px solid #d9d9d9; - font-size: 28px; - color: #8c939d; width: 120px; height: 120px; + font-size: 28px; line-height: 120px; + color: #8c939d; text-align: center; + border: 1px solid #d9d9d9; } .avatar { @@ -173,13 +199,14 @@ const handleDelete = async (item: any) => { } .digest { - width: 60%; display: inline-block; + width: 60%; vertical-align: top; } -/*新增图文*/ -/*瀑布流样式*/ +/* 新增图文 */ + +/* 瀑布流样式 */ .waterfall { width: 100%; column-gap: 10px; @@ -198,68 +225,44 @@ p { line-height: 30px; } -@media (min-width: 992px) and (max-width: 1300px) { - .waterfall { - column-count: 3; - } - p { - color: red; - } -} - -@media (min-width: 768px) and (max-width: 991px) { - .waterfall { - column-count: 2; - } - p { - color: orange; - } -} - -@media (max-width: 767px) { - .waterfall { - column-count: 1; - } -} - -/*瀑布流样式*/ +/* 瀑布流样式 */ .news-main { - background-color: #ffffff; width: 100%; - margin: auto; height: 120px; + margin: auto; + background-color: #fff; } .news-content { - background-color: #acadae; + position: relative; width: 100%; height: 120px; - position: relative; + background-color: #acadae; } .news-content-title { - display: inline-block; - font-size: 15px; - color: #ffffff; position: absolute; - left: 0px; - bottom: 0px; - background-color: black; + bottom: 0; + left: 0; + display: inline-block; width: 98%; + height: 25px; padding: 1%; - opacity: 0.65; overflow: hidden; + font-size: 15px; + color: #fff; text-overflow: ellipsis; white-space: nowrap; - height: 25px; + background-color: black; + opacity: 0.65; } .news-main-item { - background-color: #ffffff; - padding: 5px 0px; - border-top: 1px solid #eaeaea; width: 100%; + padding: 5px 0; margin: auto; + background-color: #fff; + border-top: 1px solid #eaeaea; } .news-content-item { @@ -269,8 +272,8 @@ p { .news-content-item-title { display: inline-block; - font-size: 12px; width: 70%; + font-size: 12px; } .news-content-item-img { @@ -289,9 +292,9 @@ p { .news-main-plus { width: 280px; - text-align: center; - margin: auto; height: 50px; + margin: auto; + text-align: center; } .icon-plus { @@ -302,15 +305,15 @@ p { .select-item { width: 60%; padding: 10px; - margin: 0 auto 10px auto; + margin: 0 auto 10px; border: 1px solid #eaeaea; } .father .child { - display: none; - text-align: center; position: relative; bottom: 25px; + display: none; + text-align: center; } .father:hover .child { diff --git a/src/views/mp/material/components/ImageTable.vue b/src/views/mp/material/components/ImageTable.vue index 118fc7bd..cecbb6c2 100644 --- a/src/views/mp/material/components/ImageTable.vue +++ b/src/views/mp/material/components/ImageTable.vue @@ -31,30 +31,6 @@ const emit = defineEmits<{ </script> <style lang="scss" scoped> -/*瀑布流样式*/ -.waterfall { - width: 100%; - column-gap: 10px; - column-count: 5; - margin-top: 10px; - /* 芋道源码:增加 10px,避免顶着上面 */ -} - -.waterfall-item { - padding: 10px; - margin-bottom: 10px; - break-inside: avoid; - border: 1px solid #eaeaea; -} - -.material-img { - width: 100%; -} - -p { - line-height: 30px; -} - @media (min-width: 992px) and (max-width: 1300px) { .waterfall { column-count: 3; @@ -80,4 +56,28 @@ p { column-count: 1; } } + +.waterfall { + width: 100%; + column-gap: 10px; + column-count: 5; + margin-top: 10px; + + /* 芋道源码:增加 10px,避免顶着上面 */ +} + +.waterfall-item { + padding: 10px; + margin-bottom: 10px; + break-inside: avoid; + border: 1px solid #eaeaea; +} + +.material-img { + width: 100%; +} + +p { + line-height: 30px; +} </style> diff --git a/src/views/mp/menu/index.vue b/src/views/mp/menu/index.vue index c8ed0835..442e33b5 100644 --- a/src/views/mp/menu/index.vue +++ b/src/views/mp/menu/index.vue @@ -367,7 +367,6 @@ div { margin-left: 20px; background-color: #e8e7e7; box-sizing: border-box; - box-sizing: border-box; } } </style> From d99f9e98fbfccba07f0582d2fa6eb4266c7da791 Mon Sep 17 00:00:00 2001 From: dhb52 <dhb52@126.com> Date: Fri, 21 Apr 2023 23:35:31 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20mp/menu=20=E6=8B=96=E5=8A=A8?= =?UTF-8?q?=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mp/menu/components/MenuPreviewer.vue | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/views/mp/menu/components/MenuPreviewer.vue b/src/views/mp/menu/components/MenuPreviewer.vue index aca1c2ae..637c8805 100644 --- a/src/views/mp/menu/components/MenuPreviewer.vue +++ b/src/views/mp/menu/components/MenuPreviewer.vue @@ -4,6 +4,9 @@ <div @click="menuClicked(parent, x)" class="menu_item" + draggable="true" + @dragstart="onDragStart(DragType.Parent, x)" + @dragenter.prevent="onDragEnter(DragType.Parent, x)" :class="{ active: props.activeIndex === `${x}` }" > <Icon icon="ep:fold" color="black" />{{ parent.name }} @@ -13,6 +16,9 @@ <div class="subtitle menu_bottom" v-for="(child, y) in parent.children" :key="y"> <div class="menu_subItem" + draggable="true" + @dragstart="onDragStart(DragType.Child, y)" + @dragenter.prevent="onDragEnter(DragType.Child, x, y)" v-if="parent.children" :class="{ active: props.activeIndex === `${x}-${y}` }" @click="subMenuClicked(child, x, y)" @@ -43,7 +49,7 @@ const props = defineProps<{ modelValue: Menu[] activeIndex: string parentIndex: number - accountId?: number + accountId: number }>() const emit = defineEmits<{ @@ -94,6 +100,44 @@ const menuClicked = (parent: Menu, x: number) => { const subMenuClicked = (child: Menu, x: number, y: number) => { emit('submenu-clicked', child, x, y) } + +// ======================== 菜单排序 ======================== +const dragIndex = ref<number>(0) +enum DragType { + Parent = 'parent', + Child = 'child' +} +const dragType = ref<DragType>() + +/** + * 菜单开始拖动回调,记录被拖动菜单的信息(类型,下标) + * + * @param type DragType, 拖动类型,父节点、子节点 + * @param index number, 被拖动的菜单下标 + */ +const onDragStart = (type: DragType, index: number) => { + dragIndex.value = index + dragType.value = type +} + +/** + * 拖动其他菜单位置回调, 判断【被拖动】及【被替换位置】的两个菜单是否同个类型,同类型才会进行插入 + * + * @param type: DragType, 拖动类型,父节点、子节点 + * @param x number, 准备替换父节点位置的下标 + * @param y number, 准备替换子节点位置的下标, 父节点拖动时可选 + */ +const onDragEnter = (type: DragType, x: number, y = -1) => { + if (dragIndex.value !== x && dragType.value === type) { + if (type === DragType.Parent) { + const source = menuList.value.splice(dragIndex.value, 1) + menuList.value.splice(x, 0, ...source) + } else { + const source = menuList.value[x].children?.splice(dragIndex.value, 1) + menuList.value[x].children?.splice(y, 0, ...(source as any)) + } + } +} </script> <style lang="scss" scoped> From 08eb72e56868a8fbf3bfb849c93d6c69f7a5b6c0 Mon Sep 17 00:00:00 2001 From: dhb52 <dhb52@126.com> Date: Sat, 22 Apr 2023 01:23:52 +0800 Subject: [PATCH 5/8] style: mp/WxReply objCache => tabCache --- src/views/mp/components/wx-reply/main.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/mp/components/wx-reply/main.vue b/src/views/mp/components/wx-reply/main.vue index 1ef9dde0..9a83b2fe 100644 --- a/src/views/mp/components/wx-reply/main.vue +++ b/src/views/mp/components/wx-reply/main.vue @@ -84,7 +84,7 @@ const reply = computed<Reply>({ set: (val) => emit('update:modelValue', val) }) // 作为多个标签保存各自Reply的缓存 -const objCache = new Map<ReplyType, Reply>() +const tabCache = new Map<ReplyType, Reply>() // 采用独立的ref来保存当前tab,避免在watch标签变化,对reply进行赋值会产生了循环调用 const currentTab = ref<ReplyType>(props.modelValue.type || ReplyType.Text) @@ -97,10 +97,10 @@ watch( return } - objCache.set(oldTab, unref(reply)) + tabCache.set(oldTab, unref(reply)) // 从缓存里面取出新tab内容,有则覆盖Reply,没有则创建空Reply - const temp = objCache.get(newTab) + const temp = tabCache.get(newTab) if (temp) { reply.value = temp } else { From ebacbbb9cf69bd4358367bef3d9de9bcdfaad754 Mon Sep 17 00:00:00 2001 From: dhb52 <dhb52@126.com> Date: Sat, 22 Apr 2023 09:04:21 +0800 Subject: [PATCH 6/8] =?UTF-8?q?perf:=20mp/menu=E4=BD=BF=E7=94=A8vuedraggab?= =?UTF-8?q?le=E6=9B=BF=E6=8D=A2=E6=8B=96=E5=8A=A8=E7=9A=84=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mp/menu/components/MenuPreviewer.vue | 146 ++++++++++-------- 1 file changed, 81 insertions(+), 65 deletions(-) diff --git a/src/views/mp/menu/components/MenuPreviewer.vue b/src/views/mp/menu/components/MenuPreviewer.vue index 637c8805..3066e590 100644 --- a/src/views/mp/menu/components/MenuPreviewer.vue +++ b/src/views/mp/menu/components/MenuPreviewer.vue @@ -1,41 +1,55 @@ <template> - <div class="menu_bottom" v-for="(parent, x) of menuList" :key="x"> - <!-- 一级菜单 --> - <div - @click="menuClicked(parent, x)" - class="menu_item" - draggable="true" - @dragstart="onDragStart(DragType.Parent, x)" - @dragenter.prevent="onDragEnter(DragType.Parent, x)" - :class="{ active: props.activeIndex === `${x}` }" - > - <Icon icon="ep:fold" color="black" />{{ parent.name }} - </div> - <!-- 以下为二级菜单--> - <div class="submenu" v-if="parentIndex === x && parent.children"> - <div class="subtitle menu_bottom" v-for="(child, y) in parent.children" :key="y"> + <draggable + v-model="menuList" + item-key="id" + ghost-class="draggable-ghost" + :animation="400" + @end="onDragEnd" + > + <template #item="{ element: parent, index: x }"> + <div class="menu_bottom"> + <!-- 一级菜单 --> <div - class="menu_subItem" - draggable="true" - @dragstart="onDragStart(DragType.Child, y)" - @dragenter.prevent="onDragEnter(DragType.Child, x, y)" - v-if="parent.children" - :class="{ active: props.activeIndex === `${x}-${y}` }" - @click="subMenuClicked(child, x, y)" + @click="menuClicked(parent, x)" + class="menu_item" + :class="{ active: props.activeIndex === `${x}` }" > - {{ child.name }} + <Icon icon="ep:fold" color="black" />{{ parent.name }} + </div> + <!-- 以下为二级菜单--> + <div class="submenu" v-if="props.parentIndex === x && parent.children"> + <draggable + v-model="parent.children" + item-key="id" + ghost-class="draggable-ghost" + :animation="400" + > + <template #item="{ element: child, index: y }"> + <div class="subtitle menu_bottom"> + <div + class="menu_subItem" + v-if="parent.children" + :class="{ active: props.activeIndex === `${x}-${y}` }" + @click="subMenuClicked(child, x, y)" + > + {{ child.name }} + </div> + </div> + </template> + </draggable> + <!-- 二级菜单加号, 当长度 小于 5 才显示二级菜单的加号 --> + <div + class="menu_bottom menu_addicon" + v-if="!parent.children || parent.children.length < 5" + @click="addSubMenu(x, parent)" + > + <Icon icon="ep:plus" class="plus" /> + </div> </div> </div> - <!-- 二级菜单加号, 当长度 小于 5 才显示二级菜单的加号 --> - <div - class="menu_bottom menu_addicon" - v-if="!parent.children || parent.children.length < 5" - @click="addSubMenu(x, parent)" - > - <Icon icon="ep:plus" class="plus" /> - </div> - </div> - </div> + </template> + </draggable> + <!-- 一级菜单加号 --> <div class="menu_bottom menu_addicon" v-if="menuList.length < 3" @click="addMenu"> <Icon icon="ep:plus" class="plus" /> @@ -44,6 +58,7 @@ <script setup lang="ts"> import { Menu } from './types' +import draggable from 'vuedraggable' const props = defineProps<{ modelValue: Menu[] @@ -97,46 +112,41 @@ const addSubMenu = (i: number, parent: any) => { const menuClicked = (parent: Menu, x: number) => { emit('menu-clicked', parent, x) } + const subMenuClicked = (child: Menu, x: number, y: number) => { emit('submenu-clicked', child, x, y) } -// ======================== 菜单排序 ======================== -const dragIndex = ref<number>(0) -enum DragType { - Parent = 'parent', - Child = 'child' -} -const dragType = ref<DragType>() - /** - * 菜单开始拖动回调,记录被拖动菜单的信息(类型,下标) + * 处理一级菜单展开后被拖动 * - * @param type DragType, 拖动类型,父节点、子节点 - * @param index number, 被拖动的菜单下标 + * @param oldIndex: 一级菜单拖动前的位置 + * @param newIndex: 一级菜单拖动后的位置 */ -const onDragStart = (type: DragType, index: number) => { - dragIndex.value = index - dragType.value = type -} - -/** - * 拖动其他菜单位置回调, 判断【被拖动】及【被替换位置】的两个菜单是否同个类型,同类型才会进行插入 - * - * @param type: DragType, 拖动类型,父节点、子节点 - * @param x number, 准备替换父节点位置的下标 - * @param y number, 准备替换子节点位置的下标, 父节点拖动时可选 - */ -const onDragEnter = (type: DragType, x: number, y = -1) => { - if (dragIndex.value !== x && dragType.value === type) { - if (type === DragType.Parent) { - const source = menuList.value.splice(dragIndex.value, 1) - menuList.value.splice(x, 0, ...source) - } else { - const source = menuList.value[x].children?.splice(dragIndex.value, 1) - menuList.value[x].children?.splice(y, 0, ...(source as any)) - } +const onDragEnd = ({ oldIndex, newIndex }) => { + // 二级菜单没有展开,直接返回 + if (props.activeIndex === '__MENU_NOT_SELECTED__') { + return } + + let newParent = props.parentIndex + if (props.parentIndex === oldIndex) { + newParent = newIndex + } else if (props.parentIndex === newIndex) { + newParent = oldIndex + } else { + // 如果展开的二级菜单下标`props.parentIndex`不是被移动的菜单的前后下标。 + // 那么使用一个辅助素组来模拟菜单移动,然后找到展开的二级菜单的新下标`newParent` + let positions = new Array<boolean>(menuList.value.length).fill(false) + positions[props.parentIndex] = true + positions.splice(oldIndex, 1) + positions.splice(newIndex, 0, true) + newParent = positions.indexOf(true) + } + + // 找到菜单元素,触发一级菜单点击 + const parent = menuList.value[newParent] + emit('menu-clicked', parent, newParent) } </script> @@ -199,4 +209,10 @@ const onDragEnter = (type: DragType, x: number, y = -1) => { box-sizing: border-box; } } + +.draggable-ghost { + opacity: 0.5; + background: #f7fafc; + border: 1px solid #4299e1; +} </style> From f27b93db5953d1ce499613f7151393412f668ebf Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 22 Apr 2023 20:44:32 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E8=8F=9C=E5=8D=95=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=9A=E6=B7=BB=E5=8A=A0=E5=88=B7=E6=96=B0=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/menu/index.vue | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 95e71b5c..6be82506 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -129,7 +129,6 @@ import { handleTree } from '@/utils/tree' import * as MenuApi from '@/api/system/menu' import MenuForm from './MenuForm.vue' import { CACHE_KEY, useCache } from '@/hooks/web/useCache' - const { wsCache } = useCache() const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -180,19 +179,18 @@ const toggleExpandAll = () => { refreshTable.value = true }) } + /** 刷新菜单缓存按钮操作 */ -const refreshMenu = () => { - ElMessageBox.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存', { - confirmButtonText: t('common.ok'), - cancelButtonText: t('common.cancel'), - type: 'warning' - }).then(() => { +const refreshMenu = async () => { + try { + await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存') // 清空,从而触发刷新 wsCache.delete(CACHE_KEY.ROLE_ROUTERS) // 刷新浏览器 location.reload() - }) + } catch {} } + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { From c14b5c5bff083f7289ed8e955cd74e1e9a961120 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 22 Apr 2023 21:21:45 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E7=99=BB=E5=BD=95=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E2=80=9C=E8=90=8C=E6=96=B0=E5=BF=85?= =?UTF-8?q?=E8=AF=BB=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Login/components/LoginForm.vue | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/views/Login/components/LoginForm.vue b/src/views/Login/components/LoginForm.vue index 4cd09cc6..e988ce3b 100644 --- a/src/views/Login/components/LoginForm.vue +++ b/src/views/Login/components/LoginForm.vue @@ -125,6 +125,21 @@ </div> </el-form-item> </el-col> + <el-divider content-position="center">萌新必读</el-divider> + <el-col :span="24" style="padding-left: 10px; padding-right: 10px"> + <el-form-item> + <div class="flex justify-between w-[100%]"> + <el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link> + <el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link> + <el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank"> + ⚡面试手册 + </el-link> + <el-link href="http://static.yudao.iocoder.cn/mp/Aix9975.jpeg" target="_blank"> + 🤝外包咨询 + </el-link> + </div> + </el-form-item> + </el-col> </el-row> </el-form> </template>