fix: 更新健康监测文档和页面,删除活动统计页

This commit is contained in:
liuyunqin
2026-07-13 18:57:55 +08:00
parent a11702f727
commit 80722a05e0
67 changed files with 1294 additions and 951 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+32 -36
View File
@@ -33,7 +33,7 @@
│ ├── 水质监测(达标/未达标 · 6项指标)
│ └── 花粉监测(5级评价 · 8种花粉+总数)
├── 设备详情(依 Tab 切换指标下拉、阈值线与等级文案)
└── 报警数据(依 Tab 切换可选报警类型与阈值范围
└── 报警数据(依 Tab 切换展示不同类型报警,支持按年月筛选
```
## 页面清单
@@ -225,17 +225,17 @@
**功能说明:** 穿戴设备监测数据的主页,展示设备连接状态和各项健康指标的最新数据卡片网格。
**主要UI元素:**
- 设备连接状态栏(设备图标、设备名称如"华为 WATCH GT5 Pro"、设备序列号、连接状态
- 设备连接状态栏(手表图标、设备名称如"华为 WATCH GT5 Pro"、设备序列号如"SF55996622586"
- 监测卡片网格(2列布局,共9张卡片):
- 步数卡片(当前步数、距离换算、同步时间)
- 睡眠卡片(睡眠时长、平均睡眠参考、同步时间)
- 心率卡片(当前心率、范围值、同步时间)
- 体温卡片(当前体温、范围值、同步时间)
- 血氧卡片(当前血氧、范围值、同步时间)
- 压力卡片(当前压力值、范围值、同步时间)
- 血压卡片(收缩压/舒张压、状态标签、同步时间。手表不支持时,隐藏该模块
- 心电图卡片(心率值、分类结果标签、同步时间。手表不支持时,隐藏该模块
- 锻炼卡片(锻炼次数/时长/消耗统计、同步时间)
- 步数卡片(当前步数 8,562、距离换算"相当于步行5.2公里"、同步时间)
- 睡眠卡片(睡眠时长 7h 32min、平均睡眠参考"平均睡眠8小时57分"、同步时间)
- 心率卡片(当前心率 72 bpm、范围值 89~120、同步时间)
- 体温卡片(当前体温 36.5℃、范围值 36.2~37.1℃、同步时间)
- 血氧卡片(当前血氧 98%、范围值 95~99%、同步时间)
- 压力卡片(当前压力值 42、范围值 18~82、同步时间)
- 血压卡片(收缩压/舒张压 120/78 mmHg、状态标签"正常"、同步时间)
- 心电图卡片(心率 75 次/分钟、分类结果标签"窦性心律"、同步时间
- 锻炼卡片(消耗 680 千卡、锻炼时长 52 分钟、同步时间)
**交互说明:**
- 点击各卡片进入对应详情页面
@@ -269,18 +269,17 @@
**功能说明:** 展示心率预警记录列表,支持按年份和月份筛选。
**主要UI元素:**
- 预警记录列表标题(含总条数)
- 筛选按钮
- 预警记录卡片(时间、等级、数值、持续时长、触发原因、右侧阈值范围显示)
- 时间筛选弹窗(年份/月份下拉
- 预警记录列表标题(含总条数,如"预警记录(共 3 条)"
- 筛选按钮(显示当前筛选年月,如"2026-4"
- 预警记录卡片
- 预警时间(如"2026-04-28 16:32"
- 预警数值(如"142 bpm"
- 时间筛选弹窗(年份/月份下拉选择,取消/确定按钮)
**业务规则:**
- 预警等级:严重(红色)、警告(橙色)
- 每条预警卡片右侧显示正常阈值范围参考
**交互说明:**
- 点击筛选按钮打开时间筛选弹窗
- 选择年份和月份后点击"确定"应用筛选
- 选择年份和月份后点击"确定"应用筛选,卡片列表按所选年月过滤
---
@@ -288,7 +287,7 @@
**血氧详情(wearable/blood-oxygen.html):** 展示血氧监测数据,图表采用柱状图形式,支持日/周/月/年多时间维度查看,底部时间轴滑块可交互选择具体时间点。
**血氧预警历史(wearable/blood-oxygen-alerts.html):** 页面结构与心率预警历史一致,展示血氧预警记录列表,每条卡片右侧显示正常阈值范围
**血氧预警历史(wearable/blood-oxygen-alerts.html):** 展示血氧预警记录列表,支持按年月筛选。页面结构与心率预警历史一致,区别在于确定按钮为绿色主题。示例数据:1条记录(2026年3月),93%(严重)
![血氧详情](screenshots/wearable/blood-oxygen.jpg)
![血氧预警](screenshots/wearable/blood-oxygen-alerts.jpg)
@@ -302,13 +301,13 @@
**血压详情(wearable/blood-pressure.html):** 展示收缩压/舒张压双线图表,统计卡片分别显示收缩压和舒张压的最高/最低值。
**血压预警历史(wearable/blood-pressure-alerts.html):** 预警记录卡片同时显示收缩压舒张压数值,右侧显示正常阈值范围。
**血压预警历史(wearable/blood-pressure-alerts.html):** 预警记录以"收缩压/舒张压"格式显示(如"158/98 mmHg"),支持按年月筛选。示例数据:4条记录(2026年5月)
---
### 14-15. 体温详情与预警历史
**体温详情(wearable/temperature.html****体温预警历史(wearable/temperature-alerts.html** 的页面结构与心率一致,预警历史页面每条卡片右侧显示正常阈值范围
**体温详情(wearable/temperature.html****体温预警历史(wearable/temperature-alerts.html** 的页面结构与心率一致。
![体温详情](screenshots/wearable/temperature.jpg)
![体温预警](screenshots/wearable/temperature-alerts.jpg)
@@ -317,7 +316,7 @@
### 16-17. 压力详情与预警历史
**压力详情(wearable/stress.html****压力预警历史(wearable/stress-alerts.html** 的页面结构与心率一致额外显示压力等级评估徽章,预警历史页面每条卡片右侧显示正常阈值范围
**压力详情(wearable/stress.html****压力预警历史(wearable/stress-alerts.html** 的页面结构与心率一致。压力详情额外显示压力等级评估徽章。
![压力详情](screenshots/wearable/stress.jpg)
![压力预警](screenshots/wearable/stress-alerts.jpg)
@@ -452,9 +451,6 @@
**典型设备:** 空气质量监测站、PM10检测仪、臭氧检测仪、气象参数监测仪、VOC检测仪(共6台示例)
**等级描述(示例):**
- 优:空气质量令人满意,基本无污染
- 严重污染:健康人群运动耐受力降低,有明显强烈症状,提前出现某些疾病
##### 23.1.1 设备底部弹窗
@@ -609,47 +605,47 @@
![页面截图](screenshots/environment/alarm-data.jpg)
**功能说明:** 展示环境监测设备的报警记录,支持按日期范围和报警类型筛选。页面根据 URL 参数 `tab` 决定可选报警类型与展示的报警列表。
**功能说明:** 展示环境监测设备的报警记录,支持按时间筛选。页面根据 URL 参数 `tab`outdoor/indoor/water/pollen)决定展示的报警类型与数据列表。
**通用 UI 元素:**
- 筛选区:日期范围(起始/结束日期选择器)、类型下拉、重置按钮、查询按钮
- 统计条:报警总条数(红色突出)
- 报警卡片列表:左侧红色边条,顶部"{指标} 超标报警" + 时间,中部报警值(红底)→ 阈值范围(灰底),底部设备名称/位置
- 筛选按钮(显示当前年月,如"2026-5"
- 报警卡片列表:左侧红色边条,顶部"{指标} 超标报警" + 时间,中部"报警值(数值+单位)› 阈值范围(min~max+单位)"
- 数据通过 JavaScript 动态渲染
**通用交互说明:**
- 设置日期范围和类型后点击"查询"筛选
- 点击"重置"恢复默认日期与类型筛选条件
- 点击筛选按钮打开时间筛选弹窗,选择年月后确定
- 列表为空时显示"暂无报警数据"占位
**业务规则:**
- 报警值高于阈值上限或低于阈值下限时进入报警列表
- 报警卡片同时呈现实际值与允许阈值范围(min~max),便于直观判断超标幅度
- 报警卡片同时呈现实际值与允许阈值范围(min~max),直观判断超标幅度
#### 25.1 室外空气
![室外空气报警数据](screenshots/environment/alarm-data-outdoor.jpg)
**可选报警类型:** PM2.5、PM10、温度、湿度、风速、太阳辐射、照度
**示例数据(8条,2026年5月):** PM2.5156 μg/m³,阈值 0~75)、PM10218 μg/m³,阈值 0~150)、风速(13.5 m/s,阈值 0~10)、太阳辐射(1180 W/m²,阈值 0~1000)、温度(42.3℃,阈值 -10~40)、PM2.5195 μg/m³)、PM10285 μg/m³)、照度(98500 lm/m²,阈值 0~95000
#### 25.2 室内空气
![室内空气报警数据](screenshots/environment/alarm-data-indoor.jpg)
**可选报警类型:** PM2.5、PM10、CO₂、甲醛、温度、湿度
**示例数据(6条,2026年5月):** CO₂(1250 ppm,阈值 400~1000)、甲醛(0.15 mg/m³,阈值 0~0.10)、PM2.5135 μg/m³,阈值 0~75)、CO₂(980 ppm,敏感场所阈值 400~800)、湿度(85%,阈值 30~80)、甲醛(0.12 mg/m³)
#### 25.3 水质监测
![水质监测报警数据](screenshots/environment/alarm-data-water.jpg)
**可选报警类型:** PH、电导率、盐度、TDS、电阻率、温度
**示例数据(6条,2026年5月):** 电导率(1250 μS/cm,阈值 50~1000)、PH8.5,阈值 6.5~8.0)、TDS840 mg/L,阈值 0~500)、盐度(920 mg/L,阈值 0~500)、电导率(1680 μS/cm)、PH9.2
#### 25.4 花粉监测
![花粉监测报警数据](screenshots/environment/alarm-data-pollen.jpg)
**可选报警类型:** 花粉总数
**示例数据(3条,2026年5月):** 花粉总数(1098、924、860 粒/千mm²,阈值 0~800
> 备注:花粉监测仅对"花粉总数"设置统一报警阈值,各属/科单一指标暂不单独报警。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

@@ -15,17 +15,159 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.nav-back { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.nav-title { flex: 1; text-align: center; font-size: 17px; font-weight: 600; color: #1a1a1a; margin-right: 28px; }
/* 筛选 */
.filter-bar { background: #fff; padding: 12px 16px; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; display: flex; flex-direction: column; gap: 8px; }
.filter-row { display: flex; align-items: center; gap: 8px; }
.filter-label { font-size: 12px; color: #999; flex-shrink: 0; }
.filter-date-input { height: 32px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 8px; font-size: 12px; color: #333; outline: none; background: #fff; flex: 1; min-width: 0; }
.filter-date-input:focus { border-color: #1aad8d; }
.filter-sep { font-size: 12px; color: #bbb; flex-shrink: 0; }
.filter-select { height: 32px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 24px 0 10px; font-size: 12px; color: #333; background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center; appearance: none; outline: none; cursor: pointer; flex: 1; }
.filter-btn { height: 32px; padding: 0 16px; border-radius: 8px; border: none; font-size: 12px; font-weight: 600; cursor: pointer; flex-shrink: 0; }
.filter-btn--primary { background: #1aad8d; color: #fff; }
.filter-btn--reset { background: #f5f5f5; color: #666; }
/* 筛选按钮 */
.filter-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 2px;
border: none;
background: transparent;
font-size: 13px;
color: #333;
font-weight: 600;
cursor: pointer;
}
.filter-btn__arrow {
font-size: 9px;
color: #999;
}
/* 时间筛选弹窗 */
.time-filter-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
z-index: 1000;
}
.time-filter-modal.active {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.time-filter-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
}
.time-filter-panel {
position: relative;
width: 100%;
max-width: 390px;
background: #fff;
border-radius: 16px 16px 0 0;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.time-filter-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #f0f0f0;
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
}
.time-filter-close {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: none;
font-size: 20px;
color: #999;
cursor: pointer;
}
.time-filter-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.filter-section {
display: flex;
flex-direction: column;
gap: 8px;
}
.filter-label {
font-size: 13px;
font-weight: 600;
color: #333;
}
.filter-select {
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
color: #333;
background: #fff;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23666' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
padding-right: 32px;
}
.time-filter-footer {
display: flex;
gap: 12px;
padding: 16px;
border-top: 1px solid #f0f0f0;
}
.filter-btn-cancel,
.filter-btn-confirm {
flex: 1;
padding: 12px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.filter-btn-cancel {
background: #f5f5f5;
color: #666;
}
.filter-btn-cancel:active {
background: #e8e8e8;
}
.filter-btn-confirm {
background: #ff7a8a;
color: #fff;
}
.filter-btn-confirm:active {
background: #ff5a7a;
}
/* 统计条 */
.stat-bar { padding: 10px 16px; display: flex; align-items: center; justify-content: space-between; }
@@ -70,42 +212,67 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="nav-title">报警数据</div>
</div>
<!-- 筛选 -->
<div class="filter-bar">
<div class="filter-row">
<span class="filter-label">日期</span>
<input class="filter-date-input" type="date" id="dateFrom" value="2026-05-01">
<span class="filter-sep"></span>
<input class="filter-date-input" type="date" id="dateTo" value="2026-05-25">
</div>
<div class="filter-row">
<span class="filter-label">类型</span>
<select class="filter-select" id="typeFilter">
<option value="">全部类型</option>
</select>
<button class="filter-btn filter-btn--reset" onclick="resetFilter()">重置</button>
<button class="filter-btn filter-btn--primary" onclick="applyFilter()">查询</button>
</div>
</div>
<!-- 统计 -->
<!-- 统计与筛选 -->
<div class="stat-bar">
<span class="stat-bar__count"><strong id="totalCount">0</strong> 条报警</span>
<button class="filter-btn" id="filterBtn" onclick="openTimeFilter()">
<span id="filterLabel">2026-5</span>
<span class="filter-btn__arrow"></span>
</button>
</div>
<!-- 列表 -->
<div class="list-wrap" id="listWrap"></div>
<!-- 时间筛选弹窗 -->
<div class="time-filter-modal" id="timeFilterModal">
<div class="time-filter-overlay" onclick="closeTimeFilter()"></div>
<div class="time-filter-panel">
<div class="time-filter-header">
<span>选择时间</span>
<button class="time-filter-close" onclick="closeTimeFilter()"></button>
</div>
<div class="time-filter-content">
<!-- 年份选择 -->
<div class="filter-section">
<label class="filter-label">年份</label>
<select id="yearSelect" class="filter-select">
<option value="2026">2026</option>
<option value="2025">2025</option>
<option value="2024">2024</option>
</select>
</div>
<!-- 月份选择 -->
<div class="filter-section">
<label class="filter-label">月份</label>
<select id="monthSelect" class="filter-select">
<option value="01">1月</option>
<option value="02">2月</option>
<option value="03">3月</option>
<option value="04">4月</option>
<option value="05">5月</option>
<option value="06">6月</option>
<option value="07">7月</option>
<option value="08">8月</option>
<option value="09">9月</option>
<option value="10">10月</option>
<option value="11">11月</option>
<option value="12">12月</option>
</select>
</div>
</div>
<div class="time-filter-footer">
<button class="filter-btn-cancel" onclick="closeTimeFilter()">取消</button>
<button class="filter-btn-confirm" onclick="applyTimeFilter()">确定</button>
</div>
</div>
</div>
</div>
<script>
// 各 Tab 报警类型选项
const TYPE_OPTIONS = {
outdoor: ['PM2.5','PM10','温度','湿度','风速','太阳辐射','照度'],
indoor: ['PM2.5','PM10','CO₂','甲醛','温度','湿度'],
water: ['PH','电导率','盐度','TDS','电阻率','温度'],
pollen: ['花粉总数']
};
// 报警数据
const ALARM_DATA = {
outdoor: [
@@ -143,33 +310,34 @@ const ALARM_DATA = {
const params = new URLSearchParams(location.search);
const tab = params.get('tab') || 'outdoor';
const devId = params.get('id') || '';
// 初始化类型下拉
const typeOpts = TYPE_OPTIONS[tab] || [];
const typeSelect = document.getElementById('typeFilter');
typeOpts.forEach(t => {
const o = document.createElement('option');
o.value = t; o.textContent = t;
typeSelect.appendChild(o);
});
let currentYear = '2026';
let currentMonth = '05';
function applyFilter() {
const from = document.getElementById('dateFrom').value;
const to = document.getElementById('dateTo').value;
const type = typeSelect.value;
let list = ALARM_DATA[tab] || [];
if (from) list = list.filter(r => r.time.slice(0,10) >= from);
if (to) list = list.filter(r => r.time.slice(0,10) <= to);
if (type) list = list.filter(r => r.type === type);
renderList(list);
function openTimeFilter() {
document.getElementById('yearSelect').value = currentYear;
document.getElementById('monthSelect').value = currentMonth;
document.getElementById('timeFilterModal').classList.add('active');
}
function resetFilter() {
document.getElementById('dateFrom').value = '2026-05-01';
document.getElementById('dateTo').value = '2026-05-25';
typeSelect.value = '';
applyFilter();
function closeTimeFilter() {
document.getElementById('timeFilterModal').classList.remove('active');
}
function applyTimeFilter() {
currentYear = document.getElementById('yearSelect').value;
currentMonth = document.getElementById('monthSelect').value;
let list = ALARM_DATA[tab] || [];
if (currentYear) list = list.filter(r => r.time.slice(0,4) === currentYear);
if (currentMonth) list = list.filter(r => r.time.slice(5,7) === currentMonth);
renderList(list);
updateFilterLabel();
closeTimeFilter();
}
function updateFilterLabel() {
document.getElementById('filterLabel').textContent = currentYear + '-' + parseInt(currentMonth);
}
function renderList(list) {
@@ -202,8 +370,24 @@ function renderList(list) {
`).join('');
}
// 初始渲染
applyFilter();
// 初始渲染:按默认月份筛选
let list = ALARM_DATA[tab] || [];
if (currentYear) list = list.filter(r => r.time.slice(0,4) === currentYear);
if (currentMonth) list = list.filter(r => r.time.slice(5,7) === currentMonth);
renderList(list);
updateFilterLabel();
// 点击弹窗外部关闭
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('timeFilterModal');
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === modal || e.target.classList.contains('time-filter-overlay')) {
closeTimeFilter();
}
});
}
});
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -205,41 +205,22 @@ body {
/* 底部安全区 */
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
/* 筛选按钮 */
.filter-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
font-size: 12px;
color: #666;
padding: 4px 2px;
border: none;
background: transparent;
font-size: 13px;
color: #333;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.filter-btn:active {
background: #f5f5f5;
border-color: #bbb;
.filter-btn__arrow {
font-size: 9px;
color: #999;
}
/* 时间筛选弹窗 */
@@ -422,12 +403,9 @@ body {
<!-- 预警记录列表标题 + 筛选按钮 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 1 条)</div>
<button class="filter-btn" onclick="openTimeFilter()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<path d="M16 2v4M8 2v4M3 10h18"/>
</svg>
筛选
<button class="filter-btn" id="filterBtn" onclick="openTimeFilter()">
<span id="filterLabel">2026-3</span>
<span class="filter-btn__arrow"></span>
</button>
</div>
@@ -448,11 +426,6 @@ body {
<span class="alert-card__value-unit">%</span>
</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>100%</span></div>
<div class="alert-card__threshold-item">下限 <span>95%</span></div>
</div>
</div>
<!-- 底部安全区 -->
@@ -483,7 +456,6 @@ body {
<div class="filter-section">
<label class="filter-label">月份</label>
<select id="monthSelect" class="filter-select">
<option value="">全部</option>
<option value="01">1月</option>
<option value="02">2月</option>
<option value="03">3月</option>
@@ -511,7 +483,12 @@ body {
<script>
// 时间筛选功能
let currentYear = '2026';
let currentMonth = '03';
function openTimeFilter() {
document.getElementById('yearSelect').value = currentYear;
document.getElementById('monthSelect').value = currentMonth;
document.getElementById('timeFilterModal').classList.add('active');
}
@@ -524,29 +501,38 @@ function updateMonths() {
}
function applyTimeFilter() {
const year = document.getElementById('yearSelect').value;
const month = document.getElementById('monthSelect').value;
currentYear = document.getElementById('yearSelect').value;
currentMonth = document.getElementById('monthSelect').value;
// 获取所有预警记录卡片
const alertCards = document.querySelectorAll('.alert-card');
filterCards();
updateFilterLabel();
closeTimeFilter();
}
alertCards.forEach(card => {
const timeText = card.querySelector('.alert-card__time').textContent;
// 格式:2026-03-08 03:22
const [dateStr] = timeText.split(' ');
const [cardYear, cardMonth] = dateStr.split('-');
function filterCards() {
var alertCards = document.querySelectorAll('.alert-card');
alertCards.forEach(function(card) {
var timeText = card.querySelector('.alert-card__time').textContent;
var dateStr = timeText.split(' ')[0];
var parts = dateStr.split('-');
var cardYear = parts[0];
var cardMonth = parts[1];
// 根据筛选条件显示/隐藏
let show = true;
if (year && cardYear !== year) show = false;
if (month && cardMonth !== month) show = false;
var show = true;
if (currentYear && cardYear !== currentYear) show = false;
if (currentMonth && cardMonth !== currentMonth) show = false;
card.style.display = show ? 'flex' : 'none';
});
closeTimeFilter();
}
function updateFilterLabel() {
document.getElementById('filterLabel').textContent = currentYear + '-' + parseInt(currentMonth);
}
// 初始默认筛选
filterCards();
// 点击弹窗外部关闭
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('timeFilterModal');
@@ -188,37 +188,158 @@ body {
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
.filter-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
font-size: 12px;
color: #666;
padding: 4px 2px;
border: none;
background: transparent;
font-size: 13px;
color: #333;
font-weight: 600;
cursor: pointer;
}
.filter-btn:active { background: #f5f5f5; border-color: #bbb; }
.filter-btn__arrow {
font-size: 9px;
color: #999;
}
/* 时间筛选弹窗 */
.time-filter-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
z-index: 1000;
}
.time-filter-modal.active {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.time-filter-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
}
.time-filter-panel {
position: relative;
width: 100%;
max-width: 390px;
background: #fff;
border-radius: 16px 16px 0 0;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.time-filter-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #f0f0f0;
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
}
.time-filter-close {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: none;
font-size: 20px;
color: #999;
cursor: pointer;
}
.time-filter-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.filter-section {
display: flex;
flex-direction: column;
gap: 8px;
}
.filter-label {
font-size: 13px;
font-weight: 600;
color: #333;
}
.filter-select {
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
color: #333;
background: #fff;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23666' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
padding-right: 32px;
}
.time-filter-footer {
display: flex;
gap: 12px;
padding: 16px;
border-top: 1px solid #f0f0f0;
}
.filter-btn-cancel,
.filter-btn-confirm {
flex: 1;
padding: 12px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.filter-btn-cancel {
background: #f5f5f5;
color: #666;
}
.filter-btn-cancel:active {
background: #e8e8e8;
}
.filter-btn-confirm {
background: #ff7a8a;
color: #fff;
}
.filter-btn-confirm:active {
background: #ff5a7a;
}
</style>
</head>
@@ -255,12 +376,9 @@ body {
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 4 条)</div>
<button class="filter-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<path d="M16 2v4M8 2v4M3 10h18"/>
</svg>
筛选
<button class="filter-btn" id="filterBtn" onclick="openTimeFilter()">
<span id="filterLabel">2026-5</span>
<span class="filter-btn__arrow"></span>
</button>
</div>
@@ -278,10 +396,6 @@ body {
<span class="alert-card__value-unit">mmHg</span>
</div>
</div>
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140/90</span></div>
<div class="alert-card__threshold-item">下限 <span>90/60</span></div>
</div>
</div>
<!-- 预警记录 2:警告 -->
@@ -298,10 +412,6 @@ body {
<span class="alert-card__value-unit">mmHg</span>
</div>
</div>
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140/90</span></div>
<div class="alert-card__threshold-item">下限 <span>90/60</span></div>
</div>
</div>
<!-- 预警记录 3:警告 -->
@@ -318,10 +428,6 @@ body {
<span class="alert-card__value-unit">mmHg</span>
</div>
</div>
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140/90</span></div>
<div class="alert-card__threshold-item">下限 <span>90/60</span></div>
</div>
</div>
<!-- 预警记录 4:警告 -->
@@ -338,15 +444,117 @@ body {
<span class="alert-card__value-unit">mmHg</span>
</div>
</div>
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140/90</span></div>
<div class="alert-card__threshold-item">下限 <span>90/60</span></div>
</div>
</div>
<div class="bottom-safe"></div>
</div>
<!-- 时间筛选弹窗 -->
<div class="time-filter-modal" id="timeFilterModal">
<div class="time-filter-overlay" onclick="closeTimeFilter()"></div>
<div class="time-filter-panel">
<div class="time-filter-header">
<span>选择时间</span>
<button class="time-filter-close" onclick="closeTimeFilter()"></button>
</div>
<div class="time-filter-content">
<div class="filter-section">
<label class="filter-label">年份</label>
<select id="yearSelect" class="filter-select">
<option value="2026">2026</option>
<option value="2025">2025</option>
<option value="2024">2024</option>
</select>
</div>
<div class="filter-section">
<label class="filter-label">月份</label>
<select id="monthSelect" class="filter-select">
<option value="01">1月</option>
<option value="02">2月</option>
<option value="03">3月</option>
<option value="04">4月</option>
<option value="05">5月</option>
<option value="06">6月</option>
<option value="07">7月</option>
<option value="08">8月</option>
<option value="09">9月</option>
<option value="10">10月</option>
<option value="11">11月</option>
<option value="12">12月</option>
</select>
</div>
</div>
<div class="time-filter-footer">
<button class="filter-btn-cancel" onclick="closeTimeFilter()">取消</button>
<button class="filter-btn-confirm" onclick="applyTimeFilter()">确定</button>
</div>
</div>
</div>
</div>
<script>
// 时间筛选功能
let currentYear = '2026';
let currentMonth = '05';
function openTimeFilter() {
document.getElementById('yearSelect').value = currentYear;
document.getElementById('monthSelect').value = currentMonth;
document.getElementById('timeFilterModal').classList.add('active');
}
function closeTimeFilter() {
document.getElementById('timeFilterModal').classList.remove('active');
}
function applyTimeFilter() {
currentYear = document.getElementById('yearSelect').value;
currentMonth = document.getElementById('monthSelect').value;
filterCards();
updateFilterLabel();
closeTimeFilter();
}
function filterCards() {
var alertCards = document.querySelectorAll('.alert-card');
alertCards.forEach(function(card) {
var timeText = card.querySelector('.alert-card__time').textContent;
var dateStr = timeText.split(' ')[0];
var parts = dateStr.split('-');
var cardYear = parts[0];
var cardMonth = parts[1];
var show = true;
if (currentYear && cardYear !== currentYear) show = false;
if (currentMonth && cardMonth !== currentMonth) show = false;
card.style.display = show ? 'flex' : 'none';
});
}
function updateFilterLabel() {
document.getElementById('filterLabel').textContent = currentYear + '-' + parseInt(currentMonth);
}
// 初始默认筛选
filterCards();
// 点击弹窗外部关闭
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('timeFilterModal');
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === modal || e.target.classList.contains('time-filter-overlay')) {
closeTimeFilter();
}
});
}
});
</script>
</body>
</html>
@@ -204,41 +204,22 @@ body {
/* 底部安全区 */
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
/* 筛选按钮 */
.filter-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
font-size: 12px;
color: #666;
padding: 4px 2px;
border: none;
background: transparent;
font-size: 13px;
color: #333;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.filter-btn:active {
background: #f5f5f5;
border-color: #bbb;
.filter-btn__arrow {
font-size: 9px;
color: #999;
}
/* 时间筛选弹窗 */
@@ -421,12 +402,9 @@ body {
<!-- 预警记录列表标题 + 筛选按钮 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 3 条)</div>
<button class="filter-btn" onclick="openTimeFilter()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<path d="M16 2v4M8 2v4M3 10h18"/>
</svg>
筛选
<button class="filter-btn" id="filterBtn" onclick="openTimeFilter()">
<span id="filterLabel">2026-4</span>
<span class="filter-btn__arrow"></span>
</button>
</div>
@@ -447,11 +425,6 @@ body {
<span class="alert-card__value-unit">次/分钟</span>
</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>140 次/分钟</span></div>
<div class="alert-card__threshold-item">下限 <span>50 次/分钟</span></div>
</div>
</div>
<!-- 预警记录 2:警告 - 2026-04-25 -->
@@ -471,11 +444,6 @@ body {
<span class="alert-card__value-unit">次/分钟</span>
</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>120 次/分钟</span></div>
<div class="alert-card__threshold-item">下限 <span>50 次/分钟</span></div>
</div>
</div>
<!-- 预警记录 3:警告 - 2026-04-20 -->
@@ -495,11 +463,6 @@ body {
<span class="alert-card__value-unit">次/分钟</span>
</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>120 次/分钟</span></div>
<div class="alert-card__threshold-item">下限 <span>50 次/分钟</span></div>
</div>
</div>
<!-- 底部安全区 -->
@@ -530,7 +493,6 @@ body {
<div class="filter-section">
<label class="filter-label">月份</label>
<select id="monthSelect" class="filter-select">
<option value="">全部</option>
<option value="01">1月</option>
<option value="02">2月</option>
<option value="03">3月</option>
@@ -558,7 +520,12 @@ body {
<script>
// 时间筛选功能
let currentYear = '2026';
let currentMonth = '04';
function openTimeFilter() {
document.getElementById('yearSelect').value = currentYear;
document.getElementById('monthSelect').value = currentMonth;
document.getElementById('timeFilterModal').classList.add('active');
}
@@ -571,29 +538,38 @@ function updateMonths() {
}
function applyTimeFilter() {
const year = document.getElementById('yearSelect').value;
const month = document.getElementById('monthSelect').value;
currentYear = document.getElementById('yearSelect').value;
currentMonth = document.getElementById('monthSelect').value;
// 获取所有预警记录卡片
const alertCards = document.querySelectorAll('.alert-card');
filterCards();
updateFilterLabel();
closeTimeFilter();
}
alertCards.forEach(card => {
const timeText = card.querySelector('.alert-card__time').textContent;
// 格式:2026-04-28 16:32
const [dateStr] = timeText.split(' ');
const [cardYear, cardMonth] = dateStr.split('-');
function filterCards() {
var alertCards = document.querySelectorAll('.alert-card');
alertCards.forEach(function(card) {
var timeText = card.querySelector('.alert-card__time').textContent;
var dateStr = timeText.split(' ')[0];
var parts = dateStr.split('-');
var cardYear = parts[0];
var cardMonth = parts[1];
// 根据筛选条件显示/隐藏
let show = true;
if (year && cardYear !== year) show = false;
if (month && cardMonth !== month) show = false;
var show = true;
if (currentYear && cardYear !== currentYear) show = false;
if (currentMonth && cardMonth !== currentMonth) show = false;
card.style.display = show ? 'flex' : 'none';
});
closeTimeFilter();
}
function updateFilterLabel() {
document.getElementById('filterLabel').textContent = currentYear + '-' + parseInt(currentMonth);
}
// 初始默认筛选
filterCards();
// 点击弹窗外部关闭
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('timeFilterModal');
@@ -95,7 +95,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="device-icon"></div>
<div class="device-info">
<div class="device-name">华为 WATCH GT5 Pro</div>
<div class="device-meta">SF55996622586 · 已连接</div>
<div class="device-meta">SF55996622586</div>
</div>
<!-- <div class="device-battery">-->
<!-- <div class="battery-icon"><div class="battery-fill" style="width:72%"></div></div>-->
@@ -209,41 +209,22 @@ body {
/* 底部安全区 */
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
/* 筛选按钮 */
.filter-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
font-size: 12px;
color: #666;
padding: 4px 2px;
border: none;
background: transparent;
font-size: 13px;
color: #333;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.filter-btn:active {
background: #f5f5f5;
border-color: #bbb;
.filter-btn__arrow {
font-size: 9px;
color: #999;
}
/* 时间筛选弹窗 */
@@ -426,12 +407,9 @@ body {
<!-- 预警记录列表标题 + 筛选按钮 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 2 条)</div>
<button class="filter-btn" onclick="openTimeFilter()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<path d="M16 2v4M8 2v4M3 10h18"/>
</svg>
筛选
<button class="filter-btn" id="filterBtn" onclick="openTimeFilter()">
<span id="filterLabel">2026-4</span>
<span class="filter-btn__arrow"></span>
</button>
</div>
@@ -452,11 +430,6 @@ body {
<span class="alert-card__value-unit">压力值</span>
</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>80</span></div>
<div class="alert-card__threshold-item">下限 <span>0</span></div>
</div>
</div>
<!-- 预警记录 2:严重 - 2026-04-22 15:10,压力值 88 -->
@@ -476,11 +449,6 @@ body {
<span class="alert-card__value-unit">压力值</span>
</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>85</span></div>
<div class="alert-card__threshold-item">下限 <span>0</span></div>
</div>
</div>
<!-- 底部安全区 -->
@@ -511,7 +479,6 @@ body {
<div class="filter-section">
<label class="filter-label">月份</label>
<select id="monthSelect" class="filter-select">
<option value="">全部</option>
<option value="01">1月</option>
<option value="02">2月</option>
<option value="03">3月</option>
@@ -539,7 +506,12 @@ body {
<script>
// 时间筛选功能
let currentYear = '2026';
let currentMonth = '04';
function openTimeFilter() {
document.getElementById('yearSelect').value = currentYear;
document.getElementById('monthSelect').value = currentMonth;
document.getElementById('timeFilterModal').classList.add('active');
}
@@ -552,29 +524,38 @@ function updateMonths() {
}
function applyTimeFilter() {
const year = document.getElementById('yearSelect').value;
const month = document.getElementById('monthSelect').value;
currentYear = document.getElementById('yearSelect').value;
currentMonth = document.getElementById('monthSelect').value;
// 获取所有预警记录卡片
const alertCards = document.querySelectorAll('.alert-card');
filterCards();
updateFilterLabel();
closeTimeFilter();
}
alertCards.forEach(card => {
const timeText = card.querySelector('.alert-card__time').textContent;
// 格式:2026-04-30 16:30
const [dateStr] = timeText.split(' ');
const [cardYear, cardMonth] = dateStr.split('-');
function filterCards() {
var alertCards = document.querySelectorAll('.alert-card');
alertCards.forEach(function(card) {
var timeText = card.querySelector('.alert-card__time').textContent;
var dateStr = timeText.split(' ')[0];
var parts = dateStr.split('-');
var cardYear = parts[0];
var cardMonth = parts[1];
// 根据筛选条件显示/隐藏
let show = true;
if (year && cardYear !== year) show = false;
if (month && cardMonth !== month) show = false;
var show = true;
if (currentYear && cardYear !== currentYear) show = false;
if (currentMonth && cardMonth !== currentMonth) show = false;
card.style.display = show ? 'flex' : 'none';
});
closeTimeFilter();
}
function updateFilterLabel() {
document.getElementById('filterLabel').textContent = currentYear + '-' + parseInt(currentMonth);
}
// 初始默认筛选
filterCards();
// 点击弹窗外部关闭
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('timeFilterModal');
@@ -204,41 +204,22 @@ body {
/* 底部安全区 */
.bottom-safe { height: 20px; flex-shrink: 0; }
/* 右侧阈值范围 */
.alert-card__threshold {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
padding-top: 4px;
}
.alert-card__threshold-item {
font-size: 11px;
color: #bbb;
}
.alert-card__threshold-item span {
font-weight: 600;
color: #666;
}
/* 筛选按钮 */
.filter-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
font-size: 12px;
color: #666;
padding: 4px 2px;
border: none;
background: transparent;
font-size: 13px;
color: #333;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.filter-btn:active {
background: #f5f5f5;
border-color: #bbb;
.filter-btn__arrow {
font-size: 9px;
color: #999;
}
/* 时间筛选弹窗 */
@@ -421,12 +402,9 @@ body {
<!-- 预警记录列表标题 + 筛选按钮 -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 8px;">
<div class="section-title" style="margin: 0; padding: 0;">预警记录(共 1 条)</div>
<button class="filter-btn" onclick="openTimeFilter()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<path d="M16 2v4M8 2v4M3 10h18"/>
</svg>
筛选
<button class="filter-btn" id="filterBtn" onclick="openTimeFilter()">
<span id="filterLabel">2026-4</span>
<span class="filter-btn__arrow"></span>
</button>
</div>
@@ -447,11 +425,6 @@ body {
<span class="alert-card__value-unit">°C</span>
</div>
</div>
<!-- 右侧阈值范围 -->
<div class="alert-card__threshold">
<div class="alert-card__threshold-item">上限 <span>37.3°C</span></div>
<div class="alert-card__threshold-item">下限 <span>35.0°C</span></div>
</div>
</div>
<!-- 底部安全区 -->
@@ -482,7 +455,6 @@ body {
<div class="filter-section">
<label class="filter-label">月份</label>
<select id="monthSelect" class="filter-select">
<option value="">全部</option>
<option value="01">1月</option>
<option value="02">2月</option>
<option value="03">3月</option>
@@ -510,7 +482,12 @@ body {
<script>
// 时间筛选功能
let currentYear = '2026';
let currentMonth = '04';
function openTimeFilter() {
document.getElementById('yearSelect').value = currentYear;
document.getElementById('monthSelect').value = currentMonth;
document.getElementById('timeFilterModal').classList.add('active');
}
@@ -523,29 +500,38 @@ function updateMonths() {
}
function applyTimeFilter() {
const year = document.getElementById('yearSelect').value;
const month = document.getElementById('monthSelect').value;
currentYear = document.getElementById('yearSelect').value;
currentMonth = document.getElementById('monthSelect').value;
// 获取所有预警记录卡片
const alertCards = document.querySelectorAll('.alert-card');
filterCards();
updateFilterLabel();
closeTimeFilter();
}
alertCards.forEach(card => {
const timeText = card.querySelector('.alert-card__time').textContent;
// 格式:2026-04-15 13:28
const [dateStr] = timeText.split(' ');
const [cardYear, cardMonth] = dateStr.split('-');
function filterCards() {
var alertCards = document.querySelectorAll('.alert-card');
alertCards.forEach(function(card) {
var timeText = card.querySelector('.alert-card__time').textContent;
var dateStr = timeText.split(' ')[0];
var parts = dateStr.split('-');
var cardYear = parts[0];
var cardMonth = parts[1];
// 根据筛选条件显示/隐藏
let show = true;
if (year && cardYear !== year) show = false;
if (month && cardMonth !== month) show = false;
var show = true;
if (currentYear && cardYear !== currentYear) show = false;
if (currentMonth && cardMonth !== currentMonth) show = false;
card.style.display = show ? 'flex' : 'none';
});
closeTimeFilter();
}
function updateFilterLabel() {
document.getElementById('filterLabel').textContent = currentYear + '-' + parseInt(currentMonth);
}
// 初始默认筛选
filterCards();
// 点击弹窗外部关闭
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('timeFilterModal');
@@ -709,10 +709,10 @@ body {
</div>
<!-- 今日三餐推荐 -->
<div class="meal-card" id="mealCard">
<div class="meal-card__header-row">
<div class="meal-card__title">今日三餐推荐</div>
<!-- 今日食谱推荐 -->
<div class="meal-card" id="mealCard">
<div class="meal-card__header-row">
<div class="meal-card__title">今日食谱推荐</div>
<span class="meal-card__ai-reason" id="mealReason">根据你的偏好与健康目标推荐</span>
</div>
<div class="meal-card__tabs">
-1
View File
@@ -353,7 +353,6 @@ const SITE_MAP = [
{ file:'health-checkup/survey-manage.html', label:'问卷管理' },
{ file:'health-checkup/survey-edit.html', label:'新增/编辑问卷', dialog:true },
{ file:'health-checkup/activity-manage.html', label:'活动管理' },
{ file:'health-checkup/activity-stat.html', label:'活动统计' },
{ file:'health-checkup/activity-edit.html', label:'新增/编辑活动', dialog:true },
{ file:'health-checkup/activity-detail.html', label:'活动详情', dialog:true },
{ file:'wearable/tool-manage.html', label:'穿戴监测-工具管理' },
@@ -39,8 +39,10 @@
.info-meta__item{padding:0 12px;border-right:1px solid var(--border);}
.info-meta__item:first-child{padding-left:0;}
.info-meta__item:last-child{border-right:none;}
.info-card__right{display:flex;align-items:center;gap:40px;padding-left:32px;border-left:1px solid var(--border);flex-shrink:0;}
.stat-item{text-align:center;}
.info-card__right{display:flex;align-items:center;gap:40px;padding-left:32px;border-left:1px solid var(--border);flex-shrink:0;}
.info-card__date-filter{display:flex;align-items:center;gap:8px;}
.info-card__date-filter label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
.stat-item{text-align:center;}
.stat-num{font-size:28px;font-weight:700;line-height:1.2;}
.stat-num--blue{color:var(--primary);}
.stat-num--green{color:var(--success);}
@@ -182,6 +184,16 @@
</div>
</div>
<div class="info-card__right">
<div class="info-card__date-filter">
<label>年份</label>
<select class="filter-select" id="filterYear" onchange="onYearChange()">
<option value="2026">2026年</option>
<option value="2025">2025年</option>
<option value="2024">2024年</option>
</select>
</div>
</div>
</div>
<!-- ③ Tab 切换区 + 筛选栏 + 表格 -->
@@ -479,6 +491,12 @@
</div>
<script>
/* ===== 年份切换 ===== */
var currentYear = '2026';
function onYearChange(){
currentYear = document.getElementById('filterYear').value;
// 可根据年份刷新页面数据
}
// [交互] Tab 切换
function switchTab(tab) {
document.querySelectorAll('.tab-item').forEach(function(el, i) {
@@ -42,6 +42,13 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.info-card__detail{display:flex;flex-direction:column;gap:6px;}
.info-card__name{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);}
.info-card__meta{display:flex;gap:24px;font-size:var(--font-sm);color:var(--text-secondary);}
.info-card__right{flex-shrink:0;margin-left:auto;}
.info-card__date-filter{display:flex;align-items:center;gap:8px;}
.info-card__date-filter label{font-size:var(--font-xs);color:var(--text-secondary);white-space:nowrap;}
/* 年份选择 */
.filter-select{height:32px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 10px;font-size:var(--font-xs);color:var(--text-primary);background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 8px center;cursor:pointer;outline:none;appearance:none;-webkit-appearance:none;}
/* Tab栏 */
.tab-bar{display:flex;gap:0;border-bottom:2px solid var(--border);margin-bottom:20px;background:#fff;padding:0 24px;border-radius:var(--radius-md) var(--radius-md) 0 0;box-shadow:var(--shadow-sm);}
.tab-item{padding:14px 24px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-2px;transition:all .2s;white-space:nowrap;}
@@ -206,6 +213,16 @@ tbody tr:hover{background:#F8FBFF;}
<span id="empDept">研发部</span>
</div>
</div>
<div class="info-card__right">
<div class="info-card__date-filter">
<label>年份</label>
<select class="filter-select" id="filterYear" onchange="onYearChange()">
<option value="2026">2026年</option>
<option value="2025">2025年</option>
<option value="2024">2024年</option>
</select>
</div>
</div>
</div>
<!-- [交互] 四个Tab -->
<div class="tab-bar">
@@ -364,6 +381,12 @@ tbody tr:hover{background:#F8FBFF;}
</div>
</div>
<script>
/* ===== 年份切换 ===== */
var currentYear = '2026';
function onYearChange(){
currentYear = document.getElementById('filterYear').value;
// 可根据年份刷新页面数据
}
// 主Tab切换
function switchMainPanel(name, el) {
document.querySelectorAll('.tab-bar .tab-item').forEach(function(t){t.classList.remove('active');});
+39 -47
View File
@@ -43,7 +43,6 @@
│ │ └── 新增/编辑活动
│ │ └── 活动详情
│ │ └── 内Tab:已参加人员 / 未参加人员
│ └── 活动统计
├── 穿戴监测(侧边栏)
│ ├── 设备管理
@@ -140,7 +139,6 @@
| 7 | 活动管理 | 活动列表 | health-checkup/activity-manage.html | 页面 |
| | | 新增/编辑活动 | health-checkup/activity-edit.html | 页面 |
| | | 活动详情 | health-checkup/activity-detail.html | 页面 |
| 8 | 活动统计 | 活动统计列表 | health-checkup/activity-stat.html | 页面 |
### 穿戴监测(侧边栏组:穿戴监测 / 运维中心)
@@ -242,13 +240,13 @@
**功能说明:** 展示单个员工的全维度健康数据详情,涵盖健康现状、健康排查、穿戴监测三大板块。
**页面布局:**
- 顶部员工信息卡片:头像(首字母)、姓名、工号、单位、部门
- 顶部员工信息卡片:头像(首字母)、姓名、工号、单位、部门、年份筛选(下拉,默认当年)
- 主Tab切换:健康现状 / 健康排查 / 穿戴监测
**健康现状Tab**
- 健康分类标签(如B类)
- 院信息网格:状态、医院、日期、联系方式
- 慢性病表格:病名、确诊年份、医院、治疗情况
- 院信息网格:住院状态、所在医院、住院时间、住院次数、联系人、联系电话
- 分类信息(如"B类慢病信息"+ 慢性病表格:病名、确诊年份、确诊医院、治疗情况
**健康排查Tab**
- 按参与状态筛选
@@ -468,7 +466,7 @@
## 三、健康排查(含问卷管理、活动管理)
> **说明**:健康排查模块侧边栏包含个菜单项:问卷管理、活动管理、活动统计
> **说明**:健康排查模块侧边栏包含个菜单项:问卷管理、活动管理。
### 103. 问卷管理(health-checkup/survey-manage.html
@@ -476,16 +474,20 @@
**功能说明:** 管理健康排查问卷,支持问卷的创建、编辑、预览和删除。
**筛选条件:** 问卷名称(输入框)、目标人群(多选:A~E类)
**筛选条件:** 问卷名称(输入框)、适应人群(多选:A~E类)
**表格列:** 问卷名称、人群标签(彩色)、状态(开关:开启/关闭)、操作
**表格列:** 问卷名称、适应人群(彩色标签:A类蓝色/B类绿色/C类橙色/D类紫色/E类青色)、问卷状态(开关:开启/关闭)、操作
**交互说明:**
- 点击"新增问卷"打开新增弹窗
- 点击"预览"打开预览弹窗(展示问卷基本信息+所有题目,含题型图标、选项和跳转逻辑提示)
- 点击"编辑"打开编辑弹窗(问卷名称、人群多选、描述)
- 操作列:预览、编辑、删除
- 支持导出列表数据
- 点击"编辑"跳转到编辑页面
- 操作列:问卷预览、编辑、删除
- 支持导出列表数据、导出问卷详情、查看导出任务
**新增/编辑弹窗字段:**
- 问卷名称 *(输入框)
- 适应人群 *(多选:A~E类)
---
@@ -496,7 +498,7 @@
**功能说明:** 完整的问卷编辑器,支持创建和编辑健康排查问卷。
**页面布局:** 左右分栏布局
- **左侧面板(吸顶):** 问卷名称输入、目标人群多选(A~E类)、描述文本域
- **左侧面板(吸顶):** 问卷名称输入、适应人群多选(A~E类)、描述文本域
- **右侧面板:** 题目列表区域,题目卡片
- **底部保存栏:** 显示问卷名称或"未填写",取消和保存按钮
@@ -519,15 +521,15 @@
**功能说明:** 健康排查活动的增删改查管理。
**筛选条件:** 活动名称(输入框)、所属单位(下拉)、活动状态(下拉:待开始/进行中/已完成)、日期范围
**筛选条件:** 活动名称(输入框)、组织单位(下拉)、活动状态(下拉:待开始/进行中/已完成)、日期范围
**表格列:** 活动名称、所属单位、所属部门、日期范围、人员范围(点击可查看参与人员)、状态标签、负责人、联系电话、操作
**表格列:** 活动名称、组织单位、组织部门、起止日期、人员筛选(点击可查看参与人员弹窗)、应参加人数、已参加人数(绿色)、未参加人数(红色)、活动状态标签、负责人、负责人电话、操作
**交互说明:**
- 点击"新增活动"跳转到新增活动页面
- 点击"人员范围"打开参与人员弹窗(含筛选、汇总统计、内嵌员工表格)
- 操作列:详情、编辑、删除(带确认提示)
- 支持导出列表数据
- 点击"人员筛选"列打开参与人员弹窗(含筛选:单位/部门、员工姓名、员工编号、是否参加 + 汇总统计:应参加/已参加/未参加人数 + 内嵌员工表格)
- 操作列:活动详情、编辑、删除(带确认提示)
- 支持导出列表数据、查看导出任务
---
@@ -538,16 +540,20 @@
**功能说明:** 创建或编辑健康排查活动。
**表单字段:**
- 活动名称、所属单位(下拉)、所属部门(下拉)
- 活动名称
- 组织机构(树形联动选择器:展开单位后选择部门,支持多厂/多部门)
- 问卷多选(显示每个问卷对应的人群标签)
- 人员筛选:单选切换(全员/指定)。选择"指定"时:部门多选+员工选择器
- 员工选择器弹窗:按部门、姓名、工号、人群类型筛选;复选选择,支持全选;显示单位/部门/姓名/工号/性别/年龄/人群
- 日期范围、负责人(可搜索下拉,带手机号)、活动说明(文本域)
- 文件上传区域(拖拽上传,10MB限制,支持PDF/Word/Excel/图片
- 员工选择器弹窗:按部门、姓名、工号、所属人群筛选;复选选择,支持全选;显示单位/部门/姓名/工号/性别/年龄/所属人群
- 起止日期
- 负责部门(下拉
- 负责人(可搜索下拉,带头像,含手机号自动填充)
- 活动说明(文本域)
- 活动文件上传区域(拖拽上传,10MB限制,支持PDF/Word/Excel/图片,单文件)
- 底部保存栏:取消和保存按钮
**交互说明:**
- 选择单位后启用部门和员工选择器
- 选择组织机构后启用部门和员工选择器
- 已选员工以可移除标签形式展示
- 负责人搜索带头像样式选项
@@ -560,8 +566,8 @@
**功能说明:** 查看活动详情和参与人员数据。
**页面布局:**
- 面包屑导航:根据来源动态变化(来自activity-manage或activity-stat
- 信息卡片:4列网格展示所有活动字段,含问卷及人群标签
- 面包屑导航:活动管理 / 活动详情
- 信息卡片:4列网格展示所有活动字段(活动名称、组织单位、组织部门、起止日期、人员筛选、活动状态、负责人、负责人电话、活动说明、选择问卷(含人群标签)、活动文件)
- Tab切换:已参加人员 / 未参加人员(带数量角标)
**已参加人员Tab** 筛选(部门、姓名、工号)、导出按钮、表格列:单位、部门、姓名、工号、性别、年龄、已填问卷、填写时间、操作(问卷详情)
@@ -572,22 +578,6 @@
---
### 108. 活动统计(health-checkup/activity-stat.html
![页面截图](screenshots/health-checkup/activity-stat.jpg)
**功能说明:** 查看各活动的参与统计情况。
**筛选条件:** 活动名称(输入框)、所属单位(多选)
**表格列:** 活动名称、所属单位、所属部门、日期范围、应参加人数、已参加人数(绿色)、未参加人数(红色)、状态标签、操作(详情)
**交互说明:**
- 点击"详情"跳转到活动详情页
- 支持导出列表数据
---
### 109. 心率异常(watch5)wearable/ops/alarm-heartrate.html
![页面截图](screenshots/wearable/ops/alarm-heartrate.jpg)
@@ -704,11 +694,12 @@
**交互说明:**
- 点击"新增设备"打开新增设备表单弹窗
- 点击"批量入库导入"打开文件上传弹窗(Excel导入)
- 点击"批量入库导入"打开文件上传弹窗(Excel导入,含所属单位选择
- 点击"批量绑定导入"打开批量绑定弹窗
- 点击"查看导入任务"查看导入任务状态
- 点击"导出列表数据"导出数据
- 点击"查看导出任务"查看导出任务状态
- 点击"绑定员工"打开绑定弹窗(搜索选择员工)
- 点击"绑定员工"打开绑定弹窗(搜索选择员工,含绑定日期
- 点击"解除绑定"打开确认弹窗
- 点击"绑定记录"打开该设备的绑定历史弹窗
- 点击更多菜单→"设备配置"打开设备配置弹窗
@@ -1500,7 +1491,7 @@ Tab标签:总览、心率、血氧、压力、睡眠、体温、锻炼、步
**表格列:** 设备名称、设备编码、所属单位、设备位置、设备详情、温度、湿度、CO₂、甲醛、PM2.5、PM10、总点击次数↕
> 注:实际页面各监测指标列未带"点击次数"后缀(与截图一致),多出"设备详情"列
> 注:表头一级分组为"移动端点击次数",各监测指标列名直接使用指标名称(不带"点击次数"后缀)。各类型均包含"设备详情"列,位于指标列最前
**交互说明:**
- 表头支持排序
@@ -1604,10 +1595,11 @@ Tab标签:总览、心率、血氧、压力、睡眠、体温、锻炼、步
#### APP记录页面差异
各类型的点击次数列对应各自监测指标:
-外:温度/湿度/太阳辐射/PM2.5/PM10/照度/风速点击次数 + 总点击次数
- 水质:温度/电导率/pH/盐度/电阻率/TDS点击次数 + 总点击次数
- 花粉:榆属/松属/杨属/柏科/柳属/蒿属/藜科/苋科/花粉总数点击次数 + 总点击次数
各类型的点击次数列对应各自监测指标(一级表头为"移动端点击次数",二级列名直接使用指标名称,均含"设备详情"列)
-内:设备详情 + 温度/湿度/CO₂/甲醛/PM2.5/PM10 + 总点击次数8列)
- 室外:设备详情 + 温度/湿度/太阳辐射/PM2.5/PM10/照度/风速 + 总点击次数9列)
- 水质:设备详情 + 温度/电导率/PH/盐度/电阻率/TDS + 总点击次数8列)
- 花粉:设备详情 + 榆属/松属/杨属/柏科/柳属/蒿属/藜科/苋科/花粉总数 + 总点击次数(11列)
#### 历史监测详情页面差异
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 86 KiB

@@ -193,7 +193,7 @@
<td>IA-2024-001</td>
<td>采油一厂</td>
<td>办公楼1层大厅</td>
<td><a href="indoor-device-list.html" class="link">设备详情</a></td>
<td>245</td>
<td>326</td><td>218</td><td>185</td><td>412</td><td>538</td><td>295</td><td>1974</td>
</tr>
<tr>
@@ -201,7 +201,7 @@
<td>IA-2024-002</td>
<td>采油一厂</td>
<td>办公楼2层会议室</td>
<td><a href="indoor-device-list.html" class="link">设备详情</a></td>
<td>178</td>
<td>142</td><td>98</td><td>76</td><td>203</td><td>687</td><td>345</td><td>1551</td>
</tr>
<tr>
@@ -209,7 +209,7 @@
<td>IA-2024-003</td>
<td>采油二厂</td>
<td>新装修办公室3层</td>
<td><a href="indoor-device-list.html" class="link">设备详情</a></td>
<td>203</td>
<td>89</td><td>56</td><td>112</td><td>856</td><td>234</td><td>128</td><td>1475</td>
</tr>
<tr>
@@ -217,7 +217,7 @@
<td>IA-2024-004</td>
<td>采油二厂</td>
<td>地下车库B1层</td>
<td><a href="indoor-device-list.html" class="link">设备详情</a></td>
<td>156</td>
<td>67</td><td>45</td><td>523</td><td>38</td><td>156</td><td>89</td><td>918</td>
</tr>
<tr>
@@ -225,7 +225,7 @@
<td>IA-2024-005</td>
<td>采油三厂</td>
<td>生产车间1号</td>
<td><a href="indoor-device-list.html" class="link">设备详情</a></td>
<td>289</td>
<td>458</td><td>312</td><td>267</td><td>189</td><td>723</td><td>456</td><td>2405</td>
</tr>
</tbody>
@@ -180,27 +180,27 @@
<tbody>
<tr>
<td class="td-left">空气质量监测站-A01</td><td>OA-2024-001</td><td>采油一厂</td><td>厂区大门东侧</td>
<td><a href="outdoor-device-list.html" class="link">设备详情</a></td>
<td>245</td>
<td>285</td><td>196</td><td>78</td><td>523</td><td>412</td><td>65</td><td>134</td><td>1693</td>
</tr>
<tr>
<td class="td-left">PM10检测仪-B02</td><td>OA-2024-002</td><td>采油一厂</td><td>生产区北围栏</td>
<td><a href="outdoor-device-list.html" class="link">设备详情</a></td>
<td>189</td>
<td>156</td><td>112</td><td>45</td><td>687</td><td>534</td><td>38</td><td>89</td><td>1661</td>
</tr>
<tr>
<td class="td-left">臭氧检测仪-C01</td><td>OA-2024-003</td><td>采油二厂</td><td>办公楼楼顶</td>
<td><a href="outdoor-device-list.html" class="link">设备详情</a></td>
<td>134</td>
<td>98</td><td>76</td><td>123</td><td>345</td><td>267</td><td>108</td><td>56</td><td>1073</td>
</tr>
<tr>
<td class="td-left">气象参数监测仪-D01</td><td>OA-2024-004</td><td>采油三厂</td><td>气象观测场</td>
<td><a href="outdoor-device-list.html" class="link">设备详情</a></td>
<td>278</td>
<td>412</td><td>328</td><td>256</td><td>189</td><td>145</td><td>215</td><td>367</td><td>1912</td>
</tr>
<tr>
<td class="td-left">空气质量监测站-A03</td><td>OA-2024-005</td><td>采油三厂</td><td>储油罐区南侧</td>
<td><a href="outdoor-device-list.html" class="link">设备详情</a></td>
<td>198</td>
<td>234</td><td>178</td><td>92</td><td>456</td><td>389</td><td>87</td><td>145</td><td>1581</td>
</tr>
</tbody>
@@ -182,27 +182,27 @@
<tbody>
<tr>
<td class="td-left">花粉采样器-A01</td><td>PL-2024-001</td><td>采油一厂</td><td>办公区绿化带</td>
<td><a href="pollen-device-list.html" class="link">设备详情</a></td>
<td>198</td>
<td>123</td><td>89</td><td>256</td><td>67</td><td>145</td><td>78</td><td>34</td><td>28</td><td>312</td><td>1132</td>
</tr>
<tr>
<td class="td-left">花粉浓度监测仪-B01</td><td>PL-2024-002</td><td>采油一厂</td><td>厂区东侧林带</td>
<td><a href="pollen-device-list.html" class="link">设备详情</a></td>
<td>245</td>
<td>198</td><td>156</td><td>423</td><td>112</td><td>234</td><td>145</td><td>56</td><td>42</td><td>478</td><td>1844</td>
</tr>
<tr>
<td class="td-left">花粉自动计数仪-C01</td><td>PL-2024-003</td><td>采油二厂</td><td>生活区花园</td>
<td><a href="pollen-device-list.html" class="link">设备详情</a></td>
<td>267</td>
<td>267</td><td>178</td><td>345</td><td>98</td><td>189</td><td>212</td><td>78</td><td>56</td><td>534</td><td>1957</td>
</tr>
<tr>
<td class="td-left">综合气象花粉站-D01</td><td>PL-2024-004</td><td>采油二厂</td><td>气象观测场</td>
<td><a href="pollen-device-list.html" class="link">设备详情</a></td>
<td>312</td>
<td>345</td><td>234</td><td>512</td><td>156</td><td>278</td><td>189</td><td>98</td><td>67</td><td>623</td><td>2502</td>
</tr>
<tr>
<td class="td-left">花粉采样器-A03</td><td>PL-2024-005</td><td>采油三厂</td><td>杨树林防护带</td>
<td><a href="pollen-device-list.html" class="link">设备详情</a></td>
<td>356</td>
<td>412</td><td>298</td><td>678</td><td>189</td><td>356</td><td>234</td><td>112</td><td>89</td><td>756</td><td>3124</td>
</tr>
</tbody>
@@ -179,27 +179,27 @@
<tbody>
<tr>
<td class="td-left">多参数水质分析仪-A01</td><td>WQ-2024-001</td><td>采油一厂</td><td>生活区供水站</td>
<td><a href="water-device-list.html" class="link">设备详情</a></td>
<td>223</td>
<td>312</td><td>245</td><td>478</td><td>156</td><td>89</td><td>203</td><td>1483</td>
</tr>
<tr>
<td class="td-left">在线PH计-B02</td><td>WQ-2024-002</td><td>采油一厂</td><td>污水处理站出口</td>
<td><a href="water-device-list.html" class="link">设备详情</a></td>
<td>167</td>
<td>178</td><td>134</td><td>623</td><td>98</td><td>67</td><td>145</td><td>1245</td>
</tr>
<tr>
<td class="td-left">电导率监测仪-C01</td><td>WQ-2024-003</td><td>采油二厂</td><td>注水站入口</td>
<td><a href="water-device-list.html" class="link">设备详情</a></td>
<td>145</td>
<td>95</td><td>567</td><td>112</td><td>234</td><td>189</td><td>78</td><td>1275</td>
</tr>
<tr>
<td class="td-left">TDS检测仪-D01</td><td>WQ-2024-004</td><td>采油二厂</td><td>生产区储水池</td>
<td><a href="water-device-list.html" class="link">设备详情</a></td>
<td>189</td>
<td>145</td><td>198</td><td>267</td><td>112</td><td>76</td><td>534</td><td>1332</td>
</tr>
<tr>
<td class="td-left">多参数水质分析仪-A03</td><td>WQ-2024-005</td><td>采油三厂</td><td>排污口监测点</td>
<td><a href="water-device-list.html" class="link">设备详情</a></td>
<td>234</td>
<td>256</td><td>312</td><td>345</td><td>189</td><td>123</td><td>278</td><td>1503</td>
</tr>
</tbody>
@@ -158,7 +158,6 @@
<div class="sidebar-menu__title">健康排查</div>
<a href="survey-manage.html" class="sidebar-item">问卷管理</a>
<a href="activity-manage.html" class="sidebar-item active">活动管理</a>
<a href="activity-stat.html" class="sidebar-item">活动统计</a>
</aside>
<main class="main">
<!-- 面包屑 -->
@@ -322,11 +321,6 @@ function toggleSubgroup(h){h.parentElement.classList.toggle('open');}
/* 根据来源页面切换侧边栏选中和面包屑 */
(function(){
var ref = document.referrer || '';
if(ref.indexOf('activity-stat.html') > -1){
document.getElementById('sidebarActManage').classList.remove('active');
document.getElementById('sidebarActStat').classList.add('active');
document.getElementById('breadcrumb').innerHTML = '<a href="activity-stat.html">活动统计</a><span>/</span><span>活动详情</span>';
}
})();
/* ===== 数据 ===== */
@@ -343,11 +337,11 @@ var surveyCrowdMap = {
var surveyCrowdColors = {'A类':'tag-blue','B类':'tag-green','C类':'tag-orange','D类':'tag-purple','E类':'tag-cyan'};
var activities = [
{id:1, name:'2026年上半年员工健康排查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-01-01', dateTo:'2026-03-31', personType:'全体人员', personCount:1280, status:'已完成', leader:'张明', phone:'13800001234', surveys:['健康记录问卷','职业健康排查问卷'], desc:'年度例行员工健康排查活动,覆盖全体员工。'},
{id:2, name:'2026年Q2心理健康专项排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-04-01', dateTo:'2026-06-30', personType:'指定人员', personCount:356, status:'进行中', leader:'李华', phone:'13800005678', surveys:['心理健康评估问卷'], desc:'针对C类和D类人群的心理健康专项排查。'},
{id:3, name:'2026年Q3职业健康排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-07-01', dateTo:'2026-09-30', personType:'指定人员', personCount:520, status:'待开始', leader:'王强', phone:'13800009012', surveys:['职业健康排查问卷','健康记录问卷'], desc:'第三季度职业健康专项排查,针对A类和B类人群。'},
{id:4, name:'新员工入职健康基线调查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-06-01', dateTo:'2026-12-31', personType:'指定人员', personCount:85, status:'进行中', leader:'赵芳', phone:'13800003456', surveys:['新员工健康基线问卷'], desc:'2026年度新入职员工健康基线数据采集。'},
{id:5, name:'慢病患者季度随访', org:'能源集团', dept:'工会', dateFrom:'2026-04-01', dateTo:'2026-06-30', personType:'指定人员', personCount:128, status:'进行中', leader:'孙丽', phone:'13800007890', surveys:['慢病管理随访问卷'], desc:'E类慢病患者第二季度定期随访。'}
{id:1, name:'2026年上半年员工健康排查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-01-01', dateTo:'2026-03-31', personType:'全体人员', personCount:1280, status:'已完成', leader:'张明', phone:'13800001234', surveys:['健康记录问卷','职业健康排查问卷'], desc:'年度例行员工健康排查活动,覆盖全体员工。', file:'2026上半年健康排查通知.pdf'},
{id:2, name:'2026年Q2心理健康专项排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-04-01', dateTo:'2026-06-30', personType:'指定人员', personCount:356, status:'进行中', leader:'李华', phone:'13800005678', surveys:['心理健康评估问卷'], desc:'针对C类和D类人群的心理健康专项排查。', file:'心理健康排查实施方案.pdf'},
{id:3, name:'2026年Q3职业健康排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-07-01', dateTo:'2026-09-30', personType:'指定人员', personCount:520, status:'待开始', leader:'王强', phone:'13800009012', surveys:['职业健康排查问卷','健康记录问卷'], desc:'第三季度职业健康专项排查,针对A类和B类人群。', file:''},
{id:4, name:'新员工入职健康基线调查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-06-01', dateTo:'2026-12-31', personType:'指定人员', personCount:85, status:'进行中', leader:'赵芳', phone:'13800003456', surveys:['新员工健康基线问卷'], desc:'2026年度新入职员工健康基线数据采集。', file:'新员工健康基线调查通知.pdf'},
{id:5, name:'慢病患者季度随访', org:'能源集团', dept:'工会', dateFrom:'2026-04-01', dateTo:'2026-06-30', personType:'指定人员', personCount:128, status:'进行中', leader:'孙丽', phone:'13800007890', surveys:['慢病管理随访问卷'], desc:'E类慢病患者第二季度定期随访。', file:'慢病随访安排表.xlsx'}
];
/* 参与人员模拟数据 */
@@ -551,6 +545,12 @@ var currentTab = 'done';
html += '<div class="info-item"><span class="info-item__label">负责人电话</span><span class="info-item__value">'+currentAct.phone+'</span></div>';
html += '<div class="info-item"><span class="info-item__label">活动说明</span><span class="info-item__value">'+esc(currentAct.desc||'—')+'</span></div>';
html += '<div class="info-item"><span class="info-item__label">选择问卷</span><span class="info-item__value">'+surveyNames+'</span></div>';
// 活动文件
var file = currentAct.file || '';
var fileHtml = file ? '<a class="link" href="javascript:void(0)" onclick="viewFile(\''+esc(file)+'\')" style="display:inline-flex;align-items:center;gap:4px;">'+
'<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 1h5l4 4v8H3V1z" stroke="currentColor" stroke-width="1.3"/><path d="M8 1v4h4" stroke="currentColor" stroke-width="1.3"/></svg>'+
esc(file)+'</a>' : '—';
html += '<div class="info-item info-item__half"><span class="info-item__label">活动文件</span><span class="info-item__value">'+fileHtml+'</span></div>';
document.getElementById('infoGrid').innerHTML = html;
/* 计算已参加/未参加 */
@@ -710,6 +710,11 @@ function closeModal(id){document.getElementById(id).classList.remove('show');}
function esc(s){return (s||'').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
/* ===== 查看活动文件 ===== */
function viewFile(fileName){
alert('查看文件:' + fileName + '\n(原型示意,实际项目中跳转至文件预览或下载)');
}
/* 点击遮罩关闭弹窗 */
document.querySelectorAll('.modal-mask').forEach(function(m){
m.addEventListener('click',function(e){if(e.target === m) closeModal(m.id);});
@@ -159,7 +159,28 @@
.emp-table__info{font-size:var(--font-xs);color:var(--text-secondary);}
.emp-table__info strong{color:var(--primary);}
/* 文件上传 */
/* 树形选择器(组织机构) */
.tree-select{position:relative;width:100%;}
.tree-select__trigger{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 32px 0 12px;font-size:var(--font-sm);background:#fff;cursor:pointer;display:flex;align-items:center;user-select:none;transition:border-color .2s;box-sizing:border-box;position:relative;}
.tree-select__trigger:hover{border-color:var(--primary);}
.tree-select__trigger::after{content:'';position:absolute;right:12px;top:50%;transform:translateY(-50%);border:4px solid transparent;border-top:5px solid #999;pointer-events:none;}
.tree-select__text{color:var(--text-placeholder);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.tree-select__text.has-value{color:var(--text-primary);}
.tree-select__panel{position:absolute;top:100%;left:0;right:0;margin-top:4px;background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:var(--shadow-md);z-index:500;display:none;max-height:320px;overflow-y:auto;}
.tree-select__panel.show{display:block;}
.tree-select__group{border-bottom:1px solid #F5F5F5;}
.tree-select__group:last-child{border-bottom:none;}
.tree-select__group-header{display:flex;align-items:center;gap:6px;padding:9px 16px;font-size:var(--font-sm);font-weight:500;color:var(--text-primary);cursor:pointer;transition:background .1s;user-select:none;}
.tree-select__group-header:hover{background:#F5F7FA;}
.tree-select__group-arrow{width:16px;height:16px;display:flex;align-items:center;justify-content:center;transition:transform .2s;flex-shrink:0;color:var(--text-placeholder);}
.tree-select__group-arrow.expanded{transform:rotate(90deg);}
.tree-select__group-label{flex:1;}
.tree-select__option{padding:8px 16px 8px 38px;font-size:var(--font-sm);cursor:pointer;transition:background .1s;display:none;color:var(--text-secondary);}
.tree-select__option.show{display:block;}
.tree-select__option:hover{background:#F5F7FA;}
.tree-select__option.selected{color:var(--primary);background:var(--primary-light);}
/* 文件上传 */
.upload-area{border:2px dashed var(--border);border-radius:var(--radius-md);padding:32px;text-align:center;cursor:pointer;transition:all .2s;background:#FAFAFA;}
.upload-area:hover{border-color:var(--primary);background:var(--primary-light);}
.upload-area svg{width:40px;height:40px;color:var(--text-placeholder);margin-bottom:8px;}
@@ -208,7 +229,6 @@
<div class="sidebar-menu__title">健康排查</div>
<a href="survey-manage.html" class="sidebar-item">问卷管理</a>
<a href="activity-manage.html" class="sidebar-item active">活动管理</a>
<a href="activity-stat.html" class="sidebar-item">活动统计</a>
</aside>
<main class="main">
<div class="form-card">
@@ -223,34 +243,96 @@
<input class="form-input" id="actName" placeholder="请输入活动名称">
</div>
<!-- 组织单位 & 组织部门 -->
<div class="form-row">
<div class="form-group">
<label>组织单位 <span class="req">*</span></label>
<select class="form-input" id="actOrg">
<option value="">请选择组织单位</option>
<option>第一采油厂</option>
<option>第二采油厂</option>
<option>第三采油厂</option>
<option>第四采油厂</option>
<option>第五采油厂</option>
<option>第六采油厂</option>
<option>采油厂</option>
<option>第八采油厂</option>
</select>
</div>
<div class="form-group">
<label>组织部门 <span class="req">*</span></label>
<select class="form-input" id="actDept">
<option value="">请选择组织部门</option>
<option>人力资源部</option>
<option>安全环保部</option>
<option>工会</option>
<option>生产技术部</option>
<option>财务部</option>
<option>信息中心</option>
<option>综合办公室</option>
</select>
<!-- 组织机构(树形联动选择器) -->
<div class="form-group">
<label>组织机构 <span class="req">*</span></label>
<div class="tree-select" id="orgTreeSelect">
<input type="hidden" id="actOrgValue" value="">
<div class="tree-select__trigger" onclick="toggleOrgTree()">
<span class="tree-select__text" id="orgTreeText">请选择组织机构</span>
</div>
<div class="tree-select__panel" id="orgTreePanel">
<div class="tree-select__group" data-unit="第一采油厂">
<div class="tree-select__group-header" onclick="toggleOrgGroup(this, event)">
<span class="tree-select__group-arrow"></span>
<span class="tree-select__group-label">采油厂</span>
</div>
<div class="tree-select__option" data-val="第一采油厂|人力资源部" onclick="selectOrgOption(this,'第一采油厂 / 人力资源部')">人力资源部</div>
<div class="tree-select__option" data-val="第一采油厂|安全环保部" onclick="selectOrgOption(this,'第一采油厂 / 安全环保部')">安全环保部</div>
<div class="tree-select__option" data-val="第一采油厂|生产技术部" onclick="selectOrgOption(this,'第一采油厂 / 生产技术部')">生产技术部</div>
<div class="tree-select__option" data-val="第一采油厂|综合办公室" onclick="selectOrgOption(this,'第一采油厂 / 综合办公室')">综合办公室</div>
</div>
<div class="tree-select__group" data-unit="第二采油厂">
<div class="tree-select__group-header" onclick="toggleOrgGroup(this, event)">
<span class="tree-select__group-arrow"></span>
<span class="tree-select__group-label">第二采油厂</span>
</div>
<div class="tree-select__option" data-val="第二采油厂|工会" onclick="selectOrgOption(this,'第二采油厂 / 工会')">工会</div>
<div class="tree-select__option" data-val="第二采油厂|生产技术部" onclick="selectOrgOption(this,'第二采油厂 / 生产技术部')">生产技术部</div>
<div class="tree-select__option" data-val="第二采油厂|财务部" onclick="selectOrgOption(this,'第二采油厂 / 财务部')">财务部</div>
<div class="tree-select__option" data-val="第二采油厂|信息中心" onclick="selectOrgOption(this,'第二采油厂 / 信息中心')">信息中心</div>
</div>
<div class="tree-select__group" data-unit="第三采油厂">
<div class="tree-select__group-header" onclick="toggleOrgGroup(this, event)">
<span class="tree-select__group-arrow"></span>
<span class="tree-select__group-label">第三采油厂</span>
</div>
<div class="tree-select__option" data-val="第三采油厂|信息中心" onclick="selectOrgOption(this,'第三采油厂 / 信息中心')">信息中心</div>
<div class="tree-select__option" data-val="第三采油厂|综合办公室" onclick="selectOrgOption(this,'第三采油厂 / 综合办公室')">综合办公室</div>
<div class="tree-select__option" data-val="第三采油厂|人力资源部" onclick="selectOrgOption(this,'第三采油厂 / 人力资源部')">人力资源部</div>
<div class="tree-select__option" data-val="第三采油厂|安全环保部" onclick="selectOrgOption(this,'第三采油厂 / 安全环保部')">安全环保部</div>
</div>
<div class="tree-select__group" data-unit="第四采油厂">
<div class="tree-select__group-header" onclick="toggleOrgGroup(this, event)">
<span class="tree-select__group-arrow"></span>
<span class="tree-select__group-label">第四采油厂</span>
</div>
<div class="tree-select__option" data-val="第四采油厂|安全环保部" onclick="selectOrgOption(this,'第四采油厂 / 安全环保部')">安全环保部</div>
<div class="tree-select__option" data-val="第四采油厂|生产技术部" onclick="selectOrgOption(this,'第四采油厂 / 生产技术部')">生产技术部</div>
<div class="tree-select__option" data-val="第四采油厂|工会" onclick="selectOrgOption(this,'第四采油厂 / 工会')">工会</div>
<div class="tree-select__option" data-val="第四采油厂|财务部" onclick="selectOrgOption(this,'第四采油厂 / 财务部')">财务部</div>
</div>
<div class="tree-select__group" data-unit="第五采油厂">
<div class="tree-select__group-header" onclick="toggleOrgGroup(this, event)">
<span class="tree-select__group-arrow"></span>
<span class="tree-select__group-label">第五采油厂</span>
</div>
<div class="tree-select__option" data-val="第五采油厂|工会" onclick="selectOrgOption(this,'第五采油厂 / 工会')">工会</div>
<div class="tree-select__option" data-val="第五采油厂|财务部" onclick="selectOrgOption(this,'第五采油厂 / 财务部')">财务部</div>
<div class="tree-select__option" data-val="第五采油厂|人力资源部" onclick="selectOrgOption(this,'第五采油厂 / 人力资源部')">人力资源部</div>
<div class="tree-select__option" data-val="第五采油厂|生产技术部" onclick="selectOrgOption(this,'第五采油厂 / 生产技术部')">生产技术部</div>
</div>
<div class="tree-select__group" data-unit="第六采油厂">
<div class="tree-select__group-header" onclick="toggleOrgGroup(this, event)">
<span class="tree-select__group-arrow"></span>
<span class="tree-select__group-label">第六采油厂</span>
</div>
<div class="tree-select__option" data-val="第六采油厂|信息中心" onclick="selectOrgOption(this,'第六采油厂 / 信息中心')">信息中心</div>
<div class="tree-select__option" data-val="第六采油厂|综合办公室" onclick="selectOrgOption(this,'第六采油厂 / 综合办公室')">综合办公室</div>
<div class="tree-select__option" data-val="第六采油厂|安全环保部" onclick="selectOrgOption(this,'第六采油厂 / 安全环保部')">安全环保部</div>
<div class="tree-select__option" data-val="第六采油厂|生产技术部" onclick="selectOrgOption(this,'第六采油厂 / 生产技术部')">生产技术部</div>
</div>
<div class="tree-select__group" data-unit="第七采油厂">
<div class="tree-select__group-header" onclick="toggleOrgGroup(this, event)">
<span class="tree-select__group-arrow"></span>
<span class="tree-select__group-label">第七采油厂</span>
</div>
<div class="tree-select__option" data-val="第七采油厂|人力资源部" onclick="selectOrgOption(this,'第七采油厂 / 人力资源部')">人力资源部</div>
<div class="tree-select__option" data-val="第七采油厂|安全环保部" onclick="selectOrgOption(this,'第七采油厂 / 安全环保部')">安全环保部</div>
<div class="tree-select__option" data-val="第七采油厂|工会" onclick="selectOrgOption(this,'第七采油厂 / 工会')">工会</div>
<div class="tree-select__option" data-val="第七采油厂|信息中心" onclick="selectOrgOption(this,'第七采油厂 / 信息中心')">信息中心</div>
</div>
<div class="tree-select__group" data-unit="第八采油厂">
<div class="tree-select__group-header" onclick="toggleOrgGroup(this, event)">
<span class="tree-select__group-arrow"></span>
<span class="tree-select__group-label">第八采油厂</span>
</div>
<div class="tree-select__option" data-val="第八采油厂|生产技术部" onclick="selectOrgOption(this,'第八采油厂 / 生产技术部')">生产技术部</div>
<div class="tree-select__option" data-val="第八采油厂|工会" onclick="selectOrgOption(this,'第八采油厂 / 工会')">工会</div>
<div class="tree-select__option" data-val="第八采油厂|财务部" onclick="selectOrgOption(this,'第八采油厂 / 财务部')">财务部</div>
<div class="tree-select__option" data-val="第八采油厂|综合办公室" onclick="selectOrgOption(this,'第八采油厂 / 综合办公室')">综合办公室</div>
</div>
</div>
</div>
</div>
@@ -322,6 +404,21 @@
</div>
</div>
<!-- 负责部门 -->
<div class="form-group">
<label>负责部门 <span class="req">*</span></label>
<select class="form-input" id="responsibleDept">
<option value="">请选择负责部门</option>
<option>人力资源部</option>
<option>安全环保部</option>
<option>工会</option>
<option>生产技术部</option>
<option>财务部</option>
<option>信息中心</option>
<option>综合办公室</option>
</select>
</div>
<!-- 负责人 -->
<div class="form-group">
<label>负责人 <span class="req">*</span></label>
@@ -350,9 +447,9 @@
<div class="upload-area" id="uploadArea" onclick="document.getElementById('fileInput').click()">
<svg viewBox="0 0 40 40" fill="none"><path d="M20 8v24M12 16l8-8 8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 28v4a2 2 0 002 2h20a2 2 0 002-2v-4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<div class="upload-area__text">点击或拖拽文件到此区域上传</div>
<div class="upload-area__hint">支持 PDF、Word、Excel、图片等格式,单个文件不超过 10MB</div>
<div class="upload-area__hint">支持 PDF、Word、Excel、图片等格式,文件不超过 10MB</div>
</div>
<input type="file" id="fileInput" style="display:none" multiple onchange="handleFiles(this.files)">
<input type="file" id="fileInput" style="display:none" onchange="handleFile(this.files)">
<div class="file-list" id="fileList"></div>
</div>
</div>
@@ -450,6 +547,114 @@
function toggleSubgroup(h){h.parentElement.classList.toggle('open');}
(function(){var a=document.querySelector('.sidebar-item.active');if(!a)return;var s=a.closest('.sidebar-subgroup');if(s)s.classList.add('open');})();
/* ===== 组织机构树形选择器 ===== */
// 从组织机构值中提取单位名称(兼容"单位|部门"格式)
function getOrgUnit(val){
var idx = val.indexOf('|');
return idx > -1 ? val.substring(0, idx) : val;
}
// 从组织机构值中提取部门名称(兼容"单位|部门"格式)
function getOrgDept(val){
var idx = val.indexOf('|');
return idx > -1 ? val.substring(idx + 1) : '';
}
// 点击触发器展开/收起下拉面板
function toggleOrgTree(){
var panel = document.getElementById('orgTreePanel');
var isShow = panel.classList.contains('show');
closeAllPanels();
if(!isShow) panel.classList.add('show');
}
// 点击单位标题展开/收起部门列表,或选中该单位
function toggleOrgGroup(headerEl, event){
event.stopPropagation();
var arrow = headerEl.querySelector('.tree-select__group-arrow');
var group = headerEl.parentElement;
var options = group.querySelectorAll('.tree-select__option');
var isExpanded = arrow.classList.contains('expanded');
if(isExpanded){
// 收起:隐藏部门选项,并选中该单位
arrow.classList.remove('expanded');
options.forEach(function(opt){ opt.classList.remove('show'); });
var unitName = group.getAttribute('data-unit');
selectOrgOptionInner(unitName, unitName);
} else {
// 收起其他已展开的分组
document.querySelectorAll('.tree-select__group-arrow.expanded').forEach(function(a){
a.classList.remove('expanded');
});
document.querySelectorAll('.tree-select__option.show').forEach(function(o){
o.classList.remove('show');
});
// 展开当前分组
arrow.classList.add('expanded');
options.forEach(function(opt){ opt.classList.add('show'); });
}
}
// 点击部门选项选中
function selectOrgOption(el, displayText){
var val = el.getAttribute('data-val');
selectOrgOptionInner(val, displayText);
}
// 内部选中逻辑
function selectOrgOptionInner(val, displayText){
var hidden = document.getElementById('actOrgValue');
var oldVal = hidden.value;
hidden.value = val;
// 更新触发器显示
var textEl = document.getElementById('orgTreeText');
textEl.textContent = displayText;
textEl.className = 'tree-select__text has-value';
// 更新面板选中态
document.querySelectorAll('#orgTreePanel .tree-select__option.selected').forEach(function(o){
o.classList.remove('selected');
});
var targetOpt = document.querySelector('#orgTreePanel .tree-select__option[data-val="' + val + '"]');
if(targetOpt){ targetOpt.classList.add('selected'); }
// 关闭面板
document.getElementById('orgTreePanel').classList.remove('show');
// 触发change事件
if(oldVal !== val){
updateOrgDependent();
}
}
// 监听组织机构变化,同步更新部门和员工选择器状态
function updateOrgDependent(){
var orgVal = document.getElementById('actOrgValue').value;
var orgUnit = getOrgUnit(orgVal);
var deptTrigger = document.getElementById('deptTrigger');
if(orgUnit){
deptTrigger.classList.remove('disabled');
if(selectedDepts.length===0){
deptTrigger.textContent = '请选择部门(可多选)';
}
} else {
deptTrigger.classList.add('disabled');
deptTrigger.textContent = '请先选择组织机构';
selectedDepts = [];
updateDeptTrigger();
}
var empTrigger = document.getElementById('empTrigger');
var text = document.getElementById('empTriggerText');
if(orgUnit){
empTrigger.classList.remove('disabled');
text.className = 'emp-trigger__text';
text.textContent = '点击选择员工';
} else {
empTrigger.classList.add('disabled');
text.className = 'emp-trigger__text';
text.textContent = '请先选择组织机构';
selectedEmployees = [];
renderSelectedPersons();
}
}
/* ===== 问卷多选 ===== */
var selectedSurveys = [];
@@ -498,9 +703,9 @@ function removeSurveyTag(val){
var selectedDepts = [];
function toggleDeptPanel(){
var org = document.getElementById('actOrg').value;
var org = document.getElementById('actOrgValue').value;
if(!org){
showToast('请先选择组织单位');
showToast('请先选择组织机构');
return;
}
var panel = document.getElementById('deptPanel');
@@ -579,71 +784,20 @@ var mockEmployees = [
{org:'第八采油厂',dept:'工会',name:'罗琳',id:'015123',gender:'女',age:32,crowd:'B类'},
];
// 监听组织单位变化,同步更新部门和员工选择器状态
document.getElementById('actOrg').addEventListener('change', updateOrgDependent);
function updateOrgDependent(){
var org = document.getElementById('actOrg').value;
// 更新部门选择器状态
var deptTrigger = document.getElementById('deptTrigger');
if(org){
deptTrigger.classList.remove('disabled');
if(selectedDepts.length === 0){
deptTrigger.textContent = '请选择部门(可多选)';
}
} else {
deptTrigger.classList.add('disabled');
deptTrigger.textContent = '请先选择组织单位';
// 清空已选部门
selectedDepts = [];
updateDeptTrigger();
}
// 更新员工选择器状态
var empTrigger = document.getElementById('empTrigger');
var text = document.getElementById('empTriggerText');
if(org){
empTrigger.classList.remove('disabled');
text.className = 'emp-trigger__text';
text.textContent = '点击选择员工';
} else {
empTrigger.classList.add('disabled');
text.className = 'emp-trigger__text';
text.textContent = '请先选择组织单位';
// 清空已选员工
selectedEmployees = [];
renderSelectedPersons();
}
}
// 打开员工弹窗
function openEmpModal(){
var org = document.getElementById('actOrg').value;
if(!org){
showToast('请先选择组织单位');
return;
}
// 初始化临时选中列表为当前已选
tempSelectedEmps = selectedEmployees.slice();
renderEmpTable();
document.getElementById('empModal').classList.add('show');
}
// 关闭员工弹窗
function closeEmpModal(){
document.getElementById('empModal').classList.remove('show');
}
// 渲染员工表格
function renderEmpTable(){
var org = document.getElementById('actOrg').value;
var orgVal = document.getElementById('actOrgValue').value;
var orgUnit = getOrgUnit(orgVal);
var orgDept = getOrgDept(orgVal);
var filterDept = document.getElementById('empFilterDept').value;
var filterName = document.getElementById('empFilterName').value.trim().toLowerCase();
var filterId = document.getElementById('empFilterId').value.trim().toLowerCase();
var filterCrowd = document.getElementById('empFilterCrowd').value;
// 根据组织单位筛选
// 根据组织机构筛选
var filtered = mockEmployees.filter(function(e){
if(e.org !== org) return false;
if(e.org !== orgUnit) return false;
if(orgDept && e.dept !== orgDept) return false;
if(filterDept && e.dept !== filterDept) return false;
if(filterName && e.name.indexOf(filterName) === -1) return false;
if(filterId && e.id.toLowerCase().indexOf(filterId) === -1) return false;
@@ -814,36 +968,37 @@ function removeEmp(empId){
renderSelectedPersons();
}
/* ===== 文件上传 ===== */
var uploadedFiles = [];
/* ===== 文件上传(仅支持单个文件) ===== */
var uploadedFile = null;
function handleFiles(files){
for(var i=0;i<files.length;i++){
var f = files[i];
if(f.size > 10*1024*1024){alert('文件「'+f.name+'」超过10MB限制');continue;}
uploadedFiles.push({name:f.name, size:f.size});
}
function handleFile(files){
if(!files || files.length === 0) return;
var f = files[0];
if(f.size > 10*1024*1024){alert('文件「'+f.name+'」超过10MB限制');return;}
uploadedFile = {name:f.name, size:f.size};
renderFileList();
document.getElementById('fileInput').value = '';
}
function renderFileList(){
var container = document.getElementById('fileList');
var html = '';
uploadedFiles.forEach(function(f,i){
var sizeStr = f.size > 1024*1024 ? (f.size/(1024*1024)).toFixed(1)+' MB' : (f.size/1024).toFixed(0)+' KB';
html += '<div class="file-item">';
html += '<div class="file-item__icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 1h6l4 4v10H3V1z" stroke="#1890FF" stroke-width="1.3"/><path d="M9 1v4h4" stroke="#1890FF" stroke-width="1.3"/></svg></div>';
html += '<span class="file-item__name">'+esc(f.name)+'</span>';
html += '<span class="file-item__size">'+sizeStr+'</span>';
html += '<button class="file-item__del" onclick="removeFile('+i+')" title="删除"><svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M3 3l6 6M9 3l-6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>';
html += '</div>';
});
if(!uploadedFile){
container.innerHTML = '';
return;
}
var f = uploadedFile;
var sizeStr = f.size > 1024*1024 ? (f.size/(1024*1024)).toFixed(1)+' MB' : (f.size/1024).toFixed(0)+' KB';
var html = '<div class="file-item">';
html += '<div class="file-item__icon"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 1h6l4 4v10H3V1z" stroke="#1890FF" stroke-width="1.3"/><path d="M9 1v4h4" stroke="#1890FF" stroke-width="1.3"/></svg></div>';
html += '<span class="file-item__name">'+esc(f.name)+'</span>';
html += '<span class="file-item__size">'+sizeStr+'</span>';
html += '<button class="file-item__del" onclick="removeFile()" title="删除"><svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M3 3l6 6M9 3l-6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>';
html += '</div>';
container.innerHTML = html;
}
function removeFile(idx){
uploadedFiles.splice(idx, 1);
function removeFile(){
uploadedFile = null;
renderFileList();
}
@@ -854,7 +1009,7 @@ function removeFile(idx){
area.addEventListener('dragleave', function(){area.style.borderColor='';area.style.background='';});
area.addEventListener('drop', function(e){
e.preventDefault();area.style.borderColor='';area.style.background='';
handleFiles(e.dataTransfer.files);
handleFile(e.dataTransfer.files);
});
})();
@@ -863,6 +1018,7 @@ function closeAllPanels(){
document.getElementById('surveyPanel').classList.remove('show');
document.getElementById('deptPanel').classList.remove('show');
document.getElementById('leaderPanel').classList.remove('show');
document.getElementById('orgTreePanel').classList.remove('show');
}
/* ===== 保存 ===== */
@@ -877,9 +1033,10 @@ function saveActivity(){
var dateFrom = document.getElementById('dateFrom').value;
var dateTo = document.getElementById('dateTo').value;
if(!dateFrom||!dateTo){alert('请选择起止日期');return;}
var org = document.getElementById('actOrg').value.trim();
var dept = document.getElementById('actDept').value.trim();
if(!org||!dept){alert('请输入组织单位和组织部门');return;}
var org = document.getElementById('actOrgValue').value;
var responsibleDept = document.getElementById('responsibleDept').value;
if(!org){alert('请选择组织机构');return;}
if(!responsibleDept){alert('请选择负责部门');return;}
if(!selectedLeader){alert('请选择负责人');return;}
showToast('保存成功!');
setTimeout(function(){window.location.href='activity-manage.html';},1000);
@@ -899,6 +1056,7 @@ document.addEventListener('click',function(e){
if(!e.target.closest('#surveySelect')) document.getElementById('surveyPanel').classList.remove('show');
if(!e.target.closest('#deptSelect')) document.getElementById('deptPanel').classList.remove('show');
if(!e.target.closest('#leaderSelect')) document.getElementById('leaderPanel').classList.remove('show');
if(!e.target.closest('#orgTreeSelect')) document.getElementById('orgTreePanel').classList.remove('show');
});
// 点击弹窗遮罩关闭
@@ -133,7 +133,6 @@
<div class="sidebar-menu__title">健康排查</div>
<a href="survey-manage.html" class="sidebar-item">问卷管理</a>
<a href="activity-manage.html" class="sidebar-item active">活动管理</a>
<a href="activity-stat.html" class="sidebar-item">活动统计</a>
</aside>
<main class="main">
<!-- 筛选栏 -->
@@ -193,17 +192,20 @@
</div>
<table>
<thead>
<tr>
<th style="width:18%;">活动名称</th>
<th style="width:10%;">组织单位</th>
<th style="width:10%;">组织部门</th>
<th style="width:12%;">起止日期</th>
<th style="width:10%;">人员筛选</th>
<th style="width:8%;">活动状态</th>
<th style="width:8%;">负责人</th>
<th style="width:10%;">负责人电话</th>
<th style="width:14%;">操作</th>
</tr>
<tr>
<th style="width:14%;">活动名称</th>
<th style="width:8%;">组织单位</th>
<th style="width:8%;">组织部门</th>
<th style="width:10%;">起止日期</th>
<th style="width:8%;">人员筛选</th>
<th style="width:8%;">应参加人数</th>
<th style="width:8%;">已参加人数</th>
<th style="width:8%;">未参加人数</th>
<th style="width:6%;">活动状态</th>
<th style="width:6%;">负责人</th>
<th style="width:8%;">负责人电话</th>
<th style="width:8%;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
@@ -298,13 +300,13 @@ function toggleSubgroup(h){h.parentElement.classList.toggle('open');}
/* ===== 数据 ===== */
var statusMap = {'待开始':'tag-blue','进行中':'tag-orange','已完成':'tag-green'};
var activities = [
{id:1, name:'2026年上半年员工健康排查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-01-01', dateTo:'2026-03-31', personType:'全体人员', personCount:1280, status:'已完成', leader:'张明', phone:'13800001234', surveys:['健康记录问卷','职业健康排查问卷'], desc:'年度例行员工健康排查活动,覆盖全体员工。'},
{id:2, name:'2026年Q2心理健康专项排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-04-01', dateTo:'2026-06-30', personType:'指定人员', personCount:356, status:'进行中', leader:'李华', phone:'13800005678', surveys:['心理健康评估问卷'], desc:'针对C类和D类人群的心理健康专项排查。'},
{id:3, name:'2026年Q3职业健康排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-07-01', dateTo:'2026-09-30', personType:'指定人员', personCount:520, status:'待开始', leader:'王强', phone:'13800009012', surveys:['职业健康排查问卷','健康记录问卷'], desc:'第三季度职业健康专项排查,针对A类和B类人群。'},
{id:4, name:'新员工入职健康基线调查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-06-01', dateTo:'2026-12-31', personType:'指定人员', personCount:85, status:'进行中', leader:'赵芳', phone:'13800003456', surveys:['新员工健康基线问卷'], desc:'2026年度新入职员工健康基线数据采集。'},
{id:5, name:'慢病患者季度随访', org:'能源集团', dept:'工会', dateFrom:'2026-04-01', dateTo:'2026-06-30', personType:'指定人员', personCount:128, status:'进行中', leader:'孙丽', phone:'13800007890', surveys:['慢病管理随访问卷'], desc:'E类慢病患者第二季度定期随访。'}
];
var activities = [
{id:1, name:'2026年上半年员工健康排查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-01-01', dateTo:'2026-03-31', personType:'全体人员', personCount:1280, doneCount:1150, status:'已完成', leader:'张明', phone:'13800001234', surveys:['健康记录问卷','职业健康排查问卷'], desc:'年度例行员工健康排查活动,覆盖全体员工。'},
{id:2, name:'2026年Q2心理健康专项排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-04-01', dateTo:'2026-06-30', personType:'指定人员', personCount:356, doneCount:280, status:'进行中', leader:'李华', phone:'13800005678', surveys:['心理健康评估问卷'], desc:'针对C类和D类人群的心理健康专项排查。'},
{id:3, name:'2026年Q3职业健康排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-07-01', dateTo:'2026-09-30', personType:'指定人员', personCount:520, doneCount:0, status:'待开始', leader:'王强', phone:'13800009012', surveys:['职业健康排查问卷','健康记录问卷'], desc:'第三季度职业健康专项排查,针对A类和B类人群。'},
{id:4, name:'新员工入职健康基线调查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-06-01', dateTo:'2026-12-31', personType:'指定人员', personCount:85, doneCount:60, status:'进行中', leader:'赵芳', phone:'13800003456', surveys:['新员工健康基线问卷'], desc:'2026年度新入职员工健康基线数据采集。'},
{id:5, name:'慢病患者季度随访', org:'能源集团', dept:'工会', dateFrom:'2026-04-01', dateTo:'2026-06-30', personType:'指定人员', personCount:128, doneCount:98, status:'进行中', leader:'孙丽', phone:'13800007890', surveys:['慢病管理随访问卷'], desc:'E类慢病患者第二季度定期随访。'}
];
/* 参与人员模拟数据 */
var allPersons = [
@@ -331,30 +333,34 @@ var allPersons = [
];
/* ===== 渲染表格 ===== */
function renderTable(data){
var html = '';
data.forEach(function(a){
html += '<tr>';
html += '<td>'+esc(a.name)+'</td>';
html += '<td>'+esc(a.org)+'</td>';
html += '<td>'+esc(a.dept)+'</td>';
html += '<td>'+a.dateFrom+' ~ '+a.dateTo+'</td>';
html += '<td><span class="link link-clickable" onclick="openPersonModal('+a.id+')">'+a.personCount+'人</span></td>';
html += '<td><span class="tag '+statusMap[a.status]+'">'+a.status+'</span></td>';
html += '<td>'+esc(a.leader)+'</td>';
html += '<td>'+a.phone+'</td>';
html += '<td>';
html += '<button class="link" onclick="openDetail('+a.id+')">活动详情</button>';
html += '<span class="link-separator">|</span>';
html += '<button class="link" onclick="openEdit('+a.id+')">编辑</button>';
html += '<span class="link-separator">|</span>';
html += '<button class="link link-danger" onclick="deleteActivity('+a.id+')">删除</button>';
html += '</td></tr>';
});
document.getElementById('tbody').innerHTML = html || '<tr><td colspan="9" style="text-align:center;padding:40px;color:var(--text-placeholder);">暂无数据</td></tr>';
document.getElementById('totalCount').textContent = '共 '+data.length+' 条数据';
document.getElementById('pageTotal').textContent = '共 '+data.length+' 条数据';
}
function renderTable(data){
var html = '';
data.forEach(function(a){
var pending = a.personCount - (a.doneCount||0);
html += '<tr>';
html += '<td>'+esc(a.name)+'</td>';
html += '<td>'+esc(a.org)+'</td>';
html += '<td>'+esc(a.dept)+'</td>';
html += '<td>'+a.dateFrom+' ~ '+a.dateTo+'</td>';
html += '<td><span class="link link-clickable" onclick="openPersonModal('+a.id+')">'+esc(a.personType)+'</span></td>';
html += '<td>'+a.personCount+'</td>';
html += '<td style="color:var(--success);">'+(a.doneCount||0)+'</td>';
html += '<td style="color:var(--danger);">'+pending+'</td>';
html += '<td><span class="tag '+statusMap[a.status]+'">'+a.status+'</span></td>';
html += '<td>'+esc(a.leader)+'</td>';
html += '<td>'+a.phone+'</td>';
html += '<td>';
html += '<button class="link" onclick="openDetail('+a.id+')">活动详情</button>';
html += '<span class="link-separator">|</span>';
html += '<button class="link" onclick="openEdit('+a.id+')">编辑</button>';
html += '<span class="link-separator">|</span>';
html += '<button class="link link-danger" onclick="deleteActivity('+a.id+')">删除</button>';
html += '</td></tr>';
});
document.getElementById('tbody').innerHTML = html || '<tr><td colspan="12" style="text-align:center;padding:40px;color:var(--text-placeholder);">暂无数据</td></tr>';
document.getElementById('totalCount').textContent = '共 '+data.length+' 条数据';
document.getElementById('pageTotal').textContent = '共 '+data.length+' 条数据';
}
/* ===== 筛选 ===== */
function filterTable(){
@@ -1,317 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康监测 - 活动统计 | 健康CQ原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F7FA;color:#333;min-width:1280px;}
:root{
--primary:#1890FF;--primary-light:#E6F7FF;--success:#52C41A;--warning:#FAAD14;--danger:#FF4D4F;
--text-primary:#333;--text-secondary:#666;--text-placeholder:#999;--border:#E8E8E8;--background:#F5F7FA;
--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;
--radius-sm:4px;--radius-md:8px;--radius-lg:12px;
--shadow-sm:0 1px 4px rgba(0,0,0,0.08);
}
.top-bar{display:flex;align-items:center;height:56px;background:#001529;padding:0 24px;position:fixed;top:0;left:0;right:0;z-index:100;}
.top-bar__logo{display:flex;align-items:center;gap:8px;color:#fff;font-size:var(--font-lg);font-weight:600;white-space:nowrap;width:240px;flex-shrink:0;}
.top-bar__logo svg{width:28px;height:28px;}
.top-bar__right{display:flex;align-items:center;gap:16px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);}
.btn-back{color:rgba(255,255,255,0.65);cursor:pointer;display:flex;align-items:center;gap:4px;text-decoration:none;}
.btn-back:hover{color:#fff;}
.avatar{width:32px;height:32px;border-radius:50%;background:#1890FF;display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-sm);cursor:pointer;}
.top-bar__nav{display:flex;align-items:center;gap:4px;flex:1;}
.top-bar__nav a{color:rgba(255,255,255,0.65);text-decoration:none;padding:6px 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);transition:all .2s;}
.top-bar__nav a:hover{color:#fff;background:rgba(255,255,255,0.08);}
.top-bar__nav a.active{color:#fff;background:var(--primary);}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-item.active{color:#fff;background:var(--primary);}
.sidebar-subgroup__header{display:flex;align-items:center;gap:6px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);background:rgba(255,255,255,0.04);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;transition:transform .2s;flex-shrink:0;}
.sidebar-subgroup__header .arrow path{stroke:rgba(255,255,255,0.45);}
.sidebar-subgroup.open>.sidebar-subgroup__header{color:rgba(255,255,255,0.85);}
.sidebar-subgroup.open>.sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;padding-left:16px;}
.sidebar-subgroup.open>.sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:36px;font-size:12px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 筛选栏 */
.filter-bar{display:flex;align-items:center;gap:16px;margin-bottom:16px;flex-wrap:wrap;background:#fff;padding:16px 24px;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item__label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;min-width:180px;}
.filter-input:focus{border-color:var(--primary);}
/* 多选下拉 */
.multi-select{position:relative;display:inline-block;min-width:200px;}
.multi-select__trigger{min-height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 32px 4px 8px;font-size:var(--font-sm);background:#fff;cursor:pointer;display:flex;align-items:center;gap:4px;flex-wrap:wrap;user-select:none;position:relative;}
.multi-select__trigger::after{content:'';position:absolute;right:12px;top:50%;transform:translateY(-50%);border:4px solid transparent;border-top:5px solid #999;pointer-events:none;}
.multi-select__trigger.placeholder{padding:0 32px 0 12px;color:var(--text-placeholder);align-items:center;}
.multi-select__trigger .sel-tag{display:inline-flex;align-items:center;gap:2px;background:var(--primary-light);color:var(--primary);padding:1px 6px;border-radius:var(--radius-sm);font-size:var(--font-xs);line-height:1.6;}
.multi-select__trigger .sel-tag .tag-x{cursor:pointer;font-size:12px;line-height:1;margin-left:2px;}
.multi-select__trigger .sel-tag .tag-x:hover{color:var(--danger);}
.multi-select__panel{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:0 4px 12px rgba(0,0,0,0.12);z-index:300;display:none;min-width:200px;padding:8px 0;}
.multi-select__panel.show{display:block;}
.multi-select__opt{display:flex;align-items:center;gap:8px;padding:8px 16px;font-size:var(--font-sm);cursor:pointer;transition:background .1s;}
.multi-select__opt:hover{background:#F5F7FA;}
.multi-select__opt.checked{color:var(--primary);background:var(--primary-light);}
.multi-select__cb{width:14px;height:14px;border:1px solid var(--border);border-radius:3px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .15s;}
.multi-select__cb.checked{background:var(--primary);border-color:var(--primary);}
.multi-select__cb.checked::after{content:'';width:4px;height:8px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg);margin-top:-2px;}
/* 工具栏 */
.toolbar{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap;}
.toolbar .spacer{flex:1;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:1px solid var(--border);background:#fff;color:var(--text-primary);transition:all .15s;display:inline-flex;align-items:center;gap:6px;}
.btn:hover{color:var(--primary);border-color:var(--primary);}
.btn-primary{background:var(--primary);color:#fff;border-color:var(--primary);}
.btn-primary:hover{background:#40a9ff;border-color:#40a9ff;color:#fff;}
/* 表格 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-base);font-weight:600;}
table{width:100%;border-collapse:collapse;}
thead th{background:#FAFAFA;padding:12px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);}
tbody td{padding:14px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);}
tbody tr:last-child td{border-bottom:none;}
tbody tr:hover{background:#F8FBFF;}
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-orange{background:#FFF7E6;color:#FA8C16;}
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);text-decoration:none;background:none;border:none;}
.link:hover{color:#40a9ff;}
.link-separator{color:var(--border);margin:0 6px;user-select:none;}
/* 分页 */
.pagination{display:flex;align-items:center;justify-content:flex-end;gap:16px;padding:16px 24px;font-size:var(--font-sm);color:var(--text-secondary);}
.pagination__pages{display:flex;align-items:center;gap:4px;}
.page-item{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);cursor:pointer;transition:all .15s;}
.page-item:hover{color:var(--primary);}
.page-item.active{background:var(--primary);color:#fff;}
</style>
</head>
<body>
<div class="top-bar">
<div class="top-bar__logo"><svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M7 14h14M14 7v14" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg> 健康监测</div>
<nav class="top-bar__nav">
<a href="../archive-personal.html">档案</a>
<a href="../crowd-status-employee.html">健康现状</a>
<a href="survey-manage.html" class="active">健康排查</a>
<a href="../wearable/tool-manage.html">穿戴监测</a>
<a href="../environment/indoor-device-list.html">健康环境</a>
<a href="../exception-dirty.html">异常事件</a>
</nav>
<div class="top-bar__right">
<a class="btn-back" href="../../../index.html">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
返回首页
</a>
<div class="avatar"></div>
<span style="color:rgba(255,255,255,0.65);">系统管理员 ▾</span>
</div>
</div>
<aside class="sidebar">
<div class="sidebar-menu__title">健康排查</div>
<a href="survey-manage.html" class="sidebar-item">问卷管理</a>
<a href="activity-manage.html" class="sidebar-item">活动管理</a>
<a href="activity-stat.html" class="sidebar-item active">活动统计</a>
</aside>
<main class="main">
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<span class="filter-item__label">活动名称</span>
<input class="filter-input" id="filterName" placeholder="请输入活动名称">
</div>
<div class="filter-item">
<span class="filter-item__label">组织单位</span>
<div class="multi-select" id="orgSelect">
<div class="multi-select__trigger placeholder" onclick="toggleOrgSelect()">全部</div>
<div class="multi-select__panel" id="orgPanel">
<div class="multi-select__opt" data-val="全部" onclick="toggleOrgOpt(this,'全部')"><span class="multi-select__cb"></span>全部</div>
<div class="multi-select__opt" data-val="能源集团" onclick="toggleOrgOpt(this,'能源集团')"><span class="multi-select__cb"></span>能源集团</div>
</div>
</div>
</div>
<button class="btn btn-primary" onclick="filterTable()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.5"/><path d="M10.5 10.5l2 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
查询
</button>
<button class="btn" onclick="resetFilter()">重置</button>
</div>
<!-- 操作栏 -->
<div class="toolbar">
<button class="btn" onclick="alert('导出列表数据')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1v8M4 6l3 3 3-3M2 11h10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
导出列表数据
</button>
<button class="btn" onclick="alert('查看导出任务')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.3"/><path d="M7 4v3.5l2.5 1.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
查看导出任务
</button>
</div>
<!-- 表格 -->
<div class="table-card">
<div class="table-card__header">
<span class="table-card__title">活动统计</span>
<span style="font-size:var(--font-sm);color:var(--text-secondary);" id="totalCount">共 0 条数据</span>
</div>
<table>
<thead>
<tr>
<th style="width:18%;">活动名称</th>
<th style="width:10%;">组织单位</th>
<th style="width:10%;">组织部门</th>
<th style="width:12%;">起止日期</th>
<th style="width:8%;">应参加人数</th>
<th style="width:8%;">已参加人数</th>
<th style="width:8%;">未参加人数</th>
<th style="width:8%;">活动状态</th>
<th style="width:18%;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<!-- 分页 -->
<div class="pagination">
<span class="pagination__total" id="pageTotal"></span>
<div class="pagination__pages">
<span class="page-item active">1</span>
</div>
</div>
</div>
</main>
<script>
function toggleSubgroup(h){h.parentElement.classList.toggle('open');}
(function(){var a=document.querySelector('.sidebar-item.active');if(!a)return;var s=a.closest('.sidebar-subgroup');if(s)s.classList.add('open');})();
/* ===== 数据 ===== */
var statusMap = {'待开始':'tag-blue','进行中':'tag-orange','已完成':'tag-green'};
var activities = [
{id:1, name:'2026年上半年员工健康排查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-01-01', dateTo:'2026-03-31', personCount:1280, doneCount:1150, status:'已完成', leader:'张明', surveys:['健康记录问卷','职业健康排查问卷'], desc:'年度例行员工健康排查活动,覆盖全体员工。'},
{id:2, name:'2026年Q2心理健康专项排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-04-01', dateTo:'2026-06-30', personCount:356, doneCount:280, status:'进行中', leader:'李华', surveys:['心理健康评估问卷'], desc:'针对C类和D类人群的心理健康专项排查。'},
{id:3, name:'2026年Q3职业健康排查', org:'能源集团', dept:'安全环保部', dateFrom:'2026-07-01', dateTo:'2026-09-30', personCount:520, doneCount:0, status:'待开始', leader:'王强', surveys:['职业健康排查问卷','健康记录问卷'], desc:'第三季度职业健康专项排查,针对A类和B类人群。'},
{id:4, name:'新员工入职健康基线调查', org:'能源集团', dept:'人力资源部', dateFrom:'2026-06-01', dateTo:'2026-12-31', personCount:85, doneCount:60, status:'进行中', leader:'赵芳', surveys:['新员工健康基线问卷'], desc:'2026年度新入职员工健康基线数据采集。'},
{id:5, name:'慢病患者季度随访', org:'能源集团', dept:'工会', dateFrom:'2026-04-01', dateTo:'2026-06-30', personCount:128, doneCount:98, status:'进行中', leader:'孙丽', surveys:['慢病管理随访问卷'], desc:'E类慢病患者第二季度定期随访。'}
];
/* 组织单位多选 */
var filterOrgs = [];
function toggleOrgSelect(){
var panel = document.getElementById('orgPanel');
var isShow = panel.classList.contains('show');
closeAllPanels();
if(!isShow) panel.classList.add('show');
}
function toggleOrgOpt(el, val){
var cb = el.querySelector('.multi-select__cb');
if(val === '全部'){
filterOrgs = [];
document.querySelectorAll('#orgPanel .multi-select__opt').forEach(function(opt){
opt.classList.remove('checked');opt.querySelector('.multi-select__cb').classList.remove('checked');
});
el.classList.add('checked');cb.classList.add('checked');
} else {
document.querySelector('#orgPanel .multi-select__opt[data-val="全部"]').classList.remove('checked');
document.querySelector('#orgPanel .multi-select__opt[data-val="全部"]').querySelector('.multi-select__cb').classList.remove('checked');
if(cb.classList.contains('checked')){
el.classList.remove('checked');cb.classList.remove('checked');
filterOrgs = filterOrgs.filter(function(c){return c!==val;});
} else {
el.classList.add('checked');cb.classList.add('checked');
filterOrgs.push(val);
}
}
updateOrgTrigger();
}
function updateOrgTrigger(){
var trigger = document.querySelector('#orgSelect .multi-select__trigger');
if(filterOrgs.length===0){
trigger.textContent = '全部';
trigger.className = 'multi-select__trigger placeholder';
} else {
trigger.className = 'multi-select__trigger';
var html = '';
filterOrgs.forEach(function(c){
html += '<span class="sel-tag">'+esc(c)+'<span class="tag-x" onclick="removeOrgTag(\''+c+'\')">×</span></span>';
});
trigger.innerHTML = html;
}
}
function removeOrgTag(val){
filterOrgs = filterOrgs.filter(function(c){return c!==val;});
var opt = document.querySelector('#orgPanel .multi-select__opt[data-val="'+val+'"]');
opt.classList.remove('checked');opt.querySelector('.multi-select__cb').classList.remove('checked');
updateOrgTrigger();
}
function closeAllPanels(){
document.querySelectorAll('.multi-select__panel').forEach(function(p){p.classList.remove('show');});
}
/* ===== 渲染表格 ===== */
function renderTable(data){
var html = '';
data.forEach(function(a){
var pending = a.personCount - a.doneCount;
html += '<tr>';
html += '<td>'+esc(a.name)+'</td>';
html += '<td>'+esc(a.org)+'</td>';
html += '<td>'+esc(a.dept)+'</td>';
html += '<td>'+a.dateFrom+' ~ '+a.dateTo+'</td>';
html += '<td>'+a.personCount+'</td>';
html += '<td style="color:var(--success);">'+a.doneCount+'</td>';
html += '<td style="color:var(--danger);">'+pending+'</td>';
html += '<td><span class="tag '+statusMap[a.status]+'">'+a.status+'</span></td>';
html += '<td>';
html += '<button class="link" onclick="window.location.href=\'activity-detail.html?id='+a.id+'\'">活动详情</button>';
html += '</td></tr>';
});
document.getElementById('tbody').innerHTML = html || '<tr><td colspan="9" style="text-align:center;padding:40px;color:var(--text-placeholder);">暂无数据</td></tr>';
document.getElementById('totalCount').textContent = '共 '+data.length+' 条数据';
document.getElementById('pageTotal').textContent = '共 '+data.length+' 条数据';
}
/* ===== 筛选 ===== */
function filterTable(){
var name = document.getElementById('filterName').value.trim().toLowerCase();
var filtered = activities.filter(function(a){
if(name && a.name.toLowerCase().indexOf(name) === -1) return false;
if(filterOrgs.length > 0 && filterOrgs.indexOf(a.org) === -1) return false;
return true;
});
renderTable(filtered);
}
function resetFilter(){
document.getElementById('filterName').value = '';
filterOrgs = [];
document.querySelectorAll('#orgPanel .multi-select__opt').forEach(function(opt){
opt.classList.remove('checked');opt.querySelector('.multi-select__cb').classList.remove('checked');
});
var allOpt = document.querySelector('#orgPanel .multi-select__opt[data-val="全部"]');
allOpt.classList.add('checked');allOpt.querySelector('.multi-select__cb').classList.add('checked');
updateOrgTrigger();
renderTable(activities);
}
function esc(s){return (s||'').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
/* ===== 初始化 ===== */
renderTable(activities);
document.addEventListener('click',function(e){
if(!e.target.closest('.multi-select')) closeAllPanels();
});
</script>
</body>
</html>
@@ -251,7 +251,6 @@
<div class="sidebar-menu__title">健康排查</div>
<a href="survey-manage.html" class="sidebar-item">问卷管理</a>
<a href="activity-manage.html" class="sidebar-item">活动管理</a>
<a href="activity-stat.html" class="sidebar-item">活动统计</a>
</aside>
<main class="main">
<div class="editor-layout">
@@ -161,7 +161,6 @@
<div class="sidebar-menu__title">健康排查</div>
<a href="survey-manage.html" class="sidebar-item active">问卷管理</a>
<a href="activity-manage.html" class="sidebar-item">活动管理</a>
<a href="activity-stat.html" class="sidebar-item">活动统计</a>
</aside>
<main class="main">
<!-- 筛选栏 -->
@@ -318,6 +318,9 @@
<button class="btn btn-default" onclick="openImportModal()">
批量入库导入
</button>
<button class="btn btn-default" onclick="void(0)">
批量绑定导入
</button>
<button class="btn btn-default" onclick="void(0)">
查看导入任务
</button>
@@ -570,7 +573,7 @@
</div>
<div class="form-group">
<label>绑定日期 <span style="color:var(--danger)">*</span></label>
<input class="form-control" type="date" value="2026-05-11" id="bindDate">
<input class="form-control" type="datetime-local" id="bindDate">
</div>
</div>
<div class="modal__footer">
@@ -646,6 +649,17 @@
下载导入模板
</button>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>所属单位 <span style="color:var(--danger)">*</span></label>
<select class="form-control filter-select" style="width:100%;">
<option value="">请选择所属单位</option>
<option value="采油一厂">采油一厂</option>
<option value="采油二厂">采油二厂</option>
<option value="采气一厂">采气一厂</option>
<option value="采气二厂">采气二厂</option>
<option value="天然气处理厂">天然气处理厂</option>
</select>
</div>
<div class="upload-area">
<svg width="48" height="48" viewBox="0 0 48 48" fill="none"><rect x="4" y="4" width="40" height="40" rx="6" stroke="#999" stroke-width="2"/><path d="M24 14v20M16 22l8-8 8 8" stroke="#999" stroke-width="2" stroke-linecap="round"/></svg>
<p>点击或拖拽文件到此区域上传</p>
@@ -693,10 +707,6 @@
<div style="background:#F0F7FF;border:1px solid #BAE0FF;border-radius:var(--radius-md);padding:10px 16px;flex-shrink:0;">
<span style="font-size:var(--font-sm);color:#0958D9;">设备编码:<strong id="msDeviceCode">WD-2024-001</strong></span>
</div>
<button class="btn btn-default btn-sm" onclick="showAddTypeForm()">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 1v10M1 6h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
新增类型配置
</button>
</div>
<div class="monitor-setting-list" id="monitorSettingList">
<!-- 心率 -->
@@ -877,6 +887,10 @@ function openBindModal(code, name) {
document.getElementById('bindEmpSearch').value = '';
document.getElementById('empDropdown').classList.remove('show');
document.getElementById('empCard').style.display = 'none';
// 绑定日期默认当前时间
var now = new Date();
var pad = function(n){ return n < 10 ? '0' + n : n; };
document.getElementById('bindDate').value = now.getFullYear() + '-' + pad(now.getMonth()+1) + '-' + pad(now.getDate()) + 'T' + pad(now.getHours()) + ':' + pad(now.getMinutes());
document.getElementById('bindModal').classList.add('show');
}
var selectedEmp = null;
@@ -632,6 +632,65 @@
</tbody>
</table>
</div>
<!-- 血压面板(独立:双线图 + 收缩压/舒张压分开) -->
<div class="tab-panel" id="panel-bp" style="display:none;">
<div class="metric-toolbar">
<div class="metric-toolbar__left">
<div class="time-granularity">
<button class="time-btn active" onclick="switchGranularityBp(this)"></button>
<button class="time-btn" onclick="switchGranularityBp(this)"></button>
<button class="time-btn" onclick="switchGranularityBp(this)"></button>
<button class="time-btn" onclick="switchGranularityBp(this)"></button>
</div>
</div>
<button class="btn btn-default" id="btnToggleBpList" onclick="toggleBpView()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 3h10M2 7h10M2 11h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
列表展示
</button>
</div>
<!-- 趋势图视图 -->
<div id="bp-chart-view">
<div class="date-nav">
<button class="date-nav__btn" onclick="void(0)"></button>
<span class="date-nav__text">2026-05-12</span>
<button class="date-nav__btn" onclick="void(0)"></button>
</div>
<div class="metric-summary" id="bpSummary">
<div class="metric-summary__item"><span class="metric-summary__value" style="color:#EB2F96;">135</span><span class="metric-summary__unit">mmHg</span><span class="metric-summary__label">收缩压最高</span></div>
<div class="metric-summary__item"><span class="metric-summary__value" style="color:#EB2F96;">112</span><span class="metric-summary__unit">mmHg</span><span class="metric-summary__label">收缩压最低</span></div>
<div class="metric-summary__item"><span class="metric-summary__value" style="color:#1890FF;">88</span><span class="metric-summary__unit">mmHg</span><span class="metric-summary__label">舒张压最高</span></div>
<div class="metric-summary__item"><span class="metric-summary__value" style="color:#1890FF;">72</span><span class="metric-summary__unit">mmHg</span><span class="metric-summary__label">舒张压最低</span></div>
</div>
<div class="chart-area" id="bpTrendChart"></div>
</div>
<!-- 列表视图 -->
<div id="bp-table-view" style="display:none;">
<div style="display:flex;gap:16px;margin-bottom:16px;align-items:center;">
<div class="filter-item">
<label style="font-size:var(--font-sm);color:var(--text-secondary);">日期</label>
<input type="date" value="2026-05-12" style="height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;">
</div>
<button class="btn btn-default">导出</button>
</div>
<table class="data-history-table">
<thead>
<tr>
<th>上报时间</th>
<th>收缩压 (mmHg)</th>
<th>舒张压 (mmHg)</th>
<th>状态</th>
</tr>
</thead>
<tbody id="bpTableBody">
<tr><td>14:32:05</td><td>120</td><td>78</td><td><span class="tag tag-green">正常</span></td></tr>
<tr><td>14:02:05</td><td>124</td><td>80</td><td><span class="tag tag-green">正常</span></td></tr>
<tr><td>13:32:05</td><td>118</td><td>75</td><td><span class="tag tag-green">正常</span></td></tr>
<tr><td>13:02:05</td><td>131</td><td>85</td><td><span class="tag tag-green">正常</span></td></tr>
<tr><td>12:32:05</td><td>142</td><td>92</td><td><span class="tag tag-red">异常</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@@ -675,12 +734,20 @@ function switchMainTab(el, tab) {
document.getElementById('panel-metric').style.display = 'none';
document.getElementById('panel-exercise').style.display = 'none';
document.getElementById('panel-ecg').style.display = 'none';
document.getElementById('panel-bp').style.display = 'none';
if (tab === 'overview') {
document.getElementById('panel-overview').style.display = '';
} else if (tab === 'exercise') {
document.getElementById('panel-exercise').style.display = '';
} else if (tab === 'ecg') {
document.getElementById('panel-ecg').style.display = '';
} else if (tab === 'bp') {
document.getElementById('panel-bp').style.display = '';
bpListView = false;
document.getElementById('btnToggleBpList').innerHTML = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 3h10M2 7h10M2 11h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg> 列表展示';
document.getElementById('bp-chart-view').style.display = '';
document.getElementById('bp-table-view').style.display = 'none';
renderBpTrendChart();
} else {
document.getElementById('panel-metric').style.display = '';
isListView = false;
@@ -760,8 +827,71 @@ function renderTrendChart() {
+ '<polyline points="'+pts+'" fill="none" stroke="#1890FF" stroke-width="2"/>'
+ xLabels
+ '</svg>';
}
function openEcgDetail(time, result, value) {
}
/* ===== 血压面板专用 ===== */
var bpListView = false;
function toggleBpView() {
bpListView = !bpListView;
var btn = document.getElementById('btnToggleBpList');
if (bpListView) {
document.getElementById('bp-chart-view').style.display = 'none';
document.getElementById('bp-table-view').style.display = '';
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 3h8v8H3z" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M3 7h8" stroke="currentColor" stroke-width="1"/></svg> 趋势图展示';
} else {
document.getElementById('bp-chart-view').style.display = '';
document.getElementById('bp-table-view').style.display = 'none';
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 3h10M2 7h10M2 11h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg> 列表展示';
renderBpTrendChart();
}
}
function switchGranularityBp(el) {
document.querySelectorAll('#panel-bp .time-granularity .time-btn').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
renderBpTrendChart();
}
function renderBpTrendChart() {
var c = document.getElementById('bpTrendChart');
if (!c) return;
var W = c.offsetWidth || 880, H = 280;
var pad = {t:20,r:20,b:40,l:50};
var cw = W - pad.l - pad.r, ch = H - pad.t - pad.b;
// 24小时数据点
var n = 25;
// 收缩压数据 (systolic)
var sysData = [118,116,114,112,115,118,120,122,125,128,130,128,124,122,120,124,128,132,135,130,126,122,120,118,116];
// 舒张压数据 (diastolic)
var diaData = [75,74,72,73,74,76,78,78,80,82,84,82,80,78,76,78,80,84,86,82,80,78,76,74,72];
var minV = 60, maxV = 150;
function px(i){ return pad.l + i/(n-1)*cw; }
function py(v){ return pad.t + ch - (v-minV)/(maxV-minV)*ch; }
// 网格线
var yLines = [60,80,100,120,140];
var gridLines = yLines.map(function(v){
return '<line x1="'+pad.l+'" y1="'+py(v)+'" x2="'+(pad.l+cw)+'" y2="'+py(v)+'" stroke="#f0f0f0" stroke-width="1"/>'
+ '<text x="'+(pad.l-8)+'" y="'+(py(v)+4)+'" text-anchor="end" font-size="11" fill="#999">'+v+'</text>';
}).join('');
// X轴标签
var xTicks = [0,4,8,12,16,20,24];
var xLabels = xTicks.map(function(h){
var label = (h<10?'0':'')+h+':00';
return '<text x="'+px(h)+'" y="'+(H-10)+'" text-anchor="middle" font-size="10" fill="#aaa">'+label+'</text>';
}).join('');
// 收缩压折线
var sysPts = sysData.map(function(v,i){ return px(i)+','+py(v); }).join(' ');
// 舒张压折线
var diaPts = diaData.map(function(v,i){ return px(i)+','+py(v); }).join(' ');
c.innerHTML = '<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'">'
+ gridLines + xLabels
// 舒张压(蓝色)
+ '<polyline points="'+diaPts+'" fill="none" stroke="#1890FF" stroke-width="2" stroke-dasharray="6,3"/>'
// 收缩压(粉色)
+ '<polyline points="'+sysPts+'" fill="none" stroke="#EB2F96" stroke-width="2"/>'
// 图例
+ '<circle cx="'+(W-130)+'" cy="22" r="4" fill="#EB2F96"/><text x="'+(W-120)+'" y="26" font-size="11" fill="#666">收缩压</text>'
+ '<circle cx="'+(W-60)+'" cy="22" r="4" fill="#1890FF"/><text x="'+(W-50)+'" y="26" font-size="11" fill="#666">舒张压</text>'
+ '</svg>';
}
function openEcgDetail(time, result, value) {
document.getElementById('ecgTime').textContent = time;
document.getElementById('ecgResult').textContent = result;
document.getElementById('ecgValue').textContent = value;