topfans/frontend/pages/support-activity/composables/useContributionPolling.js

201 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ref, watch, onUnmounted } from 'vue'
import { getActivityContributionsLatestApi } from '@/utils/api.js'
/**
* 贡献轮询逻辑 composable
* @param {Ref<string>} 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
}
}