Commit b5e67f80 authored by 陈冲's avatar 陈冲

feat: 更新打地鼠

parent 298716ce
...@@ -55,7 +55,7 @@ struct RankingPlayer { ...@@ -55,7 +55,7 @@ struct RankingPlayer {
enum RoomStatus { enum RoomStatus {
#[default] #[default]
Closed = 0, //关闭 Closed = 0, //关闭
Start, //开始 Start, //开始排队
Running, //进行 Running, //进行
Submit, //提交结果 Submit, //提交结果
Ended, //结束 Ended, //结束
...@@ -65,6 +65,7 @@ enum RoomStatus { ...@@ -65,6 +65,7 @@ enum RoomStatus {
struct RoomState { struct RoomState {
status: RoomStatus, status: RoomStatus,
players: HashMap<String, RoomPlayer>, // 玩家列表,key 是 userid players: HashMap<String, RoomPlayer>, // 玩家列表,key 是 userid
next_entry_order: usize,
} }
impl RoomState { impl RoomState {
...@@ -106,7 +107,7 @@ impl RoomState { ...@@ -106,7 +107,7 @@ impl RoomState {
// 房间开始进入排行 // 房间开始进入排行
fn entry_ranking(&self) -> Vec<RankingPlayer> { fn entry_ranking(&self) -> Vec<RankingPlayer> {
let mut ranking: Vec<(&String, &RoomPlayer)> = self.players.iter().collect(); let mut ranking: Vec<(&String, &RoomPlayer)> = self.players.iter().collect();
ranking.sort_by(|(_, a), (_, b)| a.entry_order.cmp(&b.entry_order)); ranking.sort_by(|(_, a), (_, b)| b.entry_order.cmp(&a.entry_order));
ranking ranking
.into_iter() .into_iter()
.take(ROOM_ENTRY_LIMIT) .take(ROOM_ENTRY_LIMIT)
...@@ -159,20 +160,10 @@ impl RoomState { ...@@ -159,20 +160,10 @@ impl RoomState {
// }) // })
// } // }
fn set_player_online(&mut self, userid: &str, online: bool) -> bool {
let Some(player) = self.players.get_mut(userid) else {
return false;
};
if player.online == online {
return false;
}
player.online = online;
true
}
fn reset(&mut self) { fn reset(&mut self) {
self.status = RoomStatus::Closed; self.status = RoomStatus::Closed;
self.players.clear(); self.players.clear();
self.next_entry_order = 0;
} }
} }
...@@ -287,22 +278,74 @@ fn broadcast_room_rank(ranking: Vec<Value>) { ...@@ -287,22 +278,74 @@ fn broadcast_room_rank(ranking: Vec<Value>) {
broadcast_msgpack_all_admin("room_rank_result", &ok_resp(Some(json!(ranking)))); broadcast_msgpack_all_admin("room_rank_result", &ok_resp(Some(json!(ranking))));
} }
async fn sleep_room_status(round_id: u64, secs: u64, expected_status: RoomStatus) -> bool {
for _ in 0..secs {
tokio::time::sleep(Duration::from_secs(1)).await;
if GAME_ROUND_ID.load(Ordering::SeqCst) != round_id {
return false;
}
if let Ok(state) = ROOM_STATE.lock() {
if state.status != expected_status {
return false;
}
}
}
true
}
fn spawn_game_timeout(round_id: u64, secs: u64) { fn spawn_game_timeout(round_id: u64, secs: u64) {
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(secs)).await; let start_countdown_secs = 3;
let result_wait_secs = 2;
let running_secs = secs.saturating_sub(start_countdown_secs + result_wait_secs);
if GAME_ROUND_ID.load(Ordering::SeqCst) != round_id { if !sleep_room_status(round_id, start_countdown_secs, RoomStatus::Running).await {
return;
}
if !sleep_room_status(round_id, running_secs, RoomStatus::Running).await {
return; return;
} }
if let Ok(mut state) = ROOM_STATE.lock() { if let Ok(mut state) = ROOM_STATE.lock() {
if state.status == RoomStatus::Running {
state.status = RoomStatus::Submit; state.status = RoomStatus::Submit;
// dbg!("submit"); } else {
drop(state); return;
}
}
if !sleep_room_status(round_id, result_wait_secs, RoomStatus::Submit).await {
return;
}
if let Ok(mut state) = ROOM_STATE.lock() {
if state.status == RoomStatus::Submit {
state.status = RoomStatus::Ended;
}
} }
}); });
} }
// fn spawn_game_timeout(round_id: u64, secs: u64) {
// tokio::spawn(async move {
// tokio::time::sleep(Duration::from_secs(secs)).await;
// if GAME_ROUND_ID.load(Ordering::SeqCst) != round_id {
// return;
// }
// if let Ok(mut state) = ROOM_STATE.lock() {
// state.status = RoomStatus::Submit;
// // dbg!("submit");
// drop(state);
// }
// });
// }
// fn current_room_snapshot() -> Value { // fn current_room_snapshot() -> Value {
// if let Ok(state) = ROOM_STATE.lock() { // if let Ok(state) = ROOM_STATE.lock() {
// let snapshot = state.snapshot(); // let snapshot = state.snapshot();
...@@ -389,7 +432,8 @@ async fn handle_room_command( ...@@ -389,7 +432,8 @@ async fn handle_room_command(
); );
return; return;
} }
let entry_order = state.players.len(); let entry_order = state.next_entry_order;
state.next_entry_order += 1;
state state
.players .players
...@@ -444,8 +488,8 @@ async fn handle_room_command( ...@@ -444,8 +488,8 @@ async fn handle_room_command(
// let snapshot = state.snapshot(); // let snapshot = state.snapshot();
drop(state); drop(state);
//通知所有玩家倒计时60秒,实际上还有3秒倒计时,所以是63 //通知所有玩家倒计时60秒,实际上还有3秒倒计时,以前2秒统计结果,所以是65
spawn_game_timeout(round_id, 63); spawn_game_timeout(round_id, 65);
broadcast_msgpack_all_player(evt, &ok_resp(Some(json!(game_index)))); broadcast_msgpack_all_player(evt, &ok_resp(Some(json!(game_index))));
//通知管理员 //通知管理员
emit_msgpack(&socket, evt, &ok_resp(Some(json!(game_index)))); emit_msgpack(&socket, evt, &ok_resp(Some(json!(game_index))));
...@@ -688,32 +732,27 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value ...@@ -688,32 +732,27 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
// 前端页面刷新时房间状态丢失体验会不好 // 前端页面刷新时房间状态丢失体验会不好
if CLIENT_ADMIN_MAP.is_empty() { if CLIENT_ADMIN_MAP.is_empty() {
if let Ok(mut state) = ROOM_STATE.lock() { if let Ok(mut state) = ROOM_STATE.lock() {
state.reset(); state.reset(); // 如果管理员都退出了,就把房间状态重置。
drop(state);
} }
// 如果管理员都退出了,就把房间状态重置。
} }
} else { } else {
CLIENT_PLAYER_MAP.remove(&userid); CLIENT_PLAYER_MAP.remove(&userid);
let ranking = ROOM_STATE.lock().ok().and_then(|mut state| { if let Ok(mut state) = ROOM_STATE.lock() {
if state.set_player_online(&userid, false) { if state.status != RoomStatus::Start {
Some(state.current_ranking_payload()) return;
} else { }
None if state.players.remove(&userid).is_none() {
return;
}
let count = state.players.len();
let ranking = state.current_ranking_payload();
drop(state);
broadcast_msgpack_all_admin(
"room_join_result",
&ok_resp(Some(json!({"list":ranking,"count":count}))),
);
} }
});
// // 有玩家退出时通知所有管理员。(暂时不管了)
// broadcast_msgpack_all_admin(
// evt,
// &ok_resp(Some(json!({"list":ranking,"count":count}))),
// )
// broadcast_msgpack_all_admin(
// "room_state_result",
// &ok_resp(Some(current_room_snapshot())),
// );
// if let Some(ranking) = ranking {
// broadcast_room_rank(ranking);
// }
} }
} }
} }
......
...@@ -17,7 +17,7 @@ const imageUrls: Record<string, string> = { ...@@ -17,7 +17,7 @@ const imageUrls: Record<string, string> = {
loadingBg1: cssAssetUrl('game6/loadingbg1.png'), loadingBg1: cssAssetUrl('game6/loadingbg1.png'),
loadingBg2: cssAssetUrl('game6/loadingbg2.png'), loadingBg2: cssAssetUrl('game6/loadingbg2.png'),
close: cssAssetUrl('game1/close.png'), close: cssAssetUrl('game1/close.png'),
clock: cssAssetUrl('game1/clock.png'),
bg2: cssAssetUrl('game6/bg2.png'), bg2: cssAssetUrl('game6/bg2.png'),
hydt: cssAssetUrl('game6/hydt.png'), hydt: cssAssetUrl('game6/hydt.png'),
rank: cssAssetUrl('game6/rank.png'), rank: cssAssetUrl('game6/rank.png'),
...@@ -219,11 +219,17 @@ onBeforeUnmount(() => { ...@@ -219,11 +219,17 @@ onBeforeUnmount(() => {
const showGameRuleHandler = () => { const showGameRuleHandler = () => {
showGameRule.value = !showGameRule.value; showGameRule.value = !showGameRule.value;
} }
const renderBG = () => {
if (currentView.value === 'loading') {
return `${imageUrls.bg} center/cover`;
}
return `${imageUrls.bg2} center/cover`
}
</script> </script>
<template> <template>
<MobileStage v-if="token" :showGameRule="showGameRule" ref="mobileStageRef" <MobileStage v-if="token" :showGameRule="showGameRule" ref="mobileStageRef" :background="`${renderBG()}`">
:background="`${imageUrls.bg2} center/cover`">
<template #gameRule> <template #gameRule>
<div class="rule-container"> <div class="rule-container">
<div class="rule-txt-container"> <div class="rule-txt-container">
...@@ -237,15 +243,12 @@ const showGameRuleHandler = () => { ...@@ -237,15 +243,12 @@ const showGameRuleHandler = () => {
</div> </div>
</div> </div>
</template> </template>
<PlayingView :image-urls="imageUrls" :tick="score" :rank="rank" :countdown-interval="countdownInterval" <LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus"
:is-div-desc-visible="isDivDescVisible" @touch="touchHandler" />
<!-- <LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus"
@touchGameRule="showGameRuleHandler" /> @touchGameRule="showGameRuleHandler" />
<PlayingView v-else-if="currentView === 'playing'" :image-urls="imageUrls" :tick="score" :rank="rank" <PlayingView v-else-if="currentView === 'playing'" :image-urls="imageUrls" :tick="score" :rank="rank"
:current-gu="currentGu" :countdown-interval="countdownInterval" :is-div-desc-visible="isDivDescVisible" :countdown-interval="countdownInterval" :is-div-desc-visible="isDivDescVisible" @touch="touchHandler" />
@touch="touchHandler" />
<ScoreView v-else :image-urls="imageUrls" :rank="rank" :tick="score" @replay="replayGame" <ScoreView v-else :image-urls="imageUrls" :rank="rank" :tick="score" @replay="replayGame"
@back="backToWaiting" /> --> @back="backToWaiting" />
</MobileStage> </MobileStage>
<div v-else style="text-align: center; width: 100vw; height: 100vh; line-height: 30; font-size: 20px;"> <div v-else style="text-align: center; width: 100vw; height: 100vh; line-height: 30; font-size: 20px;">
请使用微信扫码进入游戏 请使用微信扫码进入游戏
......
...@@ -69,13 +69,12 @@ onMounted(() => { ...@@ -69,13 +69,12 @@ onMounted(() => {
.txt-bottom { .txt-bottom {
position: absolute; position: absolute;
bottom: 100px; bottom: 130px;
width: 100%; width: 100%;
line-height: 56px; line-height: 40px;
color: white; color: white;
font-size: 36px; font-size: 28px;
text-align: center; text-align: center;
animation: loading-text-scale 1.2s ease-in-out infinite;
} }
.game-stage { .game-stage {
...@@ -168,24 +167,6 @@ onMounted(() => { ...@@ -168,24 +167,6 @@ onMounted(() => {
transform: translateX(-50%); transform: translateX(-50%);
} }
.txt-loading label {
display: inline-block;
transform-origin: center;
animation: loading-text-scale 1.2s ease-in-out infinite;
}
@keyframes loading-text-scale {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.08);
}
}
.loading-gu { .loading-gu {
position: absolute; position: absolute;
bottom: 100px; bottom: 100px;
......
This diff is collapsed.
This diff is collapsed.
export default interface Player { export default interface Player {
active?: boolean; active?: boolean;
score?: number; score?: number | string;
telephone?: string; telephone?: string;
userid?: string; userid?: string;
wechat_id?: string; wechat_id?: string;
......
...@@ -15,7 +15,6 @@ type AdminGameSocketOptions = { ...@@ -15,7 +15,6 @@ type AdminGameSocketOptions = {
onRoomState?: (data: any) => void onRoomState?: (data: any) => void
onRoomJoin?: (data: any) => void onRoomJoin?: (data: any) => void
onGameStart?: (data: any) => void | Promise<void> onGameStart?: (data: any) => void | Promise<void>
onGameStartRejected?: (data: any) => void
onRoomRank?: (data: any) => void onRoomRank?: (data: any) => void
onRelogin?: () => void onRelogin?: () => void
} }
...@@ -46,7 +45,7 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -46,7 +45,7 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
const { passphrase, token, socket_path, client_ns, domain } = $read_socket_storge() const { passphrase, token, socket_path, client_ns, domain } = $read_socket_storge()
initSocket({ initSocket({
socket_path: `${domain}${socket_path}`, socket_path: `${domain}${socket_path}`,
client_ns: "/ws", client_ns,
auth: { auth: {
userid: token, userid: token,
validuser: CryptoJS.AES.encrypt(token, passphrase).toString(), validuser: CryptoJS.AES.encrypt(token, passphrase).toString(),
...@@ -59,10 +58,9 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -59,10 +58,9 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
offSocketMessage = onSocketMessage(async (evt, payload: SocketPayload) => { offSocketMessage = onSocketMessage(async (evt, payload: SocketPayload) => {
const { msg, state, data } = payload const { msg, state, data } = payload
if (state === 0) { if (state === 0) {
if (evt === 'game_start_result' && data === 1) { // if (evt === 'game_start_result' && data === 1) {
options.onGameStartRejected?.(data) // return
return // }
}
if (evt == 'login_result') { if (evt == 'login_result') {
alert(msg) alert(msg)
options.onRelogin?.() options.onRelogin?.()
......
...@@ -17,7 +17,7 @@ const PLAYER_LIMIT = 20 ...@@ -17,7 +17,7 @@ const PLAYER_LIMIT = 20
const RANK_LIMIT = 10 const RANK_LIMIT = 10
const emptyLoadingPlayer = () => ({ const emptyLoadingPlayer = () => ({
score: 0, score: '-',
telephone: '', telephone: '',
userid: '', userid: '',
wechat_id: '', wechat_id: '',
...@@ -26,11 +26,10 @@ const emptyLoadingPlayer = () => ({ ...@@ -26,11 +26,10 @@ const emptyLoadingPlayer = () => ({
}) })
const emptyRankPlayer = (index: number): Player => ({ const emptyRankPlayer = (index: number): Player => ({
active: false,
wechat_id: `empty-${index}`, wechat_id: `empty-${index}`,
nickname: '虚位以待', nickname: '虚位以待',
avatar: '', avatar: '',
score: 0, score: '-',
}) })
const screen = ref<GameScreen>('loading') const screen = ref<GameScreen>('loading')
...@@ -64,13 +63,12 @@ const updateRankPlayers = (data: any) => { ...@@ -64,13 +63,12 @@ const updateRankPlayers = (data: any) => {
} }
const playerToRankPlayer = (player: any, index: number): Player => ({ const playerToRankPlayer = (player: any, index: number): Player => ({
active: player?.active ?? false,
telephone: player?.telephone ?? '', telephone: player?.telephone ?? '',
userid: player?.userid ?? '', userid: player?.userid ?? '',
wechat_id: player?.wechat_id || `empty-${index}`, wechat_id: player?.wechat_id || `empty-${index}`,
nickname: player?.nickname || '虚位以待', nickname: player?.nickname || '虚位以待',
avatar: player?.avatar || '', avatar: player?.avatar || '',
score: Number(player?.score ?? 0), score: player?.score ?? '-',
}) })
const seedRankPlayersFromLoading = () => { const seedRankPlayersFromLoading = () => {
...@@ -140,8 +138,6 @@ function handleRoomState(data: any) { ...@@ -140,8 +138,6 @@ function handleRoomState(data: any) {
const { startGame, createRoom, backRoom } = useAdminGameSocket({ const { startGame, createRoom, backRoom } = useAdminGameSocket({
gameId: 'game1', gameId: 'game1',
// loginRedirectPath: '/game1',
onGameStartRejected: gotoLoading,
onRoomState: handleRoomState, onRoomState: handleRoomState,
onRoomJoin: (data) => { onRoomJoin: (data) => {
renderPlayers(data) renderPlayers(data)
...@@ -156,6 +152,10 @@ const { startGame, createRoom, backRoom } = useAdminGameSocket({ ...@@ -156,6 +152,10 @@ const { startGame, createRoom, backRoom } = useAdminGameSocket({
}) })
const startGameWithMusic = () => { const startGameWithMusic = () => {
if (playerCount.value == 0) {
alert('房间中没有玩家')
return;
}
playGame1Music() playGame1Music()
startGame() startGame()
} }
......
...@@ -38,6 +38,13 @@ const pressStartButton = () => { ...@@ -38,6 +38,13 @@ const pressStartButton = () => {
const releaseStartButton = () => { const releaseStartButton = () => {
isStartPressed.value = false; isStartPressed.value = false;
}; };
const renderAvatar = (item: any | null) => {
if (item && item.avatar) {
return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;`
}
return `background: ${imageUrls.avatar} center / cover no-repeat;`
}
</script> </script>
<template> <template>
...@@ -57,7 +64,7 @@ const releaseStartButton = () => { ...@@ -57,7 +64,7 @@ const releaseStartButton = () => {
<div v-for="(item, index) in players" :key="item.userid || item.wechat_id || `empty-${index}`"> <div v-for="(item, index) in players" :key="item.userid || item.wechat_id || `empty-${index}`">
<div> <div>
<div class="img-avatar-container"> <div class="img-avatar-container">
<div class="img-avatar" :style="`background:url('${item.avatar}') center / cover no-repeat;`"></div> <div class="img-avatar" :style="`${renderAvatar(item.avatar)}`"></div>
</div> </div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div> <div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div>
</div> </div>
......
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import Loading from './views/LoadingView.vue'
import Rank1 from './views/Rank1View.vue'
import Rank2 from './views/Rank2View.vue'
import { useAdminGameSocket } from '@/composables/useAdminGameSocket'
import type Player from '@/commons/player.ts'
import { playGame1Music } from '@/commons/music'
import { $remove_socket_storage } from '@/commons/utils.ts'
const router = useRouter()
type GameScreen = 'loading' | 'rank1' | 'rank2'
const PLAYER_LIMIT = 20
const RANK_LIMIT = 10
const emptyLoadingPlayer = () => ({
score: '-',
telephone: '',
userid: '',
wechat_id: '',
nickname: '虚位以待',
avatar: '',
})
const emptyRankPlayer = (index: number): Player => ({
wechat_id: `empty-${index}`,
nickname: '虚位以待',
avatar: '',
score: '-',
})
const screen = ref<GameScreen>('loading')
const loadingPlayers = ref<any[]>(Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer))
const playerCount = ref(0)
const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)))
const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false
const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault()
event.returnValue = ''
}
const renderPlayers = (data: any) => {
const players = Array.isArray(data?.list) ? data.list : []
playerCount.value = Number(data?.count ?? players.length)
loadingPlayers.value = Array.from(
{ length: PLAYER_LIMIT },
(_, index) => players[index] ?? emptyLoadingPlayer(),
)
}
const updateRankPlayers = (data: any) => {
if (!Array.isArray(data)) return
rankPlayers.value = Array.from(
{ length: RANK_LIMIT },
(_, index) => data[index] ?? emptyRankPlayer(index),
)
}
const playerToRankPlayer = (player: any, index: number): Player => ({
telephone: player?.telephone ?? '',
userid: player?.userid ?? '',
wechat_id: player?.wechat_id || `empty-${index}`,
nickname: player?.nickname || '虚位以待',
avatar: player?.avatar || '',
score: player?.score ?? '-',
})
const seedRankPlayersFromLoading = () => {
rankPlayers.value = Array.from(
{ length: RANK_LIMIT },
(_, index) => {
const player = loadingPlayers.value[index]
if (!player?.userid && !player?.wechat_id) {
return emptyRankPlayer(index)
}
return playerToRankPlayer(player, index)
},
)
}
const resetRoundState = () => {
playerCount.value = 0
loadingPlayers.value = Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer)
rankPlayers.value = Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index))
isPlayingStartAnimation = false
}
const gotoLoading = (reset = false) => {
if (reset) {
resetRoundState()
}
screen.value = 'loading'
}
const gotoRank1WithIntro = async () => {
if (isPlayingStartAnimation) return
isPlayingStartAnimation = true
seedRankPlayersFromLoading()
screen.value = 'rank1'
await nextTick()
await rank1Ref.value?.startIntro()
screen.value = 'rank2'
isPlayingStartAnimation = false
}
function handleRoomState(data: any) {
if (Array.isArray(data?.list)) {
renderPlayers(data)
}
switch (data?.status) {
case 0:
gotoLoading(true)
createRoom(1)
break
case 1:
gotoLoading()
break
case 2:
seedRankPlayersFromLoading()
screen.value = 'rank1'
break
case 3:
screen.value = 'rank2'
break
}
}
const { startGame, createRoom, backRoom } = useAdminGameSocket({
gameId: 'game6',
onRoomState: handleRoomState,
onRoomJoin: (data) => {
renderPlayers(data)
gotoLoading()
},
onGameStart: gotoRank1WithIntro,
onRoomRank: updateRankPlayers,
onRelogin: async () => {
$remove_socket_storage();
await router.replace('/')
}
})
const startGameWithMusic = () => {
if (playerCount.value == 0) {
alert('房间中没有玩家')
return;
}
playGame1Music()
startGame()
}
const nextRound = () => {
gotoLoading(true)
//重开房间
backRoom(6)
}
const nextRoundWithMusic = () => {
playGame1Music()
nextRound()
}
onMounted(() => {
window.addEventListener('beforeunload', confirmRefresh)
playGame1Music()
})
onUnmounted(() => {
window.removeEventListener('beforeunload', confirmRefresh)
})
</script>
<template>
<Rank1 ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" />
<!-- <Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
@start="startGameWithMusic" />
<Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" />
<Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" /> -->
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { $format_str } from '@/commons/utils.ts';
import DesignStage from '@/components/DesignStage.vue'
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts';
defineProps<{
players: any[];
playerCount: number;
}>();
const emit = defineEmits<{
start: [];
}>();
const imageUrls = {
bg: cssAssetUrl('game6/bg.png'),
logo: cssAssetUrl('game1/logo.png'),
title1: cssAssetUrl('game6/title.png'),
title2: cssAssetUrl('game1/title2.png'),
qrcode: assetUrl('game6/qrcode.png'),
gu: cssAssetUrl('game1/gu.png'),
// bg: cssAssetUrl('game1/bg.png'),
// logo: cssAssetUrl('game1/logo.png'),
// title1: cssAssetUrl('game1/title1.png'),
// title2: cssAssetUrl('game1/title2.png'),
// qrcode: assetUrl('game1/qrcode.png'),
// gu: cssAssetUrl('game1/gu.png'),
imgMole:cssAssetUrl('game6/mole.png'),
imgRabbit:cssAssetUrl('game6/rabbit.png'),
bottomLayers: cssAssetUrl('game1/bottom-layers.png'),
avatarAnimation: cssAssetUrl('avatar_animation.png'),
avatar: cssAssetUrl('avatar.png'),
line: cssAssetUrl('game1/line.png'),
start1: cssAssetUrl('game1/start-1.png'),
start2: cssAssetUrl('game1/start-2.png'),
};
const isStartPressed = ref(false);
const pressStartButton = () => {
isStartPressed.value = true;
emit('start');
};
const releaseStartButton = () => {
isStartPressed.value = false;
};
const renderAvatar = (item: any | null) => {
if (item && item.avatar) {
return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;`
}
return `background: ${imageUrls.avatar} center / cover no-repeat;`
}
</script>
<template>
<DesignStage>
<div class="bg">
<div class="img-logo"></div>
<div class="img-title1"></div>
<!-- <div class="img-title2"></div> -->
<div class="img-qrcode-container">
<div class="img-qrcode"><img :src="imageUrls.qrcode" /></div>
<div class="txt-qrcode">微信扫码参与</div>
</div>
<div class="img-mole"></div>
<div class="img-rabbit"></div>
<div class="list-container">
<div class="txt-count">当前在线人数 <span>{{ playerCount }}</span></div>
<div class="list-container-left">
<div v-for="(item, index) in players" :key="item.userid || item.wechat_id || `empty-${index}`">
<div>
<div class="img-avatar-container">
<div class="img-avatar" :style="renderAvatar(item)"></div>
</div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div>
</div>
</div>
</div>
<div class="list-container-line"></div>
<div class="list-container-right">
<div
class="btn-start"
:class="{ pressed: isStartPressed }"
@pointerdown="pressStartButton"
@pointerup="releaseStartButton"
@pointerleave="releaseStartButton"
@pointercancel="releaseStartButton"
></div>
</div>
</div>
</div>
</DesignStage>
</template>
<style scoped>
.bg {
width: 1920px;
height: 1080px;
background: v-bind('imageUrls.bg') center / cover no-repeat;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.img-logo {
background: v-bind('imageUrls.logo');
background-size: cover;
width: 428px;
height: 77px;
position: absolute;
left: 37px;
top: 82px;
}
.img-title1 {
background: v-bind('imageUrls.title1');
width: 831px;
height: 314px;
margin: 0 auto;
position: relative;
top: 110px;
}
.img-title2 {
background: v-bind('imageUrls.title2');
width: 643px;
height: 140px;
margin: 0 auto;
position: relative;
top: 80px;
}
.img-qrcode-container {
width: 144px;
height: 177px;
position: absolute;
z-index: 1;
top: 40px;
right: 40px;
.img-qrcode {
width: 100%;
height: 144px;
border-radius: 10px;
}
.txt-qrcode {
font-size: 24px;
color: white;
}
}
.img-mole {
background: v-bind('imageUrls.imgMole') center / cover no-repeat;
width: 397px;
height: 301px;
position: absolute;
top: 315px;
left: 150px;
}
.img-rabbit {
background: v-bind('imageUrls.imgRabbit') center / cover no-repeat;
width: 475px;
height: 414px;
position: absolute;
top: 193px;
right: 150px;
}
.list-container {
background: v-bind('imageUrls.bottomLayers');
width: 1800px;
height: 397px;
position: absolute;
left: 60px;
bottom: 60px;
border:3px solid #AA0000;
border-radius: 10px;
.txt-count {
font-size: 36px;
margin-left: 30px;
margin-top: 20px;
color: #AA0000;
font-weight: bold;
>span {
margin-left: 20px;
font-size: 48px;
position: relative;
top: 5px;
}
}
.list-container-left {
float: left;
display: grid;
grid-template-columns: repeat(10, 1fr);
row-gap: 20px;
width: 73%;
margin-top: 30px;
height: 250px;
align-content: center;
.img-avatar-container {
position: relative;
margin: 0 auto;
width: 72px;
height: 72px;
border-radius: 36px;
}
.img-avatar-container::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: v-bind('imageUrls.avatarAnimation');
animation: avatar-rotate 10s linear infinite;
}
.img-avatar {
position: relative;
top: 6px;
z-index: 1;
margin: 0 auto;
width: 60px;
height: 60px;
border-radius: 30px;
/* background: v-bind('imageUrls.avatar'); */
}
.txt-nickname {
color: #666;
font-size: 20px;
text-align: center;
}
}
.list-container-line {
float: left;
width: 1px;
height: 281px;
margin-left: 50px;
background: v-bind('imageUrls.line') center no-repeat;
}
.list-container-right {
float: left;
.btn-start {
width: 316px;
height: 105px;
margin-left: 60px;
margin-top: 80px;
background: v-bind('imageUrls.start1');
cursor: pointer;
}
.btn-start.pressed {
background: v-bind('imageUrls.start2');
}
}
}
</style>
This diff is collapsed.
This diff is collapsed.
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import { $read } from '@/commons/utils' import { $read } from '@/commons/utils'
import Login from '@/pages/Login.vue' import Login from '@/pages/Login.vue'
import Game1 from '@/pages/game1/Game1.vue' import Game1 from '@/pages/game1/Game.vue'
import Game6 from '@/pages/game6/Game.vue'
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
...@@ -23,6 +24,14 @@ const router = createRouter({ ...@@ -23,6 +24,14 @@ const router = createRouter({
requiresAuth: true, requiresAuth: true,
}, },
}, },
{
path: '/game6',
name: 'Game6',
component: Game6,
meta: {
requiresAuth: true,
},
},
// { // {
// path: '/:pathMatch(.*)*', // path: '/:pathMatch(.*)*',
// redirect: '/', // redirect: '/',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment