Commit 2a8c14a9 authored by 陈冲's avatar 陈冲

fix: 页面修改

parent 7179132e
This diff is collapsed.
...@@ -201,7 +201,9 @@ ...@@ -201,7 +201,9 @@
this.message = msg; this.message = msg;
}, },
create_sio: function (args) { create_sio: function (args) {
let socket = io(`ws://127.0.0.1:8082/ws`, { let ws = 'https://hddp.guocai365.org.cn/ws';
ws = 'ws://127.0.0.1:8082/ws';
let socket = io(ws, {
path: "/socket.io", path: "/socket.io",
auth: args.auth, auth: args.auth,
transports: ["websocket"], transports: ["websocket"],
......
VITE_ASSET_BUILD_LOCAL=true
VITE_ASSET_BASE_URL=https://xjfcoss.guocai365.org.cn/games/h5/assets/images/
VITE_ASSET_BASE_URL=https://your-bucket.oss-cn-shanghai.aliyuncs.com VITE_ASSET_BASE_URL=https://xjfcoss.guocai365.org.cn/games/h5/assets/images/
...@@ -20,6 +20,19 @@ npm run dev ...@@ -20,6 +20,19 @@ npm run dev
npm run build npm run build
``` ```
### Build With OSS or Local Assets
```sh
# Use VITE_ASSET_BASE_URL from .env.production
npm run build:oss
# Bundle with local assets and ignore the OSS URL
npm run build:local
# Serve the local build over HTTP
npm run serve:local
```
## Image Assets ## Image Assets
Put local development images in `src/assets/images`. Put local development images in `src/assets/images`.
...@@ -40,14 +53,14 @@ Default behavior: ...@@ -40,14 +53,14 @@ Default behavior:
For OSS/CDN production builds, create `.env.production`: For OSS/CDN production builds, create `.env.production`:
```env ```env
VITE_ASSET_BASE_URL=https://your-bucket.oss-cn-shanghai.aliyuncs.com VITE_ASSET_BASE_URL=https://xjfcoss.guocai365.org.cn/games/h5/assets/images/
``` ```
If you need to test OSS during development: If you need to test OSS during development:
```env ```env
VITE_ASSET_DEV_LOCAL=false VITE_ASSET_DEV_LOCAL=false
VITE_ASSET_BASE_URL=https://your-bucket.oss-cn-shanghai.aliyuncs.com VITE_ASSET_BASE_URL=https://xjfcoss.guocai365.org.cn/games/h5/assets/images/
``` ```
Production image resources should be uploaded to OSS/CDN, or served from `/assets/images/...` if no OSS base URL is configured. H5 resources should be uploaded under `games/h5/assets/images/`. Production image resources are served from `/h5/assets/images/...` when using the local-assets build.
...@@ -8,6 +8,7 @@ declare const __DEBUG__: boolean; ...@@ -8,6 +8,7 @@ declare const __DEBUG__: boolean;
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_ASSET_BASE_URL?: string; readonly VITE_ASSET_BASE_URL?: string;
readonly VITE_ASSET_BUILD_LOCAL?: string;
readonly VITE_ASSET_DEV_LOCAL?: string; readonly VITE_ASSET_DEV_LOCAL?: string;
} }
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
<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, maximum-scale=1, minimum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-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>
</head> </head>
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "run-p type-check \"build-only {@}\" --", "build:oss": "npm run type-check && vite build --mode production",
"build:local": "npm run type-check && vite build --mode local-assets",
"preview": "vite preview", "preview": "vite preview",
"serve:local": "vite preview --host 0.0.0.0",
"build-only": "vite build", "build-only": "vite build",
"type-check": "vue-tsc --build" "type-check": "vue-tsc --build"
}, },
......
...@@ -43,6 +43,8 @@ body { ...@@ -43,6 +43,8 @@ body {
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
} }
button, button,
......
...@@ -31,6 +31,14 @@ export function assetUrl(path: string, options: AssetUrlOptions = {}): string { ...@@ -31,6 +31,14 @@ export function assetUrl(path: string, options: AssetUrlOptions = {}): string {
} }
const normalizedPath = normalizeAssetPath(path); const normalizedPath = normalizeAssetPath(path);
const configuredBaseUrl = import.meta.env.VITE_ASSET_BUILD_LOCAL === 'true'
? undefined
: options.baseUrl ?? import.meta.env.VITE_ASSET_BASE_URL;
if (import.meta.env.PROD && configuredBaseUrl) {
return joinUrl(configuredBaseUrl, normalizedPath);
}
const preferLocal = options.preferLocal ?? ( const preferLocal = options.preferLocal ?? (
import.meta.env.DEV && import.meta.env.VITE_ASSET_DEV_LOCAL !== 'false' import.meta.env.DEV && import.meta.env.VITE_ASSET_DEV_LOCAL !== 'false'
); );
...@@ -39,10 +47,8 @@ export function assetUrl(path: string, options: AssetUrlOptions = {}): string { ...@@ -39,10 +47,8 @@ export function assetUrl(path: string, options: AssetUrlOptions = {}): string {
return localAssetUrl(normalizedPath); return localAssetUrl(normalizedPath);
} }
const baseUrl = options.baseUrl ?? import.meta.env.VITE_ASSET_BASE_URL; if (configuredBaseUrl) {
return joinUrl(configuredBaseUrl, normalizedPath);
if (baseUrl) {
return joinUrl(baseUrl, normalizedPath);
} }
return localAssetUrl(normalizedPath); return localAssetUrl(normalizedPath);
......
import game1MusicUrl from '@/assets/images/game1.mp3' import { assetUrl } from '@/commons/assets'
import game6Music1Url from '@/assets/images/game6_1.mp3'
import game6Music2Url from '@/assets/images/game6_2.mp3' const game1MusicUrl = assetUrl('game1.mp3')
import go321 from '@/assets/images/321go.mp3' const game2MusicUrl = assetUrl('game2.mp3')
const game3Music1Url = assetUrl('game3_1.mp3')
const game3Music2Url = assetUrl('game3_2.mp3')
const game6Music1Url = assetUrl('game6_1.mp3')
const game6Music2Url = assetUrl('game6_2.mp3')
const go321 = assetUrl('321go.mp3')
const effectTemplates = new Map<string, HTMLAudioElement>() const effectTemplates = new Map<string, HTMLAudioElement>()
const playingEffects = new Set<HTMLAudioElement>() const playingEffects = new Set<HTMLAudioElement>()
const exclusiveEffects = new Map<string, HTMLAudioElement>()
let pendingPlay = false let pendingPlay = false
let pendingRetry: (() => void) | null = null let pendingRetry: (() => void) | null = null
export function stopAllMusic() {
stopGame1Music();
stopGame2Music();
stopGame3Music();
// stopGame4Music();
// stopGame5Music();
stopGame6Music();
playingEffects.clear();
}
function getEffectTemplate(music: string) { function getEffectTemplate(music: string) {
let template = effectTemplates.get(music) let template = effectTemplates.get(music)
...@@ -48,13 +67,25 @@ function setPendingRetry(retry: () => void) { ...@@ -48,13 +67,25 @@ function setPendingRetry(retry: () => void) {
window.addEventListener('keydown', retry, { once: true }) window.addEventListener('keydown', retry, { once: true })
} }
async function playEffect(music: string, retry: () => void) { async function playEffect(music: string, retry: () => void, exclusive = false) {
if (exclusive && exclusiveEffects.has(music)) {
return
}
const audio = getEffectTemplate(music).cloneNode(true) as HTMLAudioElement const audio = getEffectTemplate(music).cloneNode(true) as HTMLAudioElement
audio.loop = false audio.loop = false
audio.volume = 1 audio.volume = 1
playingEffects.add(audio) playingEffects.add(audio)
if (exclusive) {
exclusiveEffects.set(music, audio)
}
const release = () => releaseEffect(audio) const release = () => {
if (exclusiveEffects.get(music) === audio) {
exclusiveEffects.delete(music)
}
releaseEffect(audio)
}
audio.addEventListener('ended', release, { once: true }) audio.addEventListener('ended', release, { once: true })
audio.addEventListener('error', release, { once: true }) audio.addEventListener('error', release, { once: true })
...@@ -64,7 +95,7 @@ async function playEffect(music: string, retry: () => void) { ...@@ -64,7 +95,7 @@ async function playEffect(music: string, retry: () => void) {
clearPendingRetry() clearPendingRetry()
} }
} catch { } catch {
releaseEffect(audio) release()
if (!pendingPlay) { if (!pendingPlay) {
setPendingRetry(retry) setPendingRetry(retry)
...@@ -78,6 +109,7 @@ function stopEffects() { ...@@ -78,6 +109,7 @@ function stopEffects() {
releaseEffect(audio) releaseEffect(audio)
} }
playingEffects.clear() playingEffects.clear()
exclusiveEffects.clear()
} }
export async function playGame1Music() { export async function playGame1Music() {
...@@ -90,6 +122,7 @@ export function stopGame1Music() { ...@@ -90,6 +122,7 @@ export function stopGame1Music() {
stopEffects() stopEffects()
} }
/* 3 2 1 go */
export async function playCountdownMusic() { export async function playCountdownMusic() {
await playEffect(go321, playCountdownMusic) await playEffect(go321, playCountdownMusic)
} }
...@@ -100,6 +133,35 @@ export function stopCountdownMusic() { ...@@ -100,6 +133,35 @@ export function stopCountdownMusic() {
stopEffects() stopEffects()
} }
export async function playGame2Music() {
await playEffect(game2MusicUrl, playGame2Music, true)
}
export function stopGame2Music() {
window.removeEventListener('pointerdown', playGame2Music)
window.removeEventListener('keydown', playGame2Music)
stopEffects()
}
/*
接到红包3-1
接到炸弹3-2
*/
export async function playGame3Music(index: number) {
let arr: [string, string, string] = ['', game3Music1Url, game3Music2Url];
const music = arr[index]
if (!music) {
return
}
const retry = () => { playGame3Music(index) }
await playEffect(music, retry)
}
export function stopGame3Music() {
stopEffects()
}
/* /*
打地鼠击中爆炸6-1 打地鼠击中爆炸6-1
......
...@@ -83,7 +83,7 @@ export function $toast(message?: string, duration = 2000) { ...@@ -83,7 +83,7 @@ export function $toast(message?: string, duration = 2000) {
'border-radius: 18px', 'border-radius: 18px',
'background: rgba(0, 0, 0, 0.72)', 'background: rgba(0, 0, 0, 0.72)',
'color: #fff', 'color: #fff',
'font-size: 22px', 'font-size: 16px',
'line-height: 1.4', 'line-height: 1.4',
'text-align: center', 'text-align: center',
'transform: translate(-50%, -40%)', 'transform: translate(-50%, -40%)',
...@@ -306,6 +306,25 @@ function $query<T extends string | string[]>(q: T): T { ...@@ -306,6 +306,25 @@ function $query<T extends string | string[]>(q: T): T {
return params.get(q) as T; return params.get(q) as T;
} }
export function $remove_socket_storage(): void {
localStorage.removeItem('nickname');
localStorage.removeItem('token');
localStorage.removeItem('avatar');
localStorage.removeItem('token_origin');
}
export function $read_socket_storge(): any | null {
let token = $read('token', '');
let nickname = $read('nickname', '');
const avatar = $read('avatar', '');
const token_origin = $read('token_origin', '');
if (token && token_origin) {
return { nickname, avatar, token, token_origin }
}
return null;
}
export function $getWechat(): any | null { export function $getWechat(): any | null {
const q = $query(['token', 'nickname', 'avatar']); const q = $query(['token', 'nickname', 'avatar']);
if (q && q.length == 3) { if (q && q.length == 3) {
...@@ -316,7 +335,6 @@ export function $getWechat(): any | null { ...@@ -316,7 +335,6 @@ export function $getWechat(): any | null {
avatar: q[2], avatar: q[2],
}; };
} }
// 调试模式:无真实微信参数时使用模拟数据 // 调试模式:无真实微信参数时使用模拟数据
if (__DEBUG__) { if (__DEBUG__) {
const mockToken = 'debug_mock_token_' + Date.now() const mockToken = 'debug_mock_token_' + Date.now()
......
...@@ -57,6 +57,7 @@ export function initSocket(args: SocketInitArgs): Socket | null { ...@@ -57,6 +57,7 @@ export function initSocket(args: SocketInitArgs): Socket | null {
upgrade: false, upgrade: false,
forceNew: true, forceNew: true,
timeout: 10000, timeout: 10000,
// autoConnect: false,
}); });
socket.on('connect', () => { socket.on('connect', () => {
......
...@@ -14,6 +14,7 @@ type GameSocketOptions = { ...@@ -14,6 +14,7 @@ type GameSocketOptions = {
gameId: string, gameId: string,
auth: any, auth: any,
onConnect?: () => void, onConnect?: () => void,
onShowRank?: (data: any) => void,
onGameStart?: (data: any) => void onGameStart?: (data: any) => void
onScoreSubmitted?: (is_save: boolean, data: any) => void onScoreSubmitted?: (is_save: boolean, data: any) => void
onRescoreSubmitted?: (recount: number) => void onRescoreSubmitted?: (recount: number) => void
...@@ -64,7 +65,10 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -64,7 +65,10 @@ export function useGameSocket(options: GameSocketOptions) {
onConnect: (socket) => { onConnect: (socket) => {
options.onConnect?.(); options.onConnect?.();
}, },
onDisconnect: (_reason: string) => { }, onDisconnect: (_reason: string) => {
// $toast('网络中断,尝试重新连接...')
socket = null;
},
onError: (error: any) => { onError: (error: any) => {
// console.error('socket.io connect_error:', error) // console.error('socket.io connect_error:', error)
}, },
...@@ -74,13 +78,18 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -74,13 +78,18 @@ export function useGameSocket(options: GameSocketOptions) {
const { msg, state, data } = payload const { msg, state, data } = payload
if (state === 0) { if (state === 0) {
if (evt == 'room_rank_result') {
// options.show_rank?.(data);
// return;
}
if (evt == 'connect_result') { if (evt == 'connect_result') {
router.replace('/loading').then(() => { }); // $toast(msg);
// router.replace('/loading').then(() => { });
return; return;
} }
if (evt === 'room_join_result') { if (evt === 'room_join_result') {
$toast(msg); // $toast(msg);
router.replace('/loading').then(() => { }); // router.replace('/loading').then(() => { });
return; return;
} }
if (evt == 'submit_score_save') { if (evt == 'submit_score_save') {
...@@ -106,25 +115,25 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -106,25 +115,25 @@ export function useGameSocket(options: GameSocketOptions) {
switch (evt) { switch (evt) {
case 'room_create_result': { case 'room_create_result': {
if (`game${data}` == options.gameId) { // if (`game${data}` == options.gameId) {
// location.reload(); // // location.reload();
options.onConnect?.(); // options.onConnect?.();
// debugger // // debugger
return; // return;
} // }
const nextPath = getGameHomePath(data) // const nextPath = getGameHomePath(data)
if (nextPath && router.currentRoute.value.path !== nextPath) { // if (nextPath && router.currentRoute.value.path !== nextPath) {
router.replace(nextPath) // router.replace(nextPath)
} // }
break // break
} }
case 'room_join_result': { case 'room_join_result': {
if (data != options.gameId) { if (data != options.gameId) {
debugger // debugger
return; return;
} }
$toast('您已进入游戏房间') $toast('您已进入当前游戏房间')
userJoinStatus.value = true userJoinStatus.value = true
break break
} }
...@@ -162,6 +171,11 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -162,6 +171,11 @@ export function useGameSocket(options: GameSocketOptions) {
$toast('管理员关闭了游戏房间') $toast('管理员关闭了游戏房间')
break; break;
} }
case 'room_rank_result': {
//如果是游戏开始中再进入会收到这个排行榜消息
options.onShowRank?.(data);
return;
}
default: { default: {
debugger debugger
} }
......
...@@ -37,7 +37,7 @@ if (wechat) { ...@@ -37,7 +37,7 @@ if (wechat) {
_offSocketMessage.value = offSocketMessage _offSocketMessage.value = offSocketMessage
} }
onMounted(() => { onMounted(() => {
document.title = '互动游戏' document.title = '互动游戏 - 等待中...'
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
import { onBeforeUnmount, onMounted, ref, watch } 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, sendGameMessage, userJoinStatus, joinSharedRoom } from '@/composables/useGameSocket'
import LoadingView from './views/LoadingView.vue' import LoadingView from './views/LoadingView.vue'
import PlayingView from './views/PlayingView.vue' import PlayingView from './views/PlayingView.vue'
import ScoreView from './views/ScoreView.vue' import ScoreView from './views/ScoreView.vue'
import { $getWechat, $toast } from '@/commons/utils.ts' import { $getWechat, $toast } from '@/commons/utils.ts'
import { playGame1Music } from '@/commons/music' import { playGame1Music, stopAllMusic } from '@/commons/music'
type GameView = 'loading' | 'playing' | 'score' type GameView = 'loading' | 'playing' | 'score'
...@@ -131,6 +131,7 @@ function startGameView() { ...@@ -131,6 +131,7 @@ function startGameView() {
} }
function showScoreView() { function showScoreView() {
showGameRule.value = false;
stopGameCountdown() stopGameCountdown()
setDivDescVisible(false) setDivDescVisible(false)
currentView.value = 'score' currentView.value = 'score'
...@@ -178,6 +179,10 @@ if (wechat) { ...@@ -178,6 +179,10 @@ if (wechat) {
const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId) const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
}, },
onShowRank: (data) => {
//第一个游戏没有排行榜,只显示结果
showScoreView();
},
onGameStart: async () => { onGameStart: async () => {
showGameRule.value = false; showGameRule.value = false;
await mobileStageRef.value?.startCountdown() await mobileStageRef.value?.startCountdown()
...@@ -216,10 +221,6 @@ if (wechat) { ...@@ -216,10 +221,6 @@ if (wechat) {
$toast('管理员关闭了游戏房间') $toast('管理员关闭了游戏房间')
}, },
onRoomBack: () => { onRoomBack: () => {
resetToLoadingView()
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
}, },
}) })
_offSocketMessage.value = offSocketMessage _offSocketMessage.value = offSocketMessage
...@@ -238,6 +239,7 @@ onBeforeUnmount(() => { ...@@ -238,6 +239,7 @@ onBeforeUnmount(() => {
window.clearTimeout(guFrameTimer) window.clearTimeout(guFrameTimer)
} }
stopGameCountdown() stopGameCountdown()
stopAllMusic();
if (token.value) { if (token.value) {
window.removeEventListener('beforeunload', confirmRefresh) window.removeEventListener('beforeunload', confirmRefresh)
} }
......
...@@ -34,7 +34,8 @@ onMounted(() => { ...@@ -34,7 +34,8 @@ onMounted(() => {
<div>击鼓积分</div> <div>击鼓积分</div>
<div>{{ tick }}</div> <div>{{ tick }}</div>
<div>最终排名</div> <div>最终排名</div>
<div><label>{{ rank }}</label></div> <div v-if="rank>0"><label>{{ rank }}</label></div>
<div v-else>未上榜</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } 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, sendGameMessage, userJoinStatus, joinSharedRoom } from '@/composables/useGameSocket'
import LoadingView from './views/LoadingView.vue' import LoadingView from './views/LoadingView.vue'
import PlayingView from './views/PlayingView.vue' import PlayingView from './views/PlayingView.vue'
import { $format_str, $getWechat, $toast } from '@/commons/utils.ts' import { $format_str, $getWechat, $toast } from '@/commons/utils.ts'
import { playGame6Music, stopGame6Music } from '@/commons/music'; import { playGame2Music, stopAllMusic } from '@/commons/music';
type GameView = 'loading' | 'playing' type GameView = 'loading' | 'playing'
type RankPlayer = { type RankPlayer = {
...@@ -22,6 +22,7 @@ const imageUrls: Record<string, string> = { ...@@ -22,6 +22,7 @@ const imageUrls: Record<string, string> = {
close: cssAssetUrl('game1/close.png'), close: cssAssetUrl('game1/close.png'),
clock: cssAssetUrl('game1/clock.png'), clock: cssAssetUrl('game1/clock.png'),
bg2: cssAssetUrl('game2/bg2.png'), bg2: cssAssetUrl('game2/bg2.png'),
point: cssAssetUrl('game2/point.png'),
rule: cssAssetUrl('game1/rule.png'), rule: cssAssetUrl('game1/rule.png'),
coin: cssAssetUrl('game2/coin.png'), coin: cssAssetUrl('game2/coin.png'),
process: cssAssetUrl('game2/process.png'), process: cssAssetUrl('game2/process.png'),
...@@ -166,6 +167,7 @@ function showGameOverRank() { ...@@ -166,6 +167,7 @@ function showGameOverRank() {
const touchHandler = (currentScore: number) => { const touchHandler = (currentScore: number) => {
score.value = currentScore score.value = currentScore
playGame2Music();
submitScore(false, currentScore) submitScore(false, currentScore)
} }
...@@ -181,6 +183,12 @@ if (wechat) { ...@@ -181,6 +183,12 @@ if (wechat) {
const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId) const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
}, },
onShowRank: (data) => {
//第一个游戏没有排行榜,只显示结果
showScoreView();
rankList.value = data;
showGameRank.value = true;
},
onGameStart: async () => { onGameStart: async () => {
showGameRule.value = false; showGameRule.value = false;
await mobileStageRef.value?.startCountdown() await mobileStageRef.value?.startCountdown()
...@@ -219,7 +227,7 @@ if (wechat) { ...@@ -219,7 +227,7 @@ if (wechat) {
rank.value = Number.isFinite(recoveredRank) && recoveredRank > 0 ? recoveredRank : 0 rank.value = Number.isFinite(recoveredRank) && recoveredRank > 0 ? recoveredRank : 0
score.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0 score.value = Number.isFinite(recoveredScore) && recoveredScore > 0 ? recoveredScore : 0
showGameRule.value = false showGameRule.value = false
showGameRank.value = false // showGameRank.value = false
currentView.value = 'playing' currentView.value = 'playing'
startGameCountdown( startGameCountdown(
Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0, Number.isFinite(recoveredSeconds) && recoveredSeconds > 0 ? recoveredSeconds : 0,
...@@ -232,19 +240,21 @@ if (wechat) { ...@@ -232,19 +240,21 @@ if (wechat) {
$toast('管理员关闭了游戏房间') $toast('管理员关闭了游戏房间')
}, },
onRoomBack: () => { onRoomBack: () => {
console.log('onRoomBack');
resetToLoadingView()
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
}, },
}) })
_offSocketMessage.value = offSocketMessage _offSocketMessage.value = offSocketMessage
} }
function showScoreView() {
showGameRule.value = false;
stopGameCountdown()
setDivDescVisible(false)
currentView.value = 'loading'
}
onMounted(() => { onMounted(() => {
// currentView.value = 'playing' document.title = '互动游戏 - 聚宝接福'
document.title = '互动游戏 - 福运当头'
if (token.value) { if (token.value) {
window.addEventListener('beforeunload', confirmRefresh) window.addEventListener('beforeunload', confirmRefresh)
} }
...@@ -252,7 +262,7 @@ onMounted(() => { ...@@ -252,7 +262,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
_offSocketMessage.value?.() _offSocketMessage.value?.()
stopGame6Music() stopAllMusic()
if (guFrameTimer) { if (guFrameTimer) {
window.clearTimeout(guFrameTimer) window.clearTimeout(guFrameTimer)
} }
...@@ -327,8 +337,8 @@ const rankCloseHandler = () => { ...@@ -327,8 +337,8 @@ const rankCloseHandler = () => {
<div class="col-3">{{ rankScore }}</div> <div class="col-3">{{ rankScore }}</div>
</div> </div>
</div> </div>
<div class="img-close" @click="rankCloseHandler"></div> <div class="img-close" @click="rankCloseHandler"></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" />
......
...@@ -18,14 +18,14 @@ onMounted(() => { ...@@ -18,14 +18,14 @@ onMounted(() => {
<template> <template>
<div class="h5-page game-stage"> <div class="h5-page game-stage">
<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-coin"></div> <!-- <div class="img-coin"></div> -->
<div class="txt-bottom" v-if="userJoinStatus">您已成功加入游戏<br />等待主持人开始</div> <div class="txt-bottom" v-if="userJoinStatus">您已成功加入游戏<br />等待主持人开始</div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.img-coin { /* .img-coin {
background: v-bind('imageUrls.coin') center / cover no-repeat; background: v-bind('imageUrls.coin') center / cover no-repeat;
width: 744px; width: 744px;
height: 756px; height: 756px;
...@@ -34,16 +34,18 @@ onMounted(() => { ...@@ -34,16 +34,18 @@ onMounted(() => {
top: 50%; top: 50%;
transform: translate(-50%, -50%) scale(0.6); transform: translate(-50%, -50%) scale(0.6);
transform-origin: center; transform-origin: center;
} } */
.txt-bottom { .txt-bottom {
position: absolute; position: absolute;
bottom: 130px; bottom: 230px;
width: 100%; width: 100%;
line-height: 40px; line-height: 40px;
color: white; color: white;
font-size: 28px; font-size: 28px;
text-align: center; text-align: center;
-webkit-text-stroke: 1px #E61903;
paint-order: stroke fill;
animation: loading-text-scale 1.2s ease-in-out infinite; animation: loading-text-scale 1.2s ease-in-out infinite;
} }
......
...@@ -128,8 +128,8 @@ onBeforeUnmount(stopInertia) ...@@ -128,8 +128,8 @@ onBeforeUnmount(stopInertia)
<span class="desc-time">{{ countdownInterval }}</span> <span class="desc-time">{{ countdownInterval }}</span>
</div> </div>
</Transition> </Transition>
<div class="img-logo"></div> <!-- <div class="img-logo"></div>
<div class="img-hydt"></div> <div class="img-hydt"></div> -->
<div class="rank-content"> <div class="rank-content">
<div class="txt-left"> <div class="txt-left">
<div>排名</div> <div>排名</div>
...@@ -140,6 +140,7 @@ onBeforeUnmount(stopInertia) ...@@ -140,6 +140,7 @@ onBeforeUnmount(stopInertia)
<div class="txt-bold">{{ tick }}</div> <div class="txt-bold">{{ tick }}</div>
</div> </div>
</div> </div>
<div class="img-pointer"></div>
<div ref="coinRef" class="img-coin" :style="{ '--coin-rotation': `${coinRotation}deg` }" <div ref="coinRef" class="img-coin" :style="{ '--coin-rotation': `${coinRotation}deg` }"
@pointerdown="startRotation" @pointermove="rotateCoin" @pointerup="stopRotation" @pointerdown="startRotation" @pointermove="rotateCoin" @pointerup="stopRotation"
@pointercancel="stopRotation"></div> @pointercancel="stopRotation"></div>
...@@ -222,6 +223,19 @@ onBeforeUnmount(stopInertia) ...@@ -222,6 +223,19 @@ onBeforeUnmount(stopInertia)
} }
} }
.img-pointer {
width: 354px;
height: 354px;
background: v-bind('imageUrls.point');
position: absolute;
left: 50%;
top: 50%;
margin-left: 141px;
margin-top: -9px;
z-index: 9;
transform: scale(0.9);
}
/* /*
.txt-tick { .txt-tick {
position: absolute; position: absolute;
...@@ -239,13 +253,13 @@ onBeforeUnmount(stopInertia) ...@@ -239,13 +253,13 @@ onBeforeUnmount(stopInertia)
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, -50%) scale(0.5) rotate(var(--coin-rotation, 0deg)); transform: translate(-50%, -50%) scale(0.6) rotate(var(--coin-rotation, 0deg));
transform-origin: center; transform-origin: center;
touch-action: none; touch-action: none;
user-select: none; user-select: none;
cursor: grab; cursor: grab;
will-change: transform; will-change: transform;
margin-top:60px; margin-top: 70px;
} }
.img-coin:active { .img-coin:active {
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } 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, sendGameMessage, userJoinStatus, joinSharedRoom } from '@/composables/useGameSocket'
import LoadingView from './views/LoadingView.vue' import LoadingView from './views/LoadingView.vue'
import PlayingView from './views/PlayingView.vue' import PlayingView from './views/PlayingView.vue'
import { $getWechat, $toast, $is_run_local } from '@/commons/utils.ts' import { $getWechat, $toast, $is_run_local } from '@/commons/utils.ts'
import { playGame6Music, stopGame6Music } from '@/commons/music'; import { playGame3Music, stopAllMusic } from '@/commons/music';
type GameView = 'loading' | 'playing' type GameView = 'loading' | 'playing'
type RankPlayer = { type RankPlayer = {
...@@ -101,12 +101,7 @@ function stopGameCountdown() { ...@@ -101,12 +101,7 @@ function stopGameCountdown() {
} }
function submitScore(save: boolean = false, currentTick = score.value) { function submitScore(save: boolean = false, currentTick = score.value) {
if (!wechat && !isLocalMode) return
if (save) { if (save) {
if (isLocalMode && !wechat) {
console.log('[game3 本地模式] 提交最终分数:', currentTick)
return
}
sendGameMessage('submit_score_save', { sendGameMessage('submit_score_save', {
score: currentTick, score: currentTick,
wechat: wechat.token_origin, wechat: wechat.token_origin,
...@@ -116,10 +111,6 @@ function submitScore(save: boolean = false, currentTick = score.value) { ...@@ -116,10 +111,6 @@ function submitScore(save: boolean = false, currentTick = score.value) {
rank: rank.value, rank: rank.value,
}) })
} else { } else {
if (isLocalMode && !wechat) {
console.log('[game3 本地模式] 实时分数:', currentTick)
return
}
sendGameMessage('submit_score', { score: currentTick }) sendGameMessage('submit_score', { score: currentTick })
} }
} }
...@@ -179,28 +170,17 @@ function startGameView() { ...@@ -179,28 +170,17 @@ function startGameView() {
function showGameOverRank() { function showGameOverRank() {
stopGameCountdown() stopGameCountdown()
setDivDescVisible(false) setDivDescVisible(false)
// 保持在 playing 视图,结果页由 PlayingView 内部渲染 currentView.value = 'loading'
isGameOver.value = true showGameRank.value = true
userJoinStatus.value = false userJoinStatus.value = false;
if (isLocalMode && !wechat) {
console.log('[game3 本地模式] 游戏结束,最终分数:', score.value)
}
} }
const touchHandler = (mole: boolean) => { const scoreChangeHandler = (delta: number) => {
let scoreDelta = mole ? 5 : -3; if (delta > 0) {
let nextTick = score.value + scoreDelta playGame3Music(1);
if (nextTick < 0) nextTick = 0
score.value = nextTick
if (mole) {
playGame6Music(1);
} else { } else {
playGame6Music(2); playGame3Music(2);
} }
submitScore(false, nextTick)
}
const scoreChangeHandler = (delta: number) => {
let nextTick = score.value + delta let nextTick = score.value + delta
if (nextTick < 0) nextTick = 0 if (nextTick < 0) nextTick = 0
score.value = nextTick score.value = nextTick
...@@ -215,7 +195,7 @@ if (wechat) { ...@@ -215,7 +195,7 @@ if (wechat) {
userid: wechat.token, userid: wechat.token,
}, },
onConnect: () => { onConnect: () => {
window.setTimeout(() => { window.setTimeout(() => {
const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId) const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
}, },
...@@ -268,35 +248,24 @@ if (wechat) { ...@@ -268,35 +248,24 @@ if (wechat) {
stopGameCountdown() stopGameCountdown()
currentView.value = 'loading' currentView.value = 'loading'
setDivDescVisible(false) setDivDescVisible(false)
$toast('管理员关闭了房间') $toast('管理员关闭了游戏房间')
}, },
onRoomBack: () => { onRoomBack: () => {
resetToLoadingView()
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
}, },
}) })
_offSocketMessage.value = offSocketMessage _offSocketMessage.value = offSocketMessage
} }
onMounted(() => { onMounted(() => {
document.title = '小游戏 - 福运当头' document.title = '互动游戏 - 马上有福'
if (token.value) { if (token.value) {
window.addEventListener('beforeunload', confirmRefresh) window.addEventListener('beforeunload', confirmRefresh)
} }
// // TODO_LOCAL: 本地模拟游戏开始,稍后部署上线前删除此段
// if ($is_run_local()) {
// showGameRule.value = false
// currentView.value = 'playing'
// startGameCountdown()
// }
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
_offSocketMessage.value?.() _offSocketMessage.value?.()
stopGame6Music() stopAllMusic()
if (guFrameTimer) { if (guFrameTimer) {
window.clearTimeout(guFrameTimer) window.clearTimeout(guFrameTimer)
} }
...@@ -324,7 +293,7 @@ const rankCloseHandler = () => { ...@@ -324,7 +293,7 @@ const rankCloseHandler = () => {
</script> </script>
<template> <template>
<MobileStage v-if="token || isLocalMode" :showGameRule="showGameRule" :showGameRank="showGameRank" ref="mobileStageRef" <MobileStage v-if="token" :showGameRule="showGameRule" :showGameRank="showGameRank" ref="mobileStageRef"
:background="`${renderBG()}`"> :background="`${renderBG()}`">
<template #gameRule> <template #gameRule>
<div class="rule-container"> <div class="rule-container">
...@@ -373,16 +342,21 @@ const rankCloseHandler = () => { ...@@ -373,16 +342,21 @@ const rankCloseHandler = () => {
<div class="col-3">{{ rankScore }}</div> <div class="col-3">{{ rankScore }}</div>
</div> </div>
</div> </div>
</div> <div class="img-close" @click="rankCloseHandler"></div>
<div class="img-close" @click="rankCloseHandler"></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="score" :rank="rank" :countdown-interval="countdownInterval" <PlayingView v-else :image-urls="imageUrls" :tick="score" :rank="rank" :countdown-interval="countdownInterval"
:is-div-desc-visible="isDivDescVisible" @score-change="scoreChangeHandler" @rank-close="rankCloseHandler" />
<!-- <LoadingView v-if="currentView === 'loading'" :image-urls="imageUrls" :user-join-status="userJoinStatus"
@touchGameRule="showGameRuleHandler" />
<PlayingView v-else :image-urls="imageUrls" :tick="score" :rank="rank" :countdown-interval="countdownInterval"
:is-div-desc-visible="isDivDescVisible" :is-game-over="isGameOver" :is-div-desc-visible="isDivDescVisible" :is-game-over="isGameOver"
:rank-list="displayRankList" :user-rank="rank" :user-score="rankScore" :rank-list="displayRankList" :user-rank="rank" :user-score="rankScore"
:user-nickname="nickname" :user-avatar="avatar" :user-nickname="nickname" :user-avatar="avatar"
@touch="touchHandler" @score-change="scoreChangeHandler" @rank-close="rankCloseHandler" /> @touch="touchHandler" @score-change="scoreChangeHandler" @rank-close="rankCloseHandler" /> -->
</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;">
请使用微信扫码进入游戏 请使用微信扫码进入游戏
...@@ -510,9 +484,10 @@ const rankCloseHandler = () => { ...@@ -510,9 +484,10 @@ const rankCloseHandler = () => {
.img-close { .img-close {
width: 57px; width: 57px;
height: 57px; height: 57px;
border-radius: 29px; background: v-bind('imageUrls.close') center / cover no-repeat;
background: v-bind('imageUrls.close')center/cover; transform: translateX(-50%) scale(1.4);
transform: scale(0.7); position: absolute;
margin-top: 500px; top: 0;
left: 100%;
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch, nextTick, computed } from 'vue'; import { onBeforeUnmount, onMounted, ref, watch, nextTick, computed } from 'vue';
import { assetUrl } from '@/commons/assets.ts' import { assetUrl } from '@/commons/assets.ts'
import { playGame6Music } from '@/commons/music' // import { playGame6Music } from '@/commons/music'
const props = defineProps<{ const props = defineProps<{
imageUrls: Record<string, string> imageUrls: Record<string, string>
...@@ -9,16 +9,10 @@ const props = defineProps<{ ...@@ -9,16 +9,10 @@ const props = defineProps<{
isDivDescVisible: boolean isDivDescVisible: boolean
tick: number tick: number
rank: number rank: number
isGameOver: boolean
rankList: Array<{ rank: number; nickname: string; avatar: string; score: number | string }>
userRank: number
userScore: number | string
userNickname: string
userAvatar: string
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
touch: [mole: boolean] // touch: [mole: boolean]
scoreChange: [delta: number] scoreChange: [delta: number]
rankClose: [] rankClose: []
}>(); }>();
...@@ -275,10 +269,8 @@ const playCollectEffect = (item: RoadItem, x: number, y: number) => { ...@@ -275,10 +269,8 @@ const playCollectEffect = (item: RoadItem, x: number, y: number) => {
if (isCoin) { if (isCoin) {
emit('scoreChange', 5); emit('scoreChange', 5);
playGame6Music(1);
} else { } else {
emit('scoreChange', -3); emit('scoreChange', -3);
playGame6Music(2);
} }
const animId = Date.now() + Math.random(); const animId = Date.now() + Math.random();
...@@ -431,13 +423,13 @@ watch(() => props.isDivDescVisible, (visible) => { ...@@ -431,13 +423,13 @@ watch(() => props.isDivDescVisible, (visible) => {
}, { immediate: true }); }, { immediate: true });
// 游戏结束:暂停全部动画,显示结果页 // 游戏结束:暂停全部动画,显示结果页
watch(() => props.isGameOver, (over) => { // watch(() => props.isGameOver, (over) => {
if (over) { // if (over) {
stopRoadScroll(); // stopRoadScroll();
stopMainLoop(); // stopMainLoop();
stopItemGen(); // stopItemGen();
} // }
}); // });
</script> </script>
<template> <template>
...@@ -507,7 +499,7 @@ watch(() => props.isGameOver, (over) => { ...@@ -507,7 +499,7 @@ watch(() => props.isGameOver, (over) => {
<div class="img-right" @click="switchToRight"></div> <div class="img-right" @click="switchToRight"></div>
<!-- 游戏结束结果页覆盖层 --> <!-- 游戏结束结果页覆盖层 -->
<Transition name="rank-fade"> <!-- <Transition name="rank-fade">
<div v-if="isGameOver" class="rank-overlay"> <div v-if="isGameOver" class="rank-overlay">
<div class="ranklist-container"> <div class="ranklist-container">
<div class="txt-title">排行</div> <div class="txt-title">排行</div>
...@@ -541,7 +533,7 @@ watch(() => props.isGameOver, (over) => { ...@@ -541,7 +533,7 @@ watch(() => props.isGameOver, (over) => {
</div> </div>
<div class="img-rank-close" @click="$emit('rankClose')"></div> <div class="img-rank-close" @click="$emit('rankClose')"></div>
</div> </div>
</Transition> </Transition> -->
</div> </div>
</template> </template>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } 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, sendGameMessage, userJoinStatus, joinSharedRoom } from '@/composables/useGameSocket'
import LoadingView from './views/LoadingView.vue' import LoadingView from './views/LoadingView.vue'
import PlayingView from './views/PlayingView.vue' import PlayingView from './views/PlayingView.vue'
import ScoreView from './views/ScoreView.vue' import ScoreView from './views/ScoreView.vue'
...@@ -159,15 +159,10 @@ function replayGame() { ...@@ -159,15 +159,10 @@ function replayGame() {
} }
function backToWaiting() { function backToWaiting() {
resetToLoadingView()
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
} }
function handleRoomBack() { function handleRoomBack() {
resetToLoadingView()
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
} }
const touchHandler = () => { const touchHandler = () => {
...@@ -187,8 +182,8 @@ if (wechat) { ...@@ -187,8 +182,8 @@ if (wechat) {
userid: wechat.token, userid: wechat.token,
}, },
onConnect: () => { onConnect: () => {
window.setTimeout(() => { window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value, gameId) const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
}, },
onGameStart: async () => { onGameStart: async () => {
......
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue' import { onBeforeUnmount, onMounted, ref } from 'vue'
import shakeSound from '@/assets/images/game4/yaoyiyao.mp3' import { assetUrl } from '@/commons/assets'
const shakeSound = assetUrl('game4/yaoyiyao.mp3')
const props = defineProps<{ const props = defineProps<{
imageUrls: Record<string, string> imageUrls: Record<string, string>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } 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, sendGameMessage, userJoinStatus, joinSharedRoom } from '@/composables/useGameSocket'
import LoadingView from './views/LoadingView.vue' import LoadingView from './views/LoadingView.vue'
import PlayingView from './views/PlayingView.vue' import PlayingView from './views/PlayingView.vue'
import ScoreView from './views/ScoreView.vue' import ScoreView from './views/ScoreView.vue'
...@@ -192,10 +192,7 @@ function showGameOverRank() { ...@@ -192,10 +192,7 @@ function showGameOverRank() {
function handleRoomBack() { function handleRoomBack() {
resetToLoadingView()
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
} }
const hitHandler = (score: number) => { const hitHandler = (score: number) => {
...@@ -212,7 +209,7 @@ if (wechat) { ...@@ -212,7 +209,7 @@ if (wechat) {
userid: wechat.token, userid: wechat.token,
}, },
onConnect: () => { onConnect: () => {
window.setTimeout(() => { window.setTimeout(() => {
const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId) const joined = joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200) }, 200)
}, },
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { computed, onBeforeUnmount, onMounted, ref } 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, sendGameMessage, userJoinStatus, joinSharedRoom } from '@/composables/useGameSocket'
import LoadingView from './views/LoadingView.vue' import LoadingView from './views/LoadingView.vue'
import PlayingView from './views/PlayingView.vue' import PlayingView from './views/PlayingView.vue'
import { $format_str, $getWechat, $toast } from '@/commons/utils.ts' import { $format_str, $getWechat, $toast } from '@/commons/utils.ts'
import { playGame6Music, stopGame6Music } from '@/commons/music'; import { playGame6Music, stopAllMusic } from '@/commons/music';
type GameView = 'loading' | 'playing' type GameView = 'loading' | 'playing'
type RankPlayer = { type RankPlayer = {
...@@ -247,10 +247,6 @@ if (wechat) { ...@@ -247,10 +247,6 @@ if (wechat) {
$toast('管理员关闭了游戏房间') $toast('管理员关闭了游戏房间')
}, },
onRoomBack: () => { onRoomBack: () => {
resetToLoadingView()
window.setTimeout(() => {
joinSharedRoom(nickname.value, token.value, avatar.value, gameId)
}, 200)
}, },
}) })
_offSocketMessage.value = offSocketMessage _offSocketMessage.value = offSocketMessage
...@@ -265,7 +261,7 @@ onMounted(() => { ...@@ -265,7 +261,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
_offSocketMessage.value?.() _offSocketMessage.value?.()
stopGame6Music() stopAllMusic()
if (guFrameTimer) { if (guFrameTimer) {
window.clearTimeout(guFrameTimer) window.clearTimeout(guFrameTimer)
} }
...@@ -472,12 +468,6 @@ const rankCloseHandler = () => { ...@@ -472,12 +468,6 @@ const rankCloseHandler = () => {
} }
.img-close { .img-close {
/* width: 57px;
height: 57px;
border-radius: 29px;
background: v-bind('imageUrls.close')center/cover;
transform: scale(0.7);
margin-top: 500px; */
width: 57px; width: 57px;
height: 57px; height: 57px;
background: v-bind('imageUrls.close') center / cover no-repeat; background: v-bind('imageUrls.close') center / cover no-repeat;
......
<!-- <script setup lang="ts">
import { $getWechat } from '@/commons/utils';
import { onMounted, ref } from 'vue';
defineProps<{
imageUrls: Record<string, string>
tick: number
rank: number
}>()
defineEmits<{
showRank: []
replay: []
back: []
}>()
const nickname = ref('');
const avatar = ref('');
onMounted(() => {
const wechat = $getWechat();
nickname.value = wechat.nickname;
avatar.value = wechat.avatar;
});
</script>
<template>
<div class="h5-page game-stage">
<div class="img-logo"></div>
</div>
</template>
<style scoped>
.game-stage {
position: absolute;
width: 750px;
height: 1624px;
padding: 0;
}
.img-logo {
position: absolute;
top: 66px;
left: 50%;
width: 428px;
height: 77px;
background: v-bind('imageUrls.logo') center/cover;
transform: translateX(-50%);
}
</style> -->
...@@ -2,7 +2,7 @@ import { fileURLToPath, URL } from 'node:url' ...@@ -2,7 +2,7 @@ import { fileURLToPath, URL } from 'node:url'
import { cpSync, existsSync, rmSync } from 'node:fs' import { cpSync, existsSync, rmSync } from 'node:fs'
import { resolve } from 'node:path' import { resolve } from 'node:path'
import basicSsl from '@vitejs/plugin-basic-ssl' import basicSsl from '@vitejs/plugin-basic-ssl'
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools' import vueDevTools from 'vite-plugin-vue-devtools'
...@@ -25,35 +25,40 @@ function copyImageAssets() { ...@@ -25,35 +25,40 @@ function copyImageAssets() {
} }
} }
export default defineConfig(({ command }) => ({ export default defineConfig(({ command, mode }) => {
base: command === 'build' ? '/h5/' : '/', const env = loadEnv(mode, process.cwd(), '')
define: { const useLocalAssets = env.VITE_ASSET_BUILD_LOCAL === 'true'
__APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
__DEBUG__: JSON.stringify(command === 'serve'), return {
}, base: command === 'build' ? '/h5/' : '/',
plugins: [ define: {
vue(), __APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
vueDevTools(), __DEBUG__: JSON.stringify(command === 'serve'),
basicSsl(), },
copyImageAssets(), plugins: [
], vue(),
server: { vueDevTools(),
host: true, basicSsl(),
proxy: { ...(useLocalAssets || !env.VITE_ASSET_BASE_URL ? [copyImageAssets()] : []),
'/socket.io': { ],
target: 'http://localhost:8082', server: {
changeOrigin: true, host: true,
ws: true, proxy: {
}, '/socket.io': {
'/manager': { target: 'http://localhost:8082',
target: 'http://localhost:8082', changeOrigin: true,
changeOrigin: true, ws: true,
},
'/manager': {
target: 'http://localhost:8082',
changeOrigin: true,
},
}, },
}, },
}, resolve: {
resolve: { alias: {
alias: { '@': fileURLToPath(new URL('./src', import.meta.url))
'@': fileURLToPath(new URL('./src', import.meta.url)) },
}, },
}, }
})) })
VITE_ASSET_BUILD_LOCAL=true
VITE_ASSET_BASE_URL=https://xjfcoss.guocai365.org.cn/games/pc/assets/images/
VITE_ASSET_BASE_URL=https://your-bucket.oss-cn-shanghai.aliyuncs.com VITE_ASSET_BASE_URL=https://xjfcoss.guocai365.org.cn/jfsc/storage/
VITE_ASSET_DEV_LOCAL=false
\ No newline at end of file
...@@ -41,6 +41,16 @@ npm run dev ...@@ -41,6 +41,16 @@ npm run dev
npm run build npm run build
``` ```
### Build With OSS or Local Assets
```sh
# Use VITE_ASSET_BASE_URL from .env.production
npm run build:oss
# Bundle with local assets and ignore the OSS URL
npm run build:local
```
## Image Assets ## Image Assets
Put local development images in `src/assets/images`. Put local development images in `src/assets/images`.
......
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "run-p type-check \"build-only {@}\" --", "build:oss": "npm run type-check && vite build --mode production",
"build:local": "npm run type-check && vite build --mode local-assets",
"preview": "vite preview", "preview": "vite preview",
"build-only": "vite build", "build-only": "vite build",
"type-check": "vue-tsc --build" "type-check": "vue-tsc --build"
......
...@@ -31,6 +31,14 @@ export function assetUrl(path: string, options: AssetUrlOptions = {}): string { ...@@ -31,6 +31,14 @@ export function assetUrl(path: string, options: AssetUrlOptions = {}): string {
} }
const normalizedPath = normalizeAssetPath(path); const normalizedPath = normalizeAssetPath(path);
const configuredBaseUrl = import.meta.env.VITE_ASSET_BUILD_LOCAL === 'true'
? undefined
: options.baseUrl ?? import.meta.env.VITE_ASSET_BASE_URL;
if (import.meta.env.PROD && configuredBaseUrl) {
return joinUrl(configuredBaseUrl, normalizedPath);
}
const preferLocal = options.preferLocal ?? ( const preferLocal = options.preferLocal ?? (
import.meta.env.DEV && import.meta.env.VITE_ASSET_DEV_LOCAL !== 'false' import.meta.env.DEV && import.meta.env.VITE_ASSET_DEV_LOCAL !== 'false'
); );
...@@ -39,10 +47,8 @@ export function assetUrl(path: string, options: AssetUrlOptions = {}): string { ...@@ -39,10 +47,8 @@ export function assetUrl(path: string, options: AssetUrlOptions = {}): string {
return localAssetUrl(normalizedPath); return localAssetUrl(normalizedPath);
} }
const baseUrl = options.baseUrl ?? import.meta.env.VITE_ASSET_BASE_URL; if (configuredBaseUrl) {
return joinUrl(configuredBaseUrl, normalizedPath);
if (baseUrl) {
return joinUrl(baseUrl, normalizedPath);
} }
return localAssetUrl(normalizedPath); return localAssetUrl(normalizedPath);
......
import game1MusicUrl from '@/assets/images/game1.mp3' import { assetUrl } from '@/commons/assets'
import game2MusicUrl from '@/assets/images/game2.mp3'
import game4MusicUrl from '@/assets/images/cmyf-bgm.mp3' const game1MusicUrl = assetUrl('game1.mp3')
import game5MusicUrl from '@/assets/images/qcnf-bgm.mp3' const game2MusicUrl = assetUrl('game2.mp3')
import game6MusicUrl from '@/assets/images/game6.mp3' const game4MusicUrl = assetUrl('cmyf-bgm.mp3')
import go321 from '@/assets/images/321go.mp3' const game5MusicUrl = assetUrl('qcnf-bgm.mp3')
import applause from '@/assets/images/applause.mp3' const game6MusicUrl = assetUrl('game6.mp3')
const go321 = assetUrl('321go.mp3')
const applause = assetUrl('applause.mp3')
let backgroundMusic: HTMLAudioElement | null = null let backgroundMusic: HTMLAudioElement | null = null
let countdownTemplate: HTMLAudioElement | null = null let countdownTemplate: HTMLAudioElement | null = null
......
...@@ -147,6 +147,152 @@ export function $toast(message?: string, duration = 2000) { ...@@ -147,6 +147,152 @@ export function $toast(message?: string, duration = 2000) {
}, duration); }, duration);
} }
export type NotifyType = 'info' | 'success' | 'warning' | 'error';
export type NotifyOptions = {
title?: string;
message?: string;
type?: NotifyType;
duration?: number;
}
let notifyElement: HTMLDivElement | undefined;
let notifyTitleElement: HTMLDivElement | undefined;
let notifyMessageElement: HTMLDivElement | undefined;
let notifyTimer: ReturnType<typeof window.setTimeout> | undefined;
let notifyHideTimer: ReturnType<typeof window.setTimeout> | undefined;
function closeNotify() {
if (!notifyElement) {
return;
}
if (notifyTimer) {
window.clearTimeout(notifyTimer);
notifyTimer = undefined;
}
if (notifyHideTimer) {
window.clearTimeout(notifyHideTimer);
}
notifyElement.style.opacity = '0';
notifyElement.style.transform = 'translate(-50%, -130%)';
notifyHideTimer = window.setTimeout(() => {
if (notifyElement) {
notifyElement.style.display = 'none';
}
notifyHideTimer = undefined;
}, 260);
}
function ensureNotifyElement() {
if (notifyElement) {
return;
}
notifyElement = document.createElement('div');
notifyElement.style.cssText = [
'position: fixed',
'left: 50%',
'top: 24px',
'z-index: 100000',
'display: none',
'width: min(560px, calc(100vw - 40px))',
'box-sizing: border-box',
'padding: 18px 54px 18px 24px',
'border-radius: 16px',
'background: rgba(55, 55, 55, 0.88)',
'color: #fff',
'box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22)',
'opacity: 0',
'transform: translate(-50%, -130%)',
'transition: opacity 0.26s ease, transform 0.26s ease',
].join(';');
notifyTitleElement = document.createElement('div');
notifyTitleElement.style.cssText = [
'font-size: 22px',
'font-weight: 700',
'line-height: 1.35',
].join(';');
notifyMessageElement = document.createElement('div');
notifyMessageElement.style.cssText = [
'margin-top: 5px',
'color: rgba(255, 255, 255, 0.88)',
'font-size: 18px',
'line-height: 1.45',
'word-break: break-word',
].join(';');
const closeElement = document.createElement('button');
closeElement.type = 'button';
closeElement.textContent = '×';
closeElement.setAttribute('aria-label', 'Close notification');
closeElement.style.cssText = [
'position: absolute',
'top: 12px',
'right: 14px',
'width: 34px',
'height: 34px',
'padding: 0',
'border: 0',
'background: transparent',
'color: rgba(255, 255, 255, 0.8)',
'font-size: 28px',
'line-height: 32px',
'cursor: pointer',
].join(';');
closeElement.onclick = closeNotify;
notifyElement.append(
notifyTitleElement,
notifyMessageElement,
closeElement,
);
document.body.appendChild(notifyElement);
}
export function $notify(message?: string, title?: string): () => void;
export function $notify(options?: NotifyOptions): () => void;
export function $notify(messageOrOptions?: string | NotifyOptions, title?: string): () => void {
const options: NotifyOptions = typeof messageOrOptions === 'object'
? messageOrOptions
: { message: messageOrOptions, title };
if (!options.message) {
return closeNotify;
}
ensureNotifyElement();
if (notifyTimer) {
window.clearTimeout(notifyTimer);
}
if (notifyHideTimer) {
window.clearTimeout(notifyHideTimer);
notifyHideTimer = undefined;
}
notifyTitleElement!.textContent = options.title ?? '通知';
notifyMessageElement!.textContent = options.message;
notifyElement!.style.display = 'block';
window.requestAnimationFrame(() => {
if (!notifyElement) {
return;
}
notifyElement.style.opacity = '1';
notifyElement.style.transform = 'translate(-50%, 0)';
});
if ((options.duration ?? 4000) > 0) {
notifyTimer = window.setTimeout(closeNotify, options.duration ?? 4000);
}
return closeNotify;
}
type ConfirmOptions = { type ConfirmOptions = {
title?: string; title?: string;
...@@ -324,4 +470,4 @@ export function $confirm(messageOrOptions?: string | ConfirmOptions, title?: str ...@@ -324,4 +470,4 @@ export function $confirm(messageOrOptions?: string | ConfirmOptions, title?: str
return new Promise<boolean>((resolve) => { return new Promise<boolean>((resolve) => {
confirmResolve = resolve; confirmResolve = resolve;
}); });
} }
\ No newline at end of file
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
import { onBeforeUnmount, onMounted } from 'vue' import { onBeforeUnmount, onMounted } from 'vue'
import { $read_socket_storge, $remove_socket_storage, $toast } from '@/commons/utils' import { $read_socket_storge, $remove_socket_storage, $notify } from '@/commons/utils'
import { initSocket, onSocketMessage, sendSocketMessage } from '@/commons/ws' import { initSocket, onSocketMessage, sendSocketMessage } from '@/commons/ws'
import router from '@/router' import router from '@/router'
...@@ -13,6 +13,7 @@ type SocketPayload = { ...@@ -13,6 +13,7 @@ type SocketPayload = {
type AdminGameSocketOptions = { type AdminGameSocketOptions = {
gameId?: string, gameId?: string,
loginRedirectPath?: string loginRedirectPath?: string
onConnect?: () => boolean
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>
...@@ -57,8 +58,19 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -57,8 +58,19 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
validuser: CryptoJS.AES.encrypt(token, passphrase).toString(), validuser: CryptoJS.AES.encrypt(token, passphrase).toString(),
}, },
onConnect: () => { onConnect: () => {
if (options.onConnect) {
if (!options.onConnect()) {
return;
}
}
window.setTimeout(requestRoomState, 200) window.setTimeout(requestRoomState, 200)
}, },
onDisconnect: (_reason: string) => {
},
onError: (error: any) => {
// console.error('socket.io connect_error:', error)
},
}) })
offSocketMessage = onSocketMessage(async (evt, payload: SocketPayload) => { offSocketMessage = onSocketMessage(async (evt, payload: SocketPayload) => {
...@@ -67,18 +79,16 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -67,18 +79,16 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
if (evt == 'login_result') { if (evt == 'login_result') {
// alert(msg) // alert(msg)
// options.onRelogin?.() // options.onRelogin?.()
$toast(msg); // $notify(msg);
$remove_socket_storage(); $remove_socket_storage();
await router.replace(`/login`); await router.replace(`/login`);
location.href = location.href;
return; return;
} }
else if (evt == 'new_admin_result') { else if (evt == 'new_admin_result') {
// alert(msg); // alert(msg);
$toast(msg); // $notify(msg);
$remove_socket_storage(); $remove_socket_storage();
await router.replace(`/login`); await router.replace(`/login`);
location.href = location.href;
return; return;
} }
} }
...@@ -88,13 +98,19 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) { ...@@ -88,13 +98,19 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
if (data.gameId) { if (data.gameId) {
//如果这两个值不一致说明浏览器有直接更新游戏房间号 //如果这两个值不一致说明浏览器有直接更新游戏房间号
if (data.gameId != options.gameId) { if (data.gameId != options.gameId) {
location.reload(); // location.reload();
return; return;
} }
} }
options.onRoomState?.(data) options.onRoomState?.(data)
return return
} }
case 'player_offline_result': {
// $notify('有玩家掉线')
if (data)
options.onRoomJoin?.(data)
return
}
case 'room_join_result': { case 'room_join_result': {
options.onRoomJoin?.(data) options.onRoomJoin?.(data)
return return
......
<script setup lang="ts"> <script setup lang="ts">
import { cssAssetUrl } from '@/commons/assets'; import { cssAssetUrl } from '@/commons/assets';
import { $save, $toast } from '@/commons/utils.ts'; import { $save, $notify, $toast } from '@/commons/utils.ts';
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
...@@ -26,7 +26,7 @@ async function login() { ...@@ -26,7 +26,7 @@ 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) {
$toast(data.msg) // $notify(data.msg)
//alert(data.msg || 'Login failed') //alert(data.msg || 'Login failed')
// throw new Error(data.msg || 'Login failed') // throw new Error(data.msg || 'Login failed')
} }
...@@ -38,7 +38,7 @@ const loginHandler = async () => { ...@@ -38,7 +38,7 @@ const loginHandler = async () => {
if (state === 0) { if (state === 0) {
// alert(msg) // alert(msg)
$toast(msg) // $notify(msg)
return return
} }
for (let i in data) { for (let i in data) {
......
...@@ -41,31 +41,11 @@ const gameHandler = async (index: number) => { ...@@ -41,31 +41,11 @@ const gameHandler = async (index: number) => {
} }
await router.replace(`/game${index + 1}`); await router.replace(`/game${index + 1}`);
} }
const loginSuccessHandler = () => {
show_login.value = false;
gameHandler(item_index.value);
}
const confirmRefresh = (event: BeforeUnloadEvent) => {
event.preventDefault()
event.returnValue = ''
}
useAdminGameSocket({}) useAdminGameSocket({})
onMounted(() => {
window.addEventListener('beforeunload', confirmRefresh)
})
onUnmounted(() => {
window.removeEventListener('beforeunload', confirmRefresh)
})
</script> </script>
<template> <template>
<!-- <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>
......
...@@ -7,7 +7,7 @@ import Rank2 from './views/Rank2View.vue' ...@@ -7,7 +7,7 @@ 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 { playApplauseMusic, playGame1Music, stopAllMusic, stopGame1Music } from '@/commons/music' import { playApplauseMusic, playGame1Music, stopAllMusic, stopGame1Music } from '@/commons/music'
import { $confirm, $remove_socket_storage, $toast } from '@/commons/utils.ts' import { $confirm, $remove_socket_storage, $notify, $toast } from '@/commons/utils.ts'
const router = useRouter() const router = useRouter()
......
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import router from '@/router'; import router from '@/router';
import { $format_str } from '@/commons/utils.ts';
import DesignStage from '@/components/DesignStage.vue' import DesignStage from '@/components/DesignStage.vue'
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts'; import { assetUrl, cssAssetUrl } from '@/commons/assets.ts';
...@@ -33,6 +32,7 @@ const imageUrls = { ...@@ -33,6 +32,7 @@ const imageUrls = {
}; };
const isStartPressed = ref(false); const isStartPressed = ref(false);
const isQrcodeExpanded = ref(false);
const pressStartButton = () => { const pressStartButton = () => {
isStartPressed.value = true; isStartPressed.value = true;
...@@ -58,9 +58,12 @@ const renderAvatar = (item: any | null) => { ...@@ -58,9 +58,12 @@ const renderAvatar = (item: any | null) => {
<div class="img-title1"></div> <div class="img-title1"></div>
<div class="img-title2"></div> <div class="img-title2"></div>
<div class="img-qrcode-container"> <div class="img-qrcode-container">
<div class="img-qrcode"><img :src="imageUrls.qrcode" /></div> <div class="img-qrcode" @click="isQrcodeExpanded = true"><img :src="imageUrls.qrcode" /></div>
<div class="txt-qrcode">微信扫码参与</div> <div class="txt-qrcode">微信扫码参与</div>
</div> </div>
<div v-if="isQrcodeExpanded" class="qrcode-preview-mask" @click="isQrcodeExpanded = false">
<img class="qrcode-preview" :src="imageUrls.qrcode" @click.stop="isQrcodeExpanded = false" />
</div>
<div class="img-gu"></div> <div class="img-gu"></div>
<div class="list-container"> <div class="list-container">
<div class="txt-count">当前在线人数 <span>{{ playerCount }}</span></div> <div class="txt-count">当前在线人数 <span>{{ playerCount }}</span></div>
...@@ -70,7 +73,7 @@ const renderAvatar = (item: any | null) => { ...@@ -70,7 +73,7 @@ const renderAvatar = (item: any | null) => {
<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, 8) }}</div> <div class="txt-nickname">{{ item.nickname }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -138,6 +141,7 @@ const renderAvatar = (item: any | null) => { ...@@ -138,6 +141,7 @@ const renderAvatar = (item: any | null) => {
width: 100%; width: 100%;
height: 144px; height: 144px;
border-radius: 10px; border-radius: 10px;
cursor: pointer;
} }
.txt-qrcode { .txt-qrcode {
...@@ -146,6 +150,27 @@ const renderAvatar = (item: any | null) => { ...@@ -146,6 +150,27 @@ const renderAvatar = (item: any | null) => {
} }
} }
.qrcode-preview-mask {
position: absolute;
inset: 0;
z-index: 10;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 150px;
background: rgb(0 0 0 / 45%);
cursor: pointer;
}
.qrcode-preview {
width: 480px;
height: 480px;
padding: 18px;
border-radius: 24px;
background: #fff;
box-shadow: 0 18px 60px rgb(0 0 0 / 35%);
}
.img-gu { .img-gu {
background: v-bind('imageUrls.gu'); background: v-bind('imageUrls.gu');
width: 539px; width: 539px;
...@@ -188,6 +213,10 @@ const renderAvatar = (item: any | null) => { ...@@ -188,6 +213,10 @@ const renderAvatar = (item: any | null) => {
height: 250px; height: 250px;
align-content: center; align-content: center;
>div {
min-width: 0;
}
.img-avatar-container { .img-avatar-container {
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
...@@ -217,9 +246,15 @@ const renderAvatar = (item: any | null) => { ...@@ -217,9 +246,15 @@ const renderAvatar = (item: any | null) => {
} }
.txt-nickname { .txt-nickname {
display: block;
width: calc(100% - 12px);
margin: 4px auto 0;
overflow: hidden;
color: #666; color: #666;
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
} }
} }
......
...@@ -7,7 +7,7 @@ import Rank2 from './views/Rank2View.vue' ...@@ -7,7 +7,7 @@ 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 { playApplauseMusic, playGame2Music, stopAllMusic } from '@/commons/music' import { playApplauseMusic, playGame2Music, stopAllMusic } from '@/commons/music'
import { $confirm, $remove_socket_storage, $toast } from '@/commons/utils.ts' import { $confirm, $remove_socket_storage, $notify, $toast } from '@/commons/utils.ts'
const router = useRouter() const router = useRouter()
......
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import router from '@/router'; import router from '@/router';
import { $format_str } from '@/commons/utils.ts';
import DesignStage from '@/components/DesignStage.vue' import DesignStage from '@/components/DesignStage.vue'
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts'; import { assetUrl, cssAssetUrl } from '@/commons/assets.ts';
...@@ -19,7 +18,7 @@ const imageUrls = { ...@@ -19,7 +18,7 @@ const imageUrls = {
bg: cssAssetUrl('game2/bg.png'), bg: cssAssetUrl('game2/bg.png'),
logo: cssAssetUrl('game1/logo.png'), logo: cssAssetUrl('game1/logo.png'),
title1: cssAssetUrl('game2/title.png'), title1: cssAssetUrl('game2/title.png'),
qrcode: assetUrl('game6/qrcode.png'), qrcode: assetUrl('game2/qrcode.png'),
// gu: cssAssetUrl('game1/gu.png'), // gu: cssAssetUrl('game1/gu.png'),
// bg: cssAssetUrl('game1/bg.png'), // bg: cssAssetUrl('game1/bg.png'),
...@@ -43,6 +42,7 @@ const imageUrls = { ...@@ -43,6 +42,7 @@ const imageUrls = {
}; };
const isStartPressed = ref(false); const isStartPressed = ref(false);
const isQrcodeExpanded = ref(false);
const pressStartButton = () => { const pressStartButton = () => {
isStartPressed.value = true; isStartPressed.value = true;
...@@ -68,9 +68,12 @@ const renderAvatar = (item: any | null) => { ...@@ -68,9 +68,12 @@ const renderAvatar = (item: any | null) => {
<div class="img-title1"></div> <div class="img-title1"></div>
<!-- <div class="img-title2"></div> --> <!-- <div class="img-title2"></div> -->
<div class="img-qrcode-container"> <div class="img-qrcode-container">
<div class="img-qrcode"><img :src="imageUrls.qrcode" /></div> <div class="img-qrcode" @click="isQrcodeExpanded = true"><img :src="imageUrls.qrcode" /></div>
<div class="txt-qrcode">微信扫码参与</div> <div class="txt-qrcode">微信扫码参与</div>
</div> </div>
<div v-if="isQrcodeExpanded" class="qrcode-preview-mask" @click="isQrcodeExpanded = false">
<img class="qrcode-preview" :src="imageUrls.qrcode" @click.stop="isQrcodeExpanded = false" />
</div>
<div class="list-container"> <div class="list-container">
<div class="txt-count">当前在线人数 <span>{{ playerCount }}</span></div> <div class="txt-count">当前在线人数 <span>{{ playerCount }}</span></div>
...@@ -80,7 +83,7 @@ const renderAvatar = (item: any | null) => { ...@@ -80,7 +83,7 @@ const renderAvatar = (item: any | null) => {
<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, 8) }}</div> <div class="txt-nickname">{{ item.nickname }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -98,6 +101,9 @@ const renderAvatar = (item: any | null) => { ...@@ -98,6 +101,9 @@ const renderAvatar = (item: any | null) => {
</template> </template>
<style scoped> <style scoped>
.qrcode-preview-mask { position: absolute; inset: 0; z-index: 10; display: flex; justify-content: center; padding-top: 150px; background: rgb(0 0 0 / 45%); cursor: pointer; }
.qrcode-preview { width: 480px; height: 480px; padding: 18px; border-radius: 24px; background: #fff; }
.bg { .bg {
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
...@@ -191,6 +197,10 @@ const renderAvatar = (item: any | null) => { ...@@ -191,6 +197,10 @@ const renderAvatar = (item: any | null) => {
height: 250px; height: 250px;
align-content: center; align-content: center;
>div {
min-width: 0;
}
.img-avatar-container { .img-avatar-container {
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
...@@ -220,9 +230,15 @@ const renderAvatar = (item: any | null) => { ...@@ -220,9 +230,15 @@ const renderAvatar = (item: any | null) => {
} }
.txt-nickname { .txt-nickname {
display: block;
width: calc(100% - 12px);
margin: 4px auto 0;
overflow: hidden;
color: #666; color: #666;
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
} }
} }
......
...@@ -7,7 +7,7 @@ import Rank2 from './views/Rank2View.vue' ...@@ -7,7 +7,7 @@ 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 { playApplauseMusic, playGame6Music, stopAllMusic, stopGame6Music } from '@/commons/music' import { playApplauseMusic, playGame6Music, stopAllMusic, stopGame6Music } from '@/commons/music'
import { $confirm, $remove_socket_storage, $toast } from '@/commons/utils.ts' import { $confirm, $remove_socket_storage, $notify, $toast } from '@/commons/utils.ts'
const router = useRouter() const router = useRouter()
......
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import router from '@/router'; import router from '@/router';
import { $format_str } from '@/commons/utils.ts';
import DesignStage from '@/components/DesignStage.vue' import DesignStage from '@/components/DesignStage.vue'
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts'; import { assetUrl, cssAssetUrl } from '@/commons/assets.ts';
...@@ -44,6 +43,7 @@ const imageUrls = { ...@@ -44,6 +43,7 @@ const imageUrls = {
}; };
const isStartPressed = ref(false); const isStartPressed = ref(false);
const isQrcodeExpanded = ref(false);
const pressStartButton = () => { const pressStartButton = () => {
isStartPressed.value = true; isStartPressed.value = true;
...@@ -69,9 +69,12 @@ const renderAvatar = (item: any | null) => { ...@@ -69,9 +69,12 @@ const renderAvatar = (item: any | null) => {
<div class="img-title1"></div> <div class="img-title1"></div>
<!-- <div class="img-title2"></div> --> <!-- <div class="img-title2"></div> -->
<div class="img-qrcode-container"> <div class="img-qrcode-container">
<div class="img-qrcode"><img :src="imageUrls.qrcode" /></div> <div class="img-qrcode" @click="isQrcodeExpanded = true"><img :src="imageUrls.qrcode" /></div>
<div class="txt-qrcode">微信扫码参与</div> <div class="txt-qrcode">微信扫码参与</div>
</div> </div>
<div v-if="isQrcodeExpanded" class="qrcode-preview-mask" @click="isQrcodeExpanded = false">
<img class="qrcode-preview" :src="imageUrls.qrcode" @click.stop="isQrcodeExpanded = false" />
</div>
<div class="img-mole"></div> <div class="img-mole"></div>
<div class="img-rabbit"></div> <div class="img-rabbit"></div>
<div class="list-container"> <div class="list-container">
...@@ -82,7 +85,7 @@ const renderAvatar = (item: any | null) => { ...@@ -82,7 +85,7 @@ const renderAvatar = (item: any | null) => {
<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">{{ item.nickname }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -100,6 +103,9 @@ const renderAvatar = (item: any | null) => { ...@@ -100,6 +103,9 @@ const renderAvatar = (item: any | null) => {
</template> </template>
<style scoped> <style scoped>
.qrcode-preview-mask { position: absolute; inset: 0; z-index: 10; display: flex; justify-content: center; padding-top: 150px; background: rgb(0 0 0 / 45%); cursor: pointer; }
.qrcode-preview { width: 480px; height: 480px; padding: 18px; border-radius: 24px; background: #fff; }
.bg { .bg {
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
...@@ -216,6 +222,10 @@ const renderAvatar = (item: any | null) => { ...@@ -216,6 +222,10 @@ const renderAvatar = (item: any | null) => {
height: 250px; height: 250px;
align-content: center; align-content: center;
>div {
min-width: 0;
}
.img-avatar-container { .img-avatar-container {
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
...@@ -245,9 +255,15 @@ const renderAvatar = (item: any | null) => { ...@@ -245,9 +255,15 @@ const renderAvatar = (item: any | null) => {
} }
.txt-nickname { .txt-nickname {
display: block;
width: calc(100% - 12px);
margin: 4px auto 0;
overflow: hidden;
color: #666; color: #666;
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
} }
} }
......
...@@ -8,7 +8,7 @@ import { useAdminGameSocket } from "@/composables/useAdminGameSocket"; ...@@ -8,7 +8,7 @@ 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 { useRouter } from 'vue-router'
import { $confirm, $remove_socket_storage, $toast } from "@/commons/utils.ts"; import { $confirm, $remove_socket_storage, $notify, $toast } from "@/commons/utils.ts";
const router = useRouter() const router = useRouter()
......
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { $format_str } from "@/commons/utils.ts";
import DesignStage from "@/components/DesignStage.vue"; import DesignStage from "@/components/DesignStage.vue";
import { assetUrl, cssAssetUrl } from "@/commons/assets.ts"; import { assetUrl, cssAssetUrl } from "@/commons/assets.ts";
...@@ -34,6 +33,7 @@ const imageUrls = { ...@@ -34,6 +33,7 @@ const imageUrls = {
}; };
const isStartPressed = ref(false); const isStartPressed = ref(false);
const isQrcodeExpanded = ref(false);
const renderAvatar = (item: any | null) => { const renderAvatar = (item: any | null) => {
if (item && item.avatar) { if (item && item.avatar) {
...@@ -59,9 +59,12 @@ const releaseStartButton = () => { ...@@ -59,9 +59,12 @@ const releaseStartButton = () => {
<div class="img-title1"></div> <div class="img-title1"></div>
<div class="img-title2"></div> <div class="img-title2"></div>
<div class="img-qrcode-container"> <div class="img-qrcode-container">
<div class="img-qrcode"><img :src="imageUrls.qrcode" /></div> <div class="img-qrcode" @click="isQrcodeExpanded = true"><img :src="imageUrls.qrcode" /></div>
<div class="txt-qrcode">微信扫码参与</div> <div class="txt-qrcode">微信扫码参与</div>
</div> </div>
<div v-if="isQrcodeExpanded" class="qrcode-preview-mask" @click="isQrcodeExpanded = false">
<img class="qrcode-preview" :src="imageUrls.qrcode" @click.stop="isQrcodeExpanded = false" />
</div>
<div class="img-gift"></div> <div class="img-gift"></div>
<div class="img-small-gu"></div> <div class="img-small-gu"></div>
<div class="img-gu"></div> <div class="img-gu"></div>
...@@ -76,7 +79,7 @@ const releaseStartButton = () => { ...@@ -76,7 +79,7 @@ const releaseStartButton = () => {
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div> </div>
<div class="txt-nickname"> <div class="txt-nickname">
{{ $format_str(item.nickname, 8) }} {{ item.nickname }}
</div> </div>
</div> </div>
</div> </div>
...@@ -95,6 +98,9 @@ const releaseStartButton = () => { ...@@ -95,6 +98,9 @@ const releaseStartButton = () => {
</template> </template>
<style scoped> <style scoped>
.qrcode-preview-mask { position: absolute; inset: 0; z-index: 10; display: flex; justify-content: center; padding-top: 150px; background: rgb(0 0 0 / 45%); cursor: pointer; }
.qrcode-preview { width: 480px; height: 480px; padding: 18px; border-radius: 24px; background: #fff; }
.bg { .bg {
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
...@@ -218,6 +224,10 @@ const releaseStartButton = () => { ...@@ -218,6 +224,10 @@ const releaseStartButton = () => {
height: 250px; height: 250px;
align-content: center; align-content: center;
>div {
min-width: 0;
}
.img-avatar-container { .img-avatar-container {
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
...@@ -246,9 +256,15 @@ const releaseStartButton = () => { ...@@ -246,9 +256,15 @@ const releaseStartButton = () => {
} }
.txt-nickname { .txt-nickname {
color: #666; display: block;
font-size: 20px; width: calc(100% - 12px);
text-align: center; margin: 4px auto 0;
overflow: hidden;
color: #666;
font-size: 20px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
} }
} }
......
...@@ -22,6 +22,8 @@ const emit = defineEmits<{ ...@@ -22,6 +22,8 @@ const emit = defineEmits<{
const designStage = ref<InstanceType<typeof DesignStage> | null>(null); const designStage = ref<InstanceType<typeof DesignStage> | null>(null);
const imageUrls = { const imageUrls = {
bgTop: cssAssetUrl("game4/bg-top.webp"),
bgBottom: cssAssetUrl("game4/bg-bottom.webp"),
logo: cssAssetUrl("game1/logo.png"), logo: cssAssetUrl("game1/logo.png"),
titleBg: cssAssetUrl("game4/horse-title-bg.svg"), titleBg: cssAssetUrl("game4/horse-title-bg.svg"),
bigBrum: cssAssetUrl("game4/big-brum.svg"), bigBrum: cssAssetUrl("game4/big-brum.svg"),
...@@ -586,7 +588,7 @@ defineExpose({ ...@@ -586,7 +588,7 @@ defineExpose({
.bg-top-inner { .bg-top-inner {
width: 3840px; width: 3840px;
height: 1080px; height: 1080px;
background: url("@/assets/images/game4/bg-top.webp") 0 0 / 3840px 1080px background: v-bind("imageUrls.bgTop") 0 0 / 3840px 1080px
no-repeat; no-repeat;
flex-shrink: 0; flex-shrink: 0;
} }
...@@ -605,7 +607,7 @@ defineExpose({ ...@@ -605,7 +607,7 @@ defineExpose({
.bg-bottom-inner { .bg-bottom-inner {
width: 3840px; width: 3840px;
height: 577px; height: 577px;
background: url("@/assets/images/game4/bg-bottom.webp") 0 0 / 3840px 577px background: v-bind("imageUrls.bgBottom") 0 0 / 3840px 577px
no-repeat; no-repeat;
flex-shrink: 0; flex-shrink: 0;
} }
......
...@@ -7,7 +7,7 @@ import Rank2 from './views/Rank2View.vue' ...@@ -7,7 +7,7 @@ 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 { playApplauseMusic, playGame5Music, stopAllMusic, stopGame5Music } from '@/commons/music' import { playApplauseMusic, playGame5Music, stopAllMusic, stopGame5Music } from '@/commons/music'
import { $confirm, $remove_socket_storage, $toast } from '@/commons/utils.ts' import { $confirm, $remove_socket_storage, $notify, $toast } from '@/commons/utils.ts'
const router = useRouter() const router = useRouter()
......
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { $format_str } from '@/commons/utils.ts';
import DesignStage from '@/components/DesignStage.vue' import DesignStage from '@/components/DesignStage.vue'
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts'; import { assetUrl, cssAssetUrl } from '@/commons/assets.ts';
...@@ -33,6 +32,7 @@ const imageUrls = { ...@@ -33,6 +32,7 @@ const imageUrls = {
}; };
const isStartPressed = ref(false); const isStartPressed = ref(false);
const isQrcodeExpanded = ref(false);
const pressStartButton = () => { const pressStartButton = () => {
isStartPressed.value = true; isStartPressed.value = true;
...@@ -58,9 +58,12 @@ const renderAvatar = (item: any | null) => { ...@@ -58,9 +58,12 @@ const renderAvatar = (item: any | null) => {
<div class="img-title1"></div> <div class="img-title1"></div>
<!-- <div class="img-title2"></div> --> <!-- <div class="img-title2"></div> -->
<div class="img-qrcode-container"> <div class="img-qrcode-container">
<div class="img-qrcode"><img :src="imageUrls.qrcode" /></div> <div class="img-qrcode" @click="isQrcodeExpanded = true"><img :src="imageUrls.qrcode" /></div>
<div class="txt-qrcode">微信扫码参与</div> <div class="txt-qrcode">微信扫码参与</div>
</div> </div>
<div v-if="isQrcodeExpanded" class="qrcode-preview-mask" @click="isQrcodeExpanded = false">
<img class="qrcode-preview" :src="imageUrls.qrcode" @click.stop="isQrcodeExpanded = false" />
</div>
<div class="img-left"></div> <div class="img-left"></div>
<div class="img-right"></div> <div class="img-right"></div>
<div class="list-container"> <div class="list-container">
...@@ -71,7 +74,7 @@ const renderAvatar = (item: any | null) => { ...@@ -71,7 +74,7 @@ const renderAvatar = (item: any | null) => {
<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, 8) }}</div> <div class="txt-nickname">{{ item.nickname }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -94,6 +97,9 @@ const renderAvatar = (item: any | null) => { ...@@ -94,6 +97,9 @@ const renderAvatar = (item: any | null) => {
</template> </template>
<style scoped> <style scoped>
.qrcode-preview-mask { position: absolute; inset: 0; z-index: 10; display: flex; justify-content: center; padding-top: 150px; background: rgb(0 0 0 / 45%); cursor: pointer; }
.qrcode-preview { width: 480px; height: 480px; padding: 18px; border-radius: 24px; background: #fff; }
.bg { .bg {
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
...@@ -208,6 +214,10 @@ const renderAvatar = (item: any | null) => { ...@@ -208,6 +214,10 @@ const renderAvatar = (item: any | null) => {
height: 250px; height: 250px;
align-content: center; align-content: center;
>div {
min-width: 0;
}
.img-avatar-container { .img-avatar-container {
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
...@@ -237,9 +247,15 @@ const renderAvatar = (item: any | null) => { ...@@ -237,9 +247,15 @@ const renderAvatar = (item: any | null) => {
} }
.txt-nickname { .txt-nickname {
display: block;
width: calc(100% - 12px);
margin: 4px auto 0;
overflow: hidden;
color: #666; color: #666;
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
} }
} }
......
...@@ -21,6 +21,7 @@ const gameStarted = ref(false); ...@@ -21,6 +21,7 @@ const gameStarted = ref(false);
const showResult = ref(false); const showResult = ref(false);
const imageUrls = { const imageUrls = {
runway: cssAssetUrl("game5/paodao.webp"),
bg: cssAssetUrl("game5/bg.svg"), bg: cssAssetUrl("game5/bg.svg"),
logo: cssAssetUrl("game1/logo.png"), logo: cssAssetUrl("game1/logo.png"),
title: cssAssetUrl("game5/title3.webp"), title: cssAssetUrl("game5/title3.webp"),
...@@ -500,7 +501,7 @@ const rankIcon = (index: number) => { ...@@ -500,7 +501,7 @@ const rankIcon = (index: number) => {
.runway-inner { .runway-inner {
width: 3840px; width: 3840px;
height: 543px; height: 543px;
background: url("@/assets/images/game5/paodao.webp") 0 0 / 3840px 543px background: v-bind("imageUrls.runway") 0 0 / 3840px 543px
no-repeat; no-repeat;
flex-shrink: 0; flex-shrink: 0;
} }
......
...@@ -7,7 +7,7 @@ import Rank2 from './views/Rank2View.vue' ...@@ -7,7 +7,7 @@ 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 { playApplauseMusic, playGame6Music, stopAllMusic, stopGame6Music } from '@/commons/music' import { playApplauseMusic, playGame6Music, stopAllMusic, stopGame6Music } from '@/commons/music'
import { $confirm, $remove_socket_storage, $toast } from '@/commons/utils.ts' import { $confirm, $remove_socket_storage, $notify, $toast } from '@/commons/utils.ts'
const router = useRouter() const router = useRouter()
......
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import router from '@/router'; import router from '@/router';
import { $format_str } from '@/commons/utils.ts';
import DesignStage from '@/components/DesignStage.vue' import DesignStage from '@/components/DesignStage.vue'
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts'; import { assetUrl, cssAssetUrl } from '@/commons/assets.ts';
...@@ -44,6 +43,7 @@ const imageUrls = { ...@@ -44,6 +43,7 @@ const imageUrls = {
}; };
const isStartPressed = ref(false); const isStartPressed = ref(false);
const isQrcodeExpanded = ref(false);
const pressStartButton = () => { const pressStartButton = () => {
isStartPressed.value = true; isStartPressed.value = true;
...@@ -69,9 +69,12 @@ const renderAvatar = (item: any | null) => { ...@@ -69,9 +69,12 @@ const renderAvatar = (item: any | null) => {
<div class="img-title1"></div> <div class="img-title1"></div>
<!-- <div class="img-title2"></div> --> <!-- <div class="img-title2"></div> -->
<div class="img-qrcode-container"> <div class="img-qrcode-container">
<div class="img-qrcode"><img :src="imageUrls.qrcode" /></div> <div class="img-qrcode" @click="isQrcodeExpanded = true"><img :src="imageUrls.qrcode" /></div>
<div class="txt-qrcode">微信扫码参与</div> <div class="txt-qrcode">微信扫码参与</div>
</div> </div>
<div v-if="isQrcodeExpanded" class="qrcode-preview-mask" @click="isQrcodeExpanded = false">
<img class="qrcode-preview" :src="imageUrls.qrcode" @click.stop="isQrcodeExpanded = false" />
</div>
<div class="img-mole"></div> <div class="img-mole"></div>
<div class="img-rabbit"></div> <div class="img-rabbit"></div>
<div class="list-container"> <div class="list-container">
...@@ -82,7 +85,7 @@ const renderAvatar = (item: any | null) => { ...@@ -82,7 +85,7 @@ const renderAvatar = (item: any | null) => {
<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, 8) }}</div> <div class="txt-nickname">{{ item.nickname }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -100,6 +103,9 @@ const renderAvatar = (item: any | null) => { ...@@ -100,6 +103,9 @@ const renderAvatar = (item: any | null) => {
</template> </template>
<style scoped> <style scoped>
.qrcode-preview-mask { position: absolute; inset: 0; z-index: 10; display: flex; justify-content: center; padding-top: 150px; background: rgb(0 0 0 / 45%); cursor: pointer; }
.qrcode-preview { width: 480px; height: 480px; padding: 18px; border-radius: 24px; background: #fff; }
.bg { .bg {
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
...@@ -211,6 +217,10 @@ const renderAvatar = (item: any | null) => { ...@@ -211,6 +217,10 @@ const renderAvatar = (item: any | null) => {
height: 250px; height: 250px;
align-content: center; align-content: center;
>div {
min-width: 0;
}
.img-avatar-container { .img-avatar-container {
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
...@@ -240,9 +250,15 @@ const renderAvatar = (item: any | null) => { ...@@ -240,9 +250,15 @@ const renderAvatar = (item: any | null) => {
} }
.txt-nickname { .txt-nickname {
display: block;
width: calc(100% - 12px);
margin: 4px auto 0;
overflow: hidden;
color: #666; color: #666;
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
} }
} }
......
...@@ -2,7 +2,7 @@ import { fileURLToPath, URL } from 'node:url' ...@@ -2,7 +2,7 @@ import { fileURLToPath, URL } from 'node:url'
import { cpSync, existsSync, rmSync } from 'node:fs' import { cpSync, existsSync, rmSync } from 'node:fs'
import { resolve } from 'node:path' import { resolve } from 'node:path'
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools' import vueDevTools from 'vite-plugin-vue-devtools'
...@@ -25,34 +25,39 @@ function copyImageAssets() { ...@@ -25,34 +25,39 @@ function copyImageAssets() {
} }
} }
export default defineConfig(({ command }) => ({ export default defineConfig(({ command, mode }) => {
base: '/',//command === 'build' ? '/cc/pc/' : '/', const env = loadEnv(mode, process.cwd(), '')
define: { const useLocalAssets = env.VITE_ASSET_BUILD_LOCAL === 'true'
__APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
// __APP_DOMAIN__: JSON.stringify(buildDomain), return {
}, base: '/',//command === 'build' ? '/cc/pc/' : '/',
plugins: [ define: {
vue(), __APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
vueDevTools(), // __APP_DOMAIN__: JSON.stringify(buildDomain),
copyImageAssets(), },
], plugins: [
server: { vue(),
host: true, vueDevTools(),
proxy: { ...(useLocalAssets || !env.VITE_ASSET_BASE_URL ? [copyImageAssets()] : []),
'/socket.io': { ],
target: 'http://localhost:8082', server: {
changeOrigin: true, host: true,
ws: true, proxy: {
}, '/socket.io': {
'/manager': { target: 'http://localhost:8082',
target: 'http://localhost:8082', changeOrigin: true,
changeOrigin: true, ws: true,
},
'/manager': {
target: 'http://localhost:8082',
changeOrigin: true,
},
}, },
}, },
}, resolve: {
resolve: { alias: {
alias: { '@': fileURLToPath(new URL('./src', import.meta.url)),
'@': fileURLToPath(new URL('./src', import.meta.url)), },
}, },
}, }
})) })
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