From b0c62d461ecfad6c349318eec1931657c47984fd Mon Sep 17 00:00:00 2001 From: yuxuanhui Date: Tue, 29 Sep 2026 18:40:25 +0800 Subject: [PATCH] feat: add project icon picker component and integrate with project management - Implemented ProjectIconPicker component for selecting project icons. - Created ProjectIcon component to display selected icons. - Updated calendar page to remove unused PageHeading component. - Modified growth page to enhance UI and improve text clarity. - Refactored index page to include project icons and improve layout. - Added mine page for user profile management with avatar selection. - Introduced projects-sort page for managing project order with drag-and-drop functionality. - Enhanced projects page to allow icon selection when adding or editing projects. - Updated record page to streamline user experience and improve form actions. - Adjusted practice service to support project icon management. - Added tests for project saving functionality to ensure correct handling of icons. - Updated global type definitions to support SVG and PNG imports. --- .scratch/project-icons/spec.md | 30 +++ .../record-overview-and-project-order/spec.md | 19 ++ DESIGN.md | 35 +-- README.md | 7 +- .../internal/database/002_project_icons.sql | 15 ++ .../internal/database/003_project_order.sql | 2 + backend/internal/database/migrate.go | 27 ++- backend/internal/httpapi/practice.go | 21 +- backend/internal/httpapi/practice_test.go | 163 +++++++++++++- backend/internal/practice/icons.go | 12 + backend/internal/practice/review.go | 13 +- backend/internal/practice/store.go | 77 ++++++- docs/design/project-icons/arabesque.svg | 14 ++ docs/design/project-icons/attitude.svg | 14 ++ docs/design/project-icons/ball-core.svg | 18 ++ docs/design/project-icons/barre.svg | 11 + docs/design/project-icons/breathing.svg | 17 ++ docs/design/project-icons/core-glutes.svg | 15 ++ docs/design/project-icons/flexibility.svg | 14 ++ docs/design/project-icons/foot-hip.svg | 16 ++ docs/design/project-icons/grand-jete.svg | 15 ++ docs/design/project-icons/piano.svg | 15 ++ docs/design/project-icons/pirouette.svg | 16 ++ docs/design/project-icons/plie.svg | 13 ++ docs/design/project-icons/pointe-shoes.svg | 18 ++ docs/design/project-icons/port-de-bras.svg | 13 ++ docs/design/project-icons/preview.html | 209 ++++++++++++++++++ docs/design/project-icons/releve.svg | 14 ++ docs/design/project-icons/soft-shoes.svg | 16 ++ docs/design/project-icons/swan-arms.svg | 13 ++ docs/design/project-icons/tendu.svg | 12 + miniprogram/src/app.config.ts | 4 +- miniprogram/src/app.scss | 75 +++++-- .../src/assets/action-icons/add-project.svg | 4 + .../src/assets/action-icons/calendar.svg | 5 + .../src/assets/action-icons/drag-handle.svg | 5 + .../src/assets/action-icons/manage-list.svg | 6 + .../src/assets/project-icons/arabesque.png | Bin 0 -> 8153 bytes .../src/assets/project-icons/attitude.png | Bin 0 -> 10293 bytes .../src/assets/project-icons/ball-core.png | Bin 0 -> 12561 bytes .../src/assets/project-icons/barre.png | Bin 0 -> 4416 bytes .../src/assets/project-icons/breathing.png | Bin 0 -> 10912 bytes .../src/assets/project-icons/core-glutes.png | Bin 0 -> 8425 bytes .../src/assets/project-icons/flexibility.png | Bin 0 -> 9686 bytes .../src/assets/project-icons/foot-hip.png | Bin 0 -> 9927 bytes .../src/assets/project-icons/grand-jete.png | Bin 0 -> 9169 bytes miniprogram/src/assets/project-icons/index.ts | 45 ++++ .../src/assets/project-icons/piano.png | Bin 0 -> 5513 bytes .../src/assets/project-icons/pirouette.png | Bin 0 -> 10391 bytes miniprogram/src/assets/project-icons/plie.png | Bin 0 -> 10440 bytes .../src/assets/project-icons/pointe-shoes.png | Bin 0 -> 12110 bytes .../src/assets/project-icons/port-de-bras.png | Bin 0 -> 8770 bytes .../src/assets/project-icons/releve.png | Bin 0 -> 9394 bytes .../src/assets/project-icons/soft-shoes.png | Bin 0 -> 11144 bytes .../src/assets/project-icons/swan-arms.png | Bin 0 -> 10043 bytes .../src/assets/project-icons/tendu.png | Bin 0 -> 9644 bytes miniprogram/src/components/common.tsx | 18 +- .../src/components/project-icon-picker.tsx | 51 +++++ miniprogram/src/components/project-icon.tsx | 8 + miniprogram/src/pages/calendar/index.tsx | 5 +- miniprogram/src/pages/growth/index.tsx | 10 +- miniprogram/src/pages/index/index.tsx | 42 ++-- miniprogram/src/pages/mine/index.config.ts | 1 + miniprogram/src/pages/mine/index.tsx | 62 ++++++ .../src/pages/projects-sort/index.config.ts | 1 + miniprogram/src/pages/projects-sort/index.tsx | 107 +++++++++ miniprogram/src/pages/projects/index.tsx | 37 ++-- miniprogram/src/pages/record/index.tsx | 15 +- miniprogram/src/services/practice.ts | 7 +- miniprogram/tests/practice.test.cjs | 19 ++ miniprogram/types/global.d.ts | 9 + 71 files changed, 1305 insertions(+), 125 deletions(-) create mode 100644 .scratch/project-icons/spec.md create mode 100644 .scratch/record-overview-and-project-order/spec.md create mode 100644 backend/internal/database/002_project_icons.sql create mode 100644 backend/internal/database/003_project_order.sql create mode 100644 backend/internal/practice/icons.go create mode 100644 docs/design/project-icons/arabesque.svg create mode 100644 docs/design/project-icons/attitude.svg create mode 100644 docs/design/project-icons/ball-core.svg create mode 100644 docs/design/project-icons/barre.svg create mode 100644 docs/design/project-icons/breathing.svg create mode 100644 docs/design/project-icons/core-glutes.svg create mode 100644 docs/design/project-icons/flexibility.svg create mode 100644 docs/design/project-icons/foot-hip.svg create mode 100644 docs/design/project-icons/grand-jete.svg create mode 100644 docs/design/project-icons/piano.svg create mode 100644 docs/design/project-icons/pirouette.svg create mode 100644 docs/design/project-icons/plie.svg create mode 100644 docs/design/project-icons/pointe-shoes.svg create mode 100644 docs/design/project-icons/port-de-bras.svg create mode 100644 docs/design/project-icons/preview.html create mode 100644 docs/design/project-icons/releve.svg create mode 100644 docs/design/project-icons/soft-shoes.svg create mode 100644 docs/design/project-icons/swan-arms.svg create mode 100644 docs/design/project-icons/tendu.svg create mode 100644 miniprogram/src/assets/action-icons/add-project.svg create mode 100644 miniprogram/src/assets/action-icons/calendar.svg create mode 100644 miniprogram/src/assets/action-icons/drag-handle.svg create mode 100644 miniprogram/src/assets/action-icons/manage-list.svg create mode 100644 miniprogram/src/assets/project-icons/arabesque.png create mode 100644 miniprogram/src/assets/project-icons/attitude.png create mode 100644 miniprogram/src/assets/project-icons/ball-core.png create mode 100644 miniprogram/src/assets/project-icons/barre.png create mode 100644 miniprogram/src/assets/project-icons/breathing.png create mode 100644 miniprogram/src/assets/project-icons/core-glutes.png create mode 100644 miniprogram/src/assets/project-icons/flexibility.png create mode 100644 miniprogram/src/assets/project-icons/foot-hip.png create mode 100644 miniprogram/src/assets/project-icons/grand-jete.png create mode 100644 miniprogram/src/assets/project-icons/index.ts create mode 100644 miniprogram/src/assets/project-icons/piano.png create mode 100644 miniprogram/src/assets/project-icons/pirouette.png create mode 100644 miniprogram/src/assets/project-icons/plie.png create mode 100644 miniprogram/src/assets/project-icons/pointe-shoes.png create mode 100644 miniprogram/src/assets/project-icons/port-de-bras.png create mode 100644 miniprogram/src/assets/project-icons/releve.png create mode 100644 miniprogram/src/assets/project-icons/soft-shoes.png create mode 100644 miniprogram/src/assets/project-icons/swan-arms.png create mode 100644 miniprogram/src/assets/project-icons/tendu.png create mode 100644 miniprogram/src/components/project-icon-picker.tsx create mode 100644 miniprogram/src/components/project-icon.tsx create mode 100644 miniprogram/src/pages/mine/index.config.ts create mode 100644 miniprogram/src/pages/mine/index.tsx create mode 100644 miniprogram/src/pages/projects-sort/index.config.ts create mode 100644 miniprogram/src/pages/projects-sort/index.tsx diff --git a/.scratch/project-icons/spec.md b/.scratch/project-icons/spec.md new file mode 100644 index 0000000..b4e6326 --- /dev/null +++ b/.scratch/project-icons/spec.md @@ -0,0 +1,30 @@ +# 项目图标接入 + +Type: task +Status: resolved + +## Scope + +将已确认的 18 个项目图标接入小程序。首页和项目管理保留紧凑单列卡片;添加、编辑项目时从专业动作、基础练习、物件三类选择,也可不使用图标。图标随项目保存到现有 PostgreSQL,沿用身份隔离、归档和刷新规则。 + +## Implementation + +- 保留 `docs/design/project-icons/` 下的 SVG 原稿,小程序使用同图案的透明 PNG,随包提供,不依赖外部图片服务。 +- 新增可选请求字段 `iconId`;空字符串取消,旧客户端省略时保留已有选择。服务端仅接受已提供的 18 个标识。 +- 新增版本迁移;为已有同名预置项目一次性配置对应图标,其他项目保持无图标。重新登录不重置用户选择。 +- 项目管理的“改名”改为“编辑”,名称与图标一次保存。选择面板关闭不提交;保存失败保留表单。 +- 不更改记录统计、身份方式或发布配置。 + +## Validation + +验证新增、改名保留图标、更换、取消、未知标识拒绝、跨账号隔离、重新读取及旧数据库迁移;执行小程序 typecheck/client/build 与后端 vet/race/build、隔离 PostgreSQL 集成测试。平台运行与静态构建分别记录。 + +## Result + +2026-09-29 完成本地接入,未发布或提交。 + +- `pnpm typecheck`、`pnpm test:client`(4 项)、`pnpm build` 通过。18 张 384 × 384 透明 PNG 共约 167 KiB,均进入小程序构建产物,保留 SVG 原稿。 +- `go vet ./...`、`./scripts/test-integration.sh`(真实隔离 PostgreSQL 下执行 `go test -race ./...`)、`go build -o bin/api ./cmd/server` 通过。新增测试覆盖全部 18 个标识、旧客户端省略字段、取消、归档、未知值拒绝、用户隔离、服务重建读取、迁移重复执行和旧结构升级后历史保留。 +- 微信开发者工具连接本机真实后端,7 项交互验证通过:默认图标与紧凑卡片、6/8/4 分类切换和试选、新增并持久化、重进首页恢复、更换、取消编辑不写入、取消图标后无占位。未捕获未处理异常;只新增和清理本次唯一标记的临时项目,未变更既有项目选择或练习记录。 +- 本机后端已更新并执行迁移;默认项目图标正常读取。只读代理 `/root/project_icon_review` 已完成,未发现具体可行动缺陷。`git diff --check` 通过。 +- 已目视检查首页、项目管理与选择面板的模拟器截图。真机、其他屏幕宽度和原生键盘交互未在本轮验证。 diff --git a/.scratch/record-overview-and-project-order/spec.md b/.scratch/record-overview-and-project-order/spec.md new file mode 100644 index 0000000..64f2b4a --- /dev/null +++ b/.scratch/record-overview-and-project-order/spec.md @@ -0,0 +1,19 @@ +# 记录总览与项目排序 + +Type: task +Status: resolved + +## Scope + +记录首页展示总记录时长、坚持天数和最早记录日期;项目右侧展示累计小时数。项目操作区提供纯 SVG 图标的管理列表与添加项目入口,管理列表可拖动排序并保存。 + +## Implementation + +- 总览的两个主数字按同一基线区域排版,日期仅在有记录时显示为“从 YYYY 年 MM 月 DD 日”。 +- 项目累计分钟数换算为小时,项目时长数字加大。操作区只显示 28px 图标,保留无障碍名称。 +- 新增独立排序页;拖动右侧手柄调整本地顺序,点击保存后调用 `PUT /v1/projects/order`。 +- 服务端只接受当前用户完整的活跃项目 ID 列表,沿用既有位置槽位,保留归档项目和新增项目的排列语义。 + +## Validation + +2026-09-29,本地代码已完成,未提交或发布。小程序 `pnpm typecheck`、`pnpm test:client`、`pnpm build` 通过;后端 `go vet ./...`、`go test -race ./...`、`go build -o bin/api ./cmd/server` 和隔离 PostgreSQL 集成测试通过。服务端测试覆盖顺序持久化、后续新增、无效或跨用户 ID、归档后重排。微信开发者工具中的实际拖动与图标显示未在本轮完成交互核验。 diff --git a/DESIGN.md b/DESIGN.md index b6fed83..5b5a36e 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -31,7 +31,7 @@ | 主文字 | `#3e3435` | 正文、名称和标题,承担主要阅读信息 | | 次要文字 | `#827572` | 日期、解释与辅助说明,仍需清晰可读 | | 主强调色(Primary) | `#9c5268` | 主要操作、选中状态、记录时长和图表聚焦项 | -| 品牌小字 | `#946d76` | 低调的品牌标记,不替代页面标题 | +| 汇总标签 | `#946d76` | 标识今天或累计练习,不替代汇总数字 | | 汇总表面 / 描边 / 数字 | `#f1e7e3` / `#eddfda` / `#864b5e` | 承载今天或累计练习的核心汇总,与普通记录形成层级 | | 输入表面 / 描边 / 文字 | `#f8f4ef` / `#e8dfd8` / `#44383b` | 将可填写区域与外层面板区分 | | 普通按钮文字 / 描边 | `#655656` / `#e5dcd6` | 辅助操作,背景沿用内容表面色 | @@ -61,7 +61,7 @@ | 角色 | 字号 / 字重 | 使用规则 | | --- | --- | --- | -| 页面标题 | `26px / 600` | 表达当前页面目的;行高 `1.35`,字距 `-0.5px` | +| 页面标题 | 沿用原生导航栏 | 内容区域不重复放置页面大标题 | | 核心汇总数字 | 时长 `29px / 600`,统计 `30px / 600` | 保留已有两种汇总组件;时长行高 `1.5` | | 区块标题与项目入口名称 | `17px / 600` | 组织内容或标识可进入的项目 | | 正文与记录名称 | `15px`,记录名称字重 `500` | 支持较长名称和实际内容 | @@ -69,15 +69,15 @@ | 表单标签 | `14px / 500` | 清楚表达字段含义 | | 说明、记录时长与笔记 | `13px` | 作为局部辅助层级,不与汇总数字竞争 | | 紧凑辅助信息与单位 | `12px` | 日期、单位、帮助文字等 | -| 空状态标题 | `16px / 500` | 配合 `13px` 说明文字,说明行高 `1.8` | +| 空状态标题 | `16px / 500` | 简短说明当前无数据;可执行的下一步使用明确按钮 | 长项目名称、笔记和汇总时长允许换行,保留笔记的原始换行,不用省略号隐藏主要信息。数字必须带上可理解的单位,例如「1 小时 30 分钟」「2 次」。 -现状中品牌标记、项目编号及部分辅助文字使用 `10–11px`,趋势日期标签使用周视图 `10px`、月视图 `8px`。这些局部值不作为新正文或操作标签的默认字号;小字号和图表标签的可读性仍需在目标设备验证。 +现状中汇总标签及部分辅助文字使用 `10–11px`,趋势日期标签使用周视图 `10px`、月视图 `8px`。这些局部值不作为新正文或操作标签的默认字号;小字号和图表标签的可读性仍需在目标设备验证。 ## Layout -页面以纵向单栏组织一个主要任务;两列用于并列的项目入口或简短指标,七列仅用于日历。新增页面按「页面目的 → 主要内容或输入 → 相关操作与反馈」建立阅读顺序,避免为了填满画面增加区块。 +页面以纵向单栏组织一个主要任务;项目入口和管理列表均为一行一张卡片,两列仅用于简短指标,七列仅用于日历。新增页面按「主要内容或输入 → 相关操作与反馈」建立阅读顺序,避免为了填满画面增加区块。 尺寸均为 `375px` 设计宽度下的源码设计尺寸,由现有构建配置转换为小程序单位,不等同于设备截图的物理像素。页面基准内边距为上 `20px`、左右 `18px`、下 `32px` 加底部安全区。 @@ -94,11 +94,11 @@ ### 导航与内容组织 -页面标题区使用品牌小字、主标题和简短副标题。管理、补记等局部入口靠近所属区块,主要提交操作放在完整输入之后。 +页面用途由原生导航栏说明,内容直接从数据或字段开始,不放页面大标题、品牌小字、抒情标题或副标题。管理、补记等局部入口靠近所属区块,表单主操作统一固定在底部。 当前一级导航为「记录、日历、成长」,使用文字 TabBar;练习记录和项目管理为二级页面。延续这一层级,新页面不自行增加一套全局导航。 -快捷按钮允许换行,长内容允许撑高,底部内容保留滚动与安全区。表单操作跟随内容滚动;键盘出现时的可达性属于验收事项,不能仅凭静态布局认定已适配。 +快捷按钮允许换行,长内容允许撑高。编辑页面底部固定一行两个等宽按钮,左侧「取消」、右侧「保存」,间距 `12px`。操作栏下方保留安全区,页面底部预留 `100px` 加安全区,确保末尾内容可以滚动到操作栏上方。键盘出现时的可达性属于验收事项,不能仅凭静态布局认定已适配。 ## Elevation & Depth @@ -114,10 +114,11 @@ | --- | --- | --- | --- | | 通用面板 | `18px` | `18px` | 表单或成组内容的默认容器 | | 核心汇总 | `21px` | 上下 `23px`、左右 `22px` | 承载少量重点数字,留白比普通内容更充裕 | -| 项目入口 | `17px` | 上下 `15px`、左右 `16px` | 两列排列,最小高度 `126px`,支持名称换行 | +| 项目入口 | `17px` | 上下 `10px`、左右 `16px` | 一行一张,名称前可放 `32px` 项目图标;高度由内容决定,支持名称换行 | +| 项目管理条目 | `17px` | 上下 `8px`、左右 `16px` | 名称与操作按钮同排;高度由内容决定 | | 练习记录 | `16px` | 上下 `16px`、左右 `18px` | 可连续浏览的独立记录 | | 日历面板 | `18px` | 上下 `16px`、左右 `12px` | 沿用面板外形,为七列日期留出横向空间 | -| 输入区域 | `11px` | 上下 `12px`、左右 `14px` | 位于面板内,明确字段边界 | +| 输入区域 | `11px` | 左右 `14px`;单行输入上下 `0`,选择区域与笔记上下 `12px` | 单行原生输入依靠高度定位文字,避免垂直内边距使文字下移 | | 状态提示条 | `12px` | 上下 `13px`、左右 `15px` | 紧贴相关操作提供短反馈 | 按钮圆角为 `12px`,日期选中区域为 `13px`。这些差异是现有组件的基准,不构成一套可按大小任意套用的全局圆角等级。需要新容器时,按用途复用上表,避免再增加近似数值。 @@ -126,9 +127,9 @@ ### 操作按钮 -主要提交使用灰玫瑰实心按钮;快捷选项与重试使用描边按钮;管理、取消、更正等辅助入口使用文字按钮。危险操作使用危险文字样式,并在确认时说明后果。 +主要提交使用灰玫瑰实心按钮;底部「取消」与快捷选项使用描边按钮;管理、更正等辅助入口使用文字按钮。危险操作使用危险文字样式,并在确认时说明后果。 -表单提交沿用中号按钮,高度 `44px`、字号 `16px`;行内操作和切换沿用小号按钮,高度 `32px`、字号 `13px`。需要独占一行的主要提交按钮填满容器宽度。提交中显示加载状态并阻止重复操作,不能只靠按钮文字变化表达等待。 +表单底部两个按钮沿用中号,高度 `44px`、字号 `16px`,各填满所在的半行;行内操作和切换沿用小号按钮,高度 `32px`、字号 `13px`。提交中显示加载状态并阻止重复操作,不能只靠按钮文字变化表达等待。 复用现有按钮组件及主题;按下、禁用和加载的具体表现沿用组件行为,不在页面上另造一套状态样式。 @@ -136,13 +137,13 @@ 字段采用「上方标签 + 下方完整输入区域」,占位文字仅补充示例或提示。帮助文字靠近字段,字数计数置于笔记区下方右侧。 -普通输入高度 `48px`;选择区域最小高度 `48px`,允许长内容撑高;笔记区沿用 `140px` 高度。标签与字段保持清楚关联,字段组之间留出更大间距。主要提交与取消放在表单内容之后,删除入口与保存操作分开。 +普通输入高度 `48px`;选择区域最小高度 `48px`,允许长内容撑高;笔记区沿用 `140px` 高度。标签与字段保持清楚关联,字段组之间留出更大间距。主要提交与取消使用固定底部操作栏,删除入口留在内容区,与保存操作分开。 ### 卡片与列表 -项目卡片以名称为主体,编号和操作提示保持次要。记录卡片先展示项目名与时长,再展示日期、归档标记与更正入口;笔记置于下方,以细线和留白分隔。长名称优先获得可换行空间。 +项目入口卡片展示名称与用户选择的图标,不放编号或重复的操作提示,不设用于装饰留白的最小高度。图标采用灰玫瑰线描和浅色填充,以 `32px` 方形置于名称前;未选择或无法识别图标时不占位。记录卡片先展示项目名与时长,再展示日期、归档标记与更正入口;笔记置于下方,以细线和留白分隔。长名称优先获得可换行空间。 -连续的管理条目放在同一个面板内,用细分隔线区分行;每项的改名、移除入口靠近项目名称与状态。归档状态用文字标注,不仅通过弱化颜色表达。 +管理条目每项独立成卡,以间距区分;图标、名称、编辑和移除按钮放在同一行,不附加重复的历史记录说明。编辑表单中可选择或取消图标,底部选择面板按「基础练习、专业动作、物件」分类;名称与图标一并保存。移除的归档或删除后果在确认弹窗中说明。历史记录中的归档状态仍用文字标注。 ### 日历与图表 @@ -158,13 +159,13 @@ | --- | --- | | 加载中 | 在内容区域说明正在读取,避免暂时展示成空记录 | | 读取失败 | 说明失败并提供重试入口,与无数据状态区分 | -| 没有数据 | 简短标题与解释;有可执行的下一步时提供明确入口 | +| 没有数据 | 简短说明无数据;有可执行的下一步时提供明确入口 | | 表单错误 | 保留输入,以错误提示解释需要处理的问题 | | 操作成功 | 结果确认后再显示成功提示;反馈靠近操作或在返回后呈现 | | 保存结果未确认 | 保留提交内容,明确说明尚未确认,并提供重试确认入口 | | 删除或移除 | 确认前说明后果,明确区分删除记录与归档项目 | -文案使用具体动作,例如「保存练习」「补记练习」「取消并返回」。空状态可以写「休息也是日常。漏记了一堂课,也可以补在这天。」时长说明保留「时长记录的是投入,不是技术水平」的含义。 +按钮文案使用简短动作,例如「保存」「取消」「删除」。保存结果未确认时,主按钮使用「重试」,提示区说明需要确认的结果,并禁用取消以保留提交保护。空状态使用「暂无记录」等直接描述;不添加鼓励性口号、抒情说明或重复引导。保留字段限制、图表含义、错误反馈及危险操作后果等必要说明。 ## Do's and Don'ts @@ -186,6 +187,6 @@ - [全局样式](miniprogram/src/app.scss):实际颜色、字号、间距与按钮主题映射。 - [构建配置](miniprogram/config/index.ts):设计宽度与尺寸转换配置。 - [导航配置](miniprogram/src/app.config.ts):原生导航与底部导航。 -- [公共组件](miniprogram/src/components/common.tsx):页面标题、加载与空状态、记录卡片及按钮复用入口。 +- [公共组件](miniprogram/src/components/common.tsx):底部表单操作栏、加载与空状态、记录卡片及按钮复用入口。 新增页面优先遵循本文件的使用规则并复用现有组件。发现文档与实现不一致时,明确差异及其原因,不直接将偶然实现提升为通用规则;有意调整视觉语言时,同步更新相关规则和对应样式。 diff --git a/README.md b/README.md index 02f49de..110f20d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ pnpm install --frozen-lockfile pnpm dev ``` -微信开发者工具导入 `miniprogram/`,AppID 为 `wx6dfbf1021db8aee0`,编译目录为 `dist/`。底部入口为“记录、日历、成长”。首次请求通过 `Taro.login` 取得 code,在后端核验后建立业务会话;登录失败或网络失败会显示重试入口,不会当作空记录。 +微信开发者工具导入 `miniprogram/`,AppID 为 `wx6dfbf1021db8aee0`,编译目录为 `dist/`。底部入口为“记录、成长、我的”,日历从记录页进入。“我的”可由用户选择微信头像和昵称,仅保存在本机。首次请求通过 `Taro.login` 取得 code,在后端核验后建立业务会话;登录失败或网络失败会显示重试入口,不会当作空记录。 默认 API 为 `http://127.0.0.1:8080`。使用本地 HTTP 地址时,在开发者工具的本地设置中开启“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,仅用于本地调试。真机需使用能够访问的接口地址,并按微信要求配置域名。 @@ -56,6 +56,8 @@ TARO_APP_API_BASE_URL=https://api.example.com pnpm build 项目名最多 40 个 Unicode 字符,笔记最多 2000 个字符;前后端使用相同校验。分钟为正整数,按 PostgreSQL integer 存储范围上限为 2147483647。笔记只作为普通文本显示。每次主动新增生成独立提交标识;结果未确认时保留该标识与完整输入,重试不重复累计。首期不提供离线保存队列。 +项目可选 18 个内置图标(基础练习 6、专业动作 8、物件 4),也可选择无图标。添加或编辑时与名称一并保存,首页和管理页展示。SVG 原稿位于 `docs/design/project-icons/`,小程序包内使用对应的透明 PNG。后端迁移 002 为已有同名预置项目一次性配图;之后改名、重新登录和服务重启均不重置用户选择。更新小程序前先更新后端,使接口支持 `iconId`。 + ## 后端开发 需要 Go 1.26 或更高版本,以及 Docker Compose。仅使用 Docker 运行后端时无需本机 Go。 @@ -134,7 +136,8 @@ go build -o bin/api ./cmd/server | `GET /readyz` | 实际检查 PostgreSQL 连接 | 可用时 200,不可用时 `503 {"status":"unavailable"}` | | `POST /v1/session` | 微信 code 换业务会话 | `{token, expiresAt}` | | `GET /v1/projects` | 活跃项目;`includeArchived=true` 包含归档 | `{projects, today}` | -| `POST /v1/projects` / `PUT /v1/projects/{id}` | 新增 / 改名,JSON `{name}` | 最新项目 | +| `PUT /v1/projects/order` | 按完整活跃项目 ID 列表保存显示顺序,JSON `{projectIds: string[]}` | `{action: "reordered"}` | +| `POST /v1/projects` / `PUT /v1/projects/{id}` | 新增 / 编辑,JSON `{name, iconId?}`;空字符串取消图标,省略保留现有图标 | 最新项目(含 `iconId`) | | `DELETE /v1/projects/{id}` | 删除未使用项目或归档 | `{action: "deleted" / "archived"}` | | `GET /v1/records` | `from` / `to` 日期范围、`limit`(1–100,默认20)、`cursor` | `{records, nextCursor}` | | `GET /v1/records/{id}` | 读取本人单条记录 | 项目当前名称、日期、分钟、笔记 | diff --git a/backend/internal/database/002_project_icons.sql b/backend/internal/database/002_project_icons.sql new file mode 100644 index 0000000..432d8b5 --- /dev/null +++ b/backend/internal/database/002_project_icons.sql @@ -0,0 +1,15 @@ +ALTER TABLE projects ADD COLUMN icon_id text NOT NULL DEFAULT '' CHECK (char_length(icon_id) <= 64); + +-- Existing installations have never offered icon selection. Seed familiar names +-- once; subsequent migrations/logins must preserve explicit changes or removal. +UPDATE projects SET icon_id = CASE name + WHEN '零基础' THEN 'plie' + WHEN '基础提升' THEN 'tendu' + WHEN '软开素质' THEN 'flexibility' + WHEN '足髋训练' THEN 'foot-hip' + WHEN '核心臀腿' THEN 'core-glutes' + WHEN '小球核心' THEN 'ball-core' + WHEN '天鹅臂颈' THEN 'swan-arms' + WHEN '呼吸训练' THEN 'breathing' + ELSE '' +END; diff --git a/backend/internal/database/003_project_order.sql b/backend/internal/database/003_project_order.sql new file mode 100644 index 0000000..8dfa844 --- /dev/null +++ b/backend/internal/database/003_project_order.sql @@ -0,0 +1,2 @@ +-- Keep identity allocation for new projects while allowing owned projects to swap positions. +ALTER TABLE projects ALTER COLUMN position SET GENERATED BY DEFAULT; diff --git a/backend/internal/database/migrate.go b/backend/internal/database/migrate.go index da651ee..3e2f13b 100644 --- a/backend/internal/database/migrate.go +++ b/backend/internal/database/migrate.go @@ -11,6 +11,12 @@ import ( //go:embed 001_practice.sql var practiceSQL string +//go:embed 002_project_icons.sql +var projectIconsSQL string + +//go:embed 003_project_order.sql +var projectOrderSQL string + // Migrate applies versioned migrations atomically. A transaction-scoped lock keeps // concurrent service starts from applying the same version twice. func Migrate(ctx context.Context, pool *pgxpool.Pool) error { @@ -25,17 +31,20 @@ func Migrate(ctx context.Context, pool *pgxpool.Pool) error { if _, err = tx.Exec(ctx, "CREATE TABLE IF NOT EXISTS schema_migrations (version integer PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())"); err != nil { return err } - var applied bool - if err = tx.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version=1)").Scan(&applied); err != nil { - return err - } - if !applied { - if _, err = tx.Exec(ctx, practiceSQL); err != nil { - return fmt.Errorf("migration 1: %w", err) - } - if _, err = tx.Exec(ctx, "INSERT INTO schema_migrations(version) VALUES (1)"); err != nil { + for i, sql := range []string{practiceSQL, projectIconsSQL, projectOrderSQL} { + version := i + 1 + var applied bool + if err = tx.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version=$1)", version).Scan(&applied); err != nil { return err } + if !applied { + if _, err = tx.Exec(ctx, sql); err != nil { + return fmt.Errorf("migration %d: %w", version, err) + } + if _, err = tx.Exec(ctx, "INSERT INTO schema_migrations(version) VALUES ($1)", version); err != nil { + return err + } + } } return tx.Commit(ctx) } diff --git a/backend/internal/httpapi/practice.go b/backend/internal/httpapi/practice.go index 267306c..9649848 100644 --- a/backend/internal/httpapi/practice.go +++ b/backend/internal/httpapi/practice.go @@ -36,6 +36,7 @@ func NewAppHandler(pool *pgxpool.Pool, verifier IdentityVerifier, now func() tim mux.HandleFunc("POST /v1/session", a.login) mux.HandleFunc("GET /v1/projects", a.auth(a.projects)) mux.HandleFunc("POST /v1/projects", a.auth(a.saveProject)) + mux.HandleFunc("PUT /v1/projects/order", a.auth(a.reorderProjects)) mux.HandleFunc("PUT /v1/projects/{id}", a.auth(a.saveProject)) mux.HandleFunc("DELETE /v1/projects/{id}", a.auth(a.removeProject)) mux.HandleFunc("GET /v1/records", a.auth(a.records)) @@ -139,14 +140,28 @@ func (a *app) projects(w http.ResponseWriter, r *http.Request, user string) erro } return err } -func (a *app) saveProject(w http.ResponseWriter, r *http.Request, user string) error { +func (a *app) reorderProjects(w http.ResponseWriter, r *http.Request, user string) error { var input struct { - Name string `json:"name"` + ProjectIDs []string `json:"projectIds"` } if err := decode(w, r, &input); err != nil { return err } - p, err := a.store.SaveProject(r.Context(), user, r.PathValue("id"), input.Name) + if err := a.store.ReorderProjects(r.Context(), user, input.ProjectIDs); err != nil { + return err + } + respond(w, 200, map[string]string{"action": "reordered"}) + return nil +} +func (a *app) saveProject(w http.ResponseWriter, r *http.Request, user string) error { + var input struct { + Name string `json:"name"` + IconID *string `json:"iconId"` + } + if err := decode(w, r, &input); err != nil { + return err + } + p, err := a.store.SaveProject(r.Context(), user, r.PathValue("id"), input.Name, input.IconID) if err == nil { respond(w, 200, p) } diff --git a/backend/internal/httpapi/practice_test.go b/backend/internal/httpapi/practice_test.go index f14f326..d4fe9cd 100644 --- a/backend/internal/httpapi/practice_test.go +++ b/backend/internal/httpapi/practice_test.go @@ -160,6 +160,158 @@ func (h *harness) project(token, name string) string { return h.request("POST", "/v1/projects", token, string(body), 200)["id"].(string) } +func TestProjectOrderPersistsAndRejectsStaleOrForeignLists(t *testing.T) { + h := setup(t) + alice, bob := h.login("alice"), h.login("bob") + projectIDs := func(token string) []string { + h.t.Helper() + projects := h.request("GET", "/v1/projects", token, "", 200)["projects"].([]any) + ids := make([]string, len(projects)) + for i, raw := range projects { + ids[i] = raw.(map[string]any)["id"].(string) + } + return ids + } + setOrder := func(ids []string, status int) { + h.t.Helper() + body, _ := json.Marshal(map[string][]string{"projectIds": ids}) + h.request("PUT", "/v1/projects/order", alice, string(body), status) + } + initial := projectIDs(alice) + want := append([]string(nil), initial...) + for i, j := 0, len(want)-1; i < j; i, j = i+1, j-1 { + want[i], want[j] = want[j], want[i] + } + setOrder(want, 200) + h.h = httpapi.NewAppHandler(h.pool, testIdentity{}, func() time.Time { return h.now }) + if got := projectIDs(h.login("alice")); strings.Join(got, ",") != strings.Join(want, ",") { + t.Fatalf("order did not persist: %v", got) + } + newID := h.project(alice, "新项目") + want = append(want, newID) + if got := projectIDs(alice); strings.Join(got, ",") != strings.Join(want, ",") { + t.Fatalf("new project did not append: %v", got) + } + duplicate := append([]string(nil), want...) + duplicate[0] = duplicate[1] + setOrder(duplicate, 400) + setOrder(want[:len(want)-1], 400) + foreign := append([]string(nil), want...) + foreign[0] = projectIDs(bob)[0] + setOrder(foreign, 400) + if got := projectIDs(alice); strings.Join(got, ",") != strings.Join(want, ",") { + t.Fatalf("invalid order changed projects: %v", got) + } + h.request("POST", "/v1/records", alice, recordBody(want[0], "2026-09-28", 15, "", "sort-archive-0001"), 200) + h.request("DELETE", "/v1/projects/"+want[0], alice, "", 200) + setOrder(want, 400) + active := projectIDs(alice) + if strings.Join(active, ",") != strings.Join(want[1:], ",") { + t.Fatalf("archived project stayed active: %v", active) + } + for i, j := 0, len(active)-1; i < j; i, j = i+1, j-1 { + active[i], active[j] = active[j], active[i] + } + setOrder(active, 200) + if got := projectIDs(alice); strings.Join(got, ",") != strings.Join(active, ",") { + t.Fatalf("reorder after archive failed: %v", got) + } +} + +func TestProjectIconsPersistClearAndRespectOwnership(t *testing.T) { + h := setup(t) + alice, bob := h.login("alice"), h.login("bob") + presets := h.request("GET", "/v1/projects", alice, "", 200)["projects"].([]any) + want := []string{"plie", "tendu", "flexibility", "foot-hip", "core-glutes", "ball-core", "swan-arms", "breathing"} + for i, raw := range presets { + if raw.(map[string]any)["iconId"] != want[i] { + t.Fatalf("preset %d: %v", i, raw) + } + } + p := h.request("POST", "/v1/projects", alice, `{"name":"自选练习","iconId":"ball-core"}`, 200) + id := p["id"].(string) + if p["iconId"] != "ball-core" { + t.Fatal(p) + } + // Older clients rename without sending iconId. This must not clear artwork. + p = h.request("PUT", "/v1/projects/"+id, alice, `{"name":"晚间练习"}`, 200) + if p["iconId"] != "ball-core" { + t.Fatal("rename cleared the icon") + } + for _, icon := range []string{"plie", "tendu", "releve", "port-de-bras", "arabesque", "attitude", "pirouette", "grand-jete", "foot-hip", "flexibility", "ball-core", "swan-arms", "breathing", "core-glutes", "soft-shoes", "pointe-shoes", "barre", "piano"} { + p = h.request("PUT", "/v1/projects/"+id, alice, `{"name":"晚间练习","iconId":"`+icon+`"}`, 200) + if p["iconId"] != icon { + t.Fatalf("icon did not round trip: %s", icon) + } + } + h.request("PUT", "/v1/projects/"+id, bob, `{"name":"偷改","iconId":"breathing"}`, 404) + h.request("PUT", "/v1/projects/"+id, alice, `{"name":"不应保存","iconId":"unknown"}`, 400) + h.request("POST", "/v1/projects", alice, `{"name":"拒绝外部图片","iconId":"https://example.com/a.svg"}`, 400) + h.request("POST", "/v1/projects", alice, `{"name":"拒绝对象","iconId":{}}`, 400) + // A fresh handler and login must read the last confirmed value from the database. + h.h = httpapi.NewAppHandler(h.pool, testIdentity{}, func() time.Time { return h.now }) + find := func(token string) map[string]any { + for _, raw := range h.request("GET", "/v1/projects?includeArchived=true", token, "", 200)["projects"].([]any) { + project := raw.(map[string]any) + if project["id"] == id { + return project + } + } + t.Fatal("saved project missing") + return nil + } + p = find(h.login("alice")) + if p["name"] != "晚间练习" || p["iconId"] != "piano" { + t.Fatal("rejected write changed persisted project", p) + } + h.request("POST", "/v1/records", alice, recordBody(id, "2026-09-28", 15, "", "icon-archive-0001"), 200) + h.request("DELETE", "/v1/projects/"+id, alice, "", 200) + if p = find(alice); p["archived"] != true || p["iconId"] != "piano" { + t.Fatal("archiving lost artwork", p) + } + // Clearing a preset stays cleared after login and a service migration restart. + presetID := presets[0].(map[string]any)["id"].(string) + p = h.request("PUT", "/v1/projects/"+presetID, alice, `{"name":"零基础","iconId":""}`, 200) + if p["iconId"] != "" { + t.Fatal("explicit clear failed", p) + } + if err := database.Migrate(context.Background(), h.pool); err != nil { + t.Fatal(err) + } + projects := h.request("GET", "/v1/projects", h.login("alice"), "", 200)["projects"].([]any) + if projects[0].(map[string]any)["iconId"] != "" { + t.Fatal("restart restored a cleared icon") + } +} + +func TestProjectIconMigrationPreservesExistingProjectsAndRecords(t *testing.T) { + h := setup(t) + token := h.login("alice") + id := h.project(token, "自定义旧项目") + h.request("POST", "/v1/records", token, recordBody(id, "2026-09-28", 30, "已有笔记", "icon-migrate-001"), 200) + // Recreate the version-1 shape only inside this test's isolated schema. + ctx := context.Background() + if _, err := h.pool.Exec(ctx, "ALTER TABLE projects DROP COLUMN icon_id; DELETE FROM schema_migrations WHERE version=2"); err != nil { + t.Fatal(err) + } + if err := database.Migrate(ctx, h.pool); err != nil { + t.Fatal(err) + } + projects := h.request("GET", "/v1/projects", token, "", 200)["projects"].([]any) + if len(projects) != 9 || projects[0].(map[string]any)["iconId"] != "plie" { + t.Fatal("existing presets were not migrated", projects) + } + custom := projects[8].(map[string]any) + if custom["id"] != id || custom["iconId"] != "" || custom["name"] != "自定义旧项目" { + t.Fatal("migration changed custom project", custom) + } + records := h.request("GET", "/v1/records", token, "", 200)["records"].([]any) + if len(records) != 1 || records[0].(map[string]any)["note"] != "已有笔记" { + t.Fatal("migration changed existing history", records) + } + h.summary(token, 30, 1) +} + func recordBody(project, date string, minutes int, note, key string) string { body, _ := json.Marshal(map[string]any{"projectId": project, "date": date, "minutes": minutes, "note": note, "requestId": key}) return string(body) @@ -187,7 +339,9 @@ func TestRecordsRoundTripCorrectionsAndStatistics(t *testing.T) { token := h.login("alice") class := h.project(token, "晚课") extra := h.project(token, "课后练习") - h.summary(token, 0, 0) + if got := h.summary(token, 0, 0)["allTime"].(map[string]any)["firstDate"]; got != "" { + t.Fatalf("empty history has first date %v", got) + } one := h.request("POST", "/v1/records", token, recordBody(class, "2026-09-28", 90, "老师反馈:放松肩颈", "lesson-000000001"), 200) two := h.request("POST", "/v1/records", token, recordBody(extra, "2026-09-28", 15, "", "lesson-000000002"), 200) h.summary(token, 105, 1) @@ -198,6 +352,9 @@ func TestRecordsRoundTripCorrectionsAndStatistics(t *testing.T) { id1, id2 := one["id"].(string), two["id"].(string) h.request("PUT", "/v1/records/"+id1, token, recordBody(class, "2026-09-27", 90, "改到周日", ""), 200) review := h.summary(token, 105, 2) + if got := review["allTime"].(map[string]any)["firstDate"]; got != "2026-09-27" { + t.Fatalf("earliest practice date after correction: %v", got) + } if review["period"].(map[string]any)["totalMinutes"] != float64(15) { t.Fatal("Sunday must belong to the previous week") } @@ -207,7 +364,9 @@ func TestRecordsRoundTripCorrectionsAndStatistics(t *testing.T) { h.summary(token, 60, 1) h.request("DELETE", "/v1/records/"+id1, token, "", 200) h.request("DELETE", "/v1/records/"+id1, token, "", 200) - h.summary(token, 0, 0) + if got := h.summary(token, 0, 0)["allTime"].(map[string]any)["firstDate"]; got != "" { + t.Fatalf("deleted history retained first date %v", got) + } } func TestArchiveRenameAndOwnershipAcrossAccounts(t *testing.T) { diff --git a/backend/internal/practice/icons.go b/backend/internal/practice/icons.go new file mode 100644 index 0000000..eac72da --- /dev/null +++ b/backend/internal/practice/icons.go @@ -0,0 +1,12 @@ +package practice + +// Icon IDs address bundled artwork, never client-provided URLs or file paths. +func validProjectIcon(id string) bool { + switch id { + case "", "plie", "tendu", "releve", "port-de-bras", "arabesque", "attitude", + "pirouette", "grand-jete", "foot-hip", "flexibility", "ball-core", + "swan-arms", "breathing", "core-glutes", "soft-shoes", "pointe-shoes", "barre", "piano": + return true + } + return false +} diff --git a/backend/internal/practice/review.go b/backend/internal/practice/review.go index 5a381c0..58f9709 100644 --- a/backend/internal/practice/review.go +++ b/backend/internal/practice/review.go @@ -13,10 +13,12 @@ type ProjectMinutes struct { Minutes int64 `json:"minutes"` } type Summary struct { - TotalMinutes int64 `json:"totalMinutes"` - PracticeDays int `json:"practiceDays"` - RecordCount int64 `json:"recordCount"` - Projects []ProjectMinutes `json:"projects"` + TotalMinutes int64 `json:"totalMinutes"` + PracticeDays int `json:"practiceDays"` + // FirstDate is the earliest actual practice date, or empty without records. + FirstDate string `json:"firstDate"` + RecordCount int64 `json:"recordCount"` + Projects []ProjectMinutes `json:"projects"` } type DayMinutes struct { Date string `json:"date"` @@ -42,6 +44,9 @@ func newSummary() *summaryBuilder { func (b *summaryBuilder) add(date string, project ProjectMinutes, count int64) { b.summary.TotalMinutes += project.Minutes b.summary.RecordCount += count + if b.summary.FirstDate == "" || date < b.summary.FirstDate { + b.summary.FirstDate = date + } b.dates[date] = true previous := b.projects[project.ProjectID] project.Minutes += previous.Minutes diff --git a/backend/internal/practice/store.go b/backend/internal/practice/store.go index 5595b40..94dcee2 100644 --- a/backend/internal/practice/store.go +++ b/backend/internal/practice/store.go @@ -27,7 +27,10 @@ var ( ErrDeleted = errors.New("这次提交的记录已被删除,不会重复创建") ) -var presets = []string{"零基础", "基础提升", "软开素质", "足髋训练", "核心臀腿", "小球核心", "天鹅臂颈", "呼吸训练"} +var presets = []struct{ name, iconID string }{ + {"零基础", "plie"}, {"基础提升", "tendu"}, {"软开素质", "flexibility"}, {"足髋训练", "foot-hip"}, + {"核心臀腿", "core-glutes"}, {"小球核心", "ball-core"}, {"天鹅臂颈", "swan-arms"}, {"呼吸训练", "breathing"}, +} type Store struct { db *pgxpool.Pool @@ -73,8 +76,8 @@ func (s *Store) Login(ctx context.Context, identity string) (Session, error) { return Session{}, err } if result.RowsAffected() == 1 { - for _, name := range presets { - if _, err = tx.Exec(ctx, "INSERT INTO projects(id,user_id,name) VALUES($1,$2,$3)", newID(), id, name); err != nil { + for _, preset := range presets { + if _, err = tx.Exec(ctx, "INSERT INTO projects(id,user_id,name,icon_id) VALUES($1,$2,$3,$4)", newID(), id, preset.name, preset.iconID); err != nil { return Session{}, err } } @@ -133,12 +136,13 @@ func (s *Store) write(ctx context.Context, user string, fn func(pgx.Tx) error) e type Project struct { ID string `json:"id"` Name string `json:"name"` + IconID string `json:"iconId"` Archived bool `json:"archived"` HasRecords bool `json:"hasRecords"` } func (s *Store) Projects(ctx context.Context, user string, archived bool) ([]Project, error) { - rows, err := s.db.Query(ctx, `SELECT p.id,p.name,p.archived,EXISTS(SELECT 1 FROM records r WHERE r.user_id=p.user_id AND r.project_id=p.id) + rows, err := s.db.Query(ctx, `SELECT p.id,p.name,p.icon_id,p.archived,EXISTS(SELECT 1 FROM records r WHERE r.user_id=p.user_id AND r.project_id=p.id) FROM projects p WHERE p.user_id=$1 AND ($2 OR NOT p.archived) ORDER BY p.position`, user, archived) if err != nil { return nil, err @@ -147,7 +151,7 @@ func (s *Store) Projects(ctx context.Context, user string, archived bool) ([]Pro projects := []Project{} for rows.Next() { var p Project - if err := rows.Scan(&p.ID, &p.Name, &p.Archived, &p.HasRecords); err != nil { + if err := rows.Scan(&p.ID, &p.Name, &p.IconID, &p.Archived, &p.HasRecords); err != nil { return nil, err } projects = append(projects, p) @@ -155,20 +159,75 @@ func (s *Store) Projects(ctx context.Context, user string, archived bool) ([]Pro return projects, rows.Err() } -func (s *Store) SaveProject(ctx context.Context, user, id, name string) (Project, error) { +// ReorderProjects accepts exactly the owner's active project IDs. Reusing their +// current position slots leaves archived history and automatic append positions intact. +func (s *Store) ReorderProjects(ctx context.Context, user string, ids []string) error { + if ids == nil { + return ErrInvalid + } + return s.write(ctx, user, func(tx pgx.Tx) error { + rows, err := tx.Query(ctx, `SELECT id,position FROM projects WHERE user_id=$1 AND NOT archived ORDER BY position`, user) + if err != nil { + return err + } + positions := make([]int64, 0, len(ids)) + available := make(map[string]bool, len(ids)) + for rows.Next() { + var id string + var position int64 + if err := rows.Scan(&id, &position); err != nil { + rows.Close() + return err + } + positions = append(positions, position) + available[id] = true + } + err = rows.Err() + rows.Close() + if err != nil { + return err + } + if len(ids) != len(positions) { + return ErrInvalid + } + for _, id := range ids { + if !available[id] { + return ErrInvalid + } + delete(available, id) + } + for index, id := range ids { + if _, err := tx.Exec(ctx, `UPDATE projects SET position=$3 WHERE user_id=$1 AND id=$2 AND NOT archived`, user, id, positions[index]); err != nil { + return err + } + } + return nil + }) +} + +// SaveProject persists a name and optional icon choice within the owner's account. +// A nil iconID preserves existing artwork for older clients; an empty string clears it. +// Unknown icon IDs return ErrInvalid, and another owner's project returns ErrUnavailable. +func (s *Store) SaveProject(ctx context.Context, user, id, name string, iconID *string) (Project, error) { name = strings.TrimSpace(name) if name == "" || utf8.RuneCountInString(name) > NameLimit || strings.ContainsRune(name, 0) { return Project{}, ErrInvalid } + if iconID != nil && !validProjectIcon(*iconID) { + return Project{}, ErrInvalid + } p := Project{ID: id, Name: name} + if iconID != nil { + p.IconID = *iconID + } err := s.write(ctx, user, func(tx pgx.Tx) error { if id == "" { p.ID = newID() - _, err := tx.Exec(ctx, "INSERT INTO projects(id,user_id,name) VALUES($1,$2,$3)", p.ID, user, name) + _, err := tx.Exec(ctx, "INSERT INTO projects(id,user_id,name,icon_id) VALUES($1,$2,$3,$4)", p.ID, user, name, p.IconID) return err } - err := tx.QueryRow(ctx, `UPDATE projects SET name=$3 WHERE user_id=$1 AND id=$2 RETURNING archived, - EXISTS(SELECT 1 FROM records WHERE user_id=$1 AND project_id=$2)`, user, id, name).Scan(&p.Archived, &p.HasRecords) + err := tx.QueryRow(ctx, `UPDATE projects SET name=$3,icon_id=COALESCE($4,icon_id) WHERE user_id=$1 AND id=$2 RETURNING icon_id,archived, + EXISTS(SELECT 1 FROM records WHERE user_id=$1 AND project_id=$2)`, user, id, name, iconID).Scan(&p.IconID, &p.Archived, &p.HasRecords) if errors.Is(err, pgx.ErrNoRows) { return ErrUnavailable } diff --git a/docs/design/project-icons/arabesque.svg b/docs/design/project-icons/arabesque.svg new file mode 100644 index 0000000..b25e85e --- /dev/null +++ b/docs/design/project-icons/arabesque.svg @@ -0,0 +1,14 @@ + + Arabesque · 阿拉贝斯克 + 修长的灰玫瑰线描舞者,侧身单腿站立,后腿伸直,手臂与脚尖以自然收细的曲线舒展。 + + + + + + + + + + + diff --git a/docs/design/project-icons/attitude.svg b/docs/design/project-icons/attitude.svg new file mode 100644 index 0000000..e9df407 --- /dev/null +++ b/docs/design/project-icons/attitude.svg @@ -0,0 +1,14 @@ + + Attitude · 阿蒂蒂德 + 侧身舞者单腿支撑,工作腿向后抬起并屈膝,脚尖绷直;一臂举高,另一臂向前舒展。 + + + + + + + + + + + diff --git a/docs/design/project-icons/ball-core.svg b/docs/design/project-icons/ball-core.svg new file mode 100644 index 0000000..e69604f --- /dev/null +++ b/docs/design/project-icons/ball-core.svg @@ -0,0 +1,18 @@ + + 小球核心 + 舞者坐姿收腹,屈膝抬腿,双手在身前环抱一颗小球。 + + + + + + + + + + + + + + + diff --git a/docs/design/project-icons/barre.svg b/docs/design/project-icons/barre.svg new file mode 100644 index 0000000..734ac1d --- /dev/null +++ b/docs/design/project-icons/barre.svg @@ -0,0 +1,11 @@ + + 把杆 · Barre + 灰玫瑰线描双层练习把杆,柔和木色横杆与稳定的立柱底座。 + + + + + + + + diff --git a/docs/design/project-icons/breathing.svg b/docs/design/project-icons/breathing.svg new file mode 100644 index 0000000..aaca666 --- /dev/null +++ b/docs/design/project-icons/breathing.svg @@ -0,0 +1,17 @@ + + 呼吸训练 + 舞者自然盘坐,双手轻放身体两侧,下肋旁的柔和弧线表现呼吸节奏。 + + + + + + + + + + + + + + diff --git a/docs/design/project-icons/core-glutes.svg b/docs/design/project-icons/core-glutes.svg new file mode 100644 index 0000000..7a91366 --- /dev/null +++ b/docs/design/project-icons/core-glutes.svg @@ -0,0 +1,15 @@ + + 核心臀腿 + 侧面臀桥轮廓:肩背贴近垫面,髋部抬起,双膝屈曲,双脚平放,手臂沿身体两侧伸展。 + + + + + + + + + + + + diff --git a/docs/design/project-icons/flexibility.svg b/docs/design/project-icons/flexibility.svg new file mode 100644 index 0000000..8b08a13 --- /dev/null +++ b/docs/design/project-icons/flexibility.svg @@ -0,0 +1,14 @@ + + 软开素质 + 坐姿舞者一腿舒展、一腿收拢,手臂弧线越过头顶,身体向伸展腿一侧延长。 + + + + + + + + + + + diff --git a/docs/design/project-icons/foot-hip.svg b/docs/design/project-icons/foot-hip.svg new file mode 100644 index 0000000..de1f4ab --- /dev/null +++ b/docs/design/project-icons/foot-hip.svg @@ -0,0 +1,16 @@ + + 足髋训练 + 坐姿舞者一腿屈曲外展、一腿向前伸展并勾起脚尖,以足部与髋部的轮廓表达基础训练。 + + + + + + + + + + + + + diff --git a/docs/design/project-icons/grand-jete.svg b/docs/design/project-icons/grand-jete.svg new file mode 100644 index 0000000..9d988f6 --- /dev/null +++ b/docs/design/project-icons/grand-jete.svg @@ -0,0 +1,15 @@ + + Grand jeté · 大跳 + 腾空舞者,前后双腿伸展成大幅分腿线,双脚绷直,双臂舒展。 + + + + + + + + + + + + diff --git a/docs/design/project-icons/piano.svg b/docs/design/project-icons/piano.svg new file mode 100644 index 0000000..6e7c9d0 --- /dev/null +++ b/docs/design/project-icons/piano.svg @@ -0,0 +1,15 @@ + + 钢琴 · Piano + 灰玫瑰线描三角钢琴,弧形琴身、浅色琴盖与简洁黑白琴键。 + + + + + + + + + + + + diff --git a/docs/design/project-icons/pirouette.svg b/docs/design/project-icons/pirouette.svg new file mode 100644 index 0000000..d57a796 --- /dev/null +++ b/docs/design/project-icons/pirouette.svg @@ -0,0 +1,16 @@ + + Pirouette · 旋转 + 旋转中的代表姿态:单脚半脚尖支撑,另一脚收至支撑腿膝旁,双臂围圆,短弧线提示旋转。 + + + + + + + + + + + + + diff --git a/docs/design/project-icons/plie.svg b/docs/design/project-icons/plie.svg new file mode 100644 index 0000000..3de0691 --- /dev/null +++ b/docs/design/project-icons/plie.svg @@ -0,0 +1,13 @@ + + Plié · 一位半蹲 + 灰玫瑰线描舞者,以一位脚位半蹲,脚跟相接并着地,双膝向外打开,小腿收向脚跟,躯干保持直立。 + + + + + + + + + + diff --git a/docs/design/project-icons/pointe-shoes.svg b/docs/design/project-icons/pointe-shoes.svg new file mode 100644 index 0000000..56df4c3 --- /dev/null +++ b/docs/design/project-icons/pointe-shoes.svg @@ -0,0 +1,18 @@ + + 足尖鞋 · Pointe shoes + 灰玫瑰线描的成对足尖鞋,浅玫瑰鞋身与自然垂落的缎带。 + + + + + + + + + + + + + + + diff --git a/docs/design/project-icons/port-de-bras.svg b/docs/design/project-icons/port-de-bras.svg new file mode 100644 index 0000000..655f81d --- /dev/null +++ b/docs/design/project-icons/port-de-bras.svg @@ -0,0 +1,13 @@ + + Port de bras · 手臂练习 + 以一个手位瞬间表达手臂练习:一臂柔和地举过头顶,另一臂向侧方舒展。 + + + + + + + + + + diff --git a/docs/design/project-icons/preview.html b/docs/design/project-icons/preview.html new file mode 100644 index 0000000..cf8b70a --- /dev/null +++ b/docs/design/project-icons/preview.html @@ -0,0 +1,209 @@ + + + + + + 芭蕾岛 · 项目图标库 + + + + +
+
芭蕾岛18 枚项目图标
+
+

项目图标库

柔和线描 · 浅色填充

+
+
+
+
+

项目列表预览52 px 行高

+
阿拉贝斯克周末基础课
+
+
+
图标尺寸
+
阿拉贝斯克
+
+
+
+
+
预览底色
+
+
+ +
+ + + diff --git a/docs/design/project-icons/releve.svg b/docs/design/project-icons/releve.svg new file mode 100644 index 0000000..de3cad2 --- /dev/null +++ b/docs/design/project-icons/releve.svg @@ -0,0 +1,14 @@ + + Relevé · 立半脚尖 + 修长舞者直立,双脚脚跟抬起,前脚掌着地,双臂在身前自然围圆。 + + + + + + + + + + + diff --git a/docs/design/project-icons/soft-shoes.svg b/docs/design/project-icons/soft-shoes.svg new file mode 100644 index 0000000..0a50f56 --- /dev/null +++ b/docs/design/project-icons/soft-shoes.svg @@ -0,0 +1,16 @@ + + 软底舞鞋 · Ballet slippers + 灰玫瑰线描软底舞鞋,圆润鞋头、浅色鞋身与交叉松紧带。 + + + + + + + + + + + + + diff --git a/docs/design/project-icons/swan-arms.svg b/docs/design/project-icons/swan-arms.svg new file mode 100644 index 0000000..3361209 --- /dev/null +++ b/docs/design/project-icons/swan-arms.svg @@ -0,0 +1,13 @@ + + 天鹅臂颈 + 修长肩颈与轻柔展开的双臂,手腕自然垂落,形成舒展的翼形轮廓。 + + + + + + + + + + diff --git a/docs/design/project-icons/tendu.svg b/docs/design/project-icons/tendu.svg new file mode 100644 index 0000000..a361e37 --- /dev/null +++ b/docs/design/project-icons/tendu.svg @@ -0,0 +1,12 @@ + + Tendu · 擦地 + 修长舞者单腿直立,工作腿向侧方伸长,绷直的脚尖仍触地。 + + + + + + + + + diff --git a/miniprogram/src/app.config.ts b/miniprogram/src/app.config.ts index 37591ec..533c420 100644 --- a/miniprogram/src/app.config.ts +++ b/miniprogram/src/app.config.ts @@ -1,11 +1,11 @@ export default defineAppConfig({ - pages: ['pages/index/index', 'pages/calendar/index', 'pages/growth/index', 'pages/record/index', 'pages/projects/index'], + pages: ['pages/index/index', 'pages/calendar/index', 'pages/growth/index', 'pages/mine/index', 'pages/record/index', 'pages/projects/index', 'pages/projects-sort/index'], tabBar: { color: '#817573', selectedColor: '#9c5268', backgroundColor: '#fffdfa', borderStyle: 'white', list: [ { pagePath: 'pages/index/index', text: '记录' }, - { pagePath: 'pages/calendar/index', text: '日历' }, { pagePath: 'pages/growth/index', text: '成长' }, + { pagePath: 'pages/mine/index', text: '我的' }, ], }, window: { diff --git a/miniprogram/src/app.scss b/miniprogram/src/app.scss index b4d3e75..68b2cc2 100644 --- a/miniprogram/src/app.scss +++ b/miniprogram/src/app.scss @@ -24,33 +24,57 @@ page { } view, text, input, textarea { box-sizing: border-box; } .page { padding: 20px 18px 32px; padding-bottom: calc(32px + env(safe-area-inset-bottom)); } -.page-heading { margin: 4px 2px 24px; } .eyebrow { display: block; color: #946d76; font-size: 10px; letter-spacing: 2px; margin-bottom: 8px; } -.page-title { display: block; margin-bottom: 8px; font-size: 26px; font-weight: 600; line-height: 1.35; letter-spacing: -0.5px; } .muted { color: #827572; font-size: 13px; } .small { font-size: 12px; } .block { display: block; } .panel { padding: 18px; background: #fffdfa; border: 1px solid #ece3dd; border-radius: 18px; } .hero-card { padding: 23px 22px; border: 1px solid #eddfda; background: #f1e7e3; border-radius: 21px; } .hero-number { display: block; color: #864b5e; font-size: 29px; font-weight: 600; line-height: 1.5; margin: 4px 0 8px; overflow-wrap: anywhere; } +.record-overview { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; align-items: start; } +.overview-column { min-width: 0; } +.overview-column--right { border-left: 1px solid #e2cfd0; padding-left: 18px; } +.overview-label { display: block; color: #946d76; font-size: 12px; line-height: 18px; } +.overview-value { display: block; color: #864b5e; font-size: 28px; font-weight: 600; line-height: 1.3; margin-top: 8px; overflow-wrap: anywhere; } +.overview-unit { font-size: 15px; font-weight: 400; } +.overview-start { display: block; color: #827572; font-size: 11px; margin-top: 6px; } .section-heading, .row { display: flex; align-items: center; justify-content: space-between; gap: 10px; } .section-heading { margin: 24px 2px 12px; } +.section-heading--actions { justify-content: flex-end; margin-top: 12px; } .section-title { font-size: 17px; font-weight: 600; } -.project-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } -.project-tile { border: 1px solid #e7ded8; border-radius: 17px; min-height: 126px; padding: 15px 16px; background: #f4ece8; } +.project-list { display: flex; flex-direction: column; gap: 10px; } +.project-tile { display: flex; align-items: center; gap: 12px; border: 1px solid #e7ded8; border-radius: 17px; padding: 10px 16px; background: #f4ece8; } .project-tile--1 { background: #eef0e7; border-color: #e0e5d9; } .project-tile--2 { background: #eeeaf2; border-color: #e3dfea; } .project-tile--3 { background: #f4efdf; border-color: #eae4d5; } -.project-mark { display: block; color: #9a8583; font-size: 10px; letter-spacing: 1px; margin-bottom: 10px; } -.project-title { display: block; font-size: 17px; font-weight: 600; overflow-wrap: anywhere; } -.project-hint { display: block; font-size: 11px; color: #8b7476; margin-top: 8px; } +.project-title { display: block; flex: 1; min-width: 0; font-size: 17px; font-weight: 600; overflow-wrap: anywhere; } +.project-hours { flex-shrink: 0; color: #9c5268; font-size: 18px; font-weight: 600; white-space: nowrap; } +.project-hours-unit { font-size: 12px; font-weight: 400; } +.project-icon { display: block; flex-shrink: 0; width: 32px; height: 32px; } +.action-icon { display: block; width: 28px; height: 28px; } +.profile-card { display: flex; align-items: center; gap: 18px; padding: 24px 20px; border: 1px solid #eddfda; border-radius: 21px; background: #f1e7e3; } +.profile-avatar-button { display: flex; flex-shrink: 0; align-items: center; justify-content: center; width: 72px; height: 72px; margin: 0; padding: 0; border-radius: 50%; background: #e2cfd0; overflow: hidden; } +.profile-avatar-button::after { border: 0; } +.profile-avatar { display: block; width: 72px; height: 72px; } +.profile-avatar-placeholder { color: #864b5e; font-size: 28px; font-weight: 600; line-height: 1; } +.profile-details { flex: 1; min-width: 0; } +.profile-nickname { display: block; width: 100%; height: 36px; color: #3e3435; font-size: 21px; font-weight: 600; } +.profile-hint { display: block; margin-top: 3px; color: #827572; font-size: 12px; } +.profile-storage-note { display: block; margin: 12px 2px; color: #827572; font-size: 11px; } +.sort-instruction { display: block; color: #827572; font-size: 13px; margin-bottom: 16px; } +.sort-project-list { display: flex; flex-direction: column; gap: 10px; } +.sortable-project { position: relative; z-index: 0; display: flex; align-items: center; gap: 12px; min-height: 64px; padding: 8px 10px 8px 16px; border: 1px solid #ece3dd; border-radius: 17px; background: #fffdfa; } +.sortable-project--dragging { z-index: 2; border-color: #9c5268; background: #f4ece8; } +.sortable-project--target { border-color: #bc8292; } +.sort-project-name { flex: 1; min-width: 0; font-size: 16px; font-weight: 500; overflow-wrap: anywhere; } +.sort-drag-handle { display: flex; flex-shrink: 0; align-items: center; justify-content: center; width: 44px; height: 44px; } +.sort-drag-icon { display: block; width: 22px; height: 22px; } .record-card { padding: 16px 18px; background: #fffdfa; border: 1px solid #ece3dd; border-radius: 16px; margin-bottom: 10px; } .record-name { font-size: 15px; font-weight: 500; overflow-wrap: anywhere; flex: 1; min-width: 0; } .record-duration { color: #9c5268; font-size: 13px; text-align: right; max-width: 45%; } .record-note { display: block; white-space: pre-wrap; overflow-wrap: anywhere; padding-top: 12px; border-top: 1px solid #f0e9e3; font-size: 13px; color: #756364; } .empty { text-align: center; padding: 20px 4px; } -.empty-title { display: block; font-size: 16px; font-weight: 500; margin-bottom: 10px; } -.empty .muted { display: block; line-height: 1.8; } +.empty-title { display: block; font-size: 16px; font-weight: 500; } .status-panel { display: flex; flex-direction: column; gap: 16px; align-items: center; margin-bottom: 16px; } .error-text { color: #a43b4b; } .error-banner, .success-banner { display: block; border-radius: 12px; padding: 13px 15px; margin: 14px 0; font-size: 13px; line-height: 1.7; overflow-wrap: anywhere; } @@ -58,21 +82,32 @@ view, text, input, textarea { box-sizing: border-box; } .success-banner { color: #4f6b4c; background: #eaf0e7; } .form-panel .field-label:first-child { margin-top: 0; } .field-label { display: block; font-size: 14px; font-weight: 500; margin: 22px 0 10px; } -.field { display: block; width: 100%; background: #f8f4ef; border: 1px solid #e8dfd8; border-radius: 11px; padding: 12px 14px; height: 48px; font-size: 16px; color: #44383b; } -.picker-field { display: flex; justify-content: space-between; height: auto; min-height: 48px; overflow-wrap: anywhere; gap: 8px; } +// Native single-line inputs position their text from height; vertical padding shifts filled values down. +.field { display: block; width: 100%; background: #f8f4ef; border: 1px solid #e8dfd8; border-radius: 11px; padding: 0 14px; height: 48px; font-size: 16px; color: #44383b; } +.picker-field { display: flex; justify-content: space-between; padding: 12px 14px; height: auto; min-height: 48px; overflow-wrap: anywhere; gap: 8px; } .quick-minutes { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; } -.note-input { min-height: 140px; height: 140px; line-height: 1.6; } +.note-input { padding: 12px 14px; min-height: 140px; height: 140px; line-height: 1.6; } .field-counter { display: block; text-align: right; color: #968983; font-size: 11px; margin-top: 6px; } .field-help { display: block; color: #877872; font-size: 12px; margin: 10px 0 14px; line-height: 1.7; } -.form-actions { margin-top: 22px; } -.project-name-input { margin-top: 16px; } -.page-footnote { display: block; text-align: center; color: #998a82; font-size: 11px; line-height: 1.8; margin: 24px 12px 8px; } -.managed-project { padding: 12px 0; border-bottom: 1px solid #f0e8e1; } -.managed-project:first-child { padding-top: 0; } -.managed-project:last-child { padding-bottom: 0; border-bottom: 0; } -.managed-project > .record-name { display: block; } -.managed-project > .row { align-items: flex-end; } +.form-page { padding-bottom: calc(100px + env(safe-area-inset-bottom)); } +.form-actions { position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; display: flex; gap: 12px; padding: 12px 18px; padding-bottom: calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid #ece3dd; background: #fffdfa; } +.form-action { flex: 1; min-width: 0; } +.managed-project { display: flex; align-items: center; gap: 12px; padding: 8px 16px; background: #fffdfa; border: 1px solid #ece3dd; border-radius: 17px; } .inline-actions { display: flex; flex-shrink: 0; } +.project-icon-field { display: flex; align-items: center; justify-content: space-between; gap: 12px; } +.project-icon-current { display: flex; align-items: center; gap: 10px; min-width: 0; } +.icon-picker { --popup-background-color: #fffdfa; --popup-rounded-border-radius: 20px; } +.icon-picker-heading { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px 8px; } +.icon-picker-tabs { display: flex; gap: 8px; padding: 0 18px 14px; } +.icon-picker-scroll { height: 48vh; max-height: 400px; } +.icon-picker-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; padding: 2px 18px 16px; } +.icon-choice { position: relative; display: flex; width: 100%; min-width: 0; flex-direction: column; align-items: center; gap: 4px; margin: 0; padding: 10px 4px; border: 1px solid #ece3dd; border-radius: 12px; background: #faf7f3; color: #655656; font-size: 12px; line-height: 1.5; } +.icon-choice::after { border: 0; } +.icon-choice--selected { background: #f4ece8; border-color: #9c5268; color: #9c5268; } +.icon-choice-image { display: block; width: 60px; height: 60px; } +.icon-choice-name { display: block; overflow-wrap: anywhere; } +.icon-choice-check { position: absolute; top: 4px; right: 7px; font-size: 12px; } +.icon-picker-footer { padding: 6px 18px calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid #ece3dd; } .month-nav { display: flex; align-items: center; justify-content: space-between; gap: 4px; margin: 0 -8px 12px; } .calendar-panel { padding: 16px 12px; } .calendar-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); text-align: center; } diff --git a/miniprogram/src/assets/action-icons/add-project.svg b/miniprogram/src/assets/action-icons/add-project.svg new file mode 100644 index 0000000..79243aa --- /dev/null +++ b/miniprogram/src/assets/action-icons/add-project.svg @@ -0,0 +1,4 @@ + + + + diff --git a/miniprogram/src/assets/action-icons/calendar.svg b/miniprogram/src/assets/action-icons/calendar.svg new file mode 100644 index 0000000..ffcef36 --- /dev/null +++ b/miniprogram/src/assets/action-icons/calendar.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/miniprogram/src/assets/action-icons/drag-handle.svg b/miniprogram/src/assets/action-icons/drag-handle.svg new file mode 100644 index 0000000..6e2132b --- /dev/null +++ b/miniprogram/src/assets/action-icons/drag-handle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/miniprogram/src/assets/action-icons/manage-list.svg b/miniprogram/src/assets/action-icons/manage-list.svg new file mode 100644 index 0000000..86208be --- /dev/null +++ b/miniprogram/src/assets/action-icons/manage-list.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/miniprogram/src/assets/project-icons/arabesque.png b/miniprogram/src/assets/project-icons/arabesque.png new file mode 100644 index 0000000000000000000000000000000000000000..b6cde10bd9c8f5563661dd85bc2876e134394aa0 GIT binary patch literal 8153 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 literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/attitude.png b/miniprogram/src/assets/project-icons/attitude.png new file mode 100644 index 0000000000000000000000000000000000000000..b62206c7c5c56b22d05794fcb990933751cffba0 GIT binary patch literal 10293 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

J==tLOqcWJ2*Oc@8LfrjTI0i)Ty zPlxN18Cm)x1*@Uu*aXYv^(JMIzx^nScdFTXxlHU>;6PrH8~nMyz=z5v7_RXC=`)d^ zc>p4-9N@zsDS-PWLqo(rqysiPn1Jb9pMS~#zd*qKzax2?#z;I5sI11%^3k>7bd>+? zv+*&?s=(($fdBJ;EBxR0ZGry?ed&durMeOg02b8#!xBTzY1Urww{dsb&M^Ik0NWtp zMi;%gaB=`z^}Kiinc&4vG)Oj$9*Z$~Qt zOx#!`AdUd&!Y{L-LsN18HUar?3!TN^MFBhd`KGM#bAa+FAVZHCKL=ZAXZmk*&`szk znE?jghoDHz()ZTx0%f29_+wuy6l7#cJ%iYF@cyg({|fED_P`ZBAl`J>!vrX61zS_V zV!AR1Zt`E1`75e#B1|9tB@@g~{Nopmq0;MFwyzBE+oMp*UU$84!3~W7 zxoiQH94&AHsx-&Mi4t#lM_<8l0r`&umz`i2S|VgAa_fk3U`)uCk(dC(8RP6 zqikY4EL7<@hvnpI>`mE#p9R!whfj&D3!r4ygS%B+QchE?*N#rPYJ)JrcKTyjIKT5> z9HgwgH-&p2PUC?BZWn!(HU4&C=_xd6NEag1cHu3m#Hd8nk`iK4>ey%y%QZg`r~H)w?UeZrEQxWQD@(+uaEPYkzO; zx!D+U@=n&r{dxixs0*#Wv_#Mm>vukz0|O{mvbyUFdvQ{s%*j`S$<- literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/ball-core.png b/miniprogram/src/assets/project-icons/ball-core.png new file mode 100644 index 0000000000000000000000000000000000000000..d9ff6d98accca7c4dec168d85addec2bf8b75731 GIT binary patch literal 12561 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 R>41C%(9+OXuTZs%`aj$toLm3^ literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/barre.png b/miniprogram/src/assets/project-icons/barre.png new file mode 100644 index 0000000000000000000000000000000000000000..b17d2d20c67d6f323b5ca5bebf199f5049ded7ae GIT binary patch literal 4416 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 literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/breathing.png b/miniprogram/src/assets/project-icons/breathing.png new file mode 100644 index 0000000000000000000000000000000000000000..601b63b36a9eed791f1d97931498d0641b62829e GIT binary patch literal 10912 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 C>`lS| literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/core-glutes.png b/miniprogram/src/assets/project-icons/core-glutes.png new file mode 100644 index 0000000000000000000000000000000000000000..644d848549a7f5615dc36e59a10fcd2b41c6c69b GIT binary patch literal 8425 zcmeHt=T}ovv-b%#^avxkRnD(kP<`)AiXCvQIM`w=|z+d(jfsg z0#Ze(L7GS}QWIKo<9gQp@csqwx_8|VIcwHFnc2UYGi}c~FOBZ$aIgxp0sz2qNB5>N z0D!RneqbirN?~Um9snS~otv7bes9-)S-i3u4({CYJ`0CE2Xp_16Wi1D(7#ICeG{)>&IUERHtNPvS>68B;#$w6BkGA{Twm{)!gB@c^rKb$$iyl;059@p0&=-Q z0C4vd0Oa!iAMt-i_}}OWKkqYP1sgDbir4EKrpdpG9O%7oGqu|kF;ZWQs|%*b^o_ZY zg4f2`pT_eF$x6$rt?Cx@n07zdsuN?k8|lkz|D$&NxUozot8}_7=UUS@BX-t zd(yaMD3JbVOOl1|kReO%OBnq?+Y$$Zmd!(&OsIk4v!W%v4nxek)S+zrG|6qD`e?%!mjmJF zqyv@^JO-0xO9RG#t%5Y+V0Id$-Q1Vru0pr)T|#uLy_|xdpNRq3IIp_wX+;{yMpn9& za_e|`#B+i8KAmCLEJXIZ_HwC19euR%u1a>3&Aq#rjf$m3bjxo3uA>E5A9cBh>Ao!! z;EfV|*<(#U&;M~tj+4ql4gA4e&w|ai-2<57vrKV= z$AX_ex3dG`NwTE0Zb|@`)M2Kax`xc5nOZPauJ=a|m+9e{QD&oohYaIowij#)0K3*O zliROPIf>I^8bbn_S5*H-hAQ4^u)01uCpW(-u4(Q9R$zP4)5#8LQmtrmLK^$|T)Qb{A z*;3ssTB%2dYUF&)zhK~|UP@raWV58f6ys>8ZnZk;o5o!>O!83rP40_0Xo5o$EUNv` zFw162mlC&myt1z3W|%T_W+QmJqlG<82Y_C$nM~%Ts3vQ9wr&0{gzIhPIDSFi4uglZ z?k**dkhs(t&emej13p3w@c`U(NPKf_(kXy{uSK_=jG|w*I_i!$Sft_v1k)M2>IcuI zgwDYMD684+6U1z)?c_qqH0i(!2JJbo%>hcE)$J+|7V3dth60e*xf1G^WWy_+A`Mfz z=?8c&b$yNEm_OMU6`y0J-4G~{Vz?Mh(vxM*oXHYoG?k@0K3)Rk9#nG-Wk#rD`iin- zNTzw08J;k8I%R?A&g-fBS_c1UbV1@HI@~BEdLF@R3Q*!bwEbqQjM2oA_6~qlP77d7mJQcc$MNXZvPv><|L-% zQz4TlO&=Kxz~w(E1~`;cNwQSqM}Ld{I?j(WXNOFkZJ0G#NHaX^tToF9f>}s1^U$UI zHwVN+#aDK03OP9;Rd8YpexWUQ;_D8B5|E$YKkiI1%IE-z0fVa3N%0AD7|oh&>&aOj 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 literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/flexibility.png b/miniprogram/src/assets/project-icons/flexibility.png new file mode 100644 index 0000000000000000000000000000000000000000..905d07ad4f11f33ad3c82288a12725f27921709d GIT binary patch literal 9686 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 literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/foot-hip.png b/miniprogram/src/assets/project-icons/foot-hip.png new file mode 100644 index 0000000000000000000000000000000000000000..d9938ae6db19a511815437934a33eb0c5dc35d8a GIT binary patch literal 9927 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 literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/grand-jete.png b/miniprogram/src/assets/project-icons/grand-jete.png new file mode 100644 index 0000000000000000000000000000000000000000..c1cb7a1cdf914aaf5af85c3b0bc532f3bb48dfdf GIT binary patch literal 9169 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 QVgNv2=bm icon.id === id) +} diff --git a/miniprogram/src/assets/project-icons/piano.png b/miniprogram/src/assets/project-icons/piano.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ae31457c7edaefd60c43bd4dd512f7aa923a71 GIT binary patch literal 5513 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 COq2Hj literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/pirouette.png b/miniprogram/src/assets/project-icons/pirouette.png new file mode 100644 index 0000000000000000000000000000000000000000..b71fd8dbf3199dccfb7be64b580bc5c3837f1e44 GIT binary patch literal 10391 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 Lt5dG!5c$6VT!bOT literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/plie.png b/miniprogram/src/assets/project-icons/plie.png new file mode 100644 index 0000000000000000000000000000000000000000..447cfa275c316a5696153168c27b9f9fd8a067b2 GIT binary patch literal 10440 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 PR0A&ST+=SrvWxg%kmVba literal 0 HcmV?d00001 diff --git a/miniprogram/src/assets/project-icons/releve.png b/miniprogram/src/assets/project-icons/releve.png new file mode 100644 index 0000000000000000000000000000000000000000..9eddd48bd684c2122c894b8bd857612d4627470e GIT binary patch literal 9394 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 literal 0 HcmV?d00001 diff --git a/miniprogram/src/components/common.tsx b/miniprogram/src/components/common.tsx index 16b5d4d..06c46f4 100644 --- a/miniprogram/src/components/common.tsx +++ b/miniprogram/src/components/common.tsx @@ -7,15 +7,25 @@ import { duration } from '../utils/dates' export { Button } -export function PageHeading({ title, subtitle }: { title: string; subtitle: string }) { - return BALLET ISLAND{title}{subtitle} +export function FormActions({ busy, cancelDisabled, saveDisabled, saveLabel = '保存', onCancel, onSave }: { + busy: boolean + cancelDisabled?: boolean + saveDisabled?: boolean + saveLabel?: string + onCancel: () => unknown + onSave: () => unknown +}) { + return + + + } export function LoadState({ loading, error, retry }: { loading: boolean; error: string; retry: () => unknown }) { if (!loading && !error) return null return {loading ? '正在读取你的练习…' : error}{error && } } -export function Empty({ title, text }: { title: string; text: string }) { - return {title}{text} +export function Empty({ title }: { title: string }) { + return {title} } export function RecordCards({ records }: { records: PracticeRecord[] }) { return {records.map(record => diff --git a/miniprogram/src/components/project-icon-picker.tsx b/miniprogram/src/components/project-icon-picker.tsx new file mode 100644 index 0000000..2802e1e --- /dev/null +++ b/miniprogram/src/components/project-icon-picker.tsx @@ -0,0 +1,51 @@ +import { useState } from 'react' +import { Button as NativeButton, Image, ScrollView, Text, View } from '@tarojs/components' +import Popup from '@taroify/core/popup' +import '@taroify/core/popup/style' +import { Button } from './common' +import { ProjectIcon } from './project-icon' +import { projectIcon, projectIcons } from '../assets/project-icons' + +const groups = [ + { id: 'training', name: '基础练习' }, + { id: 'motion', name: '专业动作' }, + { id: 'object', name: '物件' }, +] as const +type Group = typeof groups[number]['id'] + +/** Selection only updates the enclosing form; its save/cancel controls own persistence. */ +export function ProjectIconPicker({ value, disabled, onChange }: { + value: string; disabled: boolean; onChange: (id: string) => void +}) { + const [open, setOpen] = useState(false) + const [group, setGroup] = useState('training') + const selected = projectIcon(value) + + function choose(id: string) { + if (disabled) return + onChange(id) + setOpen(false) + } + + return <> + + {selected?.name || '无图标'} + + + setOpen(false)}> + 项目图标 + {groups.map(item => )} + + {projectIcons.filter(icon => icon.group === group).map(icon => choose(icon.id)} + > + + {icon.name} + {value === icon.id && ✓} + )} + + + + +} diff --git a/miniprogram/src/components/project-icon.tsx b/miniprogram/src/components/project-icon.tsx new file mode 100644 index 0000000..20851f0 --- /dev/null +++ b/miniprogram/src/components/project-icon.tsx @@ -0,0 +1,8 @@ +import { Image } from '@tarojs/components' +import { projectIcon } from '../assets/project-icons' + +/** Unknown or empty IDs leave the readable project title intact without a placeholder. */ +export function ProjectIcon({ id, className = '' }: { id?: string; className?: string }) { + const icon = projectIcon(id) + return icon ? : null +} diff --git a/miniprogram/src/pages/calendar/index.tsx b/miniprogram/src/pages/calendar/index.tsx index f056229..8b55b00 100644 --- a/miniprogram/src/pages/calendar/index.tsx +++ b/miniprogram/src/pages/calendar/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import Taro, { useDidHide } from '@tarojs/taro' import { Picker, Text, View } from '@tarojs/components' -import { Button, Empty, LoadState, PageHeading, RecordCards } from '../../components/common' +import { Button, Empty, LoadState, RecordCards } from '../../components/common' import { useRemote } from '../../hooks/useRemote' import { api, errorMessage, type RecordList } from '../../services/practice' import { addMonths, displayDate, duration, monthLabel, monthStart, parseDate, shanghaiToday } from '../../utils/dates' @@ -41,7 +41,6 @@ export default function Calendar() { } return - @@ -59,7 +58,7 @@ export default function Calendar() { {review && list && <> {displayDate(selected)}共 {duration(total)} - {list.records.length ? : } + {list.records.length ? : } {moreError && {moreError}} {list.nextCursor && } } diff --git a/miniprogram/src/pages/growth/index.tsx b/miniprogram/src/pages/growth/index.tsx index 15a01db..302d6b3 100644 --- a/miniprogram/src/pages/growth/index.tsx +++ b/miniprogram/src/pages/growth/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { Text, View } from '@tarojs/components' -import { Button, Empty, LoadState, PageHeading } from '../../components/common' +import { Button, Empty, LoadState } from '../../components/common' import { useRemote } from '../../hooks/useRemote' import { api } from '../../services/practice' import { addDays, addMonths, duration, monthStart, shanghaiToday } from '../../utils/dates' @@ -19,10 +19,9 @@ export default function Growth() { } const currentPeriod = review && (period === 'week' ? review.to >= review.today : monthStart(review.from) === monthStart(review.today)) return - {review && <> - 累计练习{duration(review.allTime.totalMinutes)}每一分钟,都来自你留下的真实记录。 + 累计练习{duration(review.allTime.totalMinutes)} {review.allTime.practiceDays} 天练习天数{review.allTime.recordCount} 次练习记录 时长趋势{(['week', 'month'] as const).map(value => )} @@ -34,12 +33,11 @@ export default function Growth() { {focus ? `${focus.date} · ${duration(focus.minutes)}` : '点按柱形查看当天时长;空白表示没有记录。'} {!currentPeriod && } - 时间花在哪里全部练习 + 项目时长全部练习 {review.allTime.projects.length ? review.allTime.projects.map(project => {project.name}{project.archived ? ' · 已归档' : ''}{(project.minutes / review.allTime.totalMinutes * 100).toFixed(1)}% {duration(project.minutes)} - ) : } - 时长记录的是投入,不是技术水平。慢慢来,按自己的节奏练习。 + ) : } } } diff --git a/miniprogram/src/pages/index/index.tsx b/miniprogram/src/pages/index/index.tsx index 3cef6ba..8f97000 100644 --- a/miniprogram/src/pages/index/index.tsx +++ b/miniprogram/src/pages/index/index.tsx @@ -1,29 +1,41 @@ import Taro from '@tarojs/taro' -import { Text, View } from '@tarojs/components' -import { Button, Empty, LoadState, PageHeading, RecordCards } from '../../components/common' +import { Image, Text, View } from '@tarojs/components' +import addProjectIcon from '../../assets/action-icons/add-project.svg' +import calendarIcon from '../../assets/action-icons/calendar.svg' +import manageListIcon from '../../assets/action-icons/manage-list.svg' +import { Button, Empty, LoadState } from '../../components/common' +import { ProjectIcon } from '../../components/project-icon' import { useRemote } from '../../hooks/useRemote' import { api } from '../../services/practice' -import { duration } from '../../utils/dates' + +function hours(minutes: number) { return Number((minutes / 60).toFixed(2)) } export default function Index() { const state = useRemote(async () => { - const [projects, recent, review] = await Promise.all([api.projects(), api.records(), api.review('week')]) - return { ...projects, recent, review } + const [projects, review] = await Promise.all([api.projects(), api.review('week')]) + return { ...projects, review } }) const data = state.data - const todayMinutes = data?.review.days.find(day => day.date === data.today)?.minutes || 0 + const projectMinutes = new Map(data?.review.allTime.projects.map(project => [project.projectId, project.minutes]) || []) + const firstDate = data?.review.allTime.firstDate return - {data && <> - 今天的练习{duration(todayMinutes)}{data.today} · 按实际练习日期记录 - 这次练了什么 - {data.projects.length ? {data.projects.map((project, index) => { void Taro.navigateTo({ url: `/pages/record/index?projectId=${project.id}&date=${data.today}` }) }}> - {String(index + 1).padStart(2, '0')}{project.name}记录练习 + - )} : } - 近期记录最近 20 条 - {data.recent.records.length ? : } - + + 总记录时间{hours(data.review.allTime.totalMinutes)} h + + 坚持天数{data.review.allTime.practiceDays} 天 + {firstDate && 从 {firstDate.slice(0, 4)} 年 {firstDate.slice(5, 7)} 月 {firstDate.slice(8, 10)} 日} + + + + + + updateProfile({ ...profileRef.current, nickname: event.detail.value })} /> + 点击头像或昵称,选择微信资料 + + + {error && {error}} + 头像和昵称仅保存在当前设备 + +} diff --git a/miniprogram/src/pages/projects-sort/index.config.ts b/miniprogram/src/pages/projects-sort/index.config.ts new file mode 100644 index 0000000..4213441 --- /dev/null +++ b/miniprogram/src/pages/projects-sort/index.config.ts @@ -0,0 +1 @@ +export default definePageConfig({ navigationBarTitleText: '管理列表' }) diff --git a/miniprogram/src/pages/projects-sort/index.tsx b/miniprogram/src/pages/projects-sort/index.tsx new file mode 100644 index 0000000..126628b --- /dev/null +++ b/miniprogram/src/pages/projects-sort/index.tsx @@ -0,0 +1,107 @@ +import { useEffect, useRef, useState } from 'react' +import Taro from '@tarojs/taro' +import { Image, Text, View, type ITouchEvent } from '@tarojs/components' +import dragHandleIcon from '../../assets/action-icons/drag-handle.svg' +import { Empty, FormActions, LoadState, returnToSource } from '../../components/common' +import { ProjectIcon } from '../../components/project-icon' +import { useRemote } from '../../hooks/useRemote' +import { api, ApiError, errorMessage, type Project } from '../../services/practice' + +interface DragState { + id: string + originIndex: number + targetIndex: number + startY: number + centers: number[] +} + +function moveProject(projects: Project[], from: number, to: number) { + const next = [...projects] + next.splice(to, 0, next.splice(from, 1)[0]) + return next +} + +export default function ProjectsSort() { + const state = useRemote(() => api.projects()) + const [projects, setProjects] = useState([]) + const [dragging, setDragging] = useState<{ id: string; offset: number; targetIndex: number }>() + const dragRef = useRef() + const busyRef = useRef(false) + const [busy, setBusy] = useState(false) + const [error, setError] = useState('') + + useEffect(() => { if (state.data) setProjects(state.data.projects) }, [state.data]) + + const dirty = !!state.data && projects.length === state.data.projects.length && + projects.some((project, index) => project.id !== state.data!.projects[index].id) + + function beginDrag(id: string, event: ITouchEvent) { + if (busyRef.current || dragRef.current) return + const startY = event.touches[0]?.clientY + const originIndex = projects.findIndex(project => project.id === id) + if (startY === undefined || originIndex < 0) return + dragRef.current = { id, originIndex, targetIndex: originIndex, startY, centers: [] } + setDragging({ id, offset: 0, targetIndex: originIndex }) + const query = Taro.createSelectorQuery() + query.selectAll('.sortable-project').boundingClientRect(rects => { + if (dragRef.current?.id !== id || !Array.isArray(rects)) return + dragRef.current.centers = rects.map(rect => rect.top + rect.height / 2) + }) + query.exec() + } + + function updateDrag(event: ITouchEvent) { + const current = dragRef.current + const y = event.touches[0]?.clientY ?? event.changedTouches[0]?.clientY + if (!current || y === undefined) return + const offset = y - current.startY + if (current.centers.length === projects.length) { + const center = current.centers[current.originIndex] + offset + let closest = current.originIndex + for (let index = 0; index < current.centers.length; index++) { + if (Math.abs(current.centers[index] - center) < Math.abs(current.centers[closest] - center)) closest = index + } + current.targetIndex = closest + } + setDragging({ id: current.id, offset, targetIndex: current.targetIndex }) + } + + function endDrag(event: ITouchEvent) { + const current = dragRef.current + if (!current) return + updateDrag(event) + dragRef.current = undefined + setDragging(undefined) + if (current.targetIndex !== current.originIndex) { + setProjects(previous => moveProject(previous, current.originIndex, current.targetIndex)) + setError('') + } + } + + async function save() { + if (busyRef.current || dragRef.current || !dirty) return + busyRef.current = true; setBusy(true); setError('') + try { + await api.reorderProjects(projects.map(project => project.id)) + await returnToSource('顺序已保存') + } catch (cause) { setError(cause instanceof ApiError && cause.status === 400 ? '项目列表已变化,请返回后重新打开排序页。' : errorMessage(cause)) } + finally { busyRef.current = false; setBusy(false) } + } + + return + + {state.data && <> + 按住右侧图标拖动项目,完成后保存。 + {error && {error}} + {projects.length ? + {projects.map((project, index) => + {project.name} + beginDrag(project.id, event as ITouchEvent)} onTouchMove={event => updateDrag(event as ITouchEvent)} onTouchEnd={event => endDrag(event as ITouchEvent)} onTouchCancel={() => { dragRef.current = undefined; setDragging(undefined) }}> + + + )} + : } + returnToSource()} onSave={save} /> + } + +} diff --git a/miniprogram/src/pages/projects/index.tsx b/miniprogram/src/pages/projects/index.tsx index 6731cb2..d518413 100644 --- a/miniprogram/src/pages/projects/index.tsx +++ b/miniprogram/src/pages/projects/index.tsx @@ -1,7 +1,9 @@ import { useRef, useState } from 'react' import Taro from '@tarojs/taro' import { Input, Text, View } from '@tarojs/components' -import { Button, Empty, LoadState, PageHeading } from '../../components/common' +import { Button, Empty, FormActions, LoadState } from '../../components/common' +import { ProjectIcon } from '../../components/project-icon' +import { ProjectIconPicker } from '../../components/project-icon-picker' import { useRemote } from '../../hooks/useRemote' import { api, errorMessage, NAME_LIMIT, type Project } from '../../services/practice' @@ -9,6 +11,7 @@ export default function Projects() { const state = useRemote(() => api.projects()) const [editing, setEditing] = useState() const [name, setName] = useState('') + const [iconId, setIconId] = useState('') const [busy, setBusy] = useState(false) const busyRef = useRef(false) const [error, setError] = useState('') @@ -20,9 +23,9 @@ export default function Projects() { if (!clean || Array.from(clean).length > NAME_LIMIT || clean.includes('\0')) { setError(`项目名称须为 1–${NAME_LIMIT} 字,不能只含空白或包含空字符`); return } busyRef.current = true; setBusy(true); setError(''); setNotice('') try { - await api.saveProject(clean, editing?.id) - setNotice(editing ? '已改名,历史记录也会使用这个名称。' : '项目已添加。') - setName(''); setEditing(undefined) + await api.saveProject(clean, editing?.id, iconId) + setNotice(editing ? '项目已更新。' : '项目已添加。') + setName(''); setIconId(''); setEditing(undefined) await state.reload() } catch (cause) { setError(errorMessage(cause)) } finally { busyRef.current = false; setBusy(false) } @@ -35,35 +38,33 @@ export default function Projects() { const confirmed = await Taro.showModal({ title: project.hasRecords ? '移出常用项目?' : '移除这个项目?', content: project.hasRecords ? `“${project.name}”将归档,历史记录、笔记和累计时长都会保留。` : `“${project.name}”没有记录时会被删除。如果已有新记录,将改为归档并保留历史。`, - confirmText: '确认移除', cancelText: '保留项目', + confirmText: '移除', cancelText: '取消', }) if (!confirmed.confirm) return const result = await api.removeProject(project.id) - if (editing?.id === project.id) { setEditing(undefined); setName('') } + if (editing?.id === project.id) { setEditing(undefined); setName(''); setIconId('') } setNotice(result.action === 'archived' ? '已归档,历史记录和时长均已保留。' : '未使用的项目已删除。') await state.reload() } catch (cause) { setError(errorMessage(cause)) } finally { busyRef.current = false; setBusy(false) } } - return - + return - {editing ? '修改项目名称' : '添加练习项目'} - setName(event.detail.value)} /> + 项目名称 + setName(event.detail.value)} /> 最多 {NAME_LIMIT} 字。改名后,历史记录会统一显示新名称。 - - {editing && } + {error && {error}} {notice && {notice}} 常用项目{state.data ? `${state.data.projects.length} 个` : ''} - {state.data && - {state.data.projects.length ? state.data.projects.map(project => - {project.name}{project.hasRecords ? '已有练习 · 移除会保留历史' : '尚无练习记录'} - ) : } - } - 有练习记录的项目会归档,不会删除历史。归档项目仍计入成长,也能从原记录进入更正。 + {state.data && (state.data.projects.length ? + {state.data.projects.map(project => + {project.name} + )} + : )} + { setEditing(undefined); setName(''); setIconId(''); setError(''); setNotice('') }} onSave={save} /> } diff --git a/miniprogram/src/pages/record/index.tsx b/miniprogram/src/pages/record/index.tsx index f9203dc..3bc92b2 100644 --- a/miniprogram/src/pages/record/index.tsx +++ b/miniprogram/src/pages/record/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import Taro, { useDidHide, useDidShow, useRouter, useUnload } from '@tarojs/taro' import { Input, Picker, Text, Textarea, View } from '@tarojs/components' -import { Button, LoadState, PageHeading, returnToSource } from '../../components/common' +import { Button, FormActions, LoadState, returnToSource } from '../../components/common' import { useRemote } from '../../hooks/useRemote' import { api, ApiError, clearPendingRecord, errorMessage, getPendingRecord, MINUTE_LIMIT, NOTE_LIMIT, retainPendingRecord, submissionID, type PendingRecord } from '../../services/practice' import { validDate } from '../../utils/dates' @@ -93,7 +93,7 @@ export default function RecordForm() { if (!id || busyRef.current || uncertain) return busyRef.current = true; setBusy(true) try { - const result = await Taro.showModal({ title: '删除这条误记?', content: '这条记录的时长和笔记将被删除,日历与成长统计会一起更新。', confirmText: '删除记录', confirmColor: '#a23d4c', cancelText: '保留记录' }) + const result = await Taro.showModal({ title: '删除这条误记?', content: '这条记录的时长和笔记将被删除,日历与成长统计会一起更新。', confirmText: '删除', confirmColor: '#a23d4c', cancelText: '取消' }) if (!result.confirm) return setError('') await api.deleteRecord(id) @@ -103,7 +103,6 @@ export default function RecordForm() { } return - {!context && } {context && !done && <> {resumed.current && uncertain && 上次保存尚未确认,已恢复当时的项目、日期和填写内容。请先重试确认这次保存。} @@ -112,20 +111,20 @@ export default function RecordForm() { `${project.name}${project.archived ? '(已归档)' : ''}`)]} value={choices.findIndex(project => project.id === projectId) + 1} disabled={locked || !choices.length} onChange={event => setProjectId(choices[Number(event.detail.value) - 1]?.id || '')}> {selected ? `${selected.name}${selected.archived ? '(已归档)' : ''}` : '请选择项目'}⌄ - {!choices.length && 先添加一个项目,就能开始记录。} + {!choices.length && } {selected?.archived && 这条历史记录可以保留原项目,也可改选常用项目。} 实际练习日期 setDate(event.detail.value)}>{date}⌄ 整堂时长 · 分钟 - setMinutes(event.detail.value)} /> + setMinutes(event.detail.value)} /> {[30, 45, 60, 90].map(value => )} 练习笔记 选填 -