Files
EveryPublish/sau_frontend/src/views/MaterialManagement.vue
T
2025-06-03 21:29:53 +08:00

448 lines
12 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>
<div class="material-management">
<div class="page-header">
<h1>素材管理</h1>
</div>
<div class="material-list-container">
<div class="material-search">
<el-input
v-model="searchKeyword"
placeholder="输入文件名搜索"
prefix-icon="Search"
clearable
@clear="handleSearch"
@input="handleSearch"
/>
<div class="action-buttons">
<el-button type="primary" @click="handleUploadMaterial">上传素材</el-button>
<el-button type="info" @click="fetchMaterials" :loading="false">
<el-icon :class="{ 'is-loading': isRefreshing }"><Refresh /></el-icon>
<span v-if="isRefreshing">刷新中</span>
</el-button>
</div>
</div>
<div v-if="filteredMaterials.length > 0" class="material-list">
<el-table :data="filteredMaterials" style="width: 100%">
<el-table-column prop="filename" label="文件名" width="300" />
<el-table-column prop="filesize" label="文件大小" width="120">
<template #default="scope">
{{ scope.row.filesize }} MB
</template>
</el-table-column>
<el-table-column prop="upload_time" label="上传时间" width="180" />
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" @click="handlePreview(scope.row)">预览</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div v-else class="empty-data">
<el-empty description="暂无素材数据" />
</div>
</div>
<!-- 上传对话框 -->
<el-dialog
v-model="uploadDialogVisible"
title="上传素材"
width="40%"
@close="handleUploadDialogClose"
>
<div class="upload-form">
<el-form label-width="80px">
<el-form-item label="文件名称:">
<el-input
v-model="customFilename"
placeholder="选填"
clearable
/>
</el-form-item>
<el-form-item label="选择文件">
<el-upload
class="upload-demo"
drag
:auto-upload="false"
:on-change="handleFileChange"
:file-list="fileList"
:limit="1"
>
<el-icon class="el-icon--upload"><Upload /></el-icon>
<div class="el-upload__text">
将文件拖到此处,或<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持视频、图片等格式文件,只能上传一个文件
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="uploadDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitUpload" :loading="isUploading">
{{ isUploading ? '上传中' : '确认上传' }}
</el-button>
</div>
</template>
</el-dialog>
<!-- 预览对话框 -->
<el-dialog
v-model="previewDialogVisible"
title="素材预览"
width="50%"
:top="'10vh'"
>
<div class="preview-container" v-if="currentMaterial">
<div v-if="isVideoFile(currentMaterial.filename)" class="video-preview">
<video controls style="max-width: 100%; max-height: 60vh;">
<source :src="getPreviewUrl(currentMaterial.file_path)" type="video/mp4">
您的浏览器不支持视频播放
</video>
</div>
<div v-else-if="isImageFile(currentMaterial.filename)" class="image-preview">
<img :src="getPreviewUrl(currentMaterial.file_path)" style="max-width: 100%; max-height: 60vh;" />
</div>
<div v-else class="file-info">
<p>文件名: {{ currentMaterial.filename }}</p>
<p>文件大小: {{ currentMaterial.filesize }} MB</p>
<p>上传时间: {{ currentMaterial.upload_time }}</p>
<el-button type="primary" @click="downloadFile(currentMaterial)">下载文件</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { Refresh, Upload } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { materialApi } from '@/api/material'
import { useAppStore } from '@/stores/app'
// 获取应用状态管理
const appStore = useAppStore()
// 搜索和状态控制
const searchKeyword = ref('')
const isRefreshing = ref(false)
const isUploading = ref(false)
// 对话框控制
const uploadDialogVisible = ref(false)
const previewDialogVisible = ref(false)
const currentMaterial = ref(null)
// 文件上传
const fileList = ref([])
const customFilename = ref('')
// 获取素材列表
const fetchMaterials = async () => {
isRefreshing.value = true
try {
const response = await materialApi.getAllMaterials()
if (response.code === 200) {
appStore.setMaterials(response.data)
ElMessage.success('刷新成功')
} else {
ElMessage.error('获取素材列表失败')
}
} catch (error) {
console.error('获取素材列表出错:', error)
ElMessage.error('获取素材列表失败')
} finally {
isRefreshing.value = false
}
}
// 过滤素材
const filteredMaterials = computed(() => {
if (!searchKeyword.value) return appStore.materials
const keyword = searchKeyword.value.toLowerCase()
return appStore.materials.filter(material =>
material.filename.toLowerCase().includes(keyword)
)
})
// 搜索处理
const handleSearch = () => {
// 搜索逻辑已通过计算属性实现
}
// 上传素材
const handleUploadMaterial = () => {
// 清空变量
fileList.value = []
customFilename.value = ''
uploadDialogVisible.value = true
}
// 关闭上传对话框时清空变量
const handleUploadDialogClose = () => {
fileList.value = []
customFilename.value = ''
}
// 文件选择变更
const handleFileChange = (file, uploadFileList) => {
// 只保留最新选择的文件
console.log('选择的文件:', file)
if (file.raw) {
// 确保获取到原始文件对象
fileList.value = [file]
}
}
// 提交上传
const submitUpload = async () => {
if (fileList.value.length === 0) {
ElMessage.warning('请选择要上传的文件')
return
}
// 确保文件对象存在
const fileObj = fileList.value[0]
if (!fileObj || !fileObj.raw) {
ElMessage.warning('文件对象无效,请重新选择文件')
return
}
isUploading.value = true
try {
// 使用FormData进行表单提交
const formData = new FormData()
// 添加文件,确保使用正确的文件对象
console.log('上传文件对象:', fileObj.raw)
formData.append('file', fileObj.raw)
// 如果用户输入了自定义文件名,则添加到表单中
if (customFilename.value.trim()) {
formData.append('filename', customFilename.value.trim())
console.log('自定义文件名:', customFilename.value.trim())
}
const response = await materialApi.uploadMaterial(formData)
if (response.code === 200) {
ElMessage.success('上传成功')
uploadDialogVisible.value = false
// 上传成功后直接刷新素材列表
await fetchMaterials()
} else {
ElMessage.error(response.msg || '上传失败')
}
} catch (error) {
console.error('上传素材出错:', error)
ElMessage.error('上传失败: ' + (error.message || '未知错误'))
} finally {
isUploading.value = false
}
}
// 预览素材
const handlePreview = async (material) => {
currentMaterial.value = null
previewDialogVisible.value = true
ElMessage.info('加载中...')
try {
// 等待一小段时间以确保对话框已打开
await new Promise(resolve => setTimeout(resolve, 100))
currentMaterial.value = material
} catch (error) {
console.error('预览素材出错:', error)
ElMessage.error('预览加载失败')
previewDialogVisible.value = false
}
}
// 删除素材
const handleDelete = (material) => {
ElMessageBox.confirm(
`确定要删除素材 ${material.filename} 吗?`,
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
try {
const response = await materialApi.deleteMaterial(material.id)
if (response.code === 200) {
appStore.removeMaterial(material.id)
ElMessage.success('删除成功')
} else {
ElMessage.error(response.msg || '删除失败')
}
} catch (error) {
console.error('删除素材出错:', error)
ElMessage.error('删除失败')
}
})
.catch(() => {
// 取消删除
})
}
// 获取预览URL
const getPreviewUrl = (filePath) => {
const filename = filePath.split('/').pop()
return materialApi.getMaterialPreviewUrl(filename)
}
// 下载文件
const downloadFile = (material) => {
const url = materialApi.downloadMaterial(material.file_path)
window.open(url, '_blank')
}
// 判断文件类型
const isVideoFile = (filename) => {
const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
return videoExtensions.some(ext => filename.toLowerCase().endsWith(ext))
}
const isImageFile = (filename) => {
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
return imageExtensions.some(ext => filename.toLowerCase().endsWith(ext))
}
// 组件挂载时获取素材列表
onMounted(() => {
// 只有store中没有数据时才获取
if (appStore.materials.length === 0) {
fetchMaterials()
}
})
</script>
<style lang="scss" scoped>
@use '@/styles/variables.scss' as *;
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.material-management {
.page-header {
margin-bottom: 20px;
h1 {
font-size: 24px;
font-weight: 500;
color: $text-primary;
margin: 0;
}
}
.material-list-container {
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
padding: 20px;
.material-search {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.el-input {
width: 300px;
}
.action-buttons {
display: flex;
gap: 10px;
.is-loading {
animation: rotate 1s linear infinite;
}
}
}
.material-list {
margin-top: 20px;
}
.empty-data {
padding: 40px 0;
}
}
.material-upload {
width: 100%;
}
.preview-container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 0 20px;
.file-info {
text-align: center;
margin-top: 20px;
}
}
}
.upload-form {
padding: 0 20px;
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 5px;
}
.upload-demo {
width: 100%;
}
}
.dialog-footer {
padding: 0 20px;
display: flex;
justify-content: flex-end;
}
/* 覆盖Element Plus对话框样式 */
:deep(.el-dialog__body) {
padding: 20px 0;
}
:deep(.el-dialog__header) {
padding-left: 20px;
padding-right: 20px;
margin-right: 0;
}
:deep(.el-dialog__footer) {
padding-top: 10px;
padding-bottom: 15px;
}
</style>