<template>
|
<div>
|
<el-dialog title="查看附件"
|
v-model="dialogVisitable" width="800px" @close="cancel">
|
<div class="upload-container">
|
<!-- 生产前 -->
|
<div class="form-container">
|
<div class="title">生产前</div>
|
|
<!-- 图片列表 -->
|
<div style="display: flex; flex-wrap: wrap;">
|
<img v-for="(item, index) in beforeProductionImgs" :key="index"
|
@click="showMedia(beforeProductionImgs, index, 'image')"
|
:src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
|
</div>
|
|
<!-- 视频列表 -->
|
<div style="display: flex; flex-wrap: wrap;">
|
<div
|
v-for="(videoUrl, index) in beforeProductionVideos"
|
:key="index"
|
@click="showMedia(beforeProductionVideos, index, 'video')"
|
style="position: relative; margin: 10px; cursor: pointer;"
|
>
|
<div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
|
<img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
|
</div>
|
<div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 生产后 -->
|
<div class="form-container">
|
<div class="title">生产后</div>
|
|
<!-- 图片列表 -->
|
<div style="display: flex; flex-wrap: wrap;">
|
<img v-for="(item, index) in afterProductionImgs" :key="index"
|
@click="showMedia(afterProductionImgs, index, 'image')"
|
:src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
|
</div>
|
|
<!-- 视频列表 -->
|
<div style="display: flex; flex-wrap: wrap;">
|
<div
|
v-for="(videoUrl, index) in afterProductionVideos"
|
:key="index"
|
@click="showMedia(afterProductionVideos, index, 'video')"
|
style="position: relative; margin: 10px; cursor: pointer;"
|
>
|
<div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
|
<img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
|
</div>
|
<div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 生产问题 -->
|
<div class="form-container">
|
<div class="title">生产问题</div>
|
|
<!-- 图片列表 -->
|
<div style="display: flex; flex-wrap: wrap;">
|
<img v-for="(item, index) in productionIssuesImgs" :key="index"
|
@click="showMedia(productionIssuesImgs, index, 'image')"
|
:src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
|
</div>
|
|
<!-- 视频列表 -->
|
<div style="display: flex; flex-wrap: wrap;">
|
<div
|
v-for="(videoUrl, index) in productionIssuesVideos"
|
:key="index"
|
@click="showMedia(productionIssuesVideos, index, 'video')"
|
style="position: relative; margin: 10px; cursor: pointer;"
|
>
|
<div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
|
<img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
|
</div>
|
<div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-dialog>
|
|
<!-- 统一媒体查看器 -->
|
<div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
|
<div class="media-viewer-content" @click.stop>
|
<!-- 图片 -->
|
<vue-easy-lightbox
|
v-if="mediaType === 'image'"
|
:visible="isMediaViewerVisible"
|
:imgs="mediaList"
|
:index="currentMediaIndex"
|
@hide="closeMediaViewer"
|
></vue-easy-lightbox>
|
|
<!-- 视频 -->
|
<div v-else-if="mediaType === 'video'" style="position: relative;">
|
<video
|
:src="mediaList[currentMediaIndex]"
|
autoplay
|
controls
|
style="max-width: 90vw; max-height: 80vh;"
|
/>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
<script setup>
|
import { ref } from 'vue';
|
import VueEasyLightbox from 'vue-easy-lightbox';
|
|
// 控制弹窗显示
|
const dialogVisitable = ref(false);
|
|
// 图片数组
|
const beforeProductionImgs = ref([]);
|
const afterProductionImgs = ref([]);
|
const productionIssuesImgs = ref([]);
|
|
// 视频数组
|
const beforeProductionVideos = ref([]);
|
const afterProductionVideos = ref([]);
|
const productionIssuesVideos = ref([]);
|
|
// 媒体查看器状态
|
const isMediaViewerVisible = ref(false);
|
const currentMediaIndex = ref(0);
|
const mediaList = ref([]); // 存储当前要查看的媒体列表(含图片和视频对象)
|
const mediaType = ref('image'); // image | video
|
|
// 处理 URL:将 Windows 路径转换为可访问的 URL
|
function processFileUrl(fileUrl) {
|
if (!fileUrl) return '';
|
|
const baseUrl = import.meta.env.VITE_APP_BASE_API;
|
|
// 如果 URL 是 Windows 路径格式(包含反斜杠),需要转换
|
if (fileUrl && fileUrl.indexOf('\\') > -1) {
|
// 查找 uploads 关键字的位置,从那里开始提取相对路径
|
const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
|
if (uploadsIndex > -1) {
|
// 从 uploads 开始提取路径,并将反斜杠替换为正斜杠
|
const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
|
fileUrl = '/' + relativePath;
|
} else {
|
// 如果没有找到 uploads,提取最后一个目录和文件名
|
const parts = fileUrl.split('\\');
|
const fileName = parts[parts.length - 1];
|
fileUrl = '/uploads/' + fileName;
|
}
|
}
|
|
// 确保所有非 http 开头的 URL 都拼接 baseUrl
|
if (fileUrl && !fileUrl.startsWith('http')) {
|
// 确保路径以 / 开头
|
if (!fileUrl.startsWith('/')) {
|
fileUrl = '/' + fileUrl;
|
}
|
// 拼接 baseUrl
|
fileUrl = baseUrl + fileUrl;
|
}
|
|
return fileUrl;
|
}
|
|
// 处理每一类数据:分离图片和视频
|
function processItems(items) {
|
const images = [];
|
const videos = [];
|
|
// 检查 items 是否存在且为数组
|
if (!items || !Array.isArray(items)) {
|
return { images, videos };
|
}
|
|
items.forEach(item => {
|
if (!item || !item.url) return;
|
|
// 处理文件 URL
|
const fileUrl = processFileUrl(item.url);
|
|
// 根据文件扩展名判断是图片还是视频
|
const urlLower = fileUrl.toLowerCase();
|
if (urlLower.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/)) {
|
images.push(fileUrl);
|
} else if (urlLower.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/)) {
|
videos.push(fileUrl);
|
} else if (item.contentType) {
|
// 如果有 contentType,使用 contentType 判断
|
if (item.contentType.startsWith('image/')) {
|
images.push(fileUrl);
|
} else if (item.contentType.startsWith('video/')) {
|
videos.push(fileUrl);
|
}
|
}
|
});
|
|
return { images, videos };
|
}
|
|
// 打开弹窗并加载数据
|
const openDialog = async (row) => {
|
// 使用正确的字段名:commonFileListBefore, commonFileListAfter
|
// productionIssues 可能不存在,使用空数组
|
const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBefore || []);
|
const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfter || []);
|
const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues || []);
|
|
beforeProductionImgs.value = beforeImgs;
|
beforeProductionVideos.value = beforeVids;
|
|
afterProductionImgs.value = afterImgs;
|
afterProductionVideos.value = afterVids;
|
|
productionIssuesImgs.value = issueImgs;
|
productionIssuesVideos.value = issueVids;
|
|
dialogVisitable.value = true;
|
};
|
|
// 显示媒体(图片 or 视频)
|
function showMedia(mediaArray, index, type) {
|
mediaList.value = mediaArray;
|
currentMediaIndex.value = index;
|
mediaType.value = type;
|
isMediaViewerVisible.value = true;
|
}
|
|
// 关闭媒体查看器
|
function closeMediaViewer() {
|
isMediaViewerVisible.value = false;
|
mediaList.value = [];
|
mediaType.value = 'image';
|
}
|
|
// 表单关闭方法
|
const cancel = () => {
|
dialogVisitable.value = false;
|
};
|
|
defineExpose({ openDialog });
|
</script>
|
<style scoped lang="scss">
|
.upload-container {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
padding: 20px;
|
border: 1px solid #dcdfe6;
|
box-sizing: border-box;
|
|
.form-container {
|
flex: 1;
|
width: 100%;
|
margin-bottom: 20px;
|
}
|
}
|
|
.title {
|
font-size: 14px;
|
color: #165dff;
|
line-height: 20px;
|
font-weight: 600;
|
padding-left: 10px;
|
position: relative;
|
margin: 6px 0;
|
|
&::before {
|
content: "";
|
position: absolute;
|
left: 0;
|
top: 3px;
|
width: 4px;
|
height: 14px;
|
background-color: #165dff;
|
}
|
}
|
|
.media-viewer-overlay {
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background-color: rgba(0, 0, 0, 0.8);
|
z-index: 9999;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.media-viewer-content {
|
position: relative;
|
max-width: 90vw;
|
max-height: 90vh;
|
overflow: hidden;
|
}
|
</style>
|