修复
This commit is contained in:
115
frontend/app/web-gold/src/components/VideoPreviewModal.vue
Normal file
115
frontend/app/web-gold/src/components/VideoPreviewModal.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
width="auto"
|
||||
centered
|
||||
@cancel="handleClose"
|
||||
class="video-preview-modal"
|
||||
>
|
||||
<div class="video-container">
|
||||
<video
|
||||
v-if="visible && videoUrl"
|
||||
ref="videoRef"
|
||||
:src="videoUrl"
|
||||
controls
|
||||
autoplay
|
||||
class="preview-video"
|
||||
>
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
videoUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '视频预览'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:open'])
|
||||
|
||||
const videoRef = ref(null)
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.open,
|
||||
set: (val) => emit('update:open', val)
|
||||
})
|
||||
|
||||
const handleClose = () => {
|
||||
// 关闭时暂停视频
|
||||
if (videoRef.value) {
|
||||
videoRef.value.pause()
|
||||
}
|
||||
visible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.video-preview-modal {
|
||||
:deep(.ant-modal-content) {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.ant-modal-header) {
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-modal-body) {
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.video-container {
|
||||
background: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-video {
|
||||
max-width: 800px;
|
||||
max-height: 70vh;
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// 响应式:小屏幕
|
||||
@media (max-width: 768px) {
|
||||
.video-preview-modal {
|
||||
:deep(.ant-modal-content) {
|
||||
max-width: 95vw;
|
||||
max-height: 85vh;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-video {
|
||||
max-width: 100%;
|
||||
max-height: 60vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -62,6 +62,10 @@
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="video-duration">{{ formatDuration(video.duration) }}</div>
|
||||
<!-- hover 播放按钮 -->
|
||||
<div class="video-play-btn" @click.stop="handlePreview(video)">
|
||||
<PlayCircleOutlined />
|
||||
</div>
|
||||
<div class="video-selected-mark" v-if="selectedVideoId === video.id">
|
||||
<CheckOutlined />
|
||||
</div>
|
||||
@@ -127,6 +131,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 视频预览弹窗 -->
|
||||
<VideoPreviewModal
|
||||
v-model:open="previewVisible"
|
||||
:video-url="previewUrl"
|
||||
:title="previewVideo?.fileName || '视频预览'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -138,9 +149,11 @@ import {
|
||||
VideoCameraOutlined,
|
||||
ClockCircleOutlined,
|
||||
SearchOutlined,
|
||||
CloseOutlined
|
||||
CloseOutlined,
|
||||
PlayCircleOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { MaterialService } from '@/api/material'
|
||||
import VideoPreviewModal from '@/components/VideoPreviewModal.vue'
|
||||
|
||||
const props = defineProps({
|
||||
open: {
|
||||
@@ -169,6 +182,11 @@ const defaultCover = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9
|
||||
|
||||
const modalTitle = '选择视频'
|
||||
|
||||
// 视频预览状态
|
||||
const previewVisible = ref(false)
|
||||
const previewUrl = ref('')
|
||||
const previewVideo = ref(null)
|
||||
|
||||
// 获取视频列表
|
||||
const fetchVideoList = async () => {
|
||||
loading.value = true
|
||||
@@ -225,6 +243,35 @@ const selectVideo = (video) => {
|
||||
selectedVideo.value = video
|
||||
}
|
||||
|
||||
// 预览视频
|
||||
const handlePreview = async (video) => {
|
||||
if (!video.id) {
|
||||
message.warning('视频信息不完整')
|
||||
return
|
||||
}
|
||||
|
||||
// 显示加载提示
|
||||
const hideLoading = message.loading('正在获取视频地址...', 0)
|
||||
|
||||
try {
|
||||
// 调用后端API获取带签名的视频播放URL
|
||||
const res = await MaterialService.getVideoPlayUrl(video.id)
|
||||
hideLoading()
|
||||
|
||||
if (res.code === 0 && res.data) {
|
||||
previewVideo.value = video
|
||||
previewUrl.value = res.data
|
||||
previewVisible.value = true
|
||||
} else {
|
||||
message.error(res.msg || '获取视频地址失败')
|
||||
}
|
||||
} catch (error) {
|
||||
hideLoading()
|
||||
console.error('获取视频播放URL失败:', error)
|
||||
message.error('获取视频地址失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
const handleImageError = (event) => {
|
||||
event.target.src = defaultCover
|
||||
}
|
||||
@@ -239,7 +286,7 @@ const formatDuration = (seconds) => {
|
||||
const formatFileSize = (bytes) => {
|
||||
if (!bytes) return '0 B'
|
||||
const units = ['B', 'KB', 'MB', 'GB']
|
||||
const index = Math.floor(Math.log2(bytes) / 10)
|
||||
const index = Math.floor(Math.log(bytes) / Math.log(1024))
|
||||
const size = bytes / Math.pow(1024, index)
|
||||
return `${size.toFixed(1)} ${units[index]}`
|
||||
}
|
||||
@@ -436,6 +483,27 @@ watch(() => props.open, (isOpen) => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* hover 播放按钮 */
|
||||
.video-play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.video-play-btn .anticon {
|
||||
font-size: 40px;
|
||||
color: white;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
|
||||
.video-thumbnail:hover .video-play-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 信息区域样式 */
|
||||
.video-info {
|
||||
padding: 14px;
|
||||
|
||||
Reference in New Issue
Block a user