ok
This commit is contained in:
@@ -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
|
||||
console.log('首次加载完成,已加载', sortedItems.length, '条记录,每条记录都已计算调用次数')
|
||||
danmakuItems.value = []
|
||||
console.log('首次加载完成,没有记录')
|
||||
return
|
||||
}
|
||||
|
||||
// 先把3条记录放入堆栈(但不立即显示)
|
||||
firstLoadItems.value = sortedItems
|
||||
firstLoadDisplayIndex.value = 0
|
||||
danmakuItems.value = [] // 先清空显示列表
|
||||
isFirstLoad.value = false
|
||||
lastFetchTime.value = now
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user