BodyBalanceEvaluation/document/前端生成PDF并上传后端技术方案.md

114 lines
4.5 KiB
Markdown
Raw Normal View History

2025-12-09 13:31:01 +08:00
# 前端生成 PDF 并上传后端技术方案
## 概述
- 目标:诊断报告页面在前端生成高保真 PDF上传到 Python 后端持久化,并把文件路径写入对应检测会话。
- 推荐:使用 Electron 主进程 `webContents.printToPDF` 生成 PDF忠实度高、分页与中文字体友好作为短期备选提供 `html2canvas` 截图转 PDF 的实现。
## 架构流程
- 前端渲染进程:用户在报告页面点击“生成报告”→ 发送 IPC 请求至主进程生成 PDF → 主进程返回 PDF Buffer 给渲染进程 → 渲染进程上传后端。
- Python 后端:接收 PDF 文件流,写入 `backend/static/reports/<YYYY-MM-DD>/<session_id>.pdf`,记录相对路径到 `detection_sessions` 的报告字段。
## 前端实现Electron 渲染)
### 报告页面打印版式
- 报告根容器,例如 `#report-root`
- 打印样式建议:
- `@page { size: A4; margin: 12mm }`
- 固定宽度设计为 A4 比例,非交互元素隐藏,保留背景。
- 通过添加类名(例如 `.print-mode`)切换打印样式。
### 渲染进程触发生成
```ts
// renderer: 诊断报告视图中
import { ipcRenderer } from 'electron'
async function generatePdf(sessionId: string) {
const pdfBuffer: ArrayBuffer = await ipcRenderer.invoke('generate-report-pdf', {
selector: '#report-root',
pageSize: 'A4',
printBackground: true
})
const blob = new Blob([pdfBuffer], { type: 'application/pdf' })
const form = new FormData()
form.append('file', blob, `${sessionId}.pdf`)
const res = await fetch(`${getBackendUrl()}/api/reports/${sessionId}/upload`, {
method: 'POST',
body: form
})
const json = await res.json()
if (!json.success) throw new Error(json.error || '上传失败')
}
```
### 主进程生成 PDF
```ts
// main: 注册 IPC 处理
import { ipcMain } from 'electron'
ipcMain.handle('generate-report-pdf', async (event, payload) => {
const win = event.sender.getOwnerBrowserWindow()
await win.webContents.executeJavaScript(`
(function(){
const root = document.querySelector('${payload.selector}')
if (!root) throw new Error('报告根节点缺失')
document.body.classList.add('print-mode')
return true
})()
`)
const pdf = await win.webContents.printToPDF({
pageSize: payload.pageSize || 'A4',
printBackground: payload.printBackground !== false,
marginsType: 0
})
await win.webContents.executeJavaScript(`
(function(){ document.body.classList.remove('print-mode'); return true })()
`)
return pdf
})
```
### 更新数据库记录
```py
# database.py 片段
def update_session_report_path(self, session_id: str, report_path: str):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute('UPDATE detection_sessions SET detection_report = ? WHERE id = ?', (report_path, session_id))
conn.commit()
```
## 接口设计
- 上传 PDF`POST /api/reports/<session_id>/upload`
- 请求体:`multipart/form-data`,字段 `file`
- 返回:`{ success, path }`
## 权限与安全
- 校验 `session_id` 存在且归属当前登录用户(医生)。
- 限制最大文件大小(例如 20MB拒绝超限上传。
- 文件名使用 `session_id.pdf`,避免任意文件名写盘。
## 版式与质量建议
- 中文字体:在渲染环境预装中文字体或通过 `@font-face` 引入。
- 分页:使用 `@page``page-break-before/after` 控制分页。
- 背景Electron 打印需开启 `printBackground: true`;确保 CSS 背景不被打印忽略。
## 错误与排查
- 空白 PDF确保打印前切入 `.print-mode` 并等待资源加载;检查选择器是否找到根节点。
- 字体方框:安装中文字体或嵌入字体文件。
- 资源丢失:使用相对路径或 base64`html2canvas` 时启用 `useCORS` 且服务器设置跨域头。
- 过大或截断:分页控制,或拆页生成。
## 测试用例
- 单页报告:生成并上传,后端返回路径;数据库记录更新;历史档案中可下载或预览。
- 多页报告:存在分页断点,打印后为多页;每页标题和页码正常。
- 异常:网络断开、文件超限、会话不存在;前端提示具体错误。
## 部署说明
- Electron 打包:已使用 `electron-builder`;生成 exe 后功能一致。
- 后端存储Windows 下路径统一使用 `/` 展示;静态文件由后端提供下载或前端拼接 `BACKEND_URL + '/' + path` 访问。
## 后续扩展
- 加页眉页脚(时间、患者信息、页码)。
- 报告水印与签章。
- 历史报告下载与比对视图。