2026-09-21-homepage-shortcut-application-library-design.md 13 KB

首页快捷方式自定义 + 应用库 + 频度追踪 设计规格

  • 日期:2026-09-21
  • 状态:待用户审查
  • 涉及模块:cfc-backend(后端)+ cfc-frontend(微信小程序)

1. 背景与目标

首页(pages/index-home/index.vue)在"开通会员"卡片下方有一排 6 个硬编码快捷入口:任务、互动、天盘、成员、圈子、商城。

当前问题:

  • 6 个入口写死在模板里,用户无法自定义。
  • 平台有大量"可直接进入"的功能页面(健康报告、饮食推荐、AI 管家、成长记录、心愿、测评、小游戏、活动、会员中心、财富等),散落在各分包,用户找不到。
  • 没有记录用户对每个功能的使用频度,无法做个性化推荐。

目标:

  1. 把这 6 个快捷入口改造成用户可自定义(从应用库选择放哪些)。
  2. 为每个可直接进入的功能设计图标,形成应用库。
  3. 记录用户进入每个应用的次数。
  4. 根据使用频度自动调整首页快捷方式(锁定应用优先 + 频度填充)。
  5. 已在首页有显著入口的功能,不进快捷配置列表(避免重复)。

2. 需求澄清结论(已与用户确认)

问题 结论
频度调整机制 自动排序但可手动覆盖:系统按频度排序,但用户手动锁定后以锁定为准
配置与频度存储 服务端存储(换设备同步、频度累计准确)
适用人群 所有登录用户(非会员与会员一致)
首页重复功能 已在首页显著位置的功能,从配置列表中排除
应用库范围 全部候选功能都入库
锁定的语义 锁定 = 置顶,不会被点击次数挤掉;但用户仍可手动移除
锁定操作入口 只在应用库配置页管理,首页不显示锁图标
自动填充逻辑 锁定占位 + 频度填满剩余
锁定应用内部排序 支持手动调整锁定应用之间的顺序
频度计数触发 任意入口进入都计数(含 TabBar 页)

3. 架构方案

采用 混合方案:

  • 应用库 = 前端常量文件 utils/app-library.js,图标用 emoji(与现有 6 个快捷入口一致,零静态资源成本)。
  • 后端建 2 张表:user_shortcut_config(用户快捷配置)+ user_app_usage(使用频度)。

理由:

  • 应用库本质是静态配置(key/名称/图标/路由/是否可快捷),前端维护最灵活,迭代快。
  • 新增应用需发版(可接受,功能迭代本就发版)。
  • 符合 YAGNI:应用库无"运营后台动态增删"需求,不做成后端表。

4. 数据模型

4.1 user_shortcut_config(用户快捷配置)

记录"用户把某个应用放到快捷区"的配置,每应用一条记录,locked 决定是否锁定。

字段 类型 说明
id BIGINT PK AUTO 主键
user_id BIGINT NOT NULL 用户 ID
app_key VARCHAR(64) NOT NULL 应用 key(对应 app-library.js)
locked TINYINT DEFAULT 0 是否锁定(1=锁定/置顶,0=普通)
sort_order INT DEFAULT 0 手动排序序号(锁定的应用之间排序用)
created_at DATETIME 创建时间
updated_at DATETIME 更新时间
  • 唯一索引:(user_id, app_key)

语义:locked=1 的应用在首页快捷区始终保留(不被频度排序挤掉),按 sort_order 升序排在最前;locked=0 的应用参与频度填充。

4.2 user_app_usage(应用使用频度)

字段 类型 说明
id BIGINT PK AUTO 主键
user_id BIGINT NOT NULL 用户 ID
app_key VARCHAR(64) NOT NULL 应用 key
enter_count INT DEFAULT 0 累计进入次数
last_enter_at DATETIME 最近进入时间
  • 唯一索引:(user_id, app_key)

5. 应用库清单(前端常量 utils/app-library.js)

每个应用字段:key、name、icon(emoji)、route、canShortcut(是否可放首页快捷区)。

key 名称 图标 路由 canShortcut
task 任务 📋 /pages/tasks/tasks ✅
interaction 互动 💬 /pages/action-detail/interaction-log ✅
tiandi 天盘 🏠 /pages/mind-detail/family-dashboard ✅
members 成员 👨‍👩‍👧‍👦 /pages/profile-extra/family-members ✅
circle 圈子 🤝 /pages/discover/circles ✅
shop 商城 🛒 /pages/shop/index/index ✅
diet 饮食推荐 🥗 /pages/diet/index ✅
ai AI管家 🤖 /pages/ai/chat ✅
growth 成长记录 📈 /pages/growth-main/index ✅
wish 心愿 ⭐ /pages/wishes/index ✅
medal 勋章 🏅 /pages/rewards/rewards ✅
game 小游戏 🎮 /pages/games/list ✅
assess 测评 🧪 /pages/assessment/apply ✅
activity 活动 🎪 /pages/activity/index ✅
membership 会员中心 👑 /pages/membership/index ✅
wealth 财富 💰 /pages/wealth/index ✅
report 健康报告 🩺 /pages/health/report-upload ❌ 已在首页
article 文章中心 📖 /pages/article-center/index ❌ 已在首页(推荐阅读)

canShortcut=false 的应用在应用库页面可见但置灰 + 标注"已在首页",用户无法选入 6 格。

默认 6 格(新用户 / 无配置时)= 当前硬编码的 6 个:task、interaction、tiandi、members、circle、shop。


6. API 设计(统一 @PostMapping + Result<T>,JWT 认证)

6.1 POST /api/home/app-library

返回应用库 + 我的快捷配置 + 频度,合并为一次请求(避免 N+1)。

  • 请求:无 body(从 JWT 取 user_id)
  • 响应 data:

    {
    "apps": [
      { "key": "task", "name": "任务", "icon": "📋", "route": "/pages/tasks/tasks", "canShortcut": true }
    ],
    "myShortcuts": [
      { "appKey": "task", "locked": true, "sortOrder": 0 }
    ],
    "usage": { "task": 12, "shop": 8 }
    }
    

6.2 POST /api/home/shortcut/save

保存用户配置(全量覆盖用户对该用户的快捷配置)。

  • 请求 body:{ "items": [ { "appKey": "task", "locked": true, "sortOrder": 0 } ] }
  • 语义:删除该用户旧的 user_shortcut_config 记录,批量插入新的 items(每个 item 一条记录)。
  • 响应:Result<Void>

6.3 POST /api/home/app/enter

记录进入次数。

  • 请求 body:{ "appKey": "task" }
  • 语义:user_app_usage 的 enter_count +1,更新 last_enter_at(不存在则插入)。
  • 响应:Result<Void>

6.4 POST /api/home/shortcut/reset

重置为默认(清除锁定,回到系统按频度排序)。

  • 请求:无 body
  • 语义:删除该用户的 user_shortcut_config 记录。
  • 响应:Result<Void>

7. 前端改造

7.1 首页快捷区(pages/index-home/index.vue)

  • 将 6 个硬编码 <view class="quick-item"> 改为 v-for 动态渲染,数据来自 POST /api/home/app-library。
  • 快捷区右上角加"编辑"小图标 → 跳转应用库配置页 /pages/home/app-library。
  • 快捷区点击某个应用 → 先 navTo(route),再调用 POST /api/home/app/enter 记录频度(注意:频度计数是"任意入口",故记录逻辑放在各应用入口页 onLoad,而非仅首页点击处,见 7.4)。

7.2 应用库配置页(新增 pages/home/app-library)

  • 在 pages.json 注册(复用或新建分包 pages/home)。
  • 页面结构:
    1. 顶部:当前首页快捷区(6 格预览),支持:
      • 添加/移除应用(点击应用库里的应用切换选中态)
      • 拖动/上移下移调整锁定应用顺序(小程序用「长按排序」或「上移/下移」按钮,避免复杂拖拽)
      • 每个锁定应用显示"🔒 已锁定"标识
    2. 中部:应用库网格,展示所有应用(图标 + 名称 + 进入次数),canShortcut=false 的置灰标注"已在首页"。
    3. 底部按钮:
      • 保存 → POST /api/home/shortcut/save
      • 一键按频度排序 → 清除锁定,回到系统自动排序(调 POST /api/home/shortcut/reset)

7.3 前端 API 封装(utils/api.js)

按 AGENTS.md 规则 2(同 URL 唯一入口)新增:

export const getAppLibrary = () => request('/api/home/app-library', 'POST', {})
export const saveShortcutConfig = (items) => request('/api/home/shortcut/save', 'POST', { items })
export const reportAppEnter = (appKey) => request('/api/home/app/enter', 'POST', { appKey })
export const resetShortcutConfig = () => request('/api/home/shortcut/reset', 'POST', {})

7.4 频度计数埋点("任意入口进入都计数")

在每个应用入口页的 onLoad(非 onShow,避免 Tab 切换重复计数)中调用 reportAppEnter(appKey)。

  • 复用 _onLoadFired 模式:onLoad 只触发一次,天然适合计数。
  • 埋点页面 = 应用库清单中所有 canShortcut=true 应用的入口页(约 16 个页面),在各自 onLoad 里加一行 reportAppEnter('xxx')(静默失败,不阻塞页面)。

8. 首页快捷区排序算法(核心逻辑)

输入:myShortcuts(含 locked 标记)+ usage(频度)。

输出:6 个应用 key(有序)。

1. lockedApps = myShortcuts 中 locked=1 的应用,按 sort_order 升序
2. if lockedApps.length >= 6:
       取前 6 个
   else:
       remaining = 6 - lockedApps.length
       candidates = app-library 中 canShortcut=true 且 不在 lockedApps 里的应用
       fill = candidates 按 usage[appKey] 降序排序,取前 remaining 个
       若候选不足 remaining,用默认顺序补齐(不足 6 格就少显示)
   结果 = lockedApps + fill

说明:myShortcuts 里 locked=0 的应用本质上是"用户主动加入但未锁定"的,在填充阶段它们会被纳入 candidates 一起按频度排序,因此仍有机会显示(频度高时),也可能被更高频应用挤掉。


9. 后端实现要点

9.1 文件

文件 说明
entity/UserShortcutConfig.java 实体,@TableName("user_shortcut_config")
entity/UserAppUsage.java 实体,@TableName("user_app_usage")
mapper/UserShortcutConfigMapper.java MyBatis-Plus Mapper
mapper/UserAppUsageMapper.java MyBatis-Plus Mapper
controller/HomeController.java 4 个接口
service/HomeShortcutService.java(或并入 controller) 业务逻辑

9.2 迁移(DatabaseInitializer.runMigrations())

新增迁移编号 327,建两张表(幂等,CREATE TABLE IF NOT EXISTS),并同步 schema.sql:

CREATE TABLE IF NOT EXISTS user_shortcut_config (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  app_key VARCHAR(64) NOT NULL,
  locked TINYINT DEFAULT 0,
  sort_order INT DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_user_app (user_id, app_key)
);

CREATE TABLE IF NOT EXISTS user_app_usage (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  app_key VARCHAR(64) NOT NULL,
  enter_count INT DEFAULT 0,
  last_enter_at DATETIME,
  UNIQUE KEY uk_user_app (user_id, app_key)
);

9.3 规范遵循

  • @PostMapping、Result<T>、@Resource DI、JWT 认证(拦截 /api/**)。
  • Controller 内手动校验 @RequestAttribute("role")(所有登录角色可用,无需角色限制)。
  • Bean 命名无冲突(HomeController 需确认不与现有类重名)。

10. 边界情况

  1. 锁定数 > 6:取 sort_order 前 6 个锁定应用。
  2. 候选应用不足 6 个:少显示即可(不报错)。
  3. 频度全为 0:用默认 6 个顺序。
  4. 并发计数:app/enter 用 UPDATE ... SET enter_count = enter_count + 1 原子更新,避免覆盖。
  5. 重复上报:onLoad 只触发一次,天然去重;后端对同 key 同用户做 upsert。
  6. 换设备:配置与频度都在服务端,登录后同步。

11. 测试

  • 后端单测:HomeShortcutService 排序算法(锁定占位 + 频度填充、锁定>6、候选不足)。
  • 前端:utils/app-library.js 导出结构校验;首页快捷区 v-for 渲染(含空态)。
  • CI 门禁:node scripts/audit-duplicate-api-calls.js 通过(新页面 onLoad 埋点不重复打同一 URL)。

12. 交付范围

  • 后端:2 张表迁移 + 2 实体 + 2 Mapper + 1 Controller + 排序逻辑。
  • 前端:utils/app-library.js + utils/api.js 4 个封装 + 首页改造 + 应用库配置页 + 16 个入口页埋点。
  • 不涉及:运营后台动态维护应用库(YAGNI,后续有需求再扩展)。