修改动态血氧功能模版
This commit is contained in:
parent
dc83dbe497
commit
1cba193c9f
527
public/spo2-report-template.html
Normal file
527
public/spo2-report-template.html
Normal file
@ -0,0 +1,527 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>动态血氧监测报告</title>
|
||||
<style>
|
||||
body { background: #f5f7fa; margin: 0; font-family: 'Microsoft YaHei', Arial, sans-serif; }
|
||||
.spo2-report-template { max-width: 900px; margin: 0 auto; padding: 0; }
|
||||
.report-page { background: white; margin: 0 auto 40px auto; max-width: 900px; min-height: 900px; box-shadow: 0 2px 16px rgba(0,0,0,0.08); border-radius: 12px; padding: 48px 56px 32px 56px; page-break-after: always; }
|
||||
.report-title { text-align: center; font-size: 28px; font-weight: bold; margin-bottom: 32px; }
|
||||
.report-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 32px; font-size: 16px; margin-bottom: 32px; }
|
||||
.report-info-item { display: flex; }
|
||||
.report-info-label { display: inline-block; min-width: 80px; flex-shrink: 0; }
|
||||
.report-info-value { font-weight: 500; word-break: break-all; }
|
||||
.report-info-item.full-width { grid-column: span 2; }
|
||||
.stat-section { margin-bottom: 24px; }
|
||||
.stat-row { display: flex; flex-wrap: wrap; gap: 20px 60px; font-size: 18px; font-weight: 500; }
|
||||
.stat-item { white-space: nowrap; min-width: 180px; }
|
||||
.stat-value { color: #409eff; font-weight: bold; margin-left: 4px; }
|
||||
.distribution-section { margin-bottom: 32px; }
|
||||
.distribution-table { width: 100%; border-collapse: collapse; margin-top: 12px; margin-bottom: 12px; }
|
||||
.distribution-table th, .distribution-table td { border: 1px solid #e4e7ed; padding: 8px 12px; text-align: center; }
|
||||
.data-table-section { margin-bottom: 32px; }
|
||||
.extreme-table { width: 100%; border-collapse: collapse; margin-top: 12px; margin-bottom: 12px; }
|
||||
.extreme-table th, .extreme-table td { border: 1px solid #e4e7ed; padding: 8px 12px; text-align: center; }
|
||||
.diagnosis-content { margin: 48px 0 32px 0; padding: 32px; background: #f8f9fa; border-radius: 8px; min-height: 300px; }
|
||||
.diagnosis-text { font-size: 18px; white-space: pre-wrap; color: #303133; font-family: inherit; }
|
||||
.report-footer { display: flex; justify-content: space-between; margin-top: 80px; font-size: 16px; }
|
||||
.pie-charts-group { display: flex; gap: 32px; justify-content: center; margin-top: 32px; }
|
||||
.pie-chart-block { display: flex; flex-direction: column; align-items: center; background: #fff; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); padding: 24px 16px; min-width: 260px; }
|
||||
.pie-chart-title { font-size: 18px; font-weight: bold; margin-bottom: 12px; }
|
||||
.pie-chart-row { position: relative; }
|
||||
.pie-center-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; }
|
||||
.pie-center-value { font-size: 20px; font-weight: bold; color: #303133; }
|
||||
.pie-center-label { font-size: 12px; color: #909399; }
|
||||
.pie-legend { display: flex; flex-direction: column; }
|
||||
.pie-legend-item { display: flex; align-items: center; margin-bottom: 8px; font-size: 14px; }
|
||||
.pie-legend-color { width: 14px; height: 14px; border-radius: 2px; margin-right: 8px; }
|
||||
.pie-legend-label { width: 40px; }
|
||||
.pie-legend-range { width: 60px; color: #606266; }
|
||||
.pie-legend-value { margin-left: 8px; font-weight: 500; }
|
||||
#loading-mask { position: fixed; left: 0; top: 0; right: 0; bottom: 0; background: rgba(255,255,255,0.85); z-index: 9999; display: flex; align-items: center; justify-content: center; font-size: 22px; color: #409eff; letter-spacing: 2px; transition: opacity 0.3s; }
|
||||
#print-button { position: fixed; top: 20px; right: 20px; background-color: #409eff; color: white; border: none; border-radius: 4px; padding: 8px 16px; font-size: 16px; cursor: pointer; display: flex; align-items: center; box-shadow: 0 2px 6px rgba(0,0,0,0.15); z-index: 100; transition: all 0.2s; }
|
||||
#print-button:hover { background-color: #337ecc; }
|
||||
#print-button svg { margin-right: 6px; }
|
||||
|
||||
@media print {
|
||||
body { background: white; }
|
||||
.report-page { box-shadow: none; margin: 0; padding: 20px; page-break-after: always; }
|
||||
#print-button, #loading-mask { display: none !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading-mask">加载中...</div>
|
||||
<button id="print-button" onclick="window.print()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
|
||||
<rect x="6" y="14" width="12" height="8"></rect>
|
||||
</svg>
|
||||
打印报告
|
||||
</button>
|
||||
<div class="spo2-report-template">
|
||||
<div class="report-page">
|
||||
<h2 class="report-title">动态血氧监测报告</h2>
|
||||
<div class="report-info-grid">
|
||||
<div class="report-info-item"><span class="report-info-label">姓名:</span><span class="report-info-value patient-name">--</span></div>
|
||||
<div class="report-info-item"><span class="report-info-label">性别:</span><span class="report-info-value patient-gender">--</span></div>
|
||||
<div class="report-info-item"><span class="report-info-label">年龄:</span><span class="report-info-value patient-age">--</span></div>
|
||||
<div class="report-info-item"><span class="report-info-label">检查日期:</span><span class="report-info-value exam-date">--</span></div>
|
||||
<div class="report-info-item"><span class="report-info-label">设备:</span><span class="report-info-value device-name">--</span></div>
|
||||
<div class="report-info-item full-width"><span class="report-info-label">检查ID:</span><span class="report-info-value exam-id">--</span></div>
|
||||
</div>
|
||||
<div class="stat-section">
|
||||
<div class="stat-row">
|
||||
<div class="stat-item">平均血氧饱和度:<span class="stat-value average-spo2">--</span>%</div>
|
||||
<div class="stat-item">最低血氧饱和度:<span class="stat-value min-spo2">--</span>%</div>
|
||||
<div class="stat-item">最高血氧饱和度:<span class="stat-value max-spo2">--</span>%</div>
|
||||
<div class="stat-item">低氧时间:<span class="stat-value low-oxygen-time">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="distribution-section">
|
||||
<h3>血氧饱和度分布统计</h3>
|
||||
<table class="distribution-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间段</th>
|
||||
<th>优秀 (≥95%)</th>
|
||||
<th>良好 (90-94%)</th>
|
||||
<th>偏低 (85-89%)</th>
|
||||
<th>危险 (<85%)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>全天</td>
|
||||
<td class="all-excellent">--</td>
|
||||
<td class="all-good">--</td>
|
||||
<td class="all-warning">--</td>
|
||||
<td class="all-danger">--</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>白天</td>
|
||||
<td class="day-excellent">--</td>
|
||||
<td class="day-good">--</td>
|
||||
<td class="day-warning">--</td>
|
||||
<td class="day-danger">--</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>夜间</td>
|
||||
<td class="night-excellent">--</td>
|
||||
<td class="night-good">--</td>
|
||||
<td class="night-warning">--</td>
|
||||
<td class="night-danger">--</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="data-table-section">
|
||||
<h3>极值统计</h3>
|
||||
<table class="extreme-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>类型</th>
|
||||
<th>血氧值</th>
|
||||
<th>发生时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="color: #e53e3e; font-weight: bold">最低血氧值</td>
|
||||
<td style="color: #e53e3e; font-weight: bold" class="min-spo2-value">--</td>
|
||||
<td class="min-spo2-time">--</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #2563eb; font-weight: bold">最高血氧值</td>
|
||||
<td style="color: #2563eb; font-weight: bold" class="max-spo2-value">--</td>
|
||||
<td class="max-spo2-time">--</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-page report-diagnosis">
|
||||
<h2 class="report-title">诊断结论</h2>
|
||||
<div class="diagnosis-content">
|
||||
<pre class="diagnosis-text">--</pre>
|
||||
</div>
|
||||
<div class="report-footer">
|
||||
<div>报告医生:________________</div>
|
||||
<div>报告日期:<span class="report-date">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-page report-pie-chart">
|
||||
<h2 class="report-title">血氧分布饼状图</h2>
|
||||
<div class="pie-charts-group" style="flex-direction:column;align-items:center;gap:48px;">
|
||||
<div class="pie-chart-block" style="width:90%;max-width:600px;padding:32px 24px;">
|
||||
<div class="pie-chart-title">全天血氧分布 (24小时)</div>
|
||||
<div class="pie-chart-row" style="display:flex;align-items:center;justify-content:center;margin-top:16px;">
|
||||
<div style="position:relative;width:200px;height:200px;">
|
||||
<svg class="pie-ring all-day-pie" width="200" height="200" viewBox="0 0 200 200"></svg>
|
||||
<div class="pie-center-text all-day-center">
|
||||
<div class="pie-center-value">--</div>
|
||||
<div class="pie-center-label">平均血氧</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pie-legend all-day-legend" style="margin-left:40px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;width:90%;max-width:600px;gap:24px;justify-content:center;">
|
||||
<div class="pie-chart-block" style="flex:1;padding:24px 16px;">
|
||||
<div class="pie-chart-title">白天血氧分布 (8:00-22:00)</div>
|
||||
<div class="pie-chart-row" style="display:flex;align-items:center;justify-content:center;margin-top:16px;">
|
||||
<div style="position:relative;width:160px;height:160px;">
|
||||
<svg class="pie-ring daytime-pie" width="160" height="160" viewBox="0 0 160 160"></svg>
|
||||
<div class="pie-center-text daytime-center">
|
||||
<div class="pie-center-value">--</div>
|
||||
<div class="pie-center-label">白天</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pie-legend daytime-legend" style="margin-top:16px;"></div>
|
||||
</div>
|
||||
<div class="pie-chart-block" style="flex:1;padding:24px 16px;">
|
||||
<div class="pie-chart-title">夜间血氧分布 (22:00-8:00)</div>
|
||||
<div class="pie-chart-row" style="display:flex;align-items:center;justify-content:center;margin-top:16px;">
|
||||
<div style="position:relative;width:160px;height:160px;">
|
||||
<svg class="pie-ring nighttime-pie" width="160" height="160" viewBox="0 0 160 160"></svg>
|
||||
<div class="pie-center-text nighttime-center">
|
||||
<div class="pie-center-value">--</div>
|
||||
<div class="pie-center-label">夜间</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pie-legend nighttime-legend" style="margin-top:16px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// loading 遮罩控制
|
||||
function showLoading() { document.getElementById('loading-mask').style.display = 'flex'; }
|
||||
function hideLoading() { document.getElementById('loading-mask').style.display = 'none'; }
|
||||
|
||||
// 数据填充主入口
|
||||
function fillData(data) {
|
||||
// 基本信息
|
||||
document.querySelector('.patient-name').textContent = data.patientInfo?.name || '--';
|
||||
document.querySelector('.patient-gender').textContent = data.patientInfo?.gender === '1' ? '男' : data.patientInfo?.gender === '2' ? '女' : '--';
|
||||
document.querySelector('.patient-age').textContent = data.patientInfo?.age || '--';
|
||||
document.querySelector('.exam-id').textContent = data.patientInfo?.examid || '--';
|
||||
document.querySelector('.exam-date').textContent = formatDate(data.patientInfo?.weartime);
|
||||
document.querySelector('.device-name').textContent = data.patientInfo?.devicename || '--';
|
||||
// 统计
|
||||
document.querySelector('.average-spo2').textContent = data.spo2Stats?.averageSpO2 ?? '--';
|
||||
document.querySelector('.min-spo2').textContent = data.spo2Stats?.minSpO2 ?? '--';
|
||||
document.querySelector('.max-spo2').textContent = data.spo2Stats?.maxSpO2 ?? '--';
|
||||
document.querySelector('.low-oxygen-time').textContent = data.spo2Stats?.lowOxygenTime > 0 ? data.spo2Stats.lowOxygenTime + '分钟' : '无低氧';
|
||||
// 分布
|
||||
document.querySelector('.all-excellent').textContent = safeDist(data.timePeriodData?.all?.excellent);
|
||||
document.querySelector('.all-good').textContent = safeDist(data.timePeriodData?.all?.good);
|
||||
document.querySelector('.all-warning').textContent = safeDist(data.timePeriodData?.all?.warning);
|
||||
document.querySelector('.all-danger').textContent = safeDist(data.timePeriodData?.all?.danger);
|
||||
document.querySelector('.day-excellent').textContent = safeDist(data.timePeriodData?.daytime?.excellent);
|
||||
document.querySelector('.day-good').textContent = safeDist(data.timePeriodData?.daytime?.good);
|
||||
document.querySelector('.day-warning').textContent = safeDist(data.timePeriodData?.daytime?.warning);
|
||||
document.querySelector('.day-danger').textContent = safeDist(data.timePeriodData?.daytime?.danger);
|
||||
document.querySelector('.night-excellent').textContent = safeDist(data.timePeriodData?.nighttime?.excellent);
|
||||
document.querySelector('.night-good').textContent = safeDist(data.timePeriodData?.nighttime?.good);
|
||||
document.querySelector('.night-warning').textContent = safeDist(data.timePeriodData?.nighttime?.warning);
|
||||
document.querySelector('.night-danger').textContent = safeDist(data.timePeriodData?.nighttime?.danger);
|
||||
// 极值
|
||||
document.querySelector('.min-spo2-value').textContent = data.spo2Stats?.minSpO2 ?? '--';
|
||||
document.querySelector('.max-spo2-value').textContent = data.spo2Stats?.maxSpO2 ?? '--';
|
||||
document.querySelector('.min-spo2-time').textContent = getExtremeTime('min', data.dataTableList);
|
||||
document.querySelector('.max-spo2-time').textContent = getExtremeTime('max', data.dataTableList);
|
||||
// 诊断结论
|
||||
document.querySelector('.diagnosis-text').textContent = data.diagnosisForm?.conclusion || '--';
|
||||
document.querySelector('.report-date').textContent = formatDate(new Date());
|
||||
// 饼图
|
||||
renderPieChartRing(data.timePeriodData?.all, document.querySelector('.all-day-pie'), document.querySelector('.all-day-legend'));
|
||||
renderPieChartRing(data.timePeriodData?.daytime, document.querySelector('.daytime-pie'), document.querySelector('.daytime-legend'));
|
||||
renderPieChartRing(data.timePeriodData?.nighttime, document.querySelector('.nighttime-pie'), document.querySelector('.nighttime-legend'));
|
||||
hideLoading();
|
||||
}
|
||||
function safeDist(obj) { return obj ? `${obj.value ?? 0} (${obj.percentage ?? 0}%)` : '--'; }
|
||||
function formatDate(date) {
|
||||
if (!date) return '--';
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
function getExtremeTime(type, dataTableList) {
|
||||
if (!dataTableList || dataTableList.length === 0) return '--';
|
||||
let targetRow = dataTableList[0];
|
||||
if (type === 'min') {
|
||||
targetRow = dataTableList.reduce((min, cur) => (cur.spo2value < min.spo2value ? cur : min), dataTableList[0]);
|
||||
} else {
|
||||
targetRow = dataTableList.reduce((max, cur) => (cur.spo2value > max.spo2value ? cur : max), dataTableList[0]);
|
||||
}
|
||||
return formatDateTime(targetRow.measuretime);
|
||||
}
|
||||
function formatDateTime(date) {
|
||||
if (!date) return '--';
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
const hour = String(d.getHours()).padStart(2, '0');
|
||||
const minute = String(d.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hour}:${minute}`;
|
||||
}
|
||||
// 饼图渲染
|
||||
function renderPieChartRing(data, svgElement, legendElement) {
|
||||
// 颜色和标签 - 更新颜色方案,使用更鲜明的对比色
|
||||
const segments = [
|
||||
{ label: '优秀', color: '#10b981', range: '≥95%', value: data?.excellent?.value ?? 0, percent: data?.excellent?.percentage ?? 0 },
|
||||
{ label: '良好', color: '#3b82f6', range: '90-94%', value: data?.good?.value ?? 0, percent: data?.good?.percentage ?? 0 },
|
||||
{ label: '偏低', color: '#f59e0b', range: '85-89%', value: data?.warning?.value ?? 0, percent: data?.warning?.percentage ?? 0 },
|
||||
{ label: '危险', color: '#ef4444', range: '<85%', value: data?.danger?.value ?? 0, percent: data?.danger?.percentage ?? 0 }
|
||||
];
|
||||
|
||||
// 计算平均值并更新中心文本
|
||||
const centerTextElement = svgElement.parentElement.querySelector('.pie-center-text .pie-center-value');
|
||||
if (centerTextElement) {
|
||||
// 从数据中提取平均值
|
||||
let avgValue = '--';
|
||||
if (data === window.reportData?.timePeriodData?.all) {
|
||||
avgValue = window.reportData?.spo2Stats?.averageSpO2 ?? '--';
|
||||
} else if (data === window.reportData?.timePeriodData?.daytime) {
|
||||
avgValue = window.reportData?.spo2Stats?.daytimeAverageSpO2 ?? window.reportData?.spo2Stats?.averageSpO2 ?? '--';
|
||||
} else if (data === window.reportData?.timePeriodData?.nighttime) {
|
||||
avgValue = window.reportData?.spo2Stats?.nighttimeAverageSpO2 ?? window.reportData?.spo2Stats?.averageSpO2 ?? '--';
|
||||
}
|
||||
centerTextElement.textContent = avgValue;
|
||||
}
|
||||
|
||||
// 只保留有数据的分段
|
||||
const nonZeroSegments = segments.filter(seg => seg.percent > 0);
|
||||
svgElement.innerHTML = '';
|
||||
|
||||
// 环形参数
|
||||
const size = svgElement.getAttribute('width');
|
||||
const cx = size / 2;
|
||||
const cy = size / 2;
|
||||
const r = size * 0.38; // 稍微调整半径比例
|
||||
const strokeWidth = size * 0.18; // 调整环形厚度
|
||||
const C = 2 * Math.PI * r;
|
||||
|
||||
// 处理所有分段都为0的情况
|
||||
if (nonZeroSegments.length === 0) {
|
||||
// 画灰色圆环
|
||||
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
circle.setAttribute('cx', cx);
|
||||
circle.setAttribute('cy', cy);
|
||||
circle.setAttribute('r', r);
|
||||
circle.setAttribute('stroke', '#e5e7eb');
|
||||
circle.setAttribute('stroke-width', strokeWidth);
|
||||
circle.setAttribute('fill', 'none');
|
||||
svgElement.appendChild(circle);
|
||||
|
||||
// 图例显示"暂无数据"
|
||||
if (legendElement) {
|
||||
legendElement.innerHTML = '<div style="color:#9ca3af;font-size:14px;padding:8px;">暂无数据</div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果只有一个分段,直接画满整个圆
|
||||
if (nonZeroSegments.length === 1) {
|
||||
const seg = nonZeroSegments[0];
|
||||
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
circle.setAttribute('cx', cx);
|
||||
circle.setAttribute('cy', cy);
|
||||
circle.setAttribute('r', r);
|
||||
circle.setAttribute('stroke', seg.color);
|
||||
circle.setAttribute('stroke-width', strokeWidth);
|
||||
circle.setAttribute('fill', 'none');
|
||||
svgElement.appendChild(circle);
|
||||
|
||||
// 添加高亮效果
|
||||
const highlight = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
highlight.setAttribute('cx', cx);
|
||||
highlight.setAttribute('cy', cy);
|
||||
highlight.setAttribute('r', r);
|
||||
highlight.setAttribute('stroke', 'white');
|
||||
highlight.setAttribute('stroke-width', 2);
|
||||
highlight.setAttribute('stroke-opacity', '0.4');
|
||||
highlight.setAttribute('fill', 'none');
|
||||
svgElement.appendChild(highlight);
|
||||
|
||||
// 图例只显示这一个分段
|
||||
if (legendElement) {
|
||||
legendElement.innerHTML = '';
|
||||
renderLegendItem(legendElement, seg, 100);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 多个分段的情况,归一化处理
|
||||
const total = nonZeroSegments.reduce((sum, seg) => sum + seg.percent, 0);
|
||||
const normalizedSegments = nonZeroSegments.map(seg => ({ ...seg, percent: seg.percent / total * 100 }));
|
||||
|
||||
// 添加背景圆环
|
||||
const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
bgCircle.setAttribute('cx', cx);
|
||||
bgCircle.setAttribute('cy', cy);
|
||||
bgCircle.setAttribute('r', r);
|
||||
bgCircle.setAttribute('stroke', '#f1f5f9');
|
||||
bgCircle.setAttribute('stroke-width', strokeWidth);
|
||||
bgCircle.setAttribute('fill', 'none');
|
||||
svgElement.appendChild(bgCircle);
|
||||
|
||||
// 添加白色圆形作为中心背景
|
||||
const centerCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
centerCircle.setAttribute('cx', cx);
|
||||
centerCircle.setAttribute('cy', cy);
|
||||
centerCircle.setAttribute('r', r - strokeWidth/2);
|
||||
centerCircle.setAttribute('fill', 'white');
|
||||
svgElement.appendChild(centerCircle);
|
||||
|
||||
// 使用路径绘制饼图,而不是使用圆环的dash属性
|
||||
let startAngle = -Math.PI / 2; // 从12点钟方向开始
|
||||
|
||||
normalizedSegments.forEach(seg => {
|
||||
const segmentPercent = seg.percent / 100;
|
||||
const endAngle = startAngle + segmentPercent * 2 * Math.PI;
|
||||
|
||||
// 计算路径
|
||||
const x1 = cx + r * Math.cos(startAngle);
|
||||
const y1 = cy + r * Math.sin(startAngle);
|
||||
const x2 = cx + r * Math.cos(endAngle);
|
||||
const y2 = cy + r * Math.sin(endAngle);
|
||||
|
||||
// 内外半径
|
||||
const innerR = r - strokeWidth / 2;
|
||||
const outerR = r + strokeWidth / 2;
|
||||
|
||||
// 内外圆弧的起点和终点
|
||||
const innerStartX = cx + innerR * Math.cos(startAngle);
|
||||
const innerStartY = cy + innerR * Math.sin(startAngle);
|
||||
const innerEndX = cx + innerR * Math.cos(endAngle);
|
||||
const innerEndY = cy + innerR * Math.sin(endAngle);
|
||||
|
||||
const outerStartX = cx + outerR * Math.cos(startAngle);
|
||||
const outerStartY = cy + outerR * Math.sin(startAngle);
|
||||
const outerEndX = cx + outerR * Math.cos(endAngle);
|
||||
const outerEndY = cy + outerR * Math.sin(endAngle);
|
||||
|
||||
// 创建路径
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
|
||||
// 大弧标志 (large-arc-flag)
|
||||
const largeArcFlag = segmentPercent > 0.5 ? 1 : 0;
|
||||
|
||||
// 路径数据
|
||||
const d = [
|
||||
`M ${innerEndX} ${innerEndY}`,
|
||||
`A ${innerR} ${innerR} 0 ${largeArcFlag} 0 ${innerStartX} ${innerStartY}`,
|
||||
`L ${outerStartX} ${outerStartY}`,
|
||||
`A ${outerR} ${outerR} 0 ${largeArcFlag} 1 ${outerEndX} ${outerEndY}`,
|
||||
'Z'
|
||||
].join(' ');
|
||||
|
||||
path.setAttribute('d', d);
|
||||
path.setAttribute('fill', seg.color);
|
||||
|
||||
// 添加过渡动画
|
||||
path.style.transition = 'all 0.5s ease-out';
|
||||
|
||||
// 添加高亮效果
|
||||
path.addEventListener('mouseenter', () => {
|
||||
path.setAttribute('opacity', '0.8');
|
||||
});
|
||||
path.addEventListener('mouseleave', () => {
|
||||
path.setAttribute('opacity', '1');
|
||||
});
|
||||
|
||||
svgElement.appendChild(path);
|
||||
|
||||
// 更新起始角度
|
||||
startAngle = endAngle;
|
||||
});
|
||||
|
||||
// 图例显示归一化后的分段
|
||||
if (legendElement) {
|
||||
legendElement.innerHTML = '';
|
||||
normalizedSegments.forEach(seg => {
|
||||
renderLegendItem(legendElement, seg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染图例项
|
||||
function renderLegendItem(legendElement, segment, overridePercent) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'pie-legend-item';
|
||||
|
||||
const colorSpan = document.createElement('span');
|
||||
colorSpan.className = 'pie-legend-color';
|
||||
colorSpan.style.backgroundColor = segment.color;
|
||||
|
||||
const labelSpan = document.createElement('span');
|
||||
labelSpan.className = 'pie-legend-label';
|
||||
labelSpan.textContent = segment.label;
|
||||
|
||||
const rangeSpan = document.createElement('span');
|
||||
rangeSpan.className = 'pie-legend-range';
|
||||
rangeSpan.textContent = segment.range;
|
||||
|
||||
const valueSpan = document.createElement('span');
|
||||
valueSpan.className = 'pie-legend-value';
|
||||
valueSpan.textContent = `${Math.round(overridePercent || segment.percent)}%`;
|
||||
|
||||
item.appendChild(colorSpan);
|
||||
item.appendChild(labelSpan);
|
||||
item.appendChild(rangeSpan);
|
||||
item.appendChild(valueSpan);
|
||||
|
||||
legendElement.appendChild(item);
|
||||
}
|
||||
|
||||
// 监听父窗口传来的数据
|
||||
window.addEventListener('message', (event) => {
|
||||
showLoading();
|
||||
if (event.data?.type === 'INIT_SPO2_REPORT') {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 保存全局数据用于饼图中心显示
|
||||
window.reportData = event.data.data;
|
||||
fillData(event.data.data);
|
||||
} catch (error) {
|
||||
console.error('填充报告数据失败:', error);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
// 支持独立打开时自动填充测试数据
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
showLoading();
|
||||
if (!window.opener && !window.parent) {
|
||||
setTimeout(() => {
|
||||
fillData({
|
||||
patientInfo: { name: '测试患者', gender: '1', age: 45, examid: 'SPO2-2024-001', weartime: '2024-01-20', devicename: 'SPO2-Monitor-X1' },
|
||||
spo2Stats: { averageSpO2: 96, minSpO2: 88, maxSpO2: 99, lowOxygenTime: 15 },
|
||||
timePeriodData: {
|
||||
all: { excellent: { value: 360, percentage: 75 }, good: { value: 96, percentage: 20 }, warning: { value: 24, percentage: 5 }, danger: { value: 0, percentage: 0 } },
|
||||
daytime: { excellent: { value: 240, percentage: 80 }, good: { value: 54, percentage: 18 }, warning: { value: 6, percentage: 2 }, danger: { value: 0, percentage: 0 } },
|
||||
nighttime: { excellent: { value: 120, percentage: 67 }, good: { value: 42, percentage: 23 }, warning: { value: 18, percentage: 10 }, danger: { value: 0, percentage: 0 } }
|
||||
},
|
||||
dataTableList: [
|
||||
{ spo2value: 88, measuretime: '2024-01-20 02:00:00' },
|
||||
{ spo2value: 99, measuretime: '2024-01-20 14:00:00' }
|
||||
],
|
||||
diagnosisForm: { conclusion: '患者血氧饱和度总体表现良好,夜间偶有轻微低氧情况,建议继续观察。' }
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -17,6 +17,7 @@ export interface Spo2infoVO {
|
||||
superiorrequest: number // 上级请求
|
||||
createtime: Date // 创建时间
|
||||
updatetime: Date // 更新时间
|
||||
diagnosis: string // 诊断结论
|
||||
}
|
||||
|
||||
// 血氧信息 API
|
||||
@ -77,6 +78,14 @@ export const Spo2infoApi = {
|
||||
})
|
||||
},
|
||||
|
||||
//更新诊断结果
|
||||
upSpoInfoDiagnosis: async (data: any) => {
|
||||
return await request.put({
|
||||
url: `/system/spo2info/update-diagnosis`,
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 获取血氧信息统计数据
|
||||
getSpO2Analysis: async (regid: string, examid: string, weartime: string) => {
|
||||
return await request.get({
|
||||
|
||||
@ -225,6 +225,33 @@
|
||||
|
||||
<!-- 数据统计内容 -->
|
||||
<div v-if="activeTab === 'data-stats'" class="tab-content">
|
||||
<!-- 等级分类说明 -->
|
||||
<div class="level-classification-info">
|
||||
<h4>血氧等级分类说明</h4>
|
||||
<div class="level-grid">
|
||||
<div class="level-item">
|
||||
<el-tag type="success" size="small">优秀</el-tag>
|
||||
<span class="level-range">≥95%</span>
|
||||
<span class="level-desc">血氧饱和度正常,状态良好</span>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<el-tag type="primary" size="small">良好</el-tag>
|
||||
<span class="level-range">90-94%</span>
|
||||
<span class="level-desc">血氧饱和度轻度偏低,需要关注</span>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<el-tag type="warning" size="small">偏低</el-tag>
|
||||
<span class="level-range">85-89%</span>
|
||||
<span class="level-desc">血氧饱和度明显偏低,需要干预</span>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<el-tag type="danger" size="small">危险</el-tag>
|
||||
<span class="level-range"><85%</span>
|
||||
<span class="level-desc">血氧饱和度严重不足,需要紧急处理</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="data-table-container">
|
||||
<div class="table-header">
|
||||
<h4>监测数据列表</h4>
|
||||
@ -253,9 +280,14 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="spo2value" label="血氧饱和度" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="spo2-value" :class="getSpO2Class(row.spo2value)">
|
||||
{{ row.spo2value }}%
|
||||
</span>
|
||||
<span class="spo2-value"> {{ row.spo2value }}% </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="spo2Level" label="血氧等级" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getSpO2TagType(row.spo2value)" size="small">
|
||||
{{ getSpO2LevelText(row.spo2value) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="pulsevalue" label="脉率" min-width="100" align="center">
|
||||
@ -351,7 +383,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
save: [data: any]
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
@ -365,13 +396,15 @@ const saving = ref(false)
|
||||
|
||||
// 患者信息
|
||||
const patientInfo = ref({
|
||||
id: 0, // 患者记录ID
|
||||
name: '',
|
||||
gender: '',
|
||||
age: '',
|
||||
regid: '',
|
||||
examid: '',
|
||||
weartime: '',
|
||||
devicename: ''
|
||||
devicename: '',
|
||||
diagnosis: '' // 新增诊断结论字段
|
||||
})
|
||||
|
||||
// 血氧统计数据
|
||||
@ -499,21 +532,19 @@ const handleSave = async () => {
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
const saveData = {
|
||||
patientId: patientInfo.value.examid,
|
||||
diagnosis: diagnosisForm,
|
||||
stats: {
|
||||
spo2: spo2Stats.value,
|
||||
data: dataStats.value
|
||||
}
|
||||
// 调用更新诊断结果接口
|
||||
const updateData = {
|
||||
id: patientInfo.value.id, // 患者记录ID
|
||||
diagnosis: diagnosisForm.conclusion // 诊断结论
|
||||
}
|
||||
|
||||
emit('save', saveData)
|
||||
ElMessage.success('保存成功')
|
||||
console.log('更新诊断结果参数:', updateData)
|
||||
await Spo2infoApi.upSpoInfoDiagnosis(updateData)
|
||||
|
||||
ElMessage.success('诊断结果保存成功')
|
||||
visible.value = false
|
||||
} catch (error) {
|
||||
console.error('保存诊断结果失败:', error)
|
||||
ElMessage.error('保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
@ -541,6 +572,22 @@ const getSpO2Class = (value: number) => {
|
||||
return 'spo2-danger'
|
||||
}
|
||||
|
||||
// 获取血氧等级文本
|
||||
const getSpO2LevelText = (value: number) => {
|
||||
if (value >= 95) return '优秀'
|
||||
if (value >= 90) return '良好'
|
||||
if (value >= 85) return '偏低'
|
||||
return '危险'
|
||||
}
|
||||
|
||||
// 获取血氧等级标签类型
|
||||
const getSpO2TagType = (value: number) => {
|
||||
if (value >= 95) return 'success'
|
||||
if (value >= 90) return 'primary'
|
||||
if (value >= 85) return 'warning'
|
||||
return 'danger'
|
||||
}
|
||||
|
||||
// 获取数据表格数据
|
||||
const getDataTableList = async () => {
|
||||
// 确保有患者数据
|
||||
@ -739,6 +786,43 @@ const getAnalysisData = async () => {
|
||||
|
||||
// 重置时间段选择
|
||||
selectedTimePeriod.value = 'all'
|
||||
|
||||
// 自动生成诊断结论
|
||||
const generateDiagnosisText = (stats, timePeriodData) => {
|
||||
// 计算分析建议
|
||||
const getSimpleAnalysis = () => {
|
||||
const day = timePeriodData.daytime
|
||||
const night = timePeriodData.nighttime
|
||||
let result: string[] = []
|
||||
if (day.warning.percentage > 10 || day.danger.percentage > 0) {
|
||||
result.push('白天偏低或危险占比高,建议关注白天血氧变化。')
|
||||
} else {
|
||||
result.push('白天血氧整体良好。')
|
||||
}
|
||||
if (night.warning.percentage > 10 || night.danger.percentage > 0) {
|
||||
result.push('夜间偏低或危险占比高,建议夜间加强监测。')
|
||||
} else {
|
||||
result.push('夜间血氧整体良好。')
|
||||
}
|
||||
return result.join(' ')
|
||||
}
|
||||
return [
|
||||
`全程共测量${stats.totalRecords || '--'}次,有效率100%;`,
|
||||
`平均血氧饱和度:${stats.averageSpO2 || '--'}%;最低血氧饱和度:${stats.minSpO2 || '--'}%;最高血氧饱和度:${stats.maxSpO2 || '--'}%;低氧时间:${stats.lowOxygenTime > 0 ? stats.lowOxygenTime + '分钟' : '无低氧'};`,
|
||||
`全天分布:优秀${timePeriodData.all.excellent.value || 0}次(${timePeriodData.all.excellent.percentage || 0}% ),良好${timePeriodData.all.good.value || 0}次(${timePeriodData.all.good.percentage || 0}% ),偏低${timePeriodData.all.warning.value || 0}次(${timePeriodData.all.warning.percentage || 0}% ),危险${timePeriodData.all.danger.value || 0}次(${timePeriodData.all.danger.percentage || 0}% );`,
|
||||
`白天分布:优秀${timePeriodData.daytime.excellent.value || 0}次(${timePeriodData.daytime.excellent.percentage || 0}% ),良好${timePeriodData.daytime.good.value || 0}次(${timePeriodData.daytime.good.percentage || 0}% ),偏低${timePeriodData.daytime.warning.value || 0}次(${timePeriodData.daytime.warning.percentage || 0}% ),危险${timePeriodData.daytime.danger.value || 0}次(${timePeriodData.daytime.danger.percentage || 0}% );`,
|
||||
`夜间分布:优秀${timePeriodData.nighttime.excellent.value || 0}次(${timePeriodData.nighttime.excellent.percentage || 0}% ),良好${timePeriodData.nighttime.good.value || 0}次(${timePeriodData.nighttime.good.percentage || 0}% ),偏低${timePeriodData.nighttime.warning.value || 0}次(${timePeriodData.nighttime.warning.percentage || 0}% ),危险${timePeriodData.nighttime.danger.value || 0}次(${timePeriodData.nighttime.danger.percentage || 0}% );`,
|
||||
`分析:${getSimpleAnalysis()}`,
|
||||
`建议:请结合患者实际情况综合分析。`
|
||||
].join('\n')
|
||||
}
|
||||
// 如果诊断结论为空,自动填充
|
||||
if (!diagnosisForm.conclusion) {
|
||||
diagnosisForm.conclusion = generateDiagnosisText(
|
||||
{ ...spo2Stats.value, totalRecords: response.totalRecords },
|
||||
timePeriodData.value
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取血氧分析数据失败:', error)
|
||||
ElMessage.error('获取分析数据失败')
|
||||
@ -751,6 +835,12 @@ const getAnalysisData = async () => {
|
||||
const updatePatientInfo = () => {
|
||||
if (props.patientData) {
|
||||
patientInfo.value = { ...props.patientData }
|
||||
// 优先加载表里的诊断结论
|
||||
if (patientInfo.value.diagnosis) {
|
||||
diagnosisForm.conclusion = patientInfo.value.diagnosis
|
||||
} else {
|
||||
diagnosisForm.conclusion = ''
|
||||
}
|
||||
// 患者信息更新后,自动获取分析数据
|
||||
getAnalysisData()
|
||||
}
|
||||
@ -1281,6 +1371,51 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
// 等级分类说明样式
|
||||
.level-classification-info {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.level-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
.level-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #409eff;
|
||||
|
||||
.level-range {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.level-desc {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 数据表格样式
|
||||
.data-table-container {
|
||||
height: 100%;
|
||||
@ -1344,29 +1479,8 @@ defineExpose({
|
||||
|
||||
.spo2-value {
|
||||
font-weight: 600;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
|
||||
&.spo2-excellent {
|
||||
background: #f0f9ff;
|
||||
color: #0ea5e9;
|
||||
}
|
||||
|
||||
&.spo2-good {
|
||||
background: #f0fdf4;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
&.spo2-warning {
|
||||
background: #fefce8;
|
||||
color: #ca8a04;
|
||||
}
|
||||
|
||||
&.spo2-danger {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
}
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.pulse-value {
|
||||
|
||||
@ -290,6 +290,26 @@
|
||||
:patient-data="currentPatient"
|
||||
@save="handleAnalysisSave"
|
||||
/>
|
||||
|
||||
<!-- 报告模版弹窗(iframe方式) -->
|
||||
<el-dialog
|
||||
v-model="iframeDialogVisible"
|
||||
title="血氧监测报告"
|
||||
fullscreen
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="true"
|
||||
destroy-on-close
|
||||
>
|
||||
<iframe
|
||||
ref="reportIframe"
|
||||
:src="iframeReportUrl"
|
||||
width="100%"
|
||||
height="100%"
|
||||
frameborder="0"
|
||||
style="min-height: 100vh"
|
||||
@load="onIframeLoad"
|
||||
></iframe>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -303,6 +323,8 @@ import PatientSelect from '@/patientcom/index.vue'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { Search, Refresh, Plus, Download } from '@element-plus/icons-vue'
|
||||
import { OrgApi } from '@/api/org'
|
||||
// 新增:引入报告模版组件
|
||||
import ReportTemplate from '@/views/analysis/Spo2/ReportTemplate.vue'
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const total = ref(0)
|
||||
@ -315,6 +337,16 @@ const Profilevo = ref<any>({})
|
||||
const analysisDialogVisible = ref(false)
|
||||
const currentPatient = ref<any>({})
|
||||
|
||||
// 报告弹窗相关
|
||||
const reportDialogVisible = ref(false)
|
||||
const reportData = ref<any>({})
|
||||
|
||||
// 新增:iframe弹窗相关
|
||||
const iframeDialogVisible = ref(false)
|
||||
const iframeReportUrl = ref('')
|
||||
const reportIframe = ref()
|
||||
let lastReportData: any = null
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
@ -420,9 +452,132 @@ const handleAnalysisSave = (data: any) => {
|
||||
getList() // 刷新列表
|
||||
}
|
||||
// 查看
|
||||
const handleView = (row: any) => {
|
||||
// TODO: 调用查看接口
|
||||
ElMessage.info('查看功能待实现')
|
||||
const handleView = async (row: any) => {
|
||||
if (!row.diagnosis || row.diagnosis.trim() === '') {
|
||||
ElMessage.warning('请先填写诊断结论后再查看报告')
|
||||
return
|
||||
}
|
||||
// 格式化 weartime
|
||||
const formatDateTimeForAPI = (dateTime: string | Date) => {
|
||||
if (!dateTime) return ''
|
||||
const d = new Date(dateTime)
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hour = String(d.getHours()).padStart(2, '0')
|
||||
const minute = String(d.getMinutes()).padStart(2, '0')
|
||||
const second = String(d.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
||||
}
|
||||
try {
|
||||
// 1. 查分析数据
|
||||
const weartimeStr = formatDateTimeForAPI(row.weartime)
|
||||
const analysis = await Spo2infoApi.getSpO2Analysis(row.regid, row.examid, weartimeStr)
|
||||
// 2. 查明细列表
|
||||
const dataTableRes = await (
|
||||
await import('@/api/spo2data')
|
||||
).Spo2dataApi.getSpo2dataPage({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
regid: row.regid,
|
||||
examid: row.examid,
|
||||
weartime: weartimeStr
|
||||
})
|
||||
// 3. 百分比算法
|
||||
const calcPercent = (count: number, total: number) =>
|
||||
total > 0 ? Math.round((count / total) * 100) : 0
|
||||
const totalRecords = analysis.totalRecords || 0
|
||||
const timePeriodData = {
|
||||
all: {
|
||||
excellent: {
|
||||
value: analysis.excellentCount || 0,
|
||||
percentage: calcPercent(analysis.excellentCount || 0, totalRecords)
|
||||
},
|
||||
good: {
|
||||
value: analysis.goodCount || 0,
|
||||
percentage: calcPercent(analysis.goodCount || 0, totalRecords)
|
||||
},
|
||||
warning: {
|
||||
value: analysis.warningCount || 0,
|
||||
percentage: calcPercent(analysis.warningCount || 0, totalRecords)
|
||||
},
|
||||
danger: {
|
||||
value: analysis.dangerCount || 0,
|
||||
percentage: calcPercent(analysis.dangerCount || 0, totalRecords)
|
||||
}
|
||||
},
|
||||
daytime: (() => {
|
||||
const total =
|
||||
(analysis.daytimeExcellentCount || 0) +
|
||||
(analysis.daytimeGoodCount || 0) +
|
||||
(analysis.daytimeWarningCount || 0) +
|
||||
(analysis.daytimeDangerCount || 0)
|
||||
return {
|
||||
excellent: {
|
||||
value: analysis.daytimeExcellentCount || 0,
|
||||
percentage: calcPercent(analysis.daytimeExcellentCount || 0, total)
|
||||
},
|
||||
good: {
|
||||
value: analysis.daytimeGoodCount || 0,
|
||||
percentage: calcPercent(analysis.daytimeGoodCount || 0, total)
|
||||
},
|
||||
warning: {
|
||||
value: analysis.daytimeWarningCount || 0,
|
||||
percentage: calcPercent(analysis.daytimeWarningCount || 0, total)
|
||||
},
|
||||
danger: {
|
||||
value: analysis.daytimeDangerCount || 0,
|
||||
percentage: calcPercent(analysis.daytimeDangerCount || 0, total)
|
||||
}
|
||||
}
|
||||
})(),
|
||||
nighttime: (() => {
|
||||
const total =
|
||||
(analysis.nighttimeExcellentCount || 0) +
|
||||
(analysis.nighttimeGoodCount || 0) +
|
||||
(analysis.nighttimeWarningCount || 0) +
|
||||
(analysis.nighttimeDangerCount || 0)
|
||||
return {
|
||||
excellent: {
|
||||
value: analysis.nighttimeExcellentCount || 0,
|
||||
percentage: calcPercent(analysis.nighttimeExcellentCount || 0, total)
|
||||
},
|
||||
good: {
|
||||
value: analysis.nighttimeGoodCount || 0,
|
||||
percentage: calcPercent(analysis.nighttimeGoodCount || 0, total)
|
||||
},
|
||||
warning: {
|
||||
value: analysis.nighttimeWarningCount || 0,
|
||||
percentage: calcPercent(analysis.nighttimeWarningCount || 0, total)
|
||||
},
|
||||
danger: {
|
||||
value: analysis.nighttimeDangerCount || 0,
|
||||
percentage: calcPercent(analysis.nighttimeDangerCount || 0, total)
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
// 4. 组装数据
|
||||
const reportData = {
|
||||
patientInfo: row,
|
||||
spo2Stats: {
|
||||
averageSpO2: analysis.averageSpO2,
|
||||
minSpO2: analysis.minSpO2,
|
||||
maxSpO2: analysis.maxSpO2,
|
||||
lowOxygenTime: analysis.lowOxygenTime
|
||||
},
|
||||
timePeriodData,
|
||||
dataTableList: dataTableRes.list || [],
|
||||
diagnosisForm: { conclusion: row.diagnosis }
|
||||
}
|
||||
// 新增:弹窗展示iframe
|
||||
iframeReportUrl.value = '/spo2-report-template.html'
|
||||
iframeDialogVisible.value = true
|
||||
lastReportData = reportData
|
||||
} catch (err) {
|
||||
console.error('错误详情:', err) // 调试日志
|
||||
ElMessage.error('获取报告数据失败')
|
||||
}
|
||||
}
|
||||
// 申请
|
||||
const handleApply = async (row: any) => {
|
||||
@ -520,9 +675,35 @@ const cancelDeviceChange = (row: any) => {
|
||||
row.tempDeviceId = null
|
||||
}
|
||||
|
||||
// 新增:iframe加载后发送数据
|
||||
defineExpose({ reportIframe })
|
||||
const onIframeLoad = () => {
|
||||
if (reportIframe.value && lastReportData) {
|
||||
// 深拷贝,保证postMessage数据可被结构化克隆
|
||||
const safeData = JSON.parse(JSON.stringify(lastReportData))
|
||||
reportIframe.value.contentWindow.postMessage({ type: 'INIT_SPO2_REPORT', data: safeData }, '*')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
Profilevo.value = await getUserProfile()
|
||||
getList()
|
||||
// 新增:监听iframe请求数据
|
||||
window.addEventListener('message', (event) => {
|
||||
if (
|
||||
event.data?.type === 'REQUEST_SPO2_REPORT' &&
|
||||
iframeDialogVisible.value &&
|
||||
lastReportData &&
|
||||
reportIframe.value
|
||||
) {
|
||||
// 深拷贝,保证postMessage数据可被结构化克隆
|
||||
const safeData = JSON.parse(JSON.stringify(lastReportData))
|
||||
reportIframe.value.contentWindow.postMessage(
|
||||
{ type: 'INIT_SPO2_REPORT', data: safeData },
|
||||
'*'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user