WXL
2026-09-17 3df460667d74868d0a54fce12d5e881bbd6c0c1a
src/components/FilePreviewDialog/index.vue
@@ -11,7 +11,10 @@
  >
    <!-- 加载状态 -->
    <div v-if="loading" class="preview-loading">
      <i class="el-icon-loading" style="font-size: 40px; margin-bottom: 16px;"></i>
      <i
        class="el-icon-loading"
        style="font-size: 40px; margin-bottom: 16px;"
      ></i>
      <span>文件加载中...</span>
    </div>
@@ -19,11 +22,19 @@
    <div v-else-if="fileType === 'image'" class="preview-container">
      <div class="image-toolbar">
        <el-button-group>
          <el-button size="mini" @click="zoomImageIn" :disabled="imageScale >= 300">
          <el-button
            size="mini"
            @click="zoomImageIn"
            :disabled="imageScale >= 300"
          >
            <i class="el-icon-zoom-in"></i> 放大
          </el-button>
          <el-button size="mini" disabled>{{ imageScale }}%</el-button>
          <el-button size="mini" @click="zoomImageOut" :disabled="imageScale <= 50">
          <el-button
            size="mini"
            @click="zoomImageOut"
            :disabled="imageScale <= 50"
          >
            <i class="el-icon-zoom-out"></i> 缩小
          </el-button>
          <el-button size="mini" @click="resetImageZoom">
@@ -50,7 +61,7 @@
    <!-- PDF预览 -->
    <div v-else-if="fileType === 'pdf'" class="preview-container pdf-preview">
      <div class="pdf-toolbar">
        <el-button-group>
        <el-button-group v-if="!useNativePdf">
          <el-button
            size="mini"
            @click="changePage(currentPage - 1)"
@@ -70,7 +81,7 @@
          </el-button>
        </el-button-group>
        <el-button-group class="zoom-controls">
        <el-button-group v-if="!useNativePdf" class="zoom-controls">
          <el-button size="mini" @click="zoomOut" :disabled="scale <= 50">
            <i class="el-icon-zoom-out"></i> 缩小
          </el-button>
@@ -83,16 +94,20 @@
          </el-button>
        </el-button-group>
        <el-button
          size="mini"
          type="success"
          @click="handleDownload"
        >
        <el-button size="mini" type="success" @click="handleDownload">
          <i class="el-icon-download"></i> 下载
        </el-button>
      </div>
      <div class="pdf-content" v-loading="pdfLoading">
      <!-- pdf.js 因跨域(CORS)加载失败时,降级为浏览器原生 PDF 预览(iframe 导航不受同源策略限制) -->
      <iframe
        v-if="useNativePdf"
        :src="fileUrl"
        class="pdf-native-iframe"
        frameborder="0"
      ></iframe>
      <div class="pdf-content" v-else v-loading="pdfLoading">
        <pdf
          ref="pdf"
          :src="pdfUrl"
@@ -111,7 +126,10 @@
    </div>
    <!-- Office文档预览 -->
    <div v-else-if="fileType === 'office'" class="preview-container office-preview">
    <div
      v-else-if="fileType === 'office'"
      class="preview-container office-preview"
    >
      <div class="office-toolbar">
        <el-alert
          title="Office文档预览提示"
@@ -127,7 +145,11 @@
      <div class="office-content">
        <iframe
          :src="`https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(fileUrl)}`"
          :src="
            `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(
              fileUrl
            )}`
          "
          width="100%"
          height="600px"
          frameborder="0"
@@ -177,6 +199,11 @@
      type: Object,
      default: () => ({})
    }
    // 添加baseUrlHt作为props传入
    // baseUrlHt: {
    //   type: String,
    //   default: process.env.VUE_APP_BASE_API
    // }
  },
  data() {
    return {
@@ -185,6 +212,7 @@
      // 加载状态
      loading: false,
      pdfLoading: false,
      baseUrlHt: "",
      // 文件信息
      fileUrl: "",
      fileName: "",
@@ -194,6 +222,8 @@
      pageCount: 0,
      scale: 100,
      pageRotate: 0,
      // pdf.js 加载失败(如 CORS)时降级为浏览器原生预览
      useNativePdf: false,
      // 图片相关
      imageScale: 100
    };
@@ -214,19 +244,57 @@
      }
    },
    previewVisible(newVal) {
      this.$emit('update:visible', newVal);
      this.$emit("update:visible", newVal);
      if (!newVal) {
        this.handleClose();
      }
    }
  },
  created() {
    this.calculateBaseUrl();
  },
  methods: {
    /** URL处理函数 - 将完整URL替换为baseUrlHt */
    processFileUrl(url) {
      if (!url) return "";
      // 如果已经是完整的http或https链接
      if (url.startsWith("http://") || url.startsWith("https://")) {
        // 找到第三个斜杠后的位置,提取路径部分
        const thirdSlashIndex = url.indexOf("/", 8); // 从http://或https://之后开始找
        if (thirdSlashIndex !== -1) {
          return `${this.baseUrlHt}${url.substring(thirdSlashIndex)}`;
        }
        return this.baseUrlHt; // 如果没有路径部分,只返回baseUrlHt
      }
      // 相对路径处理
      if (url.startsWith("/")) {
        return `${this.baseUrlHt}${url}`;
      }
      return `${this.baseUrlHt}/${url}`;
    },
    /** 计算 baseUrlHt */
    calculateBaseUrl() {
      // 走当前域名(同域相对路径),由 nginx 的 /profile/ 代理转发到后端 9095。
      // 直连 9095 端口会因端口不同产生跨域(CORS),而后端存在双重 CORS 响应头问题,
      // 同域请求不触发 CORS 检查,可从根源上绕开该问题(图片/PDF/下载全部生效)。
      this.baseUrlHt = "";
      console.log("文件访问走同域代理: /profile/");
    },
    /** 初始化预览 */
    initPreview() {
      if (!this.file) return;
      this.fileName = this.file.fileName || this.file.name || "未知文件";
      this.fileUrl = this.file.fileUrl || this.file.path || this.file.url;
      const originalUrl = this.file.fileUrl || this.file.path || this.file.url;
      // 处理URL,替换域名部分
      this.fileUrl = this.processFileUrl(originalUrl);
      console.log(this.fileUrl,'this.fileUrl');
      this.fileType = this.getFileType(this.fileName);
      this.loading = true;
@@ -236,6 +304,7 @@
      this.scale = 100;
      this.imageScale = 100;
      this.pageCount = 0;
      this.useNativePdf = false;
      // 模拟加载延迟
      setTimeout(() => {
@@ -250,7 +319,10 @@
    getFileType(fileName) {
      if (!fileName) return "other";
      const extension = fileName.split('.').pop().toLowerCase();
      const extension = fileName
        .split(".")
        .pop()
        .toLowerCase();
      const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
      const pdfTypes = ["pdf"];
      const officeTypes = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"];
@@ -263,7 +335,12 @@
    /** 获取文件扩展名 */
    getFileExtension(filename) {
      return filename.split('.').pop().toLowerCase() || "未知";
      return (
        filename
          .split(".")
          .pop()
          .toLowerCase() || "未知"
      );
    },
    /** PDF加载完成 */
@@ -276,8 +353,13 @@
    pdfErrorHandler(error) {
      console.error("PDF加载失败:", error);
      this.pdfLoading = false;
      this.$message.error("PDF文件加载失败,请尝试下载后查看");
      this.previewVisible = false;
      // pdf.js 通过 fetch 加载跨域 PDF 受 CORS 限制,失败时降级为浏览器原生预览
      // (iframe 属于导航请求,不受同源策略限制,可以正常加载跨域 PDF)
      this.useNativePdf = true;
      this.$message({
        type: "info",
        message: "已切换为浏览器原生预览模式"
      });
    },
    /** 图片加载完成 */
@@ -337,7 +419,7 @@
      this.imageScale = 100;
    },
    /** 下载文件 */
    /** 下载文件 - 也需要处理URL */
    handleDownload() {
      if (!this.fileUrl) {
        this.$message.warning("文件路径不存在,无法下载");
@@ -429,6 +511,13 @@
    align-items: flex-start;
  }
  /* 原生 PDF 预览(CORS 降级方案) */
  .pdf-native-iframe {
    flex: 1;
    border: none;
    background: #f8f9fa;
  }
  /* Office预览样式 */
  .office-content {
    flex: 1;