528 lines
27 KiB
HTML
528 lines
27 KiB
HTML
<!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>
|