合并冲突
This commit is contained in:
commit
4173c3b862
1597
backend/database.py
1597
backend/database.py
File diff suppressed because it is too large
Load Diff
@ -1443,6 +1443,55 @@ class AppServer:
|
||||
self.logger.error(f'保存会话信息失败: {e}')
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
# ==================== 报告上传API ====================
|
||||
|
||||
@self.app.route('/api/reports/<session_id>/upload', methods=['POST'])
|
||||
def upload_report_pdf(session_id):
|
||||
"""接收前端生成的PDF并保存到文件系统,同时更新会话报告路径"""
|
||||
try:
|
||||
if not self.db_manager:
|
||||
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
|
||||
file = flask_request.files.get('file')
|
||||
if not file:
|
||||
return jsonify({'success': False, 'error': '缺少文件'}), 400
|
||||
# 获取会话信息以得到 patient_id
|
||||
session_data = self.db_manager.get_session_data(session_id)
|
||||
if not session_data:
|
||||
return jsonify({'success': False, 'error': '检测会话不存在'}), 404
|
||||
|
||||
patient_id = session_data.get('patient_id')
|
||||
if not patient_id:
|
||||
return jsonify({'success': False, 'error': '无法获取患者ID'}), 400
|
||||
|
||||
# 选择文件根目录:配置 FILEPATH.path
|
||||
base_dir_cfg = self.config_manager.get_config_value('FILEPATH', 'path', fallback=None) if self.config_manager else None
|
||||
if not base_dir_cfg:
|
||||
return jsonify({'success': False, 'error': '未配置文件存储路径'}), 500
|
||||
|
||||
base_dir = os.path.abspath(base_dir_cfg)
|
||||
timestamp = datetime.now().strftime('%H%M%S%f')[:-3] # 精确到毫秒
|
||||
|
||||
# 构建存储路径
|
||||
base_path = os.path.join(base_dir, str(patient_id), str(session_id))
|
||||
db_base_path = os.path.join(str(patient_id), str(session_id))
|
||||
|
||||
os.makedirs(base_path, exist_ok=True)
|
||||
|
||||
filename = f"report_{timestamp}.pdf"
|
||||
abs_path = os.path.join(base_path, filename)
|
||||
|
||||
file.save(abs_path)
|
||||
|
||||
# 生成相对路径存入数据库
|
||||
rel_path = os.path.join(db_base_path, filename).replace('\\', '/')
|
||||
self.db_manager.update_session_report_path(session_id, rel_path)
|
||||
return jsonify({'success': True, 'path': rel_path})
|
||||
except Exception as e:
|
||||
self.logger.error(f'上传报告失败: {e}')
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
|
||||
@self.app.route('/api/detection/<session_id>/save-data', methods=['POST'])
|
||||
def save_detection_data(session_id):
|
||||
"""采集检测数据"""
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
@ -8,6 +8,100 @@ let mainWindow;
|
||||
let localServer;
|
||||
let backendProcess;
|
||||
|
||||
ipcMain.handle('generate-report-pdf', async (event, payload) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (!win) throw new Error('窗口未找到');
|
||||
|
||||
// 1. 准备打印环境:克隆节点到独立的打印容器,确保流式布局
|
||||
await win.webContents.executeJavaScript(`
|
||||
(function(){
|
||||
const selector = '${(payload && payload.selector) ? payload.selector : '#report-root'}';
|
||||
const root = document.querySelector(selector);
|
||||
if (!root) throw new Error('报告根节点缺失');
|
||||
|
||||
// 创建打印容器
|
||||
const printContainer = document.createElement('div');
|
||||
printContainer.id = 'electron-print-container';
|
||||
|
||||
// 样式设置:绝对定位覆盖全屏,背景白,z-index最高
|
||||
printContainer.style.position = 'absolute';
|
||||
printContainer.style.top = '0';
|
||||
printContainer.style.left = '0';
|
||||
printContainer.style.width = '100%';
|
||||
printContainer.style.minHeight = '100vh';
|
||||
printContainer.style.background = '#ffffff';
|
||||
printContainer.style.zIndex = '2147483647';
|
||||
printContainer.style.display = 'block';
|
||||
printContainer.style.overflow = 'visible'; // 关键:允许内容溢出以触发分页
|
||||
|
||||
// 克隆目标节点
|
||||
const cloned = root.cloneNode(true);
|
||||
|
||||
// 强制重置克隆节点的关键样式,确保它是流式布局
|
||||
cloned.style.position = 'static'; // 必须是 static 或 relative
|
||||
cloned.style.display = 'block';
|
||||
cloned.style.width = '100%';
|
||||
cloned.style.height = 'auto';
|
||||
cloned.style.overflow = 'visible';
|
||||
cloned.style.margin = '0';
|
||||
cloned.style.transform = 'none'; // 移除可能影响打印的变换
|
||||
|
||||
// 将克隆节点加入容器
|
||||
printContainer.appendChild(cloned);
|
||||
|
||||
// 将容器加入 body
|
||||
document.body.appendChild(printContainer);
|
||||
|
||||
// 关键修复:打印时只保留我们的打印容器可见,其他所有元素隐藏
|
||||
// 我们创建一个 style 标签来强制隐藏除了 printContainer 以外的所有 body 直接子元素
|
||||
const style = document.createElement('style');
|
||||
style.id = 'print-style-override';
|
||||
style.innerHTML = \`
|
||||
@media print {
|
||||
body > *:not(#electron-print-container) {
|
||||
display: none !important;
|
||||
}
|
||||
#electron-print-container {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
\`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
document.body.classList.add('print-mode');
|
||||
|
||||
return true;
|
||||
})()
|
||||
`);
|
||||
|
||||
try {
|
||||
const pdf = await win.webContents.printToPDF({
|
||||
pageSize: (payload && payload.pageSize) ? payload.pageSize : 'A4',
|
||||
landscape: !!(payload && payload.landscape),
|
||||
printBackground: !(payload && payload.printBackground === false),
|
||||
marginsType: 0,
|
||||
displayHeaderFooter: true, // 启用页眉页脚
|
||||
headerTemplate: '<div></div>', // 空页眉
|
||||
footerTemplate: `
|
||||
<div style="width: 100%; text-align: center; font-size: 10px; font-family: 'Microsoft YaHei', sans-serif; padding-top: 5px; padding-bottom: 5px;">
|
||||
第 <span class="pageNumber"></span> 页 / 共 <span class="totalPages"></span> 页
|
||||
</div>
|
||||
`
|
||||
});
|
||||
return pdf;
|
||||
} finally {
|
||||
// 3. 清理环境
|
||||
await win.webContents.executeJavaScript(`
|
||||
(function(){
|
||||
const container = document.getElementById('electron-print-container');
|
||||
if (container) container.remove();
|
||||
document.body.classList.remove('print-mode');
|
||||
return true;
|
||||
})()
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
function startBackendService() {
|
||||
// 在打包后的应用中,使用process.resourcesPath获取resources目录
|
||||
const resourcesPath = process.resourcesPath || path.join(__dirname, '../..');
|
||||
@ -98,6 +192,7 @@ function createWindow() {
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: false, // 显式关闭沙盒,避免 preload 加载问题
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
},
|
||||
icon: path.join(__dirname, '../public/logo.png'),
|
||||
|
||||
@ -1,2 +1,6 @@
|
||||
const { contextBridge } = require('electron');
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
generateReportPdf: (payload) => ipcRenderer.invoke('generate-report-pdf', payload),
|
||||
getBackendUrl: () => 'http://localhost:5000'
|
||||
});
|
||||
|
||||
180
frontend/src/renderer/package-lock.json
generated
180
frontend/src/renderer/package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.3.9",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^3.0.4",
|
||||
"pinia": "^2.1.6",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"three": "^0.160.0",
|
||||
@ -64,10 +65,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.2.tgz",
|
||||
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
|
||||
"dev": true,
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -1168,6 +1168,12 @@
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pako": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
|
||||
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/plist": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@types/plist/-/plist-3.0.5.tgz",
|
||||
@ -1180,6 +1186,13 @@
|
||||
"xmlbuilder": ">=11.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/raf": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/responselike": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.3.tgz",
|
||||
@ -1190,6 +1203,13 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/verror": {
|
||||
"version": "1.10.11",
|
||||
"resolved": "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz",
|
||||
@ -1732,7 +1752,7 @@
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -1983,6 +2003,26 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
|
||||
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/raf": "^3.4.0",
|
||||
"core-js": "^3.8.3",
|
||||
"raf": "^3.4.1",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"rgbcolor": "^1.0.1",
|
||||
"stackblur-canvas": "^2.0.0",
|
||||
"svg-pathdata": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
|
||||
@ -2254,6 +2294,18 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.47.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
|
||||
"integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
@ -2343,7 +2395,7 @@
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2609,6 +2661,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
|
||||
"integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optional": true,
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-9.0.2.tgz",
|
||||
@ -3279,6 +3341,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-png": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
|
||||
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/pako": "^2.0.3",
|
||||
"iobuffer": "^5.3.2",
|
||||
"pako": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
@ -3289,6 +3362,12 @@
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz",
|
||||
@ -3894,7 +3973,7 @@
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -4026,6 +4105,12 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/iobuffer": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
|
||||
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
@ -4235,6 +4320,23 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jspdf": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz",
|
||||
"integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"fast-png": "^6.2.0",
|
||||
"fflate": "^0.8.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"canvg": "^3.0.11",
|
||||
"core-js": "^3.6.0",
|
||||
"dompurify": "^3.2.4",
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/junk": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/junk/-/junk-3.1.0.tgz",
|
||||
@ -4757,6 +4859,12 @@
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parse-author": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/parse-author/-/parse-author-2.0.0.tgz",
|
||||
@ -4864,6 +4972,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@ -5017,6 +5132,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rcedit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/rcedit/-/rcedit-3.1.0.tgz",
|
||||
@ -5104,6 +5229,13 @@
|
||||
"minimatch": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@ -5171,6 +5303,16 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/rgbcolor": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
|
||||
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
|
||||
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.15"
|
||||
}
|
||||
},
|
||||
"node_modules/roarr": {
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz",
|
||||
@ -5543,6 +5685,16 @@
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/stackblur-canvas": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
|
||||
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.14"
|
||||
}
|
||||
},
|
||||
"node_modules/stat-mode": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/stat-mode/-/stat-mode-1.0.0.tgz",
|
||||
@ -5697,6 +5849,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-pathdata": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
|
||||
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz",
|
||||
@ -5784,7 +5946,7 @@
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -5938,7 +6100,7 @@
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@ -20,9 +20,10 @@
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.3.9",
|
||||
"html2canvas": "^1.4.1",
|
||||
"three": "^0.160.0",
|
||||
"jspdf": "^3.0.4",
|
||||
"pinia": "^2.1.6",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"three": "^0.160.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-echarts": "^6.6.1",
|
||||
"vue-router": "^4.2.4"
|
||||
|
||||
@ -172,7 +172,7 @@
|
||||
<div class="body-foot-box">
|
||||
<div class="body-title-display">
|
||||
<div class="body-son-display">
|
||||
<img src="@/assets/new/title3.svg" alt="" style="margin-right: 8px;">
|
||||
<img src="@/assets/detection/title1.png" alt="" style="margin-right: 8px;">
|
||||
<div class="body-posture-text">足部压力</div>
|
||||
</div>
|
||||
<div class="body-son-display">
|
||||
@ -255,7 +255,7 @@
|
||||
<div class="body-userinfo-box">
|
||||
<div class="body-title-display">
|
||||
<div class="body-son-display">
|
||||
<img src="@/assets/new/title4.svg" alt="" style="margin-right: 8px;">
|
||||
<img src="@/assets/detection/title1.png" alt="" style="margin-right: 8px;">
|
||||
<div class="body-posture-text">用户信息</div>
|
||||
</div>
|
||||
<div class="body-son-display"></div>
|
||||
@ -317,7 +317,7 @@
|
||||
<div class="body-video-box">
|
||||
<div class="body-title-display">
|
||||
<div class="body-son-display">
|
||||
<img src="@/assets/new/title5.svg" alt="" style="margin-right: 8px;">
|
||||
<img src="@/assets/detection/title1.png" alt="" style="margin-right: 8px;">
|
||||
<div class="body-posture-text">视频</div>
|
||||
</div>
|
||||
<div class="body-son-display">
|
||||
@ -364,7 +364,7 @@
|
||||
<div class="pop-up-tip-container">
|
||||
<div class="pop-up-tip-header">
|
||||
<div>提示</div>
|
||||
<img src="@/assets/new/u264.svg" alt="" style="cursor: pointer;" @click="handleCancel">
|
||||
<img src="@/assets/detection/close.png" alt="" style="cursor: pointer;" @click="handleCancel">
|
||||
</div>
|
||||
<div class="pop-up-tip-text">本次检测未截图或录像操作,不予存档记录!</div>
|
||||
<div class="tipconfirmbutton-box">
|
||||
@ -387,7 +387,7 @@
|
||||
<div class="pop-up-camera-container">
|
||||
<div class="pop-up-camera-header">
|
||||
<div>相机参数设置</div>
|
||||
<img src="@/assets/new/u264.svg" alt="" style="cursor: pointer;" @click="handleCameraCancel">
|
||||
<img src="@/assets/detection/close.png" alt="" style="cursor: pointer;" @click="handleCameraCancel">
|
||||
</div>
|
||||
<div class="pop-up-camera-body">
|
||||
<div class="pop-up-camera-display">
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="generateReport-container-headertitle">生成报告</div>
|
||||
<div style="display: flex;">
|
||||
<div class="generateReport-container-cancelbutton" @click="closeCancel">取消</div>
|
||||
<div class="generateReport-container-confirmbutton" @click="confirmCancel">确定</div>
|
||||
<div class="generateReport-container-confirmbutton" @click="confirmGenerateReport">确定</div>
|
||||
</div>
|
||||
<img src="@/assets/archive/close.png" alt="" @click="closeCancel" style="cursor: pointer;">
|
||||
</div>
|
||||
@ -302,7 +302,7 @@ onMounted(() => {
|
||||
function closeCancel() {
|
||||
emit("closeGenerateReport",false)
|
||||
}
|
||||
function confirmCancel() {
|
||||
function confirmGenerateReport() {
|
||||
if(rawData.value.id == null){
|
||||
ElMessage.error('请选择原始数据')
|
||||
return
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="PopUpReport-container">
|
||||
<div class="PopUpReport-container" id="popup-report-root">
|
||||
<div class="PopUpReport-container-body" id="pdf-content">
|
||||
<div style="height: 100%; padding:0 90px; box-sizing: border-box;">
|
||||
<div class="PopUpReport-container-bodytitle">体态测量报告单</div>
|
||||
@ -184,12 +184,10 @@
|
||||
<div class="PopUpReport-border2">{{ detectionInfo.treatment_info }}</div>
|
||||
<div class="PopUpReport-title2">备注</div>
|
||||
<div class="PopUpReport-border3">{{ detectionInfo.suggestion_info }}</div>
|
||||
<div class="PopUpReport-footer">
|
||||
<div style="margin-right: 80px;">检测时间:{{ detectionInfo.created_at }}</div>
|
||||
<div style="margin-right: 80px;">报告时间:{{ getFormattedTime() }}</div>
|
||||
<div>检测医生:{{ detectionInfo.creator_name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -197,8 +195,8 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { getBackendUrl } from '@/services/api.js'
|
||||
import html2canvas from 'html2canvas';
|
||||
import jsPDF from 'jspdf';
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const emit = defineEmits([ 'closePopUpReport' ]);
|
||||
const props = defineProps({
|
||||
selectedPatient: {
|
||||
@ -274,65 +272,65 @@ onUnmounted(() => {
|
||||
|
||||
})
|
||||
const generatePDF = async () => {
|
||||
const element = document.getElementById('pdf-content');
|
||||
|
||||
// PDF尺寸配置
|
||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||
const pageWidth = pdf.internal.pageSize.getWidth();
|
||||
const pageHeight = pdf.internal.pageSize.getHeight();
|
||||
|
||||
// 计算缩放比例
|
||||
const scale = 2; // 提高清晰度
|
||||
const elementWidth = element.offsetWidth;
|
||||
const elementHeight = element.scrollHeight;
|
||||
const widthRatio = pageWidth / (elementWidth / scale);
|
||||
|
||||
// 智能分页算法
|
||||
const pageContentHeight = (pageHeight / widthRatio) * scale;
|
||||
let position = 0;
|
||||
let currentPage = 1;
|
||||
|
||||
while (position < elementHeight) {
|
||||
// 添加新页面(首页除外)
|
||||
if (currentPage > 1) {
|
||||
pdf.addPage();
|
||||
try {
|
||||
const root = document.getElementById('popup-report-root')
|
||||
if (!root) {
|
||||
ElMessage.error('报告容器未找到')
|
||||
return
|
||||
}
|
||||
|
||||
// 分块渲染
|
||||
const canvas = await html2canvas(element, {
|
||||
scale,
|
||||
useCORS: true,
|
||||
windowHeight: pageContentHeight,
|
||||
height: pageContentHeight,
|
||||
y: position,
|
||||
x: 0,
|
||||
scrollY: -window.scrollY // 锁定滚动位置
|
||||
});
|
||||
|
||||
// 计算当前块尺寸
|
||||
const imgData = canvas.toDataURL('image/jpeg', 1.0);
|
||||
const imgWidth = pageWidth;
|
||||
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
||||
|
||||
// 添加图片到PDF
|
||||
pdf.addImage(imgData, 'JPEG', 0, 0, imgWidth, imgHeight);
|
||||
|
||||
// 更新位置
|
||||
position += pageContentHeight;
|
||||
currentPage++;
|
||||
if (!window.electronAPI) {
|
||||
console.error('electronAPI 未定义')
|
||||
return
|
||||
}
|
||||
|
||||
// 生成并下载PDF
|
||||
const pdfBlob = pdf.output('blob');
|
||||
const url = URL.createObjectURL(pdfBlob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = '体态测量报告单.pdf';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
emit("closePopUpReport",false)
|
||||
const pdfBuffer = await window.electronAPI.generateReportPdf({
|
||||
selector: '#popup-report-root',
|
||||
pageSize: 'A4',
|
||||
landscape: true, // 横向
|
||||
printBackground: true
|
||||
})
|
||||
|
||||
const blob = new Blob([pdfBuffer], { type: 'application/pdf' })
|
||||
const form = new FormData()
|
||||
// 使用检测ID作为文件名
|
||||
const filename = `${props.detectionInfo.id || 'report'}.pdf`
|
||||
form.append('file', blob, filename)
|
||||
|
||||
// 如果有detectionInfo.id,则上传到后端
|
||||
if (props.detectionInfo.id) {
|
||||
const res = await fetch(`${BACKEND_URL}/api/reports/${props.detectionInfo.id}/upload`, {
|
||||
method: 'POST',
|
||||
body: form
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}: ${res.statusText}`)
|
||||
}
|
||||
|
||||
const json = await res.json()
|
||||
if (json.success) {
|
||||
ElMessage.success('报告生成并上传成功')
|
||||
emit('closePopUpReport', true)
|
||||
} else {
|
||||
throw new Error(json.error || '上传失败')
|
||||
}
|
||||
} else {
|
||||
// 仅供预览或下载
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
emit('closePopUpReport', true)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('报告生成异常:', e)
|
||||
ElMessage.error(`报告生成失败:${e.message}`)
|
||||
}
|
||||
};
|
||||
function getFormattedTime() {
|
||||
const now = new Date();
|
||||
@ -364,7 +362,7 @@ function getFormattedTime() {
|
||||
width: 1600px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 50px 0 20px;
|
||||
padding: 20px 0 30px;
|
||||
|
||||
|
||||
}
|
||||
@ -534,14 +532,150 @@ function getFormattedTime() {
|
||||
color: #383838;
|
||||
padding: 5px;
|
||||
}
|
||||
.PopUpOnlyReport-footer{
|
||||
margin-top: 40px;
|
||||
padding-top: 40px;
|
||||
border-top: 1px solid #333;
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
color: rgb(40, 40, 40);
|
||||
font-size: 18px;
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 10mm;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
/* 隐藏所有元素,但保留占位,避免 display:none 导致的父级隐藏问题 */
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* 显式显示目标容器及其子元素 */
|
||||
#popup-report-root,
|
||||
#popup-report-root *,
|
||||
#electron-print-container,
|
||||
#electron-print-container * {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
/* 确保根节点可见并重置布局 */
|
||||
#popup-report-root {
|
||||
position: static !important;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background-color: white !important;
|
||||
z-index: 9999 !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* 容器样式重置 */
|
||||
.PopUpReport-container-body {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
box-shadow: none !important;
|
||||
padding: 10px !important;
|
||||
border: none !important;
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
display: block !important;
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
|
||||
/* 移除内层div的固定高度 */
|
||||
.PopUpReport-container-body > div {
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 针对克隆节点的特定样式 */
|
||||
#electron-print-container .PopUpReport-container {
|
||||
position: static !important;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
/* 左右分栏调整 */
|
||||
.PopUpReport-container-leftbox,
|
||||
.PopUpReport-container-rightbox {
|
||||
width: 49% !important;
|
||||
display: inline-block !important;
|
||||
vertical-align: top !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 10px !important;
|
||||
border: none !important;
|
||||
/* 允许分栏内部内容自然分页 */
|
||||
break-inside: auto !important;
|
||||
}
|
||||
|
||||
.PopUpReport-container-leftbox {
|
||||
border-right: 1px solid #ccc !important;
|
||||
}
|
||||
|
||||
/* 图片容器自适应 */
|
||||
.PopUpReport-container-body div[style*="width: 600px"] {
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
margin-bottom: 10px !important;
|
||||
break-inside: avoid !important;
|
||||
}
|
||||
|
||||
/* 图片自适应 */
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
object-fit: contain !important;
|
||||
max-height: 300px !important; /* 限制最大高度,防止一页占满 */
|
||||
}
|
||||
|
||||
/* 头部信息紧凑化 */
|
||||
.PopUpReport-container-bodytitle {
|
||||
font-size: 24px !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.PopUpReport-container-display,
|
||||
.PopUpReport-container-userinfodisplay {
|
||||
padding: 10px 0 !important;
|
||||
}
|
||||
|
||||
.PopUpReport-container-userinfotext2 {
|
||||
font-size: 14px !important;
|
||||
width: auto !important;
|
||||
margin-right: 20px !important;
|
||||
padding-bottom: 5px !important;
|
||||
}
|
||||
|
||||
/* 标题字号调整 */
|
||||
.PopUpReport-content-title,
|
||||
.PopUpReport-container-testdatatitle,
|
||||
.PopUpReport-title2 {
|
||||
font-size: 16px !important;
|
||||
padding-top: 10px !important;
|
||||
padding-bottom: 5px !important;
|
||||
}
|
||||
|
||||
/* 分页控制 */
|
||||
.PopUpReport-content-title {
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
img {
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* 隐藏不需要的UI元素 */
|
||||
.displayflexselect-icon,
|
||||
::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user