500 lines
11 KiB
Vue
500 lines
11 KiB
Vue
<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-filter,GPU 密集型操作 */
|
||
}
|
||
|
||
.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-filter,GPU 密集型操作 */
|
||
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-filter,GPU 密集型操作 */
|
||
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>
|