diff --git a/docs/技术架构设计文档.md b/docs/技术架构设计文档.md index f362009..b5c1bc5 100644 --- a/docs/技术架构设计文档.md +++ b/docs/技术架构设计文档.md @@ -12,12 +12,14 @@ ### 1.1 核心原则 -| 原则 | 说明 | -| ------------ | ------------------------------------------------------------------------------------------------- | -| **成本优先** | 现阶段用云开发免费额度控制成本;**2026-09-15** 前复盘是否续用或迁移自建;核心逻辑仍保持前端执行 | -| **渐进增强** | 先小程序跑通,后续再扩展 PC Web,不为未来过度设计 | -| **前端为主** | Canvas 渲染、随机生成、PNG 导出等核心逻辑保持前端执行,零服务器成本;后端仅做必要的数据存储和服务 | -| **可迁移** | 核心绘制逻辑与平台 API 解耦,为将来迁移 Web 做准备 | + +| 原则 | 说明 | +| -------- | ------------------------------------------------------- | +| **成本优先** | 现阶段用云开发免费额度控制成本;**2026-09-15** 前复盘是否续用或迁移自建;核心逻辑仍保持前端执行 | +| **渐进增强** | 先小程序跑通,后续再扩展 PC Web,不为未来过度设计 | +| **前端为主** | Canvas 渲染、随机生成、PNG 导出等核心逻辑保持前端执行,零服务器成本;后端仅做必要的数据存储和服务 | +| **可迁移** | 核心绘制逻辑与平台 API 解耦,为将来迁移 Web 做准备 | + ### 1.2 后端方案决策 @@ -51,15 +53,17 @@ #### 方案对比与选择 -| 维度 | 微信云开发 | 自有云服务器(NestJS) | 现阶段决策(至 2026-09-15) | -| ----------- | ----------------------------- | --------------------------- | --------------------------- | -| 接入成本 | ⭐ 极低(原生集成) | ⭐⭐ 需搭建部署(~3-5天) | **云开发 ✅** | -| 运维成本 | ⭐ 免运维 | ⭐⭐ 需简单维护(PM2 守护) | **云开发 ✅** | -| 费用 | 免费额度内 ¥0;超出按套餐计费 | 已有服务器可零增量 | **云开发(阶段内)✅** | -| 小程序集成 | ⭐ 鉴权零成本 | ⭐⭐ 需对接微信登录+JWT | **云开发 ✅** | -| PC Web 支持 | ⭐⭐ 需通过 HTTP API 桥接 | ⭐ RESTful API 天然支持 | 远期再评估 NestJS | -| 灵活性 | ⭐⭐ 受限于云开发 SDK | ⭐ 完全自由 | 到期后按需评估 | -| 数据迁移 | ⭐⭐ 可导出 | ⭐ 标准 MySQL | 到期后按需评估 | + +| 维度 | 微信云开发 | 自有云服务器(NestJS) | 现阶段决策(至 2026-09-15) | +| --------- | ------------------ | ------------------ | ------------------- | +| 接入成本 | ⭐ 极低(原生集成) | ⭐⭐ 需搭建部署(~3-5天) | **云开发 ✅** | +| 运维成本 | ⭐ 免运维 | ⭐⭐ 需简单维护(PM2 守护) | **云开发 ✅** | +| 费用 | 免费额度内 ¥0;超出按套餐计费 | 已有服务器可零增量 | **云开发(阶段内)✅** | +| 小程序集成 | ⭐ 鉴权零成本 | ⭐⭐ 需对接微信登录+JWT | **云开发 ✅** | +| PC Web 支持 | ⭐⭐ 需通过 HTTP API 桥接 | ⭐ RESTful API 天然支持 | 远期再评估 NestJS | +| 灵活性 | ⭐⭐ 受限于云开发 SDK | ⭐ 完全自由 | 到期后按需评估 | +| 数据迁移 | ⭐⭐ 可导出 | ⭐ 标准 MySQL | 到期后按需评估 | + **最终决策(分阶段)** @@ -162,14 +166,16 @@ miniprogram/ **问题分析:** -| 问题 | 详情 | -| -------------- | ------------------------------------------------------------------- | -| Mixin 模式笨重 | `pageMixin` 通过对象展开合并,无类型安全,难以追踪数据流 | -| Draw 服务分散 | 每个分包下各有 `shared/service/`,大量重复的绘制工具方法 | -| 平台耦合严重 | Draw 服务直接使用 `wx.createImage`、`canvas.createImage` 等 API | + +| 问题 | 详情 | +| ---------- | ------------------------------------------------------------- | +| Mixin 模式笨重 | `pageMixin` 通过对象展开合并,无类型安全,难以追踪数据流 | +| Draw 服务分散 | 每个分包下各有 `shared/service/`,大量重复的绘制工具方法 | +| 平台耦合严重 | Draw 服务直接使用 `wx.createImage`、`canvas.createImage` 等 API | | 模板重复 | `mathPages` 和 `focusPages` 的 `canvas-page-template.wxml` 内容相同 | -| 无共享核心 | 数据模型、生成算法与 UI 逻辑混在一起,无法跨平台复用 | -| 状态管理原始 | 纯 `setData`,无统一的状态管理方案 | +| 无共享核心 | 数据模型、生成算法与 UI 逻辑混在一起,无法跨平台复用 | +| 状态管理原始 | 纯 `setData`,无统一的状态管理方案 | + ### 3.2 目标架构 @@ -958,21 +964,25 @@ createPage({ ### 6.1 现阶段:微信云开发(摘要) -| 组件 | 技术 | 说明 | -| ---------- | ---------------------------- | -------------------------------------- | -| 数据 | 云数据库(文档型) | worksheets / categories / users 等集合 | -| 文件 | 云存储(COS) | 预览图、涂色线稿、字体等 | -| 逻辑 | 云函数(Node.js) | 统一入口路由、业务 action | -| 小程序接入 | `wx.cloud` + `cloud-adapter` | 与业务服务层解耦,便于日后替换为 HTTP | + +| 组件 | 技术 | 说明 | +| ----- | ---------------------------- | ----------------------------------- | +| 数据 | 云数据库(文档型) | worksheets / categories / users 等集合 | +| 文件 | 云存储(COS) | 预览图、涂色线稿、字体等 | +| 逻辑 | 云函数(Node.js) | 统一入口路由、业务 action | +| 小程序接入 | `wx.cloud` + `cloud-adapter` | 与业务服务层解耦,便于日后替换为 HTTP | + ### 6.2 远期可选:自建 NestJS(摘要) -| 组件 | 技术 | 说明 | -| ---- | ----------------------- | --------------------------- | -| 应用 | NestJS + TypeScript | RESTful API,多端统一 | -| 数据 | MySQL 8.0 + Prisma | 关系型存储与迁移 | -| 文件 | 本地磁盘 + Nginx | 静态素材 URL 由自有域名提供 | -| 鉴权 | 微信 code2Session + JWT | 小程序与 Web 可共用逻辑 | + +| 组件 | 技术 | 说明 | +| --- | --------------------- | ---------------- | +| 应用 | NestJS + TypeScript | RESTful API,多端统一 | +| 数据 | MySQL 8.0 + Prisma | 关系型存储与迁移 | +| 文件 | 本地磁盘 + Nginx | 静态素材 URL 由自有域名提供 | +| 鉴权 | 微信 code2Session + JWT | 小程序与 Web 可共用逻辑 | + (接口路径、表结构、部署步骤等以 [后端部署方案](./后端部署方案.md) 为准,此处不重复。) @@ -1180,46 +1190,53 @@ PDF 导出方案(远期增值功能,会员专属): ## 十一、技术风险与应对 -| 风险 | 影响 | 应对方案 | -| -------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------- | -| 云开发免费额度不足 | 功能受限或需付费 | ① 优化查询与缓存 ② 控制云函数调用 ③ **2026-09-15 前**复盘是否升级套餐或迁移 [后端部署方案](./后端部署方案.md) | -| 服务器宕机(迁移自建后) | 后端不可用 | ① PM2 自动重启 ② 前端兜底数据 ③ 定期备份数据库 | -| Canvas 兼容性 | 低端机渲染异常 | ① 使用 Canvas 2D(已采用)② 控制单次绘制复杂度 ③ 降级方案 | -| 小程序包体积 | 超 2MB 限制 | ① 素材用云存储 / CDN ② 合理分包 ③ 图片压缩 | -| 素材制作瓶颈 | 涂色卡等需设计资源 | ① 使用开源 SVG 素材 ② AI 生成线稿 ③ 社区投稿 | -| 跨平台迁移成本 | Web 端重复开发 | ① core/ 层保持平台无关 ② 适配器隔离差异(详见 [PC-Web端技术预案](./PC-Web端技术预案.md)) | -| 带宽与加载(迁移自建远期) | 素材加载慢 | ① 图片压缩 ② 长缓存 ③ 可接入 CDN | + +| 风险 | 影响 | 应对方案 | +| ------------- | --------- | ----------------------------------------------------------------------- | +| 云开发免费额度不足 | 功能受限或需付费 | ① 优化查询与缓存 ② 控制云函数调用 ③ **2026-09-15 前**复盘是否升级套餐或迁移 [后端部署方案](./后端部署方案.md) | +| 服务器宕机(迁移自建后) | 后端不可用 | ① PM2 自动重启 ② 前端兜底数据 ③ 定期备份数据库 | +| Canvas 兼容性 | 低端机渲染异常 | ① 使用 Canvas 2D(已采用)② 控制单次绘制复杂度 ③ 降级方案 | +| 小程序包体积 | 超 2MB 限制 | ① 素材用云存储 / CDN ② 合理分包 ③ 图片压缩 | +| 素材制作瓶颈 | 涂色卡等需设计资源 | ① 使用开源 SVG 素材 ② AI 生成线稿 ③ 社区投稿 | +| 跨平台迁移成本 | Web 端重复开发 | ① core/ 层保持平台无关 ② 适配器隔离差异(详见 [PC-Web端技术预案](./PC-Web端技术预案.md)) | +| 带宽与加载(迁移自建远期) | 素材加载慢 | ① 图片压缩 ② 长缓存 ③ 可接入 CDN | + --- ## 附录 A:现有代码与目标架构映射 -| 现有文件 | 目标位置 | 迁移动作 | -| -------------------------------- | -------------------------------------- | ------------------------------------------------ | -| `constants/mathFunctions.ts` | `core/data/worksheets.ts` | 合并,增加字段 | -| `constants/focusFunctions.ts` | `core/data/worksheets.ts` | 合并,增加字段 | -| `constants/words.ts` | `core/data/words.ts` | 移动 | -| `constants/colors.ts` | `core/data/colors.ts` | 移动 | -| `constants/shapes.ts` | `core/data/shapes.ts` | 移动 | -| `service/baseDraw.ts` | `core/draw/base-draw.ts` | 重构,去除 wx.\* 依赖 | -| `service/wordDrawService.ts` | `core/draw/chinese-draw/word-draw.ts` | 移动,去除 wx.\* | -| `service/drawServiceFactory.ts` | `core/draw/draw-factory.ts` | 扩展 | -| `mathPages/shared/service/*.ts` | `core/draw/legacy/math-draw/*.ts` | 移入 legacy,去除 wx.\*,逐步迁移到模板引擎 | -| `focusPages/shared/service/*.ts` | `core/draw/legacy/focus-draw/*.ts` | 移入 legacy,去除 wx.\*,逐步迁移到模板引擎 | -| `base/pageMixin.ts` | `services/print-service.ts` + 页面代码 | 拆分职责 | -| `base/callCloud.ts` | `platform/cloud-adapter.ts` | 封装 wx.cloud;迁移 NestJS 后再考虑 http-adapter | -| `utils/saveImage.ts` | `platform/canvas-adapter.ts` | 合并到适配器 | -| `utils/downloadPrint.ts` | `services/print-service.ts` | 整合 | -| `utils/tracker.ts` | `services/stats-service.ts` | 整合 | -| `config/config.ts` | `core/models/print-config.ts` | 类型化 | + +| 现有文件 | 目标位置 | 迁移动作 | +| -------------------------------- | ------------------------------------- | --------------------------------------- | +| `constants/mathFunctions.ts` | `core/data/worksheets.ts` | 合并,增加字段 | +| `constants/focusFunctions.ts` | `core/data/worksheets.ts` | 合并,增加字段 | +| `constants/words.ts` | `core/data/words.ts` | 移动 | +| `constants/colors.ts` | `core/data/colors.ts` | 移动 | +| `constants/shapes.ts` | `core/data/shapes.ts` | 移动 | +| `service/baseDraw.ts` | `core/draw/base-draw.ts` | 重构,去除 wx. 依赖 | +| `service/wordDrawService.ts` | `core/draw/chinese-draw/word-draw.ts` | 移动,去除 wx. | +| `service/drawServiceFactory.ts` | `core/draw/draw-factory.ts` | 扩展 | +| `mathPages/shared/service/*.ts` | `core/draw/legacy/math-draw/*.ts` | 移入 legacy,去除 wx.,逐步迁移到模板引擎 | +| `focusPages/shared/service/*.ts` | `core/draw/legacy/focus-draw/*.ts` | 移入 legacy,去除 wx.,逐步迁移到模板引擎 | +| `base/pageMixin.ts` | `services/print-service.ts` + 页面代码 | 拆分职责 | +| `base/callCloud.ts` | `platform/cloud-adapter.ts` | 封装 wx.cloud;迁移 NestJS 后再考虑 http-adapter | +| `utils/saveImage.ts` | `platform/canvas-adapter.ts` | 合并到适配器 | +| `utils/downloadPrint.ts` | `services/print-service.ts` | 整合 | +| `utils/tracker.ts` | `services/stats-service.ts` | 整合 | +| `config/config.ts` | `core/models/print-config.ts` | 类型化 | + ## 附录 B:技术选型决策记录 -| 决策点 | 选项 | 决策 | 理由 | -| --------- | ---------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| 跨端框架 | Taro / uni-app / 原生 | **原生** | 项目已用原生开发,迁移成本高;Taro 等框架对 Canvas 2D 支持有限;core/ 层抽取足以实现代码复用 | -| 状态管理 | MobX / 自定义 / 原生 setData | **轻量自定义** | 小程序场景简单,不需要 Redux 级方案;页面级 setData + 全局 Store 够用 | -| UI 组件库 | Vant / 自研 | **Vant + 自定义组件** | 已用 Vant,保持;业务组件自研 | -| 后端方案 | 云开发 / 自有云服务器 | **现阶段云开发(至 2026-09-15)** | 详见 [小程序云开发方案](./小程序云开发方案.md);到期后可选迁移 [后端部署方案](./后端部署方案.md)(NestJS) | -| 导出方案 | PNG 保存相册 / PDF | **PNG 为主,PDF 为增值** | PNG 保存到相册是手机端最短路径(用户教育成本低、零服务器开销);PDF 作为远期会员增值功能,服务端 pdfkit 合并,Web 用 jsPDF | -| Monorepo | pnpm workspace / Turborepo | **暂不采用** | 当前只有小程序,过早引入增加复杂度;core/ 作为目录组织,未来再拆包 | + +| 决策点 | 选项 | 决策 | 理由 | +| -------- | -------------------------- | ------------------------ | -------------------------------------------------------------------------- | +| 跨端框架 | Taro / uni-app / 原生 | **原生** | 项目已用原生开发,迁移成本高;Taro 等框架对 Canvas 2D 支持有限;core/ 层抽取足以实现代码复用 | +| 状态管理 | MobX / 自定义 / 原生 setData | **轻量自定义** | 小程序场景简单,不需要 Redux 级方案;页面级 setData + 全局 Store 够用 | +| UI 组件库 | Vant / 自研 | **Vant + 自定义组件** | 已用 Vant,保持;业务组件自研 | +| 后端方案 | 云开发 / 自有云服务器 | **现阶段云开发(至 2026-09-15)** | 详见 [小程序云开发方案](./小程序云开发方案.md);到期后可选迁移 [后端部署方案](./后端部署方案.md)(NestJS) | +| 导出方案 | PNG 保存相册 / PDF | **PNG 为主,PDF 为增值** | PNG 保存到相册是手机端最短路径(用户教育成本低、零服务器开销);PDF 作为远期会员增值功能,服务端 pdfkit 合并,Web 用 jsPDF | +| Monorepo | pnpm workspace / Turborepo | **暂不采用** | 当前只有小程序,过早引入增加复杂度;core/ 作为目录组织,未来再拆包 | + + diff --git a/miniprogram/app.json b/miniprogram/app.json index 12f1834..dca1fff 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -4,13 +4,8 @@ "pages/age/age", "pages/favorites/favorites", "pages/profile/profile", - "pages/category/category", "pages/guide/guide", - "pages/index/index", - "pages/copyBook/copyBook", - "pages/mathIndex/mathIndex", - "pages/focusIndex/focusIndex", - "pages/debug/debug" + "pages/index/index" ], "subPackages": [ { @@ -59,10 +54,6 @@ "pages/home/home": { "network": "all", "packages": ["mathPages", "focusPages"] - }, - "pages/category/category": { - "network": "all", - "packages": ["mathPages", "focusPages"] } }, "window": {}, @@ -85,33 +76,33 @@ } }, "tabBar": { - "color": "#999999", - "selectedColor": "#FFD709", + "color": "#322e25", + "selectedColor": "#322e25", "backgroundColor": "#F8F0E0", "borderStyle": "white", "list": [ { "pagePath": "pages/home/home", - "iconPath": "assets/tabBar/icon-home.png", - "selectedIconPath": "assets/tabBar/icon-home-active.png", + "iconPath": "assets/tabBar/home.png", + "selectedIconPath": "assets/tabBar/home-active.png", "text": "发现" }, { "pagePath": "pages/age/age", - "iconPath": "assets/tabBar/icon-age.png", - "selectedIconPath": "assets/tabBar/icon-age-active.png", + "iconPath": "assets/tabBar/age.png", + "selectedIconPath": "assets/tabBar/age-active.png", "text": "分龄" }, { "pagePath": "pages/favorites/favorites", - "iconPath": "assets/tabBar/icon-fav.png", - "selectedIconPath": "assets/tabBar/icon-fav-active.png", + "iconPath": "assets/tabBar/favorites.png", + "selectedIconPath": "assets/tabBar/favorites-active.png", "text": "收藏" }, { "pagePath": "pages/profile/profile", - "iconPath": "assets/tabBar/icon-profile.png", - "selectedIconPath": "assets/tabBar/icon-profile-active.png", + "iconPath": "assets/tabBar/profile.png", + "selectedIconPath": "assets/tabBar/profile-active.png", "text": "我的" } ] diff --git a/miniprogram/assets/tabBar/icon-age-active.png b/miniprogram/assets/tabBar/2.0/icon-age-active.png similarity index 100% rename from miniprogram/assets/tabBar/icon-age-active.png rename to miniprogram/assets/tabBar/2.0/icon-age-active.png diff --git a/miniprogram/assets/tabBar/icon-age.png b/miniprogram/assets/tabBar/2.0/icon-age.png similarity index 100% rename from miniprogram/assets/tabBar/icon-age.png rename to miniprogram/assets/tabBar/2.0/icon-age.png diff --git a/miniprogram/assets/tabBar/icon-edit-active.png b/miniprogram/assets/tabBar/2.0/icon-edit-active.png similarity index 100% rename from miniprogram/assets/tabBar/icon-edit-active.png rename to miniprogram/assets/tabBar/2.0/icon-edit-active.png diff --git a/miniprogram/assets/tabBar/icon-edit.png b/miniprogram/assets/tabBar/2.0/icon-edit.png similarity index 100% rename from miniprogram/assets/tabBar/icon-edit.png rename to miniprogram/assets/tabBar/2.0/icon-edit.png diff --git a/miniprogram/assets/tabBar/icon-fav-active.png b/miniprogram/assets/tabBar/2.0/icon-fav-active.png similarity index 100% rename from miniprogram/assets/tabBar/icon-fav-active.png rename to miniprogram/assets/tabBar/2.0/icon-fav-active.png diff --git a/miniprogram/assets/tabBar/icon-fav.png b/miniprogram/assets/tabBar/2.0/icon-fav.png similarity index 100% rename from miniprogram/assets/tabBar/icon-fav.png rename to miniprogram/assets/tabBar/2.0/icon-fav.png diff --git a/miniprogram/assets/tabBar/icon-home-active.png b/miniprogram/assets/tabBar/2.0/icon-home-active.png similarity index 100% rename from miniprogram/assets/tabBar/icon-home-active.png rename to miniprogram/assets/tabBar/2.0/icon-home-active.png diff --git a/miniprogram/assets/tabBar/icon-home.png b/miniprogram/assets/tabBar/2.0/icon-home.png similarity index 100% rename from miniprogram/assets/tabBar/icon-home.png rename to miniprogram/assets/tabBar/2.0/icon-home.png diff --git a/miniprogram/assets/tabBar/icon-math-active.png b/miniprogram/assets/tabBar/2.0/icon-math-active.png similarity index 100% rename from miniprogram/assets/tabBar/icon-math-active.png rename to miniprogram/assets/tabBar/2.0/icon-math-active.png diff --git a/miniprogram/assets/tabBar/icon-math.png b/miniprogram/assets/tabBar/2.0/icon-math.png similarity index 100% rename from miniprogram/assets/tabBar/icon-math.png rename to miniprogram/assets/tabBar/2.0/icon-math.png diff --git a/miniprogram/assets/tabBar/icon-profile-active.png b/miniprogram/assets/tabBar/2.0/icon-profile-active.png similarity index 100% rename from miniprogram/assets/tabBar/icon-profile-active.png rename to miniprogram/assets/tabBar/2.0/icon-profile-active.png diff --git a/miniprogram/assets/tabBar/icon-profile.png b/miniprogram/assets/tabBar/2.0/icon-profile.png similarity index 100% rename from miniprogram/assets/tabBar/icon-profile.png rename to miniprogram/assets/tabBar/2.0/icon-profile.png diff --git a/miniprogram/assets/tabBar/icon-shape-active.png b/miniprogram/assets/tabBar/2.0/icon-shape-active.png similarity index 100% rename from miniprogram/assets/tabBar/icon-shape-active.png rename to miniprogram/assets/tabBar/2.0/icon-shape-active.png diff --git a/miniprogram/assets/tabBar/icon-shape.png b/miniprogram/assets/tabBar/2.0/icon-shape.png similarity index 100% rename from miniprogram/assets/tabBar/icon-shape.png rename to miniprogram/assets/tabBar/2.0/icon-shape.png diff --git a/miniprogram/assets/tabBar/icon-word-active.png b/miniprogram/assets/tabBar/2.0/icon-word-active.png similarity index 100% rename from miniprogram/assets/tabBar/icon-word-active.png rename to miniprogram/assets/tabBar/2.0/icon-word-active.png diff --git a/miniprogram/assets/tabBar/icon-word.png b/miniprogram/assets/tabBar/2.0/icon-word.png similarity index 100% rename from miniprogram/assets/tabBar/icon-word.png rename to miniprogram/assets/tabBar/2.0/icon-word.png diff --git a/miniprogram/assets/tabBar/age-active.png b/miniprogram/assets/tabBar/age-active.png new file mode 100644 index 0000000..e77de56 Binary files /dev/null and b/miniprogram/assets/tabBar/age-active.png differ diff --git a/miniprogram/assets/tabBar/age.png b/miniprogram/assets/tabBar/age.png new file mode 100644 index 0000000..13182d9 Binary files /dev/null and b/miniprogram/assets/tabBar/age.png differ diff --git a/miniprogram/assets/tabBar/favorites-active.png b/miniprogram/assets/tabBar/favorites-active.png new file mode 100644 index 0000000..4f65008 Binary files /dev/null and b/miniprogram/assets/tabBar/favorites-active.png differ diff --git a/miniprogram/assets/tabBar/favorites.png b/miniprogram/assets/tabBar/favorites.png new file mode 100644 index 0000000..bcafb1e Binary files /dev/null and b/miniprogram/assets/tabBar/favorites.png differ diff --git a/miniprogram/assets/tabBar/home-active.png b/miniprogram/assets/tabBar/home-active.png new file mode 100644 index 0000000..f01ead4 Binary files /dev/null and b/miniprogram/assets/tabBar/home-active.png differ diff --git a/miniprogram/assets/tabBar/home.png b/miniprogram/assets/tabBar/home.png new file mode 100644 index 0000000..4b9d345 Binary files /dev/null and b/miniprogram/assets/tabBar/home.png differ diff --git a/miniprogram/assets/tabBar/profile-active.png b/miniprogram/assets/tabBar/profile-active.png new file mode 100644 index 0000000..4441a5d Binary files /dev/null and b/miniprogram/assets/tabBar/profile-active.png differ diff --git a/miniprogram/assets/tabBar/profile.png b/miniprogram/assets/tabBar/profile.png new file mode 100644 index 0000000..7620083 Binary files /dev/null and b/miniprogram/assets/tabBar/profile.png differ diff --git a/miniprogram/pages/age/age.json b/miniprogram/pages/age/age.json index a7351f1..37e2fd0 100644 --- a/miniprogram/pages/age/age.json +++ b/miniprogram/pages/age/age.json @@ -1,8 +1,8 @@ { - "navigationBarTitleText": "分龄推荐", + "navigationStyle": "custom", "navigationBarTextStyle": "black", - "navigationBarBackgroundColor": "#ffffff", - "backgroundColor": "#F5F5F5", + "navigationBarBackgroundColor": "#FEF6E7", + "backgroundColor": "#FEF6E7", "enablePullDownRefresh": false, "usingComponents": {} } diff --git a/miniprogram/pages/age/age.less b/miniprogram/pages/age/age.less index 3fc56f4..59200f2 100644 --- a/miniprogram/pages/age/age.less +++ b/miniprogram/pages/age/age.less @@ -1,108 +1,412 @@ +@import '../../style/theme3.less'; + +@age-tab-gap: 32rpx; +@age-tab-w: 226rpx; +@age-tab-h: 156rpx; +@age-badge-current: #005e17; +/* 选中态 box-shadow + scale 外溢;内层留白 + 外层负 margin 抵消,整体上下不占更多间距 */ +@age-tab-shadow-bleed-t: 36rpx; +@age-tab-shadow-bleed-b: 52rpx; + .age-page { min-height: 100vh; - background: #f5f5f5; - padding-bottom: 48rpx; + background: @bg-page; + padding-bottom: calc(48rpx + env(safe-area-inset-bottom)); box-sizing: border-box; } -.age-hero { - background: #fff; - padding: 40rpx 32rpx 36rpx; - margin-bottom: 24rpx; +.age-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; + background: fade(@bg-header, 85%); + backdrop-filter: blur(40rpx); + -webkit-backdrop-filter: blur(40rpx); + box-shadow: 0 2rpx 12rpx rgba(50, 46, 37, 0.05); } -.age-hero__title { - display: block; - font-size: 44rpx; - font-weight: 700; - color: rgba(0, 0, 0, 0.88); - margin-bottom: 12rpx; -} - -.age-hero__subtitle { - display: block; - font-size: 26rpx; - line-height: 1.5; - color: #999; -} - -.age-grid { +.age-nav__inner { + height: @nav-inner-height; display: flex; flex-direction: row; - flex-wrap: wrap; - gap: 24rpx; - padding: 0 24rpx; + align-items: center; + justify-content: flex-start; + padding: 0 @page-padding-x; box-sizing: border-box; } -.age-card { - width: calc((100% - 24rpx) / 2); - background: #fff; - border-radius: 32rpx; +.age-nav__title { + font-size: @fs-nav-title; + font-weight: @fw-nav-title; + color: @color-nav-title; + letter-spacing: @ls-nav-title; +} + +.age-body { + width: 100%; + box-sizing: border-box; +} + +/* 横向年龄 Tab — Stitch 为你推荐版 */ +.age-tab-scroll { + width: 100%; + white-space: nowrap; + padding: @section-gap-sm 0 @section-gap-xs; + box-sizing: border-box; + margin-top: -@age-tab-shadow-bleed-t; + margin-bottom: -@age-tab-shadow-bleed-b; +} + +.age-tab-scroll__inner { + padding: @age-tab-shadow-bleed-t 0 @age-tab-shadow-bleed-b; + box-sizing: border-box; +} + +.age-tab-row { + display: inline-flex; + flex-direction: row; + align-items: center; + gap: @age-tab-gap; + padding: 0 @page-padding-x; +} + +.age-tab { + position: relative; + width: @age-tab-w; + height: @age-tab-h; + flex-shrink: 0; + padding: 24rpx 16rpx; + border-radius: @radius-lg; + background: @bg-muted; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8rpx; + border: 2rpx solid transparent; +} + +.age-tab--active { + background: @brand; + border-color: @bg-white; + box-shadow: 0 16rpx 48rpx rgba(255, 215, 9, 0.38); + transform: scale(1.05); + z-index: 1; +} + +.age-tab__current { + position: absolute; + top: -8rpx; + right: -8rpx; + padding: 4rpx 14rpx; + border-radius: 999rpx; + font-size: 18rpx; + font-weight: 700; + color: @text-white; + background: @age-badge-current; + line-height: 1.2; +} + +.age-tab__range { + font-size: 36rpx; + font-weight: 800; + color: @text-title; + line-height: 1.2; +} + +.age-tab--active .age-tab__range { + color: @text-selected-btn; +} + +.age-tab__sub { + font-size: 22rpx; + color: @text-light; + line-height: 1.3; + font-weight: 600; +} + +.age-tab--active .age-tab__sub { + color: fade(@text-selected-btn, 85%); +} + +.age-panel { + margin: 0 @page-padding-x; + background: @bg-white; + border-radius: @radius-lg; + border: 1rpx solid fade(@bg-gray, 90%); + box-shadow: @shadow; overflow: hidden; - box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); +} + +.age-goal-panel { + padding: 36rpx @page-padding-x 40rpx; box-sizing: border-box; } -.age-card__art { - height: 160rpx; +.age-goal-head { + display: flex; + flex-direction: row; + align-items: center; + gap: 12rpx; + margin-bottom: @section-gap-sm; +} + +.age-goal-head__icon-wrap { + width: 56rpx; + height: 56rpx; + border-radius: 50%; + background: @brand-light; display: flex; align-items: center; justify-content: center; } -.age-card__emoji { - font-size: 72rpx; +.age-goal-head__icon { + font-size: 28rpx; line-height: 1; } -.age-card__body { - padding: 24rpx 24rpx 28rpx; +.age-goal-head__title { + font-size: @fs-section-head-title; + font-weight: 800; + color: @text-selected-btn; + letter-spacing: -0.02em; } -.age-card__label { - display: block; - font-size: 32rpx; - font-weight: 700; - color: rgba(0, 0, 0, 0.88); - margin-bottom: 8rpx; +.age-goal-list { + display: flex; + flex-direction: column; + gap: 24rpx; } -.age-card__desc { - display: block; - font-size: 24rpx; - color: #999; - margin-bottom: 12rpx; +.age-goal-item { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 12rpx; + padding: 28rpx @page-padding-inner-x; + border-radius: @radius; + background: @bg-header; + box-sizing: border-box; + border: 1rpx solid rgba(229, 220, 201, 0.55); } -.age-card__count { - display: block; - font-size: 22rpx; - color: #ccc; -} - -.age-tip { - margin: 40rpx 24rpx 0; - background: #fff; - border-radius: 24rpx; - padding: 28rpx 24rpx; +.age-goal-item__top { display: flex; flex-direction: row; - align-items: flex-start; - gap: 20rpx; - box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04); - box-sizing: border-box; + align-items: center; + gap: 12rpx; } -.age-tip__duck { - font-size: 64rpx; +.age-goal-item__emoji { + font-size: 40rpx; line-height: 1; flex-shrink: 0; } -.age-tip__text { - flex: 1; - font-size: 26rpx; - line-height: 1.55; - color: rgba(0, 0, 0, 0.65); +.age-goal-item__title { + font-size: 28rpx; + font-weight: 700; + color: @text-selected-btn; + line-height: 1.2; } + +.age-goal-item__desc { + font-size: 24rpx; + line-height: 1.5; + color: @text-secondary; +} + +.age-path-head { + margin: @section-gap-lg @page-padding-x @section-gap-xs; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 16rpx; +} + +.age-path-head__left { + display: flex; + flex-direction: row; + align-items: center; + gap: 20rpx; + min-width: 0; +} + +.age-path-head__bar { + width: 16rpx; + height: 64rpx; + border-radius: 999rpx; + background: @text-selected-btn; + flex-shrink: 0; +} + +.age-path-head__title { + font-size: 40rpx; + font-weight: 800; + color: @text-title; + letter-spacing: -0.03em; + line-height: 1.2; +} + +.age-path-head__badge { + flex-shrink: 0; + font-size: 20rpx; + font-weight: 700; + color: @text-secondary; + background: @bg-header; + padding: 10rpx 20rpx; + border-radius: 999rpx; + border: @border; +} + +.age-week-list { + padding: 0 @page-padding-x; + display: flex; + flex-direction: column; + gap: @section-gap-lg; +} + +.age-week-card { + background: @bg-white; + border-radius: @radius-lg; + overflow: hidden; + border: 1rpx solid rgba(50, 46, 37, 0.08); + box-shadow: @shadow; +} + +.age-week-card__head { + padding: 28rpx @page-padding-x; + background: @bg-week-head; + display: flex; + flex-direction: row; + align-items: baseline; + gap: 16rpx; +} + +.age-week-card__week { + font-size: @fs-section-head-title; + font-weight: 800; + color: @text-title; +} + +.age-week-card__theme { + font-size: 26rpx; + font-weight: 600; + color: @text-gray; +} + +.age-week-card__body { + padding: 0 @page-padding-x 8rpx; +} + +.age-week-row { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 20rpx; + padding: 32rpx 0; + border-bottom: 1rpx solid #f2f2f2; +} + +.age-week-row:last-child { + border-bottom: none; +} + +.age-week-row__title { + flex: 1; + min-width: 0; + font-size: 28rpx; + font-weight: 700; + color: @text-title; +} + +.age-week-row__btn { + flex-shrink: 0; + display: flex; + flex-direction: row; + align-items: center; + gap: 6rpx; + padding: 14rpx 28rpx; + border-radius: 999rpx; + background: @brand; + font-size: 24rpx; + font-weight: 700; + color: @text-selected-btn; + box-shadow: 0 4rpx 12rpx rgba(255, 215, 9, 0.35); +} + +.age-week-row__btn-arrow { + font-size: 22rpx; + font-weight: 700; +} + +/* [为你推荐] — Stitch 栅格 */ +.age-rec { + margin-top: @section-gap-lg; + padding: 0 @page-padding-x @section-gap-xs; + box-sizing: border-box; +} + +.age-rec__head { + display: flex; + flex-direction: row; + align-items: flex-end; + justify-content: space-between; + margin-bottom: @section-gap-xs; +} + +.age-rec__title { + font-size: @fs-section-head-title; + font-weight: 800; + color: @text-title; + letter-spacing: -0.02em; +} + +.age-rec__more { + font-size: 26rpx; + font-weight: 700; + color: @text-selected-btn; + padding: 8rpx 0 8rpx 24rpx; +} + +.age-rec__grid { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + row-gap: 32rpx; +} + +.age-rec__card { + width: calc(50% - 16rpx); + background: @bg-white; + border-radius: @radius-lg; + overflow: hidden; + border: 1rpx solid rgba(50, 46, 37, 0.08); + box-shadow: @shadow; + box-sizing: border-box; +} + +.age-rec__img { + width: 100%; + height: 200rpx; + display: block; + background: fade(@brand, 15%); +} + +.age-rec__body { + padding: 20rpx 20rpx 24rpx; +} + +.age-rec__card-title { + font-size: 26rpx; + font-weight: 700; + color: @text-title; + line-height: 1.35; +} \ No newline at end of file diff --git a/miniprogram/pages/age/age.ts b/miniprogram/pages/age/age.ts index fe4973c..d8f14e7 100644 --- a/miniprogram/pages/age/age.ts +++ b/miniprogram/pages/age/age.ts @@ -1,44 +1,243 @@ -import { ALL_WORKSHEETS, getWorksheetsByAge } from '../../core/data/worksheets'; +import { AGE_BANDS, AgeBandKey } from '../../core/data/difficulty'; +import { NAV_INNER_PX } from '../../utils/navMetrics'; -interface AgeGroup { - label: string; +type AbilityItem = { + icon: string; + title: string; desc: string; - emoji: string; - minAge: number; - maxAge: number; - count: number; - gradient: string; -} +}; + +type WeekExercise = { + title: string; +}; + +type WeekPlan = { + week: number; + theme: string; + exercises: WeekExercise[]; +}; + +const AGE_TAB_SUB: Record = { + '3-4': '启蒙认知', + '4-5': '基础练习', + '5-6': '能力提升', + '6-7': '幼小衔接', + '7-8': '知识拓展', +}; + +/** Stitch「为你推荐」占位图(需在小程序后台配置 download 合法域名含 lh3.googleusercontent.com) */ +const RECOMMENDED_FROM_STITCH: { title: string; image: string }[] = [ + { + title: '10以内加减法', + image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuD-3lADhclTGODN9WtNh5v8bQPLysbllbKNkH40bAmVyE5d54PwfTBOiX9nER1hpE4rzHAfsmM_JhVnsE4EbdA914rYIb3S3e4r9QKFjUqYmj2XpE4MlYr_vLkG4oeMnvnHOqGC96nLGrPSOq-fpIddV7RSlYLDeNccBs0G2oUOy-6jIMujzTuEGk4nnxme5rpxs7rYjwcvVsqM5dlkpJthY42e4B3lSPH7ezx187WRSk--m4ZiWmAI52mMOiygzTSWoNormlvfvdM', + }, + { + title: '拼音描红', + image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBYS4O7PyflR5q8M6oXKwj_8QHzex3OL3YvdHBncutm7a_bjlZj83-8UBpGUQvJO20V0BnAdU3iKEoLlUqwpS60CiPhVyxYaHEAqv52P5YUhJdjf0hpLir1Li4rdI_-eqGkPTasYl-5kD2r8-RjUokKg_HiW5_TF72UbxFXHQh4BefYJZfl0Oc9-aTQvkbFL0C3tagi9Qv1jRsgQldMFU4M7cHt8KkxhO1QDF7-R85ZFORJM0RiqV2l-jn0c-UscKtuWtroiX6UUKk', + }, + { + title: '趣味找不同', + image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBSafbTyucs8JLpTgibRWwAJsHukrklTAuEnaAzax_Kr1RNCwHjqpQjta0Q-h47ClufzEmaHPgCIOlm_a2lL4gNlgBDyraCwzRU7aWCf_Fj_dM1cLGzROJmtqrdHKjQzTbCp0_Da8BvXi4a4H2X4RAYVPnLbZVmbgPXrTvpKgEI605kIMmr3n0--WojJQRsF4B_xCho80gABbIaMTHJHrs9xdEY6fZKg8ArQx7lRbJ6_EsLLB_C5a5crZ_begwruXKr3pmdU6KneC8', + }, + { + title: '走迷宫大挑战', + image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAOZHI1Dz6ax4iFUb3QYL-EWp2euA_3kyUeCcu6V57BBCzarMwKnFzcpJxJn4X4woklWFB08Ii1w6zsk0H5eDaPdgUYmsvY0NFqvAhKN_IbZGZjZnclZlBHkLRcX64Nl767L2pg01FQu9Tp6nq2p3lAuYeEQ2fiIdXh4j-QHyikbmx4_aDlx5X5fC0fqMMxWykhwLNkDzl2Rs3ucXj7zYnUP-dNNY-aj_JvqkY4pqoXQDt9ZJcfSeL0dWes7nvD5U2C_JFhrk1bVkw', + }, + { + title: '数字点连线', + image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuD-3lADhclTGODN9WtNh5v8bQPLysbllbKNkH40bAmVyE5d54PwfTBOiX9nER1hpE4rzHAfsmM_JhVnsE4EbdA914rYIb3S3e4r9QKFjUqYmj2XpE4MlYr_vLkG4oeMnvnHOqGC96nLGrPSOq-fpIddV7RSlYLDeNccBs0G2oUOy-6jIMujzTuEGk4nnxme5rpxs7rYjwcvVsqM5dlkpJthY42e4B3lSPH7ezx187WRSk--m4ZiWmAI52mMOiygzTSWoNormlvfvdM', + }, + { + title: '方格推理高手', + image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBYS4O7PyflR5q8M6oXKwj_8QHzex3OL3YvdHBncutm7a_bjlZj83-8UBpGUQvJO20V0BnAdU3iKEoLlUqwpS60CiPhVyxYaHEAqv52P5YUhJdjf0hpLir1Li4rdI_-eqGkPTasYl-5kD2r8-RjUokKg_HiW5_TF72UbxFXHQh4BefYJZfl0Oc9-aTQvkbFL0C3tagi9Qv1jRsgQldMFU4M7cHt8KkxhO1QDF7-R85ZFORJM0RiqV2l-jn0c-UscKtuWtroiX6UUKk', + }, +]; + +/** 各年龄段能力目标(Mock) */ +const MOCK_ABILITY: Record = { + '3-4': [ + { + icon: '🔢', + title: '数感', + desc: '认读 1-5、点数对应', + }, + { + icon: '✏️', + title: '书写', + desc: '涂鸦线条、简单描红', + }, + { + icon: '🧩', + title: '思维', + desc: '找相同、简单配对', + }, + ], + '4-5': [ + { + icon: '🔢', + title: '数感', + desc: '10 以内数数、比大小', + }, + { + icon: '✏️', + title: '书写', + desc: '笔画模仿、图形描边', + }, + { + icon: '🧩', + title: '思维', + desc: '规律排序、图形分类', + }, + ], + '5-6': [ + { + icon: '🔢', + title: '数感', + desc: '10以内加减法、凑十法', + }, + { + icon: '✏️', + title: '书写', + desc: '练字帖、拼音入门', + }, + { + icon: '🧩', + title: '思维', + desc: '方格推理、格子仿画 5×5', + }, + ], + '6-7': [ + { + icon: '🔢', + title: '数感', + desc: '20 以内运算、应用题启蒙', + }, + { + icon: '✏️', + title: '书写', + desc: '常用字书写、词语积累', + }, + { + icon: '🧩', + title: '思维', + desc: '逻辑填空、空间想象', + }, + ], + '7-8': [ + { + icon: '🔢', + title: '数感', + desc: '乘除启蒙、巧算练习', + }, + { + icon: '✏️', + title: '书写', + desc: '段落抄写、古诗诵读', + }, + { + icon: '🧩', + title: '思维', + desc: '数独入门、综合推理', + }, + ], +}; + +/** 4 周路线(各年龄段共用结构,内容 Mock) */ +const MOCK_WEEKS: WeekPlan[] = [ + { + week: 1, + theme: '数感启蒙', + exercises: [ + { title: '找数字涂一涂' }, + { title: '5 以内加法' }, + { title: '数数连一连' }, + ], + }, + { + week: 2, + theme: '形状与连线', + exercises: [ + { title: '识别形状' }, + { title: '线条识别' }, + { title: '数字点连线' }, + ], + }, + { + week: 3, + theme: '趣味专注', + exercises: [ + { title: '颜色找规律' }, + { title: '方格推理' }, + { title: '格子仿画' }, + ], + }, + { + week: 4, + theme: '综合练习', + exercises: [ + { title: '10以内加减法' }, + { title: '识字卡' }, + { title: '连连看' }, + ], + }, +]; + +const DEFAULT_AGE: AgeBandKey = '5-6'; Page({ data: { - ageGroups: [] as AgeGroup[], + statusBarHeight: 0, + navBlockHeight: 0, + ageTabs: AGE_BANDS.map((b) => ({ + key: b.key, + rangeText: b.label.replace(/\s*岁\s*$/, ''), + subLabel: AGE_TAB_SUB[b.key], + })), + activeAgeKey: DEFAULT_AGE, + goalTitle: '', + abilityItems: [] as AbilityItem[], + weekPlans: MOCK_WEEKS, + recommendedItems: RECOMMENDED_FROM_STITCH, }, onLoad() { - const groups = [ - { label: '3-4 岁', desc: '启蒙认知', emoji: '👶', minAge: 3, maxAge: 4, gradient: 'linear-gradient(135deg, #fff9c4, #fff176)' }, - { label: '4-5 岁', desc: '基础练习', emoji: '🧒', minAge: 4, maxAge: 5, gradient: 'linear-gradient(135deg, #c8e6c9, #81c784)' }, - { label: '5-6 岁', desc: '能力提升', emoji: '👦', minAge: 5, maxAge: 6, gradient: 'linear-gradient(135deg, #bbdefb, #64b5f6)' }, - { label: '6-7 岁', desc: '幼小衔接', emoji: '👧', minAge: 6, maxAge: 7, gradient: 'linear-gradient(135deg, #e1bee7, #ba68c8)' }, - { label: '7-8 岁', desc: '巩固挑战', emoji: '🎒', minAge: 7, maxAge: 8, gradient: 'linear-gradient(135deg, #ffccbc, #ff8a65)' }, - ]; - - const ageGroups = groups.map((g) => ({ - ...g, - count: getWorksheetsByAge(ALL_WORKSHEETS, g.minAge).length, - })); - - this.setData({ ageGroups }); + const win = wx.getWindowInfo(); + this.setData({ + statusBarHeight: win.statusBarHeight, + navBlockHeight: win.statusBarHeight + NAV_INNER_PX, + }); + this.applyAge(DEFAULT_AGE); }, - onAgeCardTap(e: WechatMiniprogram.TouchEvent) { - const minAge = Number(e.currentTarget.dataset.minAge); - const maxAge = Number(e.currentTarget.dataset.maxAge); - if (Number.isNaN(minAge) || Number.isNaN(maxAge)) return; - const title = `${minAge}-${maxAge}岁推荐`; - wx.navigateTo({ - url: `/pages/category/category?minAge=${minAge}&maxAge=${maxAge}&title=${encodeURIComponent(title)}`, + applyAge(key: AgeBandKey) { + const band = AGE_BANDS.find((b) => b.key === key); + const goalTitle = band ? `${band.label}能力目标` : '能力目标'; + this.setData({ + activeAgeKey: key, + goalTitle, + abilityItems: MOCK_ABILITY[key], }); }, + + onAgeTabTap(e: WechatMiniprogram.TouchEvent) { + const key = e.currentTarget.dataset.key as AgeBandKey | undefined; + if (!key) return; + this.applyAge(key); + }, + + onPracticeTap(e: WechatMiniprogram.TouchEvent) { + const title = e.currentTarget.dataset.title as string | undefined; + if (!title) return; + wx.showToast({ title: `即将前往:${title}`, icon: 'none' }); + }, + + onRecommendMore() { + wx.switchTab({ url: '/pages/home/home' }); + }, + + onRecommendTap(e: WechatMiniprogram.TouchEvent) { + const title = e.currentTarget.dataset.title as string | undefined; + if (!title) return; + wx.showToast({ title: `去看看:${title}`, icon: 'none' }); + }, }); diff --git a/miniprogram/pages/age/age.wxml b/miniprogram/pages/age/age.wxml index c6dcfb5..f3f36c5 100644 --- a/miniprogram/pages/age/age.wxml +++ b/miniprogram/pages/age/age.wxml @@ -1,31 +1,110 @@ - - 分龄推荐 - 按年龄段挑选适合的练习单,循序渐进更省心 - - - - - - {{item.emoji}} - - - {{item.label}} - {{item.desc}} - {{item.count}} 个练习 - + + + 按年龄学 - - 🦆 - 建议家长根据孩子当前能力微调难度,不必严格按年龄划分哦~ + + + + + + {{item.rangeText}} + {{item.subLabel}} + + + + + + + + + + + {{goalTitle}} + + + + + {{item.icon}} + {{item.title}} + + {{item.desc}} + + + + + + + + 4 周推荐学习路线 + + 4 周 · 每周 3 练 + + + + + + 第 {{item.week}} 周 + {{item.theme}} + + + + {{ex.title}} + + 去练习 + + + + + + + + + + 为你推荐 + 更多 + + + + + + {{item.title}} + + + + diff --git a/miniprogram/pages/category/category.json b/miniprogram/pages/category/category.json deleted file mode 100644 index 2f4c910..0000000 --- a/miniprogram/pages/category/category.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "navigationBarTitleText": "分类", - "navigationBarTextStyle": "black", - "navigationBarBackgroundColor": "#ffffff", - "backgroundColor": "#F6F6F6", - "usingComponents": { - "worksheet-card": "../../components3.0/worksheet-card/worksheet-card", - "detail-card": "../../components3.0/detail-card/detail-card" - } -} diff --git a/miniprogram/pages/category/category.less b/miniprogram/pages/category/category.less deleted file mode 100644 index 968968a..0000000 --- a/miniprogram/pages/category/category.less +++ /dev/null @@ -1,81 +0,0 @@ -.category-page { - min-height: 100vh; - background: #f6f6f6; - padding-bottom: 48rpx; -} - -.category-page__header { - padding: 24rpx 32rpx 16rpx; - background: #fff; -} - -.category-page__header-title { - font-size: 34rpx; - font-weight: 600; - color: #333; -} - -.category-page__filters { - background: #fff; - padding: 24rpx; - margin-bottom: 16rpx; -} - -.category-page__filter-row { - display: flex; - flex-direction: row; - align-items: flex-start; - margin-bottom: 20rpx; -} - -.category-page__filter-row:last-child { - margin-bottom: 0; -} - -.category-page__filter-label { - flex-shrink: 0; - width: 72rpx; - font-size: 26rpx; - color: #999; - line-height: 56rpx; -} - -.category-page__pills { - flex: 1; - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 16rpx; -} - -.category-page__pill { - font-size: 24rpx; - padding: 8rpx 20rpx; - border-radius: 24rpx; - background: #f5f5f5; - color: #333; -} - -.category-page__pill--active { - background: #f7ee47; - color: #333; -} - -.category-page__list { - display: flex; - flex-direction: column; -} - -.category-page__card-wrap { - margin: 0 24rpx 24rpx; -} - -.category-page__empty { - padding: 120rpx 48rpx; - text-align: center; -} - -.category-page__empty-text { - font-size: 28rpx; - color: #999; -} diff --git a/miniprogram/pages/category/category.ts b/miniprogram/pages/category/category.ts deleted file mode 100644 index a604814..0000000 --- a/miniprogram/pages/category/category.ts +++ /dev/null @@ -1,161 +0,0 @@ -import type { - WorksheetDefinition, - WorksheetType, -} from '../../core/models/worksheet'; -import { - getWorksheetPath, - getWorksheetsByAge, - getWorksheetsByCategory, -} from '../../core/data/worksheets'; - -type AgeFilterKey = 'all' | '3-4' | '4-5' | '5-6' | '6-7' | '7-8'; -type DiffFilterKey = 'all' | 'beginner' | 'basic' | 'intermediate'; - -const AGE_BAND: Record, [number, number]> = { - '3-4': [3, 4], - '4-5': [4, 5], - '5-6': [5, 6], - '6-7': [6, 7], - '7-8': [7, 8], -}; - -const DIFF_LABEL: Record = { - beginner: '入门', - basic: '基础', - intermediate: '进阶', - advanced: '进阶+', -}; - -type DisplayItem = WorksheetType & { - ageRangeLabel: string; - difficultyLabel: string; -}; - -function filterByAgeBand( - list: WorksheetType[], - band: AgeFilterKey, -): WorksheetType[] { - if (band === 'all') return list; - const [lo, hi] = AGE_BAND[band]; - const map = new Map(); - for (let age = lo; age <= hi; age += 1) { - getWorksheetsByAge(list, age).forEach((w) => map.set(w.id, w)); - } - return Array.from(map.values()); -} - -function filterByDifficulty( - list: WorksheetType[], - diff: DiffFilterKey, -): WorksheetType[] { - if (diff === 'all') return list; - return list.filter((w) => w.difficulty === diff); -} - -function toDisplayList(list: WorksheetType[]): DisplayItem[] { - return list.map((w) => ({ - ...w, - ageRangeLabel: - w.ageRange && w.ageRange.length === 2 - ? `${w.ageRange[0]}-${w.ageRange[1]}岁` - : '', - difficultyLabel: w.difficulty ? DIFF_LABEL[w.difficulty] || '' : '', - })); -} - -Page({ - data: { - title: '分类', - sourceItems: [] as WorksheetDefinition[], - items: [] as DisplayItem[], - ageFilters: [ - { key: 'all', label: '全部' }, - { key: '3-4', label: '3-4岁' }, - { key: '4-5', label: '4-5岁' }, - { key: '5-6', label: '5-6岁' }, - { key: '6-7', label: '6-7岁' }, - { key: '7-8', label: '7-8岁' }, - ], - diffFilters: [ - { key: 'all', label: '全部' }, - { key: 'beginner', label: '⭐ 入门' }, - { key: 'basic', label: '⭐⭐ 基础' }, - { key: 'intermediate', label: '⭐⭐⭐ 进阶' }, - ], - activeAge: 'all' as AgeFilterKey, - activeDiff: 'all' as DiffFilterKey, - }, - - onLoad(options: { - category?: string; - minAge?: string; - maxAge?: string; - title?: string; - }) { - const category = options.category || 'all'; - let base = getWorksheetsByCategory(category); - - const minAge = options.minAge != null ? Number(options.minAge) : NaN; - const maxAge = options.maxAge != null ? Number(options.maxAge) : NaN; - if (!Number.isNaN(minAge) && !Number.isNaN(maxAge)) { - const map = new Map(); - const lo = Math.min(minAge, maxAge); - const hi = Math.max(minAge, maxAge); - for (let age = lo; age <= hi; age += 1) { - getWorksheetsByAge(base, age).forEach((w) => map.set(w.id, w)); - } - base = Array.from(map.values()); - } - - const title = options.title - ? decodeURIComponent(options.title) - : '分类'; - - this.setData({ - title, - sourceItems: base, - }); - wx.setNavigationBarTitle({ title }); - this.applyFilters(); - }, - - applyFilters() { - const { sourceItems, activeAge, activeDiff } = this.data; - let next = filterByAgeBand(sourceItems, activeAge); - next = filterByDifficulty(next, activeDiff); - this.setData({ items: toDisplayList(next) }); - }, - - onAgeFilter(e: WechatMiniprogram.TouchEvent) { - const key = e.currentTarget.dataset.key as AgeFilterKey; - if (!key) return; - this.setData({ activeAge: key }, () => this.applyFilters()); - }, - - onDiffFilter(e: WechatMiniprogram.TouchEvent) { - const key = e.currentTarget.dataset.key as DiffFilterKey; - if (!key) return; - this.setData({ activeDiff: key }, () => this.applyFilters()); - }, - - buildWorksheetUrl(w: WorksheetType): string { - let url = getWorksheetPath(w); - if (!url) return ''; - if (w.mode) { - url += url.includes('?') ? `&mode=${w.mode}` : `?mode=${w.mode}`; - } - return url; - }, - - onCardTap(e: WechatMiniprogram.TouchEvent) { - const id = e.currentTarget.dataset.id as string; - const w = this.data.items.find((item) => item.id === id); - if (!w) return; - const url = this.buildWorksheetUrl(w); - if (!url) { - wx.showToast({ title: '暂无法打开', icon: 'none' }); - return; - } - wx.navigateTo({ url }); - }, -}); diff --git a/miniprogram/pages/category/category.wxml b/miniprogram/pages/category/category.wxml deleted file mode 100644 index 75f5793..0000000 --- a/miniprogram/pages/category/category.wxml +++ /dev/null @@ -1,58 +0,0 @@ - - - {{title}} - - - - - 年龄 - - - {{item.label}} - - - - - 难度 - - - {{item.label}} - - - - - - - - - - - - - 暂无符合条件的练习纸 - - diff --git a/miniprogram/pages/favorites/favorites.json b/miniprogram/pages/favorites/favorites.json index e0adf8d..b6ffb39 100644 --- a/miniprogram/pages/favorites/favorites.json +++ b/miniprogram/pages/favorites/favorites.json @@ -1,6 +1,10 @@ { + "navigationStyle": "custom", "navigationBarTitleText": "我的收藏", "navigationBarTextStyle": "black", - "navigationBarBackgroundColor": "#ffffff", - "backgroundColor": "#F6F6F6" + "navigationBarBackgroundColor": "#F8F0E0", + "backgroundColor": "#FEF6E7", + "usingComponents": { + "van-icon": "@vant/weapp/icon/index" + } } diff --git a/miniprogram/pages/favorites/favorites.less b/miniprogram/pages/favorites/favorites.less index 0a686bc..ce5dc07 100644 --- a/miniprogram/pages/favorites/favorites.less +++ b/miniprogram/pages/favorites/favorites.less @@ -1,88 +1,452 @@ -.favorites-page { +@import '../../style/theme3.less'; + +@fav-seg-track: #f0e7d6; +@fav-thumb-placeholder: #e5dcc9; +@fav-secondary-cta-bg: #ffc697; +@fav-secondary-cta-text: #6d3b00; +@fav-heart: #b02500; + +.fav-page { min-height: 100vh; - background: #f6f6f6; + background: @bg-page; + box-sizing: border-box; } -.favorites-page__title { - padding: 32rpx 32rpx 24rpx; - font-size: 40rpx; - font-weight: 600; - color: #333; - background: #fff; +.fav-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; + flex-shrink: 0; + background: fade(@bg-header, 82%); + backdrop-filter: blur(40rpx); + -webkit-backdrop-filter: blur(40rpx); + box-shadow: 0 2rpx 12rpx rgba(50, 46, 37, 0.05); } -.favorites-page__tabs { +.fav-nav__inner { + height: @nav-inner-height; display: flex; flex-direction: row; - background: #fff; - border-bottom: 1rpx solid #eee; + align-items: center; + justify-content: flex-start; + padding: 0 @page-padding-x; + box-sizing: border-box; } -.favorites-page__tab { - flex: 1; - text-align: center; - padding: 24rpx 0; - font-size: 30rpx; - color: #666; - position: relative; +.fav-nav__title { + font-size: @fs-nav-title; + font-weight: @fw-nav-title; + color: @color-nav-title; + letter-spacing: @ls-nav-title; } -.favorites-page__tab--active { - color: #333; +.fav-nav__actions { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + min-width: 120rpx; +} + +.fav-nav__icon-btn { + display: flex; + align-items: center; + justify-content: center; + padding: 8rpx; + border-radius: 50%; +} + +.fav-nav__clear { + font-size: 26rpx; font-weight: 600; + color: fade(@text-selected-btn, 65%); + padding: 12rpx 20rpx; + border-radius: 999rpx; } -.favorites-page__tab--active::after { - content: ''; +.fav-scroll { + box-sizing: border-box; + width: 100%; +} + +.fav-body { + padding: @section-gap-xs @page-padding-x; + padding-bottom: calc(48rpx + env(safe-area-inset-bottom) + 120rpx); + box-sizing: border-box; +} + +/* Segmented(Stitch 胶囊) */ +.fav-seg { + display: flex; + flex-direction: row; + align-items: stretch; + padding: 12rpx; + margin-bottom: @section-gap-sm; + border-radius: 999rpx; + background: @fav-seg-track; + box-shadow: @shadow; + box-sizing: border-box; +} + +.fav-seg__pill { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 22rpx 16rpx; + border-radius: 999rpx; + box-sizing: border-box; +} + +.fav-seg__pill--active { + background: @brand; + box-shadow: @shadow; +} + +.fav-seg__text { + font-size: 26rpx; + font-weight: 700; + color: @text-secondary; +} + +.fav-seg__pill--active .fav-seg__text { + color: @text-selected-btn; +} + +/* 收藏列表卡片 */ +.fav-list { + display: flex; + flex-direction: column; + gap: 32rpx; +} + +.fav-card { + border-radius: @radius-lg; + box-sizing: border-box; +} + +.fav-card--row { + display: flex; + flex-direction: row; + align-items: center; + gap: 32rpx; + padding: 32rpx; + background: @bg-header; + box-shadow: @shadow; +} + +.fav-card__thumb { + width: 160rpx; + height: 160rpx; + border-radius: @radius; + flex-shrink: 0; + background: @fav-thumb-placeholder; +} + +.fav-card__main { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8rpx; +} + +.fav-card__title { + font-size: 32rpx; + font-weight: 700; + color: @text-title; + line-height: 1.25; +} + +.fav-card__meta { + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + gap: 4rpx; +} + +.fav-card__meta-text { + font-size: 22rpx; + color: @text-secondary; +} + +.fav-card__stars { + display: flex; + flex-direction: row; + align-items: center; + gap: 2rpx; +} + +.fav-card__badge { + margin-top: 4rpx; + font-size: 18rpx; + font-weight: 600; + color: @text-gray; + letter-spacing: 0.06em; + text-transform: uppercase; + padding: 6rpx 16rpx; + border-radius: 999rpx; + background: @fav-seg-track; +} + +.fav-card__heart { + flex-shrink: 0; + width: 80rpx; + height: 80rpx; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; +} + +/* Bento 大卡 */ +.fav-card--bento { + position: relative; + overflow: hidden; + padding: 48rpx 140rpx 48rpx 40rpx; + background: @fav-seg-track; + box-shadow: @shadow; +} + +.fav-bento__content { + position: relative; + z-index: 1; +} + +.fav-bento__title { + font-size: 36rpx; +} + +.fav-bento__meta { + font-size: 26rpx; + color: @text-secondary; + margin-top: 8rpx; + margin-bottom: 28rpx; + line-height: 1.4; +} + +.fav-bento__tags { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 16rpx; +} + +.fav-bento__tag { + padding: 8rpx 22rpx; + border-radius: 999rpx; + font-size: 20rpx; + font-weight: 700; + color: @fav-secondary-cta-text; + background: rgba(255, 255, 255, 0.55); +} + +.fav-bento__deco { position: absolute; - left: 50%; - bottom: 0; - transform: translateX(-50%); - width: 48rpx; - height: 6rpx; - background: #f7ee47; - border-radius: 3rpx; + right: -40rpx; + bottom: -48rpx; + z-index: 0; + pointer-events: none; } -.favorites-page__empty { +.fav-bento__heart { + position: absolute; + top: 40rpx; + right: 32rpx; + z-index: 2; + width: 80rpx; + height: 80rpx; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; +} + +/* 底部去发现 */ +.fav-cta { + margin-top: @section-gap; + padding: 48rpx 0 24rpx; display: flex; flex-direction: column; align-items: center; - justify-content: center; - padding: 120rpx 48rpx 0; } -.favorites-page__emoji { - font-size: 96rpx; - line-height: 1.2; +.fav-cta__hint { + font-size: 26rpx; + font-weight: 600; + color: @text-gray; +} + +.fav-cta__btn { + margin-top: 32rpx; + padding: 22rpx 64rpx; + border-radius: 999rpx; + font-size: 26rpx; + font-weight: 700; + background: @fav-secondary-cta-bg !important; + color: @fav-secondary-cta-text !important; + border: none !important; + box-shadow: @shadow; +} + +.fav-cta__btn::after { + border: none; +} + +/* 下载历史 */ +.fav-dl { + display: flex; + flex-direction: column; + gap: 16rpx; +} + +.fav-dl__section-label { + display: block; + font-size: 22rpx; + font-weight: 700; + color: @text-gray; + letter-spacing: 0.2em; + text-transform: uppercase; + padding: 8rpx 16rpx 16rpx; +} + +.fav-dl__items { + display: flex; + flex-direction: column; + gap: 32rpx; margin-bottom: 32rpx; } -.favorites-page__empty-msg { - margin-bottom: 48rpx; +.fav-dl-row { + display: flex; + flex-direction: row; + align-items: center; + gap: 32rpx; + padding: 32rpx; + background: @bg-header; + border-radius: @radius-lg; + box-shadow: @shadow; + box-sizing: border-box; } -.favorites-page__empty-line { - display: block; - font-size: 28rpx; - color: #666; - text-align: center; - line-height: 1.6; - margin-bottom: 8rpx; +.fav-dl-row--muted { + opacity: 0.92; } -.favorites-page__btn { - width: 280rpx; - height: 80rpx; - line-height: 80rpx; - background: #f7ee47; - color: #333; +.fav-dl-row__thumb { + width: 160rpx; + height: 160rpx; + border-radius: @radius; + flex-shrink: 0; + background: @fav-thumb-placeholder; +} + +.fav-dl-row__thumb--ph { + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(145deg, @fav-secondary-cta-bg 0%, fade(@brand, 35%) 100%); +} + +.fav-dl-row__main { + flex: 1; + min-width: 0; +} + +.fav-dl-row__title { + font-size: 32rpx; + font-weight: 700; + color: @text-title; + line-height: 1.25; + margin-bottom: 12rpx; +} + +.fav-dl-row__meta { + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + gap: 12rpx; + font-size: 26rpx; + color: fade(@text-secondary, 88%); +} + +.fav-dl-row__time { + color: fade(@text-secondary, 88%); +} + +.fav-dl-row__dot { + width: 8rpx; + height: 8rpx; + border-radius: 50%; + background: @text-gray; + opacity: 0.45; +} + +.fav-dl-row__status { + color: fade(@text-secondary, 65%); +} + +.fav-dl-row__status--hi { + color: #006b1b; + font-weight: 600; +} + +.fav-dl-row__more { + flex-shrink: 0; + width: 72rpx; + height: 72rpx; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; +} + +/* 空状态 */ +.fav-empty { + display: flex; + flex-direction: column; + align-items: center; + padding: 120rpx 32rpx 64rpx; +} + +.fav-empty__emoji { + font-size: 100rpx; + margin-bottom: 24rpx; +} + +.fav-empty__icon { + margin-bottom: 24rpx; +} + +.fav-empty__msg { font-size: 30rpx; font-weight: 600; - border-radius: 40rpx; + color: @text-title; + margin-bottom: 12rpx; +} + +.fav-empty__sub { + font-size: 26rpx; + color: @text-secondary; + margin-bottom: 40rpx; + text-align: center; +} + +.fav-empty__btn { + padding: 20rpx 56rpx; + border-radius: 999rpx; + font-size: 28rpx; + font-weight: 700; + background: @brand; + color: @text-selected-btn; border: none; } -.favorites-page__btn::after { +.fav-empty__btn::after { border: none; -} +} \ No newline at end of file diff --git a/miniprogram/pages/favorites/favorites.ts b/miniprogram/pages/favorites/favorites.ts index 78f8855..6ebcc71 100644 --- a/miniprogram/pages/favorites/favorites.ts +++ b/miniprogram/pages/favorites/favorites.ts @@ -1,10 +1,136 @@ +import { NAV_INNER_PX } from '../../utils/navMetrics'; + type FavoritesTab = 'favorites' | 'downloads'; +type FavoriteItem = + | { + id: string; + variant: 'standard'; + title: string; + metaLine: string; + stars: number; + timeBadge: string; + thumb: string; + } + | { + id: string; + variant: 'featured'; + title: string; + metaLine: string; + tags: string[]; + }; + +type DownloadRow = { + id: string; + title: string; + time: string; + status: string; + statusHighlight: boolean; + thumb?: string; + placeholder?: 'draw'; +}; + +type DownloadSection = { + key: string; + label: string; + items: DownloadRow[]; +}; + +const MOCK_FAVORITES: FavoriteItem[] = [ + { + id: '1', + variant: 'standard', + title: '10以内加法', + metaLine: '数学启蒙 · 4-6岁 · ', + stars: 2, + timeBadge: '收藏于 3 天前', + thumb: 'https://lh3.googleusercontent.com/aida-public/AB6AXuD7khp0rJABfa9yFrMt9ZgIhAPyN44XmJ2pXNBgCzbgBvwA5ptFZVty013JedVzbE9_VvCQnToN7QETLDKjtiX22igzK-a5h4E8wjKrembFW4eVuk1RsJIbHjx3_cCz0hJ7_t76hF8t-Qdod_5R9sNMor1ZEjf6dGNp6UI_eIBjsN9b22iCTJ2VB-IEh0Rmil6_iC4Og-4I03IcQDV2rTHYIXcZxjiay1IiYGXw3wJ3bfo2SF2N7ZFT00akTgYU-9JyF7xJ6n15Yzc', + }, + { + id: '2', + variant: 'standard', + title: '图形连连看', + metaLine: '逻辑思维 · 3-5岁 · ', + stars: 1, + timeBadge: '收藏于 5 天前', + thumb: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBY_0Rr153ZBT4IHcMyFtrcgl-y2ZRyVnOuGecnn7f4PZKoAx2lOuCorsMmfRa72s0JmbXrt4XT3QU1x7cIbZUyhv_EXwCfMaNxtkHc-ymIBIQ5jqm5oiQebrEWH4kjv14JdkCiziT4xzac4TdVN5yqpk7Wlg80QBl_ldDmAQqIgcUeN2PvlVvhmpOYmFeWE2HS3I_e4vV1J8s_cfV9_4g7y8SbxlSfnTmgQNeXrVbcdwaNEAGQWp92Iif0d2pVIqdfuUHO1pTw8oQ', + }, + { + id: '3', + variant: 'standard', + title: '拼音字母认读', + metaLine: '语文基础 · 5-7岁 · ', + stars: 3, + timeBadge: '收藏于 1 周前', + thumb: 'https://lh3.googleusercontent.com/aida-public/AB6AXuC54mXQ8620XwIsWmUHBHJGdmpF8uE4cSYxwR2NLJTsa6gYdTCHz2tqrKbGOye-y0CGgCPPWwJXzEuB2ZYrQ3CWWPlamvbjhTUDYf8yoA9Lyapll1t2w17AqzSHrbBPNFrq931IUCis64b51kslQZoq3c0KslZsBQiky-YSnuE7xUuNsgiF4hOak_ReFFufWE53h1sPkjHFeRM-H34b-e1MCNxGObGPhI0mNlJ_qz0c8aCuS-jdnDocSvIYda-iVcJLWcIT-mO6klw', + }, + { + id: '4', + variant: 'featured', + title: '趣味英语单词卡', + metaLine: '语言学习 · 4-8岁 · ⭐⭐⭐', + tags: ['畅销经典', '全彩打印'], + }, +]; + +const MOCK_DOWNLOADS: DownloadSection[] = [ + { + key: 'today', + label: '今天', + items: [ + { + id: 'd1', + title: '拼音描红 · 声母表', + time: '14:32', + status: '已保存相册', + statusHighlight: true, + thumb: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBCm6X4rhibw2_WxP-U0dDyOsiYol3y12OnEXHRkPONZYMQBUcr3Grqo1IbwH3sdWPg5jIUeIbr_AEwv_hFwUOMMz9jTIIMc6Pke1bzSvlQcgdDq-zMEIU4A--IQFX59ac-8BscCajXZcd4v8rE6JEZFf1-aztKclPTkE_Rrgj-nbVCy5V6bOE6yDOB67VdTbcqiOdaFN18ju_MgWVo3FQZLwIUZOBahkPl0vDIKA0mkiaGK9eiFpfnERYxkYG3eWJZ40umTYp46Jo', + }, + { + id: 'd2', + title: '趣味口算题卡', + time: '09:15', + status: '已下载 PDF', + statusHighlight: false, + thumb: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBxNORxN_vftYMLaMjZVscL-o9bZnBrfUi7IAqOrFATIztGV12_0ggwgae-fmk0cORWtysZWJJDcAzj0NEyniFxbREoGDuq9U4kytprJkA70_k0MCzPgStmq0bRwu1Gx1j4peglFam8PH6IlPvOMbiHQ7-fMFfYTSBtA1p7Dp06NDOzS2VrMlF8VxjEgihVmhHUrHePVvXlktUkXBKNzrXEXrogO3iOyouTUKyvJh0TeChKUv3KohMoD5ol7C3fjBhdHf7QD4StOQ4', + }, + ], + }, + { + key: 'yesterday', + label: '昨天', + items: [ + { + id: 'd3', + title: '汉字笔画基础训练', + time: '16:40', + status: '已发送邮件', + statusHighlight: false, + placeholder: 'draw', + }, + ], + }, +]; + Page({ data: { + statusBarHeight: 0, + navBlockHeight: 0, + /** 纵向 scroll-view 可视高度(窗口高 − 顶栏占位,避免内容滚入导航下) */ + favScrollHeight: 0, activeTab: 'favorites' as FavoritesTab, - favoriteList: [] as unknown[], - downloadList: [] as unknown[], + favoriteList: MOCK_FAVORITES as FavoriteItem[], + downloadSections: MOCK_DOWNLOADS as DownloadSection[], + }, + + onLoad() { + const win = wx.getWindowInfo(); + const navBlockHeight = win.statusBarHeight + NAV_INNER_PX; + this.setData({ + statusBarHeight: win.statusBarHeight, + navBlockHeight, + favScrollHeight: win.windowHeight - navBlockHeight, + }); }, onShow() { @@ -24,4 +150,33 @@ Page({ onDiscoverTap() { wx.switchTab({ url: '/pages/home/home' }); }, + + onSearchTap() { + wx.showToast({ title: '搜索功能开发中', icon: 'none' }); + }, + + onRemoveFavorite(e: WechatMiniprogram.TouchEvent) { + const id = e.currentTarget.dataset.id as string | undefined; + if (!id) return; + const list = (this.data.favoriteList as FavoriteItem[]).filter((item) => item.id !== id); + this.setData({ favoriteList: list }); + wx.showToast({ title: '已取消收藏', icon: 'none' }); + }, + + onClearHistory() { + wx.showModal({ + title: '清空下载历史', + content: '确定清空本地展示的历史记录吗?', + success: (res) => { + if (!res.confirm) return; + this.setData({ downloadSections: [] }); + wx.showToast({ title: '已清空', icon: 'none' }); + }, + }); + }, + + onDownloadMore(e: WechatMiniprogram.TouchEvent) { + const id = e.currentTarget.dataset.id as string | undefined; + wx.showToast({ title: id ? `更多操作:${id}` : '更多', icon: 'none' }); + }, }); diff --git a/miniprogram/pages/favorites/favorites.wxml b/miniprogram/pages/favorites/favorites.wxml index 0b37144..53925aa 100644 --- a/miniprogram/pages/favorites/favorites.wxml +++ b/miniprogram/pages/favorites/favorites.wxml @@ -1,38 +1,216 @@ - - 我的收藏 - - - - 收藏的题型 - - - 下载历史 + + + + 我的收藏 - - 🦆 - - 鸭丫还没找到收藏呢 - 去发现好玩的练习纸吧~ - - - + + + + + 收藏的题型 + + + 下载历史 + + - - 🦆 - - 还没有下载记录哦 - 快去生成练习纸吧~ + + + + 🦆 + 鸭丫还没找到收藏呢 + 去发现好玩的练习纸吧~ + + + + + + + + {{item.title}} + + {{item.metaLine}} + + + + + + + {{item.timeBadge}} + + + + + + + + {{item.title}} + {{item.metaLine}} + + # {{tag}} + + + + + + + + + + + 发现更多有趣的内容? + + + + + + + + + 暂无下载记录 + + + + + + + + + + + + + {{row.title}} + + + {{row.time}} + + {{row.status}} + + + + + + + + + + - - + diff --git a/miniprogram/pages/home/home.data.ts b/miniprogram/pages/home/home.data.ts new file mode 100644 index 0000000..f0b070f --- /dev/null +++ b/miniprogram/pages/home/home.data.ts @@ -0,0 +1,748 @@ +import { AGE_BANDS } from '../../core/data/difficulty'; + +export type HomeDisplayItem = { + id: string; + title: string; + subtitle: string; + description?: string; + category: 'math' | 'chinese' | 'english' | 'puzzle' | 'craft'; + ageBand: string; + difficulty: '入门' | '基础' | '进阶' | '挑战'; + icon: string; + badge?: string; + path: string; + available: boolean; + source?: string; +}; + +export type HomeDisplaySection = { + id: string; + title: string; + subtitle: string; + morePath?: string; + items: HomeDisplayItem[]; +}; + +export type HomeDisplayDataset = { + searchPlaceholder: string; + categoryTabs: Array<{ + id: string; + name: string; + path?: string; + }>; + ageBands: Array<{ + key: string; + label: string; + desc: string; + path: string; + }>; + featured: HomeDisplayItem[]; + hot: HomeDisplayItem[]; + sections: HomeDisplaySection[]; +}; + +const HOME_CATEGORY_TABS = [ + { id: 'all', name: '全部' }, + { id: 'math', name: '数感启蒙' }, + { id: 'puzzle', name: '益智游戏' }, + { id: 'chinese', name: '趣味识字' }, + { id: 'english', name: '英语启蒙' }, + { id: 'craft', name: '创意手工' }, +] as const; + +const HOME_AGE_BANDS = AGE_BANDS.map((item, index) => ({ + key: item.key, + label: item.label, + desc: ['启蒙认知', '基础练习', '能力提升', '幼小衔接', '知识拓展'][index], + path: '/pages/age/age', +})); + +export const HOME_PRODUCT_TARGET_DATA: HomeDisplayDataset = { + searchPlaceholder: '搜索你喜欢的练习册...', + categoryTabs: [...HOME_CATEGORY_TABS], + ageBands: HOME_AGE_BANDS, + featured: [ + { + id: 'target-featured-1', + title: '圣诞主题涂色卡', + subtitle: '节日限定创意内容', + description: '首页推荐位示例,强调季节热点与真实打印效果。', + category: 'craft', + ageBand: '3-8岁', + difficulty: '入门', + icon: '🎄', + badge: 'NEW', + path: '', + available: false, + source: '产品设计文档 5.1 今日推荐', + }, + { + id: 'target-featured-2', + title: '走迷宫大挑战', + subtitle: '热门趣味训练', + description: '产品规划中的高趣味内容,用于首页焦点推荐。', + category: 'puzzle', + ageBand: '4-7岁', + difficulty: '进阶', + icon: '🌀', + badge: 'HOT', + path: '', + available: false, + source: '产品设计文档 P2-22', + }, + { + id: 'target-featured-3', + title: '拼音描红', + subtitle: '幼小衔接高频内容', + description: '语文模块重点新增内容,适合推荐位曝光。', + category: 'chinese', + ageBand: '5-7岁', + difficulty: '基础', + icon: '🔤', + badge: '推荐', + path: '', + available: false, + source: '产品设计文档 P1-12', + }, + ], + hot: [ + { + id: 'target-hot-1', + title: '迷宫', + subtitle: '高趣味路径探索', + category: 'puzzle', + ageBand: '4-7岁', + difficulty: '进阶', + icon: '🧭', + badge: '热门', + path: '', + available: false, + source: '产品设计文档 5.1 热门下载', + }, + { + id: 'target-hot-2', + title: '涂色卡', + subtitle: '动物与节日主题', + category: 'craft', + ageBand: '3-6岁', + difficulty: '入门', + icon: '🎨', + badge: '热门', + path: '', + available: false, + source: '产品设计文档 P3-29', + }, + { + id: 'target-hot-3', + title: '加减法', + subtitle: '家长高频打印内容', + category: 'math', + ageBand: '5-7岁', + difficulty: '基础', + icon: '➕', + badge: '热门', + path: '/mathPages/addition/addition', + available: true, + source: '现有加减法页面可承接', + }, + ], + sections: [ + { + id: 'math', + title: '数学启蒙', + subtitle: '数字认知、计数练习、加减运算、时钟与形状', + morePath: '/pages/mathIndex/mathIndex', + items: [ + { + id: 'target-math-1', + title: '数字认知', + subtitle: '认识数字 1-10', + category: 'math', + ageBand: '3-4岁', + difficulty: '入门', + icon: '🔢', + path: '/mathPages/numberFind/numberFind', + available: true, + source: '现有 找数字/写数字 能力', + }, + { + id: 'target-math-2', + title: '计数练习', + subtitle: '数一数、连一连、选一选', + category: 'math', + ageBand: '4-5岁', + difficulty: '基础', + icon: '🧮', + path: '/mathPages/countMatch/countMatch', + available: true, + source: '现有 countMatch/countingSelect', + }, + { + id: 'target-math-3', + title: '加减运算', + subtitle: '从 5 以内到 100 以内', + category: 'math', + ageBand: '5-7岁', + difficulty: '基础', + icon: '➕', + path: '/mathPages/addition/addition', + available: true, + source: '现有 addition/calculationPractice', + }, + { + id: 'target-math-4', + title: '时钟练习', + subtitle: '认识钟表与时间', + category: 'math', + ageBand: '5-7岁', + difficulty: '进阶', + icon: '🕒', + path: '', + available: false, + source: '产品设计文档 P1-7', + }, + { + id: 'target-math-5', + title: '形状认知', + subtitle: '图形分类与空间感', + category: 'math', + ageBand: '3-5岁', + difficulty: '入门', + icon: '📐', + path: '', + available: false, + source: '产品设计文档 3.1 信息架构', + }, + ], + }, + { + id: 'chinese', + title: '语文启蒙', + subtitle: '识字、练字、拼音、表达训练', + morePath: '/pages/index/index', + items: [ + { + id: 'target-chinese-1', + title: '识字卡', + subtitle: '自定义汉字卡片', + category: 'chinese', + ageBand: '3-6岁', + difficulty: '入门', + icon: '📖', + path: '/pages/index/index', + available: true, + source: '现有识字页', + }, + { + id: 'target-chinese-2', + title: '练字帖', + subtitle: '田字格笔顺练习', + category: 'chinese', + ageBand: '4-7岁', + difficulty: '基础', + icon: '✏️', + path: '/pages/copyBook/copyBook', + available: true, + source: '现有练字页', + }, + { + id: 'target-chinese-3', + title: '拼音练习', + subtitle: '声母韵母描红', + category: 'chinese', + ageBand: '5-7岁', + difficulty: '基础', + icon: '🔤', + path: '', + available: false, + source: '产品设计文档 P1-12', + }, + { + id: 'target-chinese-4', + title: '看图说话', + subtitle: '图片引导表达', + category: 'chinese', + ageBand: '5-7岁', + difficulty: '进阶', + icon: '🗣️', + path: '', + available: false, + source: '产品设计文档 P1-15', + }, + ], + }, + { + id: 'english', + title: '英语启蒙', + subtitle: '字母学习、闪卡、描红与自然拼读', + items: [ + { + id: 'target-english-1', + title: '字母描红', + subtitle: 'A-Z 大小写练习', + category: 'english', + ageBand: '4-6岁', + difficulty: '入门', + icon: '🔠', + path: '', + available: false, + source: '产品设计文档 P1-17', + }, + { + id: 'target-english-2', + title: '字母闪卡', + subtitle: '字母与单词联想', + category: 'english', + ageBand: '3-6岁', + difficulty: '入门', + icon: '🪪', + path: '', + available: false, + source: '产品设计文档 P1-18', + }, + { + id: 'target-english-3', + title: '自然拼读练习纸', + subtitle: 'CVC 单词启蒙', + category: 'english', + ageBand: '5-7岁', + difficulty: '进阶', + icon: '🔡', + path: '', + available: false, + source: '产品设计文档 P1-21', + }, + ], + }, + { + id: 'puzzle', + title: '益智游戏', + subtitle: '迷宫、找不同、连线、数独与控笔', + morePath: '/pages/focusIndex/focusIndex', + items: [ + { + id: 'target-puzzle-1', + title: '迷宫', + subtitle: '不同难度路径挑战', + category: 'puzzle', + ageBand: '4-7岁', + difficulty: '进阶', + icon: '🌀', + path: '', + available: false, + source: '产品设计文档 P2-22', + }, + { + id: 'target-puzzle-2', + title: '找不同', + subtitle: '观察力与专注力训练', + category: 'puzzle', + ageBand: '4-7岁', + difficulty: '进阶', + icon: '🔍', + path: '', + available: false, + source: '产品设计文档 P2-23', + }, + { + id: 'target-puzzle-3', + title: '连线题', + subtitle: '数字点连线与物品连线', + category: 'puzzle', + ageBand: '4-6岁', + difficulty: '基础', + icon: '🧵', + path: '/focusPages/dotConnect/dotConnect', + available: true, + source: '现有 dotConnect/matchConnect', + }, + { + id: 'target-puzzle-4', + title: '数独入门', + subtitle: '4x4 / 6x6 逻辑启蒙', + category: 'puzzle', + ageBand: '6-8岁', + difficulty: '挑战', + icon: '🧠', + path: '', + available: false, + source: '产品设计文档 P2-24', + }, + ], + }, + { + id: 'craft', + title: '创意手工', + subtitle: '涂色卡、折纸、贴纸和主题手工', + items: [ + { + id: 'target-craft-1', + title: '涂色卡', + subtitle: '动物、交通、节日主题', + category: 'craft', + ageBand: '3-6岁', + difficulty: '入门', + icon: '🎨', + path: '', + available: false, + source: '产品设计文档 P3-29', + }, + { + id: 'target-craft-2', + title: '折纸模板', + subtitle: '打印后即可折叠', + category: 'craft', + ageBand: '4-7岁', + difficulty: '基础', + icon: '🪭', + path: '', + available: false, + source: '产品设计文档 P3-30', + }, + { + id: 'target-craft-3', + title: '贴纸打印', + subtitle: '奖励贴纸与装饰贴纸', + category: 'craft', + ageBand: '3-8岁', + difficulty: '入门', + icon: '⭐', + path: '', + available: false, + source: '产品设计文档 P3-33', + }, + ], + }, + ], +}; + +export const HOME_CURRENT_CAPABILITY_DATA: HomeDisplayDataset = { + searchPlaceholder: '搜索你喜欢的练习册...', + categoryTabs: [...HOME_CATEGORY_TABS], + ageBands: HOME_AGE_BANDS, + featured: [ + { + id: 'current-featured-1', + title: '10以内加减法', + subtitle: '最适合首页推荐的高频内容', + description: '现有数学能力中最接近产品文档“热门下载”的内容。', + category: 'math', + ageBand: '5-6岁', + difficulty: '基础', + icon: '➕', + badge: 'HOT', + path: '/mathPages/addition/addition', + available: true, + source: '现有功能清单 addition', + }, + { + id: 'current-featured-2', + title: '涂色识字卡', + subtitle: '兼顾识字与趣味涂色', + description: '当前最接近创意内容的主包能力,可承接语文与创意入口。', + category: 'chinese', + ageBand: '3-6岁', + difficulty: '入门', + icon: '📚', + badge: '推荐', + path: '/pages/index/index', + available: true, + source: '现有识字页', + }, + { + id: 'current-featured-3', + title: '数字点连线', + subtitle: '用连点成图提升专注力', + description: '能承担产品文档中“益智游戏”内容氛围。', + category: 'puzzle', + ageBand: '4-6岁', + difficulty: '基础', + icon: '🔗', + badge: '热门', + path: '/focusPages/dotConnect/dotConnect', + available: true, + source: '现有功能清单 dot-connect', + }, + ], + hot: [ + { + id: 'current-hot-1', + title: '找数字,涂一涂', + subtitle: '低龄启蒙高频内容', + category: 'math', + ageBand: '3-4岁', + difficulty: '入门', + icon: '🔢', + badge: '热门', + path: '/mathPages/numberFind/numberFind', + available: true, + source: 'number-find', + }, + { + id: 'current-hot-2', + title: '数一数,连一连', + subtitle: '点数和数字对应', + category: 'math', + ageBand: '4-5岁', + difficulty: '基础', + icon: '🧮', + badge: '热门', + path: '/mathPages/countMatch/countMatch', + available: true, + source: 'counting-matching', + }, + { + id: 'current-hot-3', + title: '格子仿画 5×5', + subtitle: '图形观察与耐心训练', + category: 'puzzle', + ageBand: '5-7岁', + difficulty: '进阶', + icon: '🟨', + badge: '热门', + path: '/focusPages/gridDrawing/gridDrawing', + available: true, + source: 'grid-drawing-5x5', + }, + ], + sections: [ + { + id: 'math', + title: '数感启蒙', + subtitle: '优先展示现有高价值、高覆盖的数感与运算内容', + morePath: '/pages/mathIndex/mathIndex', + items: [ + { + id: 'current-math-1', + title: '数字认知', + subtitle: '找数字、写数字', + category: 'math', + ageBand: '3-4岁', + difficulty: '入门', + icon: '🔢', + path: '/mathPages/numberFind/numberFind', + available: true, + source: 'number-find / number-write', + }, + { + id: 'current-math-2', + title: '计数练习', + subtitle: '数一数、连一连、选一选', + category: 'math', + ageBand: '4-5岁', + difficulty: '基础', + icon: '🧮', + path: '/mathPages/countMatch/countMatch', + available: true, + source: 'countMatch / countingSelect', + }, + { + id: 'current-math-3', + title: '数字序列', + subtitle: '数字排序与缺失填空', + category: 'math', + ageBand: '4-5岁', + difficulty: '基础', + icon: '📈', + path: '/mathPages/numberSort/numberSort', + available: true, + source: 'numberSort / missingNumber', + }, + { + id: 'current-math-4', + title: '加减运算', + subtitle: '5以内到100以内逐步进阶', + category: 'math', + ageBand: '5-7岁', + difficulty: '基础', + icon: '➕', + path: '/mathPages/addition/addition', + available: true, + source: 'addition / calculationPractice', + }, + { + id: 'current-math-5', + title: '乘法口诀', + subtitle: '九九乘法表启蒙', + category: 'math', + ageBand: '7-8岁', + difficulty: '挑战', + icon: '✖️', + path: '/mathPages/multiplicationTable/multiplicationTable', + available: true, + source: 'multiplicationTable', + }, + ], + }, + { + id: 'chinese', + title: '趣味识字', + subtitle: '用现有识字与练字能力先覆盖核心语文场景', + morePath: '/pages/index/index', + items: [ + { + id: 'current-chinese-1', + title: '识字卡', + subtitle: '最多 6 个汉字,自定义颜色', + category: 'chinese', + ageBand: '3-6岁', + difficulty: '入门', + icon: '📖', + path: '/pages/index/index', + available: true, + source: 'pages/index/index', + }, + { + id: 'current-chinese-2', + title: '找一找识字', + subtitle: '识字 find 模板,更接近游戏化练习', + category: 'chinese', + ageBand: '4-6岁', + difficulty: '基础', + icon: '🔍', + path: '/pages/index/index', + available: true, + source: '识字页 find 模板', + }, + { + id: 'current-chinese-3', + title: '笔顺练字帖', + subtitle: '田字格与笔顺描红', + category: 'chinese', + ageBand: '4-7岁', + difficulty: '基础', + icon: '✏️', + path: '/pages/copyBook/copyBook', + available: true, + source: 'pages/copyBook/copyBook', + }, + ], + }, + { + id: 'puzzle', + title: '益智游戏', + subtitle: '从现有专注力模块中抽取最符合产品重设计的内容', + morePath: '/pages/focusIndex/focusIndex', + items: [ + { + id: 'current-puzzle-1', + title: '数字点连线', + subtitle: '按顺序连点成图', + category: 'puzzle', + ageBand: '4-6岁', + difficulty: '基础', + icon: '🔗', + path: '/focusPages/dotConnect/dotConnect', + available: true, + source: 'dot-connect', + }, + { + id: 'current-puzzle-2', + title: '连连看', + subtitle: '物品配对与关系匹配', + category: 'puzzle', + ageBand: '4-6岁', + difficulty: '基础', + icon: '🧵', + path: '/focusPages/matchConnect/matchConnect', + available: true, + source: 'match-connect', + }, + { + id: 'current-puzzle-3', + title: '颜色找规律', + subtitle: '模式识别与推理', + category: 'puzzle', + ageBand: '4-6岁', + difficulty: '基础', + icon: '🌈', + path: '/focusPages/colorPattern/colorPattern', + available: true, + source: 'color-pattern', + }, + { + id: 'current-puzzle-4', + title: '方格推理', + subtitle: '逻辑关系与观察力', + category: 'puzzle', + ageBand: '5-7岁', + difficulty: '进阶', + icon: '🧠', + path: '/focusPages/gridReasoning/gridReasoning', + available: true, + source: 'grid-reasoning', + }, + { + id: 'current-puzzle-5', + title: '格子仿画', + subtitle: '3×3 / 5×5 / 7×7 逐级挑战', + category: 'puzzle', + ageBand: '4-7岁', + difficulty: '进阶', + icon: '🟨', + path: '/focusPages/gridDrawing/gridDrawing', + available: true, + source: 'grid-drawing', + }, + ], + }, + { + id: 'craft', + title: '创意手工', + subtitle: '暂用最接近创意表达的现有能力承接展示位', + morePath: '/pages/index/index', + items: [ + { + id: 'current-craft-1', + title: '涂色识字卡', + subtitle: '在识字场景里提供涂色表达', + category: 'craft', + ageBand: '3-6岁', + difficulty: '入门', + icon: '🎨', + path: '/pages/index/index', + available: true, + source: '现有识字卡着色能力', + }, + { + id: 'current-craft-2', + title: '字帖打印', + subtitle: '打印后可反复练习', + category: 'craft', + ageBand: '4-7岁', + difficulty: '基础', + icon: '🖌️', + path: '/pages/copyBook/copyBook', + available: true, + source: '练字页打印能力', + }, + ], + }, + { + id: 'english', + title: '英语启蒙', + subtitle: + '当前暂无真实英语页面,先保留结构占位,便于首页保持完整信息架构', + items: [ + { + id: 'current-english-1', + title: '英语启蒙即将上线', + subtitle: '字母描红、闪卡、自然拼读', + category: 'english', + ageBand: '4-7岁', + difficulty: '入门', + icon: '🔤', + badge: 'Soon', + path: '', + available: false, + source: '产品目标结构保留位', + }, + ], + }, + ], +}; + +export const HOME_DISPLAY_DATA = { + productTarget: HOME_PRODUCT_TARGET_DATA, + currentCapability: HOME_CURRENT_CAPABILITY_DATA, +} as const; diff --git a/miniprogram/pages/home/home.less b/miniprogram/pages/home/home.less index 32b4d36..3b9562e 100644 --- a/miniprogram/pages/home/home.less +++ b/miniprogram/pages/home/home.less @@ -1,7 +1,5 @@ @import '../../style/theme3.less'; -@home-padding: 28rpx; -@home-section-mt: 48rpx; @home-track-gap: 16rpx; .home-page { @@ -10,16 +8,23 @@ padding-bottom: 48rpx; box-sizing: border-box; color: @text-secondary; - // padding: 0 24rpx 48rpx; } .home-nav { - background: @bg-header; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; + background: fade(@bg-header, 85%); + backdrop-filter: blur(40rpx); + -webkit-backdrop-filter: blur(40rpx); + box-shadow: 0 2rpx 12rpx rgba(50, 46, 37, 0.05); box-sizing: content-box; } .home-nav__inner { - height: 44px; + height: @nav-inner-height; display: flex; flex-direction: row; align-items: center; @@ -35,15 +40,21 @@ } .home-nav__name { - font-size: 17px; - font-weight: 700; - color: @text-title; + font-size: @fs-nav-title; + font-weight: @fw-nav-title; + color: @color-nav-title; + letter-spacing: @ls-nav-title; line-height: 1; } +.home-body { + width: 100%; + box-sizing: border-box; +} + .home-search-wrap { - margin-top: 28rpx; - padding: 0 @home-padding; + margin-top: @section-gap-xs; + padding: 0 @page-padding-x; box-sizing: border-box; } @@ -70,26 +81,117 @@ } .home-tabs-wrap { - margin-top: 48rpx; + margin-top: @section-gap; } .home-hero-placeholder { - // margin: 8rpx auto 0; - margin: 48rpx 24rpx 0; - // width: 684rpx; + margin: @section-gap @page-padding-x 0; height: 480rpx; border-radius: 40rpx; background: rgba(50, 46, 37, 0.04); } -.home-section-panel { - margin-top: @home-section-mt; - padding: 0 @home-padding; - box-sizing: border-box; +.home-track-row--hero { + padding-left: @page-padding-x; + padding-top: 24rpx; + padding-bottom: 24rpx; } -.home-section-panel--hot { - margin-top: @home-section-mt; +.home-hero-card { + width: 560rpx; + flex-shrink: 0; + border-radius: 32rpx; + /* 让阴影不被自身 overflow: hidden 裁切 */ + overflow: visible; + background: #ffffff; + box-shadow: 0 10rpx 28rpx rgba(50, 46, 37, 0.08); +} + +.home-hero-card__top { + height: 260rpx; + position: relative; + display: flex; + align-items: center; + justify-content: center; + /* 顶部背景用自身圆角裁切,替代 card 的 overflow: hidden */ + border-top-left-radius: 32rpx; + border-top-right-radius: 32rpx; + overflow: hidden; +} + +.home-hero-card__icon { + font-size: 96rpx; + line-height: 1; +} + +.home-hero-card__badge { + position: absolute; + top: 24rpx; + right: 24rpx; + padding: 8rpx 18rpx; + border-radius: 999rpx; + background: rgba(50, 46, 37, 0.12); + font-size: 22rpx; + color: @text-title; + font-weight: 600; +} + +.home-hero-card__body { + padding: 28rpx @page-padding-inner-x 30rpx; +} + +.home-hero-card__title-row { + display: flex; + flex-direction: row; + align-items: baseline; + justify-content: space-between; + gap: 16rpx; + /* 允许 flex 子项在文本过长时正确截断 */ + min-width: 0; +} + +.home-hero-card__title { + font-size: 34rpx; + line-height: 1.35; + color: @text-title; + font-weight: 700; + margin: 0; + flex: 1; + min-width: 0; + /* 标题与 meta 同行:超出后省略号(避免高度抖动) */ + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.home-hero-card__sub { + margin-top: 10rpx; + font-size: 24rpx; + line-height: 1.4; + color: @text-secondary; + /* 第二行:超出后省略号 */ + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + + +.home-hero-card__meta { + margin: 0; + font-size: 22rpx; + color: @text-gray; + /* meta 也做单行截断,防止极端长文本把布局挤爆 */ + flex: 0 1 auto; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.home-section-panel { + margin-top: @section-gap; + padding: 0 @page-padding-x; + box-sizing: border-box; } .home-section-head { @@ -99,7 +201,7 @@ } .home-section-head__title { - font-size: 32rpx; + font-size: @fs-section-head-title; font-weight: 700; color: @text-title; } @@ -110,26 +212,27 @@ } .home-section-head__title--module { - font-size: 28rpx; + font-size: @fs-section-head-title; font-weight: 700; } .home-track-scroll { - margin-top: 24rpx; + margin-top: @section-gap-sm; width: 100%; white-space: nowrap; + padding: 16rpx 0 20rpx; + box-sizing: border-box; } .home-track-row { display: inline-flex; flex-direction: row; - gap: @home-track-gap; - padding-right: @home-padding; + gap: @section-gap-sm; + padding-right: @page-padding-x; } -/* 可视约 2.2 张:2*w + 2*gap + 0.2*w ≈ 屏宽减左右 padding */ .home-track-card { - width: calc((750rpx - 2 * @home-padding - 2 * @home-track-gap) / 2.2); + width: calc((750rpx - 2 * @page-padding-x - 2 * @home-track-gap) / 2.2); flex-shrink: 0; border-radius: 24rpx; overflow: hidden; @@ -150,7 +253,7 @@ } .home-track-card__body { - padding: 20rpx 18rpx 22rpx; + padding: 20rpx @page-padding-inner-x 22rpx; background: #ffffff; } @@ -177,7 +280,7 @@ } .home-age-scroll { - margin-top: 32rpx; + margin-top: @section-gap-sm; width: 100%; white-space: nowrap; } @@ -186,12 +289,11 @@ display: inline-flex; flex-direction: row; gap: 12rpx; - padding-right: @home-padding; + padding-right: @page-padding-x; } .home-age-item { - /* 目标:首屏露出 3.5 个(第 4 个露出约 50%) */ - width: calc((750rpx - 48rpx - 36rpx) / 3.5); + width: calc((750rpx - 2 * @page-padding-x - 36rpx) / 3.5); display: flex; flex-direction: column; align-items: center; @@ -225,17 +327,24 @@ white-space: nowrap; } +.home-age-item__desc { + margin-top: 8rpx; + font-size: 22rpx; + color: @text-gray; + white-space: nowrap; +} + .home-hot-list { - margin-top: 36rpx; + margin-top: @section-gap-sm; display: flex; flex-direction: column; - gap: 28rpx; + gap: @section-gap-stack; } .home-hot-card { background: #ffffff; border-radius: 24rpx; - padding: 24rpx; + padding: 24rpx @page-padding-inner-x; display: flex; align-items: center; gap: 20rpx; @@ -246,7 +355,6 @@ width: 144rpx; height: 144rpx; border-radius: 24rpx; - background: linear-gradient(140deg, #ad060f 0%, #d93b0d 40%, #a20606 100%); display: flex; align-items: center; justify-content: center; @@ -289,7 +397,7 @@ .home-hot-card__tag { font-size: 22rpx; color: @text-selected-btn; - background: #ffd709; + background: @brand; border-radius: 999rpx; padding: 4rpx 16rpx; } @@ -303,7 +411,7 @@ width: 56rpx; height: 56rpx; border-radius: 50%; - background: #ffd709; + background: @brand; color: #2b2b2b; font-size: 36rpx; line-height: 56rpx; @@ -311,6 +419,22 @@ flex-shrink: 0; } +.home-section-panel__sub { + margin-top: 10rpx; + font-size: 24rpx; + line-height: 1.5; + color: @text-gray; + display: block; +} + +.home-track-card__meta { + margin-top: 12rpx; + font-size: 22rpx; + color: @text-gray; + line-height: 1.3; + display: block; +} + .home-page .category-tabs { background: @bg-page; } @@ -321,7 +445,7 @@ } .home-page .category-tabs__pill--active { - background: #ffd709; + background: @brand; color: @text-selected-btn; font-weight: 600; } \ No newline at end of file diff --git a/miniprogram/pages/home/home.ts b/miniprogram/pages/home/home.ts index 006cc9b..f626fa1 100644 --- a/miniprogram/pages/home/home.ts +++ b/miniprogram/pages/home/home.ts @@ -1,169 +1,185 @@ -import { CATEGORIES } from '../../core/data/categories'; -import { AGE_BANDS, AgeBandKey } from '../../core/data/difficulty'; +import { NAV_INNER_PX } from '../../utils/navMetrics'; +import { + HOME_CURRENT_CAPABILITY_DATA, + type HomeDisplayItem, + type HomeDisplaySection, +} from './home.data'; -const CATEGORY_TABS = [ - { id: 'all', name: '全部' }, - ...CATEGORIES.map((c) => ({ id: c.id, name: c.name })), -]; +type AgeBandKey = string; -const AGE_BAND_ICONS = ['🌱', '🚀', '🎓', '🧠', '🏆'] as const; - -type HomeTrackCard = { +type HomeTab = { id: string; - title: string; - subtitle: string; + name: string; +}; + +type HomeAgeBandView = { + key: string; + label: string; + desc: string; + path: string; icon: string; +}; + +type HomeDisplayItemView = HomeDisplayItem & { topBg: string; }; -const MOCK_MATH_TRACK: HomeTrackCard[] = [ - { - id: 'm1', - title: '认识数字1-10', - subtitle: '启蒙第一课', - icon: '🔢', - topBg: '#fff3c4', - }, - { - id: 'm2', - title: '形状对对碰', - subtitle: '空间感训练', - icon: '📐', - topBg: '#f5e6dc', - }, - { - id: 'm3', - title: '数量比一比', - subtitle: '数感小游戏', - icon: '⚖️', - topBg: '#d8f0d0', - }, - { - id: 'm4', - title: '规律找一找', - subtitle: '逻辑思维', - icon: '🔍', - topBg: '#e8f4ff', - }, - { - id: 'm5', - title: '简单加减', - subtitle: '动手练一练', - icon: '➕', - topBg: '#fce4ec', - }, -]; +type HomeDisplaySectionView = HomeDisplaySection & { + anchorId: string; + items: HomeDisplayItemView[]; +}; -const MOCK_CHINESE_TRACK: HomeTrackCard[] = [ - { - id: 'c1', - title: '基础笔画连连看', - subtitle: '书法初体验', - icon: '🖌', - topBg: '#f5e6dc', - }, - { - id: 'c2', - title: '看图识汉字', - subtitle: '字形联想', - icon: '📖', - topBg: '#d8f0d0', - }, - { - id: 'c3', - title: '拼音小闯关', - subtitle: '声母韵母', - icon: '🔤', - topBg: '#fff3c4', - }, - { - id: 'c4', - title: '词语搭配', - subtitle: '词汇积累', - icon: '✏️', - topBg: '#e8f4ff', - }, - { - id: 'c5', - title: '古诗诵读', - subtitle: '语感培养', - icon: '🎵', - topBg: '#fce4ec', - }, -]; +const AGE_BAND_ICONS = ['🌱', '🚀', '🎓', '🧠', '🏆'] as const; -const MOCK_HOT_RECOMMENDS = [ - { - id: 'hot-1', - title: '100以内的加减法', - desc: '让算术变得像游戏一样简单', - tag: '数学', - score: '4.8', - emoji: '🍊', - }, - { - id: 'hot-2', - title: '创意涂鸦大作战', - desc: '释放孩子的艺术想象力', - tag: '艺术', - score: '4.9', - emoji: '🎨', - }, -] as const; +const CATEGORY_TOP_BG: Record = { + math: '#fff3c4', + chinese: '#d8f0d0', + english: '#e8f4ff', + puzzle: '#f5e6dc', + craft: '#fce4ec', +}; -const { statusBarHeight } = wx.getWindowInfo(); +const SECTION_ANCHOR_PREFIX = 'section-'; +const TAB_BAR_PATHS = new Set([ + '/pages/home/home', + '/pages/age/age', + '/pages/favorites/favorites', + '/pages/profile/profile', +]); + +function toItemView(item: HomeDisplayItem): HomeDisplayItemView { + return { + ...item, + topBg: CATEGORY_TOP_BG[item.category], + }; +} + +function sortSectionsByTabs( + tabs: readonly { id: string }[], + sections: HomeDisplaySection[], +): HomeDisplaySectionView[] { + const orderMap = tabs.reduce>((acc, tab, index) => { + acc[tab.id] = index; + return acc; + }, {}); + + return [...sections] + .sort((a, b) => { + const aOrder = orderMap[a.id]; + const bOrder = orderMap[b.id]; + // 注意:0 需要保留(不能用 || 进行兜底) + return (aOrder ?? 999) - (bOrder ?? 999); + }) + .map((section) => ({ + ...section, + anchorId: `${SECTION_ANCHOR_PREFIX}${section.id}`, + items: section.items.map(toItemView), + })); +} + +function navigateByPath(path?: string, fallbackTitle?: string) { + if (!path) { + wx.showToast({ + title: fallbackTitle ? `${fallbackTitle} 即将上线` : '即将上线', + icon: 'none', + }); + return; + } + + if (TAB_BAR_PATHS.has(path)) { + wx.switchTab({ url: path }); + return; + } + + wx.navigateTo({ url: path }); +} + +const win = wx.getWindowInfo(); +const HOME_DATA = HOME_CURRENT_CAPABILITY_DATA; +const HOME_TABS: HomeTab[] = HOME_DATA.categoryTabs.map((item) => ({ + id: item.id, + name: item.name, +})); +const HOME_SECTIONS = sortSectionsByTabs(HOME_TABS, HOME_DATA.sections); +console.log('HOME_DATA', HOME_DATA); +console.log('HOME_SECTIONS', HOME_SECTIONS); Page({ data: { - statusBarHeight, - tabs: CATEGORY_TABS, + statusBarHeight: win.statusBarHeight, + navBlockHeight: win.statusBarHeight + NAV_INNER_PX, + searchPlaceholder: HOME_DATA.searchPlaceholder, + tabs: HOME_TABS, activeTab: 'all', - ageBands: AGE_BANDS.map((item, idx) => ({ + ageBands: HOME_DATA.ageBands.map((item, idx) => ({ ...item, icon: AGE_BAND_ICONS[idx] || '🌟', - })), - activeAgeBand: AGE_BANDS[0].key as AgeBandKey, - hotRecommends: [...MOCK_HOT_RECOMMENDS], - mathTrackCards: MOCK_MATH_TRACK, - chineseTrackCards: MOCK_CHINESE_TRACK, + })) as HomeAgeBandView[], + activeAgeBand: HOME_DATA.ageBands[0]?.key || ('3-4' as AgeBandKey), + featuredItems: HOME_DATA.featured.map(toItemView), + hotRecommends: HOME_DATA.hot.map(toItemView), + sections: HOME_SECTIONS, }, onTabChange(e: WechatMiniprogram.CustomEvent) { - this.setData({ activeTab: e.detail.id }); + const id = e.detail.id as string | undefined; + if (!id) return; + + this.setData({ activeTab: id }); + + if (id === 'all') { + wx.pageScrollTo({ + scrollTop: 0, + duration: 400, + }); + return; + } + + wx.pageScrollTo({ + selector: `#${SECTION_ANCHOR_PREFIX}${id}`, + duration: 400, + offsetTop: -(win.statusBarHeight + NAV_INNER_PX + 24), + }); }, onTapAgeBand(e: WechatMiniprogram.TouchEvent) { const key = e.currentTarget.dataset.key as AgeBandKey | undefined; + const path = e.currentTarget.dataset.path as string | undefined; if (!key) return; this.setData({ activeAgeBand: key }); + navigateByPath(path, '分龄内容'); + }, + + onTapFeatured(e: WechatMiniprogram.TouchEvent) { + const path = e.currentTarget.dataset.path as string | undefined; + const title = e.currentTarget.dataset.title as string | undefined; + navigateByPath(path, title); }, onTapHotRecommend(e: WechatMiniprogram.TouchEvent) { + const path = e.currentTarget.dataset.path as string | undefined; const title = e.currentTarget.dataset.title as string | undefined; - if (!title) return; - wx.showToast({ title: `已选择:${title}`, icon: 'none' }); + navigateByPath(path, title); }, onTapAgeMore() { - wx.showToast({ title: '分龄全部 即将上线', icon: 'none' }); + wx.switchTab({ url: '/pages/age/age' }); }, onTapHotMore() { - wx.showToast({ title: '热门推荐 即将上线', icon: 'none' }); + wx.showToast({ title: '热门内容整理中', icon: 'none' }); }, onTapTrackMore(e: WechatMiniprogram.TouchEvent) { - const category = e.currentTarget.dataset.category as string | undefined; - if (!category) return; - wx.navigateTo({ - url: `/pages/category/category?category=${category}`, - }); + const path = e.currentTarget.dataset.path as string | undefined; + const title = e.currentTarget.dataset.title as string | undefined; + navigateByPath(path, title); }, onTapTrackCard(e: WechatMiniprogram.TouchEvent) { + const path = e.currentTarget.dataset.path as string | undefined; const title = e.currentTarget.dataset.title as string | undefined; - if (!title) return; - wx.showToast({ title, icon: 'none' }); + navigateByPath(path, title); }, onShareAppMessage() { diff --git a/miniprogram/pages/home/home.wxml b/miniprogram/pages/home/home.wxml index 6f3cd9e..acc72ba 100644 --- a/miniprogram/pages/home/home.wxml +++ b/miniprogram/pages/home/home.wxml @@ -9,6 +9,7 @@ + - 搜索你喜欢的练习册... + {{searchPlaceholder}} @@ -27,10 +28,35 @@ bind:change="onTabChange" /> - + + + + + {{item.icon}} + {{item.badge}} + + + + {{item.title}} + {{item.ageBand}} · {{item.difficulty}} + + {{item.subtitle}} + + + + @@ -46,12 +72,14 @@ wx:key="key" class="home-age-item" data-key="{{item.key}}" + data-path="{{item.path}}" bindtap="onTapAgeBand"> {{item.icon}} {{item.label}} + {{item.desc}} @@ -70,47 +98,58 @@ wx:key="id" class="home-hot-card" data-title="{{item.title}}" + data-path="{{item.path}}" bindtap="onTapHotRecommend"> - + {{item.emoji}}{{item.icon}} {{item.title}} - {{item.desc}} + {{item.subtitle}} - {{item.tag}} + {{item.ageBand}} {{item.score}} ★{{item.difficulty}} - + + - + 数感启蒙{{item.title}} 更多 + {{item.subtitle}} {{item.subtitle}} + {{item.ageBand}} · {{item.difficulty}} - - - - 趣味中文 - 更多 - - - - - - {{item.icon}} - - - {{item.title}} - {{item.subtitle}} - - - - diff --git a/miniprogram/pages/profile/profile.json b/miniprogram/pages/profile/profile.json index 6eaa5bd..e05eddf 100644 --- a/miniprogram/pages/profile/profile.json +++ b/miniprogram/pages/profile/profile.json @@ -1,6 +1,10 @@ { + "navigationStyle": "custom", "navigationBarTitleText": "我的", "navigationBarTextStyle": "black", - "navigationBarBackgroundColor": "#ffffff", - "backgroundColor": "#F6F6F6" + "navigationBarBackgroundColor": "#F8F0E0", + "backgroundColor": "#F8F0E0", + "usingComponents": { + "van-icon": "@vant/weapp/icon/index" + } } diff --git a/miniprogram/pages/profile/profile.less b/miniprogram/pages/profile/profile.less index c04679a..2170ca9 100644 --- a/miniprogram/pages/profile/profile.less +++ b/miniprogram/pages/profile/profile.less @@ -1,115 +1,357 @@ +@import '../../style/theme3.less'; + +@profile-premium-bg: #2d2a26; + +/* 整页滚动:避免 scroll-view 在真机因未给出明确高度而高度为 0 */ +page { + min-height: 100%; + background: @bg-header; +} + .profile-page { min-height: 100vh; - background: #f6f6f6; - padding-bottom: 48rpx; + background: @bg-header; + box-sizing: border-box; } -.profile-page__header { +/* 顶栏:固定于顶部,正文区独立滚动 */ +.profile-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; + flex-shrink: 0; + background: fade(@bg-header, 85%); + backdrop-filter: blur(40rpx); + -webkit-backdrop-filter: blur(40rpx); + box-shadow: 0 2rpx 12rpx rgba(50, 46, 37, 0.05); +} + +.profile-nav__inner { + height: @nav-inner-height; display: flex; flex-direction: row; align-items: center; - background: #f7ee47; - padding: 48rpx 32rpx; + justify-content: flex-start; + padding: 0 @page-padding-x; + box-sizing: border-box; } -.profile-page__avatar { - width: 112rpx; - height: 112rpx; - border-radius: 50%; - background: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 56rpx; - flex-shrink: 0; +.profile-nav__title { + font-size: @fs-nav-title; + font-weight: @fw-nav-title; + color: @color-nav-title; + letter-spacing: @ls-nav-title; } -.profile-page__header-info { - margin-left: 28rpx; +.profile-nav__brand { + font-size: 40rpx; + font-weight: 800; + color: @brand; + letter-spacing: -0.04em; +} + +.profile-body { + width: 100%; + box-sizing: border-box; +} + +.profile-main { + padding: @section-gap-xs @page-padding-x; + padding-bottom: calc(32rpx + env(safe-area-inset-bottom) + 120rpx); + box-sizing: border-box; +} + +/* 用户主卡 */ +.profile-hero { + position: relative; display: flex; flex-direction: column; - justify-content: center; -} - -.profile-page__name { - font-size: 36rpx; - font-weight: 700; - color: #333; - margin-bottom: 12rpx; -} - -.profile-page__stat { - font-size: 26rpx; - color: #555; -} - -.profile-page__group { - background: #fff; - border-radius: 32rpx; - margin: 24rpx 24rpx 0; - box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06); - overflow: hidden; -} - -.profile-page__item { - display: flex; - flex-direction: row; align-items: center; - padding: 28rpx 32rpx; - border-bottom: 1rpx solid #f0f0f0; + padding: 80rpx @page-padding-inner-x 64rpx; + background: @bg-white; + border-radius: @radius-xl; + border: 1rpx solid rgba(124, 118, 106, 0.12); + box-shadow: @shadow; + overflow: hidden; + margin-bottom: @section-gap-xs; } -.profile-page__item--last { - border-bottom: none; +.profile-hero__tint { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 192rpx; + background: @brand; + opacity: 0.1; + pointer-events: none; } -.profile-page__icon { - font-size: 36rpx; - margin-right: 20rpx; - width: 44rpx; +.profile-hero__avatar-wrap { + position: relative; + margin-top: 16rpx; +} + +.profile-hero__avatar { + width: 256rpx; + height: 256rpx; + border-radius: 50%; + border: 8rpx solid @bg-white; + background: @bg-gray; + box-shadow: @shadow; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; +} + +.profile-hero__avatar-emoji { + font-size: 112rpx; + line-height: 1; +} + +.profile-hero__avatar-img { + width: 100%; + height: 100%; +} + +.profile-hero__edit-badge { + position: absolute; + right: 8rpx; + bottom: 8rpx; + width: 56rpx; + height: 56rpx; + border-radius: 50%; + background: @brand; + border: 8rpx solid @bg-white; + display: flex; + align-items: center; + justify-content: center; + box-shadow: @shadow; + box-sizing: border-box; +} + +.profile-hero__name { + margin-top: 32rpx; + font-size: 40rpx; + font-weight: 700; + color: @text-title; text-align: center; } -.profile-page__label { +.profile-hero__stats { + display: flex; + flex-direction: row; + width: 100%; + margin-top: 64rpx; + padding: 0 @page-padding-inner-x; + box-sizing: border-box; +} + +.profile-hero__stat { flex: 1; - font-size: 30rpx; - color: #333; + background: @bg-header; + border-radius: @radius; + border: 1rpx solid rgba(124, 118, 106, 0.12); + padding: 32rpx 24rpx; + text-align: center; + box-sizing: border-box; } -.profile-page__badge { - font-size: 20rpx; - color: #fff; - background: #ff4757; - padding: 4rpx 12rpx; - border-radius: 8rpx; - margin-right: 12rpx; +.profile-hero__stat+.profile-hero__stat { + margin-left: 32rpx; } -.profile-page__arrow { +.profile-hero__stat-num { + display: block; font-size: 36rpx; - color: #ccc; - font-weight: 300; + font-weight: 800; + color: @text-title; + line-height: 1.2; } -.profile-page__footer { +.profile-hero__stat-label { + display: block; + margin-top: 8rpx; + font-size: 20rpx; + font-weight: 600; + color: @text-secondary; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +/* 双格 */ +.profile-grid { + display: flex; + flex-direction: row; + margin-bottom: @section-gap-xs; +} + +.profile-grid__cell { + flex: 1; display: flex; flex-direction: column; align-items: center; - padding: 64rpx 32rpx 32rpx; + padding: 48rpx 24rpx; + background: @bg-white; + border-radius: @radius-xl; + border: 1rpx solid rgba(124, 118, 106, 0.12); + box-sizing: border-box; } -.profile-page__footer-emoji { - font-size: 72rpx; - margin-bottom: 16rpx; +.profile-grid__cell+.profile-grid__cell { + margin-left: 32rpx; } -.profile-page__footer-slogan { - font-size: 26rpx; - color: #999; - margin-bottom: 12rpx; +.profile-grid__icon-wrap { + width: 96rpx; + height: 96rpx; + border-radius: 50%; + background: @brand; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 32rpx; + box-shadow: @shadow; } -.profile-page__footer-version { +.profile-grid__title { + font-size: 30rpx; + font-weight: 700; + color: @text-title; +} + +.profile-grid__sub { + margin-top: 8rpx; font-size: 22rpx; - color: #ccc; + color: @text-secondary; + text-align: center; + line-height: 1.35; } + +/* 圆角列表 */ +.profile-rows { + display: flex; + flex-direction: column; + margin-bottom: @section-gap-xs; +} + +.profile-row { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 40rpx 40rpx; + background: @bg-white; + border-radius: 999rpx; + border: 1rpx solid rgba(124, 118, 106, 0.12); + box-sizing: border-box; + margin-bottom: 32rpx; +} + +.profile-row--last { + margin-bottom: 0; +} + +.profile-row__left { + display: flex; + flex-direction: row; + align-items: center; + min-width: 0; +} + +.profile-row__icon-bg { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + background: fade(@brand, 18%); + display: flex; + align-items: center; + justify-content: center; + margin-right: 32rpx; + flex-shrink: 0; +} + +.profile-row__label { + font-size: 30rpx; + font-weight: 600; + color: @text-title; +} + +.profile-row__chevron { + font-size: 40rpx; + color: @text-gray; + font-weight: 300; + flex-shrink: 0; + margin-left: 16rpx; +} + +/* 专业版卡 */ +.profile-upsell { + position: relative; + overflow: hidden; + background: @profile-premium-bg; + border-radius: @radius-xl; + padding: 64rpx 48rpx; + box-shadow: @shadow-lg; + margin-bottom: @section-gap-sm; +} + +.profile-upsell__deco { + position: absolute; + top: -24rpx; + right: -32rpx; + pointer-events: none; + opacity: 0.35; +} + +.profile-upsell__body { + position: relative; + z-index: 1; +} + +.profile-upsell__title { + display: block; + font-size: 36rpx; + font-weight: 800; + color: @text-white; +} + +.profile-upsell__desc { + display: block; + margin-top: 16rpx; + font-size: 26rpx; + line-height: 1.45; + color: rgba(255, 255, 255, 0.7); + max-width: 400rpx; +} + +.profile-upsell__btn { + display: inline-flex; + align-items: center; + justify-content: center; + margin-top: 48rpx; + padding: 24rpx 64rpx; + border-radius: 999rpx; + background: @brand; + color: @text-selected-btn; + font-size: 28rpx; + font-weight: 700; + box-shadow: @shadow-lg; +} + +.profile-version { + text-align: center; + padding: 24rpx 0 0; +} + +.profile-version__text { + font-size: 20rpx; + font-weight: 600; + color: @text-secondary; + letter-spacing: 0.2em; + text-transform: uppercase; +} \ No newline at end of file diff --git a/miniprogram/pages/profile/profile.ts b/miniprogram/pages/profile/profile.ts index f3f3df3..22c9680 100644 --- a/miniprogram/pages/profile/profile.ts +++ b/miniprogram/pages/profile/profile.ts @@ -1,13 +1,38 @@ +import { getTodayDownloadCount } from '../../utils/downloadPrint'; +import { NAV_INNER_PX } from '../../utils/navMetrics'; + Page({ data: { - generateCount: 0, + statusBarHeight: 0, + /** 状态栏 + 导航条高度,用于固定顶栏占位 */ + navBlockHeight: 0, + userName: '微信用户', + avatarUrl: '', + printCount: 0, + favoriteCount: 0, version: '', }, onLoad() { + const win = wx.getWindowInfo(); const info = wx.getAccountInfoSync(); const version = info.miniProgram.version || '开发版'; - this.setData({ version }); + this.setData({ + statusBarHeight: win.statusBarHeight, + navBlockHeight: win.statusBarHeight + NAV_INNER_PX, + version, + }); + }, + + onShow() { + this.refreshStats(); + }, + + refreshStats() { + const printCount = getTodayDownloadCount(); + // 收藏数待与收藏页数据源打通后接入 + const favoriteCount = 0; + this.setData({ printCount, favoriteCount }); }, onGoToDownloads() { @@ -20,14 +45,6 @@ Page({ wx.switchTab({ url: '/pages/favorites/favorites' }); }, - onGoToPrintPlan() { - wx.showToast({ title: '功能开发中', icon: 'none' }); - }, - - onGoToStats() { - wx.showToast({ title: '功能开发中', icon: 'none' }); - }, - onGoToGuide() { wx.navigateTo({ url: '/pages/guide/guide' }); }, @@ -36,6 +53,10 @@ Page({ wx.showToast({ title: '功能开发中', icon: 'none' }); }, + onGoToSettings() { + wx.showToast({ title: '功能开发中', icon: 'none' }); + }, + onGoToFeedback() { wx.showToast({ title: '功能开发中', icon: 'none' }); }, @@ -43,4 +64,8 @@ Page({ onGoToAbout() { wx.showToast({ title: '涂鸦丫练习纸', icon: 'none' }); }, + + onGoToPremium() { + wx.showToast({ title: '功能开发中', icon: 'none' }); + }, }); diff --git a/miniprogram/pages/profile/profile.wxml b/miniprogram/pages/profile/profile.wxml index 0ffb4da..32885a4 100644 --- a/miniprogram/pages/profile/profile.wxml +++ b/miniprogram/pages/profile/profile.wxml @@ -1,62 +1,145 @@ - - 🦆 - - 微信用户 - 已生成 {{generateCount}} 张练习纸 + + + 个人中心 - - - 📥 - 下载历史 - - - - ❤️ - 我的收藏 - - - - 📋 - 每日打印计划 - 即将上线 - - - - 📊 - 学习统计 - - - + + + + + + + + 🦆 + + + + + + + {{userName}} + + + {{printCount}} + 打印 + + + {{favoriteCount}} + 收藏 + + + - - - 🖨️ - 打印指南 - - - - ⚙️ - 打印设置 - - - - 💬 - 意见反馈 - - - - ℹ️ - 关于涂鸦丫 - - - + + + + + + + 我的收藏 + 已保存的练习题 + + + + + + 下载历史 + 查看历史文件 + + - - 🦆 - 陪孩子一起,把学习变成游戏 - v{{version}} + + + + + + + + 打印指南 + + + + + + + + + 提交反馈 + + + + + + + + + 设置 + + + + + + + + + 关于涂鸦丫 + + + + + + + + + + 升级专业版 + 解锁 500+ 精选教育模板并获得优先体验。 + 立即升级 + + + + + Doodle Mini v{{version}} + + diff --git a/miniprogram/style/theme3.less b/miniprogram/style/theme3.less index a55c84b..9177aa3 100644 --- a/miniprogram/style/theme3.less +++ b/miniprogram/style/theme3.less @@ -1,53 +1,79 @@ -// v3.0 主题色体系(theme3) -// 主题色 #FFD709 · 页背景 #FEF6E7 · Header #F8F0E0 · 主标题字 #322E25 · 选中按钮字 #6C5A00 +/** + * v3.0 主题令牌(Less 变量) + * + * 使用:在页面/组件 less 顶部 @import '../../style/theme3.less',再用 @brand、@page-padding-x、@fs-nav-title 等 + * + * 色谱速览:主题 #FFD709 · 页背景 #FEF6E7 · Header #F8F0E0 + * · 主标题 #322E25 · 选中/强调字 #6C5A00 + */ -// 品牌 / 主题色 +// ========== 品牌 / 主题色 ========== @brand: #ffd709; -@brand-dark: #e5c200; -@brand-light: #fff4c4; +@brand-dark: #e5c200; // 按下、加深态 +@brand-light: #fff4c4; // 浅底、高亮背景 // 与 theme.less 对齐的别名 @primary-color: @brand; @select-color: @brand; -// 页面结构背景 +// ========== 页面结构背景 ========== @bg-page: #fef6e7; @bg-header: #f8f0e0; @bg-default: @bg-page; @bg-white: #ffffff; @bg-gray: #f5edd8; +@bg-muted: #f5f5f5; // 分龄 Tab 未选中等弱对比底(Stitch) +@bg-week-head: #fafafa; // 分龄周卡头部(Stitch stone-50) -// 文本:标题 / title +// ========== 文本色 ========== @text-title: #322e25; @text-primary: @text-title; @text-default: @text-title; - -// 选中态按钮上的文字 -@text-selected-btn: #6c5a00; - -// 辅助文本(正文可读色,可与标题区分) +@text-selected-btn: #6c5a00; // 黄底按钮上的字、强调整示 @text-secondary: #605b50; @text-light: rgba(50, 46, 37, 0.45); @text-gray: #8a8478; @text-white: #ffffff; @text-disable: rgba(50, 46, 37, 0.35); -// 链接与外链(沿用可读蓝绿,可按需覆盖) +// ========== 链接与外链 ========== @link-color: #2794f4; @wechat-color: #1fe000; -// 边框(适配暖色底) +// ========== 边框 ========== @border: 1rpx solid rgba(50, 46, 37, 0.08); @border-color: rgba(255, 215, 9, 0.35); -// 圆角 +// ========== 页面水平内边距 ========== +// 主内容区与屏幕左右对齐;卡片内略小,避免双重视觉边距 +@page-padding-x: 40rpx; +@page-padding-inner-x: 26rpx; + +// ========== 区块纵向间距 ========== +@section-gap: 60rpx; // 主区块之间(按年龄挑选 ↔ 热门推荐 等) +@section-gap-lg: 48rpx; // 主区块之间(按年龄挑选 ↔ 热门推荐 等) +@section-gap-sm: 32rpx; // 区块内:标题与下方列表 / 横向列表顶距 +@section-gap-stack: 28rpx; // 同级卡片堆叠(周卡、热门列表卡片等) +@section-gap-xs: 24rpx; // 更小块间(搜索区顶、Tab 区等) + + +// ========== 圆角 ========== @radius-sm: 16rpx; @radius: 24rpx; @radius-lg: 32rpx; +@radius-xl: 48rpx; // 大卡片(如个人中心主卡,对齐 Stitch 1.5rem) -// 阴影 +// ========== 阴影 ========== @shadow: 0 4rpx 16rpx rgba(50, 46, 37, 0.06); @shadow-lg: 0 8rpx 32rpx rgba(50, 46, 37, 0.1); -// icon -@font-size-icon: 40rpx; \ No newline at end of file +// ========== 字号 ========== +@font-size-icon: 40rpx; +@fs-section-head-title: 36rpx; // 板块主标题(如首页区块头、分龄路径头等) + +// ========== 顶栏(Tab 页固定导航标题)========== +@nav-inner-height: 44px; +@fs-nav-title: 34rpx; +@fw-nav-title: 700; +@color-nav-title: @text-title; +@ls-nav-title: -0.02em; \ No newline at end of file diff --git a/miniprogram/utils/downloadPrint.ts b/miniprogram/utils/downloadPrint.ts index 08f652f..2193ea2 100644 --- a/miniprogram/utils/downloadPrint.ts +++ b/miniprogram/utils/downloadPrint.ts @@ -29,9 +29,9 @@ function getTodayDateString(): string { } /** - * 获取今日下载次数 + * 获取今日下载次数(供个人中心等展示;与内部计数逻辑一致) */ -function getTodayDownloadCount(): number { +export function getTodayDownloadCount(): number { const today = getTodayDateString(); const downloadDate = wx.getStorageSync(STORAGE_KEY_DOWNLOAD_DATE); diff --git a/miniprogram/utils/navMetrics.ts b/miniprogram/utils/navMetrics.ts new file mode 100644 index 0000000..630085c --- /dev/null +++ b/miniprogram/utils/navMetrics.ts @@ -0,0 +1,2 @@ +/** 与 style/theme3.less 中 @nav-inner-height 对齐(px) */ +export const NAV_INNER_PX = 44; diff --git a/project.private.config.json b/project.private.config.json index 65b9ab5..fe951a0 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -2,7 +2,7 @@ "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", "projectname": "doodle-mini", "setting": { - "compileHotReLoad": false, + "compileHotReLoad": true, "urlCheck": true, "coverView": false, "lazyloadPlaceholderEnable": false, @@ -24,12 +24,19 @@ "miniprogram": { "list": [ { - "name": "pages/copyBook/copyBook", - "pathName": "pages/copyBook/copyBook", + "name": "pages/age/age", + "pathName": "pages/age/age", "query": "", "scene": null, "launchMode": "default" }, + { + "name": "pages/copyBook/copyBook", + "pathName": "pages/copyBook/copyBook", + "query": "", + "launchMode": "default", + "scene": null + }, { "name": "mathPages/multiplicationTable/multiplicationTable", "pathName": "mathPages/multiplicationTable/multiplicationTable", diff --git a/stitch-ui/age-recommend/code.html b/stitch-ui/age-recommend/code.html new file mode 100644 index 0000000..abcf77d --- /dev/null +++ b/stitch-ui/age-recommend/code.html @@ -0,0 +1,371 @@ + + + + + + + + + + + + + +
+
+
+arrow_back +
+Duck icon +
+
+

按年龄学

+account_circle +
+
+
+ +
+
+
+
3-4 岁
+
启蒙认知
+
+ +
+
4-5 岁
+
基础练习
+
+ +
+
5-6 岁
+
能力提升
+
当前
+
+ +
+
6-7 岁
+
幼小衔接
+
+ +
+
7-8 岁
+
知识拓展
+
+
+ +
+

+stars + 5-6 岁能力目标 +

+
+
+
+🔢 +数感 +
+

10以内加减法、凑十法

+
+
+
+✏️ +书写 +
+

练字帖、拼音入门

+
+
+
+🧩 +思维 +
+

方格推理、格子仿画 5x5

+
+
+
+ +
+
+

+ + 4 周推荐学习路线 +

+
+4 周 • 每周 3 练 +
+
+
+ +
+
+第 1 周 +数感启蒙 +
+
+
+找数字涂一涂 + +
+
+5 以内加法 + +
+
+数数连一连 + +
+
+
+ +
+
+第 2 周 +形状与连线 +
+
+
+识别形状 + +
+
+线条识别 + +
+
+数字点连线 + +
+
+
+ +
+
+第 3 周 +趣味专注 +
+
+
+颜色找规律 + +
+
+方格推理 + +
+
+格子仿画 + +
+
+
+ +
+
+第 4 周 +综合练习 +
+
+
+10以内加减法 + +
+
+识字卡 + +
+
+连连看 + +
+
+
+
+
+ +
+
+

[为你推荐]

+更多 +
+
+ +
+
+ +
+
+

10以内加减法

+
+
+ +
+
+ +
+
+

拼音描红

+
+
+ +
+
+ +
+
+

趣味找不同

+
+
+ +
+
+ +
+
+

走迷宫大挑战

+
+
+ +
+
+ +
+
+

数字点连线

+
+
+ +
+
+ +
+
+

方格推理高手

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/stitch-ui/age-recommend/screen.png b/stitch-ui/age-recommend/screen.png new file mode 100644 index 0000000..46d7593 Binary files /dev/null and b/stitch-ui/age-recommend/screen.png differ diff --git a/stitch-ui/age/DESIGN.md b/stitch-ui/age/DESIGN.md new file mode 100644 index 0000000..9e50d18 --- /dev/null +++ b/stitch-ui/age/DESIGN.md @@ -0,0 +1,54 @@ +# Design System Strategy: Doodle Mini + +## 1. Overview & Creative North Star: "The Sun-Drenched Studio" +The creative direction for this design system is **"The Sun-Drenched Studio."** We are moving away from the cold, clinical "educational app" look and toward a space that feels like a physical, tactile craft room bathed in morning light. + +Instead of a rigid, boxed-in grid, we embrace **Organic Asymmetry**. Elements should feel like they were placed on a table by hand, not snapped to a pixel-perfect wireframe. We break the "template" look by using exaggerated corner radii, overlapping "paper" layers, and a hierarchy that prioritizes warmth and play over traditional data density. + +## 2. Colors: Tonal Radiance +The palette is anchored by `primary` (#6c5a00) and `primary_container` (#ffd709), creating a "Yaya Yellow" experience that is vibrant yet sophisticated. + +* **The "No-Line" Rule:** Under no circumstances should 1px solid borders be used to separate content. We define boundaries exclusively through background shifts. Use `surface_container_low` (#f8f0e0) for main content areas and `surface` (#fef6e7) for the global backdrop. +* **Surface Hierarchy & Nesting:** Treat the UI as stacked sheets of heavy-weight construction paper. + * **Level 0:** `background` (#fef6e7) – The desk surface. + * **Level 1:** `surface_container` (#f0e7d6) – The worksheet base. + * **Level 2:** `surface_container_lowest` (#ffffff) – Active drawing cards or "Paper" modals. +* **The Glass & Gradient Rule:** For navigation bars or floating tool palettes, use Glassmorphism. Apply `surface_container_low` at 80% opacity with a `20px` backdrop blur. This ensures the "sunny" background bleeds through, maintaining the "Sun-Drenched" atmosphere. +* **Signature Textures:** Main CTAs should not be flat. Apply a subtle linear gradient from `primary_fixed` (#ffd709) to `primary_fixed_dim` (#efc900) at a 145-degree angle to give buttons a "pillowy" 3D volume. + +## 3. Typography: The Friendly Guide +We use **Plus Jakarta Sans** exclusively. Its geometric yet soft terminals provide a modern, legible, and approachable voice. + +* **Display & Headlines:** Use `display-lg` (3.5rem) for "Doodle Mini" brand moments. Headlines (`headline-md`) should use a tighter letter-spacing (-0.02em) to feel "hugged" and friendly. +* **Body & Labels:** Use `body-lg` for all instructional text. In a children's context, readability is paramount; ensure a line height of at least 1.6x the font size. +* **Hierarchy as Personality:** Use `on_secondary_container` (#6d3b00) for sub-headers to provide a warm, "chocolatey" contrast against the yellow, moving away from harsh blacks. + +## 4. Elevation & Depth: Tonal Layering +Depth in this system is a result of light and shadow, not lines and strokes. + +* **The Layering Principle:** To lift a component, place a `surface_container_highest` (#e5dcc9) element inside a `surface_container_low` (#f8f0e0) environment. This creates a "recessed" or "elevated" feel through color value alone. +* **Ambient Shadows:** Use shadows sparingly. When an object "floats" (like a floating action button), use a shadow color derived from `on_surface` (#322e25) at 6% opacity, with a blur of `32px` and a Y-offset of `8px`. It should look like a soft glow, not a drop shadow. +* **The "Ghost Border" Fallback:** If a separator is required for accessibility in input fields, use `outline_variant` (#b3ac9f) at **15% opacity**. It should be felt, not seen. + +## 5. Components: Tactile & Safe +Every component must feel "squishy" and safe to touch. + +* **Buttons:** + * **Primary:** Uses `primary_container` (#ffd709) with a `full` (9999px) radius. + * **States:** On press, scale the button down to 96% size to simulate a physical "click" into the screen. +* **Cards & Lists:** Forbid divider lines. Separate list items using a `spacing-3` (1rem) gap and alternating background colors between `surface_container_low` and `surface_container`. +* **Input Fields:** Use `surface_container_highest` for the input track. Corners must be `xl` (3rem). Labels sit *above* the field in `title-sm` to ensure children can always see the context. +* **Selection Chips:** Use `secondary_container` (#ffc697) for selected states. The roundedness must be `full` to mimic smooth river stones. +* **The "Joy" Progress Bar:** A custom component for Doodle Mini. Use a thick track (`1.5rem` height) in `surface_container_highest` with the progress indicator using a gradient of `tertiary` (#006b1b) to `tertiary_fixed` (#91f78e). + +## 6. Do’s and Don’ts + +### Do: +* **Do** use `xl` and `full` corner radii for almost everything. Sharp corners are "scary." +* **Do** lean into white space. Use `spacing-12` (4rem) between major sections to let the design breathe. +* **Do** use `on_tertiary_container` (dark green) for success states; it feels organic and "earthy" against the yellow. + +### Don’t: +* **Don’t** use pure black (#000000). Use `on_surface` (#322e25) for all dark text to maintain the warm, sunny tone. +* **Don’t** use 1px dividers. If you feel you need one, use a `surface_variant` (#e5dcc9) color block that is `4px` thick with `full` rounded caps. +* **Don’t** crowd the interface. If a screen feels busy, increase the background "paper" size and use a scrollable layout instead of shrinking elements. \ No newline at end of file diff --git a/stitch-ui/age/code.html b/stitch-ui/age/code.html new file mode 100644 index 0000000..abcf77d --- /dev/null +++ b/stitch-ui/age/code.html @@ -0,0 +1,371 @@ + + + + + + + + + + + + + +
+
+
+arrow_back +
+Duck icon +
+
+

按年龄学

+account_circle +
+
+
+ +
+
+
+
3-4 岁
+
启蒙认知
+
+ +
+
4-5 岁
+
基础练习
+
+ +
+
5-6 岁
+
能力提升
+
当前
+
+ +
+
6-7 岁
+
幼小衔接
+
+ +
+
7-8 岁
+
知识拓展
+
+
+ +
+

+stars + 5-6 岁能力目标 +

+
+
+
+🔢 +数感 +
+

10以内加减法、凑十法

+
+
+
+✏️ +书写 +
+

练字帖、拼音入门

+
+
+
+🧩 +思维 +
+

方格推理、格子仿画 5x5

+
+
+
+ +
+
+

+ + 4 周推荐学习路线 +

+
+4 周 • 每周 3 练 +
+
+
+ +
+
+第 1 周 +数感启蒙 +
+
+
+找数字涂一涂 + +
+
+5 以内加法 + +
+
+数数连一连 + +
+
+
+ +
+
+第 2 周 +形状与连线 +
+
+
+识别形状 + +
+
+线条识别 + +
+
+数字点连线 + +
+
+
+ +
+
+第 3 周 +趣味专注 +
+
+
+颜色找规律 + +
+
+方格推理 + +
+
+格子仿画 + +
+
+
+ +
+
+第 4 周 +综合练习 +
+
+
+10以内加减法 + +
+
+识字卡 + +
+
+连连看 + +
+
+
+
+
+ +
+
+

[为你推荐]

+更多 +
+
+ +
+
+ +
+
+

10以内加减法

+
+
+ +
+
+ +
+
+

拼音描红

+
+
+ +
+
+ +
+
+

趣味找不同

+
+
+ +
+
+ +
+
+

走迷宫大挑战

+
+
+ +
+
+ +
+
+

数字点连线

+
+
+ +
+
+ +
+
+

方格推理高手

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/stitch-ui/age/screen.png b/stitch-ui/age/screen.png new file mode 100644 index 0000000..58e1e35 Binary files /dev/null and b/stitch-ui/age/screen.png differ diff --git a/stitch-ui/favorites/code-download-history.html b/stitch-ui/favorites/code-download-history.html new file mode 100644 index 0000000..628dc69 --- /dev/null +++ b/stitch-ui/favorites/code-download-history.html @@ -0,0 +1,209 @@ + + + + + +我的收藏 - 下载历史 + + + + + + + + + + +
+
+arrow_back +

我的收藏

+
+
+ + +
+
+
+ +
+ + +
+ +
+ +
+

今天

+
+
+
+ +
+
+

拼音描红 · 声母表

+
+schedule +14:32 + +已保存相册 +
+
+ +
+ +
+
+ +
+
+

趣味口算题卡

+
+schedule +09:15 + +已下载 PDF +
+
+ +
+
+ +
+

昨天

+
+
+
+
+draw +
+
+
+

汉字笔画基础训练

+
+schedule +16:40 + +已发送邮件 +
+
+ +
+
+
+ + +
+ + + \ No newline at end of file diff --git a/stitch-ui/favorites/code-favorites-list.html b/stitch-ui/favorites/code-favorites-list.html new file mode 100644 index 0000000..fb8b295 --- /dev/null +++ b/stitch-ui/favorites/code-favorites-list.html @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + +
+
+ +

我的收藏

+
+ +
+
+ +
+ + +
+ +
+ +
+
+Worksheet Thumbnail +
+
+

10以内加法

+

+ 数学启蒙 · 4-6岁 · + +star +star + +

+收藏于 3 天前 +
+ +
+ +
+
+Worksheet Thumbnail +
+
+

图形连连看

+

+ 逻辑思维 · 3-5岁 · + +star + +

+收藏于 5 天前 +
+ +
+ +
+
+Worksheet Thumbnail +
+
+

拼音字母认读

+

+ 语文基础 · 5-7岁 · + +star +star +star + +

+收藏于 1 周前 +
+ +
+ +
+
+

趣味英语单词卡

+

语言学习 · 4-8岁 · ⭐⭐⭐

+
+# 畅销经典 +# 全彩打印 +
+
+
+abc +
+ +
+
+ +
+

发现更多有趣的内容?

+ +
+
+ + + \ No newline at end of file diff --git a/stitch-ui/favorites/screen-download-history.png b/stitch-ui/favorites/screen-download-history.png new file mode 100644 index 0000000..ca1708d Binary files /dev/null and b/stitch-ui/favorites/screen-download-history.png differ diff --git a/stitch-ui/favorites/screen-favorites-list.png b/stitch-ui/favorites/screen-favorites-list.png new file mode 100644 index 0000000..b043027 Binary files /dev/null and b/stitch-ui/favorites/screen-favorites-list.png differ diff --git a/stitch-ui/profile/code.html b/stitch-ui/profile/code.html new file mode 100644 index 0000000..e652a8b --- /dev/null +++ b/stitch-ui/profile/code.html @@ -0,0 +1,176 @@ + + + + + + + + + + + + + +
+
+ +

个人中心

+
+
涂鸦丫
+
+
+ +
+
+
+
+ +
+
+edit +
+
+
+

Alex Rivera

+
+
+
+24 +打印 +
+
+12 +收藏 +
+
+
+ +
+
+ + + + +
+ +
+ + +
+
+ +
+
+auto_awesome +
+
+

升级专业版

+

解锁 500+ 精选教育模板并获得优先体验。

+ +
+
+ +
+ +
+

Doodle Mini v2.4.0

+
+ \ No newline at end of file diff --git a/stitch-ui/profile/profile-screen-stitch.png b/stitch-ui/profile/profile-screen-stitch.png new file mode 100644 index 0000000..8b9e19c Binary files /dev/null and b/stitch-ui/profile/profile-screen-stitch.png differ