Files
shoot-miniprograms/src/pages/friend-battle-result.vue

1078 lines
28 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, computed, onBeforeUnmount } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import Avatar from "@/components/Avatar.vue";
import UserUpgrade from "@/components/UserUpgrade.vue";
import { getBattleAPI } from "@/apis";
import { getBattleResultTips } from "@/constants";
import audioManager from "@/audioManager";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { user } = storeToRefs(store);
/** 通过 store action 获取段位名称rank_lvl → 显示文字) */
const { getLvlName } = store;
/** 结算图片资源 CDN 前缀 */
const RESULT_CDN = "https://static.shelingxingqiu.com/shootmini/static/friend-battle-result";
// ---- 数据状态 ----
/** 当前用户是否属于胜利方 */
const ifWin = ref(false);
/** 后端返回的对战数据 */
const data = ref({});
/** 进场覆盖动效层是否显示 */
const showOverlay = ref(false);
/** 自动关闭覆盖层的计时器 */
const overlayTimer = ref(null);
/** 控制经验条是否执行进场动画(弹窗关闭后才置 true避免动画被遮挡 */
const showExpAnim = ref(false);
// ---- 经验进度条数据onLoad 中从 teams.players 解析填充)----
/** 本局获得的经验值 */
const expGained = ref(0);
/** 对战结束后当前经验进度值 */
const expCurrent = ref(0);
/** 升级到下一级所需的总经验(兜底 100防止分母为 0 */
const expTotal = ref(100);
/** 用户当前等级 */
const userLvl = ref(0);
// ---- 计算属性 ----
/**
* 计算当前用户所在队伍编号
* @returns {number} 1=蓝队2=红队
*/
const myTeam = computed(() => {
const teams = data.value.teams;
if (teams && teams[1]) {
if (teams[1].players.some((p) => p.id === user.value.id)) return 1;
}
return 2;
});
/** 蓝队队伍1玩家列表 */
const blueTeamPlayers = computed(() => data.value.teams?.[1]?.players || []);
/** 红队队伍2玩家列表 */
const redTeamPlayers = computed(() => data.value.teams?.[2]?.players || []);
/**
* 蓝队得分(直接取队伍级 score 字段,与 team-battle.vue 保持一致)
*/
const blueScore = computed(() => data.value.teams?.[1]?.score ?? 0);
/**
* 红队得分(直接取队伍级 score 字段)
*/
const redScore = computed(() => data.value.teams?.[2]?.score ?? 0);
/**
* MVP 玩家对象(仅 mode=2/3 时有值)
* 兼容 mvp 为对象或数组两种格式
*/
const mvpPlayer = computed(() => {
if (data.value.mode > 1) {
const mvp = data.value.mvp;
if (mvp && mvp.id) return mvp;
if (Array.isArray(mvp) && mvp.length) return mvp[0];
}
return null;
});
/**
* MVP 玩家所在队伍编号1=蓝队2=红队)
* 通过比对 mvpPlayer.id 与 blueTeamPlayers 确定用于选择背景图mvp-blue / mvp-red
*/
const mvpTeam = computed(() => {
if (!mvpPlayer.value) return 0;
return blueTeamPlayers.value.some((p) => p.id === mvpPlayer.value.id) ? 1 : 2;
});
/**
* 激励语图片 URL在 onLoad 中确定 ifWin 后赋值,避免 Math.random 放在 computed 里产生缓存不一致问题)
*/
const tipImage = ref("");
/**
* 经验进度条宽度百分比0~100
*/
const expPercent = computed(() =>
Math.min((expCurrent.value / expTotal.value) * 100, 100)
);
/**
* 是否为大乱斗模式mode > 3
*/
const isMeleeMode = computed(() => data.value.mode > 3);
/**
* 大乱斗排行榜列表(从接口 teams["0"].players + resultList 构建)
* - 以 teams["0"].players 为全量玩家基础(包含未射击的玩家)
* - 从 resultList 中按 userId 匹配获取得分;未命中则 totalRing 为 0
* - 按 totalRing 降序排列后注入 rank
* @returns {{ id, rank, avatar, name, lvlName, totalRing, isSelf }[]}
*/
const meleeRankList = computed(() => {
const resultList = data.value.resultList || [];
const teamPlayers = data.value.teams?.[0]?.players || [];
if (!teamPlayers.length) return [];
// 以 players 为基础merge resultList 得分数据
const list = teamPlayers.map((p) => {
const resultItem = resultList.find((r) => r.userId === p.id) || {};
return {
id: p.id,
avatar: p.avatar || "",
name: p.name || "",
// rank_lvl 字段可能缺失,缺失时显示空字符串,避免 getLvlName(undefined) 返回错误段位名
lvlName: p.rank_lvl != null ? getLvlName(p.rank_lvl) : "",
totalRing: resultItem.totalRing ?? 0,
isSelf: String(p.id) === String(user.value.id),
};
});
// 按总环数降序排列,环数相同时按 id 升序保证顺序稳定
list.sort((a, b) => b.totalRing - a.totalRing || a.id - b.id);
// 注入名次从1开始
return list.map((item, index) => ({ ...item, rank: index + 1 }));
});
/**
* 获取前三名行背景装饰 SVG 路径
* @param {number} rank 名次1/2/3
* @returns {string} 静态资源路径
*/
function getMeleeRankBgSrc(rank) {
const names = ["one", "two", "three"];
return `../static/friend-battle-result/rank-${names[rank - 1]}.svg`;
}
/**
* 获取前三名头像光环颜色
* @param {number} rank 名次1=金,2=银,3=铜)
* @returns {string|undefined} 边框颜色4+名返回 undefined
*/
function getMeleeAvatarBorderColor(rank) {
const colors = ['#FFD947', '#D2D2D2', '#FFA515'];
return colors[rank - 1];
}
// ---- 生命周期 ----
onLoad(async (options) => {
if (!options.battleId) return;
const result = await getBattleAPI(options.battleId);
data.value = result;
// 从 teams 各队伍的 players 中找到当前用户,解析经验进度条所需字段
// 后端字段exp本局经验/ currentExp当前经验/ upgradeExp升级所需经验/ level当前等级
const myIdStr = String(user.value.id);
for (const team of Object.values(result.teams || {})) {
const p = (team.players || []).find((pl) => String(pl.id) === myIdStr);
if (p) {
expGained.value = p.exp ?? 0;
expCurrent.value = p.currentExp ?? 0;
expTotal.value = p.upgradeExp ?? 100;
userLvl.value = p.level ?? 0;
break;
}
}
// 判断当前用户是否在胜利队伍中
// 优先使用接口返回的 winTeam 字段;若缺失则以队伍得分高低作为兜底判断
const myId = String(user.value.id);
if (result.winTeam != null && result.teams) {
// 使用 String() 类型统一,防止后端返回字符串 ID 与 store 中数字 ID 严格不等
const winPlayers = result.teams[result.winTeam]?.players || [];
ifWin.value = winPlayers.some((p) => String(p.id) === myId);
} else if (result.teams) {
// 兜底winTeam 未返回时,比较双方分数;当前用户所在队分数 >= 对方则视为胜利
const team1Players = result.teams[1]?.players || [];
const inTeam1 = team1Players.some((p) => String(p.id) === myId);
const score1 = result.teams[1]?.score ?? 0;
const score2 = result.teams[2]?.score ?? 0;
ifWin.value = inTeam1 ? score1 > score2 : score2 > score1;
}
// 确保 ifWin 已定后再生成激励语图片,防止 Math.random 在 computed 缓存时用到旧的 ifWin 值
tipImage.value = getBattleResultTips(result.way, result.mode, { win: ifWin.value });
// 播放胜利/失败音效
if (result.mode <= 3) {
audioManager.play(ifWin.value ? "胜利" : "失败");
}
// 数据加载完成后显示覆盖动效层
showOverlay.value = true;
// 2.5 秒后自动关闭覆盖层
overlayTimer.value = setTimeout(() => {
closeOverlay();
}, 2500);
});
onBeforeUnmount(() => {
if (overlayTimer.value) clearTimeout(overlayTimer.value);
});
// ---- 方法 ----
/**
* 关闭进场覆盖动效层(点击任意位置或超时均可触发)
*/
function closeOverlay() {
showOverlay.value = false;
if (overlayTimer.value) {
clearTimeout(overlayTimer.value);
overlayTimer.value = null;
}
// 弹窗关闭后启动经验条进场动画
showExpAnim.value = true;
}
/**
* 底部按钮文案:好友约战显示“返回房间”,排位赛等其他模式显示“返回”
*/
const exitBtnText = computed(() => data.value.way === 1 ? '返回房间' : '返回');
/**
* 点击底部按钮跳转
* - 好友约战way=1返回对战房间
* - 其他模式(排位赛等):跳转到排位赛首页
*/
function exit() {
if (data.value.way === 1) {
uni.redirectTo({
url: `/pages/battle-room?roomNumber=${data.value.roomId}`,
});
} else {
uni.redirectTo({
url: '/pages/ranking',
});
}
}
/**
* 跳转到完整弓箭数据页match-detail
*/
function checkBowData() {
uni.navigateTo({
url: `/pages/match-detail?battleId=${data.value.matchId}`,
});
}
/**
* 点击导航栏返回按钮
*/
function goBack() {
uni.navigateBack();
}
</script>
<template>
<view class="page">
<!-- ===== 主体可滚动内容区 Container 接管===== -->
<Container
:bgType="data.mode > 3 ? -1 : 0"
bgColor="#000000"
:onBack="goBack"
>
<!-- ----- Banner game 胜负展示图 NvN 对抗模式----- -->
<view v-if="!isMeleeMode" class="banner-section">
<!-- 根据胜负动态展示 game-win / game-lose切图尺寸不同 style 分别设宽和叠层距 -->
<image
class="banner-result scale-in"
:src="ifWin ? `${RESULT_CDN}/game-win.png` : `${RESULT_CDN}/game-lose.png`"
:style="ifWin
? { width: '362rpx', marginBottom: '-32rpx' }
: { width: '378rpx', marginBottom: '-40rpx' }"
mode="widthFix"
/>
</view>
<!-- ----- VS 对战信息区 NvN 对抗模式----- -->
<view v-if="!isMeleeMode" class="vs-section">
<!-- 红蓝背景切图 -->
<image class="vs-bg-img" :src="`${RESULT_CDN}/vs-bg.png`" mode="aspectFill" />
<!-- 双方玩家头像行 -->
<view class="vs-players-row">
<!-- 蓝队 -->
<view class="team-players team-players-blue">
<view v-for="p in blueTeamPlayers" :key="p.id" class="player-item">
<Avatar :src="p.avatar" :size="34" borderColor="#8FB4FD" />
<text class="player-name player-name-blue">{{ p.name }}</text>
</view>
</view>
<!-- 红队 -->
<view class="team-players team-players-red">
<view v-for="p in redTeamPlayers" :key="p.id" class="player-item">
<Avatar :src="p.avatar" :size="34" borderColor="#E67470" />
<text class="player-name player-name-red">{{ p.name }}</text>
</view>
</view>
</view>
<!-- 得分行蓝队得分 + 红队得分 -->
<view class="vs-scores-row">
<view class="score-tag-blue">
<text class="score-label">蓝方得分</text>
<text class="score-num score-num-blue">{{ blueScore }}</text>
</view>
<!-- 红队得分分数在内侧标签在外侧配合 justify-content: flex-end 靠右对齐与蓝队形成镜像对称 -->
<view class="score-tag-red">
<text class="score-num score-num-red">{{ redScore }}</text>
<text class="score-label">红方得分</text>
</view>
</view>
</view>
<!-- ----- MVP 卡片 NvN mode = 2/3----- -->
<view
v-if="mvpPlayer && !isMeleeMode"
class="mvp-card"
:class="mvpTeam === 1 ? 'mvp-card-blue' : 'mvp-card-red'"
>
<!-- 背景图蓝队用 mvp-blue.png红队用 mvp-red.png平行四边形渐变切图 -->
<image
class="mvp-bg"
:src="mvpTeam === 1 ? '../static/mvp-blue.png' : '../static/mvp-red.png'"
mode="scaleToFill"
/>
<!-- MVP 标题图 + 斩获环数 -->
<view class="mvp-info">
<image class="mvp-badge" src="../static/mvp-tip.png" mode="widthFix" />
<view class="mvp-rings">
斩获<text class="mvp-rings-num">{{ mvpPlayer.totalRing }}</text>
</view>
</view>
<!-- MVP 头像 + 名字边框颜色跟随 MVP 所在队伍 -->
<view class="mvp-player">
<Avatar
:src="mvpPlayer.avatar"
:size="53"
:borderColor="mvpTeam === 1 ? '#5FADFF' : '#FF6060'"
/>
<text class="mvp-name">{{ mvpPlayer.name }}</text>
</view>
</view>
<!-- ----- 大乱斗战绩排行mode > 3----- -->
<view v-if="isMeleeMode" class="melee-section">
<!-- 本场战绩标题装饰横线 + 标题图 + 装饰横线 -->
<view class="melee-title">
<view class="melee-title-line" />
<image class="melee-title-img" src="../static/battle-result.png" mode="scaleToFill" />
<view class="melee-title-line" />
</view>
<!-- 排行榜滚动列表显示 6.5 7 条半显示提示可滚动-->
<scroll-view class="melee-rank-scroll" scroll-y>
<view
v-for="(item, index) in meleeRankList"
:key="item.id"
:class="['rank-item', item.isSelf ? 'rank-item-self' : '', index % 2 !== 0 ? 'rank-item-light' : '']"
>
<!-- 前三名左侧渐变背景装饰绝对定位于内容层之下-->
<image
v-if="item.rank <= 3"
class="rank-bg-deco"
:src="getMeleeRankBgSrc(item.rank)"
mode="scaleToFill"
/>
<!-- 行内容区域z-index 高于背景装饰-->
<view class="rank-item-content">
<!-- 名次徽章1-3 使用 champ 图标4+ 灰色圆形数字 -->
<view class="rank-badge-wrap">
<image
v-if="item.rank <= 3"
class="rank-badge-img"
:src="`../static/champ${item.rank}.png`"
mode="aspectFit"
/>
<view v-else class="rank-badge-default">
<text class="rank-badge-num">{{ item.rank }}</text>
</view>
</view>
<!-- 圆形头像前三名根据名次展示对应光环颜色-->
<Avatar :src="item.avatar" :size="44" :borderColor="getMeleeAvatarBorderColor(item.rank)" />
<!-- 昵称 + 段位 -->
<view class="rank-player-info">
<text class="rank-player-name">{{ item.name }}</text>
<text class="rank-player-lvl">{{ item.lvlName }}</text>
</view>
<!-- 总环数 -->
<view class="rank-score">
<text class="rank-score-num">{{ item.totalRing }}</text>
<text class="rank-score-unit"></text>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- ----- 查看完整成绩链接 ----- -->
<text class="view-full-btn" @click="checkBowData">查看完整成绩</text>
<!-- ----- 经验进度条区域 ----- -->
<view class="exp-section">
<text class="exp-gained">+{{ expGained }}经验</text>
<view class="exp-bar-row">
<text class="exp-lv">LV.{{ userLvl }}</text>
<view class="exp-bar-bg">
<view
class="exp-bar-fg"
:style="{ width: showExpAnim ? expPercent + '%' : '0%' }"
/>
</view>
<text class="exp-progress">{{ expCurrent }} / {{ expTotal }}</text>
</view>
</view>
<!-- 底部安全区占位 -->
<view class="safe-area-bottom" />
</Container>
<!-- ===== 底部返回房间按鈕对抗模式与大乱斗模式样式不同===== -->
<view class="bottom-bar">
<view
:class="['btn-return', data.mode > 3 ? 'btn-return-melee' : 'btn-return-battle']"
@click="exit"
>{{ exitBtnText }}</view>
</view>
<!-- ===== 进场覆盖动效层初始显示点击或2.5s后消失===== -->
<view
v-if="showOverlay"
class="overlay"
@click="closeOverlay"
>
<!-- 胜负/game-over 被摭图大乱斗模式展示 game-over.png对抗模式展示 you-win.png/you-lost.png -->
<image
:class="['overlay-trophy', isMeleeMode ? 'overlay-trophy-melee' : '']"
:src="isMeleeMode ? `${RESULT_CDN}/game-over.png` : (ifWin ? `${RESULT_CDN}/you-win.png` : `${RESULT_CDN}/you-lost.png`)"
mode="widthFix"
/>
<!-- 激励语图片 NvN 对抗模式展示大乱斗模式不展示-->
<image
v-if="tipImage && !isMeleeMode"
class="overlay-tip"
:src="tipImage"
mode="widthFix"
/>
</view>
<!-- 升级/段位提升动效组件 -->
<UserUpgrade />
</view>
</template>
<style scoped>
/* ============================
全局页面容器
============================ */
.page {
width: 750rpx;
position: relative;
}
/* ============================
Banner 区game 胜负图
============================ */
.banner-section {
position: relative;
z-index: 10; /* 高于 vs-section确保 game 图层级在上 */
width: 750rpx;
height: 300rpx;
display: flex;
align-items: flex-end; /* 图片裴底对齐 vs-section 顶边 */
justify-content: center;
overflow: visible; /* 允许 6rpx 叠入 vs-section */
margin-top: 20rpx;
}
/* game 胜负展示图(宽度和叠层距由 :style 动态赋値:胜 362rpx/-32rpx负 378rpx/-40rpx */
.banner-result {
position: relative;
z-index: 1;
}
/* 弹入缩放动画 */
@keyframes scale-in {
0% { transform: scale(0.3); opacity: 0; }
70% { transform: scale(1.08); opacity: 1; }
100% { transform: scale(1); }
}
.scale-in {
animation: scale-in 0.5s ease-out forwards;
}
/* ============================
VS 对战信息区
全宽背景条 246rpx 高,参考设计稿 375×123pt
============================ */
.vs-section {
position: relative;
width: 750rpx;
height: 246rpx;
overflow: hidden;
}
/* 红蓝背景切图 */
.vs-bg-img {
position: absolute;
inset: 0;
width: 750rpx;
height: 246rpx;
}
/* 玩家头像行padding-top 补偿 banner 图叠入 vs-section 导致的视觉偏上 */
.vs-players-row {
position: relative;
z-index: 2;
width: 750rpx;
height: 180rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 24rpx 0;
box-sizing: border-box;
}
.team-players {
display: flex;
align-items: flex-start;
gap: 12rpx;
flex: 1;
}
.team-players-blue {
justify-content: center;
}
.team-players-red {
justify-content: center;
}
.player-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.player-name {
font-size: 22rpx;
font-weight: 400;
color: #BBCBFF;
width: 74rpx;
height: 30rpx;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ---- 得分行 ---- */
.vs-scores-row {
position: relative;
z-index: 2;
width: 750rpx;
height: 66rpx; /* 33px → 66rpx */
display: flex;
align-items: center;
justify-content: space-between;
}
.score-tag-blue {
display: flex;
align-items: center;
gap: 12rpx;
width: 216rpx;
height: 54rpx;
background: linear-gradient(270deg, rgba(116,197,255,0) 0%, rgba(0,92,159,0.64) 100%);
padding: 0 20rpx;
box-sizing: border-box;
}
.score-tag-red {
display: flex;
align-items: center;
justify-content: flex-end; /* 内容靠右对齐,使标签贴近外侧渐变区 */
gap: 12rpx;
width: 216rpx;
height: 54rpx;
/* 从中心侧(左)透明渐变到外侧(右)红色,与蓝队镜像对称 */
background: linear-gradient(90deg, rgba(193,4,1,0) 0%, rgba(193,4,1,0.64) 100%);
padding: 0 20rpx;
box-sizing: border-box;
}
.score-label {
font-size: 20rpx;
font-weight: 400;
color: rgba(255, 255, 255, 0.6);
}
.score-num {
font-size: 48rpx;
font-weight: 400;
line-height: 1;
}
.score-num-blue {
color: #62CBFF;
}
.score-num-red {
color: #E86B72;
}
/* ============================
MVP 卡片
设计尺寸700×230rpx横幅风格背景由切图决定
============================ */
.mvp-card {
width: 574rpx;
height: 264rpx;
margin: 40rpx auto 0;
display: flex;
align-items: center;
justify-content: center;
gap: 56rpx;
box-sizing: border-box;
position: relative;
overflow: hidden;
}
/* 蓝队 MVP背景色透明颜色由 mvp-blue.png 决定)*/
.mvp-card-blue,
/* 红队 MVP背景色透明颜色由 mvp-red.png 决定)*/
.mvp-card-red {
background: transparent;
}
/* 背景切图:绝对定位全铺,产生平行四边形渐变效果 */
.mvp-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.mvp-info {
display: flex;
flex-direction: column;
gap: 4rpx;
position: relative;
z-index: 1;
}
.mvp-badge {
width: 228rpx; /* 114px → 228rpx */
}
.mvp-rings {
font-size: 30rpx;
color: #fff;
display: flex;
align-items: baseline;
align-self: flex-end;
transform: rotate(-5deg);
transform-origin: right center;
}
/* 环数数值40rpx 加粗,金色,由父级统一倾斜 */
.mvp-rings-num {
color: #fed847;
font-size: 40rpx;
font-weight: 700;
display: inline-block;
line-height: 1;
margin: 0 4rpx;
}
.mvp-player {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
position: relative;
z-index: 1;
transform: translateY(-22rpx);
}
.mvp-name {
font-size: 24rpx;
color: #fff;
max-width: 120rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ============================
查看完整成绩链接
============================ */
.view-full-btn {
display: block;
width: 100%;
text-align: center;
color: #4DA6FF;
font-size: 28rpx;
margin-top: 48rpx;
padding: 12rpx 0;
}
/* ============================
经验进度条区域
============================ */
.exp-section {
width: 100%;
padding: 32rpx 48rpx 0;
box-sizing: border-box;
}
.exp-gained {
display: block;
text-align: center;
color: #FAE6BC;
font-size: 20rpx;
font-weight: 400;
margin-bottom: 12rpx;
}
.exp-bar-row {
display: flex;
align-items: center;
gap: 16rpx;
}
.exp-lv {
font-size: 22rpx;
color: #ffffff;
font-weight: 400;
flex-shrink: 0;
}
.exp-bar-bg {
flex: 1;
height: 10rpx;
background: #454545;
border-radius: 5rpx;
overflow: hidden;
}
.exp-bar-fg {
height: 100%;
background: linear-gradient(90deg, #DD7B13 0%, #DD7B13 75%, rgba(244, 255, 176, 0.84) 100%);
border-radius: 5rpx;
transition: width 0.8s ease-out;
/* 发光效果:近似 rgba(244,255,176,0.84) + blur 7.7px 叠加层 */
box-shadow: 0 0 14rpx rgba(244, 255, 176, 0.84);
}
.exp-progress {
font-size: 18rpx;
font-weight: 400;
color: #C3C3C3;
flex-shrink: 0;
}
/* ============================
底部安全区占位
iPhone X 底部约 34pt → 68rpx
============================ */
.safe-area-bottom {
height: 68rpx;
}
/* ============================
底部返回房间按钮(固定定位)
设计全宽减32px边距=343pt → 686rpx高50pt → 100rpx
============================ */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
width: 750rpx;
padding: 20rpx 0;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, transparent 100%);
display: flex;
justify-content: center;
z-index: 50;
}
/* 返回房间按鈕公共样式 */
.btn-return {
background: #FED847;
font-weight: 500;
color: #000000;
display: flex;
align-items: center;
justify-content: center;
}
/* 对抗模式mode <= 3按鈕 */
.btn-return-battle {
width: 694rpx;
height: 88rpx;
border-radius: 16rpx;
font-size: 30rpx;
}
/* 大乱斗模式mode > 3按鈕 */
.btn-return-melee {
width: 232rpx;
height: 70rpx;
border-radius: 44rpx;
font-size: 28rpx;
}
/* ============================
进场覆盖动效层
============================ */
.overlay {
position: fixed;
inset: 0;
z-index: 200;
/* rgba 写法等价于 background:#000 + opacity:0.7,且不影响子元素不透明度 */
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
animation: overlay-in 0.3s ease-out;
/* 两类被摭图片都是 750×666/668rpx 全宽,去除水平 padding */
padding: 0;
box-sizing: border-box;
}
@keyframes overlay-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* 胜利/失败奖杯图片750×666rpx 全宽展示)*/
.overlay-trophy {
width: 750rpx;
height: 666rpx;
animation: trophy-bounce-in 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* 大乱斗结束图:高度覆盖为 668rpx */
.overlay-trophy-melee {
height: 668rpx;
height: 668rpx;
}
@keyframes trophy-bounce-in {
0% { transform: scale(0.3) translateY(-80rpx); opacity: 0; }
60% { transform: scale(1.1) translateY(10rpx); opacity: 1; }
100% { transform: scale(1) translateY(0); }
}
/* 覆盖层激励语图片(向上移入奖杯图底部空白区) */
.overlay-tip {
width: 600rpx;
margin-top: -145rpx;
margin-bottom: 20rpx;
}
/* 提示文字 */
.overlay-hint {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
margin-top: 24rpx;
}
/* ============================
大乱斗战绩区域
============================ */
.melee-section {
width: 750rpx;
/* 左右各 60rpx使列表项宽度 = 750 - 120 = 630rpx */
padding: 0 60rpx;
box-sizing: border-box;
margin-top: 48rpx;
/* view-full-btn 有 margin-top: 48rpx叠加后共 50rpx 间距 */
margin-bottom: 2rpx;
}
/* 标题行:装饰横线 + 标题图 + 装饰横线 */
.melee-title {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
margin-bottom: 50rpx;
}
/* 两侧装饰横线 */
.melee-title-line {
width: 120rpx;
height: 2rpx;
background: #ffffff;
opacity: 0.2;
flex-shrink: 0;
}
/* 本场战绩标题图180×44rpx*/
.melee-title-img {
width: 180rpx;
height: 44rpx;
flex-shrink: 0;
}
/*
排行榜滚动容器高度计算:
条目无间距6.5 条 × 116rpx = 754rpx第 7 条上半部分可见提示用户可滚动
*/
.melee-rank-scroll {
height: 754rpx;
}
/* 单条排行记录 */
.rank-item {
display: flex;
align-items: center;
height: 116rpx;
background: rgba(35, 33, 30, 0.85);
/* 相对定位,使背景装饰图可绝对定位于行内 */
position: relative;
overflow: hidden;
}
/* 当前用户所在行:内嵌阴影高亮,不影响尺寸与相邻条目间距 */
.rank-item-self {
box-shadow: inset 0 0 0 2rpx #ff4d4d;
}
/* 斑马色块侸数行rank 1/3/5...):白色 5% 透明底 */
.rank-item-light {
background: rgba(255, 255, 255, 0.05);
}
/* 前三名行背景装饰图SVG 设计尺寸 156×58@2x = 312×116rpx左对齐绝对定位*/
.rank-bg-deco {
display: block; /* 消除 image 默认 inline-block 行高撑开导致的条目间缝隙 */
position: absolute;
left: 0;
top: 0;
width: 312rpx;
height: 116rpx;
z-index: 0;
}
/* 行内容区域flex 水平排布,层叠高于背景装饰 */
.rank-item-content {
display: flex;
align-items: center;
width: 100%;
padding: 0 20rpx;
box-sizing: border-box;
gap: 16rpx;
position: relative;
z-index: 1;
}
/* 名次徽章容器(统一尺寸,内部放 champ 图标或灰色圆形)*/
.rank-badge-wrap {
width: 52rpx;
height: 52rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
/* 徽章右侧叠加 gap(16) + margin(18) = 34rpx 到头像 */
margin-right: 18rpx;
}
/* 1-3 名champ 图标,与容器等尺寸 */
.rank-badge-img {
width: 52rpx;
height: 52rpx;
}
/* 4+ 名:灰色圆形底托 */
.rank-badge-default {
width: 52rpx;
height: 52rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
display: flex;
align-items: center;
justify-content: center;
}
.rank-badge-num {
font-size: 26rpx;
font-weight: 700;
color: rgba(255, 255, 255, 0.7);
}
/* 玩家信息:昵称 + 段位flex 自适应伸展)*/
.rank-player-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
min-width: 0;
}
.rank-player-name {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rank-player-lvl {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
}
/* 总环数(靠右对齐,与用户信息区对比)*/
.rank-score {
display: flex;
align-items: center;
gap: 8rpx;
flex-shrink: 0;
}
.rank-score-num {
font-size: 26rpx;
font-weight: 400;
color: #ffffff;
line-height: 1;
}
.rank-score-unit {
font-size: 22rpx;
font-weight: 400;
color: rgba(255, 255, 255, 0.5);
line-height: 1;
}
</style>