diff --git a/src/assets/styles/global.css b/src/assets/styles/global.css
index e553a9d..0f384c7 100644
--- a/src/assets/styles/global.css
+++ b/src/assets/styles/global.css
@@ -844,6 +844,13 @@ button:disabled {
box-shadow:
inset 0 2px 4px rgba(255, 255, 255, 0.18),
0 6px 14px rgba(0, 0, 0, 0.22);
+ overflow: hidden;
+}
+
+.avatar-card img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
}
.player-meta p {
diff --git a/src/components/game/SeatPlayerCard.vue b/src/components/game/SeatPlayerCard.vue
index 848e7c9..3d4e552 100644
--- a/src/components/game/SeatPlayerCard.vue
+++ b/src/components/game/SeatPlayerCard.vue
@@ -3,6 +3,7 @@ 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 type { SeatPlayerCardModel } from './seat-player-card'
const props = defineProps<{
@@ -22,6 +23,10 @@ const missingSuitIcon = computed(() => {
}
return ''
})
+
+const resolvedAvatarUrl = computed(() => {
+ return props.player.avatarUrl || defaultAvatarIcon
+})
@@ -30,7 +35,9 @@ const missingSuitIcon = computed(() => {
:class="[seatClass, { 'is-turn': player.isTurn }]"
>
-
{{ player.avatar }}
+
+
![]()
+
庄
@@ -43,4 +50,4 @@ const missingSuitIcon = computed(() => {
{{ player.missingSuitLabel }}
-
\ No newline at end of file
+
diff --git a/src/components/game/seat-player-card.ts b/src/components/game/seat-player-card.ts
index 2f8971f..a77f893 100644
--- a/src/components/game/seat-player-card.ts
+++ b/src/components/game/seat-player-card.ts
@@ -1,8 +1,8 @@
// 玩家卡片展示模型(用于座位UI渲染)
export interface SeatPlayerCardModel {
- avatar: string // 头像
+ avatarUrl: string // 头像 URL
name: string // 显示名称
dealer: boolean // 是否庄家
isTurn: boolean // 是否当前轮到该玩家
missingSuitLabel: string // 定缺花色(万/筒/条)
-}
\ No newline at end of file
+}
diff --git a/src/game/actions.ts b/src/game/actions.ts
index 8c03851..f061995 100644
--- a/src/game/actions.ts
+++ b/src/game/actions.ts
@@ -13,6 +13,8 @@ export interface RoomPlayerUpdatePayload {
player_id?: string
PlayerName?: string
player_name?: string
+ AvatarUrl?: string
+ avatar_url?: string
Ready?: boolean
ready?: boolean
MissingSuit?: string | null
diff --git a/src/store/gameStore.ts b/src/store/gameStore.ts
index 484862c..08f108d 100644
--- a/src/store/gameStore.ts
+++ b/src/store/gameStore.ts
@@ -133,6 +133,7 @@ export const useGameStore = defineStore('game', {
typeof seatRaw === 'number' && Number.isFinite(seatRaw) ? seatRaw : index
const readyRaw = raw.Ready ?? raw.ready
const displayNameRaw = raw.PlayerName ?? raw.player_name
+ const avatarUrlRaw = raw.AvatarUrl ?? raw.avatar_url
const missingSuitRaw = raw.MissingSuit ?? raw.missing_suit
nextPlayers[playerId] = {
@@ -142,6 +143,10 @@ export const useGameStore = defineStore('game', {
typeof displayNameRaw === 'string' && displayNameRaw
? displayNameRaw
: previous?.displayName,
+ avatarURL:
+ typeof avatarUrlRaw === 'string'
+ ? avatarUrlRaw
+ : previous?.avatarURL,
missingSuit:
typeof missingSuitRaw === 'string' || missingSuitRaw === null
? missingSuitRaw
@@ -167,6 +172,7 @@ export const useGameStore = defineStore('game', {
playerId,
seatIndex: previous?.seatIndex ?? index,
displayName: previous?.displayName ?? playerId,
+ avatarURL: previous?.avatarURL,
missingSuit: previous?.missingSuit,
handTiles: previous?.handTiles ?? [],
melds: previous?.melds ?? [],
diff --git a/src/types/state/playerState.ts b/src/types/state/playerState.ts
index dd347fb..54791b9 100644
--- a/src/types/state/playerState.ts
+++ b/src/types/state/playerState.ts
@@ -1,8 +1,9 @@
-import type { Tile } from '../tile'
-import type { MeldState } from './meldState.ts'
+import type {Tile} from '../tile'
+import type {MeldState} from './meldState.ts'
export interface PlayerState {
playerId: string
+ avatarURL?: string
seatIndex: number
displayName?: string
missingSuit?: string | null
diff --git a/src/views/ChengduGamePage.vue b/src/views/ChengduGamePage.vue
index 19b23ff..4a96f8b 100644
--- a/src/views/ChengduGamePage.vue
+++ b/src/views/ChengduGamePage.vue
@@ -41,6 +41,8 @@ type DisplayPlayer = PlayerState & {
missingSuit?: string | null
}
+type GameActionPayload = Extract['payload']
+
interface SeatViewModel {
key: SeatKey
player?: DisplayPlayer
@@ -78,6 +80,31 @@ const loggedInUserName = computed(() => {
return auth.value?.user?.nickname || auth.value?.user?.username || ''
})
+const localCachedAvatarUrl = computed(() => {
+ const source = auth.value?.user as Record | undefined
+ if (!source) {
+ return ''
+ }
+
+ const avatarCandidates = [
+ source.avatar,
+ source.avatar_url,
+ source.avatarUrl,
+ source.head_img,
+ source.headImg,
+ source.profile_image,
+ source.profileImage,
+ ]
+
+ for (const candidate of avatarCandidates) {
+ if (typeof candidate === 'string' && candidate.trim()) {
+ return candidate
+ }
+ }
+
+ return ''
+})
+
const myPlayer = computed(() => {
return gameStore.players[loggedInUserId.value]
})
@@ -206,8 +233,8 @@ const seatDecor = computed>(() => {
const dealerIndex = roomState.value.game?.state?.dealerIndex ?? -1
const defaultMissingSuitLabel = missingSuitLabel(null)
- const emptySeat = (avatar: string): SeatPlayerCardModel => ({
- avatar,
+ const emptySeat = (): SeatPlayerCardModel => ({
+ avatarUrl: '',
name: '空位',
dealer: false,
isTurn: false,
@@ -215,20 +242,24 @@ const seatDecor = computed>(() => {
})
const result: Record = {
- top: emptySeat('1'),
- right: emptySeat('2'),
- bottom: emptySeat('我'),
- left: emptySeat('4'),
+ top: emptySeat(),
+ right: emptySeat(),
+ bottom: emptySeat(),
+ left: emptySeat(),
}
- for (const [index, seat] of seatViews.value.entries()) {
+ for (const seat of seatViews.value) {
if (!seat.player) {
continue
}
const displayName = seat.player.displayName || `玩家${seat.player.seatIndex + 1}`
+ const avatarUrl = seat.isSelf
+ ? (localCachedAvatarUrl.value || seat.player.avatarURL || '')
+ : (seat.player.avatarURL || '')
+
result[seat.key] = {
- avatar: seat.isSelf ? '我' : String(index + 1),
+ avatarUrl,
name: seat.isSelf ? '你自己' : displayName,
dealer: seat.player.seatIndex === dealerIndex,
isTurn: seat.isTurn,
@@ -334,42 +365,42 @@ function toGameAction(message: unknown): GameAction | null {
switch (type) {
case 'GAME_INIT':
if (payload && typeof payload === 'object') {
- return {type: 'GAME_INIT', payload: payload as GameAction['payload']}
+ return {type: 'GAME_INIT', payload: payload as GameActionPayload<'GAME_INIT'>}
}
return null
case 'GAME_START':
if (payload && typeof payload === 'object') {
- return {type: 'GAME_START', payload: payload as GameAction['payload']}
+ return {type: 'GAME_START', payload: payload as GameActionPayload<'GAME_START'>}
}
return null
case 'DRAW_TILE':
if (payload && typeof payload === 'object') {
- return {type: 'DRAW_TILE', payload: payload as GameAction['payload']}
+ return {type: 'DRAW_TILE', payload: payload as GameActionPayload<'DRAW_TILE'>}
}
return null
case 'PLAY_TILE':
if (payload && typeof payload === 'object') {
- return {type: 'PLAY_TILE', payload: payload as GameAction['payload']}
+ return {type: 'PLAY_TILE', payload: payload as GameActionPayload<'PLAY_TILE'>}
}
return null
case 'PENDING_CLAIM':
if (payload && typeof payload === 'object') {
- return {type: 'PENDING_CLAIM', payload: payload as GameAction['payload']}
+ return {type: 'PENDING_CLAIM', payload: payload as GameActionPayload<'PENDING_CLAIM'>}
}
return null
case 'CLAIM_RESOLVED':
if (payload && typeof payload === 'object') {
- return {type: 'CLAIM_RESOLVED', payload: payload as GameAction['payload']}
+ return {type: 'CLAIM_RESOLVED', payload: payload as GameActionPayload<'CLAIM_RESOLVED'>}
}
return null
case 'ROOM_PLAYER_UPDATE':
if (payload && typeof payload === 'object') {
- return {type: 'ROOM_PLAYER_UPDATE', payload: payload as GameAction['payload']}
+ return {type: 'ROOM_PLAYER_UPDATE', payload: payload as GameActionPayload<'ROOM_PLAYER_UPDATE'>}
}
return null
case 'ROOM_MEMBER_JOINED':
if (payload && typeof payload === 'object') {
- return {type: 'ROOM_PLAYER_UPDATE', payload: payload as GameAction['payload']}
+ return {type: 'ROOM_PLAYER_UPDATE', payload: payload as GameActionPayload<'ROOM_PLAYER_UPDATE'>}
}
return null
default:
@@ -470,6 +501,7 @@ function hydrateFromActiveRoom(routeRoomId: string): void {
playerId: player.playerId,
seatIndex: player.index,
displayName: player.displayName || player.playerId,
+ avatarURL: previous?.avatarURL,
missingSuit: player.missingSuit ?? previous?.missingSuit,
isReady: player.ready,
handTiles: previous?.handTiles ?? [],