feat: 添加局后复盘服务与页面容器组件
新增复盘服务相关DTO、Controller和Service 实现复盘页面容器组件ReviewPageContainer 更新前端页面架构文档与开发计划 移除DemoGameController中的演示复盘接口 补充复盘服务单元测试
This commit is contained in:
163
frontend/src/components/GameWorkspace.vue
Normal file
163
frontend/src/components/GameWorkspace.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<script setup lang="ts">
|
||||
import GameActionDock from './GameActionDock.vue'
|
||||
import GameMessageStack from './GameMessageStack.vue'
|
||||
import PublicEventTimeline from './PublicEventTimeline.vue'
|
||||
import PublicSeatBoard from './PublicSeatBoard.vue'
|
||||
import SelfHandPanel from './SelfHandPanel.vue'
|
||||
import ViewSwitchPanel from './ViewSwitchPanel.vue'
|
||||
import type {
|
||||
DiagnosticItem,
|
||||
GameStateResponse,
|
||||
PrivateActionCandidate,
|
||||
PrivateActionMessage,
|
||||
PrivateTeachingMessage,
|
||||
PublicGameMessage,
|
||||
ScoreChangeCardView,
|
||||
SettlementCardView,
|
||||
ViewUserOption
|
||||
} from '../types/game'
|
||||
import { phaseLabelMap } from '../utils/gameUi'
|
||||
|
||||
const props = defineProps<{
|
||||
game: GameStateResponse | null
|
||||
currentUserId: string
|
||||
currentViewLabel: string
|
||||
viewUserOptions: ViewUserOption[]
|
||||
lackSuit: string
|
||||
canSelectLack: boolean
|
||||
canDiscard: boolean
|
||||
recommendedDiscardTile: string | null
|
||||
privateTeaching: PrivateTeachingMessage | null
|
||||
privateTeachingHint: string
|
||||
privateTeachingCandidates: { tile: string; score: number; reasonTags: string[] }[]
|
||||
privateAction: PrivateActionMessage | null
|
||||
privateActionSummary: string
|
||||
actionPanelHint: string
|
||||
turnActionCandidates: PrivateActionCandidate[]
|
||||
responseActionCandidates: PrivateActionCandidate[]
|
||||
responseContextSummary: string
|
||||
actionDiagnosticItems: DiagnosticItem[]
|
||||
publicSeats: GameStateResponse['seats']
|
||||
publicEvents: PublicGameMessage[]
|
||||
latestSettlementCard: SettlementCardView | null
|
||||
latestSettlementScoreChanges: ScoreChangeCardView[]
|
||||
wsStatus: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:lackSuit': [value: string]
|
||||
refreshGameState: []
|
||||
selectLack: []
|
||||
switchUserView: [userId: string]
|
||||
discard: [tile: string]
|
||||
submitCandidateAction: [actionType: string, tile: string | null]
|
||||
}>()
|
||||
|
||||
function updateLackSuit(event: Event) {
|
||||
emit('update:lackSuit', (event.target as HTMLSelectElement).value)
|
||||
}
|
||||
|
||||
function handleSwitchUserView(userId: string) {
|
||||
emit('switchUserView', userId)
|
||||
}
|
||||
|
||||
function handleSubmitCandidateAction(actionType: string, tile: string | null) {
|
||||
emit('submitCandidateAction', actionType, tile)
|
||||
}
|
||||
|
||||
// 这是“对局工作区”级别的容器组件:统一组合手牌、动作、私有消息、公共桌面与事件时间线,
|
||||
// 让 App 容器只保留接口、WebSocket 和状态清理逻辑。
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section v-if="props.game" class="play-grid">
|
||||
<article class="panel game-panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">步骤 3</p>
|
||||
<h2>对局控制台</h2>
|
||||
</div>
|
||||
<span class="status-pill">{{ phaseLabelMap[props.game.phase] ?? props.game.phase }}</span>
|
||||
</div>
|
||||
|
||||
<div class="room-meta">
|
||||
<div>
|
||||
<span class="meta-label">对局 ID</span>
|
||||
<strong>{{ props.game.gameId }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="meta-label">当前视角</span>
|
||||
<strong>{{ props.currentViewLabel }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span class="meta-label">剩余牌墙</span>
|
||||
<strong>{{ props.game.remainingWallCount }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ViewSwitchPanel :options="props.viewUserOptions" :current-user-id="props.currentUserId" @switch="handleSwitchUserView" />
|
||||
|
||||
<div class="btn-row vertical-on-mobile">
|
||||
<button class="secondary-btn" @click="emit('refreshGameState')">刷新对局</button>
|
||||
<label class="field compact-field">
|
||||
<span>定缺</span>
|
||||
<select :value="props.lackSuit" @change="updateLackSuit">
|
||||
<option value="WAN">万</option>
|
||||
<option value="TONG">筒</option>
|
||||
<option value="TIAO">条</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="primary-btn" :disabled="!props.canSelectLack" @click="emit('selectLack')">提交定缺</button>
|
||||
</div>
|
||||
|
||||
<SelfHandPanel
|
||||
:self-seat="props.game.selfSeat"
|
||||
:current-seat-no="props.game.currentSeatNo"
|
||||
:can-discard="props.canDiscard"
|
||||
:recommended-discard-tile="props.recommendedDiscardTile"
|
||||
:teaching-explanation="props.privateTeaching?.explanation ?? null"
|
||||
@discard="emit('discard', $event)"
|
||||
/>
|
||||
|
||||
<GameActionDock
|
||||
:private-action="props.privateAction"
|
||||
:private-action-summary="props.privateActionSummary"
|
||||
:action-panel-hint="props.actionPanelHint"
|
||||
:turn-action-candidates="props.turnActionCandidates"
|
||||
:response-action-candidates="props.responseActionCandidates"
|
||||
:recommended-discard-tile="props.recommendedDiscardTile"
|
||||
:response-context-summary="props.responseContextSummary"
|
||||
@submit="handleSubmitCandidateAction"
|
||||
/>
|
||||
</article>
|
||||
|
||||
<article class="panel board-panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">步骤 4</p>
|
||||
<h2>消息与公共桌面</h2>
|
||||
</div>
|
||||
<span class="status-pill">{{ props.wsStatus }}</span>
|
||||
</div>
|
||||
|
||||
<GameMessageStack
|
||||
:private-action="props.privateAction"
|
||||
:private-action-summary="props.privateActionSummary"
|
||||
:action-diagnostic-items="props.actionDiagnosticItems"
|
||||
:action-panel-hint="props.actionPanelHint"
|
||||
:private-teaching="props.privateTeaching"
|
||||
:recommended-discard-tile="props.recommendedDiscardTile"
|
||||
:teaching-hint="props.privateTeachingHint"
|
||||
:teaching-candidates="props.privateTeachingCandidates"
|
||||
/>
|
||||
|
||||
<PublicSeatBoard :seats="props.publicSeats" />
|
||||
|
||||
<PublicEventTimeline
|
||||
:public-events="props.publicEvents"
|
||||
:latest-settlement-card="props.latestSettlementCard"
|
||||
:latest-settlement-score-changes="props.latestSettlementScoreChanges"
|
||||
/>
|
||||
</article>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user