7.3 KiB
7.3 KiB
author, created_at, purpose
| author | created_at | purpose |
|---|---|---|
| AI Assistant | 2026-07-19 | 小程序收藏功能从 localStorage 走真实 API,在现有剧本/事件表增加 is_favorite 字段 |
小程序收藏功能真实 API 化设计
概述
当前小程序的「收藏」功能(剧本收藏、人生事件收藏)完全基于前端 localStorage:
- 剧本:
uni.getStorageSync('script_favorites')→ 对象字典{ id: true/false } - 人生事件:
uni.getStorageSync('event_favorite_${id}')→ 单条布尔
后端没有真实的剧本收藏 API,人生事件收藏接口 /lifeEvent/favorite-placeholder 是假的(echo 请求参数 + placeholder: true)。
本次修复:在现有 e_epic_script 表和 t_life_event 表上新增 is_favorite 字段,新增真实 API,前端从 localStorage 切换到 API。
现状问题
前端
| 文件 | 问题 |
|---|---|
ScriptLibraryView.vue:159 |
localFavorites = ref(uni.getStorageSync('script_favorites') || {}) |
ScriptLibraryView.vue:319-369 |
toggleFavorite / deleteScriptFromFavorites 只写 localStorage |
ScriptLibraryView.vue:244 |
isFavorite 从 script.isFavorite || localFavorites.value[id] 读取 |
life-event/detail.vue:142 |
从 current_life_event localStorage 读整条事件数据 |
life-event/detail.vue:144 |
isFavorite 从 event_favorite_${id} localStorage 读 |
life-event/detail.vue:315-316 |
toggle 写/删 event_favorite_${id} localStorage |
后端
| 文件 | 问题 |
|---|---|
LifeEventController.java:153-163 |
/lifeEvent/favorite-placeholder 是空壳,返回 echo + placeholder: true |
| 无剧本收藏 Controller | /epicScript 下没有任何收藏端点 |
设计方案
存储方案(已确认)
在现有表新增 is_favorite 字段:
ALTER TABLE e_epic_script ADD COLUMN is_favorite TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否收藏:0-否 1-是';
ALTER TABLE t_life_event ADD COLUMN is_favorite TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否收藏:0-否 1-是';
选择这个方案的理由:
- 不新增表,改动最小
is_favorite是当前用户维度的标记;用户只操作自己的剧本/事件,不需要 user_id 维度- 现有
listAll/page接口返回全量字段,新增isFavorite字段后前端可直接读取,不需要额外的"收藏夹"接口 - 后续如需"按用户维度"收藏(如管理员视角),再迁移到独立表
后端改动
1. 实体类
EpicScript.java新增:private Boolean isFavorite;LifeEvent.java新增:private Boolean isFavorite;
MyBatis-Plus 自动映射下划线 is_favorite 到驼峰 isFavorite。
2. Controller API
剧本(EpicScriptController):
POST /epicScript/favorite
入参:FavoriteRequest { Long id; Boolean favorite; }
出参:Result<Void>
逻辑:
1. 通过 id 查询剧本
2. 若不存在 → 抛业务异常("剧本不存在")
3. update is_favorite = favorite
4. return Result.ok()
人生事件(LifeEventController):
POST /lifeEvent/favorite
入参:FavoriteRequest { Long id; Boolean favorite; }
出参:Result<Void>
逻辑:
1. 通过 id 查询事件
2. 若不存在 → 抛业务异常
3. update is_favorite = favorite
4. return Result.ok()
删除现有 /lifeEvent/favorite-placeholder 接口。
3. Service 层
// EpicScriptService
void setFavorite(Long id, Boolean favorite);
// LifeEventService
void setFavorite(Long id, Boolean favorite);
实现:lambdaUpdate().set(EpicScript::getIsFavorite, favorite).eq(EpicScript::getId, id).update()
4. DTO
新增公共请求对象 FavoriteRequest:
public class FavoriteRequest {
@NotNull
private Long id;
@NotNull
private Boolean favorite;
}
放在 common 模块,两个 Controller 都能引用。
前端改动
1. stores/app.js
新增两个 action:
async toggleScriptFavorite(id, favorite) {
await epicScriptService.setFavorite(id, favorite);
const script = this.scripts.find(s => String(s.id) === String(id));
if (script) script.isFavorite = favorite;
},
async toggleEventFavorite(id, favorite) {
await lifeEventService.setFavorite(id, favorite);
// 事件列表如果是 store 内状态,同步更新
}
2. services/epicScript.js
新增:
setFavorite: (id, favorite) => post('/epicScript/favorite', { id: String(id), favorite })
3. services/lifeEvent.js
修改现有 favoriteEvent 实现:
// 之前:post('/lifeEvent/favorite-placeholder', ...)
// 之后:post('/lifeEvent/favorite', { id, favorite })
4. ScriptLibraryView.vue
- 删除
const localFavorites = ref(uni.getStorageSync('script_favorites') || {}) - 删除
toggleFavorite/deleteScriptFromFavorites的 localStorage 写/删 isFavorite计算改为:Boolean(script.isFavorite)toggleFavorite改为调store.toggleScriptFavorite(id, !script.isFavorite)
5. life-event/detail.vue
- 删除
uni.getStorageSync('current_life_event')的回退读取(这是"把数据存到用户端"的另一处违规,在 sub-project B 一并清理) - 删除
uni.getStorageSync('event_favorite_${id}')读取 isFavorite改为computed(() => Boolean(displayEvent.value?.isFavorite))- toggle 改为调
store.toggleEventFavorite(id, !isFavorite.value)或lifeEventService.favoriteEvent
数据流
列表页 ScriptLibraryView
└─ fetchScripts() (GET /epicScript/listAll)
└─ 后端查 e_epic_script 含 is_favorite 字段
└─ 前端渲染 isFavorite
用户点收藏
└─ store.toggleScriptFavorite(id, true/false)
└─ POST /epicScript/favorite { id, favorite }
└─ 后端 update is_favorite
└─ 本地 state 同步,UI 响应式更新
详情页 life-event/detail
└─ fetchLifeEventDetail(id) (GET /lifeEvent/detail)
└─ 后端查 t_life_event 含 is_favorite
└─ 前端渲染 isFavorite
不影响范围
- 列表渲染、筛选、排序逻辑不变,只换数据源(localStorage → API 字段)
- 详情页展示逻辑不变(数据源变化在 sub-project B 清理)
- 其他页面的剧本/事件展示(如首页推荐)自动受益于新的
isFavorite字段
数据库迁移
新增迁移 SQL 文件 server/src/main/resources/db/migration/V{timestamp}__add_is_favorite.sql:
ALTER TABLE e_epic_script ADD COLUMN is_favorite TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否收藏:0-否 1-是';
ALTER TABLE t_life_event ADD COLUMN is_favorite TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否收藏:0-否 1-是';
如果项目不用 Flyway,则手动执行或写入项目约定的 SQL 目录。
验收标准
- 剧本收藏:点击心形图标 → 调用
POST /epicScript/favorite→ 后端字段更新 → 刷新页面后仍保持状态 - 人生事件收藏:点击收藏 → 调用
POST /lifeEvent/favorite→ 同上 - 取消收藏:再次点击 → 同上
- 列表页
isFavorite来自 API,不再依赖 localStorage - 详情页
isFavorite来自 API,不再依赖 localStorage - 浏览器 Console 无报错
- 后端
mvn clean install编译通过 - 通过
deploy.py backend部署到服务器后,在服务器环境验证