Compare commits

...
8 Commits
Author SHA1 Message Date
fengpu 70cbe278c5 Merge branch 'master' of https://gitea.rk-health.com/yixiong/platform-prototype 2026-09-21 10:18:18 +08:00
fengpu 143803a73f feat:营养管理后台取餐管理新增取餐明细列表,食谱生成净菜订单功能优化 2026-09-21 10:18:15 +08:00
liuyunqin 5e4eed60b9 feat: 健康体检模块批量上线「高级查询」条件构建器,侧边栏菜单收敛
- 检中/检后/检前多个列表页(个人档案、单位档案、体检预约跟踪、单位预约统计、指定防癌、必检项目、项目组合模板、结果动态综合统计、员工主数据等)将固定筛选下拉替换为「高级查询」动态条件构建器(字段/操作符/值/区间,支持增删行与清空),并移除冗余的性别/年龄/岗位/是否异常/体检医院/费用审核状态/防癌类型等固定筛选项
- 体检预约跟踪页重构:新增高级查询、轻提示,移除医院/性别/年龄范围/机构分类/是否上市/防癌项/体检状态/预约状态/前置机预约状态等固定筛选
- 侧边栏及 index.html SITE_MAP 收敛:体检预约、已选项目、已检员工三项入口注释下线,合并入「体检预约跟踪」
2026-09-21 10:16:04 +08:00
liuyunqin 2fc599fabe feat: 体检模块侧边栏消歧与统计页重构;一线医疗床位页筛选交互升级
- 健康体检:侧边栏「员工体检报告」按类型消歧为常规/胃肠镜/冠脉CTA体检报告
- 「结果动态综合统计」更名「项目结果综合统计」,新增图表/列表双 Tab、级联筛选、表格分页与报告详情弹窗
- 新增「体检影像管理」分组(用户体检信息/影像文件分类/影像文件详细/文件处理日志)
- 员工体检大表新增高级查询;单位体检项目统计等页面同步侧边栏文案
- 一线医疗:楼栋/床位/站点相关页面单位部门筛选改为级联组件+站点搜索下拉,新增地图选点,调整表格列
2026-09-20 18:01:48 +08:00
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
91 changed files with 4888 additions and 1102 deletions
+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>
@@ -20,9 +20,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.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; }
.filter-row { display: flex; gap: 8px; margin-top: 8px; }
.filter-chip { flex: 1; text-align: center; font-size: 12px; padding: 6px 0; border-radius: 16px; background: #f5f5f5; color: #666; cursor: pointer; }
.filter-chip.active { background: #e6f4ff; color: #1677ff; font-weight: 600; }
/* 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; }
@@ -68,12 +65,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span style="font-size:14px;">🔍</span>
<input type="text" placeholder="搜索员工姓名 / 工号" />
</div>
<div class="filter-row">
<div class="filter-chip active" onclick="filterChip(this)">全部单位</div>
<div class="filter-chip" onclick="filterChip(this)">机关</div>
<div class="filter-chip" onclick="filterChip(this)">采油一厂</div>
<div class="filter-chip" onclick="filterChip(this)">信息中心</div>
</div>
</div>
<!-- Tab:全部 / 达标 / 未达标 -->
@@ -196,11 +187,6 @@ function switchTab(el, panelId) {
});
}
/* 单位筛选 */
function filterChip(el) {
el.parentElement.querySelectorAll('.filter-chip').forEach(function(c) { c.classList.remove('active'); });
el.classList.add('active');
}
</script>
</body>
</html>
+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>' +
+7 -5
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; }
@@ -238,10 +238,10 @@ const SITE_MAP = [
{ file:'pre-cta-plan-mgmt.html', label:'冠脉CTA单位体检计划管理' },
{ file:'pre-special-cta.html', label:'冠脉CTA体检人员' },
{ file:'pre-special-hospital.html', label:'指定员工体检医院' },
{ file:'mid-appointment-track.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-selected-items.html', label:'已选项目' },
// { file:'mid-participation.html', label:'已检员工' },
{ file:'mid-unit-appointment.html', label:'单位预约人数统计' },
{ file:'mid-hospital-realtime.html', label:'医院实时预约人数' },
{ file:'mid-doctor-realtime.html', label:'医生实时检查情况' },
@@ -255,6 +255,7 @@ const SITE_MAP = [
{ 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:'医院体检项目统计' },
@@ -385,6 +386,7 @@ const SITE_MAP = [
{ file:'din-place-goal.html', label:'场所目标考核' },
{ file:'prod-recipe-plan.html', label:'食谱排期' },
{ file:'prod-clean-order.html', label:'净菜下单' },
{ file:'prod-clean-order-history.html', label:'净菜下单生成历史' },
{ file:'prod-proc-combo.html', label:'配菜组合' },
{ file:'prod-proc-cook.html', label:'烹饪加工' },
{ file:'prod-proc-serve.html', label:'出餐管理' },
@@ -707,7 +709,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:'体重记录明细' },
+192 -79
View File
@@ -45,6 +45,32 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.filter-select{height:36px;min-width:140px;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;}
.filter-input{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
/* 单位部门级联筛选 + 站点搜索下拉 */
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:36px;min-width:130px;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;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-sm);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.station-search{position:relative;}
.station-search__panel{display:none;position:absolute;top:34px;left:0;right:0;z-index:320;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);max-height:240px;overflow-y:auto;}
.station-search__panel.open{display:block;}
.station-search__item{padding:8px 12px;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:8px;}
.station-search__item:hover{background:#F0F8FF;}
.station-search__item .ss-name{color:var(--text-primary);font-weight:500;}
.station-search__item .ss-org{color:var(--text-placeholder);font-size:var(--font-xs);}
.station-search__empty{padding:16px 12px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);}
.map-picker{width:100%;height:200px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#e6f7ff;display:flex;align-items:center;justify-content:center;color:var(--primary);font-size:var(--font-sm);gap:6px;cursor:pointer;position:relative;overflow:hidden;transition:border-color .2s;}
.map-picker:hover{border-color:var(--primary);}
.map-picker__bg{position:absolute;inset:0;opacity:.15;background:repeating-linear-gradient(0deg,transparent,transparent 19px,#91d5ff 19px,#91d5ff 20px),repeating-linear-gradient(90deg,transparent,transparent 19px,#91d5ff 19px,#91d5ff 20px);}
.map-picker__pin{position:absolute;top:50%;left:50%;transform:translate(-50%,-100%);z-index:2;display:none;}
.map-picker__label{position:relative;z-index:2;display:flex;align-items:center;gap:6px;}
.map-picker__coord{position:absolute;bottom:8px;left:50%;transform:translateX(-50%);background:rgba(255,255,255,0.92);padding:4px 14px;border-radius:12px;font-size:var(--font-xs);box-shadow:0 1px 4px rgba(0,0,0,0.1);z-index:3;display:none;white-space:nowrap;color:var(--text-primary);max-width:calc(100% - 16px);overflow:hidden;text-overflow:ellipsis;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
@@ -155,16 +181,20 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 筛选栏:单位→部门→站点三级级联 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
</select>
</div>
<div class="filter-item">
<label>所属部门</label>
<select class="filter-select" id="filterDepart" onchange="onFilterDepart()" style="min-width:130px;">
<option value="">全部部门</option>
</select>
<label>所属单位部门</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>所属站点</label>
@@ -200,7 +230,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<thead>
<tr>
<th style="width:40px;"><input type="checkbox" id="ckAll" onchange="toggleAll(this)"></th>
<th style="width:56px;">序号</th><th>所属单位</th><th>所属部门</th><th style="min-width:120px;">所属站点</th><th style="min-width:140px;">楼栋名称</th>
<th style="width:56px;">序号</th><th style="min-width:140px;">楼栋名称</th><th style="min-width:120px;">所属站点</th><th style="min-width:160px;">所属单位部门</th>
<th style="min-width:220px;">详细地址</th><th>宿舍数量</th><th>负责人</th><th style="min-width:110px;">负责人电话</th><th style="width:110px;">操作</th>
</tr>
</thead>
@@ -215,28 +245,25 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="modal-mask" id="editMask">
<div class="modal--wide">
<div class="modal__title" id="editTitle">新增楼栋信息</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属单位</span>
<select class="form-control" id="eUnit" onchange="onEditUnit()"><option value="">请选择单位</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属部门</span>
<select class="form-control" id="eDepart"><option value="">请先选择单位</option></select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属站点</span>
<select class="form-control" id="eStation"><option value="">请先选择部门</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 楼栋名称</span>
<input class="form-control" id="eName" placeholder="如:樊学倒班宿舍1号楼">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属站点</span>
<div class="station-search" id="stationSearch">
<input class="form-control" id="eStation" placeholder="请输入站点名称搜索" autocomplete="off" oninput="onStationInput()" onfocus="onStationFocus()">
<div class="station-search__panel" id="stationPanel"></div>
</div>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 楼栋名称</span>
<input class="form-control" id="eName" placeholder="如:樊学倒班宿舍1号楼">
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 详细地址</span>
<div class="map-picker" id="mapPicker" onclick="pickLocation(event)">
<div class="map-picker__bg"></div>
<svg class="map-picker__pin" id="mapPin" width="28" height="36" viewBox="0 0 28 36" fill="none"><path d="M14 0C6.27 0 0 6.27 0 14c0 10.5 14 22 14 22s14-11.5 14-22C28 6.27 21.73 0 14 0z" fill="#1890FF"/><circle cx="14" cy="14" r="6" fill="#fff"/></svg>
<span class="map-picker__label" id="mapLabel"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1C4.24 1 2 3.24 2 6c0 3.75 5 7 5 7s5-3.25 5-7c0-2.76-2.24-5-5-5z" stroke="currentColor" stroke-width="1.2"/><circle cx="7" cy="6" r="1.5" stroke="currentColor" stroke-width="1"/></svg>在地图上点击选择坐标位置</span>
<div class="map-picker__coord" id="mapCoord"></div>
</div>
<input class="form-control" id="eAddr" placeholder="楼栋详细地址">
</div>
<div class="form-row">
@@ -262,7 +289,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="modal__title">批量导入数据</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 选择站点(导入的楼栋将挂在该站点下)</span>
<select class="form-control" id="iStation"><option value="">请选择站点</option><option>樊学站点基地</option><option>麻子山站点</option><option>桐园站点基地</option><option>西峰基地</option><option>姬圪台站点</option><option>靖边气田站点</option></select>
<div class="station-search" id="importStationSearch">
<input class="form-control" id="iStation" placeholder="请输入站点名称搜索" autocomplete="off" oninput="onImportStationInput()" onfocus="onImportStationFocus()">
<div class="station-search__panel" id="importStationPanel"></div>
</div>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 上传文件</span>
@@ -359,49 +389,73 @@ var bldDorms={
{name:'503室',beds:4,manager:'田广润',mobile:'13991923747',alloc:0,dev:'—'}
]
};
function fillOptions(sel,opts,keepVal){
var v=keepVal||'';
sel.innerHTML='<option value="">'+(sel.dataset.tip||'全部')+'</option>'+opts.map(function(o){return '<option'+(o===v?' selected':'')+'>'+o+'</option>';}).join('');
var selUnit='',selDept='',stationList=[],stationMeta={};
function fillOptions(sel,opts){
sel.innerHTML='<option value="">'+(sel.dataset.tip||'全部')+'</option>'+opts.map(function(o){return '<option>'+o+'</option>';}).join('');
}
function buildStationMeta(){
stationList=[];stationMeta={};
Object.keys(unitDeparts).forEach(function(u){
unitDeparts[u].forEach(function(d){
(depStations[d]||[]).forEach(function(st){
stationList.push({name:st,unit:u,depart:d});
stationMeta[st]={unit:u,depart:d};
});
});
});
}
function initFilters(){
var su=document.getElementById('filterUnit');su.dataset.tip='全部单位';
su.innerHTML='<option value="">全部单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
document.getElementById('filterDepart').dataset.tip='全部部门';
document.getElementById('filterStation').dataset.tip='全部站点';
document.getElementById('eUnit').innerHTML='<option value="">请选择单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
var unitHtml=Object.keys(unitDeparts).map(function(u){return '<div class="org-item" onclick="selectUnit(this,\''+u+'\')">'+u+'</div>';}).join('');
document.getElementById('unitCol').insertAdjacentHTML('beforeend',unitHtml);
var allStations=Object.keys(depStations).reduce(function(a,k){return a.concat(depStations[k]);},[]);
var fs=document.getElementById('filterStation');fs.dataset.tip='全部站点';
fillOptions(fs,allStations);
buildStationMeta();
}
/* [交互] 单位变更时级联刷新部门与站点 */
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var depSel=document.getElementById('filterDepart'),staSel=document.getElementById('filterStation');
var deps=u?unitDeparts[u]:Object.keys(unitDeparts).reduce(function(a,u2){return a.concat(unitDeparts[u2]);},[]);
depSel.dataset.tip='全部部门';fillOptions(depSel,deps);
var stas=deps.reduce(function(a,d){return a.concat(depStations[d]||[]);},[]);
staSel.dataset.tip='全部站点';fillOptions(staSel,stas);
/* [交互] 单位部门级联筛选 */
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(unitDeparts[unitName]||[]).forEach(function(d){html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';});
document.getElementById('deptCol').innerHTML=html;
selDept='';
renderTable();
}
function onFilterDepart(){
var d=document.getElementById('filterDepart').value;
var staSel=document.getElementById('filterStation');
staSel.dataset.tip='全部站点';
var stas=d?(depStations[d]||[]):Object.keys(depStations).reduce(function(a,k){return a.concat(depStations[k]);},[]);
fillOptions(staSel,stas);
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
renderTable();
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
function renderTable(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,s=document.getElementById('filterStation').value,k=document.getElementById('filterName').value.trim();
var list=buildings.filter(function(b){return (!u||b.unit===u)&&(!d||b.depart===d)&&(!s||b.station===s)&&(!k||b.name.indexOf(k)>=0);});
var s=document.getElementById('filterStation').value,k=document.getElementById('filterName').value.trim();
var list=buildings.filter(function(b){return (!selUnit||b.unit===selUnit)&&(!selDept||b.depart===selDept)&&(!s||b.station===s)&&(!k||b.name.indexOf(k)>=0);});
document.getElementById('tbody').innerHTML=list.map(function(b){
var i=buildings.indexOf(b);
return '<tr><td><input type="checkbox" class="rowCk" value="'+i+'" onchange="syncAllCk()"></td>'
+'<td>'+(i+1)+'</td><td>'+b.unit+'</td><td>'+b.depart+'</td><td>'+b.station+'</td><td style="font-weight:500;">'+b.name+'</td>'
+'<td>'+(i+1)+'</td><td style="font-weight:500;">'+b.name+'</td><td>'+b.station+'</td><td>'+b.unit+' / '+b.depart+'</td>'
+'<td>'+b.addr+'</td><td><span class="link-num" onclick="openDorm('+i+')">'+b.dorms+'</span></td><td>'+b.manager+'</td><td>'+b.mobile+'</td>'
+'<td><button class="btn btn-link" onclick="openEdit('+i+')">编辑</button><button class="btn btn-link--danger" onclick="askDelete('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="11">没有符合条件的楼栋</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="10">没有符合条件的楼栋</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 栋';
document.getElementById('ckAll').checked=false;
}
function resetFilter(){['filterUnit','filterDepart','filterStation'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterName').value='';onFilterUnit();}
function resetFilter(){resetOrg();document.getElementById('filterStation').value='';document.getElementById('filterName').value='';renderTable();}
/* [交互] 复选框与批量删除 */
function toggleAll(ck){document.querySelectorAll('.rowCk').forEach(function(c){c.checked=ck.checked;});}
function syncAllCk(){
@@ -416,51 +470,101 @@ function batchDelete(){
document.getElementById('confirmMask').classList.add('open');
}
/* [交互] 新增/编辑 */
var editIdx=-1;
function onEditUnit(){
var u=document.getElementById('eUnit').value;
var depSel=document.getElementById('eDepart'),staSel=document.getElementById('eStation');
depSel.innerHTML='<option value="">请选择部门</option>'+(u?unitDeparts[u]:[]).map(function(d){return '<option>'+d+'</option>';}).join('');
staSel.innerHTML='<option value="">请先选择部门</option>';
var editIdx=-1,selStation='';
function onStationInput(){selStation='';renderStationPanel();}
function onStationFocus(){renderStationPanel();}
function renderStationPanel(){
var q=document.getElementById('eStation').value.trim();
var list=stationList.filter(function(st){return !q||st.name.indexOf(q)>=0;});
document.getElementById('stationPanel').innerHTML=list.map(function(st){
return '<div class="station-search__item" onclick="pickStation(\''+st.name+'\')"><span class="ss-name">'+st.name+'</span><span class="ss-org">'+st.unit+' / '+st.depart+'</span></div>';
}).join('')||'<div class="station-search__empty">未查询到站点</div>';
document.getElementById('stationPanel').classList.add('open');
}
function onEditDepart(){
var d=document.getElementById('eDepart').value;
document.getElementById('eStation').innerHTML='<option value="">请选择站点</option>'+(d?depStations[d]:[]).map(function(s){return '<option>'+s+'</option>';}).join('');
function pickStation(name){
selStation=name;
document.getElementById('eStation').value=name;
document.getElementById('stationPanel').classList.remove('open');
}
/* [交互] 地图选点:点击地图生成坐标并回填详细地址 */
function pickLocation(e){
var picker=document.getElementById('mapPicker');
var rect=picker.getBoundingClientRect();
var x=e.clientX-rect.left;
var y=e.clientY-rect.top;
var pin=document.getElementById('mapPin');
pin.style.display='block';
pin.style.left=x+'px';
pin.style.top=y+'px';
document.getElementById('mapLabel').style.display='none';
var addresses=['樊学作业区基地营地东侧','麻子山作业区站点北门','桐园作业区生活区西区','西峰基地食堂南侧','靖边气田综合管理营地北侧'];
var idx=Math.floor(Math.random()*addresses.length);
var coord=document.getElementById('mapCoord');
coord.innerHTML='<svg width="12" height="12" viewBox="0 0 14 14" fill="none" style="vertical-align:-1px;margin-right:4px;"><path d="M7 1C4.24 1 2 3.24 2 6c0 3.75 5 7 5 7s5-3.25 5-7c0-2.76-2.24-5-5-5z" stroke="#1890FF" stroke-width="1.2"/><circle cx="7" cy="6" r="1.5" stroke="#1890FF" stroke-width="1"/></svg>'+addresses[idx];
coord.style.display='block';
document.getElementById('eAddr').value=addresses[idx];
}
function resetMapPicker(){
document.getElementById('mapPin').style.display='none';
document.getElementById('mapLabel').style.display='flex';
document.getElementById('mapCoord').style.display='none';
document.getElementById('mapCoord').innerHTML='';
}
function openEdit(i){
editIdx=i;
resetMapPicker();
document.getElementById('editTitle').textContent=i<0?'新增楼栋信息':'编辑楼栋信息';
onEditUnit();
if(i>=0){
var b=buildings[i];
document.getElementById('eUnit').value=b.unit;onEditUnit();
document.getElementById('eDepart').innerHTML='<option>请选择部门</option>'+unitDeparts[b.unit].map(function(d){return '<option'+(d===b.depart?' selected':'')+'>'+d+'</option>';}).join('');
onEditDepart();
document.getElementById('eStation').innerHTML='<option>请选择站点</option>'+depStations[b.depart].map(function(s){return '<option'+(s===b.station?' selected':'')+'>'+s+'</option>';}).join('');
selStation=b.station;
document.getElementById('eStation').value=b.station;
document.getElementById('eName').value=b.name;document.getElementById('eAddr').value=b.addr;
document.getElementById('eManager').value=b.manager;document.getElementById('eMobile').value=b.mobile;
}else{
['eName','eAddr','eManager','eMobile'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('eUnit').value='';onEditUnit();
selStation='';
['eStation','eName','eAddr','eManager','eMobile'].forEach(function(id){document.getElementById(id).value='';});
}
document.getElementById('stationPanel').classList.remove('open');
document.getElementById('editMask').classList.add('open');
}
function closeEdit(){document.getElementById('editMask').classList.remove('open');}
function saveEdit(){
var u=document.getElementById('eUnit').value,d=document.getElementById('eDepart').value,s=document.getElementById('eStation').value;
var s=selStation||document.getElementById('eStation').value.trim();
var n=document.getElementById('eName').value.trim(),a=document.getElementById('eAddr').value.trim();
if(!u||!d||!s||!n||!a){showToast('请完整填写带 * 的必填项');return;}
var meta=stationMeta[s];
if(!s||!meta||!n||!a){showToast('请完整填写带 * 的必填项');return;}
var dup=buildings.some(function(b,i){return i!==editIdx&&b.station===s&&b.name===n;});
if(dup){showToast('该站点下已存在同名楼栋');return;}
var obj={unit:u,depart:d,station:s,name:n,addr:a,dorms:editIdx>=0?buildings[editIdx].dorms:0,manager:document.getElementById('eManager').value.trim()||'—',mobile:document.getElementById('eMobile').value.trim()||'—'};
var obj={unit:meta.unit,depart:meta.depart,station:s,name:n,addr:a,dorms:editIdx>=0?buildings[editIdx].dorms:0,manager:document.getElementById('eManager').value.trim()||'—',mobile:document.getElementById('eMobile').value.trim()||'—'};
if(editIdx<0)buildings.push(obj);else buildings[editIdx]=obj;
closeEdit();renderTable();showToast(editIdx<0?'楼栋已新增':'楼栋已更新');
}
/* [交互] 导入 */
function openImport(){document.getElementById('importMask').classList.add('open');}
var selImportStation='';
function onImportStationInput(){selImportStation='';renderImportStationPanel();}
function onImportStationFocus(){renderImportStationPanel();}
function renderImportStationPanel(){
var q=document.getElementById('iStation').value.trim();
var list=stationList.filter(function(st){return !q||st.name.indexOf(q)>=0;});
document.getElementById('importStationPanel').innerHTML=list.map(function(st){
return '<div class="station-search__item" onclick="pickImportStation(\''+st.name+'\')"><span class="ss-name">'+st.name+'</span><span class="ss-org">'+st.unit+' / '+st.depart+'</span></div>';
}).join('')||'<div class="station-search__empty">未查询到站点</div>';
document.getElementById('importStationPanel').classList.add('open');
}
function pickImportStation(name){
selImportStation=name;
document.getElementById('iStation').value=name;
document.getElementById('importStationPanel').classList.remove('open');
}
function openImport(){
selImportStation='';
document.getElementById('iStation').value='';
document.getElementById('importStationPanel').classList.remove('open');
document.getElementById('importMask').classList.add('open');
}
function closeImport(){document.getElementById('importMask').classList.remove('open');}
function doImport(){
if(!document.getElementById('iStation').value){showToast('请先选择导入目标站点');return;}
if(!selImportStation){showToast('请先选择导入目标站点');return;}
closeImport();showToast('导入任务已提交,请到「查看导入任务」查看结果');
}
/* [交互] 导入任务(异步任务演示) */
@@ -515,7 +619,16 @@ 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);
}
initFilters();onFilterUnit();
/* [交互] 点击面板外部关闭下拉 */
document.addEventListener('click',function(e){
var ow=document.getElementById('orgWrap');
if(ow&&!ow.contains(e.target)){document.getElementById('orgPanel').classList.remove('open');document.getElementById('orgTrigger').classList.remove('open');}
var ss=document.getElementById('stationSearch');
if(ss&&!ss.contains(e.target)){document.getElementById('stationPanel').classList.remove('open');}
var is=document.getElementById('importStationSearch');
if(is&&!is.contains(e.target)){document.getElementById('importStationPanel').classList.remove('open');}
});
initFilters();renderTable();
</script>
</body>
</html>
@@ -45,6 +45,17 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.filter-select{height:36px;min-width:140px;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;}
.filter-input{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:36px;min-width:130px;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;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-sm);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
@@ -152,16 +163,28 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()"><option value="">全部单位</option></select>
</div>
<div class="filter-item">
<label>所属部门</label>
<select class="filter-select" id="filterDepart" onchange="renderTable()" style="min-width:130px;"><option value="">全部部门</option></select>
<label>所属单位部门</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>设备编号</label>
<input class="filter-input" id="filterCode" placeholder="如:HC-" oninput="renderTable()">
<input class="filter-input" id="filterCode" placeholder="请输入" oninput="renderTable()">
</div>
<div class="filter-item">
<label>分配状态</label>
@@ -189,7 +212,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<table>
<thead>
<tr>
<th style="width:56px;">序号</th><th>所属单位</th><th>所属部门</th><th style="min-width:100px;">设备编号</th><th>分配状态</th><th>分配类型</th>
<th style="width:56px;">序号</th><th>所属单位</th><th>所属部门</th><th style="min-width:100px;">设备编号</th><th style="min-width:130px;">MAC地址</th><th>分配状态</th><th>分配类型</th>
<th style="min-width:230px;">分配宿舍/床位</th><th style="min-width:100px;">分配日期</th><th style="width:150px;">操作</th>
</tr>
</thead>
@@ -289,43 +312,65 @@ var dormTree={
};
/* 与「设备列表」呼叫设备一致;alloc:1宿舍/2床位 */
var devices=[
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-01',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 101-1',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-02',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 101-2',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-03',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 102-1',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-04',alloc:1,type:1,space:'樊学倒班宿舍1号楼 · 101室',time:'2026-03-05'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HC-MZ-01',alloc:1,type:1,space:'麻子山倒班宿舍楼 · 101室',time:'2026-03-19'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-01',alloc:1,type:2,space:'桐园倒班宿舍1号楼 · 301-1',time:'2026-04-10'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-02',alloc:1,type:1,space:'桐园倒班宿舍1号楼 · 201室',time:'2026-04-10'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-01',alloc:1,type:2,space:'西峰基地综合宿舍楼 · 301-1',time:'2026-05-12'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-02',alloc:1,type:2,space:'西峰基地综合宿舍楼 · 302-1',time:'2026-05-12'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-03',alloc:1,type:1,space:'西峰基地综合宿舍楼 · 402室',time:'2026-05-12'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-01',alloc:1,type:2,space:'靖边气田倒班宿舍楼 · 401-1',time:'2026-06-24'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-02',alloc:0,type:0,space:'',time:''}
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-01',mac:'00:1A:2B:3C:4D:01',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 101-1',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-02',mac:'00:1A:2B:3C:4D:02',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 101-2',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-03',mac:'00:1A:2B:3C:4D:03',alloc:1,type:2,space:'樊学倒班宿舍1号楼 · 102-1',time:'2026-03-05'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-04',mac:'00:1A:2B:3C:4D:04',alloc:1,type:1,space:'樊学倒班宿舍1号楼 · 101室',time:'2026-03-05'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HC-MZ-01',mac:'00:1A:2B:3C:4D:05',alloc:1,type:1,space:'麻子山倒班宿舍楼 · 101室',time:'2026-03-19'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-01',mac:'00:1A:2B:3C:4D:06',alloc:1,type:2,space:'桐园倒班宿舍1号楼 · 301-1',time:'2026-04-10'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-02',mac:'00:1A:2B:3C:4D:07',alloc:1,type:1,space:'桐园倒班宿舍1号楼 · 201室',time:'2026-04-10'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-01',mac:'00:1A:2B:3C:4D:08',alloc:1,type:2,space:'西峰基地综合宿舍楼 · 301-1',time:'2026-05-12'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-02',mac:'00:1A:2B:3C:4D:09',alloc:1,type:2,space:'西峰基地综合宿舍楼 · 302-1',time:'2026-05-12'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-03',mac:'00:1A:2B:3C:4D:0A',alloc:1,type:1,space:'西峰基地综合宿舍楼 · 402室',time:'2026-05-12'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-01',mac:'00:1A:2B:3C:4D:0B',alloc:1,type:2,space:'靖边气田倒班宿舍楼 · 401-1',time:'2026-06-24'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-02',mac:'00:1A:2B:3C:4D:0C',alloc:0,type:0,space:'',time:''}
];
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var depSel=document.getElementById('filterDepart');
var deps=u?unitDeparts[u]:Object.keys(unitDeparts).reduce(function(a,u2){return a.concat(unitDeparts[u2]);},[]);
var uniq=[];deps.forEach(function(x){if(uniq.indexOf(x)<0)uniq.push(x);});
depSel.innerHTML='<option value="">全部部门</option>'+uniq.map(function(d){return '<option>'+d+'</option>';}).join('');
renderTable();
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(unitDeparts[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
function renderTable(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,k=document.getElementById('filterCode').value.trim().toUpperCase(),s=document.getElementById('filterStatus').value;
var u=selUnit,d=selDept,k=document.getElementById('filterCode').value.trim().toUpperCase(),s=document.getElementById('filterStatus').value;
var list=devices.filter(function(r){return (!u||r.unit===u)&&(!d||r.depart===d)&&(!k||r.code.toUpperCase().indexOf(k)>=0)&&(s===''||String(r.alloc)===s);});
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=devices.indexOf(r);
var typeTxt=r.alloc?('<span class="badge '+(r.type===1?'badge--info':'badge--success')+'">'+(r.type===1?'宿舍':'床位')+'</span>'):'<span class="badge badge--muted">-</span>';
var typeTxt=r.alloc?(r.type===1?'宿舍':'床位'):'-';
var ops=r.alloc
?'<button class="btn btn-link" onclick="openAlloc('+i+')">重新分配</button><button class="btn btn-link--danger" onclick="askUnbind('+i+')">解除分配</button>'
:'<button class="btn btn-link" onclick="openAlloc('+i+')">设备分配</button>';
return '<tr><td>'+(i+1)+'</td><td>'+r.unit+'</td><td>'+r.depart+'</td><td style="font-family:monospace;font-weight:500;">'+r.code+'</td>'
return '<tr><td>'+(i+1)+'</td><td>'+r.unit+'</td><td>'+r.depart+'</td><td style="font-family:monospace;font-weight:500;">'+r.code+'</td><td style="font-family:monospace;">'+(r.mac||'—')+'</td>'
+'<td>'+(r.alloc?'<span class="badge badge--success">已分配</span>':'<span class="badge badge--warning">未分配</span>')+'</td>'
+'<td>'+typeTxt+'</td><td>'+(r.space||'-')+'</td><td>'+(r.time||'-')+'</td><td>'+ops+'</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="9">没有符合条件的设备</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="10">没有符合条件的设备</td></tr>';
document.getElementById('totalTip').textContent='共 '+devices.length+' 台 · 已分配 '+devices.filter(function(r){return r.alloc;}).length+' 台';
}
function resetFilter(){['filterUnit','filterDepart','filterStatus'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterCode').value='';onFilterUnit();}
function resetFilter(){resetOrg();document.getElementById('filterStatus').value='';document.getElementById('filterCode').value='';renderTable();}
/* [交互] 设备分配/重新分配 */
var allocIdx=-1;
function openAlloc(i){
@@ -400,8 +445,8 @@ 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);
}
document.getElementById('filterUnit').innerHTML='<option value="">全部单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
onFilterUnit();
resetOrg();
renderTable();
</script>
</body>
</html>
+107 -58
View File
@@ -45,6 +45,17 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.filter-select{height:36px;min-width:140px;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;}
.filter-input{height:36px;min-width:120px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:36px;min-width:130px;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;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-sm);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
@@ -155,21 +166,33 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()"><option value="">全部单位</option></select>
</div>
<div class="filter-item">
<label>所属部门</label>
<select class="filter-select" id="filterDepart" onchange="renderAll()" style="min-width:130px;"><option value="">全部部门</option></select>
<label>所属单位部门</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>设备编号</label>
<input class="filter-input" id="filterCode" placeholder="如:HC-FX" oninput="renderAll()">
<input class="filter-input" id="filterCode" placeholder="请输入" oninput="renderAll()">
</div>
<div class="filter-item">
<label>设备状态</label>
<select class="filter-select" id="filterStatus" onchange="renderAll()" style="min-width:100px;">
<option value="">全部</option><option value="0">正常</option><option value="1">故障</option><option value="2">停用</option>
<option value="">全部</option><option value="0">在线</option><option value="1">离线</option>
</select>
</div>
<button class="btn btn-primary" onclick="renderAll();showToast('查询完成')">
@@ -217,53 +240,57 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<select class="form-control" id="eUnit" onchange="onEditUnit()"><option value="">请选择单位</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属部门</span>
<span class="form-label">所属部门</span>
<select class="form-control" id="eDepart"><option value="">请选择部门</option></select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 设备编号</span>
<input class="form-control" id="eCode" placeholder="全局唯一,如:HC-FX-05">
<input class="form-control" id="eCode" placeholder="请输入">
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> <span id="eNameLabel">设备名称</span></span>
<input class="form-control" id="eName" placeholder="如:床头呼叫按钮">
<input class="form-control" id="eName" placeholder="请输入">
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label">设备型号</span>
<input class="form-control" id="eModel" placeholder="如:KQ-P1">
<input class="form-control" id="eModel" placeholder="请输入">
</div>
<div class="form-item">
<span class="form-label">入库日期</span>
<input class="form-control" id="eStore" type="date">
</div>
</div>
<div class="form-item">
<span class="form-label">MAC地址</span>
<input class="form-control" id="eMac" placeholder="请输入">
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label">设备状态</span>
<select class="form-control" id="eStatus"><option value="0">正常</option><option value="1">故障</option><option value="2">停用</option></select>
<select class="form-control" id="eStatus"><option value="0">在线</option><option value="1">离线</option></select>
</div>
<div class="form-item">
<span class="form-label">管理人</span>
<input class="form-control" id="eManager" placeholder="设备管理人姓名">
<input class="form-control" id="eManager" placeholder="请输入">
</div>
</div>
<div class="form-row">
<div class="form-item">
<span class="form-label">管理人电话</span>
<input class="form-control" id="eMphone" placeholder="11位手机号">
<input class="form-control" id="eMphone" placeholder="请输入">
</div>
<div class="form-item">
<span class="form-label">设备厂家</span>
<input class="form-control" id="eMfr" placeholder="生产厂家名称">
<input class="form-control" id="eMfr" placeholder="请输入">
</div>
</div>
<div class="form-item">
<span class="form-label">厂家电话</span>
<input class="form-control" id="eMfrTel" placeholder="厂家售后服务电话">
<input class="form-control" id="eMfrTel" placeholder="请输入">
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeEdit()">取消</button>
@@ -282,7 +309,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<select class="form-control" id="iUnit"><option value="">请选择单位</option></select>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 所属部门</span>
<span class="form-label">所属部门</span>
<select class="form-control" id="iDepart"><option value="">请选择部门</option></select>
</div>
</div>
@@ -329,52 +356,52 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<script>
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
var statusBadge={'0':'badge--success','1':'badge--danger','2':'badge--muted'};
var statusText={'0':'正常','1':'故障','2':'停用'};
/* 呼叫设备:分配关系与「分配管理」「床位信息」勾稽(HC-FX-03 故障见告警记录演示) */
var statusBadge={'0':'badge--success','1':'badge--muted'};
var statusText={'0':'在线','1':'离线'};
/* 呼叫设备:分配关系与「分配管理」「床位信息」勾稽(HC-FX-03 离线见告警记录演示) */
var callDevices=[
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-01',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-02',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-03',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-03-02',status:'1',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-04',name:'房间一键呼叫面板',model:'KQ-P2',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HC-MZ-01',name:'房间一键呼叫面板',model:'KQ-P2',store:'2026-03-18',status:'0',manager:'安伟',mphone:'13991345012',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-01',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-02',name:'房间一键呼叫面板',model:'KQ-P2',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-01',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-02',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-03',name:'房间一键呼叫面板',model:'KQ-P2',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-01',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-06-23',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-02',name:'床头呼叫按钮',model:'KQ-P1',store:'2026-09-01',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'CQ中控医疗',mfrTel:'029-86521700'}
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-01',name:'床头呼叫按钮',model:'KQ-P1',mac:'00:1A:2B:3C:4D:01',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-02',name:'床头呼叫按钮',model:'KQ-P1',mac:'00:1A:2B:3C:4D:02',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-03',name:'床头呼叫按钮',model:'KQ-P1',mac:'00:1A:2B:3C:4D:03',store:'2026-03-02',status:'1',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'樊学作业区',code:'HC-FX-04',name:'房间一键呼叫面板',model:'KQ-P2',mac:'00:1A:2B:3C:4D:04',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HC-MZ-01',name:'房间一键呼叫面板',model:'KQ-P2',mac:'00:1A:2B:3C:4D:05',store:'2026-03-18',status:'0',manager:'安伟',mphone:'13991345012',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-01',name:'床头呼叫按钮',model:'KQ-P1',mac:'00:1A:2B:3C:4D:06',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油二厂',depart:'桐园作业区',code:'HC-TY-02',name:'房间一键呼叫面板',model:'KQ-P2',mac:'00:1A:2B:3C:4D:07',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-01',name:'床头呼叫按钮',model:'KQ-P1',mac:'00:1A:2B:3C:4D:08',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-02',name:'床头呼叫按钮',model:'KQ-P1',mac:'00:1A:2B:3C:4D:09',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HC-XF-03',name:'房间一键呼叫面板',model:'KQ-P2',mac:'00:1A:2B:3C:4D:0A',store:'2026-05-11',status:'0',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-01',name:'床头呼叫按钮',model:'KQ-P1',mac:'00:1A:2B:3C:4D:0B',store:'2026-06-23',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'CQ中控医疗',mfrTel:'029-86521700'},
{unit:'采气一厂',depart:'靖边气田',code:'HC-JB-02',name:'床头呼叫按钮',model:'KQ-P1',mac:'00:1A:2B:3C:4D:0C',store:'2026-09-01',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'CQ中控医疗',mfrTel:'029-86521700'}
];
/* 终端设备:值班室固定部署,不参与宿舍/床位分配 */
var termDevices=[
{unit:'采油一厂',depart:'樊学作业区',code:'HD-FX-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HD-MZ-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-03-18',status:'0',manager:'安伟',mphone:'13991345012',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油二厂',depart:'桐园作业区',code:'HD-TY-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HD-XF-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-05-11',status:'1',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采气一厂',depart:'靖边气田',code:'HD-JB-01',name:'值班室接收终端',model:'KQ-T10',store:'2026-06-23',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'华旭智能',mfrTel:'029-87334210'}
{unit:'采油一厂',depart:'樊学作业区',code:'HD-FX-01',name:'值班室接收终端',model:'KQ-T10',mac:'00:1A:2B:3C:4D:11',store:'2026-03-02',status:'0',manager:'王守正',mphone:'13991203456',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油一厂',depart:'麻子山作业区',code:'HD-MZ-01',name:'值班室接收终端',model:'KQ-T10',mac:'00:1A:2B:3C:4D:12',store:'2026-03-18',status:'0',manager:'安伟',mphone:'13991345012',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采油二厂',depart:'桐园作业区',code:'HD-TY-01',name:'值班室接收终端',model:'KQ-T10',mac:'00:1A:2B:3C:4D:13',store:'2026-04-09',status:'0',manager:'陈国栋',mphone:'13991567208',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'第四采油厂',depart:'西峰作业区',code:'HD-XF-01',name:'值班室接收终端',model:'KQ-T10',mac:'00:1A:2B:3C:4D:14',store:'2026-05-11',status:'1',manager:'马文秀',mphone:'13991784320',mfr:'华旭智能',mfrTel:'029-87334210'},
{unit:'采气一厂',depart:'靖边气田',code:'HD-JB-01',name:'值班室接收终端',model:'KQ-T10',mac:'00:1A:2B:3C:4D:15',store:'2026-06-23',status:'0',manager:'周铁祥',mphone:'13991923745',mfr:'华旭智能',mfrTel:'029-87334210'}
];
var curTab='call';
function tabName(){return curTab==='call'?'呼叫设备':'终端设备';}
function headRow(){
var name=curTab==='call'?'设备名称':'终端名称';
return '<tr><th style="width:40px;"><input type="checkbox" id="ckAll" onchange="toggleAll(this)"></th>'
+'<th style="width:56px;">序号</th><th>所属单位</th><th>所属部门</th><th style="min-width:100px;">设备编号</th><th style="min-width:120px;">'+name+'</th><th>设备型号</th><th>入库日期</th><th>设备状态</th><th>管理人</th><th style="min-width:105px;">管理人电话</th><th style="min-width:110px;">设备厂家</th><th style="min-width:110px;">厂家电话</th><th style="width:110px;">操作</th></tr>';
+'<th style="width:56px;">序号</th><th>所属单位</th><th>所属部门</th><th style="min-width:100px;">设备编号</th><th style="min-width:120px;">'+name+'</th><th>设备型号</th><th style="min-width:130px;">MAC地址</th><th>入库日期</th><th>设备状态</th><th>管理人</th><th style="min-width:105px;">管理人电话</th><th style="min-width:110px;">设备厂家</th><th style="min-width:110px;">厂家电话</th><th style="width:110px;">操作</th></tr>';
}
function renderAll(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,k=document.getElementById('filterCode').value.trim().toUpperCase(),s=document.getElementById('filterStatus').value;
var u=selUnit,d=selDept,k=document.getElementById('filterCode').value.trim().toUpperCase(),s=document.getElementById('filterStatus').value;
var src=curTab==='call'?callDevices:termDevices;
var list=src.filter(function(r){return (!u||r.unit===u)&&(!d||r.depart===d)&&(!k||r.code.toUpperCase().indexOf(k)>=0)&&(s===''||r.status===s);});
document.getElementById('thead').innerHTML=headRow();
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=src.indexOf(r);
return '<tr><td><input type="checkbox" class="rowCk" value="'+i+'" onchange="syncAllCk()"></td>'
+'<td>'+(i+1)+'</td><td>'+r.unit+'</td><td>'+r.depart+'</td><td style="font-family:monospace;font-weight:500;">'+r.code+'</td><td>'+r.name+'</td><td>'+r.model+'</td><td>'+r.store+'</td>'
+'<td>'+(i+1)+'</td><td>'+r.unit+'</td><td>'+r.depart+'</td><td style="font-family:monospace;font-weight:500;">'+r.code+'</td><td>'+r.name+'</td><td>'+r.model+'</td><td style="font-family:monospace;">'+(r.mac||'—')+'</td><td>'+r.store+'</td>'
+'<td><span class="badge '+statusBadge[r.status]+'">'+statusText[r.status]+'</span></td>'
+'<td>'+r.manager+'</td><td>'+r.mphone+'</td><td>'+r.mfr+'</td><td>'+r.mfrTel+'</td>'
+'<td><button class="btn btn-link" onclick="openEdit('+i+')">编辑</button><button class="btn btn-link--danger" onclick="askDelete('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="14">没有符合条件的设备</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 台(正常 '+list.filter(function(r){return r.status==='0';}).length+' / 故障 '+list.filter(function(r){return r.status==='1';}).length+' / 停用 '+list.filter(function(r){return r.status==='2';}).length+')';
}).join('')||'<tr><td class="td-empty" colspan="15">没有符合条件的设备</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 台(在线 '+list.filter(function(r){return r.status==='0';}).length+' / 离线 '+list.filter(function(r){return r.status==='1';}).length+')';
}
function switchTab(t){
curTab=t;
@@ -382,15 +409,37 @@ function switchTab(t){
document.getElementById('tabTerm').classList.toggle('active',t==='term');
renderAll();
}
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var depSel=document.getElementById('filterDepart');
var deps=u?unitDeparts[u]:Object.keys(unitDeparts).reduce(function(a,u2){return a.concat(unitDeparts[u2]);},[]);
var uniq=[];deps.forEach(function(x){if(uniq.indexOf(x)<0)uniq.push(x);});
depSel.innerHTML='<option value="">全部部门</option>'+uniq.map(function(d){return '<option>'+d+'</option>';}).join('');
renderAll();
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function resetFilter(){['filterUnit','filterDepart','filterStatus'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterCode').value='';onFilterUnit();}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(unitDeparts[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
function resetFilter(){resetOrg();document.getElementById('filterStatus').value='';document.getElementById('filterCode').value='';renderAll();}
/* [交互] 复选框与批量删除 */
function toggleAll(ck){document.querySelectorAll('.rowCk').forEach(function(c){c.checked=ck.checked;});}
function syncAllCk(){
@@ -420,13 +469,13 @@ function openEdit(i){
document.getElementById('eUnit').value=r.unit;onEditUnit();
document.getElementById('eDepart').innerHTML='<option value="">请选择部门</option>'+unitDeparts[r.unit].map(function(d){return '<option'+(d===r.depart?' selected':'')+'>'+d+'</option>';}).join('');
document.getElementById('eCode').value=r.code;document.getElementById('eName').value=r.name;
document.getElementById('eModel').value=r.model;document.getElementById('eStore').value=r.store;
document.getElementById('eModel').value=r.model;document.getElementById('eMac').value=r.mac;document.getElementById('eStore').value=r.store;
document.getElementById('eStatus').value=r.status;document.getElementById('eManager').value=r.manager;
document.getElementById('eMphone').value=r.mphone;document.getElementById('eMfr').value=r.mfr;
document.getElementById('eMfrTel').value=r.mfrTel;
}else{
document.getElementById('eUnit').value='';onEditUnit();
['eCode','eName','eModel','eStore','eManager','eMphone','eMfr','eMfrTel'].forEach(function(id){document.getElementById(id).value='';});
['eCode','eName','eModel','eMac','eStore','eManager','eMphone','eMfr','eMfrTel'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('eStatus').value='0';
}
document.getElementById('editMask').classList.add('open');
@@ -434,11 +483,11 @@ function openEdit(i){
function closeEdit(){document.getElementById('editMask').classList.remove('open');}
function saveEdit(){
var u=document.getElementById('eUnit').value,d=document.getElementById('eDepart').value,c=document.getElementById('eCode').value.trim(),n=document.getElementById('eName').value.trim();
if(!u||!d||!c||!n){showToast('请完整填写带 * 的必填项');return;}
if(!u||!c||!n){showToast('请完整填写带 * 的必填项');return;}
var src=curTab==='call'?callDevices:termDevices;
var dup=callDevices.concat(termDevices).some(function(r){return r!==src[editIdx]&&r.code===c;});
if(dup){showToast('设备编号已存在,须全局唯一');return;}
var obj={unit:u,depart:d,code:c,name:n,model:document.getElementById('eModel').value.trim()||'—',store:document.getElementById('eStore').value||'—',status:document.getElementById('eStatus').value,manager:document.getElementById('eManager').value.trim()||'—',mphone:document.getElementById('eMphone').value.trim()||'—',mfr:document.getElementById('eMfr').value.trim()||'—',mfrTel:document.getElementById('eMfrTel').value.trim()||'—'};
var obj={unit:u,depart:d,code:c,name:n,model:document.getElementById('eModel').value.trim()||'—',mac:document.getElementById('eMac').value.trim(),store:document.getElementById('eStore').value||'—',status:document.getElementById('eStatus').value,manager:document.getElementById('eManager').value.trim()||'—',mphone:document.getElementById('eMphone').value.trim()||'—',mfr:document.getElementById('eMfr').value.trim()||'—',mfrTel:document.getElementById('eMfrTel').value.trim()||'—'};
if(editIdx<0)src.push(obj);else src[editIdx]=obj;
closeEdit();renderAll();showToast(editIdx<0?tabName()+'已新增':tabName()+'已更新');
}
@@ -455,7 +504,7 @@ function openImport(){
}
function closeImport(){document.getElementById('importMask').classList.remove('open');}
function doImport(){
if(!document.getElementById('iUnit').value||!document.getElementById('iDepart').value){showToast('请选择单位与部门');return;}
if(!document.getElementById('iUnit').value){showToast('请选择单位');return;}
closeImport();showToast('导入任务已提交,请到「查看导入任务」查看结果');
}
/* [交互] 导入任务(异步任务演示) */
@@ -495,8 +544,8 @@ 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);
}
document.getElementById('filterUnit').innerHTML='<option value="">全部单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
onFilterUnit();
resetOrg();
renderAll();
</script>
</body>
</html>
+97 -8
View File
@@ -45,6 +45,18 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.filter-select{height:36px;min-width:140px;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;}
.filter-input{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
/* 单位部门级联筛选 */
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:36px;min-width:130px;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;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-sm);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
@@ -152,6 +164,28 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 筛选栏 -->
<div class="filter-bar">
<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">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>所属站点</label>
<select class="filter-select" id="filterStation" onchange="renderTable()" style="min-width:140px;">
<option value="">全部站点</option>
</select>
</div>
<div class="filter-item">
<label>所属楼栋</label>
<select class="filter-select" id="filterBuilding" onchange="renderTable()" style="min-width:180px;">
@@ -192,8 +226,8 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<thead>
<tr>
<th style="width:40px;"><input type="checkbox" id="ckAll" onchange="toggleAll(this)"></th>
<th style="width:56px;">序号</th><th style="min-width:110px;">宿舍名称</th><th style="min-width:180px;">所属楼栋</th><th>床位数量</th><th>负责人</th>
<th style="min-width:110px;">负责人电话</th><th>是否分配设备</th><th style="min-width:110px;">设备编号</th><th style="width:110px;">操作</th>
<th style="width:56px;">序号</th><th style="min-width:110px;">宿舍名称</th><th style="min-width:180px;">所属楼栋</th><th style="min-width:120px;">所属站点</th><th style="min-width:160px;">所属单位部门</th>
<th>床位数量</th><th>是否分配设备</th><th style="min-width:110px;">设备编号</th><th>负责人</th><th style="min-width:110px;">负责人电话</th><th style="width:110px;">操作</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
@@ -302,6 +336,17 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<script>
/* 楼栋清单与「楼栋信息」页勾稽 */
var buildings=['樊学倒班宿舍1号楼','麻子山倒班宿舍楼','桐园倒班宿舍1号楼','西峰基地综合宿舍楼','靖边气田倒班宿舍楼'];
/* 单位→部门→站点三级映射与楼栋归属,与「楼栋信息」页口径一致 */
var unitDeparts={'采油一厂':['樊学作业区','麻子山作业区'],'采油二厂':['桐园作业区','姬圪台作业区'],'第四采油厂':['西峰作业区'],'采气一厂':['靖边气田']};
var depStations={'樊学作业区':['樊学站点基地'],'麻子山作业区':['麻子山站点'],'桐园作业区':['桐园站点基地'],'姬圪台作业区':['姬圪台站点'],'西峰作业区':['西峰基地'],'靖边气田':['靖边气田站点']};
var buildingMeta={
'樊学倒班宿舍1号楼':{station:'樊学站点基地',unit:'采油一厂',depart:'樊学作业区'},
'麻子山倒班宿舍楼':{station:'麻子山站点',unit:'采油一厂',depart:'麻子山作业区'},
'桐园倒班宿舍1号楼':{station:'桐园站点基地',unit:'采油二厂',depart:'桐园作业区'},
'西峰基地综合宿舍楼':{station:'西峰基地',unit:'第四采油厂',depart:'西峰作业区'},
'靖边气田倒班宿舍楼':{station:'靖边气田站点',unit:'采气一厂',depart:'靖边气田'}
};
var selUnit='',selDept='';
/* alloc:绑定到宿舍本身的设备(房间级呼叫);床位级绑定见「分配管理」 */
var dorms=[
{name:'101室',building:'樊学倒班宿舍1号楼',beds:4,manager:'石维国',mobile:'13991203456',alloc:1,device:'HC-FX-04'},
@@ -341,26 +386,65 @@ var bedAssigns={
'靖边气田倒班宿舍楼|401-1':'CQ036441','靖边气田倒班宿舍楼|401-2':'CQ029905'
};
function initFilters(){
var unitHtml=Object.keys(unitDeparts).map(function(u){return '<div class="org-item" onclick="selectUnit(this,\''+u+'\')">'+u+'</div>';}).join('');
document.getElementById('unitCol').insertAdjacentHTML('beforeend',unitHtml);
var opts='<option value="">全部楼栋</option>'+buildings.map(function(b){return '<option>'+b+'</option>';}).join('');
document.getElementById('filterBuilding').innerHTML=opts;
document.getElementById('eBuilding').innerHTML='<option value="">请选择楼栋</option>'+buildings.map(function(b){return '<option>'+b+'</option>';}).join('');
document.getElementById('iBuilding').innerHTML='<option value="">请选择楼栋</option>'+buildings.map(function(b){return '<option>'+b+'</option>';}).join('');
var allStations=Object.keys(depStations).reduce(function(a,k){return a.concat(depStations[k]);},[]);
document.getElementById('filterStation').innerHTML='<option value="">全部站点</option>'+allStations.map(function(s){return '<option>'+s+'</option>';}).join('');
}
/* [交互] 单位部门级联筛选 */
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(unitDeparts[unitName]||[]).forEach(function(d){html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';});
document.getElementById('deptCol').innerHTML=html;
selDept='';
renderTable();
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
renderTable();
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
function renderTable(){
var b=document.getElementById('filterBuilding').value,n=document.getElementById('filterName').value.trim(),a=document.getElementById('filterAlloc').value;
var list=dorms.filter(function(r){return (!b||r.building===b)&&(!n||r.name.indexOf(n)>=0)&&(a===''||String(r.alloc)===a);});
var s=document.getElementById('filterStation').value,b=document.getElementById('filterBuilding').value,n=document.getElementById('filterName').value.trim(),a=document.getElementById('filterAlloc').value;
var list=dorms.filter(function(r){
var meta=buildingMeta[r.building]||{};
return (!selUnit||meta.unit===selUnit)&&(!selDept||meta.depart===selDept)&&(!s||meta.station===s)&&(!b||r.building===b)&&(!n||r.name.indexOf(n)>=0)&&(a===''||String(r.alloc)===a);
});
document.getElementById('tbody').innerHTML=list.map(function(r){
var i=dorms.indexOf(r);
var meta=buildingMeta[r.building]||{};
return '<tr><td><input type="checkbox" class="rowCk" value="'+i+'" onchange="syncAllCk()"></td>'
+'<td>'+(i+1)+'</td><td style="font-weight:500;">'+r.name+'</td><td>'+r.building+'</td><td><span class="link-num" onclick="openBed('+i+')">'+r.beds+'</span></td><td>'+r.manager+'</td><td>'+r.mobile+'</td>'
+'<td>'+(i+1)+'</td><td style="font-weight:500;">'+r.name+'</td><td>'+r.building+'</td><td>'+(meta.station||'')+'</td><td>'+(meta.unit?meta.unit+' / '+meta.depart:'')+'</td>'
+'<td><span class="link-num" onclick="openBed('+i+')">'+r.beds+'</span></td>'
+'<td>'+(r.alloc?'<span class="badge badge--success">已分配</span>':'<span class="badge badge--muted">未分配</span>')+'</td>'
+'<td style="font-family:monospace;">'+(r.device||'-')+'</td>'
+'<td style="font-family:monospace;">'+(r.device||'-')+'</td><td>'+r.manager+'</td><td>'+r.mobile+'</td>'
+'<td><button class="btn btn-link" onclick="openEdit('+i+')">编辑</button><button class="btn btn-link--danger" onclick="askDelete('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="10">没有符合条件的宿舍</td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="12">没有符合条件的宿舍</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 间 · 房间级设备 '+dorms.filter(function(r){return r.alloc;}).length+' 台';
document.getElementById('ckAll').checked=false;
}
function resetFilter(){['filterBuilding','filterAlloc'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterName').value='';renderTable();}
function resetFilter(){resetOrg();['filterStation','filterBuilding','filterAlloc'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterName').value='';renderTable();}
/* [交互] 复选框与批量删除 */
function toggleAll(ck){document.querySelectorAll('.rowCk').forEach(function(c){c.checked=ck.checked;});}
function syncAllCk(){
@@ -459,6 +543,11 @@ 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);
}
/* [交互] 点击面板外部关闭下拉 */
document.addEventListener('click',function(e){
var ow=document.getElementById('orgWrap');
if(ow&&!ow.contains(e.target)){document.getElementById('orgPanel').classList.remove('open');document.getElementById('orgTrigger').classList.remove('open');}
});
initFilters();renderTable();
</script>
</body>
+80 -86
View File
@@ -45,6 +45,17 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.filter-select{height:36px;min-width:140px;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;}
.filter-input{height:36px;min-width:140px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;}
.filter-input:focus{border-color:var(--primary);}
.org-wrap{position:relative;display:inline-block;}
.org-trigger{height:36px;min-width:130px;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;display:flex;align-items:center;user-select:none;white-space:nowrap;}
.org-trigger.open,.org-trigger:hover{border-color:var(--primary);}
.org-panel{display:none;position:absolute;top:36px;left:0;z-index:310;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);width:380px;}
.org-panel.open{display:flex;}
.org-col{flex:1;overflow-y:auto;max-height:200px;border-right:1px solid var(--border);}
.org-col:last-child{border-right:none;}
.org-col__title{padding:6px 12px;font-size:var(--font-sm);color:var(--text-placeholder);background:#FAFAFA;border-bottom:1px solid var(--border);font-weight:500;}
.org-item{padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;}
.org-item:hover{background:#F0F8FF;color:var(--primary);}
.org-item.active{background:var(--primary-light);color:var(--primary);font-weight:500;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
@@ -151,16 +162,24 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<!-- [交互] 筛选栏:单位→部门级联 + 站点名称 -->
<div class="filter-bar">
<div class="filter-item">
<label>所属单位</label>
<select class="filter-select" id="filterUnit" onchange="onFilterUnit()">
<option value="">全部单位</option>
</select>
</div>
<div class="filter-item">
<label>所属部门</label>
<select class="filter-select" id="filterDepart" onchange="renderTable()" style="min-width:130px;">
<option value="">全部部门</option>
</select>
<label>所属单位部门</label>
<div class="org-wrap" id="orgWrap">
<div class="org-trigger" id="orgTrigger" onclick="toggleOrg()">全部单位/部门</div>
<div class="org-panel" id="orgPanel">
<div class="org-col" id="unitCol">
<div class="org-col__title">单位</div>
<div class="org-item active" onclick="selectUnit(this,'')">全部单位/部门</div>
<div class="org-item" onclick="selectUnit(this,'采油一厂')">采油一厂</div>
<div class="org-item" onclick="selectUnit(this,'采油二厂')">采油二厂</div>
<div class="org-item" onclick="selectUnit(this,'第四采油厂')">第四采油厂</div>
<div class="org-item" onclick="selectUnit(this,'采气一厂')">采气一厂</div>
</div>
<div class="org-col" id="deptCol">
<div class="org-col__title">部门</div>
<div class="org-item active" onclick="selectDept(this,'')">全部部门</div>
</div>
</div>
</div>
</div>
<div class="filter-item">
<label>站点名称</label>
@@ -186,7 +205,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<table>
<thead>
<tr>
<th style="width:56px;">序号</th><th>所属单位</th><th>所属部门</th><th style="min-width:140px;">站点名称</th>
<th style="width:56px;">序号</th><th style="min-width:140px;">站点名称</th><th>所属单位</th><th>所属部门</th>
<th style="min-width:220px;">详细地址</th><th>负责人</th><th style="min-width:110px;">联系电话</th>
<th style="min-width:100px;">设立日期</th><th style="min-width:220px;">简介</th><th style="width:110px;">操作</th>
</tr>
@@ -198,7 +217,7 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
</main>
<!-- [交互] 新增/编辑站点:地点支持地图选点/手动输入双模式 -->
<!-- [交互] 新增/编辑站点:地点采用地图选点 + 详细地址 -->
<div class="modal-mask" id="editMask">
<div class="modal--wide">
<div class="modal__title" id="editTitle">新增站点</div>
@@ -218,41 +237,14 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 地点</span>
<div class="radio-line">
<label><input type="radio" name="geoType" value="0" checked onchange="switchGeoType()"> 地图选点</label>
<label><input type="radio" name="geoType" value="1" onchange="switchGeoType()"> 手动输入</label>
<div class="map-pick" id="mapPick" onclick="demoPickPoint()">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none"><path d="M13 3c4 0 7 3 7 6.8 0 5-7 13.2-7 13.2S6 14.8 6 9.8C6 6 9 3 13 3z" stroke="#1890FF" stroke-width="1.6" fill="none"/><circle cx="13" cy="9.8" r="2.4" stroke="#1890FF" stroke-width="1.6"/></svg>
<span id="mapPickText">点击在地图上拾取站点坐标</span>
</div>
</div>
<!-- 地图选点模式 -->
<div id="geoMapMode">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 地图选点</span>
<div class="map-pick" id="mapPick" onclick="demoPickPoint()">
<svg width="26" height="26" viewBox="0 0 26 26" fill="none"><path d="M13 3c4 0 7 3 7 6.8 0 5-7 13.2-7 13.2S6 14.8 6 9.8C6 6 9 3 13 3z" stroke="#1890FF" stroke-width="1.6" fill="none"/><circle cx="13" cy="9.8" r="2.4" stroke="#1890FF" stroke-width="1.6"/></svg>
<span id="mapPickText">点击在地图上拾取站点坐标</span>
</div>
<div class="form-item" style="margin-bottom:0;">
<span class="form-label">选址地址</span>
<input class="form-control" id="eMapAddr" readonly placeholder="选点后自动回填地址">
</div>
</div>
</div>
<!-- 手动输入模式 -->
<div id="geoInputMode" style="display:none;">
<div class="form-row">
<div class="form-item">
<span class="form-label"><span class="req">*</span> 经度</span>
<input class="form-control" id="eLng" placeholder="如:107.3412">
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 纬度</span>
<input class="form-control" id="eLat" placeholder="如:36.1147">
</div>
</div>
<div class="form-item">
<span class="form-label"><span class="req">*</span> 详细地址</span>
<input class="form-control" id="eAddr2" placeholder="站点详细地址">
</div>
<div class="form-item">
<span class="form-label">详细地址</span>
<input class="form-control" id="eAddr" placeholder="请补充详细地址,如:站点基地东侧">
</div>
<div class="form-row">
<div class="form-item">
@@ -305,37 +297,52 @@ var stations=[
{unit:'第四采油厂',depart:'西峰作业区',name:'西峰基地',addr:'甘肃省西峰市西峰基地食堂南侧',manager:'马文秀',mobile:'13991784320',setUp:'2017-09-01',desc:'第四采油厂机关基地,医疗点与救护车驻点合一。'},
{unit:'采气一厂',depart:'靖边气田',name:'靖边气田站点',addr:'陕西省靖边县靖边气田综合管理营地北侧',manager:'周铁祥',mobile:'13991923745',setUp:'2020-11-11',desc:'靖边气田处理厂站点,负责气田倒班人员床头呼叫值守。'}
];
function fillOptions(sel,opts,keepVal){
var v=keepVal||'';
sel.innerHTML='<option value="">'+(sel.dataset.tip||'全部')+'</option>'+opts.map(function(o){return '<option'+(o===v?' selected':'')+'>'+o+'</option>';}).join('');
}
function initFilters(){
var su=document.getElementById('filterUnit');su.dataset.tip='全部单位';
su.innerHTML='<option value="">全部单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
document.getElementById('filterDepart').dataset.tip='全部部门';
document.getElementById('eUnit').innerHTML='<option value="">请选择单位</option>'+Object.keys(unitDeparts).map(function(u){return '<option>'+u+'</option>';}).join('');
}
/* [交互] 筛选单位变更时级联刷新部门 */
function onFilterUnit(){
var u=document.getElementById('filterUnit').value;
var depSel=document.getElementById('filterDepart');
var deps=u?unitDeparts[u]:Object.keys(unitDeparts).reduce(function(a,u2){return a.concat(unitDeparts[u2]);},[]);
depSel.dataset.tip='全部部门';fillOptions(depSel,deps);
renderTable();
var selUnit='',selDept='';
function toggleOrg(){
document.getElementById('orgPanel').classList.toggle('open');
document.getElementById('orgTrigger').classList.toggle('open');
}
function selectUnit(elem,unitName){
document.querySelectorAll('#unitCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selUnit=unitName;
var html='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
(unitDeparts[unitName]||[]).forEach(function(d){
html+='<div class="org-item" onclick="selectDept(this,\''+d+'\')">'+d+'</div>';
});
document.getElementById('deptCol').innerHTML=html;
selDept='';
}
function selectDept(elem,deptName){
document.querySelectorAll('#deptCol .org-item').forEach(function(i){i.classList.remove('active');});
elem.classList.add('active');
selDept=deptName;
document.getElementById('orgTrigger').textContent=(selUnit||'全部单位')+' / '+(selDept||'全部部门');
}
function resetOrg(){
selUnit='';selDept='';
document.getElementById('orgTrigger').textContent='全部单位/部门';
document.getElementById('orgPanel').classList.remove('open');
document.getElementById('orgTrigger').classList.remove('open');
document.querySelectorAll('#unitCol .org-item').forEach(function(i,idx){i.classList.toggle('active',idx===0);});
document.getElementById('deptCol').innerHTML='<div class="org-col__title">部门</div><div class="org-item active" onclick="selectDept(this,\'\')">全部部门</div>';
}
function renderTable(){
var u=document.getElementById('filterUnit').value,d=document.getElementById('filterDepart').value,k=document.getElementById('filterName').value.trim();
var u=selUnit,d=selDept,k=document.getElementById('filterName').value.trim();
var list=stations.filter(function(s){return (!u||s.unit===u)&&(!d||s.depart===d)&&(!k||s.name.indexOf(k)>=0);});
document.getElementById('tbody').innerHTML=list.map(function(s){
var i=stations.indexOf(s);
return '<tr><td>'+(i+1)+'</td><td>'+s.unit+'</td><td>'+s.depart+'</td><td style="font-weight:500;">'+s.name+'</td>'
return '<tr><td>'+(i+1)+'</td><td style="font-weight:500;">'+s.name+'</td><td>'+s.unit+'</td><td>'+s.depart+'</td>'
+'<td>'+s.addr+'</td><td>'+s.manager+'</td><td>'+s.mobile+'</td><td>'+s.setUp+'</td>'
+'<td style="white-space:normal;min-width:220px;">'+s.desc+'</td>'
+'<td><button class="btn btn-link" onclick="openEdit('+i+')">编辑</button><button class="btn btn-link--danger" onclick="askDelete('+i+')">删除</button></td></tr>';
}).join('')||'<tr><td class="td-empty" colspan="10">没有符合条件的站点</td></tr>';
document.getElementById('totalTip').textContent='共 '+list.length+' 个站点';
}
function resetFilter(){['filterUnit','filterDepart'].forEach(function(id){document.getElementById(id).value='';});document.getElementById('filterName').value='';onFilterUnit();}
function resetFilter(){resetOrg();document.getElementById('filterName').value='';renderTable();}
/* [交互] 新增/编辑 */
var editIdx=-1;
function onEditUnit(){
@@ -343,19 +350,15 @@ function onEditUnit(){
var depSel=document.getElementById('eDepart');
depSel.innerHTML='<option value="">请选择部门</option>'+(u?unitDeparts[u]:[]).map(function(d){return '<option>'+d+'</option>';}).join('');
}
function geoTypeVal(){var r=document.querySelector('input[name="geoType"]:checked');return r?r.value:'0';}
function switchGeoType(){
var map=geoTypeVal()==='0';
document.getElementById('geoMapMode').style.display=map?'':'none';
document.getElementById('geoInputMode').style.display=map?'none':'';
}
/* [交互] 地图选点演示:点击拾取坐标并回填地址 */
/* [交互] 地图选点演示:点击拾取坐标并回填详细地址 */
var pickedPoint=null;
function demoPickPoint(){
pickedPoint={lng:'107.3412',lat:'36.1147'};
document.getElementById('mapPickText').innerHTML='<span class="picked">已选坐标:107.3412, 36.1147(点击可重选)</span>';
var addr=document.getElementById('eName').value.trim();
document.getElementById('eMapAddr').value='已拾取:'+(addr||'站点位置')+'(经度107.3412 纬度36.1147)';
if(!document.getElementById('eAddr').value){
var nm=document.getElementById('eName').value.trim();
document.getElementById('eAddr').value='陕西省延安市吴起县'+(nm||'站点')+'基地';
}
showToast('地图选点完成');
}
function openEdit(i){
@@ -363,21 +366,19 @@ function openEdit(i){
document.getElementById('editTitle').textContent=i<0?'新增站点':'编辑站点';
onEditUnit();
pickedPoint=null;document.getElementById('mapPickText').textContent='点击在地图上拾取站点坐标';
document.getElementById('eMapAddr').value='';
document.getElementById('eAddr').value='';
if(i>=0){
var s=stations[i];
document.getElementById('eUnit').value=s.unit;onEditUnit();
document.getElementById('eDepart').innerHTML='<option>请选择部门</option>'+unitDeparts[s.unit].map(function(d){return '<option'+(d===s.depart?' selected':'')+'>'+d+'</option>';}).join('');
document.getElementById('eName').value=s.name;
document.querySelector('input[name="geoType"][value="0"]').checked=true;switchGeoType();
document.getElementById('eMapAddr').value='已拾取:'+s.addr;
document.getElementById('eAddr').value=s.addr;
document.getElementById('mapPickText').innerHTML='<span class="picked">已选坐标:107.3412, 36.1147(点击可重选)</span>';
document.getElementById('eManager').value=s.manager;document.getElementById('eMobile').value=s.mobile;
document.getElementById('eSetUp').value=s.setUp;document.getElementById('eDesc').value=s.desc;
}else{
['eName','eManager','eMobile','eSetUp','eDesc','eLng','eLat','eAddr2'].forEach(function(id){document.getElementById(id).value='';});
['eName','eManager','eMobile','eSetUp','eDesc','eAddr'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('eUnit').value='';onEditUnit();
document.querySelector('input[name="geoType"][value="0"]').checked=true;switchGeoType();
}
document.getElementById('editMask').classList.add('open');
}
@@ -385,15 +386,8 @@ function closeEdit(){document.getElementById('editMask').classList.remove('open'
function saveEdit(){
var u=document.getElementById('eUnit').value,d=document.getElementById('eDepart').value,n=document.getElementById('eName').value.trim();
if(!u||!d||!n){showToast('请完整填写带 * 的必填项');return;}
var addr;
if(geoTypeVal()==='0'){
if(!document.getElementById('eMapAddr').value){showToast('请先在地图上拾取站点坐标');return;}
addr=document.getElementById('eMapAddr').value;
}else{
var lng=document.getElementById('eLng').value.trim(),lat=document.getElementById('eLat').value.trim(),a2=document.getElementById('eAddr2').value.trim();
if(!lng||!lat||!a2){showToast('手动输入模式下经度/纬度/详细地址为必填');return;}
addr=a2;
}
var addr=document.getElementById('eAddr').value.trim();
if(!addr){showToast('请先在地图上拾取站点坐标并补充详细地址');return;}
var dup=stations.some(function(s,i){return i!==editIdx&&s.unit===u&&s.name===n;});
if(dup){showToast('该单位下已存在同名站点');return;}
var obj={unit:u,depart:d,name:n,addr:addr,
@@ -419,7 +413,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);
}
initFilters();onFilterUnit();
initFilters();resetOrg();renderTable();
</script>
</body>
</html>
@@ -96,6 +96,15 @@ tbody tr:hover td:last-child{background:#F8FBFF;}
.form-control:focus{border-color:var(--primary);}
.form-control:disabled{background:#F5F5F5;color:var(--text-secondary);cursor:not-allowed;}
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;}
.search-select{position:relative;}
.search-select__list{position:absolute;top:100%;left:0;right:0;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);max-height:260px;overflow-y:auto;z-index:30;display:none;margin-top:2px;}
.search-select__list.open{display:block;}
.search-select__item{padding:9px 12px;font-size:var(--font-sm);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:10px;border-bottom:1px solid #F5F5F5;}
.search-select__item:last-child{border-bottom:none;}
.search-select__item:hover,.search-select__item.active{background:var(--primary-light);}
.search-select__item .plate{font-weight:600;color:var(--text-primary);}
.search-select__item .point{color:var(--text-secondary);font-size:var(--font-xs);}
.search-select__empty{padding:12px;color:var(--text-placeholder);font-size:var(--font-sm);text-align:center;}
.radio-group{display:flex;gap:20px;height:36px;align-items:center;flex-wrap:wrap;}
.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;}
@@ -246,9 +255,12 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<div class="form-section">救护车归属</div>
<div class="form-grid">
<div class="form-item"><span class="form-label"><span class="req">*</span> 所属医疗点</span>
<select class="form-control" id="fPoint" onchange="onFormPoint()"><option value="">请选择医疗点</option><option>樊学作业区医疗点</option><option>安塞采油队长住点医疗室</option><option>姬塬作业区医疗点</option><option>姬圪台作业区医疗点</option><option>桐园作业区医疗点</option><option>西峰基地医疗点</option><option>靖边气田医疗点</option><option>乌审旗采气区医疗点</option></select></div>
<input class="form-control" id="fPoint" disabled placeholder="默认医疗点"></div>
<div class="form-item"><span class="form-label"><span class="req">*</span> 救护车</span>
<select class="form-control" id="fAmb" onchange="onFormAmb()"><option value="">请先选择医疗点</option></select></div>
<div class="search-select">
<input class="form-control" id="fAmb" placeholder="请输入车牌号搜索" autocomplete="off" oninput="onAmbSearch()" onfocus="onAmbSearch()">
<div class="search-select__list" id="fAmbList"></div>
</div></div>
<div class="form-item"><span class="form-label">救护类型</span>
<input class="form-control" id="fAmbulanceType" disabled placeholder="选择救护车后自动带出"></div>
</div>
@@ -263,11 +275,10 @@ select.form-control{background:#fff url("data:image/svg+xml,%3Csvg xmlns='http:/
<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>
<input class="form-control" id="fDriver" disabled placeholder="选择救护车后自动带出"></div>
<div class="form-item"><span class="form-label">司机电话</span>
<input class="form-control" id="fTel" disabled placeholder="选择司机后自动带出"></div>
<div class="form-item form-item--full"><span class="form-label"><span class="req">*</span> 随车医护</span>
@@ -366,14 +377,6 @@ var ambulances=[
{plate:'CQ·0917E',point:'安塞采油队长住点医疗室',type:'转运型',model:'江淮瑞风转运型救护车',driver:'苏文斌',tel:'15598486703'},
{plate:'CQ·2201F',point:'乌审旗采气区医疗点',type:'急救型',model:'红旗轻型监护型救护车',driver:'曹雪芹',tel:'18642802940'}
];
/* 救护车司机列表:从救护车台账驾驶员去重提取 */
var drivers=[];
(function(){
var seen={};
ambulances.forEach(function(a){
if(!seen[a.driver]){seen[a.driver]=true;drivers.push({name:a.driver,tel:a.tel});}
});
})();
/* 医护人员池:与「基础配置·医护人员管理」一致 */
var medStaff=[
{id:1,name:'韩东学',job:'主治医师',post:'医生',point:'樊学作业区医疗点'},
@@ -406,7 +409,6 @@ var attendances=[
];
var nextId=13,editingId=null,pendingDelId=null;
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;
@@ -448,15 +450,15 @@ function renderTable(){
html+='<tr>'
+'<td>'+(start+i+1)+'</td>'
+'<td>'+r.date+'</td>'
+'<td style="text-align:left;">'+r.point+'</td>'
+'<td>'+r.point+'</td>'
+'<td style="font-weight:600;">'+r.plate+'</td>'
+'<td><span class="badge '+typeBadge[r.type]+'">'+r.type+'</span></td>'
+'<td>'+r.type+'</td>'
+'<td><span class="badge '+dispatchBadge[r.dispatch]+'">'+r.dispatch+'</span></td>'
+'<td>'+r.driver+'</td>'
+'<td>'+r.tel+'</td>'
+'<td style="text-align:left;">'+(r.staff.join('、'))+'</td>'
+'<td style="text-align:left;max-width:180px;overflow:hidden;text-overflow:ellipsis;">'+r.out+'</td>'
+'<td style="text-align:left;max-width:180px;overflow:hidden;text-overflow:ellipsis;">'+r.to+'</td>'
+'<td>'+(r.staff.join('、'))+'</td>'
+'<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;">'+r.out+'</td>'
+'<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;">'+r.to+'</td>'
+'<td class="td-actions">'
+'<button class="btn btn-link" onclick="openDetail('+r.id+')">详情</button>'
+'<button class="btn btn-link" onclick="openForm('+r.id+')">编辑</button>'
@@ -482,36 +484,27 @@ function turnPage(d){
}
/* ===== 新增/编辑抽屉 ===== */
function fillAmb(point,keep){
var sel=document.getElementById('fAmb');
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;
if(keep&&a.plate===keep)o.selected=true;
sel.appendChild(o);
});
var DEFAULT_POINT='樊学作业区医疗点';
function onAmbSearch(){
var kw=document.getElementById('fAmb').value.trim().toUpperCase();
var list=ambulances.filter(function(a){return !kw||a.plate.toUpperCase().indexOf(kw)!==-1;});
var box=document.getElementById('fAmbList');
box.innerHTML=list.length?list.map(function(a){
return '<div class="search-select__item" onmousedown="selectAmb(\''+a.plate+'\')">'
+'<span class="plate">'+a.plate+'</span>'
+'<span class="point">'+a.point+' · '+a.type+'</span></div>';
}).join(''):'<div class="search-select__empty">无匹配救护车</div>';
box.classList.add('open');
}
function fillDriverSel(keep){
var sel=document.getElementById('fDriver');
sel.innerHTML='<option value="">请选择司机</option>';
drivers.forEach(function(d){
var o=document.createElement('option');o.textContent=d.name;
if(keep&&d.name===keep)o.selected=true;
sel.appendChild(o);
});
}
function onFormPoint(){fillAmb(document.getElementById('fPoint').value);clearAmbType();}
function onFormAmb(){
var plate=document.getElementById('fAmb').value;
function selectAmb(plate){
var a=ambulances.find(function(x){return x.plate===plate;});
document.getElementById('fAmb').value=plate;
document.getElementById('fAmbulanceType').value=a?a.type:'';
document.getElementById('fDriver').value=a?a.driver:'';
document.getElementById('fTel').value=a?a.tel:'';
document.getElementById('fAmbList').classList.remove('open');
}
function clearAmbType(){document.getElementById('fAmbulanceType').value='';}
function onFormDriver(){
var name=document.getElementById('fDriver').value;
var d=drivers.find(function(x){return x.name===name;});
document.getElementById('fTel').value=d?d.tel:'';
}
function closeAmbList(){document.getElementById('fAmbList').classList.remove('open');}
var selectedStaffIds=[];
function setRadio(name,val){
document.querySelectorAll('input[name="'+name+'"]').forEach(function(r){r.checked=(r.value===val);});
@@ -535,23 +528,24 @@ function openForm(id){
editingId=id||null;
document.getElementById('formTitle').textContent=id?'编辑出勤记录':'新增出勤';
var r=id?attendances.find(function(x){return x.id===id;}):null;
document.getElementById('fPoint').value=r?r.point:'';
fillAmb(r?r.point:'',r?r.plate:'');
if(!r)document.getElementById('fAmb').innerHTML='<option value="">请先选择医疗点</option>';
fillDriverSel(r?r.driver:'');
document.getElementById('fDate').value=r?r.date:'';
document.getElementById('fPoint').value=r?r.point:DEFAULT_POINT;
document.getElementById('fAmb').value=r?r.plate:'';
document.getElementById('fAmbulanceType').value=r?r.type:'';
document.getElementById('fDriver').value=r?r.driver:'';
document.getElementById('fTel').value=r?r.tel:'';
document.getElementById('fDate').value=r?r.date:todayStr();
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:'';
document.getElementById('fTo').value=r?r.to:'';
if(r){onFormAmb();onFormDriver();}else{clearAmbType();document.getElementById('fTel').value='';}
closeAmbList();
document.getElementById('formMask').classList.add('open');
document.getElementById('formDrawer').classList.add('open');
}
function saveForm(){
var point=document.getElementById('fPoint').value;
var plate=document.getElementById('fAmb').value;
var plate=document.getElementById('fAmb').value.trim();
var date=document.getElementById('fDate').value;
var dispatch=getRadio('fDispatch');
var driver=document.getElementById('fDriver').value;
@@ -565,12 +559,11 @@ function saveForm(){
if(!out){showToast('请填写出勤地点');return;}
if(!to){showToast('请填写到达地点');return;}
var a=ambulances.find(function(x){return x.plate===plate;});
var d=drivers.find(function(x){return x.name===driver;});
var staffNames=selectedStaffIds.map(function(id){return medStaff.find(function(m){return m.id===id;}).name;});
var obj={
id:editingId||nextId++,
date:date,point:point,plate:plate,type:a.type,dispatch:dispatch,
driver:driver,tel:d.tel,staff:staffNames,out:out,to:to
driver:driver,tel:a.tel,staff:staffNames,out:out,to:to
};
if(editingId){
attendances.splice(attendances.findIndex(function(x){return x.id===editingId;}),1,obj);
@@ -588,7 +581,7 @@ function openDetail(id){
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>';}
document.getElementById('detailBody').innerHTML=
'<div class="form-section" style="margin-top:0;">救护车信息</div>'
+'<div class="detail-grid">'+d('所属医疗点',r.point)+d('救护车',r.plate)+d('救护类型','<span class="badge '+typeBadge[r.type]+'">'+r.type+'</span>')+'</div>'
+'<div class="detail-grid">'+d('所属医疗点',r.point)+d('救护车',r.plate)+d('救护类型',r.type)+'</div>'
+'<div class="form-section">出勤信息</div>'
+'<div class="detail-grid">'
+d('出勤日期',r.date)
@@ -606,7 +599,8 @@ function openDetail(id){
/* ===== 随车医护选择弹窗 ===== */
function renderStaffList(){
var kw=document.getElementById('staffKeyword').value.trim();
var list=medStaff.filter(function(m){return !kw||m.name.indexOf(kw)!==-1;});
var point=document.getElementById('fPoint').value;
var list=medStaff.filter(function(m){return (!point||m.point===point)&&(!kw||m.name.indexOf(kw)!==-1);});
var body=document.getElementById('staffListBody');
if(!list.length){body.innerHTML='<tr><td colspan="5" class="td-empty">暂无匹配人员</td></tr>';return;}
body.innerHTML=list.map(function(m){
@@ -652,6 +646,13 @@ function closeDrawer(k){
document.getElementById(k+'Drawer').classList.remove('open');
}
function closeModal(id){document.getElementById(id).classList.remove('open');}
function todayStr(){
var d=new Date();
return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');
}
document.addEventListener('click',function(e){
if(!e.target.closest('.search-select'))closeAmbList();
});
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);
@@ -30,7 +30,7 @@ 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);}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.main{margin-left:240px;margin-top:56px;padding:16px 20px;min-height:calc(100vh - 56px);}
.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;}
@@ -39,28 +39,29 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
/* [交互] 所属单位→所属医疗点级联;清空单位回到全局统计(与老系统 allowClear 一致) */
.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-bar{background:#fff;padding:12px 20px;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;}
.filter-select{height:36px;min-width:140px;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;}
.filter-select:disabled{background-color:#F5F5F5;color:var(--text-placeholder);cursor:not-allowed;}
/* 卡片:基本数据(32%)+出勤次数(66%),布局与老系统 number-container 一致 */
.num-row{display:flex;gap:16px;margin-top:16px;align-items:stretch;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:16px 20px;}
.stat-card__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin-bottom:12px;font-size:var(--font-base);}
.stat-card__grid{display:flex;justify-content:space-around;background:#F8F8FA;border-radius:10px;padding:14px 8px;}
.num-row{display:flex;gap:16px;margin-top:12px;align-items:stretch;}
.stat-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:12px 16px;}
.stat-card__title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin-bottom:8px;}
.stat-card__grid{display:flex;justify-content:space-around;background:#F8F8FA;border-radius:10px;padding:10px 8px;}
.stat-cell{text-align:center;}
.stat-cell__val{font-size:22px;font-weight:600;color:var(--text-primary);}
.stat-cell__val{font-size:20px;font-weight:600;color:var(--text-primary);}
.stat-cell__val small{font-size:var(--font-xs);font-weight:400;color:var(--text-placeholder);margin-left:2px;}
.stat-cell__name{font-size:var(--font-sm);color:var(--text-secondary);margin-top:4px;}
.stat-cell__name{font-size:var(--font-sm);color:var(--text-secondary);margin-top:2px;}
.num-card--l{width:32%;}.num-card--r{width:68%;}
.chart-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:16px;}
.chart-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 22px;}
.chart-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-top:12px;}
.chart-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:14px 18px;}
.chart-card--full{grid-column:1/-1;}
.chart-card__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);display:flex;align-items:center;justify-content:space-between;}
.chart-card__title .lg{display:flex;gap:12px;font-size:var(--font-xs);font-weight:400;color:var(--text-secondary);}
.chart-card__title .lg i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:4px;vertical-align:-1px;}
.chart-card svg{width:100%;height:auto;display:block;margin-top:10px;}
.chart-card--full svg{max-height:230px;}
.wk-switch{display:inline-flex;height:26px;line-height:26px;text-align:center;font-weight:400;cursor:pointer;font-size:var(--font-sm);}
.wk-switch span{width:50px;border:1px solid var(--primary);}
.wk-switch span:first-child{border-radius:var(--radius-sm) 0 0 var(--radius-sm);}
@@ -155,13 +156,13 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<div class="chart-grid">
<div class="chart-card">
<div class="chart-card__title">救护车类型占比<span style="font-weight:400;color:var(--text-placeholder);font-size:var(--font-xs);">(辆)</span></div>
<svg id="pieType" viewBox="0 0 560 240"></svg>
<svg id="pieType" viewBox="0 0 560 220"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">出勤次数占比
<span class="wk-switch" id="wkDisp"><span class="on" onclick="setWeek('wkDisp',true)">本周</span><span class="off" onclick="setWeek('wkDisp',false)">累计</span></span>
</div>
<svg id="pieDisp" viewBox="0 0 560 260"></svg>
<svg id="pieDisp" viewBox="0 0 560 230"></svg>
</div>
<div class="chart-card">
<div class="chart-card__title">医护人员出勤次数排名TOP5
@@ -182,7 +183,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<div class="chart-card__title">本周出勤次数变化趋势
<span class="lg" id="trendLg"></span>
</div>
<svg id="chartTrend" viewBox="0 0 1120 280"></svg>
<svg id="chartTrend" viewBox="0 0 1120 200"></svg>
</div>
</div>
</main>
@@ -191,38 +192,38 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<script>
/* 数据源与「救护车配置」页勾稽:6 辆车=4 急救型+2 转运型,辐射人数取车辆覆盖人数;
出勤类口径:急救转运/平诊转运/日常巡诊/应急戒备/其他出勤(dispatch_type 字典) */
var TYPES=['jz','pz','rc','yj','qt'];
var TYPE_NAME={jz:'急救转运',pz:'平诊转运',rc:'日常巡诊',yj:'应急戒备',qt:'其他出勤'};
var DISC=['#5087ec','#68bbc4','#58a55c','#f2bd42','#ee752f'];
出勤类口径:急救转运/平诊转运/日常巡诊/应急戒备/床头呼叫/其他出勤(dispatch_type 字典) */
var TYPES=['jz','pz','rc','yj','ct','qt'];
var TYPE_NAME={jz:'急救转运',pz:'平诊转运',rc:'日常巡诊',yj:'应急戒备',ct:'床头呼叫',qt:'其他出勤'};
var DISC=['#5087ec','#68bbc4','#58a55c','#f2bd42','#9966cc','#ee752f'];
var pts=[
{unit:'采油一厂',name:'樊学作业区医疗点',total:1,jj:1,zy:0,rad:860,
w:{jz:3,pz:2,rc:5,yj:1,qt:1},t:{jz:42,pz:28,rc:76,yj:15,qt:9},
staff:[{name:'李桂芳',w:{jz:2,pz:1,rc:3,yj:1,qt:1},t:{jz:24,pz:16,rc:41,yj:8,qt:5}},
{name:'石维国',w:{jz:1,pz:1,rc:2,yj:0,qt:0},t:{jz:18,pz:12,rc:35,yj:7,qt:4}}]},
w:{jz:3,pz:2,rc:5,yj:1,qt:1,ct:0},t:{jz:42,pz:28,rc:76,yj:15,qt:9,ct:0},
staff:[{name:'李桂芳',w:{jz:2,pz:1,rc:3,yj:1,qt:1,ct:0},t:{jz:24,pz:16,rc:41,yj:8,qt:5,ct:0}},
{name:'石维国',w:{jz:1,pz:1,rc:2,yj:0,qt:0,ct:0},t:{jz:18,pz:12,rc:35,yj:7,qt:4,ct:0}}]},
{unit:'采油一厂',name:'安塞采油队长住点医疗室',total:1,jj:0,zy:1,rad:540,
w:{jz:0,pz:3,rc:2,yj:0,qt:1},t:{jz:9,pz:41,rc:33,yj:6,qt:10},
staff:[{name:'周娟',w:{jz:0,pz:3,rc:2,yj:0,qt:1},t:{jz:9,pz:41,rc:33,yj:6,qt:10}}]},
w:{jz:0,pz:3,rc:2,yj:0,qt:1,ct:0},t:{jz:9,pz:41,rc:33,yj:6,qt:10,ct:0},
staff:[{name:'周娟',w:{jz:0,pz:3,rc:2,yj:0,qt:1,ct:0},t:{jz:9,pz:41,rc:33,yj:6,qt:10,ct:0}}]},
{unit:'采油一厂',name:'姬塬作业区医疗点',total:0,jj:0,zy:0,rad:0,
w:{jz:0,pz:0,rc:0,yj:0,qt:0},t:{jz:0,pz:0,rc:0,yj:0,qt:0},staff:[]},
w:{jz:0,pz:0,rc:0,yj:0,qt:0,ct:0},t:{jz:0,pz:0,rc:0,yj:0,qt:0,ct:0},staff:[]},
{unit:'采油二厂',name:'姬圪台作业区医疗点',total:1,jj:1,zy:0,rad:1240,
w:{jz:2,pz:1,rc:4,yj:2,qt:0},t:{jz:35,pz:22,rc:61,yj:18,qt:7},
staff:[{name:'曲志明',w:{jz:1,pz:1,rc:2,yj:1,qt:0},t:{jz:19,pz:13,rc:34,yj:10,qt:4}},
{name:'张秀兰',w:{jz:1,pz:0,rc:2,yj:1,qt:0},t:{jz:16,pz:9,rc:27,yj:8,qt:3}}]},
w:{jz:2,pz:1,rc:4,yj:2,qt:0,ct:0},t:{jz:35,pz:22,rc:61,yj:18,qt:7,ct:0},
staff:[{name:'曲志明',w:{jz:1,pz:1,rc:2,yj:1,qt:0,ct:0},t:{jz:19,pz:13,rc:34,yj:10,qt:4,ct:0}},
{name:'张秀兰',w:{jz:1,pz:0,rc:2,yj:1,qt:0,ct:0},t:{jz:16,pz:9,rc:27,yj:8,qt:3,ct:0}}]},
{unit:'采油二厂',name:'桐园作业区医疗点',total:0,jj:0,zy:0,rad:0,
w:{jz:0,pz:0,rc:0,yj:0,qt:0},t:{jz:0,pz:0,rc:0,yj:0,qt:0},staff:[]},
w:{jz:0,pz:0,rc:0,yj:0,qt:0,ct:0},t:{jz:0,pz:0,rc:0,yj:0,qt:0,ct:0},staff:[]},
{unit:'第四采油厂',name:'西峰基地医疗点',total:1,jj:0,zy:1,rad:2100,
w:{jz:1,pz:6,rc:7,yj:3,qt:2},t:{jz:18,pz:88,rc:97,yj:26,qt:14},
staff:[{name:'高建军',w:{jz:1,pz:3,rc:3,yj:2,qt:1},t:{jz:10,pz:45,rc:49,yj:14,qt:8}},
{name:'闫婷',w:{jz:0,pz:3,rc:4,yj:1,qt:1},t:{jz:8,pz:43,rc:48,yj:12,qt:6}}]},
w:{jz:1,pz:6,rc:7,yj:3,qt:2,ct:0},t:{jz:18,pz:88,rc:97,yj:26,qt:14,ct:0},
staff:[{name:'高建军',w:{jz:1,pz:3,rc:3,yj:2,qt:1,ct:0},t:{jz:10,pz:45,rc:49,yj:14,qt:8,ct:0}},
{name:'闫婷',w:{jz:0,pz:3,rc:4,yj:1,qt:1,ct:0},t:{jz:8,pz:43,rc:48,yj:12,qt:6,ct:0}}]},
{unit:'采气一厂',name:'靖边气田医疗点',total:1,jj:1,zy:0,rad:760,
w:{jz:2,pz:1,rc:3,yj:1,qt:1},t:{jz:27,pz:19,rc:48,yj:12,qt:6},
staff:[{name:'刘玉梅',w:{jz:2,pz:1,rc:2,yj:1,qt:0},t:{jz:16,pz:11,rc:29,yj:7,qt:3}},
{name:'呼建国',w:{jz:0,pz:0,rc:1,yj:0,qt:1},t:{jz:11,pz:8,rc:19,yj:5,qt:3}}]},
w:{jz:2,pz:1,rc:3,yj:1,qt:1,ct:0},t:{jz:27,pz:19,rc:48,yj:12,qt:6,ct:0},
staff:[{name:'刘玉梅',w:{jz:2,pz:1,rc:2,yj:1,qt:0,ct:0},t:{jz:16,pz:11,rc:29,yj:7,qt:3,ct:0}},
{name:'呼建国',w:{jz:0,pz:0,rc:1,yj:0,qt:1,ct:0},t:{jz:11,pz:8,rc:19,yj:5,qt:3,ct:0}}]},
{unit:'采气一厂',name:'乌审旗采气区医疗点',total:1,jj:1,zy:0,rad:680,
w:{jz:1,pz:1,rc:3,yj:2,qt:0},t:{jz:22,pz:14,rc:40,yj:16,qt:5},
staff:[{name:'徐立成',w:{jz:1,pz:0,rc:2,yj:1,qt:0},t:{jz:13,pz:8,rc:24,yj:10,qt:3}},
{name:'马丽华',w:{jz:0,pz:1,rc:1,yj:1,qt:0},t:{jz:9,pz:6,rc:16,yj:6,qt:2}}]}
w:{jz:1,pz:1,rc:3,yj:2,qt:0,ct:0},t:{jz:22,pz:14,rc:40,yj:16,qt:5,ct:0},
staff:[{name:'徐立成',w:{jz:1,pz:0,rc:2,yj:1,qt:0,ct:0},t:{jz:13,pz:8,rc:24,yj:10,qt:3,ct:0}},
{name:'马丽华',w:{jz:0,pz:1,rc:1,yj:1,qt:0,ct:0},t:{jz:9,pz:6,rc:16,yj:6,qt:2,ct:0}}]}
];
var weekState={wkDisp:true,wkTop:true};
function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
@@ -307,7 +308,7 @@ function drawTop5(id,data,color,unit){
svg.innerHTML=html;
svg.setAttribute('viewBox','0 0 '+W+' '+h);
}
/* ===== 本周逐日堆积柱状(5系列 stack,照搬老系统 barCharts) ===== */
/* ===== 本周逐日堆积柱状(6系列 stack,照搬老系统 barCharts) ===== */
var DAYS=['周一','周二','周三','周四','周五','周六','周日'];
var WT=[2,1,2,1,3,2,1];
function splitWeek(v){
@@ -323,7 +324,7 @@ function renderTrend(){
return {name:TYPE_NAME[k],color:DISC[i],data:days};
});
document.getElementById('trendLg').innerHTML=series.map(function(s){return '<span><i style="background:'+s.color+';"></i>'+s.name+'</span>';}).join('');
var svg=document.getElementById('chartTrend'),W=1120,H=280,left=44,bottom=34,top=12,right=16;
var svg=document.getElementById('chartTrend'),W=1120,H=200,left=44,bottom=34,top=12,right=16;
var max=0;DAYS.forEach(function(d,i){var s=0;series.forEach(function(se){s+=se.data[i];});if(s>max)max=s;});
max=max||1;
var step=(W-left-right)/7,bw=Math.min(56,step*0.5),html='';
@@ -375,7 +376,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);
}
/* 初始化:单位下拉 + 出勤类型下拉(dispatch_type 字典类) */
/* 初始化:单位下拉 + 出勤类型下拉(dispatch_type 字典类) */
(function(){
var us=[];pts.forEach(function(p){if(us.indexOf(p.unit)<0)us.push(p.unit);});
document.getElementById('selUnit').innerHTML='<option value="">全部单位</option>'+us.map(function(u){return '<option>'+esc(u)+'</option>';}).join('');
@@ -39,6 +39,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;}
.action-bar{display:flex;justify-content:flex-start;gap:8px;margin-top:16px;}
.date-switch{display:inline-flex;height:32px;line-height:32px;border:1px solid var(--primary);border-radius:var(--radius-sm);overflow:hidden;font-size:var(--font-sm);}
.date-switch span{padding:0 16px;cursor:pointer;color:var(--primary);background:#fff;border-right:1px solid var(--primary);white-space:nowrap;}
.date-switch span:last-child{border-right:none;}
@@ -62,7 +63,7 @@ thead tr.grp th{background:#F0F5FF;color:var(--text-primary);border-right:1px so
tbody td{padding:12px 14px;border-bottom:1px solid #F0F0F0;text-align:center;white-space:nowrap;font-size:var(--font-sm);}
tbody tr:hover td{background:#F8FBFF;}
.td-empty{text-align:center;padding:48px 0;color:var(--text-placeholder);}
.num-cur{color:var(--primary);font-weight:600;}
.num-cur{color:var(--text-primary);font-weight:600;}
.num-total{color:var(--text-secondary);}
.badge{display:inline-block;padding:1px 8px;border-radius:10px;font-size:var(--font-xs);line-height:18px;}
.badge--danger{background:#FFF1F0;color:#FF4D4F;border:1px solid #FFA39E;}
@@ -130,7 +131,7 @@ tbody tr:hover td{background:#F8FBFF;}
<!-- 主内容 -->
<main class="main">
<!-- 时间切换 + 导出操作(照搬老系统 DateButtonSelector + 导出按钮) -->
<!-- 时间切换(照搬老系统 DateButtonSelector) -->
<div class="toolbar">
<div class="date-switch" id="dateSwitch">
<span class="on" data-type="week" onclick="switchDateType('week')">本周</span>
@@ -138,10 +139,6 @@ tbody tr:hover td{background:#F8FBFF;}
<span data-type="year" onclick="switchDateType('year')">本年</span>
<span data-type="picker" onclick="switchDateType('picker')">自定义</span>
</div>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" onclick="doExport()"><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="openExportLog()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>查看导出记录</button>
</div>
</div>
<!-- 筛选栏:单位→医疗点级联 + 救护类型 + 时间 -->
@@ -178,7 +175,11 @@ tbody tr:hover td{background:#F8FBFF;}
</div>
</div>
<!-- 分组报表:基础信息 + 类出勤次数(本周/累计) -->
<!-- 分组报表:基础信息 + 类出勤次数(本周/累计) -->
<div class="action-bar">
<button class="btn btn-primary" onclick="doExport()"><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="openExportLog()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>查看导出记录</button>
</div>
<div class="table-card">
<div class="table-card__content">
<table>
@@ -193,6 +194,7 @@ tbody tr:hover td{background:#F8FBFF;}
<th colspan="2">平诊转运</th>
<th colspan="2">日常巡诊</th>
<th colspan="2">应急戒备</th>
<th colspan="2">床头呼叫</th>
<th colspan="2">其他</th>
</tr>
<tr>
@@ -206,6 +208,8 @@ tbody tr:hover td{background:#F8FBFF;}
<th>累计出勤次数</th>
<th><span class="period-text">本周</span>出勤次数</th>
<th>累计出勤次数</th>
<th><span class="period-text">本周</span>出勤次数</th>
<th>累计出勤次数</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
@@ -233,23 +237,23 @@ tbody tr:hover td{background:#F8FBFF;}
<div class="toast" id="toast"></div>
<script>
/* 数据源:6 辆救护车(与「救护车配置」页车牌/车型/救护类型勾稽,与「救护车统计-图表」类出勤口径同源);
出勤类:急诊转运/平诊转运/日常巡诊/应急戒备/其他,各分 本周/累计 出勤次数 */
/* 数据源:6 辆救护车(与「救护车配置」页车牌/车型/救护类型勾稽,与「救护车统计-图表」类出勤口径同源);
出勤类:急诊转运/平诊转运/日常巡诊/应急戒备/床头呼叫/其他,各分 本周/累计 出勤次数 */
var cars=[
{unit:'采油一厂',point:'樊学作业区医疗点',type:'急救型',model:'江铃轻型监护型救护车',plate:'CQ·0712A',
w:{jz:3,pz:2,rc:5,yj:1,qt:1},t:{jz:42,pz:28,rc:76,yj:15,qt:9}},
w:{jz:3,pz:2,rc:5,yj:1,qt:1,ct:0},t:{jz:42,pz:28,rc:76,yj:15,qt:9,ct:0}},
{unit:'采油二厂',point:'姬圪台作业区医疗点',type:'急救型',model:'福田图雅诺监护型救护车',plate:'CQ·1366B',
w:{jz:2,pz:1,rc:4,yj:2,qt:0},t:{jz:35,pz:22,rc:61,yj:18,qt:7}},
w:{jz:2,pz:1,rc:4,yj:2,qt:0,ct:0},t:{jz:35,pz:22,rc:61,yj:18,qt:7,ct:0}},
{unit:'第四采油厂',point:'西峰基地医疗点',type:'转运型',model:'福特全顺转运型救护车',plate:'CQ·0208C',
w:{jz:1,pz:6,rc:7,yj:3,qt:2},t:{jz:18,pz:88,rc:97,yj:26,qt:14}},
w:{jz:1,pz:6,rc:7,yj:3,qt:2,ct:0},t:{jz:18,pz:88,rc:97,yj:26,qt:14,ct:0}},
{unit:'采气一厂',point:'靖边气田医疗点',type:'急救型',model:'东风轻型救护车',plate:'CQ·1533D',
w:{jz:2,pz:1,rc:3,yj:1,qt:1},t:{jz:27,pz:19,rc:48,yj:12,qt:6}},
w:{jz:2,pz:1,rc:3,yj:1,qt:1,ct:0},t:{jz:27,pz:19,rc:48,yj:12,qt:6,ct:0}},
{unit:'采油一厂',point:'安塞采油队长住点医疗室',type:'转运型',model:'江淮瑞风转运型救护车',plate:'CQ·0917E',
w:{jz:0,pz:3,rc:2,yj:0,qt:1},t:{jz:9,pz:41,rc:33,yj:6,qt:10}},
w:{jz:0,pz:3,rc:2,yj:0,qt:1,ct:0},t:{jz:9,pz:41,rc:33,yj:6,qt:10,ct:0}},
{unit:'采气一厂',point:'乌审旗采气区医疗点',type:'急救型',model:'红旗轻型监护型救护车',plate:'CQ·2201F',
w:{jz:1,pz:1,rc:3,yj:2,qt:0},t:{jz:22,pz:14,rc:40,yj:16,qt:5}}
w:{jz:1,pz:1,rc:3,yj:2,qt:0,ct:0},t:{jz:22,pz:14,rc:40,yj:16,qt:5,ct:0}}
];
var TYPES=['jz','pz','rc','yj','qt'];
var TYPES=['jz','pz','rc','yj','ct','qt'];
var dateType='week';
var PERIOD_TEXT={week:'本周',month:'本月',year:'本年',picker:'时段内'};
function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;');}
@@ -38,9 +38,9 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
.sidebar-subgroup__body .sidebar-item{padding-left:40px;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 工具栏:时间切换 + 导出操作 */
/* 工具栏:时间切换 */
.toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;}
.action-bar{display:flex;justify-content:flex-end;margin-top:16px;}
.action-bar{display:flex;justify-content:flex-start;gap:8px;margin-top:16px;}
.date-switch{display:inline-flex;height:32px;line-height:32px;border:1px solid var(--primary);border-radius:var(--radius-sm);overflow:hidden;font-size:var(--font-sm);}
.date-switch span{padding:0 16px;cursor:pointer;color:var(--primary);background:#fff;border-right:1px solid var(--primary);white-space:nowrap;}
.date-switch span:last-child{border-right:none;}
@@ -135,7 +135,7 @@ th.sortable:hover{color:var(--primary);}
<!-- 主内容 -->
<main class="main">
<!-- 时间切换 + 导出操作(照搬老系统 DateButtonSelector + 导出按钮) -->
<!-- 时间切换(照搬老系统 DateButtonSelector) -->
<div class="toolbar">
<div class="date-switch" id="dateSwitch">
<span class="on" data-type="week" onclick="switchDateType('week')">本周</span>
@@ -143,10 +143,6 @@ th.sortable:hover{color:var(--primary);}
<span data-type="year" onclick="switchDateType('year')">本年</span>
<span data-type="picker" onclick="switchDateType('picker')">自定义</span>
</div>
<div style="display:flex;gap:8px;">
<button class="btn btn-primary" onclick="doExport()"><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="openExportLog()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>查看导出记录</button>
</div>
</div>
<!-- 筛选栏:所属单位 + 时间 -->
@@ -176,6 +172,7 @@ th.sortable:hover{color:var(--primary);}
<!-- 分组大表:就诊人次 / 慢病管理 / 培训宣教 / 体检报告解读 多级表头 -->
<div class="action-bar">
<button class="btn btn-primary" onclick="doExport()"><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="openExportLog()"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>查看导出记录</button>
</div>
<div class="table-card">
<div class="table-card__content">
@@ -135,11 +135,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<option value="">全部</option>
</select>
</div>
<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="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-default" onclick="showToast('救助物品模板下载功能演示')">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 2v7M4.5 6.5L7 9l2.5-2.5M2 12h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>救助物品模板下载
</button>
</div>
<!-- 小药棒:6 项指标通栏 -->
@@ -181,6 +178,9 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<div class="modal__title"><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 14V3M6.5 6.5L10 3l3.5 3.5M3 16h14" stroke="#1890FF" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg> 导入救助物品数据</div>
<div style="margin-top:14px;">
<div class="drop-zone" onclick="showToast('演示环境请选择本地文件(仅支持.xlsx)')">点击选择或拖拽文件到此处上传<br>救助物品模板.xlsx</div>
<div style="font-size:var(--font-xs);color:var(--text-placeholder);margin-top:6px;">
<a href="javascript:void(0)" style="color:var(--primary);text-decoration:none;" onclick="showToast('模板下载中…')">下载导入模板</a> · 请按模板列整理后上传
</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeImport()">取消</button>
+146 -13
View File
@@ -168,6 +168,21 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
</style>
</head>
<body>
@@ -237,16 +252,20 @@
<label>身份证号:</label>
<input class="filter-input" type="text" id="fIdCard" placeholder="请输入" style="width:160px;min-width:unset;">
</div>
<div class="filter-item">
<label>异常员工:</label>
<select class="filter-select" style="min-width:100px;" id="abnormalFilter">
<option value="">全部</option>
<option value="abnormal"></option>
<option value="normal"></option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -585,21 +604,135 @@ function hasAbnormal(r){
for(var k in r.r){if(r.r[k][1])return true;}
return false;
}
/* 按筛选条件取当前展示行:异常员工 + 姓名/员工编号/身份证号模糊匹配 */
/* 按筛选条件取当前展示行:姓名模糊匹配 + 高级查询条件 */
function getDisplayRows(){
var v=document.getElementById('abnormalFilter').value;
var name=document.getElementById('fName').value.trim();
var empNo=document.getElementById('fEmpNo').value.trim();
var idCard=document.getElementById('fIdCard').value.trim();
var conds=readAdvancedConditions();
return MASTER_ROWS.filter(function(r){
if(v==='abnormal'&&!hasAbnormal(r))return false;
if(v==='normal'&&hasAbnormal(r))return false;
if(name&&r.name.indexOf(name)<0)return false;
if(empNo&&r.empNo.indexOf(empNo)<0)return false;
if(idCard&&r.idCard.indexOf(idCard)<0)return false;
for(var i=0;i<conds.length;i++){if(!matchCondition(r,conds[i]))return false;}
return true;
});
}
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_FIELD_OPTS='';
/* 字段下拉选项:基本信息 + 全部体检项目(按一级分类分组,二级分类·项目名 去重) */
function buildAdvFieldOpts(){
var o='<optgroup label="基本信息">';
BASE_COLS.forEach(function(c){if(c.key!=='idx')o+='<option value="'+c.key+'">'+c.label+'</option>';});
o+='</optgroup>';
ITEM_TREE.forEach(function(g){
o+='<optgroup label="'+g.lvl1+'">';
g.lvl2.forEach(function(s){
s.items.forEach(function(it){o+='<option value="'+s.name+'|'+it+'">'+s.name+'·'+it+'</option>';});
});
o+='</optgroup>';
});
return o;
}
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
/* 运算符切换为"范围"时,值区域变成最小/最大两个输入框 */
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){
document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());
}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
renderBigTable();
showToast('已清空高级查询条件');
}
/* 读取面板中已填值的条件(忽略值为空的行;范围条件任一边有值即计入) */
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
/* 取某字段在某员工上的值:基本信息直接用 key,项目结果取"结果"字符串 */
function getFieldRaw(row,field){
if(field.indexOf('|')>=0){
var i=field.indexOf('|');
var cat=field.slice(0,i),name=field.slice(i+1);
var val=row.r[cat+'|'+name]||row.r[name];
if(!val)val=[(CAT_DEFAULTS[cat]||['-'])[0]];
return val[0];
}
return row[field];
}
/* 数值化:取结果中的首个数字 */
function numericOf(v){
var m=String(v).match(/[-+]?\d+(?:\.\d+)?/);
return m?parseFloat(m[0]):null;
}
/* 单条条件匹配:包含/=/!= 按字符串,>、>=、<<=、范围 按数值 */
function matchCondition(row,cond){
var raw=getFieldRaw(row,cond.field);
var str=String(raw==null?'':raw);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){
var lo=parseFloat(cond.from);
if(!isNaN(lo)&&n<lo)return false;
}
if(cond.to!==''){
var hi=parseFloat(cond.to);
if(!isNaN(hi)&&n>hi)return false;
}
return true;
}
var c=parseFloat(cond.value);
if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function renderBigTable(){
document.getElementById('bigTable').innerHTML=buildHead()+buildBody();
document.getElementById('pageTotal').textContent='共 '+getDisplayRows().length+' 条';
@@ -789,7 +922,6 @@ function applyFilter(){
showToast('查询完成');
}
function resetFilter(){
document.getElementById('abnormalFilter').value='';
document.getElementById('fName').value='';
document.getElementById('fEmpNo').value='';
document.getElementById('fIdCard').value='';
@@ -797,6 +929,7 @@ function resetFilter(){
showToast('已重置筛选条件');
}
renderBigTable();
ADV_FIELD_OPTS=buildAdvFieldOpts();
/* 体检年份选择控件 */
function pickYear(el){
var dp=el.parentElement,input=dp.parentElement.querySelector('input');
+139
View File
@@ -119,6 +119,25 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -184,6 +203,18 @@
</div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -399,6 +430,114 @@ document.querySelectorAll('.year-picker>input').forEach(function(inp){
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_ROW_SELECTOR='#unitTable tbody tr';
var ADV_FIELDS=[
{key:'unit',label:'单位',idx:1},{key:'dept',label:'部门',idx:2},{key:'year',label:'体检年份',idx:3},
{key:'signHospital',label:'签约医院',idx:4},{key:'hospital',label:'承检医院',idx:5},{key:'range',label:'体检计划日期范围',idx:6},
{key:'total',label:'总人数',idx:7},{key:'should',label:'应检人数',idx:8},{key:'selected',label:'已选项目人数',idx:9},
{key:'booked',label:'已预约人数',idx:10},{key:'checked',label:'已检人数',idx:11},{key:'unchecked',label:'未检人数',idx:12},
{key:'rate',label:'受检率',idx:13},{key:'detectRate',label:'检出率',idx:14},{key:'keyAbn',label:'重点指标异常人数',idx:15},
{key:'ctaAbn',label:'冠脉CTA异常人数',idx:16},{key:'cancerAbn',label:'防癌筛查异常人数',idx:17},
{key:'planFee',label:'计划总费用(元)',idx:18},{key:'actualFee',label:'实际总费用(元)',idx:19},{key:'perFee',label:'人均费用(元)',idx:20}
];
var ADV_FIELD_OPTS=ADV_FIELDS.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(key){for(var i=0;i<ADV_FIELDS.length;i++){if(ADV_FIELDS[i].key===key)return ADV_FIELDS[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond){
var f=fieldByKey(cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(){
var conds=readAdvancedConditions();
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i])){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
+10
View File
@@ -1,5 +1,15 @@
# 健康体检模块 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
+236 -73
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 | 页面 |
---
@@ -978,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)
@@ -1000,7 +1037,7 @@
---
#### 34. 已检员工(mid-participation.html
#### 35. 已检员工(mid-participation.html
![页面截图](screenshots/mid-participation.jpg)
@@ -1020,7 +1057,7 @@
---
#### 35. 单位预约人数统计(mid-unit-appointment.html
#### 36. 单位预约人数统计(mid-unit-appointment.html
![页面截图](screenshots/mid-unit-appointment.jpg)
@@ -1036,7 +1073,7 @@
---
#### 36. 医院实时预约人数(mid-hospital-realtime.html
#### 37. 医院实时预约人数(mid-hospital-realtime.html
![页面截图](screenshots/mid-hospital-realtime.jpg)
@@ -1050,7 +1087,7 @@
---
#### 37. 医生实时检查情况(mid-doctor-realtime.html
#### 38. 医生实时检查情况(mid-doctor-realtime.html
![页面截图](screenshots/mid-doctor-realtime.jpg)
@@ -1064,7 +1101,7 @@
---
#### 38. 医院预约人数统计(mid-hospital-appointment.html
#### 39. 医院预约人数统计(mid-hospital-appointment.html
![页面截图](screenshots/mid-hospital-appointment.jpg)
@@ -1078,7 +1115,7 @@
---
#### 39. 签约单位应检人数(mid-unit-expected.html
#### 40. 签约单位应检人数(mid-unit-expected.html
![页面截图](screenshots/mid-unit-expected.jpg)
@@ -1094,7 +1131,7 @@
### 胃肠镜体检
#### 40. 员工预约跟踪(胃肠镜)(mid-gastro-appointment-track.html
#### 41. 员工预约跟踪(胃肠镜)(mid-gastro-appointment-track.html
![页面截图](screenshots/mid-gastro-appointment-track.jpg)
@@ -1114,7 +1151,7 @@
---
#### 41. 单位预约统计(胃肠镜)(mid-gastro-unit-appointment.html
#### 42. 单位预约统计(胃肠镜)(mid-gastro-unit-appointment.html
![页面截图](screenshots/mid-gastro-unit-appointment.jpg)
@@ -1130,7 +1167,7 @@
---
#### 42. 胃肠镜问卷信息(mid-gastro-questionnaire.html
#### 43. 胃肠镜问卷信息(mid-gastro-questionnaire.html
![页面截图](screenshots/mid-gastro-questionnaire.jpg)
@@ -1148,7 +1185,7 @@
### 冠脉CTA体检
#### 43. 员工预约跟踪(冠脉CTA)(mid-cta-appointment-track.html
#### 44. 员工预约跟踪(冠脉CTA)(mid-cta-appointment-track.html
![页面截图](screenshots/mid-cta-appointment-track.jpg)
@@ -1157,7 +1194,7 @@
---
#### 44. 单位预约统计(冠脉CTA)(mid-cta-unit-appointment.html
#### 45. 单位预约统计(冠脉CTA)(mid-cta-unit-appointment.html
![页面截图](screenshots/mid-cta-unit-appointment.jpg)
@@ -1170,7 +1207,7 @@
### 常规体检
#### 45. 常规体检报告(post-report-employee.html
#### 46. 常规体检报告(post-report-employee.html
![页面截图](screenshots/post-report-employee.jpg)
@@ -1190,7 +1227,25 @@
---
#### 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)
@@ -1211,7 +1266,7 @@
---
#### 47. 大病人员信息(post-illness-personnel.html
#### 49. 大病人员信息(post-illness-personnel.html
![页面截图](screenshots/post-illness-personnel.jpg)
@@ -1235,7 +1290,7 @@
---
#### 48. 指标异常通知(post-indicator-notify.html
#### 50. 指标异常通知(post-indicator-notify.html
![页面截图](screenshots/post-indicator-notify.jpg)
@@ -1251,7 +1306,7 @@
---
#### 49. 体检报告未匹配项目(post-report-unmatched.html
#### 51. 体检报告未匹配项目(post-report-unmatched.html
![页面截图](screenshots/post-report-unmatched.jpg)
@@ -1267,7 +1322,7 @@
### 体检统计汇总
#### 50. 单位体检人数统计(post-unit-count.html
#### 52. 单位体检人数统计(post-unit-count.html
![页面截图](screenshots/post-unit-count.jpg)
@@ -1291,7 +1346,7 @@
---
#### 51. 单位体检费用统计(post-unit-cost-stat.html
#### 53. 单位体检费用统计(post-unit-cost-stat.html
![页面截图](screenshots/post-unit-cost-stat.jpg)
@@ -1317,7 +1372,39 @@
---
#### 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)
@@ -1331,7 +1418,7 @@
---
#### 53. 医生检查人数统计(post-doctor-count.html
#### 57. 医生检查人数统计(post-doctor-count.html
![页面截图](screenshots/post-doctor-count.jpg)
@@ -1345,7 +1432,7 @@
---
#### 54. 医院体检项目统计(post-item-stat.html
#### 58. 医院体检项目统计(post-item-stat.html
![页面截图](screenshots/post-item-stat.jpg)
@@ -1359,7 +1446,7 @@
---
#### 55. 防癌筛查人数统计(post-cancer-screening.html
#### 59. 防癌筛查人数统计(post-cancer-screening.html
![页面截图](screenshots/post-cancer-screening.jpg)
@@ -1373,7 +1460,7 @@
---
#### 56. 系统标准化项目统计(post-standard-stat.html
#### 60. 系统标准化项目统计(post-standard-stat.html
![页面截图](screenshots/post-standard-stat.jpg)
@@ -1387,7 +1474,7 @@
---
#### 57. 签约医院人数统计(post-contracted-hospital.html
#### 61. 签约医院人数统计(post-contracted-hospital.html
![页面截图](screenshots/post-contracted-hospital.jpg)
@@ -1401,7 +1488,7 @@
---
#### 58. 重点指标异常统计(post-key-indicator-stat.html
#### 62. 重点指标异常统计(post-key-indicator-stat.html
![页面截图](screenshots/post-key-indicator-stat.jpg)
@@ -1421,7 +1508,7 @@
### 病种信息管理
#### 59. 病种信息配置(post-disease-info.html
#### 63. 病种信息配置(post-disease-info.html
![页面截图](screenshots/post-disease-info.jpg)
@@ -1441,7 +1528,7 @@
---
#### 60. 体检结论关联病种(post-conclusion-disease.html
#### 64. 体检结论关联病种(post-conclusion-disease.html
![页面截图](screenshots/post-conclusion-disease.jpg)
@@ -1461,7 +1548,7 @@
---
#### 61. 员工病种信息(post-auto-select-disease.html
#### 65. 员工病种信息(post-auto-select-disease.html
![页面截图](screenshots/post-auto-select-disease.jpg)
@@ -1484,7 +1571,7 @@
### 胃肠镜体检
#### 62. 胃肠镜体检报告(post-report-gastro.html
#### 66. 胃肠镜体检报告(post-report-gastro.html
![页面截图](screenshots/post-report-gastro.jpg)
@@ -1519,7 +1606,7 @@
---
#### 63. 胃肠镜体检统计(post-gastro-stat.html
#### 67. 胃肠镜体检统计(post-gastro-stat.html
![页面截图](screenshots/post-gastro-stat.jpg)
@@ -1542,7 +1629,7 @@
### 冠脉CTA体检
#### 64. 冠脉CTA体检报告(post-cta-report.html
#### 68. 冠脉CTA体检报告(post-cta-report.html
![页面截图](screenshots/post-cta-report.jpg)
@@ -1572,7 +1659,7 @@
---
#### 65. 冠脉CTA体检统计(post-cta-stat.html
#### 69. 冠脉CTA体检统计(post-cta-stat.html
![页面截图](screenshots/post-cta-stat.jpg)
@@ -1582,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)
@@ -1625,7 +1788,7 @@
## 七、异常事件
### 67. 系统脏数据(exception-dirty.html
### 75. 系统脏数据(exception-dirty.html
![页面截图](screenshots/exception-dirty.jpg)
@@ -1643,7 +1806,7 @@
---
### 68. 结果异常(exception-result.html
### 76. 结果异常(exception-result.html
![页面截图](screenshots/exception-result.jpg)
@@ -55,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' },
@@ -71,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' },
@@ -78,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' },
@@ -95,6 +99,11 @@ 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' },
Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

@@ -120,6 +120,25 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -157,10 +176,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item active">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item active">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -213,12 +232,6 @@
<option>请选择单位部门</option><option>公司机关</option><option>第一采油厂</option><option>第二采油厂</option><option>第三采油厂</option>
</select>
</div>
<div class="filter-item">
<label>医院选择:</label>
<select class="filter-select">
<option>请选择医院</option><option>延安医院</option><option>泾河医院</option><option>市中心医院</option>
</select>
</div>
<div class="filter-item">
<label>姓名:</label>
<input class="filter-input" type="text" placeholder="请输入姓名" style="width:130px;min-width:unset;">
@@ -231,53 +244,23 @@
<label>身份证号:</label>
<input class="filter-input" type="text" placeholder="请输入身份证号" style="width:180px;min-width:unset;">
</div>
<div class="filter-item">
<label>性别:</label>
<select class="filter-select">
<option>请选择性别</option><option></option><option></option>
</select>
</div>
<div class="filter-item">
<label>年龄范围:</label>
<select class="filter-select">
<option>请选择年龄范围</option><option>年龄&lt;35</option><option>35≤年龄&lt;40</option><option>40≤年龄&lt;45</option><option>年龄≥45</option>
</select>
</div>
<div class="filter-item">
<label>机构分类:</label>
<select class="filter-select">
<option>请选择机构分类</option><option>子公司</option><option>三产公司</option>
</select>
</div>
<div class="filter-item">
<label>是否上市员工:</label>
<select class="filter-select">
<option>请选择</option><option></option><option></option>
</select>
</div>
<div class="filter-item">
<label>防癌项:</label>
<select class="filter-select">
<option>请选择防癌项</option><option>Septin9</option><option>CA724</option><option>两项都有</option>
</select>
</div>
<div class="filter-item">
<label>体检状态:</label>
<select class="filter-select">
<option>请选择体检状态</option><option>未体检</option><option>体检中</option><option>已体检</option>
</select>
</div>
<div class="filter-item">
<label>预约状态:</label>
<select class="filter-select">
<option>请选择预约状态</option><option>未预约</option><option>已预约</option><option>已取消预约</option>
</select>
</div>
<button class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
查询
</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -509,6 +492,117 @@ document.querySelectorAll('.year-picker>input').forEach(function(inp){
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_ROW_SELECTOR='.table-card__content tbody tr';
var ADV_FIELDS=[
{key:'name',label:'姓名',idx:1},{key:'gender',label:'性别',idx:2},{key:'age',label:'年龄',idx:3},
{key:'ageRange',label:'年龄范围',idx:4},{key:'idCard',label:'身份证号',idx:5},{key:'unit',label:'单位',idx:6},
{key:'dept',label:'部门',idx:7},{key:'orgType',label:'机构分类',idx:8},{key:'empNo',label:'员工编号',idx:9},
{key:'examCode',label:'体检编码',idx:10},{key:'empType',label:'用工形式',idx:11},{key:'marriage',label:'婚姻状态',idx:12},
{key:'examType',label:'体检类型',idx:13},{key:'cancer',label:'防癌项',idx:14},{key:'listed',label:'是否上市员工',idx:15},
{key:'hospital',label:'体检医院',idx:16},{key:'feeTotal',label:'体检总费用',idx:17},{key:'fee1',label:'必检费用',idx:18},
{key:'fee2',label:'选检费用',idx:19},{key:'fee3',label:'妇科费用',idx:20},{key:'fee4',label:'防癌筛查费用',idx:21},
{key:'bookStatus',label:'预约状态',idx:22},{key:'bookInfo',label:'体检预约信息',idx:23},{key:'bookDate',label:'预约日期',idx:24},
{key:'examStatus',label:'体检状态',idx:25},{key:'examDate',label:'体检日期',idx:26},{key:'year',label:'体检年份',idx:27},
{key:'plan',label:'体检计划',idx:28}
];
var ADV_FIELD_OPTS=ADV_FIELDS.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(key){for(var i=0;i<ADV_FIELDS.length;i++){if(ADV_FIELDS[i].key===key)return ADV_FIELDS[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond){
var f=fieldByKey(cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(){
var conds=readAdvancedConditions();
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i])){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
@@ -146,6 +146,25 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
[id^="advConditions"]{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -183,10 +202,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item active">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -249,77 +268,35 @@
<option>请选择单位部门</option><option>公司机关</option><option>第一采油厂</option><option>第二采油厂</option><option>第三采油厂</option>
</select>
</div>
<div class="filter-item">
<label>医院选择:</label>
<select class="filter-select">
<option>请选择医院</option><option>延安医院</option><option>泾河医院</option><option>市中心医院</option>
</select>
</div>
<div class="filter-item">
<label>姓名:</label>
<input class="filter-input" type="text" placeholder="请输入姓名" style="width:130px;min-width:unset;">
</div>
<div class="filter-item">
<label>员工编号:</label>
<input class="filter-input" type="text" placeholder="请输入" style="width:140px;min-width:unset;">
<input class="filter-input" type="text" placeholder="请输入员工编号" style="width:140px;min-width:unset;">
</div>
<div class="filter-item">
<label>身份证号:</label>
<input class="filter-input" type="text" placeholder="请输入身份证号" style="width:180px;min-width:unset;">
</div>
<div class="filter-item">
<label>性别:</label>
<select class="filter-select">
<option>请选择性别</option><option></option><option></option>
</select>
</div>
<div class="filter-item">
<label>年龄范围:</label>
<select class="filter-select">
<option>请选择年龄范围</option><option>年龄&lt;35</option><option>35≤年龄&lt;40</option><option>40≤年龄&lt;45</option><option>年龄≥45</option>
</select>
</div>
<div class="filter-item">
<label>机构分类:</label>
<select class="filter-select">
<option>请选择机构分类</option><option>子公司</option><option>三产公司</option>
</select>
</div>
<div class="filter-item">
<label>是否上市员工:</label>
<select class="filter-select">
<option>请选择</option><option></option><option></option>
</select>
</div>
<div class="filter-item">
<label>防癌项:</label>
<select class="filter-select">
<option>请选择防癌项</option><option>Septin9</option><option>CA724</option><option>两项都有</option>
</select>
</div>
<div class="filter-item">
<label>体检状态:</label>
<select class="filter-select">
<option>请选择体检状态</option><option>未体检</option><option>体检中</option><option>已体检</option>
</select>
</div>
<div class="filter-item">
<label>预约状态:</label>
<select class="filter-select">
<option>请选择预约状态</option><option>未预约</option><option>已预约</option><option>已取消预约</option>
</select>
</div>
<div class="filter-item">
<label>前置机预约状态:</label>
<select class="filter-select">
<option>请选择前置机预约状态</option><option>未预约</option><option>预约成功</option><option>已取消预约</option><option>预约失败</option><option>预约中</option><option>取消预约失败</option>
</select>
</div>
<button class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
查询
</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle-should" onclick="toggleAdvanced('should')">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel-should">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions-should"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition('should')">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter('should')">查询</button>
<button class="btn btn-default" onclick="clearAdvanced('should')">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -405,12 +382,6 @@
<option>请选择单位部门</option><option>公司机关</option><option>第一采油厂</option><option>第二采油厂</option><option>第三采油厂</option>
</select>
</div>
<div class="filter-item">
<label>医院选择:</label>
<select class="filter-select">
<option>请选择医院</option><option>延安医院</option><option>泾河医院</option><option>兴隆园医院</option><option>市中心医院</option>
</select>
</div>
<div class="filter-item">
<label>姓名:</label>
<input class="filter-input" type="text" placeholder="请输入姓名" style="width:130px;min-width:unset;">
@@ -428,6 +399,18 @@
查询
</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle-selected" onclick="toggleAdvanced('selected')">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel-selected">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions-selected"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition('selected')">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter('selected')">查询</button>
<button class="btn btn-default" onclick="clearAdvanced('selected')">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -509,12 +492,6 @@
<option>请选择单位部门</option><option>公司机关</option><option>第一采油厂</option><option>第二采油厂</option><option>第三采油厂</option>
</select>
</div>
<div class="filter-item">
<label>医院选择:</label>
<select class="filter-select">
<option>请选择医院</option><option>延安医院</option><option>泾河医院</option><option>兴隆园医院</option><option>市中心医院</option>
</select>
</div>
<div class="filter-item">
<label>姓名:</label>
<input class="filter-input" type="text" placeholder="请输入姓名" style="width:130px;min-width:unset;">
@@ -527,15 +504,23 @@
<label>身份证号:</label>
<input class="filter-input" type="text" placeholder="请输入身份证号" style="width:180px;min-width:unset;">
</div>
<div class="filter-item">
<label>体检编码:</label>
<input class="filter-input" type="text" placeholder="请输入体检编码" style="width:150px;min-width:unset;">
</div>
<button class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
查询
</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle-booked" onclick="toggleAdvanced('booked')">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel-booked">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions-booked"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition('booked')">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter('booked')">查询</button>
<button class="btn btn-default" onclick="clearAdvanced('booked')">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -622,54 +607,38 @@
<div class="filter-item">
<label>单位部门:</label>
<select class="filter-select">
<option>请选择</option><option>公司机关</option><option>第一采油厂</option><option>第二采油厂</option><option>第三采油厂</option>
</select>
</div>
<div class="filter-item">
<label>体检医院:</label>
<select class="filter-select">
<option>请选择</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>
<input class="filter-input" type="text" placeholder="请输入" style="width:130px;min-width:unset;">
<input class="filter-input" type="text" placeholder="请输入姓名" style="width:130px;min-width:unset;">
</div>
<div class="filter-item">
<label>员工编号:</label>
<input class="filter-input" type="text" placeholder="请输入" style="width:140px;min-width:unset;">
<input class="filter-input" type="text" placeholder="请输入员工编号" style="width:140px;min-width:unset;">
</div>
<div class="filter-item">
<label>身份证号:</label>
<input class="filter-input" type="text" placeholder="请输入" style="width:180px;min-width:unset;">
</div>
<div class="filter-item">
<label>体检编码:</label>
<input class="filter-input" type="text" placeholder="请输入" style="width:150px;min-width:unset;">
</div>
<div class="filter-item">
<label>系统体检状态:</label>
<select class="filter-select">
<option>请选择</option><option>未体检</option><option>体检中</option><option>已体检</option>
</select>
</div>
<div class="filter-item">
<label>医院体检状态:</label>
<select class="filter-select">
<option>请选择</option><option>未预约</option><option>已预约</option><option>体检中</option><option>已终审</option>
</select>
</div>
<div class="filter-item">
<label>体检日期:</label>
<input class="filter-input" type="date" style="width:150px;min-width:unset;">
<span style="color:var(--text-secondary);"></span>
<input class="filter-input" type="date" style="width:150px;min-width:unset;">
<input class="filter-input" type="text" placeholder="请输入身份证号" style="width:180px;min-width:unset;">
</div>
<button class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.4"/><path d="M10 10l2.5 2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
查询
</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle-participated" onclick="toggleAdvanced('participated')">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel-participated">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions-participated"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition('participated')">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter('participated')">查询</button>
<button class="btn btn-default" onclick="clearAdvanced('participated')">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -1114,6 +1083,148 @@ document.querySelectorAll('.year-picker>input').forEach(function(inp){
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_TABS={
'should':{
selector:'#tab-should .table-card__content tbody tr',
fields:[
{key:'name',label:'姓名',idx:1},{key:'empNo',label:'员工编号',idx:2},{key:'gender',label:'性别',idx:3},
{key:'age',label:'年龄',idx:4},{key:'idCard',label:'身份证号',idx:5},{key:'unit',label:'单位',idx:6},
{key:'dept',label:'部门',idx:7},{key:'orgType',label:'机构分类',idx:8},{key:'examCode',label:'体检编码',idx:9},
{key:'empType',label:'用工形式',idx:10},{key:'marriage',label:'婚姻状态',idx:11},{key:'cancer',label:'防癌项',idx:12},
{key:'listed',label:'是否上市员工',idx:13},{key:'bookStatus',label:'预约状态',idx:14},{key:'frontStatus',label:'前置机预约状态',idx:15},
{key:'bookDate',label:'预约日期',idx:16},{key:'examStatus',label:'体检状态',idx:17},{key:'examDate',label:'体检日期',idx:18},
{key:'year',label:'体检年份',idx:19},{key:'plan',label:'体检计划',idx:20}
]
},
'selected':{
selector:'#tab-selected .table-card__content tbody tr',
fields:[
{key:'name',label:'姓名',idx:1},{key:'empNo',label:'员工编号',idx:2},{key:'gender',label:'性别',idx:3},
{key:'age',label:'年龄',idx:4},{key:'idCard',label:'身份证号',idx:5},{key:'unit',label:'单位',idx:6},
{key:'dept',label:'部门',idx:7},{key:'hospital',label:'体检医院',idx:8},{key:'itemCount',label:'体检项目数',idx:9},
{key:'feeTotal',label:'体检总费用',idx:10},{key:'fee1',label:'必检费用',idx:11},{key:'fee2',label:'选检费用',idx:12},
{key:'fee3',label:'妇科费用',idx:13},{key:'fee4',label:'防癌筛查费用',idx:14},{key:'submitDate',label:'提交项目日期',idx:15},
{key:'year',label:'体检年份',idx:16},{key:'plan',label:'体检计划',idx:17}
]
},
'booked':{
selector:'#tab-booked .table-card__content tbody tr',
fields:[
{key:'name',label:'姓名',idx:2},{key:'empNo',label:'员工编号',idx:3},{key:'gender',label:'性别',idx:4},
{key:'age',label:'年龄',idx:5},{key:'idCard',label:'身份证号',idx:6},{key:'unit',label:'单位',idx:7},
{key:'dept',label:'部门',idx:8},{key:'hospital',label:'体检医院',idx:9},{key:'examCode',label:'体检编码',idx:10},
{key:'bookDate',label:'预约日期',idx:11},{key:'year',label:'体检年份',idx:12},{key:'plan',label:'体检计划',idx:13}
]
},
'participated':{
selector:'#tab-participated .table-card__content tbody tr',
fields:[
{key:'name',label:'姓名',idx:1},{key:'empNo',label:'员工编号',idx:2},{key:'gender',label:'性别',idx:3},
{key:'age',label:'年龄',idx:4},{key:'idCard',label:'身份证号',idx:5},{key:'unit',label:'单位',idx:6},
{key:'dept',label:'部门',idx:7},{key:'hospital',label:'体检医院',idx:8},{key:'examCode',label:'体检编码',idx:9},
{key:'sysStatus',label:'系统体检状态',idx:10},{key:'hospStatus',label:'医院体检状态',idx:11},{key:'examDate',label:'体检日期',idx:12},
{key:'checkedItems',label:'已检项/全部',idx:13},{key:'year',label:'体检年份',idx:14},{key:'plan',label:'体检计划',idx:15}
]
}
};
function fieldOpts(tabId){return ADV_TABS[tabId].fields.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');}
function advConditionRowHTML(tabId){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+fieldOpts(tabId)+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(tabId){document.getElementById('advConditions-'+tabId).insertAdjacentHTML('beforeend',advConditionRowHTML(tabId));}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(tabId){
var p=document.getElementById('advPanel-'+tabId);
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle-'+tabId).querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition(tabId);
}
function readAdvancedConditions(tabId){
var conds=[];
document.querySelectorAll('#advConditions-'+tabId+' .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(tabId,key){var fields=ADV_TABS[tabId].fields;for(var i=0;i<fields.length;i++){if(fields[i].key===key)return fields[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond,tabId){
var f=fieldByKey(tabId,cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(tabId){
var conds=readAdvancedConditions(tabId);
document.querySelectorAll(ADV_TABS[tabId].selector).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i],tabId)){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(tabId){
document.getElementById('advConditions-'+tabId).innerHTML='';
document.querySelectorAll(ADV_TABS[tabId].selector).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
@@ -150,10 +150,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -87,6 +87,25 @@
/* 三级单位tab下才显示的部门列 */
.col-dept{display:none;}
body.level-3 .col-dept{display:table-cell;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -124,10 +143,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -179,6 +198,18 @@
查询
</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -246,6 +277,110 @@ function toggleSubgroup(header){
var sub=active.closest('.sidebar-subgroup');
if(sub)sub.classList.add('open');
})();
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_ROW_SELECTOR='.table-card__content tbody tr';
var ADV_FIELDS=[
{key:'unit',label:'单位',idx:1},{key:'dept',label:'部门',idx:2},{key:'should',label:'应体检人数',idx:3},
{key:'booked',label:'已预约人数',idx:4},{key:'participated',label:'已参检人数',idx:5},{key:'unparticipated',label:'未参检人数',idx:6},
{key:'unbooked',label:'未预约人数',idx:7},{key:'year',label:'体检年份',idx:8}
];
var ADV_FIELD_OPTS=ADV_FIELDS.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(key){for(var i=0;i<ADV_FIELDS.length;i++){if(ADV_FIELDS[i].key===key)return ADV_FIELDS[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond){
var f=fieldByKey(cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(){
var conds=readAdvancedConditions();
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i])){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
@@ -128,10 +128,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item active">医生实时检查情况</a>
@@ -150,10 +150,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -129,10 +129,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -87,6 +87,25 @@
/* 三级单位tab下才显示的部门列 */
.col-dept{display:none;}
body.level-3 .col-dept{display:table-cell;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -124,10 +143,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -179,6 +198,18 @@
查询
</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -246,6 +277,110 @@ function toggleSubgroup(header){
var sub=active.closest('.sidebar-subgroup');
if(sub)sub.classList.add('open');
})();
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_ROW_SELECTOR='.table-card__content tbody tr';
var ADV_FIELDS=[
{key:'unit',label:'单位',idx:1},{key:'dept',label:'部门',idx:2},{key:'should',label:'应体检人数',idx:3},
{key:'questionnaire',label:'填写问卷人数',idx:4},{key:'booked',label:'已预约人数',idx:5},{key:'participated',label:'已参检人数',idx:6},
{key:'unparticipated',label:'未参检人数',idx:7},{key:'unbooked',label:'未预约人数',idx:8},{key:'year',label:'体检年份',idx:9}
];
var ADV_FIELD_OPTS=ADV_FIELDS.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(key){for(var i=0;i<ADV_FIELDS.length;i++){if(ADV_FIELDS[i].key===key)return ADV_FIELDS[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond){
var f=fieldByKey(cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(){
var conds=readAdvancedConditions();
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i])){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
@@ -124,10 +124,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -128,10 +128,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item active">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -147,10 +147,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item active">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item active">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -147,10 +147,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item active">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item active">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -96,6 +96,25 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -133,10 +152,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item active">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -200,6 +219,18 @@
查询
</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -286,6 +317,110 @@ document.querySelectorAll('.year-picker>input').forEach(function(inp){
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_ROW_SELECTOR='.table-card__content tbody tr';
var ADV_FIELDS=[
{key:'unit',label:'单位',idx:1},{key:'dept',label:'部门',idx:2},{key:'should',label:'应体检人数',idx:3},
{key:'selected',label:'已选项目人数',idx:4},{key:'booked',label:'已预约人数',idx:5},{key:'participated',label:'已参检人数',idx:6},
{key:'unparticipated',label:'未参检人数',idx:7},{key:'unbooked',label:'未预约人数',idx:8},{key:'year',label:'体检年份',idx:9}
];
var ADV_FIELD_OPTS=ADV_FIELDS.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(key){for(var i=0;i<ADV_FIELDS.length;i++){if(ADV_FIELDS[i].key===key)return ADV_FIELDS[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond){
var f=fieldByKey(cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(){
var conds=readAdvancedConditions();
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i])){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
@@ -113,10 +113,10 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="mid-appointment-track.html" class="sidebar-item">体检预约</a>
<!-- <a href="mid-appointment-track.html" class="sidebar-item">体检预约</a> -->
<!-- <a href="mid-selected-items.html" class="sidebar-item">已选项目</a> -->
<!-- <a href="mid-participation.html" class="sidebar-item">已检员工</a> -->
<a href="mid-appointment-track2.html" class="sidebar-item">体检预约跟踪</a>
<a href="mid-selected-items.html" class="sidebar-item">已选项目</a>
<a href="mid-participation.html" class="sidebar-item">已检员工</a>
<a href="mid-unit-appointment.html" class="sidebar-item">单位预约人数统计</a>
<a href="mid-hospital-realtime.html" class="sidebar-item">医院实时预约人数</a>
<a href="mid-doctor-realtime.html" class="sidebar-item">医生实时检查情况</a>
@@ -143,7 +143,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -158,6 +158,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -186,7 +187,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -196,7 +197,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -126,7 +126,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -142,6 +142,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -170,7 +171,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -180,7 +181,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -153,7 +153,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -168,6 +168,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -196,7 +197,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -206,7 +207,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -117,7 +117,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -132,6 +132,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -160,7 +161,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -170,7 +171,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -184,7 +184,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -199,6 +199,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -227,7 +228,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -237,7 +238,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item active">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item active">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
+4 -3
View File
@@ -143,7 +143,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -158,6 +158,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -186,7 +187,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -196,7 +197,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item active">冠脉CTA体检统计</a>
</div>
</div>
@@ -151,7 +151,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -166,6 +166,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -194,7 +195,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -204,7 +205,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -126,7 +126,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -142,6 +142,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item active">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -170,7 +171,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -180,7 +181,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -105,6 +105,21 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
</style>
</head>
<body>
@@ -142,7 +157,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item active">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -158,6 +173,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -186,7 +202,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -196,7 +212,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -248,16 +264,20 @@
<label>身份证号:</label>
<input class="filter-input" type="text" id="fIdCard" placeholder="请输入" style="width:160px;min-width:unset;">
</div>
<div class="filter-item">
<label>异常员工:</label>
<select class="filter-select" style="min-width:100px;" id="abnormalFilter">
<option value="">全部</option>
<option value="abnormal"></option>
<option value="normal"></option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作栏 -->
@@ -531,21 +551,127 @@ function hasAbnormal(r){
for(var k in r.r){if(r.r[k][1])return true;}
return false;
}
/* 按筛选条件取当前展示行:异常员工 + 姓名/员工编号/身份证号模糊匹配 */
/* 按筛选条件取当前展示行:姓名模糊匹配 + 高级查询条件 */
function getDisplayRows(){
var v=document.getElementById('abnormalFilter').value;
var name=document.getElementById('fName').value.trim();
var empNo=document.getElementById('fEmpNo').value.trim();
var idCard=document.getElementById('fIdCard').value.trim();
var conds=readAdvancedConditions();
return MASTER_ROWS.filter(function(r){
if(v==='abnormal'&&!hasAbnormal(r))return false;
if(v==='normal'&&hasAbnormal(r))return false;
if(name&&r.name.indexOf(name)<0)return false;
if(empNo&&r.empNo.indexOf(empNo)<0)return false;
if(idCard&&r.idCard.indexOf(idCard)<0)return false;
for(var i=0;i<conds.length;i++){if(!matchCondition(r,conds[i]))return false;}
return true;
});
}
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_FIELD_OPTS='';
/* 字段下拉选项:基本信息 + 全部体检项目(按一级分类分组,二级分类·项目名 去重) */
function buildAdvFieldOpts(){
var o='<optgroup label="基本信息">';
BASE_COLS.forEach(function(c){if(c.key!=='idx')o+='<option value="'+c.key+'">'+c.label+'</option>';});
o+='</optgroup>';
ITEM_TREE.forEach(function(g){
o+='<optgroup label="'+g.lvl1+'">';
g.lvl2.forEach(function(s){
s.items.forEach(function(it){o+='<option value="'+s.name+'|'+it+'">'+s.name+'·'+it+'</option>';});
});
o+='</optgroup>';
});
return o;
}
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
/* 运算符切换为"范围"时,值区域变成最小/最大两个输入框 */
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){
document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());
}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
renderBigTable();
showToast('已清空高级查询条件');
}
/* 读取面板中已填值的条件(忽略值为空的行;范围条件任一边有值即计入) */
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
/* 取某字段在某员工上的值:基本信息直接用 key,项目结果取"结果"字符串 */
function getFieldRaw(row,field){
if(field.indexOf('|')>=0){
var i=field.indexOf('|');
return resultVal(row,{cat:field.slice(0,i),name:field.slice(i+1)});
}
return row[field];
}
/* 单条条件匹配:包含/=/!= 按字符串,>、>=、<<=、范围 按数值 */
function matchCondition(row,cond){
var raw=getFieldRaw(row,cond.field);
var str=String(raw==null?'':raw);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){
var lo=parseFloat(cond.from);
if(!isNaN(lo)&&n<lo)return false;
}
if(cond.to!==''){
var hi=parseFloat(cond.to);
if(!isNaN(hi)&&n>hi)return false;
}
return true;
}
var c=parseFloat(cond.value);
if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function renderBigTable(){
document.getElementById('bigTable').innerHTML=buildHead()+buildBody();
document.getElementById('pageTotal').textContent='共 '+getDisplayRows().length+' 条';
@@ -571,7 +697,6 @@ function applyFilter(){
showToast('查询完成');
}
function resetFilter(){
document.getElementById('abnormalFilter').value='';
document.getElementById('fName').value='';
document.getElementById('fEmpNo').value='';
document.getElementById('fIdCard').value='';
@@ -579,6 +704,7 @@ function resetFilter(){
showToast('已重置筛选条件');
}
renderBigTable();
ADV_FIELD_OPTS=buildAdvFieldOpts();
/* 体检年份选择控件 */
function pickYear(el){
var dp=el.parentElement,input=dp.parentElement.querySelector('input');
@@ -143,7 +143,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -158,6 +158,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -186,7 +187,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item active">胃肠镜体检统计</a>
</div>
</div>
@@ -196,7 +197,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
+267 -78
View File
@@ -90,7 +90,7 @@
.report-head__meta{display:flex;align-items:center;gap:24px;margin-top:10px;font-size:var(--font-sm);color:var(--text-secondary);flex-wrap:wrap;}
.report-head__meta b{color:var(--text-primary);font-weight:500;}
/* 概览指标卡 */
.overview-cards{display:grid;grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:16px;}
.overview-cards{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:16px;}
.ov-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:18px 12px;text-align:center;}
.ov-card__value{font-size:var(--font-xxl);font-weight:600;color:var(--text-primary);line-height:1.2;}
.ov-card__value--primary{color:var(--primary);}
@@ -98,6 +98,10 @@
.ov-card__value--warning{color:var(--warning);}
.ov-card__value--danger{color:var(--danger);}
.ov-card__label{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:8px;}
.ov-card__trend{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:6px;font-variant-numeric:tabular-nums;}
.trend-good{color:var(--success);}
.trend-bad{color:var(--danger);}
.trend-flat{color:var(--text-placeholder);}
/* 报告卡片 */
.report-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);margin-bottom:16px;overflow:hidden;}
.report-card__header{padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;}
@@ -123,16 +127,12 @@
.bar-row__fill.top1{background:linear-gradient(90deg,#FF9C6E,#FF4D4F);}
.bar-row__fill.top2{background:linear-gradient(90deg,#FFD591,#FAAD14);}
.bar-row__fill.top3{background:linear-gradient(90deg,#95DE64,#52C41A);}
.bar-row__val{width:140px;flex-shrink:0;font-size:var(--font-sm);color:var(--text-secondary);font-variant-numeric:tabular-nums;}
.bar-row__val{width:210px;flex-shrink:0;font-size:var(--font-sm);color:var(--text-secondary);font-variant-numeric:tabular-nums;}
.bar-row__val b{color:var(--text-primary);font-weight:600;}
/* 专项筛查卡片项 */
.screen-item{display:flex;align-items:center;padding:13px 0;border-bottom:1px solid #F0F0F0;}
.screen-item:first-child{padding-top:0;}
.screen-item:last-child{border-bottom:none;padding-bottom:0;}
.screen-item__name{width:180px;flex-shrink:0;font-size:var(--font-sm);color:var(--text-primary);}
.screen-item__val{font-size:var(--font-base);font-weight:600;color:var(--danger);margin-right:6px;font-variant-numeric:tabular-nums;}
.screen-item__unit{font-size:var(--font-sm);color:var(--text-secondary);}
.screen-item__rate{margin-left:auto;font-size:var(--font-sm);color:var(--text-placeholder);font-variant-numeric:tabular-nums;}
.bar-row__delta{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:2px;}
/* 趋势图例 */
.trend-legend{display:flex;gap:18px;font-size:var(--font-xs);color:var(--text-secondary);margin-bottom:10px;}
.trend-legend i{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:5px;vertical-align:-1px;}
</style>
</head>
<body>
@@ -170,7 +170,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item active">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -185,6 +185,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -213,7 +214,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -223,10 +224,22 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</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="post-imc-user.html" class="sidebar-item">用户体检信息</a>
<a href="post-imc-class.html" class="sidebar-item">用户影像文件分类</a>
<a href="post-imc-info.html" class="sidebar-item">用户影像文件详细</a>
<a href="post-imc-log.html" class="sidebar-item">文件处理日志</a>
</div>
</div>
</aside>
<!-- 主内容 -->
@@ -274,6 +287,8 @@
/* 确定性伪随机(0-1),保证同一单位每次打开数据一致 */
function pseudo(i){var x=Math.sin(i*127.1)*43758.5453;return x-Math.floor(x);}
function fmt(n){return String(n).replace(/\B(?=(\d{3})+(?!\d))/g,',');}
function pct(a,b){return b?((a/b)*100).toFixed(1)+'%':'0.0%';}
function pct1(n){return (n==null?'0.0':n.toFixed(1))+'%';}
var UNITS=['公司机关','第一采油厂','第二采油厂','第三采油厂','第八采油厂','第九采油厂','第十一采油厂','采气一厂','水电厂','公用事业公司','运输公司','伴生气综合利用项目部','质量安全环保技术监督中心'];
var HOSPITALS=['宝石花医院','延安医院','西安高新医院','长安心血管医院','宁夏宝石花','CQ总医院'];
@@ -282,6 +297,8 @@ var HOSPITALS=['宝石花医院','延安医院','西安高新医院','长安心
var AGE_BANDS=['35岁以下','35岁至44岁','45岁及以上'];
/* 异常指标排名(名称 / 排名着色 class) */
var INDICATORS=['血脂异常','血压升高','超重(肥胖)','高尿酸','脂肪肝','空腹血糖升高','甲状腺结节','肺部结节','肝功能异常','心电图异常'];
/* 重点指标(管理层关注的核心指标,逐项拆解) */
var KEY_INDICATORS=['血压异常','血脂异常','空腹血糖升高','高尿酸','超重(BMI)'];
/* 疾病(病种)分布,按病种类别分组 */
var DISEASE_CATS=[
{name:'血液及动脉病种',items:['高脂血','高血压','痛风(高尿酸)','动脉斑块']},
@@ -296,24 +313,44 @@ var DISEASE_CATS=[
var state={unitIdx:0, year:'2026', keyword:''};
/* 某单位某年的群体基础数据(确定性生成) */
/* 某单位某年的群体基础数据(确定性生成,显式传单位/年份 */
function yearSeed(idx,year){return idx*13.7+parseInt(year,10)*5.3;}
function unitData(idx){
var s=yearSeed(idx,state.year);
function unitData(idx,year){
var s=yearSeed(idx,year);
var total=500+Math.floor(pseudo(s+1.3)*7500); // 应检人数 500~8000
var examined=Math.round(total*(0.75+pseudo(s+2.1)*0.20)); // 已检人数
var abnormal=Math.round(examined*(0.22+pseudo(s+3.7)*0.38)); // 异常人数
var keyAbn=Math.round(abnormal*(0.25+pseudo(s+4.9)*0.30)); // 重点指标异常人数
var cancerPos=Math.round(total*(0.008+pseudo(s+5.3)*0.022)); // 防癌筛查阳性人数
var ctaAbn=Math.round(examined*(0.012+pseudo(s+6.1)*0.038)); // 冠脉CTA异常人数
var healthy=examined-abnormal; // 未见异常人数
var recheckAdvised=Math.round(abnormal*(0.55+pseudo(s+12.3)*0.25));// 建议复查人数
var recheckDone=Math.round(recheckAdvised*(0.60+pseudo(s+13.1)*0.30)); // 已完成复查人数
var completeCnt=Math.round(examined*(0.90+pseudo(s+14.7)*0.09)); // 数据完整人数
var avgAge=34+Math.round(pseudo(s+15.2)*16); // 平均年龄 34~50
return {
name:UNITS[idx], total:total, examined:examined, abnormal:abnormal, keyAbn:keyAbn,
cancerPos:cancerPos, ctaAbn:ctaAbn, hospital:HOSPITALS[idx%HOSPITALS.length]
name:UNITS[idx], total:total, examined:examined, abnormal:abnormal, keyAbn:keyAbn, healthy:healthy,
recheckAdvised:recheckAdvised, recheckDone:recheckDone, completeCnt:completeCnt, avgAge:avgAge,
hospital:HOSPITALS[idx%HOSPITALS.length]
};
}
/* 汇总数值统计(含各类率,用于同比对比) */
function computeStats(idx,year){
var d=unitData(idx,year);
var ag=ageGender(idx,year);
var male=ag.reduce(function(a,r){return a+r.male;},0);
var female=ag.reduce(function(a,r){return a+r.female;},0);
return {
total:d.total, examined:d.examined, attendRate:d.examined/d.total*100,
abnormal:d.abnormal, detectRate:d.abnormal/d.examined*100, keyAbn:d.keyAbn, healthy:d.healthy,
recheckAdvised:d.recheckAdvised, recheckDone:d.recheckDone,
recheckRate:d.recheckAdvised?d.recheckDone/d.recheckAdvised*100:0,
completeCnt:d.completeCnt, completeRate:d.examined?d.completeCnt/d.examined*100:0,
avgAge:d.avgAge, male:male, female:female
};
}
/* 年龄×性别分布 */
function ageGender(d){
var s=yearSeed(state.unitIdx,state.year);
function ageGender(idx,year){
var d=unitData(idx,year);
var s=yearSeed(idx,year);
var a35=Math.round(d.examined*(0.26+pseudo(s+7.7)*0.10));
var a44=Math.round(d.examined*(0.32+pseudo(s+8.3)*0.08));
var a45=d.examined-a35-a44;
@@ -325,8 +362,9 @@ function ageGender(d){
];
}
/* 异常指标排名(人数近似递减) */
function indicatorRank(d){
var s=yearSeed(state.unitIdx,state.year);
function indicatorRank(idx,year){
var d=unitData(idx,year);
var s=yearSeed(idx,year);
var list=INDICATORS.map(function(name,ki){
var share=(0.05+pseudo(s+ki*2.9)*0.16)*(INDICATORS.length-ki)/INDICATORS.length;
return {name:name, cnt:Math.max(3,Math.round(d.examined*share))};
@@ -334,9 +372,41 @@ function indicatorRank(d){
list.sort(function(a,b){return b.cnt-a.cnt;});
return list;
}
/* 重点指标异常拆解(人次,同一人可有多项) */
function keyIndicatorDetail(idx,year){
var d=unitData(idx,year);
var s=yearSeed(idx,year);
var list=KEY_INDICATORS.map(function(name,ki){
var share=(0.10+pseudo(s+ki*3.5+0.7)*0.15)*(KEY_INDICATORS.length-ki)/KEY_INDICATORS.length;
return {name:name, cnt:Math.max(1,Math.round(d.keyAbn*share))};
});
list.sort(function(a,b){return b.cnt-a.cnt;});
return list;
}
/* 异常率×年龄/性别 交叉(异常人数归一化到总体异常人数) */
function ageGenderAbnormal(idx,year){
var d=unitData(idx,year);
var ag=ageGender(idx,year);
var s=yearSeed(idx,year);
var rows=ag.map(function(r,bi){
var mAbn=Math.round(r.male*(0.15+pseudo(s+bi*4.7+2.3)*0.35));
var fAbn=Math.round(r.female*(0.12+pseudo(s+bi*4.7+3.9)*0.32));
return {band:r.band, male:r.male, female:r.female, maleAbn:mAbn, femaleAbn:fAbn};
});
var sumAbn=rows.reduce(function(a,r){return a+r.maleAbn+r.femaleAbn;},0);
if(sumAbn>0){var k=d.abnormal/sumAbn;rows.forEach(function(r){r.maleAbn=Math.round(r.maleAbn*k);r.femaleAbn=Math.round(r.femaleAbn*k);});}
rows.forEach(function(r){
r.total=r.male+r.female; r.totalAbn=r.maleAbn+r.femaleAbn;
r.maleRate=r.male?r.maleAbn/r.male*100:0;
r.femaleRate=r.female?r.femaleAbn/r.female*100:0;
r.rate=r.total?r.totalAbn/r.total*100:0;
});
return rows;
}
/* 疾病(病种)分布:按病种类别分组,类别与病种均按人数降序 */
function diseaseDist(d){
var s=yearSeed(state.unitIdx,state.year);
function diseaseDist(idx,year){
var d=unitData(idx,year);
var s=yearSeed(idx,year);
var groups=DISEASE_CATS.map(function(cat,ci){
var items=cat.items.map(function(name,ii){
var share=(0.018+pseudo(s+ci*3.3+ii*1.9)*0.11)*(DISEASE_CATS.length-ci)/DISEASE_CATS.length;
@@ -348,6 +418,25 @@ function diseaseDist(d){
groups.sort(function(a,b){return b.items[0].cnt-a.items[0].cnt;});
return groups;
}
/* 体检计划周期(随年份与单位动态) */
function planPeriod(idx,year){
var s=yearSeed(idx,year);
var sm=4+Math.floor(pseudo(s+19.1)*3);
var sd=5+Math.floor(pseudo(s+19.2)*20);
var em=sm+2, ed=10+Math.floor(pseudo(s+19.3)*20);
function p(n){return (n<10?'0':'')+n;}
return year+'-'+p(sm)+'-'+p(sd)+' 至 '+year+'-'+p(em)+'-'+p(ed);
}
/* 同比变化(带方向着色):goodDir=up/down 表示"升是好事/降是好事"flat 中性 */
function trendSpan(numDelta,isRate,goodDir,unit){
if(Math.abs(numDelta)<0.05)return '<span class="trend-flat">— 持平</span>';
var up=numDelta>0, arrow=up?'↑':'↓';
var txt=(up?'+':'')+(isRate?Math.abs(numDelta).toFixed(1)+'pp':fmt(Math.abs(Math.round(numDelta)))+(unit||''));
var cls='trend-flat';
if(goodDir==='up')cls=up?'trend-good':'trend-bad';
else if(goodDir==='down')cls=up?'trend-bad':'trend-good';
return '<span class="'+cls+'">'+arrow+' '+txt+'</span>';
}
/* ============ 左侧单位列表 ============ */
function renderUnitList(){
@@ -356,7 +445,7 @@ function renderUnitList(){
if(!list.length){box.innerHTML='<div class="unit-list__empty">未找到匹配单位</div>';return;}
var html=list.map(function(u){
var idx=UNITS.indexOf(u);
var d=unitData(idx);
var d=unitData(idx,state.year);
return '<div class="unit-list__item'+(idx===state.unitIdx?' active':'')+'" onclick="selectUnit('+idx+')">'
+'<div class="unit-list__name">'+u+'</div>'
+'<div class="unit-list__meta"><span>应检 '+fmt(d.total)+'</span><span>检出率 '+pct(d.abnormal,d.examined)+'</span></div>'
@@ -366,107 +455,207 @@ function renderUnitList(){
}
function filterUnits(kw){state.keyword=kw.trim();renderUnitList();}
function selectUnit(idx){state.unitIdx=idx;renderUnitList();renderReport();}
function pct(a,b){return b?((a/b)*100).toFixed(1)+'%':'0.0%';}
/* ============ 近三年趋势图(受检率 / 检出率 折线) ============ */
function trendChartSvg(idx,years){
var attend=[],detect=[];
years.forEach(function(y){var s=computeStats(idx,y);attend.push(s.attendRate);detect.push(s.detectRate);});
var all=attend.concat(detect);
var min=Math.floor(Math.min.apply(null,all))-3, max=Math.ceil(Math.max.apply(null,all))+3;
var W=560,H=150,padL=38,padR=14,padT=14,padB=26;
var iw=W-padL-padR, ih=H-padT-padB;
function x(i){return padL+(years.length===1?iw/2:iw*i/(years.length-1));}
function y(v){return padT+ih*(1-(v-min)/(max-min));}
var grid='';
for(var g=0;g<=4;g++){
var gv=min+(max-min)*g/4, gy=y(gv).toFixed(1);
grid+='<line x1="'+padL+'" y1="'+gy+'" x2="'+(W-padR)+'" y2="'+gy+'" stroke="#F0F0F0"/>'
+'<text x="'+(padL-6)+'" y="'+(parseFloat(gy)+4).toFixed(1)+'" text-anchor="end" font-size="10" fill="#999">'+gv.toFixed(0)+'</text>';
}
function pline(arr,color){return '<polyline points="'+arr.map(function(v,i){return x(i).toFixed(1)+','+y(v).toFixed(1);}).join(' ')+'" fill="none" stroke="'+color+'" stroke-width="2"/>';}
var dots='';
attend.forEach(function(v,i){dots+='<circle cx="'+x(i).toFixed(1)+'" cy="'+y(v).toFixed(1)+'" r="3.5" fill="#1890FF"/>';});
detect.forEach(function(v,i){dots+='<circle cx="'+x(i).toFixed(1)+'" cy="'+y(v).toFixed(1)+'" r="3.5" fill="#FAAD14"/>';});
var labels='';
years.forEach(function(yr,i){
var active=yr===state.year;
labels+='<text x="'+x(i).toFixed(1)+'" y="'+(H-8)+'" text-anchor="middle" font-size="11" '+(active?'font-weight="600" fill="#1890FF"':'fill="#666"')+'>'+yr+'</text>';
});
return '<svg viewBox="0 0 '+W+' '+H+'" style="width:100%;height:auto;">'+grid
+pline(attend,'#1890FF')+pline(detect,'#FAAD14')+dots+labels+'</svg>';
}
/* ============ 右侧群体分析报告 ============ */
function renderReport(){
var d=unitData(state.unitIdx);
var ag=ageGender(d);
var rank=indicatorRank(d);
var dist=diseaseDist(d);
var maxCnt=rank[0].cnt;
var idx=state.unitIdx, year=state.year;
var prevYear=String(parseInt(year,10)-1);
var d=unitData(idx,year);
var cur=computeStats(idx,year);
var prev=computeStats(idx,prevYear);
var rank=indicatorRank(idx,year);
var rankPrevMap={}; indicatorRank(idx,prevYear).forEach(function(r){rankPrevMap[r.name]={cnt:r.cnt,rate:r.cnt/unitData(idx,prevYear).examined*100};});
var keyList=keyIndicatorDetail(idx,year);
var keyPrevMap={}; keyIndicatorDetail(idx,prevYear).forEach(function(k){keyPrevMap[k.name]=k.cnt;});
var dist=diseaseDist(idx,year);
var distPrevMap={}; diseaseDist(idx,prevYear).forEach(function(g){g.items.forEach(function(x){distPrevMap[x.name]=x.cnt;});});
var cross=ageGenderAbnormal(idx,year);
var years=[String(parseInt(year,10)-2),prevYear,year];
var panel=document.getElementById('report-panel');
/* 概览指标卡 */
/* 概览指标卡(含同比) */
var cards=[
['应检人数',fmt(d.total),''],
['已检人数',fmt(d.examined),'ov-card__value--primary'],
['受检率',pct(d.examined,d.total),'ov-card__value--success'],
['检出率',pct(d.abnormal,d.examined),'ov-card__value--warning'],
['异常人数',fmt(d.abnormal),'ov-card__value--danger'],
['重点指标异常',fmt(d.keyAbn),'ov-card__value--danger']
['应检人数',fmt(cur.total),'',cur.total-prev.total,false,'flat','人'],
['已检人数',fmt(cur.examined),'ov-card__value--primary',cur.examined-prev.examined,false,'flat','人'],
['受检率',pct1(cur.attendRate),'ov-card__value--success',cur.attendRate-prev.attendRate,true,'up',''],
['检出率',pct1(cur.detectRate),'ov-card__value--warning',cur.detectRate-prev.detectRate,true,'down',''],
['异常人数',fmt(cur.abnormal),'ov-card__value--danger',cur.abnormal-prev.abnormal,false,'down','人'],
['重点指标异常',fmt(cur.keyAbn),'ov-card__value--danger',cur.keyAbn-prev.keyAbn,false,'down','人'],
['复查率',pct1(cur.recheckRate),'',cur.recheckRate-prev.recheckRate,true,'up',''],
['数据完整率',pct1(cur.completeRate),'',cur.completeRate-prev.completeRate,true,'up',''],
['平均年龄',cur.avgAge+'岁','',cur.avgAge-prev.avgAge,false,'flat','岁'],
['未见异常',fmt(cur.healthy),'ov-card__value--success',cur.healthy-prev.healthy,false,'up','人']
].map(function(c){
return '<div class="ov-card"><div class="ov-card__value '+c[2]+'">'+c[1]+'</div><div class="ov-card__label">'+c[0]+'</div></div>';
return '<div class="ov-card"><div class="ov-card__value '+c[2]+'">'+c[1]+'</div><div class="ov-card__label">'+c[0]+'</div><div class="ov-card__trend">较上年 '+trendSpan(c[3],c[4],c[5],c[6])+'</div></div>';
}).join('');
/* 年龄性别分布表 */
var agRows=ag.map(function(r){
return '<tr><td>'+r.band+'</td><td class="num">'+fmt(r.male)+'</td><td class="num">'+fmt(r.female)+'</td><td class="num">'+fmt(r.male+r.female)+'</td><td class="rate">'+pct(r.male+r.female,d.examined)+'</td></tr>';
}).join('');
var agTotal=ag.reduce(function(a,r){return a+r.male+r.female;},0);
var agMale=ag.reduce(function(a,r){return a+r.male;},0);
var agFemale=ag.reduce(function(a,r){return a+r.female;},0);
/* 异常指标条形图 */
/* 异常指标条形图(带上年对照) */
var maxCnt=rank[0].cnt;
var bars=rank.map(function(r,i){
var cls=i===0?'top1':(i===1?'top2':(i===2?'top3':''));
var pv=rankPrevMap[r.name]||{cnt:0,rate:0};
var delta=r.cnt-pv.cnt;
return '<div class="bar-row">'
+'<div class="bar-row__label">'+r.name+'</div>'
+'<div class="bar-row__track"><div class="bar-row__fill '+cls+'" style="width:'+(r.cnt/maxCnt*100).toFixed(1)+'%;"></div></div>'
+'<div class="bar-row__val"><b>'+fmt(r.cnt)+'</b> 人 · '+pct(r.cnt,d.examined)+'</div>'
+'<div class="bar-row__val"><div><b>'+fmt(r.cnt)+'</b> 人 · '+pct(r.cnt,d.examined)+'</div><div class="bar-row__delta">上年 '+pct1(pv.rate)+' · '+trendSpan(delta,false,'down','人')+'</div></div>'
+'</div>';
}).join('');
/* 疾病分布表 */
/* 重点指标异常拆解 */
var keyRows=keyList.map(function(k){
var pv=keyPrevMap[k.name]||0;
return '<tr><td>'+k.name+'</td><td class="num">'+fmt(k.cnt)+'</td><td class="rate">'+pct(k.cnt,d.examined)+'</td><td>'+trendSpan(k.cnt-pv,false,'down','人')+'</td></tr>';
}).join('');
/* 异常率×年龄/性别 交叉 */
var crossRows=cross.map(function(r){
return '<tr><td>'+r.band+'</td><td class="rate">'+pct1(r.maleRate)+'</td><td class="rate">'+pct1(r.femaleRate)+'</td><td class="rate">'+pct1(r.rate)+'</td><td class="num">'+fmt(r.totalAbn)+'</td></tr>';
}).join('');
var crossTotalAbn=cross.reduce(function(a,r){return a+r.totalAbn;},0);
var crossMaleAbn=cross.reduce(function(a,r){return a+r.maleAbn;},0);
var crossFemaleAbn=cross.reduce(function(a,r){return a+r.femaleAbn;},0);
var crossMale=cross.reduce(function(a,r){return a+r.male;},0);
var crossFemale=cross.reduce(function(a,r){return a+r.female;},0);
var crossMaleRate=crossMale?crossMaleAbn/crossMale*100:0;
var crossFemaleRate=crossFemale?crossFemaleAbn/crossFemale*100:0;
/* 疾病分布(带较上年) */
var distRows='';
dist.forEach(function(g){
distRows+='<tr class="cat-row"><td colspan="4">'+g.cat+'</td></tr>';
distRows+='<tr class="cat-row"><td colspan="5">'+g.cat+'</td></tr>';
g.items.forEach(function(x){
distRows+='<tr><td></td><td>'+x.name+'</td><td class="num">'+fmt(x.cnt)+'</td><td class="rate">'+pct(x.cnt,d.examined)+'</td></tr>';
var pv=distPrevMap[x.name]||0;
distRows+='<tr><td></td><td>'+x.name+'</td><td class="num">'+fmt(x.cnt)+'</td><td class="rate">'+pct(x.cnt,d.examined)+'</td><td>'+trendSpan(x.cnt-pv,false,'down','人')+'</td></tr>';
});
});
/* 近三年趋势 */
var trendLegend='<div class="trend-legend"><span><i style="background:#1890FF"></i>受检率</span><span><i style="background:#FAAD14"></i>检出率</span></div>';
panel.innerHTML=
'<div class="report-head">'
+'<div class="report-head__title">'+d.name+' · 群体分析报告</div>'
+'<div class="report-head__meta">'
+'<span>体检年份:<b>'+state.year+'</b></span>'
+'<span>体检年份:<b>'+year+'</b></span>'
+'<span>承检医院:<b>'+d.hospital+'</b></span>'
+'<span>体检计划:<b>2026-06-23 至 2026-08-31</b></span>'
+'<span>体检计划:<b>'+planPeriod(idx,year)+'</b></span>'
+'<span>同比基准:<b>'+prevYear+' 年</b></span>'
+'</div></div>'
+'<div class="overview-cards">'+cards+'</div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">年龄与性别分布</div><div class="report-card__sub">按已检人数统计</div></div>'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">近三年趋势</div><div class="report-card__sub">受检率 / 检出率</div></div>'
+'<div class="report-card__body">'+trendLegend+trendChartSvg(idx,years)+'</div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">异常率 × 年龄 / 性别</div><div class="report-card__sub">各年龄段、性别异常检出率</div></div>'
+'<div class="report-card__body"><table class="dist-table">'
+'<thead><tr><th>年龄区间</th><th></th><th></th><th>合计</th><th>占比</th></tr></thead>'
+'<tbody>'+agRows
+'<tr><td style="font-weight:600;">合计</td><td class="num" style="font-weight:600;">'+fmt(agMale)+'</td><td class="num" style="font-weight:600;">'+fmt(agFemale)+'</td><td class="num" style="font-weight:600;">'+fmt(agTotal)+'</td><td class="rate">100%</td></tr>'
+'<thead><tr><th>年龄区间</th><th>性异常率</th><th>性异常率</th><th>合计异常率</th><th>异常人数</th></tr></thead>'
+'<tbody>'+crossRows
+'<tr><td style="font-weight:600;">合计</td><td class="rate" style="font-weight:600;">'+pct1(crossMaleRate)+'</td><td class="rate" style="font-weight:600;">'+pct1(crossFemaleRate)+'</td><td class="rate" style="font-weight:600;">'+pct1(cur.detectRate)+'</td><td class="num" style="font-weight:600;">'+fmt(crossTotalAbn)+'</td></tr>'
+'</tbody></table></div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">异常指标排名</div><div class="report-card__sub">按检出人数排序</div></div>'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">异常指标排名</div><div class="report-card__sub">按检出人数排序 · 附上年对照</div></div>'
+'<div class="report-card__body">'+bars+'</div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">疾病(病种)分布</div><div class="report-card__sub">按病种字典归类</div></div>'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">重点指标异常拆解</div><div class="report-card__sub">同一人可有多项异常</div></div>'
+'<div class="report-card__body"><table class="dist-table">'
+'<thead><tr><th style="width:24px;"></th><th>病种</th><th>人数</th><th>占比</th></tr></thead>'
+'<tbody>'+distRows+'</tbody></table></div></div>'
+'<thead><tr><th>重点指标</th><th>异常人数</th><th>检出率</th><th>较上年</th></tr></thead>'
+'<tbody>'+keyRows+'</tbody></table></div></div>'
+'<div class="report-card">'
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">专项筛查结果</div></div>'
+'<div class="report-card__body">'
+'<div class="screen-item"><div class="screen-item__name">防癌筛查 · Septin9</div><span class="screen-item__val">'+d.cancerPos+'</span><span class="screen-item__unit">阳性</span><span class="screen-item__rate">占应检 '+pct(d.cancerPos,d.total)+'</span></div>'
+'<div class="screen-item"><div class="screen-item__name">防癌筛查 · CA724</div><span class="screen-item__val">'+Math.max(1,Math.round(d.cancerPos*0.8))+'</span><span class="screen-item__unit">人阳性</span><span class="screen-item__rate">占应检 '+pct(Math.round(d.cancerPos*0.8),d.total)+'</span></div>'
+'<div class="screen-item"><div class="screen-item__name">冠脉CTA</div><span class="screen-item__val">'+d.ctaAbn+'</span><span class="screen-item__unit">人异常</span><span class="screen-item__rate">占已检 '+pct(d.ctaAbn,d.examined)+'</span></div>'
+'</div></div>';
+'<div class="report-card__header"><div class="report-card__bar"></div><div class="report-card__title">疾病(病种)分布</div><div class="report-card__sub">按病种字典归类 · 附较上年</div></div>'
+'<div class="report-card__body"><table class="dist-table">'
+'<thead><tr><th style="width:24px;"></th><th>病种</th><th></th><th>占比</th><th>较上年</th></tr></thead>'
+'<tbody>'+distRows+'</tbody></table></div></div>';
}
/* 导出群体报告(CSVExcel 兼容) */
function exportReport(){
var d=unitData(state.unitIdx);
var rank=indicatorRank(d);
var dist=diseaseDist(d);
var rows=[['单位群体报告',''],['单位',d.name],['体检年份',state.year],['承检医院',d.hospital],[],
['应检人数',d.total],['已检人数',d.examined],['受检率',pct(d.examined,d.total)],['检出率',pct(d.abnormal,d.examined)],['异常人数',d.abnormal],['重点指标异常人数',d.keyAbn],[],
['异常指标排名','人数'],[]].concat(rank.map(function(r){return [r.name,r.cnt];})).concat([[],['疾病(病种)分布','人数']]);
dist.forEach(function(g){g.items.forEach(function(x){rows.push([g.cat+' · '+x.name,x.cnt]);});});
var idx=state.unitIdx, year=state.year;
var prevYear=String(parseInt(year,10)-1);
var d=unitData(idx,year), pd=unitData(idx,prevYear);
var cur=computeStats(idx,year), prev=computeStats(idx,prevYear);
var rank=indicatorRank(idx,year);
var rankPrevMap={}; indicatorRank(idx,prevYear).forEach(function(r){rankPrevMap[r.name]=r.cnt;});
var keyList=keyIndicatorDetail(idx,year);
var keyPrevMap={}; keyIndicatorDetail(idx,prevYear).forEach(function(k){keyPrevMap[k.name]=k.cnt;});
var dist=diseaseDist(idx,year);
var distPrevMap={}; diseaseDist(idx,prevYear).forEach(function(g){g.items.forEach(function(x){distPrevMap[x.name]=x.cnt;});});
var cross=ageGenderAbnormal(idx,year);
var years=[String(parseInt(year,10)-2),prevYear,year];
var rows=[];
rows.push(['单位群体报告',''],['单位',d.name],['体检年份',year],['承检医院',d.hospital],['体检计划',planPeriod(idx,year)],['同比基准',prevYear+'年'],[]);
rows.push(['概览指标','今年','上年','较上年']);
rows.push(['应检人数',d.total,pd.total,d.total-pd.total]);
rows.push(['已检人数',d.examined,pd.examined,d.examined-pd.examined]);
rows.push(['受检率(%)',cur.attendRate.toFixed(1),prev.attendRate.toFixed(1),(cur.attendRate-prev.attendRate).toFixed(1)]);
rows.push(['检出率(%)',cur.detectRate.toFixed(1),prev.detectRate.toFixed(1),(cur.detectRate-prev.detectRate).toFixed(1)]);
rows.push(['异常人数',d.abnormal,pd.abnormal,d.abnormal-pd.abnormal]);
rows.push(['重点指标异常人数',d.keyAbn,pd.keyAbn,d.keyAbn-pd.keyAbn]);
rows.push(['复查率(%)',cur.recheckRate.toFixed(1),prev.recheckRate.toFixed(1),(cur.recheckRate-prev.recheckRate).toFixed(1)]);
rows.push(['数据完整率(%)',cur.completeRate.toFixed(1),prev.completeRate.toFixed(1),(cur.completeRate-prev.completeRate).toFixed(1)]);
rows.push(['平均年龄',cur.avgAge,prev.avgAge,cur.avgAge-prev.avgAge]);
rows.push(['未见异常人数',d.healthy,pd.healthy,d.healthy-pd.healthy]);
rows.push([]);
rows.push(['异常率×年龄/性别','男性异常率(%)','女性异常率(%)','合计异常率(%)','异常人数']);
cross.forEach(function(r){rows.push([r.band,r.maleRate.toFixed(1),r.femaleRate.toFixed(1),r.rate.toFixed(1),r.totalAbn]);});
rows.push([]);
rows.push(['异常指标排名','人数','检出率','上年人数','较上年']);
rank.forEach(function(r){var pv=rankPrevMap[r.name]||0;rows.push([r.name,r.cnt,pct(r.cnt,d.examined),pv,r.cnt-pv]);});
rows.push([]);
rows.push(['重点指标异常','异常人数','检出率','上年人数','较上年']);
keyList.forEach(function(k){var pv=keyPrevMap[k.name]||0;rows.push([k.name,k.cnt,pct(k.cnt,d.examined),pv,k.cnt-pv]);});
rows.push([]);
rows.push(['疾病(病种)分布','人数','占比','上年人数','较上年']);
dist.forEach(function(g){g.items.forEach(function(x){var pv=distPrevMap[x.name]||0;rows.push([g.cat+' · '+x.name,x.cnt,pct(x.cnt,d.examined),pv,x.cnt-pv]);});});
rows.push([]);
rows.push(['近三年趋势','受检率(%)','检出率(%)']);
years.forEach(function(y){var s=computeStats(idx,y);rows.push([y+'年',s.attendRate.toFixed(1),s.detectRate.toFixed(1)]);});
var csv=rows.map(function(r){return r.map(function(c){return '"'+String(c).replace(/"/g,'""')+'"';}).join(',');}).join('\r\n');
var a=document.createElement('a');
a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv;charset=utf-8'}));
a.download=d.name+'_'+state.year+'年_群体分析报告.csv';
a.download=d.name+'_'+year+'年_群体分析报告.csv';
document.body.appendChild(a);a.click();document.body.removeChild(a);
setTimeout(function(){URL.revokeObjectURL(a.href);},1000);
}
@@ -126,7 +126,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -142,6 +142,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item active">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -170,7 +171,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -180,7 +181,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -153,7 +153,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item active">大病人员信息</a>
@@ -168,6 +168,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -196,7 +197,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -206,7 +207,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -145,6 +145,7 @@
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
<a href="post-indicator-notify.html" class="sidebar-item">指标异常通知</a>
@@ -157,6 +158,8 @@
<div class="sidebar-subgroup__body">
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -142,6 +142,7 @@
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
<a href="post-indicator-notify.html" class="sidebar-item">指标异常通知</a>
@@ -154,6 +155,8 @@
<div class="sidebar-subgroup__body">
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -143,6 +143,7 @@
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
<a href="post-indicator-notify.html" class="sidebar-item">指标异常通知</a>
@@ -155,6 +156,8 @@
<div class="sidebar-subgroup__body">
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -171,6 +171,7 @@
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
<a href="post-indicator-notify.html" class="sidebar-item">指标异常通知</a>
@@ -183,6 +184,8 @@
<div class="sidebar-subgroup__body">
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -125,7 +125,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -140,6 +140,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -168,7 +169,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -178,7 +179,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
+4 -3
View File
@@ -117,7 +117,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -132,6 +132,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item active">医院体检项目统计</a>
@@ -160,7 +161,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -170,7 +171,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -150,7 +150,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -165,6 +165,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -193,7 +194,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -203,7 +204,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -152,7 +152,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item active">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item active">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -168,6 +168,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -196,7 +197,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -206,7 +207,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -203,7 +203,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -218,6 +218,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -246,7 +247,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item active">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item active">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -256,7 +257,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -136,7 +136,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -151,6 +151,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -179,7 +180,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -189,7 +190,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -126,7 +126,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -142,6 +142,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -170,7 +171,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -180,7 +181,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -145,7 +145,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -161,6 +161,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item active">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -189,7 +190,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -199,7 +200,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -156,7 +156,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -172,6 +172,7 @@
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -200,7 +201,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -210,7 +211,7 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</a>
</div>
</div>
@@ -137,7 +137,7 @@
常规体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-employee.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-employee.html" class="sidebar-item">常规体检报告</a>
<a href="post-group-report.html" class="sidebar-item">单位群体报告</a>
<a href="post-employee-master.html" class="sidebar-item">员工体检大表</a>
<a href="post-illness-personnel.html" class="sidebar-item">大病人员信息</a>
@@ -152,6 +152,7 @@
<a href="post-unit-count.html" class="sidebar-item">单位体检人数统计</a>
<a href="post-unit-cost-stat.html" class="sidebar-item">单位体检费用统计</a>
<a href="post-unit-item-stat.html" class="sidebar-item active">单位体检项目统计</a>
<a href="post-result-comprehensive-stat.html" class="sidebar-item">项目结果综合统计</a>
<a href="post-hospital-count.html" class="sidebar-item">医院体检人数统计</a>
<a href="post-doctor-count.html" class="sidebar-item">医生检查人数统计</a>
<a href="post-item-stat.html" class="sidebar-item">医院体检项目统计</a>
@@ -180,7 +181,7 @@
胃肠镜体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-report-gastro.html" class="sidebar-item">员工体检报告</a>
<a href="post-report-gastro.html" class="sidebar-item">胃肠镜体检报告</a>
<a href="post-gastro-stat.html" class="sidebar-item">胃肠镜体检统计</a>
</div>
</div>
@@ -190,10 +191,22 @@
冠脉CTA体检
</div>
<div class="sidebar-subgroup__body">
<a href="post-cta-report.html" class="sidebar-item">员工体检报告</a>
<a href="post-cta-report.html" class="sidebar-item">冠脉CTA体检报告</a>
<a href="post-cta-stat.html" class="sidebar-item">冠脉CTA体检统计</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="post-imc-user.html" class="sidebar-item">用户体检信息</a>
<a href="post-imc-class.html" class="sidebar-item">用户影像文件分类</a>
<a href="post-imc-info.html" class="sidebar-item">用户影像文件详细</a>
<a href="post-imc-log.html" class="sidebar-item">文件处理日志</a>
</div>
</div>
</aside>
<!-- 主内容 -->
@@ -154,6 +154,25 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -290,20 +309,20 @@
<option>请选择</option><option>泾河医院</option><option>庆阳医院</option><option>延安医院</option><option>兴隆园医院</option><option>宁夏宝石花医院</option>
</select>
</div>
<div class="filter-item">
<label>性别:</label>
<select class="filter-select" style="min-width:90px;">
<option>请选择</option><option></option><option></option><option>通用</option>
</select>
</div>
<div class="filter-item">
<label>年龄区间:</label>
<select class="filter-select">
<option>请选择</option><option>年龄&lt;35</option><option>35≤年龄&lt;40</option><option>40≤年龄&lt;45</option><option>年龄≥45</option>
</select>
</div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作按钮 -->
@@ -1041,6 +1060,110 @@ document.querySelectorAll('.year-picker>input').forEach(function(inp){
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_ROW_SELECTOR='.table-card__content tbody tr';
var ADV_FIELDS=[
{key:'hospital',label:'医院名称',idx:1},{key:'year',label:'体检年份',idx:2},{key:'tplName',label:'模板名称',idx:3},
{key:'gender',label:'性别',idx:4},{key:'ageRange',label:'年龄区间',idx:5},{key:'discountType',label:'折扣类型',idx:6},
{key:'discountValue',label:'折扣数值(元)',idx:7},{key:'discountPercent',label:'折扣百分比(%',idx:8}
];
var ADV_FIELD_OPTS=ADV_FIELDS.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(key){for(var i=0;i<ADV_FIELDS.length;i++){if(ADV_FIELDS[i].key===key)return ADV_FIELDS[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond){
var f=fieldByKey(cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(){
var conds=readAdvancedConditions();
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i])){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
+136 -24
View File
@@ -146,6 +146,25 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -282,32 +301,20 @@
<option>请选择</option><option>第一采油厂</option><option>第二采油厂</option><option>第三采油厂</option>
</select>
</div>
<div class="filter-item">
<label>体检医院:</label>
<select class="filter-select">
<option>请选择</option><option>泾河医院</option><option>庆阳医院</option><option>延安医院</option><option>兴隆园医院</option><option>宁夏宝石花医院</option>
</select>
</div>
<div class="filter-item">
<label>性别:</label>
<select class="filter-select">
<option>请选择</option><option></option><option></option><option>通用</option>
</select>
</div>
<div class="filter-item">
<label>年龄范围:</label>
<select class="filter-select">
<option>请选择</option><option>年龄&lt;35</option><option>35≤年龄&lt;40</option><option>40≤年龄&lt;45</option><option>年龄≥45</option>
</select>
</div>
<div class="filter-item">
<label>费用审核状态:</label>
<select class="filter-select">
<option>请选择</option><option>审核通过</option><option>待审核</option><option>已拒绝</option><option>未提交</option>
</select>
</div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作按钮 -->
@@ -992,6 +999,111 @@ document.querySelectorAll('.year-picker>input').forEach(function(inp){
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_ROW_SELECTOR='.table-card__content tbody tr';
var ADV_FIELDS=[
{key:'comboName',label:'项目组合名称',idx:2},{key:'hospital',label:'承检医院',idx:3},{key:'unit',label:'单位',idx:4},
{key:'year',label:'体检年份',idx:5},{key:'gender',label:'性别',idx:6},{key:'ageRange',label:'年龄范围',idx:7},
{key:'declaredFee',label:'本次提交费用(元)',idx:8},{key:'auditStatus',label:'费用审核状态',idx:9},
{key:'effectiveFee',label:'必检项目费用(已生效)',idx:10},{key:'genStatus',label:'生成状态',idx:11},{key:'genTime',label:'生成时间',idx:12}
];
var ADV_FIELD_OPTS=ADV_FIELDS.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(key){for(var i=0;i<ADV_FIELDS.length;i++){if(ADV_FIELDS[i].key===key)return ADV_FIELDS[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond){
var f=fieldByKey(cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(){
var conds=readAdvancedConditions();
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i])){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
+139 -25
View File
@@ -153,6 +153,25 @@
.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;}
/* 高级查询 */
.adv-panel{background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:none;}
.adv-panel.open{display:block;}
.adv-panel__head{font-size:var(--font-base);font-weight:600;margin-bottom:12px;}
#advConditions{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px;}
.adv-cond-row{display:flex;align-items:center;gap:8px;margin-bottom:0;min-width:0;}
.adv-field{min-width:140px;flex:1;}
.adv-op{min-width:72px;flex:none;}
.adv-value-wrap{display:flex;align-items:center;gap:8px;flex:1;min-width:0;max-width:none;}
.adv-value{flex:1;min-width:0;}
.adv-range-sep{color:var(--text-secondary);flex-shrink:0;}
.adv-del{width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;color:var(--text-secondary);cursor:pointer;flex-shrink:0;font-size:var(--font-sm);line-height:1;}
.adv-del:hover{color:var(--danger);border-color:var(--danger);}
.adv-actions{display:flex;gap:12px;margin-top:2px;}
.adv-arrow{font-size:10px;}
/* 轻提示 */
.toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-16px);background:#fff;box-shadow:var(--shadow-md);border-radius:var(--radius-sm);padding:10px 20px;font-size:var(--font-sm);color:var(--text-primary);opacity:0;pointer-events:none;transition:all .25s;z-index:1000;display:flex;align-items:center;gap:8px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
.toast svg{flex-shrink:0;}
</style>
</head>
<body>
@@ -300,33 +319,20 @@
<label>身份证号:</label>
<input class="filter-input" type="text" placeholder="请输入" style="min-width:160px;">
</div>
<div class="filter-item">
<label>性别:</label>
<select class="filter-select" style="min-width:120px;">
<option>请选择性别</option>
</select>
</div>
<div class="filter-item">
<label>年龄范围:</label>
<select class="filter-select">
<option>请选择</option>
<option>年龄&lt;35</option>
<option>35≤年龄&lt;40</option>
<option>40≤年龄&lt;45</option>
<option>年龄≥45</option>
</select>
</div>
<div class="filter-item">
<label>防癌类型:</label>
<select class="filter-select">
<option>请选择</option>
<option>Septin9</option>
<option>CA724</option>
<option>两项都有</option>
</select>
</div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
<button class="btn btn-default" id="advToggle" onclick="toggleAdvanced()">高级查询 <span class="adv-arrow"></span></button>
</div>
<!-- 高级查询 -->
<div class="adv-panel" id="advPanel">
<div class="adv-panel__head">高级查询</div>
<div id="advConditions"></div>
<div class="adv-actions">
<button class="btn btn-default" onclick="addAdvCondition()">+ 添加条件</button>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="clearAdvanced()">清空</button>
</div>
</div>
<!-- 操作按钮 -->
@@ -827,6 +833,114 @@ document.querySelectorAll('.year-picker>input').forEach(function(inp){
document.addEventListener('click',function(){
document.querySelectorAll('.year-picker__dropdown.open').forEach(function(d){d.classList.remove('open');});
});
/* ============ 高级查询 ============ */
var ADV_OPS=['包含','=','!=','>','>=','<','<=','范围'];
var ADV_ROW_SELECTOR='.table-card__content tbody tr';
var ADV_FIELDS=[
{key:'name',label:'姓名',idx:1},{key:'empNo',label:'员工编号',idx:2},{key:'gender',label:'性别',idx:3},
{key:'age',label:'年龄',idx:4},{key:'ageRange',label:'年龄范围',idx:5},{key:'idCard',label:'身份证号',idx:6},
{key:'phone',label:'手机号',idx:7},{key:'unit',label:'单位',idx:8},{key:'dept',label:'部门',idx:9},
{key:'year',label:'体检年份',idx:10},{key:'plan',label:'体检计划',idx:11},{key:'planUnit',label:'体检计划单位',idx:12},
{key:'cancerType',label:'防癌筛查类型',idx:13},{key:'cancerYear',label:'防癌检查年份',idx:14},
{key:'s9Checked',label:'Septin9是否已检',idx:15},{key:'s9Abn',label:'Septin9检查异常',idx:16},{key:'s9Value',label:'Septin9项目值',idx:17},
{key:'caChecked',label:'CA724是否已检',idx:18},{key:'caAbn',label:'CA724检查异常',idx:19},{key:'caValue',label:'CA724项目值',idx:20}
];
var ADV_FIELD_OPTS=ADV_FIELDS.map(function(f){return '<option value="'+f.key+'">'+f.label+'</option>';}).join('');
function advConditionRowHTML(){
return '<div class="adv-cond-row">'
+'<select class="filter-select adv-field">'+ADV_FIELD_OPTS+'</select>'
+'<select class="filter-select adv-op" onchange="onAdvOpChange(this)">'+ADV_OPS.map(function(o){return '<option value="'+o+'">'+o+'</option>';}).join('')+'</select>'
+'<span class="adv-value-wrap"><input class="filter-input adv-value" type="text" placeholder="请输入值"></span>'
+'<button class="adv-del" type="button" title="删除该条件" onclick="removeAdvCondition(this)"></button>'
+'</div>';
}
function onAdvOpChange(sel){
var wrap=sel.parentElement.querySelector('.adv-value-wrap');
var isRange=sel.value==='范围';
var alreadyRange=!!wrap.querySelector('.adv-range-from');
if(isRange&&!alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value adv-range-from" type="text" placeholder="最小值">'
+'<span class="adv-range-sep">~</span>'
+'<input class="filter-input adv-value adv-range-to" type="text" placeholder="最大值">';
}else if(!isRange&&alreadyRange){
wrap.innerHTML='<input class="filter-input adv-value" type="text" placeholder="请输入值">';
}
}
function addAdvCondition(){document.getElementById('advConditions').insertAdjacentHTML('beforeend',advConditionRowHTML());}
function removeAdvCondition(el){el.parentElement.remove();}
function toggleAdvanced(){
var p=document.getElementById('advPanel');
var open=p.classList.toggle('open');
var arr=document.getElementById('advToggle').querySelector('.adv-arrow');
if(arr)arr.textContent=open?'▴':'▾';
if(open&&!p.querySelector('.adv-cond-row'))addAdvCondition();
}
function readAdvancedConditions(){
var conds=[];
document.querySelectorAll('#advConditions .adv-cond-row').forEach(function(row){
var field=row.querySelector('.adv-field').value;
var op=row.querySelector('.adv-op').value;
if(op==='范围'){
var from=row.querySelector('.adv-range-from').value.trim();
var to=row.querySelector('.adv-range-to').value.trim();
if(from!==''||to!=='')conds.push({field:field,op:op,from:from,to:to});
}else{
var value=row.querySelector('.adv-value').value.trim();
if(value!=='')conds.push({field:field,op:op,value:value});
}
});
return conds;
}
function fieldByKey(key){for(var i=0;i<ADV_FIELDS.length;i++){if(ADV_FIELDS[i].key===key)return ADV_FIELDS[i];}return null;}
function getFieldRaw(tr,f){var td=tr.cells[f.idx];return td?td.textContent.trim():'';}
function numericOf(v){var s=String(v).replace(/,/g,'').replace(/[¥¥]/g,'');var m=s.match(/[-+]?\d+(?:\.\d+)?/);return m?parseFloat(m[0]):null;}
function matchCondition(tr,cond){
var f=fieldByKey(cond.field);if(!f)return true;
var str=getFieldRaw(tr,f);
if(cond.op==='包含')return str.indexOf(cond.value)>=0;
if(cond.op==='=')return str===cond.value;
if(cond.op==='!=')return str!==cond.value;
var n=numericOf(str);
if(n===null)return false;
if(cond.op==='范围'){
if(cond.from!==''){var lo=parseFloat(cond.from);if(!isNaN(lo)&&n<lo)return false;}
if(cond.to!==''){var hi=parseFloat(cond.to);if(!isNaN(hi)&&n>hi)return false;}
return true;
}
var c=parseFloat(cond.value);if(isNaN(c))return false;
if(cond.op==='>')return n>c;
if(cond.op==='>=')return n>=c;
if(cond.op==='<')return n<c;
if(cond.op==='<=')return n<=c;
return true;
}
function applyFilter(){
var conds=readAdvancedConditions();
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){
var show=true;
for(var i=0;i<conds.length;i++){if(!matchCondition(tr,conds[i])){show=false;break;}}
tr.style.display=show?'':'none';
});
showToast('查询完成');
}
function clearAdvanced(){
document.getElementById('advConditions').innerHTML='';
document.querySelectorAll(ADV_ROW_SELECTOR).forEach(function(tr){tr.style.display='';});
showToast('已清空高级查询条件');
}
/* 轻提示 */
var toastTimer=null;
function showToast(msg){
document.getElementById('toastText').textContent=msg;
var t=document.getElementById('toast');
t.classList.add('show');clearTimeout(toastTimer);
toastTimer=setTimeout(function(){t.classList.remove('show');},2400);
}
</script>
<!-- 轻提示 -->
<div class="toast" id="toast">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none"><circle cx="7.5" cy="7.5" r="6.5" fill="#52C41A"/><path d="M4.6 7.7l2 2 3.8-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span id="toastText"></span>
</div>
</body>
</html>
+251 -19
View File
@@ -95,6 +95,35 @@ tbody tr:hover{background:#F8FBFF;}
.modal-close:hover{background:#f5f5f5;color:var(--text-primary);}
.modal-body{padding:24px;overflow-y:auto;flex:1;}
/* 主 Tab 栏 */
.main-tabs{display:flex;gap:0;background:#fff;border-radius:var(--radius-md);padding:0 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.main-tab{padding:14px 20px;font-size:var(--font-base);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;transition:all .2s;user-select:none;}
.main-tab:hover{color:var(--primary);}
.main-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600;}
.main-tab-panel{display:none;}
.main-tab-panel.active{display:block;}
/* 取餐明细 - 视频回放与丢餐提示 */
.video-info{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:16px;}
.scale-info{display:flex;gap:24px;padding:12px 16px;background:#F8FAFC;border-radius:var(--radius-md);margin-bottom:16px;font-size:var(--font-sm);}
.scale-info__item{color:var(--text-primary);}
.scale-info__item strong{color:var(--primary);font-weight:600;}
.raw-data-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.raw-data-table thead th{background:#FAFAFA;padding:10px 14px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
.raw-data-table tbody td{padding:12px 14px;color:var(--text-primary);border-bottom:1px solid #f0f0f0;white-space:nowrap;}
.raw-data-table tbody tr:last-child td{border-bottom:none;}
.raw-data-table tbody tr:hover{background:#F8FBFF;}
.video-player{background:#000;border-radius:var(--radius-md);overflow:hidden;}
.video-player__screen{position:relative;height:320px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#1a1a2e 0%,#16213e 50%,#0f3460 100%);}
.video-player__placeholder{color:rgba(255,255,255,0.6);font-size:var(--font-sm);letter-spacing:2px;}
.video-player__bigplay{position:absolute;width:64px;height:64px;border-radius:50%;background:rgba(255,255,255,0.2);color:#fff;display:flex;align-items:center;justify-content:center;font-size:26px;cursor:pointer;transition:all .2s;}
.video-player__bigplay:hover{background:rgba(255,255,255,0.35);}
.video-player__bar{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#111;}
.video-player__btn{width:32px;height:32px;border:none;border-radius:50%;background:rgba(255,255,255,0.15);color:#fff;cursor:pointer;font-size:14px;}
.video-player__time{color:rgba(255,255,255,0.7);font-size:var(--font-xs);white-space:nowrap;}
.video-player__progress{flex:1;height:4px;background:rgba(255,255,255,0.2);border-radius:2px;position:relative;}
.video-player__progress-fill{width:40%;height:100%;background:var(--primary);border-radius:2px;}
/* 十维档案 Tab */
.profile-tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:20px;overflow-x:auto;}
.profile-tab{padding:10px 16px;font-size:var(--font-sm);color:var(--text-secondary);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;transition:all .2s;}
@@ -300,6 +329,15 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</div>
</div>
<!-- 主 Tab 栏 -->
<div class="main-tabs">
<div class="main-tab active" data-tab="record" onclick="switchMainTab(this,'record')">用户取餐记录</div>
<div class="main-tab" data-tab="detail" onclick="switchMainTab(this,'detail')">取餐明细</div>
</div>
<!-- Tab1: 用户取餐记录 -->
<div class="main-tab-panel active" data-panel="record">
<!-- 筛选栏 -->
<div class="filter-bar">
<div class="filter-item"><label>员工姓名</label><input class="filter-input" type="text" placeholder="请输入姓名" style="min-width:120px;"></div>
@@ -328,7 +366,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<div class="table-card__title">取餐管理</div>
</div>
<div class="table-card__content">
<table>
<table id="recordTable">
<thead>
<!-- 交互注释:结算重量=终端结算时电子秤计重;实际重量=结算后复秤电子秤上传的实际重量;重量偏差=实际重量-结算重量,偏差超过±10克(含)显示橙色预警、超过±15克显示红色异常,未复秤显示"—" -->
<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></tr>
@@ -356,6 +394,70 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</div>
</div>
</div><!-- /Tab1: 用户取餐记录 -->
<!-- Tab2: 取餐明细 -->
<div class="main-tab-panel" data-panel="detail">
<!-- 取餐明细筛选栏 -->
<div class="filter-bar">
<div class="filter-item"><label>记录状态</label><select class="filter-select"><option>全部</option><option>正常</option><option>无主记录</option><option>手动修正</option></select></div>
<div class="filter-item"><label>用户</label><input class="filter-input" type="text" placeholder="工号 / 姓名" style="min-width:140px;"></div>
<div class="filter-item"><label>餐次</label><select class="filter-select"><option>全部</option><option>早餐</option><option>中餐</option><option>晚餐</option><option>加餐</option></select></div>
<div class="filter-item"><label>餐品名称</label><input class="filter-input" type="text" placeholder="请输入餐品名称" style="min-width:140px;"></div>
<div class="filter-item"><label>取餐方式</label><select class="filter-select"><option>全部</option><option>餐线取餐</option><option>档口取餐</option></select></div>
<div class="filter-item"><label>取餐设备</label><select class="filter-select"><option>全部</option><option>一号餐线·2号秤</option><option>一号餐线·3号秤</option><option>一号餐线·5号秤</option><option>二号档口·1号秤</option><option>三号档口·2号秤</option></select></div>
<div class="filter-item"><label>就餐记录号</label><input class="filter-input" type="text" placeholder="请输入就餐记录号" style="min-width:140px;"></div>
<div class="filter-item"><label>日期范围</label><input class="filter-input" type="date" style="min-width:130px;"> <span style="margin:0 4px;color:#999;"></span> <input class="filter-input" type="date" style="min-width:130px;"></div>
<button class="btn btn-primary">查询</button>
<button class="btn btn-default">重置</button>
</div>
<!-- 取餐明细操作栏 -->
<div class="action-bar">
<button class="btn btn-primary"><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="#fff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>导出列表数据</button>
<button class="btn btn-default"><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>
<!-- 取餐明细数据表格 -->
<div class="table-card">
<div class="table-card__header">
<div class="table-card__title">取餐明细</div>
</div>
<div class="table-card__content">
<table id="detailTable">
<thead>
<!-- 交互注释:记录状态标识取餐记录与取餐人的对应关系——正常=已关联到取餐人;无主记录=未关联到取餐人(丢餐排查重点);手动修正=经人工核对后补全关联 -->
<tr><th>序号</th><th>取用记录ID</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>
<tr><td>1</td><td>P-20260428-0001</td><td>午餐</td><td>清炒生菜</td><td><span class="tag tag-green">正常</span></td><td>133.0</td><td>¥6.00</td><td>EMP001-张伟</td><td><span class="tag tag-orange">会员</span></td><td>2026-04-28 11:02:15</td><td>一号餐线·3号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td>M-20260428-001</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>2</td><td>P-20260428-0002</td><td>午餐</td><td>西红柿鸡蛋</td><td><span class="tag tag-green">正常</span></td><td>114.0</td><td>¥8.00</td><td>EMP001-张伟</td><td><span class="tag tag-orange">会员</span></td><td>2026-04-28 11:03:02</td><td>一号餐线·3号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td>M-20260428-001</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>3</td><td>P-20260428-0003</td><td>午餐</td><td>辣子鸡丁</td><td><span class="tag tag-green">正常</span></td><td>79.0</td><td>¥8.00</td><td>EMP001-张伟</td><td><span class="tag tag-orange">会员</span></td><td>2026-04-28 11:04:30</td><td>一号餐线·3号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td>M-20260428-001</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>4</td><td>P-20260428-0011</td><td>午餐</td><td>红烧肉</td><td><span class="tag tag-green">正常</span></td><td>85.0</td><td>¥16.00</td><td>EMP003-王磊</td><td><span class="tag tag-gray">非会员</span></td><td>2026-04-28 11:16:40</td><td>一号餐线·5号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td>M-20260428-003</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>5</td><td>P-20260428-0021</td><td>午餐</td><td>宫保鸡丁</td><td><span class="tag tag-red">无主记录</span></td><td>110.0</td><td>¥9.50</td><td></td><td></td><td>2026-04-28 11:25:08</td><td>一号餐线·5号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td></td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>6</td><td>P-20260428-0022</td><td>午餐</td><td>米饭</td><td><span class="tag tag-red">无主记录</span></td><td>200.0</td><td>¥12.00</td><td></td><td></td><td>2026-04-28 11:25:33</td><td>一号餐线·5号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td></td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>7</td><td>P-20260428-0031</td><td>晚餐</td><td>清蒸鲈鱼</td><td><span class="tag tag-red">无主记录</span></td><td>120.0</td><td>¥8.00</td><td></td><td></td><td>2026-04-28 18:12:45</td><td>二号档口·1号秤</td><td><span class="tag tag-green">档口取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td></td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>8</td><td>P-20260427-0005</td><td>午餐</td><td>糖醋里脊</td><td><span class="tag tag-orange">手动修正</span></td><td>95.0</td><td>¥12.00</td><td>EMP007-林教授</td><td><span class="tag tag-purple">外部人员</span></td><td>2026-04-27 12:12:20</td><td>二号档口·1号秤</td><td><span class="tag tag-green">档口取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td>M-20260428-007</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>9</td><td>P-20260427-0018</td><td>午餐</td><td>余量套餐B</td><td><span class="tag tag-orange">手动修正</span></td><td>195.0</td><td>¥5.00</td><td>EMP102-王保安</td><td><span class="tag tag-blue">工作人员</span></td><td>2026-04-27 14:11:05</td><td>一号餐线·2号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-orange">余量取餐</span></td><td>SP-001-003</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>10</td><td>P-20260428-0004</td><td>早餐</td><td>小米粥</td><td><span class="tag tag-green">正常</span></td><td>250.0</td><td>¥4.00</td><td>EMP005-陈刚</td><td><span class="tag tag-gray">非会员</span></td><td>2026-04-28 07:49:10</td><td>一号餐线·2号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td>M-20260428-005</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>11</td><td>P-20260428-0041</td><td>午餐</td><td>鸡胸肉沙拉</td><td><span class="tag tag-green">正常</span></td><td>180.0</td><td>¥10.80</td><td>EMP006-刘芳</td><td><span class="tag tag-orange">会员</span></td><td>2026-04-28 11:46:22</td><td>三号档口·2号秤</td><td><span class="tag tag-green">档口取餐</span></td><td><span class="tag tag-green">正常取餐</span></td><td>M-20260428-006</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
<tr><td>12</td><td>P-20260428-0023</td><td>午餐</td><td>余量套餐A</td><td><span class="tag tag-green">正常</span></td><td>220.0</td><td>¥5.00</td><td>EMP101-张运维</td><td><span class="tag tag-blue">员工</span></td><td>2026-04-28 14:05:50</td><td>一号餐线·2号秤</td><td><span class="tag tag-gray">餐线取餐</span></td><td><span class="tag tag-orange">余量取餐</span></td><td>SP-001-001</td><td class="td-actions"><a class="link">原始数据</a>&nbsp;<a class="link">查看视频</a></td></tr>
</tbody>
</table>
</div>
<div class="pagination">
<span>显示 12 条,共 3,562 条</span>
<button class="page-btn"></button>
<button class="page-btn active">1</button>
<button class="page-btn">2</button>
<button class="page-btn">3</button>
<button class="page-btn"></button>
</div>
</div>
</div><!-- /Tab2: 取餐明细 -->
</div>
<!-- 交互脚本 -->
@@ -367,20 +469,25 @@ function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
const Modal = {
open(title, bodyHtml) {
this.close();
this.stack(title, bodyHtml);
},
stack(title, bodyHtml) {
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.innerHTML = `<div class="modal">
<div class="modal-header"><div class="modal-title">${title}</div><button class="modal-close" onclick="Modal.close()">&times;</button></div>
<div class="modal-header"><div class="modal-title">${title}</div><button class="modal-close" onclick="Modal.closeTop(this.closest('.modal-overlay'))">&times;</button></div>
<div class="modal-body">${bodyHtml}</div>
</div>`;
overlay.addEventListener('click', e => { if (e.target === overlay) Modal.close(); });
overlay.addEventListener('click', e => { if (e.target === overlay) Modal.closeTop(overlay); });
document.body.appendChild(overlay);
},
close() {
const o = document.querySelector('.modal-overlay');
if (!o) return;
o.classList.add('closing');
setTimeout(() => o.remove(), 200);
document.querySelectorAll('.modal-overlay').forEach(o => { o.classList.add('closing'); setTimeout(() => o.remove(), 200); });
},
closeTop(overlay) {
if (!overlay) return;
overlay.classList.add('closing');
setTimeout(() => overlay.remove(), 200);
}
};
@@ -575,7 +682,7 @@ function showUserProfile(name) {
}
/* 绑定表格中的"查看用户信息"链接 */
document.querySelectorAll('tbody tr').forEach(row => {
document.querySelectorAll('#recordTable tbody tr').forEach(row => {
const link = row.querySelector('td:last-child .link');
const nameCell = row.querySelector('td:nth-child(2)');
if (link && nameCell) {
@@ -662,15 +769,24 @@ function showMealDetail(row) {
</div>
</div>`;
const tableRows = items.map(i => `<tr>
<td>${i.name}</td>
<td>¥${i.price.toFixed(2)}</td>
<td>${i.weight.toFixed(1)}</td>
<td>${i.cal.toFixed(1)}</td>
<td>${i.carb.toFixed(1)}</td>
<td>${i.fat.toFixed(1)}</td>
<td>${i.protein.toFixed(1)}</td>
</tr>`).join('');
const dateStr = (recordId.match(/\d{8}/) || ['20260428'])[0].replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
const methodDeviceMap = { '餐线取餐':'一号餐线·3号秤', '档口+餐线':'二号档口·1号秤', '点餐取餐':'一号餐线·3号秤', '外卖/外带':'二号档口·1号秤', '余量取餐':'一号餐线·2号秤' };
const device = methodDeviceMap[pickupMethod] || '一号餐线·3号秤';
const tableRows = items.map((i, idx) => {
const itemTime = dateStr + ' ' + startTime + ':' + String(idx * 8).padStart(2, '0');
return `<tr>
<td>${i.name}</td>
<td>¥${i.price.toFixed(2)}</td>
<td>${i.weight.toFixed(1)}</td>
<td>${i.cal.toFixed(1)}</td>
<td>${i.carb.toFixed(1)}</td>
<td>${i.fat.toFixed(1)}</td>
<td>${i.protein.toFixed(1)}</td>
<td><a class="link" onclick="showMealItemRawData('${i.name}', ${i.weight}, '${name}', '${itemTime}', '${device}')">查看</a></td>
<td><a class="link" onclick="showMealItemVideo('${i.name}', '${itemTime}', '${recordId}')">查看</a></td>
</tr>`;
}).join('');
const summaryRow = `<tr>
<td>合计</td>
@@ -680,12 +796,14 @@ function showMealDetail(row) {
<td>${totalCarb.toFixed(1)}克</td>
<td>${totalFat.toFixed(1)}克</td>
<td>${totalProtein.toFixed(1)}克</td>
<td></td>
<td></td>
</tr>`;
const tableHtml = `<div class="meal-section">
<div class="meal-section__title">餐品列表</div>
<table class="meal-table">
<thead><tr><th>餐品名称</th><th>金额</th><th>重量(克)</th><th>热量(千卡)</th><th>碳水化合物(克)</th><th>脂肪(克)</th><th>蛋白质(克)</th></tr></thead>
<thead><tr><th>餐品名称</th><th>金额</th><th>重量(克)</th><th>热量(千卡)</th><th>碳水化合物(克)</th><th>脂肪(克)</th><th>蛋白质(克)</th><th>原始数据</th><th>查看视频</th></tr></thead>
<tbody>${tableRows}${summaryRow}</tbody>
</table>
</div>`;
@@ -693,13 +811,127 @@ function showMealDetail(row) {
Modal.open('取餐详情 — ' + name, infoHtml + tableHtml);
}
function showMealItemRawData(dish, weight, user, time, device) {
buildRawDataModal({ dish: dish, statusText: '正常', weight: parseFloat(weight), user: user, time: time, device: device });
}
function showMealItemVideo(dish, time, recordId) {
buildVideoModal({ id: recordId, dish: dish, statusText: '正常', time: time });
}
/* 绑定"取餐总数量"列点击事件 */
document.querySelectorAll('tbody tr').forEach(row => {
document.querySelectorAll('#recordTable tbody tr').forEach(row => {
const quantityLink = row.querySelector('td:nth-child(12) .link');
if (quantityLink) {
quantityLink.addEventListener('click', () => showMealDetail(row));
}
});
/* ========== 主 Tab 切换 ========== */
function switchMainTab(el, key) {
document.querySelectorAll('.main-tab').forEach(t => t.classList.remove('active'));
el.classList.add('active');
document.querySelectorAll('.main-tab-panel').forEach(p => p.classList.remove('active'));
document.querySelector(`.main-tab-panel[data-panel="${key}"]`).classList.add('active');
}
/* ========== 取餐明细 - 原始数据弹窗 ========== */
function showRawData(row) {
const c = row.querySelectorAll('td');
Modal.close();
buildRawDataModal({
dish: c[3].textContent.trim(),
statusText: c[4].textContent.trim(),
weight: parseFloat(c[5].textContent.trim()),
user: c[7].textContent.trim(),
time: c[9].textContent.trim(),
device: c[10].textContent.trim()
});
}
function buildRawDataModal(data) {
const deviceNoMap = { '一号餐线·2号秤':'SC-002', '一号餐线·3号秤':'SC-003', '一号餐线·5号秤':'SC-005', '二号档口·1号秤':'DC-001', '三号档口·2号秤':'DC-002' };
const deviceNo = deviceNoMap[data.device] || '—';
const isOrphan = data.statusText === '无主记录';
const isManual = data.statusText === '手动修正';
const recStatus = (isOrphan || isManual) ? '未识别' : '已识别';
const recUser = (isOrphan || isManual) ? '—' : data.user;
const statusTag = recStatus === '已识别' ? '<span class="tag tag-green">已识别</span>' : '<span class="tag tag-red">未识别</span>';
const samples = [
{ t: minusSeconds(data.time, 5), w: (data.weight - 38).toFixed(1), d: '+' + (data.weight - 38).toFixed(1), stable: '否' },
{ t: minusSeconds(data.time, 2), w: (data.weight - 9).toFixed(1), d: '+29.0', stable: '否' },
{ t: data.time, w: data.weight.toFixed(1), d: '+9.0', stable: '是' }
];
const rowsHtml = samples.map(s => `<tr>
<td>${s.t}</td>
<td>${s.w}</td>
<td>${s.d}</td>
<td>${statusTag}</td>
<td>${recUser}</td>
<td>${s.stable === '是' ? '<span class="tag tag-green"></span>' : '<span class="tag tag-orange"></span>'}</td>
</tr>`).join('');
const html = `<div class="scale-info">
<div class="scale-info__item">设备编号:<strong>${deviceNo}</strong></div>
<div class="scale-info__item">设备名称:<strong>${data.device}</strong></div>
</div>
<table class="raw-data-table">
<thead><tr><th>采样时间</th><th>当前重量(g)</th><th>较上次变化重量(g)</th><th>识别状态</th><th>用户</th><th>是否稳定</th></tr></thead>
<tbody>${rowsHtml}</tbody>
</table>`;
Modal.stack('秤原始数据 — ' + data.dish, html);
}
function minusSeconds(timeStr, sec) {
const parts = timeStr.split(' ');
const date = parts[0];
const hms = parts[1].split(':').map(Number);
const d = new Date(0);
d.setHours(hms[0], hms[1], hms[2] - sec);
const pad = n => String(n).padStart(2, '0');
return date + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
}
/* ========== 取餐明细 - 视频回放弹窗 ========== */
function showPickupVideo(row) {
const c = row.querySelectorAll('td');
Modal.close();
buildVideoModal({
id: c[1].textContent.trim(),
dish: c[3].textContent.trim(),
statusText: c[4].textContent.trim(),
time: c[9].textContent.trim()
});
}
function buildVideoModal(data) {
const html = `<div class="video-info">取用记录ID${data.id} · 取餐时间:${data.time} · 记录状态:${data.statusText}</div>
<div class="video-player">
<div class="video-player__screen">
<div class="video-player__placeholder">取餐过程监控画面</div>
<div class="video-player__bigplay"></div>
</div>
<div class="video-player__bar">
<button class="video-player__btn"></button>
<span class="video-player__time">00:00 / 00:18</span>
<div class="video-player__progress"><div class="video-player__progress-fill"></div></div>
</div>
</div>`;
Modal.stack('取餐视频回放 — ' + data.dish, html);
}
/* 绑定取餐明细表格操作列 */
document.querySelectorAll('#detailTable tbody tr').forEach(row => {
const links = row.querySelectorAll('.td-actions .link');
if (links.length >= 2) {
links[0].addEventListener('click', () => showRawData(row));
links[1].addEventListener('click', () => showPickupVideo(row));
}
});
</script>
</body>
</html>
@@ -0,0 +1,567 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>营养管理 - 净菜订单生成历史 | 健康CQ原型</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F7FA;color:#333;min-width:1280px;}
:root{--primary:#1890FF;--primary-light:#E6F7FF;--success:#52C41A;--warning:#FAAD14;--danger:#FF4D4F;--text-primary:#333;--text-secondary:#666;--text-placeholder:#999;--border:#E8E8E8;--background:#F5F7FA;--font-xs:11px;--font-sm:13px;--font-base:15px;--font-lg:17px;--font-xl:20px;--font-xxl:24px;--radius-sm:4px;--radius-md:8px;--radius-lg:12px;--shadow-sm:0 1px 4px rgba(0,0,0,0.08);--shadow-md:0 4px 12px rgba(0,0,0,0.12);}
.top-bar{display:flex;align-items:center;height:56px;background:#001529;padding:0 24px;position:fixed;top:0;left:0;right:0;z-index:100;}
.top-bar__logo{display:flex;align-items:center;gap:8px;color:#fff;font-size:var(--font-lg);font-weight:600;white-space:nowrap;width:240px;flex-shrink:0;}
.top-bar__logo svg{width:28px;height:28px;}
.top-bar__nav{display:flex;align-items:center;gap:4px;flex:1;}
.top-bar__nav a{color:rgba(255,255,255,0.65);text-decoration:none;padding:6px 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);transition:all .2s;}
.top-bar__nav a:hover{color:#fff;background:rgba(255,255,255,0.08);}
.top-bar__nav a.active{color:#fff;background:var(--primary);}
.top-bar__right{display:flex;align-items:center;gap:16px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);}
.top-bar__right .btn-back{color:rgba(255,255,255,0.65);cursor:pointer;display:flex;align-items:center;gap:4px;text-decoration:none;}
.top-bar__right .btn-back:hover{color:#fff;}
.avatar{width:32px;height:32px;border-radius:50%;background:#1890FF;display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-sm);cursor:pointer;}
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;text-transform:uppercase;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
.sidebar-item:hover{color:#fff;background:rgba(255,255,255,0.06);}
.sidebar-item.active{color:#fff;background:var(--primary);}
.mi{display:inline-block;width:20px;text-align:center;filter:grayscale(1);opacity:0.65;margin-right:2px;}
.sidebar-item.active .mi{opacity:0.9;}
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
/* 页面标题行 */
.page-header{display:flex;align-items:center;justify-content:space-between;background:#fff;border-radius:var(--radius-md);padding:16px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);}
.page-header__left{display:flex;align-items:baseline;gap:12px;}
.page-header__title{font-size:var(--font-xl);font-weight:700;color:var(--text-primary);}
.page-header__desc{font-size:var(--font-sm);color:var(--text-placeholder);}
.page-header__right{display:flex;align-items:center;gap:12px;}
/* 搜索区 */
.filter-bar{background:#fff;border-radius:var(--radius-md);padding:20px 24px;margin-bottom:16px;box-shadow:var(--shadow-sm);display:flex;align-items:center;gap:16px;flex-wrap:wrap;}
.filter-item{display:flex;align-items:center;gap:8px;}
.filter-item label{font-size:var(--font-sm);color:var(--text-secondary);white-space:nowrap;}
.filter-input{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;min-width:180px;}
.filter-input:focus{border-color:var(--primary);}
.filter-select{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;min-width:130px;}
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);text-decoration:none;}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
/* 表格容器 */
.table-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);overflow:hidden;}
.table-card__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
.table-card__title{font-size:var(--font-base);font-weight:600;}
.table-card__content{overflow-x:auto;overflow-y:hidden;}
.table-card__content table{min-width:100%;white-space:nowrap;}
table{width:100%;border-collapse:collapse;}
thead th{background:#FAFAFA;padding:12px 16px;text-align:left;font-size:var(--font-sm);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
tbody td{padding:12px 16px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid var(--border);white-space:nowrap;}
tbody tr:hover{background:#F8FBFF;}
/* 餐次/人数多行单元格 */
.meal-cell{line-height:1.9;}
/* 标签 */
.tag{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
.tag-blue{background:var(--primary-light);color:var(--primary);}
.tag-green{background:#F6FFED;color:var(--success);}
.tag-orange{background:#FFF7E6;color:var(--warning);}
.tag-red{background:#FFF1F0;color:var(--danger);}
.tag-gray{background:#F5F5F5;color:#999;}
.tag-cyan{background:#E6FFFB;color:#08979C;border:1px solid #87E8DE;}
.tag-cyan::before{content:'';display:inline-block;width:6px;height:6px;border-radius:50%;background:#13C2C2;margin-right:5px;vertical-align:middle;animation:statusPulse 1.4s ease-in-out infinite;}
@keyframes statusPulse{0%,100%{opacity:0.3;transform:scale(0.8);}50%{opacity:1;transform:scale(1.2);}}
.link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);}.link:hover{color:#40a9ff;}
/* 分页 */
.pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);}
.pagination span{font-size:var(--font-sm);color:var(--text-secondary);margin-right:8px;}
.page-btn{min-width:32px;height:32px;padding:0 8px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-sm);display:inline-flex;align-items:center;justify-content:center;transition:all .2s;color:var(--text-primary);}
.page-btn:hover:not(:disabled):not(.active){color:var(--primary);border-color:var(--primary);}
.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.page-btn:disabled{color:#ccc;cursor:not-allowed;background:#f5f5f5;}
/* 弹窗 */
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:200;align-items:center;justify-content:center;}
.modal-mask.show{display:flex;}
.modal{background:#fff;border-radius:var(--radius-lg);width:1100px;max-width:92vw;max-height:88vh;display:flex;flex-direction:column;box-shadow:0 8px 24px rgba(0,0,0,0.15);}
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--border);}
.modal__title{font-size:var(--font-lg);font-weight:600;}
.modal__close{width:32px;height:32px;border:none;background:none;font-size:18px;cursor:pointer;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;color:var(--text-secondary);}
.modal__close:hover{background:#f5f5f5;color:var(--text-primary);}
.modal__body{padding:20px 24px;overflow-y:auto;flex:1;}
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 24px;border-top:1px solid var(--border);}
/* 弹窗概要 */
.detail-summary{display:flex;flex-wrap:wrap;gap:20px 40px;padding:14px 16px;background:#F7F9FC;border-radius:var(--radius-sm);margin-bottom:16px;}
.detail-summary__item{display:flex;gap:8px;font-size:var(--font-sm);}
.detail-summary__label{color:var(--text-placeholder);}
.detail-summary__value{color:var(--text-primary);font-weight:500;}
/* 弹窗内餐品表 */
.dish-table{width:100%;border-collapse:collapse;border:1px solid var(--border);}
.dish-table thead th{background:#F7F9FC;padding:8px 12px;font-size:var(--font-xs);color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);text-align:left;white-space:nowrap;}
.dish-table tbody td{padding:10px 12px;font-size:var(--font-sm);color:var(--text-primary);border-bottom:1px solid #f0f0f0;white-space:nowrap;}
.dish-table tr.dish-row{cursor:pointer;}
.dish-table tr.dish-row:hover{background:#F8FBFF;}
.dish-table tr.dish-row.expanded{background:#F0F7FF;}
.dish-table tr.dish-row:last-child td{border-bottom:none;}
.dish-table tr.ingredient-row{background:#FBFCFE;}
.dish-table tr.ingredient-row td{padding:0;border-bottom:1px solid #f0f0f0;}
.expand-arrow{display:inline-block;width:20px;text-align:center;transition:transform .2s;font-size:var(--font-xs);color:var(--text-placeholder);}
.expand-arrow.expanded{transform:rotate(90deg);}
/* 食材子表 */
.ing-table{width:100%;border-collapse:collapse;}
.ing-table thead th{background:#FAFBFC;padding:6px 12px 6px 36px;font-size:var(--font-xs);color:var(--text-placeholder);font-weight:500;border-bottom:1px dashed #e5e5e5;text-align:left;white-space:nowrap;}
.ing-table tbody td{padding:8px 12px 8px 36px;font-size:var(--font-xs);color:var(--text-secondary);border-bottom:1px dashed #f0f0f0;white-space:nowrap;}
.ing-table tbody td:first-child{padding-left:36px;font-weight:500;color:var(--text-primary);}
.ing-table tbody tr:last-child td{border-bottom:none;}
.ing-type{display:inline-block;padding:1px 7px;border-radius:8px;font-size:10px;line-height:16px;}
.ing-type--main{background:var(--primary-light);color:var(--primary);}
.ing-type--aux{background:#F6FFED;color:var(--success);}
.ing-type--season{background:#FFF7E6;color:var(--warning);}
/* 空态 */
.empty-state{text-align:center;color:var(--text-placeholder);padding:60px 0;font-size:var(--font-sm);}
.empty-state .icon{font-size:40px;display:block;margin-bottom:12px;}
</style>
</head>
<body>
<header class="top-bar">
<div class="top-bar__logo"><svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M7 14h14M14 7v14" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg>营养管理</div>
<nav class="top-bar__nav">
<a href="trace-query.html">溯源总览</a><a href="arc-employee.html">档案</a><a href="farm-land.html">绿色种采</a><a href="sup-hygiene-check.html">卫生供应</a><a href="prod-qc-daily.html" class="active">健康生产</a><a href="din-dict-mgmt.html">营养用餐</a><a href="ano-dirty.html">异常事件</a>
</nav>
<div class="top-bar__right"><a class="btn-back" href="../../index.html">← 返回首页</a><div class="avatar"></div><span>系统管理员 ▾</span></div>
</header>
<aside class="sidebar">
<div class="sidebar-menu__title">健康生产</div>
<a href="prod-qc-daily.html" class="sidebar-item"><span class="mi">🛡️</span>食安监控</a>
<a href="prod-recipe-plan.html" class="sidebar-item"><span class="mi">📋</span>食谱规划</a>
<a href="prod-clean-order.html" class="sidebar-item active"><span class="mi">📝</span>净菜订单</a>
<a href="prod-proc-cabinet.html" class="sidebar-item"><span class="mi">🗄️</span>净菜进柜</a>
<a href="prod-proc-combo.html" class="sidebar-item"><span class="mi">🔪</span>净菜组配</a>
<a href="prod-proc-cook.html" class="sidebar-item"><span class="mi">🍳</span>按单烹制</a>
<a href="prod-proc-serve.html" class="sidebar-item"><span class="mi">🍽️</span>上餐监控</a>
<a href="prod-proc-surplus.html" class="sidebar-item"><span class="mi">♻️</span>余量处置</a>
<a href="prod-proc-stats.html" class="sidebar-item"><span class="mi">📊</span>生产统计</a>
</aside>
<div class="main">
<!-- 页面标题行 -->
<div class="page-header">
<div class="page-header__left">
<span class="page-header__title">净菜订单生成历史</span>
<span class="page-header__desc">自动生成的净菜订单记录,点击「数据明细」查看餐品与食材用量对比</span>
</div>
<div class="page-header__right">
<a class="btn btn-default" href="prod-clean-order.html">← 返回净菜订单</a>
</div>
</div>
<!-- 搜索区 -->
<div class="filter-bar">
<div class="filter-item"><label>订单号</label><input class="filter-input" id="fOrderNo" placeholder="请输入订单号"></div>
<div class="filter-item"><label>生成状态</label><select class="filter-select" id="fStatus"><option value="">全部</option><option value="generating">生成中</option><option value="completed">已完成</option></select></div>
<div class="filter-item"><label>生成餐次</label><select class="filter-select" id="fMeal"><option value="">全部</option><option value="早餐">早餐</option><option value="午餐">午餐</option><option value="晚餐">晚餐</option></select></div>
<div class="filter-item"><label>食谱日期</label><input class="filter-input" type="date" id="fDate"></div>
<button class="btn btn-primary" onclick="applyFilter()">查询</button>
<button class="btn btn-default" onclick="resetFilter()">重置</button>
</div>
<!-- 生成记录表格(订单维度) -->
<div class="table-card">
<div class="table-card__header"><div class="table-card__title">生成记录</div><div class="table-card__title" style="font-weight:400;color:var(--text-secondary);" id="tableSubTitle"></div></div>
<div class="table-card__content">
<table id="historyTable">
<thead>
<tr>
<th style="width:56px;">序号</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="historyTableBody"></tbody>
</table>
<div class="empty-state" id="historyEmpty" style="display:none;"><span class="icon">📭</span>暂无符合条件的净菜订单生成记录</div>
</div>
<div class="pagination" id="pagination"></div>
</div>
</div>
<!-- 数据明细弹窗 -->
<div class="modal-mask" id="modal-detail">
<div class="modal">
<div class="modal__header">
<div class="modal__title">数据明细 — <span id="detailOrderNo"></span></div>
<button class="modal__close" onclick="closeDetail()"></button>
</div>
<div class="modal__body">
<div class="detail-summary" id="detailSummary"></div>
<table class="dish-table">
<thead><tr><th style="width:28px;"></th><th>餐次</th><th>餐品名称</th><th>规格</th><th>就餐人数</th><th>打包份数</th><th>内配料</th><th>AI 推荐生重(kg)</th><th>AI 推荐熟重(kg)</th><th>实际制作量(kg)</th><th>差异率</th></tr></thead>
<tbody id="detailDishBody"></tbody>
</table>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeDetail()">关闭</button>
</div>
</div>
</div>
<script>
/* ========== 工具函数 ========== */
function pad2(n){return String(n).padStart(2,'0');}
function dateOffsetStr(offset){
var d=new Date();
d.setDate(d.getDate()+offset);
return d.getFullYear()+'-'+pad2(d.getMonth()+1)+'-'+pad2(d.getDate());
}
/* ========== 餐品食材配方 BOM(每份基准克重) ========== */
var recipeBom={
'宫保鸡丁':[{name:'鸡胸肉',type:'主材',perServing:120},{name:'花生米',type:'辅材',perServing:30},{name:'黄瓜丁',type:'辅材',perServing:40},{name:'干辣椒',type:'调料',perServing:10},{name:'葱姜蒜',type:'调料',perServing:15}],
'清蒸鲈鱼':[{name:'鲈鱼',type:'主材',perServing:200},{name:'姜片',type:'调料',perServing:10},{name:'葱丝',type:'调料',perServing:8}],
'红烧肉':[{name:'五花肉',type:'主材',perServing:200},{name:'酱油',type:'调料',perServing:15},{name:'冰糖',type:'调料',perServing:10},{name:'八角',type:'调料',perServing:3}],
'麻婆豆腐':[{name:'嫩豆腐',type:'主材',perServing:200},{name:'肉末',type:'辅材',perServing:50},{name:'豆瓣酱',type:'调料',perServing:12},{name:'花椒',type:'调料',perServing:2}],
'番茄炒蛋':[{name:'番茄',type:'主材',perServing:150},{name:'鸡蛋',type:'主材',perServing:100},{name:'葱花',type:'调料',perServing:5}],
'炒时蔬':[{name:'西兰花',type:'主材',perServing:120},{name:'胡萝卜',type:'辅材',perServing:50},{name:'大蒜',type:'调料',perServing:8}],
'小米粥':[{name:'小米',type:'主材',perServing:80},{name:'清水',type:'辅材',perServing:250}],
'煮鸡蛋':[{name:'鸡蛋',type:'主材',perServing:60}],
'白粥':[{name:'大米',type:'主材',perServing:60},{name:'清水',type:'辅材',perServing:300}],
'豆浆':[{name:'黄豆',type:'主材',perServing:50},{name:'清水',type:'辅材',perServing:300}],
'馒头':[{name:'面粉',type:'主材',perServing:100},{name:'酵母',type:'调料',perServing:2}],
'茶叶蛋':[{name:'鸡蛋',type:'主材',perServing:60},{name:'茶叶',type:'调料',perServing:5}],
'鱼香肉丝':[{name:'猪里脊',type:'主材',perServing:120},{name:'木耳',type:'辅材',perServing:30},{name:'胡萝卜',type:'辅材',perServing:30},{name:'青椒',type:'辅材',perServing:30},{name:'豆瓣酱',type:'调料',perServing:10}],
'糖醋排骨':[{name:'猪排骨',type:'主材',perServing:180},{name:'白糖',type:'调料',perServing:20},{name:'香醋',type:'调料',perServing:10}],
'清炒西兰花':[{name:'西兰花',type:'主材',perServing:150},{name:'大蒜',type:'调料',perServing:8}],
'紫菜蛋花汤':[{name:'紫菜',type:'主材',perServing:5},{name:'鸡蛋',type:'辅材',perServing:50},{name:'清水',type:'辅材',perServing:300}],
'米饭':[{name:'大米',type:'主材',perServing:100},{name:'清水',type:'辅材',perServing:150}],
'糖醋里脊':[{name:'猪里脊',type:'主材',perServing:150},{name:'白糖',type:'调料',perServing:20},{name:'香醋',type:'调料',perServing:10}],
'冬瓜排骨汤':[{name:'冬瓜',type:'主材',perServing:120},{name:'猪排骨',type:'辅材',perServing:100},{name:'清水',type:'辅材',perServing:300}],
'酸奶':[{name:'酸奶',type:'主材',perServing:200}]
};
var ING_TYPE_CLS={'主材':'ing-type--main','辅材':'ing-type--aux','调料':'ing-type--season'};
/* ========== 净菜订单生成历史数据(订单维度,一个订单可含多个餐次) ========== */
var genOrders=[
{orderNo:'CO-1001',dayOffset:0,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'06:30',startTime:'05:30:00',successTime:'05:31:03',actualRatio:0.98,
meals:[{meal:'早餐',dinerCount:80}],
details:[
{meal:'早餐',foodName:'小米粥',spec:'300g/标准份',dinerCount:80,servings:80,needSeasoning:false,actualRatio:0.97},
{meal:'早餐',foodName:'煮鸡蛋',spec:'60g/标准份',dinerCount:80,servings:80,needSeasoning:false,actualRatio:1.02},
{meal:'早餐',foodName:'白粥',spec:'300g/标准份',dinerCount:80,servings:80,needSeasoning:false,actualRatio:0.99},
{meal:'早餐',foodName:'豆浆',spec:'350g/标准份',dinerCount:80,servings:80,needSeasoning:false,actualRatio:0.98}
]},
{orderNo:'CO-1002',dayOffset:0,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'10:00',startTime:'09:30:00',successTime:'09:31:12',actualRatio:0.97,
meals:[{meal:'午餐',dinerCount:120}],
details:[
{meal:'午餐',foodName:'宫保鸡丁',spec:'500g/标准份',dinerCount:120,servings:120,needSeasoning:true,actualRatio:0.96},
{meal:'午餐',foodName:'清蒸鲈鱼',spec:'600g/标准份',dinerCount:120,servings:120,needSeasoning:false,actualRatio:1.03},
{meal:'午餐',foodName:'红烧肉',spec:'500g/标准份',dinerCount:120,servings:120,needSeasoning:true,actualRatio:1.04},
{meal:'午餐',foodName:'番茄炒蛋',spec:'400g/标准份',dinerCount:120,servings:120,needSeasoning:false,actualRatio:0.97}
]},
{orderNo:'CO-1003',dayOffset:0,genType:'auto',genStatus:'generating',creator:'系统',deliverTime:'16:00',startTime:'14:30:00',successTime:'',actualRatio:0.99,
meals:[{meal:'早餐',dinerCount:80},{meal:'晚餐',dinerCount:100}],
details:[
{meal:'早餐',foodName:'馒头',spec:'100g/标准份',dinerCount:80,servings:160,needSeasoning:false,actualRatio:1.01},
{meal:'早餐',foodName:'茶叶蛋',spec:'60g/标准份',dinerCount:80,servings:80,needSeasoning:true,actualRatio:1.03},
{meal:'晚餐',foodName:'米饭',spec:'250g/标准份',dinerCount:100,servings:200,needSeasoning:false,actualRatio:0.98},
{meal:'晚餐',foodName:'宫保鸡丁',spec:'500g/标准份',dinerCount:100,servings:100,needSeasoning:true,actualRatio:0.96},
{meal:'晚餐',foodName:'馒头',spec:'100g/标准份',dinerCount:100,servings:200,needSeasoning:false,actualRatio:1.00}
]},
{orderNo:'CO-1004',dayOffset:0,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'16:00',startTime:'13:40:00',successTime:'13:41:20',actualRatio:1.01,
meals:[{meal:'午餐',dinerCount:110},{meal:'晚餐',dinerCount:90}],
details:[
{meal:'午餐',foodName:'糖醋排骨',spec:'350g/标准份',dinerCount:110,servings:110,needSeasoning:true,actualRatio:0.95},
{meal:'午餐',foodName:'米饭',spec:'250g/标准份',dinerCount:110,servings:220,needSeasoning:false,actualRatio:0.98},
{meal:'午餐',foodName:'鱼香肉丝',spec:'400g/标准份',dinerCount:110,servings:110,needSeasoning:true,actualRatio:0.99},
{meal:'晚餐',foodName:'米饭',spec:'250g/标准份',dinerCount:90,servings:180,needSeasoning:false,actualRatio:0.98},
{meal:'晚餐',foodName:'红烧肉',spec:'500g/标准份',dinerCount:90,servings:90,needSeasoning:true,actualRatio:1.04},
{meal:'晚餐',foodName:'清蒸鲈鱼',spec:'600g/标准份',dinerCount:90,servings:90,needSeasoning:false,actualRatio:0.99}
]},
{orderNo:'CO-1005',dayOffset:0,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'16:00',startTime:'14:05:00',successTime:'14:06:11',actualRatio:1.01,
meals:[{meal:'晚餐',dinerCount:95}],
details:[
{meal:'晚餐',foodName:'麻婆豆腐',spec:'400g/标准份',dinerCount:95,servings:95,needSeasoning:true,actualRatio:1.01},
{meal:'晚餐',foodName:'炒时蔬',spec:'300g/标准份',dinerCount:95,servings:95,needSeasoning:false,actualRatio:1.05},
{meal:'晚餐',foodName:'冬瓜排骨汤',spec:'400g/标准份',dinerCount:95,servings:95,needSeasoning:true,actualRatio:1.03}
]},
{orderNo:'CO-1006',dayOffset:0,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'06:30',startTime:'05:20:00',successTime:'05:20:58',actualRatio:0.99,
meals:[{meal:'早餐',dinerCount:75}],
details:[
{meal:'早餐',foodName:'馒头',spec:'100g/标准份',dinerCount:75,servings:150,needSeasoning:false,actualRatio:1.01},
{meal:'早餐',foodName:'煮鸡蛋',spec:'60g/标准份',dinerCount:75,servings:75,needSeasoning:false,actualRatio:1.02},
{meal:'早餐',foodName:'豆浆',spec:'350g/标准份',dinerCount:75,servings:75,needSeasoning:false,actualRatio:0.98}
]},
{orderNo:'CO-1007',dayOffset:0,genType:'auto',genStatus:'generating',creator:'系统',deliverTime:'10:00',startTime:'09:40:00',successTime:'',actualRatio:0.98,
meals:[{meal:'午餐',dinerCount:130}],
details:[
{meal:'午餐',foodName:'宫保鸡丁',spec:'500g/标准份',dinerCount:130,servings:130,needSeasoning:true,actualRatio:0.96},
{meal:'午餐',foodName:'鱼香肉丝',spec:'400g/标准份',dinerCount:130,servings:130,needSeasoning:true,actualRatio:0.99},
{meal:'午餐',foodName:'紫菜蛋花汤',spec:'350g/标准份',dinerCount:130,servings:130,needSeasoning:false,actualRatio:1.00},
{meal:'午餐',foodName:'米饭',spec:'250g/标准份',dinerCount:130,servings:260,needSeasoning:false,actualRatio:0.98}
]},
{orderNo:'CO-1008',dayOffset:0,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'16:00',startTime:'14:20:00',successTime:'14:21:05',actualRatio:0.99,
meals:[{meal:'晚餐',dinerCount:85}],
details:[
{meal:'晚餐',foodName:'糖醋里脊',spec:'350g/标准份',dinerCount:85,servings:85,needSeasoning:true,actualRatio:0.96},
{meal:'晚餐',foodName:'清炒西兰花',spec:'300g/标准份',dinerCount:85,servings:85,needSeasoning:false,actualRatio:1.02},
{meal:'晚餐',foodName:'酸奶',spec:'200g/标准份',dinerCount:85,servings:85,needSeasoning:false,actualRatio:0.97}
]},
{orderNo:'CO-1009',dayOffset:-1,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'10:00',startTime:'09:20:00',successTime:'09:21:30',actualRatio:0.99,
meals:[{meal:'午餐',dinerCount:90}],
details:[
{meal:'午餐',foodName:'番茄炒蛋',spec:'400g/标准份',dinerCount:90,servings:90,needSeasoning:false,actualRatio:0.97},
{meal:'午餐',foodName:'炒时蔬',spec:'300g/标准份',dinerCount:90,servings:90,needSeasoning:false,actualRatio:1.05}
]},
{orderNo:'CO-1010',dayOffset:-1,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'16:00',startTime:'14:10:00',successTime:'14:11:45',actualRatio:1.02,
meals:[{meal:'晚餐',dinerCount:90}],
details:[
{meal:'晚餐',foodName:'麻婆豆腐',spec:'400g/标准份',dinerCount:90,servings:90,needSeasoning:true,actualRatio:1.01},
{meal:'晚餐',foodName:'红烧肉',spec:'500g/标准份',dinerCount:90,servings:90,needSeasoning:true,actualRatio:0.98}
]},
{orderNo:'CO-1011',dayOffset:-1,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'06:30',startTime:'05:15:00',successTime:'05:16:02',actualRatio:0.99,
meals:[{meal:'早餐',dinerCount:70}],
details:[
{meal:'早餐',foodName:'白粥',spec:'300g/标准份',dinerCount:70,servings:70,needSeasoning:false,actualRatio:0.99},
{meal:'早餐',foodName:'茶叶蛋',spec:'60g/标准份',dinerCount:70,servings:70,needSeasoning:true,actualRatio:1.03},
{meal:'早餐',foodName:'豆浆',spec:'350g/标准份',dinerCount:70,servings:70,needSeasoning:false,actualRatio:0.98}
]},
{orderNo:'CO-1012',dayOffset:-1,genType:'auto',genStatus:'completed',creator:'系统',deliverTime:'10:00',startTime:'09:10:00',successTime:'09:11:10',actualRatio:1.00,
meals:[{meal:'午餐',dinerCount:100}],
details:[
{meal:'午餐',foodName:'宫保鸡丁',spec:'500g/标准份',dinerCount:100,servings:100,needSeasoning:true,actualRatio:0.96},
{meal:'午餐',foodName:'清蒸鲈鱼',spec:'600g/标准份',dinerCount:100,servings:100,needSeasoning:false,actualRatio:1.03},
{meal:'午餐',foodName:'米饭',spec:'250g/标准份',dinerCount:100,servings:200,needSeasoning:false,actualRatio:0.98}
]}
];
/* 基于名称的确定性哈希 */
function nameHash(s){
var h=0;
for(var i=0;i<s.length;i++){h=(h*31+s.charCodeAt(i))%1000;}
return h;
}
/* ========== 派生:为订单每个餐品补齐食材明细与推荐/实际数值 ========== */
function deriveDetail(order){
return order.details.map(function(d){
var bom=recipeBom[d.foodName]||[];
var baseRatio=d.actualRatio!=null?d.actualRatio:(order.actualRatio||1);
var perDishGram=bom.reduce(function(s,i){return s+i.perServing;},0);
var recRaw=parseFloat((perDishGram*d.servings/1000).toFixed(2));
var recCooked=parseFloat((recRaw*0.85).toFixed(2));
var ingredients=bom.map(function(ing){
var ingRec=parseFloat((ing.perServing*d.servings/1000).toFixed(3));
var offset=(nameHash(ing.name)%13-6)/100;
var ingRatio=baseRatio*(1+offset);
var ingAct=parseFloat((ingRec*ingRatio).toFixed(3));
return {name:ing.name,type:ing.type,perServing:ing.perServing,rec:ingRec,act:ingAct};
});
var actRaw=parseFloat(ingredients.reduce(function(s,i){return s+i.act;},0).toFixed(2));
return {
meal:d.meal,foodName:d.foodName,spec:d.spec,dinerCount:d.dinerCount,servings:d.servings,needSeasoning:d.needSeasoning,
recRaw:recRaw,recCooked:recCooked,actRaw:actRaw,ingredients:ingredients
};
});
}
/* ========== 差异率 ========== */
function diffInfo(act,rec){
if(!rec)return {cls:'flat',text:'— 持平'};
var pct=(act-rec)/rec*100;
if(pct>1)return {cls:'tag-red',text:'↑ 超 '+pct.toFixed(1)+'%'};
if(pct<-1)return {cls:'tag-green',text:' '+Math.abs(pct).toFixed(1)+'%'};
return {cls:'tag-gray',text:'— 持平'};
}
/* ========== 菜品种类(去重) ========== */
function distinctDishCount(o){
var set={};
o.details.forEach(function(d){set[d.foodName]=1;});
return Object.keys(set).length;
}
/* ========== 渲染状态 ========== */
var currentPage=1;
var pageSize=10;
var filteredOrders=[];
function applyFilter(){currentPage=1;renderList();}
function resetFilter(){
document.getElementById('fOrderNo').value='';
document.getElementById('fStatus').value='';
document.getElementById('fMeal').value='';
document.getElementById('fDate').value='';
currentPage=1;renderList();
}
function buildFilteredOrders(){
var fOrderNo=document.getElementById('fOrderNo').value.trim();
var fStatus=document.getElementById('fStatus').value;
var fMeal=document.getElementById('fMeal').value;
var fDate=document.getElementById('fDate').value;
return genOrders.filter(function(o){
if(fOrderNo&&o.orderNo.indexOf(fOrderNo)<0)return false;
if(fStatus&&o.genStatus!==fStatus)return false;
if(fMeal&&!o.meals.some(function(m){return m.meal===fMeal;}))return false;
if(fDate&&dateOffsetStr(o.dayOffset)!==fDate)return false;
return true;
});
}
function renderList(){
filteredOrders=buildFilteredOrders();
renderPage();
}
function renderPage(){
var total=filteredOrders.length;
var totalPages=Math.max(1,Math.ceil(total/pageSize));
if(currentPage<1)currentPage=1;
if(currentPage>totalPages)currentPage=totalPages;
var start=(currentPage-1)*pageSize;
var items=filteredOrders.slice(start,start+pageSize);
document.getElementById('tableSubTitle').textContent='共 '+total+' 单';
var empty=document.getElementById('historyEmpty');
empty.style.display=total?'none':'block';
var body=document.getElementById('historyTableBody');
if(!total){body.innerHTML='';renderPagination(0);return;}
var html='';
items.forEach(function(o,i){
var seq=(currentPage-1)*pageSize+i+1;
html+=renderOrderRow(o,seq);
});
body.innerHTML=html;
renderPagination(total);
}
function renderOrderRow(o,seq){
var genTypeTag='<span class="tag tag-blue">⚡ 自动</span>';
var statusTag=o.genStatus==='generating'
?'<span class="tag tag-cyan">生成中</span>'
:'<span class="tag tag-green">已完成</span>';
var mealCells=o.meals.map(function(m){return '<div>'+m.meal+'</div>';}).join('');
var countCells=o.meals.map(function(m){return '<div>'+m.dinerCount+'</div>';}).join('');
var recipeDate=dateOffsetStr(o.dayOffset);
var deliverDate=dateOffsetStr(o.dayOffset+1);
var successText=o.genStatus==='generating'?'—':(recipeDate+' '+o.successTime);
var html='<tr>';
html+='<td style="color:var(--text-secondary);">'+seq+'</td>';
html+='<td><b>'+o.orderNo+'</b></td>';
html+='<td>'+recipeDate+'</td>';
html+='<td>'+genTypeTag+'</td>';
html+='<td>'+statusTag+'</td>';
html+='<td class="meal-cell">'+mealCells+'</td>';
html+='<td class="meal-cell">'+countCells+'</td>';
html+='<td>'+distinctDishCount(o)+'</td>';
html+='<td style="color:var(--text-secondary);">'+deliverDate+' '+o.deliverTime+'</td>';
html+='<td style="color:var(--text-secondary);">'+recipeDate+' '+o.startTime+'</td>';
html+='<td style="color:var(--text-secondary);">'+successText+'</td>';
html+='<td>'+o.creator+'</td>';
html+='<td><span class="link" onclick="openDetail(\''+o.orderNo+'\')">数据明细</span></td>';
html+='</tr>';
return html;
}
/* ========== 分页 ========== */
function renderPagination(total){
var totalPages=Math.max(1,Math.ceil(total/pageSize));
var start=total?((currentPage-1)*pageSize+1):0;
var end=Math.min(currentPage*pageSize,total);
var html='<span>显示 '+start+'-'+end+' 条,共 '+total+' 条</span>';
html+='<button class="page-btn" '+(currentPage===1?'disabled':'')+' onclick="goPage('+(currentPage-1)+')"></button>';
for(var p=1;p<=totalPages;p++){
html+='<button class="page-btn'+(p===currentPage?' active':'')+'" onclick="goPage('+p+')">'+p+'</button>';
}
html+='<button class="page-btn" '+(currentPage===totalPages?'disabled':'')+' onclick="goPage('+(currentPage+1)+')"></button>';
document.getElementById('pagination').innerHTML=html;
}
function goPage(p){
var totalPages=Math.max(1,Math.ceil(filteredOrders.length/pageSize));
if(p<1||p>totalPages)return;
currentPage=p;
renderPage();
}
/* ========== 数据明细弹窗 ========== */
function openDetail(orderNo){
var o=genOrders.find(function(x){return x.orderNo===orderNo;});
if(!o)return;
document.getElementById('detailOrderNo').textContent=orderNo;
/* 概要 */
var recipeDate=dateOffsetStr(o.dayOffset);
var deliverDate=dateOffsetStr(o.dayOffset+1);
var statusText=o.genStatus==='generating'?'生成中':'已完成';
var summary='';
summary+='<div class="detail-summary__item"><span class="detail-summary__label">食谱日期</span><span class="detail-summary__value">'+recipeDate+'</span></div>';
summary+='<div class="detail-summary__item"><span class="detail-summary__label">生成方式</span><span class="detail-summary__value">自动</span></div>';
summary+='<div class="detail-summary__item"><span class="detail-summary__label">生成状态</span><span class="detail-summary__value">'+statusText+'</span></div>';
summary+='<div class="detail-summary__item"><span class="detail-summary__label">生成餐次</span><span class="detail-summary__value">'+o.meals.map(function(m){return m.meal+' '+m.dinerCount+'人';}).join('、')+'</span></div>';
summary+='<div class="detail-summary__item"><span class="detail-summary__label">预定配送时间</span><span class="detail-summary__value">'+deliverDate+' '+o.deliverTime+'</span></div>';
summary+='<div class="detail-summary__item"><span class="detail-summary__label">创建人</span><span class="detail-summary__value">'+o.creator+'</span></div>';
summary+='<div class="detail-summary__item"><span class="detail-summary__label">菜品种类</span><span class="detail-summary__value">'+distinctDishCount(o)+'</span></div>';
document.getElementById('detailSummary').innerHTML=summary;
/* 餐品列表 */
var details=deriveDetail(o);
var html='';
details.forEach(function(d){
var seasonTag=d.needSeasoning?'<span class="tag tag-blue">✓ 是</span>':'<span class="tag tag-gray">✗ 否</span>';
var diff=diffInfo(d.actRaw,d.recRaw);
html+='<tr class="dish-row" onclick="toggleIng(this)">';
html+='<td><span class="expand-arrow"></span></td>';
html+='<td><span class="tag tag-blue">'+d.meal+'</span></td>';
html+='<td style="font-weight:600;">'+d.foodName+'</td>';
html+='<td>'+d.spec+'</td>';
html+='<td>'+d.dinerCount+'</td>';
html+='<td>'+d.servings+'</td>';
html+='<td>'+seasonTag+'</td>';
html+='<td><b>'+d.recRaw.toFixed(2)+'</b></td>';
html+='<td>'+d.recCooked.toFixed(2)+'</td>';
html+='<td><b>'+d.actRaw.toFixed(2)+'</b></td>';
html+='<td><span class="tag '+diff.cls+'">'+diff.text+'</span></td>';
html+='</tr>';
html+='<tr class="ingredient-row" style="display:none;">';
html+='<td colspan="11">'+buildIngredientsDetail(d)+'</td>';
html+='</tr>';
});
document.getElementById('detailDishBody').innerHTML=html;
document.getElementById('modal-detail').classList.add('show');
}
function closeDetail(){document.getElementById('modal-detail').classList.remove('show');}
document.querySelector('.modal-mask').addEventListener('click',function(e){if(e.target===this)closeDetail();});
function buildIngredientsDetail(d){
var html='<table class="ing-table"><thead><tr><th>食材名称</th><th>类别</th><th>每份用量(g)</th><th>AI 推荐总量(kg)</th><th>实际用量(kg)</th><th>差异率</th></tr></thead><tbody>';
d.ingredients.forEach(function(ing){
var cls=ING_TYPE_CLS[ing.type]||'';
var diff=diffInfo(ing.act,ing.rec);
html+='<tr>';
html+='<td>'+ing.name+'</td>';
html+='<td><span class="ing-type '+cls+'">'+ing.type+'</span></td>';
html+='<td>'+ing.perServing+'</td>';
html+='<td>'+ing.rec.toFixed(3)+'</td>';
html+='<td>'+ing.act.toFixed(3)+'</td>';
html+='<td><span class="tag '+diff.cls+'">'+diff.text+'</span></td>';
html+='</tr>';
});
html+='</tbody></table>';
return html;
}
function toggleIng(tr){
var next=tr.nextElementSibling;
var arrow=tr.querySelector('.expand-arrow');
if(next&&next.classList.contains('ingredient-row')){
var isOpen=next.style.display!=='none';
next.style.display=isOpen?'none':'table-row';
tr.classList.toggle('expanded',!isOpen);
arrow.classList.toggle('expanded',!isOpen);
}
}
/* ========== 初始化 ========== */
renderList();
</script>
</body>
</html>
+1 -143
View File
@@ -268,7 +268,7 @@ tr.detail-row td.td-actions{background:#FAFBFC;box-shadow:-4px 0 8px rgba(0,0,0,
<div class="action-bar">
<button class="btn btn-primary" onclick="openModal('modal-order')"> 新建订单</button>
<button class="btn" style="background:#722ED1;color:#fff;" onclick="openModal('modal-recipe-order')">📋 由食谱快速创建订单</button>
<button class="btn" style="background:#13C2C2;color:#fff;" onclick="openModal('modal-gen-history')">📜 净菜订单生成历史</button>
<a class="btn" style="background:#13C2C2;color:#fff;text-decoration:none;" href="prod-clean-order-history.html">📜 净菜订单生成历史</a>
</div>
<div class="table-card">
<div class="table-card__header"><div class="table-card__title">净菜订单</div></div>
@@ -398,34 +398,6 @@ tr.detail-row td.td-actions{background:#FAFBFC;box-shadow:-4px 0 8px rgba(0,0,0,
</div>
</div>
<!-- 弹窗:净菜订单生成历史 -->
<div class="modal-mask" id="modal-gen-history">
<div class="modal" style="width:1080px;">
<div class="modal__header">
<div class="modal__title">净菜订单生成历史</div>
<button class="modal__close" onclick="closeModal('modal-gen-history')"></button>
</div>
<div class="modal__body">
<!-- 筛选 + 订单切换 -->
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:16px;">
<div class="filter-item"><label>食谱日期</label><input class="filter-input" type="date" id="ghDate" onchange="renderGenHistory()" style="min-width:150px;"></div>
<button class="btn btn-default" onclick="resetGenHistoryFilter()">重置</button>
</div>
<!-- 生成记录明细(订单信息与餐品明细合并) -->
<div style="overflow-x:auto;">
<table class="ing-table">
<thead><tr><th>食谱日期</th><th>餐次</th><th>餐品名称</th><th>就餐人数</th><th>建议熟重(kg)</th><th>内配料(是否)</th><th>规格</th><th>打包份数</th><th>预定配送时间</th><th>开始生成时间</th><th>生成成功时间</th></tr></thead>
<tbody id="genHistoryInfoBody"></tbody>
</table>
</div>
<div id="genHistoryEmpty" style="display:none;text-align:center;color:var(--text-placeholder);padding:40px 0;">暂无符合条件的生成记录</div>
</div>
<div class="modal__footer">
<button class="btn btn-default" onclick="closeModal('modal-gen-history')">关闭</button>
</div>
</div>
</div>
<script>
/* ========== 弹窗开关 ========== */
function openModal(id){
@@ -786,98 +758,6 @@ function genTypeTag(type){
:'<span class="tag tag-gray">✍ 手动</span>';
}
/* ========== 净菜订单生成历史 ========== */
var genHistory=[
{
orderNo:'CO-009',recipeDate:'2026-05-20',deliverTime:'2026-05-21 10:00',startTime:'2026-05-20 09:30:00',successTime:'2026-05-20 09:31:12',
details:[
{meal:'午餐',foodName:'宫保鸡丁',rawWeight:60,cookedWeight:51,dinerCount:120,needSeasoning:true,servings:120,avgWeight:0.5},
{meal:'午餐',foodName:'清蒸鲈鱼',rawWeight:60,cookedWeight:52.8,dinerCount:100,needSeasoning:false,servings:100,avgWeight:0.6}
]
},
{
orderNo:'CO-010',recipeDate:'2026-05-20',deliverTime:'2026-05-21 06:30',startTime:'2026-05-20 05:30:00',successTime:'2026-05-20 05:31:03',
details:[
{meal:'早餐',foodName:'小米粥',rawWeight:24,cookedWeight:20.4,dinerCount:80,needSeasoning:false,servings:80,avgWeight:0.3},
{meal:'早餐',foodName:'煮鸡蛋',rawWeight:4.8,cookedWeight:4.1,dinerCount:80,needSeasoning:false,servings:80,avgWeight:0.06}
]
},
{
orderNo:'CO-011',recipeDate:'2026-05-20',deliverTime:'2026-05-21 16:00',startTime:'2026-05-20 14:30:00',successTime:'2026-05-20 14:31:11',
details:[
{meal:'晚餐',foodName:'红烧肉',rawWeight:50,cookedWeight:42.5,dinerCount:100,needSeasoning:true,servings:100,avgWeight:0.5},
{meal:'晚餐',foodName:'清蒸鲈鱼',rawWeight:60,cookedWeight:52.8,dinerCount:100,needSeasoning:false,servings:100,avgWeight:0.6}
]
},
{
orderNo:'CO-012',recipeDate:'2026-05-19',deliverTime:'2026-05-20 16:00',startTime:'2026-05-19 14:30:00',successTime:'2026-05-19 14:31:15',
details:[
{meal:'晚餐',foodName:'麻婆豆腐',rawWeight:36,cookedWeight:30.6,dinerCount:90,needSeasoning:true,servings:90,avgWeight:0.4}
]
},
{
orderNo:'CO-013',recipeDate:'2026-05-20',deliverTime:'2026-05-21 14:00',startTime:'2026-05-20 13:30:00',successTime:'2026-05-20 13:31:02',
details:[
{meal:'加餐',foodName:'水果拼盘',rawWeight:18,cookedWeight:18,dinerCount:60,needSeasoning:false,servings:60,avgWeight:0.3}
]
}
];
function pad2(n){return String(n).padStart(2,'0');}
function formatDateTime(d){
return d.getFullYear()+'-'+pad2(d.getMonth()+1)+'-'+pad2(d.getDate())+' '+pad2(d.getHours())+':'+pad2(d.getMinutes())+':'+pad2(d.getSeconds());
}
function genSeasoningTag(needSeasoning){
return needSeasoning
?'<span class="tag tag-blue">✓ 是</span>'
:'<span class="tag tag-gray">✗ 否</span>';
}
var genHistoryFiltered=[];
function renderGenHistory(){
var date=document.getElementById('ghDate').value;
genHistoryFiltered=genHistory.filter(function(r){
if(date&&r.recipeDate!==date)return false;
return true;
});
var empty=document.getElementById('genHistoryEmpty');
empty.style.display=genHistoryFiltered.length?'none':'block';
/* 默认展示最近一次(数组首位) */
if(genHistoryFiltered.length>0){
renderGenHistoryOrder(genHistoryFiltered[0].orderNo);
}else{
document.getElementById('genHistoryInfoBody').innerHTML='';
}
}
function renderGenHistoryOrder(orderNo){
var rec=genHistoryFiltered.find(function(r){return r.orderNo===orderNo;});
if(!rec)rec=genHistory.find(function(r){return r.orderNo===orderNo;});
if(!rec)return;
/* 订单信息与餐品明细合并为一张表:每条明细一行,重复订单级字段 */
var infoBody=document.getElementById('genHistoryInfoBody');
var html='';
rec.details.forEach(function(d){
var spec=getDishSpecForRecipe(d.foodName);
var specLabel=spec.baseGram+'g/'+spec.portion;
html+='<tr>'
+'<td>'+rec.recipeDate+'</td>'
+'<td>'+d.meal+'</td>'
+'<td style="font-weight:500;">'+d.foodName+'</td>'
+'<td>'+d.dinerCount+'</td>'
+'<td>'+(parseFloat(d.cookedWeight).toFixed(2))+' kg</td>'
+'<td>'+genSeasoningTag(d.needSeasoning)+'</td>'
+'<td>'+specLabel+'</td>'
+'<td>'+d.servings+'</td>'
+'<td>'+rec.deliverTime+'</td>'
+'<td>'+rec.startTime+'</td>'
+'<td>'+rec.successTime+'</td>'
+'</tr>';
});
infoBody.innerHTML=html;
}
function resetGenHistoryFilter(){
document.getElementById('ghDate').value='';
renderGenHistory();
}
/* ========== 渲染订单表格 ========== */
function renderOrderTable(){
var tbody=document.getElementById('orderTableBody');
@@ -1596,11 +1476,6 @@ function submitRecipeOrder(){
if(missing.length>0){alert('以下餐次尚未填写就餐人数:'+missing.join('、'));return;}
/* 由食谱创建一个包含所选餐次全部菜品的订单,订单初始状态为「生成中」 */
var daily=getDailyMenu(recipeDate);
/* 记录本次生成历史:开始时间与模拟成功时间(1~2 分钟后) */
var genStart=new Date();
var genStartStr=formatDateTime(genStart);
var genSuccessStr=formatDateTime(new Date(genStart.getTime()+(60+Math.round(Math.random()*60))*1000));
var genDetails=[];
var dishMap={};
mealIndices.forEach(function(mi){
var input=document.querySelector('#dailyMenuBody .meal-diner-input[data-meal-idx="'+mi+'"]');
@@ -1612,25 +1487,9 @@ function submitRecipeOrder(){
/* 同菜品跨餐次:累加人数(按总人数核算) */
dishMap[dishName].dinerCount+=dinerCount;
}
/* 收集生成历史明细 */
var ghSpec=getDishSpecForRecipe(dishName);
var ghBaseGram=ghSpec.baseGram||500;
var ghServings=ghBaseGram>4000?Math.ceil(ghBaseGram*dinerCount/4000):dinerCount;
var ghRawWeight=parseFloat((ghBaseGram*dinerCount/1000).toFixed(2));
genDetails.push({
meal:MEAL_NAMES[mi],foodName:dishName,
rawWeight:ghRawWeight,cookedWeight:parseFloat((ghRawWeight*0.85).toFixed(2)),
dinerCount:dinerCount,needSeasoning:recipeSeasoningMap[dishName]===true,
servings:ghServings,avgWeight:parseFloat((ghRawWeight/ghServings).toFixed(2))
});
});
});
var orderNo='CO-'+String(orders.length+1).padStart(3,'0');
/* 写入净菜订单生成历史(订单粒度) */
genHistory.unshift({
orderNo:orderNo,recipeDate:recipeDate,deliverTime:deliveryTime.replace('T',' '),
startTime:genStartStr,successTime:genSuccessStr,details:genDetails
});
var newLines=[];
var di=0;
for(var name in dishMap){
@@ -1691,7 +1550,6 @@ function sanitizeIntegerInput(el){el.value=el.value.replace(/[^\d]/g,'');}
recipeSpecMap={};
onRecipeDateChange();
}
if(id==='modal-gen-history'){document.getElementById('ghDate').value='';renderGenHistory();}
document.getElementById(id).classList.add('show');
};
})();
@@ -324,18 +324,18 @@ var sportRecords = [
/* 体重记录 */
var weightRecords = [
{time:'2026-07-01 08:30',weight:81.8,bmi:28.1,fatRate:25.6,waist:93.9,source:'身高秤'},
{time:'2026-07-01 08:30',weight:81.8,bmi:28.1,fatRate:25.6,waist:93.9,source:'体重秤'},
{time:'2026-06-28 07:45',weight:82.1,bmi:28.2,fatRate:25.8,waist:94.2,source:'体脂仪'},
{time:'2026-06-25 18:20',weight:82.5,bmi:28.4,fatRate:26.1,waist:94.8,source:'蓝牙称'},
{time:'2026-06-22 08:10',weight:82.9,bmi:28.5,fatRate:26.3,waist:95.1,source:'身高秤'},
{time:'2026-06-22 08:10',weight:82.9,bmi:28.5,fatRate:26.3,waist:95.1,source:'体重秤'},
{time:'2026-06-19 20:00',weight:83.2,bmi:28.6,fatRate:26.6,waist:95.5,source:'自记'},
{time:'2026-06-16 09:00',weight:83.7,bmi:28.8,fatRate:26.9,waist:96.0,source:'体脂仪'},
{time:'2026-06-13 07:30',weight:84.1,bmi:28.9,fatRate:27.2,waist:96.4,source:'蓝牙称'},
{time:'2026-06-10 18:00',weight:84.5,bmi:29.1,fatRate:27.5,waist:96.7,source:'身高秤'},
{time:'2026-06-10 18:00',weight:84.5,bmi:29.1,fatRate:27.5,waist:96.7,source:'体重秤'},
{time:'2026-06-07 08:45',weight:84.9,bmi:29.2,fatRate:27.8,waist:97.2,source:'体脂仪'},
{time:'2026-06-04 19:30',weight:85.2,bmi:29.3,fatRate:28.1,waist:97.6,source:'自记'},
{time:'2026-06-01 08:00',weight:85.6,bmi:29.4,fatRate:28.4,waist:98.2,source:'蓝牙称'},
{time:'2026-05-28 07:15',weight:85.9,bmi:29.5,fatRate:28.7,waist:98.7,source:'身高秤'}
{time:'2026-05-28 07:15',weight:85.9,bmi:29.5,fatRate:28.7,waist:98.7,source:'体重秤'}
];
var pageSize = 10;
@@ -464,7 +464,7 @@ function renderWeight(){
var totalPages = Math.ceil(filtered.length / pageSize) || 1;
document.getElementById('weightPageInfo').textContent = '共 ' + filtered.length + ' 条';
document.getElementById('weightPageNum').textContent = '第 ' + (weightPage + 1) + '/' + totalPages + ' 页';
var srcTag = {身高秤:'tag-blue',体脂仪:'tag-orange',蓝牙称:'tag-green',自记:'tag-gray'};
var srcTag = {体重秤:'tag-blue',体脂仪:'tag-orange',蓝牙称:'tag-green',自记:'tag-gray'};
var html = '';
pageData.forEach(function(d,i){
html += '<tr>';
+3 -3
View File
@@ -252,7 +252,7 @@
**运动记录Tab表格列:** 时间、运动类型、运动时长/数据、消耗热量(千卡)、来源(标签:穿戴/微信/自记/器械)
**体重记录Tab表格列:** 时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、来源(标签:身高秤/体脂仪/蓝牙称/自记)
**体重记录Tab表格列:** 时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、来源(标签:体重秤/体脂仪/蓝牙称/自记)
**业务规则:**
- 变化正值红色标注(体重增加),负值绿色标注(体重减少)
@@ -891,7 +891,7 @@
**运动记录Tab表格列:** 时间、运动类型、运动时长/数据、消耗热量(千卡)、来源(标签:穿戴/微信/自记/器械)
**体重记录Tab表格列:** 时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、来源(标签:身高秤/体脂仪/蓝牙称/自记)
**体重记录Tab表格列:** 时间、体重(kg)、BMI、体脂率(%)、腰围(cm)、来源(标签:体重秤/体脂仪/蓝牙称/自记)
**交互说明:**
- 面包屑支持返回总览记录
@@ -1144,7 +1144,7 @@
**员工匿名 Tab:** 仅含使用日期筛选,表格列为姓名、使用时间 + 同上10项监测数据列 + 更多信息
**详情弹窗(监测数据详情 - **员工姓名**3组指标,对齐员工端身高秤报告):**
**详情弹窗(监测数据详情 - **员工姓名**3组指标,对齐员工端体重秤报告):**
- **基础指标:** 身高(cm)、体重(kg)、BMI、体脂率(%)
- **身体成分:** 体脂肪(kg)、水分率(%)、水分量(kg)、肌肉率(%)、肌肉量(kg)、骨量(kg)、去脂体重(kg)
- **体态分析:** 内脏脂肪、体型、理想体重(kg)、超重百分比(%)、体表面积(m²)
@@ -328,18 +328,18 @@ var sportRecords = [
/* 体重记录 */
var weightRecords = [
{time:'2026-07-01 08:30',weight:81.8,bmi:28.1,fatRate:25.6,waist:93.9,source:'身高秤'},
{time:'2026-07-01 08:30',weight:81.8,bmi:28.1,fatRate:25.6,waist:93.9,source:'体重秤'},
{time:'2026-06-28 07:45',weight:82.1,bmi:28.2,fatRate:25.8,waist:94.2,source:'体脂仪'},
{time:'2026-06-25 18:20',weight:82.5,bmi:28.4,fatRate:26.1,waist:94.8,source:'蓝牙称'},
{time:'2026-06-22 08:10',weight:82.9,bmi:28.5,fatRate:26.3,waist:95.1,source:'身高秤'},
{time:'2026-06-22 08:10',weight:82.9,bmi:28.5,fatRate:26.3,waist:95.1,source:'体重秤'},
{time:'2026-06-19 20:00',weight:83.2,bmi:28.6,fatRate:26.6,waist:95.5,source:'自记'},
{time:'2026-06-16 09:00',weight:83.7,bmi:28.8,fatRate:26.9,waist:96.0,source:'体脂仪'},
{time:'2026-06-13 07:30',weight:84.1,bmi:28.9,fatRate:27.2,waist:96.4,source:'蓝牙称'},
{time:'2026-06-10 18:00',weight:84.5,bmi:29.1,fatRate:27.5,waist:96.7,source:'身高秤'},
{time:'2026-06-10 18:00',weight:84.5,bmi:29.1,fatRate:27.5,waist:96.7,source:'体重秤'},
{time:'2026-06-07 08:45',weight:84.9,bmi:29.2,fatRate:27.8,waist:97.2,source:'体脂仪'},
{time:'2026-06-04 19:30',weight:85.2,bmi:29.3,fatRate:28.1,waist:97.6,source:'自记'},
{time:'2026-06-01 08:00',weight:85.6,bmi:29.4,fatRate:28.4,waist:98.2,source:'蓝牙称'},
{time:'2026-05-28 07:15',weight:85.9,bmi:29.5,fatRate:28.7,waist:98.7,source:'身高秤'}
{time:'2026-05-28 07:15',weight:85.9,bmi:29.5,fatRate:28.7,waist:98.7,source:'体重秤'}
];
var pageSize = 10;
@@ -445,7 +445,7 @@ function renderWeight(){
var totalPages = Math.ceil(weightRecords.length / pageSize) || 1;
document.getElementById('weightPageInfo').textContent = '共 ' + weightRecords.length + ' 条';
document.getElementById('weightPageNum').textContent = '第 ' + (weightPage + 1) + '/' + totalPages + ' 页';
var srcTag = {身高秤:'tag-blue',体脂仪:'tag-orange',蓝牙称:'tag-green',自记:'tag-gray'};
var srcTag = {体重秤:'tag-blue',体脂仪:'tag-orange',蓝牙称:'tag-green',自记:'tag-gray'};
var html = '';
pageData.forEach(function(d,i){
html += '<tr>';