# 按钮与跳转交互审计

基准：`dev / f079dcc951be0d098a27aae4239096468107e4f3`；检查日期：2026-10-23；负责人：Codex；状态：本轮发现的 H5 差异已修复，源码 TODO 已单列。

66 个页面/容器；6 种数据身份场景；2080 个初始按钮检查（含禁用项）；0 个脚本异常；0 个未解释的无反馈项。41/41 组事件链回归通过。浏览器实际打开 66 页、额外记录 11 条点击链。

范围：逐页检查源文件跳转和回调索引，与 H5 入口、目标、参数、返回、表单及权限比对。全页按钮逐个在隔离 DOM 中触发，单独区分禁用、当前已选项、空消息和需要输入的表单。关键修改通过真实浏览器点击验收。纯渲染、DOM 事件链和浏览器点击不是同一种证据；未声称穷尽全部接口返回与所有业务分支。

## 已修正的差异

| 类别 | 最终行为 | 源码依据 |
| --- | --- | --- |
| 绑定入口 | 活动、我的、订阅空设备入口统一按孩子数量分流，多个孩子先选孩子再扫码。 | `lib/pages/main/activity/today_page.dart:1586` |
| 关联上下文 | 新设备选择孩子与已有设备更换孩子分开；设备 SN 与当前孩子完整传递，返回刷新正确设备。 | `lib/pages/main/me/link_to_child_page.dart:44` |
| 创建孩子 | 未关联设备从设备页新建孩子直接绑定该设备；扫码流程新建后回扫码；普通添加仍进入设备选择。 | `lib/pages/main/me/add_child_page.dart:653` |
| 无家庭的新用户 | 不再把不存在的家庭默认当作查看者，允许新用户扫码、创建孩子和完成引导。 | `lib/pages/main/me/add_child_page.dart:116` |
| 设置恢复 | 五种恢复状态只开放源码当前步骤；待完成步骤不再能随意跳过。 | `lib/pages/common/setup_recovery_page.dart:1084` |
| 设备时长 | 滑动已开启的时长限制会启用保存；保存后禁用，重返页面读已保存值。 | `lib/pages/main/me/device_settings_page.dart:449` |
| 邀请 | 返回按钮逐步回退；第三步完成返回调用页；再次进入重置邀请步骤。 | `lib/pages/main/me/invite_member_page.dart:188` |
| 加入家庭 | 邀请码去除首尾空格并转大写；从恢复页加入成功返回原页刷新，从欢迎页返回去 welcome。 | `lib/pages/main/me/join_family_page.dart:766` |
| 媒体删除 | 删除照片或作品后返回真实来源页并刷新列表，不统一强跳相册。 | `lib/pages/main/photos/photo_detail_page.dart:268` |
| 详情和页签 | 新打开详情不复用旧 item、设备、订单、流程参数；相册统计使用主 Tab 切换，参数可复制到链接。 | `lib/pages/main/activity/today_page.dart:1670` |
| 关于与安全 | 关于的隐私政策进入隐私与数据；安全页的删除账户也先进入隐私与数据，再执行确认。 | `lib/pages/main/me/security_page.dart:689` |
| 成长交互 | 孩子 AI 伙伴进入探索者等级；三个说明按源码页内展开收起；奖章展示已获得和未解锁，不添加筛选和弹窗。 | `lib/pages/main/activity/explorer_level_page.dart:450` |
| 表单和冷却 | 切换登录方式、阅读协议和返回时保留页面草稿与验证码倒计时；草稿不写入 localStorage。 | `lib/pages/auth/login_page.dart:1114` |
| 资料校验 | 头像与性别独立；邮箱验证只能验证当前邮箱；修改密码时当前密码也校验长度；无孩子预览显示空态。 | `lib/pages/main/me/email_verification_page.dart:15` |
| 密码返回 | 从修改密码进入重置页时，“记起密码”返回修改密码；更新成功仍回登录。 | `lib/pages/auth/forgot_password_page.dart:412` |
| 订阅权限与恢复 | 有效订阅的查看者可进入只读续费页；国内只读隐藏续费管理；恢复购买按源码保留平台调用入口。 | `lib/pages/main/me/subscription_page.dart:530` |
| 订单继续支付 | 历史订单支付成功停留历史页刷新状态，不跳新购订阅成功页。 | `lib/pages/main/me/subscription_history_page.dart:182` |
| 未接入页面入口 | 移除活动页自行添加的探索/最近活动入口，以及订阅卡额外的设备详情入口；这些组件仍可由评审目录预览。 | `lib/config/routes/app_router.dart:791` |
| 源码路由守卫 | 已登录访问欢迎首页、启动、注册按源码分流；退出清空旧返回栈；目录预览仍可独立查看页面。 | `lib/config/routes/app_router.dart:224` |
| 源码无返回按钮的页面 | 欢迎选择、初始化失败、设置恢复和设置完成不再统一附加产品返回按钮。 | `lib/pages/guide/setup_complete_page.dart:28` |
| 客服输入 | 发送后清空输入框；保留源码本地消息行为，不增加自动回复。 | `lib/pages/main/chat/live_chat_page.dart:68` |
| 源码空回调反馈 | 在手机画面外和源码栏标记 TODO；点击空回调给评审说明，不替源码新增产品行为。 | `lib/pages/main/me/help_article_page.dart:210` |

另修复共用 HTML 参数编码：`deviceSn`、`photoTab` 等统一转为 `data-device-sn`、`data-photo-tab`，防止 DOM dataset 丢失参数名。

## 逐页覆盖

下表数字是初始按钮实例（6 场景累计，包含禁用项），不是完整分支覆盖率。每个按钮的标签、动作参数、是否禁用、目标页、弹窗及提示均保存在 `evidence/interaction-sweep.json`。

| 页面 | 源码 | 初始按钮检查 | 入口 / 动作标识 | 本轮浏览器 |
| --- | --- | ---: | --- | --- |
| 检查邮箱 / `check-email` | `lib/pages/auth/check_email_page.dart` | 18 | back、login、resend-email | 已打开；事件由隔离 DOM 检查 |
| 重置密码 / `forgot-password` | `lib/pages/auth/forgot_password_page.dart` | 48 | back、login、send-code、set:accountMode、submit:reset-password、toggle-password | 已打开；事件由隔离 DOM 检查 |
| 登录 / `login` | `lib/pages/auth/login_page.dart` | 78 | back、forgot-password、login-help、login-mode、register、set:accountMode、social-login、submit:login、toggle-password、web-view | 已打开 + 记录点击链 |
| 欢迎首页 / `onboarding` | `lib/pages/auth/onboarding_page.dart` | 18 | environment、login、register | 已打开；事件由隔离 DOM 检查 |
| 注册 / `register` | `lib/pages/auth/register_page.dart` | 72 | back、login、region-sheet、send-code、set:accountMode、submit:register、toggle-password、web-view | 已打开；事件由隔离 DOM 检查 |
| 第三方登录绑定手机 / `social-bind-phone` | `lib/pages/auth/social_bind_phone_page.dart` | 18 | back、send-code、submit:social-bind | 已打开；事件由隔离 DOM 检查 |
| 启动页 / `splash` | `lib/pages/auth/splash_page.dart` | 6 | splash-next | 已打开；事件由隔离 DOM 检查 |
| 注册成功选择 / `welcome` | `lib/pages/auth/welcome_page.dart` | 12 | add-guide、join-family | 已打开；事件由隔离 DOM 检查 |
| 初始化失败 / `init-error` | `lib/pages/common/init_error_page.dart` | 12 | init-retry、logout | 已打开；事件由隔离 DOM 检查 |
| 设置恢复 / `setup-recovery` | `lib/pages/common/setup_recovery_page.dart` | 19 | add-device-entry、enter-main、join-family、link-to-child、recovery-menu | 已打开；事件由隔离 DOM 检查 |
| 内嵌网页 / `web-view` | `lib/pages/common/web_view_page.dart` | 12 | back、web-error | 已打开 + 记录点击链 |
| 引导添加孩子 / `child-info` | `lib/pages/guide/child_add_page.dart` | 18 | avatar、back、submit:child | 已打开；事件由隔离 DOM 检查 |
| 引导连接设备 / `add-guide` | `lib/pages/guide/device_connect_page.dart` | 18 | back、help-connect、scan-device | 已打开；事件由隔离 DOM 检查 |
| 连接帮助 / `help-connect` | `lib/pages/guide/help_connect_page.dart` | 24 | add-guide、back、live-chat、submit:activation | 已打开；事件由隔离 DOM 检查 |
| 设置完成 / `setup-complete` | `lib/pages/guide/setup_complete_page.dart` | 6 | enter-main | 已打开；事件由隔离 DOM 检查 |
| 奖章 / `badges` | `lib/pages/main/activity/badges_page.dart` | 6 | back | 已打开；事件由隔离 DOM 检查 |
| 旧物种详情（未接入） / `legacy-discovery` | `lib/pages/main/activity/discovery_detail_page.dart` | 6 | back | 已打开；事件由隔离 DOM 检查 |
| 探索时间线 / `explore` | `lib/pages/main/activity/explore_page.dart` | 70 | child-selector、discovery-detail、load-more、notifications-center、set:exploreTab、tab:chat-history、tab:me、tab:photos、tab:today、time-filter | 已打开；事件由隔离 DOM 检查 |
| 探索者等级 / `explorer-level` | `lib/pages/main/activity/explorer_level_page.dart` | 24 | back、companions、earn-xp、level-path | 已打开 + 记录点击链 |
| 最近活动 / `recent-activities` | `lib/pages/main/activity/recent_activities_page.dart` | 24 | back、set:activityFilter | 已打开；事件由隔离 DOM 检查 |
| 活动 / `today` | `lib/pages/main/activity/today_page.dart` | 90 | add-device-entry、child-selector、discovery-detail、field-guide、notifications-center、photo-detail、set:time、switch-main、tab:chat-history、tab:me、tab:photos、tab:today | 已打开 + 记录点击链 |
| 对话详情 / `chat-detail` | `lib/pages/main/chat/chat_detail_page.dart` | 26 | audio、back、photo-detail | 已打开 + 记录点击链 |
| 聊天历史 / `chat-history` | `lib/pages/main/chat/chat_history_page.dart` | 40 | chat-detail、child-selector、notifications-center、tab:chat-history、tab:me、tab:photos、tab:today | 已打开；事件由隔离 DOM 检查 |
| 在线客服 / `live-chat` | `lib/pages/main/chat/live_chat_page.dart` | 36 | back、submit:support、support-question | 已打开；事件由隔离 DOM 检查 |
| 探索页导航容器 / `main-layout` | `lib/pages/main/main_layout.dart` | 70 | child-selector、discovery-detail、load-more、notifications-center、set:exploreTab、tab:chat-history、tab:me、tab:photos、tab:today、time-filter | 已打开；事件由隔离 DOM 检查 |
| 主导航容器 / `main-tabs` | `lib/pages/main/main_tab_page.dart` | 94 | add-device-entry、child-selector、discovery-detail、field-guide、notifications-center、photo-detail、set:time、switch-main、tab:chat-history、tab:me、tab:photos、tab:today | 已打开；事件由隔离 DOM 检查 |
| 关于我们 / `about` | `lib/pages/main/me/about_page.dart` | 30 | back、check-update、privacy-data、version-history、web-view | 已打开 + 记录点击链 |
| 添加孩子 / `add-child` | `lib/pages/main/me/add_child_page.dart` | 18 | avatar、back、submit:child | 已打开；事件由隔离 DOM 检查 |
| 添加设备 / `add-device` | `lib/pages/main/me/add_device_page.dart` | 12 | back、scan-device | 已打开 + 记录点击链 |
| 修改密码 / `change-password` | `lib/pages/main/me/change_password_page.dart` | 36 | back、forgot-password、submit:change-password、toggle-password | 已打开；事件由隔离 DOM 检查 |
| 孩子详情 / `child-detail` | `lib/pages/main/me/child_detail_page.dart` | 53 | back、badges、edit-child-profile、explorer-level、field-guide、guide-settings、photo-detail、switch-main | 已打开 + 记录点击链 |
| 设备设置 / `guide-settings` | `lib/pages/main/me/device_settings_page.dart` | 34 | add-child、back、child-detail、link-to-child、save-device、toggle、unlink-device | 已打开 + 记录点击链 |
| 物种详情 / `discovery-detail` | `lib/pages/main/me/discovery_detail_page.dart` | 6 | back | 已打开；事件由隔离 DOM 检查 |
| 编辑孩子资料 / `edit-child-profile` | `lib/pages/main/me/edit_child_profile_page.dart` | 20 | avatar、back、remove-child、submit:child | 已打开；事件由隔离 DOM 检查 |
| 编辑个人资料 / `edit-profile` | `lib/pages/main/me/edit_profile_page.dart` | 18 | avatar、back、submit:profile | 已打开；事件由隔离 DOM 检查 |
| 验证邮箱 / `email-verification` | `lib/pages/main/me/email_verification_page.dart` | 18 | back、send-code、submit:verify-email | 已打开；事件由隔离 DOM 检查 |
| 数据报告预览 / `export-preview` | `lib/pages/main/me/export_preview_page.dart` | 24 | back、export-options、refresh、report-family | 已打开；事件由隔离 DOM 检查 |
| 家庭成员 / `family-members` | `lib/pages/main/me/family_members_page.dart` | 29 | back、invite-entry、join-family、leave-family、remove-member | 已打开 + 记录点击链 |
| 图鉴 / `field-guide` | `lib/pages/main/me/field_guide_page.dart` | 14 | back、species-category | 已打开；事件由隔离 DOM 检查 |
| 帮助文章 / `help-article` | `lib/pages/main/me/help_article_page.dart` | 24 | back、source-noop | 已打开 + 记录点击链 |
| 帮助与支持 / `help-support` | `lib/pages/main/me/help_support_page.dart` | 84 | back、email-support、live-chat、report-complaint | 已打开；事件由隔离 DOM 检查 |
| 邀请成员 / `invite-member` | `lib/pages/main/me/invite_member_page.dart` | 22 | back、invite-child、invite-next | 已打开 + 记录点击链 |
| 加入家庭 / `join-family` | `lib/pages/main/me/join_family_page.dart` | 24 | back、scan-invite、source-noop、submit:join-family | 已打开；事件由隔离 DOM 检查 |
| 语言 / `language-settings` | `lib/pages/main/me/language_settings_page.dart` | 54 | back、save-language、set:languageDraft | 已打开；事件由隔离 DOM 检查 |
| 关联孩子 / `link-to-child` | `lib/pages/main/me/link_to_child_page.dart` | 28 | back、choose-link-child、link-child-done、new-child-for-link | 已打开 + 记录点击链 |
| 关联设备 / `link-to-device` | `lib/pages/main/me/link_to_device_page.dart` | 32 | add-device、back、enter-main、link-device-done、set:linkDevice | 已打开 + 记录点击链 |
| 我的 / `me` | `lib/pages/main/me/me_page.dart` | 138 | about、add-child、add-device-entry、child-detail、confirm-logout、edit-profile、family-members、guide-settings、help-support、language-settings、live-chat、notifications、notifications-center、privacy-data、region-sheet、security、subscription、switch-family、tab:chat-history、tab:me、tab:photos、tab:today | 已打开；事件由隔离 DOM 检查 |
| 消息中心 / `notifications-center` | `lib/pages/main/me/notifications_center_page.dart` | 37 | back、mark-all-read、open-message、set:messageFilter | 已打开；事件由隔离 DOM 检查 |
| 通知设置 / `notifications` | `lib/pages/main/me/notifications_page.dart` | 48 | back、toggle | 已打开；事件由隔离 DOM 检查 |
| 隐私与数据 / `privacy-data` | `lib/pages/main/me/privacy_data_page.dart` | 42 | back、delete-account、export-preview、toggle、web-view | 已打开 + 记录点击链 |
| 安全 / `security` | `lib/pages/main/me/security_page.dart` | 30 | back、change-password、privacy-data、social-binding | 已打开；事件由隔离 DOM 检查 |
| 物种分类 / `species-category` | `lib/pages/main/me/species_category_page.dart` | 18 | back、discovery-detail | 已打开；事件由隔离 DOM 检查 |
| 订阅结算 / `subscription-checkout` | `lib/pages/main/me/subscription_checkout_page.dart` | 56 | back、pay、select-pay-device、set:plan、web-view | 已打开；事件由隔离 DOM 检查 |
| 设备订阅详情 / `subscription-device` | `lib/pages/main/me/subscription_device_detail_page.dart` | 10 | back、checkout-entry | 已打开；事件由隔离 DOM 检查 |
| 订阅历史 / `subscription-history` | `lib/pages/main/me/subscription_history_page.dart` | 15 | back、continue-pay、subscription-order-detail、sync-order | 已打开 + 记录点击链 |
| 订单详情 / `subscription-order-detail` | `lib/pages/main/me/subscription_order_detail_page.dart` | 6 | back | 已打开；事件由隔离 DOM 检查 |
| 订阅管理 / `subscription` | `lib/pages/main/me/subscription_page.dart` | 39 | add-device-entry、back、checkout-entry、restore-purchase、subscription-history、subscription-renewal | 已打开；事件由隔离 DOM 检查 |
| 自动续费管理 / `subscription-renewal` | `lib/pages/main/me/subscription_renewal_management_page.dart` | 12 | back、subscription | 已打开；事件由隔离 DOM 检查 |
| 订阅成功 / `subscription-success` | `lib/pages/main/me/subscription_success_page.dart` | 18 | back、enter-main、subscription | 已打开；事件由隔离 DOM 检查 |
| 切换家庭 / `switch-family` | `lib/pages/main/me/switch_family_page.dart` | 16 | back、switch-family | 已打开；事件由隔离 DOM 检查 |
| 版本详情 / `version-detail` | `lib/pages/main/me/version_detail_page.dart` | 6 | back | 已打开；事件由隔离 DOM 检查 |
| 版本历史 / `version-history` | `lib/pages/main/me/version_history_page.dart` | 16 | back、version-detail | 已打开；事件由隔离 DOM 检查 |
| 作品详情 / `artwork-detail` | `lib/pages/main/photos/artwork_detail_page.dart` | 22 | back、delete-media、native-media、photo-detail | 已打开；事件由隔离 DOM 检查 |
| 相册物种分类 / `category-species` | `lib/pages/main/photos/category_species_page.dart` | 6 | back | 已打开；事件由隔离 DOM 检查 |
| 照片详情 / `photo-detail` | `lib/pages/main/photos/photo_detail_page.dart` | 34 | artwork-detail、back、chat-detail、delete-media、discovery-detail、native-media | 已打开 + 记录点击链 |
| 相册 / `photos` | `lib/pages/main/photos/photos_page.dart` | 66 | child-selector、notifications-center、photo-detail、set:photoTab、tab:chat-history、tab:me、tab:photos、tab:today、time-filter | 已打开；事件由隔离 DOM 检查 |

## 保留与验收边界

- 源码 TODO/空实现单列在 [源码TODO清单.md](源码TODO清单.md)，不作为本次漏修项。
- 主导航别名 `/main/me` 等仍按源码重定向默认活动 Tab；帮助分类仍去在线客服。
- 所有身份、内容、价格、二维码、支付、权限和分享均为离线演示；未运行同提交 Flutter 真机，也未调用真实接口。
- 初始按钮扫描不等于弹窗每个选项及接口异步状态的穷举；本轮具体事件链以回归脚本和浏览器收据为准。
- 浏览器初始页面检查未发现空白按钮或脚本错误；原文字颜色修复保留。
- 产品与真机验收负责人、验收日期：待指定。

## 复验

```sh
# jsdom 只安装到临时测试目录，不进入交付包或业务项目依赖
npm install --prefix /tmp/qmox-h5-audit-deps jsdom@26 --no-audit --no-fund
node scripts/test-interactions.cjs
node --expose-gc scripts/audit-interactions.cjs
node scripts/test.cjs
node scripts/test-buttons.cjs
python3 scripts/check_source.py
```
