diff --git a/frontend/pages/support-activity/components/ContributionList.vue b/frontend/pages/support-activity/components/ContributionList.vue
index 6151923..6418ad5 100644
--- a/frontend/pages/support-activity/components/ContributionList.vue
+++ b/frontend/pages/support-activity/components/ContributionList.vue
@@ -8,21 +8,21 @@
v-for="(record, index) in records"
:key="record.id"
class="contribution-item"
- :class="{ 'new-item': index === 0 && isNewRecord(record.id) }"
+ :class="{ 'new-item': index === 0, 'fading-out': record.fading }"
>
{{ record.user_nickname }}
贡献了
{{ record.item_name }}
- x{{ record.quantity }}
+ x{{ record.combo_count > 1 ? record.combo_count : record.quantity }}
@@ -125,6 +99,10 @@ defineExpose({
animation: slideIn 0.3s ease-out;
}
+.fading-out {
+ animation: fadeOut 0.5s forwards;
+}
+
.user-avatar {
width: 48rpx;
height: 48rpx;
@@ -186,4 +164,9 @@ defineExpose({
transform: translateX(0);
}
}
+
+@keyframes fadeOut {
+ from { opacity: 1; }
+ to { opacity: 0; }
+}
\ No newline at end of file
diff --git a/frontend/pages/support-activity/composables/useContributionPolling.js b/frontend/pages/support-activity/composables/useContributionPolling.js
index 8ddba82..df18828 100644
--- a/frontend/pages/support-activity/composables/useContributionPolling.js
+++ b/frontend/pages/support-activity/composables/useContributionPolling.js
@@ -5,7 +5,7 @@ import { getActivityContributionsLatestApi } from '@/utils/api.js'
* 贡献轮询逻辑 composable
* @param {Ref} activityId - 活动ID
* @param {boolean} isPageActive - 页面是否可见
- * @returns {Object} records, loading, error, start, stop, refresh
+ * @returns {Object} records, visible, loading, error, start, stop, reset
*/
export function useContributionPolling(activityId, isPageActive) {
const MAX_RECORDS = 5
@@ -13,22 +13,42 @@ export function useContributionPolling(activityId, isPageActive) {
const RECORD_TTL = 5000 // 每条记录 5 秒后消失
const records = ref([])
+ const visible = ref(true)
const loading = ref(false)
const error = ref(null)
- let latestId = 0
+ let latestTimestamp = 0 // 当前列表中最新记录的时间戳
+ let latestId = 0 // 当前列表中最新记录的 ID(同时间戳内二次筛选用)
let pollingTimer = null
let isPolling = false
const recordTimers = new Map()
// 重置记录计时器
function resetRecordTimer(record) {
+ // 清除已有定时器
if (recordTimers.has(record.id)) {
clearTimeout(recordTimers.get(record.id))
- }
- const timer = setTimeout(() => {
- records.value = records.value.filter(r => r.id !== record.id)
recordTimers.delete(record.id)
+ }
+ // 清除淡出状态(新数据到来时重置)
+ const target = records.value.find(r => r.id === record.id)
+ if (target) {
+ target.fading = false
+ }
+ // 设置新的消失定时器
+ const timer = setTimeout(() => {
+ // 标记为淡出状态
+ const target = records.value.find(r => r.id === record.id)
+ if (target) {
+ target.fading = true
+ // 等待动画完成后移除
+ setTimeout(() => {
+ records.value = records.value.filter(r => r.id !== record.id)
+ recordTimers.delete(record.id)
+ }, 500)
+ } else {
+ recordTimers.delete(record.id)
+ }
}, RECORD_TTL)
recordTimers.set(record.id, timer)
}
@@ -38,7 +58,7 @@ export function useContributionPolling(activityId, isPageActive) {
if (!activityId.value) return
try {
- const res = await getActivityContributionsLatestApi(activityId.value, latestId, 1)
+ const res = await getActivityContributionsLatestApi(activityId.value, latestTimestamp, latestId, 1)
// 处理 code 不为 200 的情况(静默忽略)
if (res.code !== 200) return
@@ -48,18 +68,19 @@ export function useContributionPolling(activityId, isPageActive) {
const newRecord = newRecords[0]
- // 检测到新记录(id > latestId)
- if (newRecord.id > latestId) {
- // 重置所有现有记录的计时器
- records.value.forEach(resetRecordTimer)
+ // 检测到新记录(时间戳更新,或时间戳相同但 ID 更新)
+ const isNew = newRecord.created_at > latestTimestamp ||
+ (newRecord.created_at === latestTimestamp && newRecord.id > latestId)
+ if (isNew) {
+ // 重置所有现有记录的计时器(新数据到来,刷新列表)
+ records.value.forEach(resetRecordTimer)
// 新记录插入到列表头部
records.value = [newRecord, ...records.value].slice(0, MAX_RECORDS)
-
// 为新记录启动消失计时器
resetRecordTimer(newRecord)
-
- // 更新 latestId
+ // 更新时间戳和 ID
+ latestTimestamp = newRecord.created_at
latestId = newRecord.id
}
} catch (e) {
@@ -76,7 +97,7 @@ export function useContributionPolling(activityId, isPageActive) {
error.value = null
try {
- const res = await getActivityContributionsLatestApi(activityId.value, 0, MAX_RECORDS)
+ const res = await getActivityContributionsLatestApi(activityId.value, 0, 0, MAX_RECORDS)
if (res.code !== 200) {
throw new Error(res.message || '获取贡献记录失败')
@@ -94,8 +115,11 @@ export function useContributionPolling(activityId, isPageActive) {
// 为每条记录启动计时器
newRecords.forEach(record => resetRecordTimer(record))
- // 更新 latestId
- latestId = newRecords.length > 0 ? newRecords[newRecords.length - 1].id : 0
+ // 更新 latestTimestamp 和 latestId
+ if (newRecords.length > 0) {
+ latestTimestamp = newRecords[0].created_at
+ latestId = newRecords[0].id
+ }
} catch (e) {
error.value = e.message || '获取贡献记录失败'
console.error('[useContributionPolling] fetchAll error:', e)
@@ -110,42 +134,42 @@ export function useContributionPolling(activityId, isPageActive) {
if (!activityId.value) return
isPolling = true
- latestId = 0
-
- // 全量拉取首次
- fetchAll()
-
- // 启动定时轮询
+ // 如果 latestTimestamp 为 0,说明是首次或切换活动,清空列表
+ if (latestTimestamp === 0) {
+ records.value = []
+ }
+ fetchLatest()
pollingTimer = setInterval(fetchLatest, POLL_INTERVAL)
}
- // 停止轮询
+ // 停止轮询(保留状态)
function stop() {
if (pollingTimer) {
clearInterval(pollingTimer)
pollingTimer = null
}
-
- // 清除所有计时器
+ // 停止所有记录的计时器
recordTimers.forEach(timer => clearTimeout(timer))
recordTimers.clear()
-
isPolling = false
- records.value = []
- latestId = 0
+ // 不清空 records、latestTimestamp、latestId,保留状态以便恢复
}
- // 强制刷新(全量拉取)
- function refresh() {
+ // 重置所有状态(切换活动时用)
+ function reset() {
stop()
- start()
+ latestTimestamp = 0
+ latestId = 0
+ records.value = []
}
// 监听页面可见性
watch(isPageActive, (active) => {
if (active) {
+ visible.value = true
start()
} else {
+ visible.value = false
stop()
}
}, { immediate: true })
@@ -153,7 +177,7 @@ export function useContributionPolling(activityId, isPageActive) {
// 监听 activityId 变化
watch(activityId, (newId, oldId) => {
if (newId !== oldId) {
- stop()
+ reset()
if (isPageActive.value && newId) {
start()
}
@@ -162,15 +186,16 @@ export function useContributionPolling(activityId, isPageActive) {
// 组件卸载时清理
onUnmounted(() => {
- stop()
+ reset()
})
return {
records,
+ visible,
loading,
error,
start,
stop,
- refresh
+ reset
}
}
\ No newline at end of file