901 lines
25 KiB
Vue
901 lines
25 KiB
Vue
<script setup>
|
||
import { ref, onMounted, computed, onBeforeUnmount } from "vue";
|
||
import { onShow, onLoad, onShareAppMessage } from "@dcloudio/uni-app";
|
||
import Container from "@/components/Container.vue";
|
||
import PlayerSeats from "@/components/PlayerSeats.vue";
|
||
import GuideTwo from "@/components/GuideTwo.vue";
|
||
import SButton from "@/components/SButton.vue";
|
||
import Avatar from "@/components/Avatar.vue";
|
||
import ScreenHint from "@/components/ScreenHint.vue";
|
||
import {
|
||
getRoomAPI,
|
||
destroyRoomAPI,
|
||
exitRoomAPI,
|
||
startRoomAPI,
|
||
chooseTeamAPI,
|
||
getReadyAPI,
|
||
kickPlayerAPI,
|
||
} from "@/apis";
|
||
import { MESSAGETYPES, MESSAGETYPESV2 } from "@/constants";
|
||
import useStore from "@/store";
|
||
import { storeToRefs } from "pinia";
|
||
const store = useStore();
|
||
const { user } = storeToRefs(store);
|
||
|
||
const room = ref({});
|
||
const roomNumber = ref("");
|
||
const owner = ref({});
|
||
const opponent = ref({});
|
||
const players = ref([]);
|
||
const blueTeam = ref([]);
|
||
const redTeam = ref([]);
|
||
|
||
/**
|
||
* 根据对战类型和房间人数动态生成页面标题
|
||
* battleType=1: 组队对战;count 2/4/6 分别对应 1v1/2v2/3v3
|
||
* battleType=2: 多人乱斗
|
||
*/
|
||
const battleTitle = computed(() => {
|
||
if (!room.value.battleType) return "好友约战";
|
||
if (room.value.battleType === 2) return "多人乱斗";
|
||
const half = room.value.count / 2;
|
||
return `${half}v${half}对抗赛`;
|
||
});
|
||
|
||
/** 靶纸尺寸(cm),由 URL 参数或 API 返回的 targetType 字段填充 */
|
||
const targetSize = ref(0);
|
||
|
||
/**
|
||
* 根据 targetSize 动态生成靶纸尺寸文本,如"40cm"
|
||
* 数据未就绪时显示 "--";数据来源:创建者取 URL 参数 target,加入者取 API 返回的 targetType
|
||
*/
|
||
const targetSizeLabel = computed(() =>
|
||
targetSize.value ? `${targetSize.value}cm` : '--'
|
||
);
|
||
|
||
const ready = ref(false);
|
||
const allReady = ref(false);
|
||
const timer = ref(null);
|
||
const goBattle = ref(false);
|
||
|
||
/**
|
||
* 从服务端刷新当前房间数据,更新成员列表、准备状态等信息
|
||
* 仅在 roomNumber 有效且房间未开始时执行
|
||
*/
|
||
async function refreshRoomData() {
|
||
if (!roomNumber.value) return;
|
||
const result = await getRoomAPI(roomNumber.value);
|
||
console.log(result);
|
||
if (result.started) return;
|
||
room.value = result;
|
||
// 加入者通过 API 返回的 targetType 字段同步靶纸尺寸,并持久化到本地缓存
|
||
if (result.targetType) {
|
||
targetSize.value = result.targetType;
|
||
uni.setStorageSync(`targetSize_${roomNumber.value}`, result.targetType);
|
||
} else if (targetSize.value === 0) {
|
||
// API 无该字段时,从本地缓存兜底(如"返回房间"场景)
|
||
const stored = uni.getStorageSync(`targetSize_${roomNumber.value}`);
|
||
if (stored) targetSize.value = stored;
|
||
}
|
||
owner.value = {};
|
||
opponent.value = {};
|
||
const members = result.members || [];
|
||
if (members.length === result.count) {
|
||
allReady.value = members.every((m) => !!m.userInfo.state);
|
||
}
|
||
members.some((m) => {
|
||
if (m.userInfo.id === user.value.id) {
|
||
ready.value = !!m.userInfo.state;
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
members.some((m) => {
|
||
if (m.userInfo.id === result.creator) {
|
||
owner.value = {
|
||
id: m.userInfo.id,
|
||
name: m.userInfo.name,
|
||
avatar: m.userInfo.avatar,
|
||
rankLvl: m.userInfo.rankLvl,
|
||
ready: !!m.userInfo.state,
|
||
};
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
if (result.battleType === 1 && result.count === 2) {
|
||
result.members.forEach((m) => {
|
||
if (m.userInfo.id !== owner.value.id) {
|
||
opponent.value = {
|
||
id: m.userInfo.id,
|
||
name: m.userInfo.name,
|
||
avatar: m.userInfo.avatar,
|
||
rankLvl: m.userInfo.rankLvl,
|
||
ready: !!m.userInfo.state,
|
||
};
|
||
}
|
||
});
|
||
} else if (result.battleType === 2) {
|
||
players.value = [];
|
||
const ownerIndex = result.members.findIndex(
|
||
(m) => m.userInfo.id === result.creator
|
||
);
|
||
if (ownerIndex !== -1) {
|
||
players.value.push(result.members[ownerIndex].userInfo);
|
||
} else {
|
||
players.value.push({});
|
||
}
|
||
result.members.forEach((m, index) => {
|
||
if (ownerIndex !== index) players.value.push(m.userInfo);
|
||
});
|
||
} else {
|
||
players.value = new Array(result.count).fill({});
|
||
refreshMembers(result.members);
|
||
}
|
||
if (timer.value) clearInterval(timer.value);
|
||
// timer.value = setTimeout(refreshRoomData, 2000);
|
||
}
|
||
|
||
const startGame = async () => {
|
||
const result = await startRoomAPI(room.value.number);
|
||
};
|
||
|
||
const getReady = async () => {
|
||
await getReadyAPI(roomNumber.value);
|
||
};
|
||
|
||
const refreshMembers = (members = []) => {
|
||
blueTeam.value = [];
|
||
redTeam.value = [];
|
||
members.forEach((m, index) => {
|
||
players.value[index] = { ...m.userInfo, groupType: m.groupType };
|
||
if (m.groupType === 1) {
|
||
blueTeam.value.push({ ...m.userInfo, groupType: 1 });
|
||
} else if (m.groupType === 2) {
|
||
redTeam.value.push({ ...m.userInfo, groupType: 2 });
|
||
}
|
||
});
|
||
for (let i = 0; i < room.value.count / 2; i++) {
|
||
if (!blueTeam.value[i]) blueTeam.value[i] = {};
|
||
if (!redTeam.value[i]) redTeam.value[i] = {};
|
||
}
|
||
};
|
||
|
||
async function onReceiveMessage(message) {
|
||
if (Array.isArray(message)) {
|
||
message.forEach((msg) => {
|
||
if (msg.roomNumber !== roomNumber.value) return;
|
||
if (msg.constructor === MESSAGETYPES.UserEnterRoom) {
|
||
refreshRoomData();
|
||
} else if (msg.constructor === MESSAGETYPES.UserExitRoom) {
|
||
if (msg.userId === user.value.id) {
|
||
uni.showToast({
|
||
title: "你被踢出房间",
|
||
icon: "none",
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1000);
|
||
} else {
|
||
refreshRoomData();
|
||
}
|
||
} else if (msg.constructor === MESSAGETYPES.TeamUpdate) {
|
||
refreshRoomData();
|
||
} else if (msg.constructor === MESSAGETYPES.SomeoneIsReady) {
|
||
refreshRoomData();
|
||
} else if (msg.constructor === MESSAGETYPES.RoomDestroy) {
|
||
uni.showToast({
|
||
title: "房间已解散",
|
||
icon: "none",
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1000);
|
||
}
|
||
});
|
||
} else if (message.type === MESSAGETYPESV2.AboutToStart) {
|
||
goBattle.value = true;
|
||
if (message.mode <= 3) {
|
||
uni.setStorageSync("blue-team", message.teams[1].players || []);
|
||
uni.setStorageSync("red-team", message.teams[2].players || []);
|
||
uni.redirectTo({
|
||
url: "/pages/team-battle?battleId=" + message.matchId,
|
||
});
|
||
} else {
|
||
uni.redirectTo({
|
||
url: "/pages/melee-battle?battleId=" + message.matchId,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
const chooseTeam = async (team) => {
|
||
const result = await chooseTeamAPI(roomNumber.value, team);
|
||
refreshMembers(result.members);
|
||
};
|
||
|
||
const destroyRoom = async () => {
|
||
if (roomNumber.value) await destroyRoomAPI(roomNumber.value);
|
||
};
|
||
|
||
const exitRoom = async () => {
|
||
uni.navigateBack();
|
||
};
|
||
|
||
/** 待确认踢出的玩家信息 */
|
||
const playerToKick = ref(null);
|
||
/** 控制踢出确认弹窗的显示状态 */
|
||
const showKickConfirm = ref(false);
|
||
|
||
/**
|
||
* 点击踢出按钮,弹出二次确认弹窗
|
||
* @param {object} player - 被踢的玩家信息
|
||
*/
|
||
const removePlayer = (player) => {
|
||
playerToKick.value = player;
|
||
showKickConfirm.value = true;
|
||
};
|
||
|
||
/** 确认踢出:调用 API 并关闭弹窗 */
|
||
const confirmKick = async () => {
|
||
if (!playerToKick.value) return;
|
||
await kickPlayerAPI(roomNumber.value, playerToKick.value.id);
|
||
showKickConfirm.value = false;
|
||
playerToKick.value = null;
|
||
};
|
||
|
||
/** 取消踢出:关闭弹窗 */
|
||
const cancelKick = () => {
|
||
showKickConfirm.value = false;
|
||
playerToKick.value = null;
|
||
};
|
||
|
||
const canClick = computed(() => {
|
||
if (ready.value) return false;
|
||
const { members = [] } = room.value;
|
||
if (members.length < 2) return false;
|
||
if (
|
||
owner.value.id === user.value.id &&
|
||
members.some((m) => !m.userInfo.state && m.userInfo.id !== owner.value.id)
|
||
)
|
||
return false;
|
||
return true;
|
||
});
|
||
|
||
/**
|
||
* 根据对战类型和人数动态生成分享文案
|
||
* 1v1 / 默认 → "星球论箭,来一决高下敢否?"
|
||
* 2v2 → "2v2对抗赛,是兄弟来助我一把!"
|
||
* 3v3 → "3v3对抗赛,来了马上发车!"
|
||
* 乱斗 → "热血乱斗赛,敢来争锋?"
|
||
*/
|
||
const shareTitle = computed(() => {
|
||
const { battleType, count } = room.value;
|
||
if (battleType === 2) return '热血乱斗赛,敢来争锋?';
|
||
if (battleType === 1 && count === 4) return '2v2对抗赛,是兄弟来助我一把!';
|
||
if (battleType === 1 && count === 6) return '3v3对抗赛,来了马上发车!';
|
||
return '星球论箭,来一决高下敢否?';
|
||
});
|
||
|
||
/**
|
||
* 根据对战类型动态返回分享封面图路径
|
||
* battleType === 2(多人乱斗)→ melee_share.jpg
|
||
* 其余(好友约战 / 组队对战)→ contest_share.jpg
|
||
*
|
||
* 网络图片:
|
||
* https://static.shelingxingqiu.com/shootmini/static/share/melee_share.jpg
|
||
* https://static.shelingxingqiu.com/shootmini/static/share/contest_share.jpg
|
||
*/
|
||
const shareImage = computed(() => {
|
||
if (room.value.battleType === 2) {
|
||
return 'https://static.shelingxingqiu.com/shootmini/static/share/melee_share.jpg';
|
||
}
|
||
return 'https://static.shelingxingqiu.com/shootmini/static/share/contest_share.jpg';
|
||
});
|
||
|
||
onShareAppMessage(() => {
|
||
return {
|
||
title: shareTitle.value,
|
||
path: "/pages/friend-battle?roomID=" + roomNumber.value,
|
||
imageUrl: shareImage.value,
|
||
};
|
||
});
|
||
|
||
/**
|
||
* onShow 生命周期:页面显示时刷新房间数据
|
||
* 注:uni-app 中 onShow 可能早于 onLoad 执行,此时 roomNumber 尚未赋值,
|
||
* refreshRoomData 会提前 return。
|
||
*/
|
||
onShow(() => {
|
||
goBattle.value = false;
|
||
refreshRoomData();
|
||
});
|
||
|
||
/**
|
||
* 页面加载时解析路由参数,初始化房间号
|
||
* - 存入本地 ref(供页面内部逻辑使用)
|
||
* - 同步到 Pinia Store(供 Header 组件展示房号胶囊)
|
||
*/
|
||
onLoad(async (options) => {
|
||
if (options.roomNumber) {
|
||
roomNumber.value = options.roomNumber;
|
||
store.updateRoomNumber(options.roomNumber);
|
||
}
|
||
// 创建者通过 URL 参数 target(1→20cm,2→40cm)初始化靶纸尺寸,并持久化到本地缓存
|
||
if (options.target) {
|
||
targetSize.value = parseInt(options.target) * 20;
|
||
uni.setStorageSync(`targetSize_${roomNumber.value}`, targetSize.value);
|
||
} else if (roomNumber.value) {
|
||
// "返回房间"等无 target 参数的场景:从本地缓存恢复(待 refreshRoomData 进一步覆盖)
|
||
const stored = uni.getStorageSync(`targetSize_${roomNumber.value}`);
|
||
if (stored) targetSize.value = stored;
|
||
}
|
||
});
|
||
|
||
/**
|
||
* 组件挂载后:保持屏幕常亮、注册 WebSocket 消息监听
|
||
* 房间号已通过 onLoad 同步到 Store,Header 从 Store 直接读取,无需额外通知
|
||
*/
|
||
onMounted(() => {
|
||
uni.setKeepScreenOn({
|
||
keepScreenOn: true,
|
||
});
|
||
uni.$on("socket-inbox", onReceiveMessage);
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
uni.setKeepScreenOn({
|
||
keepScreenOn: false,
|
||
});
|
||
uni.$off("socket-inbox", onReceiveMessage);
|
||
if (!goBattle.value) exitRoomAPI(roomNumber.value);
|
||
if (timer.value) clearInterval(timer.value);
|
||
timer.value = null;
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<Container :title="battleTitle">
|
||
<view class="standby-phase">
|
||
<GuideTwo>
|
||
<view class="battle-guide">
|
||
<view class="guide-tips">
|
||
<text class="guide-tips__target">请使用{{ targetSizeLabel }}全环靶</text>
|
||
<text class="guide-tips__warn">如果实际靶纸与选择靶纸不同,将导致射箭无效</text>
|
||
</view>
|
||
</view>
|
||
</GuideTwo>
|
||
<view v-if="room.battleType === 1 && room.count === 2" class="team-mode">
|
||
<image src="https://static.shelingxingqiu.com/attachment/2025-08-05/dbua9nuf5fyeph7cxi.png" mode="widthFix" />
|
||
<view>
|
||
<view v-if="owner.id" class="player" :style="{ transform: 'translateY(-60px)' }">
|
||
<Avatar :src="owner.avatar" :size="60" />
|
||
<text>管理员</text>
|
||
<text>{{ owner.name }}</text>
|
||
</view>
|
||
<view v-else class="no-player" :style="{ transform: 'translateY(-60px)' }">
|
||
<image src="../static/question-mark.png" mode="widthFix" />
|
||
</view>
|
||
<image src="../static/versus.png" mode="widthFix" />
|
||
<view v-if="opponent.id" class="player" :style="{ transform: 'translateY(60px)' }">
|
||
<Avatar :src="opponent.avatar" :size="60" />
|
||
<text class="ready" :style="{ opacity: opponent.ready ? 1 : 0 }">
|
||
已准备
|
||
</text>
|
||
<text>{{ opponent.name }}</text>
|
||
<button v-if="owner.id === user.id" hover-class="none" class="remove-player"
|
||
@click="() => removePlayer(opponent)">
|
||
<image src="../static/close-white.png" mode="widthFix" />
|
||
</button>
|
||
</view>
|
||
<view class="no-player" v-else>
|
||
<image src="../static/question-mark.png" mode="widthFix" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<block v-if="room.battleType === 1 && room.count >= 4">
|
||
<view class="all-players">
|
||
<image src="https://static.shelingxingqiu.com/attachment/2025-08-13/dc0x1p59iab6cvbhqc.png" mode="widthFix" />
|
||
<image v-if="room.count === 4" src="../static/title-2v2.png" mode="widthFix" />
|
||
<image v-if="room.count === 6" src="../static/title-3v3.png" mode="widthFix" />
|
||
<view>
|
||
<view v-for="(item, index) in players" :key="index">
|
||
<Avatar v-if="item.id" :src="item.avatar" :size="36" />
|
||
<text v-if="owner.id === item.id">管理员</text>
|
||
<!-- 仅房主可见踢人按钮,且不能踢自己 -->
|
||
<button v-if="owner.id !== item.id && item.id && owner.id === user.id" hover-class="none" class="remove-player"
|
||
@click="() => removePlayer(item)" :style="{ top: '-10rpx', right: '-10rpx' }">
|
||
<image src="../static/close-white.png" mode="widthFix" />
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="choose-side">
|
||
<view>
|
||
<view v-for="(item, index) in redTeam" :key="index" class="choose-side-left-item">
|
||
<button hover-class="none" v-if="item.id === user.id" @click="chooseTeam(0)">
|
||
<image src="../static/close-grey.png" mode="widthFix" />
|
||
</button>
|
||
<text class="truncate">{{ item.name || "我要加入" }}</text>
|
||
<view v-if="item.id">
|
||
<Avatar :src="item.avatar" :size="36" />
|
||
<text :style="{ opacity: !!item.state ? 1 : 0 }">已准备</text>
|
||
</view>
|
||
<button v-else hover-class="none" @click="chooseTeam(2)">
|
||
<image src="../static/add-grey.png" mode="widthFix" />
|
||
</button>
|
||
</view>
|
||
</view>
|
||
<view>
|
||
<view v-for="(item, index) in blueTeam" :key="index" class="choose-side-right-item">
|
||
<view v-if="item.id">
|
||
<Avatar :src="item.avatar" :size="36" />
|
||
<text :style="{ opacity: !!item.state ? 1 : 0 }">已准备</text>
|
||
</view>
|
||
<button v-else hover-class="none" @click="chooseTeam(1)">
|
||
<image src="../static/add-grey.png" mode="widthFix" />
|
||
</button>
|
||
<text class="truncate">{{ item.name || "我要加入" }}</text>
|
||
<button hover-class="none" v-if="item.id === user.id" @click="chooseTeam(0)">
|
||
<image src="../static/close-grey.png" mode="widthFix" />
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
<!-- isOwner:当前用户是房主时才展示踢人按钮 -->
|
||
<PlayerSeats v-if="room.battleType === 2" :total="room.count || 10" :players="players"
|
||
:removePlayer="removePlayer" :isOwner="owner.id === user.id" />
|
||
<view>
|
||
<SButton :disabled="!canClick" :onClick="getReady">
|
||
{{
|
||
allReady.value
|
||
? "即将进入对局..."
|
||
: owner.id === user.id
|
||
? "开始对局"
|
||
: "我准备好了"
|
||
}}
|
||
</SButton>
|
||
<text class="tips">所有人准备好后由房主点击开始</text>
|
||
</view>
|
||
</view>
|
||
</Container>
|
||
<!-- 踢出玩家二次确认弹窗(不传 onClose,屏蔽 X 关闭按钮) -->
|
||
<ScreenHint :show="showKickConfirm">
|
||
<view class="kick-confirm">
|
||
<!-- 弹窗标题,玩家名字高亮显示 -->
|
||
<text class="kick-confirm__title">
|
||
是否把「<text style="color: #F7D550">{{ playerToKick && playerToKick.name }}</text>」移出房间?
|
||
</text>
|
||
<!-- 按钮区:确认在左,取消在右 -->
|
||
<view class="kick-confirm__btns">
|
||
<button hover-class="none" class="kick-confirm__btn kick-confirm__btn--confirm" @click="confirmKick">确定</button>
|
||
<button hover-class="none" class="kick-confirm__btn kick-confirm__btn--cancel" @click="cancelKick">取消</button>
|
||
</view>
|
||
</view>
|
||
</ScreenHint>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.standby-phase {
|
||
width: 100%;
|
||
height: calc(100% - 40px);
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.tips {
|
||
color: #fff9;
|
||
width: 100%;
|
||
text-align: center;
|
||
display: block;
|
||
margin-top: 10px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.player-unknow {
|
||
width: 40px;
|
||
height: 40px;
|
||
margin: 0 10px;
|
||
border: 1px solid #fff3;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #69686866;
|
||
}
|
||
|
||
.player-unknow>image {
|
||
width: 40%;
|
||
}
|
||
|
||
.team-mode {
|
||
width: calc(100vw - 30px);
|
||
height: 125vw;
|
||
margin: 0 15px 15px 15px;
|
||
}
|
||
|
||
.team-mode>image:first-child {
|
||
position: absolute;
|
||
width: calc(100vw - 30px);
|
||
z-index: -1;
|
||
}
|
||
|
||
.team-mode>view {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 95%;
|
||
}
|
||
|
||
.player {
|
||
width: 70px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
transform: translateY(-60px);
|
||
color: #fff;
|
||
font-size: 14px;
|
||
position: relative;
|
||
}
|
||
|
||
.player>image {
|
||
width: 70px;
|
||
height: 70px;
|
||
border-radius: 50%;
|
||
background-color: #ccc;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.player>text:nth-child(2) {
|
||
color: #000;
|
||
background-color: #fed847;
|
||
font-size: 16rpx;
|
||
border-radius: 20rpx;
|
||
line-height: 26rpx;
|
||
padding: 0 10rpx;
|
||
margin-top: -16rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.player>text:nth-child(3) {
|
||
margin-top: 6rpx;
|
||
}
|
||
|
||
.player>text {
|
||
max-width: 100px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
text-align: center;
|
||
}
|
||
|
||
.player .ready {
|
||
background-color: #2c261fb3 !important;
|
||
border: 1rpx solid #a3793f66 !important;
|
||
color: #fed847 !important;
|
||
}
|
||
|
||
/* 踢出确认弹窗内容 */
|
||
.kick-confirm {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20rpx 30rpx;
|
||
color: #fff;
|
||
}
|
||
.kick-confirm__title {
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
text-align: center;
|
||
line-height: 1.5;
|
||
/* 标题与按钮区间距 */
|
||
margin-bottom: 58rpx;
|
||
}
|
||
.kick-confirm__btns {
|
||
display: flex;
|
||
/* 两个按钮间距 */
|
||
gap: 16rpx;
|
||
}
|
||
/* 按钮公共基础样式:固定宽高与圆角 */
|
||
.kick-confirm__btn {
|
||
font-size: 28rpx;
|
||
width: 232rpx;
|
||
height: 70rpx;
|
||
border-radius: 44rpx;
|
||
line-height: 70rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
/* 取消按钮:白色半透明背景(用 rgba 避免 opacity 平铺到文字) */
|
||
.kick-confirm__btn--cancel {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border: none;
|
||
color: #FFFFFF;
|
||
font-weight: 500;
|
||
font-size: 28rpx;
|
||
text-align: center;
|
||
border-radius: 44rpx;
|
||
}
|
||
.kick-confirm__btn--cancel::after {
|
||
border: none;
|
||
}
|
||
/* 确认按钮:黄色实心背景 */
|
||
.kick-confirm__btn--confirm {
|
||
background: #FED847;
|
||
border: none;
|
||
color: #000000;
|
||
font-weight: 500;
|
||
font-size: 28rpx;
|
||
text-align: center;
|
||
border-radius: 44rpx;
|
||
}
|
||
.kick-confirm__btn--confirm::after {
|
||
border: none;
|
||
}
|
||
|
||
.remove-player {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background: linear-gradient(0deg, #996c29b8 0%, #201e1aaf 100%);
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
}
|
||
|
||
.remove-player>image {
|
||
width: 90%;
|
||
height: 90%;
|
||
}
|
||
|
||
.team-mode>view>image:nth-child(2) {
|
||
width: 120px;
|
||
}
|
||
|
||
.no-player {
|
||
width: 70px;
|
||
height: 70px;
|
||
border-radius: 50%;
|
||
background-color: #ccc;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
transform: translateY(60px);
|
||
}
|
||
|
||
.no-player>image {
|
||
width: 20px;
|
||
margin-right: 2px;
|
||
}
|
||
|
||
.btns {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.battle-guide {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.battle-guide>button:last-child {
|
||
color: #fed847;
|
||
border: 1px solid #fed847;
|
||
margin-right: 10px;
|
||
padding: 5px 12px;
|
||
border-radius: 20px;
|
||
position: relative;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.all-players {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
height: 83vw;
|
||
}
|
||
|
||
.all-players>image:first-child {
|
||
position: absolute;
|
||
width: 100%;
|
||
}
|
||
|
||
.all-players>image:nth-child(2) {
|
||
width: 25vw;
|
||
position: relative;
|
||
}
|
||
|
||
.all-players>view {
|
||
position: relative;
|
||
width: 42vw;
|
||
height: 42vw;
|
||
margin-top: 7vw;
|
||
}
|
||
|
||
.all-players>view>view {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
}
|
||
|
||
/* 4个头像 - 正方形排列 */
|
||
.all-players>view>view:nth-child(1):nth-last-child(4) {
|
||
transform: translate(-50%, -50%) rotate(-30deg) translateY(-21.5vw) rotate(30deg);
|
||
}
|
||
|
||
.all-players>view>view:nth-child(2):nth-last-child(3) {
|
||
transform: translate(-50%, -50%) rotate(-120deg) translateY(-21.5vw) rotate(120deg);
|
||
}
|
||
|
||
.all-players>view>view:nth-child(3):nth-last-child(2) {
|
||
transform: translate(-50%, -50%) rotate(-210deg) translateY(-21.5vw) rotate(210deg);
|
||
}
|
||
|
||
.all-players>view>view:nth-child(4):nth-last-child(1) {
|
||
transform: translate(-50%, -50%) rotate(-300deg) translateY(-21.5vw) rotate(300deg);
|
||
}
|
||
|
||
/* 6个头像 - 六边形排列 */
|
||
.all-players>view>view:nth-child(1):nth-last-child(6) {
|
||
transform: translate(-50%, -50%) rotate(-30deg) translateY(-21vw) rotate(30deg);
|
||
}
|
||
|
||
.all-players>view>view:nth-child(2):nth-last-child(5) {
|
||
transform: translate(-50%, -50%) rotate(-90deg) translateY(-21vw) rotate(90deg);
|
||
}
|
||
|
||
.all-players>view>view:nth-child(3):nth-last-child(4) {
|
||
transform: translate(-50%, -50%) rotate(-150deg) translateY(-21vw) rotate(150deg);
|
||
}
|
||
|
||
.all-players>view>view:nth-child(4):nth-last-child(3) {
|
||
transform: translate(-50%, -50%) rotate(-210deg) translateY(-21vw) rotate(210deg);
|
||
}
|
||
|
||
.all-players>view>view:nth-child(5):nth-last-child(2) {
|
||
transform: translate(-50%, -50%) rotate(-270deg) translateY(-21vw) rotate(270deg);
|
||
}
|
||
|
||
.all-players>view>view:nth-child(6):nth-last-child(1) {
|
||
transform: translate(-50%, -50%) rotate(-330deg) translateY(-21vw) rotate(330deg);
|
||
}
|
||
|
||
.all-players>view>view>text {
|
||
position: absolute;
|
||
background-color: #fed847;
|
||
font-size: 8px;
|
||
border-radius: 10px;
|
||
padding: 1px 0px;
|
||
bottom: -20%;
|
||
left: calc(50% - 15px);
|
||
width: 30px;
|
||
text-align: center;
|
||
color: #333;
|
||
}
|
||
|
||
.choose-side {
|
||
display: flex;
|
||
}
|
||
|
||
.choose-side>view {
|
||
width: 50%;
|
||
}
|
||
|
||
.choose-side>view:first-child>view {
|
||
background: linear-gradient(270deg, #6a1212 0%, rgba(74, 0, 0, 0) 100%);
|
||
}
|
||
|
||
.choose-side>view:last-child>view {
|
||
background: linear-gradient(270deg, rgba(13, 0, 74, 0) 0%, #172a86 100%);
|
||
}
|
||
|
||
.choose-side-left-item,
|
||
.choose-side-right-item {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #fff;
|
||
border-radius: 12px;
|
||
padding: 10px;
|
||
align-items: center;
|
||
margin: 10px 5px;
|
||
position: relative;
|
||
}
|
||
|
||
.choose-side-left-item {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.choose-side-left-item>text,
|
||
.choose-side-right-item>text {
|
||
margin: 10px;
|
||
max-width: 100px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.choose-side-left-item>button:first-child,
|
||
.choose-side-right-item>button:last-child {
|
||
position: absolute;
|
||
top: 0;
|
||
}
|
||
|
||
.choose-side-left-item>button:first-child>image,
|
||
.choose-side-right-item>button:last-child>image {
|
||
width: 28px;
|
||
}
|
||
|
||
.choose-side-left-item>button:first-child {
|
||
left: 0;
|
||
}
|
||
|
||
.choose-side-right-item>button:last-child {
|
||
right: 0;
|
||
}
|
||
|
||
.choose-side-left-item>button:last-child,
|
||
.choose-side-right-item>button:first-child {
|
||
background-color: #fff;
|
||
border-radius: 50%;
|
||
width: 38px;
|
||
height: 38px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin-bottom: 14rpx;
|
||
}
|
||
|
||
.choose-side-left-item>button:last-child>image,
|
||
.choose-side-right-item>button:first-child>image {
|
||
width: 18px;
|
||
}
|
||
|
||
.choose-side-left-item>view,
|
||
.choose-side-right-item>view {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.choose-side-left-item>view>text,
|
||
.choose-side-right-item>view>text {
|
||
font-size: 16rpx;
|
||
border-radius: 20rpx;
|
||
line-height: 26rpx;
|
||
padding: 0 10rpx;
|
||
margin-top: -16rpx;
|
||
position: relative;
|
||
background-color: #2c261fb3;
|
||
border: 1rpx solid #a3793f66;
|
||
color: #fed847;
|
||
}
|
||
|
||
.guide-tips__target {
|
||
font-weight: 400;
|
||
font-size: 26rpx;
|
||
color: rgba(255, 217, 71, 0.8);
|
||
}
|
||
|
||
.guide-tips__warn {
|
||
font-weight: 400;
|
||
font-size: 22rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
margin-top: 6rpx;
|
||
}
|
||
|
||
.guide-tips {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-left: 20rpx;
|
||
}
|
||
</style>
|