feat:西安应急大屏页面优化(重构主屏页面交互+新增心血管监测副屏)

This commit is contained in:
fengpu
2026-06-26 17:32:36 +08:00
parent a8e3515a05
commit 33dcbf88c1
7 changed files with 3209 additions and 4730 deletions
@@ -1,5 +1,89 @@
# 应急大屏 - 变更记录
## [2026-06-26] feat: 西安应急中心大屏 v2 - 左下角"应急重点人员"改为"人员现状监测"环形图
### 变更(v2 文件 `xian-center/index-v2.html`
- **面板替换**:左下角"应急求助重点人员"面板整体替换为"人员现状监测"面板
- 删除原 `.help-person-list` / `.help-person-item` 等 CSS 与 5 行人员列表 HTML
- 保留面板头部下拉框(今日 / 近一周 / 近一月 / 近一年)与 `switchPeriod` 调用不变
- **环形图(SVG,自包含)**150×150 容器,viewBox `0 0 160 160`,圆心 (80,80)、半径 60、stroke-width 14
- 底环衬底 `.hm-ring--bg`rgba(24,144,255,0.08)
- 5 段采用 `<circle>` + `stroke-dasharray` / `stroke-dashoffset` 实现,整环 `transform:rotate(-90deg)` 自顶端起顺时针绘制,段间留 3 单位间隔
- 每段独立 `drop-shadow` 发光(与该段 stroke 色同色,alpha 0.55
- **5 类人员与数据**
- 健康人员 9,420 / 73.3%#52C41Adasharray "265 112" / offset 0
- 慢病人员 1,860 / 14.5%#FAAD14dasharray "52 325" / offset -268
- 指标异常 620 / 4.8%#00e5ffdasharray "18 359" / offset -323
- 大病人员 580 / 4.5%#FF4D4Fdasharray "16 361" / offset -344
- 评估异常 376 / 2.9%#b37febdasharray "11 366" / offset -363
- **中心信息**:绝对定位居中显示「总人数 / 12,856 / 人」,主数字 26px 加粗 + 蓝色文字阴影
- **图例**:环形图右侧 5 行图例,每行 = 色块(带同色 box-shadow 发光)+ 名称 + 数量 + 百分比
- **配色依据**:健康=success 绿、慢病=warning 橙、指标异常=cyan 青、大病=danger 红、评估异常=紫(沿用大屏设计令牌)
## [2026-06-26] feat: 西安应急中心大屏 v2 - 资源 Tab 图标化(色点升级为分类 SVG 图标)
### 变更(v2 文件 `xian-center/index-v2.html`
- **资源 Tab 图标升级**:7 个资源 Tab 标题前的色点(`.dot--xxx`)替换为各分类专属 inline SVG 图标
- 新增 CSS`.res-tab__icon`14×14 基础尺寸)+ 7 个 `.res-tab__icon--xxx` 修饰类,按分类设置 `color`(沿用图例原配色:all/oilfield/local 用 #1890FFcooperative 用 #722ED1clinic 用 #13C2C2ambulance 用 #FAAD14aed 用 #FF4D4F
- 激活态发光:`.res-tab.active .res-tab__icon{filter:drop-shadow(0 0 4px currentColor);}`,图标色不被激活态覆盖
- 删除原 7 个 `.dot--xxx` 色点修饰类(`.dot--all/--oilfield/--cooperative/--local/--clinic/--ambulance/--aed`
- 保留 `.res-tab .dot``.res-tab.active .dot` 规则,供「路径规划」Tab 继续使用通用灰点
- **图标设计**16×16 viewBoxstroke 线条风格,stroke-width 1.5stroke-linecap/linejoin round):
- 全部资源 → 四方格网格(2×2 rect)
- 油田医院 → 油滴轮廓 + 中央十字
- 合作医院 → 两个相扣圆圈
- 地方医院 → 三角屋顶 + 矩形墙 + 中央十字
- 医疗点 → 急救箱(带提手矩形 + 十字)
- 救护车 → 厢式车体 + 两车轮 + 车身十字
- AED → 心形 + 内部闪电折线
- **HTML 替换**7 个资源 Tab 中 `<span class="dot dot--xxx"></span>` 替换为 `<svg class="res-tab__icon res-tab__icon--xxx" viewBox="0 0 16 16" ...>...</svg>`;数量徽标 `<span class="res-tab__count">N</span>` 保持不变;路径规划 Tab 维持原 `<span class="dot"></span>`
## [2026-06-26] refactor: 西安应急中心大屏 v2 - 移除地图图例 + 资源 Tab 增加分类色点与数量
### 变更(v2 文件 `xian-center/index-v2.html`
- **移除地图图例**:删除原悬浮于地图左上角的 `.map-legend` 整块 HTML(6 项图例)及对应 CSS(`.map-legend` / `.map-legend__item` / `.map-legend__item::before` / `.map-legend__dot`
- 4 处 JS 中 `var legend=document.querySelector('.map-legend'); if(legend)legend.style.display=…` 调用因 `if` 守卫变为无害 no-op,未做改动以缩小变更面
- **资源 Tab 增加分类色点**:每个资源 Tab 标题前的 `.dot` 由通用灰点改为各分类专属色(沿用图例原配色)
- 新增 CSS 修饰类:`.dot--all`(四色渐变 1890FF/722ED1/13C2C2/FAAD14/ `.dot--oilfield`#1890FF/ `.dot--cooperative`#722ED1/ `.dot--local`(透明 + 1.5px 虚线 #1890FF/ `.dot--clinic`#13C2C2/ `.dot--ambulance`#FAAD14/ `.dot--aed`#FF4D4F
- `.res-tab.active .dot` 由"覆盖为 success 绿色"改为"保留原色 + `box-shadow:0 0 6px currentColor` 发光"
- 「路径规划」Tab 维持原通用灰点,不改色
- **资源 Tab 增加数量徽标**:每个资源 Tab 标题后追加 `<span class="res-tab__count">N</span>` 数量徽标
- 数量与图例原计数一致:全部资源 11(1+1+2+3+2+2 之和)/ 油田医院 1 / 合作医院 1 / 地方医院 2 / 医疗点 3 / 救护车 2 / AED 2;路径规划 Tab 无数量
- 新增 CSS `.res-tab__count`11px tabular-nums,半透明胶囊背景;激活态白字 + 高对比背景
- **微调**`.res-tab` 左右内边距由 `0 18px` 改为 `0 14px`,以容纳新增的数量徽标并控制 Tab 栏总宽
## [2026-06-26] feat: 西安应急中心大屏 v2 - 统计卡片下移 + 地图图例左上角单行化
### 变更(v2 文件 `xian-center/index-v2.html`
- **统计卡片从地图上方移至下方**`.top-stats` 块从 `.center``map-wrap` 之前移动到之后
- `.center` 当前结构:地图容器(flex:1)→ 底部统计卡片栏(flex-shrink:0
- HTML 注释由"顶部统计卡片栏"改为"底部统计卡片栏"
- 卡片样式(去图标、数字居中)保持不变
- **地图图例移至左上角并改为单行布局**:原位于地图左下角、2 列网格的图例改为左上角单行
- CSS `.map-legend``bottom:12px;left:12px``top:12px;left:12px``display:grid;grid-template-columns:1fr 1fr;column-gap:16px;row-gap:6px;``display:flex;align-items:center;gap:12px;`
- 新增 `z-index:12``backdrop-filter:blur(8px)``box-shadow`,与 `.res-tabs``.map-layer` 同层悬浮
- `.map-legend__item` 新增 `white-space:nowrap`,相邻项之间以竖线分隔符 `::before` 替代原网格间距
- 6 项图例顺序不变:油田医院 / 合作医院 / 地方医院 / 医疗点 / 救护车 / AED
- HTML 注释由"地图图例"改为"地图图例:悬浮于地图左上角,单行展示"
## [2026-06-26] feat: 西安应急中心大屏 v2 - 顶部统计卡片去图标化 + 视频通话弹窗化
### 变更(v2 文件 `xian-center/index-v2.html`
- **顶部统计卡片去图标化**:4 张顶部 KPI 卡片(今日应急求助总数 / 今日待处理应急数 / 今日平均响应时长 / 今日告警次数)移除左侧图标,数字与标签整体居中显示
- CSS`.top-stat-card` 改为 `flex-direction:column;justify-content:center;align-items:center;text-align:center`,删除 `.top-stat-card__icon` 相关样式及 4 种色系变体的图标规则
- HTML:删除 4 张卡片中的 `<div class="top-stat-card__icon">…</div>`
- 保留左侧 3px 色条 `::before`、数字+单位、标签、趋势小标记(↑12% / ↓8%)
- **视频通话按钮弹窗化**:地图右下角"视频通话"按钮点击行为由 `alert` 升级为选择弹窗
- 新增 `[弹窗] 视频通话 - 选择加入的应急中心``#videoCallModal`),复用 `.modal-overlay` + `.modal` 基础结构,宽度 560px
- 弹窗内含 6 张应急中心卡片(救场/标准/长庆油田/延安/庆阳/榆林),2 列网格布局
- 每张卡片显示:中心名称、在线状态徽章(在线/忙碌/离线 三态,含发光小圆点)、值班负责人、在线人数
- 卡片支持多选:点击切换 `.vc-card--selected` 选中态,右上角显示对勾角标;离线卡片不可选(半透明 + 锁图标 + `cursor:not-allowed`
- 默认预选「救场应急中心」「标准应急中心」两张在线卡片
- 底部按钮:取消 / 发起通话;`confirmVideoCall()` 收集已选中中心名称,未选时提示并重新打开弹窗
- `openEmergencyRoute()` 关闭序列同步追加 `closeVideoCallModal()`
- **新增 CSS 模块**`.vc-modal` / `.vc-grid` / `.vc-card`(含 `--selected`/`--offline` 变体)/ `.vc-card__row` / `.vc-card__name` / `.vc-card__status``--online`/`--busy`/`--offline`/ `.vc-card__dot`(三色)/ `.vc-card__meta` / `.vc-card__check` / `.vc-card__lock`
- **新增 JS 函数**`onVideoCall()`(打开弹窗)/ `closeVideoCallModal()` / `toggleVcCard(el)` / `confirmVideoCall()`
## [2026-06-25] feat: 西安应急中心大屏 v3 - 图表化重构 + 顶部 KPI 条 + 双栏可视化升级
### 新增(v3 文件 `xian-center/index-v3.html`v1/v2 保持不变)
File diff suppressed because it is too large Load Diff
@@ -107,84 +107,27 @@
.side{width:320px;flex-shrink:0;display:flex;flex-direction:column;gap:12px;}
.center{flex:1;display:flex;flex-direction:column;min-width:0;position:relative;gap:12px;}
/* ========== 地图内悬浮资源统计胶囊(医疗点/救护车/AED) ========== */
/* [交互] 默认隐藏,点击医疗点/救护车/AED Tab 后在地图底部居中悬浮显示 */
.res-stat-row{position:absolute;bottom:24px;left:50%;transform:translateX(-50%);z-index:11;
display:none;align-items:center;gap:6px;padding:6px 10px;
background:rgba(10,22,48,0.78);border:1px solid var(--panel-border);border-radius:24px;
backdrop-filter:blur(12px);box-shadow:0 6px 24px rgba(0,0,0,0.45),0 0 18px rgba(24,144,255,0.08);}
.res-stat-row::before{content:'';position:absolute;top:0;left:0;width:10px;height:10px;
border-top:1.5px solid rgba(24,144,255,0.45);border-left:1.5px solid rgba(24,144,255,0.45);border-radius:24px 0 0 0;pointer-events:none;}
.res-stat-row::after{content:'';position:absolute;bottom:0;right:0;width:10px;height:10px;
border-bottom:1.5px solid rgba(24,144,255,0.45);border-right:1.5px solid rgba(24,144,255,0.45);border-radius:0 0 24px 0;pointer-events:none;}
.res-stat-card{display:flex;align-items:center;gap:8px;padding:6px 12px;border-radius:18px;
background:transparent;border:none;position:relative;transition:all .25s;}
.res-stat-card:hover{background:rgba(24,144,255,0.1);}
.res-stat-card + .res-stat-card::before{content:'';position:absolute;left:-3px;top:50%;
transform:translateY(-50%);width:1px;height:18px;background:rgba(74,107,138,0.35);}
.res-stat-card__icon{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;
justify-content:center;flex-shrink:0;background:rgba(24,144,255,0.16);color:var(--primary);}
.res-stat-card__icon--cyan{background:rgba(19,194,194,0.16);color:var(--cyan);}
.res-stat-card__icon--warning{background:rgba(250,173,20,0.16);color:var(--warning);}
.res-stat-card__icon--danger{background:rgba(255,77,79,0.16);color:var(--danger);}
.res-stat-card__icon--success{background:rgba(82,196,26,0.16);color:var(--success);}
.res-stat-card__icon svg{width:16px;height:16px;}
.res-stat-card__body{display:flex;flex-direction:column;gap:0;min-width:0;line-height:1.1;}
.res-stat-card__num{font-size:18px;font-weight:700;color:#fff;line-height:1.1;text-shadow:0 0 10px rgba(24,144,255,0.3);}
.res-stat-card__label{font-size:10px;color:var(--text-sub);margin-top:1px;}
/* ========== 路径规划面板(Tab 栏下方悬浮) ========== */
/* [交互] 点击"路径规划"Tab 后在地图顶部 Tab 栏下方展开 */
.plan-panel{position:absolute;top:62px;left:50%;transform:translateX(-50%);z-index:19;
display:none;align-items:center;gap:8px;padding:8px 14px;
background:rgba(10,22,48,0.9);border:1px solid var(--panel-border);border-radius:10px;
backdrop-filter:blur(10px);box-shadow:0 6px 22px rgba(0,0,0,0.5),0 0 16px rgba(24,144,255,0.1);
white-space:nowrap;}
.plan-panel--show{display:flex;}
.plan-panel::before{content:'';position:absolute;top:0;left:0;width:10px;height:10px;
border-top:1.5px solid rgba(24,144,255,0.45);border-left:1.5px solid rgba(24,144,255,0.45);border-radius:10px 0 0 0;pointer-events:none;}
.plan-panel::after{content:'';position:absolute;bottom:0;right:0;width:10px;height:10px;
border-bottom:1.5px solid rgba(24,144,255,0.45);border-right:1.5px solid rgba(24,144,255,0.45);border-radius:0 0 10px 0;pointer-events:none;}
.plan-panel__field{display:flex;align-items:center;gap:6px;}
.plan-panel__label{font-size:12px;color:var(--text-sub);white-space:nowrap;}
.plan-panel__label .dot{width:6px;height:6px;border-radius:50%;display:inline-block;margin-right:4px;vertical-align:middle;}
.plan-panel__label--start .dot{background:var(--success);box-shadow:0 0 6px var(--success);}
.plan-panel__label--end .dot{background:var(--danger);box-shadow:0 0 6px var(--danger);}
.plan-panel__select{height:28px;padding:0 24px 0 10px;font-size:12px;color:var(--text-main);
background:rgba(24,144,255,0.08);border:1px solid var(--panel-border);border-radius:6px;
cursor:pointer;outline:none;transition:all .2s;min-width:130px;
appearance:none;-webkit-appearance:none;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' stroke='%237895A8' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
background-repeat:no-repeat;background-position:right 8px center;}
.plan-panel__select:hover,.plan-panel__select:focus{border-color:var(--primary);background-color:rgba(24,144,255,0.14);}
.plan-panel__btn{display:flex;align-items:center;gap:5px;height:28px;padding:0 14px;
border-radius:6px;font-size:12px;cursor:pointer;transition:all .2s;white-space:nowrap;
background:linear-gradient(90deg,rgba(24,144,255,0.3),rgba(24,144,255,0.15));
color:#fff;border:1px solid rgba(24,144,255,0.5);}
.plan-panel__btn:hover{background:linear-gradient(90deg,rgba(24,144,255,0.45),rgba(24,144,255,0.25));
box-shadow:0 0 12px rgba(24,144,255,0.3);}
.plan-panel__btn svg{width:14px;height:14px;}
.plan-panel__btn--ghost{background:rgba(74,107,138,0.18);color:var(--text-sub);border-color:rgba(74,107,138,0.35);}
.plan-panel__btn--ghost:hover{background:rgba(74,107,138,0.28);color:var(--text-main);box-shadow:none;}
.plan-panel__divider{width:1px;height:18px;background:rgba(74,107,138,0.3);}
/* ========== 路径规划 SVG 层(独立于急救路线) ========== */
.plan-route-svg{position:absolute;inset:0;width:100%;height:100%;z-index:6;pointer-events:none;display:none;}
.plan-route-svg--show{display:block;}
.plan-route-svg .plan-route-path{fill:none;stroke:#00E5FF;stroke-width:0.6;
stroke-dasharray:1.2 0.8;filter:drop-shadow(0 0 1px rgba(0,229,255,0.8));
animation:planRouteDash 1s linear infinite;}
@keyframes planRouteDash{0%{stroke-dashoffset:0;}100%{stroke-dashoffset:-20;}}
.plan-endpoint{transform-origin:center;animation:planEndpointPulse 1.6s ease-in-out infinite;}
@keyframes planEndpointPulse{0%,100%{opacity:1;}50%{opacity:0.55;}}
.plan-route-info{position:absolute;top:104px;right:12px;z-index:18;display:none;
background:rgba(10,22,48,0.88);border:1px solid var(--panel-border);border-radius:8px;
padding:8px 12px;font-size:11px;color:var(--text-sub);min-width:130px;
backdrop-filter:blur(6px);box-shadow:0 4px 16px rgba(0,0,0,0.4);}
.plan-route-info--show{display:block;}
.plan-route-info__title{font-size:12px;color:var(--text-main);font-weight:600;margin-bottom:4px;}
.plan-route-info__row{display:flex;justify-content:space-between;gap:12px;line-height:1.6;}
.plan-route-info__row b{color:#fff;font-weight:600;}
/* ========== 中心KPI卡片行 ========== */
.kpi-row{display:flex;gap:12px;flex-shrink:0;}
.kpi-card{flex:1;background:linear-gradient(135deg,rgba(10,25,50,0.85),rgba(15,40,70,0.7));
border:1px solid var(--panel-border);border-radius:10px;padding:14px 16px;position:relative;
overflow:hidden;transition:all .3s;}
.kpi-card:hover{border-color:rgba(24,144,255,0.4);box-shadow:0 0 20px rgba(24,144,255,0.12);}
.kpi-card::before{content:'';position:absolute;top:0;left:0;width:12px;height:12px;
border-top:1.5px solid rgba(24,144,255,0.4);border-left:1.5px solid rgba(24,144,255,0.4);}
.kpi-card::after{content:'';position:absolute;bottom:0;right:0;width:12px;height:12px;
border-bottom:1.5px solid rgba(24,144,255,0.4);border-right:1.5px solid rgba(24,144,255,0.4);}
.kpi-card__header{display:flex;align-items:center;gap:8px;margin-bottom:8px;}
.kpi-card__diamond{width:8px;height:8px;background:var(--primary);transform:rotate(45deg);
box-shadow:0 0 6px rgba(24,144,255,0.6);flex-shrink:0;}
.kpi-card__diamond--cyan{background:var(--cyan);box-shadow:0 0 6px rgba(0,229,255,0.6);}
.kpi-card__diamond--green{background:var(--success);box-shadow:0 0 6px rgba(82,196,26,0.6);}
.kpi-card__diamond--orange{background:var(--warning);box-shadow:0 0 6px rgba(250,173,20,0.6);}
.kpi-card__label{font-size:11px;color:var(--text-sub);}
.kpi-card__num{font-size:28px;font-weight:700;color:#fff;line-height:1.2;text-shadow:0 0 12px rgba(24,144,255,0.3);}
.kpi-card__trend{font-size:10px;color:var(--text-dim);margin-top:2px;display:flex;align-items:center;gap:4px;}
.kpi-card__trend--up{color:var(--success);}
.kpi-card__trend--down{color:var(--danger);}
/* ========== 通用面板(增强) ========== */
.panel{background:var(--panel-bg);border:1px solid var(--panel-border);border-radius:10px;
@@ -652,8 +595,8 @@
box-shadow:0 0 12px rgba(24,144,255,0.2);}
.map-ctrl__btn svg{width:16px;height:16px;flex-shrink:0;}
/* ========== 地图右角:图层切换 ========== */
.map-layer{position:absolute;top:12px;right:12px;z-index:12;
/* ========== 地图右角:图层切换 ========== */
.map-layer{position:absolute;bottom:12px;right:178px;z-index:12;
background:rgba(10,22,48,0.88);border:1px solid var(--panel-border);border-radius:8px;
padding:8px 10px;display:flex;flex-direction:column;gap:2px;
backdrop-filter:blur(6px);}
@@ -981,6 +924,43 @@
<!-- 中间地图 -->
<div class="center">
<!-- KPI 核心指标卡片行 -->
<!-- [交互] 数据大屏核心指标展示,数值可用于动态刷新 -->
<div class="kpi-row">
<div class="kpi-card">
<div class="kpi-card__header">
<div class="kpi-card__diamond"></div>
<span class="kpi-card__label">穿戴设备总数</span>
</div>
<div class="kpi-card__num">156</div>
<!-- <div class="kpi-card__trend kpi-card__trend&#45;&#45;up">▲ 较上月 +12</div>-->
</div>
<div class="kpi-card">
<div class="kpi-card__header">
<div class="kpi-card__diamond kpi-card__diamond--cyan"></div>
<span class="kpi-card__label">今日穿戴报警数</span>
</div>
<div class="kpi-card__num">23</div>
<div class="kpi-card__trend kpi-card__trend--down">▲ 较昨日 +5</div>
</div>
<div class="kpi-card">
<div class="kpi-card__header">
<div class="kpi-card__diamond kpi-card__diamond--orange"></div>
<span class="kpi-card__label">今日应急求助数</span>
</div>
<div class="kpi-card__num" style="color:var(--warning);text-shadow:0 0 12px rgba(250,173,20,0.3);">7</div>
<div class="kpi-card__trend kpi-card__trend--down">▼ 待处理 3 条</div>
</div>
<div class="kpi-card">
<div class="kpi-card__header">
<div class="kpi-card__diamond kpi-card__diamond--green"></div>
<span class="kpi-card__label">平均响应时长</span>
</div>
<div class="kpi-card__num" style="color:var(--success);text-shadow:0 0 12px rgba(82,196,26,0.3);">2.4<span style="font-size:14px;font-weight:400;"> 分钟</span></div>
<!-- <div class="kpi-card__trend kpi-card__trend&#45;&#45;up">▲ 较昨日优化 12%</div>-->
</div>
</div>
<!-- 地图容器 -->
<div class="map-wrap" id="mapWrap">
<!-- 资源Tab栏 —— 内嵌在地图顶部 -->
@@ -1006,66 +986,7 @@
<div class="res-tab" onclick="switchTab(this,'aed')">
<span class="dot"></span>AED
</div>
<div class="res-tab res-tab--plan" id="planTab" onclick="toggleRoutePlan(this)">
<span class="dot"></span>路径规划
</div>
</nav>
<!-- [交互] 路径规划面板:点击"路径规划"Tab 后在 Tab 栏下方展开 -->
<div class="plan-panel" id="planPanel">
<div class="plan-panel__field">
<label class="plan-panel__label plan-panel__label--start"><span class="dot"></span>起点</label>
<select class="plan-panel__select" id="planStart">
<option value="emp">当前位置</option>
<option value="clinic1">采油一厂医疗点</option>
<option value="clinic2">采油二厂医疗点</option>
<option value="clinic3">采油三厂医疗点</option>
<option value="hospital1">兴隆医院</option>
<option value="hospital2">宝石花医院</option>
<option value="hospital3">庆阳医院</option>
<option value="hospital4">高陵医院</option>
<option value="amb1">陕A·120-03</option>
<option value="amb2">陕A·120-01</option>
<option value="aed1">AED-001</option>
<option value="aed2">AED-002</option>
</select>
</div>
<div class="plan-panel__divider"></div>
<div class="plan-panel__field">
<label class="plan-panel__label plan-panel__label--end"><span class="dot"></span>终点</label>
<select class="plan-panel__select" id="planEnd">
<option value="emp">当前位置</option>
<option value="clinic1">采油一厂医疗点</option>
<option value="clinic2">采油二厂医疗点</option>
<option value="clinic3" selected>采油三厂医疗点</option>
<option value="hospital1">兴隆医院</option>
<option value="hospital2">宝石花医院</option>
<option value="hospital3">庆阳医院</option>
<option value="hospital4">高陵医院</option>
<option value="amb1">陕A·120-03</option>
<option value="amb2">陕A·120-01</option>
<option value="aed1">AED-001</option>
<option value="aed2">AED-002</option>
</select>
</div>
<div class="plan-panel__divider"></div>
<button class="plan-panel__btn" onclick="executeRoutePlan()">
<svg viewBox="0 0 16 16" fill="none"><path d="M2 13L6 9l3 3 5-6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6" cy="9" r="1.6" fill="currentColor"/><circle cx="13" cy="6" r="1.6" fill="currentColor"/></svg>
路径规划
</button>
<button class="plan-panel__btn plan-panel__btn--ghost" onclick="clearRoutePlan()">
<svg viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
清除
</button>
</div>
<!-- [交互] 路径规划信息卡片:规划完成后在地图右上角显示距离与用时 -->
<div class="plan-route-info" id="planRouteInfo">
<div class="plan-route-info__title">规划路线</div>
<div class="plan-route-info__row"><span>总距离</span><b id="planRouteDist">--</b></div>
<div class="plan-route-info__row"><span>预计用时</span><b id="planRouteTime">--</b></div>
</div>
<div class="map-bg"></div>
<!-- 道路 -->
<div class="map-road map-road--h" style="top:30%"></div>
@@ -1316,38 +1237,6 @@
视频通话
</button>
</div>
<!-- [交互] 路径规划 SVG 层:独立于急救路线,绘制规划路径与起终点标记 -->
<svg class="plan-route-svg" id="planRouteSvg" viewBox="0 0 100 100" preserveAspectRatio="none">
<path class="plan-route-path" id="planRoutePath" d="" opacity="0"/>
<circle class="plan-endpoint" id="planStartDot" cx="0" cy="0" r="1.2" fill="#52C41A" opacity="0"/>
<circle class="plan-endpoint" id="planEndDot" cx="0" cy="0" r="1.2" fill="#FF4D4F" opacity="0"/>
</svg>
<!-- [交互] 资源统计胶囊:点击医疗点/救护车/AED Tab 后在地图底部居中悬浮显示 -->
<div class="res-stat-row" id="resStatRow">
<div class="res-stat-card">
<div class="res-stat-card__icon" id="resStatIcon1"></div>
<div class="res-stat-card__body">
<div class="res-stat-card__num" id="resStatNum1">--</div>
<div class="res-stat-card__label" id="resStatLabel1">--</div>
</div>
</div>
<div class="res-stat-card">
<div class="res-stat-card__icon" id="resStatIcon2"></div>
<div class="res-stat-card__body">
<div class="res-stat-card__num" id="resStatNum2">--</div>
<div class="res-stat-card__label" id="resStatLabel2">--</div>
</div>
</div>
<div class="res-stat-card">
<div class="res-stat-card__icon" id="resStatIcon3"></div>
<div class="res-stat-card__body">
<div class="res-stat-card__num" id="resStatNum3">--</div>
<div class="res-stat-card__label" id="resStatLabel3">--</div>
</div>
</div>
</div>
</div>
</div>
@@ -2627,158 +2516,6 @@ function switchTab(el,type){
}else{
markers.forEach(function(m){if(m.getAttribute('data-type')===type)m.style.display='block';});
}
/* 同步更新地图下方资源统计卡片行 */
updateResStatRow(type);
/* 切换到普通资源 Tab 时,关闭路径规划面板并清除已绘制路径 */
var planPanel=document.getElementById('planPanel');
if(planPanel)planPanel.classList.remove('plan-panel--show');
clearRoutePlan();
}
/* ========== 路径规划(独立于急救路线) ========== */
/* 路径规划备选点坐标(viewBox 0 0 100 100,与地图标记点位置一致) */
var PLAN_POINTS = {
emp: {x:50, y:50, name:'当前位置'},
clinic1: {x:35, y:38, name:'采油一厂医疗点'},
clinic2: {x:65, y:43, name:'采油二厂医疗点'},
clinic3: {x:50, y:80, name:'采油三厂医疗点'},
hospital1: {x:12, y:18, name:'兴隆医院'},
hospital2: {x:75, y:20, name:'宝石花医院'},
hospital3: {x:60, y:55, name:'庆阳医院'},
hospital4: {x:15, y:75, name:'高陵医院'},
amb1: {x:35, y:60, name:'陕A·120-03'},
amb2: {x:55, y:25, name:'陕A·120-01'},
aed1: {x:28, y:33, name:'AED-001'},
aed2: {x:68, y:47, name:'AED-002'}
};
/* 切换路径规划 Tab:展开/收起规划面板,并保持所有地图标记可见 */
function toggleRoutePlan(el){
var tab=document.getElementById('planTab');
var panel=document.getElementById('planPanel');
if(!tab||!panel)return;
var isActive=tab.classList.contains('active');
if(isActive){
/* 再次点击:收起面板并清除路径 */
tab.classList.remove('active');
panel.classList.remove('plan-panel--show');
clearRoutePlan();
}else{
/* 激活:取消其他 Tab 选中、隐藏统计胶囊、显示全部标记 */
document.querySelectorAll('.res-tab').forEach(function(t){t.classList.remove('active','res-tab--route-active');});
tab.classList.add('active');
panel.classList.add('plan-panel--show');
var statRow=document.getElementById('resStatRow');
if(statRow)statRow.style.display='none';
document.querySelectorAll('.map-marker').forEach(function(m){m.style.display='block';});
/* 仅清除已绘制路径,保留面板打开 */
var svg=document.getElementById('planRouteSvg');
var path=document.getElementById('planRoutePath');
var info=document.getElementById('planRouteInfo');
var sd=document.getElementById('planStartDot');
var ed=document.getElementById('planEndDot');
if(svg)svg.classList.remove('plan-route-svg--show');
if(path)path.setAttribute('opacity','0');
if(info)info.classList.remove('plan-route-info--show');
if(sd)sd.setAttribute('opacity','0');
if(ed)ed.setAttribute('opacity','0');
}
}
/* 清除路径规划已绘制的路径与信息卡片(不动 Tab 与面板状态) */
function clearRoutePlan(){
var svg=document.getElementById('planRouteSvg');
var path=document.getElementById('planRoutePath');
var info=document.getElementById('planRouteInfo');
var sd=document.getElementById('planStartDot');
var ed=document.getElementById('planEndDot');
if(svg)svg.classList.remove('plan-route-svg--show');
if(path)path.setAttribute('opacity','0');
if(info)info.classList.remove('plan-route-info--show');
if(sd)sd.setAttribute('opacity','0');
if(ed)ed.setAttribute('opacity','0');
}
/* 执行路径规划:根据起点终点绘制曲线路径并显示距离与用时 */
function executeRoutePlan(){
var sk=document.getElementById('planStart').value;
var ek=document.getElementById('planEnd').value;
if(sk===ek){alert('起点和终点不能相同,请重新选择。');return;}
var s=PLAN_POINTS[sk], e=PLAN_POINTS[ek];
if(!s||!e)return;
var path=document.getElementById('planRoutePath');
var svg=document.getElementById('planRouteSvg');
var info=document.getElementById('planRouteInfo');
var sd=document.getElementById('planStartDot');
var ed=document.getElementById('planEndDot');
/* 二次贝塞尔曲线:控制点垂直偏移,使路径呈现弧形而非直线 */
var mx=(s.x+e.x)/2, my=(s.y+e.y)/2;
var cx=mx+(e.y-s.y)*0.18, cy=my+(s.x-e.x)*0.18;
var d='M '+s.x+' '+s.y+' Q '+cx+' '+cy+' '+e.x+' '+e.y;
path.setAttribute('d',d);
path.setAttribute('opacity','1');
sd.setAttribute('cx',s.x);sd.setAttribute('cy',s.y);sd.setAttribute('opacity','1');
ed.setAttribute('cx',e.x);ed.setAttribute('cy',e.y);ed.setAttribute('opacity','1');
svg.classList.add('plan-route-svg--show');
/* Mock 距离/用时:按 viewBox 距离换算 */
var dx=s.x-e.x, dy=s.y-e.y, dist=Math.sqrt(dx*dx+dy*dy);
var km=(dist*0.15).toFixed(1);
var min=Math.max(1,Math.round(dist*0.6));
document.getElementById('planRouteDist').textContent=km+' km';
document.getElementById('planRouteTime').textContent=min+' 分钟';
info.classList.add('plan-route-info--show');
}
/* ========== 资源统计卡片行(医疗点/救护车/AED ========== */
/* 资源统计卡片数据:点击对应 Tab 后在地图下方展示 */
var RES_STAT_CONFIG = {
clinic: {
cards: [
{label:'医疗点总数', num:'3', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M3 21V5a2 2 0 012-2h14a2 2 0 012 2v16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M3 21h18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M12 7v6M9 10h6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', tone:''},
{label:'覆盖人数', num:'2,860', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M16 21v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2"/><path d="M22 21v-2a4 4 0 00-3-3.87" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M16 3.13a4 4 0 010 7.75" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', tone:'--cyan'},
{label:'医护人员', num:'28', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M9 11H7a2 2 0 00-2 2v7a2 2 0 002 2h10a2 2 0 002-2v-7a2 2 0 00-2-2h-2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M12 11V7M10 9h4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M12 3v2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', tone:'--success'}
]
},
ambulance: {
cards: [
{label:'急救型救护车', num:'5', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M3 17V7a2 2 0 012-2h9v8h7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M3 17h18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="7.5" cy="17.5" r="2" stroke="currentColor" stroke-width="2"/><circle cx="17.5" cy="17.5" r="2" stroke="currentColor" stroke-width="2"/><path d="M15 8h4l2 3v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M9 9v4M7 11h4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', tone:'--warning'},
{label:'转运型救护车', num:'3', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M3 17V7a2 2 0 012-2h9v8h7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M3 17h18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="7.5" cy="17.5" r="2" stroke="currentColor" stroke-width="2"/><circle cx="17.5" cy="17.5" r="2" stroke="currentColor" stroke-width="2"/><path d="M15 8h4l2 3v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M8 11h6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', tone:''},
{label:'出勤次数', num:'128', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M22 12h-4l-3 9L9 3l-3 9H2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>', tone:'--danger'}
]
},
aed: {
cards: [
{label:'在线设备', num:'18', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M12 2a10 10 0 100 20 10 10 0 000-20z" stroke="currentColor" stroke-width="2"/><path d="M12 6v6l4 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', tone:'--success'},
{label:'离线设备', num:'2', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M12 2a10 10 0 100 20 10 10 0 000-20z" stroke="currentColor" stroke-width="2"/><path d="M8 12h8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', tone:''},
{label:'使用次数', num:'36', icon:'<svg viewBox="0 0 24 24" fill="none"><path d="M13 2L3 14h7l-1 8 10-12h-7l1-8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>', tone:'--danger'}
]
}
};
/* 根据当前资源 Tab 类型更新地图下方统计卡片 */
function updateResStatRow(type){
var row=document.getElementById('resStatRow');
if(!row)return;
var config=RES_STAT_CONFIG[type];
if(!config){
row.style.display='none';
return;
}
row.style.display='flex';
config.cards.forEach(function(card,idx){
var i=idx+1;
var iconEl=document.getElementById('resStatIcon'+i);
var numEl=document.getElementById('resStatNum'+i);
var labelEl=document.getElementById('resStatLabel'+i);
if(iconEl){
iconEl.innerHTML=card.icon;
iconEl.className='res-stat-card__icon'+(card.tone||'');
}
if(numEl)numEl.textContent=card.num;
if(labelEl)labelEl.textContent=card.label;
});
}
/* ========== 地图标记脉冲动画优化 ========== */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -84,30 +84,30 @@
业务支持功能只覆盖需要现场硬件、现场终端、本地任务执行、本地采集或弱网连续作业的后台二级菜单。云端只把现场执行所需的资料、任务、规则、配置或文件下发到边缘服务器,边缘服务器只上报现场产生的业务流水、设备状态、执行结果和异常事件。
| 功能菜单 | 云端下发数据 | 下发规则说明 | 边缘数据上报 | 上报规则说明 |
|-----------|----------------------------------|-------------------------------------------------------------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 食物成分 | 食材编码、食物成分项、营养素含量、单位换算、适用状态、净菜类型等 | 1. 食物成分信息调整后实时增量下发。<br>2. 新部署边缘节点执行全量初始化下发。 | 暂无 | 暂无 |
| 餐品总库 | 餐品编码、餐品名称、餐品组成、食材用量、营养指标、餐品规格等 | 1. 餐品发布、调整、停用后实时增量下发。<br>2. 新部署边缘节点执行全量初始化下发。 | 菜品研发(味养创新) | 1.新研发菜品数据实时增量上报; |
| 用户管理 | 用户、部门、权限、人脸特征、人员状态、会员标识 | 1. 人员、人脸、会员等级、权限变更后实时增量下发。<br>2. 每日低峰执行一次全量一致性校验。<br>3. 人员停用或权限回收需立即下发。 | 新用户注册、人脸录入、会员充值等 | 1. 有新用户注册、人脸录入时实时增量上传。<br>2.会员充值操作必须以云端响应状态为准,边缘服务器节点不能私自改变充值状态,弱网、断网、服务器连接异常的情况下不允许充值操作或标记为充值失败。 |
| 取餐管理 | 暂无 | 暂无 | 取餐记录、取餐核销、结算信息、用餐明细等 | 1. 取餐结算完成后立即写入边缘节点本地库。<br>2. 就餐数据3分钟一次定时增量上传。<br>3. 重复核销、异常核销需优先上报并生成告警。<br>4. 如若使用本系统的支付结算必须以云端响应状态为准,边缘服务器节点不能私自改变结算状态,弱网、断网、服务器连接异常的情况下不允许结算操作或标记为结算失败。<br>5. 支付和退款结果按交易唯一键幂等上报。 |
| 终端管理 | 设备档案、绑定关系、业务配置、升级任务、升级包元数据 | 1. 设备绑定和业务配置变更后实时下发。<br>2. 新部署边缘节点执行按食堂全量初始化下发。 | 设备状态、运行日志、升级结果、设备异常 | 1. 设备状态每30分钟周期上报。<br>2. 设备异常、升级失败、配置应用失败需立即上报。<br>3. 异常日志优先上报。 |
| 供商管理 | 内部供应商、外部供应商、供应商资质 | 1. 供应商信息发生变更后实时增量下发。<br>2. 升级任务按运维指定时间窗下发。<br>3. 供应商资质变更后实时增量下发。 | 暂无 | 暂无 |
| 采供管理 | 采购单信息、采购单明细 | 1. 产生新的采购单后实时增量下发。<br>2. 采购单状态变更后实时下发(如取消,接单,发货等)。 | 设备新增采购单 | 1. 毛菜入库的无订单验收需要同步新建采购单,新建后实时上报至云端服务。 |
| 仓储管理 | 仓库档案、区域信息 | 1. 仓库信息和区域信息变更后实时下发。<br>2. 新部署边缘节点执行按食堂全量初始化下发。 | 暂无 | 暂无 |
| 毛菜入库 | 入库单、采购单、供应商、食材档案 | 1. 采购单、供应商、食材档案等信息已在其他模块中明确了下发规则。 | 称重记录、入库批次、质检状态、现场照片、操作员、设备编号 | 1. 验收称重完成后实时增量上报。<br>2. 入库批次、称重记录、质检状态按同一业务唯一键上报。 |
| 净菜加工 | 加工任务、净菜类型、加工规格 | 1. 加工任务生成后实时下发。<br>2. 净菜类型已在食物成分模块明确下发规则。 | 毛菜加工、净菜加工、毛重、净重、出净率 | 1. 从毛菜加工开始、称重、完成节点分别写入边缘节点本地库。<br>2. 加工完成后汇总毛重、净重、出净率和损耗实时上报至云端服务。<br>3. 过程图片或者视频按文件队列异步补传。 |
| 净菜入库 | 人工录入的入库信息、入库信息在后台被修改 | 1.后台人工录入的净菜入库信息需要实时下发至边缘节点服务器。<br>2. 入库信息在后台被手动编辑、删除、出库等信息需实时下发。 | 入库记录、库位绑定、入库重量 | 1. 入库完成后实时增量上报。 |
| 净菜包装 | 包装任务、包装规格、溯源码规则 | 1. 包装任务生成后实时下发。。<br>2. 包装任务变更后增量下发。 | 包装批次、包装重量、组配信息、设备信息 | 1. 包装、称重、打标节点单菜品包装完成先记录与边缘服务器本地库。<br>2. 整个订单包装完成后实时上报云端。 |
| 净菜内供、净菜外售 | 内供订单、外售订单 | 1. 内供或内功订单产生后实时下发。<br>2. 配送计划变更后实时补发。<br>3. 订单状态变更后实时下发(如取消订单)。 | 暂无 | 暂无 |
| 净菜进柜 | 暂无 | 暂不考虑后台手动添加的进柜记录同步至边缘节点,理论上进柜记录都是从设备端主动发起 | 进柜记录、格口占用、取放记录、柜体异常、重量变更 | 1. 进柜、取出等操作实时上报。<br>2. 格口占用变化需实时上报。。 |
| 净菜组配 | 组配任务、餐品规格、份数、食材明细、标准用量、差异阈值 | 1. 组配任务发布后实时下发。<br>2. 餐品规格和标准用量按餐品版本下发(餐品总库)。 | 任务状态、称重明细、实际用量、设备编号、操作员、完成状态 | 1. 任务完成后汇总称重明细和完成状态上报。 |
| 按单烹制 | 烹制任务、餐品、需求份数、食材明细、工艺要求、温度阈值 | 1. 烹制任务发布后实时下发。<br>2. 任务调整后实时补发到后厨终端。<br>3. 工艺要求和温度阈值按版本发布后下发(餐品总库)。 | 开始/完成、厨师、生重、熟重、生熟比、温度过程、质量结果 | 1. 边缘节点服务器实时记录烹制过程,烹制完成后一次性实时上报至云端服务器。 |
| 食谱管理 | 当周食谱数据、餐品数据、食谱状态 | 1. 后台周食谱生成后实时下发。<br>2. 食谱调整后实时下发。 | 暂无 | 暂无 |
| 上餐监控 | 暂无 | 暂无(食谱、餐品、设备数据均已在其他模块下发) | 上餐时间、出锅温度、上餐重量、补餐记录、余量记录 | 1. 开餐信息实时上报。<br>2.补餐、余量监控等信息可3分钟周期性上报。 |
| 餐线管理 | 餐线档案、档口信息、餐线终端绑定、餐线状态规则 | 1.新增餐线或档案变更后实时下发。<br>2. 新部署边缘节点执行按食堂全量初始化下发; | 餐线状态、档口终端状态、餐线异常、终端应用回执 | 1. 餐线状态、监测信息按3分钟周期上报。<br>2. 档口终端离线或餐线异常需立即上报。 |
| 营养点餐 | 点餐订单、餐品信息、餐品价格、营养指标、点餐规则 | 1. 点餐订单创建或变更后实时下发。 | 暂无 | 暂无 |
| 余量处置 | 暂无 | 暂无 | 余量重量、处置方式、领取明细、捐赠记录、废弃记录 | 1. 称重、领取、捐赠、废弃节点分别写入边缘。<br>2. 员工福利领取需按人员和批次做幂等上报。<br>3. 废弃记录和异常处置需优先上报。 |
| 食安监控 | 留样规则、证件规则、能耗阈值、摄像头档案、水电气设备 | 1. 预警规则变更、设备档案信息变更、人员证件信息变更后实时下发。<br>2. 新部署边缘节点执行按食堂全量初始化下发; | 留样记录、巡检记录、证件状态、能耗数据、视频索引、设备数据 | 1. 留样、巡检、证件检查完成后立即上报业务结果。<br>2. 能耗数据按配置30分钟一次周期上报,越界数据立即上报。<br>3. 视频只上报索引和关键事件,原文件按存储策略处理。 |
| 功能菜单 | 云端下发数据 | 下发规则说明 | 边缘数据上报 | 上报规则说明 |
|-----------|----------------------------------|----------------------------------------------------------------------------------------------------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 食物成分 | 食材编码、食物成分项、营养素含量、单位换算、适用状态、净菜类型等 | 1. 食物成分信息调整后实时增量下发。<br>2. 新部署边缘节点执行全量初始化下发。 | 暂无 | 暂无 |
| 餐品总库 | 餐品编码、餐品名称、餐品组成、食材用量、营养指标、餐品规格等 | 1. 餐品发布、调整、停用后实时增量下发。<br>2. 新部署边缘节点执行全量初始化下发。 | 菜品研发(味养创新) | 1.新研发菜品数据实时增量上报; |
| 用户管理 | 用户、部门、权限、人脸特征、人员状态、会员标识 | 1. 人员、人脸、会员等级、权限变更后实时增量下发。<br>2. 每日低峰执行一次全量一致性校验。<br>3. 人员停用或权限回收需立即下发。 | 新用户注册、人脸录入、会员充值等 | 1. 有新用户注册、人脸录入时实时增量上传。<br>2.会员充值操作必须以云端响应状态为准,边缘服务器节点不能私自改变充值状态,弱网、断网、服务器连接异常的情况下不允许充值操作或标记为充值失败。 |
| 取餐管理 | 暂无 | 暂无 | 取餐记录、取餐核销、结算信息、用餐明细等 | 1. 取餐结算完成后立即写入边缘节点本地库。<br>2. 就餐数据3分钟一次定时增量上传。<br>3. 重复核销、异常核销需优先上报并生成告警。<br>4. 如若使用本系统的支付结算必须以云端响应状态为准,边缘服务器节点不能私自改变结算状态,弱网、断网、服务器连接异常的情况下不允许结算操作或标记为结算失败。<br>5. 支付和退款结果按交易唯一键幂等上报。 |
| 终端管理 | 设备档案、绑定关系、业务配置、升级任务、升级包元数据 | 1. 设备绑定和业务配置变更后实时下发。<br>2. 新部署边缘节点执行按食堂全量初始化下发。<br>3.终端设备版本升级根据运维后台的配置在低频时段定时统一下发。<br>4.后台物料信息更新实时下发(竖屏、统计屏等); | 设备状态、运行日志、升级结果、设备异常 | 1. 设备状态每30分钟周期上报。<br>2. 设备异常、升级失败、配置应用失败需立即上报。<br>3. 异常日志优先上报。 |
| 供商管理 | 内部供应商、外部供应商、供应商资质 | 1. 供应商信息发生变更后实时增量下发。<br>2. 升级任务按运维指定时间窗下发。<br>3. 供应商资质变更后实时增量下发。 | 暂无 | 暂无 |
| 采供管理 | 采购单信息、采购单明细 | 1. 产生新的采购单后实时增量下发。<br>2. 采购单状态变更后实时下发(如取消,接单,发货等)。 | 设备新增采购单 | 1. 毛菜入库的无订单验收需要同步新建采购单,新建后实时上报至云端服务。 |
| 仓储管理 | 仓库档案、区域信息 | 1. 仓库信息和区域信息变更后实时下发。<br>2. 新部署边缘节点执行按食堂全量初始化下发。 | 暂无 | 暂无 |
| 毛菜入库 | 入库单、采购单、供应商、食材档案 | 1. 采购单、供应商、食材档案等信息已在其他模块中明确了下发规则。 | 称重记录、入库批次、质检状态、现场照片、操作员、设备编号 | 1. 验收称重完成后实时增量上报。<br>2. 入库批次、称重记录、质检状态按同一业务唯一键上报。 |
| 净菜加工 | 加工任务、净菜类型、加工规格 | 1. 加工任务生成后实时下发。<br>2. 净菜类型已在食物成分模块明确下发规则。 | 毛菜加工、净菜加工、毛重、净重、出净率 | 1. 从毛菜加工开始、称重、完成节点分别写入边缘节点本地库。<br>2. 加工完成后汇总毛重、净重、出净率和损耗实时上报至云端服务。<br>3. 过程图片或者视频按文件队列异步补传。 |
| 净菜入库 | 人工录入的入库信息、入库信息在后台被修改 | 1.后台人工录入的净菜入库信息需要实时下发至边缘节点服务器。<br>2. 入库信息在后台被手动编辑、删除、出库等信息需实时下发。 | 入库记录、库位绑定、入库重量 | 1. 入库完成后实时增量上报。 |
| 净菜包装 | 包装任务、包装规格、溯源码规则 | 1. 包装任务生成后实时下发。。<br>2. 包装任务变更后增量下发。 | 包装批次、包装重量、组配信息、设备信息 | 1. 包装、称重、打标节点单菜品包装完成先记录与边缘服务器本地库。<br>2. 整个订单包装完成后实时上报云端。 |
| 净菜内供、净菜外售 | 内供订单、外售订单 | 1. 内供或内功订单产生后实时下发。<br>2. 配送计划变更后实时补发。<br>3. 订单状态变更后实时下发(如取消订单)。 | 暂无 | 暂无 |
| 净菜进柜 | 暂无 | 暂不考虑后台手动添加的进柜记录同步至边缘节点,理论上进柜记录都是从设备端主动发起 | 进柜记录、格口占用、取放记录、柜体异常、重量变更 | 1. 进柜、取出等操作实时上报。<br>2. 格口占用变化需实时上报。。 |
| 净菜组配 | 组配任务、餐品规格、份数、食材明细、标准用量、差异阈值 | 1. 组配任务发布后实时下发。<br>2. 餐品规格和标准用量按餐品版本下发(餐品总库)。 | 任务状态、称重明细、实际用量、设备编号、操作员、完成状态 | 1. 任务完成后汇总称重明细和完成状态上报。 |
| 按单烹制 | 烹制任务、餐品、需求份数、食材明细、工艺要求、温度阈值 | 1. 烹制任务发布后实时下发。<br>2. 任务调整后实时补发到后厨终端。<br>3. 工艺要求和温度阈值按版本发布后下发(餐品总库)。 | 开始/完成、厨师、生重、熟重、生熟比、温度过程、质量结果 | 1. 边缘节点服务器实时记录烹制过程,烹制完成后一次性实时上报至云端服务器。 |
| 食谱管理 | 当周食谱数据、餐品数据、食谱状态 | 1. 后台周食谱生成后实时下发。<br>2. 食谱调整后实时下发。 | 暂无 | 暂无 |
| 上餐监控 | 暂无 | 暂无(食谱、餐品、设备数据均已在其他模块下发) | 上餐时间、出锅温度、上餐重量、补餐记录、余量记录 | 1. 开餐信息实时上报。<br>2.补餐、余量监控等信息可3分钟周期性上报。 |
| 餐线管理 | 餐线档案、档口信息、餐线终端绑定、餐线状态规则 | 1.新增餐线或档案变更后实时下发。<br>2. 新部署边缘节点执行按食堂全量初始化下发; | 餐线状态、档口终端状态、餐线异常、终端应用回执 | 1. 餐线状态、监测信息按3分钟周期上报。<br>2. 档口终端离线或餐线异常需立即上报。 |
| 营养点餐 | 点餐订单、餐品信息、餐品价格、营养指标、点餐规则 | 1. 点餐订单创建或变更后实时下发。 | 暂无 | 暂无 |
| 余量处置 | 暂无 | 暂无 | 余量重量、处置方式、领取明细、捐赠记录、废弃记录 | 1. 称重、领取、捐赠、废弃节点分别写入边缘。<br>2. 员工福利领取需按人员和批次做幂等上报。<br>3. 废弃记录和异常处置需优先上报。 |
| 食安监控 | 留样规则、证件规则、能耗阈值、摄像头档案、水电气设备 | 1. 预警规则变更、设备档案信息变更、人员证件信息变更后实时下发。<br>2. 新部署边缘节点执行按食堂全量初始化下发; | 留样记录、巡检记录、证件状态、能耗数据、视频索引、设备数据 | 1. 留样、巡检、证件检查完成后立即上报业务结果。<br>2. 能耗数据按配置30分钟一次周期上报,越界数据立即上报。<br>3. 视频只上报索引和关键事件,原文件按存储策略处理。 |
## 6. 核心同步规则