332 lines
9.4 KiB
Markdown
332 lines
9.4 KiB
Markdown
# ✅ 党籍保留附件查看功能实现说明
|
||
|
||
**实现时间**: 2025-10-13
|
||
**功能描述**: 在出国申请详情弹窗中,为党籍保留附件增加"查看"按钮,支持在线预览
|
||
|
||
---
|
||
|
||
## 🎯 功能概述
|
||
|
||
在出国申请管理的详情弹窗中,党籍保留附件区域新增了"查看"按钮,用户可以:
|
||
- 📄 **PDF文件**: 在新窗口中直接预览
|
||
- 🖼️ **图片文件**: 在新窗口中查看(支持JPG、PNG、GIF、BMP、WEBP)
|
||
- 📦 **其他文件**: 自动触发下载
|
||
|
||
---
|
||
|
||
## 📝 实现细节
|
||
|
||
### 1. 界面修改
|
||
|
||
**文件位置**: `web/src/views/rs/overseas/index.vue`
|
||
|
||
**修改内容** (第1024-1060行):
|
||
|
||
```vue
|
||
<!-- 党籍保留附件 -->
|
||
<div v-if="(state.viewData as OverseasApplicationVO).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>
|
||
{{ (state.viewData as OverseasApplicationVO).partyRetentionFileName }}
|
||
</el-tag>
|
||
<span style="margin-left: 10px; font-size: 12px; color: #909399;">
|
||
上传时间: {{ (state.viewData as OverseasApplicationVO).partyRetentionUploadTime }}
|
||
</span>
|
||
</div>
|
||
<!-- 操作按钮组 -->
|
||
<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>
|
||
```
|
||
|
||
**界面变化**:
|
||
- ✅ 新增"查看"按钮,位于"下载"按钮左侧
|
||
- ✅ 使用`flex`布局,两个按钮间距8px
|
||
- ✅ 按钮样式与下载按钮保持一致
|
||
- ✅ 使用Element Plus的`View`图标
|
||
|
||
### 2. 图标导入
|
||
|
||
**修改位置**: 第1260行
|
||
|
||
```typescript
|
||
// 原代码
|
||
import { UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
|
||
|
||
// 修改后
|
||
import { UploadFilled, Document, Loading, Download, Delete, View } from '@element-plus/icons-vue';
|
||
```
|
||
|
||
### 3. 预览方法实现
|
||
|
||
**方法位置**: 第3437-3515行
|
||
|
||
```typescript
|
||
// 预览党籍保留附件
|
||
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 ? response : new Blob([response.data]);
|
||
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'
|
||
});
|
||
|
||
const blob = response instanceof Blob ? response : new Blob([response.data], { type: 'application/pdf' });
|
||
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('预览文件失败,请检查文件权限或稍后重试');
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 功能特性
|
||
|
||
### ✅ 支持的文件类型
|
||
|
||
1. **PDF文件** (`.pdf`)
|
||
- 在新浏览器标签页中打开
|
||
- 浏览器原生PDF预览器
|
||
- 支持缩放、打印等操作
|
||
|
||
2. **图片文件** (`.jpg`, `.jpeg`, `.png`, `.gif`, `.bmp`, `.webp`)
|
||
- 在新窗口中居中显示
|
||
- 自适应屏幕大小
|
||
- 灰色背景,美观舒适
|
||
|
||
3. **其他文件类型**
|
||
- 自动提示用户
|
||
- 触发下载功能
|
||
- 无缝切换到下载流程
|
||
|
||
### ✅ 安全性保障
|
||
|
||
1. **认证请求**
|
||
- 使用`request`工具发送请求
|
||
- 自动携带Token认证头
|
||
- 自动携带clientId等必需Header
|
||
|
||
2. **权限检查**
|
||
- 检查OSS ID是否存在
|
||
- 后端权限验证
|
||
- 错误提示友好
|
||
|
||
3. **资源管理**
|
||
- Blob URL自动清理
|
||
- 防止内存泄漏
|
||
- PDF预览60秒后清理
|
||
|
||
### ✅ 用户体验
|
||
|
||
1. **操作便捷**
|
||
- 一键预览
|
||
- 新窗口打开,不影响当前页面
|
||
- 支持浏览器返回键
|
||
|
||
2. **提示友好**
|
||
- 加载中提示
|
||
- 成功/失败消息
|
||
- 不支持预览时自动下载
|
||
|
||
3. **界面美观**
|
||
- 按钮对齐整齐
|
||
- 图标语义清晰
|
||
- 样式统一协调
|
||
|
||
---
|
||
|
||
## 🧪 测试场景
|
||
|
||
### 测试用例1: PDF文件预览
|
||
1. 上传PDF格式的党籍保留附件
|
||
2. 在详情弹窗中点击"查看"按钮
|
||
3. **预期结果**: 在新标签页中打开PDF,可以正常查看
|
||
|
||
### 测试用例2: 图片文件预览
|
||
1. 上传JPG/PNG格式的党籍保留附件
|
||
2. 在详情弹窗中点击"查看"按钮
|
||
3. **预期结果**: 在新窗口中居中显示图片
|
||
|
||
### 测试用例3: 不支持的文件类型
|
||
1. 上传DOC/DOCX等其他格式的文件
|
||
2. 在详情弹窗中点击"查看"按钮
|
||
3. **预期结果**: 提示"该文件类型不支持在线预览,请下载后查看",并自动触发下载
|
||
|
||
### 测试用例4: 权限验证
|
||
1. 使用没有权限的用户
|
||
2. 尝试预览党籍保留附件
|
||
3. **预期结果**: 后端返回权限错误,前端显示错误提示
|
||
|
||
### 测试用例5: 文件不存在
|
||
1. 删除OSS中的文件
|
||
2. 尝试预览党籍保留附件
|
||
3. **预期结果**: 显示"预览文件失败"错误提示
|
||
|
||
---
|
||
|
||
## 📊 技术实现对比
|
||
|
||
### 与盖章文件的对比
|
||
|
||
| 特性 | 盖章文件 | 党籍保留附件 |
|
||
|------|---------|-------------|
|
||
| 上传组件 | el-upload | el-upload |
|
||
| 显示方式 | 上传列表 | 单个附件卡片 |
|
||
| 预览方式 | 组件内预览 | 新窗口预览 |
|
||
| 下载方式 | OSS下载接口 | OSS下载接口 |
|
||
| 权限控制 | Token认证 | Token认证 |
|
||
|
||
### 技术选型原因
|
||
|
||
1. **新窗口预览 vs 弹窗预览**
|
||
- ✅ 不影响当前页面状态
|
||
- ✅ 用户可以同时查看多个文件
|
||
- ✅ 浏览器原生支持,兼容性好
|
||
- ✅ 可以使用浏览器的打印、缩放等功能
|
||
|
||
2. **Blob URL vs 直接URL**
|
||
- ✅ 支持认证请求
|
||
- ✅ 安全性更高
|
||
- ✅ 可以自定义预览样式
|
||
- ✅ 避免CORS问题
|
||
|
||
---
|
||
|
||
## 🔄 后续优化建议
|
||
|
||
### 1. 增强预览功能
|
||
- [ ] 支持Word、Excel在线预览(需要引入第三方库)
|
||
- [ ] 支持视频、音频预览
|
||
- [ ] 支持压缩包内容预览
|
||
|
||
### 2. 用户体验优化
|
||
- [ ] 添加预览加载进度条
|
||
- [ ] 支持图片放大缩小
|
||
- [ ] 支持PDF页面跳转
|
||
|
||
### 3. 性能优化
|
||
- [ ] 添加文件预览缓存
|
||
- [ ] 优化大文件预览加载速度
|
||
- [ ] 支持分页预览大型PDF
|
||
|
||
---
|
||
|
||
## 📞 相关文档
|
||
|
||
- **前端开发规范**: `.cursor/cursorrules-frontend.md`
|
||
- **API文档**: `docs/开发文档/API/出国申请管理API.md`
|
||
- **组件文档**: `web/src/components/ImagePreview/index.vue`
|
||
|
||
---
|
||
|
||
## ✅ 验收标准
|
||
|
||
- [x] 界面上显示"查看"按钮
|
||
- [x] 按钮位置在"下载"按钮左侧
|
||
- [x] 点击按钮可以预览PDF文件
|
||
- [x] 点击按钮可以预览图片文件
|
||
- [x] 不支持的文件类型自动下载
|
||
- [x] 错误提示友好
|
||
- [x] 无TypeScript编译错误
|
||
- [x] 无ESLint错误
|
||
|
||
---
|
||
|
||
**实现人**: Cursor AI
|
||
**审核人**: 待审核
|
||
**状态**: ✅ 已完成
|
||
**最后更新**: 2025-10-13
|
||
|