ok
This commit is contained in:
@@ -127,7 +127,7 @@
|
|||||||
</div>
|
</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 v-if="danmakuItems.length === 0" class="absolute inset-0 flex items-center justify-center text-gray-400 text-sm">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
@@ -147,6 +147,7 @@
|
|||||||
:class="[getDanmakuClass(item), { 'danmaku-paused': pausedDanmakuIds.has(item.id) }]"
|
:class="[getDanmakuClass(item), { 'danmaku-paused': pausedDanmakuIds.has(item.id) }]"
|
||||||
@mouseenter="pauseDanmaku(item.id)"
|
@mouseenter="pauseDanmaku(item.id)"
|
||||||
@mouseleave="resumeDanmaku(item.id)"
|
@mouseleave="resumeDanmaku(item.id)"
|
||||||
|
@animationend="onDanmakuAnimationEnd(item.id)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<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"
|
<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 danmakuItems = ref([])
|
||||||
const danmakuContainer = ref(null)
|
const danmakuContainer = ref(null)
|
||||||
const danmakuPollingTimer = ref(null)
|
const danmakuPollingTimer = ref(null)
|
||||||
|
const firstLoadDisplayTimer = ref(null) // 首次加载弹幕显示的定时器
|
||||||
const lastFetchTime = ref(null)
|
const lastFetchTime = ref(null)
|
||||||
const isFirstLoad = ref(true) // 标记是否是首次加载
|
const isFirstLoad = ref(true) // 标记是否是首次加载
|
||||||
|
const firstLoadItems = ref([]) // 首次加载的弹幕项(用于逐个显示)
|
||||||
|
const firstLoadDisplayIndex = ref(0) // 首次加载弹幕的显示索引
|
||||||
// 缓存每个用户-产品组合的调用次数,避免重复查询
|
// 缓存每个用户-产品组合的调用次数,避免重复查询
|
||||||
const callCountCache = ref(new Map()) // key: `${userId}_${productId}_${createdAt}`, value: callCount
|
const callCountCache = ref(new Map()) // key: `${userId}_${productId}_${createdAt}`, value: callCount
|
||||||
const MAX_DANMAKU_ITEMS = 100
|
const MAX_DANMAKU_ITEMS = 100
|
||||||
@@ -1430,10 +1434,10 @@ const loadDanmakuData = async () => {
|
|||||||
let params = {}
|
let params = {}
|
||||||
|
|
||||||
if (isFirstLoad.value) {
|
if (isFirstLoad.value) {
|
||||||
// 首次加载:获取最近20条记录,并计算每条记录的调用次数
|
// 首次加载:获取最近3条记录,并计算每条记录的调用次数
|
||||||
params = {
|
params = {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20,
|
page_size: 3,
|
||||||
sort_by: 'created_at',
|
sort_by: 'created_at',
|
||||||
sort_order: 'desc'
|
sort_order: 'desc'
|
||||||
}
|
}
|
||||||
@@ -1548,26 +1552,52 @@ const loadDanmakuData = async () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
if (isFirstLoad.value) {
|
if (isFirstLoad.value) {
|
||||||
// 首次加载:直接设置这20条记录(即使为空也要设置)
|
// 首次加载:先把最新的3条记录放入堆栈,然后逐个显示
|
||||||
// 确保所有记录的调用次数都已计算完成
|
// 确保所有记录的调用次数都已计算完成
|
||||||
const sortedItems = newItems.sort((a, b) => b.timestamp - a.timestamp)
|
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
|
isFirstLoad.value = false
|
||||||
lastFetchTime.value = now
|
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 {
|
} else {
|
||||||
// 持续轮询:将新记录添加到堆栈中(按时间戳倒序,最新的在前)
|
// 持续轮询:将新记录添加到堆栈中(只添加新记录,不保留旧记录)
|
||||||
if (newItems.length > 0) {
|
if (newItems.length > 0) {
|
||||||
const existingIds = new Set(danmakuItems.value.map(item => item.id))
|
const existingIds = new Set(danmakuItems.value.map(item => item.id))
|
||||||
const uniqueNewItems = newItems.filter(item => !existingIds.has(item.id))
|
const uniqueNewItems = newItems.filter(item => !existingIds.has(item.id))
|
||||||
|
|
||||||
if (uniqueNewItems.length > 0) {
|
if (uniqueNewItems.length > 0) {
|
||||||
// 合并新旧数据,按时间戳排序,保留最新的100条
|
// 只添加新记录到堆栈,不保留旧记录(旧记录会在播放完成后自动移除)
|
||||||
const allItems = [...danmakuItems.value, ...uniqueNewItems]
|
danmakuItems.value.push(...uniqueNewItems)
|
||||||
.sort((a, b) => b.timestamp - a.timestamp) // 最新的在前
|
|
||||||
.slice(0, MAX_DANMAKU_ITEMS) // 只保留100条
|
|
||||||
|
|
||||||
danmakuItems.value = allItems
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 无论是否有新记录,都要更新时间
|
// 无论是否有新记录,都要更新时间
|
||||||
@@ -1592,9 +1622,9 @@ const getDanmakuStyle = (item, index) => {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerHeight = danmakuContainer.value.clientHeight || 128
|
const containerHeight = danmakuContainer.value.clientHeight || 80
|
||||||
const itemHeight = 36 // 每条弹幕的高度(包含间距)
|
const itemHeight = 36 // 每条弹幕的高度(包含间距)
|
||||||
const maxRows = Math.floor(containerHeight / itemHeight)
|
const maxRows = 2 // 固定为两行
|
||||||
|
|
||||||
// 计算弹幕应该在哪一行(垂直位置)
|
// 计算弹幕应该在哪一行(垂直位置)
|
||||||
// 使用索引和时间戳的组合来分配行,避免重叠
|
// 使用索引和时间戳的组合来分配行,避免重叠
|
||||||
@@ -1688,6 +1718,17 @@ const resumeDanmaku = (itemId) => {
|
|||||||
pausedDanmakuIds.value.delete(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 = () => {
|
const startDanmakuPolling = () => {
|
||||||
// 立即加载一次
|
// 立即加载一次
|
||||||
@@ -1705,6 +1746,11 @@ const stopDanmakuPolling = () => {
|
|||||||
clearInterval(danmakuPollingTimer.value)
|
clearInterval(danmakuPollingTimer.value)
|
||||||
danmakuPollingTimer.value = null
|
danmakuPollingTimer.value = null
|
||||||
}
|
}
|
||||||
|
// 清理首次加载显示定时器
|
||||||
|
if (firstLoadDisplayTimer.value) {
|
||||||
|
clearInterval(firstLoadDisplayTimer.value)
|
||||||
|
firstLoadDisplayTimer.value = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -1729,9 +1775,9 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 弹幕滚动动画 - 从左到右,速度一致 */
|
/* 弹幕滚动动画 - 从左到右,速度一致,只播放一次 */
|
||||||
.danmaku-item {
|
.danmaku-item {
|
||||||
animation: danmakuScroll 15s linear infinite;
|
animation: danmakuScroll 15s linear forwards;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user