refactor(game): 重构缺门花色处理逻辑并优化组件结构
- 移除硬编码的花色图标导入,改用动态加载方式 - 添加新的 flowerColorMap 配置文件统一管理缺门图标 - 引入 clearActiveRoom 函数用于清理活动房间状态 - 在游戏数据解析中添加缺失花色的读取函数 - 当房间数据为空时自动清理房间状态并跳转回大厅 - 统一玩家缺门花色数据处理逻辑 - 注释掉浮动状态显示区域以优化界面布局 - 调整CSS样式中缺门标记尺寸和旋转效果 - 在游戏存储模块中添加清除快照功能 - 重构座位玩家卡片组件中的花色图标计算逻辑 - 优化花色标签映射和归一化处理函数
This commit is contained in:
@@ -481,10 +481,14 @@
|
|||||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.18);
|
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 {
|
.picture-scene .missing-mark {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
width: 34px;
|
width: 42px;
|
||||||
height: 34px;
|
height: 42px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: linear-gradient(180deg, rgba(114, 219, 149, 0.2) 0%, rgba(21, 148, 88, 0.34) 100%);
|
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 {
|
.picture-scene .missing-mark img {
|
||||||
width: 22px;
|
width: 42px;
|
||||||
height: 22px;
|
height: 42px;
|
||||||
object-fit: contain;
|
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 {
|
.picture-scene .missing-mark span {
|
||||||
color: #effff5;
|
color: #effff5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
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 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'
|
import type { SeatPlayerCardModel } from './seat-player-card'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -11,17 +10,26 @@ const props = defineProps<{
|
|||||||
player: SeatPlayerCardModel
|
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(() => {
|
const missingSuitIcon = computed(() => {
|
||||||
if (props.player.missingSuitLabel === '万') {
|
const suit = normalizeMissingSuit(props.player.missingSuitLabel)
|
||||||
return wanIcon
|
return suit ? getLackSuitImage(suit) : ''
|
||||||
}
|
|
||||||
if (props.player.missingSuitLabel === '筒') {
|
|
||||||
return tongIcon
|
|
||||||
}
|
|
||||||
if (props.player.missingSuitLabel === '条') {
|
|
||||||
return tiaoIcon
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const resolvedAvatarUrl = computed(() => {
|
const resolvedAvatarUrl = computed(() => {
|
||||||
|
|||||||
41
src/config/flowerColorMap.ts
Normal file
41
src/config/flowerColorMap.ts
Normal 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'
|
||||||
|
}
|
||||||
@@ -29,6 +29,10 @@ export const useGameStore = defineStore('game', {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
resetGame() {
|
||||||
|
this.$reset()
|
||||||
|
},
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
initGame(data: GameState) {
|
initGame(data: GameState) {
|
||||||
Object.assign(this, data)
|
Object.assign(this, data)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type {
|
|||||||
ActiveRoomState,
|
ActiveRoomState,
|
||||||
ActiveRoomSelectionInput,
|
ActiveRoomSelectionInput,
|
||||||
} from './state'
|
} from './state'
|
||||||
import { readActiveRoomSnapshot, saveActiveRoom } from './storage'
|
import { clearActiveRoomSnapshot, readActiveRoomSnapshot, saveActiveRoom } from './storage'
|
||||||
|
|
||||||
const activeRoom = ref<ActiveRoomState | null>(readActiveRoomSnapshot())
|
const activeRoom = ref<ActiveRoomState | null>(readActiveRoomSnapshot())
|
||||||
|
|
||||||
@@ -39,6 +39,11 @@ export function setActiveRoom(input: ActiveRoomSelectionInput) {
|
|||||||
saveActiveRoom(next)
|
saveActiveRoom(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clearActiveRoom() {
|
||||||
|
activeRoom.value = null
|
||||||
|
clearActiveRoomSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
// 使用房间状态
|
// 使用房间状态
|
||||||
export function useActiveRoomState() {
|
export function useActiveRoomState() {
|
||||||
return activeRoom
|
return activeRoom
|
||||||
|
|||||||
@@ -18,3 +18,8 @@ export function readActiveRoomSnapshot(): ActiveRoomState | null {
|
|||||||
export function saveActiveRoom(state: ActiveRoomState) {
|
export function saveActiveRoom(state: ActiveRoomState) {
|
||||||
localStorage.setItem(KEY, JSON.stringify(state))
|
localStorage.setItem(KEY, JSON.stringify(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
export function clearActiveRoomSnapshot() {
|
||||||
|
localStorage.removeItem(KEY)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
import {computed, onBeforeUnmount, onMounted, ref} from 'vue'
|
import {computed, onBeforeUnmount, onMounted, ref} from 'vue'
|
||||||
import {useRoute, useRouter} from 'vue-router'
|
import {useRoute, useRouter} from 'vue-router'
|
||||||
import deskImage from '../assets/images/desk/desk_01.png'
|
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 robotIcon from '../assets/images/icons/robot.svg'
|
||||||
import exitIcon from '../assets/images/icons/exit.svg'
|
import exitIcon from '../assets/images/icons/exit.svg'
|
||||||
import '../assets/styles/room.css'
|
import '../assets/styles/room.css'
|
||||||
@@ -30,7 +27,7 @@ import {wsClient} from '../ws/client'
|
|||||||
import {sendWsMessage} from '../ws/sender'
|
import {sendWsMessage} from '../ws/sender'
|
||||||
import {buildWsUrl} from '../ws/url'
|
import {buildWsUrl} from '../ws/url'
|
||||||
import {useGameStore} from '../store/gameStore'
|
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 {MeldState, PlayerState} from '../types/state'
|
||||||
import type {Tile} from '../types/tile'
|
import type {Tile} from '../types/tile'
|
||||||
import {getTileImage as getBottomTileImage} from '../config/bottomTileMap.ts'
|
import {getTileImage as getBottomTileImage} from '../config/bottomTileMap.ts'
|
||||||
@@ -449,6 +446,14 @@ function readBoolean(source: Record<string, unknown>, ...keys: string[]): boolea
|
|||||||
return null
|
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 {
|
function tileToText(tile: Tile): string {
|
||||||
return `${tile.suit}${tile.value}`
|
return `${tile.suit}${tile.value}`
|
||||||
}
|
}
|
||||||
@@ -601,6 +606,15 @@ function handleRoomInfoResponse(message: unknown): void {
|
|||||||
const room = asRecord(payload.room)
|
const room = asRecord(payload.room)
|
||||||
const gameState = asRecord(payload.game_state)
|
const gameState = asRecord(payload.game_state)
|
||||||
const playerView = asRecord(payload.player_view)
|
const playerView = asRecord(payload.player_view)
|
||||||
|
|
||||||
|
if (!room && !gameState && !playerView) {
|
||||||
|
clearActiveRoom()
|
||||||
|
gameStore.resetGame()
|
||||||
|
wsClient.close()
|
||||||
|
void router.push('/hall')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const roomId =
|
const roomId =
|
||||||
readString(room ?? {}, 'room_id', 'roomId') ||
|
readString(room ?? {}, 'room_id', 'roomId') ||
|
||||||
readString(gameState ?? {}, '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') ||
|
readString(player, 'player_name', 'PlayerName', 'display_name', 'displayName', 'nickname', 'username') ||
|
||||||
(playerId === loggedInUserId.value ? loggedInUserName.value : '')
|
(playerId === loggedInUserId.value ? loggedInUserName.value : '')
|
||||||
const ready = readBoolean(player, 'ready', 'Ready') ?? false
|
const ready = readBoolean(player, 'ready', 'Ready') ?? false
|
||||||
const missingSuit = readString(player, 'missing_suit', 'MissingSuit') || null
|
const missingSuit = readMissingSuit(player)
|
||||||
|
|
||||||
playerMap.set(playerId, {
|
playerMap.set(playerId, {
|
||||||
roomPlayer: {
|
roomPlayer: {
|
||||||
@@ -705,7 +719,7 @@ function handleRoomInfoResponse(message: unknown): void {
|
|||||||
readNumber(player, 'index', 'Index', 'seat_index', 'seatIndex') ??
|
readNumber(player, 'index', 'Index', 'seat_index', 'seatIndex') ??
|
||||||
fallbackIndex
|
fallbackIndex
|
||||||
const displayName = existing?.gamePlayer.displayName || (playerId === loggedInUserId.value ? loggedInUserName.value : '')
|
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 handCount = readNumber(player, 'hand_count', 'handCount') ?? 0
|
||||||
const outTiles = normalizeTiles(player.out_tiles ?? player.outTiles)
|
const outTiles = normalizeTiles(player.out_tiles ?? player.outTiles)
|
||||||
const melds = normalizeMelds(
|
const melds = normalizeMelds(
|
||||||
@@ -749,9 +763,12 @@ function handleRoomInfoResponse(message: unknown): void {
|
|||||||
if (loggedInUserId.value && playerMap.has(loggedInUserId.value)) {
|
if (loggedInUserId.value && playerMap.has(loggedInUserId.value)) {
|
||||||
const current = playerMap.get(loggedInUserId.value)
|
const current = playerMap.get(loggedInUserId.value)
|
||||||
if (current) {
|
if (current) {
|
||||||
|
const selfMissingSuit = readMissingSuit(playerView)
|
||||||
current.roomPlayer.hand = privateHand.map((tile) => tileToText(tile))
|
current.roomPlayer.hand = privateHand.map((tile) => tileToText(tile))
|
||||||
|
current.roomPlayer.missingSuit = selfMissingSuit || current.roomPlayer.missingSuit
|
||||||
current.gamePlayer.handTiles = privateHand
|
current.gamePlayer.handTiles = privateHand
|
||||||
current.gamePlayer.handCount = privateHand.length
|
current.gamePlayer.handCount = privateHand.length
|
||||||
|
current.gamePlayer.missingSuit = selfMissingSuit || current.gamePlayer.missingSuit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1030,26 +1047,11 @@ const seatDecor = computed<Record<SeatKey, SeatPlayerCardModel>>(() => {
|
|||||||
return result
|
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 {
|
function missingSuitLabel(value: string | null | undefined): string {
|
||||||
const suitMap: Record<string, string> = {
|
const suitMap: Record<string, string> = {
|
||||||
|
w: '万',
|
||||||
|
t: '筒',
|
||||||
|
b: '条',
|
||||||
wan: '万',
|
wan: '万',
|
||||||
tong: '筒',
|
tong: '筒',
|
||||||
tiao: '条',
|
tiao: '条',
|
||||||
@@ -1058,7 +1060,9 @@ function missingSuitLabel(value: string | null | undefined): string {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return suitMap[value] ?? value
|
|
||||||
|
const normalized = value.trim().toLowerCase()
|
||||||
|
return suitMap[normalized] ?? value
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMenu(): void {
|
function toggleMenu(): void {
|
||||||
@@ -1825,18 +1829,18 @@ onBeforeUnmount(() => {
|
|||||||
<span v-if="wallSeats.left.hasHu" class="wall-hu-flag">胡</span>
|
<span v-if="wallSeats.left.hasHu" class="wall-hu-flag">胡</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="floating-status top">
|
<!-- <div class="floating-status top">-->
|
||||||
<img v-if="floatingMissingSuit.top" :src="floatingMissingSuit.top" alt=""/>
|
<!-- <img v-if="floatingMissingSuit.top" :src="floatingMissingSuit.top" alt=""/>-->
|
||||||
<span>{{ seatDecor.top.missingSuitLabel }}</span>
|
<!-- <span>{{ seatDecor.top.missingSuitLabel }}</span>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<div class="floating-status left">
|
<!-- <div class="floating-status left">-->
|
||||||
<img v-if="floatingMissingSuit.left" :src="floatingMissingSuit.left" alt=""/>
|
<!-- <img v-if="floatingMissingSuit.left" :src="floatingMissingSuit.left" alt=""/>-->
|
||||||
<span>{{ seatDecor.left.missingSuitLabel }}</span>
|
<!-- <span>{{ seatDecor.left.missingSuitLabel }}</span>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<div class="floating-status right">
|
<!-- <div class="floating-status right">-->
|
||||||
<img v-if="floatingMissingSuit.right" :src="floatingMissingSuit.right" alt=""/>
|
<!-- <img v-if="floatingMissingSuit.right" :src="floatingMissingSuit.right" alt=""/>-->
|
||||||
<span>{{ seatDecor.right.missingSuitLabel }}</span>
|
<!-- <span>{{ seatDecor.right.missingSuitLabel }}</span>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
|
|
||||||
<WindSquare class="center-wind-square" :seat-winds="seatWinds"/>
|
<WindSquare class="center-wind-square" :seat-winds="seatWinds"/>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export function registerHandler(type: string, handler: Handler) {
|
|||||||
// 初始化监听
|
// 初始化监听
|
||||||
export function initWsHandler() {
|
export function initWsHandler() {
|
||||||
wsClient.onMessage((msg) => {
|
wsClient.onMessage((msg) => {
|
||||||
|
console.log('[WS] 收到消息:', msg)
|
||||||
const handlers = handlerMap[msg.type]
|
const handlers = handlerMap[msg.type]
|
||||||
|
|
||||||
if (handlers && handlers.length > 0) {
|
if (handlers && handlers.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user