bof3-demo/src/ui/BattlePanel.vue

153 lines
3.2 KiB
Vue

<script setup lang="ts">
import { actInBattle, closeBattle, useGameState } from '../game/state'
const gameState = useGameState()
const runAction = (action: 'attack' | 'skill' | 'defend' | 'item') => {
actInBattle(action)
}
</script>
<template>
<div v-if="gameState.battle.active" class="battle-overlay">
<section class="battle-card">
<header class="battle-head">
<h2>战斗中:{{ gameState.battle.enemyName }}</h2>
<p>{{ gameState.battle.message }}</p>
</header>
<div class="bars">
<div>
<strong>我方</strong>
<p>HP {{ gameState.hp }} | MP {{ gameState.mp }} | 药草 {{ gameState.player.potions }}</p>
</div>
<div>
<strong>敌方</strong>
<p>HP {{ gameState.battle.enemyHp }} / {{ gameState.battle.enemyMaxHp }}</p>
</div>
</div>
<div class="actions" v-if="gameState.battle.turn !== 'resolved'">
<button :disabled="gameState.battle.turn !== 'player'" @click="runAction('attack')">攻击</button>
<button :disabled="gameState.battle.turn !== 'player'" @click="runAction('skill')">技能</button>
<button :disabled="gameState.battle.turn !== 'player'" @click="runAction('defend')">防御</button>
<button :disabled="gameState.battle.turn !== 'player'" @click="runAction('item')">道具</button>
</div>
<div class="result" v-else>
<p>{{ gameState.battle.victory ? '战斗胜利' : '战斗失败' }}</p>
<button @click="closeBattle">返回探索</button>
</div>
<ul class="log">
<li v-for="(line, index) in gameState.battle.log" :key="`${index}-${line}`">{{ line }}</li>
</ul>
</section>
</div>
</template>
<style scoped>
.battle-overlay {
position: fixed;
inset: 0;
background: rgba(6, 10, 14, 0.78);
display: grid;
place-items: center;
z-index: 50;
padding: 14px;
}
.battle-card {
width: min(760px, 100%);
border-radius: 12px;
border: 1px solid #365064;
background: linear-gradient(180deg, #12202a, #0f1921);
padding: 14px;
}
.battle-head h2 {
margin: 0;
font-size: 20px;
}
.battle-head p {
margin: 6px 0 0;
color: #9cc0d6;
}
.bars {
margin-top: 12px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.bars > div {
border: 1px solid #2f4555;
border-radius: 8px;
padding: 10px;
background: #172632;
}
.bars p {
margin: 6px 0 0;
font-size: 13px;
}
.actions {
margin-top: 12px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
}
button {
border: 1px solid #42627a;
border-radius: 8px;
background: #203646;
color: #d7e0e6;
padding: 10px;
cursor: pointer;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.result {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: space-between;
}
.result p {
margin: 0;
}
.log {
margin: 12px 0 0;
border: 1px solid #2f4555;
border-radius: 8px;
background: #111d27;
padding: 10px 12px;
list-style: none;
display: grid;
gap: 6px;
max-height: 150px;
overflow: auto;
font-size: 13px;
}
@media (max-width: 720px) {
.bars {
grid-template-columns: 1fr;
}
.actions {
grid-template-columns: repeat(2, 1fr);
}
}
</style>