fix: code style

This commit is contained in:
dylanmay
2025-11-06 15:25:11 +08:00
parent c238920588
commit 5269d4c387
30 changed files with 189 additions and 388 deletions

View File

@@ -24,8 +24,8 @@ export namespace MpAccountApi {
} }
// 重新导出类型,方便使用 // 重新导出类型,方便使用
export type Account = MpAccountApi.Account; // export type Account = MpAccountApi.Account;
export type AccountSimple = MpAccountApi.AccountSimple; // export type AccountSimple = MpAccountApi.AccountSimple;
/** 查询公众号账号列表 */ /** 查询公众号账号列表 */
export function getAccountPage(params: PageParam) { export function getAccountPage(params: PageParam) {

View File

@@ -29,5 +29,11 @@
"tenant": { "tenant": {
"placeholder": "Please select tenant", "placeholder": "Please select tenant",
"success": "Switch tenant success" "success": "Switch tenant success"
},
"mp": {
"upload": {
"invalidFormat": "Invalid {0} format!",
"maxSize": "{0} size cannot exceed {1}M!"
}
} }
} }

View File

@@ -1,2 +1 @@
export * from './auth'; export * from './auth';
export * from './tagsView';

View File

@@ -1,176 +0,0 @@
import type { RouteLocationNormalizedLoaded } from 'vue-router';
import { useRouter } from 'vue-router';
import { findIndex } from 'lodash';
import { defineStore } from 'pinia';
import { getRawRoute } from '../utils/routerHelper';
const router = useRouter();
export interface TagsViewState {
visitedViews: RouteLocationNormalizedLoaded[];
cachedViews: Set<string>;
}
export const useTagsViewStore = defineStore('tagsView', {
state: (): TagsViewState => ({
visitedViews: [],
cachedViews: new Set(),
}),
getters: {
getVisitedViews(): RouteLocationNormalizedLoaded[] {
return this.visitedViews;
},
getCachedViews(): string[] {
return [...this.cachedViews];
},
},
actions: {
/** 新增缓存和tag */
addView(view: RouteLocationNormalizedLoaded): void {
this.addVisitedView(view);
this.addCachedView();
},
/** 新增tag */
addVisitedView(view: RouteLocationNormalizedLoaded) {
if (this.visitedViews.some((v) => v.fullPath === view.fullPath)) return;
if (view.meta?.noTagsView) return;
const visitedView = Object.assign({}, view, {
title: view.meta?.title || 'no-name',
});
if (visitedView.meta) {
const suffixList: string[] = [];
this.visitedViews.forEach((v) => {
if (
v.path === visitedView.path &&
v.meta?.title === visitedView.meta?.title
) {
const rawSuffix = v.meta?.titleSuffix;
const suffixStr =
typeof rawSuffix === 'string' || typeof rawSuffix === 'number'
? `${rawSuffix}`
: undefined;
suffixList.push(suffixStr ?? '1');
}
});
if (suffixList.length > 0) {
let suffix = 1;
while (suffixList.includes(`${suffix}`)) suffix += 1;
visitedView.meta.titleSuffix = suffix === 1 ? undefined : `${suffix}`;
}
}
this.visitedViews.push(visitedView);
},
/** 新增缓存 */
addCachedView() {
const cacheMap: Set<string> = new Set();
for (const v of this.visitedViews) {
const item = getRawRoute(v);
if (!item.meta?.noCache) {
const name = item.name as string;
cacheMap.add(name);
}
}
if (
[...this.cachedViews].sort().toString() ===
[...cacheMap].sort().toString()
) {
return;
}
this.cachedViews = cacheMap;
},
/** 删除某个tag和缓存 */
delView(view: RouteLocationNormalizedLoaded) {
this.delVisitedView(view);
this.delCachedView();
},
/** 删除tag */
delVisitedView(view: RouteLocationNormalizedLoaded) {
const index = findIndex<RouteLocationNormalizedLoaded>(
this.visitedViews,
(v) => v.fullPath === view.fullPath,
);
if (index > -1) this.visitedViews.splice(index, 1);
},
/** 删除缓存 */
delCachedView() {
const route = router.currentRoute.value;
const index = findIndex<string>(
this.getCachedViews,
(v) => v === route.name,
);
if (index > -1) {
const name = this.getCachedViews[index] as string;
this.cachedViews.delete(name);
}
},
/** 删除全部tag和缓存 */
delAllViews() {
this.visitedViews = [];
this.cachedViews.clear();
},
/** 删除其他tag和缓存 */
delOthersViews(view: RouteLocationNormalizedLoaded) {
this.visitedViews = this.visitedViews.filter(
(v) => v?.meta?.affix || v.fullPath === view.fullPath,
);
this.addCachedView();
},
/** 删除左侧tag */
delLeftViews(view: RouteLocationNormalizedLoaded) {
const index = findIndex<RouteLocationNormalizedLoaded>(
this.visitedViews,
(v) => v.fullPath === view.fullPath,
);
if (index > -1) {
this.visitedViews = this.visitedViews.filter(
(v, i) => v?.meta?.affix || v.fullPath === view.fullPath || i > index,
);
this.addCachedView();
}
},
/** 删除右侧tag */
delRightViews(view: RouteLocationNormalizedLoaded) {
const index = findIndex<RouteLocationNormalizedLoaded>(
this.visitedViews,
(v) => v.fullPath === view.fullPath,
);
if (index > -1) {
this.visitedViews = this.visitedViews.filter(
(v, i) => v?.meta?.affix || v.fullPath === view.fullPath || i < index,
);
this.addCachedView();
}
},
/** 更新tag */
updateVisitedView(view: RouteLocationNormalizedLoaded) {
const index = findIndex<RouteLocationNormalizedLoaded>(
this.visitedViews,
(v) => v.fullPath === view.fullPath,
);
if (index > -1) {
this.visitedViews[index] = {
...this.visitedViews[index],
...view,
} as RouteLocationNormalizedLoaded;
}
},
},
});

View File

@@ -1 +1 @@
export { default } from './main.vue'; export { default as WxAccountSelect } from './main.vue';

View File

@@ -1,11 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, reactive, ref, unref } from 'vue'; import type { SelectValue } from 'ant-design-vue/es/select';
import type { MpAccountApi } from '#/api/mp/account';
import { onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { message } from 'ant-design-vue'; import { message, Select } from 'ant-design-vue';
import * as MpAccountApi from '#/api/mp/account'; import { getSimpleAccountList } from '#/api/mp/account';
import { useTagsViewStore } from '#/store';
defineOptions({ name: 'WxAccountSelect' }); defineOptions({ name: 'WxAccountSelect' });
@@ -14,8 +17,7 @@ const emit = defineEmits<{
(e: 'change', id: number, name: string): void; (e: 'change', id: number, name: string): void;
}>(); }>();
// 消息弹窗 // 消息弹窗
const { delView } = useTagsViewStore(); const { push } = useRouter();
const { push, currentRoute } = useRouter();
// 当前选中的公众号 // 当前选中的公众号
const account: MpAccountApi.Account = reactive({ const account: MpAccountApi.Account = reactive({
@@ -28,10 +30,9 @@ const accountList = ref<MpAccountApi.Account[]>([]);
// 查询公众号列表 // 查询公众号列表
const handleQuery = async () => { const handleQuery = async () => {
accountList.value = await MpAccountApi.getSimpleAccountList(); accountList.value = await getSimpleAccountList();
if (accountList.value.length === 0) { if (accountList.value.length === 0) {
message.error('未配置公众号,请在【公众号管理 -> 账号管理】菜单,进行配置'); message.error('未配置公众号,请在【公众号管理 -> 账号管理】菜单,进行配置');
delView(unref(currentRoute));
await push({ name: 'MpAccount' }); await push({ name: 'MpAccount' });
return; return;
} }
@@ -46,7 +47,7 @@ const handleQuery = async () => {
}; };
// 切换选中公众号 // 切换选中公众号
const onChanged = (id?: number) => { const onChanged = (id: SelectValue) => {
const found = accountList.value.find((v) => v.id === id); const found = accountList.value.find((v) => v.id === id);
if (found) { if (found) {
account.name = found.name; account.name = found.name;
@@ -59,24 +60,14 @@ onMounted(handleQuery);
</script> </script>
<template> <template>
<a-select <Select
v-model:value="account.id" v-model:value="account.id"
placeholder="请选择公众号" placeholder="请选择公众号"
class="!w-240px" class="!w-[240px]"
@change="onChanged" @change="onChanged"
> >
<a-select-option <Select.Option v-for="item in accountList" :key="item.id" :value="item.id">
v-for="item in accountList"
:key="item.id"
:value="item.id"
>
{{ item.name }} {{ item.name }}
</a-select-option> </Select.Option>
</a-select> </Select>
</template> </template>
<style scoped>
.w-240px {
width: 240px;
}
</style>

View File

@@ -1 +1 @@
export { default } from './main.vue'; export { default as WxLocation } from './main.vue';

View File

@@ -1,3 +1,3 @@
export { default } from './main.vue'; export { default as WxMaterialSelect } from './main.vue';
export { MaterialType, NewsType } from './types'; export { MaterialType, NewsType } from './types';

View File

@@ -9,9 +9,9 @@ import { Button, Pagination, Row, Spin, Table } from 'ant-design-vue';
import * as MpDraftApi from '#/api/mp/draft'; import * as MpDraftApi from '#/api/mp/draft';
import * as MpFreePublishApi from '#/api/mp/freePublish'; import * as MpFreePublishApi from '#/api/mp/freePublish';
import * as MpMaterialApi from '#/api/mp/material'; import * as MpMaterialApi from '#/api/mp/material';
import WxNews from '#/views/mp/components/wx-news'; import { WxNews } from '#/views/mp/components/wx-news';
import WxVideoPlayer from '#/views/mp/components/wx-video-play'; import { WxVideoPlayer } from '#/views/mp/components/wx-video-play';
import WxVoicePlayer from '#/views/mp/components/wx-voice-play'; import { WxVoicePlayer } from '#/views/mp/components/wx-voice-play';
import { NewsType } from './types'; import { NewsType } from './types';

View File

@@ -1,11 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import WxLocation from '#/views/mp/components/wx-location'; import { WxLocation } from '#/views/mp/components/wx-location';
import WxMusic from '#/views/mp/components/wx-music'; import { WxMusic } from '#/views/mp/components/wx-music';
import WxNews from '#/views/mp/components/wx-news'; import { WxNews } from '#/views/mp/components/wx-news';
import WxVideoPlayer from '#/views/mp/components/wx-video-play'; import { WxVideoPlayer } from '#/views/mp/components/wx-video-play';
import WxVoicePlayer from '#/views/mp/components/wx-voice-play'; import { WxVoicePlayer } from '#/views/mp/components/wx-voice-play';
import { MsgType } from '../types'; import { MsgType } from '../types';
import MsgEvent from './MsgEvent.vue'; import MsgEvent from './MsgEvent.vue';
@@ -29,7 +29,7 @@ defineProps<{
<div v-else-if="item.type === MsgType.Image"> <div v-else-if="item.type === MsgType.Image">
<a :href="item.mediaUrl" target="_blank"> <a :href="item.mediaUrl" target="_blank">
<img :src="item.mediaUrl" style="width: 100px" alt="图片消息" /> <img :src="item.mediaUrl" class="w-[100px]" alt="图片消息" />
</a> </a>
</div> </div>
@@ -40,14 +40,14 @@ defineProps<{
<WxVideoPlayer :url="item.mediaUrl" /> <WxVideoPlayer :url="item.mediaUrl" />
</div> </div>
<div v-else-if="item.type === MsgType.Link" class="link-card"> <div v-else-if="item.type === MsgType.Link" class="flex flex-col gap-2">
<a :href="item.url" target="_blank" class="text-success no-underline"> <a :href="item.url" target="_blank" class="text-success no-underline">
<div class="link-title"> <div class="flex items-center text-sm font-medium text-[#52c41a]">
<IconifyIcon icon="mdi:link" class="mr-1" /> <IconifyIcon icon="mdi:link" class="mr-1" />
{{ item.title }} {{ item.title }}
</div> </div>
</a> </a>
<div class="link-description">{{ item.description }}</div> <div class="text-xs text-[#666]">{{ item.description }}</div>
</div> </div>
<div v-else-if="item.type === MsgType.Location"> <div v-else-if="item.type === MsgType.Location">
@@ -58,7 +58,7 @@ defineProps<{
/> />
</div> </div>
<div v-else-if="item.type === MsgType.News" class="news-wrapper"> <div v-else-if="item.type === MsgType.News" class="w-[300px]">
<WxNews :articles="item.articles" /> <WxNews :articles="item.articles" />
</div> </div>
@@ -73,28 +73,3 @@ defineProps<{
</div> </div>
</div> </div>
</template> </template>
<style scoped lang="scss">
.link-card {
display: flex;
flex-direction: column;
gap: 8px;
}
.link-title {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
color: #52c41a;
}
.link-description {
font-size: 12px;
color: #666;
}
.news-wrapper {
width: 300px;
}
</style>

View File

@@ -1,3 +1,3 @@
export { default } from './main.vue'; export { default as WxMsg } from './main.vue';
export { MsgType } from './types'; export { MsgType } from './types';

View File

@@ -9,7 +9,7 @@ import { Button, message, Spin } from 'ant-design-vue';
import { getMessagePage, sendMessage } from '#/api/mp/message'; import { getMessagePage, sendMessage } from '#/api/mp/message';
import { getUser } from '#/api/mp/user'; import { getUser } from '#/api/mp/user';
import WxReplySelect from '#/views/mp/components/wx-reply'; import { WxReplySelect } from '#/views/mp/components/wx-reply';
import MsgList from './components/MsgList.vue'; import MsgList from './components/MsgList.vue';
@@ -144,15 +144,22 @@ const scrollToBottom = async () => {
</script> </script>
<template> <template>
<div class="wx-msg-container"> <div class="flex h-full flex-col">
<div ref="msgDivRef" class="msg-div"> <div ref="msgDivRef" class="mx-2.5 flex-1 overflow-auto bg-[#eaeaea]">
<!-- 加载更多 --> <!-- 加载更多 -->
<Spin :spinning="loading" /> <Spin :spinning="loading" />
<div v-if="!loading"> <div v-if="!loading">
<div v-if="hasMore" class="load-more-btn" @click="loadMore"> <div
v-if="hasMore"
class="cursor-pointer rounded p-3 text-center text-sm text-[#409eff] transition-colors duration-300 hover:bg-[#f5f7fa]"
@click="loadMore"
>
<span>点击加载更多</span> <span>点击加载更多</span>
</div> </div>
<div v-else class="load-more-btn disabled"> <div
v-else
class="cursor-not-allowed rounded p-3 text-center text-sm text-[#909399] hover:bg-transparent"
>
<span>没有更多了</span> <span>没有更多了</span>
</div> </div>
</div> </div>
@@ -161,62 +168,13 @@ const scrollToBottom = async () => {
<MsgList :list="list" :account-id="accountId" :user="user" /> <MsgList :list="list" :account-id="accountId" :user="user" />
</div> </div>
<div class="msg-send"> <div class="p-2.5">
<Spin :spinning="sendLoading"> <Spin :spinning="sendLoading">
<WxReplySelect ref="replySelectRef" v-model="reply" /> <WxReplySelect ref="replySelectRef" v-model="reply" />
<Button type="primary" class="send-but" @click="sendMsg"> <Button type="primary" class="float-right mb-2 mt-2" @click="sendMsg">
发送(S) 发送(S)
</Button> </Button>
</Spin> </Spin>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped>
.wx-msg-container {
display: flex;
flex-direction: column;
height: 100%;
}
.msg-div {
flex: 1;
height: 50vh;
margin: 0 10px;
overflow: auto;
background-color: #eaeaea;
}
.load-more-btn {
padding: 12px;
font-size: 14px;
color: #409eff;
text-align: center;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s;
&:hover {
background-color: #f5f7fa;
}
&.disabled {
color: #909399;
cursor: not-allowed;
&:hover {
background-color: transparent;
}
}
}
.msg-send {
padding: 10px;
}
.send-but {
float: right;
margin-top: 8px;
margin-bottom: 8px;
}
</style>

View File

@@ -1 +1 @@
export { default } from './main.vue'; export { default as WxMusic } from './main.vue';

View File

@@ -1 +1 @@
export { default } from './main.vue'; export { default as WxNews } from './main.vue';

View File

@@ -10,7 +10,7 @@ import { useAccessStore } from '@vben/stores';
import { Button, Col, message, Modal, Row, Upload } from 'ant-design-vue'; import { Button, Col, message, Modal, Row, Upload } from 'ant-design-vue';
import WxMaterialSelect from '#/views/mp/components/wx-material-select'; import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload'; import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
defineOptions({ name: 'TabImage' }); defineOptions({ name: 'TabImage' });

View File

@@ -18,7 +18,7 @@ import {
Upload, Upload,
} from 'ant-design-vue'; } from 'ant-design-vue';
import WxMaterialSelect from '#/views/mp/components/wx-material-select'; import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload'; import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
defineOptions({ name: 'TabMusic' }); defineOptions({ name: 'TabMusic' });

View File

@@ -7,8 +7,8 @@ import { IconifyIcon } from '@vben/icons';
import { Button, Col, Modal, Row } from 'ant-design-vue'; import { Button, Col, Modal, Row } from 'ant-design-vue';
import WxMaterialSelect from '#/views/mp/components/wx-material-select'; import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import WxNews from '#/views/mp/components/wx-news'; import { WxNews } from '#/views/mp/components/wx-news';
import { NewsType } from './types'; import { NewsType } from './types';

View File

@@ -18,8 +18,8 @@ import {
Upload, Upload,
} from 'ant-design-vue'; } from 'ant-design-vue';
import WxMaterialSelect from '#/views/mp/components/wx-material-select'; import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import WxVideoPlayer from '#/views/mp/components/wx-video-play'; import { WxVideoPlayer } from '#/views/mp/components/wx-video-play';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload'; import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
defineOptions({ name: 'TabVideo' }); defineOptions({ name: 'TabVideo' });

View File

@@ -10,8 +10,8 @@ import { useAccessStore } from '@vben/stores';
import { Button, Col, message, Modal, Row, Upload } from 'ant-design-vue'; import { Button, Col, message, Modal, Row, Upload } from 'ant-design-vue';
import WxMaterialSelect from '#/views/mp/components/wx-material-select'; import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import WxVoicePlayer from '#/views/mp/components/wx-voice-play'; import { WxVoicePlayer } from '#/views/mp/components/wx-voice-play';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload'; import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
defineOptions({ name: 'TabVoice' }); defineOptions({ name: 'TabVoice' });

View File

@@ -1,4 +1,4 @@
export type { NewsType, Reply, ReplyType } from './components/types'; export type { NewsType, Reply, ReplyType } from './components/types';
export { createEmptyReply } from './components/types'; export { createEmptyReply } from './components/types';
export { default } from './main.vue'; export { default as WxReplySelect } from './main.vue';

View File

@@ -1 +1 @@
export { default } from './main.vue'; export { default as WxVideoPlayer } from './main.vue';

View File

@@ -1 +1 @@
export { default } from './main.vue'; export { default as WxVoicePlayer } from './main.vue';

View File

@@ -2,7 +2,7 @@
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { Spin } from 'ant-design-vue'; import { Button, Spin } from 'ant-design-vue';
const props = defineProps<{ const props = defineProps<{
list: any[]; list: any[];
@@ -25,7 +25,7 @@ const { hasAccessByCodes } = useAccess();
<div class="item-name">{{ item.name }}</div> <div class="item-name">{{ item.name }}</div>
</a> </a>
<div class="flex justify-center"> <div class="flex justify-center">
<a-button <Button
v-if="hasAccessByCodes(['mp:material:delete'])" v-if="hasAccessByCodes(['mp:material:delete'])"
danger danger
shape="circle" shape="circle"
@@ -35,7 +35,7 @@ const { hasAccessByCodes } = useAccess();
<template #icon> <template #icon>
<IconifyIcon icon="mdi:delete" /> <IconifyIcon icon="mdi:delete" />
</template> </template>
</a-button> </Button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,7 +7,7 @@ import { inject, reactive, ref } from 'vue';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { message, Upload } from 'ant-design-vue'; import { Button, message, Upload } from 'ant-design-vue';
import { import {
beforeImageUpload, beforeImageUpload,
@@ -83,22 +83,19 @@ const customRequest: UploadProps['customRequest'] = async (options) => {
:action="UPLOAD_URL" :action="UPLOAD_URL"
:before-upload="onBeforeUpload" :before-upload="onBeforeUpload"
:custom-request="customRequest" :custom-request="customRequest"
:data="uploadData"
:file-list="fileList" :file-list="fileList"
:headers="HEADERS" :headers="HEADERS"
:multiple="true" :multiple="true"
class="mb-4" class="mb-4"
> >
<a-button type="primary"> <Button type="primary">
<IconifyIcon icon="mdi:upload" class="mr-1" /> <IconifyIcon icon="mdi:upload" class="mr-1" />
点击上传 点击上传
</a-button> </Button>
<template #itemRender="{ file, actions }"> <template #itemRender="{ file, actions }">
<div class="flex items-center"> <div class="flex items-center">
<span>{{ file.name }}</span> <span>{{ file.name }}</span>
<a-button type="link" size="small" @click="actions.remove"> <Button type="link" size="small" @click="actions.remove"> 删除 </Button>
删除
</a-button>
</div> </div>
</template> </template>
</Upload> </Upload>

View File

@@ -7,7 +7,15 @@ import { inject, reactive, ref } from 'vue';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { message, Modal, Upload } from 'ant-design-vue'; import {
Button,
Divider,
Form,
Input,
message,
Modal,
Upload,
} from 'ant-design-vue';
import { beforeVideoUpload, HEADERS, UPLOAD_URL, UploadType } from './upload'; import { beforeVideoUpload, HEADERS, UPLOAD_URL, UploadType } from './upload';
@@ -28,8 +36,10 @@ const emit = defineEmits<{
const accountId = inject<number>('accountId'); const accountId = inject<number>('accountId');
const uploadRules = { const uploadRules = {
introduction: [{ message: '请输入描述', required: true, trigger: 'blur' }], introduction: [
title: [{ message: '请输入标题', required: true, trigger: 'blur' }], { message: '请输入描述', required: true, trigger: 'blur' } as const,
],
title: [{ message: '请输入标题', required: true, trigger: 'blur' } as const],
}; };
const handleCancel = () => { const handleCancel = () => {
@@ -119,36 +129,36 @@ const customRequest: UploadProps['customRequest'] = async (options) => {
:multiple="true" :multiple="true"
class="mb-4" class="mb-4"
> >
<a-button type="primary"> <Button type="primary">
<IconifyIcon icon="mdi:video-plus" class="mr-1" /> <IconifyIcon icon="mdi:video-plus" class="mr-1" />
选择视频 选择视频
</a-button> </Button>
</Upload> </Upload>
<div class="mb-4 ml-1 text-sm text-gray-500"> <div class="mb-4 ml-1 text-sm text-gray-500">
格式支持 MP4文件大小不超过 10MB 格式支持 MP4文件大小不超过 10MB
</div> </div>
<a-divider /> <Divider />
<a-form <Form
ref="uploadFormRef" ref="uploadFormRef"
:model="uploadData" :model="uploadData"
:rules="uploadRules" :rules="uploadRules"
layout="vertical" layout="vertical"
> >
<a-form-item label="标题" name="title"> <Form.Item label="标题" name="title">
<a-input <Input
v-model:value="uploadData.title" v-model:value="uploadData.title"
placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题" placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题"
/> />
</a-form-item> </Form.Item>
<a-form-item label="描述" name="introduction"> <Form.Item label="描述" name="introduction">
<a-textarea <Input.TextArea
v-model:value="uploadData.introduction" v-model:value="uploadData.introduction"
:rows="3" :rows="3"
placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容" placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"
/> />
</a-form-item> </Form.Item>
</a-form> </Form>
</Modal> </Modal>
</template> </template>

View File

@@ -3,6 +3,8 @@ import { useAccess } from '@vben/access';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { formatDate2 } from '@vben/utils'; import { formatDate2 } from '@vben/utils';
import { Button, Table } from 'ant-design-vue';
import WxVideoPlayer from '#/views/mp/components/wx-video-play'; import WxVideoPlayer from '#/views/mp/components/wx-video-play';
const props = defineProps<{ const props = defineProps<{
@@ -17,18 +19,33 @@ const emit = defineEmits<{
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const columns = [ const columns = [
{ align: 'center', dataIndex: 'mediaId', key: 'mediaId', title: '编号' },
{ align: 'center', dataIndex: 'name', key: 'name', title: '文件名' },
{ align: 'center', dataIndex: 'title', key: 'title', title: '标题' },
{ {
align: 'center', align: 'center' as const,
dataIndex: 'mediaId',
key: 'mediaId',
title: '编号',
},
{ align: 'center' as const, dataIndex: 'name', key: 'name', title: '文件名' },
{ align: 'center' as const, dataIndex: 'title', key: 'title', title: '标题' },
{
align: 'center' as const,
dataIndex: 'introduction', dataIndex: 'introduction',
key: 'introduction', key: 'introduction',
title: '介绍', title: '介绍',
}, },
{ align: 'center', key: 'video', title: '视频' }, { align: 'center' as const, key: 'video', title: '视频' },
{ align: 'center', key: 'createTime', title: '上传时间', width: 180 }, {
{ align: 'center', fixed: 'right', key: 'action', title: '操作' }, align: 'center' as const,
key: 'createTime',
title: '上传时间',
width: 180,
},
{
align: 'center' as const,
fixed: 'right' as const,
key: 'action',
title: '操作',
},
]; ];
// 下载文件 // 下载文件
@@ -38,7 +55,7 @@ const handleDownload = (url: string) => {
</script> </script>
<template> <template>
<a-table <Table
:columns="columns" :columns="columns"
:data-source="props.list" :data-source="props.list"
:loading="props.loading" :loading="props.loading"
@@ -55,11 +72,11 @@ const handleDownload = (url: string) => {
{{ formatDate2(record.createTime) }} {{ formatDate2(record.createTime) }}
</template> </template>
<template v-else-if="column.key === 'action'"> <template v-else-if="column.key === 'action'">
<a-button type="link" @click="handleDownload(record.url)"> <Button type="link" @click="handleDownload(record.url)">
<IconifyIcon icon="mdi:download" /> <IconifyIcon icon="mdi:download" />
下载 下载
</a-button> </Button>
<a-button <Button
v-if="hasAccessByCodes(['mp:material:delete'])" v-if="hasAccessByCodes(['mp:material:delete'])"
danger danger
type="link" type="link"
@@ -67,8 +84,8 @@ const handleDownload = (url: string) => {
> >
<IconifyIcon icon="mdi:delete" /> <IconifyIcon icon="mdi:delete" />
删除 删除
</a-button> </Button>
</template> </template>
</template> </template>
</a-table> </Table>
</template> </template>

View File

@@ -3,6 +3,8 @@ import { useAccess } from '@vben/access';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { formatDate2 } from '@vben/utils'; import { formatDate2 } from '@vben/utils';
import { Button, Table } from 'ant-design-vue';
import WxVoicePlayer from '#/views/mp/components/wx-voice-play'; import WxVoicePlayer from '#/views/mp/components/wx-voice-play';
const props = defineProps<{ const props = defineProps<{
@@ -17,11 +19,26 @@ const emit = defineEmits<{
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const columns = [ const columns = [
{ align: 'center', dataIndex: 'mediaId', key: 'mediaId', title: '编号' }, {
{ align: 'center', dataIndex: 'name', key: 'name', title: '文件名' }, align: 'center' as const,
{ align: 'center', key: 'voice', title: '语音' }, dataIndex: 'mediaId',
{ align: 'center', key: 'createTime', title: '上传时间', width: 180 }, key: 'mediaId',
{ align: 'center', fixed: 'right', key: 'action', title: '操作' }, title: '编号',
},
{ align: 'center' as const, dataIndex: 'name', key: 'name', title: '文件名' },
{ align: 'center' as const, key: 'voice', title: '语音' },
{
align: 'center' as const,
key: 'createTime',
title: '上传时间',
width: 180,
},
{
align: 'center' as const,
fixed: 'right' as const,
key: 'action',
title: '操作',
},
]; ];
const handleDownload = (url: string) => { const handleDownload = (url: string) => {
@@ -30,7 +47,7 @@ const handleDownload = (url: string) => {
</script> </script>
<template> <template>
<a-table <Table
:columns="columns" :columns="columns"
:data-source="props.list" :data-source="props.list"
:loading="props.loading" :loading="props.loading"
@@ -47,11 +64,11 @@ const handleDownload = (url: string) => {
{{ formatDate2(record.createTime) }} {{ formatDate2(record.createTime) }}
</template> </template>
<template v-else-if="column.key === 'action'"> <template v-else-if="column.key === 'action'">
<a-button type="link" @click="handleDownload(record.url)"> <Button type="link" @click="handleDownload(record.url)">
<IconifyIcon icon="mdi:download" /> <IconifyIcon icon="mdi:download" />
下载 下载
</a-button> </Button>
<a-button <Button
v-if="hasAccessByCodes(['mp:material:delete'])" v-if="hasAccessByCodes(['mp:material:delete'])"
danger danger
type="link" type="link"
@@ -59,8 +76,8 @@ const handleDownload = (url: string) => {
> >
<IconifyIcon icon="mdi:delete" /> <IconifyIcon icon="mdi:delete" />
删除 删除
</a-button> </Button>
</template> </template>
</template> </template>
</a-table> </Table>
</template> </template>

View File

@@ -5,10 +5,18 @@ import { useAccess } from '@vben/access';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { message, Modal, Tabs } from 'ant-design-vue'; import {
Button,
Card,
Form,
message,
Modal,
Pagination,
Tabs,
} from 'ant-design-vue';
import * as MpMaterialApi from '#/api/mp/material'; import * as MpMaterialApi from '#/api/mp/material';
import WxAccountSelect from '#/views/mp/components/wx-account-select'; import { WxAccountSelect } from '#/views/mp/components/wx-account-select';
import ImageTable from './components/ImageTable.vue'; import ImageTable from './components/ImageTable.vue';
import { UploadType } from './components/upload'; import { UploadType } from './components/upload';
@@ -97,15 +105,15 @@ const handleDelete = async (id: number) => {
title="公众号素材" title="公众号素材"
> >
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<a-card class="mb-4" :bordered="false"> <Card class="mb-4" :bordered="false">
<a-form :model="queryParams" layout="inline"> <Form :model="queryParams" layout="inline">
<a-form-item label="公众号"> <Form.Item label="公众号">
<WxAccountSelect @change="onAccountChanged" /> <WxAccountSelect @change="onAccountChanged" />
</a-form-item> </Form.Item>
</a-form> </Form>
</a-card> </Card>
<a-card :bordered="false"> <Card :bordered="false">
<Tabs v-model:active-key="type" @change="onTabChange"> <Tabs v-model:active-key="type" @change="onTabChange">
<!-- tab 1图片 --> <!-- tab 1图片 -->
<Tabs.TabPane :key="UploadType.Image"> <Tabs.TabPane :key="UploadType.Image">
@@ -126,7 +134,7 @@ const handleDelete = async (id: number) => {
<ImageTable :list="list" :loading="loading" @delete="handleDelete" /> <ImageTable :list="list" :loading="loading" @delete="handleDelete" />
<!-- 分页组件 --> <!-- 分页组件 -->
<div class="mt-4 flex justify-end"> <div class="mt-4 flex justify-end">
<a-pagination <Pagination
v-model:current="queryParams.pageNo" v-model:current="queryParams.pageNo"
v-model:page-size="queryParams.pageSize" v-model:page-size="queryParams.pageSize"
:total="total" :total="total"
@@ -140,10 +148,9 @@ const handleDelete = async (id: number) => {
<!-- tab 2语音 --> <!-- tab 2语音 -->
<Tabs.TabPane :key="UploadType.Voice"> <Tabs.TabPane :key="UploadType.Voice">
<template #tab> <template #tab>
<span class="flex items-center"> <span class="flex items-center"></span>
<IconifyIcon icon="mdi:microphone" class="mr-1" /> <IconifyIcon icon="mdi:microphone" class="mr-1" />
语音 <span>语音</span>
</span>
</template> </template>
<UploadFile <UploadFile
v-if="hasAccessByCodes(['mp:material:upload-permanent'])" v-if="hasAccessByCodes(['mp:material:upload-permanent'])"
@@ -156,7 +163,7 @@ const handleDelete = async (id: number) => {
<VoiceTable :list="list" :loading="loading" @delete="handleDelete" /> <VoiceTable :list="list" :loading="loading" @delete="handleDelete" />
<!-- 分页组件 --> <!-- 分页组件 -->
<div class="mt-4 flex justify-end"> <div class="mt-4 flex justify-end">
<a-pagination <Pagination
v-model:current="queryParams.pageNo" v-model:current="queryParams.pageNo"
v-model:page-size="queryParams.pageSize" v-model:page-size="queryParams.pageSize"
:total="total" :total="total"
@@ -175,20 +182,20 @@ const handleDelete = async (id: number) => {
视频 视频
</span> </span>
</template> </template>
<a-button <Button
v-if="hasAccessByCodes(['mp:material:upload-permanent'])" v-if="hasAccessByCodes(['mp:material:upload-permanent'])"
type="primary" type="primary"
@click="showCreateVideo = true" @click="showCreateVideo = true"
> >
新建视频 新建视频
</a-button> </Button>
<!-- 新建视频的弹窗 --> <!-- 新建视频的弹窗 -->
<UploadVideo v-model:open="showCreateVideo" @uploaded="getList" /> <UploadVideo v-model:open="showCreateVideo" @uploaded="getList" />
<!-- 列表 --> <!-- 列表 -->
<VideoTable :list="list" :loading="loading" @delete="handleDelete" /> <VideoTable :list="list" :loading="loading" @delete="handleDelete" />
<!-- 分页组件 --> <!-- 分页组件 -->
<div class="mt-4 flex justify-end"> <div class="mt-4 flex justify-end">
<a-pagination <Pagination
v-model:current="queryParams.pageNo" v-model:current="queryParams.pageNo"
v-model:page-size="queryParams.pageSize" v-model:page-size="queryParams.pageSize"
:total="total" :total="total"
@@ -199,6 +206,6 @@ const handleDelete = async (id: number) => {
</div> </div>
</Tabs.TabPane> </Tabs.TabPane>
</Tabs> </Tabs>
</a-card> </Card>
</Page> </Page>
</template> </template>

View File

@@ -5,12 +5,12 @@ import { formatDate2 } from '@vben/utils';
import { Button, Image, Table, Tag } from 'ant-design-vue'; import { Button, Image, Table, Tag } from 'ant-design-vue';
import WxLocation from '#/views/mp/components/wx-location'; import { WxLocation } from '#/views/mp/components/wx-location';
import { MsgType } from '#/views/mp/components/wx-msg/types'; import { MsgType } from '#/views/mp/components/wx-msg/types';
import WxMusic from '#/views/mp/components/wx-music'; import { WxMusic } from '#/views/mp/components/wx-music';
import WxNews from '#/views/mp/components/wx-news'; import { WxNews } from '#/views/mp/components/wx-news';
import WxVideoPlayer from '#/views/mp/components/wx-video-play'; import { WxVideoPlayer } from '#/views/mp/components/wx-video-play';
import WxVoicePlayer from '#/views/mp/components/wx-voice-play'; import { WxVoicePlayer } from '#/views/mp/components/wx-voice-play';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View File

@@ -18,8 +18,8 @@ import {
} from 'ant-design-vue'; } from 'ant-design-vue';
import { getMessagePage } from '#/api/mp/message'; import { getMessagePage } from '#/api/mp/message';
import WxAccountSelect from '#/views/mp/components/wx-account-select'; import { WxAccountSelect } from '#/views/mp/components/wx-account-select';
import WxMsg from '#/views/mp/components/wx-msg'; import { WxMsg } from '#/views/mp/components/wx-msg';
import { MsgType } from '#/views/mp/components/wx-msg/types'; import { MsgType } from '#/views/mp/components/wx-msg/types';
import MessageTable from './MessageTable.vue'; import MessageTable from './MessageTable.vue';