فهرست منبع

“rs模块上传功能修改1013版”

weizhong 1 سال پیش
والد
کامیت
69e4b08946
2فایلهای تغییر یافته به همراه603 افزوده شده و 30 حذف شده
  1. 357 2
      web/src/views/rs/overseas/approval.vue
  2. 246 28
      web/src/views/rs/overseas/index.vue

+ 357 - 2
web/src/views/rs/overseas/approval.vue

@@ -289,6 +289,125 @@
         <el-descriptions-item label="管理员备注" :span="2" v-if="viewData.adminRemark">{{ viewData.adminRemark }}</el-descriptions-item>
       </el-descriptions>
 
+      <!-- 附件区域 -->
+      <div v-if="viewData" style="margin-top: 20px;">
+        <!-- 出国境登记表盖章文件 -->
+        <div v-if="viewData.stampedFileUploadStatus === '1'" 
+             style="margin-bottom: 15px; padding: 12px; border: 1px solid #e4e7ed; border-radius: 4px; background: #f9fafb;">
+          <div style="font-size: 14px; color: #606266; margin-bottom: 8px;">
+            <el-icon><Document /></el-icon>
+            <span style="margin-left: 5px; font-weight: 500;">出国境登记表盖章文件</span>
+          </div>
+          <div style="display: flex; align-items: center; justify-content: space-between;">
+            <div style="flex: 1;">
+              <el-tag type="success" size="small">
+                <el-icon><Document /></el-icon>
+                {{ viewData.stampedRegistrationFileName }}
+              </el-tag>
+              <span style="margin-left: 10px; font-size: 12px; color: #909399;">
+                上传时间: {{ viewData.stampedFileUploadTime }}
+              </span>
+            </div>
+            <div style="display: flex; gap: 8px;">
+              <el-button 
+                link 
+                type="primary" 
+                size="small" 
+                @click="handlePreviewStampedFile(viewData)"
+              >
+                <el-icon><View /></el-icon> 查看
+              </el-button>
+              <el-button 
+                link 
+                type="primary" 
+                size="small" 
+                @click="handleDownloadStampedFile(viewData)"
+              >
+                <el-icon><Download /></el-icon> 下载
+              </el-button>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 党籍保留附件 -->
+        <div v-if="viewData.partyRetentionUploadStatus === '1'" 
+             style="margin-bottom: 15px; padding: 12px; border: 1px solid #e4e7ed; border-radius: 4px; background: #fff5f5;">
+          <div style="font-size: 14px; color: #ff4757; margin-bottom: 8px;">
+            <el-icon><Warning /></el-icon>
+            <span style="margin-left: 5px; font-weight: 500;">党籍保留附件</span>
+          </div>
+          <div style="display: flex; align-items: center; justify-content: space-between;">
+            <div style="flex: 1;">
+              <el-tag type="danger" size="small">
+                <el-icon><Document /></el-icon>
+                {{ viewData.partyRetentionFileName }}
+              </el-tag>
+              <span style="margin-left: 10px; font-size: 12px; color: #909399;">
+                上传时间: {{ viewData.partyRetentionUploadTime }}
+              </span>
+            </div>
+            <div style="display: flex; gap: 8px;">
+              <el-button 
+                link 
+                type="primary" 
+                size="small" 
+                @click="handlePreviewPartyRetentionFile(viewData)"
+              >
+                <el-icon><View /></el-icon> 查看
+              </el-button>
+              <el-button 
+                link 
+                type="primary" 
+                size="small" 
+                @click="handleDownloadPartyRetentionFile(viewData)"
+              >
+                <el-icon><Download /></el-icon> 下载
+              </el-button>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 其他申请附件列表 -->
+        <div v-if="viewAttachmentList.length > 0" style="margin-top: 15px;">
+          <div style="font-size: 14px; color: #606266; margin-bottom: 8px;">
+            <el-icon><Paperclip /></el-icon>
+            <span style="margin-left: 5px; font-weight: 500;">其他申请附件</span>
+          </div>
+          <el-table 
+            :data="viewAttachmentList" 
+            border 
+            size="small"
+          >
+            <el-table-column label="序号" type="index" width="60" align="center" />
+            <el-table-column label="附件标题" prop="attachmentTitle" min-width="150" />
+            <el-table-column label="附件说明" prop="attachmentContent" min-width="200" show-overflow-tooltip>
+              <template #default="scope">
+                {{ scope.row.attachmentContent || '-' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="文件名" prop="fileOriginalName" min-width="150" show-overflow-tooltip />
+            <el-table-column label="文件大小" prop="fileSizeFormatted" width="100" align="center" />
+            <el-table-column label="上传时间" prop="uploadTime" width="160" align="center" />
+            <el-table-column label="操作" width="100" align="center">
+              <template #default="scope">
+                <el-button link type="primary" size="small" @click="handleDownloadAttachment(scope.row)">
+                  <el-icon><Download /></el-icon> 下载
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        
+        <!-- 无任何附件时的提示 -->
+        <el-empty 
+          v-if="viewData.stampedFileUploadStatus !== '1' 
+                && viewData.partyRetentionUploadStatus !== '1' 
+                && viewAttachmentList.length === 0" 
+          description="暂无附件" 
+          :image-size="100" 
+        />
+      </div>
+
       <!-- 查看审批记录 -->
       <div class="mt-4">
         <h4>审批记录</h4>
@@ -408,19 +527,27 @@ import {
   approveOverseasApplication,
   exportOverseasApplication,
   exportOverseasApproval,
+  listOverseasAttachments,
   type OverseasApplicationQuery,
   type OverseasApplicationVO,
   type OverseasApprovalQuery,
   type OverseasApprovalVO,
   type ApprovalRequest,
+  type OverseasAttachmentVO,
   fixOverseasApprovalData
 } from '@/api/rs/overseas';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { Document, Warning, Paperclip, View, Download } from '@element-plus/icons-vue';
 import animate from '@/animate';
+import request from '@/utils/request';
+import { getToken } from '@/utils/auth';
 
 // 响应式数据
 const { proxy } = getCurrentInstance() as any;
 
+// 附件列表数据
+const viewAttachmentList = ref<OverseasAttachmentVO[]>([]);
+
 const state = reactive({
   // 选项卡相关
   activeTab: 'pending',
@@ -667,17 +794,25 @@ const resetRecordQuery = () => {
 
 // 查看申请详情
 const handleViewApplication = async (row: OverseasApplicationVO) => {
+  console.log('📄 [查看详情] 开始查看申请详情,申请ID:', row.applicationId);
   try {
     const response = await getOverseasApplication(row.applicationId);
     state.viewData = response.data;
+    console.log('✅ [查看详情] 申请详情获取成功:', response.data);
 
     // 获取审批记录
     const approvalResponse = await getOverseasApprovalByApplication(row.applicationId);
     state.applicationApprovalList = approvalResponse.data;
 
+    // 加载附件列表
+    console.log('🔄 [查看详情] 开始加载附件列表...');
+    await loadViewAttachmentList(row.applicationId);
+    console.log('✅ [查看详情] 附件列表加载完成');
+
     state.viewDialog.visible = true;
   } catch (error) {
-    console.error('获取申请详情失败:', error);
+    console.error('❌ [查看详情] 获取申请详情失败:', error);
+    ElMessage.error('获取申请详情失败');
   }
 };
 
@@ -747,19 +882,26 @@ const handleExportRecord = async () => {
 
 // 查看审批记录详情
 const handleViewRecord = async (row: OverseasApprovalVO) => {
+  console.log('📄 [查看记录详情] 开始查看审批记录详情,申请ID:', row.applicationId);
   try {
     // 通过申请ID获取申请详情
     const response = await getOverseasApplication(row.applicationId);
     state.viewData = response.data;
+    console.log('✅ [查看记录详情] 申请详情获取成功');
 
     // 获取完整的审批记录历史
     const approvalResponse = await getOverseasApprovalByApplication(row.applicationId);
     state.applicationApprovalList = approvalResponse.data;
 
+    // 加载附件列表
+    console.log('🔄 [查看记录详情] 开始加载附件列表...');
+    await loadViewAttachmentList(row.applicationId);
+    console.log('✅ [查看记录详情] 附件列表加载完成');
+
     // 显示查看对话框
     state.viewDialog.visible = true;
   } catch (error: any) {
-    console.error('获取审批记录详情失败:', error);
+    console.error('❌ [查看记录详情] 获取审批记录详情失败:', error);
     proxy.$modal.msgError('获取详情失败: ' + (error?.message || String(error)));
   }
 };
@@ -851,6 +993,219 @@ const getApprovalStatusTagType = (status: string): 'success' | 'warning' | 'dang
   };
   return typeMap[status] || 'info';
 };
+
+// ============== 附件相关方法 ==============
+
+// 加载附件列表
+const loadViewAttachmentList = async (applicationId: number | string) => {
+  console.log('🔍 [附件加载] 开始加载附件列表,applicationId:', applicationId);
+  try {
+    const response = await listOverseasAttachments(applicationId);
+    console.log('✅ [附件加载] API响应成功:', response);
+    
+    viewAttachmentList.value = response.data || [];
+    
+    if (!response.data || response.data.length === 0) {
+      console.warn('⚠️  [附件加载] 该申请暂无附件数据');
+    } else {
+      console.log('✨ [附件加载] 附件列表:', viewAttachmentList.value);
+    }
+  } catch (error: any) {
+    console.error('❌ [附件加载] 加载附件列表失败:', error);
+    viewAttachmentList.value = [];
+  }
+};
+
+// 下载附件(从详情页)
+const handleDownloadAttachment = (row: OverseasAttachmentVO) => {
+  console.log('📥 下载附件:', row.fileOriginalName);
+  // 直接通过链接下载
+  const link = document.createElement('a');
+  link.href = row.filePath;
+  link.download = row.fileOriginalName;
+  link.target = '_blank';
+  document.body.appendChild(link);
+  link.click();
+  document.body.removeChild(link);
+};
+
+// 下载出国境登记表盖章文件
+const handleDownloadStampedFile = async (data: OverseasApplicationVO) => {
+  console.log('📥 下载出国境登记表盖章文件:', data.stampedRegistrationFileName);
+  
+  if (!data.stampedFileOssId) {
+    ElMessage.warning('文件OSS ID不存在,无法下载');
+    return;
+  }
+  
+  try {
+    // 使用request下载文件
+    const response = await request({
+      url: `/resource/oss/download/${data.stampedFileOssId}`,
+      method: 'get',
+      responseType: 'blob'
+    });
+    
+    // 创建带MIME类型的Blob并触发下载
+    const blob = response instanceof Blob 
+      ? new Blob([response], { type: 'application/pdf' })
+      : new Blob([response.data], { type: 'application/pdf' });
+    const url = window.URL.createObjectURL(blob);
+    
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = data.stampedRegistrationFileName || '出国境登记表盖章文件.pdf';
+    
+    document.body.appendChild(link);
+    link.click();
+    
+    // 清理资源
+    document.body.removeChild(link);
+    window.URL.revokeObjectURL(url);
+    
+    ElMessage.success('文件下载成功');
+  } catch (error) {
+    console.error('下载出国境登记表盖章文件失败:', error);
+    ElMessage.error('下载文件失败,请检查文件权限或稍后重试');
+  }
+};
+
+// 下载党籍保留附件
+const handleDownloadPartyRetentionFile = async (data: OverseasApplicationVO) => {
+  console.log('📥 下载党籍保留附件:', data.partyRetentionFileName);
+  
+  if (!data.partyRetentionFileOssId) {
+    ElMessage.warning('文件OSS ID不存在,无法下载');
+    return;
+  }
+  
+  try {
+    // 使用request下载文件
+    const response = await request({
+      url: `/resource/oss/download/${data.partyRetentionFileOssId}`,
+      method: 'get',
+      responseType: 'blob'
+    });
+    
+    // 创建带MIME类型的Blob并触发下载
+    const blob = response instanceof Blob 
+      ? new Blob([response], { type: 'application/pdf' })
+      : new Blob([response.data], { type: 'application/pdf' });
+    const url = window.URL.createObjectURL(blob);
+    
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = data.partyRetentionFileName || '党籍保留附件.pdf';
+    
+    document.body.appendChild(link);
+    link.click();
+    
+    // 清理资源
+    document.body.removeChild(link);
+    window.URL.revokeObjectURL(url);
+    
+    ElMessage.success('文件下载成功');
+  } catch (error) {
+    console.error('下载党籍保留附件失败:', error);
+    ElMessage.error('下载文件失败,请检查文件权限或稍后重试');
+  }
+};
+
+// 预览党籍保留附件
+const handlePreviewPartyRetentionFile = async (data: OverseasApplicationVO) => {
+  console.log('👁️ 预览党籍保留附件:', data.partyRetentionFileName);
+  
+  if (!data.partyRetentionFileOssId) {
+    ElMessage.warning('文件OSS ID不存在,无法预览');
+    return;
+  }
+  
+  try {
+    // 获取文件扩展名
+    const fileName = data.partyRetentionFileName || '';
+    const fileExt = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
+    
+    // 使用request获取文件blob
+    const response = await request({
+      url: `/resource/oss/download/${data.partyRetentionFileOssId}`,
+      method: 'get',
+      responseType: 'blob'
+    });
+    
+    // 创建带MIME类型的Blob(关键:指定type为application/pdf才能正常预览)
+    const blob = response instanceof Blob 
+      ? new Blob([response], { type: 'application/pdf' })
+      : new Blob([response.data], { type: 'application/pdf' });
+    const fileUrl = window.URL.createObjectURL(blob);
+    
+    // 根据文件类型进行预览
+    if (fileExt === 'pdf') {
+      // PDF直接在新标签页打开
+      window.open(fileUrl, '_blank');
+      
+      // 60秒后清理URL
+      setTimeout(() => {
+        window.URL.revokeObjectURL(fileUrl);
+      }, 60000);
+    } 
+    // 其他文件类型提示下载
+    else {
+      ElMessage.info('该文件类型不支持在线预览,请下载后查看');
+      await handleDownloadPartyRetentionFile(data);
+    }
+  } catch (error) {
+    console.error('预览党籍保留附件失败:', error);
+    ElMessage.error('预览文件失败,请检查文件权限或稍后重试');
+  }
+};
+
+// 预览出国境登记表盖章文件
+const handlePreviewStampedFile = async (data: OverseasApplicationVO) => {
+  console.log('👁️ 预览出国境登记表盖章文件:', data.stampedRegistrationFileName);
+  
+  if (!data.stampedFileOssId) {
+    ElMessage.warning('文件OSS ID不存在,无法预览');
+    return;
+  }
+  
+  try {
+    // 获取文件扩展名
+    const fileName = data.stampedRegistrationFileName || '';
+    const fileExt = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
+    
+    // 使用request获取文件blob
+    const response = await request({
+      url: `/resource/oss/download/${data.stampedFileOssId}`,
+      method: 'get',
+      responseType: 'blob'
+    });
+    
+    // 创建带MIME类型的Blob(关键:指定type为application/pdf才能正常预览)
+    const blob = response instanceof Blob 
+      ? new Blob([response], { type: 'application/pdf' })
+      : new Blob([response.data], { type: 'application/pdf' });
+    const fileUrl = window.URL.createObjectURL(blob);
+    
+    // 根据文件类型进行预览
+    if (fileExt === 'pdf') {
+      // PDF直接在新标签页打开
+      window.open(fileUrl, '_blank');
+      
+      // 60秒后清理URL
+      setTimeout(() => {
+        window.URL.revokeObjectURL(fileUrl);
+      }, 60000);
+    } 
+    // 其他文件类型提示下载
+    else {
+      ElMessage.info('该文件类型不支持在线预览,请下载后查看');
+      await handleDownloadStampedFile(data);
+    }
+  } catch (error) {
+    console.error('预览出国境登记表盖章文件失败:', error);
+    ElMessage.error('预览文件失败,请检查文件权限或稍后重试');
+  }
+};
 </script>
 
 <style scoped>

+ 246 - 28
web/src/views/rs/overseas/index.vue

@@ -1010,14 +1010,24 @@
                 上传时间: {{ (state.viewData as OverseasApplicationVO).stampedFileUploadTime }}
               </span>
             </div>
-            <el-button 
-              link 
-              type="primary" 
-              size="small" 
-              @click="handleDownloadStampedFile((state.viewData as OverseasApplicationVO))"
-            >
-              <el-icon><Download /></el-icon> 下载
-            </el-button>
+            <div style="display: flex; gap: 8px;">
+              <el-button 
+                link 
+                type="primary" 
+                size="small" 
+                @click="handlePreviewStampedFile((state.viewData as OverseasApplicationVO))"
+              >
+                <el-icon><View /></el-icon> 查看
+              </el-button>
+              <el-button 
+                link 
+                type="primary" 
+                size="small" 
+                @click="handleDownloadStampedFile((state.viewData as OverseasApplicationVO))"
+              >
+                <el-icon><Download /></el-icon> 下载
+              </el-button>
+            </div>
           </div>
         </div>
         
@@ -1038,14 +1048,24 @@
                 上传时间: {{ (state.viewData as OverseasApplicationVO).partyRetentionUploadTime }}
               </span>
             </div>
-            <el-button 
-              link 
-              type="primary" 
-              size="small" 
-              @click="handleDownloadPartyRetentionFile((state.viewData as OverseasApplicationVO))"
-            >
-              <el-icon><Download /></el-icon> 下载
-            </el-button>
+            <div style="display: flex; gap: 8px;">
+              <el-button 
+                link 
+                type="primary" 
+                size="small" 
+                @click="handlePreviewPartyRetentionFile((state.viewData as OverseasApplicationVO))"
+              >
+                <el-icon><View /></el-icon> 查看
+              </el-button>
+              <el-button 
+                link 
+                type="primary" 
+                size="small" 
+                @click="handleDownloadPartyRetentionFile((state.viewData as OverseasApplicationVO))"
+              >
+                <el-icon><Download /></el-icon> 下载
+              </el-button>
+            </div>
           </div>
         </div>
         
@@ -1247,7 +1267,7 @@ import {
   type OverseasAttachmentVO
 } from '@/api/rs/overseas';
 import { ElMessage, ElMessageBox } from 'element-plus';
-import { UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
+import { UploadFilled, Document, Loading, Download, Delete, View } from '@element-plus/icons-vue';
 import ImagePreview from '@/components/ImagePreview/index.vue';
 import animate from '@/animate';
 import { parseTime } from '@/utils/ruoyi';
@@ -1955,11 +1975,11 @@ const resetApplicationForm = () => {
 const submitApplicationForm = () => {
   applicationFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
-      // 盖章文件上传检查已移除(不再作为必填项)
-      // if (!state.applicationForm.stampedFileUploadStatus || state.applicationForm.stampedFileUploadStatus === '0') {
-      //   ElMessage.warning('请先下载登记备案附件填写盖章后上传,才能提交申请!');
-      //   return;
-      // }
+      // ✅ 盖章文件上传检查(必填项)
+      if (!state.applicationForm.stampedFileUploadStatus || state.applicationForm.stampedFileUploadStatus === '0') {
+        ElMessage.warning('请先上传出国境登记表盖章文件才能提交申请!');
+        return;
+      }
 
       // 党籍保留附件上传检查已移除(不再作为必填项)
       // const isPartyMember = state.applicationForm.politicalStatus === '1' || state.applicationForm.politicalStatus === '中共党员';
@@ -3362,13 +3382,19 @@ const handleDownloadStampedFile = async (data: OverseasApplicationVO) => {
       responseType: 'blob' // 指定响应类型为blob
     });
 
-    // 创建blob URL并触发下载
-    const blob = response instanceof Blob ? response : new Blob([response.data]);
+    // 创建blob URL并触发下载(根据文件扩展名设置MIME类型)
+    const fileName = data.stampedRegistrationFileName || '出国境登记表盖章文件.pdf';
+    const fileExt = fileName.split('.').pop()?.toLowerCase();
+    const mimeType = fileExt === 'pdf' ? 'application/pdf' : 'application/octet-stream';
+    
+    const blob = response instanceof Blob 
+      ? new Blob([response], { type: mimeType })
+      : new Blob([response.data], { type: mimeType });
     const url = window.URL.createObjectURL(blob);
     
     const link = document.createElement('a');
     link.href = url;
-    link.download = data.stampedRegistrationFileName || '出国境登记表盖章文件.pdf';
+    link.download = fileName;
     
     document.body.appendChild(link);
     link.click();
@@ -3401,13 +3427,19 @@ const handleDownloadPartyRetentionFile = async (data: OverseasApplicationVO) =>
       responseType: 'blob' // 指定响应类型为blob
     });
 
-    // 创建blob URL并触发下载
-    const blob = response instanceof Blob ? response : new Blob([response.data]);
+    // 创建blob URL并触发下载(根据文件扩展名设置MIME类型)
+    const fileName = data.partyRetentionFileName || '党籍保留附件.pdf';
+    const fileExt = fileName.split('.').pop()?.toLowerCase();
+    const mimeType = fileExt === 'pdf' ? 'application/pdf' : 'application/octet-stream';
+    
+    const blob = response instanceof Blob 
+      ? new Blob([response], { type: mimeType })
+      : new Blob([response.data], { type: mimeType });
     const url = window.URL.createObjectURL(blob);
     
     const link = document.createElement('a');
     link.href = url;
-    link.download = data.partyRetentionFileName || '党籍保留附件.pdf';
+    link.download = fileName;
     
     document.body.appendChild(link);
     link.click();
@@ -3422,6 +3454,192 @@ const handleDownloadPartyRetentionFile = async (data: OverseasApplicationVO) =>
     ElMessage.error('下载文件失败,请检查文件权限或稍后重试');
   }
 };
+
+// 预览党籍保留附件
+const handlePreviewPartyRetentionFile = async (data: OverseasApplicationVO) => {
+  console.log('👁️ 预览党籍保留附件:', data.partyRetentionFileName);
+  
+  if (!data.partyRetentionFileOssId) {
+    ElMessage.warning('文件OSS ID不存在,无法预览');
+    return;
+  }
+  
+  try {
+    // 判断文件类型
+    const fileName = data.partyRetentionFileName || '';
+    const fileExt = fileName.split('.').pop()?.toLowerCase();
+    
+    // 图片类型直接在新窗口预览
+    if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExt || '')) {
+      // 使用OSS下载接口获取图片
+      const imageUrl = `/resource/oss/download/${data.partyRetentionFileOssId}`;
+      
+      // 获取blob URL用于预览
+      const response = await request({
+        url: imageUrl,
+        method: 'get',
+        responseType: 'blob'
+      });
+      
+      const blob = response instanceof Blob 
+        ? new Blob([response], { type: response.type || 'image/jpeg' })
+        : new Blob([response.data], { type: response.data.type || 'image/jpeg' });
+      const blobUrl = window.URL.createObjectURL(blob);
+      
+      // 在新窗口打开图片
+      const previewWindow = window.open('', '_blank');
+      if (previewWindow) {
+        previewWindow.document.write(`
+          <!DOCTYPE html>
+          <html>
+          <head>
+            <title>${fileName}</title>
+            <style>
+              body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #f5f5f5; }
+              img { max-width: 100%; max-height: 100vh; object-fit: contain; }
+            </style>
+          </head>
+          <body>
+            <img src="${blobUrl}" alt="${fileName}" />
+          </body>
+          </html>
+        `);
+        previewWindow.document.close();
+      }
+    } 
+    // PDF类型在新窗口预览
+    else if (fileExt === 'pdf') {
+      // 获取PDF文件
+      const response = await request({
+        url: `/resource/oss/download/${data.partyRetentionFileOssId}`,
+        method: 'get',
+        responseType: 'blob'
+      });
+      
+      // 正确处理Blob创建,确保包含正确的MIME类型
+      const blob = response instanceof Blob 
+        ? new Blob([response], { type: 'application/pdf' })
+        : new Blob([response.data], { type: 'application/pdf' });
+      
+      console.log('📄 [PDF预览] 党籍附件Blob创建成功:', {
+        size: blob.size,
+        type: blob.type,
+        fileName: fileName
+      });
+      
+      const blobUrl = window.URL.createObjectURL(blob);
+      
+      // 在新窗口打开PDF
+      window.open(blobUrl, '_blank');
+      
+      // 延迟清理,确保新窗口已加载
+      setTimeout(() => {
+        window.URL.revokeObjectURL(blobUrl);
+      }, 60000); // 60秒后清理
+    } 
+    // 其他文件类型提示下载
+    else {
+      ElMessage.info('该文件类型不支持在线预览,请下载后查看');
+      await handleDownloadPartyRetentionFile(data);
+    }
+  } catch (error) {
+    console.error('预览党籍保留附件失败:', error);
+    ElMessage.error('预览文件失败,请检查文件权限或稍后重试');
+  }
+};
+
+// 预览出国境登记表盖章文件
+const handlePreviewStampedFile = async (data: OverseasApplicationVO) => {
+  console.log('👁️ 预览出国境登记表盖章文件:', data.stampedRegistrationFileName);
+  
+  if (!data.stampedFileOssId) {
+    ElMessage.warning('文件OSS ID不存在,无法预览');
+    return;
+  }
+  
+  try {
+    // 判断文件类型
+    const fileName = data.stampedRegistrationFileName || '';
+    const fileExt = fileName.split('.').pop()?.toLowerCase();
+    
+    // 图片类型直接在新窗口预览
+    if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExt || '')) {
+      // 使用OSS下载接口获取图片
+      const imageUrl = `/resource/oss/download/${data.stampedFileOssId}`;
+      
+      // 获取blob URL用于预览
+      const response = await request({
+        url: imageUrl,
+        method: 'get',
+        responseType: 'blob'
+      });
+      
+      const blob = response instanceof Blob 
+        ? new Blob([response], { type: response.type || 'image/jpeg' })
+        : new Blob([response.data], { type: response.data.type || 'image/jpeg' });
+      const blobUrl = window.URL.createObjectURL(blob);
+      
+      // 在新窗口打开图片
+      const previewWindow = window.open('', '_blank');
+      if (previewWindow) {
+        previewWindow.document.write(`
+          <!DOCTYPE html>
+          <html>
+          <head>
+            <title>${fileName}</title>
+            <style>
+              body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #f5f5f5; }
+              img { max-width: 100%; max-height: 100vh; object-fit: contain; }
+            </style>
+          </head>
+          <body>
+            <img src="${blobUrl}" alt="${fileName}" />
+          </body>
+          </html>
+        `);
+        previewWindow.document.close();
+      }
+    } 
+    // PDF类型在新窗口预览
+    else if (fileExt === 'pdf') {
+      // 获取PDF文件
+      const response = await request({
+        url: `/resource/oss/download/${data.stampedFileOssId}`,
+        method: 'get',
+        responseType: 'blob'
+      });
+      
+      // 正确处理Blob创建,确保包含正确的MIME类型
+      const blob = response instanceof Blob 
+        ? new Blob([response], { type: 'application/pdf' })
+        : new Blob([response.data], { type: 'application/pdf' });
+      
+      console.log('📄 [PDF预览] 盖章文件Blob创建成功:', {
+        size: blob.size,
+        type: blob.type,
+        fileName: fileName
+      });
+      
+      const blobUrl = window.URL.createObjectURL(blob);
+      
+      // 在新窗口打开PDF
+      window.open(blobUrl, '_blank');
+      
+      // 延迟清理,确保新窗口已加载
+      setTimeout(() => {
+        window.URL.revokeObjectURL(blobUrl);
+      }, 60000); // 60秒后清理
+    } 
+    // 其他文件类型提示下载
+    else {
+      ElMessage.info('该文件类型不支持在线预览,请下载后查看');
+      await handleDownloadStampedFile(data);
+    }
+  } catch (error) {
+    console.error('预览出国境登记表盖章文件失败:', error);
+    ElMessage.error('预览文件失败,请检查文件权限或稍后重试');
+  }
+};
 </script>
 
 <style scoped>