diff --git a/src/pages/admin/statistics/SystemStatisticsPage.vue b/src/pages/admin/statistics/SystemStatisticsPage.vue index 06185a0..bc52658 100644 --- a/src/pages/admin/statistics/SystemStatisticsPage.vue +++ b/src/pages/admin/statistics/SystemStatisticsPage.vue @@ -127,7 +127,7 @@ -
+
@@ -147,6 +147,7 @@ :class="[getDanmakuClass(item), { 'danmaku-paused': pausedDanmakuIds.has(item.id) }]" @mouseenter="pauseDanmaku(item.id)" @mouseleave="resumeDanmaku(item.id)" + @animationend="onDanmakuAnimationEnd(item.id)" >
{ let params = {} if (isFirstLoad.value) { - // 首次加载:获取最近20条记录,并计算每条记录的调用次数 + // 首次加载:获取最近3条记录,并计算每条记录的调用次数 params = { page: 1, - page_size: 20, + page_size: 3, sort_by: 'created_at', sort_order: 'desc' } @@ -1548,26 +1552,52 @@ const loadDanmakuData = async () => { })) if (isFirstLoad.value) { - // 首次加载:直接设置这20条记录(即使为空也要设置) + // 首次加载:先把最新的3条记录放入堆栈,然后逐个显示 // 确保所有记录的调用次数都已计算完成 const sortedItems = newItems.sort((a, b) => b.timestamp - a.timestamp) - danmakuItems.value = sortedItems + + // 如果没有数据,直接标记为已加载,不显示任何弹幕 + if (sortedItems.length === 0) { + isFirstLoad.value = false + lastFetchTime.value = now + danmakuItems.value = [] + console.log('首次加载完成,没有记录') + return + } + + // 先把3条记录放入堆栈(但不立即显示) + firstLoadItems.value = sortedItems + firstLoadDisplayIndex.value = 0 + danmakuItems.value = [] // 先清空显示列表 isFirstLoad.value = false lastFetchTime.value = now - console.log('首次加载完成,已加载', sortedItems.length, '条记录,每条记录都已计算调用次数') + console.log('首次加载完成,已加载', sortedItems.length, '条记录到堆栈,每条记录都已计算调用次数') + + // 逐个显示弹幕,每次间隔300ms + if (firstLoadItems.value.length > 0) { + firstLoadDisplayTimer.value = setInterval(() => { + if (firstLoadDisplayIndex.value < firstLoadItems.value.length) { + // 从堆栈中取出并显示 + danmakuItems.value.push(firstLoadItems.value[firstLoadDisplayIndex.value]) + firstLoadDisplayIndex.value++ + } else { + if (firstLoadDisplayTimer.value) { + clearInterval(firstLoadDisplayTimer.value) + firstLoadDisplayTimer.value = null + } + firstLoadItems.value = [] // 清空首次加载数据 + } + }, 300) + } } else { - // 持续轮询:将新记录添加到堆栈中(按时间戳倒序,最新的在前) + // 持续轮询:将新记录添加到堆栈中(只添加新记录,不保留旧记录) if (newItems.length > 0) { const existingIds = new Set(danmakuItems.value.map(item => item.id)) const uniqueNewItems = newItems.filter(item => !existingIds.has(item.id)) if (uniqueNewItems.length > 0) { - // 合并新旧数据,按时间戳排序,保留最新的100条 - const allItems = [...danmakuItems.value, ...uniqueNewItems] - .sort((a, b) => b.timestamp - a.timestamp) // 最新的在前 - .slice(0, MAX_DANMAKU_ITEMS) // 只保留100条 - - danmakuItems.value = allItems + // 只添加新记录到堆栈,不保留旧记录(旧记录会在播放完成后自动移除) + danmakuItems.value.push(...uniqueNewItems) } } // 无论是否有新记录,都要更新时间 @@ -1592,9 +1622,9 @@ const getDanmakuStyle = (item, index) => { return {} } - const containerHeight = danmakuContainer.value.clientHeight || 128 + const containerHeight = danmakuContainer.value.clientHeight || 80 const itemHeight = 36 // 每条弹幕的高度(包含间距) - const maxRows = Math.floor(containerHeight / itemHeight) + const maxRows = 2 // 固定为两行 // 计算弹幕应该在哪一行(垂直位置) // 使用索引和时间戳的组合来分配行,避免重叠 @@ -1688,6 +1718,17 @@ const resumeDanmaku = (itemId) => { pausedDanmakuIds.value.delete(itemId) } +// 弹幕动画结束回调 - 从堆栈中移除已播放完成的弹幕 +const onDanmakuAnimationEnd = (itemId) => { + // 只有当弹幕不在暂停状态时才移除(避免鼠标悬停时误删) + if (!pausedDanmakuIds.value.has(itemId)) { + const index = danmakuItems.value.findIndex(item => item.id === itemId) + if (index !== -1) { + danmakuItems.value.splice(index, 1) + } + } +} + // 启动弹幕轮询 const startDanmakuPolling = () => { // 立即加载一次 @@ -1705,6 +1746,11 @@ const stopDanmakuPolling = () => { clearInterval(danmakuPollingTimer.value) danmakuPollingTimer.value = null } + // 清理首次加载显示定时器 + if (firstLoadDisplayTimer.value) { + clearInterval(firstLoadDisplayTimer.value) + firstLoadDisplayTimer.value = null + } } onMounted(() => { @@ -1729,9 +1775,9 @@ onUnmounted(() => {