Files
aomin a7605ef8b8 chore(repo): 导入 M-Z 业务底座 V1.9 基线并建立仓库规范
按周工交付的 SEC-Z-01 清理后源码建立 M-Z 主干,并按 Git 仓库惯例重排目录。

- aomin-gateway/  后端工程 87 文件,原样保留,路径与构建命令未变
- demo/           演示前端与设备模拟器 9 文件(原 02_前端与设备端),非正式三端
- docs/handover/  交付与安全清理材料 6 份,内容未改
- docs/contract/  J-00 规格、OpenAPI、内部五接口、状态机、Mock 样例
- docs/reference/ 指纹与校验清单,含 84 个源码文件的 sha256 清单

目录重排只动位置不动内容:源码树 84 文件哈希与交付一致,逐文件自校验通过;
12 条 Flyway 迁移与交付包内镜像逐字节一致。

未纳入:迁移镜像目录(与工程内一套重复,改留指纹)、target/、证据 raw、
应急恢复脚本、评审用虚构台账数据。

安全:SEC-Z-01 15 处明文已清零,导入前重新扫描无新增命中,本仓库无历史污染。
行尾按交付字节原样保存(.gitattributes 设 * -text),不做归一化以免整树摘要失效。
2026-11-13 17:32:37 +08:00

147 lines
9.6 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>鹤仆云 · 守望 S 线一期 演示环境</title>
<style>
:root{--ink:#1b2430;--sub:#6b7784;--line:#e4eaef;--brand:#146b7a;--brand2:#1d8fa3}
*{box-sizing:border-box}
body{margin:0;background:#f2f5f8;color:var(--ink);
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;font-size:14px}
.hero{background:linear-gradient(150deg,#0d3f4a,#146b7a 45%,#22a2a8);color:#fff;padding:42px 24px 68px}
.wrap{max-width:1020px;margin:0 auto}
.hero h1{font-size:27px;margin:0 0 8px;font-weight:600;letter-spacing:.5px}
.hero p{margin:0;font-size:13.5px;opacity:.86;line-height:1.8}
.hero .tagline{display:inline-block;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.2);
border-radius:20px;padding:5px 14px;font-size:12px;margin-bottom:16px}
.content{max-width:1020px;margin:-42px auto 60px;padding:0 24px}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(270px,1fr));gap:14px;margin-bottom:22px}
.card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:20px;
box-shadow:0 8px 24px rgba(18,40,60,.07);text-decoration:none;color:inherit;display:block;transition:.18s}
.card:hover{transform:translateY(-3px);box-shadow:0 14px 32px rgba(18,40,60,.12)}
.card .ic{width:44px;height:44px;border-radius:12px;background:#e8f3f6;color:var(--brand);
display:flex;align-items:center;justify-content:center;font-size:22px;margin-bottom:12px}
.card h2{margin:0 0 5px;font-size:16.5px;font-weight:600}
.card .d{font-size:12.5px;color:var(--sub);line-height:1.75;min-height:44px}
.card .go{margin-top:12px;color:var(--brand);font-size:12.5px;font-weight:600}
.panel{background:#fff;border:1px solid var(--line);border-radius:14px;padding:20px 22px;margin-bottom:18px;
box-shadow:0 2px 8px rgba(18,40,60,.04)}
.panel h2{font-size:16px;margin:0 0 4px;font-weight:600}
.panel .lead{font-size:12.5px;color:var(--sub);margin-bottom:16px}
.flow{display:flex;align-items:stretch;gap:10px;flex-wrap:wrap}
.node{flex:1;min-width:150px;border:1px solid var(--line);border-radius:11px;padding:13px;background:#fafcfd}
.node b{display:block;font-size:13.5px;margin-bottom:5px;color:var(--brand)}
.node .m{font-family:Consolas,Menlo,monospace;font-size:11px;color:#43536b;line-height:1.7}
.node .n{font-size:11.5px;color:var(--sub);margin-top:6px;line-height:1.6}
.arrow{display:flex;align-items:center;font-size:20px;color:#9fb6bf}
@media(max-width:760px){.arrow{transform:rotate(90deg);justify-content:center;width:100%}}
.steps{counter-reset:s}
.step{display:flex;gap:12px;padding:10px 0;border-bottom:1px solid #f0f4f6;align-items:flex-start}
.step:last-child{border-bottom:0}
.step .num{counter-increment:s;flex:0 0 24px;height:24px;border-radius:7px;background:#e8f3f6;color:var(--brand);
display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}
.step .num::before{content:counter(s)}
.step .tx{flex:1;font-size:13px;line-height:1.7}
.step .tx b{font-weight:600}
code{font-family:Consolas,Menlo,monospace;background:#f2f5f8;border:1px solid #e7ecf0;border-radius:4px;
padding:1px 6px;font-size:12px;color:#0a4c6b}
.kv{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}
.kv div{background:#fafcfd;border:1px solid var(--line);border-radius:9px;padding:10px 12px;font-size:12.5px}
.kv span{color:var(--sub);display:block;font-size:11px;margin-bottom:2px}
.note{background:#fffaf0;border:1px solid #f3e3bd;border-radius:11px;padding:13px 16px;font-size:12.5px;
color:#7a6224;line-height:1.85}
.foot{text-align:center;font-size:11.5px;color:#9aa6b2;padding:6px 0 20px}
</style>
</head>
<body>
<div class="hero"><div class="wrap">
<div class="tagline">演示环境 · 2026-11-07</div>
<h1>鹤仆云 · 守望 S 线一期</h1>
<p>老人「生活痕迹」监测 · 设备 → 网关 → 平台 → 家属<br>
前中后三段全部可点:设备是模拟的,接口是真的,数据是真的,页面是 H5(暂代小程序)</p>
</div></div>
<div class="content">
<div class="cards">
<a class="card" href="family/index.html" target="_blank">
<div class="ic">📱</div><h2>家属端(小程序 H5 版)</h2>
<div class="d">微信一键登录、老人状态、家里设备、告警处理、联系人、通知偏好</div>
<div class="go">打开 →</div>
</a>
<a class="card" href="admin/index.html" target="_blank">
<div class="ic">🖥</div><h2>运营后台</h2>
<div class="d">总览、告警中心、设备台账、住户、账号权限、字典、工单、审计</div>
<div class="go">打开 →</div>
</a>
<a class="card" href="http://127.0.0.1:9901/" target="_blank">
<div class="ic">📡</div><h2>设备模拟器</h2>
<div class="d">点一下 SOS / 漏水 / 断电,看数据怎样一路走到家属手机</div>
<div class="go">打开 →</div>
</a>
</div>
<div class="panel">
<h2>三段是怎么串起来的</h2>
<div class="lead">所有箭头都走真实 HTTP 接口,没有一处是页面里写死的假数据。</div>
<div class="flow">
<div class="node"><b>① 设备 / 网关</b>
<div class="m">sim_device.py<br>:9901</div>
<div class="n">5 台设备按真实帧格式上报;按钮触发 SOS、水浸、断电</div></div>
<div class="arrow">→</div>
<div class="node"><b>② 接入层(M 层)</b>
<div class="m">/internal/iot/v1/**<br>:8080</div>
<div class="n">I-01 事件 / I-02 状态 / I-12 原文,验签限流幂等后落 iot_event</div></div>
<div class="arrow">→</div>
<div class="node"><b>③ 业务层</b>
<div class="m">AlarmEngine + 53 API<br>:8080</div>
<div class="n">把原始事件翻成告警和通知,供家属端、后台查询与处置</div></div>
<div class="arrow">→</div>
<div class="node"><b>④ 前端</b>
<div class="m">/api/v1/**<br>H5</div>
<div class="n">家属端看与确认,后台看全量与闭环</div></div>
</div>
</div>
<div class="panel">
<h2>演示动线(8 步,约 7 分钟)</h2>
<div class="steps">
<div class="step"><div class="num"></div><div class="tx"><b>打开家属端</b>,用「微信一键授权(模拟)」进去。首页能看到老人、在线设备数、待处理告警。验证码那条链路可以点「注册」试:随机 6 位数字直接填好,不真发短信。</div></div>
<div class="step"><div class="num"></div><div class="tx"><b>打开设备模拟器</b>,点「🆘 按下 SOS」或「💧 卫生间漏水」。</div></div>
<div class="step"><div class="num"></div><div class="tx"><b>等 8 秒</b>(告警引擎扫描周期),回家属端点刷新 → 新告警出现 → 点开 → 「我知道了 / 已处理 / 误报」都能点,处理记录会留痕。</div></div>
<div class="step"><div class="num"></div><div class="tx"><b>打开运营后台</b>,总览的 KPI、近 7 天告警柱状、最新告警会跟着变;告警中心可关闭工单,审计日志能看到刚才所有写操作。</div></div>
<div class="step"><div class="num"></div><div class="tx"><b>家属端 →「设备」→ + 绑定设备</b>:扫码(或用演示设备码)/手填编号,能把一台没建档的设备激活并挂到家里。</div></div>
<div class="step"><div class="num"></div><div class="tx"><b>家属端 →「我的」→ + 创建我的家</b>:像美团新增收货地址一样,地图选点或 GPS 定位 → 省市区 → 小区 → 楼栋单元房号 → 提交;建好后顶部 ⌄ 可以在多个家之间切换。</div></div>
<div class="step"><div class="num"></div><div class="tx"><b>填写老人信息</b>:「我的」→ + 填写老人信息 → 姓名/性别/生日/照护等级/是否独居/画像授权 → 保存后点开老人,可以改资料、加紧急联系人(姓名手机号加密,列表只显示打码号码)。</div></div>
<div class="step"><div class="num"></div><div class="tx"><b>一家人怎么进来</b>:户主点「🎟️ 邀请家人」拿 6 位码;另一个浏览器 / 无痕窗口注册新账号 → 「我的 → 加入别人的家」→ 输码直接进,或搜小区房号「申请加入」。回到户主窗口点「📥 审批」,能看到申请并点通过 / 拒绝。</div></div>
</div>
</div>
<div class="panel">
<h2>账号与地址</h2>
<div class="kv">
<div><span>家属端 · 演示账号</span>13800000000 / 123456,或直接点微信一键</div>
<div><span>运营后台</span>admin(口令按部署说明)</div>
<div><span>业务服务</span><code>http://127.0.0.1:8080</code></div>
<div><span>模拟控制台</span><code>http://127.0.0.1:9901</code></div>
<div><span>健康检查</span><code>/health</code> → flyway 1.0.28 / 87 表</div>
<div><span>本页地址</span><code>/app/index.html</code></div>
</div>
</div>
<div class="note">
<b>这是演示环境,不是生产版本。</b>三件事还没做:真微信换 openid、真短信通道、告警规则表(目前只有 SOS / 水浸 / 门磁 / 断电 4 条规则,阈值、静默期、升级链要产品定)。
角色数据范围也还没收紧——后台账号现在能看全部小区,上线前必须由程序员补。<br>
地图只用腾讯地图 GL JS(SDK 不带 key、走代理、GCJ-02 坐标);
从 :8080 打开时代理未启用,会自动降级成「演示坐标板」,功能仍可用,但没有真实底图。
</div>
<div class="foot">鹤仆云 · 守望 S 线一期 · 三段贯通演示</div>
</div>
</body>
</html>