- Implemented a new Growth page to track practice time and trends. - Added API integration for fetching review data. - Created components for displaying practice statistics and trends. - Updated navigation titles for the main index and growth pages. - Removed unused styles from the index page. - Introduced a Projects management page for adding and editing practice projects. - Developed a Record form for logging practice sessions with validation. - Added utility functions for date manipulation and duration formatting. - Implemented error handling and session management in the practice service. - Created unit tests for the practice service to ensure reliability.
24 KiB
芭蕾岛第一期:练习记录与成长回顾
Status: ready-for-agent Created: 2026-09-28 Parent: 第一期决策地图
Problem Statement
芭蕾爱好者已经在线下上课或跟随现有内容练习,需要一个适合课后快速登记、长期回顾投入的工具。用户提供的 iHour 截图展示了按熟悉的课程或练习名称记录时长、回看累计与项目分布的实际使用方式,但通用时间管理中的项目层级、计时和激励体系并非第一期都需要。
用户希望练完后一笔记下整堂课,不必拆分把杆、中间或每个动作;偶尔另外练习,也能单独记录。课名和练习习惯会变化,因此项目需要可管理,改名或停用不能损失历史。回顾应能回答“练了多久、在哪些天练习、时间花在哪里、当时有什么收获”,而不是把时长当作技术水平。
Solution
提供微信小程序中的“记录、日历、成长”三个入口,沿用用户已认可的交互草图。
| 入口 | 第一期开通的能力 |
|---|---|
| 记录 | 从平铺项目进入课后登记;填写实际练习日期、整堂时长和选填笔记;查看近期记录;进入项目管理 |
| 日历 | 识别有练习的日期;点选日期查看当天记录;为所选日期补记,或更正已有记录 |
| 成长 | 累计时长、练习天数、周/月时长趋势、各项目时长与占比 |
首次使用预置八个可编辑项目:零基础、基础提升、软开素质、足髋训练、核心臀腿、小球核心、天鹅臂颈、呼吸训练。这些是来自用户使用习惯的便捷名称,不是芭蕾训练的标准分类。
数据关联微信身份并存于现有后端使用的数据库,同一微信身份换设备后可恢复记录。新账户从空记录开始;不导入 iHour 历史,不带入截图中的累计数字。练习目标、成就徽章和分享卡片留待后续版本。
User Stories
- 作为芭蕾爱好者,我希望打开小程序即可理解“记录、日历、成长”三个入口,以便快速找到登记和回顾的位置。
- 作为首次使用者,我希望获得八个熟悉的预置项目,以便无需先建立分类体系就能记录练习。
- 作为首次使用者,我希望历史与统计真实地从零开始,以便区分自己的投入和产品演示数据。
- 作为上课的学员,我希望练完后手动填写整堂课的时长,以便不在上课时操作计时器。
- 作为上课的学员,我希望一条记录只选择一个项目,以便无需拆分课堂环节,也不会重复计算同一段时间。
- 作为自主练习者,我希望能另外登记一次练习,以便保留课程之外的投入。
- 作为记录者,我希望常用项目平铺展示,以便直接选中课名或练习名称。
- 作为记录者,我希望日期默认今天,以便完成常见的课后登记。
- 作为漏记过练习的人,我希望能选择实际练习日期补记,以便日历和趋势反映练习发生的时间。
- 作为记录者,我希望按分钟填写时长,以便保留不足一小时的练习。
- 作为记录者,我希望选填老师反馈或个人收获,以便回顾那一次练习的具体内容。
- 作为记录者,我希望不写笔记也能保存,以便只想记时长时快速完成。
- 作为记录者,我希望无效日期、时长或缺失项目得到明确提示,以便在保存前改正输入。
- 作为记录者,我希望可以取消登记,以便未完成的输入不会变成正式记录。
- 作为记录者,我希望保存成功后回到发起登记的页面并看到结果,以便确认本次操作已经完成。
- 作为记录者,我希望保存失败时保留输入并允许重试,以便无需重新填写整堂课的信息。
- 作为记录者,我希望重复点击或重试同一次保存不会生成重复记录,以便统计不会被网络问题放大。
- 作为回顾练习的人,我希望在记录页看到近期记录,以便检查刚刚登记的信息。
- 作为误填信息的人,我希望更正记录的项目、日期、时长和笔记,以便保留准确的练习历史。
- 作为误记练习的人,我希望删除单条错误记录,以便将其从日历和统计中移除。
- 作为回顾练习的人,我希望日历标记有记录的日期,以便看清练习在时间上的分布。
- 作为回顾练习的人,我希望点选日期后看到当天的全部记录及笔记,以便回想那天练了什么。
- 作为补记练习的人,我希望从日历发起登记时沿用所选日期,以便不必重复选择日期。
- 作为更正记录的人,我希望修改后相关日期、累计与趋势一起更新,以便不同页面展示一致的事实。
- 作为有个人练习习惯的人,我希望新增项目,以便使用自己的课程或练习名称。
- 作为调整课名的人,我希望给项目改名,以便名称符合现在的用法。
- 作为回顾历史的人,我希望改名后的项目在旧记录和统计里也统一使用新名称,以便仍能识别为同一个项目。
- 作为整理项目的人,我希望删除没有使用过的项目,以便缩短日常选择列表。
- 作为停止某类练习的人,我希望将有记录的项目移出常用列表并保留历史,以便不用担心整理项目导致数据丢失。
- 作为回顾历史的人,我希望归档项目的记录、笔记和时长仍可查看和更正,以便过去的练习不会消失。
- 作为关注投入的人,我希望看到准确的累计时长,以便了解自己在芭蕾练习上投入了多少时间。
- 作为关注练习习惯的人,我希望同一天无论登记几次都只算一个练习日,以便练习天数不被记录条数放大。
- 作为回顾近期练习的人,我希望切换周和月查看时长趋势,以便了解一段时间内的练习安排。
- 作为回顾练习结构的人,我希望看到各项目时长及占比,以便了解投入分布。
- 作为需要休息的练习者,我希望没有练习的日期如实留空,以便回顾不依赖连续打卡或惩罚休息日。
- 作为使用多个设备的人,我希望同一微信身份能读取已保存的项目和记录,以便更换设备后继续使用。
- 作为记录个人笔记的人,我希望自己的数据只能由自己的身份读取和修改,以便笔记和练习历史保持私密。
- 作为遇到网络或登录问题的人,我希望界面说明当前未能加载或保存,而不把失败显示为零记录或保存成功,以便判断是否需要重试。
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
依据与交付边界
- 需求依据为当前对话及已解决的记录流程、项目组织、成长回顾、数据保存、核心交互决策。
- iHour 调研用于理解参考产品;用户截图中的数字与布局是使用证据,不是导入要求,也不构成训练分类或能力评估标准。
- 用户认可的交互草图是本机设计参考,不直接复制为生产实现。原型使用内存和虚构样例,未接入微信或后端;正式实现不得依赖该本机路径运行。
- 原型已实际验证新增、编辑、删除、改名、归档、日历回看和周/月切换,并检查过窄屏显示;这些证据只说明流程草图可操作,不证明生产持久化、登录或跨设备恢复可用。
- 同目录的其他规划票保持原状,未纳入这轮已确认范围。本规格不宣称整张规划地图全部完成。
实现前置条件
- 当前仓库是小程序欢迎页与 Go 服务健康检查骨架,尚无业务数据结构、身份认证、项目/记录接口或成长页面。应把本规格作为完整业务闭环的新实现,不能假设相应接口已经存在。
- 当前项目声明 Taro 4.2.1、React 18.3.1、Taroify 1.0.6,后端使用 Go、pgx 和 PostgreSQL;实现时依照各项目约定核对实际依赖和所需官方文档。此处版本描述来自配置读取,不表示本轮运行过这些工具。
- 实现阶段需要为新业务结构建立可重复执行的数据迁移,并为测试提供隔离数据库。不得使用用户实际练习数据作破坏性验收样本。
- 微信身份交换、会话生命周期、平台所需网络配置和可用的 HTTPS 后端地址需按真实应用配置接通并核实。平台凭据不能放入小程序包,部署及凭据配置不能由“规格已完成”推定为已授权发布。
ready-for-agent表示范围和验收依据足以交给开发代理,不表示业务代码已完成、构建已通过或产品已经上线。