Files
shoot-miniprograms/src/pages/ranking.vue
2025-07-14 15:13:10 +08:00

559 lines
14 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.

<script setup>
import { ref, onMounted } from "vue";
import { onHide } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import Avatar from "@/components/Avatar.vue";
import { getRankListAPI, isGamingAPI } from "@/apis";
import { topThreeColors } from "@/constants";
import { getHomeData } from "@/apis";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { user, device } = storeToRefs(store);
const { getLvlName } = store;
const defaultSeasonData = {
"1v1": { totalGames: 0, winCount: 0, winRate: 0 },
"5m": { totalGames: 0, winCount: 0, winRate: 0 },
"10m": { totalGames: 0, winCount: 0, winRate: 0 },
};
const selectedIndex = ref(0);
const currentList = ref([]);
const seasonName = ref("");
const seasonData = ref([]);
const rankData = ref({ user: {} });
const showSeasonList = ref(false);
const currentSeasonData = ref(defaultSeasonData);
const handleSelect = (index) => {
selectedIndex.value = index;
if (index === 0 && rankData.value.rank) {
currentList.value = rankData.value.rank.slice(0, 10);
} else if (index === 2 && rankData.value.ringRank) {
currentList.value = rankData.value.ringRank.slice(0, 10);
} else {
currentList.value = [];
}
};
onMounted(async () => {
const result = await getHomeData();
currentList.value = result.rank;
rankData.value = result;
const { userGameStats, seasonList } = result;
seasonData.value = seasonList;
updateData();
});
const toTeamMatchPage = async (gameType, teamSize) => {
if (!device.value.deviceId) {
return uni.showToast({
title: "请先绑定设备",
icon: "none",
});
}
const isGaming = await isGamingAPI();
if (isGaming) {
uni.$showHint(1);
return;
}
uni.navigateTo({
url: `/pages/team-match?gameType=${gameType}&teamSize=${teamSize}`,
});
};
const toMeleeMatchPage = async (gameType, teamSize) => {
if (!device.value.deviceId) {
return uni.showToast({
title: "请先绑定设备",
icon: "none",
});
}
if (!user.value.trio) {
return uni.showToast({
title: "请先完成新手试炼",
icon: "none",
});
}
const isGaming = await isGamingAPI();
if (isGaming) {
uni.$showHint(1);
return;
}
uni.navigateTo({
url: `/pages/melee-match?gameType=${gameType}&teamSize=${teamSize}`,
});
};
const toMyGrowthPage = () => {
uni.navigateTo({
url: "/pages/my-growth",
});
};
const toRankListPage = () => {
uni.navigateTo({
url: "/pages/rank-list",
});
};
const onChangeSeason = async (seasonId, name) => {
if (name !== seasonName.value) {
const result = await getHomeData(seasonId);
rankData.value = result;
seasonName.value = name;
handleSelect(selectedIndex.value);
updateData();
}
showSeasonList.value = false;
};
const updateData = () => {
const { userGameStats, seasonList } = rankData.value;
currentSeasonData.value = { ...defaultSeasonData };
if (
userGameStats &&
userGameStats.stats_list &&
userGameStats.stats_list.length
) {
userGameStats.stats_list.forEach((item) => {
if (seasonList.length) {
seasonList.some((s) => {
if (s.seasonId === item.seasonId) {
seasonName.value = s.seasonName;
return true;
}
return false;
});
}
let keyName = "";
if (item.gameType === 1 && item.teamSize === 2) keyName = "1v1";
if (item.gameType === 2 && item.teamSize === 5) keyName = "5m";
if (item.gameType === 2 && item.teamSize === 10) keyName = "10m";
if (keyName) {
currentSeasonData.value[keyName] = {
totalGames: item.totalGames,
winCount: item.winCount,
winRate: Number(((item.winCount / item.totalGames) * 100).toFixed(2)),
};
}
});
}
};
onHide(() => {
showSeasonList.value = false;
});
</script>
<template>
<Container title="排行赛">
<view class="container" @click="() => (showSeasonList = false)">
<view class="ranking-my-data" v-if="user.id">
<view>
<view class="user-info">
<Avatar
:src="user.avatar"
:rankLvl="rankData.user.rankLvl"
:size="30"
/>
<text>{{ user.nickName }}</text>
</view>
<view
class="ranking-season"
v-if="seasonData.length"
@click.stop="() => (showSeasonList = true)"
>
<text>{{ seasonName }}</text>
<image
v-if="seasonData.length > 1"
src="../static/triangle.png"
mode="widthFix"
/>
<view class="season-list" v-if="showSeasonList">
<view
v-for="(item, index) in seasonData"
:key="index"
@click.stop="
() => onChangeSeason(item.seasonId, item.seasonName)
"
>
<text>{{ item.seasonName }}</text>
<image
v-if="item.seasonName === seasonName"
src="../static/triangle.png"
mode="widthFix"
/>
</view>
</view>
</view>
</view>
<view class="my-data">
<view>
<text>段位</text>
<text :style="{ color: '#83CDFF' }">{{
getLvlName(rankData.user.scores) || "-"
}}</text>
</view>
<view>
<text>赛季平均环数</text>
<text :style="{ color: '#FFD947' }">{{
rankData.user.avg_ring ? rankData.user.avg_ring + "环" : "-"
}}</text>
</view>
<view>
<text>赛季胜率</text>
<text :style="{ color: '#FF507E' }">{{
rankData.user.avg_win ? rankData.user.avg_win + "%" : "-"
}}</text>
</view>
</view>
<view class="rank-type">
<image
src="../static/battle1v1.png"
mode="widthFix"
@click.stop="() => toTeamMatchPage(1, 2)"
/>
<image
src="../static/battle5.png"
mode="widthFix"
@click.stop="() => toMeleeMatchPage(2, 5)"
/>
<image
src="../static/battle10.png"
mode="widthFix"
@click.stop="() => toMeleeMatchPage(2, 10)"
/>
</view>
<view class="data-progress">
<text>
{{
`【1 V 1】${currentSeasonData["1v1"].totalGames}场 胜率 ${currentSeasonData["1v1"].winRate}%`
}}
</text>
<view>
<view
:style="{
width: `${currentSeasonData['1v1'].winRate}%`,
backgroundColor: '#FF507E',
}"
/>
</view>
</view>
<view class="data-progress">
<text>
{{
`【5人大乱斗】${currentSeasonData["5m"].totalGames}场 胜率 ${currentSeasonData["5m"].winRate}%`
}}
</text>
<view>
<view
:style="{
width: `${currentSeasonData['5m'].winRate}%`,
backgroundColor: '#FFD947',
}"
/>
</view>
</view>
<view class="data-progress">
<text>
{{
`【10人大乱斗】${currentSeasonData["5m"].totalGames}场 胜率 ${currentSeasonData["10m"].winRate}%`
}}
</text>
<view>
<view
:style="{
width: `${currentSeasonData['10m'].winRate}%`,
backgroundColor: '#FFD947',
}"
/>
</view>
</view>
<view @click.stop="toMyGrowthPage">查看我的比赛记录</view>
</view>
<view class="ranking-data">
<view>
<view
v-for="(rankType, index) in [
'积分榜',
'MVP榜',
'十环榜',
'最牛省份',
]"
:key="index"
:style="{
color: index === selectedIndex ? '#000' : '#fff',
backgroundColor:
index === selectedIndex ? '#FFD947' : 'transparent',
}"
@tap="handleSelect(index)"
>
{{ rankType }}
</view>
</view>
<view
v-for="(item, index) in currentList"
:key="index"
:style="{
backgroundColor: index % 2 === 0 ? '#9898981f' : 'transparent',
}"
class="rank-item"
>
<image
v-if="index === 0"
src="../static/champ1.png"
mode="widthFix"
/>
<image
v-if="index === 1"
src="../static/champ2.png"
mode="widthFix"
/>
<image
v-if="index === 2"
src="../static/champ3.png"
mode="widthFix"
/>
<view v-if="index > 2">{{ index + 1 }}</view>
<image
:src="item.avatar || '../static/user-icon.png'"
mode="widthFix"
:style="{ borderColor: index < 3 ? topThreeColors[index] : '' }"
/>
<view>
<text class="truncate">{{ item.name }}</text>
<text
>{{ getLvlName(item.totalScore) }}{{ item.TotalGames }}场</text
>
</view>
<text>{{ item.totalScore }}<text>分</text></text>
</view>
<view v-if="!currentList.length" class="no-data">
<text>筹备中...</text>
</view>
<view class="see-more" @click.stop="toRankListPage">点击查看更多</view>
</view>
</view>
</Container>
</template>
<style scoped>
.container {
width: 100%;
}
.ranking-my-data,
.ranking-data {
display: flex;
flex-direction: column;
align-items: flex-start;
background-color: #54431d33;
border: 1px solid #54431d;
border-radius: 10px;
margin: 0 15px;
}
.ranking-data {
margin-bottom: 20px;
}
.ranking-my-data {
margin-bottom: 15px;
}
.ranking-my-data {
padding: 15px;
}
.ranking-my-data > view:first-of-type {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
border-bottom: 1px solid #48494e;
padding-bottom: 15px;
}
.user-info {
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 14px;
}
.user-info > text {
margin-left: 15px;
}
.ranking-season {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.ranking-season > image {
width: 12px;
}
.ranking-season > text {
color: #ffd947;
font-size: 14px;
margin-right: 5px;
}
.my-data {
display: flex;
align-items: center;
justify-content: space-around;
color: #b3b3b3;
width: 100%;
margin-top: 15px;
}
.my-data > view {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
height: 60px;
}
.my-data > view:nth-child(2) {
border-left: 1px solid #48494e;
border-right: 1px solid #48494e;
padding: 0 20px;
}
.my-data > view > text:first-child {
font-size: 14px;
}
.my-data > view > text:last-child {
font-size: 18px;
}
.rank-type {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-top: 15px;
}
.rank-type > image {
width: 32%;
}
.data-progress {
width: 100%;
color: #b3b3b3;
display: flex;
flex-direction: column;
align-items: flex-start;
margin-top: 15px;
}
.data-progress > view {
width: 100%;
height: 5px;
border-radius: 10px;
background-color: #696969;
margin-top: 10px;
}
.data-progress > view > view {
height: 5px;
border-radius: 10px;
}
.ranking-my-data > view:last-child {
color: #39a8ff;
font-size: 14px;
text-align: center;
width: 100%;
padding-top: 15px;
}
.ranking-data > view:first-of-type {
width: calc(100% - 30px);
display: flex;
justify-content: space-around;
font-size: 15px;
padding: 15px;
}
.ranking-data > view:first-of-type > view {
width: 25%;
padding: 7px 10px;
text-align: center;
border-radius: 20px;
}
.rank-item {
width: calc(100% - 30px);
height: 55px;
padding: 0 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.rank-item > view:first-child {
width: 24px;
height: 24px;
border-radius: 12px;
background-color: #767676;
color: #fff;
text-align: center;
line-height: 24px;
font-size: 14px;
}
.rank-item > image:first-child {
width: 24px;
height: 24px;
}
.rank-item > image:nth-child(2) {
width: 35px;
min-height: 35px;
max-height: 35px;
border-radius: 50%;
border: 1px solid transparent;
}
.rank-item > view:nth-child(3) {
display: flex;
flex-direction: column;
width: 55%;
}
.rank-item > view:nth-child(3) > text:first-child {
color: #fff9;
font-size: 14px;
width: 120px;
}
.rank-item > view:nth-child(3) > text:last-child {
color: #fff4;
font-size: 13px;
}
.rank-item > text:last-child {
color: #fff;
}
.rank-item > text:last-child text {
color: #fff4;
font-size: 13px;
margin-left: 3px;
}
.see-more {
color: #39a8ff;
font-size: 14px;
text-align: center;
width: 100%;
margin-top: 5px;
margin-bottom: 10px;
}
.no-data {
width: 100%;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
color: #fff9;
}
.season-list {
background-color: #000c;
border-radius: 15px;
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
font-size: 12px;
padding: 5px 0;
position: absolute;
width: 220rpx;
top: -44rpx;
right: -30rpx;
letter-spacing: 2px;
}
.season-list > view {
display: flex;
align-items: center;
padding: 10px 20px;
}
.season-list > view > image {
width: 12px;
margin-left: 10px;
}
</style>