Compare commits

...
16 Commits
Author SHA1 Message Date
liuyunqin 80b0c78bdd fix: 体检预约跟踪侧边栏菜单位置调整至已检员工之后 2026-09-20 10:50:41 +08:00
fengpu 4767c24d3d Merge branch 'master' of https://gitea.rk-health.com/yixiong/platform-prototype 2026-09-20 10:48:54 +08:00
fengpu 44d7d0b219 feat: 导航页样式优化 2026-09-20 10:48:51 +08:00
liuyunqin 8e6c10f0bc feat: 健康体检检后新增结果动态综合统计;补全6个页面文档并重拍截图;体重管理「身高秤」统一更名「体重秤」
- 检后「体检统计汇总」新增「结果动态综合统计」页面(2×2 网格4张堆积柱状图,维度固定性别/年龄/部门/岗位,纯手写SVG),23个检后页面侧边栏及 index.html SITE_MAP 同步新增入口
- 按实际页面补全6个此前未入册的页面文档并配截图:检中体检预约跟踪、检后单位群体报告、检后体检影像管理4页;PRD/CHANGELOG/页面清单编号顺延
- 体重管理「身高秤」来源统一更名为「体重秤」(员工端/后台/应急端 PRD 及页面同步)
2026-09-20 10:34:12 +08:00
fengpu a900886061 Merge branch 'master' of https://gitea.rk-health.com/yixiong/platform-prototype 2026-09-18 17:45:32 +08:00
fengpu 71ddc7fa0a feat: 健康服务APP中新增营养师角色 ,设计营养师工作台页面原型 2026-09-18 17:45:29 +08:00
fengpu 63407a20c2 feat:健康体检后台检后新增影像数据管理;基础配置中新增前置服务器菜单 2026-09-18 17:44:26 +08:00
liuyunqin 5e479bc6fe feat: 健康体检与一线医疗PRD同步
健康体检:
- 新增「体检预约跟踪」「单位群体报告」「单位体检项目统计」三个页面并接入侧边栏与首页导航
- 员工体检大表增加结果/单位/参考范围三列、统计行与按结果排序
- 侧边栏统一「员工体检报告」命名,移除「患病率」列
- 单位体检费用统计移除部门列并加宽费用明细弹窗

一线医疗:
- 救护车配置表单字段重组,新增年份选择控件、GPS/车辆照片,移除批量删除
- 救护车出勤筛选改为单位→医疗点级联 + 车牌号搜索,出勤类型扩展为六类
- 侧边栏「救护车出勤」菜单项调整位置
2026-09-18 17:30:07 +08:00
fengpu b0479ee9d1 feat: 检后模块新增影像数据管理菜单 2026-09-18 11:51:58 +08:00
lianlonggang c3c51978de docs: 体检须知预约短信在模板中体现,这里不用 2026-09-18 08:52:14 +08:00
liuyunqin 3ae4e98f9a docs: 健康体检PRD同步17个页面最新改动并重截16张截图(报告弹窗三页签专业版式、封面图/视频上传提示、同步医院体检项目弹窗、三类计划体检年份锁定、组合模板金额列、批量导入人员、预选统计移除医院Tab、预约信息列后移、实时预约日期默认当日起30日、CTA统计移除问卷列、常规报告补录与确认弹窗、员工费用明细筛选导出、胃肠镜查看报告、结果异常精简);screenshot-prd.js支持传入页面名前缀只重截指定页 2026-09-17 18:31:16 +08:00
liuyunqin cbf21d1817 feat: 医疗PAD健康宣教抽屉移除时长字段 2026-09-17 18:10:30 +08:00
liuyunqin f1f412d897 feat: 体重管理基础信息移除身份证号与手机号展示 2026-09-17 18:10:30 +08:00
liuyunqin 05f83885ac feat: 健康体检后台单位体检费用统计按单位×体检医院拆行
- 列表增加体检医院/签约医院列并去掉总人数列,原6条单位汇总拆为16条医院明细行
- 缺少项目弹窗增加项目费用/折扣后费用列,统计明细与员工明细的缺少项目数标蓝可点击
- 员工费用明细弹窗新增筛选栏与导出列表数据按钮
- 同步更新 PRD 与 CHANGELOG
2026-09-17 18:10:24 +08:00
liuyunqin 9cd970a31e feat: 一线医疗身份证与联系电话去除脱敏 · 员工分配统计概览卡改员工口径
- 全站演示数据身份证号与联系电话由脱敏改为完整展示
- config-allocate-stat 概览卡改为员工总数量/已分配/未分配三卡,名册统计文案上移
- 同步更新 CHANGELOG
2026-09-17 18:10:16 +08:00
liuyunqin a54d933841 feat: 健康体检员工端取消预约二次确认、胃肠镜预选可重填及报告筛选调整
- 预约日期页取消预约改为二次确认
- 胃肠镜医院预选支持重新填写调整(至少2家/最多3家)
- 现场体检状态口径改为已预约/无预约
- 报告列表新增年份/类型筛选
- 同步更新 PRD 与演示截图
2026-09-17 18:10:07 +08:00
176 changed files with 12256 additions and 1964 deletions
+1 -1
View File
@@ -130,7 +130,7 @@ Web 管理端和员工端 APP 的其余模块(系统管理、运动管理、
- 2026-09-07:净菜类型拆分独立管理(commit 891678d)——①`din-ingredient.html`/`din-ingredient-add.html` 净菜类型按**规格行级**维护「可食部(%)」字段(位于规格别名后,新增规格时随行出现,新增/编辑/详情三页同步);②食材列表操作区「食材分类管理」按钮后新增「净菜管理」入口;③新建 `din-clean-veg-mgmt.html` 净菜管理二级页:统计卡片 + 食材名称(下拉搜索)/食材编码/净菜类型(净菜俗名/净菜标准名/规格俗名三源关键字)/切配形状筛选 + 异步导出按钮组(导出列表数据+查看导出任务,样式对齐全仓列表页惯例)+ 列表组级列 rowspan 合并、可食部/规格别名/切配规格/刀工按规格逐行拆分(组内虚线分隔、规格列区纵向分隔线)+ 先选食材回显已有净菜类型再新增/编辑的维护弹窗;SITE_MAP 与营养模块 CHANGELOG 同步。(相关决策:[[decisions.md#净菜类型数据模型与净菜管理页展示模式]]
- 2026-09-07:全仓截图脚本与截图质量修复(commit 7f18470)——assistant/expert/health-monitor 截图脚本失效页面引用改回现存文件名并清理孤儿截图;weight-main-v2 初始化改为解析 `?type=` 参数,修复设计总览六种目标状态截图雷同问题,重拍 weight-main/bluetooth-scale 共 10 张;补拍 PRD 引用但从未生成的视频进行中截图;体重管理 PRD 修复两处截图断链。
- 2026-09-04:医疗点PAD端首版原型(commit 15ca37b)——新建 `other-module/medical-pad/` 20 页(1280×800 横屏,抽屉交互页内实现),SITE_MAP 与项目背景尺寸规范同步,新增模块 CHANGELOG;同提交:全站演示数据脱敏(示例人名王建国→石维国、周明远→曲志明,机构长庆油田→CQ油田,覆盖约 30 页)、现场体检签到码改条形码/二维码双选项卡切换、新增健康长寿系统升级改造详细规划 V8 文档。(相关规范:[[feedback_medical_pad_migration.md]]
- 2026-09-04:医疗点PAD端首版原型(commit 15ca37b)——新建 `other-module/medical-pad/` 20 页(1280×800 横屏,抽屉交互页内实现),SITE_MAP 与项目背景尺寸规范同步,新增模块 CHANGELOG;同提交:全站演示数据脱敏(示例人名王建国→石维国、周明远→曲志明,机构CQ能源→CQ油田,覆盖约 30 页)、现场体检签到码改条形码/二维码双选项卡切换、新增健康长寿系统升级改造详细规划 V8 文档。(相关规范:[[feedback_medical_pad_migration.md]]
- 2026-09-04:体重管理员工端设计总览重构(commit 29a09e9)——总览页按目标承诺与签署/体重主页/每日打卡/历史记录/建议与报告/蓝牙设备六大主线重排,页面清单全面切换到 weight-main-v2/goal-management-v2 等 v2 新版页面,新增 playwright 统一截图脚本 `screenshot-weight.mjs` 并重新生成全部截图。
- 2026-09-03~09-04:健康体检员工端与管理端多轮完善(commit 118d143、05e157e)——员工端:选项目页费用横幅重做为体检额度卡片(额度使用进度条+必检/选检/可用额度三格,年度额度 1600,新增心脏彩超与胃幽门螺杆菌两项选检),"我的预约"更名重做为"现场体检",胃肠镜预约结果页成功/失败两态演示;管理端:体检报告模板更名体检导入模板管理、模板额度配置更名群体额度配置、大病人员信息新增弹窗(员工搜索下拉+信息卡片)、指标异常通知固定列+发送通知/短信、单位/胃肠镜预约统计改单位级别 TAB。
- 2026-09-02~09-03:健康体检预约流程与检中页面完善(commit a443c61、0d1c770、37ae054、3a838d0、2213106)——员工端"我的预约"合并单页+新增指标纵向对比与消息中心;胃肠镜医院预选征集/已提交页、检前首页预选通知条、选日期页预约状态条与可约窗口限制(常规近30天/胃肠镜7~30天)、预约结果页成功/失败演示与取消预约二次确认;管理端新增大病人员信息/指标异常通知/已选项目员工信息页,胃肠镜体检计划管理新增/编辑/详情抽屉(承检医院多选+机构树选人),组合模板/例外配置/医院费用/单位费用统一详情弹窗与编辑抽屉,检中页更名员工预约体检跟踪、统一员工编号筛选与列、体检编号更名体检编码。
+1 -1
View File
@@ -238,7 +238,7 @@
- 员工信息:姓名(张晓红)、性别、年龄、部门
- 数据对比表:初始数据 / 目标数据 / 当前数据(体重、BMI、体脂率、腰围)
- 变化指标:绿色显示下降数值
- 体重记录列表:按时间倒序,每条显示时间、体重、体脂率、腰围、来源标签(身高秤、蓝牙称、体脂仪、自记)
- 体重记录列表:按时间倒序,每条显示时间、体重、体脂率、腰围、来源标签(体重秤、蓝牙称、体脂仪、自记)
**交互说明:**
- 点击返回:返回体重记录页
@@ -158,7 +158,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="record-item__metric"><div class="record-item__metric-label">体重</div><div class="record-item__metric-val">66.8<span class="record-item__metric-unit">kg</span></div></div>
<div class="record-item__metric"><div class="record-item__metric-label">体脂率</div><div class="record-item__metric-val">29.1<span class="record-item__metric-unit">%</span></div></div>
<div class="record-item__metric"><div class="record-item__metric-label">腰围</div><div class="record-item__metric-val">80.5<span class="record-item__metric-unit">cm</span></div></div>
<div class="record-item__source"><span class="source-badge source-badge--scale">身高</span></div>
<div class="record-item__source"><span class="source-badge source-badge--scale">体重</span></div>
</div>
</div>
+2 -1
View File
@@ -129,7 +129,8 @@ function initTabLinks() {
professional: '../professional',
operator: '../operator',
onsite: '../onsite',
admin: '../admin'
admin: '../admin',
nutritionist: '../nutritionist'
};
var base = roleBaseMap[role] || '../expert';
document.getElementById('link-workbench').href = base + '/workbench/index.html';
+2 -1
View File
@@ -265,7 +265,8 @@ function initTabLinks() {
professional: '../professional',
operator: '../operator',
onsite: '../onsite',
admin: '../admin'
admin: '../admin',
nutritionist: '../nutritionist'
};
var base = roleBaseMap[role] || '../expert';
document.getElementById('link-workbench').href = base + '/workbench/index.html';
@@ -0,0 +1,159 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.back-btn { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<div class="nav-bar">
<button class="back-btn" onclick="window.location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title" id="kd-nav-title">知识详情</span>
</div>
<div class="scroll-content" style="padding:16px;">
<div id="kd-content" style="background:#fff;border-radius:16px;padding:20px;box-shadow:0 1px 6px rgba(0,0,0,0.06);"></div>
<div style="height:16px;"></div>
</div>
</div>
<script>
/* 营养知识库数据集合 */
var KB_DATA = {
'pyramid': {
icon: '🥗', bg: '#f6ffed', title: '中国居民膳食宝塔(2022', cat: '膳食营养', date: '2026-03-15',
summary: '膳食宝塔是中国居民平衡膳食的指导框架,将食物分为五大类,按推荐摄入量由多到少排列,是制定食谱与营养指导的基础依据。',
items: [
{ color: '#52c41a', name: '谷薯类', desc: '每日 250-400g,其中全谷物和杂豆 50-150g,薯类 50-100g。谷类是膳食能量的主要来源,建议粗细搭配。' },
{ color: '#1677ff', name: '蔬菜水果', desc: '每日蔬菜 300-500g(深色蔬菜占一半),水果 200-350g。保证维生素、矿物质和膳食纤维摄入。' },
{ color: '#fa8c16', name: '畜禽鱼蛋', desc: '每日畜禽肉 40-75g、水产品 40-75g、蛋类 40-50g。优先选择鱼禽,减少肥肉和加工肉制品。' },
{ color: '#722ed1', name: '奶豆坚果', desc: '每日奶及奶制品 300-500g,大豆及坚果 25-35g。是优质蛋白和钙的重要来源。' }
]
},
'macro': {
icon: '🍚', bg: '#e6f4ff', title: '三大营养素摄入指南', cat: '膳食营养', date: '2026-03-10',
summary: '蛋白质、脂肪、碳水化合物是人体必需的三大产能营养素,合理配比是维持健康体重和代谢功能的关键。',
items: [
{ color: '#1677ff', name: '碳水化合物', desc: '供能比 50-65%。优先选择全谷物、杂豆、薯类等复合碳水,限制添加糖摄入(每日<25g)。' },
{ color: '#52c41a', name: '蛋白质', desc: '供能比 15-20%。优质蛋白来源包括鱼、禽、蛋、奶、豆制品。轻体力成人每日约 1.0g/kg 体重。' },
{ color: '#fa8c16', name: '脂肪', desc: '供能比 20-30%。优先不饱和脂肪酸(橄榄油、坚果、鱼类),限制饱和脂肪(<10%)和反式脂肪(<1%)。' }
]
},
'vitamin': {
icon: '🍎', bg: '#fff7e6', title: '维生素与矿物质需求', cat: '膳食营养', date: '2026-03-05',
summary: '维生素和矿物质虽然需求量小,却是维持正常生理功能不可或缺的微量营养素,缺乏时会出现特定症状。',
items: [
{ color: '#fa8c16', name: '维生素 C', desc: '每日 100mg。来源:新鲜果蔬(猕猴桃、橙子、青椒)。缺乏可致牙龈出血、伤口愈合不良。' },
{ color: '#1677ff', name: '钙', desc: '每日 800-1000mg。来源:奶制品、豆制品、绿叶菜。缺乏可致骨质疏松、肌肉痉挛。' },
{ color: '#ff4d4f', name: '铁', desc: '男性每日 12mg、女性 20mg。来源:动物肝脏、瘦肉、动物血。缺乏可致缺铁性贫血。' }
]
},
'fatloss': {
icon: '🏃', bg: '#f6ffed', title: '科学减脂饮食原则', cat: '减脂控糖', date: '2026-02-28',
summary: '减脂的核心是热量缺口,但必须在保证营养均衡的前提下进行,避免极端节食导致肌肉流失和代谢下降。',
items: [
{ color: '#52c41a', name: '热量缺口', desc: '每日热量缺口 300-500kcal,减重速度以每周 0.5-1kg 为宜。避免过度节食(不低于基础代谢)。' },
{ color: '#1677ff', name: '高蛋白饮食', desc: '蛋白质供能比可提高至 20-30%,增加饱腹感并减少肌肉流失。选择鸡胸、鱼虾、蛋白、豆制品。' },
{ color: '#fa8c16', name: '控制精制碳水', desc: '减少白米饭、白面包等精制碳水,替换为全谷物、杂豆,稳定血糖、增强饱腹感。' }
]
},
'lowgi': {
icon: '🍬', bg: '#e6f4ff', title: '低 GI 饮食控糖策略', cat: '减脂控糖', date: '2026-02-20',
summary: '低升糖指数(GI)饮食可延缓血糖上升速度,是糖尿病管理和预防的重要饮食策略。',
items: [
{ color: '#1677ff', name: '食物选择', desc: '优先低 GI 食物:全谷物、豆类、大部分蔬菜、低糖水果。避免精制米面、含糖饮料、糕点。' },
{ color: '#52c41a', name: '进餐顺序', desc: '先吃蔬菜和蛋白质,再吃主食,可显著降低餐后血糖峰值,改善血糖控制。' },
{ color: '#fa8c16', name: '烹饪方式', desc: '主食不要煮得过烂,粗细搭配;避免勾芡、油炸;控制每餐主食总量,少量多餐。' }
]
},
'hyperlipid': {
icon: '🫀', bg: '#fff7e6', title: '高血脂饮食管理', cat: '慢性病营养', date: '2026-03-01',
summary: '高血脂是动脉粥样硬化的重要危险因素,饮食管理重点是限制饱和脂肪和反式脂肪,增加膳食纤维。',
items: [
{ color: '#ff4d4f', name: '限制饱和脂肪', desc: '每日饱和脂肪<总能量10%。减少肥肉、动物内脏、黄油、椰子油,选择瘦肉和低脂奶制品。' },
{ color: '#fa8c16', name: '杜绝反式脂肪', desc: '避免人造黄油、植脂末、油炸食品、起酥点心。反式脂肪显著升高 LDL-C,增加心血管风险。' },
{ color: '#52c41a', name: '增加膳食纤维', desc: '每日 25-30g 膳食纤维。多吃燕麦、豆类、蔬菜、水果,可降低胆固醇吸收。' }
]
},
'uric': {
icon: '🦴', bg: '#fff1f0', title: '高尿酸人群饮食禁忌', cat: '慢性病营养', date: '2026-02-15',
summary: '高尿酸血症是痛风的生化基础,通过限制高嘌呤食物、增加水分摄入可有效降低尿酸水平。',
items: [
{ color: '#ff4d4f', name: '限制高嘌呤食物', desc: '避免动物内脏、浓肉汤、海鲜(沙丁鱼、贝类)、啤酒。嘌呤>150mg/100g 的食物应严格限制。' },
{ color: '#fa8c16', name: '低嘌呤食物选择', desc: '优先选择蛋类、奶类、蔬菜、水果、谷物。肉类焯水后再烹饪可减少嘌呤含量。' },
{ color: '#1677ff', name: '多饮水', desc: '每日饮水 2000ml 以上,促进尿酸排泄。避免含糖饮料和酒精,尤其是啤酒和白酒。' }
]
},
'safety': {
icon: '🧪', bg: '#f6ffed', title: '食品添加剂使用规范', cat: '食品安全', date: '2026-02-25',
summary: '食品添加剂在合规使用范围内是安全的,了解常见添加剂的用途和限量有助于科学评估食品安全。',
items: [
{ color: '#52c41a', name: '防腐剂', desc: '如苯甲酸钠、山梨酸钾。用于抑制微生物生长,延长保质期。在规定限量内使用安全。' },
{ color: '#1677ff', name: '抗氧化剂', desc: '如 BHA、BHT、维生素 E。防止油脂氧化酸败,常用于食用油和坚果制品。' },
{ color: '#fa8c16', name: '着色剂', desc: '如焦糖色、胭脂红。用于改善食品色泽。天然色素(姜黄、红曲)安全性更高。' }
]
},
'trace': {
icon: '🔍', bg: '#e6f4ff', title: '食材溯源与质量安全', cat: '食品安全', date: '2026-02-10',
summary: '食材溯源是通过溯源码记录食材从种植养殖、加工、运输到餐桌的全链路信息,保障食品安全可追溯。',
items: [
{ color: '#1677ff', name: '溯源码体系', desc: '每一批次食材赋予唯一溯源码,关联产地、检测报告、流转节点,实现一物一码、全程可查。' },
{ color: '#52c41a', name: '质量检测节点', desc: '毛菜入库时进行农残/重金属/微生物检测,净菜加工后二次质检,合格品方可进入厨房。' },
{ color: '#fa8c16', name: '异常处置', desc: '检测不合格的食材立即隔离并追溯同批次产品,防止问题食材流入餐桌。' }
]
}
};
/* 页面加载时根据URL参数渲染知识详情 */
function renderDetail() {
var params = new URLSearchParams(window.location.search);
var key = params.get('key') || 'pyramid';
var d = KB_DATA[key];
if (!d) d = KB_DATA['pyramid'];
document.getElementById('kd-nav-title').textContent = d.title;
var itemsHtml = d.items.map(function(it) {
return '<div style="background:#f7f8fa;border-radius:10px;padding:14px;margin-bottom:12px;">' +
'<div style="font-size:13px;font-weight:600;color:' + it.color + ';margin-bottom:6px;">' + it.name + '</div>' +
'<div style="font-size:13px;color:#444;line-height:1.7;">' + it.desc + '</div>' +
'</div>';
}).join('');
document.getElementById('kd-content').innerHTML =
'<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid #f5f5f5;">' +
'<div style="width:48px;height:48px;border-radius:12px;background:' + d.bg + ';display:flex;align-items:center;justify-content:center;font-size:24px;">' + d.icon + '</div>' +
'<div><div style="font-size:17px;font-weight:700;color:#1a1a1a;">' + d.title + '</div><div style="font-size:12px;color:#999;margin-top:3px;">' + d.cat + ' · 更新于 ' + d.date + '</div></div>' +
'</div>' +
'<div style="font-size:15px;font-weight:700;color:#1a1a1a;margin-bottom:8px;">概述</div>' +
'<div style="font-size:14px;color:#444;line-height:1.8;margin-bottom:16px;">' + d.summary + '</div>' +
'<div style="font-size:15px;font-weight:700;color:#1a1a1a;margin-bottom:8px;">主要内容</div>' +
itemsHtml;
}
renderDetail();
</script>
</body>
</html>
@@ -0,0 +1,153 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.sub-tabs { display: flex; gap: 8px; padding: 10px 16px; background: #fff; flex-shrink: 0; }
.sub-tab { font-size: 12px; padding: 4px 14px; border-radius: 16px; background: #f5f5f5; color: #666; cursor: pointer; transition: all 0.2s; white-space: nowrap; }
.sub-tab.active { background: #e6f4ff; color: #1677ff; font-weight: 600; }
.tab-bar { height: 83px; background: #fff; border-top: 1px solid #e8e8e8; display: flex; align-items: flex-start; padding-top: 8px; flex-shrink: 0; }
.tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; padding: 4px 0; position: relative; text-decoration: none; }
.tab-item .tab-icon { font-size: 22px; line-height: 1; }
.tab-item .tab-label { font-size: 10px; color: #999; font-weight: 500; }
.tab-item.active .tab-label { color: #1677ff; }
.tab-badge { position: absolute; top: 2px; right: 18px; background: #ff4d4f; color: #fff; font-size: 10px; font-weight: 700; min-width: 16px; height: 16px; border-radius: 8px; display: flex; align-items: center; justify-content: center; padding: 0 4px; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 蓝色渐变头部 -->
<div style="background:linear-gradient(135deg,#1677ff,#0958d9);padding:16px 20px 20px;flex-shrink:0;">
<div style="color:rgba(255,255,255,0.75);font-size:12px;margin-bottom:2px;">营养专业参考</div>
<div style="color:#fff;font-size:20px;font-weight:700;">知识库</div>
</div>
<!-- 子分类Tab -->
<div class="sub-tabs">
<div class="sub-tab active" onclick="switchKbTab(this,'kb-diet')">膳食营养</div>
<div class="sub-tab" onclick="switchKbTab(this,'kb-weight')">减脂控糖</div>
<div class="sub-tab" onclick="switchKbTab(this,'kb-chronic')">慢性病营养</div>
<div class="sub-tab" onclick="switchKbTab(this,'kb-safe')">食品安全</div>
</div>
<div class="scroll-content">
<!-- 膳食营养 -->
<div id="kb-diet">
<div style="padding:12px 16px 0;">
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('pyramid')">
<div style="width:44px;height:44px;border-radius:10px;background:#f6ffed;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🥗</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">中国居民膳食宝塔(2022</div><div style="font-size:12px;color:#999;margin-top:3px;">五大类食物推荐摄入量与搭配原则</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('macro')">
<div style="width:44px;height:44px;border-radius:10px;background:#e6f4ff;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🍚</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">三大营养素摄入指南</div><div style="font-size:12px;color:#999;margin-top:3px;">蛋白质、脂肪、碳水化合物的合理配比</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('vitamin')">
<div style="width:44px;height:44px;border-radius:10px;background:#fff7e6;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🍎</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">维生素与矿物质需求</div><div style="font-size:12px;color:#999;margin-top:3px;">常见微量营养素的食物来源与缺乏症状</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 减脂控糖 -->
<div id="kb-weight" style="display:none;">
<div style="padding:12px 16px 0;">
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('fatloss')">
<div style="width:44px;height:44px;border-radius:10px;background:#f6ffed;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🏃</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">科学减脂饮食原则</div><div style="font-size:12px;color:#999;margin-top:3px;">热量缺口与营养均衡的减脂方案</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('lowgi')">
<div style="width:44px;height:44px;border-radius:10px;background:#e6f4ff;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🍬</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">低 GI 饮食控糖策略</div><div style="font-size:12px;color:#999;margin-top:3px;">低升糖指数食物选择与餐序调整</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 慢性病营养 -->
<div id="kb-chronic" style="display:none;">
<div style="padding:12px 16px 0;">
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('hyperlipid')">
<div style="width:44px;height:44px;border-radius:10px;background:#fff7e6;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🫀</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">高血脂饮食管理</div><div style="font-size:12px;color:#999;margin-top:3px;">限制饱和脂肪与反式脂肪的摄入</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('uric')">
<div style="width:44px;height:44px;border-radius:10px;background:#fff1f0;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🦴</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">高尿酸人群饮食禁忌</div><div style="font-size:12px;color:#999;margin-top:3px;">嘌呤含量分级与低嘌呤饮食建议</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 食品安全 -->
<div id="kb-safe" style="display:none;">
<div style="padding:12px 16px 0;">
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('safety')">
<div style="width:44px;height:44px;border-radius:10px;background:#f6ffed;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🧪</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">食品添加剂使用规范</div><div style="font-size:12px;color:#999;margin-top:3px;">常见添加剂的安全性与使用限量</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div style="background:#fff;border-radius:10px;padding:12px 14px;display:flex;align-items:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:10px;cursor:pointer;" onclick="goDetail('trace')">
<div style="width:44px;height:44px;border-radius:10px;background:#e6f4ff;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;">🔍</div>
<div style="flex:1;"><div style="font-size:14px;font-weight:600;color:#1a1a1a;">食材溯源与质量安全</div><div style="font-size:12px;color:#999;margin-top:3px;">从田间到餐桌的全链路安全把控</div></div>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
</div>
<div style="height:16px;"></div>
</div>
</div>
<!-- 底部Tab栏 -->
<div class="tab-bar">
<a class="tab-item" href="../workbench/index.html"><span class="tab-icon">🏥</span><span class="tab-label">工作台</span><span class="tab-badge">2</span></a>
<a class="tab-item active" href="./index.html"><span class="tab-icon">📚</span><span class="tab-label">知识库</span></a>
<a class="tab-item" href="../../ai-assistant/index.html"><span class="tab-icon">🤖</span><span class="tab-label">AI助手</span></a>
<a class="tab-item" href="../../message/index.html"><span class="tab-icon">💬</span><span class="tab-label">消息</span><span class="tab-badge">2</span></a>
<a class="tab-item" href="../profile/index.html"><span class="tab-icon">👤</span><span class="tab-label">我的</span></a>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
/* 切换知识库分类Tab */
function switchKbTab(el, panelId) {
el.closest('.sub-tabs').querySelectorAll('.sub-tab').forEach(function(t) { t.classList.remove('active'); });
el.classList.add('active');
['kb-diet','kb-weight','kb-chronic','kb-safe'].forEach(function(id) {
document.getElementById(id).style.display = id === panelId ? 'block' : 'none';
});
}
/* 跳转到知识详情页 */
function goDetail(key) {
window.location.href = './detail.html?key=' + key;
}
</script>
</body>
</html>
@@ -0,0 +1,82 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.back-btn { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.btn-primary { background: #1677ff; color: #fff; border: none; border-radius: 10px; padding: 12px 24px; font-size: 15px; font-weight: 600; cursor: pointer; width: 100%; }
.modal-center { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 200; align-items: center; justify-content: center; }
.modal-center.active { display: flex; }
.modal-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.modal-box .modal-title { padding: 20px 20px 8px; font-size: 17px; font-weight: 700; text-align: center; color: #1a1a1a; }
.modal-box .modal-body { padding: 8px 20px 20px; font-size: 14px; color: #666; text-align: center; line-height: 1.6; }
.modal-box .modal-actions { display: flex; border-top: 1px solid #f0f0f0; }
.modal-box .modal-actions button { flex: 1; padding: 14px; font-size: 16px; cursor: pointer; background: none; border: none; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<div class="nav-bar">
<button class="back-btn" onclick="location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">修改密码</span>
</div>
<div class="scroll-content" style="padding:16px;">
<div style="background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 6px rgba(0,0,0,0.06);margin-bottom:20px;">
<div style="display:flex;align-items:center;padding:14px 16px;border-bottom:1px solid #f5f5f5;">
<span style="font-size:14px;color:#999;width:80px;flex-shrink:0;">当前密码</span>
<input type="password" placeholder="请输入当前密码" style="flex:1;border:none;font-size:14px;color:#1a1a1a;outline:none;text-align:right;" />
</div>
<div style="display:flex;align-items:center;padding:14px 16px;border-bottom:1px solid #f5f5f5;">
<span style="font-size:14px;color:#999;width:80px;flex-shrink:0;">新密码</span>
<input type="password" placeholder="请输入新密码" style="flex:1;border:none;font-size:14px;color:#1a1a1a;outline:none;text-align:right;" />
</div>
<div style="display:flex;align-items:center;padding:14px 16px;">
<span style="font-size:14px;color:#999;width:80px;flex-shrink:0;">确认新密码</span>
<input type="password" placeholder="再次输入新密码" style="flex:1;border:none;font-size:14px;color:#1a1a1a;outline:none;text-align:right;" />
</div>
</div>
<button onclick="openModal('modal-pwd-saved')" class="btn-primary">保存修改</button>
</div>
<div id="modal-pwd-saved" class="modal-center">
<div class="modal-box">
<div class="modal-title">修改成功</div>
<div class="modal-body">密码已更新,下次登录请使用新密码。</div>
<div class="modal-actions">
<button onclick="closeModal('modal-pwd-saved');location.href='./index.html'" style="color:#1677ff;font-weight:600;flex:1;padding:14px;font-size:16px;cursor:pointer;background:none;border:none;">确定</button>
</div>
</div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
function openModal(id) { var m = document.getElementById(id); if (m) m.classList.add('active'); }
function closeModal(id) { var m = document.getElementById(id); if (m) m.classList.remove('active'); }
</script>
</body>
</html>
@@ -0,0 +1,92 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.back-btn { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.nav-right { margin-left: auto; color: #1677ff; font-size: 14px; cursor: pointer; background: none; border: none; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<div class="nav-bar">
<button class="back-btn" onclick="location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">个人信息设置</span>
<button class="nav-right">保存</button>
</div>
<div class="scroll-content" style="padding:16px;">
<!-- 头像区 -->
<div style="background:#fff;border-radius:12px;padding:20px;margin-bottom:12px;text-align:center;box-shadow:0 1px 6px rgba(0,0,0,0.06);">
<div style="position:relative;display:inline-block;">
<div style="width:80px;height:80px;border-radius:50%;background:linear-gradient(135deg,#e8f4ff,#bdd7ff);display:flex;align-items:center;justify-content:center;font-size:40px;">🥗</div>
<div style="position:absolute;bottom:0;right:0;width:26px;height:26px;background:#1677ff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;border:2px solid #fff;cursor:pointer;">📷</div>
</div>
<div style="font-size:13px;color:#999;margin-top:10px;">点击更换头像</div>
</div>
<!-- 擅长领域 -->
<div style="font-size:13px;font-weight:600;color:#999;margin:0 4px 8px;">擅长领域</div>
<div style="background:#fff;border-radius:12px;padding:14px 16px;box-shadow:0 1px 6px rgba(0,0,0,0.06);margin-bottom:16px;">
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<div onclick="toggleTag(this)" style="padding:6px 14px;border-radius:16px;font-size:13px;background:#1677ff;color:#fff;border:1px solid #1677ff;cursor:pointer;">减脂</div>
<div onclick="toggleTag(this)" style="padding:6px 14px;border-radius:16px;font-size:13px;background:#1677ff;color:#fff;border:1px solid #1677ff;cursor:pointer;">控糖</div>
<div onclick="toggleTag(this)" style="padding:6px 14px;border-radius:16px;font-size:13px;background:#1677ff;color:#fff;border:1px solid #1677ff;cursor:pointer;">增肌</div>
<div onclick="toggleTag(this)" style="padding:6px 14px;border-radius:16px;font-size:13px;background:#f5f5f5;color:#666;border:1px solid #e8e8e8;cursor:pointer;">高血脂</div>
<div onclick="toggleTag(this)" style="padding:6px 14px;border-radius:16px;font-size:13px;background:#f5f5f5;color:#666;border:1px solid #e8e8e8;cursor:pointer;">高尿酸</div>
<div onclick="toggleTag(this)" style="padding:6px 14px;border-radius:16px;font-size:13px;background:#f5f5f5;color:#666;border:1px solid #e8e8e8;cursor:pointer;">术后营养</div>
<div onclick="toggleTag(this)" style="padding:6px 14px;border-radius:16px;font-size:13px;background:#f5f5f5;color:#666;border:1px solid #e8e8e8;cursor:pointer;">孕期营养</div>
</div>
<div style="font-size:11px;color:#bbb;margin-top:10px;">点击标签切换选中状态</div>
</div>
<!-- 个人简介 -->
<div style="font-size:13px;font-weight:600;color:#999;margin:0 4px 8px;">个人简介</div>
<div style="background:#fff;border-radius:12px;padding:14px 16px;box-shadow:0 1px 6px rgba(0,0,0,0.06);margin-bottom:16px;">
<textarea style="width:100%;border:none;font-size:14px;color:#1a1a1a;outline:none;resize:none;line-height:1.7;background:transparent;" rows="4" placeholder="请输入个人简介...">注册营养师,从事营养健康管理工作 8 年,擅长体重管理、慢性病营养干预与个性化食谱制定,累计服务员工 2000 余人次。</textarea>
<div style="text-align:right;font-size:11px;color:#bbb;">已输入 68/300 字</div>
</div>
<!-- 职业经历 -->
<div style="font-size:13px;font-weight:600;color:#999;margin:0 4px 8px;">职业经历</div>
<div style="background:#fff;border-radius:12px;padding:14px 16px;box-shadow:0 1px 6px rgba(0,0,0,0.06);margin-bottom:20px;">
<textarea style="width:100%;border:none;font-size:14px;color:#1a1a1a;outline:none;resize:none;line-height:1.7;background:transparent;" rows="4" placeholder="请描述您的职业经历..."></textarea>
<div style="text-align:right;font-size:11px;color:#bbb;">已输入 62/300 字</div>
</div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
/* 切换擅长领域标签选中状态 */
function toggleTag(el) {
var isOn = el.style.background === 'rgb(22, 119, 255)' || el.style.background === '#1677ff';
el.style.background = isOn ? '#f5f5f5' : '#1677ff';
el.style.color = isOn ? '#666' : '#fff';
el.style.borderColor = isOn ? '#e8e8e8' : '#1677ff';
}
</script>
</body>
</html>
@@ -0,0 +1,155 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.modal-center { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 200; align-items: center; justify-content: center; }
.modal-center.active { display: flex; }
.modal-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.modal-box .modal-title { padding: 20px 20px 8px; font-size: 17px; font-weight: 700; text-align: center; color: #1a1a1a; }
.modal-box .modal-body { padding: 8px 20px 20px; font-size: 14px; color: #666; text-align: center; line-height: 1.6; }
.modal-box .modal-actions { display: flex; border-top: 1px solid #f0f0f0; }
.modal-box .modal-actions button { flex: 1; padding: 14px; font-size: 16px; cursor: pointer; background: none; border: none; }
.modal-box .modal-actions button:first-child { color: #666; border-right: 1px solid #f0f0f0; }
.modal-box .modal-actions button:last-child { color: #1677ff; font-weight: 600; }
.tab-bar { height: 83px; background: #fff; border-top: 1px solid #e8e8e8; display: flex; align-items: flex-start; padding-top: 8px; flex-shrink: 0; }
.tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; padding: 4px 0; position: relative; }
.tab-item .tab-icon { font-size: 22px; line-height: 1; }
.tab-item .tab-label { font-size: 10px; color: #999; font-weight: 500; }
.tab-item.active .tab-label { color: #1677ff; }
.tab-badge { position: absolute; top: 2px; right: 18px; background: #ff4d4f; color: #fff; font-size: 10px; font-weight: 700; min-width: 16px; height: 16px; border-radius: 8px; display: flex; align-items: center; justify-content: center; padding: 0 4px; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar" style="background:transparent;position:absolute;top:0;left:0;right:0;z-index:10;">
<span class="time" style="color:#fff;">9:41</span>
<div class="icons" style="color:#fff;"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 蓝色渐变头部 -->
<div style="background:linear-gradient(135deg,#1677ff,#0958d9);padding:30px 20px 24px;flex-shrink:0;padding-top:60px;">
<div style="display:flex;align-items:center;gap:16px;">
<div style="width:68px;height:68px;border-radius:50%;background:linear-gradient(135deg,#e8f4ff,#bdd7ff);display:flex;align-items:center;justify-content:center;font-size:34px;border:3px solid rgba(255,255,255,0.5);">🥗</div>
<div>
<div style="color:#fff;font-size:20px;font-weight:700;">李静</div>
<div style="color:rgba(255,255,255,0.8);font-size:13px;margin-top:3px;">注册营养师</div>
<div style="color:rgba(255,255,255,0.65);font-size:12px;margin-top:2px;">健康管理科</div>
</div>
<!-- 服务中按钮 -->
<button onclick="togglePause()" id="pause-btn-mine" style="margin-left:auto;display:flex;align-items:center;gap:6px;background:rgba(255,255,255,0.2);border:1px solid rgba(255,255,255,0.4);border-radius:20px;padding:6px 12px;color:#fff;font-size:12px;cursor:pointer;">
<span style="width:7px;height:7px;border-radius:50%;background:#52c41a;display:inline-block;"></span><span id="pause-text">咨询已开启</span>
</button>
</div>
</div>
<!-- 功能列表滚动区域 -->
<div class="scroll-content" style="padding:12px 16px;">
<div style="background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 6px rgba(0,0,0,0.06);margin-bottom:12px;">
<div onclick="location.href='./stats.html'" style="display:flex;align-items:center;padding:16px;cursor:pointer;border-bottom:1px solid #f5f5f5;">
<span style="font-size:18px;margin-right:12px;">📊</span><span style="font-size:15px;color:#1a1a1a;flex:1;">营养咨询统计</span>
<span style="font-size:13px;color:#1677ff;margin-right:8px;">今日 12 次</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div onclick="location.href='./schedule.html'" style="display:flex;align-items:center;padding:16px;cursor:pointer;border-bottom:1px solid #f5f5f5;">
<span style="font-size:18px;margin-right:12px;">📅</span><span style="font-size:15px;color:#1a1a1a;flex:1;">排班设置</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div onclick="location.href='./edit-profile.html'" style="display:flex;align-items:center;padding:16px;cursor:pointer;">
<span style="font-size:18px;margin-right:12px;">👤</span><span style="font-size:15px;color:#1a1a1a;flex:1;">个人信息设置</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
</div>
<div style="background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 6px rgba(0,0,0,0.06);margin-bottom:12px;">
<div onclick="location.href='./change-pwd.html'" style="display:flex;align-items:center;padding:16px;border-bottom:1px solid #f5f5f5;cursor:pointer;">
<span style="font-size:18px;margin-right:12px;">🔒</span><span style="font-size:15px;color:#1a1a1a;flex:1;">修改密码</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
<div style="display:flex;align-items:center;padding:16px;">
<span style="font-size:18px;margin-right:12px;"></span><span style="font-size:15px;color:#1a1a1a;flex:1;">关于我们</span>
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M1 1l5 5-5 5"/></svg>
</div>
</div>
<button onclick="openModal('modal-logout')" style="width:100%;padding:14px;background:#fff;color:#ff4d4f;border:none;border-radius:12px;font-size:15px;font-weight:600;cursor:pointer;box-shadow:0 1px 6px rgba(0,0,0,0.06);">退出登录</button>
</div>
<!-- 退出确认弹窗 -->
<div id="modal-logout" class="modal-center">
<div class="modal-box">
<div class="modal-title">退出登录</div>
<div class="modal-body">确认退出当前账号?</div>
<div class="modal-actions">
<button onclick="closeModal('modal-logout')">取消</button>
<button onclick="closeModal('modal-logout')" style="color:#ff4d4f;">确认退出</button>
</div>
</div>
</div>
<!-- 咨询状态切换确认弹窗 -->
<div id="modal-pause" class="modal-center">
<div class="modal-box">
<div class="modal-title" id="pause-modal-title">关闭咨询</div>
<div class="modal-body" id="pause-modal-body">是否要关闭营养咨询?关闭后,员工无法发起营养咨询。</div>
<div class="modal-actions">
<button onclick="closeModal('modal-pause')">取消</button>
<button onclick="confirmTogglePause()">确认</button>
</div>
</div>
</div>
<!-- 底部Tab栏,我的为active状态 -->
<div class="tab-bar">
<div class="tab-item" onclick="location.href='../workbench/index.html'"><span class="tab-icon">🏥</span><span class="tab-label">工作台</span><span class="tab-badge">2</span></div>
<div class="tab-item" onclick="location.href='../knowledge/index.html'"><span class="tab-icon">📚</span><span class="tab-label">知识库</span></div>
<div class="tab-item" onclick="location.href='../../ai-assistant/index.html'"><span class="tab-icon">🤖</span><span class="tab-label">AI助手</span></div>
<div class="tab-item" onclick="location.href='../../message/index.html'"><span class="tab-icon">💬</span><span class="tab-label">消息</span><span class="tab-badge">2</span></div>
<div class="tab-item active" onclick="location.href='./index.html'"><span class="tab-icon">👤</span><span class="tab-label">我的</span></div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
let isPaused = false;
function togglePause() {
var title = isPaused ? '开启咨询' : '关闭咨询';
var body = isPaused ? '是否要开启营养咨询?开启后,员工可发起营养咨询。' : '是否要关闭营养咨询?关闭后,员工无法发起营养咨询。';
document.getElementById('pause-modal-title').textContent = title;
document.getElementById('pause-modal-body').textContent = body;
openModal('modal-pause');
}
function confirmTogglePause() {
isPaused = !isPaused;
var bg = isPaused ? '#ff4d4f' : '#52c41a';
var text = isPaused ? '咨询已关闭' : '咨询已开启';
var mineBtn = document.getElementById('pause-btn-mine');
if (mineBtn) {
var dot = mineBtn.querySelector('span');
if (dot) dot.style.background = bg;
var pauseText = document.getElementById('pause-text');
if (pauseText) pauseText.textContent = text;
}
closeModal('modal-pause');
}
function openModal(id) { var m = document.getElementById(id); if (m) m.classList.add('active'); }
function closeModal(id) { var m = document.getElementById(id); if (m) m.classList.remove('active'); }
</script>
</body>
</html>
@@ -0,0 +1,183 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.back-btn { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.nav-right { margin-left: auto; color: #1677ff; font-size: 14px; cursor: pointer; background: none; border: none; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.toggle { position: relative; width: 44px; height: 24px; border-radius: 12px; background: #e0e0e0; cursor: pointer; transition: background .2s; flex-shrink: 0; }
.toggle.on { background: #4cd964; }
.toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.2); transition: transform .2s; }
.toggle.on::after { transform: translateX(20px); }
.day-card { background: #fff; border-radius: 12px; margin: 0 16px 10px; box-shadow: 0 1px 4px rgba(0,0,0,0.05); overflow: hidden; }
.day-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.day-header .day-name { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.day-header .day-summary { font-size: 12px; color: #999; }
.day-header .arrow { width: 16px; height: 16px; color: #ccc; transition: transform .2s; flex-shrink: 0; }
.day-header .arrow.open { transform: rotate(180deg); }
.day-body { max-height: 0; overflow: hidden; transition: max-height .25s ease; }
.day-body.open { max-height: 400px; }
.day-body-inner { padding: 0 16px 14px; }
.slot-row { display: flex; align-items: center; padding: 10px 0; border-top: 1px solid #f5f5f5; }
.slot-row:first-child { border-top: none; }
.slot-info { flex: 1; min-width: 0; }
.slot-period { font-size: 11px; color: #1677ff; font-weight: 500; margin-bottom: 1px; }
.slot-time { font-size: 13px; color: #1a1a1a; }
.slot-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.slot-toggle { position: relative; width: 40px; height: 22px; border-radius: 11px; background: #e0e0e0; cursor: pointer; transition: background .2s; }
.slot-toggle.on { background: #4cd964; }
.slot-toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.2); transition: transform .2s; }
.slot-toggle.on::after { transform: translateX(18px); }
.slot-count { display: flex; align-items: center; gap: 4px; }
.count-btn { width: 26px; height: 26px; border-radius: 50%; border: 1px solid #e8e8e8; background: #fff; font-size: 15px; color: #666; cursor: pointer; display: flex; align-items: center; justify-content: center; line-height: 1; }
.count-btn:active { background: #f5f5f5; }
.count-val { font-size: 15px; font-weight: 700; color: #1677ff; min-width: 18px; text-align: center; }
.count-unit { font-size: 11px; color: #999; }
.slot-row.disabled .slot-time { color: #ccc; }
.slot-row.disabled .slot-period { color: #b8d4ff; }
.slot-row.disabled .count-val { color: #ccc; }
.slot-row.disabled .count-btn { border-color: #f0f0f0; color: #ccc; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<div class="nav-bar">
<button class="back-btn" onclick="location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">排班设置</span>
<button class="nav-right">保存</button>
</div>
<div class="scroll-content">
<!-- 开启营养咨询 -->
<div style="background:#fff;padding:14px 16px;display:flex;align-items:center;gap:10px;margin-bottom:10px;">
<span style="font-size:14px;color:#1a1a1a;font-weight:500;">开启营养咨询</span>
<div class="toggle on" onclick="this.classList.toggle('on');var t=this.nextElementSibling;t.textContent=this.classList.contains('on')?'已开启 — 员工可发起营养咨询':'已关闭';t.style.color=this.classList.contains('on')?'#4cd964':'#999';"></div>
<span style="font-size:12px;color:#4cd964;flex:1;">已开启 — 员工可发起营养咨询</span>
</div>
<!-- 标题 -->
<div style="padding:2px 16px 10px;display:flex;align-items:center;gap:6px;">
<span style="width:3px;height:14px;background:#1677ff;border-radius:2px;display:inline-block;"></span>
<span style="font-size:14px;font-weight:600;color:#1a1a1a;">每周排班设置</span>
</div>
<div id="cardList"></div>
<div style="height:20px;"></div>
</div>
</div>
<script>
var DAYS = ['周一','周二','周三','周四','周五','周六','周日'];
var SLOTS = [
{ period: '上午', time: '8:00 10:00' },
{ period: '上午', time: '10:00 12:00' },
{ period: '下午', time: '14:00 16:00' },
{ period: '下午', time: '16:00 18:00' }
];
var DATA = DAYS.map(function(_, i) {
var off = i >= 5;
return { slots: SLOTS.map(function(_, si) {
return { on: !off, n: off ? 0 : (si < 2 ? 5 : 4) };
})};
});
var arrowSvg = '<svg class="arrow" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6l4 4 4-4"/></svg>';
function getSummary(di) {
var d = DATA[di], cnt = 0;
d.slots.forEach(function(s) { if (s.on) cnt++; });
return cnt > 0 ? cnt + '个时段开诊' : '休息';
}
function render() {
var wrap = document.getElementById('cardList');
wrap.innerHTML = '';
DAYS.forEach(function(day, di) {
var card = document.createElement('div');
card.className = 'day-card';
card.innerHTML =
'<div class="day-header" onclick="toggle(' + di + ')">' +
'<div><span class="day-name">' + day + '</span> ' +
'<span class="day-summary">' + getSummary(di) + '</span></div>' +
arrowSvg +
'</div>' +
'<div class="day-body" id="body-' + di + '">' +
'<div class="day-body-inner">' + renderSlots(di) + '</div>' +
'</div>';
wrap.appendChild(card);
});
var first = document.getElementById('body-0');
if (first) { first.classList.add('open'); first.previousElementSibling.querySelector('.arrow').classList.add('open'); }
}
function renderSlots(di) {
var html = '';
DATA[di].slots.forEach(function(s, si) {
var dis = s.on ? '' : ' disabled';
var tCls = s.on ? 'slot-toggle on' : 'slot-toggle';
html += '<div class="slot-row' + dis + '">' +
'<div class="slot-info">' +
'<div class="slot-period">' + SLOTS[si].period + '</div>' +
'<div class="slot-time">' + SLOTS[si].time + '</div>' +
'</div>' +
'<div class="slot-right">' +
'<div class="' + tCls + '" onclick="toggleSlot(' + di + ',' + si + ')"></div>' +
'<div class="slot-count">' +
'<button class="count-btn" ' + (s.on ? '' : 'disabled ') + 'onclick="adj(' + di + ',' + si + ',-1)"></button>' +
'<span class="count-val">' + (s.on ? s.n : '—') + '</span>' +
'<button class="count-btn" ' + (s.on ? '' : 'disabled ') + 'onclick="adj(' + di + ',' + si + ',1)">+</button>' +
'<span class="count-unit">人</span>' +
'</div>' +
'</div></div>';
});
return html;
}
function toggle(di) {
var body = document.getElementById('body-' + di);
var arrow = body.previousElementSibling.querySelector('.arrow');
body.classList.toggle('open');
arrow.classList.toggle('open');
}
function toggleSlot(di, si) {
var s = DATA[di].slots[si];
s.on = !s.on;
if (s.on && s.n === 0) s.n = 3;
refreshCard(di);
}
function adj(di, si, delta) {
var s = DATA[di].slots[si];
if (!s.on) return;
s.n = Math.max(1, Math.min(20, s.n + delta));
refreshCard(di);
}
function refreshCard(di) {
var body = document.getElementById('body-' + di);
var wasOpen = body.classList.contains('open');
body.querySelector('.day-body-inner').innerHTML = renderSlots(di);
var header = body.previousElementSibling;
header.querySelector('.day-summary').textContent = getSummary(di);
if (wasOpen) body.classList.add('open');
}
render();
</script>
</body>
</html>
@@ -0,0 +1,143 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.back-btn { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<div class="nav-bar">
<button class="back-btn" onclick="location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">营养咨询统计</span>
</div>
<div class="scroll-content" style="padding:16px;">
<!-- 月度统计 -->
<div style="background:#fff;border-radius:12px;padding:20px;margin-bottom:16px;box-shadow:0 1px 6px rgba(0,0,0,0.06);">
<div style="display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:20px;">
<button onclick="prevMonth()" style="background:none;border:none;color:#1677ff;font-size:18px;cursor:pointer;padding:4px 8px;">
<svg width="8" height="14" viewBox="0 0 8 14" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 1L1 7l6 6"/></svg>
</button>
<div style="font-size:17px;font-weight:600;color:#1a1a1a;min-width:120px;text-align:center;" id="current-month">2026年9月</div>
<button onclick="nextMonth()" style="background:none;border:none;color:#1677ff;font-size:18px;cursor:pointer;padding:4px 8px;">
<svg width="8" height="14" viewBox="0 0 8 14" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 1l6 6-6 6"/></svg>
</button>
</div>
<!-- 概览(3列) -->
<div style="display:flex;gap:8px;margin-bottom:24px;">
<div style="flex:1;background:#e6f4ff;border-radius:10px;padding:14px 8px;text-align:center;">
<div style="font-size:20px;font-weight:700;color:#1677ff;">213</div>
<div style="font-size:11px;color:#666;margin-top:4px;">营养咨询</div>
</div>
<div style="flex:1;background:#f6ffed;border-radius:10px;padding:14px 8px;text-align:center;">
<div style="font-size:20px;font-weight:700;color:#52c41a;">48</div>
<div style="font-size:11px;color:#666;margin-top:4px;">营养方案</div>
</div>
<div style="flex:1;background:#fff7e6;border-radius:10px;padding:14px 8px;text-align:center;">
<div style="font-size:20px;font-weight:700;color:#fa8c16;">12</div>
<div style="font-size:11px;color:#666;margin-top:4px;">发布食谱</div>
</div>
</div>
<!-- 咨询主题分布(饼状图) -->
<div style="display:flex;align-items:center;gap:24px;margin-bottom:24px;">
<svg width="130" height="130" viewBox="0 0 100 100" style="flex-shrink:0;">
<circle cx="50" cy="50" r="40" fill="none" stroke="#1677ff" stroke-width="20"
stroke-dasharray="126 250" stroke-dashoffset="0" transform="rotate(-90 50 50)"/>
<circle cx="50" cy="50" r="40" fill="none" stroke="#52c41a" stroke-width="20"
stroke-dasharray="75 250" stroke-dashoffset="-126" transform="rotate(-90 50 50)"/>
<circle cx="50" cy="50" r="40" fill="none" stroke="#fa8c16" stroke-width="20"
stroke-dasharray="49 250" stroke-dashoffset="-201" transform="rotate(-90 50 50)"/>
<circle cx="50" cy="50" r="28" fill="#fff"/>
<text x="50" y="48" text-anchor="middle" font-size="18" font-weight="700" fill="#1a1a1a">213</text>
<text x="50" y="60" text-anchor="middle" font-size="10" fill="#999">总咨询</text>
</svg>
<div style="flex:1;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="width:12px;height:12px;background:#1677ff;border-radius:3px;flex-shrink:0;"></div>
<div style="flex:1;"><span style="font-size:13px;color:#666;">减脂控糖</span></div>
<span style="font-size:15px;font-weight:700;color:#1a1a1a;">107<span style="font-size:11px;color:#999;"> (50%)</span></span>
</div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="width:12px;height:12px;background:#52c41a;border-radius:3px;flex-shrink:0;"></div>
<div style="flex:1;"><span style="font-size:13px;color:#666;">慢性病营养</span></div>
<span style="font-size:15px;font-weight:700;color:#1a1a1a;">64<span style="font-size:11px;color:#999;"> (30%)</span></span>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<div style="width:12px;height:12px;background:#fa8c16;border-radius:3px;flex-shrink:0;"></div>
<div style="flex:1;"><span style="font-size:13px;color:#666;">其他</span></div>
<span style="font-size:15px;font-weight:700;color:#1a1a1a;">42<span style="font-size:11px;color:#999;"> (20%)</span></span>
</div>
</div>
</div>
<!-- 月度趋势折线图 -->
<div style="border-top:1px solid #f0f0f0;padding-top:20px;">
<div style="font-size:14px;font-weight:600;color:#1a1a1a;margin-bottom:16px;">月度趋势</div>
<svg width="100%" height="180" viewBox="0 0 340 180" style="overflow:visible;">
<line x1="0" y1="150" x2="340" y2="150" stroke="#f0f0f0" stroke-width="1"/>
<line x1="0" y1="120" x2="340" y2="120" stroke="#f0f0f0" stroke-width="1"/>
<line x1="0" y1="90" x2="340" y2="90" stroke="#f0f0f0" stroke-width="1"/>
<line x1="0" y1="60" x2="340" y2="60" stroke="#f0f0f0" stroke-width="1"/>
<line x1="0" y1="30" x2="340" y2="30" stroke="#f0f0f0" stroke-width="1"/>
<polyline points="5,126 27,108 49,90 71,60 93,138 115,48 137,96 159,78 181,114 203,66 225,132 247,90 269,120 291,54 313,144 335,42"
fill="none" stroke="#1677ff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="93" cy="138" r="3.5" fill="#1677ff"/>
<circle cx="159" cy="78" r="3.5" fill="#1677ff"/>
<circle cx="203" cy="66" r="6" fill="#1677ff" stroke="#fff" stroke-width="2.5"/>
<text x="93" y="168" text-anchor="middle" font-size="11" fill="#bbb">6</text>
<text x="159" y="168" text-anchor="middle" font-size="11" fill="#bbb">12</text>
<text x="203" y="168" text-anchor="middle" font-size="11" fill="#1677ff" font-weight="600"></text>
<text x="335" y="168" text-anchor="middle" font-size="11" fill="#bbb">30</text>
<text x="203" y="54" text-anchor="middle" font-size="12" fill="#1677ff" font-weight="600">15</text>
<text x="-5" y="153" text-anchor="end" font-size="10" fill="#bbb">0</text>
<text x="-5" y="123" text-anchor="end" font-size="10" fill="#bbb">5</text>
<text x="-5" y="93" text-anchor="end" font-size="10" fill="#bbb">10</text>
<text x="-5" y="63" text-anchor="end" font-size="10" fill="#bbb">15</text>
<text x="-5" y="33" text-anchor="end" font-size="10" fill="#bbb">20</text>
</svg>
</div>
</div>
<div style="height:20px;"></div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
function prevMonth() { alert('切换到上个月(演示)'); }
function nextMonth() { alert('切换到下个月(演示)'); }
</script>
</body>
</html>
@@ -0,0 +1,80 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.back-btn { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<button class="back-btn" onclick="location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">赵磊</span>
</div>
<!-- 咨询已结束提示条 -->
<div style="background:#fff7e6;padding:8px 16px;font-size:12px;color:#d46b08;flex-shrink:0;text-align:center;">本次营养咨询已结束,仅可查看历史记录</div>
<!-- 消息区(只读) -->
<div class="scroll-content" style="padding:12px 16px;">
<div style="text-align:center;margin:8px 0;"><span style="font-size:11px;color:#bbb;background:#ebebeb;padding:3px 10px;border-radius:10px;">昨天 08:30</span></div>
<!-- 员工消息 -->
<div style="display:flex;gap:8px;margin-bottom:14px;">
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#ffe0e0,#ffb3b3);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">👨</div>
<div><div style="font-size:11px;color:#999;margin-bottom:4px;">赵磊</div>
<div style="background:#fff;border-radius:0 12px 12px 12px;padding:10px 14px;font-size:14px;color:#1a1a1a;max-width:220px;box-shadow:0 1px 4px rgba(0,0,0,0.06);line-height:1.5;">营养师您好,我最近体检尿酸偏高,日常饮食要注意什么?</div>
</div>
</div>
<!-- 营养师消息 -->
<div style="display:flex;gap:8px;margin-bottom:14px;flex-direction:row-reverse;">
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#e8f4ff,#bdd7ff);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">🥗</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;">
<div style="font-size:11px;color:#999;margin-bottom:4px;">李营养师(我)</div>
<div style="background:#1677ff;border-radius:12px 0 12px 12px;padding:10px 14px;font-size:14px;color:#fff;max-width:220px;line-height:1.5;">高尿酸人群建议限制高嘌呤食物(动物内脏、海鲜、浓肉汤),避免饮酒,多饮水,每日 2000ml 以上,控制体重。</div>
</div>
</div>
<div style="text-align:center;margin:8px 0;"><span style="font-size:11px;color:#bbb;background:#ebebeb;padding:3px 10px;border-radius:10px;">昨天 08:45</span></div>
<!-- 员工结束消息 -->
<div style="display:flex;gap:8px;margin-bottom:14px;">
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#ffe0e0,#ffb3b3);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">👨</div>
<div><div style="font-size:11px;color:#999;margin-bottom:4px;">赵磊</div>
<div style="background:#fff;border-radius:0 12px 12px 12px;padding:10px 14px;font-size:14px;color:#1a1a1a;max-width:220px;box-shadow:0 1px 4px rgba(0,0,0,0.06);line-height:1.5;">谢谢营养师,方案很实用,我会坚持执行。</div>
</div>
</div>
<!-- 结束标记 -->
<div style="text-align:center;margin:16px 0;">
<span style="font-size:12px;color:#bbb;">— 咨询已结束 —</span>
</div>
<div style="height:8px;"></div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
</script>
</body>
</html>
@@ -0,0 +1,328 @@
<!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>
/* 公共重置与布局 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
/* 导航栏 */
.nav-bar { height: 44px; display: flex; align-items: center; padding: 0 16px; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; position: relative; }
.back-btn { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.nav-right { margin-left: auto; color: #ff4d4f; font-size: 14px; cursor: pointer; background: none; border: none; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.modal-center { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 200; align-items: center; justify-content: center; }
.modal-center.active { display: flex; }
.modal-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.modal-box .modal-title { padding: 20px 20px 8px; font-size: 17px; font-weight: 700; text-align: center; color: #1a1a1a; }
.modal-box .modal-body { padding: 8px 20px 20px; font-size: 14px; color: #666; text-align: center; line-height: 1.6; }
.modal-box .modal-actions { display: flex; border-top: 1px solid #f0f0f0; }
.modal-box .modal-actions button { flex: 1; padding: 14px; font-size: 16px; cursor: pointer; background: none; border: none; }
.modal-box .modal-actions button:first-child { color: #666; border-right: 1px solid #f0f0f0; }
.modal-box .modal-actions button:last-child { color: #1677ff; font-weight: 600; }
/* 员工档案卡片 */
.bubble-record { max-width: 260px; border-radius: 0 12px 12px 12px; overflow: hidden; background: #fff; border: 1px solid #e8e8e8; box-shadow: 0 1px 4px rgba(0,0,0,0.06); cursor: pointer; }
.bubble-record__header { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: linear-gradient(135deg, #e8f4ff, #bae0ff); }
.bubble-record__avatar { width: 32px; height: 32px; border-radius: 50%; background: #fff; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
.bubble-record__name { font-size: 13px; font-weight: 600; color: #333; }
.bubble-record__meta { font-size: 11px; color: #666; }
.bubble-record__body { padding: 10px 12px; }
.bubble-record__title { font-size: 13px; font-weight: 600; color: #333; margin-bottom: 4px; }
.bubble-record__desc { font-size: 12px; color: #666; line-height: 1.5; }
.bubble-record__tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 6px; }
.bubble-record__tag { font-size: 10px; padding: 2px 6px; border-radius: 4px; }
.bubble-record__tag--green { color: #52c41a; background: #f6ffed; }
.bubble-record__tag--orange { color: #d46b08; background: #fff7e6; }
/* 常用语分类与列表 */
.quick-cat { font-size: 12px; padding: 4px 12px; border-radius: 14px; background: #f5f5f5; color: #666; cursor: pointer; user-select: none; transition: all .2s; }
.quick-cat.active { background: #e6f4ff; color: #1677ff; font-weight: 600; }
.quick-item { font-size: 13px; color: #1a1a1a; padding: 8px 0; border-bottom: 1px solid #f0f0f0; cursor: pointer; }
.quick-item:last-child { border-bottom: none; }
.quick-item:active { color: #1677ff; }
/* 快捷操作栏 */
.action-bar { display: flex; gap: 8px; background: #fff; border-bottom: 1px solid #f0f0f0; padding: 8px 16px; flex-shrink: 0; }
.action-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 0; border-radius: 8px; font-size: 13px; cursor: pointer; }
.action-btn--blue { background: #e6f4ff; color: #1677ff; }
.action-btn--green { background: #f6ffed; color: #52c41a; }
/* 底部弹层 */
.sheet-mask { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.45); z-index: 300; align-items: flex-end; justify-content: center; }
.sheet-mask.active { display: flex; }
.sheet-panel { width: 100%; max-height: 80%; background: #fff; border-radius: 16px 16px 0 0; display: flex; flex-direction: column; overflow: hidden; }
.sheet-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0; }
.sheet-header .s-cancel { font-size: 14px; color: #999; cursor: pointer; background: none; border: none; }
.sheet-header .s-title { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.sheet-header .s-save { font-size: 14px; color: #1677ff; font-weight: 600; cursor: pointer; background: none; border: none; }
.sheet-body { padding: 12px 16px 20px; overflow-y: auto; flex: 1; }
.sheet-body::-webkit-scrollbar { display: none; }
.form-item { margin-bottom: 14px; }
.form-item__label { font-size: 13px; font-weight: 600; color: #1a1a1a; margin-bottom: 8px; }
.form-input { width: 100%; border: 1px solid #e8e8e8; border-radius: 8px; padding: 10px 12px; font-size: 14px; outline: none; color: #1a1a1a; }
.form-input:focus { border-color: #1677ff; }
.ratio-row { display: flex; gap: 8px; }
.ratio-row .form-input { flex: 1; text-align: center; }
.tag-picker { display: flex; gap: 6px; flex-wrap: wrap; }
.tag-picker .tag-opt { font-size: 12px; padding: 5px 12px; border-radius: 14px; background: #f5f5f5; color: #666; cursor: pointer; }
.tag-picker .tag-opt.active { background: #e6f4ff; color: #1677ff; font-weight: 600; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<!-- [交互] 点击返回: ./index.html -->
<button class="back-btn" onclick="location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">张小红</span>
<!-- [交互] 点击打开结束咨询弹窗 -->
<button class="nav-right" onclick="openModal('modal-end-confirm')">结束咨询</button>
</div>
<!-- 快捷操作栏 -->
<div class="action-bar">
<!-- [交互] 点击跳转至: ./employee-health.html 查看员工健康档案 -->
<div class="action-btn action-btn--blue" onclick="location.href='./employee-health.html'">📋 健康档案</div>
<!-- [交互] 点击打开制定营养方案弹层 -->
<div class="action-btn action-btn--green" onclick="openSheet('sheet-plan')">🎯 制定方案</div>
</div>
<!-- 消息区 -->
<div class="scroll-content" id="chat-scroll" style="padding:12px 16px;">
<!-- 时间标记 -->
<div style="text-align:center;margin:8px 0;"><span style="font-size:11px;color:#bbb;background:#ebebeb;padding:3px 10px;border-radius:10px;">10:20</span></div>
<!-- [交互] 员工营养档案卡片 - 点击查看员工营养详情 -->
<div style="display:flex;gap:8px;margin-bottom:14px;">
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#ffe0e0,#ffb3b3);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">👩</div>
<div><div style="font-size:11px;color:#999;margin-bottom:4px;">张小红</div>
<div class="bubble-record" onclick="location.href='./employee-detail.html'">
<div class="bubble-record__header">
<div class="bubble-record__avatar">👩</div>
<div>
<div class="bubble-record__name">张小红 · 女 · 32岁</div>
<div class="bubble-record__meta">162cm · 67kg · BMI 25.6</div>
</div>
</div>
<div class="bubble-record__body">
<div class="bubble-record__title">减脂营养咨询</div>
<div class="bubble-record__desc">食堂饭菜偏油,想请教日常减脂怎么搭配饮食</div>
<div class="bubble-record__tags">
<span class="bubble-record__tag bubble-record__tag--green">目标:减脂 5kg</span>
</div>
</div>
</div>
</div>
</div>
<!-- 员工消息(左侧) -->
<div style="display:flex;gap:8px;margin-bottom:14px;">
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#ffe0e0,#ffb3b3);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">👩</div>
<div><div style="font-size:11px;color:#999;margin-bottom:4px;">张小红</div>
<div style="background:#fff;border-radius:0 12px 12px 12px;padding:10px 14px;font-size:14px;color:#1a1a1a;max-width:220px;box-shadow:0 1px 4px rgba(0,0,0,0.06);line-height:1.5;">营养师您好,我最近在减脂,但食堂的红烧肉、油炸类菜品偏多,不知道怎么吃才不会超标。</div>
</div>
</div>
<!-- 时间标记 -->
<div style="text-align:center;margin:8px 0;"><span style="font-size:11px;color:#bbb;background:#ebebeb;padding:3px 10px;border-radius:10px;">10:23</span></div>
<!-- 营养师消息(右侧) -->
<div style="display:flex;gap:8px;margin-bottom:14px;flex-direction:row-reverse;">
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#e8f4ff,#bdd7ff);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">🥗</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;">
<div style="font-size:11px;color:#999;margin-bottom:4px;">李营养师(我)</div>
<div style="background:#1677ff;border-radius:12px 0 12px 12px;padding:10px 14px;font-size:14px;color:#fff;max-width:220px;line-height:1.5;">您好,减脂期在食堂就餐建议:主菜优先选清蒸、白灼、凉拌类,肉类选去皮鸡胸、鱼虾;主食粗细搭配,每餐一拳主食、一掌蛋白质、两拳蔬菜。</div>
</div>
</div>
<!-- 营养师消息:推荐食谱卡片 -->
<div style="display:flex;gap:8px;margin-bottom:14px;flex-direction:row-reverse;">
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#e8f4ff,#bdd7ff);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">🥗</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;">
<div style="font-size:11px;color:#999;margin-bottom:4px;">李营养师(我)</div>
<div style="background:#fff;border-radius:12px 0 12px 12px;padding:10px;max-width:220px;box-shadow:0 1px 4px rgba(0,0,0,0.08);cursor:pointer;" onclick="location.href='./plan-detail.html'">
<div style="font-size:12px;color:#999;margin-bottom:6px;">为您推荐个性化营养方案</div>
<div style="font-size:14px;font-weight:600;color:#1a1a1a;">减脂期一周饮食方案</div>
<div style="font-size:12px;color:#666;margin-top:4px;line-height:1.5;">每日热量 1400 kcal · 蛋白质 25% · 脂肪 20% · 碳水 55%</div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-top:8px;padding-top:8px;border-top:1px solid #f0f0f0;">
<span style="font-size:11px;color:#52c41a;">● 已生成</span>
<span style="font-size:12px;color:#1677ff;">查看详情 </span>
</div>
</div>
</div>
</div>
<div style="height:8px;"></div>
</div>
<!-- 常用语面板 -->
<div id="quick-panel" style="display:none;background:#fff;border-top:1px solid #f0f0f0;padding:10px 16px;flex-shrink:0;max-height:200px;overflow-y:auto;">
<div style="display:flex;gap:6px;margin-bottom:10px;">
<span class="quick-cat active" data-cat="all" onclick="filterQuick(this)">全部</span>
<span class="quick-cat" data-cat="greet" onclick="filterQuick(this)">问候语</span>
<span class="quick-cat" data-cat="ask" onclick="filterQuick(this)">问询</span>
<span class="quick-cat" data-cat="advice" onclick="filterQuick(this)">建议</span>
<span class="quick-cat" data-cat="end" onclick="filterQuick(this)">结束语</span>
</div>
<div id="quick-list">
<div class="quick-item" data-cat="greet" onclick="useQuick(this)">您好,我是您的专属营养师</div>
<div class="quick-item" data-cat="greet" onclick="useQuick(this)">您好,请描述您的饮食与健康目标</div>
<div class="quick-item" data-cat="ask" onclick="useQuick(this)">请问您目前的身高体重是多少?</div>
<div class="quick-item" data-cat="ask" onclick="useQuick(this)">是否有高血脂、糖尿病等慢性病史?</div>
<div class="quick-item" data-cat="ask" onclick="useQuick(this)">平时主要在食堂就餐还是自带餐?</div>
<div class="quick-item" data-cat="advice" onclick="useQuick(this)">建议主食粗细搭配,每餐一拳量</div>
<div class="quick-item" data-cat="advice" onclick="useQuick(this)">优先选择清蒸、白灼类菜品</div>
<div class="quick-item" data-cat="advice" onclick="useQuick(this)">控制油脂摄入,每日不超过 25g</div>
<div class="quick-item" data-cat="end" onclick="useQuick(this)">如有不适请及时调整并反馈</div>
<div class="quick-item" data-cat="end" onclick="useQuick(this)">祝您健康,欢迎随时咨询!</div>
</div>
</div>
<!-- 输入区 -->
<div style="background:#fff;border-top:1px solid #f0f0f0;padding:8px 12px 14px;flex-shrink:0;">
<div style="display:flex;align-items:center;gap:6px;">
<button onclick="toggleQuick()" style="width:34px;height:34px;border-radius:50%;background:#f5f5f5;border:none;font-size:16px;cursor:pointer;flex-shrink:0;"></button>
<button id="voice-btn" onclick="toggleVoice()" style="width:34px;height:34px;border-radius:50%;background:#f5f5f5;border:none;font-size:16px;cursor:pointer;flex-shrink:0;">🎤</button>
<input id="chat-input" type="text" placeholder="输入营养建议..." style="flex:1;background:#f5f5f5;border:none;border-radius:20px;padding:8px 14px;font-size:14px;outline:none;color:#1a1a1a;" />
<button id="voice-record-btn" style="display:none;flex:1;height:36px;background:#f5f5f5;border:1.5px solid #d9d9d9;border-radius:20px;font-size:13px;color:#555;cursor:pointer;text-align:center;align-items:center;justify-content:center;letter-spacing:1px;">按住说话</button>
<button onclick="sendMsg()" style="width:34px;height:34px;border-radius:50%;background:#1677ff;border:none;font-size:16px;cursor:pointer;flex-shrink:0;color:#fff;display:flex;align-items:center;justify-content:center;"></button>
</div>
</div>
<!-- 结束咨询确认弹窗 -->
<div id="modal-end-confirm" class="modal-center">
<div class="modal-box">
<div class="modal-title">结束咨询</div>
<div class="modal-body">确认结束与张小红的营养咨询?结束后将无法继续发送消息。</div>
<div class="modal-actions">
<button onclick="closeModal('modal-end-confirm')">取消</button>
<button onclick="closeModal('modal-end-confirm');location.href='./index.html'">确认结束</button>
</div>
</div>
</div>
<!-- 制定营养方案底部弹层 -->
<div id="sheet-plan" class="sheet-mask" onclick="if(event.target===this)closeSheet('sheet-plan')">
<div class="sheet-panel" onclick="event.stopPropagation()">
<div class="sheet-header">
<button class="s-cancel" onclick="closeSheet('sheet-plan')">取消</button>
<span class="s-title">为张小红制定营养方案</span>
<button class="s-save" onclick="savePlan()">保存</button>
</div>
<div class="sheet-body">
<div class="form-item">
<div class="form-item__label">目标热量(kcal/日)</div>
<input class="form-input" type="text" value="1400" />
</div>
<div class="form-item">
<div class="form-item__label">三大营养素占比(%</div>
<div class="ratio-row">
<input class="form-input" type="text" value="25" placeholder="蛋白" />
<input class="form-input" type="text" value="20" placeholder="脂肪" />
<input class="form-input" type="text" value="55" placeholder="碳水" />
</div>
</div>
<div class="form-item">
<div class="form-item__label">饮食禁忌</div>
<div class="tag-picker">
<span class="tag-opt active" onclick="toggleTag(this)">高糖</span>
<span class="tag-opt active" onclick="toggleTag(this)">高脂</span>
<span class="tag-opt" onclick="toggleTag(this)">高盐</span>
<span class="tag-opt active" onclick="toggleTag(this)">高嘌呤</span>
<span class="tag-opt" onclick="toggleTag(this)">乳糖</span>
</div>
</div>
<div class="form-item">
<div class="form-item__label">目标描述</div>
<input class="form-input" type="text" value="减脂 5kg,控制尿酸" />
</div>
</div>
</div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
function openModal(id) { var m = document.getElementById(id); if (m) m.classList.add('active'); }
function closeModal(id) { var m = document.getElementById(id); if (m) m.classList.remove('active'); }
/* 切换常用语面板 */
function toggleQuick() {
var p = document.getElementById('quick-panel');
p.style.display = p.style.display === 'none' ? 'block' : 'none';
}
/* 语音模式状态 */
var isVoiceMode = false;
function toggleVoice() {
isVoiceMode = !isVoiceMode;
var input = document.getElementById('chat-input');
var voiceBtn = document.getElementById('voice-btn');
var recordBtn = document.getElementById('voice-record-btn');
if (isVoiceMode) {
input.style.display = 'none';
recordBtn.style.display = 'flex';
voiceBtn.textContent = '⌨️';
voiceBtn.style.background = '#e6f4ff';
} else {
input.style.display = '';
recordBtn.style.display = 'none';
voiceBtn.textContent = '🎤';
voiceBtn.style.background = '#f5f5f5';
}
}
/* 使用常用语填充输入框 */
function useQuick(el) {
document.getElementById('chat-input').value = el.textContent;
document.getElementById('quick-panel').style.display = 'none';
}
/* 常用语分类筛选 */
function filterQuick(tab) {
tab.parentElement.querySelectorAll('.quick-cat').forEach(function(t) { t.classList.remove('active'); });
tab.classList.add('active');
var cat = tab.dataset.cat;
document.querySelectorAll('#quick-list .quick-item').forEach(function(item) {
item.style.display = (cat === 'all' || item.dataset.cat === cat) ? '' : 'none';
});
}
/* 发送消息,在聊天区追加一条营养师消息气泡 */
function sendMsg() {
var input = document.getElementById('chat-input');
var text = input.value.trim();
if (!text) return;
var scroll = document.getElementById('chat-scroll');
var bubble = document.createElement('div');
bubble.style.cssText = 'display:flex;gap:8px;margin-bottom:14px;flex-direction:row-reverse;';
bubble.innerHTML = '<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#e8f4ff,#bdd7ff);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;">🥗</div><div style="display:flex;flex-direction:column;align-items:flex-end;"><div style="font-size:11px;color:#999;margin-bottom:4px;">李营养师(我)</div><div style="background:#1677ff;border-radius:12px 0 12px 12px;padding:10px 14px;font-size:14px;color:#fff;max-width:220px;line-height:1.5;">' + text + '</div></div>';
scroll.insertBefore(bubble, scroll.lastElementChild);
input.value = '';
scroll.scrollTop = scroll.scrollHeight;
}
/* 打开底部弹层 */
function openSheet(id) { var m = document.getElementById(id); if (m) m.classList.add('active'); }
/* 关闭底部弹层 */
function closeSheet(id) { var m = document.getElementById(id); if (m) m.classList.remove('active'); }
/* 禁忌标签选择 */
function toggleTag(el) { el.classList.toggle('active'); }
/* 保存方案并关闭弹层 */
function savePlan() {
closeSheet('sheet-plan');
alert('营养方案已保存,系统将自动生成一周专属食谱并推送至员工端(原型演示)');
}
</script>
</body>
</html>
@@ -0,0 +1,249 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f7f8fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; position: relative; }
.nav-back { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 员工信息头 */
.emp-header { background: linear-gradient(135deg,#1677ff,#0958d9); padding: 16px 20px; color: #fff; }
.emp-header__name { font-size: 18px; font-weight: 700; }
.emp-header__meta { font-size: 12px; color: rgba(255,255,255,0.85); margin-top: 4px; }
.emp-header__tags { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.emp-header__tag { font-size: 11px; padding: 3px 10px; border-radius: 12px; background: rgba(255,255,255,0.2); }
/* 区块卡片 */
.card { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.card__title { font-size: 14px; font-weight: 700; color: #1a1a1a; margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between; }
.card__title-extra { font-size: 12px; color: #1677ff; font-weight: 400; cursor: pointer; }
/* 达标概览 */
.overview-row { display: flex; }
.overview-item { flex: 1; text-align: center; }
.overview-item + .overview-item { border-left: 1px solid #f0f0f0; }
.overview-item__val { font-size: 18px; font-weight: 700; }
.overview-item__val--good { color: #52c41a; }
.overview-item__val--bad { color: #ff4d4f; }
.overview-item__label { font-size: 11px; color: #999; margin-top: 2px; }
/* 膳食宝塔 */
.pyramid { display: flex; flex-direction: column; gap: 6px; }
.pyramid-row { display: flex; align-items: center; gap: 8px; }
.pyramid-row__label { width: 56px; font-size: 12px; color: #666; flex-shrink: 0; }
.pyramid-row__bar { flex: 1; height: 10px; background: #f0f0f0; border-radius: 5px; overflow: hidden; }
.pyramid-row__fill { height: 100%; border-radius: 5px; }
.pyramid-row__val { width: 40px; font-size: 11px; color: #999; text-align: right; flex-shrink: 0; }
/* 摄入明细 */
.meal-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #f5f5f5; }
.meal-row:last-child { border-bottom: none; }
.meal-row__left { display: flex; align-items: center; gap: 8px; }
.meal-row__icon { font-size: 18px; }
.meal-row__name { font-size: 14px; color: #1a1a1a; font-weight: 500; }
.meal-row__kcal { font-size: 14px; font-weight: 700; color: #fa8c16; }
.meal-row__kcal span { font-size: 11px; font-weight: 400; color: #999; }
/* 营养方案 */
.plan-box { background: #f6ffed; border: 1px solid #b7eb8f; border-radius: 10px; padding: 12px; }
.plan-box__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.plan-box__name { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.plan-box__status { font-size: 11px; color: #52c41a; font-weight: 600; }
.plan-box__row { display: flex; justify-content: space-between; font-size: 12px; color: #666; padding: 3px 0; }
.plan-box__row .val { color: #333; font-weight: 600; }
/* 方案编辑表单 */
.plan-form { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.form-item { margin-bottom: 14px; }
.form-item__label { font-size: 13px; font-weight: 600; color: #1a1a1a; margin-bottom: 8px; }
.form-input { width: 100%; border: 1px solid #e8e8e8; border-radius: 8px; padding: 10px 12px; font-size: 14px; outline: none; color: #1a1a1a; }
.form-input:focus { border-color: #1677ff; }
.ratio-row { display: flex; gap: 8px; }
.ratio-row .form-input { flex: 1; text-align: center; }
.tag-picker { display: flex; gap: 6px; flex-wrap: wrap; }
.tag-picker .tag-opt { font-size: 12px; padding: 5px 12px; border-radius: 14px; background: #f5f5f5; color: #666; cursor: pointer; }
.tag-picker .tag-opt.active { background: #e6f4ff; color: #1677ff; font-weight: 600; }
.btn-primary { width: 100%; padding: 12px; background: #1677ff; color: #fff; border: none; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; }
.btn-ghost { width: 100%; padding: 12px; background: #fff; color: #1677ff; border: 1px solid #1677ff; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; }
.modal-center { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 200; align-items: center; justify-content: center; }
.modal-center.active { display: flex; }
.modal-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.modal-box .modal-title { padding: 20px 20px 8px; font-size: 17px; font-weight: 700; text-align: center; color: #1a1a1a; }
.modal-box .modal-body { padding: 8px 20px 20px; font-size: 14px; color: #666; text-align: center; line-height: 1.6; }
.modal-box .modal-actions { display: flex; border-top: 1px solid #f0f0f0; }
.modal-box .modal-actions button { flex: 1; padding: 14px; font-size: 16px; cursor: pointer; background: none; border: none; }
.modal-box .modal-actions button:first-child { color: #666; border-right: 1px solid #f0f0f0; }
.modal-box .modal-actions button:last-child { color: #1677ff; font-weight: 600; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<button class="nav-back" onclick="location.href='./employee-list.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">员工营养详情</span>
</div>
<div class="scroll-content">
<!-- 员工信息头 -->
<div class="emp-header">
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
<div>
<div class="emp-header__name">张小红</div>
<div class="emp-header__meta">女 · 32岁 · 公司机关 · 办公室 · 162cm · 67.2kg · BMI 25.6</div>
</div>
<!-- [交互] 点击跳转至: ./employee-health.html 查看员工健康档案 -->
<button onclick="location.href='./employee-health.html'" style="display:flex;align-items:center;gap:4px;background:rgba(255,255,255,0.2);border:1px solid rgba(255,255,255,0.4);border-radius:16px;padding:6px 12px;color:#fff;font-size:12px;cursor:pointer;flex-shrink:0;">📋 健康档案</button>
</div>
<div class="emp-header__tags">
<span class="emp-header__tag">减脂需求</span>
<span class="emp-header__tag">轻体力活动</span>
</div>
</div>
<!-- 营养达标概览 -->
<div class="card">
<div class="card__title">营养达标概览</div>
<div class="overview-row">
<div class="overview-item"><div class="overview-item__val overview-item__val--bad">68%</div><div class="overview-item__label">综合达标率</div></div>
<div class="overview-item"><div class="overview-item__val">22</div><div class="overview-item__label">本月用餐天数</div></div>
<div class="overview-item"><div class="overview-item__val">1890</div><div class="overview-item__label">日均摄入(kcal)</div></div>
</div>
</div>
<!-- 膳食宝塔评估 -->
<div class="card">
<div class="card__title">膳食宝塔评估</div>
<div class="pyramid">
<div class="pyramid-row"><span class="pyramid-row__label">谷薯类</span><div class="pyramid-row__bar"><div class="pyramid-row__fill" style="width:82%;background:#52c41a;"></div></div><span class="pyramid-row__val">82%</span></div>
<div class="pyramid-row"><span class="pyramid-row__label">蔬菜水果</span><div class="pyramid-row__bar"><div class="pyramid-row__fill" style="width:45%;background:#fa8c16;"></div></div><span class="pyramid-row__val">45%</span></div>
<div class="pyramid-row"><span class="pyramid-row__label">畜禽鱼蛋</span><div class="pyramid-row__bar"><div class="pyramid-row__fill" style="width:110%;background:#ff4d4f;"></div></div><span class="pyramid-row__val">110%</span></div>
<div class="pyramid-row"><span class="pyramid-row__label">奶豆坚果</span><div class="pyramid-row__bar"><div class="pyramid-row__fill" style="width:38%;background:#fa8c16;"></div></div><span class="pyramid-row__val">38%</span></div>
<div class="pyramid-row"><span class="pyramid-row__label">油盐</span><div class="pyramid-row__bar"><div class="pyramid-row__fill" style="width:135%;background:#ff4d4f;"></div></div><span class="pyramid-row__val">135%</span></div>
</div>
</div>
<!-- 今日摄入明细 -->
<div class="card">
<div class="card__title">今日摄入明细<span class="card__title-extra" onclick="location.href='./employee-diet-history.html'">查看历史 </span></div>
<div class="meal-row"><div class="meal-row__left"><span class="meal-row__icon">🥣</span><span class="meal-row__name">早餐</span></div><div class="meal-row__kcal">486 <span>kcal</span></div></div>
<div class="meal-row"><div class="meal-row__left"><span class="meal-row__icon">🍱</span><span class="meal-row__name">午餐</span></div><div class="meal-row__kcal">720 <span>kcal</span></div></div>
<div class="meal-row"><div class="meal-row__left"><span class="meal-row__icon">🍎</span><span class="meal-row__name">加餐</span></div><div class="meal-row__kcal">185 <span>kcal</span></div></div>
<div class="meal-row"><div class="meal-row__left"><span class="meal-row__icon">🍽️</span><span class="meal-row__name">晚餐</span></div><div class="meal-row__kcal">560 <span>kcal</span></div></div>
</div>
<!-- 营养方案 -->
<div class="card">
<div class="card__title">营养方案</div>
<div class="plan-box" id="plan-box">
<div class="plan-box__head">
<span class="plan-box__name">减脂期一周饮食方案</span>
<span class="plan-box__status">执行中</span>
</div>
<div class="plan-box__row"><span>目标热量</span><span class="val">1400 kcal/日</span></div>
<div class="plan-box__row"><span>蛋白质 : 脂肪 : 碳水</span><span class="val">25% : 20% : 55%</span></div>
<div class="plan-box__row"><span>饮食禁忌</span><span class="val">高糖 · 高脂</span></div>
<div class="plan-box__row"><span>执行进度</span><span class="val">第 3 天 / 7 天</span></div>
</div>
<div style="display:flex;gap:10px;margin-top:12px;">
<!-- [交互] 点击跳转至: ./plan-detail.html 查看方案详情 -->
<button onclick="location.href='./plan-detail.html'" style="flex:1;padding:12px;background:#fff;color:#1677ff;border:1px solid #1677ff;border-radius:10px;font-size:15px;font-weight:600;cursor:pointer;">查看详情</button>
<!-- [交互] 点击展开方案编辑表单 -->
<button onclick="togglePlanForm()" style="flex:1;padding:12px;background:#1677ff;color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:600;cursor:pointer;">调整方案</button>
</div>
</div>
<!-- 方案编辑表单 -->
<div class="plan-form" id="plan-form" style="display:none;">
<div class="form-item">
<div class="form-item__label">目标热量(kcal/日)</div>
<input class="form-input" type="text" value="1400" />
</div>
<div class="form-item">
<div class="form-item__label">三大营养素占比(%</div>
<div class="ratio-row">
<input class="form-input" type="text" value="25" placeholder="蛋白" />
<input class="form-input" type="text" value="20" placeholder="脂肪" />
<input class="form-input" type="text" value="55" placeholder="碳水" />
</div>
</div>
<div class="form-item">
<div class="form-item__label">饮食禁忌</div>
<div class="tag-picker">
<span class="tag-opt active" onclick="toggleTag(this)">高糖</span>
<span class="tag-opt active" onclick="toggleTag(this)">高脂</span>
<span class="tag-opt" onclick="toggleTag(this)">高盐</span>
<span class="tag-opt" onclick="toggleTag(this)">高嘌呤</span>
<span class="tag-opt" onclick="toggleTag(this)">乳糖</span>
</div>
</div>
<div class="form-item">
<div class="form-item__label">目标描述</div>
<input class="form-input" type="text" value="减脂 5kg,控制体脂率" />
</div>
<!-- [交互] 点击保存方案 -->
<button class="btn-primary" onclick="savePlan()">保存方案</button>
</div>
<div style="height:16px;"></div>
</div>
<!-- 保存成功弹窗 -->
<div id="modal-saved" class="modal-center">
<div class="modal-box">
<div class="modal-title">保存成功</div>
<div class="modal-body">营养方案已保存,系统将自动生成一周专属食谱并推送至员工端小程序。</div>
<div class="modal-actions">
<button onclick="closeModal('modal-saved')">知道了</button>
</div>
</div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
/* 展开/收起方案编辑表单 */
function togglePlanForm() {
var form = document.getElementById('plan-form');
var isHidden = form.style.display === 'none';
form.style.display = isHidden ? 'block' : 'none';
if (isHidden) {
/* 展开后自动滚动到表单位置,避免表单在视口外用户看不到 */
setTimeout(function() {
form.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 50);
}
}
/* 禁忌标签选择 */
function toggleTag(el) {
el.classList.toggle('active');
}
/* 保存方案 */
function savePlan() {
document.getElementById('plan-form').style.display = 'none';
openModal('modal-saved');
}
function openModal(id) { var m = document.getElementById(id); if (m) m.classList.add('active'); }
function closeModal(id) { var m = document.getElementById(id); if (m) m.classList.remove('active'); }
</script>
</body>
</html>
@@ -0,0 +1,284 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f7f8fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; position: relative; }
.nav-back { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 员工信息 */
.emp-info { background: linear-gradient(135deg,#1677ff,#0958d9); padding: 14px 20px; color: #fff; display: flex; align-items: center; gap: 12px; }
.emp-info__avatar { width: 42px; height: 42px; border-radius: 50%; background: rgba(255,255,255,0.25); border: 2px solid rgba(255,255,255,0.4); display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.emp-info__name { font-size: 16px; font-weight: 700; }
.emp-info__meta { font-size: 12px; color: rgba(255,255,255,0.85); margin-top: 2px; }
/* 日期导航 */
.date-nav { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 16px; background: #fff; border-bottom: 1px solid #f0f0f0; position: relative; flex-shrink: 0; }
.date-nav__arrow { width: 32px; height: 32px; border-radius: 50%; background: #f5f5f5; display: flex; align-items: center; justify-content: center; cursor: pointer; border: none; color: #666; font-size: 16px; flex-shrink: 0; }
.date-nav__arrow:active { background: #e6f4ff; }
.date-nav__text { font-size: 15px; font-weight: 600; color: #1a1a1a; min-width: 110px; text-align: center; }
.date-nav__cal { width: 28px; height: 28px; border-radius: 6px; background: none; display: flex; align-items: center; justify-content: center; cursor: pointer; border: none; font-size: 16px; flex-shrink: 0; color: #1677ff; }
/* 日历面板 */
.cal-panel { display: none; position: absolute; top: 42px; left: 16px; right: 16px; background: #fff; border-radius: 14px; box-shadow: 0 8px 30px rgba(0,0,0,0.15); z-index: 100; padding: 16px; }
.cal-panel.active { display: block; }
.cal-panel__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.cal-panel__month { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.cal-panel__nav { width: 28px; height: 28px; border-radius: 50%; border: none; background: #f5f5f5; cursor: pointer; font-size: 14px; color: #666; display: flex; align-items: center; justify-content: center; }
.cal-panel__weekdays { display: flex; margin-bottom: 6px; }
.cal-panel__weekday { flex: 1; text-align: center; font-size: 11px; color: #999; padding: 4px 0; }
.cal-panel__grid { display: flex; flex-wrap: wrap; }
.cal-panel__day { width: calc(100% / 7); text-align: center; padding: 8px 0; font-size: 13px; color: #333; cursor: pointer; border-radius: 8px; }
.cal-panel__day--other { color: #ddd; }
.cal-panel__day:hover { background: #f5f5f5; }
.cal-panel__day--today { font-weight: 700; color: #1677ff; }
.cal-panel__day--selected { background: #1677ff; color: #fff; font-weight: 700; }
/* 摄入汇总 */
.summary-card { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.summary-card__title { font-size: 14px; font-weight: 700; color: #1a1a1a; margin-bottom: 10px; }
.summary-row { display: flex; }
.summary-item { flex: 1; text-align: center; }
.summary-item + .summary-item { border-left: 1px solid #f0f0f0; }
.summary-item__val { font-size: 18px; font-weight: 700; }
.summary-item__val--over { color: #ff4d4f; }
.summary-item__val--ok { color: #52c41a; }
.summary-item__label { font-size: 11px; color: #999; margin-top: 2px; }
/* 三餐明细 */
.section-title { font-size: 14px; font-weight: 700; color: #1a1a1a; margin: 14px 12px 6px; }
.meal-card { background: #fff; border-radius: 12px; margin: 8px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.meal-card__head { display: flex; align-items: center; margin-bottom: 8px; }
.meal-card__name { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.meal-card__time { font-size: 12px; color: #999; margin-left: 8px; }
.meal-card__kcal { margin-left: auto; font-size: 15px; font-weight: 700; color: #fa8c16; }
.meal-card__kcal span { font-size: 11px; font-weight: 400; color: #999; }
.meal-card__foods { display: flex; flex-wrap: wrap; gap: 6px; }
.food-tag { font-size: 12px; padding: 4px 10px; border-radius: 12px; background: #f0f6ff; color: #1677ff; }
.meal-card__source { margin-top: 8px; font-size: 11px; color: #999; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; }
.tag-blue { background: #e6f4ff; color: #1677ff; }
.tag-purple { background: #f9f0ff; color: #722ed1; }
/* 空状态 */
.empty-state { text-align: center; padding: 40px 0; color: #bbb; font-size: 13px; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<button class="nav-back" onclick="location.href='./employee-detail.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">饮食记录</span>
</div>
<!-- 员工信息 -->
<div class="emp-info">
<div class="emp-info__avatar">👩</div>
<div>
<div class="emp-info__name">张小红</div>
<div class="emp-info__meta">女 · 32岁 · 公司机关 · 办公室</div>
</div>
</div>
<!-- 日期导航 -->
<div class="date-nav">
<button class="date-nav__arrow" onclick="changeDate(-1)"></button>
<button class="date-nav__cal" onclick="toggleCalPanel()">📅</button>
<span class="date-nav__text" id="dateText">2026-06-22</span>
<button class="date-nav__arrow" onclick="changeDate(1)"></button>
<!-- 日历选择面板 -->
<div class="cal-panel" id="calPanel">
<div class="cal-panel__head">
<button class="cal-panel__nav" onclick="calPrevMonth(event)"></button>
<span class="cal-panel__month" id="calMonthLabel">2026年6月</span>
<button class="cal-panel__nav" onclick="calNextMonth(event)"></button>
</div>
<div class="cal-panel__weekdays">
<div class="cal-panel__weekday"></div><div class="cal-panel__weekday"></div><div class="cal-panel__weekday"></div><div class="cal-panel__weekday"></div><div class="cal-panel__weekday"></div><div class="cal-panel__weekday"></div><div class="cal-panel__weekday"></div>
</div>
<div class="cal-panel__grid" id="calGrid"></div>
</div>
</div>
<div class="scroll-content">
<!-- 摄入汇总 -->
<div class="summary-card" id="summary-card">
<div class="summary-card__title">每日摄入汇总</div>
<div class="summary-row" id="summary-row"></div>
</div>
<!-- 当日三餐明细 -->
<div class="section-title">当日三餐明细</div>
<div id="meal-list"></div>
<div style="height:16px;"></div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
/* ===== 饮食记录历史数据 ===== */
var DIET_HISTORY = {
'2026-06-22': {
target: 1400,
meals: [
{ icon: '🥣', name: '早餐', time: '07:30', kcal: 486, source: '智记', cls: 'tag-blue', foods: ['全麦面包 80g', '水煮蛋 60g', '牛奶 250ml'] },
{ icon: '🍱', name: '午餐', time: '12:15', kcal: 720, source: '智记', cls: 'tag-blue', foods: ['清蒸鲈鱼 150g', '杂粮饭 200g', '蒜蓉西兰花 150g'] },
{ icon: '🍎', name: '加餐', time: '15:40', kcal: 185, source: '自记', cls: 'tag-purple', foods: ['苹果 1个', '坚果 30g'] },
{ icon: '🍽️', name: '晚餐', time: '18:30', kcal: 560, source: '智记', cls: 'tag-blue', foods: ['香煎鸡胸 120g', '蔬菜沙拉 200g', '小米粥 200g'] }
]
},
'2026-06-21': {
target: 1400,
meals: [
{ icon: '🥣', name: '早餐', time: '07:42', kcal: 450, source: '智记', cls: 'tag-blue', foods: ['燕麦牛奶 300ml', '水煮蛋 60g'] },
{ icon: '🍱', name: '午餐', time: '12:08', kcal: 680, source: '智记', cls: 'tag-blue', foods: ['白灼虾 150g', '糙米饭 200g', '清炒时蔬 150g'] },
{ icon: '🍎', name: '加餐', time: '15:30', kcal: 120, source: '自记', cls: 'tag-purple', foods: ['酸奶 1杯'] },
{ icon: '🍽️', name: '晚餐', time: '18:45', kcal: 520, source: '智记', cls: 'tag-blue', foods: ['蔬菜豆腐汤 300g', '杂粮馒头 100g'] }
]
},
'2026-06-20': {
target: 1400,
meals: [
{ icon: '🥣', name: '早餐', time: '07:35', kcal: 470, source: '智记', cls: 'tag-blue', foods: ['水煮蛋 60g', '豆浆 250ml', '蒸红薯 100g'] },
{ icon: '🍱', name: '午餐', time: '12:20', kcal: 660, source: '智记', cls: 'tag-blue', foods: ['卤牛肉 100g', '荞麦面 200g', '凉拌黄瓜 150g'] },
{ icon: '🍽️', name: '晚餐', time: '18:20', kcal: 540, source: '智记', cls: 'tag-blue', foods: ['清炒时蔬 200g', '玉米 150g', '菌菇汤 200g'] }
]
},
'2026-06-19': {
target: 1400,
meals: [
{ icon: '🥣', name: '早餐', time: '07:50', kcal: 430, source: '智记', cls: 'tag-blue', foods: ['杂粮粥 300g', '水煮蛋 60g'] },
{ icon: '🍱', name: '午餐', time: '12:10', kcal: 700, source: '智记', cls: 'tag-blue', foods: ['香煎鸡胸 150g', '藜麦饭 150g', '西兰花 150g'] },
{ icon: '🍎', name: '加餐', time: '16:00', kcal: 150, source: '自记', cls: 'tag-purple', foods: ['虾仁沙拉 200g'] },
{ icon: '🍽️', name: '晚餐', time: '18:40', kcal: 480, source: '智记', cls: 'tag-blue', foods: ['蔬菜沙拉 200g', '全麦面包 50g'] }
]
}
};
var currentDate = new Date(2026, 5, 22);
var calViewYear = currentDate.getFullYear();
var calViewMonth = currentDate.getMonth();
/* 计算某天总摄入 */
function dayTotal(data) {
return data.meals.reduce(function(s, m) { return s + m.kcal; }, 0);
}
/* 格式化日期为 YYYY-MM-DD */
function fmtDate(d) {
var y = d.getFullYear();
var m = String(d.getMonth() + 1).padStart(2, '0');
var day = String(d.getDate()).padStart(2, '0');
return y + '-' + m + '-' + day;
}
/* 渲染当前日期的饮食记录 */
function render() {
var key = fmtDate(currentDate);
document.getElementById('dateText').textContent = key;
var data = DIET_HISTORY[key];
if (!data) {
document.getElementById('summary-card').style.display = 'none';
document.getElementById('meal-list').innerHTML = '<div class="empty-state">当日暂无饮食记录</div>';
return;
}
document.getElementById('summary-card').style.display = 'block';
var total = dayTotal(data);
var diff = total - data.target;
var diffText = diff > 0 ? '+' + diff : diff;
var diffCls = diff > 0 ? 'summary-item__val--over' : 'summary-item__val--ok';
document.getElementById('summary-row').innerHTML =
'<div class="summary-item"><div class="summary-item__val" style="color:#1677ff;">' + total + '</div><div class="summary-item__label">总摄入 kcal</div></div>' +
'<div class="summary-item"><div class="summary-item__val" style="color:#333;">' + data.target + '</div><div class="summary-item__label">目标 kcal</div></div>' +
'<div class="summary-item"><div class="summary-item__val ' + diffCls + '">' + diffText + '</div><div class="summary-item__label">与目标差值</div></div>';
var mealsHtml = data.meals.map(function(m) {
var foodsHtml = m.foods.map(function(f) {
return '<span class="food-tag">' + f + '</span>';
}).join('');
return '<div class="meal-card">' +
'<div class="meal-card__head"><span class="meal-card__name">' + m.icon + ' ' + m.name + '</span><span class="meal-card__time">' + m.time + '</span><span class="meal-card__kcal">' + m.kcal + ' <span>kcal</span></span></div>' +
'<div class="meal-card__foods">' + foodsHtml + '</div>' +
'<div class="meal-card__source"><span class="tag ' + m.cls + '">' + m.source + '</span></div>' +
'</div>';
}).join('');
document.getElementById('meal-list').innerHTML = mealsHtml;
}
/* 切换日期 */
function changeDate(delta) {
currentDate.setDate(currentDate.getDate() + delta);
calViewYear = currentDate.getFullYear();
calViewMonth = currentDate.getMonth();
render();
}
/* 日历面板 */
function toggleCalPanel() {
var panel = document.getElementById('calPanel');
if (panel.classList.contains('active')) { panel.classList.remove('active'); return; }
calViewYear = currentDate.getFullYear();
calViewMonth = currentDate.getMonth();
renderCal();
panel.classList.add('active');
}
function calPrevMonth(e) { e.stopPropagation(); calViewMonth--; if (calViewMonth < 0) { calViewMonth = 11; calViewYear--; } renderCal(); }
function calNextMonth(e) { e.stopPropagation(); calViewMonth++; if (calViewMonth > 11) { calViewMonth = 0; calViewYear++; } renderCal(); }
function renderCal() {
document.getElementById('calMonthLabel').textContent = calViewYear + '年' + (calViewMonth + 1) + '月';
var firstDay = new Date(calViewYear, calViewMonth, 1).getDay();
var daysInMonth = new Date(calViewYear, calViewMonth + 1, 0).getDate();
var today = new Date(2026, 5, 22);
var html = '';
for (var i = 0; i < firstDay; i++) { html += '<div class="cal-panel__day cal-panel__day--other"></div>'; }
for (var d = 1; d <= daysInMonth; d++) {
var cls = 'cal-panel__day';
if (calViewYear === today.getFullYear() && calViewMonth === today.getMonth() && d === today.getDate()) cls += ' cal-panel__day--today';
if (calViewYear === currentDate.getFullYear() && calViewMonth === currentDate.getMonth() && d === currentDate.getDate()) cls += ' cal-panel__day--selected';
html += '<div class="' + cls + '" onclick="selectCalDay(event,' + d + ')">' + d + '</div>';
}
document.getElementById('calGrid').innerHTML = html;
}
function selectCalDay(e, day) {
e.stopPropagation();
currentDate = new Date(calViewYear, calViewMonth, day);
calViewYear = currentDate.getFullYear();
calViewMonth = currentDate.getMonth();
render();
document.getElementById('calPanel').classList.remove('active');
}
document.addEventListener('click', function(e) {
if (!e.target.closest('.date-nav') && !e.target.closest('.cal-panel')) {
document.getElementById('calPanel').classList.remove('active');
}
});
/* 初始化渲染 */
render();
</script>
</body>
</html>
@@ -0,0 +1,269 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f7f8fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; position: relative; }
.nav-back { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 员工卡片 */
.emp-card { background: linear-gradient(135deg,#1677ff,#0958d9); padding: 16px 20px; color: #fff; }
.emp-card__row { display: flex; align-items: center; gap: 12px; }
.emp-card__avatar { width: 48px; height: 48px; border-radius: 50%; background: rgba(255,255,255,0.25); border: 2px solid rgba(255,255,255,0.4); display: flex; align-items: center; justify-content: center; font-size: 24px; flex-shrink: 0; }
.emp-card__name { font-size: 18px; font-weight: 700; }
.emp-card__meta { font-size: 12px; color: rgba(255,255,255,0.85); margin-top: 2px; }
.emp-card__tags { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.emp-card__tag { font-size: 11px; padding: 3px 10px; border-radius: 12px; background: rgba(255,255,255,0.2); }
/* 卡片区块 */
.card { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.card__title { font-size: 14px; font-weight: 700; color: #1a1a1a; margin-bottom: 10px; display: flex; align-items: center; gap: 6px; }
.card__title .readonly { font-size: 10px; color: #999; font-weight: 400; background: #f5f5f5; padding: 1px 6px; border-radius: 8px; }
/* 关键摘要 */
.alert-item { display: flex; align-items: flex-start; gap: 8px; padding: 8px 0; border-bottom: 1px solid #f5f5f5; }
.alert-item:last-child { border-bottom: none; }
.alert-item__label { font-size: 12px; color: #999; width: 64px; flex-shrink: 0; margin-top: 2px; }
.alert-item__tags { flex: 1; display: flex; flex-wrap: wrap; gap: 6px; }
.atag { font-size: 12px; padding: 3px 10px; border-radius: 12px; }
.atag--danger { background: #fff1f0; color: #f5222d; }
.atag--warn { background: #fff7e6; color: #fa8c16; }
.atag--blue { background: #f0f7ff; color: #1677ff; }
.atag--normal { background: #f6ffed; color: #52c41a; }
/* 信息行 */
.info-row { display: flex; align-items: center; justify-content: space-between; padding: 9px 0; border-bottom: 0.5px solid #f5f5f5; }
.info-row:last-child { border-bottom: none; }
.info-row .i-label { font-size: 13px; color: #666; }
.info-row .i-value { font-size: 13px; color: #333; font-weight: 500; }
.info-row .i-value .i-unit { font-size: 11px; color: #999; margin-left: 2px; }
/* 标签组 */
.tag-group { display: flex; flex-wrap: wrap; gap: 6px; }
.tag-group .tg { font-size: 12px; padding: 4px 10px; border-radius: 12px; background: #f0f7ff; color: #1677ff; }
.tag-group .tg.warn { background: #fff7e6; color: #fa8c16; }
.tag-group .tg.danger { background: #fff1f0; color: #f5222d; }
.tag-group .tg.green { background: #f6ffed; color: #52c41a; }
/* 子标签 */
.sub-label { font-size: 12px; color: #999; margin-bottom: 6px; }
.sub-block { padding: 8px 0; border-bottom: 0.5px solid #f5f5f5; }
.sub-block:last-child { border-bottom: none; }
/* 餐次配比 */
.ratio-bar { display: flex; height: 10px; border-radius: 5px; overflow: hidden; background: #f0f0f0; margin-bottom: 10px; }
.ratio-bar .ratio-seg { height: 100%; }
.ratio-list { display: flex; }
.ratio-list .ratio-item { flex: 1; display: flex; align-items: center; gap: 4px; font-size: 12px; }
.ratio-list .ratio-item .r-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.ratio-list .ratio-item .r-name { color: #999; }
.ratio-list .ratio-item .r-val { color: #333; font-weight: 600; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<!-- [交互] 点击返回上一页 -->
<button class="nav-back" onclick="history.back()">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">员工健康档案</span>
</div>
<div class="scroll-content">
<!-- 员工卡片 -->
<div class="emp-card">
<div class="emp-card__row">
<div class="emp-card__avatar">👩</div>
<div>
<div class="emp-card__name">张小红</div>
<div class="emp-card__meta">女 · 32岁 · 公司机关 · 办公室 · 162cm · 67.2kg · BMI 25.6</div>
</div>
</div>
<div class="emp-card__tags">
<span class="emp-card__tag">减脂需求</span>
<span class="emp-card__tag">中度活动</span>
</div>
</div>
<!-- 关键摘要(营养师定制方案重点参考) -->
<div class="card" style="border-left:3px solid #1677ff;">
<div class="card__title">🎯 方案定制关键信息</div>
<div class="alert-item">
<span class="alert-item__label">体检异常</span>
<div class="alert-item__tags">
<span class="atag atag--warn">尿酸 380 偏高</span>
<span class="atag atag--normal">血压/血糖正常</span>
</div>
</div>
<div class="alert-item">
<span class="alert-item__label">疾病史</span>
<div class="alert-item__tags">
<span class="atag atag--danger">胃炎</span>
<span class="atag atag--danger">高尿酸</span>
</div>
</div>
<div class="alert-item">
<span class="alert-item__label">用药情况</span>
<div class="alert-item__tags">
<span class="atag atag--blue">别嘌醇片</span>
<span class="atag atag--blue">依那普利</span>
</div>
</div>
<div class="alert-item">
<span class="alert-item__label">过敏原</span>
<div class="alert-item__tags">
<span class="atag atag--danger">🥜 花生</span>
<span class="atag atag--danger">🦐 海鲜</span>
</div>
</div>
<div class="alert-item">
<span class="alert-item__label">饮食禁忌</span>
<div class="alert-item__tags">
<span class="atag atag--warn">🌶️ 少辣</span>
<span class="atag atag--warn">🧂 低盐</span>
<span class="atag atag--warn">🍬 控糖</span>
</div>
</div>
</div>
<!-- 1. 身体指标 -->
<div class="card">
<div class="card__title">📏 身体指标</div>
<div class="info-row"><span class="i-label">身高</span><span class="i-value">172<span class="i-unit">cm</span></span></div>
<div class="info-row"><span class="i-label">体重</span><span class="i-value">68<span class="i-unit">kg</span></span></div>
<div class="info-row"><span class="i-label">BMI</span><span class="i-value">23.0 <span style="color:#52c41a;font-size:11px;">正常</span></span></div>
<div class="info-row"><span class="i-label">体脂率</span><span class="i-value">18.5% <span style="color:#52c41a;font-size:11px;">标准</span></span></div>
<div class="info-row"><span class="i-label">活动水平</span><span class="i-value">中度活动<span class="i-unit">每周3-5次</span></span></div>
</div>
<!-- 2. 体检信息 -->
<div class="card">
<div class="card__title">🩺 体检信息<span class="readonly">只读</span></div>
<div class="info-row"><span class="i-label">体检日期</span><span class="i-value">2026-03-15</span></div>
<div class="info-row"><span class="i-label">血压</span><span class="i-value">125/80<span class="i-unit">mmHg</span></span></div>
<div class="info-row"><span class="i-label">空腹血糖</span><span class="i-value">5.2<span class="i-unit">mmol/L</span></span></div>
<div class="info-row"><span class="i-label">血脂</span><span class="i-value">正常</span></div>
<div class="info-row"><span class="i-label">尿酸</span><span class="i-value" style="color:#fa8c16;">380<span class="i-unit">μmol/L</span> <span style="color:#fa8c16;font-size:11px;">偏高</span></span></div>
<div class="info-row"><span class="i-label">肝功能</span><span class="i-value">正常</span></div>
<div class="info-row"><span class="i-label">肾功能</span><span class="i-value">正常</span></div>
</div>
<!-- 3. 疾病信息 -->
<div class="card">
<div class="card__title">🏥 疾病信息</div>
<div class="sub-block">
<div class="sub-label">疾病史</div>
<div class="tag-group"><span class="tg danger">胃炎</span><span class="tg danger">高尿酸</span></div>
</div>
<div class="sub-block">
<div class="sub-label">用药情况</div>
<div class="tag-group"><span class="tg">别嘌醇片</span><span class="tg">依那普利</span></div>
</div>
<div class="sub-block">
<div class="sub-label">家族病史</div>
<div class="tag-group"><span class="tg warn">高血压</span><span class="tg warn">糖尿病</span></div>
</div>
</div>
<!-- 4. 口味偏好 -->
<div class="card">
<div class="card__title">👅 口味偏好</div>
<div class="sub-block">
<div class="sub-label">口味偏好</div>
<div class="tag-group"><span class="tg">偏辣</span></div>
</div>
<div class="sub-block">
<div class="sub-label">地域菜系</div>
<div class="tag-group"><span class="tg">川菜</span><span class="tg">湘菜</span></div>
</div>
</div>
<!-- 5. 禁忌关联 -->
<div class="card">
<div class="card__title">🚫 禁忌关联</div>
<div class="sub-block">
<div class="sub-label">饮食禁忌</div>
<div class="tag-group"><span class="tg warn">🌶️ 少辣</span><span class="tg warn">🧂 低盐</span><span class="tg warn">🍬 控糖</span></div>
</div>
<div class="sub-block">
<div class="sub-label">过敏原</div>
<div class="tag-group"><span class="tg danger">🥜 花生</span><span class="tg danger">🦐 海鲜</span></div>
</div>
</div>
<!-- 6. 动态生理 -->
<div class="card">
<div class="card__title">📊 动态生理<span class="readonly">只读</span></div>
<div class="info-row"><span class="i-label">日均运动量</span><span class="i-value">6000步/日</span></div>
<div class="info-row"><span class="i-label">静息心率</span><span class="i-value">68<span class="i-unit">bpm</span></span></div>
<div class="info-row"><span class="i-label">平均睡眠</span><span class="i-value">7.5h</span></div>
<div class="info-row"><span class="i-label">压力指数</span><span class="i-value"></span></div>
<div class="info-row"><span class="i-label">数据来源</span><span class="i-value">智能手环</span></div>
</div>
<!-- 7. 菜品偏好 -->
<div class="card">
<div class="card__title">🍲 菜品偏好</div>
<div class="sub-block">
<div class="sub-label">常点菜品</div>
<div class="tag-group"><span class="tg green">宫保鸡丁</span><span class="tg green">清炒西兰花</span><span class="tg green">杂粮饭</span></div>
</div>
<div class="sub-block">
<div class="sub-label">忌口菜品</div>
<div class="tag-group"><span class="tg warn">油炸花生</span></div>
</div>
</div>
<!-- 8. 妈妈味道 -->
<div class="card">
<div class="card__title">🏡 妈妈味道</div>
<div class="info-row"><span class="i-label">家乡</span><span class="i-value">四川成都</span></div>
<div class="sub-block">
<div class="sub-label">口味标签</div>
<div class="tag-group"><span class="tg warn">麻辣</span><span class="tg warn">红油</span><span class="tg warn">鲜香</span></div>
</div>
<div class="sub-block">
<div class="sub-label">思乡菜清单</div>
<div class="tag-group"><span class="tg warn">回锅肉</span><span class="tg warn">麻婆豆腐</span><span class="tg warn">水煮鱼</span></div>
</div>
</div>
<!-- 9. 餐次配比 -->
<div class="card">
<div class="card__title">⚖️ 餐次配比</div>
<div class="ratio-bar">
<span class="ratio-seg" style="width:30%; background:#FFB84C;"></span>
<span class="ratio-seg" style="width:40%; background:#52C41A;"></span>
<span class="ratio-seg" style="width:30%; background:#1677FF;"></span>
</div>
<div class="ratio-list">
<div class="ratio-item"><span class="r-dot" style="background:#FFB84C;"></span><span class="r-name">早餐</span><span class="r-val">30%</span></div>
<div class="ratio-item"><span class="r-dot" style="background:#52C41A;"></span><span class="r-name">午餐</span><span class="r-val">40%</span></div>
<div class="ratio-item"><span class="r-dot" style="background:#1677FF;"></span><span class="r-name">晚餐</span><span class="r-val">30%</span></div>
</div>
</div>
<div style="height:16px;"></div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
</script>
</body>
</html>
@@ -0,0 +1,192 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f7f8fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; position: relative; }
.nav-back { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 搜索与筛选 */
.search-bar { background: #fff; padding: 10px 16px; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; }
.search-input { display: flex; align-items: center; gap: 8px; background: #f5f5f5; border-radius: 20px; padding: 8px 14px; }
.search-input input { flex: 1; border: none; background: none; font-size: 14px; outline: none; color: #1a1a1a; }
/* Tab */
.seg-tabs { display: flex; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; }
.seg-tab { flex: 1; text-align: center; padding: 12px 0; font-size: 14px; color: #666; cursor: pointer; border-bottom: 2px solid transparent; }
.seg-tab.active { color: #1677ff; border-bottom-color: #1677ff; font-weight: 600; }
.seg-tab .badge { display: inline-block; background: #e8e8e8; color: #666; font-size: 10px; font-weight: 700; min-width: 16px; height: 16px; border-radius: 8px; padding: 0 4px; margin-left: 4px; vertical-align: middle; line-height: 16px; }
.seg-tab.active .badge { background: #e6f4ff; color: #1677ff; }
/* 员工卡片 */
.employee-card { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); display: flex; align-items: center; gap: 10px; cursor: pointer; }
.employee-card:active { transform: scale(0.98); }
.employee-card__avatar { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.employee-card__info { flex: 1; min-width: 0; }
.employee-card__name { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.employee-card__meta { font-size: 12px; color: #999; margin-top: 3px; display: flex; gap: 8px; flex-wrap: wrap; }
.employee-card__rate { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.employee-card__rate-num { font-size: 15px; font-weight: 700; }
.employee-card__rate-num--good { color: #52c41a; }
.employee-card__rate-num--bad { color: #ff4d4f; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; }
.tag-green { background: #f6ffed; color: #52c41a; }
.tag-orange { background: #fff7e6; color: #fa8c16; }
.tag-red { background: #fff1f0; color: #ff4d4f; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<button class="nav-back" onclick="location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">员工营养管理</span>
</div>
<!-- 搜索与筛选 -->
<div class="search-bar">
<div class="search-input">
<span style="font-size:14px;">🔍</span>
<input type="text" placeholder="搜索员工姓名 / 工号" />
</div>
</div>
<!-- Tab:全部 / 达标 / 未达标 -->
<div class="seg-tabs">
<div class="seg-tab active" onclick="switchTab(this,'panel-all')">全部<span class="badge">86</span></div>
<div class="seg-tab" onclick="switchTab(this,'panel-good')">达标<span class="badge">62</span></div>
<div class="seg-tab" onclick="switchTab(this,'panel-bad')">未达标<span class="badge">24</span></div>
</div>
<div class="scroll-content">
<!-- 全部列表 -->
<div id="panel-all">
<!-- [交互] 点击跳转至: ./employee-detail.html -->
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#ffe0e0,#ffb3b3);">👩</div>
<div class="employee-card__info">
<div class="employee-card__name">张小红</div>
<div class="employee-card__meta"><span>女 · 32岁</span><span>公司机关 · 办公室</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--bad">68%</span><span class="tag tag-red">未达标</span></div>
</div>
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#e8f4ff,#bdd7ff);">👨</div>
<div class="employee-card__info">
<div class="employee-card__name">李建国</div>
<div class="employee-card__meta"><span>男 · 45岁</span><span>采油一厂 · 技术部</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--bad">71%</span><span class="tag tag-orange">待干预</span></div>
</div>
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#f6ffed,#b7eb8f);">👩</div>
<div class="employee-card__info">
<div class="employee-card__name">王芳</div>
<div class="employee-card__meta"><span>女 · 28岁</span><span>信息技术中心 · 数据部</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--good">92%</span><span class="tag tag-green">达标</span></div>
</div>
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#e8f4ff,#bdd7ff);">👨</div>
<div class="employee-card__info">
<div class="employee-card__name">陈伟</div>
<div class="employee-card__meta"><span>男 · 36岁</span><span>安全环保部 · 安环部</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--good">88%</span><span class="tag tag-green">达标</span></div>
</div>
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#fff7e6,#ffd591);">👨</div>
<div class="employee-card__info">
<div class="employee-card__name">赵志强</div>
<div class="employee-card__meta"><span>男 · 61岁</span><span>公司机关 · 后勤部</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--bad">59%</span><span class="tag tag-red">未达标</span></div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 达标列表 -->
<div id="panel-good" style="display:none;">
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#f6ffed,#b7eb8f);">👩</div>
<div class="employee-card__info">
<div class="employee-card__name">王芳</div>
<div class="employee-card__meta"><span>女 · 28岁</span><span>信息技术中心 · 数据部</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--good">92%</span><span class="tag tag-green">达标</span></div>
</div>
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#e8f4ff,#bdd7ff);">👨</div>
<div class="employee-card__info">
<div class="employee-card__name">陈伟</div>
<div class="employee-card__meta"><span>男 · 36岁</span><span>安全环保部 · 安环部</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--good">88%</span><span class="tag tag-green">达标</span></div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 未达标列表 -->
<div id="panel-bad" style="display:none;">
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#ffe0e0,#ffb3b3);">👩</div>
<div class="employee-card__info">
<div class="employee-card__name">张小红</div>
<div class="employee-card__meta"><span>女 · 32岁</span><span>公司机关 · 办公室</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--bad">68%</span><span class="tag tag-red">未达标</span></div>
</div>
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#e8f4ff,#bdd7ff);">👨</div>
<div class="employee-card__info">
<div class="employee-card__name">李建国</div>
<div class="employee-card__meta"><span>男 · 45岁</span><span>采油一厂 · 技术部</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--bad">71%</span><span class="tag tag-orange">待干预</span></div>
</div>
<div class="employee-card" onclick="location.href='./employee-detail.html'">
<div class="employee-card__avatar" style="background:linear-gradient(135deg,#fff7e6,#ffd591);">👨</div>
<div class="employee-card__info">
<div class="employee-card__name">赵志强</div>
<div class="employee-card__meta"><span>男 · 61岁</span><span>公司机关 · 后勤部</span></div>
</div>
<div class="employee-card__rate"><span class="employee-card__rate-num employee-card__rate-num--bad">59%</span><span class="tag tag-red">未达标</span></div>
</div>
<div style="height:16px;"></div>
</div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
/* Tab 切换 */
function switchTab(el, panelId) {
el.closest('.seg-tabs').querySelectorAll('.seg-tab').forEach(function(t) { t.classList.remove('active'); });
el.classList.add('active');
['panel-all','panel-good','panel-bad'].forEach(function(id) {
document.getElementById(id).style.display = id === panelId ? 'block' : 'none';
});
}
</script>
</body>
</html>
@@ -0,0 +1,229 @@
<!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>
/* 公共重置与布局 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
/* 手机壳容器 */
.phone-shell { width: 390px; height: 844px; background: #fff; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
/* 状态栏 */
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
/* 滚动内容区 */
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 底部Tab栏 */
.tab-bar { height: 83px; background: #fff; border-top: 1px solid #e8e8e8; display: flex; align-items: flex-start; padding-top: 8px; flex-shrink: 0; }
.tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; padding: 4px 0; position: relative; }
.tab-item .tab-icon { font-size: 22px; line-height: 1; }
.tab-item .tab-label { font-size: 10px; color: #999; font-weight: 500; }
.tab-item.active .tab-label { color: #1677ff; }
.tab-badge { position: absolute; top: 2px; right: 18px; background: #ff4d4f; color: #fff; font-size: 10px; font-weight: 700; min-width: 16px; height: 16px; border-radius: 8px; display: flex; align-items: center; justify-content: center; padding: 0 4px; }
/* 分段Tab */
.seg-tabs { display: flex; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; }
.seg-tab { flex: 1; text-align: center; padding: 12px 0; font-size: 14px; color: #666; cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s; }
.seg-tab.active { color: #1677ff; border-bottom-color: #1677ff; font-weight: 600; }
.seg-tab .badge { display: inline-block; background: #e8e8e8; color: #666; font-size: 10px; font-weight: 700; min-width: 16px; height: 16px; border-radius: 8px; padding: 0 4px; margin-left: 4px; vertical-align: middle; line-height: 16px; }
.seg-tab.active .badge { background: #e6f4ff; color: #1677ff; }
/* 标签 */
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
.tag-blue { background: #e6f4ff; color: #1677ff; }
.tag-green { background: #f6ffed; color: #52c41a; }
.tag-orange { background: #fff7e6; color: #fa8c16; }
.tag-red { background: #fff1f0; color: #ff4d4f; }
.tag-gray { background: #f5f5f5; color: #999; }
/* 快捷入口网格 */
.quick-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 10px 16px 0; }
.quick-card { background: #fff; border-radius: 12px; padding: 14px; cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.06); display: flex; align-items: center; gap: 10px; transition: transform 0.15s; }
.quick-card:active { transform: scale(0.98); }
.quick-card__icon { width: 42px; height: 42px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.quick-card__info { flex: 1; min-width: 0; }
.quick-card__title { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.quick-card__desc { font-size: 11px; color: #999; margin-top: 2px; }
/* 咨询卡片 */
.consult-card { background: #fff; border-radius: 12px; margin: 10px 16px 0; padding: 14px; cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.06); transition: transform 0.15s; }
.consult-card:active { transform: scale(0.98); }
.cc-top { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.cc-avatar { width: 42px; height: 42px; border-radius: 50%; background: linear-gradient(135deg, #e8f4ff, #bdd7ff); display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.cc-info { flex: 1; min-width: 0; }
.cc-name { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.cc-meta { font-size: 12px; color: #999; margin-top: 2px; }
.cc-bottom { display: flex; align-items: center; justify-content: space-between; }
.cc-preview { font-size: 13px; color: #666; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 210px; }
.cc-time { font-size: 12px; color: #bbb; flex-shrink: 0; }
.unread-dot { width: 8px; height: 8px; border-radius: 50%; background: #ff4d4f; flex-shrink: 0; }
/* 弹窗样式 */
.modal-center { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 200; align-items: center; justify-content: center; }
.modal-center.active { display: flex; }
.modal-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.modal-box .modal-title { padding: 20px 20px 8px; font-size: 17px; font-weight: 700; text-align: center; color: #1a1a1a; }
.modal-box .modal-body { padding: 8px 20px 20px; font-size: 14px; color: #666; text-align: center; line-height: 1.6; }
.modal-box .modal-actions { display: flex; border-top: 1px solid #f0f0f0; }
.modal-box .modal-actions button { flex: 1; padding: 14px; font-size: 16px; cursor: pointer; background: none; border: none; }
.modal-box .modal-actions button:first-child { color: #666; border-right: 1px solid #f0f0f0; }
.modal-box .modal-actions button:last-child { color: #1677ff; font-weight: 600; }
</style>
</head>
<body>
<div class="phone-shell">
<!-- 状态栏 -->
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 蓝色渐变头部区域 -->
<div style="background:linear-gradient(135deg,#1677ff,#0958d9);padding:16px 20px 20px;flex-shrink:0;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;">
<div>
<div style="color:rgba(255,255,255,0.75);font-size:12px;margin-bottom:2px;">你好,李营养师</div>
<div style="color:#fff;font-size:20px;font-weight:700;">工作台</div>
</div>
<!-- [交互] 点击切换营养咨询服务状态 -->
<button id="wb-pause-btn" onclick="togglePause()" style="display:flex;align-items:center;gap:6px;background:rgba(255,255,255,0.2);border:1px solid rgba(255,255,255,0.4);border-radius:20px;padding:6px 14px;color:#fff;font-size:13px;cursor:pointer;">
<span id="pause-dot" style="width:8px;height:8px;border-radius:50%;background:#52c41a;display:inline-block;flex-shrink:0;"></span>
<span id="pause-label">咨询已开启</span>
</button>
</div>
<!-- 统计数字 -->
<div style="display:flex;">
<div style="flex:1;text-align:center;"><div style="color:#fff;font-size:22px;font-weight:700;">2</div><div style="color:rgba(255,255,255,0.75);font-size:11px;margin-top:2px;">营养咨询待回复</div></div>
<div style="width:1px;background:rgba(255,255,255,0.25);margin:4px 0;"></div>
<div style="flex:1;text-align:center;"><div style="color:#fff;font-size:22px;font-weight:700;">3</div><div style="color:rgba(255,255,255,0.75);font-size:11px;margin-top:2px;">方案待跟进</div></div>
<div style="width:1px;background:rgba(255,255,255,0.25);margin:4px 0;"></div>
<div style="flex:1;text-align:center;"><div style="color:#fff;font-size:22px;font-weight:700;">5</div><div style="color:rgba(255,255,255,0.75);font-size:11px;margin-top:2px;">食谱被引用</div></div>
</div>
</div>
<div class="scroll-content">
<!-- 快捷功能入口(2 个大 tab -->
<div style="display:flex;gap:10px;margin:10px 16px 0;">
<!-- [交互] 点击跳转至: ./employee-list.html -->
<div onclick="location.href='./employee-list.html'" style="flex:1;background:#fff;border-radius:12px;padding:16px 14px;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,0.06);text-align:center;transition:transform 0.15s;">
<div style="width:48px;height:48px;border-radius:14px;background:linear-gradient(135deg,#e6f4ff,#b7d8ff);display:flex;align-items:center;justify-content:center;font-size:24px;margin:0 auto 8px;">👥</div>
<div style="font-size:15px;font-weight:600;color:#1a1a1a;">员工营养管理</div>
<div style="font-size:11px;color:#999;margin-top:4px;">达标监控 · 一对一指导</div>
</div>
<!-- [交互] 点击跳转至: ./recipe-list.html -->
<div onclick="location.href='./recipe-list.html'" style="flex:1;background:#fff;border-radius:12px;padding:16px 14px;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,0.06);text-align:center;transition:transform 0.15s;">
<div style="width:48px;height:48px;border-radius:14px;background:linear-gradient(135deg,#fff7e6,#ffd591);display:flex;align-items:center;justify-content:center;font-size:24px;margin:0 auto 8px;">🥗</div>
<div style="font-size:15px;font-weight:600;color:#1a1a1a;">我的食谱</div>
<div style="font-size:11px;color:#999;margin-top:4px;">按周发布 · 引用查看</div>
</div>
</div>
<!-- 营养咨询列表 -->
<div id="consult-list" style="margin-top:4px;">
<div class="seg-tabs">
<div class="seg-tab active" onclick="switchWorkTab(this,'wb-text-pending')">待回复<span class="badge">2</span></div>
<div class="seg-tab" onclick="switchWorkTab(this,'wb-text-ongoing')">进行中<span class="badge" style="background:#fff7e6;color:#fa8c16;">1</span></div>
<div class="seg-tab" onclick="switchWorkTab(this,'wb-text-done')">已结束</div>
</div>
<!-- 待回复列表 -->
<div id="wb-text-pending">
<!-- [交互] 点击跳转至: ./consult-chat.html -->
<div class="consult-card" onclick="location.href='./consult-chat.html'">
<div class="cc-top"><div class="cc-avatar">👩</div><div class="cc-info"><div class="cc-name">张小红</div><div class="cc-meta">女 · 32岁 · 减脂需求</div></div><span class="tag tag-red">待回复</span></div>
<div class="cc-bottom"><div class="cc-preview">食堂太油了,想请教怎么吃才能减脂...</div><div style="display:flex;align-items:center;gap:6px;"><span class="cc-time">10:23</span><span class="unread-dot"></span></div></div>
</div>
<!-- [交互] 点击跳转至: ./consult-chat.html -->
<div class="consult-card" onclick="location.href='./consult-chat.html'">
<div class="cc-top"><div class="cc-avatar">👨</div><div class="cc-info"><div class="cc-name">李建国</div><div class="cc-meta">男 · 45岁 · 高血脂</div></div><span class="tag tag-red">待回复</span></div>
<div class="cc-bottom"><div class="cc-preview">血脂偏高,日常饮食有什么忌口...</div><div style="display:flex;align-items:center;gap:6px;"><span class="cc-time">09:55</span><span class="unread-dot"></span></div></div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 进行中列表 -->
<div id="wb-text-ongoing" style="display:none;">
<!-- [交互] 点击跳转至: ./consult-chat.html -->
<div class="consult-card" onclick="location.href='./consult-chat.html'">
<div class="cc-top"><div class="cc-avatar">👨</div><div class="cc-info"><div class="cc-name">陈伟</div><div class="cc-meta">男 · 36岁 · 增肌需求</div></div><span class="tag tag-blue">进行中</span></div>
<div class="cc-bottom"><div class="cc-preview">好的,我按您给的食谱吃一周看看</div><span class="cc-time">昨天</span></div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 已结束列表 -->
<div id="wb-text-done" style="display:none;">
<!-- [交互] 点击跳转至: ./consult-chat-done.html -->
<div class="consult-card" onclick="location.href='./consult-chat-done.html'">
<div class="cc-top"><div class="cc-avatar">👨</div><div class="cc-info"><div class="cc-name">赵磊</div><div class="cc-meta">男 · 35岁</div></div><span class="tag tag-green">已完成</span></div>
<div class="cc-bottom"><div class="cc-preview">谢谢营养师,方案很实用</div><span class="cc-time">08:40</span></div>
</div>
<div style="height:16px;"></div>
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 服务状态切换确认弹窗 -->
<div id="modal-pause" class="modal-center">
<div class="modal-box">
<div class="modal-title" id="pause-modal-title">关闭咨询</div>
<div class="modal-body" id="pause-modal-body">是否要关闭营养咨询?关闭后,员工无法发起营养咨询。</div>
<div class="modal-actions">
<button onclick="closeModal('modal-pause')">取消</button>
<button onclick="confirmTogglePause()">确认</button>
</div>
</div>
</div>
<!-- 底部5Tab导航栏 -->
<div class="tab-bar">
<div class="tab-item active" onclick="location.href='./index.html'"><span class="tab-icon">🏥</span><span class="tab-label">工作台</span><span class="tab-badge">2</span></div>
<div class="tab-item" onclick="location.href='../knowledge/index.html'"><span class="tab-icon">📚</span><span class="tab-label">知识库</span></div>
<div class="tab-item" onclick="location.href='../../ai-assistant/index.html'"><span class="tab-icon">🤖</span><span class="tab-label">AI助手</span></div>
<div class="tab-item" onclick="location.href='../../message/index.html'"><span class="tab-icon">💬</span><span class="tab-label">消息</span><span class="tab-badge">2</span></div>
<div class="tab-item" onclick="location.href='../profile/index.html'"><span class="tab-icon">👤</span><span class="tab-label">我的</span></div>
</div>
</div>
<script>
/* 标记当前角色为营养师,供共用页面(消息、AI助手)判断返回目标 */
sessionStorage.setItem('appRole', 'nutritionist');
/**
* 切换二级Tab(待回复/进行中/已结束)
* @param {HTMLElement} el - 被点击的Tab元素
* @param {string} panelId - 目标面板ID
*/
function switchWorkTab(el, panelId) {
el.closest('.seg-tabs').querySelectorAll('.seg-tab').forEach(function(t) { t.classList.remove('active'); });
el.classList.add('active');
var scrollContent = el.closest('.seg-tabs').nextElementSibling;
Array.from(scrollContent.children).forEach(function(p) {
if (p.id) p.style.display = p.id === panelId ? 'block' : 'none';
});
}
/* 服务状态切换 */
var isPaused = false;
function togglePause() {
var title = isPaused ? '开启咨询' : '关闭咨询';
var body = isPaused ? '是否要开启营养咨询?开启后,员工可发起营养咨询。' : '是否要关闭营养咨询?关闭后,员工无法发起营养咨询。';
document.getElementById('pause-modal-title').textContent = title;
document.getElementById('pause-modal-body').textContent = body;
openModal('modal-pause');
}
function confirmTogglePause() {
isPaused = !isPaused;
var bg = isPaused ? '#ff4d4f' : '#52c41a';
var text = isPaused ? '咨询已关闭' : '咨询已开启';
document.getElementById('pause-dot').style.background = bg;
document.getElementById('pause-label').textContent = text;
closeModal('modal-pause');
}
function openModal(id) { var m = document.getElementById(id); if (m) m.classList.add('active'); }
function closeModal(id) { var m = document.getElementById(id); if (m) m.classList.remove('active'); }
</script>
</body>
</html>
@@ -0,0 +1,407 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f7f8fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; position: relative; }
.nav-back { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 头部 */
.plan-header { background: linear-gradient(135deg,#1677ff,#0958d9); padding: 16px 20px; color: #fff; }
.plan-header__top { display: flex; align-items: center; gap: 12px; }
.plan-header__avatar { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 24px; flex-shrink: 0; background: rgba(255,255,255,0.25); border: 2px solid rgba(255,255,255,0.4); }
.plan-header__name { font-size: 17px; font-weight: 700; }
.plan-header__meta { font-size: 12px; color: rgba(255,255,255,0.85); margin-top: 2px; }
.plan-header__tag { margin-left: auto; font-size: 12px; padding: 4px 12px; border-radius: 14px; background: rgba(255,255,255,0.2); }
/* 卡片 */
.card { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.card__title { font-size: 14px; font-weight: 700; color: #1a1a1a; margin-bottom: 10px; display: flex; align-items: center; }
/* 营养目标 */
.goal-row { display: flex; }
.goal-item { flex: 1; text-align: center; }
.goal-item + .goal-item { border-left: 1px solid #f0f0f0; }
.goal-item__val { font-size: 16px; font-weight: 700; color: #1677ff; }
.goal-item__label { font-size: 11px; color: #999; margin-top: 2px; }
/* 三大营养素 */
.macro-bar { display: flex; height: 24px; border-radius: 6px; overflow: hidden; margin-bottom: 8px; }
.macro-bar__seg { display: flex; align-items: center; justify-content: center; font-size: 11px; color: #fff; font-weight: 600; }
.macro-legend { display: flex; gap: 12px; }
.macro-legend__item { display: flex; align-items: center; gap: 4px; font-size: 11px; color: #666; }
.macro-legend__dot { width: 8px; height: 8px; border-radius: 50%; }
/* 一周饮食安排(按天折叠,每天三餐,食物含分量与热量) */
.day-card { background: #fff; border: 1px solid #e8e8e8; border-radius: 10px; margin-bottom: 10px; overflow: hidden; }
.day-card:last-child { margin-bottom: 0; }
.day-header { display: flex; align-items: center; justify-content: space-between; padding: 12px; cursor: pointer; }
.day-header__day { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.day-header__summary { font-size: 12px; color: #999; }
.day-header__kcal { font-size: 12px; color: #fa8c16; font-weight: 600; }
.day-header .arrow { transition: transform .2s; flex-shrink: 0; }
.day-header .arrow.open { transform: rotate(180deg); }
.day-body { display: none; padding: 0 12px 12px; }
.day-body.open { display: block; }
.meal-block { background: #fafafa; border-radius: 8px; padding: 10px; margin-bottom: 8px; }
.meal-block:last-child { margin-bottom: 0; }
.meal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.meal-head__name { font-size: 13px; font-weight: 600; color: #1a1a1a; }
.meal-head__subtotal { font-size: 12px; color: #fa8c16; font-weight: 600; }
.food-row { display: flex; align-items: center; gap: 6px; padding: 6px 0; border-bottom: 1px dashed #f0f0f0; }
.food-row:last-child { border-bottom: none; }
.food-row__name { flex: 1; font-size: 13px; color: #333; }
.food-row__amount { font-size: 12px; color: #666; width: 44px; text-align: right; flex-shrink: 0; }
.food-row__kcal { font-size: 12px; color: #fa8c16; font-weight: 600; width: 56px; text-align: right; flex-shrink: 0; }
/* 执行进度 */
.checkin-row { display: flex; gap: 6px; }
.checkin-day { flex: 1; text-align: center; }
.checkin-day__dot { width: 26px; height: 26px; border-radius: 50%; margin: 0 auto; display: flex; align-items: center; justify-content: center; font-size: 12px; }
.checkin-day__dot--done { background: #52c41a; color: #fff; }
.checkin-day__dot--today { background: #e6f4ff; color: #1677ff; border: 1px solid #1677ff; }
.checkin-day__dot--pending { background: #f5f5f5; color: #bbb; }
.checkin-day__label { font-size: 10px; color: #999; margin-top: 4px; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
.tag-red { background: #fff1f0; color: #ff4d4f; }
.tag-orange { background: #fff7e6; color: #fa8c16; }
/* 底部弹层 */
.sheet-mask { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.45); z-index: 300; align-items: flex-end; justify-content: center; }
.sheet-mask.active { display: flex; }
.sheet-panel { width: 100%; max-height: 80%; background: #fff; border-radius: 16px 16px 0 0; display: flex; flex-direction: column; overflow: hidden; }
.sheet-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0; }
.sheet-header .s-cancel { font-size: 14px; color: #999; cursor: pointer; background: none; border: none; }
.sheet-header .s-title { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.sheet-header .s-save { font-size: 14px; color: #1677ff; font-weight: 600; cursor: pointer; background: none; border: none; }
.sheet-body { padding: 12px 16px 20px; overflow-y: auto; flex: 1; }
.sheet-body::-webkit-scrollbar { display: none; }
.sheet-tip { font-size: 12px; color: #999; background: #fafafa; border-radius: 8px; padding: 10px 12px; line-height: 1.6; margin-bottom: 12px; }
.adj-meal { margin-bottom: 14px; }
.adj-meal__label { font-size: 13px; font-weight: 600; color: #1a1a1a; margin-bottom: 8px; }
.adj-dish { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; padding: 5px 10px; border-radius: 14px; background: #f0f6ff; color: #1677ff; margin: 0 6px 6px 0; cursor: pointer; }
.adj-dish .x { color: #999; font-size: 12px; line-height: 1; }
.adj-add { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; padding: 5px 12px; border-radius: 14px; border: 1px dashed #1677ff; color: #1677ff; margin-bottom: 6px; cursor: pointer; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<button class="nav-back" onclick="location.href='./employee-detail.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">营养方案详情</span>
</div>
<div class="scroll-content">
<!-- 头部 -->
<div class="plan-header">
<div class="plan-header__top">
<div class="plan-header__avatar">👩</div>
<div>
<div class="plan-header__name">张小红</div>
<div class="plan-header__meta">减脂期一周饮食方案 · 女 32岁</div>
</div>
<span class="plan-header__tag">执行中</span>
</div>
</div>
<!-- 方案依据(健康档案摘要) -->
<div class="card" style="border-left:3px solid #1677ff;">
<div class="card__title">🎯 方案依据<span style="font-size:12px;color:#1677ff;font-weight:400;margin-left:auto;cursor:pointer;" onclick="location.href='./employee-health.html'">查看完整健康档案 </span></div>
<div style="display:flex;flex-wrap:wrap;gap:6px;">
<span class="tag tag-orange">尿酸 380 偏高</span>
<span class="tag tag-red">胃炎</span>
<span class="tag tag-red">高尿酸</span>
<span class="tag tag-red">过敏原:花生</span>
<span class="tag tag-red">过敏原:海鲜</span>
<span class="tag tag-orange">少辣 · 低盐 · 控糖</span>
</div>
</div>
<!-- 营养目标 -->
<div class="card">
<div class="card__title">营养目标</div>
<div class="goal-row">
<div class="goal-item"><div class="goal-item__val">1400</div><div class="goal-item__label">热量 kcal/日</div></div>
<div class="goal-item"><div class="goal-item__val">75</div><div class="goal-item__label">蛋白质 g</div></div>
<div class="goal-item"><div class="goal-item__val">30</div><div class="goal-item__label">脂肪 g</div></div>
<div class="goal-item"><div class="goal-item__val">190</div><div class="goal-item__label">碳水 g</div></div>
</div>
</div>
<!-- 三大营养素占比 -->
<div class="card">
<div class="card__title">三大营养素占比</div>
<div class="macro-bar">
<div class="macro-bar__seg" style="width:25%;background:#1677ff;">蛋白25%</div>
<div class="macro-bar__seg" style="width:20%;background:#fa8c16;">脂肪20%</div>
<div class="macro-bar__seg" style="width:55%;background:#52c41a;">碳水55%</div>
</div>
<div class="macro-legend">
<span class="macro-legend__item"><span class="macro-legend__dot" style="background:#1677ff;"></span>蛋白质</span>
<span class="macro-legend__item"><span class="macro-legend__dot" style="background:#fa8c16;"></span>脂肪</span>
<span class="macro-legend__item"><span class="macro-legend__dot" style="background:#52c41a;"></span>碳水化合物</span>
</div>
</div>
<!-- 饮食禁忌 -->
<div class="card">
<div class="card__title">饮食禁忌</div>
<div style="display:flex;gap:6px;">
<span class="tag tag-red">高糖</span>
<span class="tag tag-red">高脂</span>
<span class="tag tag-orange">高盐</span>
</div>
</div>
<!-- 一周饮食安排 -->
<div class="card">
<div class="card__title">一周饮食安排<span style="font-size:10px;color:#52c41a;font-weight:400;background:#f6ffed;padding:2px 8px;border-radius:8px;margin-left:8px;">系统生成</span><span style="font-size:12px;color:#1677ff;font-weight:400;margin-left:auto;cursor:pointer;" onclick="openSheet('sheet-adjust')">微调食谱 </span></div>
<div id="day-list"></div>
</div>
<!-- 执行进度 -->
<div class="card">
<div class="card__title">执行进度<span style="font-size:12px;color:#999;font-weight:400;margin-left:8px;">第 3 天 / 7 天</span></div>
<div class="checkin-row">
<div class="checkin-day"><div class="checkin-day__dot checkin-day__dot--done"></div><div class="checkin-day__label">周一</div></div>
<div class="checkin-day"><div class="checkin-day__dot checkin-day__dot--done"></div><div class="checkin-day__label">周二</div></div>
<div class="checkin-day"><div class="checkin-day__dot checkin-day__dot--today">3</div><div class="checkin-day__label">周三</div></div>
<div class="checkin-day"><div class="checkin-day__dot checkin-day__dot--pending">4</div><div class="checkin-day__label">周四</div></div>
<div class="checkin-day"><div class="checkin-day__dot checkin-day__dot--pending">5</div><div class="checkin-day__label">周五</div></div>
<div class="checkin-day"><div class="checkin-day__dot checkin-day__dot--pending">6</div><div class="checkin-day__label">周六</div></div>
<div class="checkin-day"><div class="checkin-day__dot checkin-day__dot--pending">7</div><div class="checkin-day__label">周日</div></div>
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 微调食谱底部弹层 -->
<div id="sheet-adjust" class="sheet-mask" onclick="if(event.target===this)closeSheet('sheet-adjust')">
<div class="sheet-panel" onclick="event.stopPropagation()">
<div class="sheet-header">
<button class="s-cancel" onclick="closeSheet('sheet-adjust')">取消</button>
<span class="s-title">微调一周食谱</span>
<button class="s-save" onclick="closeSheet('sheet-adjust');alert('食谱已微调(原型演示)')">保存</button>
</div>
<div class="sheet-body">
<div class="sheet-tip">系统已根据方案参数(1400kcal/日 · 蛋白25% · 脂肪20% · 碳水55% · 禁忌:高糖、高脂、高嘌呤)生成一周专属食谱,可替换任意餐品。</div>
<div id="adjust-list"></div>
</div>
</div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
/* ===== 一周专属食谱数据(每天三餐,每餐食物含分量与热量) ===== */
var WEEK = [
{ day: '周一', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '全麦面包', amount: 80, unit: 'g', kcal: 200 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '清蒸鲈鱼', amount: 150, unit: 'g', kcal: 160 },
{ name: '杂粮饭', amount: 200, unit: 'g', kcal: 240 },
{ name: '蒜蓉西兰花', amount: 150, unit: 'g', kcal: 60 },
{ name: '紫菜蛋花汤', amount: 200, unit: 'ml', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '香煎鸡胸', amount: 120, unit: 'g', kcal: 200 },
{ name: '蔬菜沙拉', amount: 200, unit: 'g', kcal: 80 },
{ name: '小米粥', amount: 200, unit: 'g', kcal: 120 }
]}
]},
{ day: '周二', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '燕麦牛奶', amount: 300, unit: 'ml', kcal: 220 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '白灼虾', amount: 150, unit: 'g', kcal: 140 },
{ name: '糙米饭', amount: 200, unit: 'g', kcal: 230 },
{ name: '清炒时蔬', amount: 150, unit: 'g', kcal: 55 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '蔬菜豆腐汤', amount: 300, unit: 'g', kcal: 150 },
{ name: '杂粮馒头', amount: 100, unit: 'g', kcal: 220 }
]}
]},
{ day: '周三', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '豆浆', amount: 250, unit: 'ml', kcal: 80 },
{ name: '蒸红薯', amount: 100, unit: 'g', kcal: 90 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '卤牛肉', amount: 100, unit: 'g', kcal: 160 },
{ name: '荞麦面', amount: 200, unit: 'g', kcal: 230 },
{ name: '凉拌黄瓜', amount: 150, unit: 'g', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '清炒时蔬', amount: 200, unit: 'g', kcal: 60 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '菌菇汤', amount: 200, unit: 'g', kcal: 50 }
]}
]},
{ day: '周四', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '杂粮粥', amount: 300, unit: 'g', kcal: 200 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '香煎鸡胸', amount: 150, unit: 'g', kcal: 250 },
{ name: '藜麦饭', amount: 150, unit: 'g', kcal: 180 },
{ name: '西兰花', amount: 150, unit: 'g', kcal: 60 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '虾仁沙拉', amount: 200, unit: 'g', kcal: 150 },
{ name: '全麦面包', amount: 50, unit: 'g', kcal: 120 }
]}
]},
{ day: '周五', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '全麦面包', amount: 80, unit: 'g', kcal: 200 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 },
{ name: '香蕉', amount: 100, unit: 'g', kcal: 90 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '清蒸鱼', amount: 150, unit: 'g', kcal: 160 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '凉拌木耳', amount: 100, unit: 'g', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '菌菇汤', amount: 300, unit: 'g', kcal: 120 },
{ name: '杂粮饭', amount: 150, unit: 'g', kcal: 180 }
]}
]},
{ day: '周六', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '燕麦', amount: 50, unit: 'g', kcal: 190 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '白灼虾', amount: 150, unit: 'g', kcal: 140 },
{ name: '杂粮饭', amount: 200, unit: 'g', kcal: 240 },
{ name: '蔬菜沙拉', amount: 150, unit: 'g', kcal: 60 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '蔬菜沙拉', amount: 200, unit: 'g', kcal: 80 },
{ name: '鸡胸肉', amount: 100, unit: 'g', kcal: 170 },
{ name: '玉米', amount: 100, unit: 'g', kcal: 110 }
]}
]},
{ day: '周日', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '豆浆', amount: 250, unit: 'ml', kcal: 80 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '卤牛肉', amount: 100, unit: 'g', kcal: 160 },
{ name: '糙米饭', amount: 200, unit: 'g', kcal: 230 },
{ name: '清炒时蔬', amount: 150, unit: 'g', kcal: 55 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '鸡胸沙拉', amount: 250, unit: 'g', kcal: 200 },
{ name: '小米粥', amount: 200, unit: 'g', kcal: 120 }
]}
]}
];
/* 计算某餐总热量 */
function mealSubtotal(di, mi) {
return WEEK[di].meals[mi].foods.reduce(function(s, f) { return s + f.kcal; }, 0);
}
/* 计算某天总热量 */
function dayTotal(di) {
return WEEK[di].meals.reduce(function(s, m, mi) { return s + mealSubtotal(di, mi); }, 0);
}
/* 某天三餐小计摘要 */
function daySummary(di) {
return WEEK[di].meals.map(function(m, mi) { return m.name.charAt(0) + mealSubtotal(di, mi); }).join(' · ');
}
/* 渲染一周饮食安排(折叠卡片,只读) */
function renderDayList() {
var html = '';
WEEK.forEach(function(day, di) {
html += '<div class="day-card">' +
'<div class="day-header" onclick="toggleDay(' + di + ')">' +
'<span class="day-header__day">' + day.day + '</span>' +
'<span class="day-header__summary">' + daySummary(di) + '</span>' +
'<span class="day-header__kcal">' + dayTotal(di) + ' kcal</span>' +
'<svg class="arrow" id="arrow-' + di + '" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="#999" stroke-width="2"><path d="M4 6l4 4 4-4"/></svg>' +
'</div>' +
'<div class="day-body" id="day-body-' + di + '">' +
day.meals.map(function(m, mi) {
return '<div class="meal-block">' +
'<div class="meal-head"><span class="meal-head__name">' + m.icon + ' ' + m.name + '</span><span class="meal-head__subtotal">小计 ' + mealSubtotal(di, mi) + ' kcal</span></div>' +
m.foods.map(function(f) {
return '<div class="food-row"><span class="food-row__name">' + f.name + '</span><span class="food-row__amount">' + f.amount + f.unit + '</span><span class="food-row__kcal">' + f.kcal + ' kcal</span></div>';
}).join('') +
'</div>';
}).join('') +
'</div>' +
'</div>';
});
document.getElementById('day-list').innerHTML = html;
/* 默认展开周一 */
document.getElementById('day-body-0').classList.add('open');
document.getElementById('arrow-0').classList.add('open');
}
/* 渲染微调弹层(食物带分量与热量) */
function renderAdjustList() {
var html = '';
WEEK.forEach(function(day) {
html += '<div class="adj-meal"><div class="adj-meal__label">' + day.day + '</div>';
day.meals.forEach(function(m) {
m.foods.forEach(function(f) {
html += '<span class="adj-dish">' + m.name.charAt(0) + '·' + f.name + ' ' + f.amount + f.unit + '·' + f.kcal + 'kcal<span class="x">×</span></span>';
});
});
html += '<span class="adj-add"> 添加餐品</span></div>';
});
document.getElementById('adjust-list').innerHTML = html;
}
/* 折叠/展开某天 */
function toggleDay(di) {
var body = document.getElementById('day-body-' + di);
var arrow = document.getElementById('arrow-' + di);
body.classList.toggle('open');
arrow.classList.toggle('open');
}
/* 打开底部弹层 */
function openSheet(id) { var m = document.getElementById(id); if (m) m.classList.add('active'); }
/* 关闭底部弹层 */
function closeSheet(id) { var m = document.getElementById(id); if (m) m.classList.remove('active'); }
/* 初始化渲染 */
renderDayList();
renderAdjustList();
</script>
</body>
</html>
@@ -0,0 +1,408 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f7f8fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; position: relative; }
.nav-back { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 表单 */
.form-card { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.form-card__title { font-size: 14px; font-weight: 700; color: #1a1a1a; margin-bottom: 12px; }
.form-item { margin-bottom: 14px; }
.form-item:last-child { margin-bottom: 0; }
.form-item__label { font-size: 13px; font-weight: 600; color: #1a1a1a; margin-bottom: 8px; }
.form-item__label .req { color: #ff4d4f; margin-left: 2px; }
.form-input { width: 100%; border: 1px solid #e8e8e8; border-radius: 8px; padding: 10px 12px; font-size: 14px; outline: none; color: #1a1a1a; }
.form-input:focus { border-color: #1677ff; }
/* 分段选择 */
.seg-picker { display: flex; background: #f5f5f5; border-radius: 8px; padding: 3px; }
.seg-picker__opt { flex: 1; text-align: center; padding: 7px 0; font-size: 13px; color: #666; border-radius: 6px; cursor: pointer; }
.seg-picker__opt.active { background: #fff; color: #1677ff; font-weight: 600; box-shadow: 0 1px 4px rgba(0,0,0,0.1); }
/* 日期范围 */
.date-range { display: flex; align-items: center; gap: 8px; }
.date-range .form-input { flex: 1; text-align: center; }
.date-range .sep { color: #999; flex-shrink: 0; }
/* 餐次配置 */
.meal-item { border: 1px solid #e8e8e8; border-radius: 10px; padding: 12px; margin-bottom: 10px; }
.meal-item:last-child { margin-bottom: 0; }
.meal-item__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.meal-item__name { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.meal-item__add { font-size: 12px; color: #1677ff; cursor: pointer; }
.meal-item__dishes { display: flex; flex-wrap: wrap; gap: 6px; }
.dish-chip { font-size: 11px; padding: 4px 10px; border-radius: 12px; background: #f0f6ff; color: #1677ff; }
/* 营养汇总 */
.nutri-summary { display: flex; gap: 8px; }
.nutri-summary__item { flex: 1; text-align: center; background: #fafafa; border-radius: 8px; padding: 8px 0; }
.nutri-summary__val { font-size: 14px; font-weight: 700; color: #1677ff; }
.nutri-summary__label { font-size: 10px; color: #999; margin-top: 2px; }
/* 底部操作 */
.bottom-bar { background: #fff; border-top: 1px solid #f0f0f0; padding: 10px 16px 14px; flex-shrink: 0; display: flex; gap: 10px; }
.btn-draft { flex: 1; padding: 12px; background: #fff; color: #1677ff; border: 1px solid #1677ff; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; }
.btn-pub { flex: 1; padding: 12px; background: #1677ff; color: #fff; border: none; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; }
.modal-center { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 200; align-items: center; justify-content: center; }
.modal-center.active { display: flex; }
.modal-box { width: 280px; background: #fff; border-radius: 16px; overflow: hidden; }
.modal-box .modal-title { padding: 20px 20px 8px; font-size: 17px; font-weight: 700; text-align: center; color: #1a1a1a; }
.modal-box .modal-body { padding: 8px 20px 20px; font-size: 14px; color: #666; text-align: center; line-height: 1.6; }
.modal-box .modal-actions { display: flex; border-top: 1px solid #f0f0f0; }
.modal-box .modal-actions button { flex: 1; padding: 14px; font-size: 16px; cursor: pointer; background: none; border: none; }
.modal-box .modal-actions button:first-child { color: #666; border-right: 1px solid #f0f0f0; }
.modal-box .modal-actions button:last-child { color: #1677ff; font-weight: 600; }
/* 按周折叠卡片 */
.day-card { background: #fff; border: 1px solid #e8e8e8; border-radius: 10px; margin-bottom: 10px; overflow: hidden; }
.day-card:last-child { margin-bottom: 0; }
.day-header { display: flex; align-items: center; justify-content: space-between; padding: 12px; cursor: pointer; }
.day-header__day { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.day-header__summary { font-size: 12px; color: #999; }
.day-header__kcal { font-size: 12px; color: #fa8c16; font-weight: 600; }
.day-header .arrow { transition: transform .2s; flex-shrink: 0; }
.day-header .arrow.open { transform: rotate(180deg); }
.day-body { display: none; padding: 0 12px 12px; }
.day-body.open { display: block; }
/* 每餐块 */
.meal-block { background: #fafafa; border-radius: 8px; padding: 10px; margin-bottom: 8px; }
.meal-block:last-child { margin-bottom: 0; }
.meal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.meal-head__name { font-size: 13px; font-weight: 600; color: #1a1a1a; }
.meal-head__subtotal { font-size: 12px; color: #fa8c16; font-weight: 600; }
/* 食物行 */
.food-row { display: flex; align-items: center; gap: 6px; padding: 6px 0; border-bottom: 1px dashed #f0f0f0; }
.food-row:last-child { border-bottom: none; }
.food-row__name { flex: 1; font-size: 13px; color: #333; }
.food-row__input { width: 52px; border: 1px solid #e8e8e8; border-radius: 6px; padding: 4px 6px; font-size: 12px; text-align: center; outline: none; color: #333; background: #fff; }
.food-row__input:focus { border-color: #1677ff; }
.food-row__unit { font-size: 11px; color: #999; }
.food-row__del { width: 20px; height: 20px; border-radius: 50%; border: none; background: #f0f0f0; color: #999; font-size: 12px; cursor: pointer; flex-shrink: 0; line-height: 1; }
/* 添加食物 */
.add-food { display: flex; align-items: center; justify-content: center; gap: 4px; padding: 6px 0; font-size: 12px; color: #1677ff; border: 1px dashed #1677ff; border-radius: 8px; cursor: pointer; margin-top: 8px; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<button class="nav-back" onclick="location.href='./recipe-list.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">制定食谱</span>
</div>
<div class="scroll-content">
<!-- 基本信息 -->
<div class="form-card">
<div class="form-card__title">基本信息</div>
<div class="form-item">
<div class="form-item__label">食谱名称<span class="req">*</span></div>
<input class="form-input" type="text" placeholder="如:机关减脂周菜单(第 38 周)" />
</div>
<div class="form-item">
<div class="form-item__label">食谱类型<span class="req">*</span></div>
<div class="seg-picker">
<div class="seg-picker__opt active" onclick="segPick(this)">周菜单</div>
<div class="seg-picker__opt" onclick="segPick(this)">季节菜单</div>
</div>
</div>
<div class="form-item">
<div class="form-item__label">发布周期<span class="req">*</span></div>
<div class="date-range">
<input class="form-input" type="text" value="2026-09-21" />
<span class="sep"></span>
<input class="form-input" type="text" value="2026-09-27" />
</div>
</div>
<div class="form-item">
<div class="form-item__label">适用食堂</div>
<div class="seg-picker">
<div class="seg-picker__opt active" onclick="segPick(this)">全部食堂</div>
<div class="seg-picker__opt" onclick="segPick(this)">指定食堂</div>
</div>
</div>
</div>
<!-- 餐次配置(按周 · 每天三餐) -->
<div class="form-card">
<div class="form-card__title">餐次配置(按周 · 每天三餐)</div>
<div id="day-list"></div>
</div>
<!-- 营养汇总 -->
<div class="form-card">
<div class="form-card__title">营养汇总(日均估算)</div>
<div class="nutri-summary">
<div class="nutri-summary__item"><div class="nutri-summary__val">1380</div><div class="nutri-summary__label">热量 kcal</div></div>
<div class="nutri-summary__item"><div class="nutri-summary__val">78</div><div class="nutri-summary__label">蛋白质 g</div></div>
<div class="nutri-summary__item"><div class="nutri-summary__val">32</div><div class="nutri-summary__label">脂肪 g</div></div>
<div class="nutri-summary__item"><div class="nutri-summary__val">188</div><div class="nutri-summary__label">碳水 g</div></div>
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- 底部操作 -->
<div class="bottom-bar">
<button class="btn-draft" onclick="location.href='./recipe-list.html'">存为草稿</button>
<button class="btn-pub" onclick="openModal('modal-pub')">发布食谱</button>
</div>
<!-- 发布确认弹窗 -->
<div id="modal-pub" class="modal-center">
<div class="modal-box">
<div class="modal-title">发布食谱</div>
<div class="modal-body">发布后该食谱将推送给适用食堂,食堂可引用至食谱排期。确认发布?</div>
<div class="modal-actions">
<button onclick="closeModal('modal-pub')">取消</button>
<button onclick="closeModal('modal-pub');location.href='./recipe-list.html'">确认发布</button>
</div>
</div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
/* 分段选择 */
function segPick(el) {
el.parentElement.querySelectorAll('.seg-picker__opt').forEach(function(o) { o.classList.remove('active'); });
el.classList.add('active');
}
/* ===== 一周食谱数据(每天三餐,每餐食物含摄入量与热量) ===== */
var WEEK = [
{ day: '周一', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '全麦面包', amount: 80, unit: 'g', kcal: 200 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '清蒸鲈鱼', amount: 150, unit: 'g', kcal: 160 },
{ name: '杂粮饭', amount: 200, unit: 'g', kcal: 240 },
{ name: '蒜蓉西兰花', amount: 150, unit: 'g', kcal: 60 },
{ name: '紫菜蛋花汤', amount: 200, unit: 'ml', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '香煎鸡胸', amount: 120, unit: 'g', kcal: 200 },
{ name: '蔬菜沙拉', amount: 200, unit: 'g', kcal: 80 },
{ name: '小米粥', amount: 200, unit: 'g', kcal: 120 }
]}
]},
{ day: '周二', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '燕麦牛奶', amount: 300, unit: 'ml', kcal: 220 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '白灼虾', amount: 150, unit: 'g', kcal: 140 },
{ name: '糙米饭', amount: 200, unit: 'g', kcal: 230 },
{ name: '清炒时蔬', amount: 150, unit: 'g', kcal: 55 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '蔬菜豆腐汤', amount: 300, unit: 'g', kcal: 150 },
{ name: '杂粮馒头', amount: 100, unit: 'g', kcal: 220 }
]}
]},
{ day: '周三', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '豆浆', amount: 250, unit: 'ml', kcal: 80 },
{ name: '蒸红薯', amount: 100, unit: 'g', kcal: 90 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '卤牛肉', amount: 100, unit: 'g', kcal: 160 },
{ name: '荞麦面', amount: 200, unit: 'g', kcal: 230 },
{ name: '凉拌黄瓜', amount: 150, unit: 'g', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '清炒时蔬', amount: 200, unit: 'g', kcal: 60 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '菌菇汤', amount: 200, unit: 'g', kcal: 50 }
]}
]},
{ day: '周四', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '杂粮粥', amount: 300, unit: 'g', kcal: 200 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '香煎鸡胸', amount: 150, unit: 'g', kcal: 250 },
{ name: '藜麦饭', amount: 150, unit: 'g', kcal: 180 },
{ name: '西兰花', amount: 150, unit: 'g', kcal: 60 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '虾仁沙拉', amount: 200, unit: 'g', kcal: 150 },
{ name: '全麦面包', amount: 50, unit: 'g', kcal: 120 }
]}
]},
{ day: '周五', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '全麦面包', amount: 80, unit: 'g', kcal: 200 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 },
{ name: '香蕉', amount: 100, unit: 'g', kcal: 90 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '清蒸鱼', amount: 150, unit: 'g', kcal: 160 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '凉拌木耳', amount: 100, unit: 'g', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '菌菇汤', amount: 300, unit: 'g', kcal: 120 },
{ name: '杂粮饭', amount: 150, unit: 'g', kcal: 180 }
]}
]},
{ day: '周六', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '燕麦', amount: 50, unit: 'g', kcal: 190 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '白灼虾', amount: 150, unit: 'g', kcal: 140 },
{ name: '杂粮饭', amount: 200, unit: 'g', kcal: 240 },
{ name: '蔬菜沙拉', amount: 150, unit: 'g', kcal: 60 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '蔬菜沙拉', amount: 200, unit: 'g', kcal: 80 },
{ name: '鸡胸肉', amount: 100, unit: 'g', kcal: 170 },
{ name: '玉米', amount: 100, unit: 'g', kcal: 110 }
]}
]},
{ day: '周日', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '豆浆', amount: 250, unit: 'ml', kcal: 80 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '卤牛肉', amount: 100, unit: 'g', kcal: 160 },
{ name: '糙米饭', amount: 200, unit: 'g', kcal: 230 },
{ name: '清炒时蔬', amount: 150, unit: 'g', kcal: 55 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '鸡胸沙拉', amount: 250, unit: 'g', kcal: 200 },
{ name: '小米粥', amount: 200, unit: 'g', kcal: 120 }
]}
]}
];
/* 计算某餐总热量 */
function mealSubtotal(di, mi) {
return WEEK[di].meals[mi].foods.reduce(function(s, f) { return s + (parseInt(f.kcal) || 0); }, 0);
}
/* 计算某天总热量 */
function dayTotal(di) {
return WEEK[di].meals.reduce(function(s, m, mi) { return s + mealSubtotal(di, mi); }, 0);
}
/* 某天三餐小计摘要 */
function daySummary(di) {
return WEEK[di].meals.map(function(m, mi) { return m.name.charAt(0) + mealSubtotal(di, mi); }).join(' · ');
}
/* 渲染单个食物行 */
function renderFood(di, mi, fi) {
var f = WEEK[di].meals[mi].foods[fi];
return '<div class="food-row">' +
'<span class="food-row__name">' + f.name + '</span>' +
'<input class="food-row__input" type="text" value="' + f.amount + '" oninput="updateAmount(' + di + ',' + mi + ',' + fi + ',this)" />' +
'<span class="food-row__unit">' + f.unit + '</span>' +
'<input class="food-row__input" type="text" value="' + f.kcal + '" oninput="updateKcal(' + di + ',' + mi + ',' + fi + ',this)" />' +
'<span class="food-row__unit">kcal</span>' +
'<button class="food-row__del" onclick="removeFood(' + di + ',' + mi + ',' + fi + ')">×</button>' +
'</div>';
}
/* 渲染某餐块 */
function renderMeal(di, mi) {
var m = WEEK[di].meals[mi];
var html = '<div class="meal-block">' +
'<div class="meal-head"><span class="meal-head__name">' + m.icon + ' ' + m.name + '</span><span class="meal-head__subtotal" id="meal-sub-' + di + '-' + mi + '">小计 ' + mealSubtotal(di, mi) + ' kcal</span></div>';
m.foods.forEach(function(f, fi) { html += renderFood(di, mi, fi); });
html += '<div class="add-food" onclick="addFood(' + di + ',' + mi + ')"> 添加食物</div></div>';
return html;
}
/* 渲染整周折叠卡片 */
function renderDayList() {
var html = '';
WEEK.forEach(function(day, di) {
html += '<div class="day-card">' +
'<div class="day-header" onclick="toggleDay(' + di + ')">' +
'<span class="day-header__day">' + day.day + '</span>' +
'<span class="day-header__summary">' + daySummary(di) + '</span>' +
'<span class="day-header__kcal" id="day-kcal-' + di + '">' + dayTotal(di) + ' kcal</span>' +
'<svg class="arrow" id="arrow-' + di + '" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="#999" stroke-width="2"><path d="M4 6l4 4 4-4"/></svg>' +
'</div>' +
'<div class="day-body" id="day-body-' + di + '">' +
day.meals.map(function(m, mi) { return renderMeal(di, mi); }).join('') +
'</div>' +
'</div>';
});
document.getElementById('day-list').innerHTML = html;
/* 默认展开周一 */
document.getElementById('day-body-0').classList.add('open');
document.getElementById('arrow-0').classList.add('open');
}
/* 折叠/展开某天 */
function toggleDay(di) {
var body = document.getElementById('day-body-' + di);
var arrow = document.getElementById('arrow-' + di);
body.classList.toggle('open');
arrow.classList.toggle('open');
}
/* 更新摄入量 */
function updateAmount(di, mi, fi, el) {
WEEK[di].meals[mi].foods[fi].amount = el.value;
}
/* 更新热量并重算小计与总计 */
function updateKcal(di, mi, fi, el) {
WEEK[di].meals[mi].foods[fi].kcal = parseInt(el.value) || 0;
document.getElementById('meal-sub-' + di + '-' + mi).textContent = '小计 ' + mealSubtotal(di, mi) + ' kcal';
document.getElementById('day-kcal-' + di).textContent = dayTotal(di) + ' kcal';
}
/* 添加食物 */
function addFood(di, mi) {
WEEK[di].meals[mi].foods.push({ name: '新食物', amount: 100, unit: 'g', kcal: 100 });
var body = document.getElementById('day-body-' + di);
body.innerHTML = WEEK[di].meals.map(function(m, mi2) { return renderMeal(di, mi2); }).join('');
}
/* 删除食物 */
function removeFood(di, mi, fi) {
WEEK[di].meals[mi].foods.splice(fi, 1);
var body = document.getElementById('day-body-' + di);
body.innerHTML = WEEK[di].meals.map(function(m, mi2) { return renderMeal(di, mi2); }).join('');
document.getElementById('day-kcal-' + di).textContent = dayTotal(di) + ' kcal';
}
/* 初始化渲染 */
renderDayList();
function openModal(id) { var m = document.getElementById(id); if (m) m.classList.add('active'); }
function closeModal(id) { var m = document.getElementById(id); if (m) m.classList.remove('active'); }
</script>
</body>
</html>
@@ -0,0 +1,309 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f7f8fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; position: relative; }
.nav-back { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* 头部 */
.recipe-header { background: linear-gradient(135deg,#1677ff,#0958d9); padding: 16px 20px; color: #fff; }
.recipe-header__name { font-size: 18px; font-weight: 700; }
.recipe-header__meta { font-size: 12px; color: rgba(255,255,255,0.85); margin-top: 6px; }
.recipe-header__tags { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.recipe-header__tag { font-size: 11px; padding: 3px 10px; border-radius: 12px; background: rgba(255,255,255,0.2); }
/* 卡片 */
.card { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.card__title { font-size: 14px; font-weight: 700; color: #1a1a1a; margin-bottom: 10px; }
/* 引用统计 */
.cite-stat { display: flex; gap: 8px; }
.cite-stat__item { flex: 1; text-align: center; background: #fafafa; border-radius: 8px; padding: 10px 0; }
.cite-stat__val { font-size: 16px; font-weight: 700; color: #1677ff; }
.cite-stat__label { font-size: 11px; color: #999; margin-top: 2px; }
/* 食堂引用列表 */
.canteen-item { display: flex; align-items: center; gap: 10px; padding: 12px 0; border-bottom: 1px solid #f5f5f5; }
.canteen-item:last-child { border-bottom: none; }
.canteen-item__icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.canteen-item__info { flex: 1; min-width: 0; }
.canteen-item__name { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.canteen-item__meta { font-size: 12px; color: #999; margin-top: 2px; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
.tag-green { background: #f6ffed; color: #52c41a; }
.tag-orange { background: #fff7e6; color: #fa8c16; }
/* 一周餐次安排(按天折叠,每天三餐,食物含摄入量与热量) */
.day-card { background: #fff; border: 1px solid #e8e8e8; border-radius: 10px; margin-bottom: 10px; overflow: hidden; }
.day-card:last-child { margin-bottom: 0; }
.day-header { display: flex; align-items: center; justify-content: space-between; padding: 12px; cursor: pointer; }
.day-header__day { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.day-header__summary { font-size: 12px; color: #999; }
.day-header__kcal { font-size: 12px; color: #fa8c16; font-weight: 600; }
.day-header .arrow { transition: transform .2s; flex-shrink: 0; }
.day-header .arrow.open { transform: rotate(180deg); }
.day-body { display: none; padding: 0 12px 12px; }
.day-body.open { display: block; }
.meal-block { background: #fafafa; border-radius: 8px; padding: 10px; margin-bottom: 8px; }
.meal-block:last-child { margin-bottom: 0; }
.meal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.meal-head__name { font-size: 13px; font-weight: 600; color: #1a1a1a; }
.meal-head__subtotal { font-size: 12px; color: #fa8c16; font-weight: 600; }
.food-row { display: flex; align-items: center; gap: 6px; padding: 6px 0; border-bottom: 1px dashed #f0f0f0; }
.food-row:last-child { border-bottom: none; }
.food-row__name { flex: 1; font-size: 13px; color: #333; }
.food-row__amount { font-size: 12px; color: #666; width: 44px; text-align: right; flex-shrink: 0; }
.food-row__kcal { font-size: 12px; color: #fa8c16; font-weight: 600; width: 56px; text-align: right; flex-shrink: 0; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<button class="nav-back" onclick="location.href='./recipe-list.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">食谱详情</span>
</div>
<div class="scroll-content">
<!-- 头部 -->
<div class="recipe-header">
<div class="recipe-header__name">机关减脂周菜单(第 37 周)</div>
<div class="recipe-header__meta">周菜单 · 2026-09-14 至 09-20 · 李营养师</div>
<div class="recipe-header__tags">
<span class="recipe-header__tag">低脂</span>
<span class="recipe-header__tag">均衡营养</span>
<span class="recipe-header__tag">已发布</span>
</div>
</div>
<!-- 引用情况 -->
<div class="card">
<div class="card__title">引用情况</div>
<div class="cite-stat">
<div class="cite-stat__item"><div class="cite-stat__val">5</div><div class="cite-stat__label">已引用食堂</div></div>
<div class="cite-stat__item"><div class="cite-stat__val">3</div><div class="cite-stat__label">待确认</div></div>
<div class="cite-stat__item"><div class="cite-stat__val">0</div><div class="cite-stat__label">已拒绝</div></div>
</div>
<div style="margin-top:12px;">
<div class="canteen-item">
<div class="canteen-item__icon" style="background:linear-gradient(135deg,#f6ffed,#b7eb8f);">🏢</div>
<div class="canteen-item__info"><div class="canteen-item__name">机关食堂</div><div class="canteen-item__meta">公司机关 · 已排期</div></div>
<span class="tag tag-green">已引用</span>
</div>
<div class="canteen-item">
<div class="canteen-item__icon" style="background:linear-gradient(135deg,#e6f4ff,#bdd7ff);">🏭</div>
<div class="canteen-item__info"><div class="canteen-item__name">采油一厂食堂</div><div class="canteen-item__meta">采油一厂 · 已排期</div></div>
<span class="tag tag-green">已引用</span>
</div>
<div class="canteen-item">
<div class="canteen-item__icon" style="background:linear-gradient(135deg,#fff7e6,#ffd591);">🏪</div>
<div class="canteen-item__info"><div class="canteen-item__name">信息中心食堂</div><div class="canteen-item__meta">信息技术中心 · 待确认</div></div>
<span class="tag tag-orange">待确认</span>
</div>
</div>
</div>
<!-- 一周餐次安排 -->
<div class="card">
<div class="card__title">一周餐次安排</div>
<div id="day-list"></div>
</div>
<div style="height:16px;"></div>
</div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
/* ===== 一周食谱数据(每天三餐,每餐食物含摄入量与热量) ===== */
var WEEK = [
{ day: '周一', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '全麦面包', amount: 80, unit: 'g', kcal: 200 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '清蒸鲈鱼', amount: 150, unit: 'g', kcal: 160 },
{ name: '杂粮饭', amount: 200, unit: 'g', kcal: 240 },
{ name: '蒜蓉西兰花', amount: 150, unit: 'g', kcal: 60 },
{ name: '紫菜蛋花汤', amount: 200, unit: 'ml', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '香煎鸡胸', amount: 120, unit: 'g', kcal: 200 },
{ name: '蔬菜沙拉', amount: 200, unit: 'g', kcal: 80 },
{ name: '小米粥', amount: 200, unit: 'g', kcal: 120 }
]}
]},
{ day: '周二', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '燕麦牛奶', amount: 300, unit: 'ml', kcal: 220 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '白灼虾', amount: 150, unit: 'g', kcal: 140 },
{ name: '糙米饭', amount: 200, unit: 'g', kcal: 230 },
{ name: '清炒时蔬', amount: 150, unit: 'g', kcal: 55 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '蔬菜豆腐汤', amount: 300, unit: 'g', kcal: 150 },
{ name: '杂粮馒头', amount: 100, unit: 'g', kcal: 220 }
]}
]},
{ day: '周三', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '豆浆', amount: 250, unit: 'ml', kcal: 80 },
{ name: '蒸红薯', amount: 100, unit: 'g', kcal: 90 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '卤牛肉', amount: 100, unit: 'g', kcal: 160 },
{ name: '荞麦面', amount: 200, unit: 'g', kcal: 230 },
{ name: '凉拌黄瓜', amount: 150, unit: 'g', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '清炒时蔬', amount: 200, unit: 'g', kcal: 60 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '菌菇汤', amount: 200, unit: 'g', kcal: 50 }
]}
]},
{ day: '周四', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '杂粮粥', amount: 300, unit: 'g', kcal: 200 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '香煎鸡胸', amount: 150, unit: 'g', kcal: 250 },
{ name: '藜麦饭', amount: 150, unit: 'g', kcal: 180 },
{ name: '西兰花', amount: 150, unit: 'g', kcal: 60 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '虾仁沙拉', amount: 200, unit: 'g', kcal: 150 },
{ name: '全麦面包', amount: 50, unit: 'g', kcal: 120 }
]}
]},
{ day: '周五', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '全麦面包', amount: 80, unit: 'g', kcal: 200 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 },
{ name: '香蕉', amount: 100, unit: 'g', kcal: 90 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '清蒸鱼', amount: 150, unit: 'g', kcal: 160 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '凉拌木耳', amount: 100, unit: 'g', kcal: 40 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '菌菇汤', amount: 300, unit: 'g', kcal: 120 },
{ name: '杂粮饭', amount: 150, unit: 'g', kcal: 180 }
]}
]},
{ day: '周六', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 },
{ name: '燕麦', amount: 50, unit: 'g', kcal: 190 },
{ name: '牛奶', amount: 250, unit: 'ml', kcal: 65 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '白灼虾', amount: 150, unit: 'g', kcal: 140 },
{ name: '杂粮饭', amount: 200, unit: 'g', kcal: 240 },
{ name: '蔬菜沙拉', amount: 150, unit: 'g', kcal: 60 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '蔬菜沙拉', amount: 200, unit: 'g', kcal: 80 },
{ name: '鸡胸肉', amount: 100, unit: 'g', kcal: 170 },
{ name: '玉米', amount: 100, unit: 'g', kcal: 110 }
]}
]},
{ day: '周日', meals: [
{ icon: '🥣', name: '早餐', foods: [
{ name: '豆浆', amount: 250, unit: 'ml', kcal: 80 },
{ name: '玉米', amount: 150, unit: 'g', kcal: 160 },
{ name: '水煮蛋', amount: 60, unit: 'g', kcal: 70 }
]},
{ icon: '🍱', name: '午餐', foods: [
{ name: '卤牛肉', amount: 100, unit: 'g', kcal: 160 },
{ name: '糙米饭', amount: 200, unit: 'g', kcal: 230 },
{ name: '清炒时蔬', amount: 150, unit: 'g', kcal: 55 }
]},
{ icon: '🍽️', name: '晚餐', foods: [
{ name: '鸡胸沙拉', amount: 250, unit: 'g', kcal: 200 },
{ name: '小米粥', amount: 200, unit: 'g', kcal: 120 }
]}
]}
];
/* 计算某餐总热量 */
function mealSubtotal(di, mi) {
return WEEK[di].meals[mi].foods.reduce(function(s, f) { return s + f.kcal; }, 0);
}
/* 计算某天总热量 */
function dayTotal(di) {
return WEEK[di].meals.reduce(function(s, m, mi) { return s + mealSubtotal(di, mi); }, 0);
}
/* 某天三餐小计摘要 */
function daySummary(di) {
return WEEK[di].meals.map(function(m, mi) { return m.name.charAt(0) + mealSubtotal(di, mi); }).join(' · ');
}
/* 渲染整周折叠卡片(只读) */
function renderDayList() {
var html = '';
WEEK.forEach(function(day, di) {
html += '<div class="day-card">' +
'<div class="day-header" onclick="toggleDay(' + di + ')">' +
'<span class="day-header__day">' + day.day + '</span>' +
'<span class="day-header__summary">' + daySummary(di) + '</span>' +
'<span class="day-header__kcal">' + dayTotal(di) + ' kcal</span>' +
'<svg class="arrow" id="arrow-' + di + '" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="#999" stroke-width="2"><path d="M4 6l4 4 4-4"/></svg>' +
'</div>' +
'<div class="day-body" id="day-body-' + di + '">' +
day.meals.map(function(m, mi) {
return '<div class="meal-block">' +
'<div class="meal-head"><span class="meal-head__name">' + m.icon + ' ' + m.name + '</span><span class="meal-head__subtotal">小计 ' + mealSubtotal(di, mi) + ' kcal</span></div>' +
m.foods.map(function(f) {
return '<div class="food-row"><span class="food-row__name">' + f.name + '</span><span class="food-row__amount">' + f.amount + f.unit + '</span><span class="food-row__kcal">' + f.kcal + ' kcal</span></div>';
}).join('') +
'</div>';
}).join('') +
'</div>' +
'</div>';
});
document.getElementById('day-list').innerHTML = html;
/* 默认展开周一 */
document.getElementById('day-body-0').classList.add('open');
document.getElementById('arrow-0').classList.add('open');
}
/* 折叠/展开某天 */
function toggleDay(di) {
var body = document.getElementById('day-body-' + di);
var arrow = document.getElementById('arrow-' + di);
body.classList.toggle('open');
arrow.classList.toggle('open');
}
/* 初始化渲染 */
renderDayList();
</script>
</body>
</html>
@@ -0,0 +1,175 @@
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f7f8fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; position: relative; display: flex; flex-direction: column; flex-shrink: 0; }
.status-bar { height: 44px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; z-index: 10; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; font-size: 12px; color: #1a1a1a; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; position: relative; }
.nav-back { display: flex; align-items: center; gap: 4px; color: #1677ff; font-size: 15px; cursor: pointer; background: none; border: none; padding: 4px 0; }
.nav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1a1a1a; white-space: nowrap; }
.nav-add { margin-left: auto; color: #1677ff; font-size: 14px; cursor: pointer; background: none; border: none; font-weight: 600; }
.scroll-content { flex: 1; overflow-y: auto; background: #f7f8fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.seg-tabs { display: flex; background: #fff; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; }
.seg-tab { flex: 1; text-align: center; padding: 12px 0; font-size: 14px; color: #666; cursor: pointer; border-bottom: 2px solid transparent; }
.seg-tab.active { color: #1677ff; border-bottom-color: #1677ff; font-weight: 600; }
.seg-tab .badge { display: inline-block; background: #e8e8e8; color: #666; font-size: 10px; font-weight: 700; min-width: 16px; height: 16px; border-radius: 8px; padding: 0 4px; margin-left: 4px; vertical-align: middle; line-height: 16px; }
.seg-tab.active .badge { background: #e6f4ff; color: #1677ff; }
/* 食谱卡片 */
.recipe-card { background: #fff; border-radius: 12px; margin: 10px 12px 0; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); cursor: pointer; }
.recipe-card:active { transform: scale(0.98); }
.recipe-card__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.recipe-card__icon { width: 44px; height: 44px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 22px; flex-shrink: 0; }
.recipe-card__info { flex: 1; min-width: 0; }
.recipe-card__name { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.recipe-card__meta { font-size: 12px; color: #999; margin-top: 2px; }
.recipe-card__footer { display: flex; align-items: center; justify-content: space-between; padding-top: 10px; border-top: 1px solid #f5f5f5; }
.recipe-card__cite { font-size: 12px; color: #666; }
.recipe-card__cite .num { font-weight: 700; color: #1677ff; }
.tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
.tag-green { background: #f6ffed; color: #52c41a; }
.tag-gray { background: #f5f5f5; color: #999; }
.tag-blue { background: #e6f4ff; color: #1677ff; }
/* 制定食谱按钮 */
.fab { position: absolute; right: 20px; bottom: 100px; width: 56px; height: 56px; border-radius: 50%; background: #1677ff; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 28px; cursor: pointer; box-shadow: 0 4px 12px rgba(22,119,255,0.4); z-index: 50; }
.fab:active { transform: scale(0.94); }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">9:41</span>
<div class="icons"><span>●●●</span><span>WiFi</span><span>🔋</span></div>
</div>
<!-- 导航栏 -->
<div class="nav-bar">
<button class="nav-back" onclick="location.href='./index.html'">
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 1L2 8l6 7"/></svg>返回
</button>
<span class="nav-title">我的食谱</span>
<!-- [交互] 点击跳转至: ./recipe-create.html -->
<button class="nav-add" onclick="location.href='./recipe-create.html'">制定食谱</button>
</div>
<!-- Tab -->
<div class="seg-tabs">
<div class="seg-tab active" onclick="switchTab(this,'panel-all')">全部<span class="badge">6</span></div>
<div class="seg-tab" onclick="switchTab(this,'panel-pub')">已发布<span class="badge">4</span></div>
<div class="seg-tab" onclick="switchTab(this,'panel-draft')">草稿<span class="badge">2</span></div>
</div>
<div class="scroll-content">
<div id="panel-all">
<!-- [交互] 点击跳转至: ./recipe-detail.html -->
<div class="recipe-card" onclick="location.href='./recipe-detail.html'">
<div class="recipe-card__head">
<div class="recipe-card__icon" style="background:linear-gradient(135deg,#f6ffed,#b7eb8f);">🥗</div>
<div class="recipe-card__info">
<div class="recipe-card__name">机关减脂周菜单(第 37 周)</div>
<div class="recipe-card__meta">周菜单 · 早/中/晚 · 2026-09-14 ~ 09-20</div>
</div>
<span class="tag tag-green">已发布</span>
</div>
<div class="recipe-card__footer">
<span class="recipe-card__cite"><span class="num">5</span> 个食堂引用</span>
<span style="font-size:12px;color:#1677ff;">查看详情 </span>
</div>
</div>
<div class="recipe-card" onclick="location.href='./recipe-detail.html'">
<div class="recipe-card__head">
<div class="recipe-card__icon" style="background:linear-gradient(135deg,#fff7e6,#ffd591);">🍚</div>
<div class="recipe-card__info">
<div class="recipe-card__name">一线高能量周菜单(第 37 周)</div>
<div class="recipe-card__meta">周菜单 · 早/中/晚 · 2026-09-14 ~ 09-20</div>
</div>
<span class="tag tag-green">已发布</span>
</div>
<div class="recipe-card__footer">
<span class="recipe-card__cite"><span class="num">3</span> 个食堂引用</span>
<span style="font-size:12px;color:#1677ff;">查看详情 </span>
</div>
</div>
<div class="recipe-card" onclick="location.href='./recipe-detail.html'">
<div class="recipe-card__head">
<div class="recipe-card__icon" style="background:linear-gradient(135deg,#e6f4ff,#bdd7ff);">🍂</div>
<div class="recipe-card__info">
<div class="recipe-card__name">秋季养生季节菜单</div>
<div class="recipe-card__meta">季节菜单 · 早/中/晚 · 2026-09 ~ 11</div>
</div>
<span class="tag tag-green">已发布</span>
</div>
<div class="recipe-card__footer">
<span class="recipe-card__cite"><span class="num">8</span> 个食堂引用</span>
<span style="font-size:12px;color:#1677ff;">查看详情 </span>
</div>
</div>
<div class="recipe-card" onclick="location.href='./recipe-detail.html'">
<div class="recipe-card__head">
<div class="recipe-card__icon" style="background:linear-gradient(135deg,#f9f0ff,#d3adf7);">🥩</div>
<div class="recipe-card__info">
<div class="recipe-card__name">控糖低 GI 周菜单(第 38 周)</div>
<div class="recipe-card__meta">周菜单 · 早/中/晚 · 2026-09-21 ~ 09-27</div>
</div>
<span class="tag tag-gray">草稿</span>
</div>
<div class="recipe-card__footer">
<span class="recipe-card__cite">尚未发布</span>
<span style="font-size:12px;color:#1677ff;">继续编辑 </span>
</div>
</div>
<div style="height:90px;"></div>
</div>
<div id="panel-pub" style="display:none;">
<div class="recipe-card" onclick="location.href='./recipe-detail.html'">
<div class="recipe-card__head">
<div class="recipe-card__icon" style="background:linear-gradient(135deg,#f6ffed,#b7eb8f);">🥗</div>
<div class="recipe-card__info"><div class="recipe-card__name">机关减脂周菜单(第 37 周)</div><div class="recipe-card__meta">周菜单 · 2026-09-14 ~ 09-20</div></div>
<span class="tag tag-green">已发布</span>
</div>
<div class="recipe-card__footer"><span class="recipe-card__cite"><span class="num">5</span> 个食堂引用</span><span style="font-size:12px;color:#1677ff;">查看详情 </span></div>
</div>
<div style="height:90px;"></div>
</div>
<div id="panel-draft" style="display:none;">
<div class="recipe-card" onclick="location.href='./recipe-detail.html'">
<div class="recipe-card__head">
<div class="recipe-card__icon" style="background:linear-gradient(135deg,#f9f0ff,#d3adf7);">🥩</div>
<div class="recipe-card__info"><div class="recipe-card__name">控糖低 GI 周菜单(第 38 周)</div><div class="recipe-card__meta">周菜单 · 2026-09-21 ~ 09-27</div></div>
<span class="tag tag-gray">草稿</span>
</div>
<div class="recipe-card__footer"><span class="recipe-card__cite">尚未发布</span><span style="font-size:12px;color:#1677ff;">继续编辑 </span></div>
</div>
<div style="height:90px;"></div>
</div>
</div>
<!-- 制定食谱悬浮按钮 -->
<div class="fab" onclick="location.href='./recipe-create.html'"></div>
</div>
<script>
/* 标记当前角色为营养师 */
sessionStorage.setItem('appRole', 'nutritionist');
function switchTab(el, panelId) {
el.closest('.seg-tabs').querySelectorAll('.seg-tab').forEach(function(t) { t.classList.remove('active'); });
el.classList.add('active');
['panel-all','panel-pub','panel-draft'].forEach(function(id) {
document.getElementById(id).style.display = id === panelId ? 'block' : 'none';
});
}
</script>
</body>
</html>
@@ -136,23 +136,6 @@
background: #f7f9fb; border-radius: 12px; padding: 10px 14px; margin-bottom: 16px;
font-size: 13px; font-weight: 600; color: var(--text-secondary); text-align: center;
}
.cancel-modal__label { font-size: 13px; font-weight: 600; margin-bottom: 10px; }
.cancel-modal__label em { color: var(--danger); font-style: normal; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.chip {
padding: 7px 14px; border-radius: 16px; border: 1px solid #e8e8e8; background: #fafafa;
font-size: 13px; color: var(--text-secondary); cursor: pointer; transition: all 0.2s; user-select: none;
}
.chip.selected { background: var(--primary-light); border-color: var(--primary); color: var(--primary-dark); font-weight: 600; }
.other-input {
display: none; width: 100%; border: 1px solid #e8e8e8; border-radius: 12px;
padding: 10px 12px; font-size: 13px; font-family: inherit; line-height: 1.6;
resize: none; outline: none; margin-bottom: 6px; color: var(--text-primary);
}
.other-input.show { display: block; }
.other-input:focus { border-color: var(--primary); }
.cancel-modal__err { display: none; font-size: 12px; color: var(--danger); margin-bottom: 6px; }
.cancel-modal__err.show { display: block; }
.cancel-modal__actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; }
.cancel-modal__btn { height: 44px; border-radius: 22px; font-size: 15px; font-weight: 600; border: none; cursor: pointer; }
.cancel-modal__btn--ghost { background: #f5f5f5; color: var(--text-secondary); }
@@ -250,7 +233,7 @@
</div>
<div>
<div class="result-banner__title" id="resultTitle">预约成功</div>
<div class="result-banner__sub" id="resultSub">已为您锁定体检名额,请按预约日期准时到检</div>
<div class="result-banner__sub" id="resultSub">请按预约日期准时到检</div>
<div class="result-banner__reason">
<span>失败原因:所选日期号源已约满</span>
<span class="reason-help" onclick="openReasonModal()">?</span>
@@ -296,10 +279,10 @@
<!-- [交互] 预约成功态底部按钮 -->
<div class="bottom-actions" id="actionsSuccess">
<!-- [交互] 取消预约改为二次确认弹窗,需选择或填写取消原因 -->
<!-- [交互] 取消预约改为二次确认弹窗 -->
<button class="bottom-actions__btn bottom-actions__btn--ghost" onclick="openCancelModal()">取消预约</button>
<!-- [交互] 查看预约详情,跳转至 my-appointments-all.html(常规·待体检) -->
<button class="bottom-actions__btn bottom-actions__btn--primary" onclick="window.location.href='my-appointments-all.html?type=routine&status=pending'">查看预约详情</button>
<!-- [交互] 现场体检,跳转至 my-appointments-all.html(常规·待体检,含签到码 -->
<button class="bottom-actions__btn bottom-actions__btn--primary" onclick="window.location.href='my-appointments-all.html?type=routine&status=pending'">现场体检</button>
</div>
<!-- [交互] 预约失败态底部按钮:重新预约,跳回预约日期页 -->
<div class="bottom-actions" id="actionsFail" style="display: none;">
@@ -319,30 +302,16 @@
<div class="reason-modal__label">发生时间</div>
<div class="reason-modal__text">2026-03-16 12:00:35</div>
</div>
<div class="reason-modal__item">
<div class="reason-modal__label">处理建议</div>
<div class="reason-modal__text">请点击「重新预约」选择其他可约日期;若长期无可用号源,可联系健康管理专员协调加号。</div>
</div>
<button class="reason-modal__btn" onclick="closeReasonModal()">知道了</button>
</div>
</div>
<!-- [交互] 取消预约二次确认:单选原因chips,选「其他」时填写具体原因 -->
<!-- [交互] 取消预约二次确认弹窗 -->
<div class="modal-overlay hidden" id="cancelModal">
<div class="cancel-modal">
<div class="cancel-modal__title">确认取消预约</div>
<div class="cancel-modal__desc">取消后名额将释放,如仍需体检请重新预约</div>
<div class="cancel-modal__order">宁夏宝石花医院 · 2026年3月23日</div>
<div class="cancel-modal__label">请选择取消原因 <em>*</em></div>
<div class="chips" id="cancelChips">
<span class="chip" data-reason="时间冲突" onclick="selectChip(this)">时间冲突</span>
<span class="chip" data-reason="医院选择不当" onclick="selectChip(this)">医院选择不当</span>
<span class="chip" data-reason="日期选择不当" onclick="selectChip(this)">日期选择不当</span>
<span class="chip" data-reason="个人健康原因" onclick="selectChip(this)">个人健康原因</span>
<span class="chip" data-reason="other" onclick="selectChip(this)">其他</span>
</div>
<textarea class="other-input" id="otherInput" rows="2" maxlength="100" placeholder="请填写取消原因(必填)"></textarea>
<div class="cancel-modal__err" id="cancelErr">请选择或填写取消原因</div>
<div class="cancel-modal__actions">
<button class="cancel-modal__btn cancel-modal__btn--ghost" onclick="closeCancelModal()">再想想</button>
<button class="cancel-modal__btn cancel-modal__btn--danger" onclick="confirmCancel()">确认取消</button>
@@ -383,36 +352,14 @@
document.getElementById('reasonModal').classList.add('hidden');
}
// [交互] 取消预约二次确认:原因单选 + 其他原因填写 + 校验 + 确认后跳转重新预约
let cancelReason = null;
function selectChip(el) {
document.querySelectorAll('#cancelChips .chip').forEach(c => c.classList.remove('selected'));
el.classList.add('selected');
cancelReason = el.dataset.reason;
document.getElementById('otherInput').classList.toggle('show', cancelReason === 'other');
hideCancelErr();
}
function hideCancelErr() {
document.getElementById('cancelErr').classList.remove('show');
}
// [交互] 取消预约二次确认:确认后提示已取消,稍后跳回预约日期页
function openCancelModal() {
cancelReason = null;
document.querySelectorAll('#cancelChips .chip').forEach(c => c.classList.remove('selected'));
const input = document.getElementById('otherInput');
input.value = '';
input.classList.remove('show');
hideCancelErr();
document.getElementById('cancelModal').classList.remove('hidden');
}
function closeCancelModal() {
document.getElementById('cancelModal').classList.add('hidden');
}
function confirmCancel() {
const input = document.getElementById('otherInput');
if (!cancelReason || (cancelReason === 'other' && !input.value.trim())) {
document.getElementById('cancelErr').classList.add('show');
return;
}
closeCancelModal();
const toast = document.getElementById('stepToast');
toast.textContent = '预约已取消';
@@ -384,7 +384,7 @@
</div>
<div class="modal-field">
<span class="modal-field__label">身份证号</span>
<span class="modal-field__value">6101021994********</span>
<span class="modal-field__value">610102199403154729</span>
</div>
<div class="modal-field">
<span class="modal-field__label">性别</span>
+222 -39
View File
@@ -130,6 +130,7 @@
.project-list-panel::-webkit-scrollbar { display: none; }
.project-grid { display: flex; flex-direction: column; gap: 10px; }
.project-card {
position: relative;
background: #fff; border-radius: 14px; padding: 14px 16px; border: 2px solid #eef5f5;
cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
@@ -140,6 +141,9 @@
cursor: default; background: #fff; border-color: #eef5f5;
}
.project-card.project-card--readonly:hover { border-color: #eef5f5; }
/* 额度已满后剩余项目置灰,不可再选 */
.project-card--disabled { opacity: 0.45; cursor: not-allowed; }
.project-card--disabled:hover { border-color: #eef5f5; background: #fff; }
.project-card__check {
width: 22px; height: 22px; border-radius: 6px; border: 2px solid #d9d9d9;
display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: all 0.2s;
@@ -151,16 +155,22 @@
.project-card.selected .project-card__check { background: var(--primary); border-color: var(--primary); }
.project-card.selected .project-card__check::after { opacity: 1; }
.project-card__info { flex: 1; min-width: 0; }
.project-card__name { font-size: 14px; font-weight: 600; margin-bottom: 2px; }
.project-card__desc { font-size: 12px; color: var(--text-muted); }
.project-card__name { font-size: 15px; font-weight: 600; margin-bottom: 6px; }
/* 项目示意图标块:沿用医院卡渐变底 + emoji 占位风格 */
.project-card__img {
width: 52px; height: 52px; border-radius: 14px;
background: linear-gradient(135deg, #e6fffb 0%, #b5f5ec 100%);
display: flex; align-items: center; justify-content: center;
font-size: 24px; flex-shrink: 0;
}
.project-card.selected .project-card__img {
background: linear-gradient(135deg, #b5f5ec 0%, #87e8de 100%);
}
.project-card__side {
flex-shrink: 0; display: flex; align-items: center; gap: 10px;
margin-left: auto; padding-left: 8px;
}
.project-card__tags {
display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
}
.project-card__price { font-size: 13px; color: var(--primary-dark); font-weight: 700; }
.project-card__price { font-size: 14px; color: var(--primary-dark); font-weight: 700; }
.project-card__badge {
display: inline-flex; align-items: center; justify-content: center; height: 22px;
padding: 0 10px; border-radius: 11px; background: #f5f5f5;
@@ -170,7 +180,7 @@
background: #fff1f0; color: #ff4d4f;
}
.project-card__meta {
display: flex; align-items: center; gap: 6px;
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
/* AI 推荐标签分三档,字号阶梯放大 + 深色实底强化等级对比:
强烈推荐 12px 红橙实底 > 建议加做 11px 紫蓝实底 > 可选参考 10px 浅蓝描边 */
@@ -191,6 +201,10 @@
height: 20px; padding: 0 8px; border-radius: 10px; font-size: 10px; font-weight: 600;
background: #f0f5ff; color: #597ef7; border: 1px solid #d6e4ff;
}
/* 列表卡片上的 AI 推荐标签固定在卡片右上角(弹窗内的同名标签不受影响) */
.project-card__badge--ai-corner {
position: absolute; top: 6px; right: 16px;
}
.project-card__arrow {
flex-shrink: 0; color: #d0d0d0; font-size: 16px; cursor: pointer;
transition: color 0.2s;
@@ -247,6 +261,26 @@
.confirm-modal__btn--primary {
background: linear-gradient(135deg, #13c2c2 0%, #36cfc9 100%); color: #fff;
}
/* 互斥项目弹窗:二选一选项行 */
.mutex-options { margin-bottom: 18px; }
.mutex-option {
display: flex; align-items: center; gap: 10px;
padding: 12px 14px; border: 2px solid #eef5f5; border-radius: 14px;
background: #fff; cursor: pointer; transition: all 0.2s;
}
.mutex-option + .mutex-option { margin-top: 10px; }
.mutex-option.active { border-color: var(--primary); background: var(--primary-light); }
.mutex-option__radio {
width: 18px; height: 18px; border-radius: 50%; border: 2px solid #d9d9d9;
flex-shrink: 0; position: relative; transition: all 0.2s;
}
.mutex-option.active .mutex-option__radio { border-color: var(--primary); }
.mutex-option.active .mutex-option__radio::after {
content: ''; position: absolute; left: 3px; top: 3px; width: 8px; height: 8px;
border-radius: 50%; background: var(--primary);
}
.mutex-option__name { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; }
.mutex-option__price { font-size: 13px; font-weight: 700; color: var(--primary-dark); }
/* AI 推荐理由弹窗 */
.ai-modal { padding: 24px 20px 18px; }
.ai-modal__project {
@@ -383,6 +417,43 @@
<button class="confirm-modal__btn confirm-modal__btn--primary ai-modal__close" onclick="closeAiReason()">知道了</button>
</div>
</div>
<!-- [交互] 关联项目提醒:同步选中 / 取消选择 -->
<div class="modal-overlay hidden" id="linkModal">
<div class="confirm-modal">
<div class="confirm-modal__title">🔗 关联项目提醒</div>
<div class="confirm-modal__desc" id="linkModalDesc"></div>
<div class="confirm-modal__info" id="linkModalInfo"></div>
<div class="confirm-modal__actions">
<button class="confirm-modal__btn confirm-modal__btn--ghost" onclick="closeLinkModal()">取消选择</button>
<button class="confirm-modal__btn confirm-modal__btn--primary" onclick="confirmLinkSelect()">同步选中</button>
</div>
</div>
</div>
<!-- [交互] 互斥项目提醒:两项展示出来二选一,确定后替换已选项 -->
<div class="modal-overlay hidden" id="mutexModal">
<div class="confirm-modal">
<div class="confirm-modal__title">⚠️ 互斥项目提醒</div>
<div class="confirm-modal__desc" id="mutexModalDesc"></div>
<div class="mutex-options" id="mutexOptions"></div>
<div class="confirm-modal__actions">
<button class="confirm-modal__btn confirm-modal__btn--ghost" onclick="closeMutexModal()">取消</button>
<button class="confirm-modal__btn confirm-modal__btn--primary" onclick="confirmMutexSelect()">确定</button>
</div>
</div>
</div>
<!-- [交互] 额度不足提醒 -->
<div class="modal-overlay hidden" id="exceedModal">
<div class="confirm-modal">
<div class="confirm-modal__title">💰 额度不足提醒</div>
<div class="confirm-modal__desc" id="exceedModalDesc"></div>
<div class="confirm-modal__actions">
<button class="confirm-modal__btn confirm-modal__btn--primary" onclick="closeExceedModal()">知道了</button>
</div>
</div>
</div>
</div>
<script>
// 年度体检总额度(可用额度 = 总额度 − 已选项目费用,原型模拟数据)
@@ -394,20 +465,20 @@
1: { label: '可选参考', cls: 'low', icon: '✨', desc: '按年龄段与常见风险的一般性补充筛查,可结合剩余额度自行选择。' }
};
const projects = [
{ id: 'general', name: '一般检查', desc: '身高、体重、血压、视力', price: 120, type: 'required', level: 'normal' },
{ id: 'blood', name: '血常规', desc: '血红蛋白、白细胞、血小板等', price: 80, type: 'required', level: 'normal' },
{ id: 'urine', name: '尿常规', desc: '尿蛋白、尿糖、尿潜血等', price: 45, type: 'required', level: 'normal' },
{ id: 'liver', name: '肝功能', desc: 'ALT、AST、总胆红素等', price: 160, type: 'required', level: 'key' },
{ id: 'kidney', name: '肾功能', desc: '肌酐、尿素氮、尿酸等', price: 110, type: 'required', level: 'key' },
{ id: 'lipid', name: '血脂四项', desc: '总胆固醇、甘油三酯、HDL、LDL', price: 90, type: 'required', level: 'normal' },
{ id: 'glucose', name: '空腹血糖', desc: '空腹静脉血糖检测', price: 40, type: 'required', level: 'normal' },
{ id: 'ecg', name: '心电图', desc: '十二导联心电图检查', price: 75, type: 'required', level: 'key' },
{ id: 'xray', name: '胸部X光', desc: '胸部正位X线摄影', price: 140, type: 'optional', level: 'normal' },
{ id: 'ultrasound', name: '腹部B超', desc: '肝胆脾胰肾超声检查', price: 180, type: 'optional', level: 'key', aiLevel: 3, aiReason: '健康画像显示 BMI 26.8(超重),且近年血脂持续偏高,属于脂肪肝与胆囊病变的高风险人群,建议本年度务必完成腹部超声重点筛查。预计费用 ¥180,在本年度体检额度内。' },
{ id: 'thyroid', name: '甲状腺彩超', desc: '甲状腺结构与结节筛查', price: 160, type: 'optional', level: 'key', aiLevel: 2, aiReason: '既往史显示 2024 年体检发现甲状腺结节(TI-RADS 2 类),建议每年做一次超声随访复查,观察结节变化。预计费用 ¥160,在本年度体检额度内。' },
{ id: 'tumor', name: '肿瘤标志物', desc: '基础肿瘤风险筛查', price: 220, type: 'optional', level: 'key', aiLevel: 2, aiReason: '家族史提示直系亲属有肿瘤病史,结合您所处年龄段(45 岁以上),建议今年纳入基础肿瘤标志物筛查,做到早发现早干预。预计费用 ¥220,在本年度体检额度内。' },
{ id: 'echo', name: '心脏彩超', desc: '心脏结构与瓣膜功能超声', price: 360, type: 'optional', level: 'key', aiLevel: 1, aiReason: '近两次体检血压处于正常高值,可完成一次心脏彩超,建立心脏结构与瓣膜功能的基线资料,便于日后对比随访。预计费用 ¥360,请注意控制项目总额。' },
{ id: 'gastro', name: '胃幽门螺杆菌检测', desc: 'C13 呼气试验,筛查胃部风险', price: 480, type: 'optional', level: 'normal', aiLevel: 1, aiReason: '幽门螺杆菌是胃癌明确的危险因素,国内感染率较高。如从未检测过,建议先做一次 C13 呼气试验筛查,阳性可规范四联根除治疗。预计费用 ¥480,可结合剩余额度自行选择。' }
{ id: 'general', name: '一般检查', icon: '📏', price: 120, type: 'required', level: 'normal' },
{ id: 'blood', name: '血常规', icon: '🩸', price: 80, type: 'required', level: 'normal' },
{ id: 'urine', name: '尿常规', icon: '🧪', price: 45, type: 'required', level: 'normal' },
{ id: 'liver', name: '肝功能', icon: '⚗️', price: 160, type: 'required', level: 'key' },
{ id: 'kidney', name: '肾功能', icon: '🧫', price: 110, type: 'required', level: 'key' },
{ id: 'lipid', name: '血脂四项', icon: '🧬', price: 90, type: 'required', level: 'normal' },
{ id: 'glucose', name: '空腹血糖', icon: '🍬', price: 40, type: 'required', level: 'normal' },
{ id: 'ecg', name: '心电图', icon: '📈', price: 75, type: 'required', level: 'key' },
{ id: 'xray', name: '胸部X光', icon: '🦴', price: 140, type: 'optional', level: 'normal' },
{ id: 'ultrasound', name: '腹部B超', icon: '🔬', price: 180, type: 'optional', level: 'key', aiLevel: 3, linkId: 'thyroid', aiReason: '健康画像显示 BMI 26.8(超重),且近年血脂持续偏高,属于脂肪肝与胆囊病变的高风险人群,建议本年度务必完成腹部超声重点筛查。预计费用 ¥180,在本年度体检额度内。' },
{ id: 'thyroid', name: '甲状腺彩超', icon: '🦋', price: 160, type: 'optional', level: 'key', aiLevel: 2, linkId: 'ultrasound', aiReason: '既往史显示 2024 年体检发现甲状腺结节(TI-RADS 2 类),建议每年做一次超声随访复查,观察结节变化。预计费用 ¥160,在本年度体检额度内。' },
{ id: 'tumor', name: '肿瘤标志物', icon: '🎗️', price: 220, type: 'optional', level: 'key', aiLevel: 2, mutexId: 'echo', aiReason: '家族史提示直系亲属有肿瘤病史,结合您所处年龄段(45 岁以上),建议今年纳入基础肿瘤标志物筛查,做到早发现早干预。预计费用 ¥220,在本年度体检额度内。' },
{ id: 'echo', name: '心脏彩超', icon: '🫀', price: 360, type: 'optional', level: 'key', aiLevel: 1, mutexId: 'tumor', aiReason: '近两次体检血压处于正常高值,可完成一次心脏彩超,建立心脏结构与瓣膜功能的基线资料,便于日后对比随访。预计费用 ¥360,请注意控制项目总额。' },
{ id: 'gastro', name: '胃幽门螺杆菌检测', icon: '🦠', price: 480, type: 'optional', level: 'normal', aiLevel: 1, aiReason: '幽门螺杆菌是胃癌明确的危险因素,国内感染率较高。如从未检测过,建议先做一次 C13 呼气试验筛查,阳性可规范四联根除治疗。预计费用 ¥480,可结合剩余额度自行选择。' }
];
let activeTab = 'optional';
@@ -478,25 +549,25 @@
const card = document.createElement('div');
const isReadonly = project.type === 'required';
const isSelected = selectedOptionalIds.has(project.id);
// 额度已满时,未选中的可选项目置灰不可再选
const isDisabled = !isReadonly && !isSelected && getAvailableFee() <= 0;
const projectLevelText = project.level === 'key' ? '重点项目' : '一般项目';
const aiLevel = AI_LEVELS[project.aiLevel];
card.className = `project-card${isReadonly ? ' project-card--readonly' : ''}${isSelected ? ' selected' : ''}`;
card.className = `project-card${isReadonly ? ' project-card--readonly' : ''}${isSelected ? ' selected' : ''}${isDisabled ? ' project-card--disabled' : ''}`;
card.innerHTML = `
<div class="project-card__check${isReadonly ? ' project-card__check--hidden' : ''}"></div>
<div class="project-card__img">${project.icon}</div>
<div class="project-card__info">
<div class="project-card__name">${project.name}</div>
<div class="project-card__desc">${project.desc}</div>
<div class="project-card__meta">
<span class="project-card__badge${project.level === 'key' ? ' project-card__badge--key' : ''}">${projectLevelText}</span>
</div>
</div>
<div class="project-card__side">
<div class="project-card__tags">
<span class="project-card__price">${formatPrice(project.price)}</span>
<div class="project-card__meta">
<span class="project-card__badge${project.level === 'key' ? ' project-card__badge--key' : ''}">${projectLevelText}</span>
${aiLevel ? `<span class="project-card__badge project-card__badge--ai project-card__badge--ai-${aiLevel.cls}">${aiLevel.icon} ${aiLevel.label}</span>` : ''}
</div>
</div>
<span class="project-card__price">${formatPrice(project.price)}</span>
<span class="project-card__arrow" data-detail="${project.id}">&#8250;</span>
</div>
${aiLevel ? `<span class="project-card__badge project-card__badge--ai project-card__badge--ai-${aiLevel.cls} project-card__badge--ai-corner">${aiLevel.icon} ${aiLevel.label}</span>` : ''}
`;
// [交互] 箭头点击跳转项目详情
@@ -515,21 +586,133 @@
}
if (!isReadonly) {
card.addEventListener('click', () => {
if (selectedOptionalIds.has(project.id)) {
selectedOptionalIds.delete(project.id);
} else {
selectedOptionalIds.add(project.id);
}
renderProjects();
updateSummary();
});
card.addEventListener('click', () => trySelectProject(project));
}
grid.appendChild(card);
});
}
// ===== 选择流程:关联项目同步选中 / 互斥项目二选一 / 额度校验 =====
// 关联:ultrasound ↔ thyroid;互斥:tumor ↔ echo
let pendingLinkProject = null;
let pendingMutex = null;
function trySelectProject(project) {
// 已选中 → 取消勾选,关联项目同步取消
if (selectedOptionalIds.has(project.id)) {
selectedOptionalIds.delete(project.id);
const linked = project.linkId && projects.find(p => p.id === project.linkId);
if (linked && selectedOptionalIds.has(linked.id)) selectedOptionalIds.delete(linked.id);
renderProjects();
updateSummary();
return;
}
// 额度已满:剩余项目置灰,点击不响应
if (getAvailableFee() <= 0) return;
// 关联项目:对方未选中 → 弹窗提醒同步选中
const linked = project.linkId && projects.find(p => p.id === project.linkId);
if (linked && !selectedOptionalIds.has(linked.id)) {
openLinkModal(project, linked);
return;
}
// 互斥项目:对方已选中 → 弹窗二选一
const mutex = project.mutexId && projects.find(p => p.id === project.mutexId);
if (mutex && selectedOptionalIds.has(mutex.id)) {
openMutexModal(project, mutex);
return;
}
// 单选超额 → 弹窗提示,不生效
if (getAvailableFee() < project.price) {
openExceedModal(`${project.name}」预计费用 ${formatPrice(project.price)},选择后项目总额将超出年度体检额度,请调整已选项目后再试`);
return;
}
selectedOptionalIds.add(project.id);
renderProjects();
updateSummary();
}
// 关联项目弹窗
function openLinkModal(project, linked) {
pendingLinkProject = project;
document.getElementById('linkModalDesc').innerHTML =
`${project.name}」与「${linked.name}」为关联项目,<br>选中其中一项将同步选中另一项`;
document.getElementById('linkModalInfo').innerHTML = [project, linked].map(p =>
`<div class="confirm-modal__row"><span>${p.name}</span><span class="confirm-modal__value">${formatPrice(p.price)}</span></div>`
).join('');
document.getElementById('linkModal').classList.remove('hidden');
}
function closeLinkModal() {
document.getElementById('linkModal').classList.add('hidden');
}
function confirmLinkSelect() {
const project = pendingLinkProject;
const linked = projects.find(p => p.id === project.linkId);
const cost = project.price + linked.price;
// 两项合计超出剩余额度 → 转额度不足提醒
if (getAvailableFee() < cost) {
closeLinkModal();
openExceedModal(`${project.name}」与「${linked.name}」需同步选中,合计 ${formatPrice(cost)},选择后项目总额将超出年度体检额度,请调整已选项目后再试`);
return;
}
selectedOptionalIds.add(project.id);
selectedOptionalIds.add(linked.id);
closeLinkModal();
renderProjects();
updateSummary();
}
// 互斥项目弹窗:展示两项做单选,确定后替换已选项
function openMutexModal(clicked, other) {
pendingMutex = { clicked, other, chosenId: clicked.id };
document.getElementById('mutexModalDesc').innerHTML =
`${clicked.name}」与「${other.name}」互斥,只能同时体检其中一个项目,请选择本次要做的项目`;
renderMutexOptions();
document.getElementById('mutexModal').classList.remove('hidden');
}
function renderMutexOptions() {
document.getElementById('mutexOptions').innerHTML = [pendingMutex.clicked, pendingMutex.other].map(p => `
<div class="mutex-option${pendingMutex.chosenId === p.id ? ' active' : ''}" onclick="chooseMutexOption('${p.id}')">
<span class="mutex-option__radio"></span>
<span class="mutex-option__name">${p.name}</span>
<span class="mutex-option__price">${formatPrice(p.price)}</span>
</div>
`).join('');
}
function chooseMutexOption(id) {
pendingMutex.chosenId = id;
renderMutexOptions();
}
function closeMutexModal() {
document.getElementById('mutexModal').classList.add('hidden');
}
function confirmMutexSelect() {
const { clicked, other, chosenId } = pendingMutex;
const chosen = chosenId === clicked.id ? clicked : other;
const dropped = chosenId === clicked.id ? other : clicked;
// 替换后的可用额度 = 当前可用 + 被替换项目释放额度 - 新选项目费用
if (getAvailableFee() + dropped.price - chosen.price < 0) {
closeMutexModal();
openExceedModal(`选择「${chosen.name}」后项目总额将超出年度体检额度,请调整已选项目后再试`);
return;
}
selectedOptionalIds.delete(clicked.id);
selectedOptionalIds.delete(other.id);
selectedOptionalIds.add(chosen.id);
closeMutexModal();
renderProjects();
updateSummary();
}
// 额度不足提醒弹窗
function openExceedModal(desc) {
document.getElementById('exceedModalDesc').innerHTML = desc;
document.getElementById('exceedModal').classList.remove('hidden');
}
function closeExceedModal() {
document.getElementById('exceedModal').classList.add('hidden');
}
tabButtons.forEach(button => {
button.addEventListener('click', () => {
activeTab = button.dataset.tab;
+16
View File
@@ -166,6 +166,18 @@
.consult-table--gastro thead th:nth-child(2) { width: 70%; }
.is-abnormal { color: var(--danger); font-weight: 600; }
.consult-table tbody td:nth-child(2) { color: var(--danger); font-weight: 600; }
/* 顶部体检信息:年份 + 类型 */
.info-bar {
background: #fff;
display: flex;
align-items: center;
padding: 13px 16px;
border-bottom: 1px solid #f0f0f0;
}
.info-bar__item { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.info-bar__label { color: var(--text-muted); flex-shrink: 0; }
.info-bar__value { color: var(--text-primary); font-weight: 600; }
</style>
</head>
<body>
@@ -185,6 +197,10 @@
<div class="scroll-content">
<div class="page-body">
<!-- 顶部体检信息:当前咨询报告的年份 -->
<div class="info-bar">
<div class="info-bar__item"><span class="info-bar__label">体检年份</span><span class="info-bar__value">2025年</span></div>
</div>
<!-- 化学检查 -->
<div class="category">
<div class="category__title">化学检查</div>
+75 -67
View File
@@ -16,19 +16,19 @@
│ │ ├── 选择项目(年度额度 · 必检/选检 · AI 智能推荐)→ 项目详情
│ │ ├── 已选项目确认
│ │ ├── 预约日期(月历号源 · 近30天可约)
│ │ └── 预约结果(成功/失败 · 取消预约需填原因
│ │ └── 预约结果(成功/失败 · 取消预约二次确认
│ ├── 胃肠镜流程(步骤条:1 选择医院 → 2 填问卷 → 3 预约日期)
│ │ ├── 选择医院(唐都医院需就诊卡号)
│ │ ├── 病史问卷(是/否单选 · 就诊卡条件题)
│ │ ├── 预约日期(7~30 天可约窗口)
│ │ └── 预约结果(成功/失败 · 取消预约)
│ └── 冠脉CTA流程(免问卷,两步:选择医院 → 预约日期 → 预约结果)
├── 胃肠镜医院预选(征集期一次性活动,与正式预约解耦)
│ ├── 医院预选填写(多选意向医院 · 仅可提交一次
│ └── 预选提交完成(只读回显,不可修改
├── 现场体检(Tab:常规/胃肠镜/冠脉CTA × 状态:待体检/已完成/无预约)
├── 胃肠镜医院预选(征集期活动,与正式预约解耦)
│ ├── 医院预选填写(多选意向医院 · 至少2家/最多3家 · 可重新填写调整
│ └── 预选提交完成(回显当前提交的意向医院 · 可重新填写
├── 现场体检(Tab:常规/胃肠镜/冠脉CTA × 状态:已预约/无预约)
├── 体检报告
│ ├── 报告列表(累计年份统计 · 历年体检趋势入口 · 历年报告)
│ ├── 报告列表(累计年份统计 · 历年体检趋势入口 · 年份/类型筛选 · 历年报告)
│ ├── 常规体检报告(AI 解读/结论/建议 · 14 科室明细)
│ ├── 冠脉CTA报告(钙化积分 · 冠脉分段 · 影像诊断)
│ ├── 胃肠镜报告(胃镜/肠镜/病理诊断)
@@ -38,7 +38,7 @@
└── 操作说明(标题搜索过滤)→ 讲解详情(图文教程 / 视频播放)
```
> 胃肠镜医院预选为独立的一次性问卷收集活动:征集期内「未提交 → 已提交」,提交后不可修改;征集期结束通知条隐藏。
> 胃肠镜医院预选为独立于正式预约的意向征集活动:征集期内员工多选意向医院(至少 2 家、最多 3 家),提交后可在完成页「重新填写」再次调整提交;征集期结束通知条隐藏。
---
@@ -121,7 +121,7 @@
| 31 | 操作说明 | process-guide.html | 页面 |
| 32 | 讲解详情(图文/视频) | process-guide-detail.html | 页面 |
> 预约结果页(10/16/21)、现场体检(22)的「成功/失败/待体检/已完成/无预约」等状态由原型内演示控件切换,实际由数据驱动,逐页说明中一并描述。
> 预约结果页(9/15/20)、现场体检(21)的「成功/失败/已预约/无预约」等状态由原型内演示控件切换,实际由数据驱动,逐页说明中一并描述。
---
@@ -140,17 +140,17 @@
**主要UI元素:**
- 导航栏:返回箭头(回 APP 首页)、标题「健康体检」、消息铃铛(红色角标显示未读消息数)
- 年度体检计划 Banner 轮播(4 张滑动切换,圆点指示器随内容变色):
- 常规体检:计划标题、体检日期范围、体检费用额度胶囊、右下角状态(未预约、已预约、已参检)
- 胃肠镜体检:计划标题、体检日期范围、右下角状态(未预约、已预约、已参检)
- 冠脉CTA体检:计划标题、体检日期范围、右下角状态(未预约、已预约、已参检)
- 常规体检:计划标题、起止日期、体检费用额度胶囊、右下角状态(未预约、已预约、已参检)
- 胃肠镜体检:计划标题、起止日期、右下角状态(未预约、已预约、已参检)
- 冠脉CTA体检:计划标题、起止日期、右下角状态(未预约、已预约、已参检)
- 未开启计划占位:🌱 图标 + 占位文案
- 胃肠镜医院预选通知条(橙色,仅征集期展示):📢 图标、标题、截止日期与「每人仅可填写一次」说明、「去选择」按钮
- 胃肠镜医院预选通知条(橙色,仅征集期展示):📢 图标、标题、截止日期与「去选择」按钮
- 体检服务入口卡片:体检预约(选医院和项目,预约日期)、现场体检(到院扫码/体检进度查询)、体检报告(查看报告/解读结果)
- 其他服务宫格:操作说明、一键咨询
**业务规则:**
- Banner 按员工实际计划状态渲染:有计划显示对应类型卡片并按进度跳转,无计划的类型不出现;单位未开启任何计划时仅显示占位图
- 预选通知条三态:征集期未提交(「去选择」,进入预选填写)、已提交(标题改「预选已完成」、绿色对勾、「查看结果」,进入完成页)
- 预选通知条三态:征集期未提交(「去选择」,进入预选填写)、已提交(标题改「预选已完成」、绿色对勾、「查看结果」,进入完成页),仅预选日期范围内展示
**交互说明:**
- 左右滑动切换 Banner,点击圆点跳转到对应banner
@@ -213,13 +213,13 @@
![确认个人信息弹窗](screenshots/booking-hospital-dialog.jpg)
**确认个人信息弹窗:** 标题「确认个人信息」+ 副标题;字段:姓名、单位、身份证号(脱敏显示)、性别、年龄、体检类型(「已婚体检 / 未婚体检」胶囊切换,默认已婚);「确认」按钮关闭弹窗。
**确认个人信息弹窗:** 标题「确认个人信息」+ 副标题;字段:姓名、单位、身份证号、性别、年龄、体检类型(「已婚体检 / 未婚体检」胶囊切换,默认已婚);「确认」按钮关闭弹窗。
**业务规则:**
- 从选择体检类型页进入时自动弹出个人信息确认弹窗;也可从导航栏「个人信息」手动打开
- 已婚/未婚体检切换仅影响本次预约的检查套餐
- 可选择的医院范围:后台开展计划选择的医院&职业体检支持的医院(如需参加职业体检)&指定员工的医院(如被指定医院体检)
- 提交选择医院时需要判断当前员工选择的医院是否符合最终条件,例如已被纳入指定医院体检/已被纳入职业体检,医院需开启职业体检
**交互说明:**
- 点击医院卡片单选选中
- 点击「个人信息」打开确认个人信息弹窗,点击「确认」关闭
@@ -237,11 +237,15 @@
**主要UI元素:**
- 体检额度卡片:「年度体检总额度」、「已选项目费用」(随勾选联动)、额度进度条(超支变红)、三格统计:必检项目 / 选检项目 / 可用额度(超支标红)
- 项目 Tab:「选检项目」(默认)/「必检项目」
- 项目卡片:示意图标(渐变底 emoji 占位块)+ 项目名 +「重点项目/一般项目」徽章 + 价格;AI 推荐徽章固定于卡片右上角
- 必检项目列表(无勾选框、整卡只读)
- 选检项目列表(按 AI 推荐等级降序)
- AI 推荐徽章三档:强烈推荐、建议加做、可选参考
- 「确认提交」弹窗:体检项目数、项目总额、必检项目费用、体检总额度、可用额度;「取消 / 确认提交」
- 「✨ 智能推荐」弹窗:项目名、推荐等级徽章、等级说明、推荐理由、按钮「知道了」
- 「🔗 关联项目提醒」弹窗:关联说明 + 两项目费用;「取消选择 / 同步选中」
- 「⚠️ 互斥项目提醒」弹窗:互斥说明 + 两项目单选列表;「取消 / 确定」
- 「💰 额度不足提醒」弹窗:超额说明;按钮「知道了」
- 底部双按钮:「上一步」/「提交项目」
![确认提交弹窗](screenshots/booking-project-submit.jpg)
@@ -249,15 +253,18 @@
![智能推荐弹窗](screenshots/booking-project-ai.jpg)
**业务规则:**
- 可用额度 = 年度总额度 − 必检合计 − 选检已选合计;可用额度为负时显示 ¥0.00 并标红,进度条封顶并变红(仅视觉警示,不阻止提交
- 可用额度 = 年度总额度(实际个人总额度+所属项目模板中的折扣金额,折扣金额是隐藏的,不体现) − 必检合计 − 选检已选合计;单项选择后总额将超出额度时弹「额度不足提醒」且选择不生效;可用额度为 0 后剩余未选项目置灰不可再选(额度进度条超支变红仅作视觉警示
- 关联项目:选中其一时若另一项未选,弹「关联项目提醒」,「同步选中」后两项一起勾选(两项合计超出剩余额度则转额度不足提醒);取消其一时另一项同步取消
- 互斥项目:只能同时体检其中一个。选中其一时若另一项已选,弹「互斥项目提醒」列出两项单选,确定后替换已选项(替换后超出额度则提示额度不足)
- 必检项目固定包含,不可取消;选检项目点击整卡切换勾选,默认选中胸部X光
- 选检列表按 AI 推荐等级降序排列(强烈推荐 > 建议加做 > 可选参考 > 无推荐)
- AI 推荐理由基于员工健康画像生成,示例:腹部B超(BMI 超重+血脂偏高,脂肪肝高风险)、甲状腺彩超(既往甲状腺结节随访)、肿瘤标志物(家族史+年龄段)、心脏彩超(血压正常高值建立基线)、幽门螺杆菌(首次筛查建议)
- 金额千分位两位小数格式化
- 提交项目时需要判断当前员工选择的医院是否符合最终条件,例如已被纳入指定医院体检/已被纳入职业体检,医院需开启职业体检
**交互说明:**
- 点击 Tab 切换选检/必检列表
- 点击选检项目卡勾选/取消勾选,额度卡片金额即时联动
- 点击选检项目卡勾选/取消勾选,额度卡片金额即时联动;额度已满时点击置灰项目卡无响应
- 选择关联/互斥项目时按弹窗指引确认;「取消选择」/「取消」不改变当前勾选状态
- 点击卡片右侧箭头跳转到项目详情页
- 点击 AI 推荐徽章打开智能推荐理由弹窗(不触发勾选)
- 点击「提交项目」打开确认提交弹窗,「确认提交」跳转到已选项目确认页
@@ -328,12 +335,13 @@
![确认预约信息弹窗](screenshots/booking-date-dialog.jpg)
**业务规则:**
- 可约范围 = 当天 ~ 近 30 天;已过日期与超范围日期不可选
- 可约范围 = 当天 ~ 近 30 天(该30天作为动态字段,不是写死的,通过后台接口传递);已过日期与超范围日期不可选
- 周末不可约(显示「不可约」);工作日号源随机模拟,约满日显示「不可约」且禁点
- 不能翻到当月之前或可约范围所在月份之后
- 默认选中当天;未选中日期时「确定预约」不响应
- 可供员工预约的日期范围是医院承检日期+单位允许日期范围的交叉日期,并且在可预约日期的前一天/当天(当天可能是刚创建体检计划,但是预约日期也是当天)给员工发送短信,提醒员工进行预约体检
- 未开启预约时,隐藏日历,确定预约按钮置灰
- 提交预约时需要判断当前员工选择的医院是否符合最终条件,例如已被纳入指定医院体检/已被纳入职业体检,医院需开启职业体检
**交互说明:**
- 点击翻月按钮切换月份
@@ -348,18 +356,18 @@
![页面截图](screenshots/booking-date-selected.jpg)
**功能说明:** 预约提交结果页,含「预约成功 / 预约失败」两态。成功态展示预约信息与体检须知,支持取消预约(必填原因);失败态展示失败原因并可重新预约。
**功能说明:** 预约提交结果页,含「预约成功 / 预约失败」两态。成功态展示预约信息与体检须知,支持取消预约(二次确认);失败态展示失败原因并可重新预约。
**主要UI元素:**
- 步骤条(三步全部完成;点击已完成步骤弹 Toast 阻止回跳)
- 结果横幅:
- 成功态(绿色,圆形对勾图标):「预约成功」
- 成功态(绿色,圆形对勾图标):「预约成功」+ 提示「请按预约日期准时到检」
- 失败态(红色,圆形叉图标):「预约失败」+ 失败原因 + 红色「?」帮助按钮
- 预约信息卡片:体检医院、体检员工、体检地址、体检日期(高亮)、体检项目数
- 体检须知卡片(仅成功态显示)
- 成功态按钮:「取消预约」(红色)/「查看预约详情」(主色);失败态按钮:「重新预约」(独占一行)
- 「失败原因详情」弹窗:失败原因、发生时间、处理建议
- 「确认取消预约」弹窗(见下方表单字段表)
- 成功态按钮:「取消预约」(红色)/「现场体检」(主色);失败态按钮:「重新预约」(独占一行)
- 「失败原因详情」弹窗:失败原因、发生时间
- 「确认取消预约」弹窗:说明文案 + 医院与日期信息;「再想想 / 确认取消」
![失败原因详情弹窗](screenshots/booking-date-selected-reason.jpg)
@@ -370,24 +378,16 @@
**业务规则:**
- 失败语义为号源争抢失败(所选日期号源已被其他员工约满);失败态隐藏体检须知,引导重新预约
- 已预约状态下点击步骤条弹 Toast「当前已预约体检,无法重新选择,请先取消预约」;失败态 Toast 文案切换为「预约未成功,请点击下方『重新预约』按钮重新预约」
- 取消预约必须选择原因;选择「其他」时必须填写文字说明(上限 100 字)
- 取消成功后 Toast「预约已取消」并返回预约日期页重新预约
- 取消预约为二次确认弹窗,无需填写原因;确认后名额释放,Toast「预约已取消」并返回预约日期页重新预约
- 取消时限约束:至少提前一天操作
**交互说明:**
- 失败态点击「?」打开失败原因详情弹窗
- 成功态点击「取消预约」打开取消预约弹窗,校验通过确认后返回预约日期页
- 成功态点击「查看预约详情」跳转到「现场体检」(常规体检·待体检
- 成功态点击「取消预约」打开二次确认弹窗,「确认取消」后返回预约日期页
- 成功态点击「现场体检」跳转到「现场体检」(常规体检·已预约,含签到码
- 失败态点击「重新预约」跳转到预约日期页
- 点击返回回到健康体检主页
**表单字段表(取消预约弹窗):**
| 字段名 | 类型 | 是否必填 | 校验规则 |
|--------|------|----------|----------|
| 取消原因 | chip 单选(时间冲突/医院选择不当/日期选择不当/个人健康原因/其他) | 是 | 未选择时提示「请选择或填写取消原因」 |
| 其他原因说明 | 多行文本(maxlength=100) | 选「其他」时必填 | 去空格后非空,否则提示同上 |
---
## 四、胃肠镜体检预约流程
@@ -499,10 +499,11 @@
- 体检须知卡片(仅成功态)
- 成功态按钮「取消预约 / 查看预约详情」;失败态按钮「重新预约」
- 「失败原因详情」弹窗:失败原因说明 + 处理建议
- 「确认取消预约」弹窗原因 chips + 其他说明,与常规流程一致)
- 「确认取消预约」弹窗:取消原因 chip 单选(时间冲突/医院选择不当/日期选择不当/个人健康原因/其他),选「其他」需填写文字说明
**业务规则:**
- 与常规预约结果页一致:失败态隐藏须知、取消必填原因、取消成功后返回预约日期页
- 失败态隐藏体检须知,引导重新预约
- 取消预约必须选择原因,选择「其他」时必须填写文字说明;取消成功后返回预约日期页
- 已预约状态下点击步骤条弹 Toast 阻止回跳
**交互说明:**
@@ -514,31 +515,32 @@
## 五、胃肠镜医院预选
> 独立于正式预约的意向征集活动:征集期内员工对下一年度承检医院进行意向多选,每人仅可填写一次、提交后不可修改
> 独立于正式预约的意向征集活动:征集期内员工对下一年度承检医院进行意向多选(至少 2 家、最多 3 家),提交后可在完成页重新填写调整
### 13. 医院预选填写(gastro-preselect.html
![页面截图](screenshots/gastro-preselect.jpg)
**功能说明:** 胃肠镜体检医院意向征集填写页,员工多选意向医院后经二次确认提交。
**功能说明:** 胃肠镜体检医院意向征集填写页,员工多选意向医院(至少 2 家、最多 3 家)后经二次确认提交,可再次进入调整重新提交。
**主要UI元素:**
- 征集说明卡:征集标题、预选日期、红色警示条
- 区块标题「选择意向医院」+「点击卡片可多选,当前已选 N 家」(计数实时更新)
- 医院多选卡片(方形复选框,选中态青色对勾):各带省市信息与类型标签
- 「确认提交预选」弹窗:警示文案 + 已选医院数量与名称列表;「返回修改 / 确认提交」
- 征集说明卡:征集标题、预选日期
- 区块标题「选择意向医院」+「点击卡片可多选,至少选择2家、最多3家,当前已选 N 家」(计数实时更新)
- 医院多选卡片(方形复选框,选中态青色对勾):各带省市信息
- 「确认提交预选」弹窗:已选医院数量与名称列表;「返回修改 / 确认提交」
- Toast 轻提示:数量不满足 2~3 家时的校验提醒
- 底部「提交预选」按钮(选中后文案带已选数量)
![确认提交预选弹窗](screenshots/gastro-preselect-dialog.jpg)
**业务规则:**
- 多选互不影响,已选 0 家时提交按钮禁用
- 提交前强制二次确认(因仅可填写一次)
- 每人限提交一次,提交后不可修改
- 数量校验:少于 2 家提示「至少选择2家意向医院,请继续选择」,多于 3 家提示「最多选择3家意向医院,请调整后再提交」;校验通过才进入二次确认
- 提交前二次确认核对已选医院;支持重复提交,再次提交覆盖上一次意向
**交互说明:**
- 点击医院卡片勾选/取消勾选,按钮文案与计数联动
- 点击「提交预选」打开确认弹窗(未选医院时按钮禁用)
- 点击「提交预选」先校验数量(2~3 家),通过后打开确认弹窗(未选医院时按钮禁用)
- 弹窗「返回修改」关闭弹窗,「确认提交」跳转到预选提交完成页
- 点击返回回到健康体检主页
@@ -548,19 +550,20 @@
![页面截图](screenshots/gastro-preselect-done.jpg)
**功能说明:** 预选提交成功后的只读回显页。
**功能说明:** 预选提交成功后的回显页,展示当前提交的意向医院,支持重新填写调整
**主要UI元素:**
- 成功卡:绿色对勾图标、「预选提交成功」、提交时间、绿色提示框
- 「我选择的意向医院」区块:只读回显说明 + 只读已选医院卡片(固定青色描边 + 对勾)
- 底部「返回首页」按钮
- 成功卡:绿色对勾图标、「预选提交成功」、提交时间
- 「我选择的意向医院」区块:当前提交的意向医院卡片(固定青色描边 + 对勾)
- 底部「重新填写」按钮
**业务规则:**
- 纯只读展示,医院卡片不可点击
- 展示当前提交的意向医院,医院卡片不可直接点击
- 返回主页后,主页预选通知条变为「预选已完成」态
**交互说明:**
- 点击「返回首页」或返回箭头回到健康体检主页
- 点击「重新填写」回到预选填写页,可修改意向医院后再次提交
- 点击返回箭头回到健康体检主页
---
@@ -627,10 +630,12 @@
- 预约信息卡片:体检医院、体检员工、体检地址、体检日期、体检项目
- 体检须知卡片(仅成功态)
- 成功态按钮「取消预约 / 查看预约详情」;失败态按钮「重新预约」
- 「失败原因详情」弹窗与「确认取消预约」弹窗(与常规流程一致)
- 「失败原因详情」弹窗:失败原因说明 + 处理建议
- 「确认取消预约」弹窗:取消原因 chip 单选(时间冲突/医院选择不当/日期选择不当/个人健康原因/其他),选「其他」需填写文字说明
**业务规则:**
- 与常规预约结果页一致:失败态隐藏须知、取消必填原因、取消成功后返回预约日期页
- 失败态隐藏体检须知,引导重新预约
- 取消预约必须选择原因,选择「其他」时必须填写文字说明;取消成功后返回预约日期页
**交互说明:**
- 成功态「查看预约详情」跳转到「现场体检」(冠脉CTA体检页签)
@@ -645,17 +650,17 @@
![页面截图](screenshots/my-appointments-all.jpg)
**功能说明:** 查看各类体检的预约详情与现场体检信息:体检签到码(条形码/二维码)、预约概要、体检须知、已检/未检项目进度;无预约时提供立即预约入口。
**功能说明:** 查看各类体检的预约详情与现场体检信息:体检签到码(条形码)、预约概要、体检须知、已检/未检项目进度;无预约时提供立即预约入口。
![胃肠镜体检页签](screenshots/my-appointments-all-gastro.jpg)
**主要UI元素:**
- 体检类型 Tab(吸顶,3 等分):常规体检 / 胃肠镜体检 / 冠脉CTA体检
- 常规体检·待体检视图:
- 「体检签到码」卡片:条形码/二维码双选项卡切换,条形码下编号,提示「点击条码可放大查看」;点击打开放大遮罩
- 预约概要卡:体检医院、预约日期、体检员工、单位部门、状态徽章(待体检=橙 / 已参检=绿)
- 常规体检·已预约视图:
- 「体检签到码」卡片:条形码 + 编号,提示「点击条码可放大查看」;点击打开放大遮罩
- 预约概要卡:体检医院、体检地址、预约日期、体检员工、单位部门
- 体检进度卡:Tab「已检项目 (N) / 未检项目 (M)」,常规项目列表,已检=绿色✓「已参检」、未检=橙色●「未检」
- 胃肠镜/冠脉CTA视图:预约概要卡 + 体检须知卡(6 条,与预约结果页一致)
- 胃肠镜/冠脉CTA视图:预约概要卡(体检员工、体检地址、体检日期、体检项目)+ 体检须知卡(6 条,与预约结果页一致)
- 无预约空态:图标 +「暂无XX体检预约」+ 说明文案 +「立即预约」按钮
**业务规则:** 如未预约,显示未预约的提示页面
@@ -663,19 +668,17 @@
![冠脉CTA体检页签](screenshots/my-appointments-all-cta.jpg)
**业务规则:**
- 预约状态三态:待体检(橙)/ 已参检(绿)/ 无预约信息(空态);已完成视图无签到码卡片,进度为全部已检
- 签到码仅「常规体检·待体检」状态展示
- 预约状态两态演示:已预约 / 无预约信息(空态);已参检视图无签到码卡片,进度为全部已检
- 签到码仅「常规体检·已预约」状态展示
- 已检/未检列表空态文案:「暂无已检项目」/「当前预约项目已全部完成」
**交互说明:**
- 点击类型 Tab 切换对应体检面板
- 点击「条形码/二维码」切换签到码形态;点击码打开放大遮罩,点击遮罩空白关闭
- 点击签到码打开放大遮罩,点击遮罩空白关闭
- 点击已检/未检 Tab 切换项目列表
- 空态「立即预约」按类型分流:常规跳转到选择体检类型页,胃肠镜跳转到胃肠镜·选择医院,冠脉CTA跳转到冠脉CTA·选择医院
- 点击返回回到健康体检主页
**业务规则:** 如未预约,显示未预约的提示页面
---
## 八、体检报告与指标对比
@@ -684,19 +687,22 @@
![页面截图](screenshots/reports.jpg)
**功能说明:** 历年体检报告汇总入口页,展示体检年份统计、历年体检趋势分析入口和按年度/报告类型分类的历年报告列表。
**功能说明:** 历年体检报告汇总入口页,展示体检年份统计、历年体检趋势分析入口和历年报告列表,支持按年份与体检类型筛选
**主要UI元素:**
- 汇总卡(青色渐变):累计体检年份、起始年份、报告总数、年度时间轴装饰
- 历年体检趋势分析入口条:重点指标数目 · 纵向对比历年体检数据 + 右箭头
- 报告筛选栏:年份选择(日历面板:全部/2021~2025)+ 体检类型下拉(全部类型/常规体检/胃肠镜体检/冠脉CTA体检),默认「全部」
- 历年报告列表(图标 + 标题 + 类型标签 + 机构名 + 箭头):同一年份可有多类报告
**业务规则:**
- 报告类型标签三色:常规体检(蓝)、胃肠镜体检(青)、冠脉CTA体检(红)
- 报告按年度倒序排列
- 年份与类型两个筛选条件叠加过滤;选择非「全部」时对应入口按钮高亮
**交互说明:**
- 点击历年体检趋势分析条跳转到指标纵向对比页
- 点击年份/类型入口打开面板选择筛选条件,报告列表即时过滤;点击面板外部收起
- 点击常规体检报告跳转到常规体检报告页
- 点击胃肠镜体检报告跳转到胃肠镜报告页
- 点击冠脉CTA体检报告跳转到冠脉CTA报告页
@@ -765,7 +771,7 @@
- 报告头:报告标题 + 类型标签 + 基本信息
- 胃镜检查(橙色标题条,4 个可折叠字段):食管、胃、十二指肠所见描述;诊断结果
- 肠镜检查(青绿标题条,3 个字段):结肠所见;诊断结果;病理诊断
- 影像报告:胃镜报告、肠镜报告、活检报告「查看」入口
- 报告图片:胃镜报告、肠镜报告、活检报告「查看」入口
- 底部「返回报告列表」
**业务规则:**
@@ -867,11 +873,13 @@
**功能说明:** 集中展示体检报告中的异常指标(按检查大类分组表格呈现),每个子项提供「专家库一键咨询」入口。
**主要UI元素:**
- 顶部体检信息栏:当前咨询数据的体检年份
- 大分类区块,每区块下分子项(子项名 + 右侧红色「专家库一键咨询 ›」链接):化学检查、常规检查、影像检查、肠胃镜检查
- 异常结果表:四列(名称/结果/单位/参考值),结果列红色加粗;肠胃镜为两列(名称/结果)
**业务规则:**
- 仅展示异常项,结果列统一红色加粗标记异常
- 数据为员工最近一次的体检数据,顶部展示对应体检年份
**交互说明:**
- 点击「专家库一键咨询 ›」跳转到健康咨询模块的专家列表页,每个分类会关联科室或者疾病,优先跳对应科室,如没有科室,跳对应疾病,如两者都没,跳默认
+17 -1
View File
@@ -15,7 +15,7 @@ const docDir = __dirname;
const root = path.resolve(__dirname, '..');
// mode: auto(短页面最低 844 / 长页面真实高度)
const tasks = [
const baseTasks = [
// ========== 模块主页 ==========
{ html: 'index.html', out: 'index' },
@@ -29,6 +29,8 @@ const tasks = [
{ html: 'booking-date.html', out: 'booking-date' },
{ html: 'booking-date-selected.html', out: 'booking-date-selected' },
{ html: 'booking-date-selected.html', out: 'booking-date-selected-fail', demo: 'fail' },
{ html: 'booking-date-selected.html', out: 'booking-date-selected-reason', script: () => openReasonModal() },
{ html: 'booking-date-selected.html', out: 'booking-date-selected-cancel', script: () => openCancelModal() },
{ html: 'project-detail.html', out: 'project-detail', query: '?id=general' },
// ========== 胃肠镜体检流程 ==========
@@ -39,6 +41,7 @@ const tasks = [
{ html: 'gastro-date-selected.html', out: 'gastro-date-selected' },
{ html: 'gastro-date-selected.html', out: 'gastro-date-selected-fail', demo: 'fail' },
{ html: 'gastro-preselect.html', out: 'gastro-preselect' },
{ html: 'gastro-preselect.html', out: 'gastro-preselect-dialog', script: () => { document.querySelectorAll('.hospital-card')[0].click(); document.querySelectorAll('.hospital-card')[1].click(); openConfirm(); } },
{ html: 'gastro-preselect-done.html', out: 'gastro-preselect-done' },
// ========== 冠脉CTA体检流程 ==========
@@ -71,6 +74,13 @@ const tasks = [
];
(async () => {
// 支持命令行按 out 名称过滤,只重截部分截图:node screenshot.mjs index reports
const only = process.argv.slice(2);
const tasks = only.length ? baseTasks.filter(t => only.includes(t.out)) : baseTasks;
if (only.length && tasks.length === 0) {
console.error('未匹配到任何截图任务,可用名称:', baseTasks.map(t => t.out).join(', '));
process.exit(1);
}
const browser = await chromium.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const context = await browser.newContext({ viewport: { width: 390, height: 4000 } });
@@ -104,6 +114,12 @@ const tasks = [
await page.waitForTimeout(250);
}
// 自定义脚本(如打开弹窗后再截图)
if (t.script) {
await page.evaluate(t.script);
await page.waitForTimeout(250);
}
// 解除 phone-shell 高度限制:短页面保底 844,长页面按内容展开
await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 38 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: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 56 KiB

@@ -48,7 +48,7 @@
.scroll-content { flex: 1; overflow-y: auto; background: linear-gradient(180deg, #effbfb 0%, #f7f9fb 220px, #f7f9fb 100%); padding-bottom: 120px; }
.scroll-content::-webkit-scrollbar { display: none; }
.page-body { padding: 16px; }
/* 提交成功卡:绿色对勾 + 提交时间 + 不可修改说明 */
/* 提交成功卡:绿色对勾 + 提交时间 */
.success-card {
background: #fff; border-radius: 18px; padding: 24px 16px 18px; margin-bottom: 18px;
box-shadow: var(--shadow); border: 1px solid var(--border); text-align: center;
@@ -60,15 +60,10 @@
box-shadow: 0 10px 24px rgba(82, 196, 26, 0.28);
}
.success-card__title { font-size: 18px; font-weight: 700; margin-bottom: 6px; }
.success-card__time { font-size: 12px; color: var(--text-muted); margin-bottom: 12px; }
.success-card__note {
display: inline-block; padding: 6px 12px; border-radius: 12px;
background: #f6ffed; border: 1px solid #b7eb8f;
font-size: 12px; color: #52c41a; line-height: 1.6;
}
.success-card__time { font-size: 12px; color: var(--text-muted); }
.section-title { font-size: 16px; font-weight: 700; margin-bottom: 6px; }
.section-sub { font-size: 12px; color: var(--text-muted); margin-bottom: 12px; }
/* 已选医院只读卡片:不可点击,右侧固定绿色对勾 */
/* 已选医院回显卡片:右侧固定绿色对勾 */
.hospital-card {
background: #fff; border-radius: 18px; padding: 16px; margin-bottom: 12px;
border: 2px solid var(--primary); box-shadow: var(--shadow);
@@ -122,11 +117,10 @@
</div>
<div class="success-card__title">预选提交成功</div>
<div class="success-card__time">提交时间:2026-09-03 12:00</div>
<div class="success-card__note">感谢您的参与,您的意向将作为2027年度承检医院安排参考<br>预选结果提交后不可修改</div>
</div>
<div class="section-title">我选择的意向医院</div>
<div class="section-sub"><b style="color:#0f9b9b;">2</b> 家,以下为只读回显,不可再次编辑</div>
<div class="section-sub"><b style="color:#0f9b9b;">2</b> 家,以下为当前提交的意向医院,可重新填写调整</div>
<div class="hospital-card">
<div class="hospital-card__info">
@@ -163,8 +157,8 @@
</div>
<div class="bottom-actions">
<!-- [交互] 点击返回首页: index.html,首页通知条同步变为「预选已完成」态 -->
<button class="bottom-actions__btn" onclick="location.href='index.html'">返回首页</button>
<!-- [交互] 重新填写:返回预选表单 gastro-preselect.html,可修改后再次提交 -->
<button class="bottom-actions__btn" onclick="location.href='gastro-preselect.html'">重新填写</button>
</div>
</div>
</body>
@@ -48,7 +48,7 @@
.scroll-content { flex: 1; overflow-y: auto; background: linear-gradient(180deg, #effbfb 0%, #f7f9fb 180px, #f7f9fb 100%); padding-bottom: 120px; }
.scroll-content::-webkit-scrollbar { display: none; }
.page-body { padding: 16px; }
/* 征集说明卡:目的、周期、不可逆提示 */
/* 征集说明卡:征集活动说明与周期 */
.intro-card {
background: #fff; border-radius: 18px; padding: 16px; margin-bottom: 18px;
box-shadow: var(--shadow); border: 1px solid var(--border);
@@ -62,11 +62,6 @@
.intro-card__field span { color: var(--text-muted); }
.intro-card__field b { color: var(--text-primary); font-weight: 600; white-space: nowrap; }
.intro-card__field:last-of-type { border-bottom: none; padding-bottom: 2px; }
.intro-card__warn {
margin-top: 10px; padding: 8px 10px; border-radius: 10px;
background: #fff2f0; border: 1px solid #ffccc7;
font-size: 12px; color: #f5222d; line-height: 1.6;
}
.section-title { font-size: 16px; font-weight: 700; margin-bottom: 6px; }
.section-sub { font-size: 12px; color: var(--text-muted); margin-bottom: 12px; }
.hospital-card {
@@ -83,10 +78,6 @@
}
.hospital-card__name { font-size: 15px; font-weight: 600; color: var(--text-primary); margin-bottom: 2px; }
.hospital-card__addr { font-size: 12px; color: var(--text-muted); }
.hospital-card__tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.htag { font-size: 10px; line-height: 16px; padding: 0 8px; border-radius: 8px; }
.htag--gastro { background: #f6ffed; color: #52c41a; }
.htag--routine { background: #e6f7ff; color: #1890ff; }
/* 多选复选框:区别于正式预约的单选圆点 */
.check-box {
width: 22px; height: 22px; border-radius: 6px; border: 2px solid #d9d9d9;
@@ -108,7 +99,7 @@
.bottom-actions__btn:disabled {
background: #d9d9d9; color: #fff; box-shadow: none; cursor: not-allowed;
}
/* 提交二次确认弹窗:因预选仅可填写一次,提交前强制确认 */
/* 提交二次确认弹窗:提交前列出已选医院供用户核对 */
.modal-overlay {
position: absolute; inset: 0; background: rgba(0,0,0,0.45); z-index: 100;
display: flex; align-items: center; justify-content: center;
@@ -117,7 +108,6 @@
.modal-card { width: 340px; background: #fff; border-radius: 24px; padding: 24px 20px; box-shadow: 0 20px 60px rgba(0,0,0,0.18); }
.modal-card__title { font-size: 18px; font-weight: 700; text-align: center; margin-bottom: 6px; }
.modal-card__sub { font-size: 13px; color: var(--text-muted); text-align: center; margin-bottom: 16px; line-height: 1.6; }
.modal-card__sub b { color: #f5222d; }
.modal-hospital-list { background: #f7f9fb; border-radius: 14px; padding: 12px 14px; margin-bottom: 20px; }
.modal-hospital-list__title { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }
.modal-hospital-list__item {
@@ -135,6 +125,14 @@
background: linear-gradient(135deg, #13c2c2 0%, #36cfc9 100%);
color: #fff; box-shadow: 0 8px 20px rgba(19, 194, 194, 0.2);
}
/* 轻提示 toast:选择数量不满足 2~3 家时给出校验提醒 */
.toast {
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(0.9);
background: rgba(0,0,0,0.75); color: #fff; padding: 14px 22px; border-radius: 12px;
font-size: 13px; line-height: 1.6; text-align: center; z-index: 200;
opacity: 0; pointer-events: none; transition: all 0.25s ease; max-width: 280px;
}
.toast.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
</style>
</head>
<body>
@@ -158,23 +156,17 @@
<div class="intro-card">
<div class="intro-card__title">📋 2027 年度胃肠镜体检医院意向征集</div>
<div class="intro-card__field"><span>预选日期: 2026-07-01至2026-10-20</span></div>
<div class="intro-card__warn">⚠️ 本次预选每人仅可填写一次,提交后不可修改,请谨慎选择。</div>
</div>
<div class="section-title">选择意向医院</div>
<div class="section-sub">点击卡片可多选,当前已选 <span id="selectedCount">0</span></div>
<div class="section-sub">点击卡片可多选,至少选择2家、最多3家,当前已选 <span id="selectedCount">0</span></div>
<div class="hospital-card" onclick="toggleHospital(this)">
<div class="hospital-card__info">
<div class="hospital-card__icon">🏥</div>
<div>
<div class="hospital-card__name">宁夏宝石花医院</div>
<div class="hospital-card__addr">宁夏银川市</div>
<div class="hospital-card__tags">
<span class="htag htag--gastro">胃肠镜体检</span>
<span class="htag htag--routine">常规体检</span>
</div>
</div>
<div class="hospital-card__addr">宁夏银川市</div> </div>
</div>
<div class="check-box"></div>
</div>
@@ -184,11 +176,7 @@
<div class="hospital-card__icon">🏥</div>
<div>
<div class="hospital-card__name">CQ星龙园医院</div>
<div class="hospital-card__addr">陕西西安市</div>
<div class="hospital-card__tags">
<span class="htag htag--gastro">胃肠镜体检</span>
</div>
</div>
<div class="hospital-card__addr">陕西西安市</div> </div>
</div>
<div class="check-box"></div>
</div>
@@ -198,11 +186,7 @@
<div class="hospital-card__icon">🏥</div>
<div>
<div class="hospital-card__name">唐都医院</div>
<div class="hospital-card__addr">陕西西安市</div>
<div class="hospital-card__tags">
<span class="htag htag--gastro">胃肠镜体检</span>
</div>
</div>
<div class="hospital-card__addr">陕西西安市</div> </div>
</div>
<div class="check-box"></div>
</div>
@@ -212,12 +196,7 @@
<div class="hospital-card__icon">🏥</div>
<div>
<div class="hospital-card__name">西安市第四医院</div>
<div class="hospital-card__addr">陕西西安市</div>
<div class="hospital-card__tags">
<span class="htag htag--gastro">胃肠镜体检</span>
<span class="htag htag--routine">常规体检</span>
</div>
</div>
<div class="hospital-card__addr">陕西西安市</div> </div>
</div>
<div class="check-box"></div>
</div>
@@ -227,11 +206,7 @@
<div class="hospital-card__icon">🏥</div>
<div>
<div class="hospital-card__name">银川市第一人民医院</div>
<div class="hospital-card__addr">宁夏银川市</div>
<div class="hospital-card__tags">
<span class="htag htag--gastro">胃肠镜体检</span>
</div>
</div>
<div class="hospital-card__addr">宁夏银川市</div> </div>
</div>
<div class="check-box"></div>
</div>
@@ -247,7 +222,7 @@
<div class="modal-overlay hidden" id="confirmModal">
<div class="modal-card">
<div class="modal-card__title">确认提交预选</div>
<div class="modal-card__sub">本次预选<b>仅可填写一次,提交后不可修改</b><br>请确认以下意向医院无误</div>
<div class="modal-card__sub">请确认以下意向医院无误</div>
<div class="modal-hospital-list">
<div class="modal-hospital-list__title">已选 <span id="modalCount">0</span> 家医院</div>
<div id="modalHospitalList"></div>
@@ -258,6 +233,8 @@
</div>
</div>
</div>
<div class="toast" id="toast"></div>
</div>
<script>
@@ -274,10 +251,21 @@
btn.disabled = count === 0;
btn.textContent = count === 0 ? '提交预选' : '提交预选(已选 ' + count + ' 家)';
}
// 打开二次确认弹窗前,将已选医院名称注入弹窗列表
// toast 轻提示:展示 2.2 秒后自动消失
var toastTimer = null;
function showToast(msg) {
var t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { t.classList.remove('show'); }, 2200);
}
// 打开二次确认弹窗前校验数量(至少2家、最多3家),并将已选医院名称注入弹窗列表
function openConfirm() {
var actives = document.querySelectorAll('.hospital-card.active');
if (actives.length === 0) return;
if (actives.length < 2) { showToast('至少选择2家意向医院,请继续选择'); return; }
if (actives.length > 3) { showToast('最多选择3家意向医院,请调整后再提交'); return; }
var html = '';
actives.forEach(function (card) {
html += '<div class="modal-hospital-list__item">' + card.querySelector('.hospital-card__name').textContent + '</div>';
+3 -3
View File
@@ -464,7 +464,7 @@
<span class="banner-slide__ring"></span>
<span class="banner-slide__deco">🩺</span>
<div class="banner-slide__title">单位已制定2026年度常规体检计划</div>
<div class="banner-slide__date">🗓 体检日期:2026-09-01 至 2026-12-31</div>
<div class="banner-slide__date">🗓 起止日期:2026-09-01 至 2026-12-31</div>
<div class="banner-slide__fee">
<span class="banner-chip">💰 体检费用额度 <b>¥1500.00</b></span>
</div>
@@ -483,7 +483,7 @@
<span class="banner-slide__ring"></span>
<span class="banner-slide__deco">🔬</span>
<div class="banner-slide__title">单位已拟定2026年度胃肠镜体检计划</div>
<div class="banner-slide__date">🗓 体检日期:2026-10-08 至 2026-12-20</div>
<div class="banner-slide__date">🗓 起止日期:2026-10-08 至 2026-12-20</div>
<!-- 圆圈进度(右下角):已预约 = 60%(已选项目20%→已预约60%→体检中80%→已参检100%),dashoffset = 131.9 × 40% ≈ 52.8 -->
<div class="banner-progress">
<svg width="52" height="52" viewBox="0 0 52 52" fill="none">
@@ -499,7 +499,7 @@
<span class="banner-slide__ring"></span>
<span class="banner-slide__deco">🫀</span>
<div class="banner-slide__title">单位已拟定2026年度冠脉CTA体检计划</div>
<div class="banner-slide__date">🗓 体检日期:2026-10-08 至 2026-12-20</div>
<div class="banner-slide__date">🗓 起止日期:2026-10-08 至 2026-12-20</div>
<!-- 圆圈进度(右下角):已预约 = 60%(已选项目20%→已预约60%→体检中80%→已参检100%),dashoffset = 131.9 × 40% ≈ 52.8 -->
<div class="banner-progress">
<svg width="52" height="52" viewBox="0 0 52 52" fill="none">
@@ -199,37 +199,6 @@
border: 1px solid #e4f7f5;
box-shadow: var(--shadow);
}
.person-card__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.code-switch {
display: inline-flex;
align-items: center;
gap: 2px;
padding: 2px;
border-radius: 999px;
border: 1px solid #e4f7f5;
background: #f2fbfa;
}
.code-switch__opt {
border: none;
background: transparent;
font-size: 12px;
line-height: 1;
color: var(--text-secondary);
padding: 5px 10px;
border-radius: 999px;
cursor: pointer;
}
.code-switch__opt--active {
background: var(--primary);
color: #fff;
font-weight: 600;
box-shadow: 0 2px 6px rgba(19, 194, 194, 0.35);
}
.qrcode-wrap { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.code-img {
background: #fff;
@@ -239,14 +208,8 @@
transition: transform 0.2s;
}
.code-img:hover { transform: scale(1.04); }
.code-img--qr { width: 160px; height: 160px; }
.code-img--bar { width: 224px; height: 132px; }
.qrcode-wrap__hint { font-size: 12px; color: var(--text-muted); }
/* 条形码/二维码互斥显隐 */
.person-card .code-only--bar { display: flex; }
.person-card .code-only--qr { display: none; }
.person-card--qr .code-only--bar { display: none; }
.person-card--qr .code-only--qr { display: flex; }
/* 放大遮罩 */
.qr-overlay {
display: none;
@@ -259,7 +222,6 @@
cursor: pointer;
}
.qr-overlay--visible { display: flex; }
.qr-overlay__img--qr { width: 260px; height: 260px; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); background:#fff; }
.qr-overlay__img--bar { width: 300px; height: 176px; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); background:#fff; }
/* 体检须知 */
.tips-card {
@@ -376,8 +338,7 @@
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="demo-select__menu hidden" id="demoMenu">
<div class="demo-select__item demo-select__item--active" data-status="pending" onclick="setDemoStatus('pending')">待体检<span class="check"></span></div>
<div class="demo-select__item" data-status="done" onclick="setDemoStatus('done')">已完成<span class="check"></span></div>
<div class="demo-select__item demo-select__item--active" data-status="pending" onclick="setDemoStatus('pending')">已预约<span class="check"></span></div>
<div class="demo-select__item" data-status="none" onclick="setDemoStatus('none')">无预约信息<span class="check"></span></div>
</div>
</div>
@@ -394,54 +355,24 @@
<!-- ======================= 常规体检 · 待体检 ======================= -->
<div class="panel" data-type="routine" data-status="pending">
<div class="person-card" id="codeRoutine">
<div class="person-card__header">
<div class="section-title" style="margin-bottom:0">体检签到码</div>
<!-- [交互] 点击右上角「条形码/二维码」双选项卡切换签到码展示 -->
<div class="code-switch">
<button class="code-switch__opt code-switch__opt--active" type="button" id="codeRoutineTabBar" onclick="setCodeMode('codeRoutine', 'bar')">条形码</button>
<button class="code-switch__opt" type="button" id="codeRoutineTabQr" onclick="setCodeMode('codeRoutine', 'qr')">二维码</button>
</div>
</div>
<div class="qrcode-wrap code-only--bar">
<div class="qrcode-wrap">
<!-- [交互] 点击条码放大展示 -->
<svg class="code-img code-img--bar" onclick="openCodeOverlay('codeRoutine')" viewBox="0 0 200 110" xmlns="http://www.w3.org/2000/svg">
<svg class="code-img code-img--bar" onclick="openCodeOverlay()" viewBox="0 0 200 110" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="110" fill="#fff"/>
<rect x="10" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="16" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="20" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="25" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="32" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="36" y="10" width="2.5" height="72" fill="#1a1a1a"/><rect x="41" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="45" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="50" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="54" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="58" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="65" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="69" y="10" width="2.5" height="72" fill="#1a1a1a"/><rect x="74" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="78" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="83" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="87" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="94" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="98" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="102" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="107" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="111" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="118" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="122" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="126" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="131" y="10" width="2.5" height="72" fill="#1a1a1a"/><rect x="136" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="140" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="147" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="151" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="155" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="160" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="164" y="10" width="2.5" height="72" fill="#1a1a1a"/><rect x="169" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="176" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="180" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="184" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="189" y="10" width="2.5" height="72" fill="#1a1a1a"/>
<text x="100" y="97" text-anchor="middle" font-size="10" font-family="monospace" fill="#666">TJ2026032300871</text>
</svg>
<span class="qrcode-wrap__hint">点击条码可放大查看</span>
</div>
<div class="qrcode-wrap code-only--qr">
<!-- [交互] 点击二维码放大展示 -->
<svg class="code-img code-img--qr" onclick="openCodeOverlay('codeRoutine')" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg">
<rect width="160" height="160" fill="#fff"/>
<rect x="12" y="12" width="40" height="40" rx="4" fill="#1a1a1a"/><rect x="18" y="18" width="28" height="28" rx="2" fill="#fff"/><rect x="24" y="24" width="16" height="16" rx="2" fill="#1a1a1a"/>
<rect x="108" y="12" width="40" height="40" rx="4" fill="#1a1a1a"/><rect x="114" y="18" width="28" height="28" rx="2" fill="#fff"/><rect x="120" y="24" width="16" height="16" rx="2" fill="#1a1a1a"/>
<rect x="12" y="108" width="40" height="40" rx="4" fill="#1a1a1a"/><rect x="18" y="114" width="28" height="28" rx="2" fill="#fff"/><rect x="24" y="120" width="16" height="16" rx="2" fill="#1a1a1a"/>
<rect x="60" y="12" width="8" height="8" fill="#1a1a1a"/><rect x="76" y="12" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="12" width="8" height="8" fill="#1a1a1a"/>
<rect x="60" y="24" width="8" height="8" fill="#1a1a1a"/><rect x="84" y="24" width="8" height="8" fill="#1a1a1a"/>
<rect x="68" y="36" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="36" width="8" height="8" fill="#1a1a1a"/>
<rect x="12" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="28" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="44" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="68" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="108" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="132" y="60" width="8" height="8" fill="#1a1a1a"/>
<rect x="20" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="44" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="60" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="84" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="108" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="124" y="72" width="8" height="8" fill="#1a1a1a"/>
<rect x="12" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="36" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="60" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="76" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="100" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="140" y="84" width="8" height="8" fill="#1a1a1a"/>
<rect x="28" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="52" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="68" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="116" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="140" y="96" width="8" height="8" fill="#1a1a1a"/>
<rect x="60" y="108" width="8" height="8" fill="#1a1a1a"/><rect x="76" y="108" width="8" height="8" fill="#1a1a1a"/><rect x="100" y="108" width="8" height="8" fill="#1a1a1a"/><rect x="124" y="108" width="8" height="8" fill="#1a1a1a"/>
<rect x="60" y="120" width="8" height="8" fill="#1a1a1a"/><rect x="84" y="120" width="8" height="8" fill="#1a1a1a"/><rect x="108" y="120" width="8" height="8" fill="#1a1a1a"/><rect x="140" y="120" width="8" height="8" fill="#1a1a1a"/>
<rect x="60" y="132" width="8" height="8" fill="#1a1a1a"/><rect x="76" y="132" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="132" width="8" height="8" fill="#1a1a1a"/><rect x="116" y="132" width="8" height="8" fill="#1a1a1a"/><rect x="140" y="132" width="8" height="8" fill="#1a1a1a"/>
<rect x="68" y="140" width="8" height="8" fill="#1a1a1a"/><rect x="100" y="140" width="8" height="8" fill="#1a1a1a"/><rect x="132" y="140" width="8" height="8" fill="#1a1a1a"/>
</svg>
<span class="qrcode-wrap__hint">点击二维码可放大查看</span>
</div>
</div>
<div class="summary-card">
<div class="section-title">常规体检</div>
<div class="summary-list">
<div class="summary-item"><div class="summary-item__label">体检医院</div><div class="summary-item__value">宁夏宝石花医院</div></div>
<div class="summary-item"><div class="summary-item__label">体检地址</div><div class="summary-item__value">银川市西夏区怀远路148号</div></div>
<div class="summary-item"><div class="summary-item__label">预约日期</div><div class="summary-item__value">2026-03-23</div></div>
<div class="summary-item"><div class="summary-item__label">体检员工</div><div class="summary-item__value">张旭</div></div>
<div class="summary-item"><div class="summary-item__label">单位部门</div><div class="summary-item__value">采油一厂·xx作业区</div></div>
<div class="summary-item"><div class="summary-item__label">状态</div><div class="summary-item__value" style="color:#fa8c16;">待体检</div></div>
</div>
<div class="summary-item"><div class="summary-item__label">单位部门</div><div class="summary-item__value">采油一厂·xx作业区</div></div> </div>
</div>
<div class="progress-card">
<!-- [交互] 点击切换已检/未检 Tab -->
@@ -460,11 +391,10 @@
<div class="section-title">常规体检</div>
<div class="summary-list">
<div class="summary-item"><div class="summary-item__label">体检医院</div><div class="summary-item__value">宁夏宝石花医院</div></div>
<div class="summary-item"><div class="summary-item__label">体检地址</div><div class="summary-item__value">银川市西夏区怀远路148号</div></div>
<div class="summary-item"><div class="summary-item__label">预约日期</div><div class="summary-item__value">2026-03-23</div></div>
<div class="summary-item"><div class="summary-item__label">体检员工</div><div class="summary-item__value">张旭</div></div>
<div class="summary-item"><div class="summary-item__label">单位部门</div><div class="summary-item__value">采油一厂·xx作业区</div></div>
<div class="summary-item"><div class="summary-item__label">状态</div><div class="summary-item__value" style="color:#52c41a;">已参检</div></div>
</div>
<div class="summary-item"><div class="summary-item__label">单位部门</div><div class="summary-item__value">采油一厂·xx作业区</div></div> </div>
</div>
<div class="progress-card">
<!-- [交互] 点击切换已检/未检 Tab -->
@@ -499,9 +429,7 @@
<div class="summary-item"><div class="summary-item__label">体检员工</div><div class="summary-item__value">刘韵琴</div></div>
<div class="summary-item"><div class="summary-item__label">体检地址</div><div class="summary-item__value">银川市西夏区怀远路148号</div></div>
<div class="summary-item"><div class="summary-item__label">体检日期</div><div class="summary-item__value">2026年3月23日</div></div>
<div class="summary-item"><div class="summary-item__label">体检项目</div><div class="summary-item__value">胃肠镜体检</div></div>
<div class="summary-item"><div class="summary-item__label">状态</div><div class="summary-item__value" style="color:#fa8c16;">待体检</div></div>
</div>
<div class="summary-item"><div class="summary-item__label">体检项目</div><div class="summary-item__value">胃肠镜体检</div></div> </div>
</div>
<div class="tips-card">
<div class="tips-card__title">体检须知</div>
@@ -523,9 +451,7 @@
<div class="summary-item"><div class="summary-item__label">体检员工</div><div class="summary-item__value">刘韵琴</div></div>
<div class="summary-item"><div class="summary-item__label">体检地址</div><div class="summary-item__value">银川市西夏区怀远路148号</div></div>
<div class="summary-item"><div class="summary-item__label">体检日期</div><div class="summary-item__value">2026年3月23日</div></div>
<div class="summary-item"><div class="summary-item__label">体检项目</div><div class="summary-item__value">胃肠镜体检</div></div>
<div class="summary-item"><div class="summary-item__label">状态</div><div class="summary-item__value" style="color:#52c41a;">已参检</div></div>
</div>
<div class="summary-item"><div class="summary-item__label">体检项目</div><div class="summary-item__value">胃肠镜体检</div></div> </div>
</div>
</div>
@@ -551,9 +477,7 @@
<div class="summary-item"><div class="summary-item__label">体检员工</div><div class="summary-item__value">刘韵琴</div></div>
<div class="summary-item"><div class="summary-item__label">体检地址</div><div class="summary-item__value">银川市西夏区怀远路148号</div></div>
<div class="summary-item"><div class="summary-item__label">体检日期</div><div class="summary-item__value">2026年3月23日</div></div>
<div class="summary-item"><div class="summary-item__label">体检项目</div><div class="summary-item__value">冠脉CTA体检</div></div>
<div class="summary-item"><div class="summary-item__label">状态</div><div class="summary-item__value" style="color:#fa8c16;">待体检</div></div>
</div>
<div class="summary-item"><div class="summary-item__label">体检项目</div><div class="summary-item__value">冠脉CTA体检</div></div> </div>
</div>
<div class="tips-card">
<div class="tips-card__title">体检须知</div>
@@ -575,9 +499,7 @@
<div class="summary-item"><div class="summary-item__label">体检员工</div><div class="summary-item__value">刘韵琴</div></div>
<div class="summary-item"><div class="summary-item__label">体检地址</div><div class="summary-item__value">银川市西夏区怀远路148号</div></div>
<div class="summary-item"><div class="summary-item__label">体检日期</div><div class="summary-item__value">2026年3月23日</div></div>
<div class="summary-item"><div class="summary-item__label">体检项目</div><div class="summary-item__value">冠脉CTA体检</div></div>
<div class="summary-item"><div class="summary-item__label">状态</div><div class="summary-item__value" style="color:#52c41a;">已参检</div></div>
</div>
<div class="summary-item"><div class="summary-item__label">体检项目</div><div class="summary-item__value">冠脉CTA体检</div></div> </div>
</div>
</div>
@@ -599,28 +521,11 @@
<!-- 签到码放大遮罩 -->
<div class="qr-overlay" id="qrOverlay" onclick="this.classList.remove('qr-overlay--visible')">
<div onclick="event.stopPropagation()">
<svg class="qr-overlay__img--bar" id="ovlBarcode" viewBox="0 0 200 110" xmlns="http://www.w3.org/2000/svg" style="display:none;">
<svg class="qr-overlay__img--bar" id="ovlBarcode" viewBox="0 0 200 110" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="110" fill="#fff"/>
<rect x="10" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="16" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="20" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="25" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="32" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="36" y="10" width="2.5" height="72" fill="#1a1a1a"/><rect x="41" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="45" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="50" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="54" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="58" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="65" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="69" y="10" width="2.5" height="72" fill="#1a1a1a"/><rect x="74" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="78" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="83" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="87" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="94" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="98" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="102" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="107" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="111" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="118" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="122" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="126" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="131" y="10" width="2.5" height="72" fill="#1a1a1a"/><rect x="136" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="140" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="147" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="151" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="155" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="160" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="164" y="10" width="2.5" height="72" fill="#1a1a1a"/><rect x="169" y="10" width="4" height="72" fill="#1a1a1a"/><rect x="176" y="10" width="1.5" height="72" fill="#1a1a1a"/><rect x="180" y="10" width="2" height="72" fill="#1a1a1a"/><rect x="184" y="10" width="3" height="72" fill="#1a1a1a"/><rect x="189" y="10" width="2.5" height="72" fill="#1a1a1a"/>
<text x="100" y="97" text-anchor="middle" font-size="10" font-family="monospace" fill="#666">签到码编号</text>
</svg>
<svg class="qr-overlay__img--qr" id="ovlQr" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" style="display:none;">
<rect width="160" height="160" fill="#fff"/>
<rect x="12" y="12" width="40" height="40" rx="4" fill="#1a1a1a"/><rect x="18" y="18" width="28" height="28" rx="2" fill="#fff"/><rect x="24" y="24" width="16" height="16" rx="2" fill="#1a1a1a"/>
<rect x="108" y="12" width="40" height="40" rx="4" fill="#1a1a1a"/><rect x="114" y="18" width="28" height="28" rx="2" fill="#fff"/><rect x="120" y="24" width="16" height="16" rx="2" fill="#1a1a1a"/>
<rect x="12" y="108" width="40" height="40" rx="4" fill="#1a1a1a"/><rect x="18" y="114" width="28" height="28" rx="2" fill="#fff"/><rect x="24" y="120" width="16" height="16" rx="2" fill="#1a1a1a"/>
<rect x="60" y="12" width="8" height="8" fill="#1a1a1a"/><rect x="76" y="12" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="12" width="8" height="8" fill="#1a1a1a"/>
<rect x="60" y="24" width="8" height="8" fill="#1a1a1a"/><rect x="84" y="24" width="8" height="8" fill="#1a1a1a"/>
<rect x="68" y="36" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="36" width="8" height="8" fill="#1a1a1a"/>
<rect x="12" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="28" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="44" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="68" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="108" y="60" width="8" height="8" fill="#1a1a1a"/><rect x="132" y="60" width="8" height="8" fill="#1a1a1a"/>
<rect x="20" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="44" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="60" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="84" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="108" y="72" width="8" height="8" fill="#1a1a1a"/><rect x="124" y="72" width="8" height="8" fill="#1a1a1a"/>
<rect x="12" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="36" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="60" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="76" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="100" y="84" width="8" height="8" fill="#1a1a1a"/><rect x="140" y="84" width="8" height="8" fill="#1a1a1a"/>
<rect x="28" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="52" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="68" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="116" y="96" width="8" height="8" fill="#1a1a1a"/><rect x="140" y="96" width="8" height="8" fill="#1a1a1a"/>
<rect x="60" y="108" width="8" height="8" fill="#1a1a1a"/><rect x="76" y="108" width="8" height="8" fill="#1a1a1a"/><rect x="100" y="108" width="8" height="8" fill="#1a1a1a"/><rect x="124" y="108" width="8" height="8" fill="#1a1a1a"/>
<rect x="60" y="120" width="8" height="8" fill="#1a1a1a"/><rect x="84" y="120" width="8" height="8" fill="#1a1a1a"/><rect x="108" y="120" width="8" height="8" fill="#1a1a1a"/><rect x="140" y="120" width="8" height="8" fill="#1a1a1a"/>
<rect x="60" y="132" width="8" height="8" fill="#1a1a1a"/><rect x="76" y="132" width="8" height="8" fill="#1a1a1a"/><rect x="92" y="132" width="8" height="8" fill="#1a1a1a"/><rect x="116" y="132" width="8" height="8" fill="#1a1a1a"/><rect x="140" y="132" width="8" height="8" fill="#1a1a1a"/>
<rect x="68" y="140" width="8" height="8" fill="#1a1a1a"/><rect x="100" y="140" width="8" height="8" fill="#1a1a1a"/><rect x="132" y="140" width="8" height="8" fill="#1a1a1a"/>
</svg>
</div>
</div>
@@ -668,19 +573,8 @@
}
});
// ===== 签到码:条形码 ↔ 二维码切换 =====
// [交互] 点击右上角双选项卡切换条形码/二维码,默认条形码,当前模式选项高亮
function setCodeMode(cardId, mode) {
var isQr = mode === 'qr';
document.getElementById(cardId).classList.toggle('person-card--qr', isQr);
document.getElementById(cardId + 'TabBar').classList.toggle('code-switch__opt--active', !isQr);
document.getElementById(cardId + 'TabQr').classList.toggle('code-switch__opt--active', isQr);
}
// [交互] 点击签到码放大查看(遮罩跟随当前类型)
function openCodeOverlay(cardId) {
var isQr = document.getElementById(cardId).classList.contains('person-card--qr');
document.getElementById('ovlQr').style.display = isQr ? 'block' : 'none';
document.getElementById('ovlBarcode').style.display = isQr ? 'none' : 'block';
// [交互] 点击条码放大查看
function openCodeOverlay() {
document.getElementById('qrOverlay').classList.add('qr-overlay--visible');
}
@@ -421,9 +421,9 @@
<div class="divider"></div>
<!-- ========== 影像报告 ========== -->
<!-- ========== 报告图片 ========== -->
<div class="report-section">
<div class="report-section__title">影像报告</div>
<div class="report-section__title">报告图片</div>
<div class="image-section__label">胃镜报告</div>
<div class="image-view-link">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
+123 -8
View File
@@ -83,6 +83,42 @@
.trend-entry__go svg { width: 14px; height: 14px; }
/* 报告列表 */
.section-title { font-size: 16px; font-weight: 700; color: #1a1a1a; margin: 22px 2px 12px; }
/* 报告筛选:年份日历 + 体检类型下拉,默认全部 */
.filter-bar { display: flex; gap: 10px; margin: 0 2px 14px; position: relative; z-index: 5; }
.filter-picker { position: relative; }
.filter-picker__trigger {
display: inline-flex; align-items: center; gap: 6px;
height: 32px; padding: 0 12px; border-radius: 16px;
border: 1px solid #e8e8e8; background: #fff;
font-size: 13px; color: #1a1a1a; cursor: pointer; transition: all 0.2s;
}
.filter-picker__trigger--active { border-color: #13c2c2; color: #0f9b9b; font-weight: 600; }
.year-panel {
position: absolute; top: 38px; left: 0; width: 236px;
background: #fff; border-radius: 16px; padding: 12px;
border: 1px solid #f0f0f0; box-shadow: 0 12px 32px rgba(0,0,0,0.12); z-index: 10;
}
.year-panel.hidden, .type-menu.hidden { display: none; }
.year-panel__title { font-size: 13px; font-weight: 700; text-align: center; margin-bottom: 10px; }
.year-panel__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.year-cell {
height: 34px; display: flex; align-items: center; justify-content: center;
border-radius: 10px; background: #f7f9fb; font-size: 13px; color: #1a1a1a; cursor: pointer; transition: all 0.2s;
}
.year-cell.selected { background: #e6fffb; color: #0f9b9b; font-weight: 700; }
.type-menu {
position: absolute; top: 38px; left: 0; min-width: 132px;
background: #fff; border-radius: 14px; padding: 6px;
border: 1px solid #f0f0f0; box-shadow: 0 12px 32px rgba(0,0,0,0.12); z-index: 10;
}
.type-menu__item {
display: flex; align-items: center; justify-content: space-between;
padding: 9px 12px; border-radius: 10px; font-size: 13px; color: #1a1a1a; cursor: pointer;
}
.type-menu__item:hover { background: #f7f9fb; }
.type-menu__item.selected { background: #e6fffb; color: #0f9b9b; font-weight: 700; }
.type-menu__item .check { visibility: hidden; font-size: 12px; }
.type-menu__item.selected .check { visibility: visible; }
.report-list { display: flex; flex-direction: column; gap: 12px; }
.report-item {
background: #fff; border-radius: 16px; padding: 16px; display: flex; align-items: center; gap: 14px;
@@ -165,9 +201,42 @@
<!-- 报告列表 -->
<div class="section-title">我的历年报告</div>
<div class="report-list">
<!-- [交互] 报告筛选:年份点开日历面板选择,体检类型下拉选择,默认「全部」,两个条件叠加过滤 -->
<div class="filter-bar">
<div class="filter-picker">
<button class="filter-picker__trigger" type="button" id="yearTrigger" onclick="toggleYearPanel(event)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
<span id="yearTriggerText">全部年份</span>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="year-panel hidden" id="yearPanel">
<div class="year-panel__title">选择年份</div>
<div class="year-panel__grid">
<span class="year-cell selected" onclick="pickYear('all', this)">全部</span>
<span class="year-cell" onclick="pickYear('2025', this)">2025</span>
<span class="year-cell" onclick="pickYear('2024', this)">2024</span>
<span class="year-cell" onclick="pickYear('2023', this)">2023</span>
<span class="year-cell" onclick="pickYear('2022', this)">2022</span>
<span class="year-cell" onclick="pickYear('2021', this)">2021</span>
</div>
</div>
</div>
<div class="filter-picker">
<button class="filter-picker__trigger" type="button" id="typeTrigger" onclick="toggleTypeMenu(event)">
<span id="typeTriggerText">全部类型</span>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="type-menu hidden" id="typeMenu">
<div class="type-menu__item selected" onclick="pickType('all', this)">全部类型<span class="check"></span></div>
<div class="type-menu__item" onclick="pickType('regular', this)">常规体检<span class="check"></span></div>
<div class="type-menu__item" onclick="pickType('gastro', this)">胃肠镜体检<span class="check"></span></div>
<div class="type-menu__item" onclick="pickType('cta', this)">冠脉CTA体检<span class="check"></span></div>
</div>
</div>
</div>
<div class="report-list" id="reportList">
<!-- [交互] 点击跳转至: report-gastro.html(胃肠镜报告) -->
<div class="report-item" onclick="location.href='report-gastro.html'">
<div class="report-item" data-year="2025" data-type="gastro" onclick="location.href='report-gastro.html'">
<div class="report-item__icon" style="background:#e6fffb;color:#08979c;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</div>
@@ -181,7 +250,7 @@
<span class="report-item__arrow"></span>
</div>
<!-- [交互] 点击跳转至: report-cta.html(冠脉CTA报告) -->
<div class="report-item" onclick="location.href='report-cta.html'">
<div class="report-item" data-year="2025" data-type="cta" onclick="location.href='report-cta.html'">
<div class="report-item__icon" style="background:#fff1f0;color:#cf1322;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/><path d="M3.2 12h5.3l1.5-3 2.5 6 1.5-3h5.3"/></svg>
</div>
@@ -195,7 +264,7 @@
<span class="report-item__arrow"></span>
</div>
<!-- [交互] 点击跳转至: report-query.html(常规体检) -->
<div class="report-item" onclick="location.href='report-query.html'">
<div class="report-item" data-year="2025" data-type="regular" onclick="location.href='report-query.html'">
<div class="report-item__icon" style="background:#e6f7ff;color:#096dd9;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="14" y2="17"/></svg>
</div>
@@ -209,7 +278,7 @@
<span class="report-item__arrow"></span>
</div>
<!-- [交互] 点击跳转至: report-query.html(常规体检) -->
<div class="report-item" onclick="location.href='report-query.html'">
<div class="report-item" data-year="2024" data-type="regular" onclick="location.href='report-query.html'">
<div class="report-item__icon" style="background:#e6f7ff;color:#096dd9;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="14" y2="17"/></svg>
</div>
@@ -223,7 +292,7 @@
<span class="report-item__arrow"></span>
</div>
<!-- [交互] 点击跳转至: report-query.html(常规体检) -->
<div class="report-item" onclick="location.href='report-query.html'">
<div class="report-item" data-year="2023" data-type="regular" onclick="location.href='report-query.html'">
<div class="report-item__icon" style="background:#e6f7ff;color:#096dd9;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="14" y2="17"/></svg>
</div>
@@ -237,7 +306,7 @@
<span class="report-item__arrow"></span>
</div>
<!-- [交互] 点击跳转至: report-query.html(常规体检) -->
<div class="report-item" onclick="location.href='report-query.html'">
<div class="report-item" data-year="2022" data-type="regular" onclick="location.href='report-query.html'">
<div class="report-item__icon" style="background:#e6f7ff;color:#096dd9;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="14" y2="17"/></svg>
</div>
@@ -251,7 +320,7 @@
<span class="report-item__arrow"></span>
</div>
<!-- [交互] 点击跳转至: report-query.html(常规体检) -->
<div class="report-item" onclick="location.href='report-query.html'">
<div class="report-item" data-year="2021" data-type="regular" onclick="location.href='report-query.html'">
<div class="report-item__icon" style="background:#e6f7ff;color:#096dd9;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="14" y2="17"/></svg>
</div>
@@ -269,5 +338,51 @@
</div>
</div>
</div>
<script>
// 报告筛选:年份日历 + 体检类型下拉,两个条件叠加过滤,默认「全部」
var filterYear = 'all';
var filterType = 'all';
function toggleYearPanel(e) {
e.stopPropagation();
closeTypeMenu();
document.getElementById('yearPanel').classList.toggle('hidden');
}
function toggleTypeMenu(e) {
e.stopPropagation();
closeYearPanel();
document.getElementById('typeMenu').classList.toggle('hidden');
}
function closeYearPanel() { document.getElementById('yearPanel').classList.add('hidden'); }
function closeTypeMenu() { document.getElementById('typeMenu').classList.add('hidden'); }
function pickYear(year, el) {
filterYear = year;
el.parentNode.querySelectorAll('.year-cell').forEach(function (c) { c.classList.remove('selected'); });
el.classList.add('selected');
document.getElementById('yearTriggerText').textContent = year === 'all' ? '全部年份' : year + '年';
document.getElementById('yearTrigger').classList.toggle('filter-picker__trigger--active', year !== 'all');
closeYearPanel();
applyReportFilter();
}
function pickType(type, el) {
filterType = type;
el.parentNode.querySelectorAll('.type-menu__item').forEach(function (i) { i.classList.remove('selected'); });
el.classList.add('selected');
document.getElementById('typeTriggerText').textContent = el.textContent.replace('✓', '').trim();
document.getElementById('typeTrigger').classList.toggle('filter-picker__trigger--active', type !== 'all');
closeTypeMenu();
applyReportFilter();
}
function applyReportFilter() {
document.querySelectorAll('#reportList .report-item').forEach(function (item) {
var show = (filterYear === 'all' || item.dataset.year === filterYear) &&
(filterType === 'all' || item.dataset.type === filterType);
item.style.display = show ? 'flex' : 'none';
});
}
// 点击弹层外部收起面板
document.addEventListener('click', function (e) {
if (!e.target.closest('.filter-picker')) { closeYearPanel(); closeTypeMenu(); }
});
</script>
</body>
</html>
@@ -140,14 +140,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="info-row__label">基础代谢率 (kcal)</span>
<span class="info-row__value">1963.2</span>
</div>
<div class="info-row">
<span class="info-row__label">身份证号</span>
<span class="info-row__value">640382199006054618</span>
</div>
<div class="info-row">
<span class="info-row__label">手机号</span>
<span class="info-row__value">13581557014</span>
</div>
</div>
</div>
+9 -9
View File
@@ -17,7 +17,7 @@
│ ├── 吃动平衡
│ ├── 饮食记录 → 饮食录入 → 拍照识别
│ ├── 运动记录 → 运动录入
│ ├── 体重记录 → 详细报告 / 体脂仪报告 / 身高秤报告
│ ├── 体重记录 → 详细报告 / 体脂仪报告 / 体重秤报告
│ ├── 今日饮食建议
│ ├── 智能运动计划
│ ├── 排行榜
@@ -89,7 +89,7 @@ stateDiagram-v2
| 17 | 体重记录 | weight-detail-v2.html | 页面 |
| 18 | 详细报告 | report-detail.html | 页面 |
| 19 | 体脂仪报告 | body-fat-report.html | 页面 |
| 20 | 身高秤报告 | height-scale-report.html | 页面 |
| 20 | 体重秤报告 | height-scale-report.html | 页面 |
| 21 | 体重报告 | weight-report.html | 页面 |
| 22 | 数据明细 | weight-record-detail-v2.html | 页面 |
| 23 | 排行榜 | weight-ranking.html | 页面 |
@@ -693,9 +693,9 @@ stateDiagram-v2
- 无记录日期显示空状态("立即称重"按钮)
- BMI 分档:<18.5 偏瘦 / 18.5-24 正常 / 24-28 超重 / ≥28 肥胖
- 非自记来源(蓝牙称/体重秤/体脂仪)记录可点击查看详细报告,自记不可点击;蓝牙称跳 report-detail.html,体脂仪跳 body-fat-report.html,体重秤跳 height-scale-report.html
- 原"记录体重"底部按钮已移除,记录入口收敛至数据来源卡片的"自记"卡片;原"身高秤"来源在本页统一更名为"体重秤"(对应报告页 height-scale-report.html 仍为身高秤报告)
- 原"记录体重"底部按钮已移除,记录入口收敛至数据来源卡片的"自记"卡片
- 当日最新记录与上一次记录对比显示变化,无历史记录时显示"首次记录"
- 体重数据来源目前有体检数据、自记数据、后台管理员记录、身高秤、蓝牙秤、体脂仪这6种,其中体检数据和后台管理员需要更新最新体重但记录不计入员工打卡行动,另外三种测量设备的数据可能出现延迟上传的情况,系统至多允许补录前一天的体重数据(定时修正前一天的行动状态和排名数据),再早的数据补传后不计入体重管理行动体重打卡
- 体重数据来源目前有体检数据、自记数据、后台管理员记录、体重秤、蓝牙秤、体脂仪这6种,其中体检数据和后台管理员需要更新最新体重但记录不计入员工打卡行动,另外三种测量设备的数据可能出现延迟上传的情况,系统至多允许补录前一天的体重数据(定时修正前一天的行动状态和排名数据),再早的数据补传后不计入体重管理行动体重打卡
---
### 18. 详细报告(report-detail.html
@@ -718,7 +718,7 @@ stateDiagram-v2
| 蓝牙秤(device=bluetooth) | 基础指标、身体成分、脂肪分析、体态管理、体重管控、细胞液分析(8电极)、进阶成分(8电极)、躯干/四肢脂肪(8电极)、躯干/四肢肌肉(8电极) |
| 体脂仪(device=fat) | 基础指标、体脂分析、体成分、体重管理、节段脂肪、节段肌肉 |
注:体重记录页中"体重秤"(原身高秤)来源的记录已改跳转至独立页面 height-scale-report.html"体脂仪"来源的记录已改跳转至 body-fat-report.html,本页 device=fat / device=scale 模式保留用于兼容。
注:体重记录页中"体重秤"来源的记录已改跳转至独立页面 height-scale-report.html"体脂仪"来源的记录已改跳转至 body-fat-report.html,本页 device=fat / device=scale 模式保留用于兼容。
**交互说明:**
- 点击返回按钮返回来源页
@@ -750,16 +750,16 @@ stateDiagram-v2
---
### 20. 身高秤报告(height-scale-report.html
### 20. 体重秤报告(height-scale-report.html
![页面截图](screenshots/height-scale-report.jpg)
**功能说明:** 展示身高秤单次测量的身体成分报告,含身高、体重及身体成分、体态相关指标。
**功能说明:** 展示体重秤单次测量的身体成分报告,含身高、体重及身体成分、体态相关指标。
**页面状态:** 从体重记录页(weight-detail-v2.html)记录明细列表中体重秤(原身高秤)来源的记录跳转进入,页面只读。
**页面状态:** 从体重记录页(weight-detail-v2.html)记录明细列表中体重秤来源的记录跳转进入,页面只读。
**主要UI元素:**
- 报告头部(蓝青渐变卡片):测量时间、设备标签「身高秤」、当前体重(大字)、BMI 状态标签、较上次变化
- 报告头部(蓝青渐变卡片):测量时间、设备标签「体重秤」、当前体重(大字)、BMI 状态标签、较上次变化
- 基础指标:身高 / 体重 / BMI / 体脂率(含 BMI 指示条:消瘦/正常/超重/肥胖四段色轨 + 指针)
- 身体成分:体脂肪 / 水分率 / 水分量 / 肌肉率 / 肌肉量 / 骨量 / 去脂体重
- 体态分析:内脏脂肪 / 体型 / 理想体重 / 超重百分比 / 体表面积
@@ -350,7 +350,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<section class="section" id="sec-history">
<div class="section__header">
<h2 class="section__title">四、历史记录与报告详情</h2>
<span class="section__count">16</span>
<span class="section__count">14</span>
</div>
<div class="sub-title">记录明细(周视图 / 月历浮层 / 数据点浮签 / 设备来源)</div>
<div class="scroll-row">
@@ -408,16 +408,8 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
</div>
</div>
<div class="sub-title">详细报告(report-detail.html 三设备 + 体脂 / 身高报告)</div>
<div class="sub-title">详细报告(report-detail.html 蓝牙秤 + 体脂 / 身高报告)</div>
<div class="scroll-row">
<div class="card" onclick="openLightbox('screenshots/report-detail-fat.jpg','详细报告 · 体脂仪')">
<div class="card__img"><img src="screenshots/report-detail-fat.jpg" alt=""></div>
<div class="card__label">报告 · 体脂仪</div>
</div>
<div class="card" onclick="openLightbox('screenshots/report-detail-scale.jpg','详细报告 · 体重秤')">
<div class="card__img"><img src="screenshots/report-detail-scale.jpg" alt=""></div>
<div class="card__label">报告 · 体重秤</div>
</div>
<div class="card" onclick="openLightbox('screenshots/report-detail-bluetooth.jpg','详细报告 · 蓝牙秤')">
<div class="card__img"><img src="screenshots/report-detail-bluetooth.jpg" alt=""></div>
<div class="card__label">报告 · 蓝牙秤</div>
@@ -426,9 +418,9 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
<div class="card__img"><img src="screenshots/body-fat-report.jpg" alt=""></div>
<div class="card__label">体脂报告</div>
</div>
<div class="card" onclick="openLightbox('screenshots/height-scale-report.jpg','身高测量报告')">
<div class="card" onclick="openLightbox('screenshots/height-scale-report.jpg','体重秤报告')">
<div class="card__img"><img src="screenshots/height-scale-report.jpg" alt=""></div>
<div class="card__label">身高测量报告</div>
<div class="card__label">体重秤报告</div>
</div>
</div>
</section>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

@@ -87,7 +87,7 @@ function goBack() {
}
/* ========================================================================
* 身高秤报告数据
* 体重秤报告数据
* 字段名沿用统一后的命名:脂肪率→体脂率、脂肪量→体脂肪
* ======================================================================== */
var report = {
@@ -183,7 +183,7 @@ var changeText = (report.weightChange < 0 ? '' : '+') + fmtVal(report.weightChan
document.getElementById('reportHeader').innerHTML =
'<div class="report-header__top">' +
'<div class="report-header__time"><svg width="14" height="14" fill="none"><circle cx="7" cy="7" r="6" stroke="#fff" stroke-width="1.5"/><path d="M7 4v3.5l2.5 1.5" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg> ' + report.time + '</div>' +
'<span class="device-tag device-tag--stadiometer">身高</span>' +
'<span class="device-tag device-tag--stadiometer">体重</span>' +
'</div>' +
'<div class="report-header__main">' +
'<div class="report-header__weight"><span class="report-header__weight-num">' + fmtVal(report.weight) + '</span><span class="report-header__weight-unit">kg</span></div>' +
+36 -4
View File
@@ -33,7 +33,7 @@
body { background: var(--background); font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', 'Segoe UI', sans-serif; color: var(--text-primary); min-height: 100vh; }
/* ===== Header ===== */
.header { position: relative; background: linear-gradient(135deg, #0b3d91 0%, var(--primary) 40%, #4facfe 100%); color: #fff; padding: 40px 48px 20px; overflow: hidden; }
.header { position: sticky; top: 0; z-index: 300; background: linear-gradient(135deg, #0b3d91 0%, var(--primary) 40%, #4facfe 100%); color: #fff; padding: 40px 48px 20px; overflow: hidden; }
.header::before { content: ''; position: absolute; top: -60px; right: -40px; width: 260px; height: 260px; background: radial-gradient(circle, rgba(255,255,255,0.12) 0%, transparent 70%); border-radius: 50%; }
.header::after { content: ''; position: absolute; bottom: -80px; left: 20%; width: 360px; height: 360px; background: radial-gradient(circle, rgba(255,255,255,0.06) 0%, transparent 70%); border-radius: 50%; }
.header__inner { position: relative; z-index: 1; max-width: 1400px; margin: 0 auto; text-align: center; }
@@ -219,6 +219,7 @@ const SITE_MAP = [
{ file:'config-standard-category.html', label:'体检项目分类' },
{ file:'config-standard-basic.html', label:'体检基本项目' },
{ file:'config-standard-hospital.html', label:'医院体检项目' },
{ file:'agent-device.html', label:'前置机管理' },
{ file:'pre-hospital-scope.html', label:'医院承检范围' },
{ file:'pre-plan.html', label:'单位体检计划管理' },
{ file:'pre-quota-group.html', label:'群体额度配置' },
@@ -238,6 +239,7 @@ const SITE_MAP = [
{ file:'pre-special-cta.html', label:'冠脉CTA体检人员' },
{ file:'pre-special-hospital.html', label:'指定员工体检医院' },
{ file:'mid-appointment-track.html', label:'体检预约' },
{ file:'mid-appointment-track2.html', label:'体检预约跟踪' },
{ file:'mid-selected-items.html', label:'已选项目' },
{ file:'mid-participation.html', label:'已检员工' },
{ file:'mid-unit-appointment.html', label:'单位预约人数统计' },
@@ -252,6 +254,8 @@ const SITE_MAP = [
{ file:'mid-unit-expected.html', label:'签约单位应检人数' },
{ file:'post-unit-count.html', label:'单位体检人数统计' },
{ file:'post-unit-cost-stat.html', label:'单位体检费用统计' },
{ file:'post-unit-item-stat.html', label:'单位体检项目统计' },
{ file:'post-result-comprehensive-stat.html', label:'结果动态综合统计' },
{ file:'post-hospital-count.html', label:'医院体检人数统计' },
{ file:'post-doctor-count.html', label:'医生检查人数统计' },
{ file:'post-item-stat.html', label:'医院体检项目统计' },
@@ -271,7 +275,10 @@ const SITE_MAP = [
{ file:'post-disease-info.html', label:'病种信息配置' },
{ file:'post-conclusion-disease.html', label:'体检结论关联病种' },
{ file:'post-auto-select-disease.html', label:'员工病种信息' },
{ file:'agent-device.html', label:'前置机管理 · 常规体检前置服务' },
{ file:'post-imc-user.html', label:'用户体检信息' },
{ file:'post-imc-class.html', label:'用户影像文件分类' },
{ file:'post-imc-info.html', label:'用户影像文件详细' },
{ file:'post-imc-log.html', label:'文件处理日志' },
{ file:'exception-dirty.html', label:'系统脏数据' },
{ file:'exception-result.html', label:'结果异常' }
]},
@@ -701,7 +708,7 @@ const SITE_MAP = [
{ file:'weight-detail-v2.html', label:'体重明细' },
{ file:'report-detail.html', label:'测量报告详情' },
{ file:'body-fat-report.html', label:'体脂报告' },
{ file:'height-scale-report.html', label:'身高测量报告' },
{ file:'height-scale-report.html', label:'体重秤报告' },
{ file:'diet-advice-v2.html', label:'膳食建议' },
{ file:'exercise-advice-v2.html', label:'运动建议' },
{ file:'weight-record-detail-v2.html', label:'体重记录明细' },
@@ -833,7 +840,7 @@ const SITE_MAP = [
{ file:'phone-list.html', label:'手机号' }
]}
]},
{ id:'emergency-app', label:'健康应急 APP', icon:'emergency', color:'#ffc069',
{ id:'emergency-app', label:'健康服务 APP', icon:'emergency', color:'#ffc069',
roles:[
{ role:'咨询专家', id:'expert', modules:[
{ name:'工作台', dir:'emergency-app/expert/workbench', prd:'emergency-app/expert/doc/PRD.md', done:true, pages:[
@@ -952,6 +959,31 @@ const SITE_MAP = [
{file:'index.html',label:'个人中心'},
{file:'edit-profile.html',label:'修改个人信息'},{file:'change-pwd.html',label:'修改密码'}
]}
]},
{ role:'营养师', id:'nutritionist', modules:[
{name:'工作台',dir:'emergency-app/nutritionist/workbench',pages:[
{file:'index.html',label:'工作台首页'},
{file:'consult-chat.html',label:'营养咨询聊天'},
{file:'consult-chat-done.html',label:'营养咨询聊天(已结束)'},
{file:'employee-list.html',label:'员工营养管理'},
{file:'employee-detail.html',label:'员工营养详情'},
{file:'employee-health.html',label:'员工健康档案'},
{file:'employee-diet-history.html',label:'员工饮食记录'},
{file:'plan-detail.html',label:'营养方案详情'},
{file:'recipe-list.html',label:'我的食谱'},
{file:'recipe-create.html',label:'制定食谱'},
{file:'recipe-detail.html',label:'食谱详情'}
]},
{name:'知识库',dir:'emergency-app/nutritionist/knowledge',pages:[
{file:'index.html',label:'知识库首页'},{file:'detail.html',label:'知识详情'}
]},
{name:'我的',dir:'emergency-app/nutritionist/profile',pages:[
{file:'index.html',label:'个人中心'},
{file:'edit-profile.html',label:'编辑资料'},
{file:'schedule.html',label:'排班设置'},
{file:'stats.html',label:'营养咨询统计'},
{file:'change-pwd.html',label:'修改密码'}
]}
]}
],
shared:[
@@ -83,7 +83,6 @@
<div class="fld"><span class="fld__label">活动日期</span><input value="2026-09-04" readonly></div>
<div class="fld"><span class="fld__label"><span class="fld__req">*</span>参加人数</span><input value="40"><span class="fld__unit"></span></div>
<div class="fld"><span class="fld__label"><span class="fld__req">*</span>覆盖区域</span><input placeholder="如:管理四中心 / 全作业区" value="管理四中心"></div>
<div class="fld"><span class="fld__label">时长</span><input value="45"><span class="fld__unit">分钟</span></div>
<div class="fld fld--full"><span class="fld__label"><span class="fld__req">*</span>内容</span><textarea placeholder="请输入宣教/培训内容摘要">围绕秋冬季流感与肺炎支原体感染高发季,讲解通风消毒、疫苗接种指征与出现症状后的处置流程;现场演示正确洗手七步法,发放防护要点折页。</textarea></div>
</div>
</div>
-209
View File
@@ -1,209 +0,0 @@
/**
* 体重管理模块统一截图脚本
* 规则参考健康监测 screenshot-final.mjs
* - phone-shell 尺寸 390
* - 'fixed'弹窗/浮层类截图严格 390×844弹窗叠在当前页面之上
* - 'auto' 纯页面截图短页面最低 844长页面按真实内容展开
* 用法node screenshot-weight.mjs [输出名子串过滤...]
*/
import { chromium } from 'playwright';
import { fileURLToPath } from 'url';
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const docDir = path.join(__dirname, 'employee-app', 'weight-management', 'doc');
const root = path.join(__dirname, 'employee-app', 'weight-management');
const tasks = [
// ========== 目标承诺与签署 ==========
{ html: 'goal-management-v2.html', out: 'goal-management', mode: 'auto' },
{ html: 'goal-management-v2.html', out: 'goal-management-personal', mode: 'auto', eval: "switchType('personal')" },
{ html: 'goal-management-v2.html', out: 'goal-management-activity', mode: 'fixed', eval: 'openActivitySheet()' },
{ html: 'goal-management-v2.html', out: 'goal-management-confirm', mode: 'fixed', eval: "openActivitySheet();var i=document.querySelector('#actSheet .sheet-item');i.click();confirmActivity();closeActivitySheet();submitGoal()" },
{ html: 'goal-management-v2.html', out: 'goal-management-confirm-personal', mode: 'fixed', eval: "switchType('personal');submitGoal()" },
{ html: 'goal-detail-v2.html', out: 'goal-detail', mode: 'auto', query: '?type=activity&state=review' },
{ html: 'goal-detail-v2.html', out: 'goal-detail-approved', mode: 'auto', query: '?type=activity&state=approved' },
{ html: 'goal-detail-v2.html', out: 'goal-detail-rejected', mode: 'auto', query: '?type=activity&state=rejected' },
{ html: 'goal-detail-v2.html', out: 'goal-detail-signed', mode: 'auto', query: '?type=activity&state=signed' },
{ html: 'goal-detail-v2.html', out: 'goal-detail-ended', mode: 'auto', query: '?type=activity&state=ended' },
{ html: 'goal-detail-v2.html', out: 'goal-detail-personal', mode: 'auto', query: '?type=personal' },
{ html: 'goal-detail-v2.html', out: 'goal-detail-edit', mode: 'fixed', query: '?type=personal', eval: 'openEditGoalModal()' },
{ html: 'goal-detail-v2.html', out: 'goal-detail-menu', mode: 'fixed', query: '?type=activity&state=signed', eval: "document.getElementById('navMore').click()" },
{ html: 'promise-signing.html', out: 'promise-signing', mode: 'auto', landscape: true },
{ html: 'promise-signing.html', out: 'promise-signing-giveup', mode: 'fixed', landscape: true, eval: 'openGiveup()' },
{ html: 'my-promise.html', out: 'my-promise', mode: 'auto' },
{ html: 'goal-history-v2.html', out: 'goal-history', mode: 'auto' },
{ html: 'goal-history-v2.html', out: 'goal-history-personal', mode: 'auto', eval: "switchTab('personal')" },
{ html: 'activity-detail.html', out: 'activity-detail', mode: 'auto' },
{ html: 'basic-info.html', out: 'basic-info', mode: 'auto' },
{ html: 'basic-info.html', out: 'basic-info-height', mode: 'fixed', eval: 'openHeightSheet()' },
{ html: 'basic-info.html', out: 'basic-info-activity', mode: 'fixed', eval: 'openSheet()' },
// ========== 体重管理主页 ==========
{ html: 'weight-main-v2.html', out: 'weight-main', mode: 'auto', query: '?type=hasact-signed' },
{ html: 'weight-main-v2.html', out: 'weight-main-review', mode: 'auto', query: '?type=hasact-review' },
{ html: 'weight-main-v2.html', out: 'weight-main-approved', mode: 'auto', query: '?type=hasact-approved' },
{ html: 'weight-main-v2.html', out: 'weight-main-rejected', mode: 'auto', query: '?type=hasact-rejected' },
{ html: 'weight-main-v2.html', out: 'weight-main-personal', mode: 'auto', query: '?type=personal' },
{ html: 'weight-main-v2.html', out: 'weight-main-none', mode: 'auto', query: '?type=none' },
{ html: 'weight-main-v2.html', out: 'weight-main-record', mode: 'fixed', query: '?type=hasact-signed', eval: 'openRecordSheet()' },
{ html: 'weight-main-v2.html', out: 'weight-main-record-success', mode: 'fixed', query: '?type=hasact-signed', eval: "openRecordSheet();submitRecord()" },
// ========== 每日打卡(吃/动/体重) ==========
{ html: 'eat-checkin-v2.html', out: 'eat-checkin', mode: 'auto' },
{ html: 'eat-checkin-v2.html', out: 'eat-checkin-lunch', mode: 'auto', query: '?meal=lunch' },
{ html: 'eat-checkin-v2.html', out: 'eat-checkin-recorded', mode: 'auto', eval: "selectCategory('recorded')" },
{ html: 'eat-checkin-v2.html', out: 'eat-checkin-bag', mode: 'fixed', eval: 'openBagPanel()' },
{ html: 'eat-checkin-v2.html', out: 'eat-checkin-gram', mode: 'fixed', eval: "var d;Object.keys(foodData).some(function(k){return foodData[k].items.some(function(it){if(it.name){d=it;return true}})});openGramPanel(d.id)" },
{ html: 'eat-checkin-v2.html', out: 'eat-checkin-success', mode: 'fixed', eval: 'submitCheckin()' },
{ html: 'eat-checkin-v2.html', out: 'eat-checkin-meals', mode: 'fixed', eval: "document.querySelector('.filter-row__meal-btn').click()" },
{ html: 'exercise-checkin-v2.html', out: 'exercise-checkin', mode: 'auto' },
{ html: 'exercise-checkin-v2.html', out: 'exercise-checkin-recorded', mode: 'auto', eval: "selectCategory('recorded')" },
{ html: 'exercise-checkin-v2.html', out: 'exercise-checkin-bag', mode: 'fixed', eval: 'openBagPanel()' },
{ html: 'exercise-checkin-v2.html', out: 'exercise-checkin-duration', mode: 'fixed', eval: "var d;Object.keys(exerciseData).some(function(k){return exerciseData[k].items.some(function(it){if(it.name){d=it;return true}})});openDurPanel(d.id)" },
{ html: 'exercise-checkin-v2.html', out: 'exercise-checkin-success', mode: 'fixed', eval: 'submitCheckin()' },
{ html: 'scan-checkin.html', out: 'scan-checkin', mode: 'fixed' },
{ html: 'scan-checkin.html', out: 'scan-checkin-analyzing', mode: 'fixed', eval: "takePhoto();clearInterval(analyzeTimer);analyzeTimer=null;setTimeout(function(){document.getElementById('analyzingBar').style.width='45%';document.getElementById('analyzingSub').textContent='正在匹配食物数据库...'},300)", wait: 900 },
{ html: 'scan-checkin.html', out: 'scan-checkin-result', mode: 'fixed', eval: 'showResult()' },
// ========== 历史记录 ==========
{ html: 'weight-record-detail-v2.html', out: 'weight-record-detail', mode: 'auto' },
{ html: 'weight-record-detail-v2.html', out: 'weight-record-detail-activity', mode: 'auto', eval: "switchTab('activity')" },
{ html: 'eat-detail-v2.html', out: 'eat-detail', mode: 'auto' },
{ html: 'eat-detail-v2.html', out: 'eat-detail-month', mode: 'fixed', eval: 'toggleMonthPicker()' },
{ html: 'exercise-detail-v2.html', out: 'exercise-detail', mode: 'auto' },
{ html: 'exercise-detail-v2.html', out: 'exercise-detail-month', mode: 'fixed', eval: 'toggleMonthPicker()' },
{ html: 'exercise-detail-v2.html', out: 'exercise-detail-tip', mode: 'fixed', eval: "document.querySelector('.chart-dot').click()" },
{ html: 'eat-exercise-detail-v2.html', out: 'eat-exercise-detail', mode: 'auto' },
{ html: 'eat-exercise-detail-v2.html', out: 'eat-exercise-detail-month', mode: 'fixed', eval: 'toggleMonthPicker()' },
{ html: 'weight-detail-v2.html', out: 'weight-detail', mode: 'auto' },
{ html: 'weight-detail-v2.html', out: 'weight-detail-record', mode: 'fixed', eval: 'openRecordSheet()' },
{ html: 'weight-detail-v2.html', out: 'weight-detail-month', mode: 'fixed', eval: 'toggleMonthPicker()' },
{ html: 'weight-detail-v2.html', out: 'weight-detail-tip', mode: 'fixed', eval: "document.querySelector('.chart-dot').click()" },
{ html: 'report-detail.html', out: 'report-detail-fat', mode: 'auto', query: '?device=fat' },
{ html: 'report-detail.html', out: 'report-detail-scale', mode: 'auto', query: '?device=scale' },
{ html: 'report-detail.html', out: 'report-detail-bluetooth', mode: 'auto', query: '?device=bluetooth' },
{ html: 'body-fat-report.html', out: 'body-fat-report', mode: 'auto' },
{ html: 'height-scale-report.html', out: 'height-scale-report', mode: 'auto' },
// ========== 建议与报告 ==========
{ html: 'diet-advice-v2.html', out: 'diet-advice', mode: 'auto' },
{ html: 'diet-advice-v2.html', out: 'diet-advice-detail', mode: 'fixed', eval: "openFoodDetail('breakfast',0)" },
{ html: 'diet-advice-v2.html', out: 'diet-advice-dislike', mode: 'fixed', eval: 'showDislikeSheet()' },
{ html: 'diet-advice-v2.html', out: 'diet-advice-nutrition', mode: 'fixed', eval: 'showNutritionEmpty(true)' },
{ html: 'diet-advice-v2.html', out: 'diet-advice-nutrition-tab', mode: 'auto', eval: "switchTab('nutrition')" },
{ html: 'diet-advice-v2.html', out: 'diet-advice-month', mode: 'fixed', eval: 'toggleMonthPicker()' },
{ html: 'exercise-advice-v2.html', out: 'exercise-advice', mode: 'auto' },
{ html: 'exercise-advice-v2.html', out: 'exercise-advice-month', mode: 'fixed', eval: 'toggleMonthPicker()' },
{ html: 'weight-report.html', out: 'weight-report', mode: 'auto', query: '?type=hasact' },
{ html: 'weight-report.html', out: 'weight-report-personal', mode: 'auto', query: '?type=personal' },
{ html: 'weight-report.html', out: 'weight-report-none', mode: 'auto', query: '?type=none' },
{ html: 'weight-ranking.html', out: 'weight-ranking', mode: 'auto', query: '?type=hasact' },
{ html: 'weight-ranking.html', out: 'weight-ranking-personal', mode: 'auto', query: '?type=personal' },
{ html: 'weight-ranking.html', out: 'weight-ranking-none', mode: 'auto', query: '?type=none' },
// ========== 蓝牙设备 ==========
{ html: 'bluetooth-scale-v2.html', out: 'bluetooth-scale', mode: 'auto', query: '#paired' },
{ html: 'bluetooth-scale-v2.html', out: 'bluetooth-scale-empty', mode: 'auto' },
{ html: 'bluetooth-device-members-v2.html', out: 'bluetooth-device-members', mode: 'auto', query: '?device=s1' },
{ html: 'bluetooth-device-members-v2.html', out: 'bluetooth-device-members-empty', mode: 'auto', query: '?device=b2' },
{ html: 'bluetooth-device-members-v2.html', out: 'bluetooth-device-members-delete', mode: 'fixed', query: '?device=s1', eval: "document.querySelector('.action-btn--delete').click()" },
{ html: 'bluetooth-device-members-v2.html', out: 'bluetooth-device-members-sync', mode: 'fixed', query: '?device=s1', eval: 'syncMembers()', wait: 1600 },
{ html: 'bluetooth-search-v2.html', out: 'bluetooth-search', mode: 'auto' },
{ html: 'bluetooth-wifi-v2.html', out: 'bluetooth-wifi', mode: 'auto' },
];
(async () => {
const filters = process.argv.slice(2);
const runTasks = filters.length ? tasks.filter(t => filters.some(f => t.out.includes(f))) : tasks;
const browser = await chromium.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const context = await browser.newContext({ viewport: { width: 390, height: 4000 } });
for (const t of runTasks) {
const page = await context.newPage();
const filePath = path.join(root, t.html);
const url = `file:///${filePath.replace(/\\/g, '/')}${t.query || ''}`;
await page.goto(url, { waitUntil: 'networkidle' });
if (t.eval) {
await page.evaluate(t.eval).catch(e => console.log(`! eval failed [${t.out}]: ${e.message}`));
await page.waitForTimeout(t.wait || 300);
}
if (t.landscape) {
// 横屏页:phone-shell 固定 844×390,不展开、按元素原尺寸截取
await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
if (shell) {
shell.style.borderRadius = '0';
shell.style.boxShadow = 'none';
}
document.body.style.padding = '0';
document.body.style.margin = '0';
document.body.style.minHeight = 'auto';
document.body.style.background = '#fff';
document.documentElement.style.display = 'block';
});
await page.setViewportSize({ width: 844, height: 390 });
await page.waitForTimeout(200);
} else if (t.mode === 'fixed') {
await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
if (shell) {
shell.style.borderRadius = '0';
shell.style.boxShadow = 'none';
}
document.body.style.padding = '0';
document.body.style.minHeight = 'auto';
document.body.style.background = '#fff';
});
await page.setViewportSize({ width: 390, height: 844 });
await page.waitForTimeout(200);
} else {
await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
if (shell) {
shell.style.height = 'auto';
shell.style.minHeight = '844px';
shell.style.maxHeight = 'none';
shell.style.borderRadius = '0';
shell.style.boxShadow = 'none';
shell.style.overflow = 'visible';
shell.style.display = 'block';
}
document.querySelectorAll('.scroll-content, .page-content, .main-content, .list-wrap, .content').forEach(c => {
c.style.overflow = 'visible';
c.style.maxHeight = 'none';
c.style.height = 'auto';
c.style.flex = 'none';
});
document.body.style.padding = '0';
document.body.style.minHeight = 'auto';
document.body.style.background = '#fff';
});
await page.waitForTimeout(300);
const measured = await page.evaluate(() => {
const shell = document.querySelector('.phone-shell');
if (!shell) return document.body.scrollHeight;
return Math.ceil(shell.getBoundingClientRect().height);
});
const h = Math.max(measured, 844);
await page.setViewportSize({ width: 390, height: h });
await page.waitForTimeout(150);
}
const shell = await page.$('.phone-shell');
const target = shell || page;
await target.screenshot({
path: path.join(docDir, 'screenshots', `${t.out}.jpg`),
type: 'jpeg',
quality: 85,
});
console.log(`${t.out}.jpg [${t.mode}]`);
await page.close();
}
await browser.close();
console.log(`\n完成!共截取 ${runTasks.length}`);
})();
+13 -13
View File
@@ -359,20 +359,20 @@ var dormMeta=[
{name:'401室',building:'靖边气田倒班宿舍楼',beds:4},
{name:'402室',building:'靖边气田倒班宿舍楼',beds:2}
];
/* empPool:与全站演示数据共享的员工池(已脱敏) */
/* empPool:与全站演示数据共享的员工池 */
var empPool=[
{no:'CQ021578',name:'石维国',org:'采油一厂',depart:'樊学作业区',phone:'139****3456'},
{no:'CQ032144',name:'李桂芳',org:'采油一厂',depart:'樊学作业区',phone:'139****7788'},
{no:'CQ045130',name:'杨慧敏',org:'采油一厂',depart:'麻子山作业区',phone:'139****5012'},
{no:'CQ019203',name:'曲志明',org:'采油一厂',depart:'樊学作业区',phone:'139****7208'},
{no:'CQ033096',name:'赵万福',org:'采油二厂',depart:'桐园作业区',phone:'139****6630'},
{no:'CQ027710',name:'张秀兰',org:'采油二厂',depart:'姬圪台作业区',phone:'139****4415'},
{no:'CQ010564',name:'高建军',org:'第四采油厂',depart:'西峰作业区',phone:'139****4320'},
{no:'CQ041822',name:'闫婷',org:'第四采油厂',depart:'西峰作业区',phone:'139****4531'},
{no:'CQ036441',name:'刘玉梅',org:'采气一厂',depart:'靖边气田',phone:'139****3745'},
{no:'CQ029905',name:'呼建国',org:'采气一厂',depart:'靖边气田',phone:'139****3870'},
{no:'CQ044871',name:'白瑞',org:'采气一厂',depart:'靖边气田(轮训)',phone:'139****2317'},
{no:'CQ022317',name:'徐立成',org:'第四采油厂',depart:'西峰作业区(轮训)',phone:'139****4906'}
{no:'CQ021578',name:'石维国',org:'采油一厂',depart:'樊学作业区',phone:'13961153456'},
{no:'CQ032144',name:'李桂芳',org:'采油一厂',depart:'樊学作业区',phone:'13927127788'},
{no:'CQ045130',name:'杨慧敏',org:'采油一厂',depart:'麻子山作业区',phone:'13941215012'},
{no:'CQ019203',name:'曲志明',org:'采油一厂',depart:'樊学作业区',phone:'13989577208'},
{no:'CQ033096',name:'赵万福',org:'采油二厂',depart:'桐园作业区',phone:'13969296630'},
{no:'CQ027710',name:'张秀兰',org:'采油二厂',depart:'姬圪台作业区',phone:'13943704415'},
{no:'CQ010564',name:'高建军',org:'第四采油厂',depart:'西峰作业区',phone:'13941064320'},
{no:'CQ041822',name:'闫婷',org:'第四采油厂',depart:'西峰作业区',phone:'13900024531'},
{no:'CQ036441',name:'刘玉梅',org:'采气一厂',depart:'靖边气田',phone:'13986823745'},
{no:'CQ029905',name:'呼建国',org:'采气一厂',depart:'靖边气田',phone:'13920263870'},
{no:'CQ044871',name:'白瑞',org:'采气一厂',depart:'靖边气田(轮训)',phone:'13928272317'},
{no:'CQ022317',name:'徐立成',org:'第四采油厂',depart:'西峰作业区(轮训)',phone:'13937154906'}
];
/* 床位→员工分配(key=楼栋|床位编号) */
var assigns={
@@ -253,15 +253,15 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
var curTab='in';
/* 员工档案简版:供工单详情「员工信息」区块取数 */
var empProfiles={
'CQ021578':{name:'石维国',sex:'男',age:52,unit:'采油一厂',dept:'樊学作业区',phone:'182****3367',chronic:'高血压(2级)'},
'CQ032144':{name:'李桂芳',sex:'女',age:47,unit:'采油一厂',dept:'樊学作业区',phone:'137****2208',chronic:'无明确慢性病史'},
'CQ033096':{name:'赵万福',sex:'男',age:58,unit:'采油二厂',dept:'桐园作业区',phone:'158****6620',chronic:'高血压(3级)、2型糖尿病'},
'CQ036441':{name:'刘玉梅',sex:'女',age:44,unit:'采气一厂',dept:'靖边气田',phone:'188****9032',chronic:'无明确慢性病史'},
'CQ010564':{name:'高建军',sex:'男',age:55,unit:'第四采油厂',dept:'西峰作业区',phone:'135****4471',chronic:'颈椎病史、高脂血症'},
'CQ045130':{name:'杨慧敏',sex:'女',age:38,unit:'采油一厂',dept:'麻子山作业区',phone:'176****5583',chronic:'无明确慢性病史'},
'CQ027710':{name:'张秀兰',sex:'女',age:50,unit:'第四采油厂',dept:'西峰作业区',phone:'139****2760',chronic:'冠心病'},
'CQ022317':{name:'徐立成',sex:'男',age:49,unit:'采油二厂',dept:'桐园作业区',phone:'150****8819',chronic:'慢性支气管炎'},
'CQ029905':{name:'呼建国',sex:'男',age:53,unit:'采气一厂',dept:'靖边气田',phone:'187****3345',chronic:'支气管哮喘'}
'CQ021578':{name:'石维国',sex:'男',age:52,unit:'采油一厂',dept:'樊学作业区',phone:'18238083367',chronic:'高血压(2级)'},
'CQ032144':{name:'李桂芳',sex:'女',age:47,unit:'采油一厂',dept:'樊学作业区',phone:'13798352208',chronic:'无明确慢性病史'},
'CQ033096':{name:'赵万福',sex:'男',age:58,unit:'采油二厂',dept:'桐园作业区',phone:'15869506620',chronic:'高血压(3级)、2型糖尿病'},
'CQ036441':{name:'刘玉梅',sex:'女',age:44,unit:'采气一厂',dept:'靖边气田',phone:'18814269032',chronic:'无明确慢性病史'},
'CQ010564':{name:'高建军',sex:'男',age:55,unit:'第四采油厂',dept:'西峰作业区',phone:'13515064471',chronic:'颈椎病史、高脂血症'},
'CQ045130':{name:'杨慧敏',sex:'女',age:38,unit:'采油一厂',dept:'麻子山作业区',phone:'17646095583',chronic:'无明确慢性病史'},
'CQ027710':{name:'张秀兰',sex:'女',age:50,unit:'第四采油厂',dept:'西峰作业区',phone:'13968902760',chronic:'冠心病'},
'CQ022317':{name:'徐立成',sex:'男',age:49,unit:'采油二厂',dept:'桐园作业区',phone:'15089628819',chronic:'慢性支气管炎'},
'CQ029905':{name:'呼建国',sex:'男',age:53,unit:'采气一厂',dept:'靖边气田',phone:'18730413345',chronic:'支气管哮喘'}
};
/* 员工呼入:主呼设备=床头/面板(HC-*),被呼设备=值班室终端(HD-*);赵万福(182/110)与结果异常 YC-20260905-05 同源事件 */
var incoming=[
+12 -12
View File
@@ -321,18 +321,18 @@ var dorms=[
];
/* [交互] 床位下钻演示数据:员工池与「床位信息」页一致(key=楼栋|床位编号) */
var empLite={
'CQ021578':{name:'石维国',unit:'采油一厂',depart:'樊学作业区',phone:'139****3456'},
'CQ032144':{name:'李桂芳',unit:'采油一厂',depart:'樊学作业区',phone:'139****7788'},
'CQ019203':{name:'曲志明',unit:'采油一厂',depart:'樊学作业区',phone:'139****7208'},
'CQ045130':{name:'杨慧敏',unit:'采油一厂',depart:'麻子山作业区',phone:'139****5012'},
'CQ033096':{name:'赵万福',unit:'采油二厂',depart:'桐园作业区',phone:'139****6630'},
'CQ022317':{name:'徐立成',unit:'第四采油厂',depart:'西峰作业区(轮训)',phone:'139****4906'},
'CQ010564':{name:'高建军',unit:'第四采油厂',depart:'西峰作业区',phone:'139****4320'},
'CQ041822':{name:'闫婷',unit:'第四采油厂',depart:'西峰作业区',phone:'139****4531'},
'CQ044871':{name:'白瑞',unit:'采气一厂',depart:'靖边气田(轮训)',phone:'139****2317'},
'CQ027710':{name:'张秀兰',unit:'采油二厂',depart:'姬圪台作业区',phone:'139****4415'},
'CQ036441':{name:'刘玉梅',unit:'采气一厂',depart:'靖边气田',phone:'139****3745'},
'CQ029905':{name:'呼建国',unit:'采气一厂',depart:'靖边气田',phone:'139****3870'}
'CQ021578':{name:'石维国',unit:'采油一厂',depart:'樊学作业区',phone:'13961153456'},
'CQ032144':{name:'李桂芳',unit:'采油一厂',depart:'樊学作业区',phone:'13927127788'},
'CQ019203':{name:'曲志明',unit:'采油一厂',depart:'樊学作业区',phone:'13989577208'},
'CQ045130':{name:'杨慧敏',unit:'采油一厂',depart:'麻子山作业区',phone:'13941215012'},
'CQ033096':{name:'赵万福',unit:'采油二厂',depart:'桐园作业区',phone:'13969296630'},
'CQ022317':{name:'徐立成',unit:'第四采油厂',depart:'西峰作业区(轮训)',phone:'13937154906'},
'CQ010564':{name:'高建军',unit:'第四采油厂',depart:'西峰作业区',phone:'13941064320'},
'CQ041822':{name:'闫婷',unit:'第四采油厂',depart:'西峰作业区',phone:'13900024531'},
'CQ044871':{name:'白瑞',unit:'采气一厂',depart:'靖边气田(轮训)',phone:'13928272317'},
'CQ027710':{name:'张秀兰',unit:'采油二厂',depart:'姬圪台作业区',phone:'13943704415'},
'CQ036441':{name:'刘玉梅',unit:'采气一厂',depart:'靖边气田',phone:'13986823745'},
'CQ029905':{name:'呼建国',unit:'采气一厂',depart:'靖边气田',phone:'13920263870'}
};
var bedAssigns={
'樊学倒班宿舍1号楼|101-1':'CQ021578','樊学倒班宿舍1号楼|101-2':'CQ032144','樊学倒班宿舍1号楼|102-1':'CQ019203',
@@ -46,7 +46,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 概览卡 */
.stat-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:16px;}
.stat-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 20px;display:flex;align-items:center;gap:14px;}
.stat-card__icon{width:44px;height:44px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}
.stat-card__icon--blue{background:var(--primary-light);}
@@ -113,8 +113,8 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.drawer__close:hover{color:var(--text-primary);}
.drawer__body{flex:1;overflow-y:auto;padding:24px;}
.drawer__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.drawer__hint{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:14px;}
.drawer__toolbar{display:flex;align-items:center;gap:8px;margin-bottom:14px;}
.drawer__toolbar .grow{margin-left:auto;font-size:var(--font-sm);color:var(--text-placeholder);}
/* 内嵌小表格 */
.mini-table{width:100%;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;min-width:0;}
@@ -227,12 +227,12 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<div class="drawer__header"><span id="rosterTitle">已分配员工名册</span><button class="drawer__close" onclick="closeDrawer()">×</button></div>
<div class="drawer__body">
<!-- [交互] 名册按姓名/员工编号即时过滤 -->
<div class="drawer__hint" id="rosterHint"></div>
<div class="drawer__toolbar">
<div class="filter-item"><label>姓名</label>
<input class="filter-input" id="rsName" placeholder="请输入姓名" style="min-width:100px;" oninput="renderRoster()"></div>
<div class="filter-item"><label>员工编号</label>
<input class="filter-input" id="rsCode" placeholder="请输入员工编号" style="min-width:100px;" oninput="renderRoster()"></div>
<span class="grow" id="rosterHint"></span>
</div>
<table class="mini-table" style="border-collapse:separate;">
<thead><tr><th>单位名称</th><th>部门名称</th><th>姓名</th><th>性别</th><th>年龄</th><th>员工编号</th></tr></thead>
@@ -269,6 +269,9 @@ var statPoints=[
roster:[{unit:'采气一厂',dept:'苏里格南作业区',name:'徐立成',sex:'男',age:44,code:'CQ022317'}]}
];
/* 未分配员工按单位汇总(与「员工分配」页未分配 Tab 演示数据一致,共 9 人) */
var unallocByUnit={'采油一厂':3,'采油二厂':2,'第四采油厂':2,'采气一厂':2};
/* ===== 单位→医疗点级联(由统计数据生成,保证单一数据源) ===== */
function initUnitSel(){
var sel=document.getElementById('filterUnit');
@@ -291,14 +294,15 @@ function currentData(){
return statPoints.filter(function(p){return (!unit||p.unit===unit)&&(!point||p.name===point);});
}
function renderStats(list){
var staff=0,alloc=0;
list.forEach(function(p){staff+=p.staff;alloc+=p.roster.length;});
var avg=list.length?(alloc/list.length).toFixed(1):'0.0';
var alloc=0;
list.forEach(function(p){alloc+=p.roster.length;});
var unit=document.getElementById('filterUnit').value,un=0;
for(var k in unallocByUnit){if(!unit||k===unit)un+=unallocByUnit[k];}
var total=alloc+un;
document.getElementById('statCards').innerHTML=
'<div class="stat-card"><div class="stat-card__icon stat-card__icon--blue">🏥</div><div><div class="stat-card__label">医疗点数量</div><div class="stat-card__value">'+list.length+'</div><div class="stat-card__extra">当前筛选范围内</div></div></div>'
+'<div class="stat-card"><div class="stat-card__icon stat-card__icon--green">👩‍⚕️</div><div><div class="stat-card__label">医护人员总数</div><div class="stat-card__value">'+staff+'</div><div class="stat-card__extra">来自系统管理医护角色统计</div></div></div>'
+'<div class="stat-card"><div class="stat-card__icon stat-card__icon--orange">👥</div><div><div class="stat-card__label">分配员工</div><div class="stat-card__value">'+alloc+'</div><div class="stat-card__extra">在「员工分配」页维护</div></div></div>'
+'<div class="stat-card"><div class="stat-card__icon stat-card__icon--purple">📊</div><div><div class="stat-card__label">平均每点分配人数</div><div class="stat-card__value">'+avg+'</div><div class="stat-card__extra">已分配员工总数 ÷ 医疗点数</div></div></div>';
'<div class="stat-card"><div class="stat-card__icon stat-card__icon--blue">👥</div><div><div class="stat-card__label">员工总数量</div><div class="stat-card__value">'+total+'</div><div class="stat-card__extra">已分配 + 未分配</div></div></div>'
+'<div class="stat-card"><div class="stat-card__icon stat-card__icon--green"></div><div><div class="stat-card__label">已分配员工数量</div><div class="stat-card__value">'+alloc+'</div><div class="stat-card__extra">在「员工分配」页维护</div></div></div>'
+'<div class="stat-card"><div class="stat-card__icon stat-card__icon--orange">📋</div><div><div class="stat-card__label">分配员工数</div><div class="stat-card__value">'+un+'</div><div class="stat-card__extra">在「员工分配」页进行分配</div></div></div>';
}
function renderTable(){
var list=currentData();
+318 -64
View File
@@ -133,6 +133,35 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
select.form-control{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 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.drop-zone{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:16px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);cursor:pointer;line-height:1.8;}
.drop-zone:hover{border-color:var(--primary);color:var(--primary);background:#F8FBFF;}
/* 分配员工弹窗(左右双栏) */
.modal--xl{width:1080px;}
.alloc-emp{display:flex;gap:16px;}
.alloc-emp__left,.alloc-emp__right{flex:1;min-width:0;}
.alloc-emp__panel-title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:10px;display:flex;align-items:center;gap:6px;}
.alloc-emp__filter{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px;}
.alloc-emp__filter .filter-input{flex:1;min-width:90px;height:32px;}
.alloc-emp__filter .org-trigger{height:32px;min-width:150px;}
.alloc-emp__filter .org-panel{top:32px;}
.alloc-emp__list{border:1px solid var(--border);border-radius:var(--radius-sm);height:300px;overflow-y:auto;overflow-x:hidden;}
.ae-table{width:100%;min-width:0;border-collapse:separate;border-spacing:0;font-size:var(--font-sm);}
.ae-table th:last-child,.ae-table td:last-child{position:static;box-shadow:none;}
.ae-table th,.ae-table td{padding:8px 10px;text-align:left;vertical-align:middle;border-bottom:1px solid #F0F0F0;}
.ae-table th{background:#FAFAFA;font-weight:500;color:var(--text-secondary);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:1;}
.ae-table td{color:var(--text-primary);}
.ae-table th:first-child,.ae-table td:first-child{width:44px;text-align:center;padding-left:4px;padding-right:4px;}
.ae-table tbody tr:hover{background:#F8FBFF;}
.ae-table tbody tr:last-child td{border-bottom:none;}
.alloc-emp__item{display:flex;align-items:center;gap:8px;padding:7px 12px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #F0F0F0;cursor:pointer;}
.alloc-emp__item:hover{background:#F8FBFF;}
.alloc-emp__item:last-child{border-bottom:none;}
.alloc-emp__item input[type=checkbox]{cursor:pointer;flex-shrink:0;}
.alloc-emp__item .ae-meta{flex:1;min-width:0;color:var(--text-secondary);font-size:var(--font-xs);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.alloc-emp__remove{color:var(--danger);cursor:pointer;margin-left:auto;font-size:var(--font-xs);flex-shrink:0;}
.alloc-emp__empty{text-align:center;padding:40px 0;color:var(--text-placeholder);font-size:var(--font-sm);}
.alloc-emp__alloc{background:#FAFAFA;border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;margin-bottom:10px;display:flex;gap:12px;}
.alloc-emp__alloc .form-item{flex:1;margin-bottom:0;}
.ae-cnt{display:inline-block;min-width:18px;padding:0 5px;height:18px;line-height:18px;border-radius:9px;background:var(--primary-light);color:var(--primary);font-size:var(--font-xs);text-align:center;font-weight:600;}
</style>
</head>
<body>
@@ -171,14 +200,22 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 状态Tab:已分配/未分配 -->
<div class="tabs">
<div class="tab active" id="tabAlloc" onclick="switchTab(true)">已分配<span class="tab__count" id="cntAlloc">0</span></div>
<div class="tab" id="tabUnalloc" onclick="switchTab(false)">未分配<span class="tab__count" id="cntUnalloc">0</span></div>
<div class="tab active" id="tabAlloc" onclick="switchTab(true)">已分配</div>
<div class="tab" id="tabUnalloc" onclick="switchTab(false)">未分配</div>
</div>
<!-- [交互] 筛选栏:单位部门级联 + 姓名/员工编号 + 医疗点(仅已分配) -->
<!-- [交互] 筛选栏:姓名/员工编号/单位部门 + 医疗点/分配日期(仅已分配) -->
<div class="filter-bar">
<div class="filter-item">
<label>员工所属单位</label>
<label>姓名</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
<label>员工编号</label>
<input class="filter-input" id="filterCode" placeholder="请输入员工编号" style="min-width:100px;">
</div>
<div class="filter-item">
<label>单位部门</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
@@ -197,14 +234,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
</div>
<div class="filter-item">
<label>员工姓名</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
<label>员工编号</label>
<input class="filter-input" id="filterCode" placeholder="请输入员工编号" style="min-width:100px;">
</div>
<div class="filter-item" id="mfUnitWrap">
<label>医疗点所属单位</label>
<select class="filter-select" id="filterMedUnit" onchange="onMedUnit()" style="min-width:110px;">
@@ -218,6 +247,12 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<option value="">全部医疗点</option>
</select>
</div>
<div class="filter-item" id="mfDateWrap">
<label>分配日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
</div>
<button class="btn btn-primary" onclick="applyFilter()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="#fff" stroke-width="1.3"/><path d="M9.5 9.5L13 13" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>查询
</button>
@@ -227,13 +262,13 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 操作栏:按钮随Tab切换 -->
<div class="action-bar">
<span id="allocBtns">
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" onclick="doExport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
</span>
<span id="unallocBtns" style="display:none;">
<button class="btn btn-primary" onclick="batchAllocate()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7h10M7 2v10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg>分配医疗点</button>
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-primary" onclick="openAllocEmp()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="4" r="2.5" stroke="#fff" stroke-width="1.3"/><path d="M2.5 12c.5-2.5 2.3-4 4.5-4s4 1.5 4.5 4" stroke="#fff" stroke-width="1.3" stroke-linecap="round"/></svg>分配员工</button>
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量分配导入</button>
<button class="btn btn-default" onclick="openImportTasks()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" onclick="doExport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
</span>
</div>
@@ -271,6 +306,71 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 分配员工弹窗(左右双栏:选择员工 + 已选择员工) -->
<div class="modal-mask" id="allocEmpMask">
<div class="modal modal--xl">
<div class="modal__title">分配员工</div>
<div class="alloc-emp">
<div class="alloc-emp__left">
<div class="alloc-emp__panel-title">选择员工</div>
<div class="alloc-emp__filter">
<input class="filter-input" id="aeName" placeholder="姓名" oninput="renderAeList()">
<input class="filter-input" id="aeCode" placeholder="员工编号" oninput="renderAeList()">
<div class="org-wrap" id="aeOrgWrap">
<div class="org-trigger" id="aeOrgTrigger" onclick="toggleAeOrg()">全部单位/部门</div>
<div class="org-panel" id="aeOrgPanel">
<div class="org-col" id="aeUnitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" data-unit="" onclick="aeSelectUnit(this,'全部单位/部门','')">全部单位/部门</div>
<div class="org-item" data-unit="采油一厂" onclick="aeSelectUnit(this,'采油一厂','采油一厂')">采油一厂</div>
<div class="org-item" data-unit="采油二厂" onclick="aeSelectUnit(this,'采油二厂','采油二厂')">采油二厂</div>
<div class="org-item" data-unit="第四采油厂" onclick="aeSelectUnit(this,'第四采油厂','第四采油厂')">第四采油厂</div>
<div class="org-item" data-unit="采气一厂" onclick="aeSelectUnit(this,'采气一厂','采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="aeDeptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" data-dept="" onclick="aeSelectDept(this,'全部部门','')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="alloc-emp__list">
<table class="ae-table">
<thead><tr><th><input type="checkbox" id="aeCheckAll" onchange="aeToggleAll(this)"></th><th>姓名</th><th>员工编号</th><th>所属单位</th><th>所属部门</th></tr></thead>
<tbody id="aeTbody"></tbody>
</table>
</div>
</div>
<div class="alloc-emp__right">
<div class="alloc-emp__panel-title">已选择员工 <span class="ae-cnt" id="aeCnt">0</span></div>
<div class="alloc-emp__alloc">
<div class="form-item"><span class="form-label"><span class="req">*</span> 医疗点所属单位</span>
<select class="form-control" id="aeUnit" onchange="onAeUnit()"><option value="">请选择单位</option><option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 分配医疗点</span>
<select class="form-control" id="aePoint"><option value="">请先选择医疗点所属单位</option></select></div>
</div>
<div class="alloc-emp__list" id="aeSelected"></div>
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('allocEmpMask')">取消</button>
<button class="btn btn-primary" onclick="confirmAllocEmp()">确认分配</button>
</div>
</div>
</div>
<!-- [交互] 移除分配确认弹窗 -->
<div class="modal-mask" id="removeMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 移除分配</div>
<div class="modal__text" id="removeText">确认移除该员工的分配?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('removeMask')">取消</button>
<button class="btn btn-primary" onclick="confirmRemoveAllocate()">确认移除</button>
</div>
</div>
</div>
<!-- [交互] 批量导入弹窗 -->
<div class="modal-mask" id="importMask">
<div class="modal">
@@ -280,7 +380,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="form-item"><span class="form-label"><span class="req">*</span> 分配医疗点</span>
<select class="form-control" id="iPoint"><option value="">请先选择单位</option></select></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 上传文件(员工编号清单)</span>
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">点击选择或拖拽文件到此处上传<br>员工分配导入模板.xlsx</div></div>
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">点击选择或拖拽文件到此处上传<br>员工分配导入模板.xlsx</div>
<button class="btn btn-default" onclick="showToast('员工分配导入模板下载中…')" style="align-self:flex-start;margin-top:8px;"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>下载Excel模板</button></div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('importMask')">取消</button>
<button class="btn btn-primary" onclick="doImport()">开始导入</button>
@@ -306,7 +407,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
/* ===== 演示数据 ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
@@ -321,30 +422,30 @@ var deptMap={
};
/* 已分配:unit/dept=员工所属,mUnit/mPoint=分配去向 */
var alloc=[
{id:1,name:'石维国',sex:'男',age:46,code:'CQ021578',tel:'139****2051',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'樊学作业区医疗点',date:'2026-03-02'},
{id:2,name:'李桂芳',sex:'女',age:38,code:'CQ032144',tel:'138****7742',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'樊学作业区医疗点',date:'2026-03-02'},
{id:3,name:'白瑞',sex:'男',age:31,code:'CQ044871',tel:'155****3316',unit:'采油一厂',dept:'生产技术部',mUnit:'采油一厂',mPoint:'安塞采油队长住点医疗室',date:'2026-04-15'},
{id:4,name:'曲志明',sex:'男',age:42,code:'CQ019203',tel:'137****8890',unit:'采油二厂',dept:'姬圪台联合站',mUnit:'采油二厂',mPoint:'姬圪台作业区医疗点',date:'2026-01-08'},
{id:5,name:'张秀兰',sex:'女',age:45,code:'CQ027710',tel:'136****2248',unit:'采油二厂',dept:'姬圪台联合站',mUnit:'采油二厂',mPoint:'姬圪台作业区医疗点',date:'2026-01-08'},
{id:6,name:'赵万福',sex:'男',age:39,code:'CQ033096',tel:'150****6127',unit:'采油二厂',dept:'桐园作业区',mUnit:'采油二厂',mPoint:'桐园作业区医疗点',date:'2026-02-20'},
{id:7,name:'高建军',sex:'男',age:50,code:'CQ010564',tel:'139****0086',unit:'第四采油厂',dept:'基地管理处',mUnit:'第四采油厂',mPoint:'西峰基地医疗点',date:'2025-12-01'},
{id:8,name:'闫婷',sex:'女',age:34,code:'CQ041822',tel:'186****5527',unit:'第四采油厂',dept:'健康管理部',mUnit:'第四采油厂',mPoint:'西峰基地医疗点',date:'2026-01-12'},
{id:9,name:'刘玉梅',sex:'女',age:40,code:'CQ036441',tel:'135****7719',unit:'采气一厂',dept:'靖边净化厂',mUnit:'采气一厂',mPoint:'靖边气田医疗点',date:'2026-03-25'},
{id:10,name:'呼建国',sex:'男',age:36,code:'CQ029905',tel:'147****9003',unit:'采气一厂',dept:'靖边净化厂',mUnit:'采气一厂',mPoint:'靖边气田医疗点',date:'2026-03-25'},
{id:11,name:'徐立成',sex:'男',age:44,code:'CQ022317',tel:'133****4860',unit:'采气一厂',dept:'苏里格南作业区',mUnit:'采气一厂',mPoint:'乌审旗采气区医疗点',date:'2026-05-10'},
{id:12,name:'杨慧敏',sex:'女',age:33,code:'CQ045130',tel:'188****7214',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'姬塬作业区医疗点',date:'2026-06-18'}
{id:1,name:'石维国',sex:'男',age:46,code:'CQ021578',tel:'13928562051',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'樊学作业区医疗点',date:'2026-03-02'},
{id:2,name:'李桂芳',sex:'女',age:38,code:'CQ032144',tel:'13836547742',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'樊学作业区医疗点',date:'2026-03-02'},
{id:3,name:'白瑞',sex:'男',age:31,code:'CQ044871',tel:'15529403316',unit:'采油一厂',dept:'生产技术部',mUnit:'采油一厂',mPoint:'安塞采油队长住点医疗室',date:'2026-04-15'},
{id:4,name:'曲志明',sex:'男',age:42,code:'CQ019203',tel:'13720568890',unit:'采油二厂',dept:'姬圪台联合站',mUnit:'采油二厂',mPoint:'姬圪台作业区医疗点',date:'2026-01-08'},
{id:5,name:'张秀兰',sex:'女',age:45,code:'CQ027710',tel:'13649162248',unit:'采油二厂',dept:'姬圪台联合站',mUnit:'采油二厂',mPoint:'姬圪台作业区医疗点',date:'2026-01-08'},
{id:6,name:'赵万福',sex:'男',age:39,code:'CQ033096',tel:'15029836127',unit:'采油二厂',dept:'桐园作业区',mUnit:'采油二厂',mPoint:'桐园作业区医疗点',date:'2026-02-20'},
{id:7,name:'高建军',sex:'男',age:50,code:'CQ010564',tel:'13956200086',unit:'第四采油厂',dept:'基地管理处',mUnit:'第四采油厂',mPoint:'西峰基地医疗点',date:'2025-12-01'},
{id:8,name:'闫婷',sex:'女',age:34,code:'CQ041822',tel:'18629135527',unit:'第四采油厂',dept:'健康管理部',mUnit:'第四采油厂',mPoint:'西峰基地医疗点',date:'2026-01-12'},
{id:9,name:'刘玉梅',sex:'女',age:40,code:'CQ036441',tel:'13572867719',unit:'采气一厂',dept:'靖边净化厂',mUnit:'采气一厂',mPoint:'靖边气田医疗点',date:'2026-03-25'},
{id:10,name:'呼建国',sex:'男',age:36,code:'CQ029905',tel:'14729539003',unit:'采气一厂',dept:'靖边净化厂',mUnit:'采气一厂',mPoint:'靖边气田医疗点',date:'2026-03-25'},
{id:11,name:'徐立成',sex:'男',age:44,code:'CQ022317',tel:'13319284860',unit:'采气一厂',dept:'苏里格南作业区',mUnit:'采气一厂',mPoint:'乌审旗采气区医疗点',date:'2026-05-10'},
{id:12,name:'杨慧敏',sex:'女',age:33,code:'CQ045130',tel:'18829587214',unit:'采油一厂',dept:'樊学作业区',mUnit:'采油一厂',mPoint:'姬塬作业区医疗点',date:'2026-06-18'}
];
/* 未分配:可被分配的驻点后备医护与协管人员 */
var unalloc=[
{id:101,name:'周瑞琪',sex:'女',age:29,code:'CQ049218',tel:'151****3072',unit:'采油一厂',dept:'生产技术部'},
{id:102,name:'田震',sex:'男',age:35,code:'CQ037744',tel:'139****5218',unit:'采油一厂',dept:'樊学作业区'},
{id:103,name:'韩梅',sex:'女',age:41,code:'CQ026109',tel:'137****0833',unit:'采油二厂',dept:'安全环保科'},
{id:104,name:'冯保山',sex:'男',age:48,code:'CQ015523',tel:'135****6174',unit:'采油二厂',dept:'桐园作业区'},
{id:105,name:'曹雪芹',sex:'女',age:30,code:'CQ047831',tel:'186****2940',unit:'第四采油厂',dept:'健康管理部'},
{id:106,name:'罗建平',sex:'男',age:37,code:'CQ033485',tel:'150****8357',unit:'第四采油厂',dept:'基地管理处'},
{id:107,name:'梁小燕',sex:'女',age:32,code:'CQ042296',tel:'188****1029',unit:'采气一厂',dept:'生产技术部'},
{id:108,name:'邓成',sex:'男',age:43,code:'CQ024778',tel:'133****9481',unit:'采气一厂',dept:'苏里格南作业区'},
{id:109,name:'苏文斌',sex:'男',age:27,code:'CQ051027',tel:'155****6703',unit:'采油一厂',dept:'工会办公室'}
{id:101,name:'周瑞琪',sex:'女',age:29,code:'CQ049218',tel:'15148943072',unit:'采油一厂',dept:'生产技术部'},
{id:102,name:'田震',sex:'男',age:35,code:'CQ037744',tel:'13936485218',unit:'采油一厂',dept:'樊学作业区'},
{id:103,name:'韩梅',sex:'女',age:41,code:'CQ026109',tel:'13761800833',unit:'采油二厂',dept:'安全环保科'},
{id:104,name:'冯保山',sex:'男',age:48,code:'CQ015523',tel:'13581136174',unit:'采油二厂',dept:'桐园作业区'},
{id:105,name:'曹雪芹',sex:'女',age:30,code:'CQ047831',tel:'18642802940',unit:'第四采油厂',dept:'健康管理部'},
{id:106,name:'罗建平',sex:'男',age:37,code:'CQ033485',tel:'15077088357',unit:'第四采油厂',dept:'基地管理处'},
{id:107,name:'梁小燕',sex:'女',age:32,code:'CQ042296',tel:'18894041029',unit:'采气一厂',dept:'生产技术部'},
{id:108,name:'邓成',sex:'男',age:43,code:'CQ024778',tel:'13392699481',unit:'采气一厂',dept:'苏里格南作业区'},
{id:109,name:'苏文斌',sex:'男',age:27,code:'CQ051027',tel:'15598486703',unit:'采油一厂',dept:'工会办公室'}
];
var nextId=200,curAllocTab=true,allocTargets=[],allocRealloc=false;
@@ -355,6 +456,7 @@ function switchTab(toAlloc){
document.getElementById('tabUnalloc').classList.toggle('active',!toAlloc);
document.getElementById('mfUnitWrap').style.display=toAlloc?'':'none';
document.getElementById('mfPointWrap').style.display=toAlloc?'':'none';
document.getElementById('mfDateWrap').style.display=toAlloc?'':'none';
document.getElementById('allocBtns').style.display=toAlloc?'':'none';
document.getElementById('unallocBtns').style.display=toAlloc?'none':'';
currentPage=1;renderTable();
@@ -400,17 +502,23 @@ function currentData(){
var code=document.getElementById('filterCode').value.trim();
var mUnit=document.getElementById('filterMedUnit').value;
var mPoint=document.getElementById('filterMedPoint').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
var list=(curAllocTab?alloc:unalloc).filter(function(p){
return (!name||p.name.indexOf(name)!==-1)
&&(!code||p.code.toLowerCase().indexOf(code.toLowerCase())!==-1)
&&(!selUnit||p.unit===selUnit)&&(!selDept||p.dept===selDept)
&&(!curAllocTab||((!mUnit||p.mUnit===mUnit)&&(!mPoint||p.mPoint===mPoint)));
&&(!curAllocTab||((!mUnit||p.mUnit===mUnit)&&(!mPoint||p.mPoint===mPoint)&&(!from||p.date>=from)&&(!to||p.date<=to)));
});
return list;
}
function renderHead(){
var h='<tr><th style="width:40px"><input type="checkbox" onchange="toggleAll(this)"></th><th>序号</th><th>员工所属单位</th><th>员工所属部门</th><th>员工姓名</th><th>性别</th><th>年龄</th><th>员工编号</th><th>联系电话</th>';
h+=curAllocTab?'<th>医疗点所属单位</th><th>分配医疗点</th><th>分配日期</th><th>操作</th></tr>':'<th>操作</th></tr>';
var h;
if(curAllocTab){
h='<tr><th>序号</th><th>姓名</th><th>员工编号</th><th>性别</th><th>年龄</th><th>联系电话</th><th>所属单位</th><th>所属部门</th><th>医疗点所属单位</th><th>分配医疗点</th><th>分配日期</th><th>操作</th></tr>';
}else{
h='<tr><th>序号</th><th>姓名</th><th>员工编号</th><th>性别</th><th>年龄</th><th>联系电话</th><th>所属单位</th><th>所属部门</th><th>操作</th></tr>';
}
document.getElementById('tableHead').innerHTML=h;
}
function renderTable(){
@@ -422,26 +530,26 @@ function renderTable(){
document.getElementById('pageNum').textContent=currentPage+'/'+totalPages;
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
document.getElementById('cntAlloc').textContent=alloc.length;
document.getElementById('cntUnalloc').textContent=unalloc.length;
var colspan=curAllocTab?13:10;
var colspan=curAllocTab?12:9;
if(!pageData.length){document.getElementById('tableBody').innerHTML='<tr><td colspan="'+colspan+'" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(p,i){
html+='<tr>'
+'<td><input type="checkbox" class="row-check" value="'+p.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td>'+p.unit+'</td><td>'+p.dept+'</td>'
+'<td style="font-weight:600;">'+p.name+'</td>'
+'<td><span class="badge '+(p.sex==='男'?'badge--muted':'badge--muted')+'">'+p.sex+'</span></td>'
+'<td>'+p.age+'</td><td>'+p.code+'</td><td>'+p.tel+'</td>';
if(curAllocTab){
html+='<td>'+p.mUnit+'</td><td>'+p.mPoint+'</td><td>'+p.date+'</td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openAllocate(['+p.id+'],true)">重新分配</button></td>';
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+p.name+'</td>'
+'<td>'+p.code+'</td><td>'+p.sex+'</td><td>'+p.age+'</td><td>'+p.tel+'</td>'
+'<td>'+p.unit+'</td><td>'+p.dept+'</td>'
+'<td>'+p.mUnit+'</td><td>'+p.mPoint+'</td><td>'+p.date+'</td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openAllocate(['+p.id+'],true)">重新分配</button><button class="btn btn-link btn-link--danger" onclick="askRemoveAllocate('+p.id+')">移除分配</button></td></tr>';
}else{
html+='<td class="td-actions"><button class="btn btn-link" onclick="openAllocate(['+p.id+'])">分配医疗点</button></td>';
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td style="font-weight:600;">'+p.name+'</td>'
+'<td>'+p.code+'</td><td>'+p.sex+'</td><td>'+p.age+'</td><td>'+p.tel+'</td>'
+'<td>'+p.unit+'</td><td>'+p.dept+'</td>'
+'<td class="td-actions"><button class="btn btn-link" onclick="openAllocate(['+p.id+'])">分配医疗点</button></td></tr>';
}
html+='</tr>';
});
document.getElementById('tableBody').innerHTML=html;
}
@@ -455,6 +563,8 @@ function resetFilter(){
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'全部部门\',\'\')">全部部门</div>';
document.getElementById('filterMedUnit').value='';
onMedUnit();
document.getElementById('filterFrom').value='';
document.getElementById('filterTo').value='';
applyFilter();
}
function turnPage(d){
@@ -463,15 +573,7 @@ function turnPage(d){
currentPage=Math.min(Math.max(1,currentPage+d),totalPages);
renderTable();
}
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
/* ===== 分配 ===== */
function checkedIds(){return [].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return +c.value;});}
function batchAllocate(){
var ids=checkedIds();
if(!ids.length){showToast('请先勾选未分配员工');return;}
openAllocate(ids);
}
function openAllocate(ids,realloc){
allocTargets=ids;allocRealloc=!!realloc;
var src=realloc?alloc:unalloc;
@@ -509,6 +611,155 @@ function confirmAllocate(){
closeModal('allocMask');renderTable();
showToast('已为 '+allocTargets.length+' 人'+(allocRealloc?'重新':'')+'分配「'+point+'」');
}
/* [交互] 移除分配(二次确认):已分配员工退回未分配 */
var pendingRemove=null;
function askRemoveAllocate(id){
var p=alloc.find(function(x){return x.id===id;});
if(!p)return;
pendingRemove=id;
document.getElementById('removeText').textContent='确认移除「'+p.name+'」的分配?移除后该员工将回到未分配列表。';
document.getElementById('removeMask').classList.add('open');
}
function confirmRemoveAllocate(){
if(pendingRemove===null)return;
var id=pendingRemove;
pendingRemove=null;
closeModal('removeMask');
removeAllocate(id);
}
function removeAllocate(id){
var idx=alloc.findIndex(function(x){return x.id===id;});
if(idx<0)return;
var p=alloc.splice(idx,1)[0];
delete p.mUnit;delete p.mPoint;delete p.date;
unalloc.push(p);
renderTable();
showToast('已移除「'+p.name+'」的分配');
}
/* ===== 分配员工弹窗(左右双栏) ===== */
var aeSelUnit='',aeSelDept='',aeSelectedIds=[];
function openAllocEmp(){
aeSelectedIds=[];aeSelUnit='';aeSelDept='';
document.getElementById('aeName').value='';
document.getElementById('aeCode').value='';
document.getElementById('aeOrgTrigger').textContent='全部单位/部门';
document.getElementById('aeOrgPanel').classList.remove('open');
document.getElementById('aeOrgTrigger').classList.remove('open');
document.querySelectorAll('#aeUnitCol .org-item').forEach(function(i,x){i.classList.toggle('active',x===0);});
document.getElementById('aeDeptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="aeSelectDept(this,\'全部部门\',\'\')">全部部门</div>';
document.getElementById('aeUnit').value='';
document.getElementById('aePoint').innerHTML='<option value="">请先选择医疗点所属单位</option>';
document.getElementById('allocEmpMask').classList.add('open');
renderAeList();renderAeSelected();
}
function toggleAeOrg(){
document.getElementById('aeOrgPanel').classList.toggle('open');
document.getElementById('aeOrgTrigger').classList.toggle('open');
}
function aeSelectUnit(elem,unitName,unitCode){
document.querySelectorAll('#aeUnitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
aeSelUnit=unitCode;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="aeSelectDept(this,\'全部部门\',\'\')">全部部门</div>';
(deptMap[unitCode]||[]).forEach(function(d){
html+='<div class="org-item" data-dept="'+d+'" onclick="aeSelectDept(this,\''+d+'\',\''+d+'\')">'+d+'</div>';
});
document.getElementById('aeDeptCol').innerHTML=html;
aeSelDept='';renderAeList();
}
function aeSelectDept(elem,deptName,deptCode){
document.querySelectorAll('#aeDeptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
aeSelDept=deptCode;
var label=(aeSelUnit||'全部单位')+(aeSelDept?' / '+aeSelDept:' / 全部部门');
document.getElementById('aeOrgTrigger').textContent=label;
renderAeList();
}
function aeFiltered(){
var name=document.getElementById('aeName').value.trim();
var code=document.getElementById('aeCode').value.trim();
return unalloc.filter(function(p){
return (!name||p.name.indexOf(name)!==-1)
&&(!code||p.code.toLowerCase().indexOf(code.toLowerCase())!==-1)
&&(!aeSelUnit||p.unit===aeSelUnit)&&(!aeSelDept||p.dept===aeSelDept);
});
}
function renderAeList(){
var list=aeFiltered();
var checkAll=document.getElementById('aeCheckAll');
var html='';
if(!list.length){
checkAll.checked=false;
html='<tr><td colspan="5" style="text-align:center;padding:48px 0;color:var(--text-placeholder);">暂无匹配员工</td></tr>';
}else{
var allChecked=list.every(function(p){return aeSelectedIds.indexOf(p.id)>-1;});
checkAll.checked=allChecked;
list.forEach(function(p){
html+='<tr>'
+'<td><input type="checkbox" data-id="'+p.id+'" '+(aeSelectedIds.indexOf(p.id)>-1?'checked':'')+' onchange="aeToggle(this)"></td>'
+'<td style="font-weight:600;">'+p.name+'</td>'
+'<td>'+p.code+'</td>'
+'<td>'+p.unit+'</td><td>'+p.dept+'</td>'
+'</tr>';
});
}
document.getElementById('aeTbody').innerHTML=html;
}
function aeToggleAll(el){
aeFiltered().forEach(function(p){
var idx=aeSelectedIds.indexOf(p.id);
if(el.checked&&idx<0)aeSelectedIds.push(p.id);
else if(!el.checked&&idx>-1)aeSelectedIds.splice(idx,1);
});
renderAeList();renderAeSelected();
}
function aeToggle(el){
var id=+el.getAttribute('data-id');
var idx=aeSelectedIds.indexOf(id);
if(el.checked&&idx<0)aeSelectedIds.push(id);
else if(!el.checked&&idx>-1)aeSelectedIds.splice(idx,1);
renderAeSelected();renderAeList();
}
function aeRemove(id){
var idx=aeSelectedIds.indexOf(id);
if(idx>-1)aeSelectedIds.splice(idx,1);
renderAeSelected();renderAeList();
}
function renderAeSelected(){
var sel=aeSelectedIds.map(function(id){return unalloc.find(function(x){return x.id===id;});}).filter(Boolean);
document.getElementById('aeCnt').textContent=sel.length;
var html='';
if(!sel.length){html='<div class="alloc-emp__empty">尚未选择员工</div>';}
else{
sel.forEach(function(p){
html+='<div class="alloc-emp__item"><span style="font-weight:600;">'+p.name+'</span><span class="ae-meta">'+p.code+' · '+p.unit+' · '+p.dept+'</span><span class="alloc-emp__remove" onclick="aeRemove('+p.id+')">移除</span></div>';
});
}
document.getElementById('aeSelected').innerHTML=html;
}
function onAeUnit(){
var unit=document.getElementById('aeUnit').value;
var sel=document.getElementById('aePoint');
sel.innerHTML='<option value="">请选择医疗点</option>';
if(unit){(pointMap[unit]||[]).forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});}
}
function confirmAllocEmp(){
if(!aeSelectedIds.length){showToast('请先选择要分配的员工');return;}
var unit=document.getElementById('aeUnit').value;
var point=document.getElementById('aePoint').value;
if(!unit){showToast('请选择医疗点所属单位');return;}
if(!point){showToast('请选择分配医疗点');return;}
var n=aeSelectedIds.length;
aeSelectedIds.forEach(function(id){
var idx=unalloc.findIndex(function(x){return x.id===id;});
if(idx<0)return;
var p=unalloc.splice(idx,1)[0];
p.mUnit=unit;p.mPoint=point;p.date='2026-09-07';
alloc.push(p);
});
closeModal('allocEmpMask');renderTable();
showToast('已为 '+n+' 名员工分配「'+point+'」');
}
/* ===== 批量导入 ===== */
function openImport(){
document.getElementById('iUnit').value='';
@@ -530,6 +781,9 @@ function doImport(){
/* [交互] 查看导入任务(异步任务演示) */
function openImportTasks(){document.getElementById('taskMask').classList.add('open');}
/* [交互] 导出列表数据 */
function doExport(){showToast(curAllocTab?'已分配员工列表已导出(演示)':'未分配员工列表已导出(演示)');}
/* ===== 通用 ===== */
function closeModal(id){document.getElementById(id).classList.remove('open');}
function showToast(msg){
@@ -116,6 +116,15 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.form-control:focus{border-color:var(--primary);}
textarea.form-control{height:64px;padding:8px 10px;resize:vertical;line-height:1.5;}
select.form-control{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 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
/* 年份选择控件 */
.year-picker{position:relative;}
.year-picker .form-control{padding-right:30px;cursor:pointer;}
.year-picker>svg{position:absolute;right:9px;top:50%;transform:translateY(-50%);pointer-events:none;}
.year-picker__dropdown{display:none;position:absolute;top:36px;left:0;width:100%;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);z-index:50;max-height:180px;overflow-y:auto;}
.year-picker__dropdown.open{display:block;}
.year-picker__dropdown div{padding:8px 14px;font-size:var(--font-sm);cursor:pointer;}
.year-picker__dropdown div:hover{background:var(--primary-light);}
.year-picker__dropdown div.selected{color:var(--primary);font-weight:600;}
.radio-group{display:flex;gap:20px;height:32px;align-items:center;}
.radio{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;}
.radio input{accent-color:var(--primary);cursor:pointer;}
@@ -223,7 +232,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<button class="btn btn-primary" onclick="openImport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 9V2M4.5 4.5L7 2l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>批量导入数据</button>
<button class="btn btn-default" onclick="openImportTasks()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导入任务</button>
<button class="btn btn-primary" onclick="showToast('救护车台账已导出(演示)')"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
<button class="btn btn-default" onclick="askDelete(checkedIds())">批量删除</button>
</div>
<!-- 表格 -->
@@ -261,54 +269,67 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属医疗点</span>
<select class="form-control" id="fPoint"><option value="">请先选择所属单位</option></select></div>
</div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 救护类型</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fType" value="急救型" checked>急救型</label>
<label class="radio"><input type="radio" name="fType" value="转运型">转运型</label>
</div></div>
<div class="form-section">车辆信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> </span><input class="form-control" id="fModel" placeholder="如:江铃轻型监护型救护车"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 车牌号</span><input class="form-control" id="fPlate" placeholder="如:CQ·0712A"></div>
<div class="form-item"><span class="form-label">年支付费用(万元)</span><input class="form-control" id="fFee" type="number" min="0" step="0.1" placeholder="0.0"></div>
<div class="form-item"><span class="form-label">救护车所属机构名称</span><input class="form-control" id="fOrg" placeholder="如:CQ油田总医院"></div>
<div class="form-item"><span class="form-label">开始服务年份</span><input class="form-control" id="fYear" type="number" min="2000" max="2026" placeholder="不晚于当年"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 救护类</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fType" value="急救型" checked>急救型</label>
<label class="radio"><input type="radio" name="fType" value="转运型">转运型</label>
</div></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 车型</span><input class="form-control" id="fModel" placeholder="请输入"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 车牌号</span><input class="form-control" id="fPlate" placeholder="请输入"></div>
<div class="form-item"><span class="form-label">救护车所属机构名称</span><input class="form-control" id="fOrg" placeholder="请输入"></div>
<div class="form-item"><span class="form-label">开始服务年份</span>
<div class="year-picker">
<input class="form-control" id="fYear" type="text" readonly placeholder="请选择年份">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="1.5" y="2.5" width="11" height="10" rx="1.5" stroke="#999" stroke-width="1.2"/><path d="M1.5 5.5h11M4.5 1v3M9.5 1v3" stroke="#999" stroke-width="1.2" stroke-linecap="round"/></svg>
<div class="year-picker__dropdown">
<div onclick="pickYear(this)">2026</div>
<div onclick="pickYear(this)">2025</div>
<div onclick="pickYear(this)">2024</div>
<div onclick="pickYear(this)">2023</div>
<div onclick="pickYear(this)">2022</div>
<div onclick="pickYear(this)">2021</div>
<div onclick="pickYear(this)">2020</div>
<div onclick="pickYear(this)">2019</div>
<div onclick="pickYear(this)">2018</div>
<div onclick="pickYear(this)">2017</div>
<div onclick="pickYear(this)">2016</div>
</div>
</div></div>
<div class="form-item"><span class="form-label">年支付费用(万元)</span><input class="form-control" id="fFee" type="number" min="0" step="0.1" placeholder="请输入"></div>
<div class="form-item"><span class="form-label">是否安装GPS</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fGps" value="是" checked></label>
<label class="radio"><input type="radio" name="fGps" value="否"></label>
</div></div>
<div class="form-item"><span class="form-label">是否链接公司车辆管理系统</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fLink" value="是"></label>
<label class="radio"><input type="radio" name="fLink" value="否" checked></label>
</div></div>
<div class="form-item form-item--full"><span class="form-label">救护车照片(最多5张)</span>
<div class="photo-row" id="phCar"></div>
<div class="photo-tip">点击 + 上传,支持 jpg/png,单张不超过 5MB</div></div>
</div>
<div class="form-section">覆盖范围</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 覆盖区域</span><input class="form-control" id="fArea" placeholder="如:樊学作业区及沿线井站"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 覆盖人数</span><input class="form-control" id="fPeople" type="number" min="1" placeholder="正整数"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 覆盖区域</span><input class="form-control" id="fArea" placeholder="请输入"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 覆盖人数</span><input class="form-control" id="fPeople" type="number" min="1" placeholder="请输入"></div>
</div>
<div class="form-section">急救能力</div>
<div class="form-item form-item--full"><span class="form-label">所配急救设备</span>
<textarea class="form-control" id="fEquip" placeholder="如:除颤仪、心电监护仪、氧气袋、创伤固定包"></textarea></div>
<textarea class="form-control" id="fEquip" placeholder="请输入"></textarea></div>
<div class="form-item form-item--full"><span class="form-label">救护能力</span>
<textarea class="form-control" id="fAbility" placeholder="描述救护车可承担的院前急救与转运能力"></textarea></div>
<textarea class="form-control" id="fAbility" placeholder="请输入"></textarea></div>
<div class="form-section">驾驶员信息</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 驾驶员姓名</span><input class="form-control" id="fDriver" placeholder="请输入驾驶员姓名"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 联系电话</span><input class="form-control" id="fTel" placeholder="手机号或区号-座机号"></div>
</div>
<div class="form-item"><span class="form-label">是否安装GPS</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fGps" value="是" checked></label>
<label class="radio"><input type="radio" name="fGps" value="否"></label>
</div></div>
<div class="form-section">证照与照片</div>
<div class="form-item"><span class="form-label">救护车照片(最多5张)</span>
<div class="photo-row" id="phCar"></div>
<div class="photo-tip">点击 + 上传,支持 jpg/png,单张不超过 5MB</div></div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 驾驶员姓名</span><input class="form-control" id="fDriver" placeholder="请输入"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 联系电话</span><input class="form-control" id="fTel" placeholder="请输入"></div>
<div class="form-item"><span class="form-label">驾驶证图片(最多5张)</span>
<div class="photo-row" id="phLicense"></div></div>
<div class="form-item"><span class="form-label">资格证图片(最多5张)</span>
@@ -376,7 +397,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏) ===== */
/* ===== 演示数据 ===== */
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
@@ -384,12 +405,12 @@ var pointMap={
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var ambulances=[
{id:1,unit:'采油一厂',point:'樊学作业区医疗点',type:'急救型',model:'江铃轻型监护型救护车',plate:'CQ·0712A',area:'樊学作业区及沿线井站',people:860,driver:'田震',tel:'139****5218',gps:'是',fee:8.5,org:'CQ油田总医院',year:2021,link:'是',equip:'除颤仪、心电监护仪、氧气袋、创伤固定包',ability:'可承担院内心跳呼吸骤停复苏与创伤伤员的第一时间转运'},
{id:2,unit:'采油二厂',point:'姬圪台作业区医疗点',type:'急救型',model:'福田图雅诺监护型救护车',plate:'CQ·1366B',area:'姬圪台联合站及管线区',people:1240,driver:'冯保山',tel:'135****6174',gps:'是',fee:9.2,org:'CQ油田总医院',year:2020,link:'是',equip:'除颤仪、便携式彩超、呼吸机',ability:'具备危重伤员途中生命支持与心梗卒中协同转运能力'},
{id:3,unit:'第四采油厂',point:'西峰基地医疗点',type:'转运型',model:'福特全顺转运型救护车',plate:'CQ·0208C',area:'西峰基地至第四采油厂机关沿线',people:2100,driver:'罗建平',tel:'150****8357',gps:'是',fee:12,org:'第四采油厂总医院',year:2019,link:'是',equip:'担架系统、输液泵、供氧系统',ability:'承担慢性病患者往返医院透析、复查等非急救转运'},
{id:4,unit:'采气一厂',point:'靖边气田医疗点',type:'急救型',model:'东风轻型救护车',plate:'CQ·1533D',area:'靖边净化厂及井场',people:760,driver:'邓成',tel:'133****9481',gps:'否',fee:7.8,org:'CQ油田总医院',year:2022,link:'否',equip:'除颤监护仪、氧气瓶、急救药箱',ability:'满足井场外伤、中暑、心源性猝倒的现场急救与送医'},
{id:5,unit:'采油一厂',point:'安塞采油队长住点医疗室',type:'转运型',model:'江淮瑞风转运型救护车',plate:'CQ·0917E',area:'安塞基地及队部常驻区',people:540,driver:'苏文斌',tel:'155****6703',gps:'是',fee:6.5,org:'吴起油田医院',year:2023,link:'是',equip:'自动上车担架、颈胸腰固定支具',ability:'常驻点员工常规转运与陪同就医'},
{id:6,unit:'采气一厂',point:'乌审旗采气区医疗点',type:'急救型',model:'红旗轻型监护型救护车',plate:'CQ·2201F',area:'苏里格南作业区营地',people:680,driver:'曹雪芹',tel:'186****2940',gps:'是',fee:8,org:'庆阳市人民医院',year:2024,link:'否',equip:'除颤仪、心电监护仪、简易呼吸器',ability:'高寒沙尘环境下的急救响应与危重员转运'}
{id:1,unit:'采油一厂',point:'樊学作业区医疗点',type:'急救型',model:'江铃轻型监护型救护车',plate:'CQ·0712A',area:'樊学作业区及沿线井站',people:860,driver:'田震',tel:'13936485218',gps:'是',fee:8.5,org:'CQ油田总医院',year:2021,link:'是',equip:'除颤仪、心电监护仪、氧气袋、创伤固定包',ability:'可承担院内心跳呼吸骤停复苏与创伤伤员的第一时间转运'},
{id:2,unit:'采油二厂',point:'姬圪台作业区医疗点',type:'急救型',model:'福田图雅诺监护型救护车',plate:'CQ·1366B',area:'姬圪台联合站及管线区',people:1240,driver:'冯保山',tel:'13581136174',gps:'是',fee:9.2,org:'CQ油田总医院',year:2020,link:'是',equip:'除颤仪、便携式彩超、呼吸机',ability:'具备危重伤员途中生命支持与心梗卒中协同转运能力'},
{id:3,unit:'第四采油厂',point:'西峰基地医疗点',type:'转运型',model:'福特全顺转运型救护车',plate:'CQ·0208C',area:'西峰基地至第四采油厂机关沿线',people:2100,driver:'罗建平',tel:'15077088357',gps:'是',fee:12,org:'第四采油厂总医院',year:2019,link:'是',equip:'担架系统、输液泵、供氧系统',ability:'承担慢性病患者往返医院透析、复查等非急救转运'},
{id:4,unit:'采气一厂',point:'靖边气田医疗点',type:'急救型',model:'东风轻型救护车',plate:'CQ·1533D',area:'靖边净化厂及井场',people:760,driver:'邓成',tel:'13392699481',gps:'否',fee:7.8,org:'CQ油田总医院',year:2022,link:'否',equip:'除颤监护仪、氧气瓶、急救药箱',ability:'满足井场外伤、中暑、心源性猝倒的现场急救与送医'},
{id:5,unit:'采油一厂',point:'安塞采油队长住点医疗室',type:'转运型',model:'江淮瑞风转运型救护车',plate:'CQ·0917E',area:'安塞基地及队部常驻区',people:540,driver:'苏文斌',tel:'15598486703',gps:'是',fee:6.5,org:'吴起油田医院',year:2023,link:'是',equip:'自动上车担架、颈胸腰固定支具',ability:'常驻点员工常规转运与陪同就医'},
{id:6,unit:'采气一厂',point:'乌审旗采气区医疗点',type:'急救型',model:'红旗轻型监护型救护车',plate:'CQ·2201F',area:'苏里格南作业区营地',people:680,driver:'曹雪芹',tel:'18642802940',gps:'是',fee:8,org:'庆阳市人民医院',year:2024,link:'否',equip:'除颤仪、心电监护仪、简易呼吸器',ability:'高寒沙尘环境下的急救响应与危重员转运'}
];
var nextId=7,editingId=null,pendingDel=[];
@@ -425,7 +446,7 @@ function renderTable(){
+'<td><input type="checkbox" class="row-check" value="'+a.id+'"></td>'
+'<td>'+(start+i+1)+'</td>'
+'<td>'+a.unit+'</td>'
+'<td style="text-align:left;font-weight:600;">'+a.point+'</td>'
+'<td style="text-align:left;">'+a.point+'</td>'
+'<td>'+typeBadge+'</td>'
+'<td style="text-align:left;max-width:200px;overflow:hidden;text-overflow:ellipsis;">'+a.model+'</td>'
+'<td>'+a.plate+'</td>'
@@ -458,6 +479,29 @@ function turnPage(d){
function toggleAll(el){document.querySelectorAll('.row-check').forEach(function(c){c.checked=el.checked;});}
function checkedIds(){return [].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return +c.value;});}
/* ===== 年份选择控件 ===== */
function pickYear(el){
var dp=el.parentElement,input=dp.parentElement.querySelector('input');
input.value=el.textContent;
dp.querySelectorAll('div').forEach(function(d){d.classList.toggle('selected',d===el);});
dp.classList.remove('open');
}
document.querySelectorAll('.year-picker>input').forEach(function(inp){
inp.addEventListener('click',function(e){
e.stopPropagation();
var dp=inp.parentElement.querySelector('.year-picker__dropdown');
var wasOpen=dp.classList.contains('open');
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
if(!wasOpen){
dp.querySelectorAll('div').forEach(function(d){d.classList.toggle('selected',d.textContent===inp.value);});
dp.classList.add('open');
}
});
});
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* ===== 照片占位 ===== */
function renderPhotos(boxId,count){
var html='';
@@ -558,17 +602,19 @@ function openDetail(id){
var a=ambulances.find(function(x){return x.id===id;});
document.getElementById('detailTitle').textContent='救护车详情 · '+a.plate;
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v===''||v==null?'—':v)+'</div></div>';}
function dPhoto(k,tiles){return '<div class="detail-item detail-item--full"><div class="dk">'+k+'</div><div class="dv"><div class="photo-row">'+tiles+'</div></div></div>';}
document.getElementById('detailBody').innerHTML=
'<div class="form-section" style="margin-top:0;">归属与类型</div>'
+'<div class="detail-grid">'+d('所属单位',a.unit)+d('所属医疗点',a.point)+d('救护类型',a.type)+d('车型',a.model)+'</div>'
'<div class="form-section" style="margin-top:0;">归属信息</div>'
+'<div class="detail-grid">'+d('所属单位',a.unit)+d('所属医疗点',a.point)+'</div>'
+'<div class="form-section">车辆信息</div>'
+'<div class="detail-grid">'+d('车牌号',a.plate)+d('年支付费用(万元)',a.fee)+d('所属机构',a.org)+d('开始服务年份',a.year)+d('链接车辆管理系统',a.link)+d('是否安装GPS',a.gps)+'</div>'
+'<div class="detail-grid">'+d('救护类型',a.type)+d('车型',a.model)+d('车牌号',a.plate)+d('所属机构',a.org)+d('开始服务年份',a.year)+d('年支付费用(万元)',a.fee)+d('是否安装GPS',a.gps)+d('链接车辆管理系统',a.link)+'</div>'
+dPhoto('救护车照片','<div class="photo-tile">🚑</div><div class="photo-tile">🚑</div>')
+'<div class="form-section">覆盖与急救能力</div>'
+'<div class="detail-grid">'+d('覆盖区域',a.area)+d('覆盖人数',a.people.toLocaleString()+' 人')+d('所配急救设备',a.equip,true)+d('救护能力',a.ability,true)+'</div>'
+'<div class="form-section">驾驶员</div>'
+'<div class="form-section">驾驶员信息</div>'
+'<div class="detail-grid">'+d('驾驶员姓名',a.driver)+d('联系电话',a.tel)+'</div>'
+'<div class="form-section">证照与照片</div>'
+'<div class="photo-row"><div class="photo-tile">🚑</div><div class="photo-tile">🚑</div><div class="photo-tile">🪪</div><div class="photo-tile">📄</div></div>';
+dPhoto('驾驶证图片','<div class="photo-tile">🪪</div>')
+dPhoto('资格证图片','<div class="photo-tile">📄</div>');
document.getElementById('detailMask').classList.add('open');
document.getElementById('detailDrawer').classList.add('open');
}
@@ -30,25 +30,16 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 统计卡片 */
.stat-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:16px;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 20px;display:flex;align-items:center;gap:14px;}
.stat-card__icon{width:44px;height:44px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;}
.stat-card__icon--blue{background:var(--primary-light);}
.stat-card__icon--green{background:#F6FFED;}
.stat-card__icon--orange{background:#FFFBE6;}
.stat-card__icon--red{background:#FFF1F0;}
.stat-card__label{font-size:var(--font-sm);color:var(--text-secondary);}
.stat-card__value{font-size:24px;font-weight:600;color:var(--text-primary);line-height:1.3;font-variant-numeric:tabular-nums;}
.stat-card__extra{font-size:var(--font-xs);color:var(--text-placeholder);}
/* 筛选栏 */
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;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;}
@@ -157,52 +148,51 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item active">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item active">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
<main class="main">
<!-- [状态] 统计卡片 -->
<div class="stat-cards">
<div class="stat-card"><div class="stat-card__icon stat-card__icon--blue">📅</div><div><div class="stat-card__label">当周出勤</div><div class="stat-card__value" id="kWeek">0</div><div class="stat-card__extra" id="kWeekExtra">本周</div></div></div>
<div class="stat-card"><div class="stat-card__icon stat-card__icon--green">🗓️</div><div><div class="stat-card__label">当月出勤</div><div class="stat-card__value" id="kMonth">0</div><div class="stat-card__extra" id="kMonthExtra">本月</div></div></div>
<div class="stat-card"><div class="stat-card__icon stat-card__icon--orange">📆</div><div><div class="stat-card__label">年度出勤</div><div class="stat-card__value" id="kYear">0</div><div class="stat-card__extra" id="kYearExtra">本年度</div></div></div>
<div class="stat-card"><div class="stat-card__icon stat-card__icon--red">📊</div><div><div class="stat-card__label">累计出勤</div><div class="stat-card__value" id="kTotal">0</div><div class="stat-card__extra">历史累计</div></div></div>
</div>
<!-- [交互] 筛选栏:医疗点→救护车级联 + 出勤类型 + 日期区间 -->
<!-- [交互] 筛选栏:单位→医疗点级联 + 车牌号搜索 + 出勤类型 + 日期区间 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()" style="min-width:140px;">
<option value="">全部单位</option>
<option>采油一厂</option><option>采油二厂</option><option>第四采油厂</option><option>采气一厂</option>
</select>
</div>
<div class="filter-item">
<label>所属医疗点</label>
<select class="filter-select" id="filterPoint" onchange="onFilterPoint()" style="min-width:170px;">
<select class="filter-select" id="filterPoint" style="min-width:170px;">
<option value="">全部医疗点</option>
<option>樊学作业区医疗点</option><option>安塞采油队长住点医疗室</option><option>姬塬作业区医疗点</option><option>姬圪台作业区医疗点</option><option>桐园作业区医疗点</option><option>西峰基地医疗点</option><option>靖边气田医疗点</option><option>乌审旗采气区医疗点</option>
</select>
</div>
<div class="filter-item">
<label>救护</label>
<select class="filter-select" id="filterAmb" style="min-width:130px;">
<option value="">全部救护车</option>
</select>
<label>牌号</label>
<input class="filter-input" id="filterPlate" placeholder="请输入车牌号" style="min-width:130px;">
</div>
<div class="filter-item">
<label>出勤类型</label>
<select class="filter-select" id="filterType" style="min-width:110px;">
<option value="">全部类型</option>
<option>病情急救</option><option>伤情急救</option><option>日常巡诊</option><option>应急戒备</option>
<option>急诊转运</option><option>平诊转运</option><option>日常巡诊</option><option>应急戒备</option><option>床头呼叫</option><option>其他</option>
</select>
</div>
<div class="filter-item">
@@ -269,10 +259,12 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<input class="form-control" type="date" id="fDate"></div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> 出勤类型</span>
<div class="radio-group">
<label class="radio"><input type="radio" name="fDispatch" value="病情急救" checked>病情急救</label>
<label class="radio"><input type="radio" name="fDispatch" value="伤情急救">伤情急救</label>
<label class="radio"><input type="radio" name="fDispatch" value="急诊转运" checked>急诊转运</label>
<label class="radio"><input type="radio" name="fDispatch" value="平诊转运">平诊转运</label>
<label class="radio"><input type="radio" name="fDispatch" value="日常巡诊">日常巡诊</label>
<label class="radio"><input type="radio" name="fDispatch" value="应急戒备">应急戒备</label>
<label class="radio"><input type="radio" name="fDispatch" value="床头呼叫">床头呼叫</label>
<label class="radio"><input type="radio" name="fDispatch" value="其他">其他</label>
</div></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 出勤司机</span>
<select class="form-control" id="fDriver" onchange="onFormDriver()"><option value="">请选择司机</option></select></div>
@@ -356,16 +348,23 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="toast" id="toast"></div>
<script>
/* ===== 基础数据(脱敏) ===== */
var points=['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点','姬圪台作业区医疗点','桐园作业区医疗点','西峰基地医疗点','靖边气田医疗点','乌审旗采气区医疗点'];
/* ===== 基础数据 ===== */
/* 单位→医疗点映射:与「基础配置·救护车配置」一致 */
var unitMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
'采油二厂':['姬圪台作业区医疗点','桐园作业区医疗点'],
'第四采油厂':['西峰基地医疗点'],
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var allPoints=['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点','姬圪台作业区医疗点','桐园作业区医疗点','西峰基地医疗点','靖边气田医疗点','乌审旗采气区医疗点'];
/* 救护车台账:与「基础配置·救护车配置」一致 */
var ambulances=[
{plate:'CQ·0712A',point:'樊学作业区医疗点',type:'急救型',model:'江铃轻型监护型救护车',driver:'田震',tel:'139****5218'},
{plate:'CQ·1366B',point:'姬圪台作业区医疗点',type:'急救型',model:'福田图雅诺监护型救护车',driver:'冯保山',tel:'135****6174'},
{plate:'CQ·0208C',point:'西峰基地医疗点',type:'转运型',model:'福特全顺转运型救护车',driver:'罗建平',tel:'150****8357'},
{plate:'CQ·1533D',point:'靖边气田医疗点',type:'急救型',model:'东风轻型救护车',driver:'邓成',tel:'133****9481'},
{plate:'CQ·0917E',point:'安塞采油队长住点医疗室',type:'转运型',model:'江淮瑞风转运型救护车',driver:'苏文斌',tel:'155****6703'},
{plate:'CQ·2201F',point:'乌审旗采气区医疗点',type:'急救型',model:'红旗轻型监护型救护车',driver:'曹雪芹',tel:'186****2940'}
{plate:'CQ·0712A',point:'樊学作业区医疗点',type:'急救型',model:'江铃轻型监护型救护车',driver:'田震',tel:'13936485218'},
{plate:'CQ·1366B',point:'姬圪台作业区医疗点',type:'急救型',model:'福田图雅诺监护型救护车',driver:'冯保山',tel:'13581136174'},
{plate:'CQ·0208C',point:'西峰基地医疗点',type:'转运型',model:'福特全顺转运型救护车',driver:'罗建平',tel:'15077088357'},
{plate:'CQ·1533D',point:'靖边气田医疗点',type:'急救型',model:'东风轻型救护车',driver:'邓成',tel:'13392699481'},
{plate:'CQ·0917E',point:'安塞采油队长住点医疗室',type:'转运型',model:'江淮瑞风转运型救护车',driver:'苏文斌',tel:'15598486703'},
{plate:'CQ·2201F',point:'乌审旗采气区医疗点',type:'急救型',model:'红旗轻型监护型救护车',driver:'曹雪芹',tel:'18642802940'}
];
/* 救护车司机列表:从救护车台账驾驶员去重提取 */
var drivers=[];
@@ -390,71 +389,50 @@ var medStaff=[
{id:11,name:'荣大海',job:'副主任医师',post:'医生',point:'乌审旗采气区医疗点'},
{id:12,name:'牛玉洁',job:'护师',post:'药师',point:'姬塬作业区医疗点'}
];
/* 救护车出勤记录:多救护车混合,覆盖类出勤类型 */
/* 救护车出勤记录:多救护车混合,覆盖类出勤类型 */
var attendances=[
{id:1,date:'2026-09-08',point:'樊学作业区医疗点',plate:'CQ·0712A',type:'急救型',dispatch:'病情急救',driver:'田震',tel:'139****5218',staff:['韩东学','兰小娟'],out:'樊学作业区 7#井站',to:'CQ油田总医院'},
{id:2,date:'2026-09-08',point:'姬圪台作业区医疗点',plate:'CQ·1366B',type:'急救型',dispatch:'伤情急救',driver:'冯保山',tel:'135****6174',staff:['窦志远','苗慧敏'],out:'姬圪台联合站',to:'庆阳市人民医院'},
{id:3,date:'2026-09-07',point:'西峰基地医疗点',plate:'CQ·0208C',type:'转运型',dispatch:'日常巡诊',driver:'罗建平',tel:'150****8357',staff:['霍静怡'],out:'西峰基地机关',to:'西峰基地卫生室'},
{id:4,date:'2026-09-07',point:'靖边气田医疗点',plate:'CQ·1533D',type:'急救型',dispatch:'应急戒备',driver:'邓成',tel:'133****9481',staff:['康永强','花灵芝'],out:'靖边净化厂',to:'现场应急指挥部'},
{id:5,date:'2026-09-06',point:'安塞采油队长住点医疗室',plate:'CQ·0917E',type:'转运型',dispatch:'日常巡诊',driver:'苏文斌',tel:'155****6703',staff:['白宇峰'],out:'安塞基地队部',to:'吴起油田医院'},
{id:6,date:'2026-09-06',point:'乌审旗采气区医疗点',plate:'CQ·2201F',type:'急救型',dispatch:'伤情急救',driver:'曹雪芹',tel:'186****2940',staff:['荣大海','花灵芝'],out:'苏里格南作业区营地',to:'乌审旗人民医院'},
{id:7,date:'2026-09-05',point:'樊学作业区医疗点',plate:'CQ·0712A',type:'急救型',dispatch:'应急戒备',driver:'田震',tel:'139****5218',staff:['韩东学'],out:'樊学作业区应急演练现场',to:'演练指挥部'},
{id:8,date:'2026-09-05',point:'姬圪台作业区医疗点',plate:'CQ·1366B',type:'急救型',dispatch:'病情急救',driver:'冯保山',tel:'135****6174',staff:['窦志远','屈二娃'],out:'姬圪台作业区 3#站',to:'庆阳市人民医院'},
{id:9,date:'2026-09-04',point:'西峰基地医疗点',plate:'CQ·0208C',type:'转运型',dispatch:'病情急救',driver:'罗建平',tel:'150****8357',staff:['霍静怡'],out:'西峰基地生活区',to:'第四采油厂总医院'},
{id:10,date:'2026-09-04',point:'靖边气田医疗点',plate:'CQ·1533D',type:'急救型',dispatch:'日常巡诊',driver:'邓成',tel:'133****9481',staff:['康永强'],out:'靖边气田井场巡诊',to:'靖边气田医疗点'},
{id:11,date:'2026-09-03',point:'安塞采油队长住点医疗室',plate:'CQ·0917E',type:'转运型',dispatch:'日常巡诊',driver:'苏文斌',tel:'155****6703',staff:['白宇峰','牛玉洁'],out:'安塞基地各井站',to:'安塞采油队长住点医疗室'},
{id:12,date:'2026-09-03',point:'乌审旗采气区医疗点',plate:'CQ·2201F',type:'急救型',dispatch:'病情急救',driver:'曹雪芹',tel:'186****2940',staff:['荣大海'],out:'乌审旗采气区集气站',to:'乌审旗人民医院'}
{id:1,date:'2026-09-08',point:'樊学作业区医疗点',plate:'CQ·0712A',type:'急救型',dispatch:'急诊转运',driver:'田震',tel:'13936485218',staff:['韩东学','兰小娟'],out:'樊学作业区 7#井站',to:'CQ油田总医院'},
{id:2,date:'2026-09-08',point:'姬圪台作业区医疗点',plate:'CQ·1366B',type:'急救型',dispatch:'急诊转运',driver:'冯保山',tel:'13581136174',staff:['窦志远','苗慧敏'],out:'姬圪台联合站',to:'庆阳市人民医院'},
{id:3,date:'2026-09-07',point:'西峰基地医疗点',plate:'CQ·0208C',type:'转运型',dispatch:'日常巡诊',driver:'罗建平',tel:'15077088357',staff:['霍静怡'],out:'西峰基地机关',to:'西峰基地卫生室'},
{id:4,date:'2026-09-07',point:'靖边气田医疗点',plate:'CQ·1533D',type:'急救型',dispatch:'应急戒备',driver:'邓成',tel:'13392699481',staff:['康永强','花灵芝'],out:'靖边净化厂',to:'现场应急指挥部'},
{id:5,date:'2026-09-06',point:'安塞采油队长住点医疗室',plate:'CQ·0917E',type:'转运型',dispatch:'日常巡诊',driver:'苏文斌',tel:'15598486703',staff:['白宇峰'],out:'安塞基地队部',to:'吴起油田医院'},
{id:6,date:'2026-09-06',point:'乌审旗采气区医疗点',plate:'CQ·2201F',type:'急救型',dispatch:'平诊转运',driver:'曹雪芹',tel:'18642802940',staff:['荣大海','花灵芝'],out:'苏里格南作业区营地',to:'乌审旗人民医院'},
{id:7,date:'2026-09-05',point:'樊学作业区医疗点',plate:'CQ·0712A',type:'急救型',dispatch:'应急戒备',driver:'田震',tel:'13936485218',staff:['韩东学'],out:'樊学作业区应急演练现场',to:'演练指挥部'},
{id:8,date:'2026-09-05',point:'姬圪台作业区医疗点',plate:'CQ·1366B',type:'急救型',dispatch:'急诊转运',driver:'冯保山',tel:'13581136174',staff:['窦志远','屈二娃'],out:'姬圪台作业区 3#站',to:'庆阳市人民医院'},
{id:9,date:'2026-09-04',point:'西峰基地医疗点',plate:'CQ·0208C',type:'转运型',dispatch:'床头呼叫',driver:'罗建平',tel:'15077088357',staff:['霍静怡'],out:'西峰基地生活区',to:'第四采油厂总医院'},
{id:10,date:'2026-09-04',point:'靖边气田医疗点',plate:'CQ·1533D',type:'急救型',dispatch:'日常巡诊',driver:'邓成',tel:'13392699481',staff:['康永强'],out:'靖边气田井场巡诊',to:'靖边气田医疗点'},
{id:11,date:'2026-09-03',point:'安塞采油队长住点医疗室',plate:'CQ·0917E',type:'转运型',dispatch:'日常巡诊',driver:'苏文斌',tel:'15598486703',staff:['白宇峰','牛玉洁'],out:'安塞基地各井站',to:'安塞采油队长住点医疗室'},
{id:12,date:'2026-09-03',point:'乌审旗采气区医疗点',plate:'CQ·2201F',type:'急救型',dispatch:'其他',driver:'曹雪芹',tel:'18642802940',staff:['荣大海'],out:'乌审旗采气区集气站',to:'乌审旗人民医院'}
];
var nextId=13,editingId=null,pendingDelId=null;
var dispatchBadge={'病情急救':'badge--danger','伤情急救':'badge--warning','日常巡诊':'badge--info','应急戒备':'badge--success'};
var dispatchBadge={'急诊转运':'badge--danger','平诊转运':'badge--info','日常巡诊':'badge--success','应急戒备':'badge--warning','床头呼叫':'badge--info','其他':'badge--muted'};
var typeBadge={'急救型':'badge--danger','转运型':'badge--info'};
/* ===== 筛选与渲染 ===== */
var pageSize=10,currentPage=1;
function onFilterPoint(){
var point=document.getElementById('filterPoint').value;
var sel=document.getElementById('filterAmb');
sel.innerHTML='<option value="">全部救护车</option>';
ambulances.filter(function(a){return !point||a.point===point;}).forEach(function(a){
var o=document.createElement('option');o.textContent=a.plate;sel.appendChild(o);
function fillFilterPoints(unit){
var sel=document.getElementById('filterPoint');
var list=unit?(unitMap[unit]||[]):allPoints;
sel.innerHTML='<option value="">全部医疗点</option>';
list.forEach(function(p){
var o=document.createElement('option');o.textContent=p;sel.appendChild(o);
});
}
function onFilterUnit(){fillFilterPoints(document.getElementById('filterUnit').value);}
function currentData(){
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var amb=document.getElementById('filterAmb').value;
var plate=document.getElementById('filterPlate').value.trim();
var type=document.getElementById('filterType').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
var points=unit?(unitMap[unit]||[]):null;
return attendances.filter(function(r){
return (!point||r.point===point)&&(!amb||r.plate===amb)
return (!point||r.point===point)&&(!points||points.indexOf(r.point)!==-1)
&&(!plate||r.plate.indexOf(plate)!==-1)
&&(!type||r.dispatch===type)&&(!from||r.date>=from)&&(!to||r.date<=to);
});
}
var TODAY='2026-09-09';
function pad(n){return n<10?'0'+n:''+n;}
function fmtDate(d){return d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate());}
function weekRange(){
var base=new Date(TODAY+'T00:00:00');
var dow=base.getDay();
var monOffset=dow===0?-6:1-dow;
var mon=new Date(base);mon.setDate(base.getDate()+monOffset);
var sun=new Date(mon);sun.setDate(mon.getDate()+6);
return {start:fmtDate(mon),end:fmtDate(sun)};
}
function renderStats(){
var list=attendances;
var wk=weekRange();
var week=list.filter(function(r){return r.date>=wk.start&&r.date<=wk.end;}).length;
var month=list.filter(function(r){return r.date.slice(0,7)===TODAY.slice(0,7);}).length;
var year=list.filter(function(r){return r.date.slice(0,4)===TODAY.slice(0,4);}).length;
document.getElementById('kWeek').textContent=week;
document.getElementById('kWeekExtra').textContent=wk.start+' ~ '+wk.end;
document.getElementById('kMonth').textContent=month;
document.getElementById('kMonthExtra').textContent=parseInt(TODAY.slice(5,7),10)+'月';
document.getElementById('kYear').textContent=year;
document.getElementById('kYearExtra').textContent=TODAY.slice(0,4)+'年度';
document.getElementById('kTotal').textContent=list.length;
}
function renderTable(){
var list=currentData();
var start=(currentPage-1)*pageSize,pageData=list.slice(start,start+pageSize);
@@ -489,7 +467,9 @@ function renderTable(){
}
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterPoint').value='';onFilterPoint();
document.getElementById('filterUnit').value='';
fillFilterPoints('');
document.getElementById('filterPlate').value='';
document.getElementById('filterType').value='';
document.getElementById('filterFrom').value='';
document.getElementById('filterTo').value='';
@@ -560,7 +540,7 @@ function openForm(id){
if(!r)document.getElementById('fAmb').innerHTML='<option value="">请先选择医疗点</option>';
fillDriverSel(r?r.driver:'');
document.getElementById('fDate').value=r?r.date:'';
setRadio('fDispatch',r?r.dispatch:'病情急救');
setRadio('fDispatch',r?r.dispatch:'急诊转运');
selectedStaffIds=(r?r.staff.slice():[]).map(function(n){var m=medStaff.find(function(x){return x.name===n;});return m?m.id:null;}).filter(function(id){return id!=null;});
renderStaffChips();
document.getElementById('fOut').value=r?r.out:'';
@@ -597,7 +577,7 @@ function saveForm(){
}else{
attendances.push(obj);
}
closeDrawer('form');renderStats();renderTable();
closeDrawer('form');renderTable();
showToast(editingId?'出勤记录已更新':'出勤记录已新增');
}
@@ -657,7 +637,7 @@ function askDelete(id){
}
function confirmDelete(){
attendances=attendances.filter(function(r){return r.id!==pendingDelId;});
closeModal('delMask');renderStats();renderTable();
closeModal('delMask');renderTable();
showToast('出勤记录已删除');
pendingDelId=null;
}
@@ -677,8 +657,8 @@ function showToast(msg){
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
onFilterPoint();renderStats();renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
renderTable();
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.page-header{margin-bottom:16px;display:flex;align-items:center;gap:12px;}
.page-header__back{display:inline-flex;align-items:center;gap:4px;font-size:var(--font-sm);color:var(--primary);text-decoration:none;padding:4px 10px;border:1px solid var(--primary);border-radius:var(--radius-sm);background:#fff;}
@@ -128,19 +129,21 @@ tbody tr:hover{background:#F8FBFF;}
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item active">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -390,7 +393,7 @@ function saveMed(){
showToast('新增用药已保存(演示)');
}
renderHead();renderAll();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
+19 -16
View File
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
@@ -144,19 +145,21 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item active">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -570,7 +573,7 @@ function showToast(msg){
renderTable();
document.addEventListener('click',function(){document.getElementById('orgPanel').classList.remove('open');});
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
+180 -111
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>健康宣教管理 | 一线医疗</title>
<title>健康宣教 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);background:#F5F7FA;color:#333;min-width:1280px;}
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
@@ -119,6 +120,27 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
.modal{width:440px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:24px;}
.modal__title{font-size:var(--font-base);font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;}
/* 负责人签字 */
.sign-box{border:1px dashed #B7C3D6;border-radius:8px;background:#F7F9FC;height:110px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;color:#969EB0;font-size:13px;cursor:pointer;}
.sign-box svg{width:22px;height:22px;stroke:#969EB0;fill:none;stroke-width:2;}
.radio{display:flex;gap:18px;align-items:center;font-size:13px;color:#333;}
.radio input{accent-color:#1890FF;width:14px;height:14px;}
.sign-note{display:flex;align-items:center;gap:6px;font-size:12px;color:#4A5A7F;margin-top:10px;}
.sign-view{display:flex;flex-direction:column;align-items:flex-start;gap:6px;}
.sign-view__name{font-family:KaiTi,serif;font-size:26px;color:#333;}
.sign-view__note{font-size:11px;color:#969EB0;}
.sign-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:970;align-items:center;justify-content:center;}
.sign-mask.open{display:flex;}
.sign-modal{background:#fff;border-radius:12px;width:720px;display:flex;flex-direction:column;box-shadow:0 12px 40px rgba(0,0,0,.25);}
.sign-modal__head{padding:15px 20px;border-bottom:1px solid #F0F2F5;display:flex;align-items:center;}
.sign-modal__title{font-size:15px;font-weight:600;color:#333;flex:1;}
.sign-modal__close{cursor:pointer;color:#999;font-size:18px;border:none;background:none;}
.sign-modal__foot{padding:13px 20px;border-top:1px solid #F0F2F5;display:flex;justify-content:flex-end;gap:10px;}
.canvas-wrap{position:relative;margin:20px;border:1px solid #DDD;background:#EEE;border-radius:6px;overflow:hidden;}
.canvas-wrap canvas{display:block;width:100%;height:260px;cursor:crosshair;}
.canvas-hint{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;color:#BBB;font-size:14px;pointer-events:none;}
.eraser{position:absolute;right:12px;bottom:12px;width:40px;height:40px;border-radius:8px;border:1px solid #D9DEE8;background:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;}
.eraser svg{width:20px;height:20px;stroke:#4A5A7F;fill:none;stroke-width:2;}
</style>
</head>
<body>
@@ -145,19 +167,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item active">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item active">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -166,8 +190,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label id="lblFilterTheme">宣教主</label>
<input class="filter-input" id="filterTheme" placeholder="请输入主题关键字">
<label id="lblFilterTheme">活动标</label>
<input class="filter-input" id="filterTheme" placeholder="请输入活动标题">
</div>
<div class="filter-item">
<label>医疗点所属单位</label>
@@ -183,7 +207,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</select>
</div>
<div class="filter-item">
<label>宣教日期</label>
<label>开展日期</label>
<input class="filter-input" type="date" id="filterFrom" style="min-width:130px;">
<span style="color:var(--text-placeholder);"></span>
<input class="filter-input" type="date" id="filterTo" style="min-width:130px;">
@@ -194,7 +218,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- [交互] Tab切换:健康宣教/急救培训(老系统为同一数据模型,publicityType=1/0 区分,仅文案与列名切换) -->
<!-- [交互] Tab切换:健康宣教/急救培训(仅筛选类型,字段文案统一) -->
<div class="tabs-bar" style="margin-top:16px;">
<button class="tab-btn active" id="tabEdu" onclick="switchTab(1)">健康宣教 <span class="cnt" id="cntEdu"></span></button>
<button class="tab-btn" id="tabTr" onclick="switchTab(0)">急救培训 <span class="cnt" id="cntTr"></span></button>
@@ -203,11 +227,9 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" id="btnAdd" onclick="openForm()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg><span id="addBtnText">新增宣教记录</span>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v10M2 7h10" stroke="#fff" stroke-width="1.6" stroke-linecap="round"/></svg><span id="addBtnText">新增活动</span>
</button>
<button class="btn btn-default btn-link--danger" onclick="askBatchDelete()">批量删除</button>
<button class="btn btn-primary" onclick="doExport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 10v2h10v-2M7 2v7M4.5 6.5L7 9l2.5-2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
<button class="btn btn-default" onclick="openExport()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出任务</button>
</div>
<!-- 表格 -->
@@ -216,7 +238,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<table>
<thead>
<tr>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th><th>序号</th><th id="thTheme">宣教主</th><th id="thDate">宣教日期</th><th>开展医疗点</th><th>所属单位</th><th>覆盖区域</th><th>参加人数</th><th>主讲人</th><th id="thContent">宣教内容</th><th>操作</th>
<th style="width:40px"><input type="checkbox" id="checkAll" onchange="toggleAll(this)"></th><th>序号</th><th>活动标</th><th>开展日期</th><th>开展医疗点</th><th>所属单位</th><th>覆盖区域</th><th>参加人数</th><th>活动内容</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
@@ -235,18 +257,19 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 新增/编辑宣教抽屉 -->
<div class="drawer-mask" id="formMask" onclick="closeDrawer('form')"></div>
<div class="drawer" id="formDrawer">
<div class="drawer__header"><span id="formTitle">新增宣教记录</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__header"><span id="formTitle">新增活动</span><button class="drawer__close" onclick="closeDrawer('form')">×</button></div>
<div class="drawer__body">
<div class="form-section" style="margin-top:0;" id="formSecTitle">宣教活动信息</div>
<div class="form-section" style="margin-top:0;" id="formSecTitle">活动信息</div>
<div class="form-grid">
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> <span id="lblTheme">宣教主题</span></span><input class="form-control" id="eTheme" placeholder="如:全国高血压日专题宣教"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> <span id="lblDate">宣教日期</span></span><input class="form-control" type="date" id="eDate"></div>
<div class="form-item"><span class="form-label">主讲人</span><input class="form-control" id="eLecturer" placeholder="医护人员姓名"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 活动类型</span>
<span class="radio"><label><input type="radio" name="eType" value="1" checked>健康宣教</label><label><input type="radio" name="eType" value="0">急救培训</label></span></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> <span id="lblDate">开展日期</span></span><input class="form-control" type="date" id="eDate"></div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> <span id="lblTheme">活动标题</span></span><input class="form-control" id="eTheme" placeholder="如:全国高血压日专题宣教"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 开展医疗点</span>
<select class="form-control" id="ePoint"><option value="">请选择医疗点</option></select></div>
<div class="form-item"><span class="form-label">覆盖区域</span><input class="form-control" id="eArea" placeholder="如:桐园作业区各井站"></div>
<div class="form-item"><span class="form-label">参加人数</span><input class="form-control" type="number" id="eNum" placeholder="现场签到人数"></div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> <span id="lblContent">宣教内容</span></span><textarea class="form-control" id="eContent" placeholder="活动内容与要点摘要"></textarea></div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> <span id="lblContent">活动内容</span></span><textarea class="form-control" id="eContent" placeholder="活动内容与要点摘要"></textarea></div>
</div>
<div class="form-section" id="secPhoto">现场照片</div>
<div class="photo-row">
@@ -254,21 +277,28 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="photo-ph"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><rect x="1" y="4" width="16" height="12" rx="2" stroke="#bbb" stroke-width="1.3"/><circle cx="9" cy="10" r="3.2" stroke="#bbb" stroke-width="1.3"/><path d="M6 4l1.2-2h3.6L12 4" stroke="#bbb" stroke-width="1.3"/></svg>照片 2</div>
<div class="photo-ph" onclick="showToast('照片上传功能演示')"><svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M9 4v10M4 9h10" stroke="#bbb" stroke-width="1.5" stroke-linecap="round"/></svg>继续添加</div>
</div>
<div class="form-section" id="secMaterial">宣教资料</div>
<div class="form-section" id="secMaterial">活动资料</div>
<div class="upload-box" id="materialBox" onclick="showToast('资料上传功能演示')">
<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><path d="M10 13V4m0 0L6.5 7.5M10 4l3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M3 14v2a2 2 0 002 2h10a2 2 0 002-2v-2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
<span id="materialText">点击上传宣教手册 / PPT / 折页等电子资料(支持 PDF、PPT,单个不超过 20MB)</span>
<span id="materialText">点击上传活动手册 / PPT / 折页等电子资料(支持 PDF、Word、Excel、PPT,单个不超过 20MB)</span>
</div>
<div class="form-section">员工确认签字</div>
<div class="upload-box">
<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><path d="M3 17c2-4 4 3 6-2s3 4 8-1" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/></svg>
参加人员在 PAD 端签到屏完成电子确认签字,签到记录自动回传关联本宣教活动
</div>
<div class="form-section">签字图片</div>
<div class="photo-row" id="signImgRow">
<div class="photo-ph" id="signImgPh" onclick="showToast('签字图片上传功能演示')">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M9 4v10M4 9h10" stroke="#bbb" stroke-width="1.5" stroke-linecap="round"/></svg>上传签字图片
<div class="form-section">负责人签字</div>
<div class="form-item" style="margin-bottom:0;">
<span class="form-label"><span class="req">*</span> 签字</span>
<div class="radio">
<label><input type="radio" name="signType" value="hand" checked onclick="switchSignType('hand')">手写签名</label>
<label><input type="radio" name="signType" value="img" onclick="switchSignType('img')">签字图片</label>
</div>
<div class="sign-box" id="signHandBox" onclick="openSign()">
<svg viewBox="0 0 24 24"><path d="M17 3a2.8 2.8 0 014 4L7.5 20.5 2 22l1.5-5.5z"/></svg>
点击签名
</div>
<div class="sign-box" id="signImgBox" style="display:none;" onclick="document.getElementById('signImgInput').click()">
<svg viewBox="0 0 24 24"><path d="M12 16V4M7 9l5-5 5 5"/><path d="M4 20h16"/></svg>
上传签字图片
</div>
<input type="file" id="signImgInput" accept="image/*" style="display:none;" onchange="onSignImg(this)">
<div class="sign-note" style="margin-top:0;">签字确认该活动记录真实有效</div>
</div>
</div>
<div class="drawer__footer">
@@ -280,7 +310,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 查看详情抽屉 -->
<div class="drawer-mask" id="viewMask" onclick="closeDrawer('view')"></div>
<div class="drawer" id="viewDrawer">
<div class="drawer__header"><span id="viewTitle">宣教详情</span><button class="drawer__close" onclick="closeDrawer('view')">×</button></div>
<div class="drawer__header"><span id="viewTitle">活动详情</span><button class="drawer__close" onclick="closeDrawer('view')">×</button></div>
<div class="drawer__body" id="viewBody"></div>
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('view')">关闭</button></div>
</div>
@@ -289,7 +319,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="modal-mask" id="delMask">
<div class="modal">
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#FAAD14" stroke-width="1.6"/><path d="M10 5.5v6M10 13.5v1" stroke="#FAAD14" stroke-width="1.8" stroke-linecap="round"/></svg> 删除确认</div>
<div id="delText">确认删除该条宣教记录?</div>
<div id="delText">确认删除该条活动记录?</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('delMask')">取消</button>
<button class="btn btn-primary" onclick="confirmDelete()">确认删除</button>
@@ -310,6 +340,22 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="drawer__footer"><button class="btn btn-default" onclick="closeDrawer('export')">关闭</button></div>
</div>
<!-- [交互] 手写签名弹窗 -->
<div class="sign-mask" id="signMask">
<div class="sign-modal">
<div class="sign-modal__head"><span class="sign-modal__title">负责人签字</span><button class="sign-modal__close" onclick="closeSign()"></button></div>
<div class="canvas-wrap">
<canvas id="signCanvas" width="780" height="260"></canvas>
<div class="canvas-hint" id="signHint">请在区域内签名<div style="font-size:12px;">保证字迹清晰可辨</div></div>
<button class="eraser" onclick="clearCanvas()" title="清空"><svg viewBox="0 0 24 24"><path d="M20 20H8.5l-4-4a2 2 0 010-2.8l8.2-8.2a2 2 0 012.8 0l5 5a2 2 0 010 2.8L13 20M8 11l6 6"/></svg></button>
</div>
<div class="sign-modal__foot">
<button class="btn btn-default" onclick="closeSign()">取消</button>
<button class="btn btn-primary" onclick="confirmSign()">确认</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
@@ -324,43 +370,21 @@ var pointMap={
/* 全量医疗点(新增/编辑直接选择,不依赖单位联动) */
var allPoints=[];
Object.keys(pointMap).forEach(function(u){pointMap[u].forEach(function(p){allPoints.push(p);});});
/* 签字图片回显与占位(纯静态原型,用内联 SVG 模拟签字图片) */
var SIGN_IMG_THUMB='<svg width="34" height="20" viewBox="0 0 40 22" fill="none"><path d="M4 17c4-6 8 4 11-3s6 5 12-1 5 2 9-2" stroke="#1890FF" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg><span>签字图片</span>';
var SIGN_IMG_PLACEHOLDER='<svg width="18" height="18" viewBox="0 0 18 18" fill="none"><path d="M9 4v10M4 9h10" stroke="#bbb" stroke-width="1.5" stroke-linecap="round"/></svg>上传签字图片';
/* type:1=健康宣教 0=急救培训(与老系统 publicityType 口径一致) */
/* type:1=健康宣教 0=急救培训 */
var typeMap={1:'健康宣教',0:'急救培训'};
var edus=[
{id:1,type:1,theme:'全国高血压日:减盐降血压专题宣教',date:'2026-09-02',point:'桐园作业区医疗点',area:'桐园作业区各井站、联合站',num:86,lecturer:'曲志明',content:'讲解高血压危害与低盐饮食要点,现场免费测血压42人次,发放限盐勺与宣教折页。'},
{id:2,type:1,theme:'联合国糖尿病日:吃动平衡控血糖',date:'2026-08-24',point:'西峰基地医疗点',area:'基地管理处及后勤单元',num:64,lecturer:'闫婷',content:'糖尿病饮食换算实操演示,餐后运动处方宣讲,血糖义务检测31人次。'},
{id:3,type:0,theme:'现场急救技能培训(心肺复苏+AED)',date:'2026-08-15',point:'樊学作业区医疗点',area:'樊学作业区倒班点',num:52,lecturer:'李桂芳',content:'CPR操作要领与AED使用流程分组的演练,逐人考核通过47人,发放急救明白卡。'},
{id:4,type:0,theme:'夏季高温防暑与中暑急救培训',date:'2026-07-18',point:'乌审旗采气区医疗点',area:'苏里格南作业区井场',num:73,lecturer:'刘玉梅',content:'先兆中暑识别与处置流程实操,防暑药品配备讲解,现场发放藿香正气类物资。'},
{id:5,type:1,theme:'职业人群颈椎健康与工间操推广',date:'2026-07-05',point:'姬圪台作业区医疗点',area:'姬圪台联合站中控、化验室',num:45,lecturer:'张秀兰',content:'久坐危害讲解,颈椎工间操带练教学,建立各班组工间操提醒机制。'},
{id:6,type:1,theme:'低盐低脂饮食与血脂管理讲座',date:'2026-06-20',point:'姬圪台作业区医疗点',area:'姬圪台作业区食堂',num:58,lecturer:'曲志明',content:'血脂异常人群配餐原则,食堂减油减盐行动倡议,现场答疑并发放食谱卡。'},
{id:7,type:0,theme:'创伤救护四项技术(止血包扎固定搬运)培训',date:'2026-06-06',point:'姬圪台作业区医疗点',area:'姬圪台联合站各班组',num:48,lecturer:'张秀兰',content:'绷带三角巾包扎、夹板固定与脊柱板搬运分项实操,逐组过关考核通过44人。'},
{id:8,type:1,theme:'无烟单位健康宣教周活动',date:'2026-05-30',point:'安塞采油队长住点医疗室',area:'安塞长住点全区域',num:91,lecturer:'白瑞',content:'世界无烟日主题宣讲,戒烟门诊转介渠道说明,签名承诺活动参与76人。'},
{id:9,type:1,theme:'女性健康两癌筛查知识讲座',date:'2026-05-14',point:'靖边气田医疗点',area:'靖边净化厂女工集中单元',num:39,lecturer:'刘玉梅',content:'乳腺癌宫颈癌筛查指征与周期讲解,预约转诊通道介绍,现场咨询12人次。'},
{id:10,type:0,theme:'受限空间窒息事故急救演练',date:'2026-04-22',point:'靖边气田医疗点',area:'靖边净化厂检修现场',num:35,lecturer:'李桂芳',content:'受限空间窒息事故情景模拟,救援佩戴与心肺复苏衔接流程演练,复盘讲评。'}
{id:1,type:1,theme:'全国高血压日:减盐降血压专题宣教',date:'2026-09-02',point:'桐园作业区医疗点',area:'桐园作业区各井站、联合站',num:86,content:'讲解高血压危害与低盐饮食要点,现场免费测血压42人次,发放限盐勺与宣教折页。'},
{id:2,type:1,theme:'联合国糖尿病日:吃动平衡控血糖',date:'2026-08-24',point:'西峰基地医疗点',area:'基地管理处及后勤单元',num:64,content:'糖尿病饮食换算实操演示,餐后运动处方宣讲,血糖义务检测31人次。'},
{id:3,type:0,theme:'现场急救技能培训(心肺复苏+AED)',date:'2026-08-15',point:'樊学作业区医疗点',area:'樊学作业区倒班点',num:52,content:'CPR操作要领与AED使用流程分组的演练,逐人考核通过47人,发放急救明白卡。'},
{id:4,type:0,theme:'夏季高温防暑与中暑急救培训',date:'2026-07-18',point:'乌审旗采气区医疗点',area:'苏里格南作业区井场',num:73,content:'先兆中暑识别与处置流程实操,防暑药品配备讲解,现场发放藿香正气类物资。'},
{id:5,type:1,theme:'职业人群颈椎健康与工间操推广',date:'2026-07-05',point:'姬圪台作业区医疗点',area:'姬圪台联合站中控、化验室',num:45,content:'久坐危害讲解,颈椎工间操带练教学,建立各班组工间操提醒机制。'},
{id:6,type:1,theme:'低盐低脂饮食与血脂管理讲座',date:'2026-06-20',point:'姬圪台作业区医疗点',area:'姬圪台作业区食堂',num:58,content:'血脂异常人群配餐原则,食堂减油减盐行动倡议,现场答疑并发放食谱卡。'},
{id:7,type:0,theme:'创伤救护四项技术(止血包扎固定搬运)培训',date:'2026-06-06',point:'姬圪台作业区医疗点',area:'姬圪台联合站各班组',num:48,content:'绷带三角巾包扎、夹板固定与脊柱板搬运分项实操,逐组过关考核通过44人。'},
{id:8,type:1,theme:'无烟单位健康宣教周活动',date:'2026-05-30',point:'安塞采油队长住点医疗室',area:'安塞长住点全区域',num:91,content:'世界无烟日主题宣讲,戒烟门诊转介渠道说明,签名承诺活动参与76人。'},
{id:9,type:1,theme:'女性健康两癌筛查知识讲座',date:'2026-05-14',point:'靖边气田医疗点',area:'靖边净化厂女工集中单元',num:39,content:'乳腺癌宫颈癌筛查指征与周期讲解,预约转诊通道介绍,现场咨询12人次。'},
{id:10,type:0,theme:'受限空间窒息事故急救演练',date:'2026-04-22',point:'靖边气田医疗点',area:'靖边净化厂检修现场',num:35,content:'受限空间窒息事故情景模拟,救援佩戴与心肺复苏衔接流程演练,复盘讲评。'}
];
var nextId=11,editingId=null,pendingDelId=null,curTab=1;
/* 当前Tab文案映射:宣教↔培训 */
function T(a,b){return curTab===1?a:b;}
function switchTab(t){
curTab=t;currentPage=1;
document.getElementById('tabEdu').classList.toggle('active',t===1);
document.getElementById('tabTr').classList.toggle('active',t===0);
document.getElementById('thTheme').textContent=T('宣教主题','培训标题');
document.getElementById('thDate').textContent=T('宣教日期','培训日期');
document.getElementById('thContent').textContent=T('宣教内容','培训内容');
document.getElementById('addBtnText').textContent=T('新增宣教记录','新增急救培训');
document.getElementById('lblFilterTheme').textContent=T('宣教主题','培训标题');
document.getElementById('formSecTitle').textContent=T('宣教活动信息','培训活动信息');
document.getElementById('lblTheme').textContent=T('宣教主题','培训标题');
document.getElementById('lblDate').textContent=T('宣教日期','培训日期');
document.getElementById('lblContent').textContent=T('宣教内容','培训内容');
document.getElementById('secPhoto').textContent=T('现场照片','现场培训照片');
document.getElementById('secMaterial').textContent=T('宣教资料','培训资料');
document.getElementById('materialText').textContent=T('点击上传宣教手册 / PPT / 折页等电子资料(支持 PDF、PPT,单个不超过 20MB)','点击上传培训课件 / 操作手册 / 明白卡等电子资料(支持 PDF、PPT,单个不超过 20MB)');
renderTable();
}
/* ===== 异步导出与导出任务 ===== */
var exportLogs=[
@@ -392,7 +416,7 @@ function nowHM(){
return '2026-09-07 '+p(d.getHours())+':'+p(d.getMinutes());
}
function doExport(){
exportLogs.unshift({file:T('健康教育台账导出_','急救培训台账导出_')+nowHM()+'.xlsx',status:'ing',time:'—'});
exportLogs.unshift({file:'活动台账导出_'+nowHM()+'.xlsx',status:'ing',time:'—'});
showToast('导出任务已提交,完成后可在「查看导出任务」下载');
if(document.getElementById('exportDrawer').classList.contains('open'))renderExportList();
setTimeout(function(){
@@ -418,7 +442,7 @@ function askBatchDelete(){
var ids=[].slice.call(document.querySelectorAll('.row-check:checked')).map(function(c){return parseInt(c.value);});
if(!ids.length){showToast('请先勾选要删除的记录');return;}
pendingDelId=null;delMode='batch';
document.getElementById('delText').textContent='确认删除勾选的 '+ids.length+' 条'+T('宣教记录','急救培训')+'?删除后关联的签到记录一并解除关联。';
document.getElementById('delText').textContent='确认删除勾选的 '+ids.length+' 条活动记录?删除后关联的签到记录一并解除关联。';
document.getElementById('delMask').classList.add('open');
window._batchIds=ids;
}
@@ -444,6 +468,12 @@ function currentData(){
&&(!from||e.date>=from)&&(!to||e.date<=to);
});
}
function switchTab(t){
curTab=t;currentPage=1;
document.getElementById('tabEdu').classList.toggle('active',t===1);
document.getElementById('tabTr').classList.toggle('active',t===0);
renderTable();
}
function renderTabCounts(){
document.getElementById('cntEdu').textContent=edus.filter(function(e){return e.type===1;}).length;
document.getElementById('cntTr').textContent=edus.filter(function(e){return e.type===0;}).length;
@@ -458,7 +488,7 @@ function renderTable(){
document.getElementById('prevBtn').disabled=currentPage<=1;
document.getElementById('nextBtn').disabled=currentPage>=totalPages;
var tbody=document.getElementById('tableBody');
if(!pageData.length){tbody.innerHTML='<tr><td colspan="11" class="td-empty">暂无匹配数据</td></tr>';return;}
if(!pageData.length){tbody.innerHTML='<tr><td colspan="10" class="td-empty">暂无匹配数据</td></tr>';return;}
var html='';
pageData.forEach(function(e,i){
html+='<tr>'
@@ -467,7 +497,7 @@ function renderTable(){
+'<td style="font-weight:600;text-align:left;max-width:230px;overflow:hidden;text-overflow:ellipsis;" title="'+e.theme+'">'+e.theme+'</td>'
+'<td>'+e.date+'</td><td>'+e.point+'</td><td>'+pointUnit[e.point]+'</td>'
+'<td style="max-width:170px;overflow:hidden;text-overflow:ellipsis;" title="'+e.area+'">'+e.area+'</td>'
+'<td><span class="badge badge--info">'+e.num+' 人</span></td><td>'+e.lecturer+'</td>'
+'<td><span class="badge badge--info">'+e.num+' 人</span></td>'
+'<td style="text-align:left;max-width:220px;overflow:hidden;text-overflow:ellipsis;" title="'+e.content+'">'+e.content+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openView('+e.id+')">详情</button>'
@@ -497,27 +527,19 @@ function initPointOptions(){
sel.innerHTML='<option value="">请选择医疗点</option>';
allPoints.forEach(function(p){var o=document.createElement('option');o.textContent=p;sel.appendChild(o);});
}
function renderSignImg(hasImg){
var row=document.getElementById('signImgRow');
if(hasImg){
row.innerHTML='<div class="photo-ph" style="border-color:#1890FF;background:#fff;cursor:pointer;" onclick="showToast(\'签字图片查看演示\')">'+SIGN_IMG_THUMB+'</div>';
}else{
row.innerHTML='<div class="photo-ph" id="signImgPh" onclick="showToast(\'签字图片上传功能演示\')">'+SIGN_IMG_PLACEHOLDER+'</div>';
}
}
function openForm(id){
editingId=id||null;
var e=id?edus.find(function(x){return x.id===id;}):null;
document.getElementById('formTitle').textContent=id?((curTab===1?'编辑宣教记录 · ':'编辑急救培训 · ')+e.theme):T('新增宣教记录','新增急救培训');
document.getElementById('formTitle').textContent=id?'编辑活动':'新增活动';
document.querySelector('input[name="eType"][value="'+(e?String(e.type):String(curTab))+'"]').checked=true;
document.getElementById('eTheme').value=e?e.theme:'';
document.getElementById('eDate').value=e?e.date:'2026-09-07';
document.getElementById('eLecturer').value=e?e.lecturer:'';
initPointOptions();
document.getElementById('ePoint').value=e?e.point:'';
document.getElementById('eArea').value=e?e.area:'';
document.getElementById('eNum').value=e?e.num:'';
document.getElementById('eContent').value=e?e.content:'';
renderSignImg(!!e);
resetSign();
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
@@ -526,39 +548,36 @@ function saveForm(){
var date=document.getElementById('eDate').value;
var point=document.getElementById('ePoint').value;
var content=document.getElementById('eContent').value.trim();
if(!theme){showToast('请填写'+T('宣教主题','培训标题'));return;}
if(!date){showToast('请选择'+T('宣教日期','培训日期'));return;}
if(!theme){showToast('请填写活动标题');return;}
if(!date){showToast('请选择开展日期');return;}
if(!point){showToast('请选择开展医疗点');return;}
if(!content){showToast('请填写'+T('宣教内容','培训内容'));return;}
if(!content){showToast('请填写活动内容');return;}
var obj={
id:editingId||nextId++,type:curTab,theme:theme,date:date,point:point,
id:editingId||nextId++,type:parseInt(document.querySelector('input[name="eType"]:checked').value,10),theme:theme,date:date,point:point,
area:document.getElementById('eArea').value||'—',
num:parseInt(document.getElementById('eNum').value)||0,
lecturer:document.getElementById('eLecturer').value||'—',
content:content
};
if(editingId){edus.splice(edus.findIndex(function(x){return x.id===editingId;}),1,obj);}
else{edus.unshift(obj);}
closeDrawer('form');renderTable();
showToast(editingId?T('宣教记录已更新','急救培训已更新'):T('宣教记录已保存,PAD 端签到数据将自动关联','急救培训已保存,PAD 端签到数据将自动关联'));
showToast(editingId?'活动记录已更新':'活动记录已保存,PAD 端签到数据将自动关联');
}
/* ===== 详情 ===== */
function openView(id){
var e=edus.find(function(x){return x.id===id;});
document.getElementById('viewTitle').textContent=T('宣教详情','急救培训详情');
document.getElementById('viewTitle').textContent='活动详情';
function d(k,v,full){return '<div class="detail-item'+(full?' detail-item--full':'')+'"><div class="dk">'+k+'</div><div class="dv">'+(v||'—')+'</div></div>';}
document.getElementById('viewBody').innerHTML=
'<div class="form-section" style="margin-top:0;">活动信息</div>'
+'<div class="detail-grid">'+d(T('宣教主题','培训标题'),e.theme,true)+d(T('宣教日期','培训日期'),e.date)+d('主讲人',e.lecturer)+d('开展医疗点',e.point)+d('所属单位',pointUnit[e.point])+d('覆盖区域',e.area,true)+d('参加人数',e.num+' 人(PAD 端签到回传)')+d(T('宣教内容','培训内容'),e.content,true)+'</div>'
+'<div class="form-section">'+T('现场照片','现场培训照片')+'</div>'
+'<div class="detail-grid">'+d('活动类型',typeMap[e.type],true)+d('活动标题',e.theme,true)+d('开展日期',e.date)+d('开展医疗点',e.point)+d('所属单位',pointUnit[e.point])+d('覆盖区域',e.area,true)+d('参加人数',e.num+' 人(PAD 端签到回传)')+d('活动内容',e.content,true)+'</div>'
+'<div class="form-section">现场照片</div>'
+'<div class="photo-row"><div class="photo-ph">照片 1</div><div class="photo-ph">照片 2</div></div>'
+'<div class="form-section">'+T('宣教资料','培训资料')+'</div>'
+'<div class="upload-box"><svg width="16" height="16" viewBox="0 0 20 20" fill="none"><path d="M4 2h8l4 4v12H4z" stroke="#999" stroke-width="1.4" fill="none"/><path d="M12 2v4h4" stroke="#999" stroke-width="1.4"/></svg>《'+e.theme+'》'+T('宣教手册','培训资料')+'.pdf(演示附件)</div>'
+'<div class="form-section">员工确认签字</div>'
+'<div class="upload-box"><svg width="16" height="16" viewBox="0 0 20 20" fill="none"><path d="M3 17c2-4 4 3 6-2s3 4 8-1" stroke="#999" stroke-width="1.5" fill="none" stroke-linecap="round"/></svg>参加人员在 PAD 端签到屏完成电子确认签字,签到记录自动回传关联本'+T('宣教活动','培训活动')+'</div>'
+'<div class="form-section">签字图片</div>'
+'<div class="photo-row"><div class="photo-ph" style="border-color:#1890FF;background:#fff;cursor:pointer;" onclick="showToast(\'签字图片查看演示\')">'+SIGN_IMG_THUMB+'</div></div>';
+'<div class="form-section">活动资料</div>'
+'<div class="upload-box"><svg width="16" height="16" viewBox="0 0 20 20" fill="none"><path d="M4 2h8l4 4v12H4z" stroke="#999" stroke-width="1.4" fill="none"/><path d="M12 2v4h4" stroke="#999" stroke-width="1.4"/></svg>《'+e.theme+'》活动资料.pdf(演示附件)</div>'
+'<div class="form-section">负责人签字</div>'
+'<div class="sign-view"><span class="sign-view__name">负责人</span><span class="sign-view__note">已签名</span></div>';
document.getElementById('viewMask').classList.add('open');
document.getElementById('viewDrawer').classList.add('open');
}
@@ -567,19 +586,68 @@ function openView(id){
function askDelete(id){
pendingDelId=id;delMode='single';
var e=edus.find(function(x){return x.id===id;});
document.getElementById('delText').textContent='确认删除'+T('宣教记录','急救培训')+'「'+e.theme+'」?删除后关联的签到记录一并解除关联。';
document.getElementById('delText').textContent='确认删除活动记录「'+e.theme+'」?删除后关联的签到记录一并解除关联。';
document.getElementById('delMask').classList.add('open');
}
function confirmDelete(){
if(delMode==='batch'){
edus=edus.filter(function(e){return window._batchIds.indexOf(e.id)===-1;});
closeModal('delMask');renderTable();
showToast('已批量删除 '+window._batchIds.length+' 条'+T('宣教记录','急救培训'));
showToast('已批量删除 '+window._batchIds.length+' 条活动记录');
return;
}
edus=edus.filter(function(e){return e.id!==pendingDelId;});
closeModal('delMask');renderTable();
showToast(T('宣教记录已删除','急救培训已删除'));
showToast('活动记录已删除');
}
/* ===== 负责人签字(手写/图片,参考 medical-pad/health-education-drawer) ===== */
var signType='hand',signDone=false;
function switchSignType(type){
signType=type;
document.getElementById('signHandBox').style.display=type==='hand'?'flex':'none';
document.getElementById('signImgBox').style.display=type==='hand'?'none':'flex';
}
function resetSign(){
signType='hand';signDone=false;
document.querySelector('input[name="signType"][value="hand"]').checked=true;
document.getElementById('signHandBox').style.display='flex';
document.getElementById('signImgBox').style.display='none';
document.getElementById('signHandBox').innerHTML='<svg viewBox="0 0 24 24"><path d="M17 3a2.8 2.8 0 014 4L7.5 20.5 2 22l1.5-5.5z"/></svg>点击签名';
document.getElementById('signImgBox').innerHTML='<svg viewBox="0 0 24 24"><path d="M12 16V4M7 9l5-5 5 5"/><path d="M4 20h16"/></svg>上传签字图片';
}
function openSign(){clearCanvas();document.getElementById('signMask').classList.add('open');}
function closeSign(){document.getElementById('signMask').classList.remove('open');}
function onSignImg(input){
var f=input.files&&input.files[0];
if(!f)return;
var r=new FileReader();
r.onload=function(e){
document.getElementById('signImgBox').innerHTML='<img src="'+e.target.result+'" style="max-width:100%;max-height:80px;object-fit:contain;"><span style="font-size:11px;color:#969EB0;">已上传 · 点击可重新上传</span>';
signDone=true;
};
r.readAsDataURL(f);
}
var signCanvas=null,signCtx=null,drawing=false,hasInk=false;
function initSignCanvas(){
signCanvas=document.getElementById('signCanvas');
signCtx=signCanvas.getContext('2d');
signCtx.lineWidth=3;signCtx.lineCap='round';signCtx.strokeStyle='#333';
signCanvas.onmousedown=function(e){drawing=true;var p=canvasPos(e);signCtx.beginPath();signCtx.moveTo(p.x,p.y);};
signCanvas.onmousemove=function(e){if(!drawing)return;var p=canvasPos(e);signCtx.lineTo(p.x,p.y);signCtx.stroke();hasInk=true;document.getElementById('signHint').style.display='none';};
window.addEventListener('mouseup',function(){drawing=false;});
}
function canvasPos(e){
var r=signCanvas.getBoundingClientRect();
return {x:(e.clientX-r.left)*(signCanvas.width/r.width),y:(e.clientY-r.top)*(signCanvas.height/r.height)};
}
function clearCanvas(){signCtx.clearRect(0,0,signCanvas.width,signCanvas.height);hasInk=false;document.getElementById('signHint').style.display='flex';}
function confirmSign(){
if(!hasInk){showToast('您还没有进行签字');return;}
var name='负责人';
document.getElementById('signHandBox').innerHTML='<span style="font-size:26px;font-family:KaiTi,serif;color:#333;">'+name+'</span><span style="font-size:11px;color:#969EB0;">已签名 · 点击可重签</span>';
signDone=true;
closeSign();
}
/* ===== 通用 ===== */
@@ -594,7 +662,8 @@ function showToast(msg){
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
initSignCanvas();
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.page-header{margin-bottom:16px;display:flex;align-items:center;gap:12px;}
.page-header__back{display:inline-flex;align-items:center;gap:4px;font-size:var(--font-sm);color:var(--primary);text-decoration:none;padding:4px 10px;border:1px solid var(--primary);border-radius:var(--radius-sm);background:#fff;}
@@ -192,19 +193,21 @@ tbody tr:hover{background:#F8FBFF;}
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item active">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -631,7 +634,7 @@ function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
/* ===== 初始化 ===== */
renderHead();
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
@@ -127,19 +128,21 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item active">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -416,7 +419,7 @@ function showToast(msg){
renderTable();
document.addEventListener('click',function(){document.getElementById('orgPanel').classList.remove('open');});
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
@@ -30,17 +30,16 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 16px;display:flex;align-items:center;gap:12px;flex-wrap:nowrap;margin-top:16px;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow-x:auto;}
.filter-bar::-webkit-scrollbar{height:6px;}
.filter-bar::-webkit-scrollbar-thumb{background:#E0E0E0;border-radius:3px;}
.filter-bar{background:#fff;padding:16px 16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:16px;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:6px;flex-shrink:0;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-select{height:36px;min-width:110px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);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;}
@@ -191,19 +190,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item active">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group open" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub active">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -454,7 +455,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):与员工分配名册/慢病台账/随访管理勾稽 ===== */
/* ===== 演示数据:与员工分配名册/慢病台账/随访管理勾稽 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂','姬塬作业区医疗点':'采油一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
@@ -463,16 +464,16 @@ var pointMap={
'采气一厂':['靖边气田医疗点','乌审旗采气区医疗点']
};
var empPool=[
{code:'CQ021578',name:'石维国',sex:'男',age:46,tel:'139****2051',idcard:'6101**********0011',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',group:'高血压'},
{code:'CQ027710',name:'张秀兰',sex:'女',age:45,tel:'136****2248',idcard:'6101**********0022',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',group:'肥胖'},
{code:'CQ019203',name:'曲志明',sex:'男',age:42,tel:'137****8890',idcard:'6101**********0033',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',group:'冠心病'},
{code:'CQ033096',name:'赵万福',sex:'男',age:39,tel:'150****6127',idcard:'6101**********0044',unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',group:'高血压'},
{code:'CQ010564',name:'高建军',sex:'男',age:50,tel:'139****0086',idcard:'6101**********0055',unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',group:'糖尿病'},
{code:'CQ036441',name:'刘玉梅',sex:'女',age:40,tel:'135****7719',idcard:'6101**********0066',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',group:'高血压'},
{code:'CQ029905',name:'呼建国',sex:'男',age:36,tel:'147****9003',idcard:'6101**********0077',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',group:'其他'},
{code:'CQ044871',name:'白瑞',sex:'男',age:31,tel:'155****3316',idcard:'6101**********0088',unit:'采油一厂',dept:'生产技术部',point:'安塞采油队长住点医疗室',group:'其他'},
{code:'CQ022317',name:'徐立成',sex:'男',age:44,tel:'133****4860',idcard:'6101**********0099',unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',group:'肥胖'},
{code:'CQ041822',name:'闫婷',sex:'女',age:34,tel:'186****5527',idcard:'6101**********0100',unit:'第四采油厂',dept:'健康管理部',point:'西峰基地医疗点',group:'其他'}
{code:'CQ021578',name:'石维国',sex:'男',age:46,tel:'13928562051',idcard:'610102197804154213',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',group:'高血压'},
{code:'CQ027710',name:'张秀兰',sex:'女',age:45,tel:'13649162248',idcard:'610102197912064629',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',group:'肥胖'},
{code:'CQ019203',name:'曲志明',sex:'男',age:42,tel:'13720568890',idcard:'610102198209285310',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',group:'冠心病'},
{code:'CQ033096',name:'赵万福',sex:'男',age:39,tel:'15029836127',idcard:'610102198511224737',unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',group:'高血压'},
{code:'CQ010564',name:'高建军',sex:'男',age:50,tel:'13956200086',idcard:'610102197406304419',unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',group:'糖尿病'},
{code:'CQ036441',name:'刘玉梅',sex:'女',age:40,tel:'13572867719',idcard:'610102198408214523',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',group:'高血压'},
{code:'CQ029905',name:'呼建国',sex:'男',age:36,tel:'14729539003',idcard:'610102198805174635',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',group:'其他'},
{code:'CQ044871',name:'白瑞',sex:'男',age:31,tel:'15529403316',idcard:'610102199307104517',unit:'采油一厂',dept:'生产技术部',point:'安塞采油队长住点医疗室',group:'其他'},
{code:'CQ022317',name:'徐立成',sex:'男',age:44,tel:'13319284860',idcard:'610102198207153412',unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',group:'肥胖'},
{code:'CQ041822',name:'闫婷',sex:'女',age:34,tel:'18629135527',idcard:'610102199001184928',unit:'第四采油厂',dept:'健康管理部',point:'西峰基地医疗点',group:'其他'}
];
function E(code){return empPool.find(function(e){return e.code===code;});}
/* 单次巡诊任务:status 1待随访 2已逾期 3已完成 */
@@ -891,7 +892,7 @@ function showToast(msg){
}
switchTab('once');
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
+32 -29
View File
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:12px;flex-wrap:nowrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
@@ -185,19 +186,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item active">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -487,7 +490,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):随访对象为已分配员工 ===== */
/* ===== 演示数据:随访对象为已分配员工 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
@@ -498,18 +501,18 @@ var pointMap={
var doctorPool=['石维国','李桂芳','曲志明','张秀兰','高建军','闫婷','刘玉梅'];
/* 可选随访员工池(与员工查询/员工分配一致的12人已分配名册) */
var empPool=[
{code:'CQ021578',name:'石维国',sex:'男',age:46,unit:'采油一厂',dept:'樊学作业区',tel:'139****2051',point:'樊学作业区医疗点',chronic:'血压偏高'},
{code:'CQ032144',name:'李桂芳',sex:'女',age:38,unit:'采油一厂',dept:'樊学作业区',tel:'138****7742',point:'樊学作业区医疗点',chronic:''},
{code:'CQ045130',name:'杨慧敏',sex:'女',age:33,unit:'采油一厂',dept:'樊学作业区',tel:'188****7214',point:'樊学作业区医疗点',chronic:''},
{code:'CQ019203',name:'曲志明',sex:'男',age:42,unit:'采油二厂',dept:'姬圪台联合站',tel:'137****8890',point:'姬圪台作业区医疗点',chronic:''},
{code:'CQ027710',name:'张秀兰',sex:'女',age:45,unit:'采油二厂',dept:'姬圪台联合站',tel:'136****2248',point:'姬圪台作业区医疗点',chronic:'超重/血脂偏高'},
{code:'CQ033096',name:'赵万福',sex:'男',age:39,unit:'采油二厂',dept:'桐园作业区',tel:'150****6127',point:'桐园作业区医疗点',chronic:'高血压2级'},
{code:'CQ010564',name:'高建军',sex:'男',age:50,unit:'第四采油厂',dept:'基地管理处',tel:'139****0086',point:'西峰基地医疗点',chronic:'2型糖尿病/超重'},
{code:'CQ041822',name:'闫婷',sex:'女',age:34,unit:'第四采油厂',dept:'健康管理部',tel:'186****5527',point:'西峰基地医疗点',chronic:''},
{code:'CQ036441',name:'刘玉梅',sex:'女',age:40,unit:'采气一厂',dept:'靖边净化厂',tel:'135****7719',point:'靖边气田医疗点',chronic:''},
{code:'CQ029905',name:'呼建国',sex:'男',age:36,unit:'采气一厂',dept:'靖边净化厂',tel:'147****9003',point:'靖边气田医疗点',chronic:''},
{code:'CQ044871',name:'白瑞',sex:'男',age:31,unit:'采油一厂',dept:'生产技术部',tel:'155****3316',point:'安塞采油队长住点医疗室',chronic:''},
{code:'CQ022317',name:'徐立成',sex:'男',age:44,unit:'采气一厂',dept:'苏里格南作业区',tel:'133****4860',point:'乌审旗采气区医疗点',chronic:'超重'}
{code:'CQ021578',name:'石维国',sex:'男',age:46,unit:'采油一厂',dept:'樊学作业区',tel:'13928562051',point:'樊学作业区医疗点',chronic:'血压偏高'},
{code:'CQ032144',name:'李桂芳',sex:'女',age:38,unit:'采油一厂',dept:'樊学作业区',tel:'13836547742',point:'樊学作业区医疗点',chronic:''},
{code:'CQ045130',name:'杨慧敏',sex:'女',age:33,unit:'采油一厂',dept:'樊学作业区',tel:'18829587214',point:'樊学作业区医疗点',chronic:''},
{code:'CQ019203',name:'曲志明',sex:'男',age:42,unit:'采油二厂',dept:'姬圪台联合站',tel:'13720568890',point:'姬圪台作业区医疗点',chronic:''},
{code:'CQ027710',name:'张秀兰',sex:'女',age:45,unit:'采油二厂',dept:'姬圪台联合站',tel:'13649162248',point:'姬圪台作业区医疗点',chronic:'超重/血脂偏高'},
{code:'CQ033096',name:'赵万福',sex:'男',age:39,unit:'采油二厂',dept:'桐园作业区',tel:'15029836127',point:'桐园作业区医疗点',chronic:'高血压2级'},
{code:'CQ010564',name:'高建军',sex:'男',age:50,unit:'第四采油厂',dept:'基地管理处',tel:'13956200086',point:'西峰基地医疗点',chronic:'2型糖尿病/超重'},
{code:'CQ041822',name:'闫婷',sex:'女',age:34,unit:'第四采油厂',dept:'健康管理部',tel:'18629135527',point:'西峰基地医疗点',chronic:''},
{code:'CQ036441',name:'刘玉梅',sex:'女',age:40,unit:'采气一厂',dept:'靖边净化厂',tel:'13572867719',point:'靖边气田医疗点',chronic:''},
{code:'CQ029905',name:'呼建国',sex:'男',age:36,unit:'采气一厂',dept:'靖边净化厂',tel:'14729539003',point:'靖边气田医疗点',chronic:''},
{code:'CQ044871',name:'白瑞',sex:'男',age:31,unit:'采油一厂',dept:'生产技术部',tel:'15529403316',point:'安塞采油队长住点医疗室',chronic:''},
{code:'CQ022317',name:'徐立成',sex:'男',age:44,unit:'采气一厂',dept:'苏里格南作业区',tel:'13319284860',point:'乌审旗采气区医疗点',chronic:'超重'}
];
/* 随访记录:s=症状 form=形式 confirm=员工确认 */
var follows=[
@@ -961,7 +964,7 @@ renderTable();
var q=new URLSearchParams(location.search);
if(q.get('action')==='new'){ setTimeout(function(){ openForm(); },100); }
})();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>报告解读管理 | 一线医疗</title>
<title>报告解读记录 | 一线医疗</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;font-size:var(--font-sm);background:#F5F7FA;color:#333;min-width:1280px;}
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
@@ -105,33 +106,27 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.modal__close:hover{color:var(--text-primary);}
.modal__body{flex:1;overflow-y:auto;padding:20px 24px;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
/* 详情弹窗左右两栏 */
/* 解读详情弹窗左右两栏(参考 daily-interpret) */
.report-cols{display:flex;gap:0;height:calc(90vh - 130px);}
.report-cols__left{width:50%;overflow-y:auto;padding-right:20px;border-right:1px dashed var(--border);}
.report-cols__right{width:50%;overflow-y:auto;padding-left:20px;}
.report-title{font-size:var(--font-base);font-weight:700;padding:10px 0 10px 12px;position:relative;margin-top:4px;}
.report-title::before{content:'';position:absolute;left:0;top:13px;width:6px;height:18px;background:#E3CB2C;border-radius:2px;}
.report-sub{font-size:var(--font-sm);color:var(--text-secondary);line-height:1.7;padding:0 12px 8px;}
.report-group{margin-top:16px;}
.report-group__name{font-weight:700;font-size:var(--font-base);padding:10px 12px;background:#F6F6F6;border-radius:var(--radius-sm) var(--radius-sm) 0 0;}
.report-cat{padding-left:20px;}
.report-cat__name{font-size:var(--font-sm);font-weight:700;padding:10px 0 8px 12px;position:relative;}
.report-cat__name::before{content:'';position:absolute;left:0;top:12px;width:6px;height:16px;background:#02C2A6;border-radius:2px;}
.report-ul{padding:0;margin:0;width:100%;display:flex;}
.report-ul li{flex:1;text-align:center;padding:6px 0;font-size:var(--font-sm);border:1px solid #E6E6E6;border-left:none;border-top:none;}
.report-ul li:first-child{border-left:1px solid #E6E6E6;}
.report-ul.report-ul--head{font-weight:700;}
.report-ul.report-ul--head li{background:#F0F2FF;}
.report-ul.report-ul--head li:first-child{background:#F0F2FF;}
.report-ul:not(.report-ul--head) li:first-child{background:#F6F6F6;}
.emp-basic{display:flex;flex-wrap:wrap;gap:4px 0;}
.emp-basic div{font-size:var(--font-sm);color:var(--text-secondary);padding:4px 0;}
.emp-basic b{color:var(--text-primary);font-weight:600;}
.emp-basic .w25{width:25%;}
.emp-basic .w50{width:50%;}
.emp-basic .w100{width:100%;}
.advise-box{background:#F4F4F4;border-radius:var(--radius-sm);padding:10px;font-size:var(--font-sm);line-height:1.7;color:var(--text-primary);min-height:88px;}
.sign-box{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:16px;text-align:center;min-height:100px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;}
.report-cols__left{width:50%;overflow-y:auto;padding:16px 18px;border-right:1px dashed var(--border);}
.report-cols__right{width:50%;overflow-y:auto;padding:16px 18px;background:#FAFBFD;}
.rpt-title{font-size:15px;font-weight:600;color:#333;margin:14px 0 8px;padding-left:8px;border-left:4px solid #E3CB2C;}
.rpt-title--sub{border-left-color:#02C2A6;}
.rpt-text{font-size:12px;color:#4A5A7F;line-height:1.9;}
.rpt-major{background:#F6F6F6;border-radius:8px;padding:8px 12px;font-size:13px;font-weight:600;color:#333;margin:12px 0 8px;}
.rpt-table{width:100%;min-width:0;border-collapse:collapse;font-size:12px;margin-bottom:6px;}
.rpt-table th{background:#F0F2FF;color:#4A5A7F;font-weight:500;padding:7px 10px;text-align:left;white-space:nowrap;}
.rpt-table td{padding:7px 10px;border-bottom:1px solid #F0F2F5;color:#333;white-space:normal;word-break:break-word;}
.rpt-table td.abnormal{color:#F5222D;font-weight:600;}
.rpt-table thead th:last-child{position:static;right:auto;z-index:auto;background:#F0F2FF;box-shadow:none;}
.rpt-table tbody td:last-child{position:static;right:auto;z-index:auto;background:transparent;box-shadow:none;}
.form-card{background:#fff;border-radius:10px;padding:14px 16px;margin-bottom:12px;box-shadow:0 2px 8px rgba(74,90,127,.05);}
.form-card__title{font-size:14px;font-weight:600;color:#333;margin-bottom:10px;padding-left:8px;border-left:3px solid #1890FF;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px 18px;font-size:12px;color:#4A5A7F;line-height:2.1;}
.form-grid b{color:#333;font-weight:500;}
.sign-view{display:flex;flex-direction:column;align-items:flex-start;gap:6px;}
.sign-view__name{font-family:KaiTi,serif;font-size:26px;color:#333;}
.sign-view__note{font-size:11px;color:#969EB0;}
.group-badge{display:inline-block;padding:2px 12px;border-radius:10px;font-size:var(--font-sm);font-weight:600;}
</style>
</head>
@@ -159,19 +154,21 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item active">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item active">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -180,7 +177,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>员工姓名</label>
<label>姓名</label>
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
@@ -188,7 +185,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<input class="filter-input" id="filterCode" placeholder="请输入员工编号" style="min-width:110px;">
</div>
<div class="filter-item">
<label>员工所属单位</label>
<label>单位部门</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
@@ -254,7 +251,7 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
<table>
<thead>
<tr>
<th>序号</th><th>解读员工</th><th>员工编号</th><th>性别</th><th>年龄</th><th>员工所属单位</th><th>员工所属部门</th><th>分配医疗点</th><th>医疗点所属单位</th><th>体检年份</th><th>解读医生</th><th>解读日期</th><th>操作</th>
<th>序号</th><th>姓名</th><th>员工编号</th><th>性别</th><th>年龄</th><th>所属单位</th><th>所属部门</th><th>分配医疗点</th><th>医疗点所属单位</th><th>体检年份</th><th>解读医生</th><th>解读日期</th><th>操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
@@ -270,41 +267,42 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
</div>
</main>
<!-- [交互] 解读详情弹窗(左右两栏) -->
<!-- [交互] 解读详情弹窗(只读,参考 daily-interpret) -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
<div class="modal__header"><span id="detailTitle">解读详情</span><button class="modal__close" onclick="closeModal('detailMask')">×</button></div>
<div class="modal__body" style="padding:0;">
<div class="report-cols">
<div class="report-cols__left">
<div class="report-title">体检结论</div>
<div class="report-sub" id="dConclusion"></div>
<div class="report-title">体检建议</div>
<div class="report-sub" id="dSuggest"></div>
<div class="rpt-title">体检结论</div>
<div class="rpt-text" id="dConclusion"></div>
<div class="rpt-title">体检建议</div>
<div class="rpt-text" id="dSuggest"></div>
<div id="dReport"></div>
</div>
<div class="report-cols__right">
<div class="report-title">员工基本信息</div>
<div class="emp-basic" id="dEmp"></div>
<div class="report-title">综合解读建议</div>
<div class="advise-box" id="dAdvise"></div>
<div class="report-title">五类人群重新分布</div>
<div style="display:flex;padding:6px 0 10px;gap:16px;">
<span id="dGroupBefore" style="font-size:var(--font-sm);color:var(--text-secondary);"></span>
<span id="dGroupAfter" style="font-size:var(--font-sm);color:var(--text-secondary);"></span>
<div class="form-card">
<div class="form-card__title">员工基本信息</div>
<div class="form-grid" id="dEmp"></div>
</div>
<div class="report-title">员工确认签字</div>
<div id="dSign"></div>
<div style="display:flex;width:100%;margin-top:16px;">
<div style="width:50%;">
<div class="report-title" style="margin-top:0;">解读医生</div>
<div style="padding-left:12px;" id="dDoctor"></div>
<div class="form-card">
<div class="form-card__title">解读信息</div>
<div class="form-grid">
<div>解读医生:<b id="dDoctor"></b></div><div>解读日期:<b id="dDate"></b></div>
</div>
<div style="width:50%;">
<div class="report-title" style="margin-top:0;">解读日期</div>
<div style="padding-left:12px;" id="dDate"></div>
</div>
<div class="form-card">
<div class="form-card__title">解读意见</div>
<div style="font-size:13px;color:#333;line-height:1.9;" id="dAdvise"></div>
<div style="display:flex;gap:16px;margin-top:10px;">
<span id="dGroupBefore" style="font-size:12px;color:#4A5A7F;"></span>
<span id="dGroupAfter" style="font-size:12px;color:#4A5A7F;"></span>
</div>
</div>
<div class="form-card">
<div class="form-card__title">员工确认签字</div>
<div id="dSign"></div>
</div>
</div>
</div>
</div>
@@ -351,19 +349,30 @@ var records=[
{id:7,code:'CQ022317',name:'徐立成',sex:'男',age:44,unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',year:'2025',doctor:'刘玉梅',date:'2025-10-28',summary:'BMI 26.8;尿酸 438μmol/L;血压正常高值',advise:'体重与尿酸共管,清淡饮食,多饮水。',group:'d',beforeGroup:'d'}
];
/* 体检报告明细模板 */
/* 体检报告明细模板(参考 daily-interpret) */
function buildReport(r){
return {
conclusion: r.summary,
suggest: '结合既往史与生活方式,建议定期复查并保持健康管理。',
groups: [
{analysisName:'一般检查', cats:[
{cls:'体格检查', rows:[{name:'血压',result:'128/82',unit:'mmHg',ref:'<140/90'},{name:'心率',result:'74',unit:'/',ref:'60-100'}]},
{cls:'人体测量', rows:[{name:'身高',result:'172',unit:'cm',ref:''},{name:'体重',result:'78',unit:'kg',ref:''},{name:'BMI',result:'26.4',unit:'',ref:'18.5-23.9'},{name:'腰围',result:'88',unit:'cm',ref:'<85'}]}
conclusion: r.summary || '各项指标未见明显异常',
suggest: '低盐低脂饮食,规律监测血压并记录;3个月内复查血脂、血糖、糖化血红蛋白;每周中等强度运动不少于150分钟;如出现头晕、心悸等症状及时就诊。',
majors: [
{name:'实验室检查', cats:[
{cls:'生化全套', rows:[
{name:'空腹血糖',result:'7.8 ↑',unit:'mmol/L',ref:'3.9~6.1',abnormal:true},
{name:'血清总胆固醇',result:'6.12',unit:'mmol/L',ref:'0~5.2',abnormal:true},
{name:'血清甘油三酯',result:'2.35',unit:'mmol/L',ref:'0~1.7',abnormal:true},
{name:'糖化血红蛋白',result:'6.9',unit:'%',ref:'4~6.5',abnormal:true}
]},
{cls:'血常规', rows:[
{name:'白细胞计数',result:'6.8',unit:'10^9/L',ref:'3.5~9.5'},
{name:'血红蛋白',result:'145',unit:'g/L',ref:'130~175'},
{name:'血小板计数',result:'210',unit:'10^9/L',ref:'125~350'}
]}
]},
{analysisName:'实验室检查', cats:[
{cls:'血常规', rows:[{name:'白细胞',result:'6.1',unit:'×10⁹/L',ref:'3.5-9.5'},{name:'血红蛋白',result:'142',unit:'g/L',ref:'130-175'},{name:'血小板',result:'235',unit:'×10⁹/L',ref:'125-350'}]},
{cls:'生化检验', rows:[{name:'空腹血糖',result:'5.8',unit:'mmol/L',ref:'3.9-6.1'},{name:'总胆固醇',result:'5.6',unit:'mmol/L',ref:'<5.2'},{name:'甘油三酯',result:'1.7',unit:'mmol/L',ref:'<1.7'},{name:'尿酸',result:'398',unit:'μmol/L',ref:'208-428'}]}
{name:'辅助检查', cats:[
{cls:'心电图', rows:[
{name:'窦性心律',result:'未见异常',unit:'—',ref:'—'},
{name:'ST-T 改变',result:'轻度异常',unit:'—',ref:'—',abnormal:true}
]}
]}
]
};
@@ -526,26 +535,26 @@ function turnPage(d){
/* ===== 详情弹窗 ===== */
function renderReportHtml(report){
if(!report.groups||!report.groups.length)return '<div class="td-empty">暂无体检数据</div>';
if(!report.majors||!report.majors.length)return '<div class="td-empty">暂无体检数据</div>';
var html='';
report.groups.forEach(function(g){
html+='<div class="report-group"><div class="report-group__name">'+g.analysisName+'</div>';
g.cats.forEach(function(c){
html+='<div class="report-cat"><div class="report-cat__name">'+c.cls+'</div>';
html+='<ul class="report-ul report-ul--head"><li>名称</li><li>结果</li><li>单位</li><li>参考值</li></ul>';
report.majors.forEach(function(m){
html+='<div class="rpt-major">'+m.name+'</div>';
m.cats.forEach(function(c){
html+='<div class="rpt-title rpt-title--sub">'+c.cls+'</div>';
html+='<table class="rpt-table"><tr><th>名称</th><th>结果</th><th>单位</th><th>参考值</th></tr>';
c.rows.forEach(function(row){
html+='<ul class="report-ul"><li>'+row.name+'</li><li>'+row.result+'</li><li>'+row.unit+'</li><li>'+row.ref+'</li></ul>';
html+='<tr><td>'+row.name+'</td><td class="'+(row.abnormal?'abnormal':'')+'">'+row.result+'</td><td>'+row.unit+'</td><td>'+row.ref+'</td></tr>';
});
html+='</div>';
html+='</table>';
});
html+='</div>';
});
return html;
}
function empBasicHtml(r){
return '<div class="w25">姓名<b> '+r.name+'</b></div><div class="w25">性别<b> '+r.sex+'</b></div><div class="w25">年龄<b> '+r.age+'</b></div><div class="w25">员工编号<b> '+r.code+'</b></div>'
+'<div class="w50">所属单位<b> '+r.unit+'</b></div><div class="w50">所属部门<b> '+r.dept+'</b></div>'
+'<div class="w100">慢病类型<b> '+(CHRONIC[r.code]||'无')+'</b></div>';
return '<div>姓名<b>'+r.name+'</b></div><div>性别<b>'+r.sex+'</b></div>'
+'<div>年龄:<b>'+r.age+' 岁</b></div><div>员工编号:<b>'+r.code+'</b></div>'
+'<div style="grid-column:1/-1;">所属单位:<b>'+r.unit+'</b> 所属部门:<b>'+r.dept+'</b></div>'
+'<div style="grid-column:1/-1;">体检年份:<b>'+r.year+'年</b> 报告:'+r.year+'年体检报告</div>';
}
function groupBadgeHtml(v){
var color=GROUP_BADGE[v]||'#999';
@@ -562,7 +571,7 @@ function openDetail(id){
document.getElementById('dAdvise').textContent=r.advise||'—';
document.getElementById('dGroupBefore').innerHTML='解读前:'+groupBadgeHtml(r.beforeGroup);
document.getElementById('dGroupAfter').innerHTML='解读后:'+groupBadgeHtml(r.group);
document.getElementById('dSign').innerHTML='<div class="sign-box" style="min-height:80px;"><svg width="180" height="52" viewBox="0 0 180 52"><path d="M10 38c14-20 20 12 32-6 9-14 14 16 26-2 8-12 12 10 22-2 8-12 14 8 26 2" stroke="#14213D" stroke-width="2.4" fill="none" stroke-linecap="round"/></svg><div style="font-size:var(--font-xs);color:var(--text-placeholder);">'+r.name+' · '+r.date+' 于 PAD 端确认</div></div>';
document.getElementById('dSign').innerHTML='<div class="sign-view"><span class="sign-view__name">'+r.name+'</span></div>';
document.getElementById('dDoctor').textContent=r.doctor||'—';
document.getElementById('dDate').textContent=r.date||'—';
document.getElementById('detailMask').classList.add('open');
@@ -576,7 +585,7 @@ function showToast(msg){
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
+248 -125
View File
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.filter-bar{background:#fff;padding:16px 24px;display:flex;align-items:center;gap:16px;flex-wrap:wrap;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
.filter-item{display:flex;align-items:center;gap:8px;}
@@ -125,34 +126,51 @@ textarea.form-control{height:88px;padding:8px 10px;resize:vertical;line-height:1
select.form-control{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 10px center;appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;}
.section-title{display:flex;align-items:center;gap:8px;font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin:16px 0 10px;}
.section-title::before{content:'';width:4px;height:16px;background:var(--primary);border-radius:2px;}
/* 解读弹窗左右两栏 */
/* 解读弹窗左右两栏(报告与表单样式参考 medical-pad/interpret-drawer) */
.report-cols{display:flex;gap:0;height:calc(90vh - 130px);}
.report-cols__left{width:50%;overflow-y:auto;padding-right:20px;border-right:1px dashed var(--border);}
.report-cols__right{width:50%;overflow-y:auto;padding-left:20px;}
.report-title{font-size:var(--font-base);font-weight:700;padding:10px 0 10px 12px;position:relative;margin-top:4px;}
.report-title::before{content:'';position:absolute;left:0;top:13px;width:6px;height:18px;background:#E3CB2C;border-radius:2px;}
.report-sub{font-size:var(--font-sm);color:var(--text-secondary);line-height:1.7;padding:0 12px 8px;}
.report-group{margin-top:16px;}
.report-group__name{font-weight:700;font-size:var(--font-base);padding:10px 12px;background:#F6F6F6;border-radius:var(--radius-sm) var(--radius-sm) 0 0;}
.report-cat{padding-left:20px;}
.report-cat__name{font-size:var(--font-sm);font-weight:700;padding:10px 0 8px 12px;position:relative;}
.report-cat__name::before{content:'';position:absolute;left:0;top:12px;width:6px;height:16px;background:#02C2A6;border-radius:2px;}
.report-ul{padding:0;margin:0;width:100%;display:flex;}
.report-ul li{flex:1;text-align:center;padding:6px 0;font-size:var(--font-sm);border:1px solid #E6E6E6;border-left:none;border-top:none;}
.report-ul li:first-child{border-left:1px solid #E6E6E6;}
.report-ul.report-ul--head{font-weight:700;}
.report-ul.report-ul--head li{background:#F0F2FF;}
.report-ul.report-ul--head li:first-child{background:#F0F2FF;}
.report-ul:not(.report-ul--head) li:first-child{background:#F6F6F6;}
.emp-basic{display:flex;flex-wrap:wrap;gap:4px 0;}
.emp-basic div{font-size:var(--font-sm);color:var(--text-secondary);padding:4px 0;}
.emp-basic b{color:var(--text-primary);font-weight:600;}
.emp-basic .w25{width:25%;}
.emp-basic .w50{width:50%;}
.emp-basic .w100{width:100%;}
.advise-box{background:#F4F4F4;border-radius:var(--radius-sm);padding:10px;font-size:var(--font-sm);line-height:1.7;color:var(--text-primary);min-height:88px;}
.sign-box{border:1px dashed var(--border);border-radius:var(--radius-sm);padding:16px;text-align:center;min-height:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;}
.report-cols__left{width:50%;overflow-y:auto;padding:16px 18px;border-right:1px dashed var(--border);}
.report-cols__right{width:50%;overflow-y:auto;padding:16px 18px;background:#FAFBFD;}
.rpt-title{font-size:15px;font-weight:600;color:#333;margin:14px 0 8px;padding-left:8px;border-left:4px solid #E3CB2C;}
.rpt-title--sub{border-left-color:#02C2A6;}
.rpt-text{font-size:12px;color:#4A5A7F;line-height:1.9;}
.rpt-major{background:#F6F6F6;border-radius:8px;padding:8px 12px;font-size:13px;font-weight:600;color:#333;margin:12px 0 8px;}
.rpt-table{width:100%;min-width:0;border-collapse:collapse;font-size:12px;margin-bottom:6px;}
.rpt-table th{background:#F0F2FF;color:#4A5A7F;font-weight:500;padding:7px 10px;text-align:left;white-space:nowrap;}
.rpt-table td{padding:7px 10px;border-bottom:1px solid #F0F2F5;color:#333;white-space:normal;word-break:break-word;}
.rpt-table td.abnormal{color:#F5222D;font-weight:600;}
.rpt-table thead th:last-child{position:static;right:auto;z-index:auto;background:#F0F2FF;box-shadow:none;}
.rpt-table tbody td:last-child{position:static;right:auto;z-index:auto;background:transparent;box-shadow:none;}
.form-card{background:#fff;border-radius:10px;padding:14px 16px;margin-bottom:12px;box-shadow:0 2px 8px rgba(74,90,127,.05);}
.form-card__title{font-size:14px;font-weight:600;color:#333;margin-bottom:10px;padding-left:8px;border-left:3px solid #1890FF;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px 18px;font-size:12px;color:#4A5A7F;line-height:2.1;}
.form-grid b{color:#333;font-weight:500;}
.fld{display:flex;align-items:flex-start;gap:8px;font-size:13px;color:#4A5A7F;margin-bottom:12px;}
.fld__label{width:96px;text-align:right;flex-shrink:0;padding-top:6px;}
.fld__req{color:#FD2121;}
.fld textarea,.fld select{flex:1;border:1px solid #D9DEE8;border-radius:6px;padding:7px 10px;font-size:13px;color:#333;outline:none;font-family:inherit;min-width:0;}
.fld textarea{resize:none;height:76px;}
.fld textarea:focus,.fld select:focus{border-color:#1890FF;}
.radio{display:flex;gap:16px;align-items:center;font-size:13px;color:#333;}
.radio input{accent-color:#1890FF;width:14px;height:14px;}
.sign-box{border:1px dashed #B7C3D6;border-radius:8px;background:#F7F9FC;height:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;color:#969EB0;font-size:13px;cursor:pointer;}
.sign-box svg{width:22px;height:22px;stroke:#969EB0;fill:none;stroke-width:2;}
.sign-view{display:flex;flex-direction:column;align-items:flex-start;gap:6px;}
.sign-view__name{font-family:KaiTi,serif;font-size:26px;color:#333;}
.sign-view__note{font-size:11px;color:#969EB0;}
.group-badge{display:inline-block;padding:2px 12px;border-radius:10px;font-size:var(--font-sm);font-weight:600;}
/* 手写签名弹窗 */
.sign-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:970;align-items:center;justify-content:center;}
.sign-mask.open{display:flex;}
.sign-modal{background:#fff;border-radius:12px;width:720px;display:flex;flex-direction:column;box-shadow:0 12px 40px rgba(0,0,0,.25);}
.sign-modal__head{padding:15px 20px;border-bottom:1px solid #F0F2F5;display:flex;align-items:center;}
.sign-modal__title{font-size:15px;font-weight:600;color:#333;flex:1;}
.sign-modal__close{cursor:pointer;color:#999;font-size:18px;border:none;background:none;}
.sign-modal__foot{padding:13px 20px;border-top:1px solid #F0F2F5;display:flex;justify-content:flex-end;gap:10px;}
.canvas-wrap{position:relative;margin:20px;border:1px solid #DDD;background:#EEE;border-radius:6px;overflow:hidden;}
.canvas-wrap canvas{display:block;width:100%;height:260px;cursor:crosshair;}
.canvas-hint{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;color:#BBB;font-size:14px;pointer-events:none;}
.eraser{position:absolute;right:12px;bottom:12px;width:40px;height:40px;border-radius:8px;border:1px solid #D9DEE8;background:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;}
.eraser svg{width:20px;height:20px;stroke:#4A5A7F;fill:none;stroke-width:2;}
</style>
</head>
<body>
@@ -179,19 +197,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup open">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item active">解读任务</a>
<div class="sidebar-group open" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub active">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -204,7 +224,11 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<input class="filter-input" id="filterName" placeholder="请输入姓名">
</div>
<div class="filter-item">
<label>员工所属单位</label>
<label>员工编号</label>
<input class="filter-input" id="filterCode" placeholder="请输入员工编号">
</div>
<div class="filter-item">
<label>单位部门</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
@@ -263,8 +287,6 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<button class="btn btn-default" onclick="openExport()">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="2" y="2" width="10" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/><path d="M4 5h6M4 7.5h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>查看导出记录
</button>
<button class="btn btn-primary" onclick="openGenRecord()">生成记录</button>
<button class="btn btn-primary" id="btnResetTask" onclick="resetTask()">重新生成解读任务</button>
</div>
<div class="seg">
<button class="active" data-v="0" onclick="setStatusGroup(this,'0')">全部</button>
@@ -296,30 +318,50 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</main>
<!-- [交互] 体检报告解读弹窗(可编辑) -->
<!-- [交互] 体检报告解读弹窗(可编辑,内容参考 medical-pad/interpret-drawer) -->
<div class="modal-mask" id="intMask">
<div class="modal--wide">
<div class="modal__header"><span id="intTitle">体检报告解读</span><button class="modal__close" onclick="closeModal('intMask')">×</button></div>
<div class="modal__body" style="padding:0;">
<div class="report-cols">
<div class="report-cols__left">
<div class="report-title">体检结论</div>
<div class="report-sub" id="intConclusion"></div>
<div class="report-title">体检建议</div>
<div class="report-sub" id="intSuggest"></div>
<div class="rpt-title">体检结论</div>
<div class="rpt-text" id="intConclusion"></div>
<div class="rpt-title">体检建议</div>
<div class="rpt-text" id="intSuggest"></div>
<div id="intReport"></div>
</div>
<div class="report-cols__right">
<div class="report-title">员工基本信息</div>
<div class="emp-basic" id="intEmp"></div>
<div class="report-title">综合解读建议</div>
<textarea class="form-control" id="intAdvise" placeholder="结合体检指标、既往史与生活方式,给出个体化综合解读与建议"></textarea>
<div class="report-title">五类人群重新分类</div>
<select class="form-control" id="intGroup" style="max-width:280px;"></select>
<div class="report-title">员工确认签字</div>
<div class="sign-box" id="signBox">
<div id="signHolder" style="color:var(--text-placeholder);">本人已确认健康随访</div>
<button class="btn btn-default" id="signBtn" onclick="doSign()">员工签名</button>
<div class="form-card">
<div class="form-card__title">员工基本信息</div>
<div class="form-grid" id="intEmp"></div>
</div>
<div class="form-card">
<div class="form-card__title">解读意见</div>
<div class="fld"><span class="fld__label"><span class="fld__req">*</span>综合解读建议</span><textarea id="intAdvise" placeholder="请输入综合解读建议"></textarea></div>
<div class="fld"><span class="fld__label">五类人群分类</span><select id="intGroup"></select></div>
</div>
<div class="form-card">
<div class="form-card__title">员工确认签字</div>
<div style="font-size:12px;color:#969EB0;margin-bottom:8px;">本人已确认健康随访</div>
<div class="fld"><span class="fld__label"><span class="fld__req">*</span>签字</span>
<div id="signField" style="flex:1;">
<div class="radio" style="margin-bottom:10px;">
<label><input type="radio" name="signType" value="hand" checked onclick="switchSignType('hand')">手写签名</label>
<label><input type="radio" name="signType" value="img" onclick="switchSignType('img')">签字图片</label>
</div>
<div class="sign-box" id="signHandBox" onclick="openSignModal()">
<svg viewBox="0 0 24 24"><path d="M17 3a2.8 2.8 0 014 4L7.5 20.5 2 22l1.5-5.5z"/></svg>
点击签名
</div>
<div class="sign-box" id="signImgBox" style="display:none;" onclick="document.getElementById('signImgInput').click()">
<svg viewBox="0 0 24 24"><path d="M12 16V4M7 9l5-5 5 5"/><path d="M4 20h16"/></svg>
上传签字图片
</div>
<input type="file" id="signImgInput" accept="image/*" style="display:none;" onchange="onSignImg(this)">
</div>
<div id="signLockView" style="flex:1;display:none;"></div>
</div>
</div>
</div>
</div>
@@ -331,6 +373,22 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</div>
<!-- [交互] 手写签名弹窗 -->
<div class="sign-mask" id="signMask">
<div class="sign-modal">
<div class="sign-modal__head"><span class="sign-modal__title">签名</span><button class="sign-modal__close" onclick="closeSignModal()"></button></div>
<div class="canvas-wrap">
<canvas id="signCanvas" width="780" height="260"></canvas>
<div class="canvas-hint" id="signHint">请在区域内签名<div style="font-size:12px;">保证字迹清晰可辨</div></div>
<button class="eraser" onclick="clearCanvas()" title="清空"><svg viewBox="0 0 24 24"><path d="M20 20H8.5l-4-4a2 2 0 010-2.8l8.2-8.2a2 2 0 012.8 0l5 5a2 2 0 010 2.8L13 20M8 11l6 6"/></svg></button>
</div>
<div class="sign-modal__foot">
<button class="btn btn-default" onclick="closeSignModal()">取消</button>
<button class="btn btn-primary" onclick="confirmSign()">确认</button>
</div>
</div>
</div>
<!-- [交互] 解读详情弹窗(只读) -->
<div class="modal-mask" id="detailMask">
<div class="modal--wide">
@@ -338,34 +396,35 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="modal__body" style="padding:0;">
<div class="report-cols">
<div class="report-cols__left">
<div class="report-title">体检结论</div>
<div class="report-sub" id="dConclusion"></div>
<div class="report-title">体检建议</div>
<div class="report-sub" id="dSuggest"></div>
<div class="rpt-title">体检结论</div>
<div class="rpt-text" id="dConclusion"></div>
<div class="rpt-title">体检建议</div>
<div class="rpt-text" id="dSuggest"></div>
<div id="dReport"></div>
</div>
<div class="report-cols__right">
<div class="report-title">员工基本信息</div>
<div class="emp-basic" id="dEmp"></div>
<div class="report-title">综合解读建议</div>
<div class="advise-box" id="dAdvise"></div>
<div class="report-title">五类人群重新分布</div>
<div style="display:flex;padding:6px 0 10px;gap:16px;">
<span id="dGroupBefore" style="font-size:var(--font-sm);color:var(--text-secondary);"></span>
<span id="dGroupAfter" style="font-size:var(--font-sm);color:var(--text-secondary);"></span>
<div class="form-card">
<div class="form-card__title">员工基本信息</div>
<div class="form-grid" id="dEmp"></div>
</div>
<div class="report-title">员工确认签字</div>
<div id="dSign"></div>
<div style="display:flex;width:100%;margin-top:16px;">
<div style="width:50%;">
<div class="report-title" style="margin-top:0;">解读医生</div>
<div style="padding-left:12px;" id="dDoctor"></div>
<div class="form-card">
<div class="form-card__title">解读信息</div>
<div class="form-grid">
<div>解读医生:<b id="dDoctor"></b></div><div>解读日期:<b id="dDate"></b></div>
</div>
<div style="width:50%;">
<div class="report-title" style="margin-top:0;">解读日期</div>
<div style="padding-left:12px;" id="dDate"></div>
</div>
<div class="form-card">
<div class="form-card__title">解读意见</div>
<div style="font-size:13px;color:#333;line-height:1.9;" id="dAdvise"></div>
<div style="display:flex;gap:16px;margin-top:10px;">
<span id="dGroupBefore" style="font-size:12px;color:#4A5A7F;"></span>
<span id="dGroupAfter" style="font-size:12px;color:#4A5A7F;"></span>
</div>
</div>
<div class="form-card">
<div class="form-card__title">员工确认签字</div>
<div id="dSign"></div>
</div>
</div>
</div>
</div>
@@ -419,7 +478,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="toast" id="toast"></div>
<script>
/* ===== 演示数据(脱敏):解读任务由年度体检报告回传生成 ===== */
/* ===== 演示数据:解读任务由年度体检报告回传生成 ===== */
var pointUnit={'樊学作业区医疗点':'采油一厂','姬圪台作业区医疗点':'采油二厂','西峰基地医疗点':'第四采油厂','靖边气田医疗点':'采气一厂','桐园作业区医疗点':'采油二厂','安塞采油队长住点医疗室':'采油一厂','乌审旗采气区医疗点':'采气一厂','姬塬作业区医疗点':'采油一厂'};
var pointMap={
'采油一厂':['樊学作业区医疗点','安塞采油队长住点医疗室','姬塬作业区医疗点'],
@@ -433,17 +492,17 @@ var GROUP_BADGE={a:'#CF1322',b:'#FA8C16',c:'#D48806',d:'#096DD9',e:'#389E0D'};
var CHRONIC={'CQ033096':'高血压','CQ010564':'糖尿病','CQ019203':'冠心病','CQ021578':'高血压','CQ027710':'肥胖','CQ036441':'高血压','CQ022317':'肥胖','CQ029905':'无','CQ044871':'无','CQ041822':'无','CQ032144':'无','CQ045130':'无'};
/* status: 1待解读 2已逾期 3已完成 ; interpretDate 为解读完成日期 */
var tasks=[
{id:1,code:'CQ033096',name:'赵万福',sex:'男',age:39,tel:'150****6127',unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'2026-09-07',doctor:'曲志明',status:3,advise:'高血压2级合并超重,已纳入慢病管理;建议低盐减重、规律服药并每月随访。',group:'b',beforeGroup:'d'},
{id:2,code:'CQ010564',name:'高建军',sex:'男',age:50,tel:'139****0086',unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'2026-08-30',doctor:'闫婷',status:3,advise:'2型糖尿病诊断明确,转内分泌专科复核用药方案;双周随访控制血糖。',group:'b',beforeGroup:'c'},
{id:3,code:'CQ027710',name:'张秀兰',sex:'女',age:45,tel:'136****2248',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:4,code:'CQ021578',name:'石维国',sex:'男',age:46,tel:'139****2051',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:5,code:'CQ022317',name:'徐立成',sex:'男',age:44,tel:'133****4860',unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:6,code:'CQ036441',name:'刘玉梅',sex:'女',age:40,tel:'135****7719',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:2,advise:'',group:'',beforeGroup:''},
{id:7,code:'CQ019203',name:'曲志明',sex:'男',age:42,tel:'137****8890',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:2,advise:'',group:'',beforeGroup:''},
{id:8,code:'CQ044871',name:'白瑞',sex:'男',age:31,tel:'155****3316',unit:'采油一厂',dept:'生产技术部',point:'安塞采油队长住点医疗室',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:9,code:'CQ032144',name:'李桂芳',sex:'女',age:38,tel:'138****7742',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'2026-09-02',doctor:'李桂芳',status:3,advise:'健康状况良好,补充维生素D并保持运动习惯。',group:'e',beforeGroup:'e'},
{id:10,code:'CQ041822',name:'闫婷',sex:'女',age:34,tel:'186****5527',unit:'第四采油厂',dept:'健康管理部',point:'西峰基地医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:11,code:'CQ045130',name:'杨慧敏',sex:'女',age:33,tel:'188****7214',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'2026-09-04',doctor:'石维国',status:3,advise:'轻度缺铁性贫血,加强营养,三个月复查血常规。',group:'c',beforeGroup:'e'}
{id:1,code:'CQ033096',name:'赵万福',sex:'男',age:39,tel:'15029836127',unit:'采油二厂',dept:'桐园作业区',point:'桐园作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'2026-09-07',doctor:'曲志明',status:3,advise:'高血压2级合并超重,已纳入慢病管理;建议低盐减重、规律服药并每月随访。',group:'b',beforeGroup:'d'},
{id:2,code:'CQ010564',name:'高建军',sex:'男',age:50,tel:'13956200086',unit:'第四采油厂',dept:'基地管理处',point:'西峰基地医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'2026-08-30',doctor:'闫婷',status:3,advise:'2型糖尿病诊断明确,转内分泌专科复核用药方案;双周随访控制血糖。',group:'b',beforeGroup:'c'},
{id:3,code:'CQ027710',name:'张秀兰',sex:'女',age:45,tel:'13649162248',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:4,code:'CQ021578',name:'石维国',sex:'男',age:46,tel:'13928562051',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:5,code:'CQ022317',name:'徐立成',sex:'男',age:44,tel:'13319284860',unit:'采气一厂',dept:'苏里格南作业区',point:'乌审旗采气区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:6,code:'CQ036441',name:'刘玉梅',sex:'女',age:40,tel:'13572867719',unit:'采气一厂',dept:'靖边净化厂',point:'靖边气田医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:2,advise:'',group:'',beforeGroup:''},
{id:7,code:'CQ019203',name:'曲志明',sex:'男',age:42,tel:'13720568890',unit:'采油二厂',dept:'姬圪台联合站',point:'姬圪台作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:2,advise:'',group:'',beforeGroup:''},
{id:8,code:'CQ044871',name:'白瑞',sex:'男',age:31,tel:'15529403316',unit:'采油一厂',dept:'生产技术部',point:'安塞采油队长住点医疗室',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:9,code:'CQ032144',name:'李桂芳',sex:'女',age:38,tel:'13836547742',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'2026-09-02',doctor:'李桂芳',status:3,advise:'健康状况良好,补充维生素D并保持运动习惯。',group:'e',beforeGroup:'e'},
{id:10,code:'CQ041822',name:'闫婷',sex:'女',age:34,tel:'18629135527',unit:'第四采油厂',dept:'健康管理部',point:'西峰基地医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'',doctor:'',status:1,advise:'',group:'',beforeGroup:''},
{id:11,code:'CQ045130',name:'杨慧敏',sex:'女',age:33,tel:'18829587214',unit:'采油一厂',dept:'樊学作业区',point:'樊学作业区医疗点',year:'2026',deadline:'2026-10-31',interpretDate:'2026-09-04',doctor:'石维国',status:3,advise:'轻度缺铁性贫血,加强营养,三个月复查血常规。',group:'c',beforeGroup:'e'}
];
var nextId=12,curInterpId=null,statusGroup='0',today='2026-09-07';
var statusMap={1:'待解读',2:'已逾期',3:'已完成'};
@@ -453,15 +512,26 @@ var statusBadge={1:'badge--warning',2:'badge--danger',3:'badge--success'};
function buildReport(t){
return {
conclusion: t.summary || '各项指标未见明显异常',
suggest: '结合既往史与生活方式,建议定期复查并保持健康管理。',
groups: [
{analysisName:'一般检查', cats:[
{cls:'体格检查', rows:[{name:'血压',result:'128/82',unit:'mmHg',ref:'<140/90'},{name:'心率',result:'74',unit:'/',ref:'60-100'}]},
{cls:'人体测量', rows:[{name:'身高',result:'172',unit:'cm',ref:''},{name:'体重',result:'78',unit:'kg',ref:''},{name:'BMI',result:'26.4',unit:'',ref:'18.5-23.9'},{name:'腰围',result:'88',unit:'cm',ref:'<85'}]}
suggest: '低盐低脂饮食,规律监测血压并记录;3个月内复查血脂、血糖、糖化血红蛋白;每周中等强度运动不少于150分钟;如出现头晕、心悸等症状及时就诊。',
majors: [
{name:'实验室检查', cats:[
{cls:'生化全套', rows:[
{name:'空腹血糖',result:'7.8 ↑',unit:'mmol/L',ref:'3.9~6.1',abnormal:true},
{name:'血清总胆固醇',result:'6.12',unit:'mmol/L',ref:'0~5.2',abnormal:true},
{name:'血清甘油三酯',result:'2.35',unit:'mmol/L',ref:'0~1.7',abnormal:true},
{name:'糖化血红蛋白',result:'6.9',unit:'%',ref:'4~6.5',abnormal:true}
]},
{cls:'血常规', rows:[
{name:'白细胞计数',result:'6.8',unit:'10^9/L',ref:'3.5~9.5'},
{name:'血红蛋白',result:'145',unit:'g/L',ref:'130~175'},
{name:'血小板计数',result:'210',unit:'10^9/L',ref:'125~350'}
]}
]},
{analysisName:'实验室检查', cats:[
{cls:'血常规', rows:[{name:'白细胞',result:'6.1',unit:'×10⁹/L',ref:'3.5-9.5'},{name:'血红蛋白',result:'142',unit:'g/L',ref:'130-175'},{name:'血小板',result:'235',unit:'×10⁹/L',ref:'125-350'}]},
{cls:'生化检验', rows:[{name:'空腹血糖',result:'5.8',unit:'mmol/L',ref:'3.9-6.1'},{name:'总胆固醇',result:'5.6',unit:'mmol/L',ref:'<5.2'},{name:'甘油三酯',result:'1.7',unit:'mmol/L',ref:'<1.7'},{name:'尿酸',result:'398',unit:'μmol/L',ref:'208-428'}]}
{name:'辅助检查', cats:[
{cls:'心电图', rows:[
{name:'窦性心律',result:'未见异常',unit:'—',ref:'—'},
{name:'ST-T 改变',result:'轻度异常',unit:'—',ref:'—',abnormal:true}
]}
]}
]
};
@@ -574,13 +644,15 @@ function onFilterUnit(){
}
function currentData(){
var q=document.getElementById('filterName').value.trim();
var code=document.getElementById('filterCode').value.trim();
var unit=document.getElementById('filterUnit').value;
var point=document.getElementById('filterPoint').value;
var year=document.getElementById('filterYear').value;
var from=document.getElementById('filterFrom').value;
var to=document.getElementById('filterTo').value;
return tasks.filter(function(t){
return (!q||t.name.indexOf(q)!==-1||t.code.toLowerCase().indexOf(q.toLowerCase())!==-1)
return (!q||t.name.indexOf(q)!==-1)
&&(!code||t.code.toLowerCase().indexOf(code.toLowerCase())!==-1)
&&(!selUnit||t.unit===selUnit)&&(!selDept||t.dept===selDept)
&&(!unit||pointUnit[t.point]===unit)&&(!point||t.point===point)
&&(!year||t.year===year)
@@ -611,14 +683,13 @@ function renderTable(){
tbody.innerHTML=html;
}
/* 行操作:体检报告解读/重新解读/编辑/解读详情 */
function canRedo(t){ return t.status===3 && t.interpretDate>=today; }
function actionHtml(t){
var h='';
if(t.status!==3){
h+='<button class="btn btn-link" onclick="openInterpret('+t.id+')">体检报告解读</button>';
h+='<button class="btn btn-link" onclick="openEdit('+t.id+')">编辑</button>';
}else{
if(canRedo(t))h+='<button class="btn btn-link" onclick="openInterpret('+t.id+',true)">重新解读</button>';
h+='<button class="btn btn-link" onclick="openInterpret('+t.id+',true)">重新解读</button>';
h+='<button class="btn btn-link" onclick="openDetail('+t.id+')">解读详情</button>';
}
return h;
@@ -631,6 +702,7 @@ function setStatusGroup(btn,v){
function applyFilter(){currentPage=1;renderTable();}
function resetFilter(){
document.getElementById('filterName').value='';
document.getElementById('filterCode').value='';
document.getElementById('filterUnit').value='';onFilterUnit();
document.getElementById('filterYear').value='';
document.getElementById('filterFrom').value='';document.getElementById('filterTo').value='';
@@ -645,30 +717,30 @@ function turnPage(d){
/* ===== 体检报告渲染 ===== */
function renderReportHtml(report){
if(!report.groups||!report.groups.length)return '<div class="td-empty">暂无体检数据</div>';
if(!report.majors||!report.majors.length)return '<div class="td-empty">暂无体检数据</div>';
var html='';
report.groups.forEach(function(g){
html+='<div class="report-group"><div class="report-group__name">'+g.analysisName+'</div>';
g.cats.forEach(function(c){
html+='<div class="report-cat"><div class="report-cat__name">'+c.cls+'</div>';
html+='<ul class="report-ul report-ul--head"><li>名称</li><li>结果</li><li>单位</li><li>参考值</li></ul>';
report.majors.forEach(function(m){
html+='<div class="rpt-major">'+m.name+'</div>';
m.cats.forEach(function(c){
html+='<div class="rpt-title rpt-title--sub">'+c.cls+'</div>';
html+='<table class="rpt-table"><tr><th>名称</th><th>结果</th><th>单位</th><th>参考值</th></tr>';
c.rows.forEach(function(r){
html+='<ul class="report-ul"><li>'+r.name+'</li><li>'+r.result+'</li><li>'+r.unit+'</li><li>'+r.ref+'</li></ul>';
html+='<tr><td>'+r.name+'</td><td class="'+(r.abnormal?'abnormal':'')+'">'+r.result+'</td><td>'+r.unit+'</td><td>'+r.ref+'</td></tr>';
});
html+='</div>';
html+='</table>';
});
html+='</div>';
});
return html;
}
function empBasicHtml(t){
return '<div class="w25">姓名<b> '+t.name+'</b></div><div class="w25">性别<b> '+t.sex+'</b></div><div class="w25">年龄<b> '+t.age+'</b></div><div class="w25">员工编号<b> '+t.code+'</b></div>'
+'<div class="w50">所属单位<b> '+t.unit+'</b></div><div class="w50">所属部门<b> '+t.dept+'</b></div>'
+'<div class="w100">慢病类型<b> '+(CHRONIC[t.code]||'无')+'</b></div>';
return '<div>姓名<b>'+t.name+'</b></div><div>性别<b>'+t.sex+'</b></div>'
+'<div>年龄:<b>'+t.age+' 岁</b></div><div>员工编号:<b>'+t.code+'</b></div>'
+'<div style="grid-column:1/-1;">所属单位:<b>'+t.unit+'</b></div>'
+'<div style="grid-column:1/-1;">体检日期:<b>'+t.year+'-08-15</b> 报告:'+t.year+'年体检报告</div>';
}
function fillGroupSel(val){
var sel=document.getElementById('intGroup');
sel.innerHTML='<option value="">请选择分类</option>'+Object.keys(GROUP_MAP).map(function(k){
sel.innerHTML='<option value="">请选择五类人群分类</option>'+Object.keys(GROUP_MAP).map(function(k){
return '<option value="'+k+'"'+(k===val?' selected':'')+'>'+GROUP_MAP[k]+'</option>';
}).join('');
}
@@ -685,26 +757,76 @@ function openInterpret(id,redo){
document.getElementById('intEmp').innerHTML=empBasicHtml(t);
document.getElementById('intAdvise').value=redo?t.advise:'';
fillGroupSel(redo?t.group:'');
document.getElementById('signHolder').innerHTML='本人已确认健康随访';
if(redo){lockSign(t.name);}else{resetSign();}
document.getElementById('intMask').classList.add('open');
}
function doSign(){
/* ===== 员工签字(手写/图片,参考 medical-pad/interpret-drawer) ===== */
var signType='hand',signDone=false;
function switchSignType(type){
signType=type;
document.getElementById('signHandBox').style.display=type==='hand'?'flex':'none';
document.getElementById('signImgBox').style.display=type==='hand'?'none':'flex';
}
function resetSign(){
signType='hand';signDone=false;
document.getElementById('signField').style.display='block';
document.getElementById('signLockView').style.display='none';
document.querySelector('input[name="signType"][value="hand"]').checked=true;
document.getElementById('signHandBox').style.display='flex';
document.getElementById('signImgBox').style.display='none';
document.getElementById('signHandBox').innerHTML='<svg viewBox="0 0 24 24"><path d="M17 3a2.8 2.8 0 014 4L7.5 20.5 2 22l1.5-5.5z"/></svg>点击签名';
document.getElementById('signImgBox').innerHTML='<svg viewBox="0 0 24 24"><path d="M12 16V4M7 9l5-5 5 5"/><path d="M4 20h16"/></svg>上传签字图片';
}
function lockSign(name){
signType='hand';signDone=true;
document.getElementById('signField').style.display='none';
document.getElementById('signLockView').style.display='block';
document.getElementById('signLockView').innerHTML='<div class="sign-view"><span class="sign-view__name">'+name+'</span><span class="sign-view__note">已签名</span></div>';
}
function openSignModal(){clearCanvas();document.getElementById('signMask').classList.add('open');}
function closeSignModal(){document.getElementById('signMask').classList.remove('open');}
function onSignImg(input){
var f=input.files&&input.files[0];
if(!f)return;
var r=new FileReader();
r.onload=function(e){
document.getElementById('signImgBox').innerHTML='<img src="'+e.target.result+'" style="max-width:100%;max-height:80px;object-fit:contain;"><span style="font-size:11px;color:#969EB0;">已上传 · 点击可重新上传</span>';
signDone=true;
};
r.readAsDataURL(f);
}
var signCanvas=null,signCtx=null,drawing=false,hasInk=false;
function initSignCanvas(){
signCanvas=document.getElementById('signCanvas');
signCtx=signCanvas.getContext('2d');
signCtx.lineWidth=3;signCtx.lineCap='round';signCtx.strokeStyle='#333';
signCanvas.onmousedown=function(e){drawing=true;var p=canvasPos(e);signCtx.beginPath();signCtx.moveTo(p.x,p.y);};
signCanvas.onmousemove=function(e){if(!drawing)return;var p=canvasPos(e);signCtx.lineTo(p.x,p.y);signCtx.stroke();hasInk=true;document.getElementById('signHint').style.display='none';};
window.addEventListener('mouseup',function(){drawing=false;});
}
function canvasPos(e){
var r=signCanvas.getBoundingClientRect();
return {x:(e.clientX-r.left)*(signCanvas.width/r.width),y:(e.clientY-r.top)*(signCanvas.height/r.height)};
}
function clearCanvas(){signCtx.clearRect(0,0,signCanvas.width,signCanvas.height);hasInk=false;document.getElementById('signHint').style.display='flex';}
function confirmSign(){
if(!hasInk){showToast('您还没有进行签字');return;}
var t=tasks.find(function(x){return x.id===curInterpId;});
document.getElementById('signHolder').innerHTML='<svg width="200" height="60" viewBox="0 0 200 60"><path d="M14 42c16-24 22 14 36-8 10-18 16 20 30-2 9-16 14 12 24-2 8-14 14 10 26 4 12-6 24 8 38 2" stroke="#14213D" stroke-width="2.4" fill="none" stroke-linecap="round"/></svg>'
+'<div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-top:6px;">'+t.name+' · '+today+' 电子签名存证</div>';
document.getElementById('signHandBox').innerHTML='<span style="font-size:26px;font-family:KaiTi,serif;color:#333;">'+t.name+'</span><span style="font-size:11px;color:#969EB0;">已签名 · 点击可重签</span>';
signDone=true;
closeSignModal();
}
function saveInterpret(){
var t=tasks.find(function(x){return x.id===curInterpId;});
var advise=document.getElementById('intAdvise').value.trim();
var group=document.getElementById('intGroup').value;
if(!advise){showToast('请填写综合解读建议');return;}
if(!group){showToast('请选择五类人群重新分类');return;}
var signed=document.getElementById('signHolder').textContent.indexOf('电子签名')!==-1;
if(!group){showToast('请选择五类人群分类');return;}
t.advise=advise;t.group=group;t.status=3;t.interpretDate=today;
t.beforeGroup=t.beforeGroup||'e';
t.doctor=t.doctor||'张秀兰';
closeModal('intMask');renderTable();
showToast(signed?'「'+t.name+'」报告解读已完成,结果同步至健康档案':'「'+t.name+'」报告解读已完成,员工签字将在 PAD 端确认');
showToast(signDone?'「'+t.name+'」报告解读已完成,结果同步至健康档案':'「'+t.name+'」报告解读已完成,员工签字将在 PAD 端确认');
}
/* ===== 解读详情弹窗 ===== */
@@ -719,7 +841,7 @@ function openDetail(id){
document.getElementById('dAdvise').textContent=t.advise||'—';
document.getElementById('dGroupBefore').innerHTML='解读前:<span class="group-badge" style="color:'+(GROUP_BADGE[t.beforeGroup]||'#999')+';background:'+(GROUP_BADGE[t.beforeGroup]||'#999')+'1a;">'+(GROUP_MAP[t.beforeGroup]||'健康-E类')+'</span>';
document.getElementById('dGroupAfter').innerHTML='解读后:<span class="group-badge" style="color:'+(GROUP_BADGE[t.group]||'#999')+';background:'+(GROUP_BADGE[t.group]||'#999')+'1a;">'+(GROUP_MAP[t.group]||'健康-E类')+'</span>';
document.getElementById('dSign').innerHTML='<div class="sign-box" style="min-height:80px;"><svg width="180" height="52" viewBox="0 0 180 52"><path d="M10 38c14-20 20 12 32-6 9-14 14 16 26-2 8-12 12 10 22-2 8-12 14 8 26 2" stroke="#14213D" stroke-width="2.4" fill="none" stroke-linecap="round"/></svg><div style="font-size:var(--font-xs);color:var(--text-placeholder);">'+t.name+' · '+t.interpretDate+' 于 PAD 端确认</div></div>';
document.getElementById('dSign').innerHTML='<div class="sign-view"><span class="sign-view__name">'+t.name+'</span></div>';
document.getElementById('dDoctor').textContent=t.doctor||'—';
document.getElementById('dDate').textContent=t.interpretDate||'—';
document.getElementById('detailMask').classList.add('open');
@@ -780,7 +902,8 @@ function showToast(msg){
}
renderTable();
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
initSignCanvas();
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
</script>
</body>
</html>
@@ -30,13 +30,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-item{display:flex;align-items:center;gap:8px;padding:9px 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:9px 24px;font-size:var(--font-sm);color:rgba(255,255,255,0.45);cursor:pointer;user-select:none;}
.sidebar-subgroup__header:hover{color:rgba(255,255,255,0.85);}
.sidebar-subgroup__header .arrow{width:10px;height:10px;stroke:currentColor;transition:transform .2s;}
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 二级分组菜单展开/收起(参考床头呼叫) */
.sidebar-group__label{display:flex;align-items:center;justify-content:space-between;padding:9px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;}
.sidebar-group__label:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-group__label svg{transition:transform .2s;flex-shrink:0;}
.sidebar-group.open .sidebar-group__label svg{transform:rotate(180deg);}
.sidebar-group__items{display:none;}
.sidebar-group.open .sidebar-group__items{display:block;}
.sidebar-item--sub{padding-left:44px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 顶部Tab:员工就诊 / 非员工就诊 */
.page-tabs{display:flex;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);margin-bottom:16px;overflow:hidden;}
@@ -187,19 +188,21 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<aside class="sidebar">
<div class="sidebar-menu__title">日常业务</div>
<a href="daily-treatment.html" class="sidebar-item active">日常诊疗</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-follow.html" class="sidebar-item">随访管理</a>
<a href="daily-chronic.html" class="sidebar-item">慢病管理</a>
<a href="daily-employee-search.html" class="sidebar-item">员工档案</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)"><svg class="arrow" viewBox="0 0 10 10" fill="none"><path d="M3 1l4 4-4 4" stroke-width="1.5"/></svg>任务管理</div>
<div class="sidebar-subgroup__body">
<a href="daily-follow-task.html" class="sidebar-item">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item">解读任务</a>
<div class="sidebar-group" id="grpTask">
<div class="sidebar-group__label" onclick="toggleGroup('grpTask')">任务管理
<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 3.5L5 6.5l3-3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</div>
<div class="sidebar-group__items">
<a href="daily-follow-task.html" class="sidebar-item sidebar-item--sub">随访任务</a>
<a href="daily-interpret.html" class="sidebar-item sidebar-item--sub">解读任务</a>
</div>
</div>
<a href="daily-education.html" class="sidebar-item">健康宣教管理</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读管理</a>
<a href="daily-education.html" class="sidebar-item">健康宣教</a>
<a href="daily-ambulance-attendance.html" class="sidebar-item">救护车出勤</a>
<a href="daily-interpret-manage.html" class="sidebar-item">报告解读记录</a>
</aside>
<!-- 主内容 -->
@@ -633,7 +636,7 @@ function showToast(msg){
var t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');
clearTimeout(t._timer);t._timer=setTimeout(function(){t.classList.remove('show');},2200);
}
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
function toggleGroup(id){document.getElementById(id).classList.toggle('open');}
function switchTab(tab){
document.querySelectorAll('.page-tab').forEach(function(t){t.classList.toggle('active',t.getAttribute('data-tab')===tab);});
document.getElementById('panelStaff').classList.toggle('active',tab==='staff');
@@ -198,3 +198,23 @@
- `web-admin/frontline-medical/daily-treatment-staff.html` / `daily-treatment-nonstaff.html` — 删除
- `web-admin/frontline-medical/` 其余 45 页 — 侧边栏菜单与顶栏链接同步
- `D:\Work\platform-prototype\index.html` — 站点地图同步
## [2026-09-15] feat: 身份证与联系电话全站去除脱敏
按需求将一线医疗模块所有页面演示数据中的身份证号与联系电话由脱敏展示改为完整展示,员工数据与「日常诊疗管理」就诊员工池保持一致。
**涉及文件:**
- `bed-bed.html` / `bed-dormitory.html` / `bed-call-record.html` — 床位/宿舍/呼叫记录员工电话
- `config-allocate.html` / `config-ambulance.html` — 员工分配/救护车配置电话
- `daily-follow.html` / `daily-follow-task.html` / `daily-interpret.html` — 随访/解读任务电话,随访任务页身份证
- `daily-ambulance-attendance.html` — 救护车出勤电话
- `hut-appointment.html` / `hut-cabin.html` — 健康小屋预约/台账电话
## [2026-09-15] feat: 员工分配统计概览卡改为员工口径 · 名册统计文案上移
按需求调整员工分配统计页。
**涉及文件:**
- `config-allocate-stat.html` — 概览卡由四卡(医疗点数量/医护人员总数/已分配员工总数/平均每点分配人数)改为三卡:员工总数量、已分配员工数量、未分配员工数量(未分配按单位汇总,与「员工分配」页未分配 Tab 演示数据一致共 9 人,仍随筛选实时计算);已分配员工名册抽屉中「共 N 人 · 分配至『单位/医疗点』」统计文案由筛选框右侧移至筛选框上方
@@ -241,18 +241,18 @@ var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油
var stBadge={'待使用':'badge--info','使用中':'badge--warning','已完成':'badge--success','已取消':'badge--muted','爽约':'badge--danger'};
/* 预约记录:健康室与设备与 hut-room 勾稽;监测设备不产生预约 */
var appts=[
{code:'YY-20260901-001',name:'石维国',sex:'男',unit:'采油一厂',depar:'樊学作业区',phone:'138****2211',status:'已完成',room:'樊学站点健康小屋',dev:'血压检测仪',date:'2026-09-01',start:'2026-09-01 09:00',end:'2026-09-01 09:32',created:'2026-08-30 16:22',useTime:'2026-09-01 09:02',backTime:'2026-09-01 09:32'},
{code:'YY-20260901-002',name:'李桂芳',sex:'女',unit:'采油一厂',depar:'樊学作业区',phone:'139****0834',status:'已完成',room:'樊学站点健康小屋',dev:'血糖分析仪',date:'2026-09-01',start:'2026-09-01 10:00',end:'2026-09-01 10:18',created:'2026-08-31 08:40',useTime:'2026-09-01 10:01',backTime:'2026-09-01 10:18'},
{code:'YY-20260902-003',name:'曲志明',sex:'男',unit:'采油一厂',depar:'樊学作业区',phone:'137****5520',status:'已取消',room:'樊学站点健康小屋',dev:'心理测评系统',date:'2026-09-02',start:'2026-09-02 14:00',end:'—',created:'2026-09-01 11:05',cancelTime:'2026-09-02 08:12'},
{code:'YY-20260903-004',name:'刘玉梅',sex:'女',unit:'采气一厂',depar:'靖边气田',phone:'136****7712',status:'已完成',room:'靖边气田健康小屋',dev:'体成分分析仪',date:'2026-09-03',start:'2026-09-03 15:00',end:'2026-09-03 15:21',created:'2026-09-02 17:33',useTime:'2026-09-03 15:00',backTime:'2026-09-03 15:21'},
{code:'YY-20260904-005',name:'高建军',sex:'男',unit:'第四采油厂',depar:'西峰作业区',phone:'135****9086',status:'爽约',room:'西峰基地健康小屋',dev:'激光治疗仪',date:'2026-09-04',start:'2026-09-04 14:30',end:'—',created:'2026-09-03 09:12',cancelTime:'2026-09-04 15:10'},
{code:'YY-20260905-006',name:'徐立成',sex:'男',unit:'采油二厂',depar:'桐园作业区',phone:'138****4417',status:'已完成',room:'桐园站点健康小屋',dev:'血压检测仪',date:'2026-09-05',start:'2026-09-05 09:30',end:'2026-09-05 10:02',created:'2026-09-04 15:48',useTime:'2026-09-05 09:31',backTime:'2026-09-05 10:02'},
{code:'YY-20260905-007',name:'呼建国',sex:'男',unit:'采气一厂',depar:'靖边气田',phone:'139****2680',status:'已取消',room:'靖边气田健康小屋',dev:'血压检测仪',date:'2026-09-05',start:'2026-09-05 09:00',end:'—',created:'2026-09-04 18:02',cancelTime:'2026-09-04 20:15'},
{code:'YY-20260906-008',name:'白瑞',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'137****3341',status:'已取消',room:'西峰基地健康小屋',dev:'血糖分析仪',date:'2026-09-06',start:'2026-09-06 16:00',end:'—',created:'2026-09-05 10:26',cancelTime:'2026-09-06 07:50'},
{code:'YY-20260907-009',name:'赵万福',sex:'男',unit:'采油二厂',depar:'桐园作业区',phone:'136****8123',status:'使用中',room:'桐园站点健康小屋',dev:'音乐放松舱',date:'2026-09-07',start:'2026-09-07 10:00',end:'—',created:'2026-09-06 14:19',useTime:'2026-09-07 10:03',backTime:'—'},
{code:'YY-20260907-010',name:'闫婷',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'135****6208',status:'使用中',room:'西峰基地健康小屋',dev:'血糖分析仪',date:'2026-09-07',start:'2026-09-07 11:00',end:'—',created:'2026-09-06 16:55',useTime:'2026-09-07 11:01',backTime:'—'},
{code:'YY-20260908-011',name:'杨慧敏',sex:'女',unit:'采油一厂',depar:'麻子山作业区',phone:'138****9975',status:'待使用',room:'麻子山站点健康小屋',dev:'体成分分析仪',date:'2026-09-08',start:'2026-09-08 15:00',end:'—',created:'2026-09-07 08:31'},
{code:'YY-20260908-012',name:'张秀兰',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'139****5402',status:'待使用',room:'西峰基地健康小屋',dev:'血压检测仪',date:'2026-09-08',start:'2026-09-08 10:00',end:'—',created:'2026-09-07 09:04'}
{code:'YY-20260901-001',name:'石维国',sex:'男',unit:'采油一厂',depar:'樊学作业区',phone:'13821622211',status:'已完成',room:'樊学站点健康小屋',dev:'血压检测仪',date:'2026-09-01',start:'2026-09-01 09:00',end:'2026-09-01 09:32',created:'2026-08-30 16:22',useTime:'2026-09-01 09:02',backTime:'2026-09-01 09:32'},
{code:'YY-20260901-002',name:'李桂芳',sex:'女',unit:'采油一厂',depar:'樊学作业区',phone:'13937440834',status:'已完成',room:'樊学站点健康小屋',dev:'血糖分析仪',date:'2026-09-01',start:'2026-09-01 10:00',end:'2026-09-01 10:18',created:'2026-08-31 08:40',useTime:'2026-09-01 10:01',backTime:'2026-09-01 10:18'},
{code:'YY-20260902-003',name:'曲志明',sex:'男',unit:'采油一厂',depar:'樊学作业区',phone:'13708495520',status:'已取消',room:'樊学站点健康小屋',dev:'心理测评系统',date:'2026-09-02',start:'2026-09-02 14:00',end:'—',created:'2026-09-01 11:05',cancelTime:'2026-09-02 08:12'},
{code:'YY-20260903-004',name:'刘玉梅',sex:'女',unit:'采气一厂',depar:'靖边气田',phone:'13607587712',status:'已完成',room:'靖边气田健康小屋',dev:'体成分分析仪',date:'2026-09-03',start:'2026-09-03 15:00',end:'2026-09-03 15:21',created:'2026-09-02 17:33',useTime:'2026-09-03 15:00',backTime:'2026-09-03 15:21'},
{code:'YY-20260904-005',name:'高建军',sex:'男',unit:'第四采油厂',depar:'西峰作业区',phone:'13589259086',status:'爽约',room:'西峰基地健康小屋',dev:'激光治疗仪',date:'2026-09-04',start:'2026-09-04 14:30',end:'—',created:'2026-09-03 09:12',cancelTime:'2026-09-04 15:10'},
{code:'YY-20260905-006',name:'徐立成',sex:'男',unit:'采油二厂',depar:'桐园作业区',phone:'13848934417',status:'已完成',room:'桐园站点健康小屋',dev:'血压检测仪',date:'2026-09-05',start:'2026-09-05 09:30',end:'2026-09-05 10:02',created:'2026-09-04 15:48',useTime:'2026-09-05 09:31',backTime:'2026-09-05 10:02'},
{code:'YY-20260905-007',name:'呼建国',sex:'男',unit:'采气一厂',depar:'靖边气田',phone:'13976362680',status:'已取消',room:'靖边气田健康小屋',dev:'血压检测仪',date:'2026-09-05',start:'2026-09-05 09:00',end:'—',created:'2026-09-04 18:02',cancelTime:'2026-09-04 20:15'},
{code:'YY-20260906-008',name:'白瑞',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'13720283341',status:'已取消',room:'西峰基地健康小屋',dev:'血糖分析仪',date:'2026-09-06',start:'2026-09-06 16:00',end:'—',created:'2026-09-05 10:26',cancelTime:'2026-09-06 07:50'},
{code:'YY-20260907-009',name:'赵万福',sex:'男',unit:'采油二厂',depar:'桐园作业区',phone:'13661218123',status:'使用中',room:'桐园站点健康小屋',dev:'音乐放松舱',date:'2026-09-07',start:'2026-09-07 10:00',end:'—',created:'2026-09-06 14:19',useTime:'2026-09-07 10:03',backTime:'—'},
{code:'YY-20260907-010',name:'闫婷',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'13547386208',status:'使用中',room:'西峰基地健康小屋',dev:'血糖分析仪',date:'2026-09-07',start:'2026-09-07 11:00',end:'—',created:'2026-09-06 16:55',useTime:'2026-09-07 11:01',backTime:'—'},
{code:'YY-20260908-011',name:'杨慧敏',sex:'女',unit:'采油一厂',depar:'麻子山作业区',phone:'13856939975',status:'待使用',room:'麻子山站点健康小屋',dev:'体成分分析仪',date:'2026-09-08',start:'2026-09-08 15:00',end:'—',created:'2026-09-07 08:31'},
{code:'YY-20260908-012',name:'张秀兰',sex:'女',unit:'第四采油厂',depar:'西峰作业区',phone:'13916355402',status:'待使用',room:'西峰基地健康小屋',dev:'血压检测仪',date:'2026-09-08',start:'2026-09-08 10:00',end:'—',created:'2026-09-07 09:04'}
];
var delCodes=[];/* 当前待删除单号集合 */
+8 -8
View File
@@ -296,14 +296,14 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
/* 小屋台账:负责人沿用站点负责人池;records 为每日测量人次(血压/血糖) */
var cabins=[
{name:'樊学基地健康小屋',unit:'采油一厂',depar:'樊学作业区',mgr:'王守正',office:'0963-88201',mobile:'138****2211',date:'2024-05-18',money:86000,devNum:6},
{name:'麻子山站点健康小屋',unit:'采油一厂',depar:'麻子山作业区',mgr:'安伟',office:'0963-88302',mobile:'139****0834',date:'2024-06-02',money:75000,devNum:5},
{name:'桐园基地健康小屋',unit:'采油二厂',depar:'桐园作业区',mgr:'陈国栋',office:'0963-88401',mobile:'137****5520',date:'2024-07-11',money:86000,devNum:6},
{name:'姬圪台站点健康小屋',unit:'采油二厂',depar:'姬圪台作业区',mgr:'刘志强',office:'0963-88402',mobile:'136****7712',date:'2024-09-20',money:68000,devNum:4},
{name:'西峰基地健康小屋',unit:'第四采油厂',depar:'西峰作业区',mgr:'马文秀',office:'0963-88501',mobile:'135****9086',date:'2024-08-15',money:92000,devNum:7},
{name:'靖边气田健康小屋',unit:'采气一厂',depar:'靖边气田',mgr:'周铁祥',office:'0963-88601',mobile:'138****4417',date:'2025-03-06',money:86000,devNum:6},
{name:'樊学作业区第二健康小屋',unit:'采油一厂',depar:'樊学作业区',mgr:'张国亮',office:'0963-88203',mobile:'139****2680',date:'2025-04-22',money:75000,devNum:5},
{name:'西峰前线健康小屋',unit:'第四采油厂',depar:'西峰作业区',mgr:'冯建军',office:'0963-88502',mobile:'137****3341',date:'2025-06-10',money:68000,devNum:4}
{name:'樊学基地健康小屋',unit:'采油一厂',depar:'樊学作业区',mgr:'王守正',office:'0963-88201',mobile:'13821622211',date:'2024-05-18',money:86000,devNum:6},
{name:'麻子山站点健康小屋',unit:'采油一厂',depar:'麻子山作业区',mgr:'安伟',office:'0963-88302',mobile:'13937440834',date:'2024-06-02',money:75000,devNum:5},
{name:'桐园基地健康小屋',unit:'采油二厂',depar:'桐园作业区',mgr:'陈国栋',office:'0963-88401',mobile:'13708495520',date:'2024-07-11',money:86000,devNum:6},
{name:'姬圪台站点健康小屋',unit:'采油二厂',depar:'姬圪台作业区',mgr:'刘志强',office:'0963-88402',mobile:'13607587712',date:'2024-09-20',money:68000,devNum:4},
{name:'西峰基地健康小屋',unit:'第四采油厂',depar:'西峰作业区',mgr:'马文秀',office:'0963-88501',mobile:'13589259086',date:'2024-08-15',money:92000,devNum:7},
{name:'靖边气田健康小屋',unit:'采气一厂',depar:'靖边气田',mgr:'周铁祥',office:'0963-88601',mobile:'13848934417',date:'2025-03-06',money:86000,devNum:6},
{name:'樊学作业区第二健康小屋',unit:'采油一厂',depar:'樊学作业区',mgr:'张国亮',office:'0963-88203',mobile:'13976362680',date:'2025-04-22',money:75000,devNum:5},
{name:'西峰前线健康小屋',unit:'第四采油厂',depar:'西峰作业区',mgr:'冯建军',office:'0963-88502',mobile:'13720283341',date:'2025-06-10',money:68000,devNum:4}
];
/* 每座小屋生成 09-01~09-07 七日测量记录(血压人次=基准±波动,血糖≈血压×0.8) */
(function(){
+56 -4
View File
@@ -33,6 +33,15 @@
.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);}
/* 按钮行 */
@@ -168,11 +177,10 @@
</div>
<nav class="top-bar__nav">
<a href="archive-personal.html">档案</a>
<a href="config-material-notice.html">基础配置</a>
<a href="config-material-notice.html" class="active">基础配置</a>
<a href="pre-hospital-scope.html">检前</a>
<a href="mid-appointment-track.html">检中</a>
<a href="post-unit-count.html">检后</a>
<a href="agent-device.html" class="active">前置机管理</a>
<a href="exception-dirty.html">异常事件</a>
</nav>
<div class="top-bar__right">
@@ -187,8 +195,40 @@
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="sidebar-menu__title">前置机管理</div>
<a href="agent-device.html" class="sidebar-item active">常规体检前置服务</a>
<div class="sidebar-menu__title">基础配置</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 12 12"><path d="M4 2l4 4-4 4" fill="none" stroke-width="1.5" stroke-linecap="round"/></svg>
素材信息
</div>
<div class="sidebar-subgroup__body">
<a href="config-material-notice.html" class="sidebar-item">体检须知</a>
<a href="config-material-video.html" class="sidebar-item">操作说明</a>
</div>
</div>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 12 12"><path d="M4 2l4 4-4 4" fill="none" stroke-width="1.5" stroke-linecap="round"/></svg>
医院配置
</div>
<div class="sidebar-subgroup__body">
<a href="config-hospital-basic.html" class="sidebar-item">基本信息配置</a>
<a href="config-hospital-combo.html" class="sidebar-item">检查项组合配置</a>
</div>
</div>
<a href="config-report-template.html" class="sidebar-item">体检导入模板管理</a>
<div class="sidebar-subgroup">
<div class="sidebar-subgroup__header" onclick="toggleSubgroup(this)">
<svg class="arrow" viewBox="0 0 12 12"><path d="M4 2l4 4-4 4" fill="none" stroke-width="1.5" stroke-linecap="round"/></svg>
体检标准化项目
</div>
<div class="sidebar-subgroup__body">
<a href="config-standard-category.html" class="sidebar-item">体检项目分类</a>
<a href="config-standard-basic.html" class="sidebar-item">体检基本项目</a>
<a href="config-standard-hospital.html" class="sidebar-item">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item active">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -726,6 +766,18 @@ datasource:
<div class="toast" id="toast"></div>
<script>
/* 侧边栏展开/折叠交互 */
function toggleSubgroup(header){
header.parentElement.classList.toggle('open');
}
/* 页面加载时自动展开当前页面所在的子分组 */
(function(){
var active=document.querySelector('.sidebar-item.active');
if(!active)return;
var sub=active.closest('.sidebar-subgroup');
if(sub)sub.classList.add('open');
})();
/* 弹窗开关 */
function openModal(id){ document.getElementById(id).classList.add('open'); }
function closeModal(id){ document.getElementById(id).classList.remove('open'); }
+230 -21
View File
@@ -113,12 +113,52 @@
.report-note{border-radius:var(--radius-sm);padding:12px 16px;font-size:var(--font-sm);line-height:1.9;margin-bottom:16px;}
.report-note--conclusion{border:1px solid #FFD591;background:#FFF7E6;color:#873800;}
.report-note--advice{border:1px solid #B7EB8F;background:#F6FFED;color:#389E0D;}
.report-section{margin-bottom:24px;}
.report-section:last-child{margin-bottom:0;}
.report-section__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);padding-left:10px;border-left:3px solid var(--primary);margin-bottom:12px;line-height:1.4;}
.report-sub__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin:14px 0 8px;}
.report-sub:first-of-type .report-sub__title{margin-top:0;}
.result-abnormal{color:var(--danger);font-weight:600;}
/* 报告弹窗内 Tab 切换 */
.report-tabs{display:flex;gap:28px;padding:0 24px;border-bottom:1px solid var(--border);flex-shrink:0;}
.report-tab{position:relative;padding:12px 2px;font-size:var(--font-base);color:var(--text-secondary);cursor:pointer;transition:color .2s;}
.report-tab:hover{color:var(--primary);}
.report-tab.active{color:var(--primary);font-weight:600;}
.report-tab.active::after{content:'';position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--primary);border-radius:2px;}
.report-pane{display:none;}
.report-pane.active{display:block;}
/* 专业报告版式(冠脉CTA / 胃肠镜共用,与检后报告页一致) */
.rpt-empty{padding:48px 0;text-align:center;font-size:var(--font-sm);color:var(--text-placeholder);}
.cta-rpt{border:1px solid var(--border);border-radius:var(--radius-md);padding:0;overflow:hidden;}
.cta-rpt__band{background:#001529;color:#fff;padding:14px 20px;font-size:var(--font-base);font-weight:600;display:flex;align-items:center;justify-content:space-between;}
.cta-rpt__band small{font-size:var(--font-xs);font-weight:400;opacity:.7;}
.cta-rpt__meta{display:grid;grid-template-columns:repeat(3,1fr);gap:8px 20px;padding:14px 20px;border-bottom:1px dashed var(--border);font-size:var(--font-sm);}
.cta-rpt__meta b{color:var(--text-secondary);font-weight:400;}
.cta-rpt__sec{padding:14px 20px;border-bottom:1px solid var(--border);}
.cta-rpt__sec:last-child{border-bottom:none;}
.cta-rpt__sec-t{font-size:var(--font-sm);font-weight:600;color:var(--primary);margin-bottom:8px;}
.cta-rpt__txt{font-size:var(--font-sm);color:var(--text-primary);line-height:1.7;}
.cta-vessel{width:100%;border-collapse:collapse;}
.cta-vessel th,.cta-vessel td{border:1px solid var(--border);padding:8px 10px;font-size:var(--font-sm);text-align:left;vertical-align:top;}
.cta-vessel th{background:#FAFAFA;color:var(--text-secondary);font-weight:500;white-space:nowrap;}
.cta-vessel td.v-name{white-space:nowrap;font-weight:500;}
.cta-vessel td.v-grade{white-space:nowrap;text-align:center;}
.cta-plaque{display:inline-flex;align-items:center;padding:1px 8px;border-radius:10px;font-size:var(--font-xs);margin-right:6px;}
.cta-plaque--calc{background:#E6F7FF;color:#1890FF;}
.cta-plaque--noncalc{background:#FFF7E6;color:#FAAD14;}
.cta-plaque--mixed{background:#FFF1F0;color:#FF4D4F;}
.cta-plaque--none{background:#F6FFED;color:#52C41A;}
.cta-narrow{font-weight:600;}
.cta-narrow--0{color:#52C41A;}
.cta-narrow--1{color:#8CB800;}
.cta-narrow--2{color:#FAAD14;}
.cta-narrow--3{color:#FA8C16;}
.cta-narrow--4{color:#FF4D4F;}
.cta-imp{display:flex;align-items:center;gap:14px;padding:14px 20px;background:#F6FBFF;border-top:1px solid var(--border);}
.cta-cad{flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;width:96px;height:64px;border-radius:var(--radius-md);color:#fff;font-weight:700;line-height:1.1;}
.cta-cad small{font-size:var(--font-xs);font-weight:400;opacity:.9;}
.cta-cad--0{background:#52C41A;}.cta-cad--1{background:#8CB800;}.cta-cad--2{background:#FAAD14;}.cta-cad--3{background:#FA8C16;}.cta-cad--4{background:#FF4D4F;}
.cta-imp__list{font-size:var(--font-sm);color:var(--text-primary);line-height:1.7;}
.cta-imgs{display:flex;gap:12px;flex-wrap:wrap;}
.cta-img{display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border:1px dashed var(--border);border-radius:var(--radius-sm);background:#FAFAFA;font-size:var(--font-sm);color:var(--text-secondary);}
.cta-img .link{font-size:var(--font-xs);}
/* 体检年份选择控件 */
.year-picker{position:relative;}
.year-picker .filter-input{padding-right:30px;cursor:pointer;}
@@ -240,20 +280,30 @@
<div class="modal__title" id="report-title">体检报告</div>
<button class="modal__close" onclick="closeReport()"></button>
</div>
<!-- 报告类型切换 -->
<div class="report-tabs">
<div class="report-tab active" data-tab="regular" onclick="switchReportTab('regular')">常规体检</div>
<div class="report-tab" data-tab="gastro" onclick="switchReportTab('gastro')">胃肠镜体检</div>
<div class="report-tab" data-tab="cta" onclick="switchReportTab('cta')">冠脉CTA体检</div>
</div>
<div class="modal__body">
<div class="report-pane active" data-tab="regular">
<div class="cta-rpt">
<div class="cta-rpt__band">健康体检报告</div>
<div class="cta-rpt__meta" id="regular-meta"></div>
<!-- 体检结论 -->
<div class="report-section">
<div class="report-section__title">体检结论</div>
<div class="report-note report-note--conclusion">1. 血脂异常:甘油三酯轻度偏高;<br>2. 视力疲劳:双眼裸眼视力下降;<br>3. 其余各项体检指标未见明显异常。</div>
<div class="cta-rpt__sec">
<div class="cta-rpt__sec-t">体检结论</div>
<div class="report-note report-note--conclusion" style="margin-bottom:0;">1. 血脂异常:甘油三酯轻度偏高;<br>2. 视力疲劳:双眼裸眼视力下降;<br>3. 其余各项体检指标未见明显异常。</div>
</div>
<!-- 体检建议 -->
<div class="report-section">
<div class="report-section__title">体检建议</div>
<div class="report-note report-note--advice">1. 建议低脂清淡饮食,少油少糖,规律运动,3个月后复查血脂;<br>2. 注意用眼卫生,避免长时间使用电子屏幕,必要时眼科随诊;<br>3. 建议每年进行一次健康体检,动态关注各项指标变化。</div>
<div class="cta-rpt__sec">
<div class="cta-rpt__sec-t">体检建议</div>
<div class="report-note report-note--advice" style="margin-bottom:0;">1. 建议低脂清淡饮食,少油少糖,规律运动,3个月后复查血脂;<br>2. 注意用眼卫生,避免长时间使用电子屏幕,必要时眼科随诊;<br>3. 建议每年进行一次健康体检,动态关注各项指标变化。</div>
</div>
<!-- 物理检查 -->
<div class="report-section">
<div class="report-section__title">物理检查</div>
<div class="cta-rpt__sec">
<div class="cta-rpt__sec-t">物理检查</div>
<div class="report-sub">
<div class="report-sub__title">一般检查</div>
<table>
@@ -279,8 +329,8 @@
</div>
</div>
<!-- 内科 -->
<div class="report-section">
<div class="report-section__title">内科</div>
<div class="cta-rpt__sec">
<div class="cta-rpt__sec-t">内科</div>
<div class="report-sub">
<div class="report-sub__title">心肺听诊</div>
<table>
@@ -304,8 +354,8 @@
</div>
</div>
<!-- 外科 -->
<div class="report-section">
<div class="report-section__title">外科</div>
<div class="cta-rpt__sec">
<div class="cta-rpt__sec-t">外科</div>
<div class="report-sub">
<div class="report-sub__title">皮肤与淋巴结</div>
<table>
@@ -328,8 +378,8 @@
</div>
</div>
<!-- 化学检查 -->
<div class="report-section">
<div class="report-section__title">化学检查</div>
<div class="cta-rpt__sec">
<div class="cta-rpt__sec-t">化学检查</div>
<div class="report-sub">
<div class="report-sub__title">血常规</div>
<table>
@@ -374,6 +424,10 @@
</table>
</div>
</div>
</div>
</div>
<div class="report-pane" data-tab="gastro" id="gastro-pane"></div>
<div class="report-pane" data-tab="cta" id="cta-pane"></div>
</div>
<div class="modal__footer">
<button class="btn btn-primary" onclick="closeReport()">关闭</button>
@@ -521,7 +575,7 @@ function buildBody(){
if(!val)val=[(CAT_DEFAULTS[col.cat]||['-'])[0]];
cells+='<td class="item'+(val[1]?' res-abn':'')+'">'+val[0]+'</td>';
});
cells+='<td class="td-op"><span class="link" onclick="openReport(\''+r.name+'\')">查看报告</span></td>';
cells+='<td class="td-op"><span class="link" onclick="openReport('+MASTER_ROWS.indexOf(r)+')">查看报告</span></td>';
return '<tr>'+cells+'</tr>';
}).join('');
return '<tbody>'+rows+'</tbody></table>';
@@ -566,11 +620,166 @@ function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
var sub=active.closest('.sidebar-subgroup');
while(sub){sub.classList.add('open');sub=sub.parentElement.closest('.sidebar-subgroup');}
})();
/* 查看体检报告弹窗(版式参考常规体检报告页) */
function openReport(name){
document.getElementById('report-title').textContent=name+' · 体检报告';
/* 查看体检报告弹窗:常规体检 / 胃肠镜体检 / 冠脉CTA体检 三个页签 */
function openReport(idx){
var r=MASTER_ROWS[idx];
document.getElementById('report-title').textContent=r.name+' · 体检报告';
document.getElementById('regular-meta').innerHTML=
'<span><b>姓名:</b>'+r.name+'</span><span><b>性别:</b>'+r.gender+'</span><span><b>年龄:</b>'+r.age+'岁</span>'
+'<span><b>体检编号:</b>'+r.empNo+'</span><span><b>体检日期:</b>'+r.checkDate+'</span><span><b>体检医院:</b>'+r.hospital+'</span>';
document.getElementById('gastro-pane').innerHTML=gastroReportHtml(r);
document.getElementById('cta-pane').innerHTML=ctaReportHtml(r);
switchReportTab('regular');
document.getElementById('report-modal').classList.add('open');
}
function switchReportTab(key){
var tabs=document.querySelectorAll('#report-modal .report-tab');
for(var i=0;i<tabs.length;i++)tabs[i].classList.toggle('active',tabs[i].getAttribute('data-tab')===key);
var panes=document.querySelectorAll('#report-modal .report-pane');
for(var j=0;j<panes.length;j++)panes[j].classList.toggle('active',panes[j].getAttribute('data-tab')===key);
}
/* ============ 胃肠镜报告(内容参考员工端 report-gastro.html,版式与冠脉CTA报告一致) ============ */
var GASTRO_PROFILES=[
{esophagus:'食管黏膜光滑,色泽正常,血管纹理清晰,未见静脉曲张及占位性病变。贲门开闭良好。',
stomach:'胃底、胃体黏膜光滑,色泽橘红,黏液湖清亮。胃角切迹光滑,胃窦黏膜轻度充血水肿,未见糜烂及溃疡。幽门口圆,开闭良好。',
duodenum:'十二指肠球部及降部黏膜光滑,未见溃疡、糜烂及占位性病变。乳头形态正常。',
gastroDx:'慢性非萎缩性胃炎(胃窦)',
colon:'全结肠黏膜光滑,血管纹理清晰,未见充血、水肿、糜烂、溃疡及占位性病变。回盲瓣呈唇形,阑尾开口形态正常。结肠袋形态规整,蠕动良好。',
colonDx:'全结肠黏膜未见明显异常',
pathology:'(降结肠活检)送检肠黏膜组织,腺体排列整齐,间质少量淋巴细胞浸润,未见异型增生及癌细胞。病理诊断:黏膜慢性炎。'},
{esophagus:'食管黏膜光滑,色泽正常,血管纹理清晰,未见静脉曲张及占位性病变。贲门开闭良好。',
stomach:'胃底、胃体黏膜光滑,未见糜烂及溃疡;胃窦黏膜可见散在点状糜烂,覆薄白苔,周围黏膜充血水肿。幽门口圆,开闭良好。',
duodenum:'十二指肠球部及降部黏膜光滑,未见溃疡、糜烂及占位性病变。乳头形态正常。',
gastroDx:'糜烂性胃炎(胃窦)',
colon:'升结肠、横结肠黏膜光滑,血管纹理清晰;乙状结肠距肛门约25cm处可见一枚直径约0.4cm息肉,表面光滑,亚蒂。余结肠及直肠黏膜未见异常。',
colonDx:'乙状结肠息肉(内镜下已钳除,建议半年后复查)',
pathology:'(乙状结肠活检)送检黏膜组织呈管状腺瘤样改变,伴低级别上皮内瘤变,基底切缘干净。病理诊断:管状腺瘤伴低级别上皮内瘤变。'}
];
function gastroProfile(r){
var t=0;for(var i=0;i<r.empNo.length;i++)t+=r.empNo.charCodeAt(i);
return GASTRO_PROFILES[t%GASTRO_PROFILES.length];
}
function gastroReportHtml(r){
var p=gastroProfile(r);
var eye='<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
return '<div class="cta-rpt">'
+'<div class="cta-rpt__band">胃肠镜检查报告</div>'
+'<div class="cta-rpt__meta">'
+'<span><b>姓名:</b>'+r.name+'</span><span><b>性别:</b>'+r.gender+'</span><span><b>年龄:</b>'+r.age+'岁</span>'
+'<span><b>检查编号:</b>'+r.empNo+'</span><span><b>检查日期:</b>'+r.checkDate+'</span><span><b>检查医院:</b>'+r.hospital+'</span>'
+'</div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">胃镜检查</div>'
+'<table class="cta-vessel"><thead><tr><th style="width:110px;">项目</th><th>所见 / 结论</th></tr></thead><tbody>'
+'<tr><td class="v-name">食管</td><td>'+p.esophagus+'</td></tr>'
+'<tr><td class="v-name"></td><td>'+p.stomach+'</td></tr>'
+'<tr><td class="v-name">十二指肠</td><td>'+p.duodenum+'</td></tr>'
+'<tr><td class="v-name">诊断结果</td><td>'+p.gastroDx+'</td></tr>'
+'</tbody></table></div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">肠镜检查</div>'
+'<table class="cta-vessel"><thead><tr><th style="width:110px;">项目</th><th>所见 / 结论</th></tr></thead><tbody>'
+'<tr><td class="v-name">结肠</td><td>'+p.colon+'</td></tr>'
+'<tr><td class="v-name">诊断结果</td><td>'+p.colonDx+'</td></tr>'
+'<tr><td class="v-name">病理诊断</td><td>'+p.pathology+'</td></tr>'
+'</tbody></table></div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">报告图片</div><div class="cta-imgs">'
+'<span class="cta-img">'+eye+'胃镜报告<span class="link">查看</span></span>'
+'<span class="cta-img">'+eye+'肠镜报告<span class="link">查看</span></span>'
+'<span class="cta-img">'+eye+'活检报告<span class="link">查看</span></span>'
+'</div></div>'
+'</div>';
}
/* ============ 冠脉CTA报告(数据与版式取自检后-冠脉CTA体检报告页 post-cta-report.html ============ */
var CTA_PROFILES=[
{dominant:'右优势型',rate:'68',beta:true,agatston:86,cad:2,
vessels:[
{n:'左主干 LM',p:'none',g:0,d:'未见斑块及狭窄'},
{n:'左前降支 LAD(近段)',p:'mixed',g:2,d:'混合斑块,管腔狭窄约30%'},
{n:'左前降支 LAD(中段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'第一对角支 D1',p:'calc',g:1,d:'钙化斑块,管腔狭窄<25%'},
{n:'左回旋支 LCX(近段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'左回旋支 LCX(远段)',p:'calc',g:1,d:'少量钙化斑块,管腔未见明显狭窄'},
{n:'钝缘支 OM',p:'none',g:0,d:'未见异常'},
{n:'右冠状动脉 RCA(近段)',p:'calc',g:2,d:'钙化斑块,管腔狭窄约40%'},
{n:'右冠状动脉 RCA(中远段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'后降支 PDA',p:'none',g:0,d:'未见异常'}
],
cardiac:'各心腔大小未见扩大,左心耳未见血栓,主动脉及瓣膜未见明显钙化,心包少量积液。双肺下叶少许条索影。',
impression:['冠状动脉粥样硬化表现','LAD近段混合斑块伴管腔约30%狭窄;RCA近段钙化斑块伴约40%狭窄','余所见冠脉未见明显狭窄'],
advise:'建议心内科专科门诊评估,控制血压、血脂等危险因素,必要时行运动负荷心肌灌注或冠脉造影进一步检查。'}
,
{dominant:'右优势型',rate:'72',beta:true,agatston:0,cad:0,
vessels:[
{n:'左主干 LM',p:'none',g:0,d:'未见斑块及狭窄'},
{n:'左前降支 LAD(近段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'左前降支 LAD(中段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'第一对角支 D1',p:'none',g:0,d:'未见异常'},
{n:'左回旋支 LCX(近段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'左回旋支 LCX(远段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'钝缘支 OM',p:'none',g:0,d:'未见异常'},
{n:'右冠状动脉 RCA(近段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'右冠状动脉 RCA(中远段)',p:'none',g:0,d:'管壁光滑,未见狭窄'},
{n:'后降支 PDA',p:'none',g:0,d:'未见异常'}
],
cardiac:'各心腔大小、形态正常,左心耳未见血栓,瓣膜未见明显钙化,心包未见积液。双肺未见明显异常。',
impression:['冠脉CTA未见明显异常','未见确切斑块及管腔狭窄'],
advise:'本次冠脉CTA未见明显异常,建议结合临床,定期体检随访。'}
,
{dominant:'均衡型',rate:'65',beta:false,agatston:265,cad:3,
vessels:[
{n:'左主干 LM',p:'calc',g:1,d:'钙化斑块,管腔狭窄<25%'},
{n:'左前降支 LAD(近段)',p:'mixed',g:3,d:'混合斑块,管腔狭窄约60%'},
{n:'左前降支 LAD(中段)',p:'noncalc',g:2,d:'非钙化斑块,管腔狭窄约45%'},
{n:'第一对角支 D1',p:'calc',g:1,d:'钙化斑块,管腔狭窄<25%'},
{n:'左回旋支 LCX(近段)',p:'calc',g:2,d:'钙化斑块,管腔狭窄约35%'},
{n:'左回旋支 LCX(远段)',p:'none',g:0,d:'未见明显狭窄'},
{n:'钝缘支 OM',p:'calc',g:1,d:'少量钙化,管腔未见明显狭窄'},
{n:'右冠状动脉 RCA(近段)',p:'mixed',g:2,d:'混合斑块,管腔狭窄约45%'},
{n:'右冠状动脉 RCA(中远段)',p:'calc',g:3,d:'钙化斑块,管腔狭窄约55%'},
{n:'后降支 PDA',p:'none',g:0,d:'未见异常'}
],
cardiac:'左心室稍增大,各瓣膜未见明显钙化,心包未见积液。升主动脉增宽约38mm。双肺下叶纤维灶。',
impression:['多支冠状动脉粥样硬化并多发斑块形成','LAD近段混合斑块伴管腔中度狭窄(约60%);RCA远段钙化斑块伴中度狭窄(约55%','LAD中段、LCX近段、RCA近段轻-中度狭窄'],
advise:'建议尽快心内科专科就诊,行冠脉造影及必要时的血流储备分数(FFR)评估,规范抗血小板及降脂治疗。'}
];
function ctaProfile(r){
var t=0;for(var i=0;i<r.empNo.length;i++)t+=r.empNo.charCodeAt(i);
return CTA_PROFILES[t%CTA_PROFILES.length];
}
var PLAQUE_TXT={none:['未见斑块','none'],calc:['钙化斑块','calc'],noncalc:['非钙化斑块','noncalc'],mixed:['混合斑块','mixed']};
var NARROW_TXT=['未见','<25%','25%~49%','50%~69%','70%'];
var CAD_TXT=['CAD-RADS 0','CAD-RADS 1','CAD-RADS 2','CAD-RADS 3','CAD-RADS 4'];
function ctaReportHtml(r){
var p=ctaProfile(r);
var vrows=p.vessels.map(function(v){
var pl=PLAQUE_TXT[v.p];
var gcls=v.g>0?('cta-narrow--'+Math.min(v.g,4)):'';
return '<tr><td class="v-name">'+v.n+'</td>'
+'<td><span class="cta-plaque cta-plaque--'+pl[1]+'">'+pl[0]+'</span></td>'
+'<td class="v-grade">'+(v.g>0?('<span class="cta-narrow '+gcls+'">'+NARROW_TXT[v.g]+'</span>'):'<span class="cta-narrow cta-narrow--0">0%</span>')+'</td>'
+'<td>'+v.d+'</td></tr>';
}).join('');
return '<div class="cta-rpt">'
+'<div class="cta-rpt__band">冠状动脉CT血管成像报告</div>'
+'<div class="cta-rpt__meta">'
+'<span><b>姓名:</b>'+r.name+'</span><span><b>性别:</b>'+r.gender+'</span><span><b>年龄:</b>'+r.age+'岁</span>'
+'<span><b>检查编号:</b>'+r.empNo+'</span><span><b>检查日期:</b>'+r.checkDate+'</span><span><b>检查医院:</b>'+r.hospital+'</span>'
+'</div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">检查条件</div><div class="cta-rpt__txt">'
+'扫描时心率 '+p.rate+' 次/分'+(p.beta?'(已使用β受体阻滞剂控制心率)':'')+',屏气良好;图像质量满足诊断要求。冠脉分布:'+p.dominant+'。'
+'</div></div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">冠状动脉钙化积分</div><div class="cta-rpt__txt">'
+'Agatston 总积分约 '+p.agatston+' 分'+(p.agatston===0?'(未见钙化)':(p.agatston<100?'轻度':(p.agatston<400?'中度':'重度')))+''
+'</div></div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">冠状动脉分段所见</div>'
+'<table class="cta-vessel"><thead><tr><th style="width:170px;">血管 / 段</th><th style="width:110px;">斑块性质</th><th style="width:88px;">狭窄程度</th><th>描述</th></tr></thead><tbody>'
+vrows+'</tbody></table></div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">心脏及冠脉外所见</div><div class="cta-rpt__txt">'+p.cardiac+'</div></div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">结论与分级</div>'
+'<div class="cta-imp"><div class="cta-cad cta-cad--'+p.cad+'"><span style="font-size:22px;">'+p.cad+'</span><small>CAD-RADS 分级</small></div>'
+'<div class="cta-imp__list">'+p.impression.map(function(x,i){return ''+(i+1)+''+x;}).join('<br>')+'</div></div></div>'
+'<div class="cta-rpt__sec"><div class="cta-rpt__sec-t">建议</div><div class="cta-rpt__txt">'+p.advise+'</div></div>'
+'</div>';
}
function closeReport(){
document.getElementById('report-modal').classList.remove('open');
}
+4 -5
View File
@@ -211,16 +211,15 @@
<th>总人数</th><th>应检人数</th><th>已选项目人数</th><th>已预约人数</th><th>已检人数</th><th>未检人数</th>
<th>受检率<span class="th-help">?<span class="th-help__tip">受检率 = 实际检查人数 ÷ 应检人数</span></span></th>
<th>检出率<span class="th-help">?<span class="th-help__tip">检出率 = 异常人数 ÷ 实际检查人数</span></span></th>
<th>患病率<span class="th-help">?<span class="th-help__tip">患病率 = 单病种异常人数 ÷ 实际参加检查人数</span></span></th>
<th>重点指标异常人数</th><th>冠脉CTA异常人数</th><th>防癌筛查异常人数</th>
<th>计划总费用(元)</th><th>实际总费用(元)</th><th>人均费用(元)</th>
</tr>
</thead>
<tbody>
<tr><td class="td-frz frz0">1</td><td class="td-frz frz1">公司机关</td><td class="td-frz frz2 col-dept">综合管理部</td><td>2025</td><td>宝石花医院</td><td>宝石花医院</td><td>2025-06-23至2025-08-31</td><td>1000</td><td>1000</td><td>980</td><td>850</td><td>800</td><td>200</td><td>80.0%</td><td>41.0%</td><td>18.0%</td><td>328</td><td>9</td><td>12</td><td>2,600,000</td><td>2,180,000</td><td>2,725.00</td></tr>
<tr><td class="td-frz frz0">2</td><td class="td-frz frz1">采油一厂</td><td class="td-frz frz2 col-dept">采油作业一区</td><td>2025</td><td>宝石花医院</td><td>西安高新医院</td><td>2025-07-01至2025-09-15</td><td>3200</td><td>3000</td><td>2860</td><td>2450</td><td>2300</td><td>700</td><td>76.7%</td><td>40.0%</td><td>16.5%</td><td>920</td><td>24</td><td>35</td><td>7,800,000</td><td>6,210,000</td><td>2,700.00</td></tr>
<tr><td class="td-frz frz0">3</td><td class="td-frz frz1">采油二厂</td><td class="td-frz frz2 col-dept">采油作业二区</td><td>2025</td><td>宝石花医院</td><td>长安心血管医院</td><td>2025-07-10至2025-09-30</td><td>2800</td><td>2600</td><td>2480</td><td>2100</td><td>1950</td><td>650</td><td>75.0%</td><td>40.0%</td><td>17.2%</td><td>780</td><td>21</td><td>28</td><td>6,760,000</td><td>5,265,000</td><td>2,700.00</td></tr>
<tr><td class="td-frz frz0">4</td><td class="td-frz frz1">公司机关</td><td class="td-frz frz2 col-dept">综合管理部</td><td>2024</td><td>宝石花医院</td><td>宝石花医院</td><td>2024-06-20至2024-08-25</td><td>980</td><td>980</td><td>950</td><td>830</td><td>790</td><td>190</td><td>80.6%</td><td>39.0%</td><td>17.5%</td><td>308</td><td>8</td><td>10</td><td>2,450,000</td><td>2,020,000</td><td>2,556.96</td></tr>
<tr><td class="td-frz frz0">1</td><td class="td-frz frz1">公司机关</td><td class="td-frz frz2 col-dept">综合管理部</td><td>2025</td><td>宝石花医院</td><td>宝石花医院</td><td>2025-06-23至2025-08-31</td><td>1000</td><td>1000</td><td>980</td><td>850</td><td>800</td><td>200</td><td>80.0%</td><td>41.0%</td><td>328</td><td>9</td><td>12</td><td>2,600,000</td><td>2,180,000</td><td>2,725.00</td></tr>
<tr><td class="td-frz frz0">2</td><td class="td-frz frz1">采油一厂</td><td class="td-frz frz2 col-dept">采油作业一区</td><td>2025</td><td>宝石花医院</td><td>西安高新医院</td><td>2025-07-01至2025-09-15</td><td>3200</td><td>3000</td><td>2860</td><td>2450</td><td>2300</td><td>700</td><td>76.7%</td><td>40.0%</td><td>920</td><td>24</td><td>35</td><td>7,800,000</td><td>6,210,000</td><td>2,700.00</td></tr>
<tr><td class="td-frz frz0">3</td><td class="td-frz frz1">采油二厂</td><td class="td-frz frz2 col-dept">采油作业二区</td><td>2025</td><td>宝石花医院</td><td>长安心血管医院</td><td>2025-07-10至2025-09-30</td><td>2800</td><td>2600</td><td>2480</td><td>2100</td><td>1950</td><td>650</td><td>75.0%</td><td>40.0%</td><td>780</td><td>21</td><td>28</td><td>6,760,000</td><td>5,265,000</td><td>2,700.00</td></tr>
<tr><td class="td-frz frz0">4</td><td class="td-frz frz1">公司机关</td><td class="td-frz frz2 col-dept">综合管理部</td><td>2024</td><td>宝石花医院</td><td>宝石花医院</td><td>2024-06-20至2024-08-25</td><td>980</td><td>980</td><td>950</td><td>830</td><td>790</td><td>190</td><td>80.6%</td><td>39.0%</td><td>308</td><td>8</td><td>10</td><td>2,450,000</td><td>2,020,000</td><td>2,556.96</td></tr>
</tbody>
</table>
</div>
@@ -204,6 +204,7 @@
<a href="config-standard-hospital.html" class="sidebar-item">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -171,6 +171,7 @@
<a href="config-standard-hospital.html" class="sidebar-item">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -166,6 +166,7 @@
<a href="config-standard-hospital.html" class="sidebar-item">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -198,6 +198,7 @@
<a href="config-standard-hospital.html" class="sidebar-item">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -332,6 +333,7 @@
<img id="cover-preview" style="display:none;">
</div>
<input type="file" id="cover-file" accept="image/*" style="display:none;" onchange="onCoverChange(this)">
<div class="form-item__hint">支持 JPG/PNG 格式,大小不超过 5MB,建议尺寸 1280×72016:9</div>
</div>
<div class="form-item">
<label class="form-item__label">视频文件</label>
@@ -342,7 +344,8 @@
</div>
<span id="video-name" style="display:none;font-size:var(--font-sm);color:var(--text-primary);"></span>
</div>
<input type="file" id="video-file" accept="video/*" style="display:none;" onchange="onVideoChange(this)">
<input type="file" id="video-file" accept="video/mp4" style="display:none;" onchange="onVideoChange(this)">
<div class="form-item__hint">支持 MP4 格式,大小不超过 200MB,时长建议 10 分钟以内</div>
</div>
</div>
</div>
@@ -208,6 +208,7 @@
<a href="config-standard-hospital.html" class="sidebar-item">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -215,6 +215,7 @@
<a href="config-standard-hospital.html" class="sidebar-item">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -219,6 +219,7 @@
<a href="config-standard-hospital.html" class="sidebar-item">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -92,6 +92,11 @@
.form-input:focus{border-color:var(--primary);box-shadow:0 0 0 2px rgba(24,144,255,0.2);}
.form-select{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 28px 0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;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 10px center;appearance:none;cursor:pointer;}
.form-select:focus{border-color:var(--primary);}
/* 复选列表 */
.checkbox-list{border:1px solid var(--border);border-radius:var(--radius-sm);padding:4px 16px;}
.checkbox-item{display:flex;align-items:center;gap:8px;padding:9px 0;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;}
.checkbox-item + .checkbox-item{border-top:1px solid #F0F0F0;}
.checkbox-item input{accent-color:var(--primary);width:14px;height:14px;cursor:pointer;flex-shrink:0;}
</style>
</head>
<body>
@@ -155,6 +160,7 @@
<a href="config-standard-hospital.html" class="sidebar-item active">医院体检项目</a>
</div>
</div>
<a href="agent-device.html" class="sidebar-item">前置机管理</a>
</aside>
<!-- 主内容 -->
@@ -296,6 +302,31 @@
</div>
</div>
<!-- 同步医院体检项目确认弹窗 -->
<div class="modal-overlay" id="sync-confirm-modal" onclick="if(event.target===this)closeSyncConfirmModal()">
<div class="modal">
<div class="modal__header">
<div class="modal__title">同步医院体检项目</div>
<button class="modal__close" onclick="closeSyncConfirmModal()"></button>
</div>
<div class="modal__body">
<div style="font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:16px;">选择医院进行同步医院体检项目,结果可在查看同步任务中查看</div>
<div class="form-item">
<div class="form-label"><span class="required">*</span> 选择医院(可多选)</div>
<div class="checkbox-list" id="sync-hospital-list">
<label class="checkbox-item"><input type="checkbox" id="sync-hospital-all" onchange="toggleAllSyncHospitals(this)">全选</label>
<label class="checkbox-item"><input type="checkbox" value="泾河医院">泾河医院</label>
<label class="checkbox-item"><input type="checkbox" value="庆阳医院">庆阳医院</label>
</div>
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeSyncConfirmModal()">取消</button>
<button class="btn btn-primary" onclick="confirmSync()">确认同步</button>
</div>
</div>
</div>
<script>
/* 侧边栏展开/折叠交互 */
function toggleSubgroup(header){
@@ -395,16 +426,17 @@ var syncTasks = [
]}
];
/* 模拟一次同步拉取到的增量项目 */
function makeSyncItems(){
/* 模拟一次同步拉取到的增量项目(按所选医院过滤) */
function makeSyncItems(hospitals){
var pool = [
{ hospital: '泾河医院', code: 'JH-XM015', name: '糖类抗原199', basic: 'XM015(糖类抗原)', sys: '肿瘤标志物套餐', action: '新增' },
{ hospital: '泾河医院', code: 'JH-XM016', name: '甲胎蛋白', basic: 'XM016(甲胎蛋白)', sys: '肿瘤标志物套餐', action: '新增' },
{ hospital: '泾河医院', code: 'JH-XM001', name: '身高测量', basic: 'XM001(身高)', sys: '身高体重检测, 基础体检套餐', action: '更新' },
{ hospital: '庆阳医院', code: 'QY-XM008', name: '癌胚抗原', basic: 'XM008(癌胚抗原)', sys: '肿瘤标志物套餐', action: '新增' }
];
var n = 2 + Math.floor(Math.random() * 3);
return pool.slice(0, n);
var items = pool.filter(function(it){ return hospitals.indexOf(it.hospital) !== -1; });
var n = Math.min(items.length, 2 + Math.floor(Math.random() * 3));
return items.slice(0, n);
}
function nowTimeStr(){
@@ -456,9 +488,29 @@ function closeSyncDetailModal(){
document.getElementById('sync-detail-modal').classList.remove('open');
}
/* 打开同步确认弹窗 */
function syncHospitalItems(){
if(!confirm('是否从医院同步增量体检项目?')) return;
var task = { time: nowTimeStr(), user: '系统管理员', status: 'running', items: makeSyncItems() };
var boxes = document.querySelectorAll('#sync-hospital-list input[type=checkbox]');
boxes.forEach(function(c){ c.checked = false; });
document.getElementById('sync-confirm-modal').classList.add('open');
}
function closeSyncConfirmModal(){
document.getElementById('sync-confirm-modal').classList.remove('open');
}
function toggleAllSyncHospitals(master){
document.querySelectorAll('#sync-hospital-list input[type=checkbox]').forEach(function(c){
if(c.id !== 'sync-hospital-all') c.checked = master.checked;
});
}
/* 确认同步:校验医院后创建同步任务 */
function confirmSync(){
var selected = [];
document.querySelectorAll('#sync-hospital-list input[type=checkbox]').forEach(function(c){
if(c.checked && c.value) selected.push(c.value);
});
if(!selected.length){ alert('请选择医院'); return; }
closeSyncConfirmModal();
var task = { time: nowTimeStr(), user: '系统管理员', status: 'running', items: makeSyncItems(selected) };
syncTasks.unshift(task);
renderSyncTasks();
/* 模拟异步同步:2 秒后完成 */
+30
View File
@@ -1,5 +1,35 @@
# 健康体检模块 CHANGELOG
## [Unreleased] 2026-09-20
### Added
- 检后「体检统计汇总」分组新增「结果动态综合统计」页面 `post-result-comprehensive-stat.html`:以 2×2 网格展示 4 张堆积柱状图,统计维度固定为性别/年龄/部门/岗位,每张图自带「项目」「维度」下拉可独立切换,查看该维度各取值下的正常/异常人数及占比——竖向堆积柱状图(X 轴为维度取值、Y 轴为人数,每柱正常在下绿/异常在上红堆叠,柱顶标总数、段内标百分比、悬停显示明细);纯手写 SVG,无第三方图表库;体检项目下拉 12 项数据驱动,异常判定按年龄/性别/岗位浮动、哈希保证结果稳定;4 个面板互相独立,默认项目均为血常规、维度分别为性别/年龄/部门/岗位
- 23 个检后页面侧边栏「体检统计汇总」分组批量同步新增「结果动态综合统计」菜单项(紧邻「单位体检项目统计」之后)
- `index.html` SITE_MAP 健康体检模块新增「结果动态综合统计」入口(紧邻「单位体检项目统计」之后)
- `PRD.md`:补全缺失的「单位体检项目统计(post-unit-item-stat.html)」文档,新增「结果动态综合统计」导航节点/页面清单条目/逐页说明(52、53 节),页面清单与逐页说明 52 节及之后编号整体顺延 +2(原 52–68 → 54–70
- `PRD.md` 按实际页面补全 6 个此前未入册的页面文档并配截图——检中「体检预约跟踪(mid-appointment-track2.html4 页签贯穿应检→已选→已预约→已参检)」、检后常规体检「单位群体报告(post-group-report.html10 张概览卡含较上年趋势 + 6 个报告卡片)」、检后新增「体检影像管理」分组 4 页(用户体检信息/用户影像文件分类/用户影像文件详细/文件处理日志);导航树新增「体检影像管理」分组节点,页面清单与逐页说明编号顺延(33–45 → 34–46、4667 → 4869、6870 → 7476,新增 33/47/7073 六节)
- `screenshot-prd.js` TASKS 新增 6 页并重拍截图 `mid-appointment-track2 / post-group-report / post-imc-user / post-imc-class / post-imc-info / post-imc-log`
## [Unreleased] 2026-09-18
### Added
- 检后侧边栏新增「体检影像管理」二级菜单(位于冠脉CTA体检分组之后),复刻老系统 data-center-vue3 `views/imc` 四个列表页为纯 HTML 原型:
- `post-imc-user.html`(用户体检信息):筛选(姓名/身份证号/体检年份/医院名称);行勾选 + 批量删除;主表列(单位信息/姓名/身份证号/体检年份/医院名称/体检编号/创建日期);行首箭头展开子表(检查项目名称/处理状态/影像信息·预览/影像报告·查看,无数据显示「暂无/无文件」);操作列编辑/详情/删除 + 编辑弹窗(7 字段)+ 详情弹窗
- `post-imc-class.html`(用户影像文件分类):筛选(影像分类/检查项目名称/处理状态);操作栏新增/导出/导入 + 批量删除;列(影像分类/检查项目名称/影像文件类型/报告文件类型/处理状态/检查结果/错误信息/出报告时间/创建日期),检查结果与错误信息长文本截断、悬停显示全文;操作列编辑/详情/删除 + 新增/编辑弹窗(10 字段)+ 详情弹窗
- `post-imc-info.html`(用户影像文件详细):筛选(文件类型/患者ID/研究实例ID);操作栏新增/导出/导入 + 批量删除;列(文件类型·影像/报告标签/影像序号/错误信息/文件地址·下载/dcm实例ID/研究实例ID/患者ID/研究ID/创建日期);操作列编辑/详情/删除 + 新增/编辑弹窗(10 字段)+ 详情弹窗
- `post-imc-log.html`(文件处理日志):筛选(身份证号/体检年份/体检编号/影像分类/处理状态);行勾选 + 批量删除;列(医院名称/姓名/身份证号/体检年份/体检编号/影像分类/json数据·截断/文件地址·下载zip/文件大小MB/处理状态/错误信息/传输开始/传输完成/解析开始/解析完成时间),默认按传输开始时间倒序;操作列详情/删除 + 详情弹窗(15 字段含 json 数据代码块)
- 数据字典沿用老系统:影像分类(超声/CT/MRI/X射线/心电)、处理状态(新建/待解析/失败/解析中/成功/失败待重试/最终失败,彩色标签)、文件类型(影像/报告)
- `index.html` SITE_MAP 健康体检模块新增「用户体检信息/用户影像文件分类/用户影像文件详细/文件处理日志」四个入口
- 25 个检后页面侧边栏统一新增「体检影像管理」分组(21 个存量页面批量同步 + 4 个新页面自身 active 指向)
## [Unreleased] 2026-09-17
### Changed
- `post-unit-cost-stat.html`:单位体检费用统计列表增加"体检医院"列(部门列之后)并在"体检年份"后增加"签约医院"列(该单位签约的体检医院,可能存在多个,逗号相隔);因一个单位会存在多个体检医院,列表按"单位 × 体检医院"拆行——原 6 条单位年度汇总行拆为 16 条医院明细行(各分组内人数/报告/费用合计与原数据保持一致,计划费用仍按 2062.5 元/人 × 已体检人数口径),分页条数同步更新为 16 条,行配置 ROWS 补充 hospital/signed 字段供弹窗标题使用
- `post-unit-cost-stat.html`:列表去掉"总人数"列;"体检报告缺少体检项目信息"弹窗增加"项目费用(元)""折扣后费用(元)"两列(折扣后统一按 8 折示例);统计明细弹窗缺少项目口径的"项目总数"标蓝可点击,点击同样打开体检报告缺少体检项目信息弹窗;员工费用明细弹窗"报告缺少项目数"标蓝可点击(报告不完整时),点击同样打开体检报告缺少体检项目信息弹窗;员工费用明细弹窗顶部新增筛选栏(姓名/员工编号模糊查询、报告完整状态 全部/完整/不完整,查询/重置,打开弹窗时自动重置,无结果显示"暂无数据",合计行随筛选结果汇总)及"导出列表数据"按钮
- `PRD.md`:第 51 节"单位体检费用统计"功能说明改为按"单位 × 体检医院"统计(一行是一个体检医院),表格列说明同步补充"体检医院""签约医院"两列并去掉"总人数",统计明细弹窗/缺少项目弹窗说明同步更新
- `PRD.md`/`screenshot-prd.js`/截图:按当日 17 个页面改动同步更新 PRD 说明并重截 16 张页面截图;`screenshot-prd.js` 支持传入页面名前缀只重截指定页——具体更新:第 1 节个人体检数据报告弹窗改为常规/胃肠镜/冠脉CTA三页签专业版式;第 4 节操作说明补充封面图(JPG/PNG ≤5MB 1280×720)与视频(MP4 ≤200MB)上传提示;第 10 节医院体检项目新增"同步医院体检项目"多选医院弹窗说明并删除已落地的同步方式补充说明;第 12/27/30 节三类体检计划管理补充"新增模式体检年份锁定当年、隐藏参加人员范围"规则;第 17 节组合模板项目表格增加"金额(元)"列说明并删除已落地的默认费用补充说明;第 22 节指定员工体检医院操作栏增加"批量导入人员""查看导入任务";第 25 节预选信息统计移除"医院"Tab 仅保留部门维度;第 32 节体检预约"体检预约信息"列移至"预约状态"之后;第 36 节医院实时预约人数日期默认改为当日至 30 日后;第 44 节冠脉CTA单位预约统计移除"填写问卷人数"列;第 45 节常规体检报告补充复选框列、"报告模板导入"按钮、体检报告补录弹窗、员工操作确认弹窗及专业版式报告弹窗;第 51 节员工费用明细弹窗补充筛选栏/导出/缺少项目数蓝链;第 62 节胃肠镜体检报告"详情"弹窗改为"查看报告"专业版式、编辑弹窗仅报告图片可修改;第 68 节结果异常移除"异常等级"筛选/列与页首定义说明框
## [Unreleased] 2026-09-15
### Added
+292 -132
View File
@@ -13,6 +13,7 @@
│ ├── 基础配置 → config-material-notice.html
│ ├── 检前 → pre-hospital-scope.html
│ ├── 检中 → mid-appointment-track.html
│ │ ├── 体检预约跟踪 → mid-appointment-track2.html
│ ├── 检后 → post-unit-count.html
│ ├── 前置机管理 → agent-device.html
│ └── 异常事件 → exception-dirty.html
@@ -93,6 +94,7 @@
├── 检后(侧边栏)
│ ├── 常规体检
│ │ ├── 常规体检报告 → post-report-employee.html
│ │ ├── 单位群体报告 → post-group-report.html
│ │ ├── 员工体检大表 → post-employee-master.html
│ │ ├── 大病人员信息 → post-illness-personnel.html
│ │ ├── 指标异常通知 → post-indicator-notify.html
@@ -102,6 +104,8 @@
│ │ │ └── Tab:二级单位 / 三级单位 / 体检医院
│ │ ├── 单位体检费用统计 → post-unit-cost-stat.html
│ │ │ └── Tab:二级单位 / 三级单位
│ │ ├── 单位体检项目统计 → post-unit-item-stat.html
│ │ ├── 结果动态综合统计 → post-result-comprehensive-stat.html
│ │ ├── 医院体检人数统计 → post-hospital-count.html
│ │ ├── 医生检查人数统计 → post-doctor-count.html
│ │ ├── 医院体检项目统计 → post-item-stat.html
@@ -117,10 +121,15 @@
│ │ ├── 胃肠镜体检报告 → post-report-gastro.html
│ │ └── 胃肠镜体检统计 → post-gastro-stat.html
│ │ └── Tab:二级单位 / 三级单位
── 冠脉CTA体检
── 冠脉CTA体检
│ ├── 冠脉CTA体检报告 → post-cta-report.html
│ └── 冠脉CTA体检统计 → post-cta-stat.html
│ └── Tab:二级单位 / 三级单位
│ └── 体检影像管理
│ ├── 用户体检信息 → post-imc-user.html
│ ├── 用户影像文件分类 → post-imc-class.html
│ ├── 用户影像文件详细 → post-imc-info.html
│ └── 文件处理日志 → post-imc-log.html
├── 前置机管理(侧边栏)
│ └── 常规体检前置服务 → agent-device.html
@@ -196,57 +205,65 @@
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 32 | 体检预约 | mid-appointment-track.html | 页面 |
| 33 | 已选项目 | mid-selected-items.html | 页面 |
| 34 | 已检员工 | mid-participation.html | 页面 |
| 35 | 单位预约人数统计 | mid-unit-appointment.html | 页面 |
| 36 | 医院实时预约人数 | mid-hospital-realtime.html | 页面 |
| 37 | 医实时检查情况 | mid-doctor-realtime.html | 页面 |
| 38 | 医院预约人数统计 | mid-hospital-appointment.html | 页面 |
| 39 | 签约单位应检人数 | mid-unit-expected.html | 页面 |
| 40 | 员工预约跟踪(胃肠镜) | mid-gastro-appointment-track.html | 页面 |
| 41 | 单位预约统计(胃肠镜) | mid-gastro-unit-appointment.html | 页面 |
| 42 | 胃肠镜问卷信息 | mid-gastro-questionnaire.html | 页面 |
| 43 | 员工预约跟踪(冠脉CTA | mid-cta-appointment-track.html | 页面 |
| 44 | 单位预约统计(冠脉CTA | mid-cta-unit-appointment.html | 页面 |
| 33 | 体检预约跟踪 | mid-appointment-track2.html | 页面 |
| 34 | 已选项目 | mid-selected-items.html | 页面 |
| 35 | 已检员工 | mid-participation.html | 页面 |
| 36 | 单位预约人数统计 | mid-unit-appointment.html | 页面 |
| 37 | 医实时预约人数 | mid-hospital-realtime.html | 页面 |
| 38 | 医生实时检查情况 | mid-doctor-realtime.html | 页面 |
| 39 | 医院预约人数统计 | mid-hospital-appointment.html | 页面 |
| 40 | 签约单位应检人数 | mid-unit-expected.html | 页面 |
| 41 | 员工预约跟踪(胃肠镜) | mid-gastro-appointment-track.html | 页面 |
| 42 | 单位预约统计(胃肠镜) | mid-gastro-unit-appointment.html | 页面 |
| 43 | 胃肠镜问卷信息 | mid-gastro-questionnaire.html | 页面 |
| 44 | 员工预约跟踪(冠脉CTA | mid-cta-appointment-track.html | 页面 |
| 45 | 单位预约统计(冠脉CTA | mid-cta-unit-appointment.html | 页面 |
### 五、检后(侧边栏:检后)
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 45 | 常规体检报告 | post-report-employee.html | 页面 |
| 46 | 员工体检大表 | post-employee-master.html | 页面 |
| 47 | 大病人员信息 | post-illness-personnel.html | 页面 |
| 48 | 指标异常通知 | post-indicator-notify.html | 页面 |
| 49 | 体检报告未匹配项目 | post-report-unmatched.html | 页面 |
| 50 | 单位体检人数统计 | post-unit-count.html | 页面 |
| 51 | 单位体检费用统计 | post-unit-cost-stat.html | 页面 |
| 52 | 医院体检人数统计 | post-hospital-count.html | 页面 |
| 53 | 医生检查人数统计 | post-doctor-count.html | 页面 |
| 54 | 医院体检项目统计 | post-item-stat.html | 页面 |
| 55 | 防癌筛查人数统计 | post-cancer-screening.html | 页面 |
| 56 | 系统标准化项目统计 | post-standard-stat.html | 页面 |
| 57 | 签约医院人数统计 | post-contracted-hospital.html | 页面 |
| 58 | 重点指标异常统计 | post-key-indicator-stat.html | 页面 |
| 59 | 病种信息配置 | post-disease-info.html | 页面 |
| 60 | 体检结论关联病种 | post-conclusion-disease.html | 页面 |
| 61 | 员工病种信息 | post-auto-select-disease.html | 页面 |
| 62 | 胃肠镜体检报告 | post-report-gastro.html | 页面 |
| 63 | 胃肠镜体检统计 | post-gastro-stat.html | 页面 |
| 64 | 冠脉CTA体检报告 | post-cta-report.html | 页面 |
| 65 | 冠脉CTA体检统计 | post-cta-stat.html | 页面 |
| 46 | 常规体检报告 | post-report-employee.html | 页面 |
| 47 | 单位群体报告 | post-group-report.html | 页面 |
| 48 | 员工体检大表 | post-employee-master.html | 页面 |
| 49 | 大病人员信息 | post-illness-personnel.html | 页面 |
| 50 | 指标异常通知 | post-indicator-notify.html | 页面 |
| 51 | 体检报告未匹配项目 | post-report-unmatched.html | 页面 |
| 52 | 单位体检人数统计 | post-unit-count.html | 页面 |
| 53 | 单位体检费用统计 | post-unit-cost-stat.html | 页面 |
| 54 | 单位体检项目统计 | post-unit-item-stat.html | 页面 |
| 55 | 结果动态综合统计 | post-result-comprehensive-stat.html | 页面 |
| 56 | 医院体检人数统计 | post-hospital-count.html | 页面 |
| 57 | 医生检查人数统计 | post-doctor-count.html | 页面 |
| 58 | 医院体检项目统计 | post-item-stat.html | 页面 |
| 59 | 防癌筛查人数统计 | post-cancer-screening.html | 页面 |
| 60 | 系统标准化项目统计 | post-standard-stat.html | 页面 |
| 61 | 签约医院人数统计 | post-contracted-hospital.html | 页面 |
| 62 | 重点指标异常统计 | post-key-indicator-stat.html | 页面 |
| 63 | 病种信息配置 | post-disease-info.html | 页面 |
| 64 | 体检结论关联病种 | post-conclusion-disease.html | 页面 |
| 65 | 员工病种信息 | post-auto-select-disease.html | 页面 |
| 66 | 胃肠镜体检报告 | post-report-gastro.html | 页面 |
| 67 | 胃肠镜体检统计 | post-gastro-stat.html | 页面 |
| 68 | 冠脉CTA体检报告 | post-cta-report.html | 页面 |
| 69 | 冠脉CTA体检统计 | post-cta-stat.html | 页面 |
| 70 | 用户体检信息 | post-imc-user.html | 页面 |
| 71 | 用户影像文件分类 | post-imc-class.html | 页面 |
| 72 | 用户影像文件详细 | post-imc-info.html | 页面 |
| 73 | 文件处理日志 | post-imc-log.html | 页面 |
### 六、前置机管理(侧边栏:前置机管理)
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 66 | 常规体检前置服务(含设备详情/编辑设备/日志查看/升级管理/接口测试/操作审计/重启确认弹窗) | agent-device.html | 页面 |
| 74 | 常规体检前置服务(含设备详情/编辑设备/日志查看/升级管理/接口测试/操作审计/重启确认弹窗) | agent-device.html | 页面 |
### 七、异常事件(侧边栏:异常事件)
| 序号 | 页面/弹窗 | 文件 | 类型 |
|------|-----------|------|------|
| 67 | 系统脏数据 | exception-dirty.html | 页面 |
| 68 | 结果异常 | exception-result.html | 页面 |
| 75 | 系统脏数据 | exception-dirty.html | 页面 |
| 76 | 结果异常 | exception-result.html | 页面 |
---
@@ -269,7 +286,11 @@
- 一级分类→ 二级分类→ 具体项目,重点项目列分三组:
- 操作列(冻结):蓝色链接"查看报告"
**体检报告弹窗:** 标题"姓名 · 体检报告"含体检结论、体检建议、体检项目;每张子表列为项目名称/结果/单位/参考值,异常结果红色标注。支持 ✕/关闭按钮/遮罩/Esc 关闭。
**体检报告弹窗:** 标题"姓名 · 体检报告"顶部页签切换三类报告(常规体检 / 胃肠镜体检 / 冠脉CTA体检,默认常规体检),均采用专业报告版式(深色报告头 + 基本信息 + 分节内容):
- 常规体检:报告头"健康体检报告",基本信息含姓名/性别/年龄/体检编号/体检日期/体检医院;正文含体检结论(橙色提示框)、体检建议(绿色提示框)及物理检查/内科/外科/化学检查等分科明细;每张子表列为项目名称/结果/单位/参考值,异常结果红色标注
- 胃肠镜体检:报告头"胃肠镜检查报告",基本信息含检查编号/检查日期/检查医院;胃镜检查表(食管/胃/十二指肠/诊断结果)、肠镜检查表(结肠/诊断结果/病理诊断)+ 报告图片(胃镜报告/肠镜报告/活检报告,"查看"链接)
- 冠脉CTA体检:报告头"冠状动脉CT血管成像报告",基本信息含检查编号/检查日期/检查医院;检查条件(心率/屏气/冠脉分布)、冠状动脉钙化积分(Agatston 总积分)、冠脉分段表格(血管/段、斑块性质标签:钙化/非钙化/混合/无、狭窄程度按分级着色、描述)+ 心脏及心包所见 + 印象(CAD-RADS 评分色块 + 结论列表)+ 建议 + 报告图片
- 报告内容按员工自动匹配两套示例档案(正常/异常),支持 ✕/关闭按钮/遮罩/Esc 关闭。
**业务规则:**
- 异常判定:任一项目结果带异常标记即视为异常员工,"异常员工"筛选据此过滤
@@ -325,9 +346,6 @@
- "发布生效"将该类型须知发布至员工端
- 三个 Tab 内容独立:常规(体检前准备/体检当天/特殊项目提醒等)、胃肠镜(检前饮食/肠道准备/检查当天等)、冠脉CTA(检前准备/心率控制/屏气练习等)
**补充说明**
- 体检须知在预约时间点会按规则发送短信,短信内容要不要在这里配置和预览?注意单条短信字数有限,字数多了会分多条短信发送
---
#### 4. 操作说明(config-material-video.html
@@ -346,7 +364,7 @@
- 讲解类型 *(分段切换:图文讲解/视频讲解;编辑态锁定为只读)
- 标题 *(输入框)、简介(多行文本域)、排序(数字,越小越靠前)
- 图文讲解专属:操作说明(富文本编辑器)
- 视频讲解专属:封面图(上传)、视频文件(上传
- 视频讲解专属:封面图(上传,提示"支持 JPG/PNG 格式,大小不超过 5MB,建议尺寸 1280×720(16:9)")、视频文件(上传,仅 MP4,提示"支持 MP4 格式,大小不超过 200MB,时长建议 10 分钟以内"
- 底部:取消、保存
**预览弹窗:** 视频类型为 16:9 播放器占位;图文类型渲染富文本内容。
@@ -490,14 +508,13 @@
**新增/编辑弹窗:** 医院 *(下拉)、体检项目名称 *、体检项目编码 *;底部取消、保存
**同步医院体检项目弹窗:** 点击"同步医院体检项目"打开;说明文案"选择医院进行同步医院体检项目,结果可在查看同步任务中查看";选择医院 *(复选列表:全选 + 各医院多选);底部取消、确认同步。
**同步任务记录弹窗:** 表格列:触发时间、同步人、状态(蓝色"同步中"/绿色"成功"/红色"失败"标签)、同步数据信息("查看详情"链接);底部关闭
**同步数据信息弹窗:** 点击任务行"查看详情"打开;顶部摘要显示触发时间、同步人、共同步条数;表格列:序号、医院、体检项目编码、体检项目名称、关联体检基本项、关联医院系统项目、同步结果(蓝色"新增"/绿色"更新"标签);底部关闭
**业务规则:** "同步医院体检项目"确认后生成一条"同步中"任务(同步中即可查看本次明细),完成后变"成功";编辑仅回填医院/编码/名称;删除有确认提示。
**补充说明**
考虑一下选择指定医院同步和全部医院同步
**业务规则:** "同步医院体检项目"支持指定医院同步(可多选)或全选全部医院同步;未选医院时提示"请选择医院";确认后生成一条"同步中"任务(同步中即可查看本次明细),完成后变"成功",增量项目按所选医院过滤;编辑仅回填医院/编码/名称;删除有确认提示。
---
@@ -544,11 +561,11 @@
**表格列:** 序号、组织单位、体检年份、体检计划名称、承检医院、签约医院、有效标志(绿色标签)、体检计划日期范围、允许预约日期范围(未开启时灰色"未开启")、创建人、创建时间、操作(详情/编辑/删除)
**新增/编辑抽屉:**
- 组织单位 *(下拉)、体检年份 *(下拉)、体检计划名称 *(输入框)
- 组织单位 *(下拉)、体检年份 *(下拉,新增模式锁定为当年不可改)、体检计划名称 *(输入框)
- 承检医院 *(多选下拉,标签可删)
- 签约医院(多选,选项仅限已勾选的承检医院,未选承检医院时禁用并提示"请先选择承检医院")
- 体检计划日期范围 *(日期至日期)
- 参加人员范围 *(指定人员面板):指定机构(单位-部门两级树多选,部门全选自动收拢为单位级;默认选中组织单位)+ 指定员工(打开二级"选择员工"弹窗)+ 已选员工标签
- 参加人员范围 *(指定人员面板,新增模式下隐藏该区域,编辑时显示):指定机构(单位-部门两级树多选,部门全选自动收拢为单位级;默认选中组织单位)+ 指定员工(打开二级"选择员工"弹窗)+ 已选员工标签
- 是否开启预约(开关,默认开启)、允许预约日期范围 *(开启时显示)
- 底部:取消、确认新增/保存
@@ -562,7 +579,8 @@
- 承检医院变化时同步剔除失效签约医院;
- 是否开启预约关闭后,员工在移动端无法进行预约
- 机构勾选联动员工弹窗过滤;
- 保存校验组织单位/承检医院/计划名称/日期范围/参加人员/预约范围必填且结束≥开始;
- 新增模式:体检年份锁定为当年、隐藏参加人员范围区域(新增后通过编辑补充);编辑模式恢复可编辑
- 保存校验组织单位/承检医院/计划名称/日期范围/预约范围必填且结束≥开始,参加人员范围仅编辑时校验必选;
- 删除有确认提示。
- 可供员工预约的日期范围是医院承检日期+单位允许日期范围的交叉日期,并且在可预约日期的前一天/当天(当天可能是刚创建体检计划,但是预约日期也是当天)给员工发送短信,提醒员工进行预约体检
@@ -668,16 +686,14 @@
**新增/编辑组合模板抽屉:**
- 组合模板名称 *、体检医院 *(搜索下拉)、性别 *(男/女/通用)、年龄范围 *、女性专项费用(元)(仅性别=女时显示)、折扣类型 *(数值/百分比)、折扣金额 *(数字)
- 内嵌 Tab:必检项目 / 自选项目 / 妇科未婚项目 / 妇科已婚项目 / 防癌筛查项目(性别=男隐藏两个妇科 Tab;防癌筛查 Tab 显示"防癌筛查默认费用(元)"输入)
- 每个 Tab:+ 添加项目 + 表格(体检项目[可搜索下拉]、项目编码[自动填充只读]、项目标记[一般项目/重点项目]、操作[删除])
- 每个 Tab:+ 添加项目 + 表格(体检项目[可搜索下拉]、项目编码[自动填充只读]、项目标记[一般项目/重点项目]、金额(元)[自动填充只读]、操作[删除]
- 底部:取消、确认保存
**复制上一年度模板弹窗:** 上一年度(复制来源,禁用)、本年度(复制目标,禁用)、体检医院 *、模板 *(未选医院时禁用);提示"确认后将把所选医院的上一年度模板复制到本年度,已存在的同名模板将被覆盖。";取消/确认复制
**组合模板详情弹窗(只读):** 医院名称、体检年份、模板名称、性别、年龄范围、女性专项费用、折扣类型、折扣金额 + 内嵌同名 5 Tab 项目表格(防癌 Tab 末尾显示默认费用);底部关闭
**业务规则:** 性别联动女性专项费用与妇科 Tab 显隐;折扣类型决定金额落在"折扣数值"或"折扣百分比"列;复制弹窗医院→模板二级联动。
**补充说明:** 建议增加体检项目的默认费用显示。
**业务规则:** 性别联动女性专项费用与妇科 Tab 显隐;折扣类型决定金额落在"折扣数值"或"折扣百分比"列;复制弹窗医院→模板二级联动;选择体检项目后自动带出该项目金额(按项目库单价,妇科/防癌等特殊项目按编码匹配)
---
@@ -785,6 +801,7 @@
**业务规则:**
- 添加为职业体检的员工,在移动端选择医院时,医院列表只显示支持职业体检的医院
- 导入和添加员工时,需判断当前员工是否已预约,如果已预约,则不能被添加
---
#### 22. 指定员工体检医院(pre-special-hospital.html
@@ -795,7 +812,7 @@
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位(下拉)、部门(下拉)、指定体检医院(下拉)、姓名、员工编号、身份证号(输入框)+ 查询/重置按钮
**操作按钮:** + 添加指定体检医院员工、导出列表数据、查看导出任务、批量删除
**操作按钮:** + 添加指定体检医院员工、导出列表数据、查看导出任务、批量导入人员、查看导入任务、批量删除
**表格列:** 复选框(全选)、姓名、员工编号、性别、年龄、指定体检医院、身份证号、手机号、单位、部门、年度体检、创建日期、操作(删除)
@@ -847,19 +864,15 @@
![页面截图](screenshots/pre-gastro-stat.jpg)
**功能说明:** 按"部门"和"医院"两个维度统计胃肠镜预选人数;点击预选人数数字弹出人员明细。
**功能说明:** 按"部门"维度统计胃肠镜预选人数;点击预选人数数字弹出人员明细。
**Tab 切换:** 部门 / 医院(各自独立筛选栏与表格)
**筛选条件:** 体检年份(年份日历选择控件)、单位部门(下拉)、预选医院(下拉)+ 查询/重置
**部门 Tab 筛选:** 体检年份(年份日历选择控件)、单位部门(下拉)、预选医院(下拉)+ 查询/重置
**医院 Tab 筛选:** 体检年份(年份日历选择控件)、单位(下拉)、预选医院(下拉)+ 查询/重置
**操作按钮:** 导出列表数据、查看导出任务
**操作按钮(两个 Tab 相同):** 导出列表数据、查看导出任务
**表格列:** 单位、部门、体检年份、预选计划、预选医院、预选人数(蓝色数字链接)
**部门 Tab 表格列:** 单位部门、体检年份、预选计划、预选医院、预选人数(蓝色数字链接)
**医院 Tab 表格列:** 预选医院、体检年份、单位、预选人数(蓝色数字链接)
**预选人数明细弹窗(860px):** 标题"预选人数明细 - {单位部门/医院}";表格列:姓名、员工编号、身份证号、单位、部门、预选医院、提交时间;右上"导出列表数据";底部关闭
**预选人数明细弹窗(860px):** 标题"预选人数明细 - {单位部门}";表格列:姓名、员工编号、身份证号、单位、部门、预选医院、提交时间;右上"导出列表数据";底部关闭
---
@@ -891,9 +904,9 @@
**表格列:** 序号、单位、体检年份、体检计划名称、承检医院、有效标志、体检计划日期范围、允许预约日期范围、创建人、创建时间、操作(详情/编辑/删除)
**新增/编辑抽屉 / 详情弹窗 / 选择员工二级弹窗:** 字段与常规体检计划管理一致(抽屉标题为"新增/编辑胃肠镜体检计划",计划名称示例为"XX 2026年员工胃肠镜体检")。
**新增/编辑抽屉 / 详情弹窗 / 选择员工二级弹窗:** 字段与常规体检计划管理一致(抽屉标题为"新增/编辑胃肠镜体检计划",计划名称示例为"XX 2026年员工胃肠镜体检";新增模式体检年份锁定为当年、隐藏参加人员范围区域,编辑模式恢复)。
**业务规则:** 保存校验(toast 提示)与常规版一致;删除有确认提示。
**业务规则:** 保存校验(toast 提示)与常规版一致(参加人员范围仅编辑时校验必选);删除有确认提示。
---
@@ -934,7 +947,7 @@
![页面截图](screenshots/pre-cta-plan-mgmt.jpg)
**功能说明:** 管理各单位年度冠脉CTA体检计划。页面结构与胃肠镜版完全一致(克隆页)。
**功能说明:** 管理各单位年度冠脉CTA体检计划。页面结构与胃肠镜版完全一致(克隆页);新增模式体检年份锁定为当年、隐藏参加人员范围区域,编辑模式恢复(与常规/胃肠镜版一致)
---
@@ -971,7 +984,7 @@
**操作按钮:** 导出列表数据、查看导出任务、同步医院体检预约信息
**表格列(两级表头):** 复选框(全选)、姓名、性别、年龄、年龄范围、身份证号、单位、部门、机构分类、员工编号、体检编码、用工形式、婚姻状态、体检类型、防癌项、是否上市员工、体检医院、体检预约信息、体检总费用、体检明细费用(colspan=4:必检费用/选检费用/妇科费用/防癌筛查费用)、预约状态(标签)、预约日期、体检状态(标签)、体检日期、体检年份、体检计划、操作(详情);前 3 列左冻结、操作列右冻结
**表格列(两级表头):** 复选框(全选)、姓名、性别、年龄、年龄范围、身份证号、单位、部门、机构分类、员工编号、体检编码、用工形式、婚姻状态、体检类型、防癌项、是否上市员工、体检医院、体检总费用、体检明细费用(colspan=4:必检费用/选检费用/妇科费用/防癌筛查费用)、预约状态(标签)、体检预约信息、预约日期、体检状态(标签)、体检日期、体检年份、体检计划、操作(详情);前 3 列左冻结、操作列右冻结
**预约跟踪详情弹窗(880px):**
- 基本信息:姓名/员工编号/性别/年龄/年龄范围/身份证号/单位/部门/机构分类/体检编码/用工形式/婚姻状态/体检类型/防癌项/是否上市员工
@@ -982,7 +995,27 @@
---
#### 33. 已选项目(mid-selected-items.html
#### 33. 体检预约跟踪(mid-appointment-track2.html
![页面截图](screenshots/mid-appointment-track2.jpg)
**功能说明:** 以 4 个页签纵向贯穿「应检 → 已选项目 → 已预约 → 已参检」的完整预约跟踪流程,支持批量状态流转与逐项置检/弃检。
**Tab 切换:** 所有员工 / 已选项目员工 / 已预约员工 / 已参检员工
**筛选条件:** 体检年份(年份选择控件,默认当年)、单位部门、医院选择、姓名、员工编号、身份证号,另有性别/年龄范围/机构分类/是否上市员工/防癌项/体检状态/预约状态/前置机预约状态(仅所有员工页签);已参检页签额外含体检编码、系统体检状态、医院体检状态、体检日期起止;各页签均带查询/重置
**操作按钮:** 所有员工——导出列表数据、查看导出任务、同步医院体检预约信息;已选项目员工——导出列表/项目/预约数据、查看导出任务;已预约员工——置为已体检、置为未体检、取消预约、导出列表/项目/预约数据、查看导出任务;已参检员工——导出项目状态、导出体检指引单、查看导出任务
**表格列(按页签):** 所有员工单级表头(姓名/员工编号/性别/年龄/身份证号/单位/部门/机构分类/体检编码/用工形式/婚姻状态/防癌项/是否上市员工/预约状态/前置机预约状态/预约日期/体检状态/体检日期/体检年份/体检计划/操作);已选项目员工两级表头(体检明细费用下分必检/选检/妇科/防癌筛查费用 4 子列,另含体检项目数/体检总费用/提交项目日期等);已预约员工单级表头(含体检编码/预约日期);已参检员工单级表头(含系统体检状态/医院体检状态/已检项/全部)
**详情弹窗(4 个页签各 1 个):** 所有员工「预约跟踪详情」(基本信息 + 预约与体检信息);已选项目员工(信息卡 + 已选项目列表含费用合计);已预约/已参检员工(信息卡 + 3 个 Tab:员工体检表信息/已体检项目/未体检项目,支持逐项置为已检/未检/弃检)
**业务规则:** 状态彩色标签(未预约橙/已预约绿/已取消红/未体检橙/体检中与已体检蓝);前置机预约状态独立枚举(未预约/预约成功/已取消预约/预约失败/预约中/取消预约失败);已预约页签批量置为已体检/未体检/取消预约需先勾选行;已参检员工区分系统体检状态与医院体检状态双口径。
---
#### 34. 已选项目(mid-selected-items.html
![页面截图](screenshots/mid-selected-items.jpg)
@@ -1004,7 +1037,7 @@
---
#### 34. 已检员工(mid-participation.html
#### 35. 已检员工(mid-participation.html
![页面截图](screenshots/mid-participation.jpg)
@@ -1024,7 +1057,7 @@
---
#### 35. 单位预约人数统计(mid-unit-appointment.html
#### 36. 单位预约人数统计(mid-unit-appointment.html
![页面截图](screenshots/mid-unit-appointment.jpg)
@@ -1040,13 +1073,13 @@
---
#### 36. 医院实时预约人数(mid-hospital-realtime.html
#### 37. 医院实时预约人数(mid-hospital-realtime.html
![页面截图](screenshots/mid-hospital-realtime.jpg)
**功能说明:** 按医院+日期查看医院承检能力与当日已预约人数的实时统计,用于监控各医院预约饱和度。
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、医院(下拉)、日期范围(日期至日期,默认当月 1 号至当月最后一天
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、医院(下拉)、日期范围(日期至日期,默认当日至 30 日后
**操作按钮:** 导出列表数据、查看导出任务
@@ -1054,7 +1087,7 @@
---
#### 37. 医生实时检查情况(mid-doctor-realtime.html
#### 38. 医生实时检查情况(mid-doctor-realtime.html
![页面截图](screenshots/mid-doctor-realtime.jpg)
@@ -1068,7 +1101,7 @@
---
#### 38. 医院预约人数统计(mid-hospital-appointment.html
#### 39. 医院预约人数统计(mid-hospital-appointment.html
![页面截图](screenshots/mid-hospital-appointment.jpg)
@@ -1082,7 +1115,7 @@
---
#### 39. 签约单位应检人数(mid-unit-expected.html
#### 40. 签约单位应检人数(mid-unit-expected.html
![页面截图](screenshots/mid-unit-expected.jpg)
@@ -1098,7 +1131,7 @@
### 胃肠镜体检
#### 40. 员工预约跟踪(胃肠镜)(mid-gastro-appointment-track.html
#### 41. 员工预约跟踪(胃肠镜)(mid-gastro-appointment-track.html
![页面截图](screenshots/mid-gastro-appointment-track.jpg)
@@ -1118,7 +1151,7 @@
---
#### 41. 单位预约统计(胃肠镜)(mid-gastro-unit-appointment.html
#### 42. 单位预约统计(胃肠镜)(mid-gastro-unit-appointment.html
![页面截图](screenshots/mid-gastro-unit-appointment.jpg)
@@ -1134,7 +1167,7 @@
---
#### 42. 胃肠镜问卷信息(mid-gastro-questionnaire.html
#### 43. 胃肠镜问卷信息(mid-gastro-questionnaire.html
![页面截图](screenshots/mid-gastro-questionnaire.jpg)
@@ -1152,7 +1185,7 @@
### 冠脉CTA体检
#### 43. 员工预约跟踪(冠脉CTA)(mid-cta-appointment-track.html
#### 44. 员工预约跟踪(冠脉CTA)(mid-cta-appointment-track.html
![页面截图](screenshots/mid-cta-appointment-track.jpg)
@@ -1161,11 +1194,11 @@
---
#### 44. 单位预约统计(冠脉CTA)(mid-cta-unit-appointment.html
#### 45. 单位预约统计(冠脉CTA)(mid-cta-unit-appointment.html
![页面截图](screenshots/mid-cta-unit-appointment.jpg)
**功能说明:** 按单位(二级/三级)统计冠脉CTA体检的应检、问卷填写、预约与参检人数。页面结构与胃肠镜版单位预约统计完全一致(克隆页)。
**功能说明:** 按单位(二级/三级)统计冠脉CTA体检的应检、预约与参检人数(冠脉CTA免问卷,无问卷填写列)。页面结构与胃肠镜版单位预约统计类似(克隆页,差异:无"填写问卷人数"列)。
---
@@ -1174,25 +1207,45 @@
### 常规体检
#### 45. 常规体检报告(post-report-employee.html
#### 46. 常规体检报告(post-report-employee.html
![页面截图](screenshots/post-report-employee.jpg)
**功能说明:** 常规体检报告列表,支持更新报告数据、报告补录、获取影像报告、导出,并可打开单份报告详情弹窗(结论/建议/分科明细)。
**功能说明:** 常规体检报告列表,支持勾选员工后批量更新报告数据/获取影像报告、报告补录、报告模板导入、导出,并可打开单份专业版式报告弹窗(结论/建议/分科明细)。
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位部门(下拉)、姓名、员工编号、身份证号(输入框)+ 查询/重置按钮
**操作按钮:** 更新体检报告数据、体检报告补录、获取影像报告、导出详细报告、查看导出任务
**操作按钮:** 更新体检报告数据、体检报告补录、获取影像报告、报告模板导入、导出详细报告、查看导出任务
**表格列:** 序号、姓名、员工编号、性别、年龄、身份证号、单位、部门、体检医院、体检类型、体检日期、终审日期、更新日期、是否已读、已读时间、操作(查看报告/查看影像/下载报告)
**表格列:** 复选框(全选)、序号、姓名、员工编号、性别、年龄、身份证号、单位、部门、体检医院、体检类型、体检日期、终审日期、更新日期、是否已读、已读时间、操作(查看报告/查看影像/下载报告)
**体检报告弹窗:**
- 每个项目结果表列固定:项目名称/结果/单位/参考值,异常结果红色标注(带 ↑↓ 箭头)
- 更新体检报告数据、体检报告补录、获取影像报告按钮:参考之前代码逻辑
**体检报告弹窗:** 标题"姓名 · 常规体检报告",专业报告版式:深色报告头"健康体检报告"+ 基本信息区(姓名/性别/年龄/体检编号/体检日期/体检医院);正文含体检结论(橙色提示框)、体检建议(绿色提示框)及物理检查/内科/外科/化学检查等分科明细;每个项目结果表列固定:项目名称/结果/单位/参考值,异常结果红色标注(带 ↑↓ 箭头);底部关闭
**体检报告补录弹窗(480px):** 体检医院 *(下拉)、身份证号、体检编号、体检年份 *(年份选择控件,默认当年);提示"身份证号与体检编号必须至少填写一项",两者均为空时确定报错"身份证号与体检编号不能都为空,请至少填写一项";底部取消、确定
**员工操作确认弹窗(420px):** 点击"更新体检报告数据"或"获取影像报告"时触发;未勾选员工则提示"请先选择至少一位员工";已勾选时弹窗列出已选员工("已选择 N 位员工,请确认:姓名、…"),确认后执行操作;底部取消、确定
---
#### 46. 员工体检大表(post-employee-master.html
#### 47. 单位群体报告(post-group-report.html
![页面截图](screenshots/post-group-report.jpg)
**功能说明:** 选定某单位与体检年份后,以概览指标卡 + 图表 + 表格呈现该单位群体体检的受检/检出/异常/疾病/专项筛查情况,并支持导出 CSV。
**筛选条件:** 体检年份(年份选择控件,默认当年)、单位(左侧列表,含单位名称模糊搜索)
**操作按钮:** 导出群体报告(导出 CSV)
**概览指标卡(5 列 × 2 行共 10 张):** 应检人数、已检人数、受检率(绿)、检出率(橙)、异常人数(红)、重点指标异常(红)、复查率、数据完整率、平均年龄、未见异常(绿);每张卡下方带「较上年」趋势标注(率类 ±X.Xpp、人数/年龄类整数 + 单位,按指标方向判定绿 good/红 bad/灰 flat
**报告卡片(6 个):** ① 近三年趋势(受检率/检出率双折线图);② 异常率 × 年龄/性别(表:年龄区间/男性异常率/女性异常率/合计异常率/异常人数,含合计行);③ 异常指标排名(横向条形,按检出人数降序,top1 红/top2 橙/top3 绿、其余蓝,附上年对照与趋势);④ 重点指标异常拆解(表:重点指标/异常人数/检出率/较上年,5 项:血压/血脂/空腹血糖/高尿酸/超重 BMI);⑤ 疾病(病种)分布(按 8 类病种分组归类,表列:病种/人数/占比/较上年);⑥ 专项筛查结果(防癌筛查 Septin9、CA724、冠脉 CTA、胃肠镜 4 项,阳性/异常率 + 已筛人数 + 较上年)
**业务规则:** 受检率 = 已检 ÷ 应检、检出率 = 异常 ÷ 已检,各率保留 1 位小数、除数为 0 显示 0.0%;同单位同一年数据恒定(确定性伪随机);报告头元信息含体检年份/承检医院/体检计划起止/同比基准上一年。
---
#### 48. 员工体检大表(post-employee-master.html
![页面截图](screenshots/post-employee-master.jpg)
@@ -1213,7 +1266,7 @@
---
#### 47. 大病人员信息(post-illness-personnel.html
#### 49. 大病人员信息(post-illness-personnel.html
![页面截图](screenshots/post-illness-personnel.jpg)
@@ -1237,7 +1290,7 @@
---
#### 48. 指标异常通知(post-indicator-notify.html
#### 50. 指标异常通知(post-indicator-notify.html
![页面截图](screenshots/post-indicator-notify.jpg)
@@ -1253,7 +1306,7 @@
---
#### 49. 体检报告未匹配项目(post-report-unmatched.html
#### 51. 体检报告未匹配项目(post-report-unmatched.html
![页面截图](screenshots/post-report-unmatched.jpg)
@@ -1269,7 +1322,7 @@
### 体检统计汇总
#### 50. 单位体检人数统计(post-unit-count.html
#### 52. 单位体检人数统计(post-unit-count.html
![页面截图](screenshots/post-unit-count.jpg)
@@ -1293,11 +1346,11 @@
---
#### 51. 单位体检费用统计(post-unit-cost-stat.html
#### 53. 单位体检费用统计(post-unit-cost-stat.html
![页面截图](screenshots/post-unit-cost-stat.jpg)
**功能说明:** 按单位统计体检费用与报告完整性;最多可下钻 3 层弹窗:员工费用明细 → 单人应检/已检项目列表 或 报告缺少项目明细 → 员工缺少项目清单。
**功能说明:** 按"单位 × 体检医院"统计体检费用与报告完整性(一个单位会存在多个体检医院,一行是一个体检医院);最多可下钻 3 层弹窗:员工费用明细 → 单人应检/已检项目列表 或 报告缺少项目明细 → 员工缺少项目清单。
**Tab 切换:** 二级单位 / 三级单位(三级单位显示部门列)
@@ -1305,21 +1358,53 @@
**操作按钮:** 导出列表数据、导出员工明细、查看导出任务
**表格列:** 序号、单位、部门(仅三级单位 Tab)、总人数、已体检人数、报告完整人数、报告不完整人数、报告缺少项目总数、计划总体检费用(元)、实际总体检费用(元)、体检年份、操作(员工费用明细);已体检人数/报告完整人数/报告不完整人数/报告缺少项目总数 4 列为蓝色数字链接
**表格列:** 序号、单位、部门(仅三级单位 Tab)、体检医院、已体检人数、报告完整人数、报告不完整人数、报告缺少项目总数、计划总体检费用(元)、实际总体检费用(元)、体检年份、签约医院(该单位签约的体检医院,可能存在多个,逗号相隔,位于体检年份之后)、操作(员工费用明细);已体检人数/报告完整人数/报告不完整人数/报告缺少项目总数 4 列为蓝色数字链接
**员工费用明细弹窗:** 标题"「单位」YYYY年 员工费用明细";表格列:序号、姓名、员工编号、部门、体检日期、体检医院、应检项目数(蓝链)、已检项目数(蓝链,附灰色提示"含未检/弃检 N")、报告完整状态(绿"完整"/橙"不完整"标签)、报告缺少项目数、计划体检费用(元)、实际体检费用(元);底部合计行;关闭
**员工费用明细弹窗:** 标题"「单位」YYYY年 员工费用明细";顶部筛选栏(姓名/员工编号模糊查询、报告完整状态 全部/完整/不完整 + 查询/重置按钮,打开弹窗时自动重置)及"导出列表数据"按钮;表格列:序号、姓名、员工编号、部门、体检日期、体检医院、应检项目数(蓝链)、已检项目数(蓝链,附灰色提示"含未检/弃检 N")、报告完整状态(绿"完整"/橙"不完整"标签)、报告缺少项目数(不完整时标蓝可点击,打开体检报告缺少体检项目信息弹窗)、计划体检费用(元)、实际体检费用(元);无结果时显示"暂无数据";底部合计行随筛选结果汇总;关闭
**项目列表弹窗(560px,二层):** 标题"「姓名」应检项目/已检项目(YYYY年)";表头动态:序号、项目名称、项目类别、应检项目费用(元)/项目费用(元)、体检状态(绿"已检"/红"弃检"/橙"未检"标签);底部费用合计行。项目库为 4 档套餐,费用合计恰与额度档 1000/1500/2500/4000 元一致
**统计明细弹窗(三层):** 由主表 4 个数字链接打开,标题随口径(已体检人员明细/报告完整人员明细/报告不完整人员明细/报告缺少项目明细);前三种口径列:序号、姓名、员工编号、部门、体检日期、体检医院、已检、应检项目数、报告状态、(缺少项目数[蓝链]);缺少项目口径列:序号、姓名、员工编号、部门、体检医院、报告缺少项目、项目总数;底部"共 N 人"+ 关闭
**统计明细弹窗(三层):** 由主表 4 个数字链接打开,标题随口径(已体检人员明细/报告完整人员明细/报告不完整人员明细/报告缺少项目明细);前三种口径列:序号、姓名、员工编号、部门、体检日期、体检医院、已检、应检项目数、报告状态、(缺少项目数[蓝链]);缺少项目口径列:序号、姓名、员工编号、部门、体检医院、报告缺少项目、项目总数(蓝链,点击同样打开体检报告缺少体检项目信息弹窗);底部"共 N 人"+ 关闭
**体检报告缺少体检项目信息弹窗(四层):** 列:序号、项目名称、项目编码;底部"「姓名」共 N 项"+ 关闭
**体检报告缺少体检项目信息弹窗(四层):** 列:序号、项目名称、项目编码、项目费用(元)、折扣后费用(元)(折扣后费用统一按 8 折示例);底部"「姓名」共 N 项"+ 关闭
**业务规则:** 弃检项目费用≥240 标红;实际费用 = 应检合计 − 弃检/未检合计;金额千分位格式化;Esc/点遮罩逐层关闭(四层 z-index 递增)。
---
#### 52. 医院体检人数统计(post-hospital-count.html
#### 54. 单位体检项目统计(post-unit-item-stat.html
![页面截图](screenshots/post-unit-item-stat.jpg)
**功能说明:** 按"单位 × 体检医院 × 体检项目"维度统计各体检项目的已选/已检/异常人数及患病率,人数列可点击下钻查看对应人员明细。
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位(下拉)、医院(下拉)、项目名称(输入)、项目编码(输入)
**操作按钮:** 导出列表数据、查看导出任务
**表格列:** 序号、单位、体检医院、体检项目名称、项目编码、已选人数、已检人数、异常人数、患病率
**业务规则:** 患病率 = 异常人数 ÷ 已检人数,患病率 ≥10% 标红;已选/已检/异常三列数字为蓝色链接,点击打开对应口径人员列表弹窗(列:序号、姓名、员工编号、部门、体检医院、体检日期、体检状态/体检结果/异常结果)。
---
#### 55. 结果动态综合统计(post-result-comprehensive-stat.html
![页面截图](screenshots/post-result-comprehensive-stat.jpg)
**功能说明:** 以 2×2 网格展示 4 张堆积柱状图,统计维度固定为性别/年龄/部门/岗位;每张图自带「项目」「维度」两个下拉,可独立切换体检项目与统计维度,查看该维度各取值下的正常/异常人数及占比。
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位(下拉,默认第一采油厂)
**图表面板:** 每个面板顶部为标题 + 项目下拉(12 项)+ 维度下拉(性别/年龄/部门/岗位)+ 正常/异常图例,下方为竖向堆积柱状图。
**图表展示:** 竖向堆积柱状图——X 轴为所选维度取值(性别→男/女、年龄→各年龄段、部门→各部门、岗位→各岗位),Y 轴为人数;每根柱子正常在下(绿)/异常在上(红)堆叠,柱顶标总数、段内标百分比,悬停显示明细。
**业务规则:** 4 个面板互相独立,默认项目均为血常规、维度分别为性别/年龄/部门/岗位;异常率 = 异常人数 ÷ 受检人数;维度取值(性别男/女、年龄 5 档、部门 6 个、岗位 6 个)。
---
#### 56. 医院体检人数统计(post-hospital-count.html
![页面截图](screenshots/post-hospital-count.jpg)
@@ -1333,7 +1418,7 @@
---
#### 53. 医生检查人数统计(post-doctor-count.html
#### 57. 医生检查人数统计(post-doctor-count.html
![页面截图](screenshots/post-doctor-count.jpg)
@@ -1347,7 +1432,7 @@
---
#### 54. 医院体检项目统计(post-item-stat.html
#### 58. 医院体检项目统计(post-item-stat.html
![页面截图](screenshots/post-item-stat.jpg)
@@ -1361,7 +1446,7 @@
---
#### 55. 防癌筛查人数统计(post-cancer-screening.html
#### 59. 防癌筛查人数统计(post-cancer-screening.html
![页面截图](screenshots/post-cancer-screening.jpg)
@@ -1375,7 +1460,7 @@
---
#### 56. 系统标准化项目统计(post-standard-stat.html
#### 60. 系统标准化项目统计(post-standard-stat.html
![页面截图](screenshots/post-standard-stat.jpg)
@@ -1389,7 +1474,7 @@
---
#### 57. 签约医院人数统计(post-contracted-hospital.html
#### 61. 签约医院人数统计(post-contracted-hospital.html
![页面截图](screenshots/post-contracted-hospital.jpg)
@@ -1403,7 +1488,7 @@
---
#### 58. 重点指标异常统计(post-key-indicator-stat.html
#### 62. 重点指标异常统计(post-key-indicator-stat.html
![页面截图](screenshots/post-key-indicator-stat.jpg)
@@ -1423,7 +1508,7 @@
### 病种信息管理
#### 59. 病种信息配置(post-disease-info.html
#### 63. 病种信息配置(post-disease-info.html
![页面截图](screenshots/post-disease-info.jpg)
@@ -1443,7 +1528,7 @@
---
#### 60. 体检结论关联病种(post-conclusion-disease.html
#### 64. 体检结论关联病种(post-conclusion-disease.html
![页面截图](screenshots/post-conclusion-disease.jpg)
@@ -1463,7 +1548,7 @@
---
#### 61. 员工病种信息(post-auto-select-disease.html
#### 65. 员工病种信息(post-auto-select-disease.html
![页面截图](screenshots/post-auto-select-disease.jpg)
@@ -1486,39 +1571,42 @@
### 胃肠镜体检
#### 62. 胃肠镜体检报告(post-report-gastro.html
#### 66. 胃肠镜体检报告(post-report-gastro.html
![页面截图](screenshots/post-report-gastro.jpg)
**功能说明:** 管理员工胃肠镜体检的报告图片,支持查看报告详情(体检项目/病种勾选/体检处理情况/病变情况四类结构化数据)与上传/替换报告图片。提供列表导出导入及列设置。
**功能说明:** 管理员工胃肠镜体检的报告图片,支持以专业版式查看报告(胃镜/肠镜所见与诊断、病理诊断、报告图片)及上传/替换报告图片。提供列表导出导入及列设置。
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位部门(下拉,单位/部门两级分组)、体检医院(下拉)、姓名、员工编号、身份证号(输入框)+ 查询/重置按钮
**操作按钮:** 导出列表数据、导出体检数据、查看导出任务、导入体检数据、查看导入任务、列设置
**表格列:** 姓名(固定)、员工编号(固定)、性别、年龄、单位(固定)、部门(固定)、身份证号、体检医院、检查日期、胃镜报告(缩略图)、肠镜报告(缩略图)、创建时间、体检年份、操作(编辑/详情
**表格列:** 姓名(固定)、员工编号(固定)、性别、年龄、单位(固定)、部门(固定)、身份证号、体检医院、检查日期、胃镜报告(缩略图)、肠镜报告(缩略图)、创建时间、体检年份、操作(编辑/查看报告
**业务规则:**
- 报告缩略图:有报告时点击打开详情弹窗;无报告时灰底占位,点击打开编辑弹窗上传
- 报告缩略图:有报告时点击打开查看报告弹窗;无报告时灰底占位,点击打开编辑弹窗上传
- 列设置气泡:固定列(姓名/员工编号/单位/部门/操作)不可隐藏,其余列可勾选显隐
- 上传为模拟交互(选择文件后 toast 提示),保存写回行数据
**报告详情弹窗(900px):** 标题"姓名 · 胃肠镜报告详情"
**查看报告弹窗(宽版):** 标题"姓名 · 胃肠镜检查报告",专业报告版式(与个人体检数据-胃肠镜体检页签一致):深色报告头"胃肠镜检查报告"+ 基本信息区(姓名/性别/年龄/检查编号/检查日期/检查医院);
- 胃镜检查表:项目(食管/胃/十二指肠/诊断结果)× 所见/结论
- 肠镜检查表:项目(结肠/诊断结果/病理诊断)× 所见/结论
- 报告图片:胃镜报告/肠镜报告/活检报告("查看"链接);底部关闭
**编辑报告弹窗(900px):**
- 基本信息两列表单(姓名/身份证号/单位/部门/承检医院/体检年份,只读)
- 胃镜报告图片 *、肠镜报告图片 *、活检报告图片 *(图片预览块,无图时虚线框"暂无图片"
- 4 个页签:
- 胃镜报告图片 *、肠镜报告图片 *、活检报告图片三个图片区(已上传:图片预览 + "重新上传"/"删除"链接;未上传:"上传图片"链接
- 4 个只读页签:
- 体检项目:列 #、检查类别、项目编号、项目名称、结果(胃镜/肠镜分段所见描述)
- 病种勾选:列 #、项目编号、项目名称、结果(如 SE02.02 糜烂性胃炎、SE02.05 黏膜萎缩)
- 体检处理情况:同列结构(如 PE01.01 病理取材、PE01.02 建议复查)
- 病变情况:同列结构(如 LE01.01 胃黏膜糜烂、LE01.02 胃黏膜萎缩)
- 病种/处理/病变三个页签仅当该员工有胃镜报告时有数据,否则"暂无数据"
- 底部关闭
**编辑报告弹窗(900px):** 基本信息(只读信息网格)+ 胃镜报告图片、肠镜报告图片两个上传区(已上传显示大图预览 + "重新上传""删除"链接;未上传显示"点击上传");取消/保存
- 仅报告图片可修改,其余字段只读;底部取消/保存
---
#### 63. 胃肠镜体检统计(post-gastro-stat.html
#### 67. 胃肠镜体检统计(post-gastro-stat.html
![页面截图](screenshots/post-gastro-stat.jpg)
@@ -1541,7 +1629,7 @@
### 冠脉CTA体检
#### 64. 冠脉CTA体检报告(post-cta-report.html
#### 68. 冠脉CTA体检报告(post-cta-report.html
![页面截图](screenshots/post-cta-report.jpg)
@@ -1571,7 +1659,7 @@
---
#### 65. 冠脉CTA体检统计(post-cta-stat.html
#### 69. 冠脉CTA体检统计(post-cta-stat.html
![页面截图](screenshots/post-cta-stat.jpg)
@@ -1581,9 +1669,85 @@
---
### 体检影像管理
#### 70. 用户体检信息(post-imc-user.html
![页面截图](screenshots/post-imc-user.jpg)
**功能说明:** 以「人」为维度聚合展示用户体检影像信息,行内可展开查看该人各项检查项目的影像与报告处理情况。
**筛选条件:** 姓名、身份证号、体检年份(年份选择控件,默认当年)、医院名称
**操作按钮:** 批量删除
**表格列:** 复选框、序号、展开箭头列、单位信息、姓名、身份证号、体检年份、医院名称、体检编号、创建日期、操作(删除)
**展开子表列:** 序号、检查项目名称、处理状态、影像信息、影像报告;影像信息列渲染「预览」链接(无数据显示「暂无」)、影像报告列渲染「查看」链接(无数据显示「无文件」)、子表无数据显示「暂无检查项目数据」
**业务规则:** 姓名/身份证号/医院名称为模糊匹配、体检年份精确匹配;分页 10/20/50 条/页;删除/批量删除带确认。
---
#### 71. 用户影像文件分类(post-imc-class.html
![页面截图](screenshots/post-imc-class.jpg)
**功能说明:** 按「检查项目」维度展示影像文件与报告文件的类型分类、处理状态与结果。
**筛选条件:** 影像分类(超声/CT/MRI/X射线/心电)、检查项目名称、处理状态
**操作按钮:** 批量删除
**表格列:** 复选框、序号、影像分类、检查项目名称、影像文件类型、报告文件类型、处理状态、检查结果、错误信息、出报告时间、创建日期、操作(详情 + 删除)
**详情弹窗:** 标题「用户影像文件分类详情」,字段:用户体检信息ID、影像分类、检查项目名称、影像文件类型、报告文件类型、处理状态、检查结果、错误信息、出报告时间、创建日期
**业务规则:** 检查结果/错误信息超 18 字符截断并悬停显示全文、空值显示「—」;影像文件类型 dcm/非DICOM、报告文件类型 pdf/png/jpg。
---
#### 72. 用户影像文件详细(post-imc-info.html
![页面截图](screenshots/post-imc-info.jpg)
**功能说明:** 文件级别的影像/报告明细,展示单个文件的存储地址、DICOM 实例与研究/患者标识。
**筛选条件:** 文件类型(影像/报告)、患者ID、研究实例ID
**操作按钮:** 批量删除
**表格列:** 复选框、序号、文件类型(影像蓝标签/报告紫标签)、影像序号、错误信息、文件地址(下载链接)、dcm实例ID、研究实例ID、患者ID、研究ID、创建日期、操作(详情 + 删除)
**详情弹窗:** 标题「用户影像文件详细」,字段:用户影像文件分类ID、文件类型、影像序号、错误信息、文件地址、dcm实例ID、研究实例ID、患者ID、研究ID、创建日期
**业务规则:** 错误信息超 18 字符截断并悬停全文;患者ID/dcm实例ID/错误信息可为空(空显示「—」,报告的 dcm实例ID 恒为空)。
---
#### 73. 文件处理日志(post-imc-log.html
![页面截图](screenshots/post-imc-log.jpg)
**功能说明:** 展示影像压缩包的传输、解析全过程日志(时间线 + 状态 + 原始 json)。
**筛选条件:** 身份证号、体检年份(年份选择控件)、体检编号、影像分类(超声/CT/MRI/X射线/心电)、处理状态
**操作按钮:** 批量删除
**表格列:** 复选框、序号、医院名称、姓名、身份证号、体检年份、体检编号、影像分类、json数据、文件地址(下载链接)、文件大小(MB)、处理状态、错误信息、传输开始时间、传输完成时间、解析开始时间、解析完成时间、操作(详情 + 删除)
**详情弹窗:** 标题「文件处理日志详情」,字段含医院名称/姓名/身份证号/体检年份/体检编号/影像分类/json 数据(代码块展示)/文件地址/文件大小(MB)/处理状态/错误信息/传输开始/传输完成/解析开始/解析完成时间
**业务规则:** json 数据超 24 字符截断为等宽字体并悬停全文、详情弹窗内 json 代码块可滚动;错误信息超 18 字符截断;空时间显示「—」;示例数据按传输开始时间倒序。
**数据字典(四页通用):** 处理状态 1 新建(蓝)/2 待解析(灰)/3 失败(红)/4 解析中(橙)/5 成功(绿)/6 失败待重试(橙)/7 最终失败(红)。
---
## 六、前置机管理
### 66. 常规体检前置服务(agent-device.html
### 74. 常规体检前置服务(agent-device.html
![页面截图](screenshots/agent-device.jpg)
@@ -1624,7 +1788,7 @@
## 七、异常事件
### 67. 系统脏数据(exception-dirty.html
### 75. 系统脏数据(exception-dirty.html
![页面截图](screenshots/exception-dirty.jpg)
@@ -1642,18 +1806,14 @@
---
### 68. 结果异常(exception-result.html
### 76. 结果异常(exception-result.html
![页面截图](screenshots/exception-result.jpg)
**功能说明:** 展示体检结果中指标超出正常参考范围的异常记录(危急/异常两级),支持按单位、时间范围、异常等级筛选与导出。
**功能说明:** 展示体检结果中指标超出正常参考范围的异常记录,支持按单位、时间范围筛选与导出。
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位(下拉)、时间范围(日期至日期)、异常等级(下拉:全部/危急/异常)+ 查询/重置按钮
**筛选条件:** 体检年份(年份日历选择控件,默认当年)、单位(下拉)、时间范围(日期至日期)+ 查询/重置按钮
**操作按钮:** 导出列表数据、查看导出任务
**表格列:** 序号、异常等级(红色"危急"/橙色"异常"标签)、员工姓名、员工编号、单位、异常项目、异常值、参考范围、发现时间
**业务规则:**
- 页首异常数据定义说明框(黄色提示框):危急(指标严重偏离正常范围,需立即处理)、异常(指标超出参考范围但未达危急程度);系统自动检测并记录
- 异常等级措辞统一不带"值"字(危急/异常),表头数据列"异常值"(指偏离数值本身)不受影响
**表格列:** 序号、员工姓名、员工编号、单位、异常项目、异常值、参考范围、发现时间
+20 -2
View File
@@ -2,6 +2,7 @@
* 健康体检管理端PRD 截图生成脚本
*
* 用法 web-admin/health-checkup 目录下执行 node doc/screenshot-prd.js
* 可选传入页面名前缀只重截指定页 node doc/screenshot-prd.js post-unit-cost-stat pre-plan
* 依赖playwrightnpm i playwright && npx playwright install chromium
* 输出doc/screenshots/*.jpg doc/PRD.md ![页面截图](screenshots/xxx.jpg) 引用一一对应
*
@@ -54,6 +55,7 @@ const TASKS = [
{ page: 'pre-special-cta.html', output: 'pre-special-cta.jpg' },
// 四、检中 · 常规体检
{ page: 'mid-appointment-track.html', output: 'mid-appointment-track.jpg' },
{ page: 'mid-appointment-track2.html', output: 'mid-appointment-track2.jpg' },
{ page: 'mid-selected-items.html', output: 'mid-selected-items.jpg' },
{ page: 'mid-participation.html', output: 'mid-participation.jpg' },
{ page: 'mid-unit-appointment.html', output: 'mid-unit-appointment.jpg' },
@@ -70,6 +72,7 @@ const TASKS = [
{ page: 'mid-cta-unit-appointment.html', output: 'mid-cta-unit-appointment.jpg' },
// 五、检后 · 常规体检
{ page: 'post-report-employee.html', output: 'post-report-employee.jpg' },
{ page: 'post-group-report.html', output: 'post-group-report.jpg' },
{ page: 'post-employee-master.html', output: 'post-employee-master.jpg' },
{ page: 'post-illness-personnel.html', output: 'post-illness-personnel.jpg' },
{ page: 'post-indicator-notify.html', output: 'post-indicator-notify.jpg' },
@@ -77,6 +80,8 @@ const TASKS = [
// 五、检后 · 体检统计汇总
{ page: 'post-unit-count.html', output: 'post-unit-count.jpg' },
{ page: 'post-unit-cost-stat.html', output: 'post-unit-cost-stat.jpg' },
{ page: 'post-unit-item-stat.html', output: 'post-unit-item-stat.jpg' },
{ page: 'post-result-comprehensive-stat.html', output: 'post-result-comprehensive-stat.jpg' },
{ page: 'post-hospital-count.html', output: 'post-hospital-count.jpg' },
{ page: 'post-doctor-count.html', output: 'post-doctor-count.jpg' },
{ page: 'post-item-stat.html', output: 'post-item-stat.jpg' },
@@ -94,12 +99,25 @@ const TASKS = [
// 五、检后 · 冠脉CTA体检
{ page: 'post-cta-report.html', output: 'post-cta-report.jpg' },
{ page: 'post-cta-stat.html', output: 'post-cta-stat.jpg' },
// 五、检后 · 体检影像管理
{ page: 'post-imc-user.html', output: 'post-imc-user.jpg' },
{ page: 'post-imc-class.html', output: 'post-imc-class.jpg' },
{ page: 'post-imc-info.html', output: 'post-imc-info.jpg' },
{ page: 'post-imc-log.html', output: 'post-imc-log.jpg' },
// 六、异常事件
{ page: 'exception-dirty.html', output: 'exception-dirty.jpg' },
{ page: 'exception-result.html', output: 'exception-result.jpg' },
];
(async () => {
const args = process.argv.slice(2);
const tasks = args.length
? TASKS.filter(t => args.some(a => t.page.startsWith(a) || t.output.startsWith(a)))
: TASKS;
if (!tasks.length) {
console.error('没有匹配到页面:', args.join(' '));
process.exit(1);
}
fs.mkdirSync(OUT_DIR, { recursive: true });
const browser = await chromium.launch();
const context = await browser.newContext({
@@ -109,7 +127,7 @@ const TASKS = [
const page = await context.newPage();
let failed = 0;
for (const task of TASKS) {
for (const task of tasks) {
const file = path.join(ROOT, task.page);
if (!fs.existsSync(file)) {
console.error('✗ 缺少文件:', task.page);
@@ -134,6 +152,6 @@ const TASKS = [
}
await browser.close();
console.log(`\n完成:成功 ${TASKS.length - failed} / ${TASKS.length} 张 → ${OUT_DIR}`);
console.log(`\n完成:成功 ${tasks.length - failed} / ${tasks.length} 张 → ${OUT_DIR}`);
if (failed > 0) process.exit(1);
})();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Some files were not shown because too many files have changed in this diff Show More