topfans/frontend/pages/components/RankingListItem.vue
2026-04-07 23:08:49 +08:00

500 lines
11 KiB
Vue
Raw Permalink 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 class="ranking-list-item">
<!-- 排名编号 -->
<view class="rank-number">
<text class="rank-text">{{ rank }}</text>
</view>
<!-- 左侧图片区域作品图片 + 头像 + 昵称 -->
<view :class="['left-section', { 'no-artwork': !artworkImage }]">
<!-- 图片容器 -->
<view class="image-container" :class="{ 'no-artwork': !artworkImage }">
<!-- 作品图片可选 - 使用原生懒加载 -->
<image
v-if="artworkImage"
:src="artworkImage"
mode="aspectFill"
class="artwork-image"
lazy-load
@error="handleArtworkError"
@tap="handleArtworkClick"
/>
</view>
<view class="user-box" :class="{ 'avatar-standalone': !artworkImage }">
<!-- 头像 - 叠加在作品图片右下角,或单独显示 - 使用原生懒加载 -->
<image
:class="['user-avatar', { 'avatar-standalone': !artworkImage }]"
:src="avatar || '/static/avatar/1.jpeg'"
mode="aspectFill"
lazy-load
@error="handleAvatarError"
@tap="handleAvatarClick"
/>
<!-- 昵称 - 在头像下方(有作品时)或右下角(无作品时) -->
<text :class="['user-nickname', { 'nickname-overlay': !artworkImage }]">
{{'用户 :'}}<text class="user-nickname-name">{{ nickname || '未知用户' }}</text>
</text>
</view>
</view>
<!-- 右侧:人气值和拜访按钮 -->
<view class="right-section">
<!-- 人气值 -->
<view class="popularity-container">
<image class="fire-icon" src="/static/rank/spark.png" mode="aspectFit" lazy-load></image>
<text class="popularity-score">{{ formattedScore }}</text>
</view>
<!-- 拜访按钮 -->
<view
v-if="showVisitButton"
class="visit-button"
@tap="handleVisit"
>
<image
class="visit-icon"
src="/static/icon/visit-house.png"
mode="aspectFit"
lazy-load
></image>
<text class="visit-text">拜访小屋</text>
</view>
</view>
<!-- 操作菜单 -->
<view v-if="showActionMenu" class="action-menu-overlay" @tap="closeActionMenu">
<view class="action-menu" @tap.stop>
<view
v-if="!isCurrentUser"
class="menu-item"
@tap="handleMenuVisit"
>
<image class="menu-icon" src="/static/icon/visit-house.png" mode="aspectFit" lazy-load></image>
<text class="menu-text">拜访小屋</text>
</view>
<view class="menu-item" @tap="handleMenuViewProfile">
<image class="menu-icon" src="/static/icon/character.png" mode="aspectFit" lazy-load></image>
<text class="menu-text">查看个人信息</text>
</view>
</view>
</view>
<view class="rank-decoration"></view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
const props = defineProps({
rank: {
type: Number,
required: true
},
userId: {
type: String,
required: true
},
avatar: {
type: String,
required: true
},
nickname: {
type: String,
required: true
},
popularityScore: {
type: Number,
required: true
},
artworkImage: {
type: String,
default: ''
},
artworkId: {
type: String,
default: ''
},
showVisitButton: {
type: Boolean,
default: true
},
isCurrentUser: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['visit', 'avatar-click', 'artwork-click', 'view-profile']);
// 操作菜单显示状态
const showActionMenu = ref(false);
// 格式化人气值(超过百万显示为 M超过千显示为 K
const formattedScore = computed(() => {
const score = props.popularityScore;
// 处理无效数据
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 handleVisit = () => {
emit('visit');
};
// 处理头像点击 - 显示操作菜单
const handleAvatarClick = () => {
showActionMenu.value = true;
emit('avatar-click', {
userId: props.userId,
isCurrentUser: props.isCurrentUser
});
};
// 关闭操作菜单
const closeActionMenu = () => {
showActionMenu.value = false;
};
// 处理菜单中的拜访选项
const handleMenuVisit = () => {
closeActionMenu();
emit('visit');
};
// 处理菜单中的查看个人信息选项
const handleMenuViewProfile = () => {
closeActionMenu();
emit('view-profile', props.userId);
};
// 处理作品图片点击
const handleArtworkClick = () => {
if (props.artworkId && props.artworkImage) {
emit('artwork-click', {
artworkId: props.artworkId,
userId: props.userId
});
}
};
// 处理头像加载失败
const handleAvatarError = (e) => {
// 设置默认头像 - 使用现有的头像作为备用
e.target.src = '/static/avatar/1.jpeg';
};
// 处理作品图片加载失败
const handleArtworkError = (e) => {
// 设置默认占位图
e.target.src = '/static/nft/collection.png';
};
</script>
<style scoped>
.ranking-list-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32rpx;
gap: 20rpx;
position:relative;
}
.rank-decoration {
position: absolute;
bottom: -24rpx;
left: 32rpx;
width: 92%;
height: 8rpx;
background: linear-gradient(90deg, #ff9a9e 0%, #fecfef 50%, #ff9a9e 100%);
border-radius: 4rpx;
pointer-events: none; /* 防止遮挡点击事件 */
}
/* 排名编号 */
.rank-number {
min-width: 48rpx;
display: flex;
align-items: center;
padding: 8rpx;
}
.rank-text {
font-size: 48rpx;
font-weight: bold;
color: #FFFFFF;
text-shadow:
0 3rpx 6rpx rgba(0, 0, 0, 0.4),
0 1rpx 3rpx rgba(0, 0, 0, 0.3);
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
}
/* 左侧区域 - 图片和昵称 */
.left-section {
display: flex;
align-items: center;
gap: 8rpx;
flex-shrink: 0;
}
/* 当没有作品时,左侧区域改为横向布局 */
.left-section.no-artwork {
flex-direction: row;
align-items: center;
gap: 12rpx;
}
/* 图片容器 - 作品图片和头像叠加 */
.image-container {
position: relative;
width: 120rpx;
height: 120rpx;
flex-shrink: 0;
}
.image-container.no-artwork{
display:none;
}
/* 作品图片 */
.artwork-image {
width: 96rpx;
height: 120rpx;
border-radius: 16rpx;
border: 3rpx solid rgba(255, 255, 255, 0.6);
/* 优化2层阴影简化为1层 */
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.25);
/* 优化:只动画 transform避免 transition: all */
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.artwork-image:active {
transform: scale(0.95);
}
.user-box{
display: flex;
/* gap: 16rpx; */
flex-direction: column;
}
.user-box.avatar-standalone{
flex-direction: row;
align-items: flex-end;
}
/* 头像 - 叠加在作品图片右下角 */
.user-avatar {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 3rpx solid rgba(255, 255, 255, 0.9);
/* 优化2层阴影简化为1层 */
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
/* 优化:只动画 transform避免 transition: all */
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
background: #FFFFFF;
margin-top: 8rpx;
}
.user-avatar:active {
transform: scale(0.95);
}
/* 当没有作品图片时,头像单独显示并居中 */
.user-avatar.avatar-standalone {
width: 120rpx;
height: 120rpx;
}
.user-nickname {
font-size: 14rpx;
margin-top: 16rpx;
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: 'ZaoZiGongFangJianHei-1', sans-serif;
}
.user-nickname-name{
font-size: 14rpx;
margin-left: 0.5em; /* 缩进2格 */
color: #FFA500; /* 橙色,接近图中 LISA 的颜色 */
text-shadow:
0 3rpx 6rpx rgba(0, 0, 0, 0.4),
0 1rpx 3rpx rgba(0, 0, 0, 0.3);
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
}
.user-nickname.nickname-overlay{
margin-bottom:16rpx
}
/* 右侧区域 - 人气值和拜访按钮 */
.right-section {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4rpx;
min-width: 0;
margin-right: 32rpx;
}
.popularity-container {
display: flex;
align-items: center;
gap: 20rpx;
padding: 8rpx;
/* 优化5色渐变简化为2色 */
background: linear-gradient(135deg, #FF6B9D 0%, #FFB199 100%);
border-radius: 16rpx;
/* 优化3层阴影简化为1层 */
box-shadow: 0 -4rpx 16rpx rgba(255, 107, 157, 0.35);
border: 2rpx solid rgba(255, 255, 255, 0.3);
/* 优化:移除 backdrop-filterGPU 密集型操作 */
}
.fire-icon {
width: 32rpx;
height: 40rpx;
}
.popularity-score {
font-size: 24rpx;
color: #FFFFFF;
text-shadow:
0 2rpx 4rpx rgba(0, 0, 0, 0.4),
0 1rpx 2rpx rgba(0, 0, 0, 0.3);
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
}
/* 拜访按钮 - 只显示图标 */
.visit-button {
display: flex;
align-items: center;
justify-content: center;
width: 80rpx;
height: 80rpx;
padding: 8rpx;
flex-direction: column;
}
.visit-icon {
width: 80rpx;
height: 80rpx;
transform: scale(1.2);
}
.visit-text{
color: #FFFFFF;
font-size:16rpx;
}
/* 操作菜单 */
.action-menu-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
/* 优化:移除 backdrop-filterGPU 密集型操作 */
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.action-menu {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%);
/* 优化:移除 backdrop-filterGPU 密集型操作 */
border-radius: 24rpx;
padding: 20rpx;
min-width: 400rpx;
/* 优化2层阴影简化为1层 */
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.3);
border: 2rpx solid rgba(255, 255, 255, 0.8);
animation: scaleIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9) translateY(20rpx);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.menu-item {
display: flex;
align-items: center;
gap: 20rpx;
padding: 28rpx 32rpx;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.5);
margin-bottom: 12rpx;
/* 优化:只动画必要属性,避免 transition: all */
transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
border: 1rpx solid rgba(123, 104, 238, 0.2);
}
.menu-item:last-child {
margin-bottom: 0;
}
.menu-item:active {
background: linear-gradient(135deg, rgba(123, 104, 238, 0.3) 0%, rgba(123, 104, 238, 0.2) 100%);
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(123, 104, 238, 0.3);
}
.menu-icon {
width: 40rpx;
height: 40rpx;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.2));
}
.menu-text {
font-size: 32rpx;
font-weight: 600;
color: #333333;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
}
</style>