This commit is contained in:
2025-12-06 18:49:02 +08:00
parent ee93acac07
commit f030c15fe4

View File

@@ -127,7 +127,7 @@
</div>
<!-- 弹幕容器 -->
<div ref="danmakuContainer" class="relative h-32 overflow-hidden bg-gradient-to-b from-gray-50 to-white rounded-lg border border-gray-100">
<div ref="danmakuContainer" class="relative h-20 overflow-hidden bg-gradient-to-b from-gray-50 to-white rounded-lg border border-gray-100">
<!-- 无数据提示 -->
<div v-if="danmakuItems.length === 0" class="absolute inset-0 flex items-center justify-center text-gray-400 text-sm">
<div class="text-center">
@@ -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)"
>
<div class="flex items-center gap-2">
<div class="flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-white text-xs font-bold"
@@ -585,8 +586,11 @@ const todayCertifiedEnterprises = ref([])
const danmakuItems = ref([])
const danmakuContainer = ref(null)
const danmakuPollingTimer = ref(null)
const firstLoadDisplayTimer = ref(null) // 首次加载弹幕显示的定时器
const lastFetchTime = ref(null)
const isFirstLoad = ref(true) // 标记是否是首次加载
const firstLoadItems = ref([]) // 首次加载的弹幕项(用于逐个显示)
const firstLoadDisplayIndex = ref(0) // 首次加载弹幕的显示索引
// 缓存每个用户-产品组合的调用次数,避免重复查询
const callCountCache = ref(new Map()) // key: `${userId}_${productId}_${createdAt}`, value: callCount
const MAX_DANMAKU_ITEMS = 100
@@ -1430,10 +1434,10 @@ const loadDanmakuData = async () => {
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(() => {
</script>
<style scoped>
/* 弹幕滚动动画 - 从左到右,速度一致 */
/* 弹幕滚动动画 - 从左到右,速度一致,只播放一次 */
.danmaku-item {
animation: danmakuScroll 15s linear infinite;
animation: danmakuScroll 15s linear forwards;
will-change: transform;
}