Commit 4b927c24 authored by 董政锦's avatar 董政锦

Merge branch 'main' into 'game4-0604'

# Conflicts: # frontend-h5/src/components/MobileStage.vue
parents 62122a8e 4a6831ac
...@@ -278,6 +278,45 @@ fn broadcast_room_rank(ranking: Vec<Value>) { ...@@ -278,6 +278,45 @@ 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))));
} }
fn emit_msgpack_to_user(userid: &str, event: &str, payload: &Value) {
if userid.is_empty() {
return;
}
if let Some(socket) = CLIENT_PLAYER_MAP.get(userid) {
emit_msgpack(&socket.1, event, payload);
}
}
fn push_room_score_ranking() -> bool {
let (ranking, player_ranks) = if let Ok(state) = ROOM_STATE.lock() {
let full_ranking = state.full_score_ranking();
let ranking = RoomState::ranking_payload(
&full_ranking
.iter()
.take(ROOM_SCORE_RANK_LIMIT)
.cloned()
.collect::<Vec<_>>(),
);
let player_ranks = full_ranking
.into_iter()
.enumerate()
.map(|(index, player)| (player.wechat, json!(index + 1)))
.collect::<Vec<_>>();
(ranking, player_ranks)
} else {
return false;
};
broadcast_room_rank(ranking);
for (userid, rank) in player_ranks {
emit_msgpack_to_user(&userid, "submit_score_result", &ok_resp(Some(rank)));
}
true
}
async fn sleep_room_status(round_id: u64, secs: u64, expected_status: RoomStatus) -> bool { async fn sleep_room_status(round_id: u64, secs: u64, expected_status: RoomStatus) -> bool {
for _ in 0..secs { for _ in 0..secs {
tokio::time::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(1)).await;
...@@ -306,10 +345,25 @@ fn spawn_game_timeout(round_id: u64, secs: u64) { ...@@ -306,10 +345,25 @@ fn spawn_game_timeout(round_id: u64, secs: u64) {
return; return;
} }
if !sleep_room_status(round_id, running_secs, RoomStatus::Running).await { for _ in 0..running_secs {
tokio::time::sleep(Duration::from_secs(1)).await;
if GAME_ROUND_ID.load(Ordering::SeqCst) != round_id {
return; return;
} }
if let Ok(state) = ROOM_STATE.lock() {
if state.status != RoomStatus::Running {
return;
}
}
// 游戏正式计时期间,每秒统一给大屏和玩家推一次实时排名。
if !push_room_score_ranking() {
return;
}
}
if let Ok(mut state) = ROOM_STATE.lock() { if let Ok(mut state) = ROOM_STATE.lock() {
if state.status == RoomStatus::Running { if state.status == RoomStatus::Running {
state.status = RoomStatus::Submit; state.status = RoomStatus::Submit;
...@@ -525,14 +579,16 @@ async fn handle_room_command( ...@@ -525,14 +579,16 @@ async fn handle_room_command(
player.score = score; player.score = score;
// player.wechat = wechat.to_string(); // player.wechat = wechat.to_string();
player.online = true; player.online = true;
drop(state);
// 方案1:每有一名玩家提交时发送给所有玩家跟管理员,玩家过多时发送太频繁,
// 这里会选第2种方案, 游戏中时每秒发给所有玩家跟管理员 (push_room_score_ranking)
// 玩家分数排序 // 玩家分数排序
let ranking = RoomState::ranking_payload(&state.score_ranking()); // let ranking = RoomState::ranking_payload(&state.score_ranking());
let player_rank = state.score_result_payload(userid); // let player_rank = state.score_result_payload(userid);
drop(state); // drop(state);
emit_msgpack(&socket, evt, &ok_resp(player_rank)); // emit_msgpack(&socket, evt, &ok_resp(player_rank));
//通知管理员 // broadcast_room_rank(ranking);
broadcast_room_rank(ranking);
} else { } else {
drop(state); drop(state);
emit_msgpack(&socket, evt, &err_resp("提交异常", None)); emit_msgpack(&socket, evt, &err_resp("提交异常", None));
......
...@@ -151,9 +151,11 @@ defineExpose({ ...@@ -151,9 +151,11 @@ defineExpose({
<div v-if="countdownVisible" class="modal-container"> <div v-if="countdownVisible" class="modal-container">
<div :key="countdownKey" class="countdown-number">{{ countdown }}</div> <div :key="countdownKey" class="countdown-number">{{ countdown }}</div>
</div> </div>
<div v-else-if="showGameRule" class="modal-container"> <Transition name="game-rule-fade">
<slot name="gameRule" ></slot> <div v-if="!countdownVisible && showGameRule" class="modal-container">
<slot name="gameRule"></slot>
</div> </div>
</Transition>
</main> </main>
</template> </template>
...@@ -190,6 +192,16 @@ defineExpose({ ...@@ -190,6 +192,16 @@ defineExpose({
/* pointer-events: none; */ /* pointer-events: none; */
} }
.game-rule-fade-enter-active,
.game-rule-fade-leave-active {
transition: opacity 0.25s ease;
}
.game-rule-fade-enter-from,
.game-rule-fade-leave-to {
opacity: 0;
}
.countdown-number { .countdown-number {
color: white; color: white;
font-size: 200pt; font-size: 200pt;
......
...@@ -16,7 +16,7 @@ const imageUrls = { ...@@ -16,7 +16,7 @@ const imageUrls = {
bg2: cssAssetUrl('game1/bg2.png'), bg2: cssAssetUrl('game1/bg2.png'),
logo: cssAssetUrl('logo.png'), logo: cssAssetUrl('logo.png'),
title: cssAssetUrl('game1/title.png'), title: cssAssetUrl('game1/title.png'),
avatar: cssAssetUrl('avatar.png'), avatar: cssAssetUrl('avatar-yellow.png'),
gu: cssAssetUrl('game1/gu.png'), gu: cssAssetUrl('game1/gu.png'),
clock: cssAssetUrl('game1/clock.png'), clock: cssAssetUrl('game1/clock.png'),
gu01: cssAssetUrl('game1/gu01.png'), gu01: cssAssetUrl('game1/gu01.png'),
...@@ -26,6 +26,8 @@ const imageUrls = { ...@@ -26,6 +26,8 @@ const imageUrls = {
back: cssAssetUrl('game1/back.png'), back: cssAssetUrl('game1/back.png'),
replay: cssAssetUrl('game1/replay.png'), replay: cssAssetUrl('game1/replay.png'),
close: cssAssetUrl('game1/close.png'), close: cssAssetUrl('game1/close.png'),
rule: cssAssetUrl('game1/rule.png')
} }
const wechat = $getWechat() const wechat = $getWechat()
...@@ -140,14 +142,6 @@ function showScoreView() { ...@@ -140,14 +142,6 @@ function showScoreView() {
currentView.value = 'score' currentView.value = 'score'
} }
function replayGame() {
startGameView()
}
function backToWaiting() {
resetToLoadingView()
joinSharedRoom(nickname.value, token.value, avatar.value)
}
function handleRoomBack() { function handleRoomBack() {
resetToLoadingView() resetToLoadingView()
...@@ -161,7 +155,7 @@ const touchHandler = () => { ...@@ -161,7 +155,7 @@ const touchHandler = () => {
return return
} }
tick.value += 1 tick.value += 5
submitScore() submitScore()
playGame1Music(); playGame1Music();
isGuPlaying.value = true isGuPlaying.value = true
...@@ -250,12 +244,14 @@ const showGameRuleHandler = () => { ...@@ -250,12 +244,14 @@ const showGameRuleHandler = () => {
<div class="rule-txt-container"> <div class="rule-txt-container">
<div class="rule-title">游戏规则</div> <div class="rule-title">游戏规则</div>
<div class="rule-txt"> <div class="rule-txt">
微信用户通过扫描大屏幕二维码参与,可容纳多人同时进行且比拼敲鼓,谁敲的最多即为获胜,游戏结束按分值高低先后排名,有机会获得新疆福彩定制周边礼品一份。 1.主持人开赛即可点击鼓面,单次点击得 5 分;<br />
2.限时比拼手速,结束后按积分排名;<br />
3.高分用户有机会领取新疆福彩定制周边;<br />
4.禁止外挂,作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快!
</div> </div>
</div> </div>
<div class="rule-close"> <div class="rule-close" @click="showGameRuleHandler"></div>
<div @click="showGameRuleHandler"></div>
</div>
</div> </div>
</template> </template>
<LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus" <LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus"
...@@ -263,8 +259,7 @@ const showGameRuleHandler = () => { ...@@ -263,8 +259,7 @@ const showGameRuleHandler = () => {
<PlayingView v-else-if="currentView === 'playing'" :image-urls="imageUrls" :tick="tick" :rank="rank" <PlayingView v-else-if="currentView === 'playing'" :image-urls="imageUrls" :tick="tick" :rank="rank"
:current-gu="currentGu" :countdown-interval="countdownInterval" :is-div-desc-visible="isDivDescVisible" :current-gu="currentGu" :countdown-interval="countdownInterval" :is-div-desc-visible="isDivDescVisible"
@touch="touchHandler" /> @touch="touchHandler" />
<ScoreView v-else :image-urls="imageUrls" :rank="rank" :tick="tick" @replay="replayGame" <ScoreView v-else :image-urls="imageUrls" :rank="rank" :tick="tick" />
@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;">
请使用微信扫码进入游戏 请使用微信扫码进入游戏
...@@ -273,40 +268,39 @@ const showGameRuleHandler = () => { ...@@ -273,40 +268,39 @@ const showGameRuleHandler = () => {
<style scoped> <style scoped>
.rule-container { .rule-container {
.rule-txt-container { .rule-txt-container {
background: #FFF9E4; position: absolute;
margin:0 40px; background: v-bind('imageUrls.rule') center / cover no-repeat;
border-radius: 5px; width: 692px;
padding-top: 20px; height: 797px;
padding-bottom: 10px; left: -157px;
top: -75px;
transform: scale(0.5);
.rule-title { .rule-title {
text-align: center; text-align: center;
color: #850000; color: white;
font-weight: bold; font-weight: bold;
font-size: 15pt; font-size: 22pt;
margin-top: 10px;
letter-spacing: 4px;
} }
.rule-txt { .rule-txt {
font-size: 12pt; font-size: 26pt;
padding: 10px; padding: 40px;
margin-top: 10px; margin-top: 10px;
color: #AA0000;
} }
} }
.rule-close { .rule-close {
width: 32px; width: 57px;
height: 130px; height: 57px;
margin: 0 auto; margin: 0 auto;
margin-top: 450px;
background: v-bind('imageUrls.close') center/cover; background: v-bind('imageUrls.close') center/cover;
position: relative; position: relative;
>div { transform: scale(0.8);
position: absolute;
bottom: 0;
width: 32px;
height: 32px;
border-radius: 16px;
}
} }
} }
</style> </style>
\ No newline at end of file
...@@ -20,12 +20,11 @@ onMounted(() => { ...@@ -20,12 +20,11 @@ onMounted(() => {
<div class="game-desc" @click="$emit('touchGameRule')">游戏规则</div> <div class="game-desc" @click="$emit('touchGameRule')">游戏规则</div>
<div class="img-logo"></div> <div class="img-logo"></div>
<div class="img-title"></div> <div class="img-title"></div>
<div class="img-avatar" :style="`background: url(${avatar});border-radius:66px;width:132px;height:132px;`"></div> <div class="img-avatar" :style="`background: url(${avatar});border-radius:66px;width:132px;height:132px;`">
<div class="txt-nickname">{{ nickname }}</div>
<div class="txt-loading">
<label v-if="userJoinStatus">您已成功加入游戏等待主持人开始</label>
<label v-else>正在加入游戏...</label>
</div> </div>
<div class="txt-nickname">{{ nickname }}</div>
<div class="txt-loading">您已成功加入游戏等待主持人开始</div>
<div class="txt-loading2">游戏即将开始 敬请期待</div>
<div class="bg-bottom"></div> <div class="bg-bottom"></div>
<div class="loading-gu"></div> <div class="loading-gu"></div>
</div> </div>
...@@ -105,7 +104,7 @@ onMounted(() => { ...@@ -105,7 +104,7 @@ onMounted(() => {
top: 640px; top: 640px;
left: 50%; left: 50%;
color: #E00000; color: #E00000;
font-size: 25pt; font-size: 30pt;
font-weight: bold; font-weight: bold;
transform: translateX(-50%); transform: translateX(-50%);
} }
...@@ -115,29 +114,28 @@ onMounted(() => { ...@@ -115,29 +114,28 @@ onMounted(() => {
top: 710px; top: 710px;
left: 50%; left: 50%;
width: 288px; width: 288px;
color: #E00000; color: #6F0000;
font-size: 25pt; font-size: 25pt;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
transform: translateX(-50%); transform: translateX(-50%);
} }
.txt-loading label { .txt-loading2 {
display: inline-block; width: 500px;
transform-origin: center; height: 90px;
animation: loading-text-scale 1.2s ease-in-out infinite; background: #E61903;
} color: white;
position: absolute;
@keyframes loading-text-scale { top:850px;
left: 50%;
0%, transform: translate(-50%);
100% { line-height: 90px;
transform: scale(1); text-align: center;
} font-size: 36px;
font-weight: bold;
50% { letter-spacing:5px;
transform: scale(1.08); border-radius: 10px;
}
} }
.loading-gu { .loading-gu {
......
...@@ -37,10 +37,6 @@ onMounted(() => { ...@@ -37,10 +37,6 @@ onMounted(() => {
<div><label>{{ rank }}</label></div> <div><label>{{ rank }}</label></div>
</div> </div>
</div> </div>
<div class="btn-group">
<div class="btn-replay" @click="$emit('replay')"></div>
<div class="btn-back" @click="$emit('back')"></div>
</div>
</div> </div>
</template> </template>
...@@ -142,26 +138,4 @@ onMounted(() => { ...@@ -142,26 +138,4 @@ onMounted(() => {
margin: 0 10px; margin: 0 10px;
} }
.btn-group {
position: absolute;
top: 1320px;
left: 50%;
display: flex;
gap: 40px;
transform: translateX(-50%);
}
.btn-replay,
.btn-back {
width: 315px;
height: 100px;
}
.btn-replay {
background: v-bind('imageUrls.replay') center/cover;
}
.btn-back {
background: v-bind('imageUrls.back') center/cover;
}
</style> </style>
...@@ -120,7 +120,7 @@ function handleTargetTouch(index: number) { ...@@ -120,7 +120,7 @@ function handleTargetTouch(index: number) {
return return
} }
const scoreDelta = item.kind === 'mole' ? 1 : -1 const scoreDelta = item.kind === 'mole' ? 5 : -3
emit('touch', scoreDelta) emit('touch', scoreDelta)
clearTargetTimer(item) clearTargetTimer(item)
item.state = 'hit' item.state = 'hit'
......
<script setup lang="ts">
import { cssAssetUrl } from '@/commons/assets.ts';
const imageUrls = {
bg1: cssAssetUrl('main-bg1.png'),
logo: cssAssetUrl('game1/logo.png'),
title1: cssAssetUrl('main-title.png'),
}
</script>
<template>
<div class="bg">
<div class="img-logo"></div>
<div class="img-title1"></div>
<div class="rule-container">
<div class="rule-title">游戏规则</div>
<div class="rule-text">
扫码即玩,大屏实时同步。每局结束大屏公布前10名。主持人邀请获奖用户,凭获奖界面作为凭证,上台领奖。,核对后发放实物奖品。奖品不折现、不替换,逾期作废。
</div>
</div>
</div>
</template>
<style scoped>
.bg {
width: 1920px;
height: 1080px;
background: v-bind('imageUrls.bg1') 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') center / cover no-repeat;
width: 1024px;
height: 143px;
margin: 0 auto;
position: relative;
top: 50px;
}
.rule-container {
box-sizing: border-box;
width: 638px;
min-height: 162px;
border-radius: 12px;
background:
linear-gradient(90deg, rgba(124, 77, 28, 0.74), rgba(136, 122, 76, 0.72)),
rgba(124, 91, 48, 0.52);
backdrop-filter: blur(18px) saturate(0.9);
-webkit-backdrop-filter: blur(18px) saturate(0.9);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.18),
inset 0 -1px 0 rgba(0, 0, 0, 0.08);
color: white;
padding: 27px 34px 24px;
position: absolute;
left: 0;
top: 0;
font-weight: 700;
text-shadow:
0 2px 0 rgba(0, 0, 0, 0.65),
1px 0 0 rgba(0, 0, 0, 0.45),
-1px 0 0 rgba(0, 0, 0, 0.45),
0 -1px 0 rgba(0, 0, 0, 0.45);
}
.rule-title {
margin-bottom: 12px;
font-size: 22px;
line-height: 1.2;
text-align: center;
}
.rule-text {
font-size: 16px;
line-height: 1.58;
}
</style>
...@@ -64,7 +64,7 @@ const renderAvatar = (item: any | null) => { ...@@ -64,7 +64,7 @@ const renderAvatar = (item: any | null) => {
<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="`${renderAvatar(item.avatar)}`"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></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>
......
...@@ -182,9 +182,12 @@ onUnmounted(() => { ...@@ -182,9 +182,12 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<Rank1 ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" /> <!-- <Loading :players="loadingPlayers" :player-count="playerCount"
<!-- <Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount" @start="startGameWithMusic" /> -->
<!-- <Rank1 ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" /> -->
<!-- <Rank2 :rank-list="rankPlayers" @next="nextRoundWithMusic" /> -->
<Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
@start="startGameWithMusic" /> @start="startGameWithMusic" />
<Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" /> <Rank1 v-else-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" />
<Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" /> --> <Rank2 v-else :rank-list="rankPlayers" @next="nextRoundWithMusic" />
</template> </template>
...@@ -19,7 +19,7 @@ const imageUrls = { ...@@ -19,7 +19,7 @@ const imageUrls = {
title1: cssAssetUrl('game6/title.png'), title1: cssAssetUrl('game6/title.png'),
title2: cssAssetUrl('game1/title2.png'), title2: cssAssetUrl('game1/title2.png'),
qrcode: assetUrl('game6/qrcode.png'), qrcode: assetUrl('game6/qrcode.png'),
gu: cssAssetUrl('game1/gu.png'), // gu: cssAssetUrl('game1/gu.png'),
// bg: cssAssetUrl('game1/bg.png'), // bg: cssAssetUrl('game1/bg.png'),
// logo: cssAssetUrl('game1/logo.png'), // logo: cssAssetUrl('game1/logo.png'),
...@@ -27,12 +27,12 @@ const imageUrls = { ...@@ -27,12 +27,12 @@ const imageUrls = {
// title2: cssAssetUrl('game1/title2.png'), // title2: cssAssetUrl('game1/title2.png'),
// qrcode: assetUrl('game1/qrcode.png'), // qrcode: assetUrl('game1/qrcode.png'),
// gu: cssAssetUrl('game1/gu.png'), // gu: cssAssetUrl('game1/gu.png'),
imgMole:cssAssetUrl('game6/mole.png'), imgMole: cssAssetUrl('game6/mole.png'),
imgRabbit:cssAssetUrl('game6/rabbit.png'), imgRabbit: cssAssetUrl('game6/rabbit.png'),
bottomLayers: cssAssetUrl('game1/bottom-layers.png'), bottomLayers: cssAssetUrl('game1/bottom-layers.png'),
avatarAnimation: cssAssetUrl('avatar_animation.png'), avatarAnimation: cssAssetUrl('avatar-red_animation.png'),
avatar: cssAssetUrl('avatar.png'), avatar: cssAssetUrl('avatar-red.png'),
line: cssAssetUrl('game1/line.png'), line: cssAssetUrl('game1/line.png'),
start1: cssAssetUrl('game1/start-1.png'), start1: cssAssetUrl('game1/start-1.png'),
start2: cssAssetUrl('game1/start-2.png'), start2: cssAssetUrl('game1/start-2.png'),
...@@ -75,7 +75,7 @@ const renderAvatar = (item: any | null) => { ...@@ -75,7 +75,7 @@ const renderAvatar = (item: any | null) => {
<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="renderAvatar(item)"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></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>
...@@ -83,14 +83,9 @@ const renderAvatar = (item: any | null) => { ...@@ -83,14 +83,9 @@ const renderAvatar = (item: any | null) => {
</div> </div>
<div class="list-container-line"></div> <div class="list-container-line"></div>
<div class="list-container-right"> <div class="list-container-right">
<div <div class="btn-start" :class="{ pressed: isStartPressed }" @pointerdown="pressStartButton"
class="btn-start" @pointerup="releaseStartButton" @pointerleave="releaseStartButton"
:class="{ pressed: isStartPressed }" @pointercancel="releaseStartButton"></div>
@pointerdown="pressStartButton"
@pointerup="releaseStartButton"
@pointerleave="releaseStartButton"
@pointercancel="releaseStartButton"
></div>
</div> </div>
</div> </div>
</div> </div>
...@@ -164,6 +159,7 @@ const renderAvatar = (item: any | null) => { ...@@ -164,6 +159,7 @@ const renderAvatar = (item: any | null) => {
top: 315px; top: 315px;
left: 150px; left: 150px;
} }
.img-rabbit { .img-rabbit {
background: v-bind('imageUrls.imgRabbit') center / cover no-repeat; background: v-bind('imageUrls.imgRabbit') center / cover no-repeat;
width: 475px; width: 475px;
...@@ -180,7 +176,7 @@ const renderAvatar = (item: any | null) => { ...@@ -180,7 +176,7 @@ const renderAvatar = (item: any | null) => {
position: absolute; position: absolute;
left: 60px; left: 60px;
bottom: 60px; bottom: 60px;
border:3px solid #AA0000; border: 3px solid #AA0000;
border-radius: 10px; border-radius: 10px;
.txt-count { .txt-count {
...@@ -221,7 +217,7 @@ const renderAvatar = (item: any | null) => { ...@@ -221,7 +217,7 @@ const renderAvatar = (item: any | null) => {
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: inherit; border-radius: inherit;
background: v-bind('imageUrls.avatarAnimation'); background: v-bind('imageUrls.avatarAnimation') center / cover no-repeat;
animation: avatar-rotate 10s linear infinite; animation: avatar-rotate 10s linear infinite;
} }
......
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import DesignStage from '../../../components/DesignStage.vue' import DesignStage from '../../../components/DesignStage.vue'
import type Player from '../../../commons/player.ts' import type Player from '../../../commons/player.ts'
import { $is_run_local } from '@/commons/utils.ts'; import { $format_str, $is_run_local } from '@/commons/utils.ts';
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts'; import { assetUrl, cssAssetUrl } from '@/commons/assets.ts';
const props = defineProps<{ const props = defineProps<{
...@@ -17,6 +17,10 @@ const RANK_LIMIT = 10; ...@@ -17,6 +17,10 @@ const RANK_LIMIT = 10;
const START_COUNTDOWN_SECONDS = 3; const START_COUNTDOWN_SECONDS = 3;
const GU_FRAME_DURATION = 300; const GU_FRAME_DURATION = 300;
const GU_LOOP_DELAY = 1000; const GU_LOOP_DELAY = 1000;
const MOLE_CELL_COUNT = 9;
const MOLE_LAND_DURATION = 5000;
const MOLE_SHOW_DURATION = 1200;
const MOLE_HIDE_DURATION = 650;
const designStage = ref<InstanceType<typeof DesignStage> | null>(null); const designStage = ref<InstanceType<typeof DesignStage> | null>(null);
const rankEffects = ref<Record<string, "up" | "down" | "new">>({}); const rankEffects = ref<Record<string, "up" | "down" | "new">>({});
const guFrameIndex = ref(0); const guFrameIndex = ref(0);
...@@ -26,15 +30,15 @@ let guFramesReady: Promise<void> | undefined; ...@@ -26,15 +30,15 @@ let guFramesReady: Promise<void> | undefined;
let guTimer: ReturnType<typeof setTimeout> | undefined; let guTimer: ReturnType<typeof setTimeout> | undefined;
let guBurstTimer: ReturnType<typeof setTimeout> | undefined; let guBurstTimer: ReturnType<typeof setTimeout> | undefined;
const loadGuFrames = (dir: string) => Array.from( // const loadGuFrames = (dir: string) => Array.from(
{ length: 10 }, // { length: 10 },
(_, index) => assetUrl(`${dir}/${String(index + 1).padStart(2, '0')}.png`), // (_, index) => assetUrl(`${dir}/${String(index + 1).padStart(2, '0')}.png`),
); // );
const guFrames = [ // const guFrames = [
loadGuFrames('game1/gu'), // loadGuFrames('game1/gu'),
loadGuFrames('game1/gu2'), // loadGuFrames('game1/gu2'),
]; // ];
const imageUrls = { const imageUrls = {
bg: cssAssetUrl('game6/bg2.png'), bg: cssAssetUrl('game6/bg2.png'),
...@@ -52,9 +56,15 @@ const imageUrls = { ...@@ -52,9 +56,15 @@ const imageUrls = {
// rankNo1Icon: cssAssetUrl('game1/no1-icon.png'), // rankNo1Icon: cssAssetUrl('game1/no1-icon.png'),
// rankNo2Icon: cssAssetUrl('game1/no2-icon.png'), // rankNo2Icon: cssAssetUrl('game1/no2-icon.png'),
// rankNo3Icon: cssAssetUrl('game1/no3-icon.png'), // rankNo3Icon: cssAssetUrl('game1/no3-icon.png'),
avatar: assetUrl('avatar.png'), avatar: cssAssetUrl('avatar-yellow.png'),
avatarAnimation: cssAssetUrl('avatar-yellow_animation.png'),
mole2: cssAssetUrl('game6/mole2.png'), mole2: cssAssetUrl('game6/mole2.png'),
rankContainer: cssAssetUrl('game6/rank-container.png'), rankContainer: cssAssetUrl('game6/rank-container.png'),
land: cssAssetUrl('game6/land.png'),
moleShow: cssAssetUrl('game6/mole_show.png'),
moleHide: cssAssetUrl('game6/mole_hide.png'),
rabbitShow: cssAssetUrl('game6/rabbit_show.png'),
rabbitHide: cssAssetUrl('game6/rabbit_hide.png'),
}; };
const emptyPlayer = (index: number): Player => ({ const emptyPlayer = (index: number): Player => ({
...@@ -67,6 +77,21 @@ const emptyPlayer = (index: number): Player => ({ ...@@ -67,6 +77,21 @@ const emptyPlayer = (index: number): Player => ({
const list = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyPlayer(index))); const list = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyPlayer(index)));
const playerKey = (player: Player, index = 0) => player.userid || player.wechat_id || `empty-${index}`; const playerKey = (player: Player, index = 0) => player.userid || player.wechat_id || `empty-${index}`;
const mockRankStep = ref(0); const mockRankStep = ref(0);
type MoleKind = "land" | "mole" | "rabbit";
type MolePhase = "land" | "show" | "hide";
type MoleCell = {
kind: MoleKind;
phase: MolePhase;
animationKey: number;
};
const moleCells = ref<MoleCell[]>(
Array.from({ length: MOLE_CELL_COUNT }, () => ({
kind: "land",
phase: "land",
animationKey: 0,
})),
);
const moleTimers: Array<ReturnType<typeof setTimeout> | undefined> = [];
const makeMockPlayer = (id: number, score: number): Player => ({ const makeMockPlayer = (id: number, score: number): Player => ({
wechat_id: `mock-${id}`, wechat_id: `mock-${id}`,
...@@ -155,96 +180,153 @@ const startGame = () => { ...@@ -155,96 +180,153 @@ const startGame = () => {
} }
}; };
const stopPlayerGu = () => { const randomMoleKind = (includeLand = true): MoleKind => {
if (guTimer) { const kinds: MoleKind[] = includeLand ? ["land", "mole", "rabbit"] : ["mole", "rabbit"];
clearTimeout(guTimer); return kinds[Math.floor(Math.random() * kinds.length)] ?? "land";
guTimer = undefined; };
}
if (guBurstTimer) {
clearTimeout(guBurstTimer);
guBurstTimer = undefined;
}
guBurstVisible.value = false;
guFrameIndex.value = 0;
}
const preloadGuFrames = () => { const clearMoleTimer = (index: number) => {
if (!guFramesReady) { const timer = moleTimers[index];
guFramesReady = Promise.all(guFrames.flat().map((src) => { if (timer) {
const img = new Image(); clearTimeout(timer);
img.src = src; moleTimers[index] = undefined;
return img.decode
? img.decode().catch(() => undefined)
: new Promise<void>((resolve) => {
img.onload = () => resolve();
img.onerror = () => resolve();
});
})).then(() => undefined);
} }
};
return guFramesReady; const setMoleCell = (index: number, kind: MoleKind, phase: MolePhase) => {
} const next = [...moleCells.value];
const current = next[index];
const playGuLoopDelayAnimation = () => { next[index] = {
if (guBurstTimer) { kind,
clearTimeout(guBurstTimer); phase,
guBurstTimer = undefined; animationKey: (current?.animationKey ?? 0) + 1,
} };
moleCells.value = next;
};
guBurstVisible.value = false; const scheduleMoleCell = (index: number, kind = randomMoleKind()) => {
requestAnimationFrame(() => { clearMoleTimer(index);
guBurstKey.value += 1;
guBurstVisible.value = true;
guBurstTimer = setTimeout(() => {
guBurstVisible.value = false;
guBurstTimer = undefined;
}, GU_LOOP_DELAY);
});
}
const playerGu = async () => { if (kind === "land") {
stopPlayerGu(); setMoleCell(index, "land", "land");
const frameCount = Math.min(...guFrames.map((frames) => frames.length)); moleTimers[index] = setTimeout(() => scheduleMoleCell(index, randomMoleKind(false)), MOLE_LAND_DURATION);
if (frameCount <= 0) return; return;
}
await preloadGuFrames(); setMoleCell(index, kind, "show");
moleTimers[index] = setTimeout(() => {
setMoleCell(index, kind, "hide");
moleTimers[index] = setTimeout(() => scheduleMoleCell(index, "land"), MOLE_HIDE_DURATION);
}, MOLE_SHOW_DURATION);
};
let frameIndex = 0; const moleCellClass = (cell: MoleCell) => [
const tick = () => { `mole-${cell.kind}`,
guFrameIndex.value = frameIndex; `mole-${cell.phase}`,
];
if (frameIndex >= frameCount - 1) { onMounted(() => {
frameIndex = 0; moleCells.value.forEach((_, index) => {
playGuLoopDelayAnimation(); moleTimers[index] = setTimeout(
guTimer = setTimeout(tick, GU_LOOP_DELAY); () => scheduleMoleCell(index, randomMoleKind()),
return; Math.floor(Math.random() * MOLE_LAND_DURATION),
} );
});
});
frameIndex += 1; onBeforeUnmount(() => {
guTimer = setTimeout(tick, GU_FRAME_DURATION); moleTimers.forEach((_, index) => clearMoleTimer(index));
} });
tick(); // const stopPlayerGu = () => {
} // if (guTimer) {
// clearTimeout(guTimer);
// guTimer = undefined;
// }
// if (guBurstTimer) {
// clearTimeout(guBurstTimer);
// guBurstTimer = undefined;
// }
// guBurstVisible.value = false;
// guFrameIndex.value = 0;
// }
// const preloadGuFrames = () => {
// if (!guFramesReady) {
// guFramesReady = Promise.all(guFrames.flat().map((src) => {
// const img = new Image();
// img.src = src;
// return img.decode
// ? img.decode().catch(() => undefined)
// : new Promise<void>((resolve) => {
// img.onload = () => resolve();
// img.onerror = () => resolve();
// });
// })).then(() => undefined);
// }
// return guFramesReady;
// }
// const playGuLoopDelayAnimation = () => {
// if (guBurstTimer) {
// clearTimeout(guBurstTimer);
// guBurstTimer = undefined;
// }
// guBurstVisible.value = false;
// requestAnimationFrame(() => {
// guBurstKey.value += 1;
// guBurstVisible.value = true;
// guBurstTimer = setTimeout(() => {
// guBurstVisible.value = false;
// guBurstTimer = undefined;
// }, GU_LOOP_DELAY);
// });
// }
// const playerGu = async () => {
// stopPlayerGu();
// const frameCount = Math.min(...guFrames.map((frames) => frames.length));
// if (frameCount <= 0) return;
// await preloadGuFrames();
// let frameIndex = 0;
// const tick = () => {
// guFrameIndex.value = frameIndex;
// if (frameIndex >= frameCount - 1) {
// frameIndex = 0;
// playGuLoopDelayAnimation();
// guTimer = setTimeout(tick, GU_LOOP_DELAY);
// return;
// }
// frameIndex += 1;
// guTimer = setTimeout(tick, GU_FRAME_DURATION);
// }
// tick();
// }
const startIntro = async () => { const startIntro = async () => {
playerGu(); // playerGu();
await designStage.value?.startCountdown(START_COUNTDOWN_SECONDS, 60); await designStage.value?.startCountdown(START_COUNTDOWN_SECONDS, 60);
stopPlayerGu(); // stopPlayerGu();
}; };
defineExpose({ defineExpose({
startIntro, startIntro,
}); });
const renderAvatar = (item: any | null) => {
const avatarStyle = (item: Player) => { if (item && item.avatar) {
const avatar = item.avatar || imageUrls.avatar return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;`
return {
backgroundImage: `url("${avatar}")`,
} }
return `background: ${imageUrls.avatar} center / cover no-repeat;`
} }
const rankIcon = (index: number) => { const rankIcon = (index: number) => {
const icons = [ const icons = [
imageUrls.rankNo1, imageUrls.rankNo1,
...@@ -266,7 +348,9 @@ const rankIcon = (index: number) => { ...@@ -266,7 +348,9 @@ const rankIcon = (index: number) => {
width: w + 'px', width: w + 'px',
height: h + 'px', height: h + 'px',
left: l + 'px', left: l + 'px',
top: t + 'px' top: t + 'px',
lineHeight: h + 'px',
color: index <= 2 ? 'white' : '#CC7F36',
}; };
} }
</script> </script>
...@@ -278,14 +362,27 @@ const rankIcon = (index: number) => { ...@@ -278,14 +362,27 @@ const rankIcon = (index: number) => {
<div class="img-title"></div> <div class="img-title"></div>
<div class="img-mole"></div> <div class="img-mole"></div>
<div class="rank-container"> <div class="rank-container">
<div v-for="(item, index) in list"> <div v-for="(item, index) in list" :key="playerKey(item, index)">
<div> <div>
<div class="rank-icon" :style="rankIcon(index)"></div> <div class="rank-icon" :style="rankIcon(index)">{{ index + 1 }}</div>
<div class="rank-item">{{ index }}</div> <div class="rank-item">
<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 class="txt-score">{{ item.score }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="mole-container">
<div v-for="(cell, index) in moleCells" :key="index" class="mole-hole" :class="moleCellClass(cell)">
<div :key="cell.animationKey" class="mole-sprite"></div>
</div>
</div>
</div>
</DesignStage> </DesignStage>
</template> </template>
...@@ -334,7 +431,7 @@ const rankIcon = (index: number) => { ...@@ -334,7 +431,7 @@ const rankIcon = (index: number) => {
align-items: center; align-items: center;
column-gap: 42px; column-gap: 42px;
box-sizing: border-box; box-sizing: border-box;
padding: 42px 42px; padding: 30px;
width: 1832px; width: 1832px;
height: 244px; height: 244px;
position: absolute; position: absolute;
...@@ -357,90 +454,174 @@ const rankIcon = (index: number) => { ...@@ -357,90 +454,174 @@ const rankIcon = (index: number) => {
.rank-icon { .rank-icon {
position: absolute; position: absolute;
color: #CC7F36;
font-weight: bold;
}
.rank-icon:nth-child(1),
.rank-icon:nth-child(2),
.rank-icon:nth-child(3) {
color: white;
} }
} }
} }
}
.img-gu-group {
.img-gu {
width: 400px; .img-avatar-container {
height: 400px; position: relative;
transform: scale(1.6); margin: 0 auto;
position: absolute; width: 90px;
top: 400px; height: 90px;
border-radius: 36px;
/* transform: scale(1.3); */
top: 18px
} }
.img-gu:nth-child(1) { .img-avatar-container::before {
left: 200px; content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: v-bind('imageUrls.avatarAnimation');
animation: avatar-rotate 10s linear infinite;
} }
.img-gu:nth-child(2) { .img-avatar {
right: 200px; width: 80px;
height: 80px;
border-radius: 40px;
position: absolute;
left: 6px;
top: 5px;
} }
.img-gu { .txt-nickname {
.gu-frame {
position: absolute; position: absolute;
inset: 0; bottom: 40px;
opacity: 0; text-align: center;
background-position: center; width: 100%;
background-repeat: no-repeat; color: #F9EBCB;
background-size: contain;
pointer-events: none;
will-change: opacity;
} }
.gu-frame.active { .txt-score {
opacity: 1; position: absolute;
bottom: 5px;
text-align: center;
width: 100%;
color: #F9EBCB;
} }
}
.gu-burst { .mole-container {
width: 200px;
height: 200px;
position: absolute; position: absolute;
right: 80px; top: 200px;
top: 30px; /* inset: 0; */
opacity: 0;
transform: scale(0.6);
transform-origin: center center;
background: v-bind('imageUrls.fireworks') center / cover no-repeat;
pointer-events: none; pointer-events: none;
&.active { .mole-hole {
animation: gu-burst-expand 1s ease-out both; position: absolute;
width: 500px;
height: 500px;
transform: translate(-125px, -125px);
overflow: hidden;
.mole-sprite {
width: 500px;
height: 500px;
background-repeat: no-repeat;
background-position: 0 0;
background-size: auto 500px;
} }
&.mole-land .mole-sprite {
background: v-bind('imageUrls.land') center / cover no-repeat;
} }
&.mole-mole.mole-show .mole-sprite {
background-image: v-bind('imageUrls.moleShow');
animation: mole-show 1.2s steps(30) both;
} }
}
.rank-move { &.mole-mole.mole-hide .mole-sprite {
transition: transform 0.45s ease; background-image: v-bind('imageUrls.moleHide');
} animation: mole-hide 0.65s steps(15) both;
}
.rank-enter-active, &.mole-rabbit.mole-show .mole-sprite {
.rank-leave-active { background-image: v-bind('imageUrls.rabbitShow');
transition: opacity 0.25s ease, transform 0.25s ease; animation: mole-show 1.2s steps(30) both;
} }
.rank-leave-active { &.mole-rabbit.mole-hide .mole-sprite {
position: absolute; background-image: v-bind('imageUrls.rabbitHide');
left: 17px; animation: mole-hide 0.65s steps(15) both;
} }
.rank-enter-from, &:nth-child(1) {
.rank-leave-to { left: 247px;
opacity: 0; top: 312px;
transform: translateY(90px); }
&:nth-child(2) {
left: 547px;
top: 300px;
}
&:nth-child(3) {
left: 847px;
top: 312px;
}
&:nth-child(4) {
left: 1147px;
top: 300px;
}
&:nth-child(5) {
left: 1447px;
top: 312px;
}
&:nth-child(6) {
left: 397px;
top: 480px;
}
&:nth-child(7) {
left: 697px;
top: 460px;
}
&:nth-child(8) {
left: 997px;
top: 460px;
}
&:nth-child(9) {
left: 1297px;
top: 480px;
}
}
} }
.rank-item.up, @keyframes mole-show {
.rank-item.new { from {
animation: rank-up-flash 0.8s ease; background-position-x: 0;
}
to {
background-position-x: -15000px;
}
} }
.rank-item.down { @keyframes mole-hide {
animation: rank-down-flash 0.8s ease; from {
background-position-x: 0;
}
to {
background-position-x: -7500px;
}
} }
</style> </style>
...@@ -14,22 +14,15 @@ const emit = defineEmits<{ ...@@ -14,22 +14,15 @@ const emit = defineEmits<{
const FINAL_RANK_LIMIT = 10; const FINAL_RANK_LIMIT = 10;
const imageUrls = { const imageUrls = {
bg: cssAssetUrl('game1/bg.png'), bg: cssAssetUrl('game6/bg3.png'),
logo: cssAssetUrl('game1/logo.png'), logo: cssAssetUrl('game1/logo.png'),
title: cssAssetUrl('game1/title3.png'), title: cssAssetUrl('game6/title3.png'),
title2: cssAssetUrl('game1/title4.png'), podium: cssAssetUrl('game6/podium.png'),
podium: cssAssetUrl('game1/podium.png'), vip: cssAssetUrl('game6/vip.png'),
fireworks: cssAssetUrl('game1/fireworks.png'), avatar: cssAssetUrl('avatar-yellow.png'),
avatar: cssAssetUrl('avatar.png'), avatarAnimation: cssAssetUrl('avatar-yellow_animation.png'),
guan1: cssAssetUrl('game1/guan1.png'), rankIcon: cssAssetUrl('game6/no4.png'),
pai1: cssAssetUrl('game1/pai1.png'), next: cssAssetUrl('game6/next-1.png'),
guan2: cssAssetUrl('game1/guan2.png'),
pai2: cssAssetUrl('game1/pai2.png'),
guan3: cssAssetUrl('game1/guan3.png'),
pai3: cssAssetUrl('game1/pai3.png'),
rankIcon: cssAssetUrl('game1/no-icon.png'),
avatarAnimation: cssAssetUrl('avatar_animation.png'),
next: cssAssetUrl('game1/next-1.png'),
}; };
const emptyPlayer = (index: number): Player => ({ const emptyPlayer = (index: number): Player => ({
...@@ -59,7 +52,7 @@ const renderAvatar = (item: any | null) => { ...@@ -59,7 +52,7 @@ const renderAvatar = (item: any | null) => {
if (item && item.avatar) { if (item && item.avatar) {
return `background: url('${item.avatar}') center / cover no-repeat;` return `background: url('${item.avatar}') center / cover no-repeat;`
} }
return `background: v-bind('imageUrls.avatar') center / cover no-repeat;` return `background: ${imageUrls.avatar} center / cover no-repeat;`
} }
</script> </script>
...@@ -71,8 +64,8 @@ const renderAvatar = (item: any | null) => { ...@@ -71,8 +64,8 @@ const renderAvatar = (item: any | null) => {
<div class="img-title2"></div> <div class="img-title2"></div>
<div class="podium"> <div class="podium">
<div class="img-vip"></div>
<div class="rank-no1"> <div class="rank-no1">
<div class="gu-burst"></div>
<div class="avatar" :style="`${renderAvatar(list[0])}`"> <div class="avatar" :style="`${renderAvatar(list[0])}`">
</div> </div>
<div class="nickname">{{ list[0]?.nickname }}</div> <div class="nickname">{{ list[0]?.nickname }}</div>
...@@ -80,7 +73,6 @@ const renderAvatar = (item: any | null) => { ...@@ -80,7 +73,6 @@ const renderAvatar = (item: any | null) => {
<div class="pai"></div> <div class="pai"></div>
</div> </div>
<div class="rank-no2"> <div class="rank-no2">
<div class="gu-burst"></div>
<div class="avatar" :style="`${renderAvatar(list[1])}`"> <div class="avatar" :style="`${renderAvatar(list[1])}`">
</div> </div>
<div class="nickname">{{ list[1]?.nickname }}</div> <div class="nickname">{{ list[1]?.nickname }}</div>
...@@ -88,7 +80,6 @@ const renderAvatar = (item: any | null) => { ...@@ -88,7 +80,6 @@ const renderAvatar = (item: any | null) => {
<div class="pai"></div> <div class="pai"></div>
</div> </div>
<div class="rank-no3"> <div class="rank-no3">
<div class="gu-burst"></div>
<div class="avatar" :style="`${renderAvatar(list[2])}`"> <div class="avatar" :style="`${renderAvatar(list[2])}`">
</div> </div>
<div class="nickname">{{ list[2]?.nickname }}</div> <div class="nickname">{{ list[2]?.nickname }}</div>
...@@ -136,11 +127,11 @@ const renderAvatar = (item: any | null) => { ...@@ -136,11 +127,11 @@ const renderAvatar = (item: any | null) => {
} }
.img-title { .img-title {
background: v-bind('imageUrls.title'); background: v-bind('imageUrls.title') center / cover no-repeat;
width: 967px; width: 831px;
height: 122px; height: 120px;
margin: 0 auto; margin: 0 auto;
margin-top: 37px; margin-top: 64px;
} }
.img-title2 { .img-title2 {
...@@ -153,176 +144,106 @@ const renderAvatar = (item: any | null) => { ...@@ -153,176 +144,106 @@ const renderAvatar = (item: any | null) => {
.podium { .podium {
background: v-bind('imageUrls.podium') no-repeat center; background: v-bind('imageUrls.podium') no-repeat center;
width: 1347px; width: 1920px;
height: 269px; height: 740px;
position: absolute; position: absolute;
top: 427px; bottom: 0;
left: 287px; left: 0;
.gu-burst { .img-vip {
width: 200px; width: 969px;
height: 200px; height: 238px;
position: absolute; position: absolute;
z-index: 1; left: 480px;
border-radius: 50%; top: -60px;
opacity: 1; background: v-bind('imageUrls.vip') center / cover no-repeat;
transform: scale(0.6);
transform-origin: center center;
background: v-bind('imageUrls.fireworks') center / cover no-repeat;
pointer-events: none;
animation: gu-burst-expand 1.2s ease-out infinite;
} }
.rank-no1 { .rank-no1 {
position: relative; position: relative;
.gu-burst {
left: 580px;
top: -200px;
}
.avatar { .avatar {
/* background: v-bind('imageUrls.avatar') center / cover no-repeat; */ width: 160px;
width: 120px; height: 160px;
height: 120px; border-radius: 80px;
border-radius: 60px;
position: absolute; position: absolute;
left: 610px; left: 884px;
top: -136px; top: -7px;
} }
.nickname { .nickname {
width: 167px; width: 220px;
height: 46px; height: 52px;
background: #FFCF44; background: linear-gradient(0deg, #FFBF5A 0%, #FFE47A 100%);
color: white; border: 1px solid #FFF9DC;
text-align: center; text-align: center;
line-height: 46px; line-height: 46px;
position: absolute; position: absolute;
left: 590px; left: 850px;
top: -25px; top: 136px;
z-index: 1; z-index: 1;
border-radius: 23px; border-radius: 26px;
} font-size: 30px;
color: #4B1A14;
.guan {
background: v-bind('imageUrls.guan1');
width: 64px;
height: 64px;
position: absolute;
left: 580px;
top: -153px
}
.pai {
background: v-bind('imageUrls.pai1');
width: 212px;
height: 200px;
position: absolute;
left: 570px;
top: 40px;
} }
} }
.rank-no2 { .rank-no2 {
position: relative; position: relative;
.gu-burst {
left: 140px;
top: -170px;
}
.avatar { .avatar {
background: v-bind('imageUrls.avatar') center / cover no-repeat; width: 144px;
width: 120px; height: 144px;
height: 120px; border-radius: 72px;
border-radius: 60px;
position: absolute; position: absolute;
left: 180px; left: 488px;
top: -110px; top: 27px;
/* border: 1px solid red; */
} }
.nickname { .nickname {
width: 167px; width: 220px;
height: 46px; height: 52px;
background: #CA9600; background: linear-gradient(0deg, #AEA6A7 0%, #EAE4DF 100%);
color: white; border: 1px solid white;
color: #383636;
text-align: center; text-align: center;
line-height: 46px; line-height: 52px;
position: absolute; position: absolute;
left: 157px; left: 447px;
top: 2px; top: 160px;
z-index: 1; z-index: 1;
border-radius: 23px; border-radius: 23px;
} font-size: 30px;
.guan {
background: v-bind('imageUrls.guan2') no-repeat center;
width: 96px;
height: 96px;
position: absolute;
left: 134px;
top: -144px;
}
.pai {
background: v-bind('imageUrls.pai2') no-repeat center;
width: 212px;
height: 200px;
position: absolute;
left: 140px;
top: 50px;
} }
} }
.rank-no3 { .rank-no3 {
position: relative; position: relative;
.gu-burst {
left: 1020px;
top: -170px;
}
.avatar { .avatar {
background: v-bind('imageUrls.avatar') center / cover no-repeat; width: 144px;
width: 120px; height: 144px;
height: 120px; border-radius: 72px;
border-radius: 60px;
position: absolute; position: absolute;
left: 1046px; left: 1297px;
top: -110px; top: 26px;
} }
.nickname { .nickname {
width: 167px; width: 220px;
height: 46px; height: 52px;
background: #E2E2E2; background: linear-gradient(0deg, #F3AE76 0%, #F9CDA3 100%);
color: #AFAFAF; color: #6F3A0E;
text-align: center; text-align: center;
line-height: 46px; line-height: 46px;
position: absolute; position: absolute;
left: 1028px; left: 1255px;
top: 2px; top: 160px;
z-index: 1; z-index: 1;
border-radius: 23px; border-radius: 26px;
} font-size: 30px;
border: 1px solid #FEFEFD;
.guan {
background: v-bind('imageUrls.guan3') no-repeat center;
width: 96px;
height: 96px;
position: absolute;
left: 1003px;
top: -146px;
}
.pai {
background: v-bind('imageUrls.pai3') no-repeat center;
width: 212px;
height: 200px;
position: absolute;
right: 130px;
top: 50px;
} }
} }
} }
...@@ -330,11 +251,11 @@ const renderAvatar = (item: any | null) => { ...@@ -330,11 +251,11 @@ const renderAvatar = (item: any | null) => {
.rank-container { .rank-container {
display: flex; display: flex;
align-items: center; align-items: center;
width: 1270px; width: 1570px;
height: 115px; height: 115px;
position: absolute; position: absolute;
bottom: 243px; bottom: 263px;
left: 325px; left: 200px;
>div { >div {
flex: 1; flex: 1;
...@@ -344,13 +265,14 @@ const renderAvatar = (item: any | null) => { ...@@ -344,13 +265,14 @@ const renderAvatar = (item: any | null) => {
.no-icon { .no-icon {
background: v-bind('imageUrls.rankIcon') no-repeat center; background: v-bind('imageUrls.rankIcon') no-repeat center;
width: 40px; width: 42px;
height: 40px; height: 47px;
position: absolute; position: absolute;
text-align: center; text-align: center;
line-height: 40px; line-height: 42px;
color: white; color: #CC7F36;
font-weight: bold; font-weight: bold;
top: 50px;
} }
.img-avatar-container { .img-avatar-container {
...@@ -360,6 +282,8 @@ const renderAvatar = (item: any | null) => { ...@@ -360,6 +282,8 @@ const renderAvatar = (item: any | null) => {
width: 72px; width: 72px;
height: 72px; height: 72px;
border-radius: 36px; border-radius: 36px;
transform: scale(1.6);
top: 50px;
} }
.img-avatar-container::before { .img-avatar-container::before {
...@@ -367,7 +291,7 @@ const renderAvatar = (item: any | null) => { ...@@ -367,7 +291,7 @@ const renderAvatar = (item: any | null) => {
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: inherit; border-radius: inherit;
background: v-bind('imageUrls.avatarAnimation'); background: v-bind('imageUrls.avatarAnimation') center / cover no-repeat;
animation: avatar-rotate 10s linear infinite; animation: avatar-rotate 10s linear infinite;
} }
...@@ -379,23 +303,28 @@ const renderAvatar = (item: any | null) => { ...@@ -379,23 +303,28 @@ const renderAvatar = (item: any | null) => {
width: 60px; width: 60px;
height: 60px; height: 60px;
border-radius: 30px; border-radius: 30px;
background: v-bind('imageUrls.avatar'); background: v-bind('imageUrls.avatar') center / cover no-repeat;
} }
.txt-nickname { .txt-nickname {
color: white; color: #F9EBCB;
font-size: 20px; font-size: 22px;
text-align: center;
position: absolute;
top: 170px;
text-align: center; text-align: center;
/* left:10px; */
margin-left: 68px;
} }
} }
} }
.btn-next { .btn-next {
position: absolute; position: absolute;
bottom: 100px; bottom: 50px;
left: 820px; left: 820px;
width: 316px; width: 318px;
height: 105px; height: 95px;
cursor: pointer; cursor: pointer;
background: v-bind('imageUrls.next') center / cover no-repeat; background: v-bind('imageUrls.next') center / cover no-repeat;
} }
......
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import { $read } from '@/commons/utils' import { $read } from '@/commons/utils'
import Main from '@/pages/Main.vue'
import Login from '@/pages/Login.vue' import Login from '@/pages/Login.vue'
import Game1 from '@/pages/game1/Game.vue' import Game1 from '@/pages/game1/Game.vue'
import Game4 from '@/pages/game4/Game4.vue' import Game4 from '@/pages/game4/Game4.vue'
...@@ -10,7 +11,12 @@ const router = createRouter({ ...@@ -10,7 +11,12 @@ const router = createRouter({
routes: [ routes: [
{ {
path: '/', path: '/',
redirect: () => ($read('token', '') ? '/game1' : '/login'), redirect: () => ($read('token', '') ? '/game1' : '/main'),
},
{
path: '/main',
name: 'Main',
component: Main,
}, },
{ {
path: '/login', path: '/login',
......
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