From f97f1ffdbcec6f4377668feec32749835ace5c79 Mon Sep 17 00:00:00 2001 From: wsy182 <2392948297@qq.com> Date: Tue, 24 Mar 2026 14:02:21 +0800 Subject: [PATCH] ``` feat(game): add player cards and topbar styling for Chengdu Mahjong game - Add new CSS classes for topbar layout including .topbar-left, .topbar-back-btn, .topbar-room-meta, .eyebrow, and .topbar-room-name - Create dedicated player card components for each seat position (top, right, bottom, left) - Refactor seatDecor computed property to use SeatPlayerCardModel interface with proper typing - Replace inline player badge rendering with reusable player card components - Update game header layout to use new topbar structure with back button and room metadata - Adjust spacing and font sizes in game header elements ``` --- src/assets/styles/global.css | 41 ++++++++- src/components/game/BottomPlayerCard.vue | 12 +++ src/components/game/LeftPlayerCard.vue | 12 +++ src/components/game/RightPlayerCard.vue | 12 +++ src/components/game/SeatPlayerCard.vue | 23 +++++ src/components/game/TopPlayerCard.vue | 12 +++ src/components/game/seat-player-card.ts | 9 ++ src/views/ChengduGamePage.vue | 103 +++++++++++++++-------- 8 files changed, 189 insertions(+), 35 deletions(-) create mode 100644 src/components/game/BottomPlayerCard.vue create mode 100644 src/components/game/LeftPlayerCard.vue create mode 100644 src/components/game/RightPlayerCard.vue create mode 100644 src/components/game/SeatPlayerCard.vue create mode 100644 src/components/game/TopPlayerCard.vue create mode 100644 src/components/game/seat-player-card.ts diff --git a/src/assets/styles/global.css b/src/assets/styles/global.css index a629333..1d22c72 100644 --- a/src/assets/styles/global.css +++ b/src/assets/styles/global.css @@ -459,6 +459,39 @@ button:disabled { min-width: 0; } +.topbar-left { + display: flex; + align-items: center; + gap: 14px; + min-width: 0; +} + +.topbar-back-btn { + flex: 0 0 auto; + min-width: 108px; +} + +.topbar-room-meta { + min-width: 0; +} + +.eyebrow { + color: #f7e4b0; + font-size: 12px; + letter-spacing: 2px; + text-transform: uppercase; +} + +.topbar-room-name { + margin-top: 4px; + color: #f6edd5; + font-size: 20px; + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .game-header h1 { font-size: 28px; font-weight: 800; @@ -467,9 +500,9 @@ button:disabled { } .game-header .sub-title { - margin-top: 6px; + margin-top: 4px; color: #d7eadf; - font-size: 13px; + font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -1195,6 +1228,10 @@ button:disabled { justify-items: stretch; } + .topbar-left { + flex-wrap: wrap; + } + .topbar-center, .topbar-right { justify-content: flex-start; diff --git a/src/components/game/BottomPlayerCard.vue b/src/components/game/BottomPlayerCard.vue new file mode 100644 index 0000000..a5b6361 --- /dev/null +++ b/src/components/game/BottomPlayerCard.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/game/LeftPlayerCard.vue b/src/components/game/LeftPlayerCard.vue new file mode 100644 index 0000000..c8c1fb5 --- /dev/null +++ b/src/components/game/LeftPlayerCard.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/game/RightPlayerCard.vue b/src/components/game/RightPlayerCard.vue new file mode 100644 index 0000000..87df4f9 --- /dev/null +++ b/src/components/game/RightPlayerCard.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/game/SeatPlayerCard.vue b/src/components/game/SeatPlayerCard.vue new file mode 100644 index 0000000..8544e36 --- /dev/null +++ b/src/components/game/SeatPlayerCard.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/components/game/TopPlayerCard.vue b/src/components/game/TopPlayerCard.vue new file mode 100644 index 0000000..9c4dcb9 --- /dev/null +++ b/src/components/game/TopPlayerCard.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/game/seat-player-card.ts b/src/components/game/seat-player-card.ts new file mode 100644 index 0000000..284b81e --- /dev/null +++ b/src/components/game/seat-player-card.ts @@ -0,0 +1,9 @@ +export interface SeatPlayerCardModel { + avatar: string + name: string + money: string + dealer: boolean + isTurn: boolean + isOnline: boolean + missingSuitLabel: string +} diff --git a/src/views/ChengduGamePage.vue b/src/views/ChengduGamePage.vue index 761b0d6..1da2b6d 100644 --- a/src/views/ChengduGamePage.vue +++ b/src/views/ChengduGamePage.vue @@ -6,9 +6,14 @@ import topBackImage from '../assets/images/tiles/top/tbgs_2.png' import rightBackImage from '../assets/images/tiles/right/tbgs_1.png' import bottomBackImage from '../assets/images/tiles/bottom/tdbgs_4.png' import leftBackImage from '../assets/images/tiles/left/tbgs_3.png' +import TopPlayerCard from '../components/game/TopPlayerCard.vue' +import RightPlayerCard from '../components/game/RightPlayerCard.vue' +import BottomPlayerCard from '../components/game/BottomPlayerCard.vue' +import LeftPlayerCard from '../components/game/LeftPlayerCard.vue' import type { AuthSession } from '../api/authed-request' import { refreshAccessToken } from '../api/auth' import { getUserInfo } from '../api/user' +import type { SeatPlayerCardModel } from '../components/game/seat-player-card' import { DEFAULT_MAX_PLAYERS, activeRoomState, @@ -213,25 +218,66 @@ const wallBacks = computed>(() => { } }) -const seatDecor = computed(() => { +const seatDecor = computed>(() => { const scoreMap = roomState.value.game?.state?.scores ?? {} const dealerIndex = roomState.value.game?.state?.dealerIndex ?? -1 - return seatViews.value.map((seat, index) => { + return seatViews.value.reduce( + (acc, seat, index) => { const playerId = seat.player?.playerId ?? '' const score = playerId ? scoreMap[playerId] : undefined - return { - seat: seat.key, - avatar: seat.isSelf ? '我' : String(index + 1), - name: seat.player ? (seat.isSelf ? '你' : playerId) : '空位', - money: typeof score === 'number' ? `${score}` : '--', - dealer: seat.player?.index === dealerIndex, - isTurn: seat.isTurn, - isOnline: Boolean(seat.player), - missingSuit: null as string | null, - } - }) + acc[seat.key] = { + avatar: seat.isSelf ? '我' : String(index + 1), + name: seat.player ? (seat.isSelf ? '你' : playerId) : '空位', + money: typeof score === 'number' ? `${score}` : '--', + dealer: seat.player?.index === dealerIndex, + isTurn: seat.isTurn, + isOnline: Boolean(seat.player), + missingSuitLabel: missingSuitLabel(null), + } + + return acc + }, + { + top: { + avatar: '1', + name: '空位', + money: '--', + dealer: false, + isTurn: false, + isOnline: false, + missingSuitLabel: missingSuitLabel(null), + }, + right: { + avatar: '2', + name: '空位', + money: '--', + dealer: false, + isTurn: false, + isOnline: false, + missingSuitLabel: missingSuitLabel(null), + }, + bottom: { + avatar: '我', + name: '空位', + money: '--', + dealer: false, + isTurn: false, + isOnline: false, + missingSuitLabel: missingSuitLabel(null), + }, + left: { + avatar: '4', + name: '空位', + money: '--', + dealer: false, + isTurn: false, + isOnline: false, + missingSuitLabel: missingSuitLabel(null), + }, + }, + ) }) const centerTimer = computed(() => { @@ -905,14 +951,15 @@ onBeforeUnmount(() => {