Files

214 lines
24 KiB
Markdown
Raw Permalink Normal View History

# 芭蕾岛第一期:练习记录与成长回顾
Status: ready-for-agent
Created: 2026-09-28
Parent: [第一期决策地图](map.md)
## Problem Statement
芭蕾爱好者已经在线下上课或跟随现有内容练习,需要一个适合课后快速登记、长期回顾投入的工具。用户提供的 iHour 截图展示了按熟悉的课程或练习名称记录时长、回看累计与项目分布的实际使用方式,但通用时间管理中的项目层级、计时和激励体系并非第一期都需要。
用户希望练完后一笔记下整堂课,不必拆分把杆、中间或每个动作;偶尔另外练习,也能单独记录。课名和练习习惯会变化,因此项目需要可管理,改名或停用不能损失历史。回顾应能回答“练了多久、在哪些天练习、时间花在哪里、当时有什么收获”,而不是把时长当作技术水平。
## Solution
提供微信小程序中的“记录、日历、成长”三个入口,沿用用户已认可的交互草图。
| 入口 | 第一期开通的能力 |
| --- | --- |
| 记录 | 从平铺项目进入课后登记;填写实际练习日期、整堂时长和选填笔记;查看近期记录;进入项目管理 |
| 日历 | 识别有练习的日期;点选日期查看当天记录;为所选日期补记,或更正已有记录 |
| 成长 | 累计时长、练习天数、周/月时长趋势、各项目时长与占比 |
首次使用预置八个可编辑项目:零基础、基础提升、软开素质、足髋训练、核心臀腿、小球核心、天鹅臂颈、呼吸训练。这些是来自用户使用习惯的便捷名称,不是芭蕾训练的标准分类。
数据关联微信身份并存于现有后端使用的数据库,同一微信身份换设备后可恢复记录。新账户从空记录开始;不导入 iHour 历史,不带入截图中的累计数字。练习目标、成就徽章和分享卡片留待后续版本。
## User Stories
1. 作为芭蕾爱好者,我希望打开小程序即可理解“记录、日历、成长”三个入口,以便快速找到登记和回顾的位置。
2. 作为首次使用者,我希望获得八个熟悉的预置项目,以便无需先建立分类体系就能记录练习。
3. 作为首次使用者,我希望历史与统计真实地从零开始,以便区分自己的投入和产品演示数据。
4. 作为上课的学员,我希望练完后手动填写整堂课的时长,以便不在上课时操作计时器。
5. 作为上课的学员,我希望一条记录只选择一个项目,以便无需拆分课堂环节,也不会重复计算同一段时间。
6. 作为自主练习者,我希望能另外登记一次练习,以便保留课程之外的投入。
7. 作为记录者,我希望常用项目平铺展示,以便直接选中课名或练习名称。
8. 作为记录者,我希望日期默认今天,以便完成常见的课后登记。
9. 作为漏记过练习的人,我希望能选择实际练习日期补记,以便日历和趋势反映练习发生的时间。
10. 作为记录者,我希望按分钟填写时长,以便保留不足一小时的练习。
11. 作为记录者,我希望选填老师反馈或个人收获,以便回顾那一次练习的具体内容。
12. 作为记录者,我希望不写笔记也能保存,以便只想记时长时快速完成。
13. 作为记录者,我希望无效日期、时长或缺失项目得到明确提示,以便在保存前改正输入。
14. 作为记录者,我希望可以取消登记,以便未完成的输入不会变成正式记录。
15. 作为记录者,我希望保存成功后回到发起登记的页面并看到结果,以便确认本次操作已经完成。
16. 作为记录者,我希望保存失败时保留输入并允许重试,以便无需重新填写整堂课的信息。
17. 作为记录者,我希望重复点击或重试同一次保存不会生成重复记录,以便统计不会被网络问题放大。
18. 作为回顾练习的人,我希望在记录页看到近期记录,以便检查刚刚登记的信息。
19. 作为误填信息的人,我希望更正记录的项目、日期、时长和笔记,以便保留准确的练习历史。
20. 作为误记练习的人,我希望删除单条错误记录,以便将其从日历和统计中移除。
21. 作为回顾练习的人,我希望日历标记有记录的日期,以便看清练习在时间上的分布。
22. 作为回顾练习的人,我希望点选日期后看到当天的全部记录及笔记,以便回想那天练了什么。
23. 作为补记练习的人,我希望从日历发起登记时沿用所选日期,以便不必重复选择日期。
24. 作为更正记录的人,我希望修改后相关日期、累计与趋势一起更新,以便不同页面展示一致的事实。
25. 作为有个人练习习惯的人,我希望新增项目,以便使用自己的课程或练习名称。
26. 作为调整课名的人,我希望给项目改名,以便名称符合现在的用法。
27. 作为回顾历史的人,我希望改名后的项目在旧记录和统计里也统一使用新名称,以便仍能识别为同一个项目。
28. 作为整理项目的人,我希望删除没有使用过的项目,以便缩短日常选择列表。
29. 作为停止某类练习的人,我希望将有记录的项目移出常用列表并保留历史,以便不用担心整理项目导致数据丢失。
30. 作为回顾历史的人,我希望归档项目的记录、笔记和时长仍可查看和更正,以便过去的练习不会消失。
31. 作为关注投入的人,我希望看到准确的累计时长,以便了解自己在芭蕾练习上投入了多少时间。
32. 作为关注练习习惯的人,我希望同一天无论登记几次都只算一个练习日,以便练习天数不被记录条数放大。
33. 作为回顾近期练习的人,我希望切换周和月查看时长趋势,以便了解一段时间内的练习安排。
34. 作为回顾练习结构的人,我希望看到各项目时长及占比,以便了解投入分布。
35. 作为需要休息的练习者,我希望没有练习的日期如实留空,以便回顾不依赖连续打卡或惩罚休息日。
36. 作为使用多个设备的人,我希望同一微信身份能读取已保存的项目和记录,以便更换设备后继续使用。
37. 作为记录个人笔记的人,我希望自己的数据只能由自己的身份读取和修改,以便笔记和练习历史保持私密。
38. 作为遇到网络或登录问题的人,我希望界面说明当前未能加载或保存,而不把失败显示为零记录或保存成功,以便判断是否需要重试。
## Implementation Decisions
### 决策来源与实现默认值
- 用户已确认:练后填写、整堂记录、选填笔记、八个可编辑平铺项目、删除项目保留历史、改名统一显示新名称、三页流程、云端身份关联保存、不衔接旧数据、激励与分享后置。
- 已认可的原型决定页面入口、操作顺序、保存后返回位置、空状态与更正路径。正式界面适配小程序;演示场景切换、固定演示日期和演示数据面板不进入产品。
- 为使规格可执行,采用已讨论过的常规默认值:正整数分钟、日期默认今天、允许过去日期而不允许未来日期、周一至周日为一周、自然月为一个月。这些是实现默认值,不表述为用户逐项确认的产品取舍。
- 日历日期统一按一个业务时区解释,首期默认 Asia/Shanghai;练习日期存为日期值,与创建时间分开。不依据设备时区或创建时间重新划分已保存记录的日期,首期不增加时区设置页面。
- 下文身份校验、失败反馈、幂等写入和数据约束属于完成云端保存及准确统计所需的实现要求,不扩展为新的独立产品功能。
### 架构与模块职责
- 延续 Taro React 小程序和 Go/PostgreSQL 后端。云端保存指现有后端持久化,不因此改用另一套云服务或建立第二套业务数据源。
- 身份与会话模块负责核验微信身份、映射内部用户标识、建立与校验业务会话。外部身份核验集中在一个适配边界;不得将客户端自报的用户标识直接作为授权依据。
- 练习业务模块统一负责项目管理、记录增删改查和回顾统计。对外提供业务操作,内部封装归属校验、分钟与日期规则、归档规则及数据库操作;不按每个页面重复实现统计逻辑。
- 小程序负责页面状态、表单反馈与展示,通过统一请求边界使用业务接口。服务端对所有写入再次校验,不依赖客户端校验保证正确性。
- 复用现有服务启动、数据库连接和 HTTP 请求处理结构。现有存活及就绪检查继续保持职责独立;新增业务接口必须校验身份。
- 首期统计直接从有效记录计算,避免维护另一套容易失真的累计余额。需要优化时以相同可观察结果为约束,不提前建立复杂缓存或异步统计系统。
### 数据模型与不变量
| 对象 | 必要信息 | 约束 |
| --- | --- | --- |
| 用户 | 内部身份、经服务端验证的微信身份关联 | 同一身份恢复同一份数据;身份密钥与平台凭据仅存于服务端 |
| 练习项目 | 稳定标识、所属用户、当前名称、是否归档 | 用户之间隔离;改名保留标识;有历史记录时不可级联删除 |
| 练习记录 | 稳定标识、所属用户、项目标识、实际练习日期、整数分钟、可空文本笔记 | 每条只属于一个本人的项目;日期不晚于业务当天;分钟大于零;笔记可留空 |
| 写入识别信息 | 用户范围内的请求标识及处理结果 | 同一次新增的重试可识别;不能吞掉用户有意新建的第二条相同内容记录 |
- 服务端首次建立用户时一次性创建八个预置项目。初始化应原子且可重入;再次登录不得重复创建,也不得恢复用户已删除或归档的预置项目。
- 项目改名后,历史记录通过同一项目标识读取当前名称。记录日期、分钟与笔记不变,不另存一份用于展示的旧项目名称。
- 没有记录的项目可以删除;存在记录的项目执行归档。判定与移除应具有一致性,避免与同时新增记录竞争时删掉历史。
- 归档项目不出现在新增记录的项目选择中,仍参与历史查询和全部统计。编辑其原有记录时可保留该项目或改选一个活跃项目;不能把另一条记录新改入已归档项目。
- 删除练习记录后,该记录不再参与日历、练习天数和时长统计。项目归档与记录删除是不同操作:前者保留历史,后者去掉误记。
- 新增、改名时拒绝全空白项目名;笔记按普通文本保存和展示。客户端与服务端保持一致的输入长度限制和错误提示,不把原型中的临时输入上限当作产品决策。
- 所有关联校验都包含用户归属;不能通过猜测另一个项目或记录标识跨账户读取或修改数据。
### 接口契约
以下约定业务输入输出,不预先固定路由命名或内部函数形状。所有业务操作从有效会话识别用户,错误返回应能区分需要重新登录、输入无效、对象不可用和暂时性服务失败。
| 操作 | 输入与条件 | 可观察结果 |
| --- | --- | --- |
| 建立/恢复会话 | 微信端取得的有效身份交换凭据 | 返回业务会话;恢复同一用户,必要时完成一次性预置初始化 |
| 查询项目 | 有效会话 | 返回当前活跃项目;查询历史时仍能解析归档项目的名称和状态 |
| 新增/改名项目 | 名称;改名另带项目标识 | 返回稳定标识与最新名称;改名在后续历史和统计查询中统一生效 |
| 移除项目 | 本人项目标识 | 未使用项目删除,已使用项目归档;结果明确告知采取的操作 |
| 查询练习记录 | 日期或日期范围;近期列表使用有界查询 | 返回本人记录、当前项目名称、日期、分钟及笔记;归档项目记录仍可返回 |
| 新增记录 | 活跃项目、练习日期、分钟、可空笔记、此次提交标识 | 完整持久化后返回记录;相同提交标识的重试不重复新增 |
| 更正记录 | 本人记录标识和完整有效表单 | 原记录被更新;不新增记录;遵守归档项目的编辑规则 |
| 删除记录 | 本人记录标识 | 记录不再可见且不参与汇总;重试不会作用于其他记录 |
| 查询回顾 | 累计范围、选定周或月等所需范围 | 返回总分钟、去重练习天数、按日期的分钟及项目分布;结果使用一致的范围口径 |
- 对同一次新增,客户端在等待或重试期间复用提交标识,首次成功后结束该次提交;再次主动登记生成新的标识。重复提交不同内容时应给出可识别错误,不静默覆盖第一次结果。
- 加载失败与有效空结果是不同状态。失败不得被映射为零分钟、零天或空历史;保存提示以服务端确认持久化为准。
- 写入成功后更新或重新获取受影响的记录、日历和成长数据。迟到的旧请求响应不得覆盖新结果;返回来源页面时应能看到本次修改。
- 会话失效时允许重新建立会话并继续操作;在结果未确认前保留用户填写内容。客户端仅作临时表单保留,不承担跨设备数据持久化或离线冲突合并。
### 页面与交互
- 记录页以活跃项目平铺列表作为主要录入入口,展示近期记录并提供项目管理入口;新用户看到真实空状态和预置项目。
- 记录表单包含项目、实际练习日期、分钟、选填笔记。整堂课只产生一条记录,额外练习另建记录。保存期间阻止重复操作;失败保留表单;取消不写入。
- 从记录页新增时日期默认今天;从日历新增时沿用选中日期;编辑时读取原记录。保存完成回到原页面,并展示成功反馈及更新后的数据。
- 记录删除沿用原型中的误记删除路径并清楚提示影响,避免将“移除项目”和“删除记录”混淆。无效提交不能关闭表单或显示成功。
- 项目管理支持新增、改名和移除。有历史时明确告知将从常用项目中移除、历史仍保留;首期不增加项目分组、层级或独立归档恢复中心。
- 日历支持月份切换、练习日期标记和当天明细。没有记录的日期展示可补记的空状态;有多条时完整列出,不把当天汇总伪装成一堂课。
- 成长页展示累计、练习天数、周/月趋势及项目时长分布。采用原型中的趋势切换路径;首期不再增加年趋势或任意范围分析面板。
- 所有项目均移出活跃列表时,仍可查看历史和成长,并提供新增项目入口。零记录时占比展示为空状态,不出现无效百分比或虚构数据。
### 统计口径
- 总时长等于所选范围内有效记录的整数分钟之和;先汇总,再格式化为小时和分钟。展示取整不得反向参与统计。
- 练习天数等于所选范围内至少有一条有效记录的不同练习日期数,不等于记录条数、注册天数或连续打卡天数。
- 每条记录计入一个项目一次;所有项目分钟之和等于同范围总分钟。有历史的归档项目照常计入,名称使用最新名称。
- 项目占比以该项目分钟除以同范围总分钟计算;零总量时不做除法。显示百分比可按统一精度取整,因此显示值总和可能有舍入差异,不能靠改动分钟数强凑。
- 周趋势按所选周的日期汇总,月趋势按所选自然月的日期汇总;无记录日为零。日历与图表按实际练习日期归属,不按提交日期归属。
- 补记、更改日期、更改项目、修改分钟或删除记录后,所有受影响日期、范围和项目的统计应重新反映有效记录。改名和归档本身不改变累计时长或练习天数。
## Testing Decisions
### 主要自动化边界
优先使用现有 HTTP handler 的公开请求/响应边界,覆盖身份后的项目、记录、回顾完整业务路径。现有健康检查测试已经使用 Go 标准测试工具和 HTTP 测试请求验证状态码、响应及上下文行为,可沿用该风格。
业务集成测试连接独立 PostgreSQL 测试数据库或隔离命名空间,运行相同的数据结构初始化,验证真实持久化、关联和查询。只在外部微信身份核验边界替换不可控的平台网络响应,业务会话、归属校验和数据库行为继续实际执行。不为每个内部函数、SQL 语句或页面组件另设一套测试边界。
仓库当前只有健康检查测试,没有项目、练习记录或前端端到端测试。上述业务测试环境需要随功能建立;默认不另引入小程序端到端自动化框架。该安排是依据现有测试入口提出的执行默认方案,尚未经用户单独确认;不影响已经确认的产品范围。
### 验收场景
| 场景 | 对外结果 |
| --- | --- |
| 同一身份首次进入与再次登录 | 首次得到八个活跃项目和零记录;再次进入不重复预置、不恢复已移除项目;有效历史保持不变 |
| 不同用户访问同一对象标识 | 第二个用户不能读取、更改或删除第一个用户的项目及记录;接口不泄露其笔记 |
| 同日登记 90 分钟课程及 15 分钟额外练习 | 两条记录、105 分钟、1 个练习日;项目分布分钟合计为 105 |
| 将 90 分钟改为 60,再删除 15 分钟误记 | 先得到 75 分钟,再得到 60 分钟;日历、近期记录与成长一致 |
| 将同日两条中的一条移到另一日期 | 总分钟不变,练习日从 1 变为 2;原日期和新日期的日历与趋势同时变化 |
| 改名并归档有记录的项目 | 历史和统计统一显示新名称,时长及练习天数不变;新增选择器中不再出现该项目 |
| 编辑归档项目原有记录 | 可更正日期、时长、笔记并保留原项目,或改选活跃项目;不能新增记录到归档项目 |
| 删除未使用项目并重新登录 | 项目仍保持移除状态,不被再次初始化;仍可添加新的项目 |
| 删除某天最后一条记录 | 该日期不再计入练习天数;删除全部记录后累计为 0、练习天数为 0,分布展示空状态 |
| 分钟精度与边界日期 | 45 分钟和 30 分钟合计显示 1 小时 15 分钟;月底、周日/周一的记录按约定边界归属,补记不落在提交日 |
| 可空笔记和无效表单 | 空笔记成功保存;零、负数、非整数分钟、未来日期、空项目名以及不可用项目写入被拒绝,不产生部分数据 |
| 重复提交与主动重复登记 | 同一提交标识重试只得到一条记录;用户两次主动登记相同内容可得到两条记录 |
| 提交已成功但响应丢失 | 使用原提交标识重试后得到已保存记录,不重复累计;输入不会因未经确认的结果而丢失 |
| 服务异常或会话失效 | 明确区分失败与空历史;重试或重新登录后可继续;不显示虚假保存成功 |
好的测试从请求开始,断言返回结果及后续读取能看到的事实;时长、练习天数和归档语义均通过业务接口验证。不以内部调用次数、SQL 字符串、组件结构或样式快照替代业务结果。日期测试使用受控的业务当天,避免执行日期不同造成不稳定。
### 小程序与真实平台验收
- 用微信开发者工具走完三页导航、首次空状态、整堂记录、补记、更正、误记删除、项目改名与归档,核对表单键盘、滚动和窄屏布局。
- 用真实微信身份完成首次使用、会话恢复和另一设备读取同一份记录;重启客户端后仍能读取,另一身份看不到这些记录。
- 检查弱网/断网保存失败、重复点击、会话失效、重新进入页面后的反馈。确认保存成功来源于真实后端响应。
- 小程序源码变更后,在其项目目录执行 `pnpm typecheck`、`pnpm build`;后端源码变更后,在其项目目录执行 `go vet ./...`、`go test -race ./...`、`go build -o bin/api ./cmd/server`。若调整部署配置,另验证 Compose 和镜像构建。
- 静态检查、构建、HTTP 集成测试和真实微信设备验收分别记录结果;任何一项通过均不替代其他项。本文是规格,本轮未执行这些生产实现检查。
## Out of Scope
- 实时计时器、后台计时、番茄钟,以及把一堂课拆为多个动作或训练环节。
- 自建课程、教学视频、动作知识库、训练计划、自动推荐、技能等级评估或医疗/康复建议。
- 语音录入、LLM 解析练习、动作识别。其他尚未确认的规划事项不自动扩充本规格。
- 练习目标、成就徽章、分享卡片、连续打卡奖励和社交排行。
- iHour 自动同步、数据导入、历史迁移、期初累计;普通的按实际日期补记仍在范围内。
- 项目父子层级、同时给多项目累计同一条记录、完整归档管理或回收站体系。
- 笔记附件、图片或视频上传,以及笔记内容的自动分析。
- 独立手机号注册、多平台原生客户端、订阅或付费体系。
- 离线保存队列、多设备同时编辑冲突合并、实时推送同步。已确认的云端记录跨设备读取与恢复仍在范围内。
- 本次规格交付不包含业务代码实现、生产部署或发布。
## Further Notes
### 依据与交付边界
- 需求依据为当前对话及已解决的[记录流程](issues/02-recording-flow.md)、[项目组织](issues/03-practice-organization.md)、[成长回顾](issues/04-growth-review.md)、[数据保存](issues/05-record-continuity.md)、[核心交互](issues/06-core-flow-prototype.md)决策。
- [iHour 调研](issues/01-ihour-evidence.md)用于理解参考产品;用户截图中的数字与布局是使用证据,不是导入要求,也不构成训练分类或能力评估标准。
- [用户认可的交互草图](/Users/yuxuanhui/.codex/visualizations/2026/09/28/01a0e6a5-d2a3-7f51-bff3-545ef8c42df0/ballet-practice-flow.html)是本机设计参考,不直接复制为生产实现。原型使用内存和虚构样例,未接入微信或后端;正式实现不得依赖该本机路径运行。
- 原型已实际验证新增、编辑、删除、改名、归档、日历回看和周/月切换,并检查过窄屏显示;这些证据只说明流程草图可操作,不证明生产持久化、登录或跨设备恢复可用。
- 同目录的其他规划票保持原状,未纳入这轮已确认范围。本规格不宣称整张规划地图全部完成。
### 实现前置条件
- 当前仓库是小程序欢迎页与 Go 服务健康检查骨架,尚无业务数据结构、身份认证、项目/记录接口或成长页面。应把本规格作为完整业务闭环的新实现,不能假设相应接口已经存在。
- 当前项目声明 Taro 4.2.1、React 18.3.1、Taroify 1.0.6,后端使用 Go、pgx 和 PostgreSQL;实现时依照各项目约定核对实际依赖和所需官方文档。此处版本描述来自配置读取,不表示本轮运行过这些工具。
- 实现阶段需要为新业务结构建立可重复执行的数据迁移,并为测试提供隔离数据库。不得使用用户实际练习数据作破坏性验收样本。
- 微信身份交换、会话生命周期、平台所需网络配置和可用的 HTTPS 后端地址需按真实应用配置接通并核实。平台凭据不能放入小程序包,部署及凭据配置不能由“规格已完成”推定为已授权发布。
- `ready-for-agent` 表示范围和验收依据足以交给开发代理,不表示业务代码已完成、构建已通过或产品已经上线。