gongchunyi
7 天以前 a7a9f53ef126659f664f5cbfe4eb10bebdfc4a6a
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -1,218 +1,242 @@
<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>
    <el-dialog title="巡检详情"
               v-model="dialogVisitable" width="700px" @close="cancel">
      <div class="detail-container">
        <!-- 基本信息 -->
        <div class="info-section">
          <div class="section-title">基本信息</div>
          <el-descriptions :column="2" border>
            <el-descriptions-item label="设备名称">{{ rowData.taskName || '-' }}</el-descriptions-item>
            <el-descriptions-item label="巡检地点">{{ rowData.inspectionLocation || '-' }}</el-descriptions-item>
            <el-descriptions-item label="执行巡检人">{{ rowData.inspector || '-' }}</el-descriptions-item>
            <el-descriptions-item label="巡检时间">{{ rowData.dateStr || '-' }}</el-descriptions-item>
            <el-descriptions-item label="巡检状态">
              <el-tag v-if="rowData.inspectionStatus === 1" type="success" size="small">正常</el-tag>
              <el-tag v-else-if="rowData.inspectionStatus === 2" type="danger" size="small">异常</el-tag>
              <el-tag v-else size="small">未巡检</el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="登记人">{{ rowData.registrant || '-' }}</el-descriptions-item>
            <el-descriptions-item label="验收状态">
              <el-tag v-if="rowData.acceptStatus === 1" type="success" size="small">已通过</el-tag>
              <el-tag v-else-if="rowData.acceptStatus === 2" type="danger" size="small">已退回</el-tag>
              <el-tag v-else-if="rowData.inspectionStatus > 0" type="warning" size="small">待验收</el-tag>
              <span v-else>--</span>
            </el-descriptions-item>
            <el-descriptions-item label="验收人">{{ rowData.inspectionAcceptor || '-' }}</el-descriptions-item>
          </el-descriptions>
        </div>
        <!-- 异常描述 -->
        <div v-if="rowData.inspectionStatus === 2" class="info-section">
          <div class="section-title">异常描述</div>
          <div class="exception-content">
            {{ rowData.inspectionRemark || '无' }}
          </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 class="info-section">
          <div class="section-title">附件 ({{ attachmentList.length }}个)</div>
          <div v-if="attachmentList.length > 0" class="attachment-list">
            <div v-for="(file, index) in attachmentList" :key="index" class="attachment-item">
              <div class="attachment-preview" @click="previewFile(file, index)">
                <img v-if="isImage(file)" :src="getFileUrl(file)" alt="附件" />
                <video v-else-if="isVideo(file)" :src="getFileUrl(file)"></video>
                <div v-else class="file-icon">
                  <i class="el-icon-document"></i>
                </div>
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
              <div class="attachment-info">
                <span class="file-name">{{ file.originalFilename || file.name || '附件' }}</span>
              </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 v-else class="empty-attachment">
            暂无附件
          </div>
        </div>
      </div>
      <template #footer>
        <el-button @click="cancel">关闭</el-button>
      </template>
    </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>
    <!-- 图片预览 -->
    <vue-easy-lightbox
        v-if="showLightbox"
        :visible="showLightbox"
        :imgs="previewImages"
        :index="previewIndex"
        @hide="closeLightbox"
    ></vue-easy-lightbox>
    <!-- 视频预览 -->
    <el-dialog v-if="showVideoDialog" v-model="showVideoDialog" title="视频预览" width="800px" append-to-body>
      <video
          ref="videoPlayer"
          :src="videoUrl"
          controls
          autoplay
          style="width: 100%; max-height: 60vh;"
      ></video>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { ref, getCurrentInstance } from 'vue';
import VueEasyLightbox from 'vue-easy-lightbox';
const { proxy } = getCurrentInstance();
// 控制弹窗显示
const dialogVisitable = ref(false);
// 图片数组
const beforeProductionImgs = ref([]);
const afterProductionImgs = ref([]);
const productionIssuesImgs = ref([]);
// 行数据
const rowData = ref({});
// 视频数组
const beforeProductionVideos = ref([]);
const afterProductionVideos = ref([]);
const productionIssuesVideos = ref([]);
// 附件列表
const attachmentList = ref([]);
// 媒体查看器状态
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
const mediaList = ref([]); // 存储当前要查看的媒体列表(含图片和视频对象)
const mediaType = ref('image'); // image | video
// 图片预览
const showLightbox = ref(false);
const previewImages = ref([]);
const previewIndex = ref(0);
// 处理每一类数据:分离图片和视频
function processItems(items) {
  const images = [];
  const videos = [];
  items.forEach(item => {
    if (item.contentType?.startsWith('image/')) {
      images.push(item.url);
    } else if (item.contentType?.startsWith('video/')) {
      videos.push(item.url);
// 视频预览
const showVideoDialog = ref(false);
const videoUrl = ref('');
const javaApi = proxy.javaApi;
// 处理文件URL
function processFileUrl(fileUrl) {
  if (!fileUrl) return '';
  if (fileUrl && fileUrl.indexOf('\\') > -1) {
    const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
    if (uploadsIndex > -1) {
      const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
      fileUrl = '/' + relativePath;
    } else {
      const parts = fileUrl.split('\\');
      const fileName = parts[parts.length - 1];
      fileUrl = '/uploads/' + fileName;
    }
  });
  return { images, videos };
  }
  if (fileUrl && !fileUrl.startsWith('http')) {
    if (!fileUrl.startsWith('/')) {
      fileUrl = '/' + fileUrl;
    }
    fileUrl = javaApi + fileUrl;
  }
  return fileUrl;
}
// 打开弹窗并加载数据
const openDialog = async (row) => {
  const { images: beforeImgs, videos: beforeVids } = processItems(row.beforeProduction);
  const { images: afterImgs, videos: afterVids } = processItems(row.afterProduction);
  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;
// 获取文件访问URL
function getFileUrl(file) {
  // 优先使用 link 字段
  let url = file?.link;
  if (!url) {
    url = file?.url || file?.downloadUrl || file?.tempPath || '';
  }
  return processFileUrl(url);
}
// 判断是否为图片
function isImage(file) {
  const name = file?.originalFilename || file?.bucketFilename || file?.name || '';
  const ext = name.split('.').pop()?.toLowerCase();
  return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext);
}
// 判断是否为视频
function isVideo(file) {
  const name = file?.originalFilename || file?.bucketFilename || file?.name || '';
  const ext = name.split('.').pop()?.toLowerCase();
  return ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm'].includes(ext);
}
// 预览文件
function previewFile(file, index) {
  if (isImage(file)) {
    // 图片预览
    previewImages.value = attachmentList.value
      .filter(f => isImage(f))
      .map(f => getFileUrl(f));
    previewIndex.value = previewImages.value.indexOf(getFileUrl(file));
    showLightbox.value = true;
  } else if (isVideo(file)) {
    // 视频预览
    videoUrl.value = getFileUrl(file);
    showVideoDialog.value = true;
  }
}
// 关闭图片预览
function closeLightbox() {
  showLightbox.value = false;
  previewImages.value = [];
  previewIndex.value = 0;
}
// 打开弹窗
const openDialog = (row) => {
  rowData.value = { ...row };
  // 收集所有附件
  let files = [];
  if (row?.commonFileList) {
    files = files.concat(row.commonFileList);
  }
  if (row?.commonFileListBefore) {
    files = files.concat(row.commonFileListBefore);
  }
  if (row?.commonFileListAfter) {
    files = files.concat(row.commonFileListAfter);
  }
  attachmentList.value = files;
  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;
  showVideoDialog.value = false;
  rowData.value = {};
  attachmentList.value = [];
};
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;
.detail-container {
  max-height: 60vh;
  overflow-y: auto;
}
.info-section {
  margin-bottom: 20px;
  &:last-child {
    margin-bottom: 0;
  }
}
.title {
.section-title {
  font-size: 14px;
  color: #165dff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0;
  margin-bottom: 12px;
  &::before {
    content: "";
    position: absolute;
@@ -224,23 +248,81 @@
  }
}
.media-viewer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
.exception-content {
  padding: 12px;
  background-color: #fff2f0;
  border: 1px solid #ffccc7;
  border-radius: 4px;
  color: #ff4d4f;
  font-size: 14px;
  line-height: 1.6;
}
.attachment-list {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.attachment-item {
  width: 100px;
  text-align: center;
}
.attachment-preview {
  width: 100px;
  height: 100px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f5f7fa;
  transition: all 0.3s;
  &:hover {
    border-color: #409eff;
    transform: scale(1.02);
  }
  img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  video {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .file-icon {
    font-size: 32px;
    color: #909399;
  }
}
.media-viewer-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  overflow: hidden;
.attachment-info {
  margin-top: 4px;
  .file-name {
    font-size: 12px;
    color: #606266;
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}
</style>
.empty-attachment {
  padding: 30px;
  text-align: center;
  color: #909399;
  background-color: #f5f7fa;
  border-radius: 4px;
}
</style>