189 lines
3.7 KiB
Vue
189 lines
3.7 KiB
Vue
<template>
|
|
<view class="contribution-list" v-if="visible">
|
|
<view class="list-header">
|
|
<text class="header-title">实时贡献</text>
|
|
</view>
|
|
<scroll-view class="list-content" scroll-y>
|
|
<view
|
|
v-for="(record, index) in records"
|
|
:key="record.id"
|
|
class="contribution-item"
|
|
:class="{ 'new-item': index === 0 && isNewRecord(record.id) }"
|
|
>
|
|
<image class="user-avatar" :src="record.user_avatar" mode="aspectFill" />
|
|
<text class="user-nickname">{{ record.user_nickname }}</text>
|
|
<text class="contribute-text">贡献了</text>
|
|
<image class="item-icon" :src="record.item_icon" mode="aspectFill" />
|
|
<text class="item-name">{{ record.item_name }}</text>
|
|
<text class="item-quantity">x{{ record.quantity }}</text>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
|
import { useContributionPolling } from '../composables/useContributionPolling.js'
|
|
|
|
const props = defineProps({
|
|
activityId: {
|
|
type: String,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
const visible = ref(true)
|
|
const isPageActive = ref(true)
|
|
const newRecordIds = ref(new Set())
|
|
|
|
// 使用轮询 composable
|
|
const { records, loading, error, start, stop, refresh } = useContributionPolling(
|
|
computed(() => props.activityId),
|
|
isPageActive
|
|
)
|
|
|
|
// 判断记录是否为新记录(刚加入的)
|
|
function isNewRecord(id) {
|
|
return newRecordIds.value.has(id)
|
|
}
|
|
|
|
// 监听 records 变化,标记新记录
|
|
watch(records, (newRecords, oldRecords) => {
|
|
if (newRecords.length > 0 && oldRecords) {
|
|
const newIds = newRecords.map(r => r.id)
|
|
const oldIds = oldRecords.map(r => r.id)
|
|
|
|
// 找出新增的记录ID
|
|
for (const id of newIds) {
|
|
if (!oldIds.includes(id)) {
|
|
newRecordIds.value.add(id)
|
|
// 2秒后移除新记录标记
|
|
setTimeout(() => {
|
|
newRecordIds.value.delete(id)
|
|
}, 2000)
|
|
}
|
|
}
|
|
}
|
|
}, { deep: true })
|
|
|
|
onMounted(() => {
|
|
isPageActive.value = true
|
|
})
|
|
|
|
// 页面生命周期集成
|
|
onUnmounted(() => {
|
|
stop()
|
|
})
|
|
|
|
// 暴露给父组件使用
|
|
defineExpose({
|
|
refresh
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.contribution-list {
|
|
width: 100%;
|
|
padding: 0 24rpx;
|
|
margin-top: 20rpx;
|
|
}
|
|
|
|
.list-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.header-title {
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
color: #fff;
|
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.list-content {
|
|
height: 200rpx;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-radius: 16rpx;
|
|
padding: 16rpx;
|
|
}
|
|
|
|
.contribution-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 12rpx 0;
|
|
border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
|
|
animation: fadeIn 0.3s ease-out;
|
|
}
|
|
|
|
.contribution-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.new-item {
|
|
animation: slideIn 0.3s ease-out;
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 48rpx;
|
|
height: 48rpx;
|
|
border-radius: 50%;
|
|
margin-right: 12rpx;
|
|
}
|
|
|
|
.user-nickname {
|
|
font-size: 24rpx;
|
|
color: #fff;
|
|
max-width: 120rpx;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.contribute-text {
|
|
font-size: 24rpx;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
margin: 0 12rpx;
|
|
}
|
|
|
|
.item-icon {
|
|
width: 36rpx;
|
|
height: 36rpx;
|
|
margin-right: 8rpx;
|
|
}
|
|
|
|
.item-name {
|
|
font-size: 24rpx;
|
|
color: #fff;
|
|
margin-right: 8rpx;
|
|
}
|
|
|
|
.item-quantity {
|
|
font-size: 24rpx;
|
|
color: #ffcc00;
|
|
font-weight: bold;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10rpx);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes slideIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateX(-20rpx);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
}
|
|
</style> |