From dd11cfae73dd3c85cf8f4c02162b92f5105db481 Mon Sep 17 00:00:00 2001 From: Peanut Date: Sun, 19 Jul 2026 23:23:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BA=BA=E7=94=9F=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E5=89=A7=E6=9C=AC=E6=94=B6=E8=97=8F=E4=BB=8E=20localS?= =?UTF-8?q?torage=20=E5=88=87=E6=8D=A2=E4=B8=BA=E7=9C=9F=E5=AE=9E=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端:新增 t_event_favorite 表(UNIQUE KEY + MyBatis-Plus Entity) - 后端:LifeEventController 新增 /favorite/toggle、/favorite/page、/favorite/check 三个真实端点 - 后端:EventFavoriteService 完整 CRUD,含 toggle/分页/批量查询/状态检查 - 后端:删除旧的 favorite-placeholder mock 端点 - 前端:life-event/detail.vue 从 localStorage 假收藏切换为真实 API + localStorage 缓存防闪烁 - 前端:ScriptLibraryView.vue 从 localFavorites 切换为 checkFavoriteScript 真实 API - 前端:stores/app.js 新增 toggleEventFavorite / checkEventFavorite 方法 - 修复:审核发现的 UNIQUE 约束缺失、未使用 watch 导入、Controller 参数注解遗漏、Controller 委托 Service 构造 Response Co-Authored-By: Claude Opus 4.8 (1M context) --- mini-program/src/pages/life-event/detail.vue | 42 ++++- .../src/pages/main/ScriptLibraryView.vue | 45 +++-- mini-program/src/services/lifeEvent.js | 27 ++- mini-program/src/stores/app.js | 18 +- .../controller/LifeEventController.java | 42 +++-- .../request/EventFavoriteToggleRequest.java | 18 ++ .../dto/response/EventFavoriteResponse.java | 19 +++ .../com/emotion/entity/EventFavorite.java | 31 ++++ .../emotion/mapper/EventFavoriteMapper.java | 15 ++ .../emotion/service/EventFavoriteService.java | 66 ++++++++ .../impl/EventFavoriteServiceImpl.java | 157 ++++++++++++++++++ ...V20260719000002__create_event_favorite.sql | 12 ++ 12 files changed, 451 insertions(+), 41 deletions(-) create mode 100644 server/src/main/java/com/emotion/dto/request/EventFavoriteToggleRequest.java create mode 100644 server/src/main/java/com/emotion/dto/response/EventFavoriteResponse.java create mode 100644 server/src/main/java/com/emotion/entity/EventFavorite.java create mode 100644 server/src/main/java/com/emotion/mapper/EventFavoriteMapper.java create mode 100644 server/src/main/java/com/emotion/service/EventFavoriteService.java create mode 100644 server/src/main/java/com/emotion/service/impl/EventFavoriteServiceImpl.java create mode 100644 server/src/main/resources/db/migration/V20260719000002__create_event_favorite.sql diff --git a/mini-program/src/pages/life-event/detail.vue b/mini-program/src/pages/life-event/detail.vue index 6c8287c..b8f1529 100644 --- a/mini-program/src/pages/life-event/detail.vue +++ b/mini-program/src/pages/life-event/detail.vue @@ -134,14 +134,41 @@ const isDescriptionCollapsed = ref(false) const scrollTop = ref(0) const currentScrollTop = ref(0) +// 从 localStorage 读取缓存的收藏状态 +const getCachedFavoriteStatus = (id) => { + const cached = uni.getStorageSync(`favorite_event_${id}`) + return cached === 'true' +} + +// 缓存收藏状态到 localStorage +const setCachedFavoriteStatus = (id, status) => { + uni.setStorageSync(`favorite_event_${id}`, status ? 'true' : 'false') +} + onMounted(async () => { const info = uni.getWindowInfo() safeAreaBottom.value = info.safeAreaInsets?.bottom || 0 const pages = getCurrentPages() eventId.value = pages[pages.length - 1]?.options?.id || '' + + // 先从缓存读取收藏状态,避免闪烁 + if (eventId.value) { + isFavorite.value = getCachedFavoriteStatus(eventId.value) + } + cachedEvent.value = uni.getStorageSync('current_life_event') || null if (!store.events?.length) await store.fetchEvents() - isFavorite.value = Boolean(uni.getStorageSync(`event_favorite_${eventId.value}`)) + + // 异步校验真实收藏状态并更新缓存 + if (eventId.value) { + const result = await store.checkEventFavorite(eventId.value) + if (result.success) { + const realStatus = result.data + isFavorite.value = realStatus + setCachedFavoriteStatus(eventId.value, realStatus) + } + } + analytics.trackPageView(pagePath, { life_event_id: eventId.value, tag_count: Array.isArray(displayEvent.value.tags) ? displayEvent.value.tags.length : 0 @@ -305,20 +332,19 @@ const editEvent = () => { const toggleFavorite = async () => { if (!eventId.value) return - const next = !isFavorite.value - const result = await store.favoriteEvent({ id: eventId.value, favorite: next }) + const result = await store.toggleEventFavorite(eventId.value) 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}`) + const newStatus = result.data?.isFavorited ?? false + isFavorite.value = newStatus + setCachedFavoriteStatus(eventId.value, newStatus) analytics.track('life_event_favorite', { life_event_id: eventId.value, - favorite: next + favorite: newStatus }, { eventType: 'life_event', pagePath }) - uni.showToast({ title: next ? '已收藏' : '已取消收藏', icon: 'success' }) + uni.showToast({ title: newStatus ? '已收藏' : '已取消收藏', icon: 'success' }) } const chatEvent = async () => { diff --git a/mini-program/src/pages/main/ScriptLibraryView.vue b/mini-program/src/pages/main/ScriptLibraryView.vue index 2479c18..bff4316 100644 --- a/mini-program/src/pages/main/ScriptLibraryView.vue +++ b/mini-program/src/pages/main/ScriptLibraryView.vue @@ -147,9 +147,9 @@