编辑页面

修改后将从当前时间重新计算过期时间
禁用后该账号在管的患者数据仍保留,但不可再下发参数。
`, ok:'确认禁用', danger:true, act:"toast('账号已禁用,操作已留痕')"}, 'm-publish':{t:'保存并发布默认参数', s:'发布后仅影响新建会话的默认值,不回溯修改已有会话与设备内参数', b:`
新版本号
v1.5
生效时间
立即生效
发布操作需超级管理员权限,并记入操作日志。
`, ok:'确认发布', act:"toast('默认参数 v1.5 已发布')"}, 'm-stop':{t:'暂停 OTA 任务', s:'暂停后已下发但未完成的升级将在设备下次连接时继续', b:`

确认暂停任务 OTA-20261015-02?当前进度 68%(39 成功 / 2 失败)。

`, ok:'确认暂停', act:"toast('OTA 任务已暂停')"}, 'm-i18n':{t:'新增语言', s:'新增后需导入或人工补全词条,完成度达 100% 方可发布', b:`
否
是
`, ok:'确认新增', act:"toast('语言已新增,请补全词条')"}, 'm-role':{t:'新建角色', s:'角色权限保存后,对该角色下全部账号生效', b:`
`, ok:'创建角色', act:"toast('角色已创建,请配置权限项')"}, 'm-diff':{t:'参数变更对比', s:'张明远 · 刘桂芳 · 10月19日 08:42', b:`
参数项
变更前
变更后
刺激幅度
50%
55%
脉冲频率
30 Hz
35 Hz
脉冲时间
200 μs
200 μs
刺激时间
2 s
2 s
刺激间隔
5 s
5 s
下发结果:成功(1.2 s)· 服务器已同步保存。
`, ok:'知道了', act:"closeModal()"}, 'm-privacy':{t:'发布隐私协议 v2.2', s:'协议将在 App 首次启动、「我的 → 隐私协议」与后台登录页页脚同步生效', b:`
需要
不需要
`, ok:'确认发布', act:"toast('隐私协议 v2.2 已发布')"}, 'm-hospital':{t:'新增医院', s:'医院是科室的上级维度,编码创建后不可修改', b:`
启用(App 端可选)
停用
编码按 H- 三位流水自动生成;机构名称与资质材料不一致时,审核中心会以「机构信息不符」驳回。
`, ok:'保存', act:"closeModal();toast('医院已保存,App 端下拉即时生效')"}, 'm-dept':{t:'新增科室', s:'科室挂靠在医院下,编码全局唯一', b:`
启用
停用
编码规则:D-医院序号(2 位)+科室序号(2 位);跨院同名科室可重复,编码不可重复。
`, ok:'保存', act:"closeModal();toast('科室已保存,App 端级联下拉已更新')"}, 'm-title':{t:'新增职称', s:'保存后 App 端职称下拉立即可选', b:`
启用
停用
排序值决定 App 下拉顺序;同一职称被引用后不可删除,只能停用。
`, ok:'保存', act:"closeModal();toast('职称已保存,App 端下拉即时生效')"}, 'm-hosp-off':{t:'停用医院', s:'停用不等同于删除,历史数据全部保留', b:`
停用后该医院及其下全部科室将从 App 端下拉中移除,已归属的医护人员不会被解除关系,但资料编辑时需重新选择机构。
`, ok:'确认停用', act:"closeModal();toast('医院已停用,App 端不再展示')", danger:true}, 'm-dept-off':{t:'停用科室', s:'停用后 App 端不可再选择该科室', b:`
该科室下仍有医护人员时,停用不会解除其归属,但会提示管理员先完成人员调整;历史会话与报告不受影响。
`, ok:'确认停用', act:"closeModal();toast('科室已停用')", danger:true}, 'm-title-off':{t:'停用职称', s:'已被引用的职称只能停用,不能删除', b:`
停用后该职称不再出现在 App 下拉中;已引用该职称的医护人员资料保留原值,重新编辑时需改选其他职称。
仅停用
停用并批量改选
`, ok:'确认停用', act:"closeModal();toast('职称已停用')", danger:true}, 'm-staff':{t:'新增人员', s:'手机号是 App 注册匹配名册的唯一标识,须与本人注册号码一致', b:`
男
女
在职
停用
离职
所属医院与科室为级联关系,仅可选择启用条目;保存后该手机号注册 App 将自动匹配本名册并进入资质审核。
`, ok:'保存', act:"closeModal();toast('人员已保存,可凭手机号注册 App')"}, 'm-staff-off':{t:'停用人员', s:'停用后该人员无法登录 App,历史数据保留', b:`
停用后该人员立即退出登录,不能再绑定设备或调整参数;名下在管患者的随访任务需转交同科室其他医师。
`, ok:'确认停用', act:"closeModal();toast('人员已停用,账号同步禁用')", danger:true}, 'm-unbind-reject':{t:'驳回解绑申请', s:'驳回原因将推送至患者端与主治医生', b:`
`, ok:'确认驳回', act:"closeModal();toast('已驳回,结果已推送患者端')", danger:true} }; function openModal(id){ const mask = document.getElementById('mask'); const box = document.getElementById('modalBox'); let t,s,b,ok,act,danger,wide; if(id==='m-func'){ wide=true; t='后台功能对照'; s='PRD 第七章 20 项后台功能 ↔ 本原型页面'; b=funcTable(); ok='知道了'; act='closeModal()'; } else if(id==='m-notice'){ t='待办事项'; s='按类型查看待处理事项'; b=noticeList(); ok='全部标记已读'; act="toast('已全部标记为已读')"; } else if(id==='m-export'){ wide=true; t='新建导出任务'; s='导出内容将添加水印并记录操作人'; b=exportForm(); ok='创建导出任务'; act="closeModal();toast('导出任务已创建,完成后可下载')"; } else if(id==='m-session'){ wide=true; t='会话详情'; s='SES-20261019-0142'; b=sessionDetail(); ok='关闭'; act='closeModal()'; } else if(id==='m-restore'){ t='确认恢复数据'; s='此操作将覆盖当前数据库'; b=`
恢复至 10/19 03:00 的备份将丢失此后产生的全部会话与操作数据,且服务不可用约 10–20 分钟。
`; ok='执行恢复'; act="closeModal();toast('恢复任务已提交,等待云厂商执行')"; danger=true; } else if(id==='m-threshold'){ t='异常提示阈值配置'; s='阈值须由甲方书面提供,本平台不自行判定'; b=thresholdForm(); ok='保存阈值'; act="toast('阈值已保存')"; } else if(id==='m-newver'){ wide=true; t='发布 APP 版本'; s='版本号与更新说明须与商店提交内容一致'; b=versionForm(); ok='提交发布'; act="closeModal();toast('版本已提交发布流程')"; } else if(id==='m-ota'){ t='新建 OTA 推送任务'; s='依赖 OTA 协议:分包大小、断点续传、校验方式'; b=otaForm(); ok='创建任务'; act="closeModal();toast('OTA 任务已创建')"; } else if(id==='m-reject'){ t='驳回审核'; s='驳回原因将推送至申请人 App'; b=`
`; ok='确认驳回'; act="closeModal();toast('已驳回,结果已推送申请人')"; danger=true; } else if(GENERIC[id]){ const g=GENERIC[id]; t=g.t; s=g.s; b=g.b; ok=g.ok; act=g.act; danger=g.danger; } else { t='提示'; s=''; b='

原型演示

'; ok='知道了'; act='closeModal()'; } document.getElementById('mTitle').textContent = t; document.getElementById('mSub').textContent = s || ''; document.getElementById('mBody').innerHTML = b; box.classList.toggle('wide', !!wide); document.getElementById('mFoot').innerHTML = ` `; mask.classList.add('show'); modalBuild = id; } function closeModal(){ document.getElementById('mask').classList.remove('show'); } document.getElementById('mask').addEventListener('click', e=>{ if(e.target.id==='mask') closeModal(); }); document.addEventListener('keydown', e=>{ if(e.key==='Escape'){ closeModal(); closeDrawer(); } }); function funcTable(){ const rows = [ ['F-后台-01','患者用户列表','p-patient','✅ 已实现'], ['F-后台-02','患者状态管理(启用/禁用、重置密码)','p-patient','✅ 已实现'], ['F-后台-03','医护人员用户列表','p-doctor','✅ 已实现'], ['F-后台-04','医护人员审核与状态管理','p-doctor','✅ 已实现'], ['F-后台-05','设备列表(含最后活跃时间)','p-device','✅ 已实现'], ['F-后台-06','设备状态监控(60 秒轮询)','p-device','✅ 已实现'], ['F-后台-07','设备绑定记录','p-bind','✅ 已实现'], ['F-后台-08','固件版本管理 / OTA','p-firmware','⚠ 依赖 OTA 协议'], ['F-后台-09','患者会话数据查看','p-session','✅ 已实现'], ['F-后台-10','会话数据导出(PDF / Excel)','p-export','✅ 已实现'], ['F-后台-11','设备数据异常提示','p-alert','⚠ 阈值待确认'], ['F-后台-12','默认参数配置','p-param','⚠ 依赖协议'], ['F-后台-13','参数修改审计','p-auditlog','✅ 已实现'], ['F-后台-14','参数下发日志','p-pushlog','✅ 已实现'], ['F-后台-15','APP 版本管理 / 强制更新','p-version','✅ 已实现'], ['F-后台-16','多语言管理','p-i18n','⚠ 语种待确认'], ['F-后台-17','隐私协议管理','p-privacy','✅ 已实现'], ['F-后台-18','操作日志','p-oplog','✅ 已实现'], ['F-后台-19','角色权限管理','p-role','✅ 已实现'], ['F-后台-20','数据备份与恢复','p-backup','✅ 已实现'], ['附-01','医院信息维护(医院字典)','p-hospital','✅ 本次新增'], ['附-02','科室信息维护(科室字典)','p-dept','✅ 本次新增'], ['附-03','职称信息维护(职称字典)','p-title','✅ 本次新增'], ['附-04','院内人员名册(姓名 / 手机号 / 医院 / 科室 / 职称 / 状态)','p-staff','✅ 本次新增'] ]; return `
${rows.map(r=>``).join('')}
编号功能原型页面状态
${r[0]}${r[1]}${r[2]}${r[3].includes('⚠')?`${r[3].replace('⚠ ','')}`:`${r[3].replace('✅ ','')}`}
另有 3 项为医生端衍生需求,已并入后台:解绑审核、随访任务、重点关注名单;「医院信息」模块(附-01–03)为本次新增的组织字典能力。
`; } function noticeList(){ const items = [ ['设备解绑申请 2 条','陈志强 / 周明辉','a','p-audit'], ['数据异常提示 6 条待处理','按触发次数排序','r','p-alert'], ['设备超 7 天未活跃 14 台','建议核对绑定关系','grey','p-device'], ['OTA 任务 OTA-20261018-03 待执行','目标 210 台设备','p','p-firmware'], ['隐私协议 v2.2 待甲方确认','影响三处呈现文案','v','p-privacy'] ]; return `
${items.map(i=>`
${i[0]}
${i[1]}
待处理
`).join('')}
`; } function exportForm(){ return `
按医疗机构
按患者
按设备
PDF 报告
Excel 明细
包含患者身份信息
关闭时姓名脱敏、手机号打码
添加水印(导出人 + 时间)
建议始终开启,便于溯源
下载链接 7 天有效期
过期后需重新导出
导出内容将标注「仅统计已同步数据」,设备离线期间缺失的事件不计入合计。
`; } function sessionDetail(){ return `
患者
王慧敏 · PT-100238
设备
PT2026A00317 · S1
会话时段
10/19 23:02 → 10/19 06:31
时长
7 小时 29 分
刺激模式
跟随呼吸刺激
幅度
起始 50% → 结束 50%

呼吸波形与暂停区间

设备上报原始记录
23:0001:1203:4006:30
呼吸波形呼吸暂停区间
暂停事件(3 次)
01:12持续 12 秒幅度 50%
02:35持续 8 秒幅度 50%
04:08持续 21 秒幅度 52%
会话参数快照
脉冲时间 / 频率
200 μs / 30 Hz
刺激时间 / 间隔
2 s / 5 s
结束原因
患者手动停止
数据完整性
完整 · 无断连
`; } function thresholdForm(){ return `
次 / 夜,连续夜触发
低于% 或高于%
小时无上报
降级为排序与筛选
不展示该模块
阈值一旦由本方自行设定,即构成医学判断,与「健康管理类软件」定位冲突。须由甲方书面提供并留存确认记录。
`; } function versionForm(){ return `
iOS + Android
仅 iOS
仅 Android
强制更新
低版本客户端启动时弹出不可跳过提示
`; } function otaForm(){ return `
低于目标版本的全部设备(210 台)
手动选择设备
升级期间设备不可用,建议在日间执行;患者端会收到「固件升级中」提示,升级过程中请勿关闭设备。
`; } /* ================= 抽屉 ================= */ const PATIENTS = { '王慧敏':{sex:'女 · 52岁', id:'PT-100238', phone:'138****2156', dev:'S1 · PT2026A00317', fw:'v2.4.1', day:12, avg:'2.1', adh:'6/7', doc:'张明远', status:'正常'}, '刘桂芳':{sex:'女 · 47岁', id:'PT-100256', phone:'137****4021', dev:'S2 · PT2026B00921', fw:'v2.6.0', day:5, avg:'9.0', adh:'5/7', doc:'张明远', status:'重点关注'}, '陈志强':{sex:'男 · 58岁', id:'PT-100233', phone:'135****6672', dev:'S1 · PT2026A00276', fw:'v2.4.1', day:19, avg:'3.4', adh:'6/7', doc:'张明远', status:'解绑审核中'}, '赵文博':{sex:'男 · 45岁', id:'PT-100264', phone:'186****1190', dev:'S1 · PT2026A00408', fw:'v2.4.1', day:7, avg:'—', adh:'0/7', doc:'张明远', status:'数据缺漏'}, '吴雅琴':{sex:'女 · 49岁', id:'PT-100280', phone:'189****5521', dev:'S2-Pro · PT2026B00736', fw:'v2.8.3', day:4, avg:'6.1', adh:'2/7', doc:'李静', status:'重点关注'}, '孙丽娟':{sex:'女 · 61岁', id:'PT-100219', phone:'158****3374', dev:'S1 · PT2026A00408', fw:'v2.4.1', day:14, avg:'3.2', adh:'6/7', doc:'张明远', status:'已暂停'}, '周明辉':{sex:'男 · 55岁', id:'PT-100271', phone:'133****9087', dev:'S2 · PT2026B01044', fw:'v2.6.0', day:9, avg:'2.8', adh:'5/7', doc:'李静', status:'解绑审核中'}, '李建国':{sex:'男 · 63岁', id:'PT-100241', phone:'139****8830', dev:'S2 · PT2026B00921', fw:'v2.6.0', day:3, avg:'4.3', adh:'4/7', doc:'李静', status:'血氧偏低'}, '张凤仪':{sex:'女 · 67岁', id:'PT-100112', phone:'136****7742', dev:'S1 · PT2026A00158', fw:'v2.4.1', day:0, avg:'1.4', adh:'—', doc:'张明远', status:'已禁用'}, '何秀兰':{sex:'女 · 56岁', id:'PT-100305', phone:'150****2288', dev:'未绑定', fw:'—', day:0, avg:'—', adh:'—', doc:'—', status:'未开始'}, '郑海涛':{sex:'男 · 39岁', id:'PT-100318', phone:'177****6034', dev:'S4 · PT2027A00412', fw:'v3.1.0', day:1, avg:'1.0', adh:'1/7', doc:'赵敏', status:'正常'} }; /* ---------- 患者详情页 ---------- */ const NIGHTS = [ {d:'10/19', w:'周一', s:'23:02', e:'06:31', dur:449}, {d:'10/18', w:'周日', s:'22:48', e:'06:12', dur:444}, {d:'10/17', w:'周六', s:'23:15', e:'06:04', dur:409}, {d:'10/16', w:'周五', s:'22:56', e:'06:20', dur:444}, {d:'10/15', w:'周四', s:'23:34', e:'06:02', dur:388}, {d:'10/14', w:'周三', s:'22:41', e:'06:15', dur:454}, {d:'10/13', w:'周二', s:'23:08', e:'05:58', dur:410} ]; const EV_T = ['00:12','00:47','01:12','01:38','02:05','02:35','03:02','03:29','04:08','04:41','05:12','05:33']; const EV_D = [8,12,21,9,15,11,7,18,25,10,14,16]; const PD_META = { '王慧敏':{reg:'2026-08-30 14:22', last:'3 分钟前', bind:'2026-09-22 21:40', mode:'跟随呼吸刺激', amp:50, hz:30, stim:2, gap:5, delay:0, bat:86, mac:'A4:C1:38:2E:71:0B', ota:'2026-09-22', src:'医生端下发 · 10-18 08:42', ap:[3,2,2,3,1,2,2]}, '李建国':{reg:'2026-09-02 09:41', last:'8 分钟前', bind:'2026-09-18 20:15', mode:'跟随呼吸刺激', amp:55, hz:30, stim:2, gap:5, delay:0, bat:74, mac:'A4:C1:38:9D:04:52', ota:'2026-09-28', src:'医生端下发 · 10-17 09:12', ap:[4,5,4,4,3,5,4]}, '刘桂芳':{reg:'2026-09-11 16:05', last:'12 分钟前', bind:'2026-09-15 22:03', mode:'呼吸暂停刺激', amp:60, hz:32, stim:2, gap:6, delay:10, bat:63, mac:'A4:C1:38:1B:66:AE', ota:'2026-09-15', src:'医生端下发 · 10-19 08:42', ap:[9,10,8,11,9,7,9]}, '陈志强':{reg:'2026-09-04 10:18', last:'2 小时前', bind:'2026-08-30 19:26', mode:'跟随呼吸刺激', amp:50, hz:30, stim:2, gap:5, delay:0, bat:41, mac:'A4:C1:38:7C:31:90', ota:'2026-09-20', src:'医生端下发 · 10-17 10:22', ap:[3,4,3,3,4,3,3]}, '赵文博':{reg:'2026-09-20 20:44', last:'3 天前', bind:'2026-10-12 21:11', mode:'跟随呼吸刺激', amp:40, hz:28, stim:2, gap:5, delay:0, bat:8, mac:'A4:C1:38:52:8F:1C', ota:'2026-10-12', src:'医生端下发 · 10-18 09:12', missing:true, ap:[0,0,0,0,0,0,0]}, '孙丽娟':{reg:'2026-08-14 11:02', last:'1 小时前', bind:'2026-08-16 20:38', mode:'固定时间刺激', amp:45, hz:26, stim:3, gap:8, delay:15, bat:78, mac:'A4:C1:38:33:DD:47', ota:'2026-08-16', src:'出厂默认参数', ap:[3,3,4,2,3,3,4]}, '周明辉':{reg:'2026-09-26 08:37', last:'26 分钟前', bind:'2026-09-27 22:50', mode:'呼吸暂停刺激', amp:55, hz:30, stim:2, gap:6, delay:0, bat:69, mac:'A4:C1:38:6E:22:B3', ota:'2026-09-27', src:'医生端下发 · 10-16 14:05', ap:[3,2,3,3,2,2,3]}, '吴雅琴':{reg:'2026-10-01 19:12', last:'昨天 23:10', bind:'2026-10-02 21:05', mode:'呼吸暂停刺激', amp:60, hz:34, stim:2, gap:6, delay:5, bat:52, mac:'A4:C1:38:B1:7A:29', ota:'2026-10-19', src:'医生端下发 · 10-19 07:40', ap:[6,7,6,6,8,5,6]}, '张凤仪':{reg:'2026-07-08 15:30', last:'9 天前', bind:'2026-07-10 19:44', mode:'固定时间刺激', amp:45, hz:26, stim:3, gap:8, delay:0, bat:20, mac:'A4:C1:38:0F:5C:12', ota:'2026-07-10', src:'医生端下发 · 09-28 10:30', ap:[]}, '何秀兰':{reg:'2026-10-17 21:08', last:'4 天前', bind:null, mode:null, amp:null, hz:null, stim:null, gap:null, delay:null, bat:null, mac:null, ota:null, src:null, ap:[]}, '郑海涛':{reg:'2026-10-18 22:41', last:'41 分钟前', bind:'2026-10-18 23:02', mode:'跟随呼吸刺激', amp:50, hz:30, stim:2, gap:5, delay:0, bat:95, mac:'B8:27:EB:4A:19:6D', ota:'2026-10-18', src:'出厂默认参数', ap:[1]} }; let PD_CUR = '王慧敏'; const fmtDur = m => Math.floor(m/60)+'h '+String(m%60).padStart(2,'0')+'m'; function pdSessions(key){ const m = PD_META[key] || {}, ap = m.ap || []; return NIGHTS.slice(0, ap.length).map((n,i)=>({ d:n.d, w:n.w, s:n.s, e:n.e, dur:n.dur, i, apnea: ap[i], hr: m.missing ? null : (17 - (i%2) - (ap[i] >= 8 ? 1 : 0)), amp: (m.amp == null) ? null : Math.max(30, m.amp - (i%3)), ok: !m.missing })); } function pdEvHtml(c){ if(!c) return '未检出'; const idx = []; for(let i=0;i`${EV_T[k]} · ${EV_D[k]}s`).join(''); return show + (uniq.length>4 ? `+${uniq.length-4}` : ''); } function xOfTime(t){ const [h,mi] = t.split(':').map(Number); const mins = (h >= 23 ? h*60+mi : (h+24)*60+mi) - 23*60; return Math.min(688, Math.max(8, mins/450*700)); } function openPatient(name){ renderPatientDetail(name); go('p-patient-detail'); } function renderPatientDetail(name){ const key = PATIENTS[name] ? name : '王慧敏'; const p = PATIENTS[key], m = PD_META[key] || {}; const doc = DOCTORS[p.doc] || null; const hasDev = p.dev !== '未绑定'; const ses = pdSessions(key), okSes = ses.filter(x=>x.ok); PD_CUR = key; const stCls = {'正常':'g','重点关注':'a','数据缺漏':'r','已暂停':'grey','已禁用':'grey','解绑审核中':'a','未开始':'grey','血氧偏低':'a'}[p.status] || 'grey'; const kv = arr => arr.map(([k,v])=>`
${k}
${v}
`).join(''); const devName = p.dev.split(' · ')[0], devSn = p.dev.split(' · ')[1] || '—'; document.getElementById('pdAv').textContent = key.slice(0,1); document.getElementById('pdName').textContent = key; document.getElementById('pdTags').innerHTML = `${p.status}` + (hasDev ? `${devName} · 已绑定` : `未绑定设备`); document.getElementById('pdMeta').innerHTML = `${p.id}${p.sex}${p.phone}主治医生 ${p.doc}注册于 ${m.reg || '—'}`; /* ---- 1 · 基本信息 ---- */ const docCard = doc ? `

主治医生信息

来自人员管理名册
${kv([ ['姓名', p.doc], ['人员编码', `${doc.id}`], ['职称', doc.title], ['所属医院', doc.org], ['所属科室', doc.dept], ['手机号', `${doc.phone}`], ['在管患者', doc.pat + ' 人'], ['绑定设备', doc.dev + ' 台'] ])}
` : `

主治医生信息

未分配
尚未分配主治医生 患者绑定设备后由审核或人工指派主治医生,指派后此处展示医生所属医院、科室与职称。
`; const mod1 = `
1

基本信息

患者资料与主治医生归属

用户基本信息

患者端注册资料
${kv([ ['姓名', key], ['患者 ID', `${p.id}`], ['性别 / 年龄', p.sex], ['手机号', `${p.phone}`], ['注册时间', m.reg || '—'], ['最近使用', m.last || '—'], ['账号状态', `${p.status}`], ['设备绑定', hasDev ? '已绑定 · ' + devName : '未绑定'] ])}
${docCard}
`; /* ---- 2 · 设备信息 ---- */ const devCard = hasDev ? `

硬件设备信息

设备档案 ${p.status==='解绑审核中'?'解绑审核中':'已绑定'}
睡眠治疗仪 ${devName}
SN ${devSn}
电量 ${m.bat!=null?m.bat+'%':'—'}
${kv([ ['设备型号', '睡眠治疗仪 ' + devName], ['设备 SN', `${devSn}`], ['固件版本', `${p.fw}`], ['蓝牙 MAC', `${m.mac || '—'}`], ['绑定时间', m.bind || '—'], ['最后活跃', m.last || '—'], ['最近升级', m.ota || '—'], ['归属患者', key + ' · ' + p.id] ])}
` : `

硬件设备信息

设备档案
尚未绑定设备 患者完成设备绑定后,此处展示设备型号、SN、固件版本、电量与绑定时间。
`; const paramCard = m.mode ? `

刺激参数信息

设备端当前生效值 ${m.mode}
治疗幅度 ${m.amp}%
脉冲频率
${m.hz}Hz
刺激时间
${m.stim}s
刺激间隔
${m.gap}s
开机延迟
${m.delay}min
来源:${m.src}
` : `

刺激参数信息

设备端当前生效值
暂无生效参数 设备绑定并完成首次参数下发后,此处展示刺激模式与各项参数。
`; const mod2 = `
2

设备信息

硬件档案与当前生效的刺激参数
${devCard}${paramCard}
`; /* ---- 3 · 使用数据 ---- */ const avgDur = okSes.length ? Math.round(okSes.reduce((a,b)=>a+b.dur,0)/okSes.length) : null; const avgAp = okSes.length ? (okSes.reduce((a,b)=>a+b.apnea,0)/okSes.length).toFixed(1) : null; const rows = ses.map(s=>`
${s.d}
${s.w}
${s.s} – ${s.e}
跨零点 · 归属开始日
${fmtDur(s.dur)} ${s.hr!=null ? `${s.hr} 次/分` : '—'} ${s.ok ? `${s.apnea} 次` : '—'} ${s.ok ? pdEvHtml(s.apnea) : '数据缺漏,事件未上传'} ${s.amp!=null ? `${s.amp}%` : '—'} ${(m.mode || '').replace('刺激','')} ${s.ok ? '已同步' : '数据缺漏'} `).join(''); const usageBody = ses.length ? `
${rows}
日期 使用时段 时长 呼吸频率 呼吸暂停 暂停事件(时刻 · 持续) 平均幅度 刺激模式 数据状态 操作
显示第 1–${ses.length} 条,共 ${ses.length} 条 · 仅统计已同步数据
` : `
${hasDev ? '近 7 天无使用记录' : '尚未绑定设备'} ${hasDev ? (p.status==='已禁用' ? '账号已禁用,设备在近 7 天内未上传任何会话数据。' : '设备在近 7 天内未上传会话数据,患者开启治疗后自动同步。') : '患者绑定设备并开始首次治疗后,此处按夜展示呼吸频率、呼吸暂停事件与刺激幅度。'}
`; const mod3 = `
3

使用数据

每次使用设备的会话记录 · 字段口径与患者端「数据查看」一致
近 7 日使用
${okSes.length || '—'}天
平均使用时长
${avgDur ? fmtDur(avgDur) : '—'}
呼吸暂停均值
${avgAp ?? '—'}次/夜
依从性(有效使用 / 7 天)
${p.adh}天

使用记录

近 7 天 · 按使用日期倒序
${usageBody}
`; document.getElementById('pdBody').innerHTML = mod1 + mod2 + mod3; } function openUsageDetail(i){ const key = PD_CUR, p = PATIENTS[key], m = PD_META[key] || {}; const s = pdSessions(key)[i]; if(!s) return; const idxs = []; for(let k=0;k``).join(''); const evList = uniq.length ? uniq.map(k=>`
${EV_T[k]}持续 ${EV_D[k]} 秒幅度 ${s.amp}%
`).join('') : '
本夜未检出呼吸暂停事件。
'; document.getElementById('mTitle').textContent = '使用记录详情'; document.getElementById('mSub').textContent = key + ' · ' + s.d + ' ' + s.s + ' – ' + s.e; document.getElementById('modalBox').classList.add('wide'); document.getElementById('mBody').innerHTML = `
患者
${key} · ${p.id}
设备
${(p.dev.split(' · ')[1]) || '—'}
使用时段
${s.d} ${s.s} → ${s.e}
使用时长
${fmtDur(s.dur)}
呼吸频率均值
${s.hr!=null ? s.hr + ' 次/分' : '—'}
呼吸暂停
${s.ok ? s.apnea + ' 次' : '数据缺漏'}
刺激模式
${m.mode || '—'}
平均刺激幅度
${s.amp!=null ? s.amp + '%' : '—'}

呼吸波形与暂停区间

设备上报原始记录 · 当晚
${rects} ${s.s}03:06${s.e}
呼吸波形呼吸暂停区间
暂停事件(${s.ok ? s.apnea : 0} 次)
${evList}
当夜参数快照
刺激模式
${m.mode || '—'}
脉冲频率 / 刺激时间
${m.hz != null ? m.hz + ' Hz / ' + m.stim + ' s' : '—'}
刺激间隔 / 开机延迟
${m.gap != null ? m.gap + ' s / ' + m.delay + ' min' : '—'}
数据完整性
${s.ok ? '完整 · 无断连' : '缺漏 · 部分时段未上报'}
`; document.getElementById('mFoot').innerHTML = ``; document.getElementById('mask').classList.add('show'); } const DOCTORS = { '张明远':{id:'DR-0021', title:'主治医师', org:'武汉市第一医院', dept:'呼吸内科', dev:3, pat:142, bind:'09月23日', phone:'139****0088'}, '李静':{id:'DR-0023', title:'副主任医师', org:'同济医院', dept:'睡眠医学中心', dev:2, pat:96, bind:'08月02日', phone:'138****4412'}, '赵敏':{id:'DR-0018', title:'主任医师', org:'同济医院', dept:'睡眠医学中心', dev:4, pat:188, bind:'09月10日', phone:'186****5503'}, '孙伟':{id:'DR-0027', title:'主治医师', org:'武汉市中心医院', dept:'耳鼻喉科', dev:1, pat:34, bind:'10月12日', phone:'159****6408'} }; function openDoctor(name){ renderDoctorDetail(name); go('p-doctor-detail'); } /* ---------- 医护详情页 ---------- */ const DOC_META = { '张明远':{sex:'男', age:46, license:'141420100012345', join:'2024-03-12', audit:'2026-08-13 09:05', auditor:'审核员 · 李颖', acct:'启用', last:'今天 09:12', scope:'本院 · 呼吸内科', devs:[ {model:'S1', sn:'PT2026A00317', fw:'v2.4.1', bat:86, pat:'王慧敏', bind:'2026-09-22 21:40', last:'3 分钟前', st:'使用中'}, {model:'S1', sn:'PT2026A00276', fw:'v2.4.1', bat:41, pat:'陈志强', bind:'2026-08-30 19:26', last:'2 小时前', st:'解绑审核中'}, {model:'S1', sn:'PT2026A00408', fw:'v2.4.1', bat:8, pat:'赵文博', bind:'2026-10-12 21:11', last:'3 天前', st:'数据缺漏'} ]}, '李静':{sex:'女', age:41, license:'141420100023871', join:'2025-01-06', audit:'2026-08-02 10:20', auditor:'审核员 · 李颖', acct:'启用', last:'今天 08:40', scope:'本院 · 睡眠医学中心', devs:[ {model:'S2', sn:'PT2026B01044', fw:'v2.6.0', bat:69, pat:'周明辉', bind:'2026-09-27 22:50', last:'26 分钟前', st:'使用中'}, {model:'S2-Pro', sn:'PT2026B00736', fw:'v2.8.3', bat:52, pat:'吴雅琴', bind:'2026-10-02 21:05', last:'昨天 23:10', st:'使用中'} ]}, '赵敏':{sex:'女', age:53, license:'141420100008912', join:'2023-09-18', audit:'2026-09-10 15:32', auditor:'审核员 · 李颖', acct:'启用', last:'今天 08:05', scope:'本院 · 睡眠医学中心', devs:[ {model:'S2', sn:'PT2026B00921', fw:'v2.6.0', bat:74, pat:'李建国', bind:'2026-09-18 20:15', last:'8 分钟前', st:'使用中'}, {model:'S2', sn:'PT2026B00671', fw:'v2.6.0', bat:63, pat:'刘桂芳', bind:'2026-09-15 22:03', last:'12 分钟前', st:'使用中'}, {model:'S4', sn:'PT2027A00412', fw:'v3.1.0', bat:95, pat:'郑海涛', bind:'2026-10-18 23:02', last:'41 分钟前', st:'使用中'}, {model:'S1', sn:'PT2026A00158', fw:'v2.4.1', bat:62, pat:'—', bind:'—', last:'5 天前', st:'待绑定'} ]}, '孙伟':{sex:'男', age:38, license:'141420100031567', join:'2025-06-20', audit:'2026-10-12 11:08', auditor:'审核员 · 李颖', acct:'已禁用', last:'10-17 21:30', scope:'本院 · 耳鼻喉科', transferred:true, devs:[ {model:'S2', sn:'PT2026B00882', fw:'v2.6.0', bat:55, pat:'徐国强', bind:'2026-09-08 21:12', last:'10-17 19:40', st:'待回收'} ]} }; let DD_CUR = '张明远'; function renderDoctorDetail(name){ const key = DOCTORS[name] ? name : '张明远'; const d = DOCTORS[key], m = DOC_META[key] || DOC_META['张明远']; DD_CUR = key; const stCls = {'正常':'g','重点关注':'a','数据缺漏':'r','已暂停':'grey','已禁用':'grey','解绑审核中':'a','未开始':'grey','血氧偏低':'a'}; const kv = arr => arr.map(([k,v])=>`
${k}
${v}
`).join(''); const acctOn = m.acct === '启用'; document.getElementById('ddAv').textContent = key.slice(0,1); document.getElementById('ddName').textContent = key; document.getElementById('ddTags').innerHTML = `${m.acct}` + `${d.title}`; document.getElementById('ddMeta').innerHTML = `${d.id}${d.org} · ${d.dept}${d.phone}在管 ${d.pat} 人 · 绑定 ${d.dev} 台`; const act = document.getElementById('ddAct'); act.textContent = acctOn ? '禁用账号' : '启用账号'; act.className = 'btn btn-p'; act.setAttribute('onclick', acctOn ? "openModal('m-disable')" : "openModal('m-enable')"); /* ---- 1 · 医生基础信息 ---- */ const mod1 = `
1

医生基础信息

执业资质与机构归属

执业信息

来自人员管理名册
${kv([ ['姓名', key + '(' + m.sex + ' · ' + m.age + ' 岁)'], ['人员编码', `${d.id}`], ['手机号', `${d.phone}`], ['职称', d.title], ['所属医院', d.org], ['所属科室', d.dept], ['执业证号', `${m.license}`], ['入职时间', m.join] ])}
`; /* ---- 2 · 绑定设备信息 ---- */ const devRows = m.devs.map(v=>`
睡眠治疗仪 ${v.model}
${v.sn}
${v.pat === '—' ? '未绑定患者' : v.pat} ${v.fw} ${v.bat}% ${v.bind} ${v.last} ${v.st==='使用中' ? '使用中' : (v.st==='待绑定' ? '待绑定' : (v.st==='待回收' ? '待回收' : '' + v.st + ''))} ${v.pat==='—' ? '' : ``} `).join(''); const mod2 = `
2

绑定设备信息

该医师名下正在使用与待处理的设备

设备清单

共 ${m.devs.length} 台 · 数据为设备最近一次上报
${devRows}
设备 / SN 绑定患者 固件版本 电量 绑定时间 最后活跃 状态 操作
`; /* ---- 3 · 在管患者 ---- */ const names = Object.keys(PATIENTS).filter(n=>PATIENTS[n].doc === key); const patRows = names.map((n,i)=>{ const pp = PATIENTS[n], pm = PD_META[n] || {}; const ses = pdSessions(n).filter(x=>x.ok); const ap = ses.length ? (ses.reduce((a,b)=>a+b.apnea,0)/ses.length).toFixed(1) : '—'; const cls = stCls[pp.status] || 'grey'; const hasDev = pp.dev !== '未绑定'; return `
${n.slice(0,1)}
${n}
${pp.id} · ${pp.sex}
${hasDev ? `
${pp.dev.split(' · ')[0]}
${pp.dev.split(' · ')[1]}
` : '未绑定'} ${pm.last || '—'} ${ap === '—' ? '—' : `${ap} 次/夜`} ${pp.adh || '—'} ${pp.status} `; }).join(''); const cntSt = arr => names.filter(n=>arr.includes(PATIENTS[n].status)).length; const sumTotal = m.transferred ? 0 : d.pat; const sumAttn = m.transferred ? 0 : cntSt(['重点关注','血氧偏低']); const sumMiss = m.transferred ? 0 : cntSt(['数据缺漏']); const sumUnbind = m.transferred ? 0 : cntSt(['解绑审核中']); const patBody = (m.transferred || !names.length) ? `
${m.transferred ? '账号已禁用,在管患者已转交' : '暂无在管患者'} ${m.transferred ? '该账号于 10-17 停用,在管 ' + d.pat + ' 名患者已转交至同院其他医师,历史数据保留在患者账号下。' : '医师绑定设备并为患者开启治疗后,在管患者将在此列出。'}
` : `
${patRows}
患者 绑定设备 最近使用 呼吸暂停均值 依从性 状态 操作
显示第 1–${names.length} 条,共 ${d.pat} 条 · 仅列出名册内已建档患者
`; const mod3 = `
3

在管患者列表

当前由该医师负责的患者 · 点击可进入患者详情
在管患者总数
${sumTotal}人
需重点关注
${sumAttn}人
数据缺漏
${sumMiss}人
解绑待处理
${sumUnbind}人

在管患者

按最近使用排序
${patBody}
`; document.getElementById('ddBody').innerHTML = mod1 + mod2 + mod3; } function openDevice(sn){ document.getElementById('drTitle').textContent = 'PETU 睡眠治疗仪'; document.getElementById('drSub').textContent = 'SN · ' + sn; document.getElementById('drBody').innerHTML = `

设备信息

设备名称
PETU 睡眠治疗仪 S1
SN
${sn}
固件版本
v2.4.1
电量
86%
最后活跃
3 分钟前
累计会话
128 次
设备为 BLE 连接、无网络能力,「最后活跃」为最近一次 App 上报时间,不代表设备实时在线。

当前绑定

王
王慧敏
患者 · 张明远医生在管
生效中

近 7 日活跃

${[52,48,55,50,57,54,59].map((h,i)=>``).join('')} ${['三','四','五','六','日','一','二'].map((d,i)=>`${d}`).join('')}
`; document.getElementById('drFoot').innerHTML = ` `; document.getElementById('drawer').classList.add('show'); } function closeDrawer(){ document.getElementById('drawer').classList.remove('show'); } /* ================= 审核动作 ================= */ const AUDIT_DOCS = [ {name:'王海涛', gender:'男', code:'DR-0031', phone:'137****9931', hospital:'湖北省人民医院', dept:'耳鼻喉科', title:'主治医师', time:'提交于 10月17日 14:20 · 等待 1 天 19 小时'}, {name:'陈晓芸', gender:'女', code:'DR-0032', phone:'151****2780', hospital:'武汉市中心医院', dept:'呼吸与危重症医学科', title:'住院医师', time:'提交于 10月17日 18:05 · 等待 1 天 15 小时'}, {name:'周立群', gender:'男', code:'DR-0033', phone:'133****7712', hospital:'武汉市第三医院', dept:'呼吸内科', title:'主治医师', time:'提交于 10月18日 09:12 · 等待 1 天'}, {name:'吴梅', gender:'女', code:'DR-0034', phone:'180****3155', hospital:'湖北省中医院', dept:'呼吸内科', title:'医师', time:'已驳回', rejected:true, reason:'在职证明与实际机构不一致'} ]; function openAudit(i){ const d = AUDIT_DOCS[i] || AUDIT_DOCS[0]; if(d.rejected){ toast('该申请已驳回:' + d.reason); return; } document.getElementById('mTitle').textContent = '医护资质审核'; document.getElementById('mSub').textContent = d.code + ' · ' + d.name; document.getElementById('mBody').innerHTML = `
姓名${d.name}(${d.gender})
人员编码${d.code}
手机号${d.phone}
职称${d.title}
医疗机构${d.hospital}
科室${d.dept}
执业医师证.jpg 在职证明.pdf
${d.time}。通过后该人员可登录医生端并绑定设备;驳回后仅可见「审核进度」页。
`; document.getElementById('mFoot').innerHTML = ` `; document.getElementById('mask').classList.add('show'); } function approveAudit(btn, name){ const card = btn.closest('.audit-card, tr'); card.style.transition='.25s'; card.style.opacity='0'; card.style.transform='translateY(-6px)'; setTimeout(()=>{ card.remove(); toast('已通过:' + name); }, 240); } function openReject(){ openModal('m-reject'); } function openSession(id){ document.getElementById('mTitle').textContent = '会话详情'; document.getElementById('mSub').textContent = id; document.getElementById('mBody').innerHTML = sessionDetail(); document.getElementById('modalBox').classList.add('wide'); document.getElementById('mFoot').innerHTML = ``; document.getElementById('mask').classList.add('show'); } function openPrivacyPreview(){ document.getElementById('mTitle').textContent = '隐私协议预览'; document.getElementById('mSub').textContent = 'v2.1 · 生效中 · 患者端 / 医生端'; document.getElementById('modalBox').classList.add('wide'); document.getElementById('mBody').innerHTML = `

睡眠监测系统隐私协议

版本 v2.1 · 2026年9月30日生效

一、我们收集的信息

手机号(账号注册与登录)、设备 SN 与固件版本、会话起止时间、刺激幅度记录、设备上报的呼吸事件记录。

二、产品性质与责任边界

本软件为健康管理辅助工具,不构成医疗器械,不提供医学诊断。数据为设备上报的原始记录,未经医学解读,不替代医务人员的专业判断。

三、数据同步范围

呼吸暂停事件次数仅统计已同步数据。设备离线期间未补传的事件不计入统计。

四、数据存储与导出

会话数据归属患者账号,不随设备绑定关系变动。后台导出默认脱敏并添加水印,操作全程留痕。

`; document.getElementById('mFoot').innerHTML = ``; document.getElementById('mask').classList.add('show'); } /* 初始化 */ renderNav(); renderPatientDetail('王慧敏'); renderDoctorDetail('张明远'); /* 支持深链:#p-xxx 直接进入对应页面(便于评审时直接分享单页) */ (function(){ const h = location.hash.replace('#',''); if(h && document.getElementById(h)){ doLogin(); go(h); } })();
取消
访问码: gfthnvhi