Merge commit 'a6382d6'
# Conflicts: # README.md # src/app.config.ts # src/pages/index/index.scss # src/pages/index/index.tsx
This commit is contained in:
658
docs/superpowers/plans/2026-05-08-sleep-report-page.md
Normal file
658
docs/superpowers/plans/2026-05-08-sleep-report-page.md
Normal file
@@ -0,0 +1,658 @@
|
||||
# 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<T>(
|
||||
records: Record<string, Record<string, Record<string, T>>>,
|
||||
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
|
||||
<View className="device-report-entry" onClick={openReportPage}>
|
||||
<View>
|
||||
<Text className="device-report-entry__eyebrow">昨夜睡眠分析</Text>
|
||||
<Text className="device-report-entry__title">查看睡眠报告</Text>
|
||||
</View>
|
||||
<Text className="device-report-entry__arrow">></Text>
|
||||
</View>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 新建最小报告页壳子**
|
||||
|
||||
```ts
|
||||
export default definePageConfig({
|
||||
navigationStyle: "custom"
|
||||
});
|
||||
```
|
||||
|
||||
```tsx
|
||||
export default function ReportPage() {
|
||||
return <View className="report-page">睡眠报告加载中</View>;
|
||||
}
|
||||
```
|
||||
|
||||
```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<ReportDimension>("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
|
||||
<View className="report-header">
|
||||
<View className="report-header__back" onClick={() => Taro.navigateBack()} />
|
||||
<View className="report-header__main">
|
||||
<Text className="report-header__name">{currentRecord.babyName}</Text>
|
||||
<Text className="report-header__date">{dateKey}</Text>
|
||||
</View>
|
||||
<View className="report-header__share" onClick={() => showToast("分享功能待接入")} />
|
||||
</View>
|
||||
```
|
||||
|
||||
```tsx
|
||||
<View className="report-score-card">
|
||||
<Text className="report-score-card__score">{currentRecord.score}</Text>
|
||||
<Text className={`report-score-card__level report-score-card__level--${sleepLevel.tone}`}>
|
||||
{sleepLevel.label}
|
||||
</Text>
|
||||
</View>
|
||||
```
|
||||
|
||||
- [ ] **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
|
||||
<View className="report-section">
|
||||
<Text className="report-section__title">睡眠质量趋势</Text>
|
||||
<View className="trend-chart">
|
||||
{currentRecord.sleepTrend.map((point, index) => (
|
||||
<View
|
||||
key={point.time}
|
||||
className={`trend-chart__point ${selectedTrendTime === point.time ? "is-active" : ""}`}
|
||||
style={{ left: `${index * 12}%`, bottom: `${point.score}%` }}
|
||||
onClick={() => setSelectedTrendTime(point.time)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
```
|
||||
|
||||
```tsx
|
||||
<View className="metric-grid">
|
||||
{currentRecord.metrics.map((metric) => (
|
||||
<View className="metric-card" key={metric.label}>
|
||||
<Text className="metric-card__label">{metric.label}</Text>
|
||||
<Text className="metric-card__value">
|
||||
{metric.value}
|
||||
<Text className="metric-card__unit">{metric.unit}</Text>
|
||||
</Text>
|
||||
<Text className={`metric-card__status metric-card__status--${getStatusTone(metric.status)}`}>
|
||||
{metric.status}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
```
|
||||
|
||||
- [ ] **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
|
||||
<View className="report-section">
|
||||
<Text className="report-section__title">AI 分析</Text>
|
||||
{currentRecord.aiSummary.map((item) => (
|
||||
<View className="insight-card" key={item.title}>
|
||||
<Text className="insight-card__title">{item.title}</Text>
|
||||
<Text className="insight-card__body">{item.body}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
```
|
||||
|
||||
```tsx
|
||||
<View className="report-section">
|
||||
<Text className="report-section__title">心率变异性(HRV)</Text>
|
||||
{currentRecord.hrvMetrics.map((item) => (
|
||||
<View className="kv-row" key={item.label}>
|
||||
<Text>{item.label}</Text>
|
||||
<Text>{item.value}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
```
|
||||
|
||||
```tsx
|
||||
<View className="report-section">
|
||||
<Text className="report-section__title">日间状态预测</Text>
|
||||
<View className="daytime-grid">
|
||||
{currentRecord.daytimePrediction.map((item) => (
|
||||
<View className={`daytime-chip daytime-chip--${getStatusTone(item.status)}`} key={item.label}>
|
||||
<Text>{item.label}</Text>
|
||||
<Text>{item.status}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
```
|
||||
|
||||
- [ ] **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` 这些命名,避免偏离计划。
|
||||
467
docs/superpowers/specs/2026-05-08-sleep-report-design.md
Normal file
467
docs/superpowers/specs/2026-05-08-sleep-report-design.md
Normal file
@@ -0,0 +1,467 @@
|
||||
# 睡眠报告页设计说明
|
||||
|
||||
**日期:** 2026-05-08
|
||||
|
||||
## 目标
|
||||
|
||||
新增一个“健康报告页 / 睡眠健康分析页”,为用户提供宝宝睡眠质量、呼吸状态、心率状态、HRV 分析、异常事件统计、睡眠评分和 AI 分析建议等完整的睡眠健康信息展示能力。
|
||||
|
||||
首版定位为“可交互演示版”:
|
||||
|
||||
- 页面结构完整
|
||||
- 使用本地 mock 数据驱动
|
||||
- 支持核心筛选和切换交互
|
||||
- 图表以轻量演示方式实现
|
||||
- 暂不接入真实后端接口
|
||||
|
||||
## 范围
|
||||
|
||||
- 新增独立页面 `pages/report/index`
|
||||
- 首页保留现有设备绑定页
|
||||
- 新增两个进入报告页的入口:
|
||||
- 底部导航里的“报告”
|
||||
- 首页主体区域里的“查看睡眠报告”入口
|
||||
- 报告页支持 `日报 / 周报 / 月报` 切换
|
||||
- 报告页支持日期、房间、设备筛选
|
||||
- 报告页包含评分总览、趋势图、统计卡片、AI 分析、异常统计、HRV、低氧、打鼾、睡眠结构、日间状态预测等模块
|
||||
- 不接入真实接口,不新增大型依赖,不引入全局状态管理
|
||||
|
||||
## 页面定位
|
||||
|
||||
这是一个面向宝宝睡眠监测场景的深色数据报告页。页面需要延续当前首页已经建立的深色监测风格,让“首页”和“报告页”看起来属于同一个产品,而不是两个割裂的界面。
|
||||
|
||||
页面重点不是营销展示,而是帮助用户快速判断一晚睡眠是否稳定、有没有异常风险、接下来应关注什么。
|
||||
|
||||
## 页面结构
|
||||
|
||||
### 1. 页面入口
|
||||
|
||||
- 首页底部导航“报告”点击后跳转到报告页
|
||||
- 首页新增一个明显的“查看睡眠报告”入口卡片或快捷入口
|
||||
- 报告页顶部返回按钮点击后返回首页
|
||||
|
||||
### 2. 顶部区域
|
||||
|
||||
- 返回按钮
|
||||
- 宝宝名称
|
||||
- 日期展示与切换
|
||||
- 分享按钮
|
||||
|
||||
说明:
|
||||
|
||||
- 分享按钮首版只做 `toast` 占位,提示“分享功能待接入”
|
||||
- 宝宝名称使用 mock 数据,如“安安的睡眠报告”
|
||||
|
||||
### 3. 时间维度切换
|
||||
|
||||
- 日报
|
||||
- 周报
|
||||
- 月报
|
||||
|
||||
默认选中:
|
||||
|
||||
- 日报
|
||||
|
||||
切换后行为:
|
||||
|
||||
- 重新读取对应维度的 mock 数据
|
||||
- 同步更新评分、趋势图、统计卡片和分析模块
|
||||
|
||||
### 4. 顶部筛选区
|
||||
|
||||
- 日期选择
|
||||
- 房间选择
|
||||
- 设备选择
|
||||
|
||||
首版交互:
|
||||
|
||||
- 使用页面内本地状态维护当前筛选条件
|
||||
- 切换筛选项后刷新页面展示内容
|
||||
- 日期切换以预设 mock 日期集合实现,不引入复杂日期组件
|
||||
|
||||
### 5. 评分总览区
|
||||
|
||||
显示内容:
|
||||
|
||||
- 综合睡眠评分
|
||||
- 睡眠评级
|
||||
- 睡眠质量状态
|
||||
- 评分组成项:入睡速度、睡眠稳定性、呼吸状态、心率状态、异常事件
|
||||
|
||||
评分规则:
|
||||
|
||||
- `90-100`:优秀
|
||||
- `75-89`:良好
|
||||
- `60-74`:合格
|
||||
- `0-59`:异常
|
||||
|
||||
视觉要求:
|
||||
|
||||
- 使用明显的中心评分视觉
|
||||
- 颜色随等级变化
|
||||
- 同时展示评分细分项,避免只有总分
|
||||
|
||||
### 6. 睡眠趋势与核心图表区
|
||||
|
||||
包含两组主要内容:
|
||||
|
||||
- 整夜睡眠趋势图
|
||||
- 睡眠分数趋势图
|
||||
|
||||
整夜趋势图展示:
|
||||
|
||||
- 深睡
|
||||
- 浅睡
|
||||
- REM
|
||||
- 清醒
|
||||
- 呼吸波动
|
||||
|
||||
睡眠分数趋势图展示:
|
||||
|
||||
- 时间
|
||||
- 睡眠评分
|
||||
- 阶段变化
|
||||
|
||||
首版支持:
|
||||
|
||||
- 切换时间维度后刷新图表
|
||||
- 点击数据点后显示当前时间点的简要数值
|
||||
|
||||
首版不做:
|
||||
|
||||
- 手势缩放
|
||||
- 复杂联动高亮
|
||||
- 大型图表引擎能力
|
||||
|
||||
### 7. 睡眠数据统计卡片区
|
||||
|
||||
展示以下 9 项核心指标:
|
||||
|
||||
1. 入睡时长
|
||||
2. 平均呼吸率
|
||||
3. 平均血氧
|
||||
4. 呼吸暂停次数
|
||||
5. 平均 HRV
|
||||
6. 平均心率
|
||||
7. 异常翻身次数
|
||||
8. 呼吸暂停时长
|
||||
9. 心率过快次数
|
||||
|
||||
每张卡片显示:
|
||||
|
||||
- 指标名称
|
||||
- 数值
|
||||
- 单位
|
||||
- 状态标签
|
||||
|
||||
状态分级:
|
||||
|
||||
- 正常
|
||||
- 注意
|
||||
- 异常
|
||||
|
||||
### 8. AI 分析模块
|
||||
|
||||
模块拆分为两部分:
|
||||
|
||||
- AI 睡眠分析摘要
|
||||
- 睡眠改善建议
|
||||
|
||||
分析内容包括:
|
||||
|
||||
- 睡眠稳定性分析
|
||||
- 呼吸状态分析
|
||||
- 心率变化分析
|
||||
- 异常风险提醒
|
||||
- 睡眠改善建议
|
||||
|
||||
首版要求:
|
||||
|
||||
- 文案由本地 mock 数据提供
|
||||
- 页面结构按真实业务报告组织
|
||||
- 保留后续接 AI 接口的扩展空间
|
||||
|
||||
### 9. 深度分析模块
|
||||
|
||||
包含以下内容:
|
||||
|
||||
- 异常统计模块
|
||||
- HRV 分析模块
|
||||
- 心率散点图
|
||||
- 呼吸波形图
|
||||
- 低氧统计模块
|
||||
- 打鼾监测模块
|
||||
- 异常事件散点图
|
||||
- 睡眠结构模块
|
||||
- 自主神经分析模块
|
||||
- 日间状态预测模块
|
||||
|
||||
各模块展示重点如下:
|
||||
|
||||
#### 异常统计
|
||||
|
||||
- 心率异常
|
||||
- 呼吸异常
|
||||
- 血氧异常
|
||||
- 呼吸暂停
|
||||
- 离床行为
|
||||
|
||||
显示:
|
||||
|
||||
- 次数
|
||||
- 最大值
|
||||
- 平均值
|
||||
- 持续时长
|
||||
|
||||
#### HRV 分析
|
||||
|
||||
- HRV 值
|
||||
- RMSSD
|
||||
- SDNN
|
||||
- LF/HF
|
||||
- 压力指数
|
||||
|
||||
状态:
|
||||
|
||||
- 正常
|
||||
- 压力偏高
|
||||
- 疲劳风险
|
||||
|
||||
#### 心率散点图
|
||||
|
||||
- 横轴:时间
|
||||
- 纵轴:心率值
|
||||
- 异常点高亮
|
||||
|
||||
#### 呼吸波形图
|
||||
|
||||
- 呼吸波动曲线
|
||||
- 异常点标记
|
||||
- 呼吸暂停标记
|
||||
|
||||
#### 低氧统计
|
||||
|
||||
- 最低血氧
|
||||
- 低氧次数
|
||||
- 持续时长
|
||||
|
||||
状态:
|
||||
|
||||
- 正常
|
||||
- 低氧风险
|
||||
- 高风险
|
||||
|
||||
#### 打鼾监测
|
||||
|
||||
- 打鼾时长
|
||||
- 打鼾频率
|
||||
- 峰值强度
|
||||
|
||||
#### 异常事件散点图
|
||||
|
||||
事件类型:
|
||||
|
||||
- 呼吸暂停
|
||||
- 心率异常
|
||||
- 哭闹
|
||||
- 离床
|
||||
|
||||
#### 睡眠结构
|
||||
|
||||
- 深睡比例
|
||||
- 浅睡比例
|
||||
- REM 比例
|
||||
- 清醒比例
|
||||
|
||||
#### 自主神经分析
|
||||
|
||||
- 交感活跃度
|
||||
- 副交感活跃度
|
||||
- 压力状态
|
||||
|
||||
#### 日间状态预测
|
||||
|
||||
- 精神状态
|
||||
- 疲劳程度
|
||||
- 情绪状态
|
||||
|
||||
状态颜色:
|
||||
|
||||
- 优秀:绿色
|
||||
- 良好:青色
|
||||
- 注意:橙色
|
||||
- 异常:红色
|
||||
|
||||
## 交互设计
|
||||
|
||||
### 1. 首页跳转
|
||||
|
||||
- 点击底部导航“报告”进入报告页
|
||||
- 点击首页快捷入口进入报告页
|
||||
|
||||
### 2. 报表维度切换
|
||||
|
||||
- 点击 `日报 / 周报 / 月报`
|
||||
- 更新当前维度状态
|
||||
- 刷新当前维度下的所有展示数据
|
||||
|
||||
### 3. 筛选切换
|
||||
|
||||
- 日期切换:切换到不同 mock 日期
|
||||
- 房间切换:切换到不同房间 mock 数据
|
||||
- 设备切换:切换到不同设备 mock 数据
|
||||
|
||||
### 4. 图表点按
|
||||
|
||||
- 点击折线图或散点图中的数据点
|
||||
- 页面显示对应时间点的数据提示
|
||||
|
||||
### 5. 分享
|
||||
|
||||
- 点击分享按钮
|
||||
- 弹出“分享功能待接入”
|
||||
|
||||
## 数据组织设计
|
||||
|
||||
建议新增本地 mock 数据文件:
|
||||
|
||||
- `src/pages/report/mock.ts`
|
||||
|
||||
数据按三个时间维度组织:
|
||||
|
||||
- `daily`
|
||||
- `weekly`
|
||||
- `monthly`
|
||||
|
||||
每个维度下再包含不同日期、房间、设备的数据组合。每组数据至少包含:
|
||||
|
||||
- 页面头部信息
|
||||
- 睡眠评分总览
|
||||
- 评分组成项
|
||||
- 趋势图数据
|
||||
- 核心统计卡片
|
||||
- AI 分析文案
|
||||
- 异常统计数据
|
||||
- HRV 数据
|
||||
- 心率与呼吸图表数据
|
||||
- 低氧、打鼾、睡眠结构、自主神经、日间状态数据
|
||||
|
||||
这样做的目的:
|
||||
|
||||
- 首版无需后端即可完整演示
|
||||
- 后续接接口时,页面结构可以保持不变
|
||||
- 只需要把 mock 数据来源替换成接口返回
|
||||
|
||||
## 实现建议
|
||||
|
||||
### 1. 页面与组件拆分
|
||||
|
||||
新增页面:
|
||||
|
||||
- `src/pages/report/index.tsx`
|
||||
- `src/pages/report/index.scss`
|
||||
- `src/pages/report/index.config.ts`
|
||||
|
||||
建议补充少量组件,避免单文件过长:
|
||||
|
||||
- `src/components/report-header/`
|
||||
- `src/components/report-score-card/`
|
||||
- `src/components/report-metric-grid/`
|
||||
- `src/components/report-chart-card/`
|
||||
|
||||
拆分原则:
|
||||
|
||||
- 页面文件负责组装和状态控制
|
||||
- 组件负责展示某一类稳定模块
|
||||
- 保持文件数量适中,优先让新手易读
|
||||
|
||||
### 2. 状态管理
|
||||
|
||||
仅使用 React 自带状态管理当前页面所需状态:
|
||||
|
||||
- 当前时间维度
|
||||
- 当前日期
|
||||
- 当前房间
|
||||
- 当前设备
|
||||
- 当前图表选中点
|
||||
|
||||
不引入全局状态库。
|
||||
|
||||
### 3. 图表实现方式
|
||||
|
||||
首版不引入重型图表依赖。
|
||||
|
||||
推荐实现方式:
|
||||
|
||||
- 使用 `View` 组合、绝对定位、渐变背景和简单几何块实现折线、散点、柱条和波形效果
|
||||
- 圆环评分使用纯样式方案或可被 Taro 兼容的轻量视觉实现
|
||||
- 图表重点是表达业务信息和交互反馈,而不是实现完整图表引擎
|
||||
|
||||
这样更符合当前项目目标:
|
||||
|
||||
- 依赖更少
|
||||
- 结构更简单
|
||||
- 更适合零基础项目维护
|
||||
|
||||
## 视觉方向
|
||||
|
||||
- 延续首页现有深色监测风格
|
||||
- 页面背景保持深灰蓝色系
|
||||
- 模块之间通过卡片分组,但避免过度装饰
|
||||
- 信息密度高,但层次必须清楚
|
||||
- 使用状态色表达风险等级:
|
||||
- 绿色:优秀 / 正常
|
||||
- 青色:良好
|
||||
- 橙色:注意
|
||||
- 红色:异常
|
||||
|
||||
## 实现边界
|
||||
|
||||
首版明确不包含以下内容:
|
||||
|
||||
- 真实接口请求
|
||||
- 真正的小程序分享链路
|
||||
- 手势缩放图表
|
||||
- 图表间复杂联动
|
||||
- 大型第三方 UI 库
|
||||
- 大型第三方图表库
|
||||
- 全局状态管理方案
|
||||
|
||||
## 验收标准
|
||||
|
||||
### 页面与导航
|
||||
|
||||
- 首页正常显示,不影响原有设备绑定逻辑
|
||||
- 底部“报告”点击可进入报告页
|
||||
- 首页新增入口点击可进入报告页
|
||||
- 报告页返回按钮可回到首页
|
||||
|
||||
### 交互行为
|
||||
|
||||
- `日报 / 周报 / 月报` 切换后,页面摘要和图表同步变化
|
||||
- 日期、房间、设备切换后,报告内容同步变化
|
||||
- 点击趋势图或散点图数据点时,能显示对应数值提示
|
||||
- 分享按钮点击后有 `toast` 占位反馈
|
||||
|
||||
### 视觉与内容
|
||||
|
||||
- 页面整体风格与首页一致
|
||||
- 核心模块齐全,长页面滚动时层级清晰
|
||||
- 状态色使用一致,不混乱
|
||||
- 重点信息在首屏和前几屏内能快速看到
|
||||
|
||||
### 代码与维护
|
||||
|
||||
- 不新增重型依赖
|
||||
- 使用本地 mock 数据驱动
|
||||
- 代码结构保持简单,适合新手阅读
|
||||
- `README.md` 更新新增报告页说明
|
||||
|
||||
### 验证方式
|
||||
|
||||
- 至少运行一次 `npm run build:weapp`
|
||||
- 确认没有新增 TypeScript 或 Taro 构建错误
|
||||
- 对可抽离的纯逻辑补最小必要测试,例如:
|
||||
- 评分等级映射
|
||||
- 状态颜色映射
|
||||
- mock 数据选择逻辑
|
||||
Reference in New Issue
Block a user