| | |
| | | <template> |
| | | <div> |
| | | <el-dialog title="查看附件" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | v-model="dialogVisitable" |
| | | width="800px" |
| | | @close="cancel"> |
| | | <div class="upload-container"> |
| | | <!-- 生产前 --> |
| | | <div class="form-container"> |
| | | <div class="title">生产前</div> |
| | | |
| | | <div class="title">附件列表</div> |
| | | <!-- 图片列表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in beforeProductionImgs" :key="index" |
| | | <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=""> |
| | | :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" |
| | | <div v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="index" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | > |
| | | 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;" /> |
| | | <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> |
| | | </el-dialog> |
| | | |
| | | <!-- 统一媒体查看器 --> |
| | | <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> |
| | | <div class="media-viewer-content" @click.stop> |
| | | <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'" |
| | | <vue-easy-lightbox v-if="mediaType === 'image'" |
| | | :visible="isMediaViewerVisible" |
| | | :imgs="mediaList" |
| | | :index="currentMediaIndex" |
| | | @hide="closeMediaViewer" |
| | | ></vue-easy-lightbox> |
| | | |
| | | @hide="closeMediaViewer"></vue-easy-lightbox> |
| | | <!-- 视频 --> |
| | | <div v-else-if="mediaType === 'video'" style="position: relative;"> |
| | | <video |
| | | :src="mediaList[currentMediaIndex]" |
| | | <div v-else-if="mediaType === 'video'" |
| | | style="position: relative;"> |
| | | <video :src="mediaList[currentMediaIndex]" |
| | | autoplay |
| | | controls |
| | | style="max-width: 90vw; max-height: 80vh;" |
| | | /> |
| | | style="max-width: 90vw; max-height: 80vh;" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import VueEasyLightbox from 'vue-easy-lightbox'; |
| | | import { ref } from "vue"; |
| | | import VueEasyLightbox from "vue-easy-lightbox"; |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // 控制弹窗显示 |
| | |
| | | const isMediaViewerVisible = ref(false); |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); // 存储当前要查看的媒体列表(含图片和视频对象) |
| | | const mediaType = ref('image'); // image | video |
| | | const mediaType = ref("image"); // image | video |
| | | const javaApi = proxy.javaApi; |
| | | |
| | | // 处理 URL:将 Windows 路径转换为可访问的 URL |
| | | function processFileUrl(fileUrl) { |
| | | if (!fileUrl) return ''; |
| | | if (!fileUrl) return ""; |
| | | |
| | | // 如果 URL 是 Windows 路径格式(包含反斜杠),需要转换 |
| | | if (fileUrl && fileUrl.indexOf('\\') > -1) { |
| | | if (fileUrl && fileUrl.indexOf("\\") > -1) { |
| | | // 查找 uploads 关键字的位置,从那里开始提取相对路径 |
| | | const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads'); |
| | | const uploadsIndex = fileUrl.toLowerCase().indexOf("uploads"); |
| | | if (uploadsIndex > -1) { |
| | | // 从 uploads 开始提取路径,并将反斜杠替换为正斜杠 |
| | | const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/'); |
| | | fileUrl = '/' + relativePath; |
| | | const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, "/"); |
| | | fileUrl = "/" + relativePath; |
| | | } else { |
| | | // 如果没有找到 uploads,提取最后一个目录和文件名 |
| | | const parts = fileUrl.split('\\'); |
| | | const parts = fileUrl.split("\\"); |
| | | const fileName = parts[parts.length - 1]; |
| | | fileUrl = '/uploads/' + fileName; |
| | | fileUrl = "/uploads/" + fileName; |
| | | } |
| | | } |
| | | |
| | | // 确保所有非 http 开头的 URL 都拼接 baseUrl |
| | | if (fileUrl && !fileUrl.startsWith('http')) { |
| | | if (fileUrl && !fileUrl.startsWith("http")) { |
| | | // 确保路径以 / 开头 |
| | | if (!fileUrl.startsWith('/')) { |
| | | fileUrl = '/' + fileUrl; |
| | | if (!fileUrl.startsWith("/")) { |
| | | fileUrl = "/" + fileUrl; |
| | | } |
| | | // 拼接 baseUrl |
| | | fileUrl = javaApi + fileUrl; |
| | |
| | | videos.push(fileUrl); |
| | | } else if (item.contentType) { |
| | | // 如果有 contentType,使用 contentType 判断 |
| | | if (item.contentType.startsWith('image/')) { |
| | | if (item.contentType.startsWith("image/")) { |
| | | images.push(fileUrl); |
| | | } else if (item.contentType.startsWith('video/')) { |
| | | } else if (item.contentType.startsWith("video/")) { |
| | | videos.push(fileUrl); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | // 打开弹窗并加载数据 |
| | | const openDialog = async (row) => { |
| | | 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 || []); |
| | | 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; |
| | |
| | | function closeMediaViewer() { |
| | | isMediaViewerVisible.value = false; |
| | | mediaList.value = []; |
| | | mediaType.value = 'image'; |
| | | mediaType.value = "image"; |
| | | } |
| | | |
| | | // 表单关闭方法 |
| | |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | margin-bottom: 20px; |
| | | |
| | | .form-container { |
| | | flex: 1; |