first commit
This commit is contained in:
427
src/components/MediaPreview/index.vue
Normal file
427
src/components/MediaPreview/index.vue
Normal file
@@ -0,0 +1,427 @@
|
||||
<template>
|
||||
<view v-if="visible" class="preview-overlay" @click="handleOverlayClick">
|
||||
<view class="preview-container">
|
||||
<!-- 返回按钮(与微信胶囊对齐) -->
|
||||
<view
|
||||
class="back-btn"
|
||||
:style="{ top: navButtonTop + 'px' }"
|
||||
@click.stop="handleClose"
|
||||
>
|
||||
<image src="/static/icons/Left (左).png" class="back-icon" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<!-- 媒体内容 -->
|
||||
<view class="media-wrapper" @click.stop>
|
||||
<!-- 视频预览 -->
|
||||
<video
|
||||
v-if="currentMedia.isVideo"
|
||||
:src="currentMedia.url"
|
||||
class="preview-video"
|
||||
controls
|
||||
autoplay
|
||||
object-fit="contain"
|
||||
/>
|
||||
|
||||
<!-- 图片预览 -->
|
||||
<swiper
|
||||
v-else
|
||||
class="preview-swiper"
|
||||
:current="currentIndex"
|
||||
@change="handleSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in mediaList" :key="index">
|
||||
<view class="swiper-item-wrapper">
|
||||
<image
|
||||
:src="item.url"
|
||||
class="preview-image"
|
||||
mode="aspectFit"
|
||||
@click.stop
|
||||
/>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<!-- 底部信息栏 -->
|
||||
<view class="info-bar" @click.stop>
|
||||
<view class="info-left">
|
||||
<text class="model-name">{{ currentMedia.modelName }}</text>
|
||||
<text class="create-time">{{ currentMedia.createTime }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 图片计数 -->
|
||||
<view class="page-indicator" v-if="!currentMedia.isVideo && mediaList.length > 1">
|
||||
<text class="indicator-text">{{ currentIndex + 1 }} / {{ mediaList.length }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<view class="icon-btn" @click.stop="handleDownload">
|
||||
<image src="/static/icons/To-bottom.png" class="btn-icon" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="icon-btn" @click.stop="handleDelete">
|
||||
<image src="/static/icons/del.png" class="btn-icon" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
|
||||
// 计算返回按钮的top位置(与微信胶囊对齐)
|
||||
const navButtonTop = ref(20)
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
// 获取微信胶囊按钮位置信息
|
||||
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
||||
if (menuButtonInfo) {
|
||||
// 返回按钮与胶囊按钮垂直居中对齐
|
||||
// 胶囊top + (胶囊高度 - 按钮高度) / 2
|
||||
navButtonTop.value = menuButtonInfo.top + (menuButtonInfo.height - 40) / 2
|
||||
}
|
||||
} catch (e) {
|
||||
// 获取失败时使用默认值
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
navButtonTop.value = (systemInfo.statusBarHeight || 20) + 6
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
// 非微信小程序使用状态栏高度
|
||||
try {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
navButtonTop.value = (systemInfo.statusBarHeight || 20) + 10
|
||||
} catch (e) {
|
||||
navButtonTop.value = 30
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
task: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
taskList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'download', 'delete'])
|
||||
|
||||
const currentIndex = ref(0)
|
||||
|
||||
// 当前媒体信息
|
||||
const currentMedia = computed(() => {
|
||||
if (!props.task) return {}
|
||||
|
||||
const isVideoTask = isVideo(props.task)
|
||||
// 视频任务使用视频URL,图片任务使用图片URL
|
||||
const url = isVideoTask ? getVideoUrl(props.task) : getTaskImage(props.task)
|
||||
return {
|
||||
url: url,
|
||||
isVideo: isVideoTask,
|
||||
modelName: props.task.modelName || '未知模型',
|
||||
createTime: formatTime(props.task.createdAt),
|
||||
taskId: props.task.id
|
||||
}
|
||||
})
|
||||
|
||||
// 媒体列表(仅图片)
|
||||
const mediaList = computed(() => {
|
||||
if (!props.task || isVideo(props.task)) return []
|
||||
|
||||
// 获取同一日期的所有图片任务
|
||||
const currentDate = formatDate(props.task.createdAt)
|
||||
return props.taskList
|
||||
.filter(t => {
|
||||
const taskDate = formatDate(t.createdAt)
|
||||
return taskDate === currentDate && !isVideo(t) && getTaskImage(t)
|
||||
})
|
||||
.map(t => ({
|
||||
url: getTaskImage(t),
|
||||
taskId: t.id,
|
||||
modelName: t.modelName,
|
||||
createTime: formatTime(t.createdAt)
|
||||
}))
|
||||
})
|
||||
|
||||
// 监听任务变化,更新当前索引
|
||||
watch(() => props.task, (newTask) => {
|
||||
if (newTask && !isVideo(newTask)) {
|
||||
const index = mediaList.value.findIndex(item => item.taskId === newTask.id)
|
||||
currentIndex.value = index >= 0 ? index : 0
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${month}月${day}日 ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
// 视频模型编码列表
|
||||
const videoModelCodes = ['grok-video', 'tencent-sora2-video', 'tencent-aigc-video', 'sora2-video']
|
||||
|
||||
// 判断是否为视频
|
||||
const isVideo = (task) => {
|
||||
// 优先通过模型编码判断
|
||||
if (task.modelCode && videoModelCodes.includes(task.modelCode)) {
|
||||
return true
|
||||
}
|
||||
if (!task.outputResult) return false
|
||||
try {
|
||||
const result = JSON.parse(task.outputResult)
|
||||
if (result.result && (result.result.endsWith('.mp4') || result.result.includes('.mp4') || result.result.includes('video'))) {
|
||||
return true
|
||||
}
|
||||
return !!result.videoUrl
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取视频URL(与图片分开)
|
||||
const getVideoUrl = (task) => {
|
||||
if (!task.outputResult) return null
|
||||
try {
|
||||
const result = JSON.parse(task.outputResult)
|
||||
return result.result || result.videoUrl || result.url || null
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 获取任务图片/视频
|
||||
const getTaskImage = (task) => {
|
||||
if (!task.outputResult) return null
|
||||
|
||||
try {
|
||||
const result = JSON.parse(task.outputResult)
|
||||
|
||||
if (result.coverUrl) return result.coverUrl
|
||||
if (result.result) return result.result
|
||||
if (result.videoUrl) return result.videoUrl
|
||||
if (result.imageUrl) return result.imageUrl
|
||||
if (result.images && result.images.length > 0) return result.images[0]
|
||||
if (result.url) return result.url
|
||||
if (result.image) return result.image
|
||||
} catch (e) {
|
||||
console.error('解析任务结果失败:', e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// 轮播图切换
|
||||
const handleSwiperChange = (e) => {
|
||||
currentIndex.value = e.detail.current
|
||||
}
|
||||
|
||||
// 关闭预览
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// 点击遮罩关闭
|
||||
const handleOverlayClick = () => {
|
||||
handleClose()
|
||||
}
|
||||
|
||||
// 下载
|
||||
const handleDownload = () => {
|
||||
const media = currentMedia.value
|
||||
if (!currentMedia.value.isVideo && mediaList.value.length > 0) {
|
||||
// 图片模式,下载当前图片
|
||||
const currentItem = mediaList.value[currentIndex.value]
|
||||
emit('download', currentItem.taskId)
|
||||
} else {
|
||||
// 视频模式
|
||||
emit('download', media.taskId)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = () => {
|
||||
const media = currentMedia.value
|
||||
if (!currentMedia.value.isVideo && mediaList.value.length > 0) {
|
||||
// 图片模式,删除当前图片
|
||||
const currentItem = mediaList.value[currentIndex.value]
|
||||
emit('delete', currentItem.taskId)
|
||||
} else {
|
||||
// 视频模式
|
||||
emit('delete', media.taskId)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.preview-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 返回按钮(top通过:style动态设置,与微信胶囊对齐) */
|
||||
.back-btn {
|
||||
position: absolute;
|
||||
top: 20px; /* 默认值,实际由:style覆盖 */
|
||||
left: 16px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* 媒体容器 */
|
||||
.media-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 视频预览 */
|
||||
.preview-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 图片轮播 */
|
||||
.preview-swiper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.swiper-item-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 底部信息栏 */
|
||||
.info-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.create-time {
|
||||
color: #a1a1aa;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 页码指示器 */
|
||||
.page-indicator {
|
||||
padding: 6px 12px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.indicator-text {
|
||||
color: #ffffff;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user