# Sleep Report Page Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 为当前小程序新增一个可交互的睡眠报告页,并在首页和底部导航中接入跳转入口,同时保持项目结构简单、适合新手继续维护。 **Architecture:** 报告页采用单页纵向滚动布局,使用本地 mock 数据驱动 `日报 / 周报 / 月报`、日期、房间、设备切换。纯逻辑抽到独立工具文件中,用最小测试基建验证评分等级、状态颜色和 mock 选择逻辑;页面层保持轻量组件拆分,不引入大型图表或状态管理依赖。 **Tech Stack:** Taro 4、React 18、TypeScript、SCSS、Node.js 内置 `node:test` --- ## File Structure - Create: `src/pages/report/index.tsx` - 报告页主文件,负责状态切换、数据选择、页面组装和点击反馈 - Create: `src/pages/report/index.scss` - 报告页整体样式和各个数据卡片、图表占位样式 - Create: `src/pages/report/index.config.ts` - 报告页页面配置 - Create: `src/pages/report/mock.ts` - 睡眠报告 mock 数据和维度、日期、房间、设备选项 - Create: `src/pages/report/report-utils.ts` - 评分等级、状态色、mock 选择等纯逻辑 - Create: `scripts/tests/report-utils.test.cjs` - 使用 Node 内置测试的 JS 测试文件 - Create: `tsconfig.report-tests.json` - 仅为纯逻辑测试编译 `report-utils.ts` - Modify: `package.json` - 增加最小测试命令 - Modify: `src/app.config.ts` - 注册报告页 - Modify: `src/pages/index/index.tsx` - 底部导航与首页入口接入报告页跳转 - Modify: `src/pages/index/index.scss` - 首页报告入口样式和导航点击态微调 - Modify: `README.md` - 更新新增报告页和运行说明 ### Task 1: 建立可测试的报告工具函数与最小测试基建 **Files:** - Create: `src/pages/report/report-utils.ts` - Create: `scripts/tests/report-utils.test.cjs` - Create: `tsconfig.report-tests.json` - Modify: `package.json` - [ ] **Step 1: 写评分和数据选择工具的失败测试** ```js const test = require("node:test"); const assert = require("node:assert/strict"); const { getSleepLevel, getStatusTone, pickReportRecord } = require("../../tmp/report-tests/report-utils.js"); test("getSleepLevel maps score 65 to 合格", () => { assert.deepEqual(getSleepLevel(65), { label: "合格", tone: "warning" }); }); test("getStatusTone maps 异常 to danger", () => { assert.equal(getStatusTone("异常"), "danger"); }); test("pickReportRecord falls back to first device record", () => { const record = pickReportRecord( { "2026-05-08": { roomA: { deviceA: { score: 65 }, deviceB: { score: 79 } } } }, "2026-05-08", "roomA", "missing-device" ); assert.equal(record.score, 65); }); ``` - [ ] **Step 2: 运行测试并确认当前失败** Run: ```bash npm run test:report ``` Expected: - 命令失败 - 报错提示 `report-utils.js` 不存在或相关导出不存在 - [ ] **Step 3: 写最小实现与测试编译配置** ```ts export type SleepLevel = "excellent" | "good" | "warning" | "danger"; export function getSleepLevel(score: number) { if (score >= 90) return { label: "优秀", tone: "excellent" as const }; if (score >= 75) return { label: "良好", tone: "good" as const }; if (score >= 60) return { label: "合格", tone: "warning" as const }; return { label: "异常", tone: "danger" as const }; } export function getStatusTone(status: string) { if (status === "正常" || status === "优秀") return "excellent"; if (status === "良好") return "good"; if (status === "注意" || status === "合格") return "warning"; return "danger"; } export function pickReportRecord( records: Record>>, dateKey: string, roomKey: string, deviceKey: string ) { const rooms = records[dateKey] ?? {}; const devices = rooms[roomKey] ?? {}; if (devices[deviceKey]) { return devices[deviceKey]; } const firstDevice = Object.values(devices)[0]; if (!firstDevice) { throw new Error("未找到报告数据"); } return firstDevice; } ``` ```json { "extends": "./tsconfig.json", "compilerOptions": { "noEmit": false, "outDir": "tmp/report-tests", "module": "CommonJS", "target": "ES2020", "declaration": false }, "include": ["src/pages/report/report-utils.ts"] } ``` ```json { "scripts": { "test:report": "tsc -p tsconfig.report-tests.json && node --test scripts/tests/report-utils.test.cjs" } } ``` - [ ] **Step 4: 再次运行测试并确认通过** Run: ```bash npm run test:report ``` Expected: - `3` 个测试通过 - 退出码为 `0` - [ ] **Step 5: 提交这一小步** ```bash git add package.json tsconfig.report-tests.json src/pages/report/report-utils.ts scripts/tests/report-utils.test.cjs git commit -m "test: add report utils coverage" ``` ### Task 2: 注册报告页并接入首页两个入口 **Files:** - Modify: `src/app.config.ts` - Modify: `src/pages/index/index.tsx` - Modify: `src/pages/index/index.scss` - Create: `src/pages/report/index.config.ts` - Create: `src/pages/report/index.tsx` - Create: `src/pages/report/index.scss` - [ ] **Step 1: 先为首页跳转行为写失败测试或可验证占位** 由于当前项目没有页面级单测基础,这一步使用最小可验证方式:先在报告页文件不存在的状态下运行编译,确认新增页面前不能通过对应页面注册与引用。 Run: ```bash npm run build:weapp ``` Expected: - 当前构建通过 - 但还没有报告页,也不存在新的页面注册和跳转逻辑 - [ ] **Step 2: 注册报告页并在首页接入跳转** ```ts export default defineAppConfig({ pages: ["pages/index/index", "pages/report/index"], window: { navigationBarTitleText: "新手小程序", navigationBarBackgroundColor: "#1AAD19", navigationBarTextStyle: "white", backgroundTextStyle: "light", backgroundColor: "#f6f7fb" } }); ``` ```ts const navItems = [ { key: "home", label: "首页", active: true }, { key: "report", label: "报告", active: false }, { key: "assistant", label: "小e", active: false }, { key: "message", label: "消息", active: false, badge: true }, { key: "mine", label: "我的", active: false } ]; const openReportPage = () => { Taro.navigateTo({ url: "/pages/report/index" }); }; const handleTabClick = (key: string, label: string) => { if (key === "report") { openReportPage(); return; } showToast(`${label}功能待接入`); }; ``` ```tsx 昨夜睡眠分析 查看睡眠报告 > ``` - [ ] **Step 3: 新建最小报告页壳子** ```ts export default definePageConfig({ navigationStyle: "custom" }); ``` ```tsx export default function ReportPage() { return 睡眠报告加载中; } ``` ```scss .report-page { min-height: 100vh; background: linear-gradient(180deg, #1e2432 0%, #191f2c 100%); } ``` - [ ] **Step 4: 运行构建确认页面注册与跳转壳子可编译** Run: ```bash npm run build:weapp ``` Expected: - 构建通过 - 编译产物包含 `pages/report` - [ ] **Step 5: 提交这一小步** ```bash git add src/app.config.ts src/pages/index/index.tsx src/pages/index/index.scss src/pages/report/index.config.ts src/pages/report/index.tsx src/pages/report/index.scss git commit -m "feat: add sleep report page entry" ``` ### Task 3: 接入 mock 数据、筛选状态和顶部总览区 **Files:** - Create: `src/pages/report/mock.ts` - Modify: `src/pages/report/index.tsx` - Modify: `src/pages/report/index.scss` - Modify: `src/pages/report/report-utils.ts` - [ ] **Step 1: 为 mock 选择逻辑补一个新的失败测试** ```js test("pickReportRecord returns selected device when it exists", () => { const record = pickReportRecord( { "2026-05-08": { roomA: { deviceA: { score: 65 }, deviceB: { score: 88 } } } }, "2026-05-08", "roomA", "deviceB" ); assert.equal(record.score, 88); }); ``` - [ ] **Step 2: 运行测试并确认新用例失败** Run: ```bash npm run test:report ``` Expected: - 新增用例失败,提示逻辑未覆盖或实现未更新 - [ ] **Step 3: 写报告页 mock 数据、顶部状态和总览区最小实现** ```ts export const reportDimensions = ["daily", "weekly", "monthly"] as const; export const reportOptions = { daily: { dates: ["2026-05-08", "2026-05-07"], rooms: [ { label: "婴儿房", value: "roomA" }, { label: "主卧", value: "roomB" } ], devices: [ { label: "床垫监测仪 A1", value: "deviceA" }, { label: "床垫监测仪 B2", value: "deviceB" } ] } }; ``` ```tsx const [dimension, setDimension] = useState("daily"); const [dateKey, setDateKey] = useState(reportOptions.daily.dates[0]); const [roomKey, setRoomKey] = useState(reportOptions.daily.rooms[0].value); const [deviceKey, setDeviceKey] = useState(reportOptions.daily.devices[0].value); const currentRecord = pickReportRecord(reportRecords[dimension], dateKey, roomKey, deviceKey); const sleepLevel = getSleepLevel(currentRecord.score); ``` ```tsx Taro.navigateBack()} /> {currentRecord.babyName} {dateKey} showToast("分享功能待接入")} /> ``` ```tsx {currentRecord.score} {sleepLevel.label} ``` - [ ] **Step 4: 运行测试和构建** Run: ```bash npm run test:report npm run build:weapp ``` Expected: - 报告工具测试全部通过 - 报告页能完成顶部区、维度切换和筛选区的编译 - [ ] **Step 5: 提交这一小步** ```bash git add src/pages/report/mock.ts src/pages/report/index.tsx src/pages/report/index.scss src/pages/report/report-utils.ts scripts/tests/report-utils.test.cjs git commit -m "feat: add report header and summary state" ``` ### Task 4: 实现评分总览、趋势图和核心统计卡片 **Files:** - Modify: `src/pages/report/index.tsx` - Modify: `src/pages/report/index.scss` - [ ] **Step 1: 先为状态色映射补一个失败测试** ```js test("getStatusTone maps 合格 to warning", () => { assert.equal(getStatusTone("合格"), "warning"); }); ``` - [ ] **Step 2: 运行测试并确认失败** Run: ```bash npm run test:report ``` Expected: - 新增状态映射用例失败 - [ ] **Step 3: 实现评分构成、主趋势图、睡眠分数趋势图和 9 项统计卡片** ```tsx 睡眠质量趋势 {currentRecord.sleepTrend.map((point, index) => ( setSelectedTrendTime(point.time)} /> ))} ``` ```tsx {currentRecord.metrics.map((metric) => ( {metric.label} {metric.value} {metric.unit} {metric.status} ))} ``` - [ ] **Step 4: 运行测试与构建,确认可交互卡片和趋势点正常编译** Run: ```bash npm run test:report npm run build:weapp ``` Expected: - 所有测试通过 - 报告页的评分、趋势图和统计卡片编译通过 - [ ] **Step 5: 提交这一小步** ```bash git add src/pages/report/index.tsx src/pages/report/index.scss src/pages/report/report-utils.ts scripts/tests/report-utils.test.cjs git commit -m "feat: add report charts and metrics" ``` ### Task 5: 实现 AI 分析与深度分析模块 **Files:** - Modify: `src/pages/report/index.tsx` - Modify: `src/pages/report/index.scss` - Modify: `src/pages/report/mock.ts` - [ ] **Step 1: 为缺省报告数据错误路径补一个失败测试** ```js test("pickReportRecord throws when no room data exists", () => { assert.throws( () => pickReportRecord({}, "2026-05-08", "roomA", "deviceA"), /未找到报告数据/ ); }); ``` - [ ] **Step 2: 运行测试并确认失败** Run: ```bash npm run test:report ``` Expected: - 新增错误路径用例失败 - [ ] **Step 3: 实现 AI 摘要、异常统计、HRV、心率散点、呼吸波形、低氧、打鼾、睡眠结构、自主神经和日间预测模块** ```tsx AI 分析 {currentRecord.aiSummary.map((item) => ( {item.title} {item.body} ))} ``` ```tsx 心率变异性(HRV) {currentRecord.hrvMetrics.map((item) => ( {item.label} {item.value} ))} ``` ```tsx 日间状态预测 {currentRecord.daytimePrediction.map((item) => ( {item.label} {item.status} ))} ``` - [ ] **Step 4: 运行测试与构建** Run: ```bash npm run test:report npm run build:weapp ``` Expected: - 错误路径和已有逻辑测试通过 - 报告页所有深度分析模块通过编译 - [ ] **Step 5: 提交这一小步** ```bash git add src/pages/report/index.tsx src/pages/report/index.scss src/pages/report/mock.ts src/pages/report/report-utils.ts scripts/tests/report-utils.test.cjs git commit -m "feat: complete sleep report sections" ``` ### Task 6: 更新 README 并做最终验证 **Files:** - Modify: `README.md` - Modify: `src/app.config.ts` - Modify: `src/pages/index/index.tsx` - Modify: `src/pages/index/index.scss` - Modify: `src/pages/report/index.tsx` - Modify: `src/pages/report/index.scss` - Modify: `src/pages/report/index.config.ts` - Modify: `src/pages/report/mock.ts` - Modify: `src/pages/report/report-utils.ts` - Modify: `package.json` - Modify: `tsconfig.report-tests.json` - Modify: `scripts/tests/report-utils.test.cjs` - [ ] **Step 1: 更新 README 的页面说明和运行命令说明** ```md ## 1. 目前已经包含什么 - 首页设备绑定业务示例页面 - 睡眠报告演示页 ## 13. 你接下来最常做的开发动作 ### 改报告页 编辑: - `src/pages/report/index.tsx` - `src/pages/report/mock.ts` ``` - [ ] **Step 2: 运行完整验证** Run: ```bash npm run test:report npm run build:weapp ``` Expected: - 报告页工具测试全部通过 - Taro 小程序构建通过 - 无新增依赖安装步骤 - [ ] **Step 3: 检查最终改动范围** Run: ```bash git status --short ``` Expected: - 只出现本计划涉及的文件改动 - [ ] **Step 4: 提交最终实现** ```bash git add README.md package.json tsconfig.report-tests.json scripts/tests/report-utils.test.cjs src/app.config.ts src/pages/index/index.tsx src/pages/index/index.scss src/pages/report/index.config.ts src/pages/report/index.tsx src/pages/report/index.scss src/pages/report/mock.ts src/pages/report/report-utils.ts git commit -m "feat: add sleep report page" ``` ## Self-Review - 规格要求的两处入口、顶部区域、三种时间维度、筛选区、评分总览、趋势图、统计卡片、AI 分析、深度分析模块、README 更新和验证命令都已经映射到具体任务。 - 计划没有使用 `TODO`、`TBD`、`类似上一步` 这类占位写法。 - 任务里的函数名、文件路径和命令保持一致,后续执行时应继续沿用 `getSleepLevel`、`getStatusTone`、`pickReportRecord` 这些命名,避免偏离计划。