Commit a8efe97a authored by 陈冲's avatar 陈冲

feat: 功能修复

parent c1dbe1ba
...@@ -14,8 +14,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; ...@@ -14,8 +14,8 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
lazy_static::lazy_static! { lazy_static::lazy_static! {
/// 可以有多个管理员,但不要用同一个账户登录,否则后面的没效果 /// 当前唯一在线管理员:(token, socket)。
static ref CLIENT_ADMIN_MAP: DashMap<String, SocketRef> = DashMap::new(); static ref CLIENT_ADMIN_SESSION: std::sync::Mutex<Option<(String, SocketRef)>> = std::sync::Mutex::new(None);
static ref CLIENT_PLAYER_MAP: DashMap<String, (i32,SocketRef)> = DashMap::new(); // 1: 正在游戏中 static ref CLIENT_PLAYER_MAP: DashMap<String, (i32,SocketRef)> = DashMap::new(); // 1: 正在游戏中
static ref ROOM_STATE: std::sync::Mutex<RoomState> = std::sync::Mutex::new(RoomState::default()); static ref ROOM_STATE: std::sync::Mutex<RoomState> = std::sync::Mutex::new(RoomState::default());
} }
...@@ -234,7 +234,10 @@ pub fn get_player_count() -> usize { ...@@ -234,7 +234,10 @@ pub fn get_player_count() -> usize {
} }
// 获取管理员数量 // 获取管理员数量
pub fn get_admin_count() -> usize { pub fn get_admin_count() -> usize {
CLIENT_ADMIN_MAP.len() CLIENT_ADMIN_SESSION
.lock()
.map(|session| usize::from(session.is_some()))
.unwrap_or(0)
} }
pub fn emit_to_user(userid: &str, event: &str, payload: &Value) { pub fn emit_to_user(userid: &str, event: &str, payload: &Value) {
...@@ -295,12 +298,47 @@ fn broadcast_msgpack_all_player(event: &str, payload: &Value) { ...@@ -295,12 +298,47 @@ fn broadcast_msgpack_all_player(event: &str, payload: &Value) {
} }
/// 发送给所有管理员 /// 发送给所有管理员
fn broadcast_msgpack_all_admin(event: &str, payload: &Value) { fn broadcast_msgpack_all_admin(event: &str, payload: &Value) {
if let Some(payload) = encode_msgpack(payload) { let Some(payload) = encode_msgpack(payload) else {
for socket in CLIENT_ADMIN_MAP.iter() { return;
_ = socket.value().emit(event, &payload); };
}
let socket = CLIENT_ADMIN_SESSION
.lock()
.ok()
.and_then(|session| session.as_ref().map(|(_, socket)| socket.clone()));
if let Some(socket) = socket {
_ = socket.emit(event, &payload);
} }
} }
fn replace_current_admin(userid: String, socket: SocketRef) -> Result<Option<SocketRef>, ()> {
CLIENT_ADMIN_SESSION
.lock()
.map(|mut session| {
let old_socket = session.take().map(|(_, socket)| socket);
*session = Some((userid, socket));
old_socket
})
.map_err(|_| ())
}
fn clear_current_admin_if_match(userid: &str, socket: &SocketRef) -> bool {
CLIENT_ADMIN_SESSION
.lock()
.map(|mut session| {
let should_clear = session
.as_ref()
.map(|(current_userid, current_socket)| {
current_userid == userid && current_socket.id == socket.id
})
.unwrap_or(false);
if should_clear {
*session = None;
}
should_clear
})
.unwrap_or(false)
}
/// 发送给所有管理员和玩家 /// 发送给所有管理员和玩家
fn broadcast_msgpack_all(event: &str, payload: &Value) { fn broadcast_msgpack_all(event: &str, payload: &Value) {
broadcast_msgpack_all_admin(event, payload); broadcast_msgpack_all_admin(event, payload);
...@@ -503,6 +541,8 @@ async fn handle_room_command( ...@@ -503,6 +541,8 @@ async fn handle_room_command(
emit_msgpack(&socket, evt, &ok_resp(Some(json!(index)))); emit_msgpack(&socket, evt, &ok_resp(Some(json!(index))));
if cmd == "room_back" { if cmd == "room_back" {
broadcast_msgpack_all_player(evt, &ok_resp(Some(json!(index)))); broadcast_msgpack_all_player(evt, &ok_resp(Some(json!(index))));
} else if cmd == "room_create" {
broadcast_msgpack_all_player(evt, &ok_resp(Some(json!(index))));
} }
} }
} }
...@@ -534,6 +574,10 @@ async fn handle_room_command( ...@@ -534,6 +574,10 @@ async fn handle_room_command(
.to_string(); .to_string();
if let Ok(mut state) = ROOM_STATE.lock() { if let Ok(mut state) = ROOM_STATE.lock() {
// dbg!(
// &state.current_game_id.clone(),
// &state.status.clone().as_str()
// );
let Some(current_game_id) = state.current_game_id.clone() else { let Some(current_game_id) = state.current_game_id.clone() else {
drop(state); drop(state);
return; return;
...@@ -773,17 +817,30 @@ async fn handle_room_command( ...@@ -773,17 +817,30 @@ async fn handle_room_command(
if !is_admin { if !is_admin {
return; return;
} }
let mut status = "".to_string();
let mut title = "".to_string();
if let Ok(mut state) = ROOM_STATE.lock() { if let Ok(mut state) = ROOM_STATE.lock() {
status = state.current_game_id.clone().unwrap_or_default();
title = get_game_tilte(&status);
state.reset(); state.reset();
drop(state); drop(state);
} }
broadcast_msgpack_all_player(evt, &json!({})); broadcast_msgpack_all_player(
evt,
&err_resp(
&format!("管理员关闭了 {} 该房间", title),
Some(json!(status)),
),
);
broadcast_room_rank(Vec::new()); broadcast_room_rank(Vec::new());
emit_msgpack(&socket, evt, &ok_resp(None)); emit_msgpack(&socket, evt, &ok_resp(None));
} }
// 获取房间状态 // 获取房间状态
"room_state" => { "room_state" => {
if let Ok(state) = ROOM_STATE.lock() { if let Ok(state) = ROOM_STATE.lock() {
let gameId = state.current_game_id.clone().unwrap_or_default();
let count = state.players.len(); let count = state.players.len();
let ranking = state.current_ranking_payload(); let ranking = state.current_ranking_payload();
let status = state.status as i32; let status = state.status as i32;
...@@ -792,7 +849,9 @@ async fn handle_room_command( ...@@ -792,7 +849,9 @@ async fn handle_room_command(
emit_msgpack( emit_msgpack(
&socket, &socket,
evt, evt,
&ok_resp(Some(json!({"status":status,"list":ranking,"count":count}))), &ok_resp(Some(
json!({"status":status,"list":ranking,"count":count,"gameId":gameId,}),
)),
); );
} }
} }
...@@ -858,24 +917,25 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value ...@@ -858,24 +917,25 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
return; return;
} }
} }
// 原子检查加写入,避免并发下同一个 userid 同时登录。
if is_admin { if is_admin {
match CLIENT_ADMIN_MAP.entry(userid.clone()) { match replace_current_admin(userid.clone(), socket.clone()) {
Entry::Occupied(_) => { Ok(Some(old_socket)) => {
emit_msgpack( emit_msgpack(
&socket, &old_socket,
"login_result", "new_admin_result",
&err_resp("当前管理员账号已登录,不能重复登录", Some(json!(1))), &err_resp("有其他管理员登录,您已下线", None),
); );
_ = old_socket.disconnect();
}
Ok(None) => {}
Err(_) => {
_ = socket.disconnect(); _ = socket.disconnect();
return; return;
} }
Entry::Vacant(entry) => {
entry.insert(socket.clone());
}
} }
} else { } else {
if CLIENT_ADMIN_MAP.is_empty() { if get_admin_count() == 0 {
emit_msgpack( emit_msgpack(
&socket, &socket,
"connect_result", "connect_result",
...@@ -896,7 +956,6 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value ...@@ -896,7 +956,6 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
} }
} }
} }
// dbg!(&is_admin,&userid);
// 鉴权通过后,在这里挂玩家消息事件处理器。 // 鉴权通过后,在这里挂玩家消息事件处理器。
let message_userid = userid.clone(); let message_userid = userid.clone();
socket.on("message", move |socket: SocketRef, data: Data<Bytes>| { socket.on("message", move |socket: SocketRef, data: Data<Bytes>| {
...@@ -921,9 +980,11 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value ...@@ -921,9 +980,11 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
move |socket: SocketRef, _reason: DisconnectReason| async move { move |socket: SocketRef, _reason: DisconnectReason| async move {
if !is_create_user { if !is_create_user {
if is_admin { if is_admin {
CLIENT_ADMIN_MAP.remove(&userid); if !clear_current_admin_if_match(&userid, &socket) {
return;
}
// 前端页面刷新时房间状态丢失体验会不好 // 前端页面刷新时房间状态丢失体验会不好
if CLIENT_ADMIN_MAP.is_empty() { if get_admin_count() == 0 {
if let Ok(mut state) = ROOM_STATE.lock() { if let Ok(mut state) = ROOM_STATE.lock() {
state.reset(); // 如果管理员都退出了,就把房间状态重置。 state.reset(); // 如果管理员都退出了,就把房间状态重置。
drop(state); drop(state);
...@@ -1013,12 +1074,25 @@ pub async fn login(req: &mut Request, res: &mut Response) { ...@@ -1013,12 +1074,25 @@ pub async fn login(req: &mut Request, res: &mut Response) {
res.render(Json(err_resp("用户名或密码不能为空", None))); res.render(Json(err_resp("用户名或密码不能为空", None)));
return; return;
} }
//当前管理员已登录,不能重复登录 if get_admin_count() > 0 {
if (CLIENT_ADMIN_MAP.contains_key(&manager.token)) { if let Ok(state) = ROOM_STATE.lock() {
res.render(Json(err_resp("当前管理员已登录,不能重复登录", None))); let game_id = state.current_game_id.clone().unwrap_or("".to_string());
return; if matches!(
state.status,
RoomStatus::Start | RoomStatus::Running | RoomStatus::Submit
) {
drop(state);
res.render(Json(err_resp(
&format!(
"当前管理员已开启游戏房间 - {},请稍后再登录",
get_game_tilte(&game_id)
),
None,
)));
return;
}
}
} }
res.render(Json(ok_resp(Some(json!({ res.render(Json(ok_resp(Some(json!({
"passphrase": passphrase, "passphrase": passphrase,
"token": manager.token, "token": manager.token,
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>小游戏-H5</title> <title>小游戏-H5</title>
<style>body{background: #FA6047;}</style> <style>body{background: #FA6047;}</style>
<script>localStorage.setItem('domain', '');</script> <script>localStorage.setItem('domain', '');</script>
......
...@@ -21,6 +21,7 @@ html, ...@@ -21,6 +21,7 @@ html,
body { body {
width: 100%; width: 100%;
height: 100%; height: 100%;
touch-action: manipulation;
} }
body { body {
......
...@@ -67,8 +67,8 @@ export function initSocket(args: SocketInitArgs): Socket | null { ...@@ -67,8 +67,8 @@ export function initSocket(args: SocketInitArgs): Socket | null {
}); });
socket.onAny((evt, payload) => { socket.onAny((evt, payload) => {
const data = decode(payload); const data = decode(payload);
if($is_run_local()){ if ($is_run_local()) {
console.log(evt, data); console.log("recv<============", evt, data);
} }
messageHandlers.forEach((handler) => handler(evt, data)); messageHandlers.forEach((handler) => handler(evt, data));
}); });
...@@ -94,7 +94,9 @@ export function sendSocketMessage(cmd: string, data: any = {}): boolean { ...@@ -94,7 +94,9 @@ export function sendSocketMessage(cmd: string, data: any = {}): boolean {
} }
return false; return false;
} }
if ($is_run_local()) {
console.log('================>send', cmd, data);
}
socket.send(encode({ cmd, data })); socket.send(encode({ cmd, data }));
return true; return true;
} }
......
...@@ -16,7 +16,7 @@ const props = withDefaults(defineProps<{ ...@@ -16,7 +16,7 @@ const props = withDefaults(defineProps<{
height: 1624, height: 1624,
mode: 'contain', mode: 'contain',
backgroundMode: 'cover', backgroundMode: 'cover',
showGameRule: false, showGameRule: true, //玩家要有操作动作才能播放音乐,这里会默认显示游戏规则,让玩家有关闭操作
showGameRank: false showGameRank: false
}) })
......
...@@ -91,6 +91,12 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -91,6 +91,12 @@ export function useGameSocket(options: GameSocketOptions) {
resubmitcount++; resubmitcount++;
options.onRescoreSubmitted?.(resubmitcount); options.onRescoreSubmitted?.(resubmitcount);
}, 200); }, 200);
} else if (evt == 'room_close_result') {
userJoinStatus.value = false
if (data == options.gameId) {
options.onRoomClosed?.(data)
}
return;
} }
return; return;
} }
...@@ -101,11 +107,15 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -101,11 +107,15 @@ export function useGameSocket(options: GameSocketOptions) {
} }
switch (evt) { switch (evt) {
case 'connect_result':{ case 'connect_result': {
debugger debugger
break; break;
} }
case 'room_create_result': { case 'room_create_result': {
if (`game${data}` == options.gameId) {
location.reload();
return;
}
const nextPath = getGameHomePath(data) const nextPath = getGameHomePath(data)
if (nextPath && router.currentRoute.value.path !== nextPath) { if (nextPath && router.currentRoute.value.path !== nextPath) {
...@@ -153,15 +163,13 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -153,15 +163,13 @@ export function useGameSocket(options: GameSocketOptions) {
options.onScoreSubmitted?.(true, data) options.onScoreSubmitted?.(true, data)
break break
} }
case 'room_close_result': {
userJoinStatus.value = false
options.onRoomClosed?.(data)
break
}
case 'room_back_result': { case 'room_back_result': {
options.onRoomBack?.(data); options.onRoomBack?.(data);
break; break;
} }
default: {
debugger
}
} }
options.onEvent?.(evt, payload) options.onEvent?.(evt, payload)
......
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue' import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { cssAssetUrl } from '@/commons/assets.ts' import { cssAssetUrl } from '@/commons/assets.ts'
import MobileStage from '@/components/MobileStage.vue' import MobileStage from '@/components/MobileStage.vue'
import { useGameSocket, joinSharedRoom, sendGameMessage, userJoinStatus } from '@/composables/useGameSocket' import { useGameSocket, joinSharedRoom, sendGameMessage, userJoinStatus } from '@/composables/useGameSocket'
...@@ -42,7 +42,7 @@ const isGuPlaying = ref(false) ...@@ -42,7 +42,7 @@ const isGuPlaying = ref(false)
const token = ref(wechat?.token ?? '') const token = ref(wechat?.token ?? '')
const nickname = ref(wechat?.nickname ?? '') const nickname = ref(wechat?.nickname ?? '')
const avatar = ref(wechat?.avatar ?? '') const avatar = ref(wechat?.avatar ?? '')
const showGameRule = ref(false) const showGameRule = ref(true)
const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null) const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null)
const _offSocketMessage = ref<(() => void) | undefined>() const _offSocketMessage = ref<(() => void) | undefined>()
...@@ -261,8 +261,8 @@ const showGameRuleHandler = () => { ...@@ -261,8 +261,8 @@ const showGameRuleHandler = () => {
4.禁止外挂,作弊,违规账号取消成绩与领奖资格;<br /><br /> 4.禁止外挂,作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快! 祝您游戏愉快!
</div> </div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div> </div>
<div class="rule-close" @click="showGameRuleHandler"></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"
...@@ -279,12 +279,9 @@ const showGameRuleHandler = () => { ...@@ -279,12 +279,9 @@ const showGameRuleHandler = () => {
<style scoped> <style scoped>
.rule-container { .rule-container {
.rule-txt-container { .rule-txt-container {
position: absolute;
background: v-bind('imageUrls.rule') center / cover no-repeat; background: v-bind('imageUrls.rule') center / cover no-repeat;
width: 692px; width: 692px;
height: 797px; height: 797px;
left: -157px;
top: -75px;
transform: scale(0.5); transform: scale(0.5);
.rule-title { .rule-title {
...@@ -307,11 +304,11 @@ const showGameRuleHandler = () => { ...@@ -307,11 +304,11 @@ const showGameRuleHandler = () => {
.rule-close { .rule-close {
width: 57px; width: 57px;
height: 57px; height: 57px;
margin: 0 auto; background: v-bind('imageUrls.close') center / cover no-repeat;
margin-top: 450px; transform: translateX(-50%) scale(1.4);
background: v-bind('imageUrls.close') center/cover; position: absolute;
position: relative; bottom: -75px;
transform: scale(0.8); left: 50%;
} }
} }
</style> </style>
...@@ -23,7 +23,7 @@ onMounted(() => { ...@@ -23,7 +23,7 @@ onMounted(() => {
<div class="img-avatar" :style="`background: url(${avatar});border-radius:66px;width:132px;height:132px;`"> <div class="img-avatar" :style="`background: url(${avatar});border-radius:66px;width:132px;height:132px;`">
</div> </div>
<div class="txt-nickname">{{ nickname }}</div> <div class="txt-nickname">{{ nickname }}</div>
<div class="txt-loading">您已成功加入游戏等待主持人开始</div> <div class="txt-loading" v-if="userJoinStatus">您已成功加入游戏等待主持人开始</div>
<div class="txt-loading2">游戏即将开始 敬请期待</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>
......
...@@ -8,6 +8,7 @@ import PlayingView from './views/PlayingView.vue' ...@@ -8,6 +8,7 @@ import PlayingView from './views/PlayingView.vue'
import ScoreView from './views/ScoreView.vue' import ScoreView from './views/ScoreView.vue'
import { $getWechat } from '@/commons/utils.ts' import { $getWechat } from '@/commons/utils.ts'
const gameId = 'game4';
type GameView = 'loading' | 'playing' | 'score' type GameView = 'loading' | 'playing' | 'score'
const imageUrls = { const imageUrls = {
...@@ -30,7 +31,9 @@ const imageUrls = { ...@@ -30,7 +31,9 @@ const imageUrls = {
scoreBg: cssAssetUrl('game1/score-bg.png'), scoreBg: cssAssetUrl('game1/score-bg.png'),
back: cssAssetUrl('game4/back.svg'), back: cssAssetUrl('game4/back.svg'),
replay: cssAssetUrl('game4/replay.svg'), replay: cssAssetUrl('game4/replay.svg'),
close: cssAssetUrl('game4/h5-game4-close.svg'),
close: cssAssetUrl('game1/close.png'),
rule: cssAssetUrl('game1/rule.png')
} }
const wechat = $getWechat() const wechat = $getWechat()
...@@ -52,7 +55,7 @@ const currentYaoyiyao = ref(imageUrls.yaoyiyao) ...@@ -52,7 +55,7 @@ const currentYaoyiyao = ref(imageUrls.yaoyiyao)
const token = ref(wechat?.token ?? '') const token = ref(wechat?.token ?? '')
const nickname = ref(wechat?.nickname ?? '') const nickname = ref(wechat?.nickname ?? '')
const avatar = ref(wechat?.avatar ?? '') const avatar = ref(wechat?.avatar ?? '')
const showGameRule = ref(false) const showGameRule = ref(true)
const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null) const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null)
const _offSocketMessage = ref<(() => void) | undefined>() const _offSocketMessage = ref<(() => void) | undefined>()
...@@ -120,6 +123,7 @@ function showLoadingView() { ...@@ -120,6 +123,7 @@ function showLoadingView() {
stopGameCountdown() stopGameCountdown()
currentView.value = 'loading' currentView.value = 'loading'
setDivDescVisible(false) setDivDescVisible(false)
alert('管理员关闭了该房间')
} }
function resetToLoadingView() { function resetToLoadingView() {
...@@ -155,13 +159,13 @@ function replayGame() { ...@@ -155,13 +159,13 @@ function replayGame() {
function backToWaiting() { function backToWaiting() {
resetToLoadingView() resetToLoadingView()
joinSharedRoom(nickname.value, token.value, avatar.value) joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
} }
function handleRoomBack() { function handleRoomBack() {
resetToLoadingView() resetToLoadingView()
window.setTimeout(() => { window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value) joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
} }
...@@ -175,14 +179,15 @@ const showGameRuleHandler = () => { ...@@ -175,14 +179,15 @@ const showGameRuleHandler = () => {
} }
if (wechat) { if (wechat) {
const { offSocketMessage } = useGameSocket({ const { offSocketMessage } = useGameSocket({
gameId: 'game4', gameId,
auth: { auth: {
userid: wechat.token, userid: wechat.token,
}, },
onConnect: () => { onConnect: () => {
window.setTimeout(() => { window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value) joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
}, },
onGameStart: async () => { onGameStart: async () => {
...@@ -224,8 +229,7 @@ onBeforeUnmount(() => { ...@@ -224,8 +229,7 @@ onBeforeUnmount(() => {
</script> </script>
<template> <template>
<MobileStage v-if="token" :showGameRule="showGameRule" ref="mobileStageRef" <MobileStage v-if="token" :showGameRule="showGameRule" ref="mobileStageRef" :background="stageBackground">
:background="stageBackground">
<template #gameRule> <template #gameRule>
<div class="rule-container"> <div class="rule-container">
<div class="rule-txt-container"> <div class="rule-txt-container">
...@@ -239,16 +243,14 @@ onBeforeUnmount(() => { ...@@ -239,16 +243,14 @@ onBeforeUnmount(() => {
5. 禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br /> 5. 禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快! 祝您游戏愉快!
</div> </div>
</div> <div class="rule-close" @click="showGameRuleHandler"></div>
<div class="rule-close">
<div @click="showGameRuleHandler"></div>
</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"
@touchGameRule="showGameRuleHandler" /> @touchGameRule="showGameRuleHandler" />
<!-- <PlayingView v-else :image-urls="imageUrls" :tick="tick" :rank="rank" --> <!-- <PlayingView v-else :image-urls="imageUrls" :tick="tick" :rank="rank" -->
<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"
:currentHorse="currentHorse" :currentYaoyiyao="currentYaoyiyao" :countdown-interval="countdownInterval" :currentHorse="currentHorse" :currentYaoyiyao="currentYaoyiyao" :countdown-interval="countdownInterval"
:is-div-desc-visible="isDivDescVisible" @touch="touchHandler" /> :is-div-desc-visible="isDivDescVisible" @touch="touchHandler" />
<ScoreView v-else :image-urls="imageUrls" :tick="tick" :level="level" @replay="replayGame" <ScoreView v-else :image-urls="imageUrls" :tick="tick" :level="level" @replay="replayGame"
...@@ -261,42 +263,36 @@ onBeforeUnmount(() => { ...@@ -261,42 +263,36 @@ onBeforeUnmount(() => {
<style scoped> <style scoped>
.rule-container { .rule-container {
.rule-txt-container { .rule-txt-container {
width: 62vw; background: v-bind('imageUrls.rule') center / cover no-repeat;
margin: 0 40px; width: 692px;
border-radius: 5px; height: 797px;
background: #FFF9E4; transform: scale(0.5);
padding-top: 20px;
padding-bottom: 10px;
.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; background: v-bind('imageUrls.close') center / cover no-repeat;
background: v-bind('imageUrls.close') center/cover; transform: translateX(-50%) scale(1.4);
position: relative; position: absolute;
bottom: -75px;
>div { left: 50%;
position: absolute;
bottom: 0;
width: 32px;
height: 32px;
border-radius: 16px;
}
} }
} }
</style> </style>
...@@ -9,6 +9,7 @@ import ScoreView from './views/ScoreView.vue' ...@@ -9,6 +9,7 @@ import ScoreView from './views/ScoreView.vue'
import { $getWechat } from '@/commons/utils.ts' import { $getWechat } from '@/commons/utils.ts'
import { playGame1Music } from '@/commons/music' import { playGame1Music } from '@/commons/music'
const gameId = 'game5';
type GameView = 'loading' | 'playing' | 'score' type GameView = 'loading' | 'playing' | 'score'
type RankPlayer = { type RankPlayer = {
rank: number rank: number
...@@ -63,7 +64,7 @@ const isGuPlaying = ref(false) ...@@ -63,7 +64,7 @@ const isGuPlaying = ref(false)
const token = ref(wechat?.token ?? '') const token = ref(wechat?.token ?? '')
const nickname = ref(wechat?.nickname ?? '') const nickname = ref(wechat?.nickname ?? '')
const avatar = ref(wechat?.avatar ?? '') const avatar = ref(wechat?.avatar ?? '')
const showGameRule = ref(false) const showGameRule = ref(true)
const showGameRank = ref(false) const showGameRank = ref(false)
const rankList = ref<RankPlayer[]>([]) const rankList = ref<RankPlayer[]>([])
const displayRankList = computed<RankPlayer[]>(() => { const displayRankList = computed<RankPlayer[]>(() => {
...@@ -149,6 +150,7 @@ function showLoadingView() { ...@@ -149,6 +150,7 @@ function showLoadingView() {
stopGameCountdown() stopGameCountdown()
currentView.value = 'loading' currentView.value = 'loading'
setDivDescVisible(false) setDivDescVisible(false)
alert('管理员关闭了该房间')
} }
function resetToLoadingView() { function resetToLoadingView() {
...@@ -191,7 +193,7 @@ function showGameOverRank() { ...@@ -191,7 +193,7 @@ function showGameOverRank() {
function handleRoomBack() { function handleRoomBack() {
resetToLoadingView() resetToLoadingView()
window.setTimeout(() => { window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value) joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
} }
...@@ -210,7 +212,7 @@ if (wechat) { ...@@ -210,7 +212,7 @@ if (wechat) {
}, },
onConnect: () => { onConnect: () => {
window.setTimeout(() => { window.setTimeout(() => {
const joined = joinSharedRoom(nickname.value, token.value, avatar.value) const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
}, },
onGameStart: async () => { onGameStart: async () => {
...@@ -325,16 +327,16 @@ const rankCloseHandler = () => { ...@@ -325,16 +327,16 @@ const rankCloseHandler = () => {
4.禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br /> 4.禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快! 祝您游戏愉快!
</div> </div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div> </div>
<div class="rule-close" @click="showGameRuleHandler"></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"
@touchGameRule="showGameRuleHandler" /> @touchGameRule="showGameRuleHandler" />
<!-- <PlayingView v-else :image-urls="imageUrls" :tick="tick" :rank="rank" --> <!-- <PlayingView v-else :image-urls="imageUrls" :tick="tick" :rank="rank" -->
<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"
@hit="hitHandler" /> @hit="hitHandler" />
<!-- <ScoreView v-else :image-urls="imageUrls" :rank="rank" :tick="tick" /> --> <!-- <ScoreView v-else :image-urls="imageUrls" :rank="rank" :tick="tick" /> -->
</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;">
...@@ -344,12 +346,9 @@ const rankCloseHandler = () => { ...@@ -344,12 +346,9 @@ const rankCloseHandler = () => {
<style scoped> <style scoped>
.rule-container { .rule-container {
.rule-txt-container { .rule-txt-container {
position: absolute;
background: v-bind('imageUrls.rule') center / cover no-repeat; background: v-bind('imageUrls.rule') center / cover no-repeat;
width: 692px; width: 692px;
height: 797px; height: 797px;
left: -157px;
top: -75px;
transform: scale(0.5); transform: scale(0.5);
.rule-title { .rule-title {
...@@ -372,11 +371,11 @@ const rankCloseHandler = () => { ...@@ -372,11 +371,11 @@ const rankCloseHandler = () => {
.rule-close { .rule-close {
width: 57px; width: 57px;
height: 57px; height: 57px;
margin: 0 auto; background: v-bind('imageUrls.close') center / cover no-repeat;
margin-top: 450px; transform: translateX(-50%) scale(1.4);
background: v-bind('imageUrls.close') center/cover; position: absolute;
position: relative; bottom: -75px;
transform: scale(0.8); left: 50%;
} }
} }
......
...@@ -145,6 +145,7 @@ onMounted(() => { ...@@ -145,6 +145,7 @@ onMounted(() => {
height: 126px; height: 126px;
color: white; color: white;
position: absolute; position: absolute;
z-index: -1;
top:786px; top:786px;
left: 50%; left: 50%;
transform: translate(-50%); transform: translate(-50%);
......
...@@ -57,13 +57,20 @@ const displayRankList = computed<RankPlayer[]>(() => { ...@@ -57,13 +57,20 @@ const displayRankList = computed<RankPlayer[]>(() => {
} }
}) })
}) })
const rankScore = computed<number | string>(() => {
const currentPlayer = rankList.value
.slice(0, 10)
.find(item => item.rank === rank.value)
return currentPlayer?.score ?? score.value
})
// const guFrames: [string, string, string] = [imageUrls.gu01, imageUrls.gu02, imageUrls.gu03] // const guFrames: [string, string, string] = [imageUrls.gu01, imageUrls.gu02, imageUrls.gu03]
// const currentGu = ref(imageUrls.gu01) // const currentGu = ref(imageUrls.gu01)
const isGuPlaying = ref(false) const isGuPlaying = ref(false)
const token = ref(wechat?.token ?? '') const token = ref(wechat?.token ?? '')
const nickname = ref(wechat?.nickname ?? '') const nickname = ref(wechat?.nickname ?? '')
const avatar = ref(wechat?.avatar ?? '') const avatar = ref(wechat?.avatar ?? '')
const showGameRule = ref(false) const showGameRule = ref(true)
const showGameRank = ref(false) const showGameRank = ref(false)
const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null) const mobileStageRef = ref<InstanceType<typeof MobileStage> | null>(null)
...@@ -297,8 +304,8 @@ const rankCloseHandler = () => { ...@@ -297,8 +304,8 @@ const rankCloseHandler = () => {
4. 禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br /> 4. 禁止外挂作弊,违规账号取消成绩与领奖资格;<br /><br />
祝您游戏愉快! 祝您游戏愉快!
</div> </div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div> </div>
<div class="rule-close" @click="showGameRuleHandler"></div>
</div> </div>
</template> </template>
<template #gameRank> <template #gameRank>
...@@ -330,7 +337,7 @@ const rankCloseHandler = () => { ...@@ -330,7 +337,7 @@ const rankCloseHandler = () => {
<div class="avatar" :style="{ backgroundImage: `url(${avatar})` }"></div> <div class="avatar" :style="{ backgroundImage: `url(${avatar})` }"></div>
<div>{{ nickname }}</div> <div>{{ nickname }}</div>
</div> </div>
<div class="col-3">{{ score }}</div> <div class="col-3">{{ rankScore }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -348,12 +355,9 @@ const rankCloseHandler = () => { ...@@ -348,12 +355,9 @@ const rankCloseHandler = () => {
<style scoped> <style scoped>
.rule-container { .rule-container {
.rule-txt-container { .rule-txt-container {
position: absolute;
background: v-bind('imageUrls.rule') center / cover no-repeat; background: v-bind('imageUrls.rule') center / cover no-repeat;
width: 692px; width: 692px;
height: 797px; height: 797px;
left: -157px;
top: -75px;
transform: scale(0.5); transform: scale(0.5);
.rule-title { .rule-title {
...@@ -376,11 +380,11 @@ const rankCloseHandler = () => { ...@@ -376,11 +380,11 @@ const rankCloseHandler = () => {
.rule-close { .rule-close {
width: 57px; width: 57px;
height: 57px; height: 57px;
margin: 0 auto; background: v-bind('imageUrls.close') center / cover no-repeat;
margin-top: 450px; transform: translateX(-50%) scale(1.4);
background: v-bind('imageUrls.close') center/cover; position: absolute;
position: relative; bottom: -75px;
transform: scale(0.8); left: 50%;
} }
} }
......
...@@ -77,7 +77,7 @@ onMounted(() => { ...@@ -77,7 +77,7 @@ onMounted(() => {
.game-desc { .game-desc {
position: absolute; position: absolute;
z-index: 2; z-index: 9999999999;
top: 50%; top: 50%;
left: var(--stage-viewport-left, 0); left: var(--stage-viewport-left, 0);
display: flex; display: flex;
......
...@@ -26,7 +26,7 @@ function copyImageAssets() { ...@@ -26,7 +26,7 @@ function copyImageAssets() {
} }
export default defineConfig(({ command }) => ({ export default defineConfig(({ command }) => ({
base: command === 'build' ? '/cc/h5/' : '/', base: command === 'build' ? '/h5/' : '/',
define: { define: {
__APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''), __APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
__DEBUG__: JSON.stringify(command === 'serve'), __DEBUG__: JSON.stringify(command === 'serve'),
......
import game1MusicUrl from '@/assets/images/game1.mp3' import game1MusicUrl from '@/assets/images/game1.mp3'
import game4MusicUrl from '@/assets/images/game1.mp3' // 需要改成正式的路径
import game5MusicUrl from '@/assets/images/game1.mp3' // 需要改成正式的路径
import game6MusicUrl from '@/assets/images/game6.mp3' import game6MusicUrl from '@/assets/images/game6.mp3'
import go321 from '@/assets/images/321go.mp3' import go321 from '@/assets/images/321go.mp3'
import applause from '@/assets/images/applause.mp3'
let backgroundMusic: HTMLAudioElement | null = null let backgroundMusic: HTMLAudioElement | null = null
let countdownTemplate: HTMLAudioElement | null = null let countdownTemplate: HTMLAudioElement | null = null
let pendingCountdownRetry: (() => void) | null = null let pendingCountdownRetry: (() => void) | null = null
const countdownAudios = new Set<HTMLAudioElement>() const countdownAudios = new Set<HTMLAudioElement>()
function getBackgroundMusic(music: string) {
export function stopAllMusic() {
stopApplauseMusic();
stopGame1Music();
// stopGame2Music();
// stopGame3Music();
stopGame4Music();
stopGame5Music();
stopGame6Music();
backgroundMusic = null;
}
function getBackgroundMusic(music: string, force: boolean = true) {
if (force) { //
if (backgroundMusic) {
stopApplauseMusic();
stopGame1Music();
// stopGame2Music();
// stopGame3Music();
stopGame4Music();
stopGame5Music();
stopGame6Music();
backgroundMusic = null;
}
}
if (!backgroundMusic) { if (!backgroundMusic) {
backgroundMusic = new Audio(music) backgroundMusic = new Audio(music)
backgroundMusic.loop = true backgroundMusic.loop = true
...@@ -19,6 +47,26 @@ function getBackgroundMusic(music: string) { ...@@ -19,6 +47,26 @@ function getBackgroundMusic(music: string) {
return backgroundMusic return backgroundMusic
} }
export async function playApplauseMusic() {
const audio = getBackgroundMusic(applause)
try {
await audio.play()
window.removeEventListener('pointerdown', playApplauseMusic)
window.removeEventListener('keydown', playApplauseMusic)
} catch {
window.addEventListener('pointerdown', playApplauseMusic, { once: false })
window.addEventListener('keydown', playApplauseMusic, { once: false })
}
}
export function stopApplauseMusic() {
window.removeEventListener('pointerdown', playApplauseMusic)
window.removeEventListener('keydown', playApplauseMusic)
backgroundMusic?.pause()
}
export async function playGame1Music() { export async function playGame1Music() {
const audio = getBackgroundMusic(game1MusicUrl) const audio = getBackgroundMusic(game1MusicUrl)
...@@ -38,6 +86,45 @@ export function stopGame1Music() { ...@@ -38,6 +86,45 @@ export function stopGame1Music() {
backgroundMusic?.pause() backgroundMusic?.pause()
} }
export async function playGame4Music() {
const audio = getBackgroundMusic(game4MusicUrl)
try {
await audio.play()
window.removeEventListener('pointerdown', playGame4Music)
window.removeEventListener('keydown', playGame4Music)
} catch {
window.addEventListener('pointerdown', playGame4Music, { once: true })
window.addEventListener('keydown', playGame4Music, { once: true })
}
}
export function stopGame4Music() {
window.removeEventListener('pointerdown', playGame4Music)
window.removeEventListener('keydown', playGame4Music)
backgroundMusic?.pause()
}
export async function playGame5Music() {
const audio = getBackgroundMusic(game4MusicUrl)
try {
await audio.play()
window.removeEventListener('pointerdown', playGame5Music)
window.removeEventListener('keydown', playGame5Music)
} catch {
window.addEventListener('pointerdown', playGame5Music, { once: true })
window.addEventListener('keydown', playGame5Music, { once: true })
}
}
export function stopGame5Music() {
window.removeEventListener('pointerdown', playGame5Music)
window.removeEventListener('keydown', playGame5Music)
backgroundMusic?.pause()
}
export async function playGame6Music() { export async function playGame6Music() {
const audio = getBackgroundMusic(game6MusicUrl) const audio = getBackgroundMusic(game6MusicUrl)
......
...@@ -44,7 +44,7 @@ export function initSocket(args: SocketInitArgs): Socket | null { ...@@ -44,7 +44,7 @@ export function initSocket(args: SocketInitArgs): Socket | null {
socket = io(args.client_ns, { socket = io(args.client_ns, {
path: args.socket_path, path: args.socket_path,
auth: args.auth, auth: args.auth,
transports: ["websocket"], transports: ["websocket"],
upgrade: false, upgrade: false,
forceNew: true, forceNew: true,
...@@ -57,7 +57,7 @@ export function initSocket(args: SocketInitArgs): Socket | null { ...@@ -57,7 +57,7 @@ export function initSocket(args: SocketInitArgs): Socket | null {
socket.onAny((evt, payload) => { socket.onAny((evt, payload) => {
const data = decode(payload); const data = decode(payload);
if ($is_run_local()) { if ($is_run_local()) {
console.log(evt, data); console.log("recv<============", evt, data);
} }
messageHandlers.forEach((handler) => handler(evt, data)); messageHandlers.forEach((handler) => handler(evt, data));
}); });
...@@ -78,7 +78,7 @@ export function sendSocketMessage(cmd: string, data: any = {}): boolean { ...@@ -78,7 +78,7 @@ export function sendSocketMessage(cmd: string, data: any = {}): boolean {
return false; return false;
} }
if ($is_run_local()) { if ($is_run_local()) {
console.log('===================>send', cmd, data); console.log('================>send', cmd, data);
} }
socket.send(encode({ cmd, data })); socket.send(encode({ cmd, data }));
return true; return true;
......
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
import { onBeforeUnmount, onMounted } from 'vue' import { onBeforeUnmount, onMounted } from 'vue'
import { $read_socket_storge } from '@/commons/utils' import { $read_socket_storge, $remove_socket_storage } from '@/commons/utils'
import { initSocket, onSocketMessage, sendSocketMessage } from '@/commons/ws' import { initSocket, onSocketMessage, sendSocketMessage } from '@/commons/ws'
import router from '@/router'
type SocketPayload = { type SocketPayload = {
msg?: string msg?: string
...@@ -10,13 +11,14 @@ type SocketPayload = { ...@@ -10,13 +11,14 @@ type SocketPayload = {
} }
type AdminGameSocketOptions = { type AdminGameSocketOptions = {
gameId: string gameId?: string,
loginRedirectPath?: string loginRedirectPath?: string
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>
onRoomRank?: (data: any) => void onRoomRank?: (data: any) => void
onRelogin?: () => void onRelogin?: () => void
onAny?: (evt: string, data: any) => void
} }
export function useAdminGameSocket(options: AdminGameSocketOptions) { export function useAdminGameSocket(options: AdminGameSocketOptions) {
...@@ -41,13 +43,13 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -41,13 +43,13 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
return sendSocketMessage(cmd, data) return sendSocketMessage(cmd, data)
} }
function closeRoom(){ function closeRoom() {
return sendSocketMessage('room_close', {}) return sendSocketMessage('room_close', {})
} }
onMounted(() => { onMounted(() => {
const { passphrase, token, socket_path, client_ns, domain } = $read_socket_storge() const { passphrase, token, socket_path, client_ns, domain } = $read_socket_storge()
initSocket({ let socket = initSocket({
socket_path: `${domain}${socket_path}`, socket_path: `${domain}${socket_path}`,
client_ns, client_ns,
auth: { auth: {
...@@ -67,33 +69,51 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -67,33 +69,51 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
// } // }
if (evt == 'login_result') { if (evt == 'login_result') {
alert(msg) alert(msg)
options.onRelogin?.() // options.onRelogin?.()
socket?.close();
$remove_socket_storage();
await router.replace(`/login`);
location.href = location.href;
return;
}
else if (evt == 'new_admin_result') {
alert(msg);
socket?.close();
$remove_socket_storage();
await router.replace(`/login`);
location.href = location.href;
return; return;
} }
alert(msg)
return
} }
switch (evt) { switch (evt) {
case 'room_state_result': { case 'room_state_result': {
if (data.gameId) {
//如果这两个值不一致说明浏览器有直接更新游戏房间号
if (data.gameId != options.gameId) {
location.reload();
return;
}
}
options.onRoomState?.(data) options.onRoomState?.(data)
break return
} }
case 'room_join_result': { case 'room_join_result': {
options.onRoomJoin?.(data) options.onRoomJoin?.(data)
break return
} }
case 'game_start_result': { case 'game_start_result': {
if (data === options.gameId) { if (data === options.gameId) {
await options.onGameStart?.(data) await options.onGameStart?.(data)
} }
break return
} }
case 'room_rank_result': { case 'room_rank_result': {
options.onRoomRank?.(data) options.onRoomRank?.(data)
break return
} }
} }
options?.onAny?.(evt, payload);
}) })
}) })
......
<script setup lang="ts"> <script setup lang="ts">
import { cssAssetUrl } from '@/commons/assets';
import { $save } from '@/commons/utils.ts'; import { $save } from '@/commons/utils.ts';
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const emit = defineEmits<{
loginSuccess: []
}>()
const router = useRouter() const router = useRouter()
const username = ref('') const username = ref('')
const password = ref('') const password = ref('')
const imageUrls = {
bg1: cssAssetUrl('main-bg1.png'),
logo: cssAssetUrl('game1/logo.png'),
title1: cssAssetUrl('main-title.png'),
}
async function login() { async function login() {
let domain = localStorage.getItem('domain') || ''; let domain = localStorage.getItem('domain') || '';
const res = await fetch(`${domain}/manager/login`, { const res = await fetch(`${domain}/manager/login`, {
...@@ -22,11 +25,10 @@ async function login() { ...@@ -22,11 +25,10 @@ async function login() {
}) })
const data = await res.json() const data = await res.json()
if (!res.ok || data.state !== 1) { // if (!res.ok || data.state !== 1) {
alert(data.msg || 'Login failed') // alert(data.msg || 'Login failed')
throw new Error(data.msg || 'Login failed') // throw new Error(data.msg || 'Login failed')
} // }
return data return data
} }
...@@ -40,7 +42,8 @@ const loginHandler = async () => { ...@@ -40,7 +42,8 @@ const loginHandler = async () => {
for (let i in data) { for (let i in data) {
$save(i, data[i]); $save(i, data[i]);
} }
emit('loginSuccess'); alert('登录成功')
await router.replace(`/main`);
} }
onMounted(() => { onMounted(() => {
...@@ -48,28 +51,62 @@ onMounted(() => { ...@@ -48,28 +51,62 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="login-page"> <div class="bg">
<form class="login-panel" @submit.prevent="loginHandler"> <div class="img-logo"></div>
<div class="panel-header"> <div class="img-title1"></div>
<h1>管理登录</h1> <div class="login-page">
</div> <form class="login-panel" @submit.prevent="loginHandler">
<div class="panel-header">
<label class="field"> <h1>管理登录</h1>
<span>用户名</span> </div>
<input v-model="username" type="text" autocomplete="username" placeholder="请输入用户名" />
</label> <label class="field">
<span>用户名</span>
<label class="field"> <input v-model="username" type="text" autocomplete="username" placeholder="请输入用户名" />
<span>密码</span> </label>
<input v-model="password" type="password" autocomplete="current-password" placeholder="请输入密码" />
</label> <label class="field">
<span>密码</span>
<button class="login-button" type="submit">登录</button> <input v-model="password" type="password" autocomplete="current-password" placeholder="请输入密码" />
</form> </label>
<button class="login-button" type="submit">登录</button>
</form>
</div>
</div> </div>
</template> </template>
<style scoped> <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;
}
}
.login-page { .login-page {
position: absolute; position: absolute;
z-index: 1; z-index: 1;
...@@ -79,7 +116,6 @@ onMounted(() => { ...@@ -79,7 +116,6 @@ onMounted(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden; overflow: hidden;
background: rgba(0, 0, 0, 0.8);
} }
.login-panel { .login-panel {
......
<script setup lang="ts"> <script setup lang="ts">
import { cssAssetUrl } from '@/commons/assets.ts'; import { cssAssetUrl } from '@/commons/assets.ts';
import { $read } from '@/commons/utils'; import { $read } from '@/commons/utils';
import { useAdminGameSocket } from '@/composables/useAdminGameSocket';
import router from '@/router'; import router from '@/router';
import { ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import Login from '../components/Login.vue';
const imageUrls = { const imageUrls = {
bg1: cssAssetUrl('main-bg1.png'), bg1: cssAssetUrl('main-bg1.png'),
...@@ -45,10 +45,27 @@ const loginSuccessHandler = () => { ...@@ -45,10 +45,27 @@ const loginSuccessHandler = () => {
show_login.value = false; show_login.value = false;
gameHandler(item_index.value); gameHandler(item_index.value);
} }
const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault()
event.returnValue = ''
}
useAdminGameSocket({})
onMounted(() => {
window.addEventListener('beforeunload', confirmRefresh)
})
onUnmounted(() => {
window.removeEventListener('beforeunload', confirmRefresh)
})
</script> </script>
<template> <template>
<Login v-if="show_login" @loginSuccess="loginSuccessHandler" /> <!-- <Login v-if="show_login" @loginSuccess="loginSuccessHandler" /> -->
<div class="bg"> <div class="bg">
<div class="img-logo"></div> <div class="img-logo"></div>
<div class="img-title1"></div> <div class="img-title1"></div>
......
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue' import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import Loading from './views/LoadingView.vue' import Loading from './views/LoadingView.vue'
import Rank1 from './views/Rank1View.vue' import Rank1 from './views/Rank1View.vue'
import Rank2 from './views/Rank2View.vue' import Rank2 from './views/Rank2View.vue'
import { useAdminGameSocket } from '@/composables/useAdminGameSocket' import { useAdminGameSocket } from '@/composables/useAdminGameSocket'
import type Player from '@/commons/player.ts' import type Player from '@/commons/player.ts'
import { playGame1Music, stopGame1Music } from '@/commons/music' import { playApplauseMusic, playGame1Music, stopAllMusic, stopGame1Music } from '@/commons/music'
import { $remove_socket_storage } from '@/commons/utils.ts' import { $remove_socket_storage } from '@/commons/utils.ts'
const router = useRouter() const router = useRouter()
...@@ -114,32 +114,33 @@ const gotoRank1WithIntro = async () => { ...@@ -114,32 +114,33 @@ const gotoRank1WithIntro = async () => {
isPlayingStartAnimation = false isPlayingStartAnimation = false
} }
function handleRoomState(data: any) { const gameId = 'game1'
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, closeRoom } = useAdminGameSocket({ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
gameId: 'game1', gameId,
onRoomState: handleRoomState, onRoomState: (data: any) => {
if (Array.isArray(data?.list)) {
renderPlayers(data)
}
switch (data?.status) {
case 0:
gotoLoading(true)
createRoom(1)
break
case 1:
// if(data.gameId && data.gameId!=gameId){
// return;
// }
gotoLoading()
break
case 2:
seedRankPlayersFromLoading()
screen.value = 'rank1'
break
case 3:
screen.value = 'rank2'
break
}
},
onRoomJoin: (data) => { onRoomJoin: (data) => {
renderPlayers(data) renderPlayers(data)
gotoLoading() gotoLoading()
...@@ -184,9 +185,18 @@ onMounted(() => { ...@@ -184,9 +185,18 @@ onMounted(() => {
}) })
onUnmounted(() => { onUnmounted(() => {
stopGame1Music() stopAllMusic()
window.removeEventListener('beforeunload', confirmRefresh) window.removeEventListener('beforeunload', confirmRefresh)
}) })
watch(screen, (value) => {
// console.log(value);
if(value == 'rank2'){
playApplauseMusic();
}else if(value == 'loading'){
playGame1Music();
}
})
</script> </script>
<template> <template>
......
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from "vue"; import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { playApplauseMusic, playGame4Music, stopAllMusic, stopGame4Music } from '@/commons/music'
import Loading from "./views/LoadingView.vue"; import Loading from "./views/LoadingView.vue";
import Rank1 from "./views/Rank1View.vue"; import Rank1 from "./views/Rank1View.vue";
import Rank2 from "./views/Rank2View.vue"; import Rank2 from "./views/Rank2View.vue";
import { useAdminGameSocket } from "@/composables/useAdminGameSocket"; import { useAdminGameSocket } from "@/composables/useAdminGameSocket";
import { onSocketMessage } from "@/commons/ws"; import { onSocketMessage } from "@/commons/ws";
import type Player from "@/commons/player.ts"; import type Player from "@/commons/player.ts";
import { useRouter } from 'vue-router'
import { $remove_socket_storage } from "@/commons/utils.ts";
type GameScreen = "loading" | "rank1" | "rank2"; const router = useRouter()
const PLAYER_LIMIT = 20; type GameScreen = 'loading' | 'rank1' | 'rank2'
const RANK_LIMIT = 10;
const PLAYER_LIMIT = 20
const RANK_LIMIT = 10
const emptyLoadingPlayer = () => ({ const emptyLoadingPlayer = () => ({
score: 0, score: '-',
telephone: "", telephone: '',
userid: "", userid: '',
wechat_id: "", wechat_id: '',
nickname: "虚位以待", nickname: '虚位以待',
avatar: "", avatar: '',
}); })
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: '-',
score: 0, })
});
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;
// H5 操控传来的加速度: { [userid]: boost(叠加系数, 0~N) }
const horseBoosts = ref<Record<string, number>>({});
let offHorseBoost: (() => void) | undefined; const screen = ref<GameScreen>('loading')
const loadingPlayers = ref<any[]>(Array.from({ length: PLAYER_LIMIT }, emptyLoadingPlayer))
// 监听 WebSocket 获取 H5 玩家操控的马匹加速度 const playerCount = ref(0)
offHorseBoost = onSocketMessage((evt, payload) => { const rankPlayers = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyRankPlayer(index)))
const data = payload?.data ?? payload; const rank1Ref = ref<InstanceType<typeof Rank1> | null>(null)
let isPlayingStartAnimation = false
// 调试日志:打印 WebSocket 原始数据
console.log("[Game4 WebSocket] 收到消息:", {
event: evt,
payloadType: typeof payload,
dataType: typeof data,
isArray: Array.isArray(data),
rawPayload: payload,
});
if (!Array.isArray(data)) return;
const boosts: Record<string, number> = {};
let hasBoost = false;
data.forEach((item: any) => {
// WebSocket 数据无 userid/wechat_id,用 avatar 作为稳定标识
const id = item.userid || item.wechat_id || item.avatar || "";
if (!id) return;
// 尝试多种字段名: score / boost / acceleration / speed / progress
const raw = item.score ?? item.boost ?? item.acceleration ?? item.speed ?? item.progress;
if (raw === undefined || raw === null) return;
const val = Number(raw);
if (isNaN(val)) return;
// score/boost 范围 0~N,0 表示无加速,越大越快
boosts[id] = Math.max(0, val);
hasBoost = true;
});
if (hasBoost) {
console.log("[Game4 WebSocket] 解析到加速数据:", boosts);
horseBoosts.value = { ...boosts };
}
});
const confirmRefresh = (event: BeforeUnloadEvent) => { const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault(); // closeRoom();
event.returnValue = ""; event.preventDefault()
}; event.returnValue = ''
}
const renderPlayers = (data: any) => { const renderPlayers = (data: any) => {
const players = Array.isArray(data?.list) ? data.list : []; const players = Array.isArray(data?.list) ? data.list : []
playerCount.value = Number(data?.count ?? players.length); playerCount.value = Number(data?.count ?? players.length)
loadingPlayers.value = Array.from( loadingPlayers.value = Array.from(
{ length: PLAYER_LIMIT }, { length: PLAYER_LIMIT },
(_, index) => players[index] ?? emptyLoadingPlayer(), (_, index) => players[index] ?? emptyLoadingPlayer(),
); )
}; }
const updateRankPlayers = (data: any) => { const updateRankPlayers = (data: any) => {
if (!Array.isArray(data)) return; if (!Array.isArray(data)) return
rankPlayers.value = Array.from( rankPlayers.value = Array.from(
{ length: RANK_LIMIT }, { length: RANK_LIMIT },
(_, index) => data[index] ?? emptyRankPlayer(index), (_, index) => data[index] ?? emptyRankPlayer(index),
); )
}; }
const gotoLoading = (reset = false) => { const playerToRankPlayer = (player: any, index: number): Player => ({
if (reset) { telephone: player?.telephone ?? '',
playerCount.value = 0; userid: player?.userid ?? '',
loadingPlayers.value = Array.from( wechat_id: player?.wechat_id || `empty-${index}`,
{ length: PLAYER_LIMIT }, nickname: player?.nickname || '虚位以待',
emptyLoadingPlayer, avatar: player?.avatar || '',
); score: player?.score ?? '-',
})
const seedRankPlayersFromLoading = () => {
rankPlayers.value = Array.from( rankPlayers.value = Array.from(
{ length: RANK_LIMIT }, { length: RANK_LIMIT },
(_, index) => emptyRankPlayer(index), (_, index) => {
); const player = loadingPlayers.value[index]
isPlayingStartAnimation = false;
} if (!player?.userid && !player?.wechat_id) {
screen.value = "loading"; 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 () => { const gotoRank1WithIntro = async () => {
if (isPlayingStartAnimation) return; if (isPlayingStartAnimation) return
isPlayingStartAnimation = true; isPlayingStartAnimation = true
screen.value = "rank1"; seedRankPlayersFromLoading()
await nextTick(); screen.value = 'rank1'
await rank1Ref.value?.startIntro(); await nextTick()
// 结果页已在 Rank1View 内部渲染,不需要切到 rank2 await rank1Ref.value?.startIntro()
isPlayingStartAnimation = false; screen.value = 'rank2'
}; isPlayingStartAnimation = false
function handleRoomState(data: any) {
if (Array.isArray(data?.list)) {
renderPlayers(data);
}
switch (data?.status) {
case 0:
gotoLoading(true);
createRoom(4);
break;
case 1:
gotoLoading();
break;
case 2:
screen.value = "rank1";
break;
case 3:
screen.value = "rank2";
break;
}
} }
const { startGame, createRoom, backRoom, requestRoomState } = useAdminGameSocket({ const horseBoosts = ref<Record<string, number>>({});
gameId: "game4",
// loginRedirectPath: '/game4', const gameId = 'game4'
onRoomState: handleRoomState, const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
onRoomJoin: (data) => { gameId,
renderPlayers(data); onAny: (evt, payload) => {
gotoLoading(); const data = payload?.data ?? payload;
},
onGameStart: gotoRank1WithIntro, // 调试日志:打印 WebSocket 原始数据
onRoomRank: updateRankPlayers, console.log("[Game4 WebSocket] 收到消息:", {
}); event: evt,
payloadType: typeof payload,
dataType: typeof data,
isArray: Array.isArray(data),
rawPayload: payload,
});
if (!Array.isArray(data)) return;
const boosts: Record<string, number> = {};
let hasBoost = false;
data.forEach((item: any) => {
// WebSocket 数据无 userid/wechat_id,用 avatar 作为稳定标识
const id = item.userid || item.wechat_id || item.avatar || "";
if (!id) return;
// 尝试多种字段名: score / boost / acceleration / speed / progress
const raw = item.score ?? item.boost ?? item.acceleration ?? item.speed ?? item.progress;
if (raw === undefined || raw === null) return;
const val = Number(raw);
if (isNaN(val)) return;
// score/boost 范围 0~N,0 表示无加速,越大越快
boosts[id] = Math.max(0, val);
hasBoost = true;
});
if (hasBoost) {
console.log("[Game4 WebSocket] 解析到加速数据:", boosts);
horseBoosts.value = { ...boosts };
}
},
onRoomState: (data: any) => {
if (Array.isArray(data?.list)) {
renderPlayers(data)
}
switch (data?.status) {
case 0:
gotoLoading(true)
createRoom(4)
break
case 1:
// if(data.gameId && data.gameId!=gameId){
// return;
// }
gotoLoading()
break
case 2:
seedRankPlayersFromLoading()
screen.value = 'rank1'
break
case 3:
screen.value = 'rank2'
break
}
},
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;
}
startGame()
}
const nextRound = () => { const nextRound = () => {
gotoLoading(true); gotoLoading(true)
// 使用 backRoom 重置已有房间,而非 createRoom 重复创建 //重开房间
backRoom(1); backRoom(4)
setTimeout(() => { }
requestRoomState();
}, 200); const nextRoundWithMusic = () => {
}; nextRound()
}
const backHandler = () => {
if (confirm('是否关闭当前房间回到首页')) {
closeRoom();
stopGame4Music()
router.replace('/main');
}
}
onMounted(() => { onMounted(() => {
window.addEventListener("beforeunload", confirmRefresh); window.addEventListener('beforeunload', confirmRefresh)
}); playGame4Music()
})
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener("beforeunload", confirmRefresh); stopAllMusic()
offHorseBoost?.(); window.removeEventListener('beforeunload', confirmRefresh)
}); })
watch(screen, (value) => {
// console.log(value);
if (value == 'rank2') {
playApplauseMusic();
} else if (value == 'loading') {
playGame4Music();
}
})
// type GameScreen = "loading" | "rank1" | "rank2";
// const PLAYER_LIMIT = 20;
// const RANK_LIMIT = 10;
// const emptyLoadingPlayer = () => ({
// score: 0,
// telephone: "",
// userid: "",
// wechat_id: "",
// nickname: "虚位以待",
// avatar: "",
// });
// const emptyRankPlayer = (index: number): Player => ({
// active: false,
// wechat_id: `empty-${index}`,
// nickname: "虚位以待",
// avatar: "",
// score: 0,
// });
// 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;
// // H5 操控传来的加速度: { [userid]: boost(叠加系数, 0~N) }
// const horseBoosts = ref<Record<string, number>>({});
// let offHorseBoost: (() => void) | undefined;
// // 监听 WebSocket 获取 H5 玩家操控的马匹加速度
// offHorseBoost = onSocketMessage((evt, payload) => {
// const data = payload?.data ?? payload;
// // 调试日志:打印 WebSocket 原始数据
// console.log("[Game4 WebSocket] 收到消息:", {
// event: evt,
// payloadType: typeof payload,
// dataType: typeof data,
// isArray: Array.isArray(data),
// rawPayload: payload,
// });
// if (!Array.isArray(data)) return;
// const boosts: Record<string, number> = {};
// let hasBoost = false;
// data.forEach((item: any) => {
// // WebSocket 数据无 userid/wechat_id,用 avatar 作为稳定标识
// const id = item.userid || item.wechat_id || item.avatar || "";
// if (!id) return;
// // 尝试多种字段名: score / boost / acceleration / speed / progress
// const raw = item.score ?? item.boost ?? item.acceleration ?? item.speed ?? item.progress;
// if (raw === undefined || raw === null) return;
// const val = Number(raw);
// if (isNaN(val)) return;
// // score/boost 范围 0~N,0 表示无加速,越大越快
// boosts[id] = Math.max(0, val);
// hasBoost = true;
// });
// if (hasBoost) {
// console.log("[Game4 WebSocket] 解析到加速数据:", boosts);
// horseBoosts.value = { ...boosts };
// }
// });
// 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 gotoLoading = (reset = false) => {
// if (reset) {
// playerCount.value = 0;
// loadingPlayers.value = Array.from(
// { length: PLAYER_LIMIT },
// emptyLoadingPlayer,
// );
// rankPlayers.value = Array.from(
// { length: RANK_LIMIT },
// (_, index) => emptyRankPlayer(index),
// );
// isPlayingStartAnimation = false;
// }
// screen.value = "loading";
// };
// const gotoRank1WithIntro = async () => {
// if (isPlayingStartAnimation) return;
// isPlayingStartAnimation = true;
// screen.value = "rank1";
// await nextTick();
// await rank1Ref.value?.startIntro();
// // 结果页已在 Rank1View 内部渲染,不需要切到 rank2
// isPlayingStartAnimation = false;
// };
// function handleRoomState(data: any) {
// if (Array.isArray(data?.list)) {
// renderPlayers(data);
// }
// switch (data?.status) {
// case 0:
// gotoLoading(true);
// createRoom(4);
// break;
// case 1:
// gotoLoading();
// break;
// case 2:
// screen.value = "rank1";
// break;
// case 3:
// screen.value = "rank2";
// break;
// }
// }
// const { startGame, createRoom, backRoom, requestRoomState, closeRoom } = useAdminGameSocket({
// gameId: "game4",
// // loginRedirectPath: '/game4',
// onRoomState: handleRoomState,
// onRoomJoin: (data) => {
// renderPlayers(data);
// gotoLoading();
// },
// onGameStart: gotoRank1WithIntro,
// onRoomRank: updateRankPlayers,
// });
// const nextRound = () => {
// gotoLoading(true);
// // 使用 backRoom 重置已有房间,而非 createRoom 重复创建
// backRoom(1);
// setTimeout(() => {
// requestRoomState();
// }, 200);
// };
// onMounted(() => {
// window.addEventListener("beforeunload", confirmRefresh);
// playGame4Music()
// });
// onUnmounted(() => {
// stopGame4Music()
// window.removeEventListener("beforeunload", confirmRefresh);
// offHorseBoost?.();
// });
// const backHandler = () => {
// if (confirm('是否关闭当前房间回到首页')) {
// closeRoom();
// stopGame4Music()
// router.replace('/main');
// }
// }
</script> </script>
<template> <template>
<Loading <Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
v-if="screen === 'loading'" @start="startGameWithMusic" @back="backHandler" />
:players="loadingPlayers" <Rank1 v-if="screen === 'rank1'" ref="rank1Ref" :rank-list="rankPlayers" :horse-boosts="horseBoosts"
:player-count="playerCount" @start="startGameWithMusic" @next="nextRound" />
@start="startGame" <!-- <Rank2 v-else-if="screen === 'rank2'" :rank-list="rankPlayers" @next="nextRound" /> -->
/>
<Rank1
v-if="screen === 'rank1'"
ref="rank1Ref"
:rank-list="rankPlayers"
:horse-boosts="horseBoosts"
@start="startGame"
@next="nextRound"
/>
<!-- <Rank2 v-else-if="screen === 'rank2'" :rank-list="rankPlayers" @next="nextRound" /> -->
</template> </template>
...@@ -11,6 +11,7 @@ defineProps<{ ...@@ -11,6 +11,7 @@ defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
start: []; start: [];
back:[];
}>(); }>();
const imageUrls = { const imageUrls = {
...@@ -26,17 +27,19 @@ const imageUrls = { ...@@ -26,17 +27,19 @@ const imageUrls = {
avatarAnimation: cssAssetUrl("avatar_animation.png"), avatarAnimation: cssAssetUrl("avatar_animation.png"),
avatar: cssAssetUrl("avatar.png"), avatar: cssAssetUrl("avatar.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"),
start: cssAssetUrl('game1/start.png'),
back: cssAssetUrl('game1/back.png'),
}; };
const isStartPressed = ref(false); const isStartPressed = ref(false);
const renderAvatar = (item: any | null) => { const renderAvatar = (item: any | null) => {
if (item && item.avatar) { if (item && item.avatar) {
return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;` return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;`
} }
return `background: ${imageUrls.avatar} center / cover no-repeat;` return `background: ${imageUrls.avatar} center / cover no-repeat;`
} }
const pressStartButton = () => { const pressStartButton = () => {
...@@ -67,10 +70,7 @@ const releaseStartButton = () => { ...@@ -67,10 +70,7 @@ const releaseStartButton = () => {
当前在线人数 <span>{{ playerCount }}</span> 当前在线人数 <span>{{ playerCount }}</span>
</div> </div>
<div class="list-container-left"> <div class="list-container-left">
<div <div v-for="(item, index) in players" :key="item.userid || item.wechat_id || `empty-${index}`">
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>
...@@ -83,14 +83,11 @@ const releaseStartButton = () => { ...@@ -83,14 +83,11 @@ const releaseStartButton = () => {
</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" @pointercancel="releaseStartButton">
:class="{ pressed: isStartPressed }" </div> -->
@pointerdown="pressStartButton" <div class="btn-start" @click="pressStartButton"></div>
@pointerup="releaseStartButton" <div class="btn-back" @click="emit('back')"></div>
@pointerleave="releaseStartButton"
@pointercancel="releaseStartButton"
></div>
</div> </div>
</div> </div>
</div> </div>
...@@ -148,6 +145,7 @@ const releaseStartButton = () => { ...@@ -148,6 +145,7 @@ const releaseStartButton = () => {
width: 100%; width: 100%;
height: 144px; height: 144px;
border-radius: 10px; border-radius: 10px;
img { img {
width: 140px; width: 140px;
height: 140px; height: 140px;
...@@ -159,6 +157,7 @@ const releaseStartButton = () => { ...@@ -159,6 +157,7 @@ const releaseStartButton = () => {
color: white; color: white;
} }
} }
.img-gift { .img-gift {
background: v-bind("imageUrls.gift") no-repeat; background: v-bind("imageUrls.gift") no-repeat;
width: 315px; width: 315px;
...@@ -167,6 +166,7 @@ const releaseStartButton = () => { ...@@ -167,6 +166,7 @@ const releaseStartButton = () => {
top: 124px; top: 124px;
right: 320px; right: 320px;
} }
.img-small-gu { .img-small-gu {
background: v-bind("imageUrls.smallGu") no-repeat; background: v-bind("imageUrls.smallGu") no-repeat;
width: 161px; width: 161px;
...@@ -175,6 +175,7 @@ const releaseStartButton = () => { ...@@ -175,6 +175,7 @@ const releaseStartButton = () => {
top: 500px; top: 500px;
right: 340px; right: 340px;
} }
.img-gu { .img-gu {
background: v-bind("imageUrls.gu") no-repeat; background: v-bind("imageUrls.gu") no-repeat;
width: 320px; width: 320px;
...@@ -199,7 +200,7 @@ const releaseStartButton = () => { ...@@ -199,7 +200,7 @@ const releaseStartButton = () => {
color: #aa0000; color: #aa0000;
font-weight: bold; font-weight: bold;
> span { >span {
margin-left: 20px; margin-left: 20px;
font-size: 48px; font-size: 48px;
position: relative; position: relative;
...@@ -266,13 +267,21 @@ const releaseStartButton = () => { ...@@ -266,13 +267,21 @@ const releaseStartButton = () => {
width: 316px; width: 316px;
height: 105px; height: 105px;
margin-left: 60px; margin-left: 60px;
margin-top: 80px; margin-top: 10px;
background: v-bind("imageUrls.start1"); background: v-bind("imageUrls.start");
cursor: pointer; cursor: pointer;
} }
.btn-start.pressed { /* .btn-start.pressed {
background: v-bind("imageUrls.start2"); background: v-bind("imageUrls.start2");
} */
.btn-back {
width: 316px;
height: 105px;
margin-left: 60px;
margin-top: 30px;
background: v-bind('imageUrls.back');
cursor: pointer;
} }
} }
} }
......
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue' import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import Loading from './views/LoadingView.vue' import Loading from './views/LoadingView.vue'
import Rank1 from './views/Rank1View.vue' import Rank1 from './views/Rank1View.vue'
import Rank2 from './views/Rank2View.vue' import Rank2 from './views/Rank2View.vue'
import { useAdminGameSocket } from '@/composables/useAdminGameSocket' import { useAdminGameSocket } from '@/composables/useAdminGameSocket'
import type Player from '@/commons/player.ts' import type Player from '@/commons/player.ts'
import { playGame1Music } from '@/commons/music' import { playApplauseMusic, playGame5Music, stopAllMusic, stopGame5Music } from '@/commons/music'
import { $remove_socket_storage } from '@/commons/utils.ts' import { $remove_socket_storage } from '@/commons/utils.ts'
const router = useRouter() const router = useRouter()
...@@ -124,7 +124,7 @@ function handleRoomState(data: any) { ...@@ -124,7 +124,7 @@ function handleRoomState(data: any) {
switch (data?.status) { switch (data?.status) {
case 0: case 0:
gotoLoading(true) gotoLoading(true)
createRoom(1) createRoom(5)
break break
case 1: case 1:
gotoLoading() gotoLoading()
...@@ -139,7 +139,7 @@ function handleRoomState(data: any) { ...@@ -139,7 +139,7 @@ function handleRoomState(data: any) {
} }
} }
const { startGame, createRoom, backRoom } = useAdminGameSocket({ const { startGame, createRoom, closeRoom, backRoom } = useAdminGameSocket({
gameId: 'game5', gameId: 'game5',
onRoomState: handleRoomState, onRoomState: handleRoomState,
onRoomJoin: (data) => { onRoomJoin: (data) => {
...@@ -166,36 +166,54 @@ const startGameWithMusic = () => { ...@@ -166,36 +166,54 @@ const startGameWithMusic = () => {
alert('房间中没有玩家') alert('房间中没有玩家')
return; return;
} }
playGame1Music() playGame5Music()
startGame() startGame()
} }
const nextRound = () => { const nextRound = () => {
gotoLoading(true) gotoLoading(true)
//重开房间 //重开房间
backRoom(1) backRoom(5)
} }
const nextRoundWithMusic = () => { const nextRoundWithMusic = () => {
playGame1Music() playGame5Music()
nextRound() nextRound()
} }
onMounted(() => { onMounted(() => {
window.addEventListener('beforeunload', confirmRefresh) window.addEventListener('beforeunload', confirmRefresh)
playGame1Music() playGame5Music()
}) })
onUnmounted(() => { onUnmounted(() => {
stopAllMusic()
window.removeEventListener('beforeunload', confirmRefresh) window.removeEventListener('beforeunload', confirmRefresh)
}) })
const backHandler = () => {
if (confirm('是否关闭当前房间回到首页')) {
closeRoom();
stopGame5Music()
router.replace('/main');
}
}
watch(screen, (value) => {
// console.log(value);
if(value == 'rank2'){
playApplauseMusic();
}else if(value == 'loading'){
playGame5Music();
}
})
</script> </script>
<template> <template>
<Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount" <Loading v-if="screen === 'loading'" :players="loadingPlayers" :player-count="playerCount"
@start="startGameWithMusic" /> @start="startGameWithMusic" @back="backHandler" />
<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"
@next="nextRoundWithMusic" /> @next="nextRoundWithMusic" />
<!-- <Rank1 v-else ref="rank1Ref" :rank-list="rankPlayers" @start="startGameWithMusic" --> <!-- <Rank1 v-else 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>
...@@ -11,6 +11,7 @@ defineProps<{ ...@@ -11,6 +11,7 @@ defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
start: []; start: [];
back: [];
}>(); }>();
const imageUrls = { const imageUrls = {
...@@ -25,8 +26,10 @@ const imageUrls = { ...@@ -25,8 +26,10 @@ const imageUrls = {
avatarAnimation: cssAssetUrl('avatar_animation.png'), avatarAnimation: cssAssetUrl('avatar_animation.png'),
avatar: cssAssetUrl('avatar.png'), avatar: cssAssetUrl('avatar.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'),
start: cssAssetUrl('game1/start.png'),
back: cssAssetUrl('game1/back.png'),
}; };
const isStartPressed = ref(false); const isStartPressed = ref(false);
...@@ -43,7 +46,7 @@ const releaseStartButton = () => { ...@@ -43,7 +46,7 @@ const releaseStartButton = () => {
const renderAvatar = (item: any | null) => { const renderAvatar = (item: any | null) => {
if (item && item.avatar) { if (item && item.avatar) {
return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;` return `background: url("${String(item.avatar).replace(/"/g, '\\"')}") center / cover no-repeat;`
} }
return `background: ${imageUrls.avatar} center / cover no-repeat;` return `background: ${imageUrls.avatar} center / cover no-repeat;`
} }
</script> </script>
...@@ -74,14 +77,16 @@ const renderAvatar = (item: any | null) => { ...@@ -74,14 +77,16 @@ 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="btn-start"
:class="{ pressed: isStartPressed }" :class="{ pressed: isStartPressed }"
@pointerdown="pressStartButton" @pointerdown="pressStartButton"
@pointerup="releaseStartButton" @pointerup="releaseStartButton"
@pointerleave="releaseStartButton" @pointerleave="releaseStartButton"
@pointercancel="releaseStartButton" @pointercancel="releaseStartButton"
></div> ></div> -->
<div class="btn-start" @click="pressStartButton"></div>
<div class="btn-back" @click="emit('back')"></div>
</div> </div>
</div> </div>
</div> </div>
...@@ -139,6 +144,7 @@ const renderAvatar = (item: any | null) => { ...@@ -139,6 +144,7 @@ const renderAvatar = (item: any | null) => {
width: 100%; width: 100%;
height: 144px; height: 144px;
border-radius: 10px; border-radius: 10px;
img { img {
width: 147px; width: 147px;
height: 147px; height: 147px;
...@@ -159,6 +165,7 @@ const renderAvatar = (item: any | null) => { ...@@ -159,6 +165,7 @@ const renderAvatar = (item: any | null) => {
top: 193px; top: 193px;
left: 47px; left: 47px;
} }
.img-right { .img-right {
background: v-bind('imageUrls.right') no-repeat; background: v-bind('imageUrls.right') no-repeat;
width: 423px; width: 423px;
...@@ -251,13 +258,21 @@ const renderAvatar = (item: any | null) => { ...@@ -251,13 +258,21 @@ const renderAvatar = (item: any | null) => {
width: 316px; width: 316px;
height: 105px; height: 105px;
margin-left: 60px; margin-left: 60px;
margin-top: 80px; margin-top: 10px;
background: v-bind('imageUrls.start1'); background: v-bind('imageUrls.start');
cursor: pointer; cursor: pointer;
} }
.btn-start.pressed { /* .btn-start.pressed {
background: v-bind('imageUrls.start2'); background: v-bind('imageUrls.start2');
} */
.btn-back {
width: 316px;
height: 105px;
margin-left: 60px;
margin-top: 30px;
background: v-bind('imageUrls.back');
cursor: pointer;
} }
} }
} }
......
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue' import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import Loading from './views/LoadingView.vue' import Loading from './views/LoadingView.vue'
import Rank1 from './views/Rank1View.vue' import Rank1 from './views/Rank1View.vue'
import Rank2 from './views/Rank2View.vue' import Rank2 from './views/Rank2View.vue'
import { useAdminGameSocket } from '@/composables/useAdminGameSocket' import { useAdminGameSocket } from '@/composables/useAdminGameSocket'
import type Player from '@/commons/player.ts' import type Player from '@/commons/player.ts'
import { playGame6Music, stopGame6Music } from '@/commons/music' import { playApplauseMusic, playGame6Music, stopAllMusic, stopGame6Music } from '@/commons/music'
import { $remove_socket_storage } from '@/commons/utils.ts' import { $remove_socket_storage } from '@/commons/utils.ts'
const router = useRouter() const router = useRouter()
...@@ -135,9 +135,9 @@ function handleRoomState(data: any) { ...@@ -135,9 +135,9 @@ function handleRoomState(data: any) {
break break
} }
} }
const gameId='game6'
const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({ const { startGame, createRoom, backRoom, closeRoom } = useAdminGameSocket({
gameId: 'game6', gameId,
onRoomState: handleRoomState, onRoomState: handleRoomState,
onRoomJoin: (data) => { onRoomJoin: (data) => {
renderPlayers(data) renderPlayers(data)
...@@ -183,9 +183,18 @@ onMounted(() => { ...@@ -183,9 +183,18 @@ onMounted(() => {
}) })
onUnmounted(() => { onUnmounted(() => {
stopGame6Music(); stopAllMusic();
window.removeEventListener('beforeunload', confirmRefresh) window.removeEventListener('beforeunload', confirmRefresh)
}) })
watch(screen, (value) => {
// console.log(value);
if(value == 'rank2'){
playApplauseMusic();
}else if(value == 'loading'){
playGame6Music();
}
})
</script> </script>
<template> <template>
......
...@@ -89,7 +89,7 @@ const renderAvatar = (item: any | null) => { ...@@ -89,7 +89,7 @@ const renderAvatar = (item: any | null) => {
<div class="no-icon">{{ index + 4 }}</div> <div class="no-icon">{{ index + 4 }}</div>
<div> <div>
<div class="img-avatar-container"> <div class="img-avatar-container">
<div class="img-avatar"></div> <div class="img-avatar" :style="`${renderAvatar(list[index + 3])}`"></div>
</div> </div>
<div class="txt-nickname">{{ item.nickname }}</div> <div class="txt-nickname">{{ item.nickname }}</div>
</div> </div>
......
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 Main from '@/pages/Main.vue' import Main from '@/pages/Main.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'
...@@ -11,12 +13,20 @@ const router = createRouter({ ...@@ -11,12 +13,20 @@ const router = createRouter({
routes: [ routes: [
{ {
path: '/', path: '/',
redirect: () => ($read('token', '') ? '/game1' : '/main'), redirect: () => ($read('token', '') ? '/main' : '/login'),
},
{
path: '/login',
name: 'Login',
component: Login,
}, },
{ {
path: '/main', path: '/main',
name: 'Main', name: 'Main',
component: Main, component: Main,
meta: {
requiresAuth: true,
},
}, },
{ {
path: '/game1', path: '/game1',
......
...@@ -26,7 +26,7 @@ function copyImageAssets() { ...@@ -26,7 +26,7 @@ function copyImageAssets() {
} }
export default defineConfig(({ command }) => ({ export default defineConfig(({ command }) => ({
base: command === 'build' ? '/cc/pc/' : '/', base: '/',//command === 'build' ? '/cc/pc/' : '/',
define: { define: {
__APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''), __APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
// __APP_DOMAIN__: JSON.stringify(buildDomain), // __APP_DOMAIN__: JSON.stringify(buildDomain),
......
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