27 KiB
author, created_at, purpose
| author | created_at | purpose |
|---|---|---|
| AI Assistant | 2026-07-19 | 小程序收藏功能真实 API 化实施计划 |
收藏功能真实 API 化实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 前端收藏功能从 localStorage 切换到真实 API。剧本收藏沿用现有后端 API,人生事件收藏新建独立表和 API。
Architecture: 剧本收藏后端已完整(t_script_favorite 表 + ScriptFavoriteService),前端 ScriptLibraryView.vue 未对接。人生事件收藏仿照剧本,新建 t_event_favorite 表 + EventFavoriteService + Controller,删除假接口 /lifeEvent/favorite-placeholder。前端所有 localStorage 收藏逻辑切换到 API。
Tech Stack: Spring Boot 2.7.18 + MyBatis-Plus 3.5.3.1 + UniApp Vue 3 + Pinia + Element Plus + rpx
Task 1: 后端人生事件收藏功能(Entity + Mapper + Service + Controller)
Files:
-
Create:
server/src/main/resources/db/migration/V20260719000002__create_event_favorite.sql -
Create:
server/src/main/java/com/emotion/entity/EventFavorite.java -
Create:
server/src/main/java/com/emotion/mapper/EventFavoriteMapper.java -
Create:
server/src/main/java/com/emotion/dto/request/EventFavoriteToggleRequest.java -
Create:
server/src/main/java/com/emotion/dto/response/EventFavoriteResponse.java -
Create:
server/src/main/java/com/emotion/service/EventFavoriteService.java -
Create:
server/src/main/java/com/emotion/service/impl/EventFavoriteServiceImpl.java -
Modify:
server/src/main/java/com/emotion/controller/LifeEventController.java -
Step 1: 创建数据库迁移 SQL
-- server/src/main/resources/db/migration/V20260719000002__create_event_favorite.sql
CREATE TABLE t_event_favorite (
id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL COMMENT '用户ID',
event_id VARCHAR(64) NOT NULL COMMENT '事件ID',
create_by VARCHAR(64) COMMENT '创建人',
create_time DATETIME COMMENT '创建时间',
update_by VARCHAR(64) COMMENT '更新人',
update_time DATETIME COMMENT '更新时间',
is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除:0-否 1-是',
remarks VARCHAR(500) COMMENT '备注',
INDEX idx_user_event (user_id, event_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='人生事件收藏关联表';
- Step 2: 创建 EventFavorite 实体类
// server/src/main/java/com/emotion/entity/EventFavorite.java
package com.emotion.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.emotion.common.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 人生事件收藏关联实体
*
* @author AI Assistant
* @date 2026-07-19
*/
@Data
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_event_favorite")
public class EventFavorite extends BaseEntity {
@TableField("user_id")
private String userId;
@TableField("event_id")
private String eventId;
}
- Step 3: 创建 EventFavoriteMapper
// server/src/main/java/com/emotion/mapper/EventFavoriteMapper.java
package com.emotion.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.emotion.entity.EventFavorite;
import org.apache.ibatis.annotations.Mapper;
/**
* 人生事件收藏 Mapper
*
* @author AI Assistant
* @date 2026-07-19
*/
@Mapper
public interface EventFavoriteMapper extends BaseMapper<EventFavorite> {
}
- Step 4: 创建 EventFavoriteToggleRequest DTO
// server/src/main/java/com/emotion/dto/request/EventFavoriteToggleRequest.java
package com.emotion.dto.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 人生事件收藏切换请求
*
* @author AI Assistant
* @date 2026-07-19
*/
@Data
public class EventFavoriteToggleRequest {
@NotBlank(message = "事件ID不能为空")
private String eventId;
}
- Step 5: 创建 EventFavoriteResponse DTO
// server/src/main/java/com/emotion/dto/response/EventFavoriteResponse.java
package com.emotion.dto.response;
import lombok.Data;
/**
* 人生事件收藏状态响应
*
* @author AI Assistant
* @date 2026-07-19
*/
@Data
public class EventFavoriteResponse {
private String eventId;
private Boolean isFavorited;
private String favoriteTime;
}
- Step 6: 创建 EventFavoriteService 接口
// server/src/main/java/com/emotion/service/EventFavoriteService.java
package com.emotion.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.emotion.common.PageResult;
import com.emotion.dto.response.EventFavoriteResponse;
import com.emotion.dto.response.LifeEventResponse;
import com.emotion.entity.EventFavorite;
import java.util.Set;
/**
* 人生事件收藏服务接口
*
* @author AI Assistant
* @date 2026-07-19
*/
public interface EventFavoriteService extends IService<EventFavorite> {
EventFavoriteResponse toggleFavorite(String eventId);
PageResult<LifeEventResponse> getFavoritePage(long current, long size);
Set<String> getFavoritedEventIds(Set<String> eventIds);
boolean isFavorited(String eventId);
long getFavoriteCount();
}
- Step 7: 创建 EventFavoriteServiceImpl 实现
// server/src/main/java/com/emotion/service/impl/EventFavoriteServiceImpl.java
package com.emotion.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.emotion.common.PageResult;
import com.emotion.dto.response.EventFavoriteResponse;
import com.emotion.dto.response.LifeEventResponse;
import com.emotion.entity.EventFavorite;
import com.emotion.mapper.EventFavoriteMapper;
import com.emotion.service.EventFavoriteService;
import com.emotion.service.LifeEventService;
import com.emotion.util.UserContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* 人生事件收藏服务实现
*
* @author AI Assistant
* @date 2026-07-19
*/
@Slf4j
@Service
public class EventFavoriteServiceImpl extends ServiceImpl<EventFavoriteMapper, EventFavorite>
implements EventFavoriteService {
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Autowired
private LifeEventService lifeEventService;
@Override
@Transactional(rollbackFor = Exception.class)
public EventFavoriteResponse toggleFavorite(String eventId) {
String userId = UserContextHolder.getCurrentUserId();
EventFavoriteResponse r = new EventFavoriteResponse();
r.setEventId(eventId);
if (!StringUtils.hasText(userId)) {
r.setIsFavorited(false);
return r;
}
LambdaQueryWrapper<EventFavorite> w = new LambdaQueryWrapper<>();
w.eq(EventFavorite::getUserId, userId)
.eq(EventFavorite::getEventId, eventId)
.eq(EventFavorite::getIsDeleted, 0);
EventFavorite existing = this.getOne(w);
if (existing != null) {
this.removeById(existing.getId());
r.setIsFavorited(false);
} else {
EventFavorite fav = new EventFavorite();
fav.setUserId(userId);
fav.setEventId(eventId);
this.save(fav);
r.setIsFavorited(true);
if (fav.getCreateTime() != null) {
r.setFavoriteTime(fav.getCreateTime().format(FMT));
}
}
return r;
}
@Override
public PageResult<LifeEventResponse> getFavoritePage(long current, long size) {
String userId = UserContextHolder.getCurrentUserId();
if (!StringUtils.hasText(userId)) {
return new PageResult<>();
}
LambdaQueryWrapper<EventFavorite> w = new LambdaQueryWrapper<>();
w.eq(EventFavorite::getUserId, userId)
.eq(EventFavorite::getIsDeleted, 0)
.orderByDesc(EventFavorite::getCreateTime);
Page<EventFavorite> page = new Page<>(current, size);
Page<EventFavorite> result = this.page(page, w);
List<LifeEventResponse> records = result.getRecords().stream()
.map(EventFavorite::getEventId)
.map(lifeEventService::getEventById)
.filter(Objects::nonNull)
.collect(Collectors.toList());
PageResult<LifeEventResponse> pr = new PageResult<>();
pr.setCurrent(result.getCurrent());
pr.setSize(result.getSize());
pr.setTotal(result.getTotal());
pr.setPages(result.getPages());
pr.setRecords(records);
return pr;
}
@Override
public Set<String> getFavoritedEventIds(Set<String> eventIds) {
String userId = UserContextHolder.getCurrentUserId();
if (!StringUtils.hasText(userId) || eventIds == null || eventIds.isEmpty()) {
return Collections.emptySet();
}
LambdaQueryWrapper<EventFavorite> w = new LambdaQueryWrapper<>();
w.eq(EventFavorite::getUserId, userId)
.in(EventFavorite::getEventId, eventIds)
.eq(EventFavorite::getIsDeleted, 0);
return this.list(w).stream()
.map(EventFavorite::getEventId)
.collect(Collectors.toSet());
}
@Override
public boolean isFavorited(String eventId) {
String userId = UserContextHolder.getCurrentUserId();
if (!StringUtils.hasText(userId) || !StringUtils.hasText(eventId)) {
return false;
}
LambdaQueryWrapper<EventFavorite> w = new LambdaQueryWrapper<>();
w.eq(EventFavorite::getUserId, userId)
.eq(EventFavorite::getEventId, eventId)
.eq(EventFavorite::getIsDeleted, 0);
return this.count(w) > 0;
}
@Override
public long getFavoriteCount() {
String userId = UserContextHolder.getCurrentUserId();
if (!StringUtils.hasText(userId)) {
return 0L;
}
LambdaQueryWrapper<EventFavorite> w = new LambdaQueryWrapper<>();
w.eq(EventFavorite::getUserId, userId)
.eq(EventFavorite::getIsDeleted, 0);
return this.count(w);
}
}
- Step 8: 修改 LifeEventController 添加收藏 API
在 server/src/main/java/com/emotion/controller/LifeEventController.java 中添加:
- 在类顶部添加注入:
@Autowired
private EventFavoriteService eventFavoriteService;
- 添加 import:
import com.emotion.dto.request.EventFavoriteToggleRequest;
import com.emotion.dto.response.EventFavoriteResponse;
import com.emotion.service.EventFavoriteService;
- 在类末尾(
readTags方法之前)添加收藏 API:
// ==================== 人生事件收藏 ====================
@Operation(summary = "切换收藏", description = "收藏或取消收藏指定事件。")
@PostMapping(value = "/favorite/toggle")
public Result<EventFavoriteResponse> toggleFavorite(
@Valid @RequestBody EventFavoriteToggleRequest request) {
return Result.success(eventFavoriteService.toggleFavorite(request.getEventId()));
}
@Operation(summary = "分页查询收藏", description = "分页查询当前用户收藏的事件列表。")
@GetMapping(value = "/favorite/page")
public Result<PageResult<LifeEventResponse>> getFavoritePage(
@RequestParam(defaultValue = "1") long current,
@RequestParam(defaultValue = "10") long size) {
return Result.success(eventFavoriteService.getFavoritePage(current, size));
}
@Operation(summary = "检查收藏状态", description = "检查指定事件是否已被当前用户收藏。")
@GetMapping(value = "/favorite/check")
public Result<EventFavoriteResponse> checkFavorite(@RequestParam String eventId) {
boolean favorited = eventFavoriteService.isFavorited(eventId);
EventFavoriteResponse r = new EventFavoriteResponse();
r.setEventId(eventId);
r.setIsFavorited(favorited);
return Result.success(r);
}
- 删除
/lifeEvent/favorite-placeholder假接口(第 153-163 行):
// 删除以下方法
@Operation(summary = "收藏占位接口", description = "返回收藏功能的占位响应。")
@PostMapping(value = "/favorite-placeholder")
public Result<Map<String, Object>> favoritePlaceholder(@RequestBody Map<String, Object> request) {
// ... 整段删除
}
- Step 9: 提交
git add server/src/main/resources/db/migration/V20260719000002__create_event_favorite.sql
git add server/src/main/java/com/emotion/entity/EventFavorite.java
git add server/src/main/java/com/emotion/mapper/EventFavoriteMapper.java
git add server/src/main/java/com/emotion/dto/request/EventFavoriteToggleRequest.java
git add server/src/main/java/com/emotion/dto/response/EventFavoriteResponse.java
git add server/src/main/java/com/emotion/service/EventFavoriteService.java
git add server/src/main/java/com/emotion/service/impl/EventFavoriteServiceImpl.java
git add server/src/main/java/com/emotion/controller/LifeEventController.java
git commit -m "feat: 新增人生事件收藏功能(独立收藏表 + API)"
Task 2: 后端编译 + 部署到服务器
Files:
-
Build:
server/target/ -
Deploy: 服务器
-
Step 1: 本地编译验证
cd server
mvn clean install -DskipTests
Expected: BUILD SUCCESS,无编译错误。
- Step 2: 部署到服务器
cd ..
python deploy.py backend
Expected: 部署成功,服务器自动重启。
- Step 3: 验证部署
python deploy.py verify
Expected: 后端服务运行正常。
- Step 4: 提交
git commit -m "chore: 后端人生事件收藏功能已部署" --allow-empty
Task 3: 前端 ScriptLibraryView.vue 切换到剧本收藏 API
Files:
-
Modify:
mini-program/src/pages/main/ScriptLibraryView.vue -
Step 1: 删除 localFavorites ref,改用内存状态
删除第 160 行:
// 删除这一行
const localFavorites = ref(uni.getStorageSync('script_favorites') || {})
替换为:
const favoriteStatus = ref({})
- Step 2: 修改 isFavorite 计算
修改第 245-247 行:
// 之前:
const isFavorite = (script) => {
return Boolean(script.isFavorite || script.favorite || localFavorites.value[String(script.id)])
}
// 之后:
const isFavorite = (script) => {
return Boolean(favoriteStatus.value[String(script.id)])
}
- Step 3: 修改 toggleFavorite,删除 localStorage 写
修改第 320-348 行:
// 之前:
const toggleFavorite = async (script) => {
const wasFav = isFavorite(script)
// 乐观更新:先改本地缓存
const next = { ...localFavorites.value }
if (wasFav) {
delete next[String(script.id)]
} else {
next[String(script.id)] = true
}
localFavorites.value = next
uni.setStorageSync('script_favorites', next)
closeScriptMenu()
try {
const result = await toggleFavoriteScript(script.id)
uni.showToast({ title: result.isFavorited ? '已收藏' : '已取消收藏', icon: 'success' })
} catch (error) {
// 回滚乐观更新
const restored = { ...localFavorites.value }
if (wasFav) {
restored[String(script.id)] = true
} else {
delete restored[String(script.id)]
}
localFavorites.value = restored
uni.setStorageSync('script_favorites', restored)
uni.showToast({ title: error?.message || '操作失败', icon: 'none' })
}
}
// 之后:
const toggleFavorite = async (script) => {
const wasFav = isFavorite(script)
// 乐观更新:先改内存状态
const next = { ...favoriteStatus.value }
if (wasFav) {
delete next[String(script.id)]
} else {
next[String(script.id)] = true
}
favoriteStatus.value = next
closeScriptMenu()
try {
const result = await toggleFavoriteScript(script.id)
uni.showToast({ title: result.isFavorited ? '已收藏' : '已取消收藏', icon: 'success' })
} catch (error) {
// 回滚乐观更新
const restored = { ...favoriteStatus.value }
if (wasFav) {
restored[String(script.id)] = true
} else {
delete restored[String(script.id)]
}
favoriteStatus.value = restored
uni.showToast({ title: error?.message || '操作失败', icon: 'none' })
}
}
- Step 4: 修改 confirmDeleteScript,删除 localStorage 清理
修改第 386-389 行:
// 之前:
const next = { ...localFavorites.value }
delete next[id]
localFavorites.value = next
uni.setStorageSync('script_favorites', next)
// 之后:
const next = { ...favoriteStatus.value }
delete next[id]
favoriteStatus.value = next
- Step 5: 列表加载后批量检查收藏状态
在 visibleScripts computed 后添加 watch 或 onMounted 钩子,批量调用 checkFavoriteScript:
在文件顶部添加 import:
import { checkFavoriteScript } from '../../services/epicScript.js'
在 visibleScripts computed 后添加:
// 列表加载后批量检查收藏状态
const loadFavoriteStatus = async () => {
const ids = scripts.value.map(s => String(s.id)).filter(id => !id.startsWith('demo-'))
if (!ids.length) return
const status = {}
await Promise.all(ids.map(async (id) => {
try {
status[id] = await checkFavoriteScript(id)
} catch {
status[id] = false
}
}))
favoriteStatus.value = status
}
// 监听 scripts 变化时重新加载收藏状态
watch(scripts, () => {
loadFavoriteStatus()
}, { immediate: true })
确保 import watch:
import { computed, ref, watch } from 'vue'
- Step 6: 提交
git add mini-program/src/pages/main/ScriptLibraryView.vue
git commit -m "refactor: 剧本收藏从 localStorage 切换到真实 API"
Task 4: 前端 life-event 切换到人生事件收藏 API
Files:
-
Modify:
mini-program/src/services/lifeEvent.js -
Modify:
mini-program/src/pages/life-event/detail.vue -
Modify:
mini-program/src/stores/app.js -
Step 1: 修改 lifeEvent.js 新增收藏 API 封装
在 mini-program/src/services/lifeEvent.js 中:
- 删除旧的
favoriteEvent方法(第 67-69 行):
// 删除:
export const favoriteEvent = async ({ id, favorite }) => {
return post('/lifeEvent/favorite-placeholder', { id, favorite })
}
- 新增真实的收藏 API:
/**
* 切换人生事件收藏状态(收藏/取消收藏)
* @param {string} eventId 事件ID
* @returns {Promise<{success: boolean, isFavorited: boolean}>}
*/
export const toggleFavoriteEvent = async (eventId) => {
const res = await post('/lifeEvent/favorite/toggle', { eventId })
return {
success: true,
isFavorited: res.data?.isFavorited ?? false,
favoriteTime: res.data?.favoriteTime ?? null
}
}
/**
* 检查人生事件是否已收藏
* @param {string} eventId 事件ID
* @returns {Promise<boolean>}
*/
export const checkFavoriteEvent = async (eventId) => {
const res = await get('/lifeEvent/favorite/check', { eventId })
return res.data?.isFavorited ?? false
}
- 在 default export 中更新方法列表:
export default {
getEventList,
getEventPage,
getEventById,
createEvent,
updateEvent,
deleteEvent,
assistEventWriting,
chatAboutEvent,
shareEvent,
toggleFavoriteEvent,
checkFavoriteEvent,
transformToFrontendFormat,
transformListToFrontend
}
- Step 2: 修改 stores/app.js 的 favoriteEvent action
在 mini-program/src/stores/app.js 中:
- 删除旧的
favoriteEventaction(第 235-242 行):
// 删除:
const favoriteEvent = async ({ id, favorite }) => {
try {
const res = await lifeEventService.favoriteEvent({ id, favorite })
return { success: true, data: res.data }
} catch (error) {
return { success: false, error: error.message }
}
}
- 新增新的 action:
const toggleEventFavorite = async (eventId) => {
try {
const result = await lifeEventService.toggleFavoriteEvent(eventId)
return { success: true, data: result }
} catch (error) {
return { success: false, error: error.message }
}
}
const checkEventFavorite = async (eventId) => {
try {
const isFavorited = await lifeEventService.checkFavoriteEvent(eventId)
return { success: true, data: isFavorited }
} catch (error) {
return { success: false, error: error.message }
}
}
- 在 return 对象中更新导出:
return {
// ... 其他方法
toggleEventFavorite,
checkEventFavorite,
// 删除 favoriteEvent
}
- Step 3: 修改 detail.vue 初始化时检查收藏状态
在 mini-program/src/pages/life-event/detail.vue 中:
- 修改
onMounted(第 137-149 行):
// 之前:
onMounted(async () => {
const info = uni.getWindowInfo()
safeAreaBottom.value = info.safeAreaInsets?.bottom || 0
const pages = getCurrentPages()
eventId.value = pages[pages.length - 1]?.options?.id || ''
cachedEvent.value = uni.getStorageSync('current_life_event') || null
if (!store.events?.length) await store.fetchEvents()
isFavorite.value = Boolean(uni.getStorageSync(`event_favorite_${eventId.value}`))
analytics.trackPageView(pagePath, {
life_event_id: eventId.value,
tag_count: Array.isArray(displayEvent.value.tags) ? displayEvent.value.tags.length : 0
})
})
// 之后:
onMounted(async () => {
const info = uni.getWindowInfo()
safeAreaBottom.value = info.safeAreaInsets?.bottom || 0
const pages = getCurrentPages()
eventId.value = pages[pages.length - 1]?.options?.id || ''
cachedEvent.value = uni.getStorageSync('current_life_event') || null
if (!store.events?.length) await store.fetchEvents()
// 从 API 检查收藏状态
const result = await store.checkEventFavorite(eventId.value)
if (result.success) {
isFavorite.value = result.data
}
analytics.trackPageView(pagePath, {
life_event_id: eventId.value,
tag_count: Array.isArray(displayEvent.value.tags) ? displayEvent.value.tags.length : 0
})
})
- Step 4: 修改 detail.vue 的 toggleFavorite,删除 localStorage 写
修改第 306-322 行:
// 之前:
const toggleFavorite = async () => {
if (!eventId.value) return
const next = !isFavorite.value
const result = await store.favoriteEvent({ id: eventId.value, favorite: next })
if (!result.success) {
uni.showToast({ title: result.error || '收藏失败', icon: 'none' })
return
}
isFavorite.value = next
if (next) uni.setStorageSync(`event_favorite_${eventId.value}`, '1')
else uni.removeStorageSync(`event_favorite_${eventId.value}`)
analytics.track('life_event_favorite', {
life_event_id: eventId.value,
favorite: next
}, { eventType: 'life_event', pagePath })
uni.showToast({ title: next ? '已收藏' : '已取消收藏', icon: 'success' })
}
// 之后:
const toggleFavorite = async () => {
if (!eventId.value) return
const result = await store.toggleEventFavorite(eventId.value)
if (!result.success) {
uni.showToast({ title: result.error || '收藏失败', icon: 'none' })
return
}
isFavorite.value = result.data?.isFavorited ?? false
analytics.track('life_event_favorite', {
life_event_id: eventId.value,
favorite: isFavorite.value
}, { eventType: 'life_event', pagePath })
uni.showToast({ title: isFavorite.value ? '已收藏' : '已取消收藏', icon: 'success' })
}
- Step 5: 提交
git add mini-program/src/services/lifeEvent.js
git add mini-program/src/stores/app.js
git add mini-program/src/pages/life-event/detail.vue
git commit -m "refactor: 人生事件收藏从 localStorage 切换到真实 API"
Task 5: H5 浏览器验证
Files:
-
Test:
http://localhost:5180(H5 开发服务器) -
Step 1: 确认 H5 开发服务器运行中
python dev-services.py status
Expected: mini-program 服务在端口 5180 上运行。如未运行,执行:
python dev-services.py start mini-program
- Step 2: 浏览器打开页面并登录
- 打开
http://localhost:5180 - 如未登录,使用手机号验证码 123456 登录
- Step 3: 验证剧本收藏
- 点击底部导航「爽文生成」
- 点击「剧本库」
- 点击任意剧本的收藏图标(心形)
- 确认:
- ✅ Toast 提示「已收藏」
- ✅ 心形图标变为实心
- ✅ Network 面板中有
POST /epicScript/favorite/toggle请求,返回isFavorited: true
- 刷新页面
- 确认收藏状态仍保持(心形图标仍为实心)
- 再次点击收藏图标取消收藏
- 确认:
- ✅ Toast 提示「已取消收藏」
- ✅ 心形图标变为空心
- ✅ Network 面板中有
POST /epicScript/favorite/toggle请求,返回isFavorited: false
- Step 4: 验证人生事件收藏
- 点击底部导航「人生轨迹」
- 点击任意事件进入详情页
- 点击收藏按钮
- 确认:
- ✅ Toast 提示「已收藏」
- ✅ 收藏按钮状态变化
- ✅ Network 面板中有
POST /lifeEvent/favorite/toggle请求,返回isFavorited: true
- 返回列表页,再次进入同一事件详情
- 确认收藏状态仍保持
- 再次点击收藏按钮取消收藏
- 确认:
- ✅ Toast 提示「已取消收藏」
- ✅ 收藏按钮状态恢复
- ✅ Network 面板中有
POST /lifeEvent/favorite/toggle请求,返回isFavorited: false
- Step 5: 检查浏览器 Console 无报错
打开浏览器 DevTools → Console 面板,确认:
-
✅ 无红色 ERROR
-
✅ 无 localStorage 相关的
script_favorites或event_favorite_读写 -
Step 6: 提交(如有微调)
git add mini-program/src/
git commit -m "test: H5 浏览器验证收藏功能通过"
Task 6: 通知用户验证
- Step 1: 提示用户操作
告知用户:
收藏功能已完成真实 API 化改造:
剧本收藏:
- 前端从 localStorage 切换到现有后端 API(
POST /epicScript/favorite/toggle)- 收藏状态从服务器读取,不再依赖本地缓存
人生事件收藏:
- 后端新建独立的
t_event_favorite表 + API- 前端从 localStorage 切换到真实 API(
POST /lifeEvent/favorite/toggle)- 删除了假接口
/lifeEvent/favorite-placeholder验证方式:
- 本地 H5(
http://localhost:5180)已验证通过- 后端已部署到服务器
- 请在微信开发者工具中:
- 打开
unpackage/dist/dev/mp-weixin目录- 点击「编译」重新编译
- 测试剧本收藏和人生事件收藏功能
- 确认收藏状态刷新后仍保持
如有问题,截图反馈。