refactor(game): 重构缺门花色处理逻辑并优化组件结构

- 移除硬编码的花色图标导入,改用动态加载方式
- 添加新的 flowerColorMap 配置文件统一管理缺门图标
- 引入 clearActiveRoom 函数用于清理活动房间状态
- 在游戏数据解析中添加缺失花色的读取函数
- 当房间数据为空时自动清理房间状态并跳转回大厅
- 统一玩家缺门花色数据处理逻辑
- 注释掉浮动状态显示区域以优化界面布局
- 调整CSS样式中缺门标记尺寸和旋转效果
- 在游戏存储模块中添加清除快照功能
- 重构座位玩家卡片组件中的花色图标计算逻辑
- 优化花色标签映射和归一化处理函数
This commit is contained in:
2026-03-28 09:59:44 +08:00
parent d60a505226
commit 4f7a54cf08
8 changed files with 136 additions and 56 deletions

View File

@@ -481,10 +481,14 @@
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.18);
}
.picture-scene .player-badge.seat-right .dealer-mark {
transform: rotate(-90deg);
}
.picture-scene .missing-mark {
margin-left: auto;
width: 34px;
height: 34px;
width: 42px;
height: 42px;
padding: 0;
overflow: hidden;
background: linear-gradient(180deg, rgba(114, 219, 149, 0.2) 0%, rgba(21, 148, 88, 0.34) 100%);
@@ -492,11 +496,19 @@
}
.picture-scene .missing-mark img {
width: 22px;
height: 22px;
width: 42px;
height: 42px;
object-fit: contain;
}
.picture-scene .player-badge.seat-right .missing-mark {
transform: rotate(-90deg);
}
.picture-scene .player-badge.seat-left .missing-mark {
transform: rotate(-90deg);
}
.picture-scene .missing-mark span {
color: #effff5;
}

View File

@@ -1,9 +1,8 @@
<script setup lang="ts">
import { computed } from 'vue'
import wanIcon from '../../assets/images/flowerClolor/wan.png'
import tongIcon from '../../assets/images/flowerClolor/tong.png'
import tiaoIcon from '../../assets/images/flowerClolor/tiao.png'
import defaultAvatarIcon from '../../assets/images/icons/avatar.svg'
import { getLackSuitImage } from '../../config/flowerColorMap'
import type { Suit } from '../../types/tile'
import type { SeatPlayerCardModel } from './seat-player-card'
const props = defineProps<{
@@ -11,17 +10,26 @@ const props = defineProps<{
player: SeatPlayerCardModel
}>()
function normalizeMissingSuit(value: string): Suit | null {
const normalized = value.trim().toLowerCase()
const missingSuitMap: Record<string, Suit> = {
: 'W',
: 'T',
: 'B',
w: 'W',
t: 'T',
b: 'B',
wan: 'W',
tong: 'T',
tiao: 'B',
}
return missingSuitMap[normalized] ?? null
}
const missingSuitIcon = computed(() => {
if (props.player.missingSuitLabel === '万') {
return wanIcon
}
if (props.player.missingSuitLabel === '筒') {
return tongIcon
}
if (props.player.missingSuitLabel === '条') {
return tiaoIcon
}
return ''
const suit = normalizeMissingSuit(props.player.missingSuitLabel)
return suit ? getLackSuitImage(suit) : ''
})
const resolvedAvatarUrl = computed(() => {

View File

@@ -0,0 +1,41 @@
// src/config/lackSuitMap.ts
import type {Suit} from "../types/tile.ts";
const lackSuitImageModules = import.meta.glob(
'/src/assets/images/flowerClolor/*.png',
{
eager: true,
import: 'default',
},
) as Record<string, string>
const SUIT_FILE_MAP: Record<Suit, 'wan' | 'tong' | 'tiao'> = {
W: 'wan',
T: 'tong',
B: 'tiao',
}
function buildLackSuitImageKey(suit: Suit): string {
const fileName = SUIT_FILE_MAP[suit]
return `/src/assets/images/flowerClolor/${fileName}.png`
}
/**
* 根据花色获取缺门图标
* W -> wan.png
* T -> tong.png
* B -> tiao.png
*/
export function getLackSuitImage(suit: Suit): string {
const key = buildLackSuitImageKey(suit)
return lackSuitImageModules[key] || ''
}
/**
* 判断是否为合法缺门花色
*/
export function isValidLackSuit(suit: string): suit is Suit {
return suit === 'W' || suit === 'T' || suit === 'B'
}

View File

@@ -29,6 +29,10 @@ export const useGameStore = defineStore('game', {
}),
actions: {
resetGame() {
this.$reset()
},
// 初始化
initGame(data: GameState) {
Object.assign(this, data)

View File

@@ -3,7 +3,7 @@ import type {
ActiveRoomState,
ActiveRoomSelectionInput,
} from './state'
import { readActiveRoomSnapshot, saveActiveRoom } from './storage'
import { clearActiveRoomSnapshot, readActiveRoomSnapshot, saveActiveRoom } from './storage'
const activeRoom = ref<ActiveRoomState | null>(readActiveRoomSnapshot())
@@ -39,6 +39,11 @@ export function setActiveRoom(input: ActiveRoomSelectionInput) {
saveActiveRoom(next)
}
export function clearActiveRoom() {
activeRoom.value = null
clearActiveRoomSnapshot()
}
// 使用房间状态
export function useActiveRoomState() {
return activeRoom

View File

@@ -18,3 +18,8 @@ export function readActiveRoomSnapshot(): ActiveRoomState | null {
export function saveActiveRoom(state: ActiveRoomState) {
localStorage.setItem(KEY, JSON.stringify(state))
}
// 清除缓存
export function clearActiveRoomSnapshot() {
localStorage.removeItem(KEY)
}

View File

@@ -2,9 +2,6 @@
import {computed, onBeforeUnmount, onMounted, ref} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import deskImage from '../assets/images/desk/desk_01.png'
import wanIcon from '../assets/images/flowerClolor/wan.png'
import tongIcon from '../assets/images/flowerClolor/tong.png'
import tiaoIcon from '../assets/images/flowerClolor/tiao.png'
import robotIcon from '../assets/images/icons/robot.svg'
import exitIcon from '../assets/images/icons/exit.svg'
import '../assets/styles/room.css'
@@ -30,7 +27,7 @@ import {wsClient} from '../ws/client'
import {sendWsMessage} from '../ws/sender'
import {buildWsUrl} from '../ws/url'
import {useGameStore} from '../store/gameStore'
import {setActiveRoom, useActiveRoomState} from '../store'
import {clearActiveRoom, setActiveRoom, useActiveRoomState} from '../store'
import type {MeldState, PlayerState} from '../types/state'
import type {Tile} from '../types/tile'
import {getTileImage as getBottomTileImage} from '../config/bottomTileMap.ts'
@@ -449,6 +446,14 @@ function readBoolean(source: Record<string, unknown>, ...keys: string[]): boolea
return null
}
function readMissingSuit(source: Record<string, unknown> | null | undefined): string | null {
if (!source) {
return null
}
return readString(source, 'missing_suit', 'MissingSuit', 'ding_que', 'dingQue', 'suit', 'Suit') || null
}
function tileToText(tile: Tile): string {
return `${tile.suit}${tile.value}`
}
@@ -601,6 +606,15 @@ function handleRoomInfoResponse(message: unknown): void {
const room = asRecord(payload.room)
const gameState = asRecord(payload.game_state)
const playerView = asRecord(payload.player_view)
if (!room && !gameState && !playerView) {
clearActiveRoom()
gameStore.resetGame()
wsClient.close()
void router.push('/hall')
return
}
const roomId =
readString(room ?? {}, 'room_id', 'roomId') ||
readString(gameState ?? {}, 'room_id', 'roomId') ||
@@ -657,7 +671,7 @@ function handleRoomInfoResponse(message: unknown): void {
readString(player, 'player_name', 'PlayerName', 'display_name', 'displayName', 'nickname', 'username') ||
(playerId === loggedInUserId.value ? loggedInUserName.value : '')
const ready = readBoolean(player, 'ready', 'Ready') ?? false
const missingSuit = readString(player, 'missing_suit', 'MissingSuit') || null
const missingSuit = readMissingSuit(player)
playerMap.set(playerId, {
roomPlayer: {
@@ -705,7 +719,7 @@ function handleRoomInfoResponse(message: unknown): void {
readNumber(player, 'index', 'Index', 'seat_index', 'seatIndex') ??
fallbackIndex
const displayName = existing?.gamePlayer.displayName || (playerId === loggedInUserId.value ? loggedInUserName.value : '')
const missingSuit = readString(player, 'missing_suit', 'MissingSuit') || existing?.gamePlayer.missingSuit || null
const missingSuit = readMissingSuit(player) || existing?.gamePlayer.missingSuit || null
const handCount = readNumber(player, 'hand_count', 'handCount') ?? 0
const outTiles = normalizeTiles(player.out_tiles ?? player.outTiles)
const melds = normalizeMelds(
@@ -749,9 +763,12 @@ function handleRoomInfoResponse(message: unknown): void {
if (loggedInUserId.value && playerMap.has(loggedInUserId.value)) {
const current = playerMap.get(loggedInUserId.value)
if (current) {
const selfMissingSuit = readMissingSuit(playerView)
current.roomPlayer.hand = privateHand.map((tile) => tileToText(tile))
current.roomPlayer.missingSuit = selfMissingSuit || current.roomPlayer.missingSuit
current.gamePlayer.handTiles = privateHand
current.gamePlayer.handCount = privateHand.length
current.gamePlayer.missingSuit = selfMissingSuit || current.gamePlayer.missingSuit
}
}
@@ -1030,26 +1047,11 @@ const seatDecor = computed<Record<SeatKey, SeatPlayerCardModel>>(() => {
return result
})
const floatingMissingSuit = computed(() => {
const suitMap: Record<string, string> = {
: wanIcon,
: tongIcon,
: tiaoIcon,
}
const topLabel = seatDecor.value.top?.missingSuitLabel ?? ''
const leftLabel = seatDecor.value.left?.missingSuitLabel ?? ''
const rightLabel = seatDecor.value.right?.missingSuitLabel ?? ''
return {
top: suitMap[topLabel] ?? '',
left: suitMap[leftLabel] ?? '',
right: suitMap[rightLabel] ?? '',
}
})
function missingSuitLabel(value: string | null | undefined): string {
const suitMap: Record<string, string> = {
w: '万',
t: '筒',
b: '条',
wan: '万',
tong: '筒',
tiao: '条',
@@ -1058,7 +1060,9 @@ function missingSuitLabel(value: string | null | undefined): string {
if (!value) {
return ''
}
return suitMap[value] ?? value
const normalized = value.trim().toLowerCase()
return suitMap[normalized] ?? value
}
function toggleMenu(): void {
@@ -1825,18 +1829,18 @@ onBeforeUnmount(() => {
<span v-if="wallSeats.left.hasHu" class="wall-hu-flag"></span>
</div>
<div class="floating-status top">
<img v-if="floatingMissingSuit.top" :src="floatingMissingSuit.top" alt=""/>
<span>{{ seatDecor.top.missingSuitLabel }}</span>
</div>
<div class="floating-status left">
<img v-if="floatingMissingSuit.left" :src="floatingMissingSuit.left" alt=""/>
<span>{{ seatDecor.left.missingSuitLabel }}</span>
</div>
<div class="floating-status right">
<img v-if="floatingMissingSuit.right" :src="floatingMissingSuit.right" alt=""/>
<span>{{ seatDecor.right.missingSuitLabel }}</span>
</div>
<!-- <div class="floating-status top">-->
<!-- <img v-if="floatingMissingSuit.top" :src="floatingMissingSuit.top" alt=""/>-->
<!-- <span>{{ seatDecor.top.missingSuitLabel }}</span>-->
<!-- </div>-->
<!-- <div class="floating-status left">-->
<!-- <img v-if="floatingMissingSuit.left" :src="floatingMissingSuit.left" alt=""/>-->
<!-- <span>{{ seatDecor.left.missingSuitLabel }}</span>-->
<!-- </div>-->
<!-- <div class="floating-status right">-->
<!-- <img v-if="floatingMissingSuit.right" :src="floatingMissingSuit.right" alt=""/>-->
<!-- <span>{{ seatDecor.right.missingSuitLabel }}</span>-->
<!-- </div>-->
<WindSquare class="center-wind-square" :seat-winds="seatWinds"/>

View File

@@ -16,6 +16,7 @@ export function registerHandler(type: string, handler: Handler) {
// 初始化监听
export function initWsHandler() {
wsClient.onMessage((msg) => {
console.log('[WS] 收到消息:', msg)
const handlers = handlerMap[msg.type]
if (handlers && handlers.length > 0) {