Files
shoot-miniprograms/src/pages/match-detail.vue
T

278 lines
7.2 KiB
Vue

<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import BattleHeader from "@/components/BattleHeader.vue";
import Avatar from "@/components/Avatar.vue";
import PlayerScore2 from "@/components/PlayerScore2.vue";
import { getBattleAPI } from "@/apis";
const battleId = ref("");
const data = ref({
teams: [],
rounds: [],
});
const players = ref([]);
const isLoading = ref(true);
const loadError = ref("");
const loadBattle = async () => {
const result = await getBattleAPI(battleId.value);
data.value = result;
if (result.mode > 3) {
const plist = result.teams[0] ? result.teams[0].players : [];
// 以 id 为 key 建立 teams 玩家快速查找表
const teamPlayerMap = {};
plist.forEach((p) => { teamPlayerMap[p.id] = p; });
// 处理有成绩的玩家(resultList 顺序即排名顺序)
const rankedPlayers = (result.resultList || []).map((item, index) => {
const playerId = item.userId || item.id;
const p = teamPlayerMap[playerId] || item;
const arrows = new Array(12);
result.rounds.forEach((r, rIndex) => {
if (r.shoots[playerId]) {
r.shoots[playerId].forEach((s, sIndex) => {
arrows[sIndex + rIndex * 6] = s;
});
}
});
return {
...item,
id: playerId,
rank: index + 1,
name: (p && p.name) || item.name,
avatar: (p && p.avatar) || item.avatar || "",
arrows,
};
});
// 追加未出现在 resultList 中的玩家(未射箭),rank=0 隐藏角标
const rankedIds = new Set(rankedPlayers.map((p) => p.id));
const unrankedPlayers = plist
.filter((p) => !rankedIds.has(p.id))
.map((p) => ({
id: p.id,
name: p.name,
avatar: p.avatar || "",
arrows: [],
totalScore: 0,
rank: 0,
}));
players.value = [...rankedPlayers, ...unrankedPlayers];
}
// 数据和派生列表均已就绪后再关闭占位,避免空壳闪烁。
isLoading.value = false;
};
const requestBattle = () => {
if (!battleId.value) return;
isLoading.value = true;
loadError.value = "";
loadBattle().catch((error) => {
console.error("加载比赛详情失败:", error);
loadError.value = "赛况加载失败";
isLoading.value = false;
});
};
onLoad((options) => {
if (!options.battleId) {
loadError.value = "缺少比赛信息";
isLoading.value = false;
return;
}
battleId.value = options.battleId;
requestBattle();
});
const checkBowData = (selected) => {
if (data.value.mode <= 3) {
uni.navigateTo({
url: `/pages/team-battle/team-bow-data?battleId=${battleId.value}&selected=${selected}`,
});
} else {
uni.navigateTo({
url: `/pages/melee-bow-data?battleId=${battleId.value}`,
});
}
};
</script>
<template>
<Container title="详情">
<view class="container">
<view v-if="isLoading" class="page-state">
<text>赛况加载中...</text>
</view>
<view
v-else-if="loadError"
class="page-state page-state--error"
@click="requestBattle"
>
<text>{{ loadError }}</text>
<text v-if="battleId" class="retry-text">点击重新加载</text>
</view>
<BattleHeader
v-if="data.mode <= 3"
:winner="data.winTeam"
:blueTeam="data.teams[1] ? data.teams[1].players : []"
:redTeam="data.teams[2] ? data.teams[2].players : []"
:players="players"
/>
<view
v-if="data.mode > 3"
class="score-header"
:style="{ border: 'none', padding: '5px 15px' }"
>
<text>大乱斗</text>
<view @click="checkBowData">
<text>查看靶纸</text>
<image src="../static/back.png" mode="widthFix" />
</view>
</view>
<PlayerScore2
v-if="data.mode > 3"
v-for="(player, index) in players"
:key="index"
:name="player.name"
:avatar="player.avatar"
:arrows="player.arrows"
:totalScore="player.totalScore"
:rank="player.rank"
/>
<view
v-if="data.mode <= 3"
v-for="(round, index) in data.rounds"
:key="index"
:style="{ marginBottom: '5px' }"
>
<view class="score-header">
<text>{{ round.ifGold ? "决金箭" : `第${index + 1}轮` }}</text>
<view @click="() => checkBowData(index)">
<text>查看靶纸</text>
<image src="../static/back.png" mode="widthFix" />
</view>
</view>
<view
class="score-row"
v-for="team in Object.keys(round.shoots)"
:key="team"
>
<view>
<view>
<image
v-for="(p, index) in data.teams[team].players"
:style="{
borderColor: '#64BAFF',
transform: `translateX(-${index * 15}px)`,
}"
:src="p.avatar || '../static/user-icon.png'"
:key="index"
mode="widthFix"
/>
</view>
<text
v-for="(arrow, index2) in round.shoots[team]"
:key="index2"
:style="{ color: arrow.ringX ? '#fed847' : '#ccc' }"
>
{{ arrow.ringX ? "X" : `${arrow.ring}环` }}
</text>
</view>
<view>
<text :style="{ color: team == 1 ? '#64BAFF' : '#FF6767' }">
{{ round.shoots[team].reduce((acc, cur) => acc + cur.ring, 0) }}
</text>
<text>得分 {{ round.scores[team].score }}</text>
</view>
</view>
</view>
<view :style="{ height: '20px' }"></view>
</view>
</Container>
</template>
<style scoped>
.container {
width: 100%;
height: 100%;
}
.page-state {
min-height: 60vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.6);
font-size: 26rpx;
}
.page-state--error {
color: rgba(255, 255, 255, 0.72);
}
.retry-text {
margin-top: 16rpx;
color: #fed847;
}
.score-header,
.score-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
color: #fff9;
font-size: 15px;
border-bottom: 1px solid #fff3;
}
.score-header > text:first-child {
color: #fed847;
}
.score-header > view:last-child {
display: flex;
align-items: center;
}
.score-header > view:last-child > image {
margin-left: 5px;
width: 15px;
height: 15px;
transform: rotate(180deg);
margin-top: -2px;
}
.score-row > view {
display: flex;
align-items: center;
font-size: 12px;
}
.score-row > view:first-child > view:first-child {
display: flex;
align-items: center;
width: 70px;
}
.score-row > view:first-child > view:first-child > image {
width: 25px;
height: 25px;
min-width: 25px;
min-height: 25px;
border: 1px solid;
border-radius: 50%;
}
.score-row > view:first-child > text {
color: #fff;
display: block;
width: 35px;
}
.score-row > image:last-child {
width: 40px;
}
.score-row > view:nth-child(2) {
padding-right: 5px;
}
.score-row > view:nth-child(2) > text:last-child {
margin-left: 20px;
}
</style>