Files
cultivation-world-simulator/web/src/App.vue
2025-11-22 20:48:51 +08:00

206 lines
5.1 KiB
Vue

<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { NConfigProvider, darkTheme, NMessageProvider } from 'naive-ui'
import { useWorldStore } from './stores/world'
import { useUiStore } from './stores/ui'
import { useGameSocket } from './composables/useGameSocket'
import { gameApi } from './api/game'
import GameCanvas from './components/game/GameCanvas.vue'
import InfoPanelContainer from './components/game/panels/info/InfoPanelContainer.vue'
import StatusBar from './components/layout/StatusBar.vue'
import EventPanel from './components/panels/EventPanel.vue'
import SystemMenu from './components/SystemMenu.vue'
// Composables
useGameSocket()
const worldStore = useWorldStore()
const uiStore = useUiStore()
const showMenu = ref(false)
const isManualPaused = ref(false)
onMounted(async () => {
await worldStore.initialize()
window.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
window.removeEventListener('keydown', handleKeydown)
})
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') {
if (uiStore.selectedTarget) {
uiStore.clearSelection()
} else {
showMenu.value = !showMenu.value
}
} else if (e.key === ' ') {
// Space to toggle pause? Optional but good UX
// toggleManualPause()
}
}
function handleSelection(target: { type: 'avatar' | 'region'; id: string; name?: string }) {
uiStore.select(target.type, target.id)
}
function handleMenuClose() {
showMenu.value = false
}
function toggleManualPause() {
isManualPaused.value = !isManualPaused.value
}
// 监听菜单状态和手动暂停状态,控制游戏暂停/继续
watch([showMenu, isManualPaused], ([menuVisible, manualPaused]) => {
if (menuVisible || manualPaused) {
gameApi.pauseGame().catch(console.error)
} else {
gameApi.resumeGame().catch(console.error)
}
})
</script>
<template>
<n-config-provider :theme="darkTheme">
<n-message-provider>
<div class="app-layout">
<StatusBar />
<div class="main-content">
<div class="map-container">
<!-- 顶部控制栏 -->
<div class="top-controls">
<!-- 暂停/播放按钮 -->
<button class="control-btn pause-toggle" @click="toggleManualPause" :title="isManualPaused ? '继续游戏' : '暂停游戏'">
<!-- 播放图标 (当暂停时显示) -->
<svg v-if="isManualPaused" viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M8 5v14l11-7z"/>
</svg>
<!-- 暂停图标 (当播放时显示) -->
<svg v-else viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
</svg>
</button>
<!-- 菜单按钮 -->
<button class="control-btn menu-toggle" @click="showMenu = true">
<svg viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
</svg>
</button>
</div>
<!-- 暂停状态提示 -->
<div v-if="isManualPaused" class="pause-indicator">
<div class="pause-text">已暂停</div>
</div>
<GameCanvas
@avatarSelected="handleSelection"
@regionSelected="handleSelection"
/>
<InfoPanelContainer />
</div>
<aside class="sidebar">
<EventPanel />
</aside>
</div>
<SystemMenu
:visible="showMenu"
@close="handleMenuClose"
/>
</div>
</n-message-provider>
</n-config-provider>
</template>
<style scoped>
.app-layout {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
background: #000;
color: #eee;
overflow: hidden;
position: relative;
}
.main-content {
flex: 1;
display: flex;
position: relative;
overflow: hidden;
}
.map-container {
flex: 1;
position: relative;
background: #111;
overflow: hidden;
}
.top-controls {
position: absolute;
top: 10px;
right: 10px;
z-index: 100;
display: flex;
gap: 10px;
}
.control-btn {
background: rgba(0,0,0,0.5);
border: 1px solid #444;
color: #ddd;
width: 40px;
height: 40px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.control-btn:hover {
background: rgba(0,0,0,0.8);
border-color: #666;
color: #fff;
}
.pause-indicator {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 90;
pointer-events: none;
}
.pause-text {
background: rgba(0, 0, 0, 0.6);
color: #fff;
padding: 6px 16px;
border-radius: 20px;
font-size: 14px;
letter-spacing: 2px;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(4px);
}
.sidebar {
width: 400px;
background: #181818;
border-left: 1px solid #333;
display: flex;
flex-direction: column;
z-index: 20;
}
</style>