topfans/frontend/pages/support-activity/components/ActivityRankingModal.vue
2026-05-14 15:59:56 +08:00

1386 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view v-if="visible" class="modal-wrapper">
<transition name="fade">
<view v-if="visible" class="modal-mask" @tap="handleMaskClick">
</view>
</transition>
<transition name="slide-up">
<view v-if="visible" class="modal-container" :class="{ 'dragging': isDragging }" @tap.stop>
<!-- 背景图 -->
<view class="modal-background"></view>
<!-- 内容区域 -->
<view class="modal-content">
<!-- 头部区域 - 活动名称 + 关闭按钮 -->
<view class="header-section" @touchstart.stop="handleTouchStart" @touchmove.stop="handleTouchMove"
@touchend.stop="handleTouchEnd">
<view class="close-button" @tap="handleClose">
<image class="close-icon-img" src="/static/starbookcontent/tuichu.png" mode="aspectFit">
</image>
</view>
<image class="activity-title-img" src="/static/rank/activity-rank-badge.png" mode="aspectFit">
</image>
</view>
<!-- 滚动内容区域 -->
<scroll-view class="scrollable-content" scroll-y="true" :show-scrollbar="false"
@scrolltolower="handleScrollToLower" :lower-threshold="100" :refresher-enabled="true"
:refresher-triggered="isRefreshing" @refresherrefresh="handleRefresh"
refresher-background="transparent">
<!-- 加载中提示 -->
<view v-if="isLoadingData && !isRefreshing" class="loading-container">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 加载错误提示 -->
<view v-else-if="dataLoadError && !isRefreshing" class="error-container">
<text class="error-text">{{ dataLoadError }}</text>
<button class="retry-button" @tap="loadRankingData">重试</button>
</view>
<!-- TOP3 展示区域 -->
<view v-else-if="top3Users.length > 0" class="top3-section">
<view v-for="user in top3Users" :key="user.userId" class="top3-card-item">
<!-- 头像区域(居中显示,大尺寸) -->
<view class="avatar-container">
<image class="user-avatar" :src="user.avatar || '/static/avatar/1.jpeg'"
mode="aspectFill" @error="handleAvatarError"
@tap="handleViewProfile(user.userId)">
</image>
<!-- 排名图标在头像下方 -->
<view class="rank-badge-bottom">
<image class="rank-icon" :src="`/static/rank/charm-rank-icon${user.rank}.png`"
mode="aspectFit" @error="handleRankIconError"></image>
</view>
<view class="rank-badge-bottom rank-badge-bottom2">
<image class="rank-icon" :src="`/static/rank/rank-icon${user.rank}.png`"
mode="aspectFit" @error="handleRankIconError"></image>
</view>
</view>
<!-- 昵称(在头像下方) -->
<view class="nickname-container">
<text class="user-nickname-name">{{ user.nickname || '未知用户' }}</text>
</view>
<!-- 消耗水晶值(在头像上方) -->
<view class="popularity-container-top">
<image class="fire-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
<text class="popularity-score-top">{{ formatPopularityScore(user.popularityScore)
}}</text>
</view>
<!-- 拜访按钮 -->
<view v-if="!isCurrentUser(user.userId) && user.rank >= 4" class="visit-btn"
@tap="handleVisit(user.userId, user.nickname)">
<text class="visit-text">拜访</text>
</view>
</view>
</view>
<!-- 空数据提示 -->
<view v-if="!isLoadingData && !dataLoadError && rankingData.length === 0 && !isRefreshing"
class="empty-data">
<text class="empty-text">暂无排名数据</text>
</view>
<!-- 排名列表区域 -->
<view v-if="!isLoadingData && !dataLoadError && listUsers.length > 0"
class="ranking-list-section">
<view v-for="item in listUsers" :key="item.userId" class="ranking-list-item">
<!-- 排名编号 -->
<view class="rank-number">
<text class="rank-text">{{ item.rank }}</text>
</view>
<!-- 左侧:头像 + 昵称 -->
<view class="left-section">
<image class="user-avatar-small" :src="item.avatar || '/static/avatar/1.jpeg'"
mode="aspectFill" @error="handleItemAvatarError"
@tap="handleViewProfile(item.userId)"></image>
<text class="item-nickname">{{ item.nickname || '未知用户' }}</text>
</view>
<!-- 右侧:人气值和拜访按钮 -->
<view class="right-section">
<view class="popularity-container-top">
<image class="fire-icon" src="/static/icon/crystal.png" mode="aspectFit">
</image>
<text class="popularity-score-top">{{
formatPopularityScore(item.popularityScore)
}}</text>
</view>
<view v-if="!isCurrentUser(item.userId)" class="visit-button"
@tap="handleVisit(item.userId, item.nickname)">
<text class="visit-btn-text">拜访</text>
</view>
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view v-if="isLoadingMore" class="loading-more-container">
<view class="loading-spinner-small"></view>
<text class="loading-more-text">加载中...</text>
</view>
<!-- 没有更多数据提示 -->
<view v-if="!isLoadingMore && hasNoMoreData && rankingData.length > 0" class="no-more-data">
<text class="no-more-text">没有更多了</text>
</view>
<!-- 底部占位,防止内容被当前用户栏遮挡 -->
<view class="bottom-spacer"></view>
</scroll-view>
<!-- 当前用户栏 - 固定在底部 -->
<view v-if="!isLoadingData" class="current-user-bar">
<view class="current-user-content">
<!-- 用户头像 -->
<image class="current-user-avatar" :src="currentUserInfo.avatar || '/static/avatar/1.jpeg'"
mode="aspectFill" @error="handleCurrentUserAvatarError"></image>
<!-- 用户信息 -->
<view class="current-user-info">
<view class="current-user-score">
<image class="flame-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
<text class="score-text">{{ formatPopularityScore(currentUserInfo.popularityScore)
}}</text>
</view>
</view>
<!-- 排名状态 -->
<view class="current-user-rank">
<text class="rank-text">{{ formatCurrentUserRank(currentUserInfo.rank) }}</text>
</view>
</view>
</view>
</view>
</view>
</transition>
</view>
</template>
<script setup>
import { ref, computed, watch, onUnmounted } from 'vue';
import { getActivityRankingApi } from '@/utils/api.js';
const props = defineProps({
visible: {
type: Boolean,
default: false
},
activityId: {
type: [String, Number],
required: true
},
starId: {
type: [String, Number],
default: null
},
activityTitle: {
type: String,
default: '活动排名'
},
currentUser: {
type: Object,
default: null
}
});
const emit = defineEmits(['update:visible', 'visit', 'view-profile', 'view-artwork']);
// 数据状态管理
const rankingData = ref([]);
const isLoadingData = ref(false);
const dataLoadError = ref(null);
// 分页状态管理
const currentPage = ref(1);
const isLoadingMore = ref(false);
const hasNoMoreData = ref(false);
const isRefreshing = ref(false);
const PAGE_SIZE = 10;
// 当前用户数据
const currentUserInfo = ref({
userId: 'currentUser',
avatar: '/static/avatar/1.jpeg',
nickname: '我',
popularityScore: 0,
rank: null
});
// 下拉关闭状态管理
const dragOffset = ref(0);
const startY = ref(0);
const isDragging = ref(false);
const DRAG_THRESHOLD = 150;
// 加载锁,防止重复触发
let _scrollLoadLocked = false;
// TOP3 用户数据
const top3Users = computed(() => {
return rankingData.value.filter(user => user.rank >= 1 && user.rank <= 3);
});
// 列表用户数据第4名及以后
const listUsers = computed(() => {
return rankingData.value.filter(user => user.rank >= 4);
});
// 判断是否为当前用户
const isCurrentUser = (userId) => {
return userId === currentUserInfo.value.userId;
};
// 转换活动排名数据
const transformActivityRankingData = (apiResponse) => {
if (!apiResponse || !apiResponse.data) {
return [];
}
const { items } = apiResponse.data;
if (!Array.isArray(items)) {
return [];
}
return items.map(item => ({
rank: item.rank,
userId: String(item.user_id),
avatar: item.avatar_url || '/static/avatar/1.jpeg',
nickname: item.nickname || '未知用户',
popularityScore: item.total_contribution || 0,
artworkImage: '',
artworkId: ''
}));
};
// 转换当前用户数据
const transformMyActivityContribution = (myContribution) => {
return {
userId: 'currentUser',
avatar: myContribution?.avatar_url || '/static/avatar/1.jpeg',
popularityScore: myContribution?.total_contribution || 0,
rank: (myContribution?.rank > 0) ? myContribution.rank : null
};
};
// 加载排名数据
const loadRankingData = async (page = 1, isRefreshAction = false) => {
try {
if (page === 1 && !isRefreshAction) {
isLoadingData.value = true;
} else if (page > 1) {
isLoadingMore.value = true;
}
dataLoadError.value = null;
const starId = props.starId || uni.getStorageSync('star_id');
const apiResponse = await getActivityRankingApi(
props.activityId,
starId,
page,
PAGE_SIZE
);
if (apiResponse && apiResponse.code === 200 && apiResponse.data) {
const transformedData = transformActivityRankingData(apiResponse);
if (page === 1) {
rankingData.value = transformedData;
} else {
rankingData.value.push(...transformedData);
}
hasNoMoreData.value = transformedData.length < PAGE_SIZE;
if (page === 1 && apiResponse.data.my_contribution) {
const myContributionData = transformMyActivityContribution(apiResponse.data.my_contribution);
if (!props.currentUser) {
currentUserInfo.value = myContributionData;
}
}
return true;
} else {
throw new Error(apiResponse?.message || '获取排行榜数据失败');
}
} catch (error) {
console.error('Failed to fetch ranking data:', error);
dataLoadError.value = error.message;
uni.showToast({
title: '加载失败',
icon: 'none',
duration: 2000
});
return false;
} finally {
if (page === 1 && !isRefreshAction) {
isLoadingData.value = false;
} else if (page > 1) {
isLoadingMore.value = false;
}
}
};
// 处理滚动到底部
const handleScrollToLower = async () => {
if (_scrollLoadLocked || isLoadingMore.value || hasNoMoreData.value || isLoadingData.value) {
return;
}
_scrollLoadLocked = true;
try {
await loadRankingData(currentPage.value + 1);
currentPage.value += 1;
} finally {
_scrollLoadLocked = false;
}
};
// 处理下拉刷新
const handleRefresh = async () => {
if (isRefreshing.value) {
return;
}
isRefreshing.value = true;
try {
currentPage.value = 1;
hasNoMoreData.value = false;
const success = await loadRankingData(1, true);
if (success) {
uni.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500
});
} else {
uni.showToast({
title: '刷新失败',
icon: 'none',
duration: 2000
});
}
} catch (error) {
console.error('Refresh failed:', error);
} finally {
isRefreshing.value = false;
}
};
// 格式化人气值显示
const formatPopularityScore = (score) => {
if (typeof score !== 'number' || isNaN(score) || score < 0) {
return '0';
}
if (score >= 1000000) {
return (score / 1000000).toFixed(1) + 'M';
} else if (score >= 1000) {
return (score / 1000).toFixed(1) + 'K';
}
return score.toString();
};
// 格式化当前用户排名显示
const formatCurrentUserRank = (rank) => {
if (rank === null || rank === undefined || typeof rank !== 'number' || isNaN(rank) || rank <= 0) {
return '未上榜';
}
return `${rank}`;
};
// 处理拜访按钮点击
const handleVisit = (userId, nickname) => {
emit('visit', { userId, nickname });
};
// 处理查看个人信息
const handleViewProfile = (userId) => {
emit('view-profile', { userId });
};
// 处理作品点击
const handleArtworkClick = () => {
// 预留,后续可扩展查看作品详情
};
// 处理关闭
const handleClose = () => {
emit('update:visible', false);
};
// 处理遮罩层点击
const handleMaskClick = () => {
handleClose();
};
// 处理触摸开始
const handleTouchStart = (e) => {
const touch = e.touches[0];
startY.value = touch.clientY;
isDragging.value = true;
};
// 处理触摸移动
const handleTouchMove = (e) => {
if (!isDragging.value) {
return;
}
const touch = e.touches[0];
const deltaY = touch.clientY - startY.value;
if (deltaY > 0) {
dragOffset.value = deltaY;
}
};
// 处理触摸结束
const handleTouchEnd = () => {
if (!isDragging.value) {
return;
}
isDragging.value = false;
if (dragOffset.value > DRAG_THRESHOLD) {
handleClose();
}
dragOffset.value = 0;
};
// 处理当前用户头像加载失败
const handleCurrentUserAvatarError = (e) => {
e.target.src = '/static/avatar/1.jpeg';
};
// 处理排名图标加载失败
const handleRankIconError = (e) => {
e.target.src = '/static/rank/charm-rank-icon1.png';
};
// 处理头像加载失败
const handleAvatarError = (e) => {
e.target.src = '/static/avatar/1.jpeg';
};
// 处理列表项头像加载失败
const handleItemAvatarError = (e) => {
e.target.src = '/static/avatar/1.jpeg';
};
// 监听可见性变化
watch(() => props.visible, async (newVisible, oldVisible) => {
if (newVisible && !oldVisible) {
rankingData.value = [];
currentPage.value = 1;
hasNoMoreData.value = false;
isLoadingData.value = true;
dataLoadError.value = null;
if (props.currentUser) {
currentUserInfo.value = { ...props.currentUser };
} else {
currentUserInfo.value = {
userId: 'currentUser',
avatar: '/static/avatar/1.jpeg',
nickname: '我',
popularityScore: 0,
rank: null
};
}
await loadRankingData(1);
}
}, { immediate: false });
// 监听传入的 currentUser 变化
watch(() => props.currentUser, (newUser) => {
if (newUser) {
currentUserInfo.value = { ...newUser };
}
}, { immediate: true });
onUnmounted(() => {
_scrollLoadLocked = false;
});
</script>
<style scoped>
/* 动画效果 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-up-enter-from,
.slide-up-leave-to {
opacity: 0;
transform: translateY(100%);
}
.modal-wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 9999;
display: flex;
align-items: flex-end;
justify-content: center;
box-sizing: border-box;
pointer-events: auto;
}
.modal-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 5;
pointer-events: auto;
cursor: pointer;
}
.modal-container {
position: absolute;
width: 100%;
height: 80%;
top: 50%;
transform: translateY(-50%);
border-radius: 48rpx 48rpx 0 0;
overflow: hidden;
z-index: 10;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.modal-container.dragging {
transition: none;
}
.modal-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url('/static/rank/activity-support-icon/shengrihuipaihangbang.png');
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
z-index: 0;
}
.modal-background::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
}
.modal-content {
position: relative;
z-index: 2;
padding: 50rpx 10rpx;
display: flex;
flex-direction: column;
height: 100%;
align-items: center;
}
/* 头部区域 */
.header-section {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
padding: 0 14rpx;
position: relative;
top: 48rpx;
width: 100%;
}
.activity-title-img {
flex: 1;
max-width: 400rpx;
height: 60rpx;
object-fit: contain;
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.4));
pointer-events: none;
opacity: 1;
transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
will-change: opacity, transform;
transform: scale(2.3);
}
.close-button {
position: absolute;
left: 64rpx;
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
}
.close-icon-img {
width: 80rpx;
height: 80rpx;
}
/* 滚动内容区域 */
.scrollable-content {
flex: 1;
overflow-y: auto;
padding-bottom: 180rpx;
padding-top: 16rpx;
}
/* 加载中容器 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400rpx;
padding: 40rpx;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
border-top-color: #FFFFFF;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
font-family: 'yt', sans-serif;
}
/* 错误容器 */
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400rpx;
padding: 40rpx;
}
.error-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
font-family: 'yt', sans-serif;
text-align: center;
margin-bottom: 30rpx;
}
.retry-button {
padding: 16rpx 40rpx;
background: linear-gradient(135deg, rgba(255, 107, 157, 0.9) 0%, rgba(255, 177, 153, 0.9) 100%);
border-radius: 40rpx;
border: none;
color: #FFFFFF;
font-size: 28rpx;
font-family: 'yt', sans-serif;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 157, 0.3);
}
/* TOP3 展示区域 */
.top3-section {
display: flex;
justify-content: space-between;
gap: 60rpx;
margin-bottom: 84rpx;
padding: 0 48rpx;
}
.top3-card-item {
display: flex;
flex-direction: column;
align-items: center;
width: 25%;
position: relative;
}
/* 人气值容器(在头像上方) */
.popularity-container-top {
display: flex;
align-items: center;
justify-content: center;
padding: 4rpx 20rpx 4rpx 64rpx;
background: linear-gradient(to bottom right,
#F0E4B1 0%,
#F08399 50%,
#B94E73 100%);
border-radius: 24rpx;
box-shadow:
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
}
.popularity-container-top .fire-icon {
width: 80rpx;
height: 80rpx;
position: absolute;
left: -8rpx;
transform: rotate(-15deg);
}
.popularity-container-top .popularity-score-top {
font-size: 24rpx;
font-weight: bold;
color: #FFFFFF;
text-shadow:
0 2rpx 4rpx rgba(0, 0, 0, 0.5),
0 1rpx 2rpx rgba(0, 0, 0, 0.3);
font-family: 'yt', sans-serif;
margin-left: 16rpx;
}
/* 头像容器(居中显示,大尺寸) */
.avatar-container {
width: 180rpx;
height: 180rpx;
margin-bottom: 64rpx;
margin-top: 64rpx;
position: relative;
}
.user-avatar {
width: 180rpx;
height: 180rpx;
border-radius: 50%;
border: 5rpx solid rgba(255, 255, 255, 0.8);
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
}
/* 排名图标在头像下方 */
.rank-badge-bottom {
position: absolute;
bottom: -40rpx;
left: 50%;
transform: translateX(-50%) scale(2.5);
z-index: 10;
width: 80rpx;
height: 80rpx;
pointer-events: none;
}
.rank-badge-bottom2 {
bottom: -50rpx;
transform: translateX(-50%) scale(3);
}
.rank-icon {
width: 100%;
height: 100%;
filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3));
}
/* 昵称容器(在头像下方) */
.nickname-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 0 12rpx;
margin-bottom: 16rpx;
}
.user-nickname {
font-size: 12rpx;
margin-left: 10rpx;
color: #FFFFFF;
text-align: center;
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
text-shadow:
0 3rpx 6rpx rgba(0, 0, 0, 0.4),
0 1rpx 3rpx rgba(0, 0, 0, 0.3);
font-family: 'yt', sans-serif;
}
.user-nickname-name {
font-size: 24rpx;
margin-left: 0.5em;
color: #FFA500;
text-shadow:
0 3rpx 6rpx rgba(0, 0, 0, 0.4),
0 1rpx 3rpx rgba(0, 0, 0, 0.3);
font-family: 'yt', sans-serif;
}
.visit-btn {
margin-top: 12rpx;
padding: 8rpx 24rpx;
background: linear-gradient(135deg, rgba(255, 107, 157, 0.9), rgba(255, 177, 153, 0.9));
border-radius: 20rpx;
}
.visit-text {
font-size: 22rpx;
color: #FFFFFF;
font-family: 'yt', sans-serif;
}
/* 空数据提示 */
.empty-data {
display: flex;
align-items: center;
justify-content: center;
min-height: 320rpx;
background: rgba(255, 255, 255, 0.08);
border-radius: 24rpx;
border: 1rpx solid rgba(255, 255, 255, 0.15);
margin: 0 15rpx;
}
.empty-text {
font-size: 30rpx;
color: rgba(255, 255, 255, 0.7);
font-family: 'yt', sans-serif;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
}
/* 排名列表区域 */
.ranking-list-section {
margin-top: 20rpx;
padding: 0 48rpx;
}
.ranking-list-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
gap: 20rpx;
}
.rank-number {
min-width: 64rpx;
display: flex;
align-items: center;
justify-content: center;
}
.rank-text {
font-size: 32rpx;
font-weight: bold;
color: #FFFFFF;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
font-family: 'yt', sans-serif;
}
.left-section {
display: flex;
align-items: center;
gap: 16rpx;
flex: 1;
}
.user-avatar-small {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
border: 3rpx solid rgba(255, 255, 255, 0.7);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
}
.item-nickname {
font-size: 28rpx;
color: #FFFFFF;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
font-family: 'yt', sans-serif;
max-width: 200rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.right-section {
display: flex;
align-items: center;
gap: 16rpx;
}
.popularity-container {
display: flex;
align-items: center;
gap: 4rpx;
}
.fire-icon-small {
width: 28rpx;
height: 28rpx;
}
.item-score {
font-size: 24rpx;
color: #FFFFFF;
font-weight: bold;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
font-family: 'yt', sans-serif;
}
.visit-button {
padding: 12rpx 24rpx;
background: linear-gradient(135deg, rgba(255, 107, 157, 0.9), rgba(255, 177, 153, 0.9));
border-radius: 24rpx;
}
.visit-btn-text {
font-size: 24rpx;
color: #FFFFFF;
font-family: 'yt', sans-serif;
}
/* 加载更多容器 */
.loading-more-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 30rpx 20rpx;
gap: 12rpx;
}
.loading-spinner-small {
width: 40rpx;
height: 40rpx;
border: 3rpx solid rgba(255, 255, 255, 0.3);
border-top-color: #FFFFFF;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.loading-more-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.7);
font-family: 'yt', sans-serif;
}
/* 没有更多数据提示 */
.no-more-data {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 20rpx;
}
.no-more-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
font-family: 'yt', sans-serif;
}
/* 底部占位 */
.bottom-spacer {
height: 20rpx;
}
/* 当前用户栏 - 固定在底部 */
.current-user-bar {
position: absolute;
bottom: 152rpx;
left: 84rpx;
right: 84rpx;
padding: 24rpx;
background: linear-gradient(135deg, rgba(255, 107, 157, 0.9) 0%, rgba(255, 177, 153, 0.9) 100%);
border-radius: 32rpx;
box-shadow: 0 -4rpx 16rpx rgba(255, 107, 157, 0.35);
border: 2rpx solid rgba(255, 255, 255, 0.3);
z-index: 10;
}
.current-user-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, transparent 50%, rgba(255, 255, 255, 0.1) 100%);
border-radius: 30rpx;
pointer-events: none;
}
.current-user-content {
display: flex;
align-items: center;
position: relative;
z-index: 1;
height: 64rpx;
}
.current-user-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
border: 4rpx solid rgba(255, 255, 255, 0.9);
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.3), 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
}
.current-user-info {
flex: 1;
display: flex;
margin-left: 34rpx;
margin-right: 34rpx;
flex-direction: column;
}
.current-user-score {
display: flex;
align-items: center;
justify-content: center;
}
.flame-icon {
width: 80rpx;
height: 80rpx;
left: -8rpx;
transform: rotate(-15deg);
/* filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3)); */
}
.score-text {
font-size: 26rpx;
color: #fff;
font-family: 'yt', sans-serif;
font-weight: 500;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.current-user-rank {
display: flex;
align-items: center;
padding: 16rpx 28rpx;
background-image: url('@/static/rank/activity-support-icon/beijingkuang.png');
background-size: cover;
background-position: center;
border-radius: 40rpx;
}
.rank-text {
font-size: 30rpx;
color: #FFFFFF;
font-family: 'yt', sans-serif;
text-shadow: 0 3rpx 6rpx rgba(0, 0, 0, 0.4), 0 1rpx 3rpx rgba(0, 0, 0, 0.3);
}
/* 响应式布局 */
@media screen and (max-width: 750rpx) {
.modal-container {
border-radius: 40rpx 40rpx 0 0;
}
.modal-content {
padding: 40rpx 30rpx;
}
.header-section {
margin-bottom: 32rpx;
}
.activity-title-img {
width: 280rpx;
height: 75rpx;
}
.scrollable-content {
padding-bottom: 160rpx;
}
.top3-section {
gap: 12rpx;
padding: 0 8rpx;
}
.ranking-list-section {
padding: 0 8rpx;
}
/* TOP3 头像区域 */
.avatar-container {
width: 160rpx;
height: 160rpx;
}
.user-avatar {
width: 160rpx;
height: 160rpx;
}
.rank-badge-bottom {
width: 72rpx;
height: 72rpx;
bottom: -18rpx;
}
.current-user-bar {
bottom: 100rpx;
left: 15rpx;
right: 15rpx;
padding: 20rpx;
border-radius: 28rpx;
}
}
@media screen and (max-width: 600rpx) {
.modal-container {
border-radius: 36rpx 36rpx 0 0;
max-height: 85vh;
}
.modal-content {
padding: 36rpx 24rpx;
}
.header-section {
margin-bottom: 28rpx;
padding: 0 24rpx;
}
.activity-title-img {
width: 260rpx;
height: 70rpx;
}
.scrollable-content {
padding-bottom: 140rpx;
}
.top3-section {
gap: 8rpx;
margin-bottom: 32rpx;
padding: 0 4rpx;
}
/* TOP3 头像区域 */
.avatar-container {
width: 140rpx;
height: 140rpx;
margin-bottom: 14rpx;
}
.user-avatar {
width: 140rpx;
height: 140rpx;
}
.rank-badge-bottom {
width: 68rpx;
height: 68rpx;
bottom: -16rpx;
}
.popularity-container-top {
margin-top: 20rpx;
margin-bottom: 14rpx;
gap: 6rpx;
}
.ranking-list-section {
padding: 0 4rpx;
}
.current-user-bar {
bottom: 80rpx;
left: 12rpx;
right: 12rpx;
padding: 18rpx 24rpx;
border-radius: 24rpx;
}
.current-user-content {
gap: 16rpx;
}
.current-user-avatar {
width: 72rpx;
height: 72rpx;
}
.current-user-info {
margin-left: 20rpx;
margin-right: 20rpx;
}
.score-text {
font-size: 22rpx;
}
.rank-text {
font-size: 26rpx;
}
}
@media screen and (max-width: 480rpx) {
.modal-wrapper {
align-items: flex-end;
}
.modal-container {
border-radius: 32rpx 32rpx 0 0;
max-height: 80vh;
}
.modal-content {
padding: 32rpx 20rpx;
}
.header-section {
margin-bottom: 24rpx;
}
.close-button {
left: 16rpx;
width: 48rpx;
height: 48rpx;
}
.close-icon-img {
width: 48rpx;
height: 48rpx;
}
.activity-title-img {
width: 240rpx;
height: 65rpx;
}
.scrollable-content {
padding-bottom: 120rpx;
}
.top3-section {
gap: 6rpx;
margin-bottom: 28rpx;
padding: 0 2rpx;
}
/* TOP3 头像区域 */
.avatar-container {
width: 120rpx;
height: 120rpx;
margin-bottom: 12rpx;
}
.user-avatar {
width: 120rpx;
height: 120rpx;
border-width: 3rpx;
}
.rank-badge-bottom {
width: 64rpx;
height: 64rpx;
bottom: -14rpx;
}
.popularity-container-top {
margin-top: 16rpx;
margin-bottom: 12rpx;
gap: 5rpx;
}
.user-nickname {
font-size: 22rpx;
}
.fire-icon {
width: 24rpx;
height: 24rpx;
}
.popularity-score-top {
font-size: 24rpx;
}
.ranking-list-section {
padding: 0 2rpx;
}
.current-user-bar {
bottom: 60rpx;
left: 10rpx;
right: 10rpx;
padding: 16rpx 20rpx;
border-radius: 20rpx;
}
.current-user-content {
gap: 12rpx;
}
.current-user-avatar {
width: 64rpx;
height: 64rpx;
border-width: 3rpx;
}
.flame-icon {
width: 22rpx;
height: 22rpx;
}
.score-text {
font-size: 20rpx;
}
.current-user-rank {
padding: 10rpx 16rpx;
}
.rank-text {
font-size: 24rpx;
}
}
/* 触摸优化 */
@media (hover: none) and (pointer: coarse) {
.close-button {
min-width: 88rpx;
min-height: 88rpx;
}
}
</style>