import { ref, watch, onUnmounted } from 'vue' import { getActivityContributionsLatestApi } from '@/utils/api.js' /** * 贡献轮询逻辑 composable * @param {Ref} activityId - 活动ID * @param {boolean} isPageActive - 页面是否可见 * @returns {Object} records, visible, loading, error, start, stop, reset */ export function useContributionPolling(activityId, isPageActive) { const MAX_RECORDS = 5 const POLL_INTERVAL = 1000 // 每秒拉取 const RECORD_TTL = 5000 // 每条记录 5 秒后消失 const records = ref([]) const visible = ref(true) const loading = ref(false) const error = ref(null) 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)) 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) } // 获取最新贡献记录 async function fetchLatest() { if (!activityId.value) return try { const res = await getActivityContributionsLatestApi(activityId.value, latestTimestamp, latestId, 1) // 处理 code 不为 200 的情况(静默忽略) if (res.code !== 200) return const newRecords = res.data?.records || [] if (newRecords.length === 0) return const newRecord = newRecords[0] // 检测到新记录(时间戳更新,或时间戳相同但 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) // 更新时间戳和 ID latestTimestamp = newRecord.created_at latestId = newRecord.id } } catch (e) { // 网络错误等,静默忽略,继续等待下一次轮询 console.error('[useContributionPolling] fetchLatest error:', e) } } // 全量拉取(首次或重新开始) async function fetchAll() { if (!activityId.value) return loading.value = true error.value = null try { const res = await getActivityContributionsLatestApi(activityId.value, 0, 0, MAX_RECORDS) if (res.code !== 200) { throw new Error(res.message || '获取贡献记录失败') } const newRecords = res.data?.records || [] // 清除所有计时器 recordTimers.forEach(timer => clearTimeout(timer)) recordTimers.clear() // 重置列表 records.value = newRecords // 为每条记录启动计时器 newRecords.forEach(record => resetRecordTimer(record)) // 更新 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) } finally { loading.value = false } } // 开始轮询 function start() { if (pollingTimer) return if (!activityId.value) return isPolling = true // 如果 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、latestTimestamp、latestId,保留状态以便恢复 } // 重置所有状态(切换活动时用) function reset() { stop() latestTimestamp = 0 latestId = 0 records.value = [] } // 监听页面可见性 watch(isPageActive, (active) => { if (active) { visible.value = true start() } else { visible.value = false stop() } }, { immediate: true }) // 监听 activityId 变化 watch(activityId, (newId, oldId) => { if (newId !== oldId) { reset() if (isPageActive.value && newId) { start() } } }) // 组件卸载时清理 onUnmounted(() => { reset() }) return { records, visible, loading, error, start, stop, reset } }