topfans/frontend/pages/support-activity/components/TopRanking.vue
2026-06-17 23:12:59 +08:00

241 lines
5.6 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 class="top-ranking" @tap="handleOpenRanking">
<!-- TOP3 头像组对应 Figma 110-671 -->
<view v-if="top3List.length > 0" class="top3-card">
<view v-for="item in top3List" :key="item.userId" class="top3-item">
<image
class="top3-avatar"
:src="item.avatar || '/static/avatar/1.jpeg'"
mode="aspectFill"
@error="handleAvatarError"
/>
<image
class="top3-medal"
:src="`/static/rank/rank-icon${item.rank}.png`"
mode="aspectFit"
/>
</view>
</view>
<!-- 我的排名条(对应 Figma 110-662 -->
<view v-if="myInfo.rank" class="my-rank-card">
<view class="my-rank-row">
<image
class="my-avatar"
:src="myInfo.avatar || '/static/avatar/1.jpeg'"
mode="aspectFill"
@error="handleAvatarError"
/>
<text class="my-rank-label">当前排名</text>
<text class="my-rank-number">{{ myInfo.rank }}</text>
<image
class="my-rank-icon"
src="/static/rank/lsph.png"
mode="aspectFit"
/>
</view>
<view class="my-rank-row">
<text class="my-rank-label">距离上一名贡献值</text>
<text class="my-rank-number">{{ myInfo.gapToPrev }}</text>
<image
class="my-rank-icon"
src="/static/icon/crystal.png"
mode="aspectFit"
/>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { getActivityRankingApi } from "@/utils/api.js";
const props = defineProps({
activityId: {
type: [String, Number],
required: true,
},
starId: {
type: [String, Number],
default: null,
},
});
const emit = defineEmits(["open-ranking"]);
// TOP3 数据
const top3List = ref([]);
// 我的排名数据
const myInfo = ref({
rank: null,
avatar: "",
gapToPrev: 0,
});
// 头像加载失败兜底
function handleAvatarError(e) {
e.target.src = "/static/avatar/1.jpeg";
}
// 打开排行榜弹窗
function handleOpenRanking() {
emit("open-ranking");
}
// 计算「距离上一名贡献值」:取 (第 N-1 名的 total_contribution - 我的 total_contribution)
function calcGapToPrev(myContribution, allItems) {
if (!myContribution || !Array.isArray(allItems)) return 0;
const myRank = myContribution.rank;
if (!myRank || myRank <= 1) return 0; // 第 1 名没有上一名
const prev = allItems.find((u) => u.rank === myRank - 1);
if (!prev) return 0;
const gap =
(prev.total_contribution || 0) - (myContribution.total_contribution || 0);
return gap > 0 ? gap : 0;
}
// 加载排行数据
async function loadRanking() {
if (!props.activityId) return;
try {
const sid = props.starId || uni.getStorageSync("star_id");
const res = await getActivityRankingApi(props.activityId, sid, 1, 3);
if (res && res.code === 0 && res.data) {
const items = Array.isArray(res.data.items) ? res.data.items : [];
// TOP3
top3List.value = items
.filter((u) => u.rank >= 1 && u.rank <= 3)
.sort((a, b) => a.rank - b.rank)
.map((u) => ({
rank: u.rank,
userId: String(u.user_id),
avatar: u.avatar_url || "/static/avatar/1.jpeg",
}));
// 我的信息
const my = res.data.my_contribution;
if (my && my.rank) {
myInfo.value = {
rank: my.rank,
avatar: my.avatar_url || "/static/avatar/1.jpeg",
gapToPrev: calcGapToPrev(my, items),
};
} else {
myInfo.value = { rank: null, avatar: "", gapToPrev: 0 };
}
}
} catch (err) {
console.error("[TopRanking] 加载排行失败", err);
}
}
onMounted(() => {
loadRanking();
});
defineExpose({
refresh: loadRanking,
});
</script>
<style scoped>
.top-ranking {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 12rpx;
}
/* TOP3 卡片(对应 110-671圆角深红半透明 + 3 个头像 + 奖牌) */
.top3-card {
display: flex;
align-items: center;
gap: 8rpx;
padding: 6rpx 12rpx;
background: rgba(42, 17, 17, 0.3);
border-radius: 22rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.15);
}
.top3-item {
position: relative;
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
}
.top3-avatar {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
border: 2rpx solid rgba(255, 255, 255, 0.6);
background: #fff;
}
.top3-medal {
position: absolute;
bottom: -10rpx;
left: 50%;
transform: translateX(-50%);
width: 32rpx;
height: 32rpx;
}
/* 我的排名卡片(对应 110-662圆角深红半透明 + 头像 + 文案 + 数字 + 图标) */
.my-rank-card {
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 8rpx 14rpx;
background: rgba(42, 17, 17, 0.3);
border-radius: 22rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.15);
min-width: 208rpx;
}
.my-rank-row {
display: flex;
align-items: center;
gap: 8rpx;
}
.my-avatar {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid rgba(255, 255, 255, 0.6);
box-shadow: 1px 1px 4px 0 rgba(181, 7, 7, 0.54);
background: #fff;
margin-right: 4rpx;
}
.my-rank-label {
font-size: 20rpx;
color: #fff;
font-weight: bold;
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.45);
flex: 1;
white-space: nowrap;
text-align: right;
}
.my-rank-number {
font-size: 28rpx;
color: #fffabd;
font-weight: bold;
font-family: "yt", "Baloo Bhai", sans-serif;
text-shadow: -1px 1px 4px rgba(206, 9, 9, 0.45);
margin: 0 8rpx;
}
.my-rank-icon {
width: 40rpx;
height: 32rpx;
transform: rotate(-10deg);
}
</style>