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 @@
+
+
+
+
+ {{ player.avatar }}
+
+ 庄
+ {{ player.missingSuitLabel }}
+
+
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(() => {