Commit 01d3642f authored by 董政锦's avatar 董政锦

feat: game5 PC端第一个页面完成;

parent df306b30
This source diff could not be displayed because it is too large. You can view the blob instead.
<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: 'game1',
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(1)
}
const nextRoundWithMusic = () => {
playGame1Music()
nextRound()
}
onMounted(() => {
window.addEventListener('beforeunload', confirmRefresh)
playGame1Music()
})
onUnmounted(() => {
window.removeEventListener('beforeunload', confirmRefresh)
})
</script>
<template>
<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('game5/bg.svg'),
logo: cssAssetUrl('game1/logo.png'),
title1: cssAssetUrl('game5/title1.webp'),
// title2: cssAssetUrl('game1/title2.png'),
qrcode: assetUrl('game5/qrcode.png'),
left: cssAssetUrl('game5/left.webp'),
right: cssAssetUrl('game5/right.webp'),
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-left"></div>
<div class="img-right"></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: 891px;
height: 309px;
margin: 0 auto;
position: relative;
top: 60px;
}
/* .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;
img {
width: 147px;
height: 147px;
}
}
.txt-qrcode {
font-size: 24px;
color: white;
}
}
.img-left {
background: v-bind('imageUrls.left') no-repeat;
width: 531px;
height: 418px;
position: absolute;
top: 193px;
left: 47px;
}
.img-right {
background: v-bind('imageUrls.right') no-repeat;
width: 423px;
height: 419px;
position: absolute;
top: 193px;
right: 50px;
}
.list-container {
background: v-bind('imageUrls.bottomLayers');
width: 1800px;
height: 397px;
position: absolute;
left: 60px;
bottom: 60px;
.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.
......@@ -4,6 +4,7 @@ import Main from '@/pages/Main.vue'
import Login from '@/pages/Login.vue'
import Game1 from '@/pages/game1/Game.vue'
import Game4 from '@/pages/game4/Game4.vue'
import Game5 from '@/pages/game5/Game5.vue'
import Game6 from '@/pages/game6/Game.vue'
const router = createRouter({
......@@ -40,6 +41,14 @@ const router = createRouter({
},
},
{
path: '/game5',
name: 'Game5',
component: Game5,
meta: {
requiresAuth: true,
},
},
{
path: '/game6',
name: 'Game6',
component: Game6,
......
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