Skip to content

2.4 页面状态与浏览器验收 ​

Page 接上 client、store 和 route 后,先写清每种状态对应的页面输出。

状态页面输出允许的动作
idle尚未发出请求无
loading“正在加载推荐…”等待
success + items数量、卡片、收藏态切阶段、收藏、进详情
success + empty“当前阶段还没有推荐内容。”切阶段
error安全文案与“重新加载”retry
persistence notice当前内存修改与保存风险关闭提示、继续操作
detail not found“没有找到这条推荐。”返回

Empty 是成功响应,不显示 retry。合法 ID 没有对应详情也不是 network error。

本节检查:

text
apps/web/src/components/AsyncStatePanel.vue
apps/web/src/components/RecommendationCard.vue
apps/web/src/pages/index/index.vue
apps/web/src/pages/detail/index.vue
apps/web/src/pages/saved/index.vue
apps/web/src/pages.json
apps/web/src/utils/keyboard-activation.ts

三个 page 不共享查询细节 ​

PageClient 调用Store action
首页list(selectedStage)selectStage、setFavorite
详情get(id)找到 item 后 recordViewed、setFavorite
我的记录listByIds(savedIds)setFavorite

三个 page 都从 app provider 取得 client,并使用共享请求 controller。它们不 import Mock 数据,不直接碰 storage。

详情页先检查 query:

ts
recommendationId.value = isRecommendationId(query?.id)
  ? query.id
  : undefined;

请求成功后,只有拿到 item 才记录 history。未知、空白或目录之外的 ID 显示 not-found,不产生副作用。

AsyncStatePanel 只接 loading | empty | error、一条安全文案和 retry event。Component 不发请求,不读 store,也不知道当前 stage。

Retry 复用最近一次请求 ​

切换 stage 或重新进入页面会触发新的 loader。共享 controller 记录最近 loader,并用 token 丢弃 stale success 和 stale error。

Page 只负责给出本次查询:

ts
function loadRecommendations(stageId: StageId): Promise<void> {
  return loadRequest(() => client.list(stageId));
}

retryRequest() 重跑最近的 client.list(stageId),不复制 query 和错误映射。新请求开始时旧错误文案被清空。

一套键盘 helper 覆盖所有紧凑操作 ​

卡片打开与收藏是 sibling 控件。点击收藏不应冒泡成打开详情。阶段按钮、卡片、收藏、“我的记录”、详情收藏、retry 和通知关闭共用同一个 helper:

ts
export function createKeyboardActivationHandler<Arguments extends unknown[]>(
  action: (...args: Arguments) => void,
): (event: KeyboardEvent, ...args: Arguments) => void {
  return (event, ...args) => {
    if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
      event.preventDefault();
      action(...args);
    }
  };
}

Enter、空格和 Spacebar 执行 action 并阻止默认事件,其他键不执行。Helper 有 6 项行为测试。

紧凑操作的 CSS 同时声明 min-width: 44px 与 min-height: 44px。CSS guard 有 1 项测试,防止收藏、记录入口、retry、详情收藏与通知关闭被改回小触点。源码声明还不够,H5 要量 computed style 和 bounding box。

自动 gate 是 9 个文件、143 项 ​

bash
npm run check
npm --workspace apps/web run dev:h5 -- --host 127.0.0.1 --port 4173

Fresh npm run check 结果:9 个 test files、143 tests passed,随后 typecheck 与 H5 build 均为 exit 0。143 项包括 keyboard 6 和 CSS guard 1。

按真实 H5 路径操作 ​

打开 http://127.0.0.1:4173/。同一首页只改 hash query 后执行 refresh。

路径操作与结果
阶段 refresh选择 1–3 岁 后显示 1 条;refresh 后仍选中且仍为 1 条
阶段切换请求slow 完成后切到孕期,立即回到 loading;约 900 ms 后显示 1 条
收藏 refresh用 Space 切换“日常主题图画书”收藏,URL 留在首页;refresh 后收藏态保留
历史去重连续两次打开 rec-picture-book,最近浏览仍为 1 条
scenario=slowrefresh 后先显示 loading,约 900 ms 后显示当前阶段 1 条
scenario=empty显示 0 条和“当前阶段还没有推荐内容。”
scenario=retry首次显示错误与“重新加载”;Space 激活后恢复为 1 条

补查这些结果:

  • 点击收藏前后 URL 都是首页,没有打开详情;
  • 未知详情 ID 显示 not-found,“最近浏览”数量不变;
  • Enter 激活阶段,Space 激活 retry;
  • “我的记录”、详情收藏、retry、卡片收藏和通知关闭的实际 bounding box 均至少 44px × 44px;
  • 390 × 844 下 document 与 body 的 clientWidth/scrollWidth 都是 390/390;
  • 正常关键路径 console error 为 0。

失败时沿链路查第一处断点 ​

症状先查
收藏却打开详情卡片与收藏是否 sibling,event 是否分开
切阶段仍显示旧列表page 是否把新的 stage 传给 client.list(stageId),controller 是否丢弃 stale result
未知详情写入历史query 是否先经 isRecommendationId,是否拿到 item 后才 recordViewed
Refresh 丢状态hydrate()、store action、persistence key
Retry 没有恢复scenario 是否位于 hash query,是否 refresh,controller 是否保留最近 loader
键盘无反应role、tabindex、共享 helper 与 preventDefault()

下一步:2.5 PR Review 与交付。

晨悦 AI 实践手册