feat:西安应急大屏模块标题规范化(应急求助数据/穿戴告警分布/人群分布)+ cv 副屏时间段 Tab 统一控制/地图缩放与手表弹窗/睡眠柱图悬浮tip + 求助副屏时段配色统一 + 地图弹窗增加所属单位/医疗点字段

This commit is contained in:
liuyunqin
2026-06-30 18:05:58 +08:00
parent 328c6f0aaf
commit ada9d81017
3 changed files with 470 additions and 214 deletions
@@ -117,6 +117,14 @@
.header__btn--main:hover{background:rgba(24,144,255,0.25);box-shadow:0 0 10px rgba(24,144,255,0.2);}
.header__runtime{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-sub);}
.header__runtime-num{font-size:18px;font-weight:700;color:var(--primary);text-shadow:0 0 8px rgba(24,144,255,0.4);}
/* 时间段 Tab(统一控制全屏统计图表的时间范围) */
.header__tabs{display:flex;align-items:center;gap:2px;padding:3px;background:rgba(10,22,48,0.6);
border:1px solid var(--panel-border);border-radius:16px;}
.header__tab{height:22px;padding:0 12px;border-radius:13px;font-size:11px;cursor:pointer;transition:all .2s;
border:none;background:transparent;color:var(--text-sub);white-space:nowrap;letter-spacing:0.3px;
display:inline-flex;align-items:center;font-family:inherit;}
.header__tab:hover{color:var(--text-main);}
.header__tab--active{background:rgba(24,144,255,0.22);color:#fff;box-shadow:0 0 8px rgba(24,144,255,0.3);}
.header__deco{position:absolute;top:6px;width:88px;height:10px;pointer-events:none;z-index:1;
background-repeat:no-repeat;background-position:center;background-size:100% 100%;}
.header__deco--left{left:32px;
@@ -220,7 +228,7 @@
.cv-ring__legend-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.cv-ring__legend-value{font-weight:700;color:var(--text-main);font-variant-numeric:tabular-nums;white-space:nowrap;}
/* ========== 睡眠柱图(沿用主屏 ========== */
/* ========== 睡眠柱图(口径参考求助趋势 help-trend ========== */
.sleep-chart{display:flex;flex-direction:column;gap:6px;}
.sleep-chart__summary{display:flex;justify-content:space-between;align-items:center;gap:8px;font-size:11px;color:var(--text-sub);}
.sleep-chart__summary b{color:#fff;font-size:13px;}
@@ -229,15 +237,25 @@
.sleep-chart__plot::before,.sleep-chart__plot::after{content:'';position:absolute;left:0;right:0;border-top:1px dashed rgba(24,144,255,0.12);}
.sleep-chart__plot::before{top:30px;}
.sleep-chart__plot::after{top:60px;}
.sleep-day{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:4px;position:relative;z-index:1;}
.sleep-day{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:4px;position:relative;z-index:1;min-width:0;}
.sleep-day__bars{height:72px;display:flex;align-items:flex-end;gap:3px;}
.sleep-day__bar{width:7px;border-radius:5px 5px 2px 2px;min-height:6px;box-shadow:0 0 8px currentColor;}
.sleep-day__bar--deep{background:#13C2C2;color:#13C2C2;}
.sleep-day__bar--light{background:#1890FF;color:#1890FF;}
.sleep-day__label{font-size:10px;color:var(--text-dim);line-height:1;}
.sleep-day__label{font-size:10px;color:var(--text-dim);line-height:1;white-space:nowrap;}
.sleep-chart__legend{display:flex;align-items:center;justify-content:center;gap:12px;font-size:10px;color:var(--text-sub);}
.sleep-chart__legend span{display:flex;align-items:center;gap:5px;}
.sleep-chart__dot{width:8px;height:8px;border-radius:2px;box-shadow:0 0 7px currentColor;}
/* 睡眠柱图悬浮 tip(口径参考求助趋势 help-trend-tip */
.sleep-tip{position:fixed;z-index:200;min-width:132px;padding:8px 10px;
background:rgba(10,22,48,0.96);border:1px solid rgba(24,144,255,0.4);border-radius:8px;
box-shadow:0 0 16px rgba(24,144,255,0.25);font-size:11px;color:var(--text-main);
pointer-events:none;backdrop-filter:blur(6px);}
.sleep-tip__head{font-size:12px;font-weight:700;color:#fff;margin-bottom:5px;
padding-bottom:4px;border-bottom:1px solid rgba(24,144,255,0.2);}
.sleep-tip__row{display:flex;align-items:center;gap:6px;line-height:1.6;color:var(--text-sub);}
.sleep-tip__row i{width:8px;height:8px;border-radius:2px;box-shadow:0 0 6px currentColor;flex-shrink:0;}
.sleep-tip__row b{color:#fff;font-weight:700;margin:0 2px;font-variant-numeric:tabular-nums;}
/* ========== 步数环形图 ========== */
.steps-ring{display:flex;flex-direction:column;gap:6px;}
@@ -331,7 +349,38 @@
display:flex;align-items:center;gap:6px;}
.map-title-pill .dot{width:6px;height:6px;border-radius:50%;background:var(--cyan);box-shadow:0 0 6px var(--cyan);}
/* ========== 单位手表配备情况卡片(横向滚动 ========== */
/* ========== 地图缩放交互(点击手表图标放大居中 + 信息弹窗 ========== */
.map-stage{position:absolute;inset:0;z-index:1;transform-origin:center center;
transition:transform .55s cubic-bezier(.22,.61,.36,1);}
.map-marker.is-active{z-index:12;}
.map-marker.is-active .map-marker__pin{filter:drop-shadow(0 0 5px rgba(255,255,255,0.95)) drop-shadow(0 0 10px rgba(0,229,255,0.55));}
/* 手表信息弹窗(浮于地图之上,不随缩放变化;由 JS 按地图正中自适应定位) */
.watch-marker-popup{position:absolute;left:0;top:0;z-index:20;width:290px;padding:12px 14px 13px;
background:rgba(10,22,48,0.96);border:1px solid rgba(24,144,255,0.4);border-radius:10px;
box-shadow:0 0 22px rgba(24,144,255,0.3);backdrop-filter:blur(8px);
font-size:12px;color:var(--text-main);opacity:1;transition:opacity .2s ease;
animation:watchPopIn .26s ease;}
@keyframes watchPopIn{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
.watch-marker-popup__arrow{position:absolute;left:50%;bottom:-7px;width:13px;height:13px;
transform:translateX(-50%) rotate(45deg);background:rgba(10,22,48,0.96);
border-right:1px solid rgba(24,144,255,0.4);border-bottom:1px solid rgba(24,144,255,0.4);}
.watch-marker-popup__close{position:absolute;top:6px;right:8px;font-size:16px;line-height:1;
color:var(--text-dim);cursor:pointer;transition:color .2s;}
.watch-marker-popup__close:hover{color:var(--text-main);}
.watch-marker-popup__title{display:flex;align-items:center;gap:6px;flex-wrap:wrap;
font-size:13px;font-weight:700;color:#fff;margin-bottom:8px;padding-right:16px;}
.watch-marker-popup__status{font-size:10px;font-weight:600;padding:1px 7px;border-radius:4px;margin-left:auto;}
.watch-marker-popup__status--normal{color:var(--success);background:rgba(82,196,26,0.15);border:1px solid rgba(82,196,26,0.3);}
.watch-marker-popup__status--offline{color:var(--text-dim);background:rgba(74,107,138,0.15);border:1px solid rgba(74,107,138,0.3);}
.watch-marker-popup__status--warning{color:var(--warning);background:rgba(250,173,20,0.15);border:1px solid rgba(250,173,20,0.3);}
.watch-marker-popup__status--alarm{color:var(--danger);background:rgba(255,77,79,0.15);border:1px solid rgba(255,77,79,0.3);}
.watch-marker-popup__section{font-size:10px;color:var(--primary);font-weight:600;margin:6px 0 3px;
padding-top:5px;border-top:1px solid rgba(24,144,255,0.12);letter-spacing:0.5px;}
.watch-marker-popup__row{display:flex;align-items:flex-start;gap:8px;line-height:1.7;font-size:11px;}
.watch-marker-popup__k{flex-shrink:0;width:72px;color:var(--text-dim);}
.watch-marker-popup__v{flex:1;min-width:0;color:var(--text-main);word-break:break-all;}
/* ========== 各单位告警统计卡片(横向滚动,沿用 watch-stat ========== */
.watch-stat-panel{flex-shrink:0;}
.watch-stat-cards{position:relative;overflow:hidden;padding:8px 0;}
.watch-stat-cards::before,.watch-stat-cards::after{content:'';position:absolute;top:0;bottom:0;width:36px;z-index:5;pointer-events:none;}
@@ -366,8 +415,8 @@
.watch-stat-card__chip{display:flex;flex-direction:column;align-items:center;gap:1px;flex:1;min-width:0;}
.watch-stat-card__chip-num{font-size:12px;font-weight:700;color:#fff;font-variant-numeric:tabular-nums;line-height:1;}
.watch-stat-card__chip-label{font-size:9px;color:var(--text-dim);white-space:nowrap;}
.watch-stat-card__chip--online .watch-stat-card__chip-num{color:var(--success);}
.watch-stat-card__chip--offline .watch-stat-card__chip-num{color:var(--text-dim);}
.watch-stat-card__chip--wearable .watch-stat-card__chip-num{color:var(--primary);}
.watch-stat-card__chip--cardio .watch-stat-card__chip-num{color:var(--danger);}
.watch-stat-card--total{background:linear-gradient(135deg,rgba(0,229,255,0.12),rgba(10,30,60,0.6));
border-color:rgba(0,229,255,0.35);box-shadow:0 0 12px rgba(0,229,255,0.18);}
.watch-stat-card--total::before{background:var(--cyan);box-shadow:0 0 10px rgba(0,229,255,0.6);}
@@ -461,6 +510,13 @@
<span class="header__title-text">西安应急中心 · 心血管监测副屏</span>
</div>
<div class="header__right">
<!-- [交互] 时间段切换:今日 / 近一周 / 近一月 / 近一年(统一控制全屏统计图表) -->
<div class="header__tabs" id="cvPeriodTabs">
<button type="button" class="header__tab" data-period="today" onclick="switchCvPeriod('today',this)">今日</button>
<button type="button" class="header__tab header__tab--active" data-period="week" onclick="switchCvPeriod('week',this)">近一周</button>
<button type="button" class="header__tab" data-period="month" onclick="switchCvPeriod('month',this)">近一月</button>
<button type="button" class="header__tab" data-period="year" onclick="switchCvPeriod('year',this)">近一年</button>
</div>
<div class="header__runtime">
<span>系统累计运行</span>
<span class="header__runtime-num" id="runDays">0</span>
@@ -479,12 +535,6 @@
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">心率区间分布</span>
<select class="panel__header-select" id="heartRangeSelect" onchange="renderCvRing('heart')">
<option value="today">今日</option>
<option value="week" selected>近一周</option>
<option value="month">近一月</option>
<option value="year">近一年</option>
</select>
</div>
<div class="panel__body" id="heartRing"></div>
</div>
@@ -493,12 +543,6 @@
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">血氧区间分布</span>
<select class="panel__header-select" id="spo2RangeSelect" onchange="renderCvRing('spo2')">
<option value="today">今日</option>
<option value="week" selected>近一周</option>
<option value="month">近一月</option>
<option value="year">近一年</option>
</select>
</div>
<div class="panel__body" id="spo2Ring"></div>
</div>
@@ -507,12 +551,6 @@
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">压力区间分布</span>
<select class="panel__header-select" id="pressureRangeSelect" onchange="renderCvRing('pressure')">
<option value="today">今日</option>
<option value="week" selected>近一周</option>
<option value="month">近一月</option>
<option value="year">近一年</option>
</select>
</div>
<div class="panel__body" id="pressureRing"></div>
</div>
@@ -521,18 +559,12 @@
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">体温区间分布</span>
<select class="panel__header-select" id="tempRangeSelect" onchange="renderCvRing('temp')">
<option value="today">今日</option>
<option value="week" selected>近一周</option>
<option value="month">近一月</option>
<option value="year">近一年</option>
</select>
</div>
<div class="panel__body" id="tempRing"></div>
</div>
</aside>
<!-- 中央:地图 + 单位手表配备情况统计 -->
<!-- 中央:地图 + 单位告警统计 -->
<div class="center">
<!-- 顶部 KPI 行 -->
<div class="top-stats">
@@ -563,70 +595,13 @@
</div>
<!-- 地图:手表位置分布 -->
<div class="map-wrap" id="mapWrap">
<div class="map-bg"></div>
<div class="map-wrap" id="cvMapWrap">
<div class="map-stage" id="cvMapStage">
<div class="map-bg"></div>
<!-- 手表位置标记点由 JS 渲染(绿=正常 / 黄=预警 / 红=告警 / 灰=离线) -->
<div class="map-markers" id="cvMapMarkers"></div>
</div>
<div class="map-title-pill"><span class="dot"></span>手表位置分布</div>
<!-- 手表位置标记点(绿=正常 / 黄=预警 / 红=告警 / 灰=离线) -->
<div class="map-marker" style="top:38%;left:33%;" title="张建国 · 采油一厂 · 心率异常">
<div class="map-marker__pulse" style="background:rgba(255,77,79,0.25);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#FF4D4F" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:42%;left:38%;" title="李明辉 · 采油一厂 · 正常">
<div class="map-marker__pulse" style="background:rgba(82,196,26,0.2);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#52C41A" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:43%;left:63%;" title="王芳 · 采油二厂 · 血氧偏低">
<div class="map-marker__pulse" style="background:rgba(250,173,20,0.2);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#FAAD14" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:46%;left:67%;" title="刘雪梅 · 采油二厂 · 体温发热">
<div class="map-marker__pulse" style="background:rgba(255,77,79,0.25);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#FF4D4F" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:78%;left:48%;" title="赵刚 · 采油三厂 · 正常">
<div class="map-marker__pulse" style="background:rgba(82,196,26,0.2);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#52C41A" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:80%;left:52%;" title="杨丽娟 · 采油三厂 · 正常">
<div class="map-marker__pulse" style="background:rgba(82,196,26,0.2);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#52C41A" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:14%;left:13%;" title="孙志强 · 兴隆医院 · 心率异常">
<div class="map-marker__pulse" style="background:rgba(255,77,79,0.25);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#FF4D4F" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:70%;left:15%;" title="许建平 · 高陵医院 · 压力偏高">
<div class="map-marker__pulse" style="background:rgba(250,173,20,0.2);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#FAAD14" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:16%;left:75%;" title="周丽华 · 宝石花医院 · 正常">
<div class="map-marker__pulse" style="background:rgba(82,196,26,0.2);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#52C41A" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:54%;left:60%;" title="吴永强 · 庆阳医院 · 压力偏高">
<div class="map-marker__pulse" style="background:rgba(250,173,20,0.2);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#FAAD14" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:12%;left:48%;" title="韩雪 · 公司机关 · 离线">
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#4A6B8A" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:15%;left:52%;" title="马国强 · 公司机关 · 正常">
<div class="map-marker__pulse" style="background:rgba(82,196,26,0.2);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#52C41A" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:40%;left:35%;" title="陈晓东 · 采油一厂 · 压力高压">
<div class="map-marker__pulse" style="background:rgba(255,77,79,0.25);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#FF4D4F" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:45%;left:65%;" title="郑海龙 · 采油一厂 · 体温发热">
<div class="map-marker__pulse" style="background:rgba(255,77,79,0.25);"></div>
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#FF4D4F" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<div class="map-marker" style="top:78%;left:55%;" title="冯国强 · 采油三厂 · 离线">
<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="#4A6B8A" stroke="#fff" stroke-width="1.5"/></svg>
</div>
<!-- 图例 -->
<div class="map-legend">
<div class="map-legend__item"><span class="map-legend__dot map-legend__dot--normal"></span>正常</div>
@@ -636,11 +611,11 @@
</div>
</div>
<!-- 单位手表配备情况统计 -->
<!-- 单位告警统计 -->
<div class="panel watch-stat-panel">
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">单位手表配备情况统计</span>
<span class="panel__header-title">单位告警统计</span>
</div>
<div class="panel__body" style="padding:0;overflow:hidden;max-height:200px;">
<div class="watch-stat-cards">
@@ -657,11 +632,6 @@
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">睡眠监测</span>
<select class="panel__header-select" id="sleepRangeSelect" onchange="renderSleepChart()">
<option value="today">今日</option>
<option value="week" selected>近一周</option>
<option value="month">近一月</option>
</select>
</div>
<div class="panel__body" style="padding:8px 10px;overflow:hidden;" id="sleepChart"></div>
</div>
@@ -671,11 +641,6 @@
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">步数监测</span>
<select class="panel__header-select" id="stepsRangeSelect" onchange="renderStepsChart()">
<option value="today">今日</option>
<option value="week" selected>近一周</option>
<option value="month">近一月</option>
</select>
</div>
<div class="panel__body" style="padding:8px 10px;overflow:hidden;" id="stepsChart"></div>
</div>
@@ -929,10 +894,23 @@ var CV_RING_DATA={
/* 时间范围缩放系数 */
var CV_RANGE_FACTOR={today:0.18,week:1,month:3.4,year:11.6};
/* 当前统一时间段(由顶部 Tab 切换,默认近一周,对应各模块原下拉默认值) */
var currentCvPeriod='week';
/* [交互] 切换全屏统计图表的时间段:更新选中态并重新渲染各图表 */
function switchCvPeriod(period,el){
currentCvPeriod=period||'week';
var tabs=document.getElementById('cvPeriodTabs');
if(tabs){tabs.querySelectorAll('.header__tab').forEach(function(t){t.classList.remove('header__tab--active');});}
var target=el||(tabs&&tabs.querySelector('.header__tab[data-period="'+currentCvPeriod+'"]'));
if(target){target.classList.add('header__tab--active');}
renderCvRing('heart');renderCvRing('spo2');renderCvRing('pressure');renderCvRing('temp');
renderSleepChart();
renderStepsChart();
}
function getCvScale(metric){
var sel=document.getElementById(metric+'RangeSelect');
var v=sel?sel.value:'week';
return CV_RANGE_FACTOR[v]||1;
return CV_RANGE_FACTOR[currentCvPeriod]||1;
}
function formatCvNum(n){return Math.max(1,Math.round(n)).toLocaleString('zh-CN');}
@@ -989,46 +967,70 @@ renderCvRing('pressure');
renderCvRing('temp');
/* ========== 右侧睡眠柱图 ========== */
var SLEEP_DATA={
today:{days:[{day:'06/26',deep:1.6,light:4.0}],avg:'5.6h'},
week:{days:[
{day:'06/20',deep:2.4,light:4.8},
{day:'06/21',deep:1.7,light:4.1},
{day:'06/22',deep:2.6,light:5.0},
{day:'06/23',deep:2.0,light:4.4},
{day:'06/24',deep:1.8,light:4.2},
{day:'06/25',deep:2.2,light:4.6},
{day:'06/26',deep:1.6,light:4.0}
],avg:'6.7h'},
month:{days:[
{day:'05月',deep:2.1,light:4.5},
{day:'06月上',deep:1.9,light:4.3},
{day:'06月中',deep:2.3,light:4.7},
{day:'06月下',deep:2.0,light:4.4}
],avg:'6.6h'}
};
/* 睡眠监测数据(按时间段聚合:今日24时 / 近一周7日 / 近一月30日 / 近一年12月) */
/* 以数据最新日 2026-06-26 为基准生成各粒度深睡/浅睡数据(原型演示数据),口径参考求助趋势 */
function buildSleepData(){
function pad(n){return n<10?'0'+n:''+n;}
function dateFrom(daysAgo){var dt=new Date(2026,5,26);dt.setDate(dt.getDate()-daysAgo);return dt;}
function md(dt){return pad(dt.getMonth()+1)+'-'+pad(dt.getDate());}
var data={today:[],week:[],month:[],year:[]};
/* 今日:24 小时睡眠分布,深夜0-6时与23时为睡眠段,深睡集中在前半夜 */
var deepShape=[0.3,0.3,0.3,0.3,0.2,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1];
var lightShape=[0.6,0.6,0.6,0.6,0.5,0.4,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.4];
for(var h=0;h<24;h++){data.today.push({label:pad(h)+'时',deep:deepShape[h],light:lightShape[h]});}
/* 近一周:7 日(06-20~06-26 */
var wk=[[2.4,4.8],[1.7,4.1],[2.6,5.0],[2.0,4.4],[1.8,4.2],[2.2,4.6],[1.6,4.0]];
for(var d=0;d<7;d++){data.week.push({label:md(dateFrom(6-d)),deep:wk[d][0],light:wk[d][1]});}
/* 近一月:30 日(05-28~06-26),日均深睡~2.0/浅睡~4.5,含周期波动 */
for(var i=29;i>=0;i--){
var deep=2.0+Math.round(10*Math.sin(i/3))/10+((i%7===6)?0.3:0);
var light=4.5+Math.round(10*Math.sin(i/2.3))/10+((i%7===5||i%7===6)?0.3:0);
data.month.push({label:md(dateFrom(i)),deep:Math.max(0.5,deep),light:Math.max(2,light)});
}
/* 近一年:12 月(2025-07~2026-06),冬季睡眠略长,6月与今日一致 */
var monthLabels=['7月','8月','9月','10月','11月','12月','1月','2月','3月','4月','5月','6月'];
var yr=[[2.0,4.6],[1.9,4.5],[2.1,4.7],[2.2,4.8],[2.3,4.9],[2.4,5.0],[2.4,5.0],[2.3,4.8],[2.2,4.7],[2.1,4.6],[2.0,4.5],[1.6,4.0]];
for(var m=0;m<12;m++){data.year.push({label:monthLabels[m],deep:yr[m][0],light:yr[m][1]});}
return data;
}
var SLEEP_DATA=buildSleepData();
/* 睡眠柱图(按时间段渲染深睡/浅睡,布局自适应与求助趋势一致) */
function renderSleepChart(){
var range=document.getElementById('sleepRangeSelect').value;
var data=SLEEP_DATA[range]||SLEEP_DATA.week;
var maxHour=6;
var daysHtml=data.days.map(function(d){
var deep=Math.min(maxHour,d.deep);
var light=Math.min(maxHour,d.light);
return '<div class="sleep-day">'+
var period=currentCvPeriod||'week';
var buckets=SLEEP_DATA[period]||SLEEP_DATA.week;
var n=buckets.length;
/* 自适应间距与标签步长:桶越多间距越小、标签越稀疏(与求助趋势一致) */
var gap=n<=7?10:(n<=12?6:2);
var labelStep=n<=12?1:(n===24?6:5);
var periodText={today:'今日24小时',week:'近7日',month:'近30日',year:'近12个月'}[period]||'';
/* 按数据集最大值缩放,使柱体铺满绘图区高度(非固定满量程) */
var maxVal=0;
buckets.forEach(function(b){if(b.deep>maxVal)maxVal=b.deep;if(b.light>maxVal)maxVal=b.light;});
var scale=maxVal>0?72/maxVal:0;
var sumDeep=0,sumLight=0;
var barsHtml=buckets.map(function(b,idx){
sumDeep+=b.deep;sumLight+=b.light;
var dh=b.deep*scale,lh=b.light*scale;
var show=(idx%labelStep===0)||(idx===n-1);
return '<div class="sleep-day" data-label="'+b.label+'" data-deep="'+b.deep.toFixed(1)+'" data-light="'+b.light.toFixed(1)+'">'+
'<div class="sleep-day__bars">'+
'<span class="sleep-day__bar sleep-day__bar--deep" title="深睡 '+deep.toFixed(1)+'小时" style="height:'+(deep/maxHour*72).toFixed(0)+'px"></span>'+
'<span class="sleep-day__bar sleep-day__bar--light" title="浅睡 '+light.toFixed(1)+'小时" style="height:'+(light/maxHour*72).toFixed(0)+'px"></span>'+
'<span class="sleep-day__bar sleep-day__bar--deep" style="height:'+dh.toFixed(1)+'px'+(b.deep<=0?';min-height:0':'')+'"></span>'+
'<span class="sleep-day__bar sleep-day__bar--light" style="height:'+lh.toFixed(1)+'px'+(b.light<=0?';min-height:0':'')+'"></span>'+
'</div>'+
'<span class="sleep-day__label">'+d.day+'</span>'+
'<span class="sleep-day__label">'+(show?b.label:'&nbsp;')+'</span>'+
'</div>';
}).join('');
/* 今日为单日24小时,平均睡眠取合计;其余时间段取日均 */
var total=sumDeep+sumLight;
var avg=(period==='today'?total:total/n).toFixed(1)+'h';
document.getElementById('sleepChart').innerHTML=
'<div class="sleep-chart">'+
'<div class="sleep-chart__summary">'+
'<span>按天展示深睡/浅睡</span>'+
'<span>平均睡眠 <b>'+data.avg+'</b></span>'+
'<span>'+periodText+'深睡/浅睡分布</span>'+
'<span>平均睡眠 <b>'+avg+'</b></span>'+
'</div>'+
'<div class="sleep-chart__plot">'+daysHtml+'</div>'+
'<div class="sleep-chart__plot" style="grid-template-columns:repeat('+n+',1fr);gap:'+gap+'px;">'+barsHtml+'</div>'+
'<div class="sleep-chart__legend">'+
'<span><i class="sleep-chart__dot" style="color:#13C2C2;background:#13C2C2"></i>深睡</span>'+
'<span><i class="sleep-chart__dot" style="color:#1890FF;background:#1890FF"></i>浅睡</span>'+
@@ -1037,6 +1039,40 @@ function renderSleepChart(){
}
renderSleepChart();
/* ========== 睡眠柱图悬浮 tip(口径参考求助趋势 help-trend-tip ========== */
/* 悬浮某根柱时弹出深睡/浅睡/合计(小时),挂在 #sleepChart 上以代理方式响应(重渲染后仍生效) */
var sleepTip=document.createElement('div');
sleepTip.className='sleep-tip';
sleepTip.style.display='none';
document.body.appendChild(sleepTip);
var sleepTipDay=null;
function fillSleepTip(dayEl){
var label=dayEl.getAttribute('data-label');
var deep=dayEl.getAttribute('data-deep'),light=dayEl.getAttribute('data-light');
var t=(+deep)+(+light);
sleepTip.innerHTML=
'<div class="sleep-tip__head">'+label+' · 合计 '+t.toFixed(1)+' 小时</div>'+
'<div class="sleep-tip__row"><i style="color:#13C2C2;background:#13C2C2"></i>深睡 <b>'+deep+'</b> 小时</div>'+
'<div class="sleep-tip__row"><i style="color:#1890FF;background:#1890FF"></i>浅睡 <b>'+light+'</b> 小时</div>';
}
function moveSleepTip(x,y){
var w=sleepTip.offsetWidth||130,h=sleepTip.offsetHeight||70;
var px=x+14,py=y+14;
if(px+w>window.innerWidth) px=x-w-14;
if(py+h>window.innerHeight) py=y-h-14;
sleepTip.style.left=px+'px';sleepTip.style.top=py+'px';
}
document.getElementById('sleepChart').addEventListener('mousemove',function(e){
var dayEl=e.target.closest&&e.target.closest('.sleep-day');
if(!dayEl){ if(sleepTip.style.display!=='none') sleepTip.style.display='none'; sleepTipDay=null; return; }
if(dayEl!==sleepTipDay){ sleepTipDay=dayEl; fillSleepTip(dayEl); }
sleepTip.style.display='block';
moveSleepTip(e.clientX,e.clientY);
});
document.getElementById('sleepChart').addEventListener('mouseleave',function(){
sleepTip.style.display='none';sleepTipDay=null;
});
/* ========== 右侧步数环形图 ========== */
/* 步数区间分布(单位时间内各步数区间的人数) */
var STEPS_DATA={
@@ -1066,7 +1102,7 @@ var STEPS_DATA={
],total:6635,avg:'7,775 步'}
};
function renderStepsChart(){
var range=document.getElementById('stepsRangeSelect').value;
var range=currentCvPeriod;
var data=STEPS_DATA[range]||STEPS_DATA.week;
var total=data.total;
var c=2*Math.PI*42; /* r=42 周长 ≈263.9 */
@@ -1113,56 +1149,62 @@ function renderStepsChart(){
}
renderStepsChart();
/* ========== 单位手表配备情况统计 ========== */
var WATCH_STAT_DATA=[
{unit:'采油一厂',target:520,deployed:498,online:470,offline:28},
{unit:'采油二厂',target:480,deployed:455,online:430,offline:25},
{unit:'采油三厂',target:420,deployed:398,online:375,offline:23},
{unit:'公司机关',target:380,deployed:372,online:358,offline:14},
{unit:'兴隆医院',target:160,deployed:155,online:148,offline:7},
{unit:'宝石花医院',target:180,deployed:170,online:162,offline:8},
{unit:'高陵医院',target:120,deployed:115,online:108,offline:7},
{unit:'庆阳医院',target:140,deployed:128,online:118,offline:10}
];
function renderWatchStatTable(){
/* ========== 单位告警统计(横向滚动卡片,口径参考 help-monitor 各单位求助情况) ========== */
/* 告警类型归口:穿戴告警(摔倒/一键告警)/ 心血管告警(心率/血氧/压力/体温异常) */
var ALARM_CATEGORY={'摔倒':'wearable','一键告警':'wearable','心率异常':'cardio','血氧异常':'cardio','压力异常':'cardio','体温异常':'cardio'};
function buildUnitAlarmStats(){
var unitMap={},detail={};
ALARM_RECORD_DATA.forEach(function(r){
var u=r.unit;
if(!unitMap[u]) unitMap[u]=0;
unitMap[u]++;
if(!detail[u]) detail[u]={wearable:0,cardio:0};
var cat=ALARM_CATEGORY[r.alarmType]||'cardio';
if(cat==='wearable') detail[u].wearable++; else detail[u].cardio++;
});
var rows=Object.keys(unitMap).map(function(u){return{unit:u,total:unitMap[u]};})
.sort(function(a,b){return b.total-a.total;});/* 按告警次数从高到低排序 */
return{rows:rows,detail:detail};
}
function renderUnitAlarmStat(){
var track=document.getElementById('watchStatTrack');
var cards='';
var sumTarget=0,sumDeployed=0,sumOnline=0,sumOffline=0;
WATCH_STAT_DATA.forEach(function(r){
var rate=r.target>0?r.deployed/r.target*100:0;
var rateClass=rate>=95?'high':(rate>=85?'mid':'low');
sumTarget+=r.target;sumDeployed+=r.deployed;sumOnline+=r.online;sumOffline+=r.offline;
cards+='<div class="watch-stat-card watch-stat-card--'+rateClass+'">'+
if(!track) return;
var stats=buildUnitAlarmStats(),rows=stats.rows,detail=stats.detail;
var maxTotal=Math.max.apply(null,rows.map(function(r){return r.total;}))||1;
var sumTotal=0,sumWearable=0,sumCardio=0;
var cards=rows.map(function(r){
var d=detail[r.unit]||{wearable:0,cardio:0};
var rate=maxTotal>0?r.total/maxTotal*100:0;
sumTotal+=r.total;sumWearable+=d.wearable;sumCardio+=d.cardio;
return '<div class="watch-stat-card">'+
'<div class="watch-stat-card__head">'+
'<span class="watch-stat-card__unit" title="'+r.unit+'">'+r.unit+'</span>'+
'<span class="watch-stat-card__rate">'+rate.toFixed(1)+'%</span>'+
'<span class="watch-stat-card__rate">'+r.total+'</span>'+
'</div>'+
'<div class="watch-stat-card__bar"><span class="watch-stat-card__bar-fill" style="width:'+rate.toFixed(0)+'%"></span></div>'+
'<div class="watch-stat-card__chips">'+
'<div class="watch-stat-card__chip watch-stat-card__chip--online"><span class="watch-stat-card__chip-num">'+r.online.toLocaleString('zh-CN')+'</span><span class="watch-stat-card__chip-label">在线</span></div>'+
'<div class="watch-stat-card__chip watch-stat-card__chip--offline"><span class="watch-stat-card__chip-num">'+r.offline.toLocaleString('zh-CN')+'</span><span class="watch-stat-card__chip-label">离线</span></div>'+
'<div class="watch-stat-card__chip"><span class="watch-stat-card__chip-num">'+r.target.toLocaleString('zh-CN')+'</span><span class="watch-stat-card__chip-label">应配</span></div>'+
'<div class="watch-stat-card__chip watch-stat-card__chip--wearable"><span class="watch-stat-card__chip-num">'+d.wearable+'</span><span class="watch-stat-card__chip-label">穿戴告警</span></div>'+
'<div class="watch-stat-card__chip watch-stat-card__chip--cardio"><span class="watch-stat-card__chip-num">'+d.cardio+'</span><span class="watch-stat-card__chip-label">心血管告警</span></div>'+
'<div class="watch-stat-card__chip"><span class="watch-stat-card__chip-num">'+r.total+'</span><span class="watch-stat-card__chip-label">合计</span></div>'+
'</div>'+
'</div>';
});
var totalRate=sumTarget>0?sumDeployed/sumTarget*100:0;
var totalRateClass=totalRate>=95?'high':(totalRate>=85?'mid':'low');
cards+='<div class="watch-stat-card watch-stat-card--total watch-stat-card--'+totalRateClass+'">'+
}).join('');
var totalRate=maxTotal>0?sumTotal/maxTotal*100:0;
cards+='<div class="watch-stat-card watch-stat-card--total">'+
'<div class="watch-stat-card__head">'+
'<span class="watch-stat-card__unit">合计</span>'+
'<span class="watch-stat-card__rate">'+totalRate.toFixed(1)+'%</span>'+
'<span class="watch-stat-card__rate">'+sumTotal+'</span>'+
'</div>'+
'<div class="watch-stat-card__bar"><span class="watch-stat-card__bar-fill" style="width:'+totalRate.toFixed(0)+'%"></span></div>'+
'<div class="watch-stat-card__chips">'+
'<div class="watch-stat-card__chip watch-stat-card__chip--online"><span class="watch-stat-card__chip-num">'+sumOnline.toLocaleString('zh-CN')+'</span><span class="watch-stat-card__chip-label">在线</span></div>'+
'<div class="watch-stat-card__chip watch-stat-card__chip--offline"><span class="watch-stat-card__chip-num">'+sumOffline.toLocaleString('zh-CN')+'</span><span class="watch-stat-card__chip-label">离线</span></div>'+
'<div class="watch-stat-card__chip"><span class="watch-stat-card__chip-num">'+sumTarget.toLocaleString('zh-CN')+'</span><span class="watch-stat-card__chip-label">应配</span></div>'+
'<div class="watch-stat-card__chip watch-stat-card__chip--wearable"><span class="watch-stat-card__chip-num">'+sumWearable+'</span><span class="watch-stat-card__chip-label">穿戴告警</span></div>'+
'<div class="watch-stat-card__chip watch-stat-card__chip--cardio"><span class="watch-stat-card__chip-num">'+sumCardio+'</span><span class="watch-stat-card__chip-label">心血管告警</span></div>'+
'<div class="watch-stat-card__chip"><span class="watch-stat-card__chip-num">'+sumTotal+'</span><span class="watch-stat-card__chip-label">合计</span></div>'+
'</div>'+
'</div>';
/* 复制一份用于无缝滚动 */
track.innerHTML=cards+cards;
}
renderWatchStatTable();
/* ========== 穿戴告警事件列表点击 → 打开告警记录弹窗 ========== */
document.getElementById('alarmTrack').addEventListener('click',function(e){
@@ -1204,6 +1246,9 @@ var ALARM_RECORD_DATA=[
{name:'贺军',empId:'GL-2021-0634',gender:'男',age:46,unit:'高陵医院',dept:'后勤',alarmType:'压力异常',alarmValue:'170/108 mmHg',alarmTime:'2026-06-20 16:18:59',address:'高陵医院后勤服务楼3层307室'}
];
/* 渲染各单位告警统计(依赖上方 ALARM_RECORD_DATA,故在数据定义后调用) */
renderUnitAlarmStat();
var alarmRecordFilteredData=ALARM_RECORD_DATA.slice();
var alarmRecordPage=1;
var alarmRecordPageSizeVal=10;
@@ -1316,6 +1361,190 @@ function resetAlarmRecordFilter(){
alarmRecordPage=1;
renderAlarmRecordTable();
}
/* ========== 手表位置标记数据 & 点击交互(参考 help-monitor 地图缩放 + 弹窗) ========== */
var WATCH_MARKER_DATA = [
{name:'张建国',gender:'男',age:52,unit:'采油一厂',dept:'生产区',
watchId:'CW-2024-0837',watchModel:'Huawei Watch D2',position:'采油一厂生产基地B区3号井组作业平台',
status:'alarm',alarmType:'心率异常',alarmTime:'2026-06-30 14:32:15',top:38,left:33},
{name:'李明辉',gender:'男',age:38,unit:'采油一厂',dept:'设备区',
watchId:'CW-2024-0512',watchModel:'Apple Watch Ultra 2',position:'采油一厂设备区2号楼208室',
status:'normal',lastActiveTime:'2026-06-30 14:28:03',top:42,left:38},
{name:'王芳',gender:'女',age:45,unit:'采油二厂',dept:'行政楼',
watchId:'CW-2024-0128',watchModel:'Xiaomi Watch S4',position:'采油二厂行政楼5层503室',
status:'warning',riskType:'脑梗',riskLevel:'中危',probability:'42.3%',warningTime:'2026-06-30 13:55:21',top:43,left:63},
{name:'刘雪梅',gender:'女',age:36,unit:'采油二厂',dept:'行政楼',
watchId:'CW-2024-0442',watchModel:'OPPO Watch 4 Pro',position:'采油二厂行政楼5层503室',
status:'alarm',alarmType:'体温异常',alarmTime:'2026-06-30 13:20:35',top:46,left:67},
{name:'赵刚',gender:'男',age:29,unit:'采油三厂',dept:'调度室',
watchId:'CW-2024-0305',watchModel:'Huawei Watch D2',position:'采油三厂调度中心大楼2层201室',
status:'normal',lastActiveTime:'2026-06-30 13:58:22',top:78,left:48},
{name:'杨丽娟',gender:'女',age:31,unit:'采油三厂',dept:'行政楼',
watchId:'CW-2024-0156',watchModel:'Samsung Galaxy Watch7',position:'采油三厂行政楼4层402室',
status:'normal',lastActiveTime:'2026-06-30 14:10:44',top:80,left:52},
{name:'孙志强',gender:'男',age:47,unit:'兴隆医院',dept:'后勤',
watchId:'CW-2024-0215',watchModel:'Apple Watch Ultra 2',position:'兴隆医院后勤服务楼1层102室',
status:'alarm',alarmType:'心率异常',alarmTime:'2026-06-30 12:55:08',top:14,left:13},
{name:'许建平',gender:'男',age:53,unit:'高陵医院',dept:'后勤',
watchId:'CW-2024-0589',watchModel:'OPPO Watch 4 Pro',position:'高陵医院后勤服务楼2层205室',
status:'warning',riskType:'心梗',riskLevel:'高危',probability:'67.8%',warningTime:'2026-06-30 11:42:15',top:70,left:15},
{name:'周丽华',gender:'女',age:33,unit:'宝石花医院',dept:'护理部',
watchId:'CW-2024-0521',watchModel:'Xiaomi Watch S4',position:'宝石花医院护理部大楼3层302室',
status:'normal',lastActiveTime:'2026-06-30 14:15:47',top:16,left:75},
{name:'吴永强',gender:'男',age:55,unit:'庆阳医院',dept:'急诊科',
watchId:'CW-2024-0193',watchModel:'Samsung Galaxy Watch7',position:'庆阳医院急诊科一楼抢救室',
status:'warning',riskType:'脑出血',riskLevel:'中危',probability:'38.5%',warningTime:'2026-06-30 10:18:33',top:54,left:60},
{name:'韩雪',gender:'女',age:35,unit:'公司机关',dept:'办公楼',
watchId:'CW-2024-0533',watchModel:'Huawei Watch D2',position:'公司机关办公楼6层602室',
status:'offline',lastActiveTime:'2026-06-29 18:30:12',top:12,left:48},
{name:'马国强',gender:'男',age:50,unit:'公司机关',dept:'办公楼',
watchId:'CW-2024-0388',watchModel:'Apple Watch Ultra 2',position:'公司机关办公楼大厅一楼东侧走廊',
status:'normal',lastActiveTime:'2026-06-30 14:22:08',top:15,left:52},
{name:'陈晓东',gender:'男',age:41,unit:'采油一厂',dept:'维修队',
watchId:'CW-2024-0678',watchModel:'OPPO Watch 4 Pro',position:'采油一厂维修队办公楼3层305室',
status:'alarm',alarmType:'压力异常',alarmTime:'2026-06-30 13:42:10',top:40,left:35},
{name:'郑海龙',gender:'男',age:43,unit:'采油一厂',dept:'生产区',
watchId:'CW-2024-0923',watchModel:'Xiaomi Watch S4',position:'采油一厂生产基地A区1号井组平台',
status:'alarm',alarmType:'体温异常',alarmTime:'2026-06-30 10:45:33',top:45,left:65},
{name:'冯国强',gender:'男',age:48,unit:'采油三厂',dept:'维修队',
watchId:'CW-2024-0814',watchModel:'Samsung Galaxy Watch7',position:'采油三厂维修车间东区2层平台',
status:'offline',lastActiveTime:'2026-06-29 22:15:40',top:78,left:55}
];
var WATCH_STATUS_COLOR={normal:'#52C41A',warning:'#FAAD14',alarm:'#FF4D4F',offline:'#4A6B8A'};
var WATCH_STATUS_LABEL={normal:'正常',warning:'预警',alarm:'告警',offline:'离线'};
var WATCH_PULSE_BG={alarm:'rgba(255,77,79,0.25)',warning:'rgba(250,173,20,0.2)',normal:'rgba(82,196,26,0.2)',offline:null};
/* 渲染手表位置标记点 */
function renderCvMapMarkers(){
var container=document.getElementById('cvMapMarkers');
if(!container) return;
var html=WATCH_MARKER_DATA.map(function(r,idx){
var color=WATCH_STATUS_COLOR[r.status];
var pulseBg=WATCH_PULSE_BG[r.status];
var pulse= pulseBg ? '<div class="map-marker__pulse" style="background:'+pulseBg+';"></div>' : '';
return '<div class="map-marker" data-idx="'+idx+'" data-top="'+r.top+'" data-left="'+r.left+'"'+
' style="top:'+r.top+'%;left:'+r.left+'%;"'+
' title="'+r.name+' · '+r.unit+' · '+WATCH_STATUS_LABEL[r.status]+'" onclick="openWatchMarkerPopup('+idx+')">'+
pulse+
'<svg class="map-marker__pin" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="'+color+'" stroke="#fff" stroke-width="1.5"/></svg>'+
'</div>';
}).join('');
container.innerHTML=html;
}
/* ========== 点击手表图标:地图放大居中 + 图标上方弹窗 ========== */
var cvMapScale=2;
function openWatchMarkerPopup(idx){
var r=WATCH_MARKER_DATA[idx];
if(!r) return;
var marker=document.querySelector('#cvMapMarkers .map-marker[data-idx="'+idx+'"]');
if(!marker) return;
var top=parseFloat(marker.getAttribute('data-top'));
var left=parseFloat(marker.getAttribute('data-left'));
/* 以地图中心(50,50)为缩放原点:先 scale(S),再 translate 把 (left,top) 拉到 (50,50) */
var tx=-(cvMapScale*(left-50));
var ty=-(cvMapScale*(top-50));
var stage=document.getElementById('cvMapStage');
if(stage) stage.style.transform='translate('+tx.toFixed(2)+'%, '+ty.toFixed(2)+'%) scale('+cvMapScale+')';
/* 选中态高亮 */
document.querySelectorAll('#cvMapMarkers .map-marker.is-active').forEach(function(m){m.classList.remove('is-active');});
marker.classList.add('is-active');
renderWatchMarkerPopup(r);
}
function closeWatchMarkerPopup(){
var stage=document.getElementById('cvMapStage');
if(stage) stage.style.transform='';
document.querySelectorAll('#cvMapMarkers .map-marker.is-active').forEach(function(m){m.classList.remove('is-active');});
var popup=document.getElementById('watchMarkerPopup');
if(popup){
popup.style.opacity='0';
setTimeout(function(){ var p=document.getElementById('watchMarkerPopup'); if(p) p.remove(); },220);
}
}
function renderWatchMarkerPopup(r){
var wrap=document.getElementById('cvMapWrap');
if(!wrap) return;
var old=document.getElementById('watchMarkerPopup');
if(old) old.remove();
/* 构建状态相关行 */
var statusRows='';
if(r.status==='normal'||r.status==='offline'){
statusRows='<div class="watch-marker-popup__section">活跃状态</div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">最近活跃</span><span class="watch-marker-popup__v">'+r.lastActiveTime+'</span></div>';
}else if(r.status==='warning'){
statusRows='<div class="watch-marker-popup__section">预警信息</div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">风险疾病</span><span class="watch-marker-popup__v" style="color:#FF4D4F;">'+r.riskType+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">风险等级</span><span class="watch-marker-popup__v" style="color:#FAAD14;">'+r.riskLevel+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">预测概率</span><span class="watch-marker-popup__v">'+r.probability+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">预警时间</span><span class="watch-marker-popup__v">'+r.warningTime+'</span></div>';
}else if(r.status==='alarm'){
statusRows='<div class="watch-marker-popup__section">告警信息</div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">告警类型</span><span class="watch-marker-popup__v" style="color:#FF4D4F;">'+r.alarmType+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">告警时间</span><span class="watch-marker-popup__v">'+r.alarmTime+'</span></div>';
}
var html='<div class="watch-marker-popup" id="watchMarkerPopup" style="visibility:hidden;">'+
'<span class="watch-marker-popup__close" title="关闭" onclick="closeWatchMarkerPopup()">&times;</span>'+
'<div class="watch-marker-popup__title">'+
'<span>'+r.name+'</span>'+
'<span class="watch-marker-popup__status watch-marker-popup__status--'+r.status+'">'+WATCH_STATUS_LABEL[r.status]+'</span>'+
'</div>'+
'<div class="watch-marker-popup__section">员工信息</div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">姓名</span><span class="watch-marker-popup__v">'+r.name+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">性别</span><span class="watch-marker-popup__v">'+r.gender+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">年龄</span><span class="watch-marker-popup__v">'+r.age+'岁</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">单位</span><span class="watch-marker-popup__v">'+r.unit+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">部门</span><span class="watch-marker-popup__v">'+r.dept+'</span></div>'+
'<div class="watch-marker-popup__section">手表信息</div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">手表编号</span><span class="watch-marker-popup__v">'+r.watchId+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">手表型号</span><span class="watch-marker-popup__v">'+r.watchModel+'</span></div>'+
'<div class="watch-marker-popup__row"><span class="watch-marker-popup__k">最新位置</span><span class="watch-marker-popup__v">'+r.position+'</span></div>'+
statusRows+
'<div class="watch-marker-popup__arrow"></div>'+
'</div>';
var tmp=document.createElement('div');
tmp.innerHTML=html;
var popup=tmp.firstElementChild;
wrap.appendChild(popup);
/* 先隐藏布局、量好尺寸再自适应定位,避免闪烁 */
positionWatchMarkerPopup(popup);
popup.style.visibility='visible';
}
/* 弹窗自适应定位:图标已居中于地图正中。
优先置于图标上方;空间不足改放下方;仍不足则贴顶夹紧。 */
function positionWatchMarkerPopup(popup){
var wrap=document.getElementById('cvMapWrap');
if(!wrap) return;
var wr=wrap.getBoundingClientRect();
var cx=wr.width/2, cy=wr.height/2;
var pw=popup.offsetWidth, ph=popup.offsetHeight;
var gap=30, margin=8;
var aboveTop=cy-gap-ph;
var belowTop=cy+gap;
var top, placement;
if(aboveTop>=margin){ top=aboveTop; placement='above'; }
else if(belowTop+ph<=wr.height-margin){ top=belowTop; placement='below'; }
else { top=margin; placement='clamp'; }
var left=Math.max(margin, Math.min(cx-pw/2, wr.width-pw-margin));
popup.style.left=left.toFixed(1)+'px';
popup.style.top=top.toFixed(1)+'px';
var arrow=popup.querySelector('.watch-marker-popup__arrow');
if(arrow){
if(placement!=='above'){ arrow.style.display='none'; }
else{
arrow.style.display='';
var ax=Math.max(10, Math.min(cx-left, pw-10));
arrow.style.left=ax.toFixed(1)+'px';
}
}
}
window.addEventListener('resize',function(){
var p=document.getElementById('watchMarkerPopup');
if(p) positionWatchMarkerPopup(p);
});
/* 初始渲染手表标记 */
renderCvMapMarkers();
</script>
</body>
</html>
@@ -1207,7 +1207,7 @@ document.getElementById('trendChart').addEventListener('mouseleave',function(){
function renderHourChart(){
var bins=STATS.hourBin;
var max=Math.max.apply(null,Object.keys(bins).map(function(k){return bins[k];}))||1;
var colors={'凌晨(00-06)':'#4A6B8A','上午(06-12)':'#1890FF','下午(12-18)':'#FF4D4F','夜间(18-24)':'#b37feb'};
var colors={'凌晨(00-06)':'#1890FF','上午(06-12)':'#1890FF','下午(12-18)':'#1890FF','夜间(18-24)':'#1890FF'};
var rows=Object.keys(bins).map(function(k){
var v=bins[k];
var pct=max>0?v/max*100:0;
@@ -1070,7 +1070,7 @@
<div class="panel" style="flex-shrink:0;">
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">应急求助</span>
<span class="panel__header-title">应急求助数据</span>
<!-- [交互] 点击打开应急求助记录弹窗(全部类型 + 当前时间段) -->
<span class="panel__header-link" style="margin-left:0;font-weight:500;" onclick="openHelpRecordFromStat('');">(历史记录)</span>
<!-- [交互] 时间段切换:近一周/近一月/近一年 -->
@@ -1093,12 +1093,12 @@
<div class="stat-card__label">应急电话次数</div>
</div>
<!-- [交互] 点击打开应急求助记录弹窗,并按“拨打120 + 当前时间段”筛选 -->
<div class="stat-card stat-card--danger" onclick="openHelpRecordFromStat('拨打120');event.stopPropagation();">
<div class="stat-card" onclick="openHelpRecordFromStat('拨打120');event.stopPropagation();">
<div class="stat-card__num">23</div>
<div class="stat-card__label">拨打120次数</div>
</div>
<!-- [交互] 点击打开应急求助记录弹窗,并按“全部类型 + 当前时间段”筛选 -->
<div class="stat-card stat-card--success" onclick="openHelpRecordFromStat('');event.stopPropagation();">
<div class="stat-card" onclick="openHelpRecordFromStat('');event.stopPropagation();">
<div class="stat-card__num">3,562</div>
<div class="stat-card__label">累计工单</div>
</div>
@@ -1120,7 +1120,7 @@
<div class="panel" style="flex-shrink:0;">
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">穿戴告警</span>
<span class="panel__header-title">穿戴告警分布</span>
<span class="panel__header-link" style="margin-left:-8px;" onclick="openAlarmRecordModal();event.stopPropagation();">(历史记录)</span>
<!-- [交互] 时间段切换:近一周/近一月/近一年 -->
<select class="panel__header-select" onchange="switchWearableStatPeriod(this.value)">
@@ -1135,42 +1135,42 @@
<div class="wearable-stat-chart__col">
<div class="wearable-stat-chart__label">心率异常</div>
<div class="wearable-stat-chart__bar-wrap">
<div class="wearable-stat-chart__bar" style="width:90%;background:#FF4D4F;color:#FF4D4F;"></div>
<div class="wearable-stat-chart__bar" style="width:90%;background:var(--primary);color:var(--primary);"></div>
</div>
<div class="wearable-stat-chart__bar-value">45<span class="wearable-stat-chart__bar-unit"></span></div>
</div>
<div class="wearable-stat-chart__col">
<div class="wearable-stat-chart__label">体温异常</div>
<div class="wearable-stat-chart__bar-wrap">
<div class="wearable-stat-chart__bar" style="width:56%;background:#FAAD14;color:#FAAD14;"></div>
<div class="wearable-stat-chart__bar" style="width:56%;background:var(--primary);color:var(--primary);"></div>
</div>
<div class="wearable-stat-chart__bar-value">28<span class="wearable-stat-chart__bar-unit"></span></div>
</div>
<div class="wearable-stat-chart__col">
<div class="wearable-stat-chart__label">血氧异常</div>
<div class="wearable-stat-chart__bar-wrap">
<div class="wearable-stat-chart__bar" style="width:70%;background:#1890FF;color:#1890FF;"></div>
<div class="wearable-stat-chart__bar" style="width:70%;background:var(--primary);color:var(--primary);"></div>
</div>
<div class="wearable-stat-chart__bar-value">35<span class="wearable-stat-chart__bar-unit"></span></div>
</div>
<div class="wearable-stat-chart__col">
<div class="wearable-stat-chart__label">压力异常</div>
<div class="wearable-stat-chart__bar-wrap">
<div class="wearable-stat-chart__bar" style="width:36%;background:#00e5ff;color:#00e5ff;"></div>
<div class="wearable-stat-chart__bar" style="width:36%;background:var(--primary);color:var(--primary);"></div>
</div>
<div class="wearable-stat-chart__bar-value">18<span class="wearable-stat-chart__bar-unit"></span></div>
</div>
<div class="wearable-stat-chart__col">
<div class="wearable-stat-chart__label">一键告警</div>
<div class="wearable-stat-chart__bar-wrap">
<div class="wearable-stat-chart__bar" style="width:24%;background:#b37feb;color:#b37feb;"></div>
<div class="wearable-stat-chart__bar" style="width:24%;background:var(--primary);color:var(--primary);"></div>
</div>
<div class="wearable-stat-chart__bar-value">12<span class="wearable-stat-chart__bar-unit"></span></div>
</div>
<div class="wearable-stat-chart__col">
<div class="wearable-stat-chart__label">摔倒</div>
<div class="wearable-stat-chart__bar-wrap">
<div class="wearable-stat-chart__bar" style="width:16%;background:#FF7875;color:#FF7875;"></div>
<div class="wearable-stat-chart__bar" style="width:16%;background:var(--primary);color:var(--primary);"></div>
</div>
<div class="wearable-stat-chart__bar-value">8<span class="wearable-stat-chart__bar-unit"></span></div>
</div>
@@ -1247,7 +1247,7 @@
<div class="panel panel--health-monitor" style="flex-shrink:0;">
<div class="panel__header">
<div class="panel__header-diamond"></div>
<span class="panel__header-title">人群现状</span>
<span class="panel__header-title">人群分布</span>
</div>
<div class="panel__body">
<div class="health-monitor">
@@ -1583,6 +1583,10 @@
<div class="marker-popup" id="markerPopup">
<div class="marker-popup__name" id="markerPopupName"></div>
<div class="marker-popup__cat" id="markerPopupCat"></div>
<div class="marker-popup__label" id="markerPopupUnitLabel" style="display:none;">所属单位</div>
<div class="marker-popup__addr" id="markerPopupUnit" style="display:none;"></div>
<div class="marker-popup__label" id="markerPopupClinicLabel" style="display:none;">所属医疗点</div>
<div class="marker-popup__addr" id="markerPopupClinic" style="display:none;"></div>
<div class="marker-popup__label">详细地址</div>
<div class="marker-popup__addr" id="markerPopupAddr"></div>
<div class="marker-popup__label">联系电话</div>
@@ -2295,7 +2299,8 @@
<span class="vc-card__name">救场应急中心</span>
<span class="vc-card__status vc-card__status--online"><span class="vc-card__dot vc-card__dot--online"></span>在线</span>
</div>
<div class="vc-card__meta"><span>值班负责人:<b>李志强</b></span></div>
<div class="vc-card__meta"><span>负责人:<b>李志强</b></span></div>
<div class="vc-card__meta"><span>中心电话:<b>029-8696 8001</b></span></div>
<span class="vc-card__check"><svg viewBox="0 0 12 12" fill="none"><path d="M2.5 6.2l2.3 2.3L9.5 3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<!-- 标准应急中心 · 在线 -->
@@ -2304,34 +2309,18 @@
<span class="vc-card__name">标准应急中心</span>
<span class="vc-card__status vc-card__status--online"><span class="vc-card__dot vc-card__dot--online"></span>在线</span>
</div>
<div class="vc-card__meta"><span>值班负责人:<b>王建华</b></span></div>
<div class="vc-card__meta"><span>负责人:<b>王建华</b></span></div>
<div class="vc-card__meta"><span>中心电话:<b>029-8696 8002</b></span></div>
<span class="vc-card__check"><svg viewBox="0 0 12 12" fill="none"><path d="M2.5 6.2l2.3 2.3L9.5 3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<!-- 长庆油田应急中心 · 忙碌 -->
<div class="vc-card" data-name="长庆油田应急中心" data-online="1" data-busy="1" onclick="toggleVcCard(this)">
<div class="vc-card__row">
<span class="vc-card__name">长庆油田应急中心</span>
<span class="vc-card__status vc-card__status--busy"><span class="vc-card__dot vc-card__dot--busy"></span>忙碌</span>
</div>
<div class="vc-card__meta"><span>值班负责人:<b>赵明</b></span></div>
<span class="vc-card__check"><svg viewBox="0 0 12 12" fill="none"><path d="M2.5 6.2l2.3 2.3L9.5 3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<!-- 延安应急中心 · 离线 -->
<div class="vc-card vc-card--offline" data-name="延安应急中心" data-online="0" onclick="toggleVcCard(this)">
<div class="vc-card__row">
<span class="vc-card__name">延安应急中心</span>
<span class="vc-card__status vc-card__status--offline"><span class="vc-card__dot vc-card__dot--offline"></span>离线</span>
</div>
<div class="vc-card__meta"><span>值班负责人:<b>陈昊</b></span></div>
<span class="vc-card__lock"><svg viewBox="0 0 12 12" fill="none"><path d="M3.5 5V4a2.5 2.5 0 015 0v1M3 5h6v4.5H3V5z" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<!-- 庆阳应急中心 · 在线 -->
<div class="vc-card" data-name="庆阳应急中心" data-online="1" onclick="toggleVcCard(this)">
<div class="vc-card__row">
<span class="vc-card__name">庆阳应急中心</span>
<span class="vc-card__status vc-card__status--online"><span class="vc-card__dot vc-card__dot--online"></span>在线</span>
</div>
<div class="vc-card__meta"><span>值班负责人:<b>刘文博</b></span></div>
<div class="vc-card__meta"><span>负责人:<b>刘文博</b></span></div>
<div class="vc-card__meta"><span>中心电话:<b>0934-3212 0101</b></span></div>
<span class="vc-card__check"><svg viewBox="0 0 12 12" fill="none"><path d="M2.5 6.2l2.3 2.3L9.5 3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<!-- 榆林应急中心 · 在线 -->
@@ -2340,9 +2329,30 @@
<span class="vc-card__name">榆林应急中心</span>
<span class="vc-card__status vc-card__status--online"><span class="vc-card__dot vc-card__dot--online"></span>在线</span>
</div>
<div class="vc-card__meta"><span>值班负责人:<b>孙鹏</b></span></div>
<div class="vc-card__meta"><span>负责人:<b>孙鹏</b></span></div>
<div class="vc-card__meta"><span>中心电话:<b>0912-3623 0101</b></span></div>
<span class="vc-card__check"><svg viewBox="0 0 12 12" fill="none"><path d="M2.5 6.2l2.3 2.3L9.5 3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<!-- 长庆油田应急中心 · 忙碌 -->
<div class="vc-card" data-name="长庆油田应急中心" data-online="1" data-busy="1" onclick="toggleVcCard(this)">
<div class="vc-card__row">
<span class="vc-card__name">长庆油田应急中心</span>
<span class="vc-card__status vc-card__status--busy"><span class="vc-card__dot vc-card__dot--busy"></span>忙碌</span>
</div>
<div class="vc-card__meta"><span>负责人:<b>赵明</b></span></div>
<div class="vc-card__meta"><span>中心电话:<b>029-8696 8005</b></span></div>
<span class="vc-card__check"><svg viewBox="0 0 12 12" fill="none"><path d="M2.5 6.2l2.3 2.3L9.5 3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<!-- 延安应急中心 · 离线 -->
<div class="vc-card vc-card--offline" data-name="延安应急中心" data-online="0" onclick="toggleVcCard(this)">
<div class="vc-card__row">
<span class="vc-card__name">延安应急中心</span>
<span class="vc-card__status vc-card__status--offline"><span class="vc-card__dot vc-card__dot--offline"></span>离线</span>
</div>
<div class="vc-card__meta"><span>负责人:<b>陈昊</b></span></div>
<div class="vc-card__meta"><span>中心电话:<b>0911-3625 0101</b></span></div>
<span class="vc-card__lock"><svg viewBox="0 0 12 12" fill="none"><path d="M3.5 5V4a2.5 2.5 0 015 0v1M3 5h6v4.5H3V5z" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
</div>
<div class="modal__actions">
<button class="modal__btn" onclick="closeVideoCallModal()">取消</button>
@@ -2922,13 +2932,13 @@ var RESOURCE_INFO={
'local-gaoling': {name:'高陵医院', cat:'地方医院',catColor:'#1890FF',addr:'西安市高陵区能源北路32号高陵医院住院部',phone:'029-8602 0120'},
'cooperative': {name:'宝石花医院', cat:'合作医院',catColor:'#722ED1',addr:'西安市高陵区宝石花大道126号宝石花医院综合楼',phone:'029-8671 9120'},
'local': {name:'庆阳医院', cat:'地方医院',catColor:'#1890FF',addr:'甘肃省庆阳市庆城县北大街55号庆阳医院急诊楼',phone:'0934-3212 0120'},
'clinic': {name:'采油一厂医疗点',cat:'医疗点',catColor:'#13C2C2',addr:'采油一厂生产基地综合楼1层医疗点',phone:'029-8696 8001'},
'clinic-eryuan': {name:'采油二厂医疗点',cat:'医疗点',catColor:'#13C2C2',addr:'采油二厂生产基地综合楼1层医疗点',phone:'029-8696 8002'},
'clinic-sanyuan': {name:'采油三厂医疗点',cat:'医疗点',catColor:'#13C2C2',addr:'采油三厂生产基地综合楼1层医疗点',phone:'029-8696 8003'},
'aed': {name:'AED-001', cat:'AED设备',catColor:'#FF4D4F',addr:'采油一厂生产区B区办公楼大厅东侧走廊',phone:'138 9192 0086'},
'aed-002': {name:'AED-002', cat:'AED设备',catColor:'#FF4D4F',addr:'采油二厂设备区主控楼1层大厅入口处',phone:'138 9192 0087'},
'ambulance': {name:'陕A·120-03', cat:'救护车',catColor:'#FAAD14',addr:'兴隆医院急救站停车场,24小时待命',phone:'189 9120 6603'},
'ambulance-001': {name:'陕A·120-01', cat:'救护车',catColor:'#FAAD14',addr:'宝石花医院急救站停车场,24小时待命',phone:'189 9120 6601'}
'clinic': {name:'采油一厂医疗点',cat:'医疗点',catColor:'#13C2C2',unit:'采油一厂',addr:'采油一厂生产基地综合楼1层医疗点',phone:'029-8696 8001'},
'clinic-eryuan': {name:'采油二厂医疗点',cat:'医疗点',catColor:'#13C2C2',unit:'采油二厂',addr:'采油二厂生产基地综合楼1层医疗点',phone:'029-8696 8002'},
'clinic-sanyuan': {name:'采油三厂医疗点',cat:'医疗点',catColor:'#13C2C2',unit:'采油三厂',addr:'采油三厂生产基地综合楼1层医疗点',phone:'029-8696 8003'},
'aed': {name:'AED-001', cat:'AED设备',catColor:'#FF4D4F',unit:'采油一厂',addr:'采油一厂生产区B区办公楼大厅东侧走廊',phone:'138 9192 0086'},
'aed-002': {name:'AED-002', cat:'AED设备',catColor:'#FF4D4F',unit:'采油二厂',addr:'采油二厂设备区主控楼1层大厅入口处',phone:'138 9192 0087'},
'ambulance': {name:'陕A·120-03', cat:'救护车',catColor:'#FAAD14',unit:'兴隆医院',clinic:'兴隆医院急救站',addr:'兴隆医院急救站停车场,24小时待命',phone:'189 9120 6603'},
'ambulance-001': {name:'陕A·120-01', cat:'救护车',catColor:'#FAAD14',unit:'宝石花医院',clinic:'宝石花医院急救站',addr:'宝石花医院急救站停车场,24小时待命',phone:'189 9120 6601'}
};
function onMapMarkerClick(el, key){
@@ -2968,6 +2978,23 @@ function showResourcePopup(el, key, withDispatch){
/* 填充内容 */
document.getElementById('markerPopupName').textContent=info.name;
document.getElementById('markerPopupCat').innerHTML='<span style="background:'+info.catColor+';box-shadow:0 0 6px '+info.catColor+';"></span>'+info.cat;
/* 所属单位 / 所属医疗点:按资源类型显示(医疗点、AED 显示所属单位;救护车额外显示所属医疗点) */
var unitEl=document.getElementById('markerPopupUnit');
var unitLabel=document.getElementById('markerPopupUnitLabel');
var clinicEl=document.getElementById('markerPopupClinic');
var clinicLabel=document.getElementById('markerPopupClinicLabel');
if(unitEl&&unitLabel){
var showUnit=!!info.unit;
unitEl.style.display=showUnit?'':'none';
unitLabel.style.display=showUnit?'':'none';
unitEl.textContent=info.unit||'';
}
if(clinicEl&&clinicLabel){
var showClinic=!!info.clinic;
clinicEl.style.display=showClinic?'':'none';
clinicLabel.style.display=showClinic?'':'none';
clinicEl.textContent=info.clinic||'';
}
document.getElementById('markerPopupAddr').textContent=info.addr;
document.getElementById('markerPopupPhone').textContent=info.phone||'--';
/* 下发急救通知按钮:仅应急路线模式下显示 */