WholeProcessPlatform/frontend-sjgl/src/components/previewMedia/index.vue
2026-07-20 16:02:51 +08:00

316 lines
8.0 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>
<!-- 媒体预览 Modal -->
<a-modal
:open="visible"
:title="videoPreviewTitle"
:footer="null"
width="900px"
@cancel="closeMediaPreview"
:zIndex="2000"
>
<div class="flex h-[60vh] gap-4">
<!-- 左侧混合列表 (图片+视频) -->
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
<div
v-for="(item, index) in previewList"
:key="index"
class="mb-2 cursor-pointer list-item"
:class="{ select: index == currentMediaIndex }"
@click="currentMediaIndex = index"
>
<span class="file-name">{{ item.name }}</span>
<!-- 删除按钮 -->
<div
class="list-item-delete"
v-if="isDel"
@click.stop="handleDeleteMedia(item, index)"
>
<CloseCircleOutlined />
</div>
</div>
</div>
<!-- 右侧:动态预览区域 -->
<div
class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4"
>
<a-spin v-if="imageLoading" tip="图片加载中..." size="large" />
<!-- 图片预览 -->
<div
v-if="
currentMediaItem &&
currentMediaItem.url != '' &&
currentMediaItem.type === 'image'
"
>
<img
v-show="!imageLoading && !imageLoadError"
:src="currentMediaItem.url"
alt="preview"
class="max-w-full max-h-[50vh] object-contain"
@load="onImageLoad"
@error="onImageError"
/>
<!-- 3. 加载失败提示 -->
<div
v-if="!imageLoading && imageLoadError"
class="text-gray-400 flex flex-col items-center"
>
<ExclamationCircleOutlined style="font-size: 24px; margin-bottom: 8px" />
<span>图片加载失败</span>
</div>
</div>
<div
v-else-if="
currentMediaItem &&
currentMediaItem.url == '' &&
currentMediaItem.type === 'image'
"
class="text-gray-400"
>
暂无图片预览
</div>
<!-- 视频预览 -->
<video
v-else-if="
currentMediaItem &&
currentMediaItem.url != '' &&
currentMediaItem.type === 'video'
"
:src="currentMediaItem.url"
controls
autoplay
class="max-w-full max-h-[50vh]"
>
您的浏览器不支持视频播放
</video>
<div
v-else-if="
currentMediaItem &&
currentMediaItem.url == '' &&
currentMediaItem.type === 'video'
"
class="text-gray-400"
>
暂无视频预览
</div>
<!-- 底部切换控制 -->
<div class="absolute bottom-4 flex gap-4 z-10">
<a-button
shape="circle"
:icon="h(LeftOutlined)"
@click="prevMedia"
:disabled="currentMediaIndex === 0"
/>
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
</span>
<a-button
shape="circle"
:icon="h(RightOutlined)"
@click="nextMedia"
:disabled="currentMediaIndex === previewList.length - 1"
/>
</div>
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref, nextTick, computed, watch, h } from "vue";
import { message, Modal } from "ant-design-vue"; // 假设使用 ant-design-vue
import {
LeftOutlined,
RightOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined,
} from "@ant-design/icons-vue";
import { deleteFile } from "@/api/guoYuSheShiShuJuTianBao";
// 图片加载状态
const imageLoading = ref(false);
const imageLoadError = ref(false);
interface MediaItem {
id: string;
type: "image" | "video";
name: string;
url: string;
}
const props = defineProps<{
visible: boolean;
videoPreviewTitle: string;
previewList: MediaItem[];
previewListIndex: number;
taskId?: string;
isDel?: boolean;
}>();
const emit = defineEmits(["update:visible", "deleteMedia"]);
const currentMediaIndex = ref(props.previewListIndex);
// 计算当前选中的项
const currentMediaItem = computed(() => props.previewList[currentMediaIndex.value]);
const closeMediaPreview = () => {
emit("update:visible", false);
nextTick(() => {
currentMediaIndex.value = 0;
});
};
// 【新增】统一切换逻辑
const prevMedia = () => {
if (currentMediaIndex.value > 0) {
currentMediaIndex.value--;
}
};
// 【新增】统一下一逻辑
const nextMedia = () => {
if (currentMediaIndex.value < props.previewList.length - 1) {
currentMediaIndex.value++;
}
};
// 【新增】统一删除逻辑
const handleDeleteMedia = (item: any, index: number) => {
Modal.confirm({
title: "确认删除",
content: "确定要从预览列表中移除该项吗?",
zIndex: 2002,
onOk: async () => {
return new Promise(async (resolve, reject) => {
const params = {
id: item.id,
taskId: props.taskId,
type:
props.videoPreviewTitle == "图片预览"
? 1
: props.videoPreviewTitle == "视频预览"
? 2
: 0,
filename: item.name,
};
try {
let res: any = await deleteFile(params);
if (res && res?.code == 0) {
message.success("删除成功");
emit("deleteMedia", item, props.videoPreviewTitle, index);
if (props.previewList.length === 0) {
closeMediaPreview();
} else {
// 调整索引
if (index <= currentMediaIndex.value) {
currentMediaIndex.value = Math.max(0, currentMediaIndex.value - 1);
}
}
resolve(true);
} else {
message.error("删除失败");
reject();
}
} catch (e) {
message.error("删除失败");
reject();
}
}).catch(() => console.log("Oops errors!"));
},
});
};
// 【新增】图片加载完成回调
const onImageLoad = () => {
imageLoading.value = false;
imageLoadError.value = false;
};
// 【新增】图片加载失败回调
const onImageError = () => {
imageLoading.value = false;
imageLoadError.value = true;
};
watch(
() => props.previewListIndex,
(newIndex) => {
currentMediaIndex.value = newIndex;
}
);
watch(
() => props.visible,
(newVisible) => {
if (newVisible) {
if (props.videoPreviewTitle == "图片预览") {
imageLoading.value = true;
imageLoadError.value = false;
}
}
}
);
watch(currentMediaIndex, (newIndex) => {
const item = props.previewList[newIndex];
if (item && item.type === "image") {
imageLoading.value = true;
imageLoadError.value = false;
} else {
imageLoading.value = false;
imageLoadError.value = false;
}
});
</script>
<style scoped lang="scss">
.media-list-container {
background-color: rgb(234, 241, 251);
padding: 10px;
.list-item {
width: 100%;
height: 36px;
line-height: 36px;
padding: 0 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background-color: rgba(255, 255, 255, 0.5);
}
.file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
&.select {
color: #ffffff;
background-color: rgb(37, 93, 138);
}
.list-item-delete {
color: red;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
margin-left: 8px;
&:hover {
transform: scale(1.1);
}
}
&:hover .list-item-delete {
opacity: 1;
}
}
}
</style>