1386 lines
28 KiB
Vue
1386 lines
28 KiB
Vue
<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> |