feat(game): 添加成都麻将游戏页面和大厅功能

- 实现 ChengduGamePage.vue 组件,包含完整的麻将游戏界面
- 实现 HallPage.vue 组件,支持房间列表展示、创建和加入功能
- 添加 mahjong API 接口用于房间管理操作
- 集成 store 状态管理和本地存储功能
- 实现 ChengduBottomActions 等游戏控制组件
- 添加 websocket 连接和游戏会话管理逻辑
- 实现游戏倒计时、结算等功能模块
This commit is contained in:
2026-04-03 20:46:50 +08:00
parent 39d85f2998
commit e96c45739e
32 changed files with 4006 additions and 3287 deletions

View File

@@ -0,0 +1,112 @@
<script setup lang="ts">
import type { ActionCountdownView } from '../../views/chengdu/types'
defineProps<{
leaveRoomPending: boolean
menuOpen: boolean
menuTriggerActive: boolean
isTrustMode: boolean
wallCount: number
networkLabel: string
wsStatus: string
formattedClock: string
roomName: string
currentPhaseText: string
playerCount: number
maxPlayers: number
roundText: string
roomStatusText: string
wsError: string
actionCountdown: ActionCountdownView | null
}>()
const emit = defineEmits<{
toggleMenu: []
toggleTrustMode: []
leaveRoom: []
}>()
</script>
<template>
<div class="top-left-tools">
<div class="menu-trigger-wrap">
<button
class="metal-circle menu-trigger"
:class="{ 'is-feedback': menuTriggerActive }"
type="button"
:disabled="leaveRoomPending"
@click.stop="emit('toggleMenu')"
>
<span class="menu-trigger-icon"></span>
</button>
<transition name="menu-pop">
<div v-if="menuOpen" class="menu-popover" @click.stop>
<div class="menu-list">
<button class="menu-item menu-item-delay-1" type="button" @click="emit('toggleTrustMode')">
<slot name="robot-icon" />
<span>{{ isTrustMode ? '取消托管' : '托管' }}</span>
</button>
<button
class="menu-item menu-item-danger menu-item-delay-2"
type="button"
:disabled="leaveRoomPending"
@click="emit('leaveRoom')"
>
<slot name="exit-icon" />
<span>{{ leaveRoomPending ? '退出中...' : '退出' }}</span>
</button>
</div>
</div>
</transition>
</div>
<div class="left-counter">
<span class="counter-light"></span>
<strong>{{ wallCount }}</strong>
</div>
<span v-if="isTrustMode" class="trust-chip">托管中</span>
</div>
<div class="top-right-clock">
<div class="signal-chip">
<span class="wifi-dot" :class="`is-${wsStatus}`"></span>
<strong>{{ networkLabel }}</strong>
</div>
<span>{{ formattedClock }}</span>
</div>
<div class="room-status-panel">
<div class="room-status-grid">
<div class="room-status-item">
<span>房间</span>
<strong>{{ roomName || '未命名' }}</strong>
</div>
<div class="room-status-item">
<span>阶段</span>
<strong>{{ currentPhaseText }}</strong>
</div>
<div class="room-status-item">
<span>人数</span>
<strong>{{ playerCount }}/{{ maxPlayers }}</strong>
</div>
<div v-if="roundText" class="room-status-item">
<span>局数</span>
<strong>{{ roundText }}</strong>
</div>
<div class="room-status-item">
<span>状态</span>
<strong>{{ roomStatusText }}</strong>
</div>
</div>
<p v-if="wsError" class="room-status-error">{{ wsError }}</p>
</div>
<div v-if="actionCountdown" class="action-countdown" :class="{ 'is-self': actionCountdown.isSelf }">
<div class="action-countdown-head">
<span>{{ actionCountdown.playerLabel }}操作倒计时</span>
<strong>{{ actionCountdown.remaining }}s</strong>
</div>
<div class="action-countdown-track">
<span class="action-countdown-fill" :style="{ width: `${actionCountdown.progress}%` }"></span>
</div>
</div>
</template>