⚠️ 9-24 更正:本文第 4.3 节与 H8「红线文件冻结、认领后查不出来」有误 —— 可见性 6 处已于 9-17 上生产;真正的坑是另两道门,见 home-redesign-decisions-v3-20260924.md 第二节。
2026-09-23 · 效果图
zhaoyang-home-6styles-20260921-flat1.html
基线:origin/test(本 worktree 是 main,落后 test 1362 commit,实施必须从 test 拉分支)
方法:8 路并行子代理物理 grep + 主会话复核 + 与 Phase 83 批次五会话直接对接
前作:home-redesign-verify-20260923.md(v1,本文件更正其中一处并大幅扩写)
v1 写「Phase 83 批次五要改 chat/page.tsx:791 家长端密码门」—— 这条不成立。
物理核实:
$ sed -n '789,791p' web/app/h5/chat/page.tsx
// Phase 46.8 · 席位门控决策(阻塞式 · 替代 magic-link)
// enable_password_login=true 且本设备无有效 seat token → 弹阻塞登录窗
// 家长流(enable_password_login=false)→ 永不开门(红线:原逻辑不变)
$ grep -rn "enable_password_login" app/models/database.py
94: comment="是否开启多租户用户密码登录(首页密码登录入口 · 功能待实现 · 当前仅存配置)"
这段管的是 Phase 46.8 时代的 role='h5_user' 老家长,不是 Phase 74 的家长子席位;而且这个开关在模型里明写「功能待实现 · 仅存配置」。批次五那边也已自行纠正过这条(它一开始按字面理解,被长官当场纠正)。
真实撞车点是 :861(品牌码微信失效兜底)、:930-980(微信身份带入)、以及批次五要新建的「点任意功能全量拦截」gate。
┌─────────────────────────────────────────┐
│ 硬前置 P0 · 登录层抽取(370行鉴权gate) │ ← 不做这个,两个首页都落不了地
└───────────────┬─────────────────────────┘
│
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ 件1 家长端 │ │ 件1 门店端 │ │ Phase83 批次五│
│ 新首页 │ │ 新首页 │ │ (登录体系改造) │
└──────┬──────┘ └──────┬──────┘ └──────┬───────┘
│ │ │
│ 需要 │ 需要 │ 会在每个功能入口
▼ ▼ ▼ 挂全量拦截gate
┌─────────────┐ ┌─────────────┐ ←─── 必须与件1对齐接法
│ 件2 家长打卡 │───────→│ 门店记录汇总 │
│ (新表+新API) │ 数据源 │ (依赖件2) │
└─────────────┘ └─────────────┘
▲
┌─────────────┐ │
│ 件3 互扫绑定 │─────────────────┘ 门店端首页的"绑定家长"入口
│ (后端已通) │
└──────┬──────┘
│ 卡在两个独立前置
├─→ P1 扫码能力(全仓0实现)
└─→ P2 可见性红线文件解冻(写得进查不出)
三个跨件硬前置(详见第六节):
- P0 登录层抽取 —— 两个首页的地基
- P1 扫码能力 —— 互扫绑定的地基,全仓 0 实现
- P2 触达能力 —— 「提醒未记录家长」的地基,当前几乎为零
家长端 11 个区块、门店端 13 个区块(v1 已逐块 verify,此处只列判定结论):
| 判定 | 家长端 | 门店端 |
|---|---|---|
| ✅ 直接复用 | 品牌头卡、档案头、孩子切换/添加、AI解读卡、常用功能入口 | 品牌头卡、示范档案、同款案例、常用工具 8 项 |
| 🟡 需改造 | 最近一次检查(补度数/视力的「较上次」)、趋势图(从 1 条扩到 3 条) | 最近处理列表(补「结论已读/未读」字段)、快捷操作(新增检查缺首页级入口、海报入口要从设置页挪) |
| ❌ 新建 | 消息通知条、今日护眼打卡(见件 2) | 双 tab、待办聚合 API、今日数据统计 API、待跟进复查、家长记录汇总(依赖件 2)、家长连接统计 API |
| 🛑 建议摘出 | 眼轴百分位 | 眼轴百分位 |
chat/page.tsx 是"整站大门"不是"聊天页"3412 行里焊了四样东西:
| 部分 | 内容 | 行号 | 拆分处置 |
|---|---|---|---|
| AI 对话本体 | messages/sendText/SSE 流式/语音 |
1484-1497, 2012-2286 | 留在对话页 |
| 首页壳 | 品牌头卡 StoreBrandHero、头图数据横条、底部功能胶囊条 |
2500-2545, 3007-3230 | 搬去新首页 |
| 独立挂件 | 历史解读、4L 报告详情、上传流程、量身定案 | 3299-3350 | 跟入口走(首页触发就搬首页) |
| 🔴 登录大门 | 鉴权 gate 总控(?t= / ?code= / ?login=seat / 老URL拦截 / 席位微信OAuth / 静默重登 / 兜底失败,7+ 分支) |
764-1136,370 行 | 抽成公共层,谁都不重写 |
必须跟着改的(代码内):
| 文件:行 | 现在指向 | 为什么必须改 |
|---|---|---|
deploy/nginx/chat.tongban.love.conf:156 |
return 301 /h5/chat |
网站根地址落地页 |
app/services/org_service.py:370 |
{host}/h5/chat?t={token} |
机构 access_link 分享链接 |
app/api/temp_link.py:11 |
同上 | 临时链接 |
web/app/h5/brand-scan/page.tsx:99 |
/h5/chat?login=seat |
品牌码扫码落地 |
web/app/h5/tablet/page.tsx:333 |
/h5/chat |
平板端家长身份兜底 |
web/app/h5/family/page.tsx:125 |
/h5/chat |
档案页回首页 |
web/app/h5/settings/page.tsx:145、ParentAccountCard.tsx:73 |
/h5/chat |
登出落地 |
web/app/h5/store-refractive/page.tsx:100,199,209 |
/h5/chat |
门禁失败/返回 |
web/app/h5/family/[id]/page.tsx:587 |
/h5/chat |
鉴权失败兜底 |
web/app/h5/tablet/components/*(4 个首页壳组件) |
— | 平板端是平行实现,不会自动联动,必须人工同步 |
🔴 会直接坏掉的(如果只删首页壳不抽登录层):
- web/app/h5/find-similar/page.tsx:37,216 和 web/app/h5/eye-nav/page.tsx:205 —— 代码注释原文「chat 有完整登录 UI 兜底」,这两个页面把 /h5/chat 当通用救援页。登录 UI 被搬走 → 用户卡在没有登录框的裸对话页。
⚠️ 代码之外的(改代码没用,必须通知运维):
- 微信公众号菜单 URL、二维码物料落地地址,配的都是 /h5/chat?login=seat 之类。在微信后台配置里,仓库 grep 不到。漏改 = 所有扫码/菜单入口全挂。
必须补的一个洞:
- session_id 只活在内存 state,不在 URL 上(grep session_id 确认无 ?session_id= 机制)。从新首页跳进对话页会开新会话、接不上刚才那段。需要新增透传或"自动选中最近会话"。
- 流式读取无 AbortController(grep AbortController 0 命中,对照组 grep -c sessionId = 19 证明查法有效)→ 聊到一半跳走,前端丢这条回复。
可以顺手变干净的:
- isParentSeat 在 chat 页驱动了 11 处分支,其中 :3013 整条功能胶囊区就是"首页入口区"(家长 4 个 / 门店 7 个,互斥)。拆成两个独立首页后这个三元判断应当消失,各自首页写死自己那套。
家长端:四类设备当日记录(离焦镜 h / 红光仪 次 / 护眼灯 h / 读写台 h)+ 完成度 2/4
门店端:本店家长记录汇总(已记录/未记录家长数、今日记录条数、每类设备记录人数与均值)+ 提醒未记录家长
① 数据表:必须新建,不能扩展现有 DeviceData —— 三条依据:
- 鉴权体系不同:device_report.py:34-63 走厂商 X-API-Key/X-API-Secret,打卡要走家长登录态
- 数据语义不同:DeviceData.data 是厂商自由 JSONB 无 schema,打卡是 4 个强结构化字段
- 归属维度不同:DeviceData 按 phone 路由、无 patient_id、无按天唯一约束(同一天可插无数条)
另:DeviceTypeConfig 现有 4 个值是 desk_lamp / distance_screen / posture_sensor / outdoor_tracker(migrations/versions/015:65-71),与打卡四类完全不重合。
② 挂 patient(孩子)维度,不挂 user(家长)维度:
- 语义上"孩子今天戴了多久"属于孩子
- 家长多孩子是现成机制(parent_patients.py:223 list_my_children 按 owner_seat_id 查列表,无数量限制)
- 门店端汇总要复用现成的机构可见性判据(按 patient 过滤),挂 user 维度就得另起一套
③ 「今天」按 Asia/Shanghai 切天:
- 仓库正确惯例:activity_rollup_task.py:37,65-75、admin_activity.py:37,157-166(注释"D17 天边界")、admin_brand_code.py:253 func.date(func.timezone("Asia/Shanghai", ...))
- ⚠️ 反面教材别学:h5_chat.py:153,325、sms_verify_service.py:55 用裸 date.today(),是已有技术债
④ 门店端汇总的 WHERE 必须复用 _org_visibility_clause(refractive_record_service.py:111-131):
未被认领(service_org_id IS NULL) → 按 org_id 可见
已被认领 → 只由 service_org_id 可见,原 org_id 不再可见
自己写 Patient.org_id == org_id 会漏掉"已认领"和"家长自建"两类,统计口径跟其他门店页对不上。
⑤ 接口挂哪个中间件(app/auth/middleware.py):
- 家长端打卡提交/查询 → require_h5_user(157-322)或 require_org_seat(322-397)
- 门店端汇总/提醒 → 必须 require_store_seat(398-409,seat_type=="parent" 直接 403)—— 这是 Phase 74 专门堵"家长 token 打门店接口"的门,不能只用宽松的 require_org_seat
| 件 | 位置 | 复用程度 |
|---|---|---|
| 数值/时长输入 | tablet/pane/panes/refractive-record/constrained-number-picker.tsx(带 min/max/step/decimals),门店端 record/new/page.tsx:520-650 已 1:1 移植过 |
高,改个单位就能用 |
| 底部弹层骨架 | useSheetBackButton hook + upload-sheet.tsx / today-detail-sheet.tsx 等 |
高 |
| 四设备视觉骨架 | today-band.tsx(4 卡完成度)+ today-detail-sheet.tsx |
高,但只有展示、无表单交互,提交逻辑全新写 |
惊喜发现:today-band.tsx:44-79 的 mock 里已硬编码四个设备 label:大路灯 / 红光仪 / 离焦镜 / 读写台;today-detail-sheet.tsx 里已有「红光仪 · 650nm · 3 分钟 × 2 次 / 天」的文案 —— 跟效果图和长官口径完全一致。组件注释写着「等 Phase 21B 设备数据打通后接真实接口」,说明这套视觉件当初就是为这个场景设计的,只是一直没接后端。
⚠️ 命名待对齐:代码里叫「大路灯」,效果图叫「护眼灯」,是不是同一个东西要长官确认。
| 通道 | 现状 | 能不能用 |
|---|---|---|
| 微信订阅消息 | wechat_client.py:22-46 send_subscribe_message 函数体只有一行 logger.info + 固定返回 {"errcode":0},注释写「真实 HTTP 接入将在 Go-Live 前补齐」;且 wechat_push.py 接收方是 doctor_id(医生),openid 还是 f"openid_for_{doctor_id}" 硬编码占位 |
❌ 纯 stub,对任何人都不可用 |
| 短信 | sms_service.py:34-77 是真实接入的阿里云 SDK(非 stub),但只有一个已注册模板("方案完成");验证码模板注释写着「TODO Go-Live:阿里云后台注册新 template + sign」;sms_notify.py:38 手机号还是硬编码 "13800138000" stub |
🟡 通道真的在,但要新申请模板 + 接真实手机号 + 做限流(按条计费,门店高频点会烧钱) |
| 站内消息 | 全仓无 notification/inbox 模型 | ❌ 不存在 |
这条必须单独拍板:是走短信(要先走阿里云模板申请流程,不在开发周期内能自动完成),还是等微信订阅消息 Go-Live,还是本期先不做"主动提醒"、只做"未记录名单展示"。
| 环节 | 状态 |
|---|---|
门店扫家长码 · 家长码签发(jwt_local.py:251-266,HS256,TTL 24h,payload 只装 seat_id) |
✅ |
门店扫家长码 · 后端 4 端点(brand_code_claim.py:57/87/117/161 = family-code / resolve / claim / release) |
✅ |
门店扫家长码 · 集成测试 9 条(tests/integration/test_brand_code_claim.py) |
✅ |
| 门店扫家长码 · 家长端「我的二维码」页 | ❌ 全仓 0 命中 |
| 门店扫家长码 · 门店端「扫一扫」页 | ❌ 全仓 0 命中 |
| 门店扫家长码 · 「已绑别家确认转入」弹窗 | ❌ 不存在 |
| 门店扫家长码 · 认领后本机构档案列表可见 | 🟡 写得进,查不出(见 4.3) |
| 家长扫门店码 · 后端 | ❌ 不存在 |
| 家长扫门店码 · 前端 | ❌ 不存在 |
9-17 的缺口清单 phase48a-gap-list-20260917.md:22 原文已记载此事,结论是「48B 作为独立功能目前完全不可用(无入口)…建议下批做」。
$ grep -rn "jsweixin|scanQRCode|getUserMedia|html5-qrcode|jsQR|BarcodeDetector" --include=*.ts --include=*.tsx --include=*.py .
(扫码相关 0 命中;命中的是 settlement 的微信支付 WeixinJSBridge 和 test-audio 的麦克风 getUserMedia,均无关)
现有能"扫"的只有品牌码,那是微信原生相机识别二维码里的网址直接打开页面,代码侧零扫码逻辑。
→ 做这个功能第一步是先接扫码能力。走微信 JSSDK 要配 JS 安全域名 + 后端签名接口,是独立前置工作量。
认领会写对 patients.service_org_id / service_org_bound_at,但读取过滤条件在 refractive_record_service.py::_patients_filtered_stmt —— 那是长官明令冻结的红线文件,phase48a-visibility-pending-20260916.md 列了 6 处待改,状态「已阻塞,等门店手机端视光档案那条线合并后再做」。
不解冻 = 扫码绑定做完也是白做。
claim_patient(brand_code_claim_service.py:104-140):孩子已绑 A 店,B 店扫到码,只要 B 店自己把 force_transfer 置 true 就能直接抢过来,不需要 A 店或家长任何确认。测试 test_06_claim_transfer_with_force_updates_org_and_bound_at 白纸黑字验证了这个行为(不带 flag 返 409 提示,带了就直接改)。
解绑侧反而是收紧的:只有当前服务机构能解绑自己名下的(release_patient_claim:143-159,否则 409)。
| 关系 | 落库 | 写入方 |
|---|---|---|
| 孩子 ↔ 服务机构 | patients.service_org_id / service_org_bound_at |
brand_code_claim_service.py:104/143 |
| 孩子 ↔ 家长子席位 | patients.owner_seat_id |
refractive_record_service.py:539/587 |
| 家长微信身份 ↔ 品牌方机构 | brand_code_activations 表 |
brand_code_service.py:116 |
三者代码层面互不调用(claim_patient 注释明写「本批完全不碰 owner_seat_id」),是刻意的独立判据,不是遗漏。
「二维码与端口绑定断开」是遥控自动生成的乱名,跟二维码/端口无关(对面会话原话),是上一个会话(Phase 83 发生产)留下的名字。它真正在做的是:
Phase 83 批次五 = 家长席位账号加大写 T 前缀 + 新席位改用「手机号+密码」登录 + 首登强制收手机号 + 品牌码扫码家长补一条账号密码兜底 + 品牌码后台页加手机号列和重置密码按钮。
| 项 | 说明 |
|---|---|
| 🔴 全量拦截 gate | 长官拍板「品牌码家长点任意功能就弹初始化弹窗」是全量拦截,而本仓前端没有集中式路由保护机制(各页面内联判断)。对面要新建一个共享 gate 挂到每个功能入口 —— 新首页恰好就是"一堆功能入口",两边必须对齐接法 |
🟠 chat/page.tsx:861 |
品牌码家长微信失效时的手机号+密码兜底出口 |
🟠 chat/page.tsx:930-980 |
微信身份带入(wx-identity 返回体加手机号字段) |
🟠 tablet/page.tsx:322-342 |
平板端同款改动 |
| 🟢 对面明确不动 | 首页视觉/布局/卡片结构/功能入口排布 |
当前无实际文件冲突 —— 对面只出了方案,一行代码没改,分支干净(feature/subseat-login-v2,基线 origin/test e6770bb7)。
🔴 #730 #731 #732 #733 必须先上生产。生产 alembic 还停在 phase48a_002,批次五的迁移要接在 phase83_001(批次三)之后,中间断一环跑不起来。这四个 MR 已在 test 验完,等长官一句话。
| 前置 | 挡住谁 | 内容 | 能不能并行 |
|---|---|---|---|
| P0 登录层抽取 | 件 1(两个首页) | 把 chat/page.tsx:764-1136 那 370 行鉴权 gate 抽成公共层(Next layout 或共享 hook),新首页与对话页都套在下面。必须与批次五的全量拦截 gate 对齐挂载点 |
必须最先做,且要跟批次五同桌定 |
| P1 扫码能力 | 件 3(互扫绑定) | 接微信 JSSDK(JS 安全域名 + 后端签名接口)或引扫码库。全仓 0 实现 | 可与 P0 并行 |
| P2 触达能力 | 件 2 的"提醒未记录家长" | 阿里云短信新模板申请(走运维流程)+ 真实手机号接通 + 限流设计;或等微信订阅消息 Go-Live | 申请流程需提前启动,不占开发工时 |
| P3 可见性解冻 | 件 3 的"认领后可见" | refractive_record_service.py::_patients_filtered_stmt 红线文件 6 处待改 |
需长官解冻授权 |
| 批 | 内容 | 前置 | 后端量 |
|---|---|---|---|
| 批 0 | P0 登录层抽取(与批次五同桌定 gate 挂载点) | 批次五 #730-#733 先上生产 | 0(纯重构) |
| 批 1 | 两个新首页容器 + 已有能力重排(品牌卡、孩子切换、最近检查、AI解读、示范档案、同款案例、工具入口、快捷操作)+ 全套跳转目标改造 + 通知运维改公众号菜单 URL | 批 0 | 0(纯前端) |
| 批 2 | 三个只读聚合端点(门店待办 3 类 / 今日数据 4 项 / 家长连接统计) | — | 小 |
| 批 3 | 趋势图补两条 + 度数/裸眼视力的「较上次」+ 最近处理列表补已读状态 | — | 小 |
| 批 4 | 家长每日打卡(新表 + 录入/汇总 API + 家长端表单 + 门店端汇总) | — | 大 |
| 批 5 | 互扫绑定前端(家长出码页 + 门店扫一扫页 + 转入确认弹窗) | P1 + P3 | 中(后端已通) |
| 批 6 | 「提醒未记录家长」+ 复查提醒体系 | P2 + 业务规则定义 | 中 |
| 摘出 | 眼轴百分位 | 卡门诊 5411 条数据入库,文件至今未上服务器 | — |
| # | 决策点 | 我的建议 |
|---|---|---|
| H1 | 新首页走独立路由(家长 /h5/home、门店 /h5/store-home),对话页回归纯对话? |
是。冲突面最小、可灰度、能让 isParentSeat 那 11 处分支消失 |
| H2 | 首页改版与批次五谁先谁后 | 批次五先(已拍板 13 条且卡着生产迁移);我们做 P0 登录层抽取时与它同桌定 gate 挂载点 |
| H3 | 「提醒未记录家长」走短信(要先申请阿里云模板)/ 等微信订阅消息 Go-Live / 本期只做名单展示不做主动提醒? | 本期只做名单展示,主动提醒单独立项(模板申请周期不可控) |
| H4 | 打卡「护眼灯」与代码里现有的「大路灯」是不是同一个设备? | 需您确认命名 |
| H5 | 门店端「未记录家长数」在多孩子家庭下怎么算 —— 任一孩子没打卡算未记录,还是全部孩子都没打卡才算? | 建议"任一孩子未打卡即算未记录"(更保守、不漏人) |
| H6 | 打卡当天能否重复提交覆盖? | 建议可覆盖(UNIQUE(patient_id, device_type, 本地日期)),家长填错能改 |
| H7 | 互扫绑定的抢占口径:B 店能不带原店/家长同意直接把孩子转过来,保留还是加确认? | 需您定。现状对已服务机构不友好 |
| H8 | 是否解冻可见性红线文件(refractive_record_service.py 6 处)?不解冻则件 3 做完也查不出来 |
需您授权 |
| H9 | 眼轴百分位确认摘出本期? | 建议摘出 |
/h5/chat 系列 URL —— 需要运维确认实际配的是什么/h5/tablet 本轮只验到"是平行实现、不会自动联动",没有逐块盘点它的首页壳 —— 若本期要一并覆盖平板,需要再查一轮8 路并行 grep + 主会话复核 + 跨会话对接。0 命中处均带对照组。本机为云端隔离开发机,非验收环境。