Industrial-image-management.../src/views/operation-platform/data-processing/data-storage/components/PreviewModal.vue

338 lines
7.1 KiB
Vue

<template>
<!-- 图片预览模态框 -->
<a-modal
v-model:visible="previewModalVisible"
:width="1000"
:footer="false"
title="多媒体预览"
class="preview-modal"
@close="handleModalClose"
>
<div class="preview-container">
<!-- 图片预览区域 -->
<div class="image-section">
<img
:src="currentPreviewItem?.url"
:alt="currentPreviewItem?.name"
class="preview-image"
/>
</div>
<!-- 音频列表区域 -->
<div class="audio-section">
<h3 class="audio-title">关联音频 ({{ audioList.length }})</h3>
<div class="audio-list">
<div
v-for="(audio, index) in audioList"
:key="index"
class="audio-item"
:class="{ 'active': currentAudioIndex === index }"
>
<div class="audio-info">
<span class="audio-name">{{ audio.audioId || `音频 ${index + 1}` }}</span>
<span class="audio-duration">{{ formatDuration(audio.duration) }}</span>
</div>
<div class="audio-controls">
<a-button
size="mini"
@click="toggleAudio(index)"
:loading="audioLoading && currentAudioIndex === index"
>
{{ currentAudioIndex === index && isPlaying ? '暂停' : '播放' }}
</a-button>
<a-progress
v-if="currentAudioIndex === index"
:percent="playProgress"
:show-text="false"
size="small"
class="progress-bar"
/>
</div>
<!-- 隐藏的audio元素 -->
<audio
ref="audioPlayers"
:src="audio.url"
@timeupdate="handleTimeUpdate"
@ended="handleAudioEnded"
preload="metadata"
/>
</div>
</div>
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref, computed, nextTick } from 'vue'
import { Message } from '@arco-design/web-vue'
interface AudioItem {
url: string
name?: string
duration?: number // 单位秒
}
interface PreviewItem {
url: string
name: string
audios?: AudioItem[]
}
// 模态框显示状态
const previewModalVisible = ref(false)
const currentPreviewItem = ref<PreviewItem | null>(null)
// 音频相关状态
const audioList = ref<AudioItem[]>([])
const currentAudioIndex = ref(-1)
const isPlaying = ref(false)
const audioLoading = ref(false)
const playProgress = ref(0)
const audioPlayers = ref<HTMLAudioElement[]>([])
const getAudioUrl = (filePath: string): string => {
if (!filePath) return ''
if (filePath.startsWith('http')) return filePath
const baseUrl = 'http://pms.dtyx.net:9158'
return `${baseUrl}${filePath}`
}
// 打开预览模态框
const openPreview = (item: PreviewItem) => {
currentPreviewItem.value = item
audioList.value = []
if(item.audios){
for (const audio of item.audios) {
let temp={
audioId:audio.audioId,
url:getAudioUrl(audio.filePath)
}
audioList.value.push(temp)
}
}
previewModalVisible.value = true
resetAudioState()
// 预加载音频时长
nextTick(() => {
loadAudioDurations()
})
}
// 关闭模态框
const handleModalClose = () => {
stopAudio()
previewModalVisible.value = false
}
// 重置音频状态
const resetAudioState = () => {
currentAudioIndex.value = -1
isPlaying.value = false
playProgress.value = 0
}
// 加载音频时长信息
const loadAudioDurations = () => {
audioPlayers.value.forEach((player, index) => {
player.onloadedmetadata = () => {
if (!audioList.value[index].duration) {
audioList.value[index].duration = player.duration
}
}
})
}
// 切换音频播放状态
const toggleAudio = (index: number) => {
if (currentAudioIndex.value === index) {
// 同一音频,切换播放/暂停
if (isPlaying.value) {
pauseAudio()
} else {
playAudio(index)
}
} else {
// 切换不同音频
stopAudio()
playAudio(index)
}
}
// 播放音频
const playAudio = (index: number) => {
audioLoading.value = true
currentAudioIndex.value = index
const player = audioPlayers.value[index]
player.play()
.then(() => {
isPlaying.value = true
audioLoading.value = false
})
.catch(err => {
console.error('播放失败:', err)
Message.error('音频播放失败')
audioLoading.value = false
})
}
// 暂停音频
const pauseAudio = () => {
const player = audioPlayers.value[currentAudioIndex.value]
player.pause()
isPlaying.value = false
}
// 停止音频
const stopAudio = () => {
if (currentAudioIndex.value !== -1) {
const player = audioPlayers.value[currentAudioIndex.value]
player.pause()
player.currentTime = 0
}
isPlaying.value = false
currentAudioIndex.value = -1
playProgress.value = 0
}
// 音频时间更新
const handleTimeUpdate = (e: Event) => {
const player = e.target as HTMLAudioElement
if (player.duration) {
playProgress.value = (player.currentTime / player.duration) * 100
}
}
// 音频播放结束
const handleAudioEnded = () => {
isPlaying.value = false
playProgress.value = 100
}
// 格式化时长显示
const formatDuration = (seconds?: number) => {
if (!seconds) return '--:--'
const mins = Math.floor(seconds / 60)
const secs = Math.floor(seconds % 60)
return `${mins}:${secs < 10 ? '0' : ''}${secs}`
}
// 暴露方法供父组件调用
defineExpose({
openPreview
})
</script>
<style scoped lang="less">
.preview-modal {
:deep(.arco-modal-content) {
padding: 0;
}
}
.preview-container {
display: flex;
height: 70vh;
max-height: 800px;
}
.image-section {
flex: 1;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
overflow: hidden;
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
}
.audio-section {
width: 350px;
border-left: 1px solid #e5e5e5;
display: flex;
flex-direction: column;
.audio-title {
padding: 16px;
margin: 0;
font-size: 16px;
border-bottom: 1px solid #e5e5e5;
}
.audio-list {
flex: 1;
overflow-y: auto;
padding: 8px 0;
}
.audio-item {
padding: 12px 16px;
transition: background-color 0.2s;
&.active {
background-color: #f0f7ff;
}
&:hover {
background-color: #f9f9f9;
}
}
.audio-info {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
.audio-name {
font-weight: 500;
}
.audio-duration {
color: #8c8c8c;
font-size: 12px;
}
}
.audio-controls {
display: flex;
align-items: center;
gap: 12px;
.progress-bar {
flex: 1;
}
}
}
@media (max-width: 1200px) {
.preview-container {
flex-direction: column;
height: 80vh;
}
.image-section {
height: 60%;
}
.audio-section {
width: 100%;
height: 40%;
border-left: none;
border-top: 1px solid #e5e5e5;
}
}
</style>