Files
frontend/apps/web-antd/src/views/mp/material/index.vue
2025-12-15 10:13:13 +08:00

253 lines
6.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MpMaterialApi } from '#/api/mp/material';
import { computed, provide, ref } from 'vue';
import { useAccess } from '@vben/access';
import { confirm, DocAlert, Page } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Button, message, Tabs } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deletePermanentMaterial, getMaterialPage } from '#/api/mp/material';
import { WxAccountSelect } from '#/views/mp/components';
import {
useGridFormSchema,
useImageGridColumns,
useVideoGridColumns,
useVoiceGridColumns,
} from './modules/data';
import { UploadType } from './modules/upload';
import UploadFile from './modules/UploadFile.vue';
import UploadVideo from './modules/UploadVideo.vue';
defineOptions({ name: 'MpMaterial' });
const { hasAccessByCodes } = useAccess();
const type = ref<UploadType>(UploadType.Image); // 素材类型
const showCreateVideo = ref(false); // 是否新建视频的弹窗
const accountId = ref(-1);
provide('accountId', accountId);
// 根据类型获取对应的列配置
const getColumnsByType = () => {
switch (type.value) {
case UploadType.Image: {
return useImageGridColumns();
}
case UploadType.Video: {
return useVideoGridColumns();
}
case UploadType.Voice: {
return useVoiceGridColumns();
}
default: {
return [];
}
}
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: getColumnsByType(),
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
const finalAccountId = formValues?.accountId ?? accountId.value;
if (!finalAccountId || finalAccountId === -1) {
return { list: [], total: 0 };
}
return await getMaterialPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
type: type.value,
permanent: true,
accountId: finalAccountId,
...formValues,
});
},
},
autoLoad: false,
},
rowConfig: {
keyField: 'id',
isHover: true,
height: type.value === UploadType.Image ? 220 : 'auto',
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MpMaterialApi.Material>,
});
// 当 tab 切换时,更新 Grid 的 columns 和 rowConfig
async function onTabChange() {
const columns = getColumnsByType();
await gridApi.setGridOptions({
columns,
rowConfig: {
keyField: 'id',
isHover: true,
height: type.value === UploadType.Image ? 220 : 'auto',
},
});
await gridApi.reload();
}
async function handleAccountChange(id: number) {
accountId.value = id;
// 同步设置表单值
gridApi.formApi.setValues({ accountId: id });
await gridApi.formApi.submitForm();
}
async function handleRefresh() {
await gridApi.query();
}
/** 处理删除操作 */
async function handleDelete(id: number) {
await confirm('此操作将永久删除该文件, 是否继续?');
const hideLoading = message.loading({
content: '正在删除...',
duration: 0,
});
try {
await deletePermanentMaterial(id);
message.success('删除成功');
handleRefresh();
} finally {
hideLoading();
}
}
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
</template>
<Grid class="material-grid">
<template #form-accountId>
<WxAccountSelect @change="handleAccountChange" />
</template>
<template #toolbar-actions>
<Tabs v-model:active-key="type" class="w-full" @change="onTabChange">
<!-- tab 1图片 -->
<Tabs.TabPane :key="UploadType.Image">
<template #tab>
<span class="flex items-center">
<IconifyIcon icon="lucide:image" class="mr-1" />
图片
</span>
</template>
</Tabs.TabPane>
<!-- tab 2语音 -->
<Tabs.TabPane :key="UploadType.Voice">
<template #tab>
<span class="flex items-center">
<IconifyIcon icon="lucide:mic" class="mr-1" />
语音
</span>
</template>
</Tabs.TabPane>
<!-- tab 3视频 -->
<Tabs.TabPane :key="UploadType.Video">
<template #tab>
<span class="flex items-center">
<IconifyIcon icon="lucide:video" class="mr-1" />
视频
</span>
</template>
</Tabs.TabPane>
</Tabs>
</template>
<template #toolbar-tools>
<UploadFile
v-if="
hasAccessByCodes(['mp:material:upload-permanent']) &&
type === UploadType.Image
"
:type="UploadType.Image"
@uploaded="handleRefresh"
/>
<UploadFile
v-if="
hasAccessByCodes(['mp:material:upload-permanent']) &&
type === UploadType.Voice
"
:type="UploadType.Voice"
@uploaded="handleRefresh"
/>
<Button
v-if="
hasAccessByCodes(['mp:material:upload-permanent']) &&
type === UploadType.Video
"
type="primary"
@click="showCreateVideo = true"
>
新建视频
</Button>
</template>
<!-- 图片列的slot -->
<template #image="{ row }">
<div class="flex items-center justify-center" style="height: 192px">
<img
:src="row.url"
class="object-contain"
style="display: block; max-width: 100%; max-height: 192px"
/>
</div>
</template>
<!-- 语音列的slot -->
<template #voice="{ row }">
<audio :src="row.url" controls style="width: 160px"></audio>
</template>
<!-- 视频列的slot -->
<template #video="{ row }">
<video
:src="row.url"
controls
style="width: 200px; height: 150px"
></video>
</template>
<!-- 操作列的slot -->
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '删除',
icon: ACTION_ICON.delete,
color: 'danger',
auth: ['mp:material:delete'],
onClick: () => handleDelete(row.id),
},
]"
/>
</template>
</Grid>
<!-- 新建视频的弹窗 -->
<UploadVideo v-model:open="showCreateVideo" @uploaded="handleRefresh" />
</Page>
</template>