2023-04-14 15:22:23 +08:00

513 lines
15 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.

<template>
<doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form class="-mb-15px" :inline="true" label-width="68px">
<el-form-item label="公众号" prop="accountId">
<WxAccountSelect @change="onAccountChanged" />
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-tabs v-model="type" @tab-change="onTabChange">
<!-- tab 1图片 -->
<el-tab-pane name="image">
<template #label>
<span> <Icon icon="ep:picture" />图片 </span>
</template>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-upload
:action="uploadUrl"
:headers="headers"
multiple
:limit="1"
:file-list="fileList"
:data="uploadData"
:before-upload="beforeImageUpload"
:on-success="handleUploadSuccess"
>
<el-button type="primary" plain>点击上传</el-button>
<template #tip>
<span class="el-upload__tip" style="margin-left: 5px">
支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M
</span>
</template>
</el-upload>
</div>
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :key="item.id">
<a target="_blank" :href="item.url">
<img class="material-img" :src="item.url" />
<div class="item-name">{{ item.name }}</div>
</a>
<el-row justify="center">
<el-button
type="danger"
circle
@click="handleDelete(item)"
v-hasPermi="['mp:material:delete']"
>
<Icon icon="ep:delete" />
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-tab-pane>
<!-- tab 2语音 -->
<el-tab-pane name="voice">
<template #label>
<span> <Icon icon="ep:microphone" />语音 </span>
</template>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-upload
:action="uploadUrl"
:headers="headers"
multiple
:limit="1"
:file-list="fileList"
:data="uploadData"
:on-success="handleUploadSuccess"
:before-upload="beforeVoiceUpload"
>
<el-button type="primary" plain>点击上传</el-button>
<template #tip>
<span class="el-upload__tip" style="margin-left: 5px">
格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s
</span>
</template>
</el-upload>
</div>
<!-- 列表 -->
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="语音" align="center">
<template #default="scope">
<WxVoicePlayer v-if="scope.row.url" :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column
label="上传时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
>
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button type="primary" link plain @click="handleDownload(scope.row)">
<Icon icon="ep:download" />下载
</el-button>
<el-button
type="primary"
link
plain
@click="handleDelete(scope.row)"
v-hasPermi="['mp:material:delete']"
>
<Icon icon="ep:delete" />删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-tab-pane>
<!-- tab 3视频 -->
<el-tab-pane name="video">
<template #label>
<span> <Icon icon="ep:video-play" /> 视频 </span>
</template>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-button type="primary" plain @click="handleAddVideo">新建视频</el-button>
</div>
<!-- 新建视频的弹窗 -->
<el-dialog
title="新建视频"
v-model="dialogVideoVisible"
width="600px"
v-loading="addMaterialLoading"
>
<el-upload
:action="uploadUrl"
:headers="headers"
multiple
:limit="1"
:file-list="fileList"
:data="uploadData"
:before-upload="beforeVideoUpload"
:on-success="handleUploadSuccess"
ref="uploadVideoRef"
:auto-upload="false"
>
<template #trigger>
<el-button size="small" type="primary">选择视频</el-button>
</template>
<span class="el-upload__tip" style="margin-left: 10px"
>格式支持 MP4文件大小不超过 10MB</span
>
</el-upload>
<el-form :model="uploadData" :rules="uploadRules" ref="uploadFormRef" label-width="80px">
<el-row>
<el-form-item label="标题" prop="title">
<el-input
v-model="uploadData.title"
placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题"
/>
</el-form-item>
</el-row>
<el-row>
<el-form-item label="描述" prop="introduction">
<el-input
:rows="3"
type="textarea"
v-model="uploadData.introduction"
placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"
/>
</el-form-item>
</el-row>
</el-form>
<template #footer>
<el-button @click="cancelVideo"> </el-button>
<el-button type="primary" @click="submitVideo"> </el-button>
</template>
</el-dialog>
<!-- 列表 -->
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
<el-table-column label="编号" align="center" prop="mediaId" />
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="介绍" align="center" prop="introduction" />
<el-table-column label="视频" align="center">
<template #default="scope">
<WxVideoPlayer v-if="scope.row.url" :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column
label="上传时间"
align="center"
:formatter="dateFormatter"
prop="createTime"
width="180"
>
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" link plain @click="handleDownload(scope.row)">
<Icon icon="ep:download" />下载
</el-button>
<el-button
type="primary"
link
size="small"
plain
@click="handleDelete(scope.row)"
v-hasPermi="['mp:material:delete']"
>
<Icon icon="ep:delete" />删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script lang="ts" setup name="MpMaterial">
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
import * as MpMaterialApi from '@/api/mp/material'
import * as authUtil from '@/utils/auth'
import { dateFormatter } from '@/utils/formatTime'
import type {
FormInstance,
FormRules,
TabPaneName,
UploadInstance,
UploadProps,
UploadRawFile,
UploadUserFile
} from 'element-plus'
const BASE_URL = import.meta.env.VITE_BASE_URL
const uploadUrl = BASE_URL + '/admin-api/mp/material/upload-permanent'
const headers = { Authorization: 'Bearer ' + authUtil.getAccessToken() }
const message = useMessage()
const uploadFormRef = ref<FormInstance>()
const uploadVideoRef = ref<UploadInstance>()
const uploadRules: FormRules = {
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
}
// 素材类型
type MaterialType = 'image' | 'voice' | 'video'
const type = ref<MaterialType>('image')
const loading = ref(false) // 遮罩层
const list = ref<any[]>([]) // 总条数
const total = ref(0) // 数据列表
// 查询参数
interface QueryParams {
pageNo: number
pageSize: number
accountId?: number
permanent: boolean
}
const queryParams: QueryParams = reactive({
pageNo: 1,
pageSize: 10,
accountId: undefined,
permanent: true
})
const fileList = ref<UploadUserFile[]>([])
interface UploadData {
type: MaterialType
title: string
introduction: string
}
const uploadData: UploadData = reactive({
type: 'image',
title: '',
introduction: ''
})
// === 视频上传,独有变量 ===
const dialogVideoVisible = ref(false)
const addMaterialLoading = ref(false)
/** 侦听公众号变化 **/
const onAccountChanged = (id?: number) => {
queryParams.accountId = id
getList()
}
// ======================== 列表查询 ========================
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await MpMaterialApi.getMaterialPage({
...queryParams,
type: type.value
})
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const onTabChange = (tabName: TabPaneName) => {
// 设置 type
uploadData.type = tabName as MaterialType
// 提前情况数据避免tab切换后显示垃圾数据
list.value = []
total.value = 0
// 从第一页开始查询
handleQuery()
}
// ======================== 文件上传 ========================
const beforeUpload = (rawFile: UploadRawFile, category: 'image' | 'audio' | 'video'): boolean => {
let allowTypes: string[] = []
let maxSizeMB = 0
let name = ''
switch (category) {
case 'image':
allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg']
maxSizeMB = 2
name = '图片'
break
case 'audio':
allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr']
maxSizeMB = 2
name = '图片'
break
case 'video':
allowTypes = ['video/mp4']
maxSizeMB = 10
name = '视频'
}
if (!allowTypes.includes(rawFile.type)) {
message.error(`上传${name}格式不对!`)
return false
}
// 校验大小
if (rawFile.size / 1024 / 1024 > maxSizeMB) {
message.error(`上传${name}大小不能超过${maxSizeMB}M!`)
return false
}
loading.value = true
return true
}
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
beforeUpload(rawFile, 'image')
const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
beforeUpload(rawFile, 'audio')
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
beforeUpload(rawFile, 'video')
const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
loading.value = false
addMaterialLoading.value = false
if (response.code !== 0) {
message.error('上传出错:' + response.msg)
return false
}
// 清空上传时的各种数据
dialogVideoVisible.value = false
fileList.value = []
uploadData.title = ''
uploadData.introduction = ''
// 加载数据
getList()
}
// 下载文件
const handleDownload = (row: any) => {
window.open(row.url, '_blank')
}
// 提交 video 新建的表单
const submitVideo = () => {
uploadFormRef.value?.validate((valid) => {
if (!valid) {
return false
}
uploadVideoRef.value?.submit()
})
}
// 弹出 video 新建的表单
const handleAddVideo = () => {
resetVideo()
dialogVideoVisible.value = true
}
/** 取消按钮 */
const cancelVideo = () => {
dialogVideoVisible.value = false
resetVideo()
}
/** 表单重置 */
const resetVideo = () => {
fileList.value = []
uploadData.title = ''
uploadData.introduction = ''
uploadFormRef.value?.resetFields()
}
// ======================== 其它操作 ========================
const handleDelete = async (item: any) => {
await message.confirm('此操作将永久删除该文件, 是否继续?')
await MpMaterialApi.deletePermanentMaterial(item.id)
message.alertSuccess('删除成功')
}
</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;
}
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;
}
}
</style>