mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +08:00
feat: 初始化项目结构与核心功能
引入项目基础目录结构,包含多语言支持、主要页面与组件、核心游戏逻辑、UI 组件库、加密与本地持久化、自动化 Docker 构建流程、GitHub issue 模板(中英文)、README(中英文)、LICENSE 及开发配置文件。实现 OGame 单机版主要功能模块,为后续开发和扩展奠定基础。
This commit is contained in:
556
src/App.vue
Normal file
556
src/App.vue
Normal file
@@ -0,0 +1,556 @@
|
||||
<template>
|
||||
<div class="flex h-screen bg-background overflow-hidden">
|
||||
<!-- 遮罩层(移动端) -->
|
||||
<div v-if="!gameStore.sidebarCollapsed" class="fixed inset-0 bg-black/50 z-30 lg:hidden" @click="toggleSidebar" />
|
||||
|
||||
<!-- 侧边导航栏 -->
|
||||
<aside
|
||||
:class="[
|
||||
'border-r bg-card flex flex-col transition-all duration-300 ease-in-out shadow-lg z-40',
|
||||
'fixed lg:relative h-full',
|
||||
gameStore.sidebarCollapsed ? '-translate-x-full lg:translate-x-0 lg:w-16' : 'translate-x-0 w-64'
|
||||
]"
|
||||
>
|
||||
<!-- Logo -->
|
||||
<div class="p-4 border-b flex items-center justify-center">
|
||||
<h1 v-if="!gameStore.sidebarCollapsed" class="text-xl font-bold flex items-center gap-2">
|
||||
<span class="text-2xl">
|
||||
<img src="@/assets/logo.svg" class="w-10" />
|
||||
</span>
|
||||
{{ pkg.title }}
|
||||
</h1>
|
||||
<span v-else class="text-2xl">
|
||||
<img src="@/assets/logo.svg" class="w-10" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 星球信息 -->
|
||||
<div v-if="planet && !gameStore.sidebarCollapsed" class="p-4 border-b">
|
||||
<div class="text-sm space-y-2">
|
||||
<div>
|
||||
<p class="font-semibold mb-1">
|
||||
{{ planet.name }}
|
||||
<Badge v-if="planet.isMoon" variant="secondary" class="ml-1 text-xs">{{ t('planet.moon') }}</Badge>
|
||||
</p>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
[{{ planet.position.galaxy }}:{{ planet.position.system }}:{{ planet.position.position }}]
|
||||
</p>
|
||||
</div>
|
||||
<!-- 玩家积分显示 -->
|
||||
<div class="bg-muted/50 rounded-lg p-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-muted-foreground">{{ t('player.points') }}</span>
|
||||
<span class="text-sm font-bold text-primary">{{ formatNumber(gameStore.player.points) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 月球切换按钮 -->
|
||||
<div v-if="hasMoon || planet.isMoon" class="flex gap-1">
|
||||
<Button v-if="planet.isMoon" @click="switchToParentPlanet" variant="outline" size="sm" class="w-full text-xs h-7">
|
||||
{{ t('planet.backToPlanet') }}
|
||||
</Button>
|
||||
<Button v-else-if="moon" @click="switchToMoon" variant="outline" size="sm" class="w-full text-xs h-7">
|
||||
{{ t('planet.switchToMoon') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导航菜单 -->
|
||||
<nav class="flex-1 p-2 space-y-1 overflow-y-auto">
|
||||
<RouterLink v-for="item in navItems" :key="item.path" :to="item.path" v-slot="{ isActive: routeActive }">
|
||||
<Button
|
||||
:variant="routeActive ? 'secondary' : 'ghost'"
|
||||
:class="['w-full transition-all', gameStore.sidebarCollapsed ? 'justify-center px-0' : 'justify-start']"
|
||||
:title="gameStore.sidebarCollapsed ? item.name.value : undefined"
|
||||
>
|
||||
<component :is="item.icon" :class="['h-4 w-4', !gameStore.sidebarCollapsed && 'mr-3']" />
|
||||
<span v-if="!gameStore.sidebarCollapsed">{{ item.name.value }}</span>
|
||||
</Button>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
|
||||
<!-- 语言切换 -->
|
||||
<div class="p-2 border-t">
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="ghost" class="w-full" size="sm">
|
||||
<Languages class="h-4 w-4" />
|
||||
<span v-if="!gameStore.sidebarCollapsed" class="ml-2">{{ localeNames[gameStore.locale] }}</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-48 p-2" :align="gameStore.sidebarCollapsed ? 'start' : 'center'">
|
||||
<div class="space-y-1">
|
||||
<Button
|
||||
v-for="locale in locales"
|
||||
:key="locale"
|
||||
@click="gameStore.locale = locale"
|
||||
:variant="gameStore.locale === locale ? 'secondary' : 'ghost'"
|
||||
class="w-full justify-start"
|
||||
size="sm"
|
||||
>
|
||||
{{ localeNames[locale] }}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<!-- 夜间模式切换 -->
|
||||
<div class="p-2 border-t">
|
||||
<Button @click="isDark = !isDark" variant="ghost" class="w-full" size="sm">
|
||||
<Sun v-if="isDark" class="h-4 w-4" />
|
||||
<Moon v-else class="h-4 w-4" />
|
||||
<span v-if="!gameStore.sidebarCollapsed" class="ml-2">{{ isDark ? t('sidebar.lightMode') : t('sidebar.darkMode') }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="p-2 border-t">
|
||||
<Button @click="toggleSidebar" variant="ghost" class="w-full" size="sm">
|
||||
<ChevronLeft v-if="!gameStore.sidebarCollapsed" class="h-4 w-4" />
|
||||
<ChevronRight v-else class="h-4 w-4" />
|
||||
<span v-if="!gameStore.sidebarCollapsed" class="ml-2">{{ t('sidebar.collapse') }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<!-- 顶部资源栏 -->
|
||||
<header v-if="planet" class="bg-card border-b px-4 sm:px-6 py-4.5 shadow-md">
|
||||
<div class="flex items-center justify-between gap-3 sm:gap-6">
|
||||
<!-- 汉堡菜单(移动端)- 左侧占位 -->
|
||||
<div class="lg:flex-1">
|
||||
<Button @click="toggleSidebar" variant="ghost" size="icon" class="lg:hidden h-8 w-8">
|
||||
<component :is="gameStore.sidebarCollapsed ? Menu : X" class="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 资源显示 - PC端居中 -->
|
||||
<div class="flex items-center gap-3 sm:gap-6 flex-1 lg:flex-none overflow-x-auto lg:justify-center">
|
||||
<div v-for="resourceType in resourceTypes" :key="resourceType.key" class="flex items-center gap-1.5 sm:gap-2 flex-shrink-0">
|
||||
<ResourceIcon :type="resourceType.key" size="md" />
|
||||
<div class="min-w-0">
|
||||
<!-- 电量显示 -->
|
||||
<template v-if="resourceType.key === 'energy'">
|
||||
<p
|
||||
class="text-xs sm:text-sm font-medium truncate"
|
||||
:class="
|
||||
planet.resources[resourceType.key] >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'
|
||||
"
|
||||
>
|
||||
{{ formatNumber(planet.resources[resourceType.key]) }}
|
||||
</p>
|
||||
<p class="text-[10px] sm:text-xs text-muted-foreground truncate">
|
||||
{{ formatNumber(energyProduction || 0) }} / {{ formatNumber(energyConsumption || 0) }}
|
||||
</p>
|
||||
</template>
|
||||
<!-- 其他资源显示 -->
|
||||
<template v-else>
|
||||
<p
|
||||
class="text-xs sm:text-sm font-medium truncate"
|
||||
:class="getResourceColor(planet.resources[resourceType.key], capacity?.[resourceType.key] || Infinity)"
|
||||
>
|
||||
{{ formatNumber(planet.resources[resourceType.key]) }} / {{ formatNumber(capacity?.[resourceType.key] || 0) }}
|
||||
</p>
|
||||
<p class="text-[10px] sm:text-xs text-muted-foreground truncate">
|
||||
+{{ formatNumber(production?.[resourceType.key] || 0) }}/{{ t('resources.perHour') }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧状态 - 右侧占位 -->
|
||||
<div class="flex items-center gap-2 sm:gap-4 flex-shrink-0 lg:flex-1 lg:justify-end">
|
||||
<!-- 建造队列状态 -->
|
||||
<div v-if="planet.buildQueue.length > 0" class="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm">
|
||||
<div class="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
||||
<span class="text-muted-foreground hidden sm:inline">{{ t('queue.building') }}</span>
|
||||
</div>
|
||||
<div v-if="gameStore.player.researchQueue.length > 0" class="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm">
|
||||
<div class="h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
|
||||
<span class="text-muted-foreground hidden sm:inline">{{ t('queue.researching') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 建造队列 -->
|
||||
<div
|
||||
v-if="planet && (planet.buildQueue.length > 0 || gameStore.player.researchQueue.length > 0)"
|
||||
class="bg-card border-b px-4 sm:px-6 py-4.5"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<!-- 建造队列 -->
|
||||
<div v-for="item in planet.buildQueue" :key="item.id" class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm gap-2">
|
||||
<div class="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<div class="h-2 w-2 rounded-full bg-green-500 animate-pulse flex-shrink-0" />
|
||||
<span class="font-medium truncate">{{ getItemName(item) }}</span>
|
||||
<span class="text-muted-foreground hidden sm:inline flex-shrink-0 text-[10px] sm:text-xs">
|
||||
→ {{ t('queue.level') }} {{ item.targetLevel }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs whitespace-nowrap">{{ formatTime(getRemainingTime(item)) }}</span>
|
||||
<Button
|
||||
@click="handleCancelBuild(item.id)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-5 sm:h-6 px-1.5 sm:px-2 text-[10px] sm:text-xs"
|
||||
>
|
||||
{{ t('queue.cancel') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getQueueProgress(item)" class="h-1.5" />
|
||||
</div>
|
||||
<!-- 研究队列 -->
|
||||
<div v-for="item in gameStore.player.researchQueue" :key="item.id" class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm gap-2">
|
||||
<div class="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<div class="h-2 w-2 rounded-full bg-blue-500 animate-pulse flex-shrink-0" />
|
||||
<span class="font-medium truncate">{{ getItemName(item) }}</span>
|
||||
<span class="text-muted-foreground hidden sm:inline flex-shrink-0 text-[10px] sm:text-xs">
|
||||
→ {{ t('queue.level') }} {{ item.targetLevel }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs whitespace-nowrap">{{ formatTime(getRemainingTime(item)) }}</span>
|
||||
<Button
|
||||
@click="handleCancelResearch(item.id)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-5 sm:h-6 px-1.5 sm:px-2 text-[10px] sm:text-xs"
|
||||
>
|
||||
{{ t('queue.cancel') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getQueueProgress(item)" class="h-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<div class="animate-fade-in">
|
||||
<RouterView />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 确认对话框 -->
|
||||
<ConfirmDialog ref="confirmDialog" />
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<DetailDialog />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, computed, ref } from 'vue'
|
||||
import { RouterView, RouterLink } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { localeNames, detectBrowserLocale, type Locale } from '@/locales'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'
|
||||
import ResourceIcon from '@/components/ResourceIcon.vue'
|
||||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||
import DetailDialog from '@/components/DetailDialog.vue'
|
||||
import { BuildingType, TechnologyType, ShipType, DefenseType, MissionType } from '@/types/game'
|
||||
import type { BuildQueueItem, FleetMission } from '@/types/game'
|
||||
import { BUILDINGS, TECHNOLOGIES, SHIPS, DEFENSES } from '@/config/gameConfig'
|
||||
import { formatNumber, formatTime, getResourceColor } from '@/utils/format'
|
||||
import {
|
||||
Moon,
|
||||
Sun,
|
||||
Home,
|
||||
Building2,
|
||||
FlaskConical,
|
||||
Ship,
|
||||
Rocket,
|
||||
Shield,
|
||||
Mail,
|
||||
Globe,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Menu,
|
||||
X,
|
||||
Users,
|
||||
Swords,
|
||||
Languages,
|
||||
Settings
|
||||
} from 'lucide-vue-next'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import * as planetLogic from '@/logic/planetLogic'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
import * as buildingValidation from '@/logic/buildingValidation'
|
||||
import * as resourceLogic from '@/logic/resourceLogic'
|
||||
import * as researchValidation from '@/logic/researchValidation'
|
||||
import * as fleetLogic from '@/logic/fleetLogic'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
import pkg from '../package.json'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const { isDark } = useTheme()
|
||||
const { t } = useI18n()
|
||||
const confirmDialog = ref<InstanceType<typeof ConfirmDialog> | null>(null)
|
||||
|
||||
// 所有可用的语言选项
|
||||
const locales: Locale[] = ['zh-CN', 'zh-TW', 'en', 'de', 'ru', 'ko', 'ja']
|
||||
|
||||
const initGame = () => {
|
||||
const shouldInit = gameLogic.shouldInitializeGame(gameStore.player.planets)
|
||||
if (!shouldInit) {
|
||||
const now = Date.now()
|
||||
gameLogic.updatePlanetsLastUpdate(gameStore.player.planets, now)
|
||||
gameStore.player.planets.forEach(planet => {
|
||||
const key = gameLogic.generatePositionKey(planet.position.galaxy, planet.position.system, planet.position.position)
|
||||
gameStore.universePlanets[key] = planet
|
||||
})
|
||||
generateNPCPlanets()
|
||||
return
|
||||
}
|
||||
gameStore.player = gameLogic.initializePlayer(gameStore.player.id, t('common.playerName'))
|
||||
const initialPlanet = planetLogic.createInitialPlanet(gameStore.player.id, t('planet.homePlanet'))
|
||||
gameStore.player.planets = [initialPlanet]
|
||||
gameStore.currentPlanetId = initialPlanet.id
|
||||
const key = gameLogic.generatePositionKey(initialPlanet.position.galaxy, initialPlanet.position.system, initialPlanet.position.position)
|
||||
gameStore.universePlanets[key] = initialPlanet
|
||||
}
|
||||
|
||||
const generateNPCPlanets = () => {
|
||||
const npcCount = 200
|
||||
for (let i = 0; i < npcCount; i++) {
|
||||
const position = gameLogic.generateRandomPosition()
|
||||
const key = gameLogic.generatePositionKey(position.galaxy, position.system, position.position)
|
||||
if (gameStore.universePlanets[key]) continue
|
||||
const npcPlanet = planetLogic.createNPCPlanet(i, position, t('planet.planetPrefix'))
|
||||
gameStore.universePlanets[key] = npcPlanet
|
||||
}
|
||||
}
|
||||
|
||||
const updateGame = () => {
|
||||
if (gameStore.isPaused) return
|
||||
const now = Date.now()
|
||||
gameStore.gameTime = now
|
||||
gameLogic.checkOfficersExpiration(gameStore.player.officers, now)
|
||||
const result = gameLogic.processGameUpdate(gameStore.player, now)
|
||||
gameStore.player.researchQueue = result.updatedResearchQueue
|
||||
gameStore.player.fleetMissions.forEach(mission => {
|
||||
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
|
||||
processMissionArrival(mission)
|
||||
} else if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) {
|
||||
processMissionReturn(mission)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const processMissionArrival = (mission: FleetMission) => {
|
||||
const targetPlanet = gameStore.player.planets.find(
|
||||
p =>
|
||||
p.position.galaxy === mission.targetPosition.galaxy &&
|
||||
p.position.system === mission.targetPosition.system &&
|
||||
p.position.position === mission.targetPosition.position
|
||||
)
|
||||
|
||||
if (mission.missionType === MissionType.Transport) {
|
||||
fleetLogic.processTransportArrival(mission, targetPlanet)
|
||||
} else if (mission.missionType === MissionType.Attack) {
|
||||
const attackResult = fleetLogic.processAttackArrival(mission, targetPlanet, gameStore.player, null, gameStore.player.planets)
|
||||
if (attackResult) {
|
||||
gameStore.player.battleReports.push(attackResult.battleResult)
|
||||
if (attackResult.moon) {
|
||||
gameStore.player.planets.push(attackResult.moon)
|
||||
}
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Colonize) {
|
||||
const newPlanet = fleetLogic.processColonizeArrival(mission, targetPlanet, gameStore.player.id, t('planet.colonyPrefix'))
|
||||
if (newPlanet) gameStore.player.planets.push(newPlanet)
|
||||
} else if (mission.missionType === MissionType.Spy) {
|
||||
const spyReport = fleetLogic.processSpyArrival(mission, targetPlanet, gameStore.player.id)
|
||||
if (spyReport) gameStore.player.spyReports.push(spyReport)
|
||||
} else if (mission.missionType === MissionType.Deploy) {
|
||||
const deployed = fleetLogic.processDeployArrival(mission, targetPlanet, gameStore.player.id)
|
||||
if (deployed) {
|
||||
const missionIndex = gameStore.player.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) gameStore.player.fleetMissions.splice(missionIndex, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const processMissionReturn = (mission: FleetMission) => {
|
||||
const originPlanet = gameStore.player.planets.find(p => p.id === mission.originPlanetId)
|
||||
if (!originPlanet) return
|
||||
shipLogic.addFleet(originPlanet.fleet, mission.fleet)
|
||||
resourceLogic.addResources(originPlanet.resources, mission.cargo)
|
||||
const missionIndex = gameStore.player.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) gameStore.player.fleetMissions.splice(missionIndex, 1)
|
||||
}
|
||||
|
||||
// 初始化游戏
|
||||
onMounted(() => {
|
||||
// 如果是首次访问(没有星球数据),使用浏览器语言自动检测
|
||||
const isFirstVisit = gameStore.player.planets.length === 0
|
||||
if (isFirstVisit) {
|
||||
gameStore.locale = detectBrowserLocale()
|
||||
}
|
||||
|
||||
initGame()
|
||||
|
||||
// 启动游戏循环
|
||||
const gameLoop = setInterval(() => {
|
||||
updateGame()
|
||||
}, 1000) // 每秒更新一次
|
||||
|
||||
// 清理定时器
|
||||
onUnmounted(() => {
|
||||
clearInterval(gameLoop)
|
||||
})
|
||||
})
|
||||
|
||||
const navItems = [
|
||||
{ name: computed(() => t('nav.overview')), path: '/', icon: Home },
|
||||
{ name: computed(() => t('nav.buildings')), path: '/buildings', icon: Building2 },
|
||||
{ name: computed(() => t('nav.research')), path: '/research', icon: FlaskConical },
|
||||
{ name: computed(() => t('nav.shipyard')), path: '/shipyard', icon: Ship },
|
||||
{ name: computed(() => t('nav.defense')), path: '/defense', icon: Shield },
|
||||
{ name: computed(() => t('nav.fleet')), path: '/fleet', icon: Rocket },
|
||||
{ name: computed(() => t('nav.officers')), path: '/officers', icon: Users },
|
||||
{ name: computed(() => t('nav.simulator')), path: '/battle-simulator', icon: Swords },
|
||||
{ name: computed(() => t('nav.galaxy')), path: '/galaxy', icon: Globe },
|
||||
{ name: computed(() => t('nav.messages')), path: '/messages', icon: Mail },
|
||||
{ name: computed(() => t('nav.settings')), path: '/settings', icon: Settings }
|
||||
]
|
||||
|
||||
const planet = computed(() => gameStore.currentPlanet)
|
||||
const production = computed(() => (planet.value ? publicLogic.getResourceProduction(planet.value, gameStore.player.officers) : null))
|
||||
const capacity = computed(() => (planet.value ? publicLogic.getResourceCapacity(planet.value, gameStore.player.officers) : null))
|
||||
|
||||
// 电量产出和消耗
|
||||
const energyProduction = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, Date.now())
|
||||
return resourceLogic.calculateEnergyProduction(planet.value, { energyProductionBonus: bonuses.energyProductionBonus })
|
||||
})
|
||||
|
||||
const energyConsumption = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
return resourceLogic.calculateEnergyConsumption(planet.value)
|
||||
})
|
||||
|
||||
// 资源类型配置
|
||||
const resourceTypes = [
|
||||
{ key: 'metal' as const },
|
||||
{ key: 'crystal' as const },
|
||||
{ key: 'deuterium' as const },
|
||||
{ key: 'energy' as const },
|
||||
{ key: 'darkMatter' as const }
|
||||
]
|
||||
|
||||
// 月球相关
|
||||
const moon = computed(() => {
|
||||
if (!planet.value || planet.value.isMoon) return null
|
||||
return gameStore.getMoonForPlanet(planet.value.id)
|
||||
})
|
||||
const hasMoon = computed(() => !!moon.value)
|
||||
|
||||
// 切换到月球
|
||||
const switchToMoon = () => {
|
||||
if (moon.value) {
|
||||
gameStore.currentPlanetId = moon.value.id
|
||||
}
|
||||
}
|
||||
|
||||
// 切换回母星
|
||||
const switchToParentPlanet = () => {
|
||||
if (planet.value?.parentPlanetId) {
|
||||
gameStore.currentPlanetId = planet.value.parentPlanetId
|
||||
}
|
||||
}
|
||||
|
||||
// 切换侧边栏
|
||||
const toggleSidebar = () => {
|
||||
gameStore.sidebarCollapsed = !gameStore.sidebarCollapsed
|
||||
}
|
||||
|
||||
// 获取队列项的名称
|
||||
const getItemName = (item: BuildQueueItem): string => {
|
||||
if (item.type === 'building' || item.type === 'demolish') {
|
||||
const buildingName = BUILDINGS[item.itemType as BuildingType]?.name || item.itemType
|
||||
return item.type === 'demolish' ? `${t('buildingsView.demolish')} - ${buildingName}` : buildingName
|
||||
} else if (item.type === 'technology') {
|
||||
return TECHNOLOGIES[item.itemType as TechnologyType]?.name || item.itemType
|
||||
} else if (item.type === 'ship') {
|
||||
return SHIPS[item.itemType as ShipType]?.name || item.itemType
|
||||
} else if (item.type === 'defense') {
|
||||
return DEFENSES[item.itemType as DefenseType]?.name || item.itemType
|
||||
}
|
||||
return item.itemType
|
||||
}
|
||||
|
||||
// 获取剩余时间
|
||||
const getRemainingTime = (item: BuildQueueItem): number => {
|
||||
const now = Date.now()
|
||||
return Math.max(0, Math.floor((item.endTime - now) / 1000))
|
||||
}
|
||||
|
||||
// 获取队列进度
|
||||
const getQueueProgress = (item: BuildQueueItem): number => {
|
||||
const now = Date.now()
|
||||
const total = item.endTime - item.startTime
|
||||
const elapsed = now - item.startTime
|
||||
return Math.min(100, Math.max(0, (elapsed / total) * 100))
|
||||
}
|
||||
|
||||
// 取消建造
|
||||
const handleCancelBuild = (queueId: string) => {
|
||||
confirmDialog.value?.show({
|
||||
title: t('queue.cancelBuild'),
|
||||
message: t('queue.confirmCancel'),
|
||||
onConfirm: () => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const { item, index } = buildingValidation.findQueueItem(gameStore.currentPlanet.buildQueue, queueId)
|
||||
if (!item) return false
|
||||
if (item.type === 'building') {
|
||||
const refund = buildingValidation.cancelBuildingUpgrade(gameStore.currentPlanet, item)
|
||||
resourceLogic.addResources(gameStore.currentPlanet.resources, refund)
|
||||
}
|
||||
gameStore.currentPlanet.buildQueue.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 取消研究
|
||||
const handleCancelResearch = (queueId: string) => {
|
||||
confirmDialog.value?.show({
|
||||
title: t('queue.cancelResearch'),
|
||||
message: t('queue.confirmCancel'),
|
||||
onConfirm: () => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const { item, index } = buildingValidation.findQueueItem(gameStore.player.researchQueue, queueId)
|
||||
if (!item) return false
|
||||
if (item.type === 'technology') {
|
||||
const refund = researchValidation.cancelTechnologyResearch(item)
|
||||
resourceLogic.addResources(gameStore.currentPlanet.resources, refund)
|
||||
}
|
||||
gameStore.player.researchQueue.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 平滑滚动 */
|
||||
main {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
||||
1
src/assets/logo.svg
Normal file
1
src/assets/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
51
src/components/AlertDialog.vue
Normal file
51
src/components/AlertDialog.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="fixed inset-0 bg-black/50" @click="handleClose" />
|
||||
<div class="relative bg-card border rounded-lg shadow-lg p-6 max-w-md w-full mx-4 z-10">
|
||||
<h2 class="text-lg font-semibold mb-2">{{ dialogProps?.title }}</h2>
|
||||
<p class="text-sm text-muted-foreground mb-6 whitespace-pre-line">{{ dialogProps?.message }}</p>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button v-if="dialogProps?.onConfirm" @click="handleClose" variant="outline">{{ t('common.cancel') }}</Button>
|
||||
<Button @click="handleConfirm" variant="default">{{ t('common.confirm') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface AlertDialogProps {
|
||||
title: string
|
||||
message: string
|
||||
onConfirm?: () => void
|
||||
}
|
||||
|
||||
const isOpen = ref(false)
|
||||
const dialogProps = ref<AlertDialogProps | null>(null)
|
||||
|
||||
const show = (props: AlertDialogProps) => {
|
||||
dialogProps.value = props
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (dialogProps.value?.onConfirm) {
|
||||
dialogProps.value.onConfirm()
|
||||
}
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
77
src/components/CardUnlockOverlay.vue
Normal file
77
src/components/CardUnlockOverlay.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div v-if="!isUnlocked" class="absolute inset-0 z-10 bg-background/70 backdrop-blur-[2px] rounded-lg flex items-center justify-center">
|
||||
<div class="text-center p-4 space-y-2">
|
||||
<div class="flex justify-center">
|
||||
<div class="rounded-full bg-muted p-2">
|
||||
<Lock :size="20" class="text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs font-medium text-muted-foreground">{{ t('common.locked') }}</p>
|
||||
<Button variant="outline" size="sm" @click="showRequirements" class="text-xs">
|
||||
{{ t('common.viewRequirements') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 前置条件详情对话框 -->
|
||||
<AlertDialog ref="requirementsDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useGameConfig } from '@/composables/useGameConfig'
|
||||
import { BuildingType, TechnologyType } from '@/types/game'
|
||||
import { Lock } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import AlertDialog from '@/components/AlertDialog.vue'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
|
||||
interface Props {
|
||||
requirements?: Partial<Record<BuildingType | TechnologyType, number>>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const gameStore = useGameStore()
|
||||
const { t } = useI18n()
|
||||
const { BUILDINGS, TECHNOLOGIES } = useGameConfig()
|
||||
const requirementsDialog = ref<InstanceType<typeof AlertDialog> | null>(null)
|
||||
|
||||
const isUnlocked = computed(() => {
|
||||
if (!props.requirements || !gameStore.currentPlanet) return true
|
||||
return publicLogic.checkRequirements(gameStore.currentPlanet, gameStore.player.technologies, props.requirements)
|
||||
})
|
||||
|
||||
const getRequirementsList = (): string => {
|
||||
if (!props.requirements || !gameStore.currentPlanet) return ''
|
||||
|
||||
const lines: string[] = []
|
||||
for (const [key, requiredLevel] of Object.entries(props.requirements)) {
|
||||
// 检查是否为建筑类型
|
||||
if (Object.values(BuildingType).includes(key as BuildingType)) {
|
||||
const buildingType = key as BuildingType
|
||||
const currentLevel = gameStore.currentPlanet.buildings[buildingType] || 0
|
||||
const name = BUILDINGS.value[buildingType]?.name || buildingType
|
||||
const status = currentLevel >= requiredLevel ? '✓' : '✗'
|
||||
lines.push(`${status} ${name}: Lv ${requiredLevel} (${t('common.current')}: Lv ${currentLevel})`)
|
||||
}
|
||||
// 检查是否为科技类型
|
||||
else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
|
||||
const techType = key as TechnologyType
|
||||
const currentLevel = gameStore.player.technologies[techType] || 0
|
||||
const name = TECHNOLOGIES.value[techType]?.name || techType
|
||||
const status = currentLevel >= requiredLevel ? '✓' : '✗'
|
||||
lines.push(`${status} ${name}: Lv ${requiredLevel} (${t('common.current')}: Lv ${currentLevel})`)
|
||||
}
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
const showRequirements = () => {
|
||||
requirementsDialog.value?.show({
|
||||
title: t('common.requirementsNotMet'),
|
||||
message: getRequirementsList()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
51
src/components/ConfirmDialog.vue
Normal file
51
src/components/ConfirmDialog.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="fixed inset-0 bg-black/50" @click="handleCancel" />
|
||||
<div class="relative bg-card border rounded-lg shadow-lg p-6 max-w-md w-full mx-4 z-10">
|
||||
<h2 class="text-lg font-semibold mb-2">{{ dialogProps?.title }}</h2>
|
||||
<p class="text-sm text-muted-foreground mb-6">{{ dialogProps?.message }}</p>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<Button @click="handleCancel" variant="outline">{{ t('common.cancel') }}</Button>
|
||||
<Button @click="handleConfirm" variant="default">{{ t('common.confirm') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface ConfirmDialogProps {
|
||||
title: string
|
||||
message: string
|
||||
onConfirm: () => void
|
||||
}
|
||||
|
||||
const isOpen = ref(false)
|
||||
const dialogProps = ref<ConfirmDialogProps | null>(null)
|
||||
|
||||
const show = (props: ConfirmDialogProps) => {
|
||||
dialogProps.value = props
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (dialogProps.value) {
|
||||
dialogProps.value.onConfirm()
|
||||
}
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
84
src/components/DetailDialog.vue
Normal file
84
src/components/DetailDialog.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<Dialog :open="dialogStore.isOpen" @update:open="handleClose">
|
||||
<DialogContent class="max-w-[calc(100%-1rem)] sm:max-w-[90vw] md:max-w-3xl lg:max-w-4xl max-h-[90vh] flex flex-col p-0">
|
||||
<!-- 建筑详情 -->
|
||||
<template v-if="dialogStore.type === 'building' && dialogStore.itemType">
|
||||
<DialogHeader class="px-6 pt-6 pb-4 shrink-0">
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
{{ t(`buildings.${dialogStore.itemType}`) }}
|
||||
<Badge variant="outline">{{ t('common.currentLevel') }} {{ dialogStore.currentLevel || 0 }}</Badge>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t(`buildingDescriptions.${dialogStore.itemType}`) }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="overflow-y-auto px-6 pb-6">
|
||||
<BuildingDetailView :buildingType="dialogStore.itemType as BuildingType" :currentLevel="dialogStore.currentLevel || 0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 科技详情 -->
|
||||
<template v-else-if="dialogStore.type === 'technology' && dialogStore.itemType">
|
||||
<DialogHeader class="px-6 pt-6 pb-4 shrink-0">
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
{{ t(`technologies.${dialogStore.itemType}`) }}
|
||||
<Badge variant="outline">{{ t('common.currentLevel') }} {{ dialogStore.currentLevel || 0 }}</Badge>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t(`technologyDescriptions.${dialogStore.itemType}`) }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="overflow-y-auto px-6 pb-6">
|
||||
<TechnologyDetailView :technologyType="dialogStore.itemType as TechnologyType" :currentLevel="dialogStore.currentLevel || 0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 舰船详情 -->
|
||||
<template v-else-if="dialogStore.type === 'ship' && dialogStore.itemType">
|
||||
<DialogHeader class="px-6 pt-6 pb-4 shrink-0">
|
||||
<DialogTitle>{{ t(`ships.${dialogStore.itemType}`) }}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t(`shipDescriptions.${dialogStore.itemType}`) }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="overflow-y-auto px-6 pb-6">
|
||||
<ShipDetailView :shipType="dialogStore.itemType as ShipType" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 防御详情 -->
|
||||
<template v-else-if="dialogStore.type === 'defense' && dialogStore.itemType">
|
||||
<DialogHeader class="px-6 pt-6 pb-4 shrink-0">
|
||||
<DialogTitle>{{ t(`defenses.${dialogStore.itemType}`) }}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t(`defenseDescriptions.${dialogStore.itemType}`) }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="overflow-y-auto px-6 pb-6">
|
||||
<DefenseDetailView :defenseType="dialogStore.itemType as DefenseType" />
|
||||
</div>
|
||||
</template>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { useDetailDialogStore } from '@/stores/detailDialogStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/game'
|
||||
import BuildingDetailView from './detail-views/BuildingDetailView.vue'
|
||||
import TechnologyDetailView from './detail-views/TechnologyDetailView.vue'
|
||||
import ShipDetailView from './detail-views/ShipDetailView.vue'
|
||||
import DefenseDetailView from './detail-views/DefenseDetailView.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dialogStore = useDetailDialogStore()
|
||||
|
||||
const handleClose = (open: boolean) => {
|
||||
if (!open) {
|
||||
dialogStore.close()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
28
src/components/ResourceIcon.vue
Normal file
28
src/components/ResourceIcon.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div :class="[colors[type], sizes[size], 'rounded shadow-sm']" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
type: 'metal' | 'crystal' | 'deuterium' | 'darkMatter' | 'energy'
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 'md'
|
||||
})
|
||||
|
||||
const colors = {
|
||||
metal: 'bg-gradient-to-br from-slate-400 to-slate-600',
|
||||
crystal: 'bg-gradient-to-br from-cyan-400 to-blue-600',
|
||||
deuterium: 'bg-gradient-to-br from-green-400 to-emerald-600',
|
||||
darkMatter: 'bg-gradient-to-br from-purple-600 to-indigo-900',
|
||||
energy: 'bg-gradient-to-br from-yellow-400 to-orange-500'
|
||||
}
|
||||
|
||||
const sizes = {
|
||||
sm: 'w-3 h-3',
|
||||
md: 'w-4 h-4',
|
||||
lg: 'w-5 h-5'
|
||||
}
|
||||
</script>
|
||||
73
src/components/UnlockRequirement.vue
Normal file
73
src/components/UnlockRequirement.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div v-if="!isUnlocked" class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<Card class="max-w-md w-full">
|
||||
<CardHeader class="text-center">
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="rounded-full bg-muted p-4">
|
||||
<Lock :size="48" class="text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle class="text-xl sm:text-2xl">{{ t('common.featureLocked') }}</CardTitle>
|
||||
<CardDescription class="text-sm sm:text-base">{{ t('common.unlockRequired') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="p-4 bg-muted rounded-lg space-y-2">
|
||||
<p class="text-sm font-medium text-center">{{ t('common.requiredBuilding') }}:</p>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<span class="text-base sm:text-lg font-bold">{{ buildingName }}</span>
|
||||
<Badge variant="default">Lv {{ requiredLevel }}</Badge>
|
||||
</div>
|
||||
<p v-if="currentLevel !== undefined" class="text-xs text-center text-muted-foreground">
|
||||
{{ t('common.currentLevel') }}: Lv {{ currentLevel }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Button @click="goToBuildings" class="flex-1">
|
||||
<Building2 :size="16" class="mr-2" />
|
||||
{{ t('common.goToBuildings') }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useGameConfig } from '@/composables/useGameConfig'
|
||||
import { BuildingType } from '@/types/game'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Lock, Building2 } from 'lucide-vue-next'
|
||||
|
||||
interface Props {
|
||||
requiredBuilding: BuildingType
|
||||
requiredLevel: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const { t } = useI18n()
|
||||
const { BUILDINGS } = useGameConfig()
|
||||
|
||||
const buildingName = computed(() => BUILDINGS.value[props.requiredBuilding]?.name || props.requiredBuilding)
|
||||
|
||||
const currentLevel = computed(() => {
|
||||
if (!gameStore.currentPlanet) return 0
|
||||
return gameStore.currentPlanet.buildings[props.requiredBuilding] || 0
|
||||
})
|
||||
|
||||
const isUnlocked = computed(() => {
|
||||
return currentLevel.value >= props.requiredLevel
|
||||
})
|
||||
|
||||
const goToBuildings = () => {
|
||||
router.push('/buildings')
|
||||
}
|
||||
</script>
|
||||
195
src/components/detail-views/BuildingDetailView.vue
Normal file
195
src/components/detail-views/BuildingDetailView.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 建筑等级范围表格 -->
|
||||
<div class="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-20 text-center">{{ t('buildings.levelRange') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.metal') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.crystal') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.deuterium') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('buildings.buildTime') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('buildings.production') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('buildings.consumption') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('player.points') }}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="level in levelRange" :key="level" :class="{ 'bg-muted/50': level === currentLevel }">
|
||||
<TableCell class="text-center font-medium">
|
||||
<Badge v-if="level === currentLevel" variant="default">{{ level }}</Badge>
|
||||
<span v-else>{{ level }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.metal) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.crystal) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.deuterium) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatTime(getLevelData(level).buildTime) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">
|
||||
<span v-if="getLevelData(level).production > 0" class="text-green-600 dark:text-green-400">
|
||||
+{{ formatNumber(getLevelData(level).production) }}/{{ t('resources.perHour') }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center text-sm">
|
||||
<span v-if="getLevelData(level).consumption > 0" class="text-red-600 dark:text-red-400">
|
||||
-{{ formatNumber(getLevelData(level).consumption) }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center text-sm">
|
||||
<span class="text-primary font-medium">+{{ getLevelData(level).points }}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<!-- 累积统计 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm">{{ t('buildings.totalCost') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.metal) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.crystal) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.deuterium) }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm">{{ t('buildings.totalPoints') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-3xl font-bold text-primary">{{ formatNumber(totalStats.points) }}</div>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
{{ t('buildings.levelRange') }}: {{ Math.max(0, currentLevel - 10) }} - {{ Math.min(currentLevel + 10, currentLevel + 10) }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { BuildingType } from '@/types/game'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import * as buildingLogic from '@/logic/buildingLogic'
|
||||
import * as pointsLogic from '@/logic/pointsLogic'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
buildingType: BuildingType
|
||||
currentLevel: number
|
||||
}>()
|
||||
|
||||
// 等级范围:当前等级 ±10
|
||||
const levelRange = computed(() => {
|
||||
const start = Math.max(0, props.currentLevel - 10)
|
||||
const end = props.currentLevel + 10
|
||||
const levels = []
|
||||
for (let i = start; i <= end; i++) {
|
||||
levels.push(i)
|
||||
}
|
||||
return levels
|
||||
})
|
||||
|
||||
// 获取某个等级的详细数据
|
||||
const getLevelData = (level: number) => {
|
||||
if (level === 0) {
|
||||
return {
|
||||
cost: { metal: 0, crystal: 0, deuterium: 0 },
|
||||
buildTime: 0,
|
||||
production: 0,
|
||||
consumption: 0,
|
||||
points: 0
|
||||
}
|
||||
}
|
||||
|
||||
const cost = buildingLogic.calculateBuildingCost(props.buildingType, level)
|
||||
const buildTime = buildingLogic.calculateBuildingTime(props.buildingType, level)
|
||||
|
||||
// 计算产量和消耗
|
||||
let production = 0
|
||||
let consumption = 0
|
||||
|
||||
// 资源矿产量
|
||||
if (props.buildingType === 'metalMine') {
|
||||
production = Math.floor(30 * level * Math.pow(1.1, level))
|
||||
} else if (props.buildingType === 'crystalMine') {
|
||||
production = Math.floor(20 * level * Math.pow(1.1, level))
|
||||
} else if (props.buildingType === 'deuteriumSynthesizer') {
|
||||
production = Math.floor(10 * level * Math.pow(1.1, level))
|
||||
}
|
||||
|
||||
// 能量产出
|
||||
if (props.buildingType === 'solarPlant') {
|
||||
production = Math.floor(20 * level * Math.pow(1.1, level))
|
||||
}
|
||||
|
||||
// 能量消耗(矿场和合成器)
|
||||
if (['metalMine', 'crystalMine', 'deuteriumSynthesizer'].includes(props.buildingType)) {
|
||||
consumption = Math.floor(10 * level * Math.pow(1.1, level))
|
||||
}
|
||||
|
||||
// 计算积分
|
||||
const points = pointsLogic.calculateBuildingPoints(props.buildingType, level - 1, level)
|
||||
|
||||
return {
|
||||
cost,
|
||||
buildTime,
|
||||
production,
|
||||
consumption,
|
||||
points
|
||||
}
|
||||
}
|
||||
|
||||
// 累积统计
|
||||
const totalStats = computed(() => {
|
||||
let metal = 0
|
||||
let crystal = 0
|
||||
let deuterium = 0
|
||||
let points = 0
|
||||
|
||||
for (const level of levelRange.value) {
|
||||
if (level === 0) continue
|
||||
const data = getLevelData(level)
|
||||
metal += data.cost.metal
|
||||
crystal += data.cost.crystal
|
||||
deuterium += data.cost.deuterium
|
||||
points += data.points
|
||||
}
|
||||
|
||||
return { metal, crystal, deuterium, points }
|
||||
})
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}${t('common.timeSecond')}`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
if (minutes < 60) return `${minutes}${t('common.timeMinute')}${secs}${t('common.timeSecond')}`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}${t('common.timeHour')}${mins}${t('common.timeMinute')}`
|
||||
}
|
||||
</script>
|
||||
168
src/components/detail-views/DefenseDetailView.vue
Normal file
168
src/components/detail-views/DefenseDetailView.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 防御基础信息 -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Sword class="h-4 w-4" />
|
||||
{{ t('defense.attack') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.attack) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Shield class="h-4 w-4" />
|
||||
{{ t('defense.shield') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.shield) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<ShieldCheck class="h-4 w-4" />
|
||||
{{ t('defense.armor') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.armor) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 建造成本和时间 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('defense.buildCost') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div v-if="config.cost.metal > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.metal) }}</span>
|
||||
</div>
|
||||
<div v-if="config.cost.crystal > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.crystal) }}</span>
|
||||
</div>
|
||||
<div v-if="config.cost.deuterium > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.deuterium) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm pt-2 border-t">
|
||||
<span class="text-muted-foreground">{{ t('player.points') }}:</span>
|
||||
<span class="font-bold text-primary">{{ pointsPerUnit }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('defense.buildTime') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-3xl font-bold">{{ formatTime(config.buildTime) }}</div>
|
||||
<p class="text-xs text-muted-foreground mt-2">{{ t('defense.perUnit') }}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 批量建造计算器 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('defense.batchCalculator') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<Label class="w-20">{{ t('defense.quantity') }}:</Label>
|
||||
<Input v-model.number="quantity" type="number" min="1" class="flex-1" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 pt-4 border-t">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t('defense.totalCost') }}:</p>
|
||||
<div class="space-y-1 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.metal) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.crystal) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.deuterium) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t('defense.totalTime') }}:</p>
|
||||
<div class="text-xl font-bold">{{ formatTime(config.buildTime * quantity) }}</div>
|
||||
<p class="text-xs text-muted-foreground">{{ t('player.points') }}: +{{ formatNumber(batchPoints) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { DefenseType } from '@/types/game'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Sword, Shield, ShieldCheck } from 'lucide-vue-next'
|
||||
import * as pointsLogic from '@/logic/pointsLogic'
|
||||
import { DEFENSES } from '@/config/gameConfig'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
defenseType: DefenseType
|
||||
}>()
|
||||
|
||||
const config = computed(() => DEFENSES[props.defenseType])
|
||||
const quantity = ref(1)
|
||||
|
||||
// 单个防御的积分
|
||||
const pointsPerUnit = computed(() => {
|
||||
return pointsLogic.calculateDefensePoints(props.defenseType, 1)
|
||||
})
|
||||
|
||||
// 批量建造成本
|
||||
const batchCost = computed(() => ({
|
||||
metal: config.value.cost.metal * quantity.value,
|
||||
crystal: config.value.cost.crystal * quantity.value,
|
||||
deuterium: config.value.cost.deuterium * quantity.value
|
||||
}))
|
||||
|
||||
// 批量建造积分
|
||||
const batchPoints = computed(() => {
|
||||
return pointsLogic.calculateDefensePoints(props.defenseType, quantity.value)
|
||||
})
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}${t('common.timeSecond')}`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
if (minutes < 60) return `${minutes}${t('common.timeMinute')}${secs}${t('common.timeSecond')}`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}${t('common.timeHour')}${mins}${t('common.timeMinute')}`
|
||||
}
|
||||
</script>
|
||||
204
src/components/detail-views/ShipDetailView.vue
Normal file
204
src/components/detail-views/ShipDetailView.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 舰船基础信息 -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Sword class="h-4 w-4" />
|
||||
{{ t('shipyard.attack') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.attack) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Shield class="h-4 w-4" />
|
||||
{{ t('shipyard.shield') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.shield) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<ShieldCheck class="h-4 w-4" />
|
||||
{{ t('shipyard.armor') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.armor) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Zap class="h-4 w-4" />
|
||||
{{ t('shipyard.speed') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.speed) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Package class="h-4 w-4" />
|
||||
{{ t('shipyard.cargoCapacity') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.cargoCapacity) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Fuel class="h-4 w-4" />
|
||||
{{ t('shipyard.fuelConsumption') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.fuelConsumption) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 建造成本和时间 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('shipyard.buildCost') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div v-if="config.cost.metal > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.metal) }}</span>
|
||||
</div>
|
||||
<div v-if="config.cost.crystal > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.crystal) }}</span>
|
||||
</div>
|
||||
<div v-if="config.cost.deuterium > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.deuterium) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm pt-2 border-t">
|
||||
<span class="text-muted-foreground">{{ t('player.points') }}:</span>
|
||||
<span class="font-bold text-primary">{{ pointsPerUnit }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('shipyard.buildTime') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-3xl font-bold">{{ formatTime(config.buildTime) }}</div>
|
||||
<p class="text-xs text-muted-foreground mt-2">{{ t('shipyard.perUnit') }}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 批量建造计算器 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('shipyard.batchCalculator') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<Label class="w-20">{{ t('shipyard.quantity') }}:</Label>
|
||||
<Input v-model.number="quantity" type="number" min="1" class="flex-1" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 pt-4 border-t">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t('shipyard.totalCost') }}:</p>
|
||||
<div class="space-y-1 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.metal) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.crystal) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.deuterium) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t('shipyard.totalTime') }}:</p>
|
||||
<div class="text-xl font-bold">{{ formatTime(config.buildTime * quantity) }}</div>
|
||||
<p class="text-xs text-muted-foreground">{{ t('player.points') }}: +{{ formatNumber(batchPoints) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { ShipType } from '@/types/game'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Sword, Shield, ShieldCheck, Zap, Package, Fuel } from 'lucide-vue-next'
|
||||
import * as pointsLogic from '@/logic/pointsLogic'
|
||||
import { SHIPS } from '@/config/gameConfig'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
shipType: ShipType
|
||||
}>()
|
||||
|
||||
const config = computed(() => SHIPS[props.shipType])
|
||||
const quantity = ref(1)
|
||||
|
||||
// 单艘舰船的积分
|
||||
const pointsPerUnit = computed(() => {
|
||||
return pointsLogic.calculateShipPoints(props.shipType, 1)
|
||||
})
|
||||
|
||||
// 批量建造成本
|
||||
const batchCost = computed(() => ({
|
||||
metal: config.value.cost.metal * quantity.value,
|
||||
crystal: config.value.cost.crystal * quantity.value,
|
||||
deuterium: config.value.cost.deuterium * quantity.value
|
||||
}))
|
||||
|
||||
// 批量建造积分
|
||||
const batchPoints = computed(() => {
|
||||
return pointsLogic.calculateShipPoints(props.shipType, quantity.value)
|
||||
})
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}${t('common.timeSecond')}`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
if (minutes < 60) return `${minutes}${t('common.timeMinute')}${secs}${t('common.timeSecond')}`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}${t('common.timeHour')}${mins}${t('common.timeMinute')}`
|
||||
}
|
||||
</script>
|
||||
154
src/components/detail-views/TechnologyDetailView.vue
Normal file
154
src/components/detail-views/TechnologyDetailView.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 科技等级范围表格 -->
|
||||
<div class="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-20 text-center">{{ t('research.levelRange') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.metal') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.crystal') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.deuterium') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('research.researchTime') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('player.points') }}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="level in levelRange" :key="level" :class="{ 'bg-muted/50': level === currentLevel }">
|
||||
<TableCell class="text-center font-medium">
|
||||
<Badge v-if="level === currentLevel" variant="default">{{ level }}</Badge>
|
||||
<span v-else>{{ level }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.metal) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.crystal) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.deuterium) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatTime(getLevelData(level).researchTime) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">
|
||||
<span class="text-primary font-medium">+{{ getLevelData(level).points }}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<!-- 累积统计 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm">{{ t('research.totalCost') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.metal) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.crystal) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.deuterium) }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm">{{ t('research.totalPoints') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-3xl font-bold text-primary">{{ formatNumber(totalStats.points) }}</div>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
{{ t('research.levelRange') }}: {{ Math.max(0, currentLevel - 10) }} - {{ Math.min(currentLevel + 10, currentLevel + 10) }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { TechnologyType } from '@/types/game'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import * as researchLogic from '@/logic/researchLogic'
|
||||
import * as pointsLogic from '@/logic/pointsLogic'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
technologyType: TechnologyType
|
||||
currentLevel: number
|
||||
}>()
|
||||
|
||||
// 等级范围:当前等级 ±10
|
||||
const levelRange = computed(() => {
|
||||
const start = Math.max(0, props.currentLevel - 10)
|
||||
const end = props.currentLevel + 10
|
||||
const levels = []
|
||||
for (let i = start; i <= end; i++) {
|
||||
levels.push(i)
|
||||
}
|
||||
return levels
|
||||
})
|
||||
|
||||
// 获取某个等级的详细数据
|
||||
const getLevelData = (level: number) => {
|
||||
if (level === 0) {
|
||||
return {
|
||||
cost: { metal: 0, crystal: 0, deuterium: 0 },
|
||||
researchTime: 0,
|
||||
points: 0
|
||||
}
|
||||
}
|
||||
|
||||
const cost = researchLogic.calculateTechnologyCost(props.technologyType, level)
|
||||
const researchTime = researchLogic.calculateTechnologyTime(props.technologyType, level - 1)
|
||||
|
||||
// 计算积分
|
||||
const points = pointsLogic.calculateTechnologyPoints(props.technologyType, level - 1, level)
|
||||
|
||||
return {
|
||||
cost,
|
||||
researchTime,
|
||||
points
|
||||
}
|
||||
}
|
||||
|
||||
// 累积统计
|
||||
const totalStats = computed(() => {
|
||||
let metal = 0
|
||||
let crystal = 0
|
||||
let deuterium = 0
|
||||
let points = 0
|
||||
|
||||
for (const level of levelRange.value) {
|
||||
if (level === 0) continue
|
||||
const data = getLevelData(level)
|
||||
metal += data.cost.metal
|
||||
crystal += data.cost.crystal
|
||||
deuterium += data.cost.deuterium
|
||||
points += data.points
|
||||
}
|
||||
|
||||
return { metal, crystal, deuterium, points }
|
||||
})
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}${t('common.timeSecond')}`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
if (minutes < 60) return `${minutes}${t('common.timeMinute')}${secs}${t('common.timeSecond')}`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}${t('common.timeHour')}${mins}${t('common.timeMinute')}`
|
||||
}
|
||||
</script>
|
||||
15
src/components/ui/alert-dialog/AlertDialog.vue
Normal file
15
src/components/ui/alert-dialog/AlertDialog.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<AlertDialogRoot v-slot="slotProps" data-slot="alert-dialog" v-bind="forwarded">
|
||||
<slot v-bind="slotProps" />
|
||||
</AlertDialogRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogEmits, AlertDialogProps } from 'reka-ui'
|
||||
import { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps<AlertDialogProps>()
|
||||
const emits = defineEmits<AlertDialogEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
18
src/components/ui/alert-dialog/AlertDialogAction.vue
Normal file
18
src/components/ui/alert-dialog/AlertDialogAction.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
|
||||
<slot />
|
||||
</AlertDialogAction>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogActionProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogAction } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
18
src/components/ui/alert-dialog/AlertDialogCancel.vue
Normal file
18
src/components/ui/alert-dialog/AlertDialogCancel.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<AlertDialogCancel v-bind="delegatedProps" :class="cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', props.class)">
|
||||
<slot />
|
||||
</AlertDialogCancel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogCancelProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogCancel } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = defineProps<AlertDialogCancelProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
39
src/components/ui/alert-dialog/AlertDialogContent.vue
Normal file
39
src/components/ui/alert-dialog/AlertDialogContent.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay
|
||||
data-slot="alert-dialog-overlay"
|
||||
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80"
|
||||
/>
|
||||
<AlertDialogContent
|
||||
data-slot="alert-dialog-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogContentEmits, AlertDialogContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogContent, AlertDialogOverlay, AlertDialogPortal, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<AlertDialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
21
src/components/ui/alert-dialog/AlertDialogDescription.vue
Normal file
21
src/components/ui/alert-dialog/AlertDialogDescription.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<AlertDialogDescription
|
||||
data-slot="alert-dialog-description"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</AlertDialogDescription>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogDescriptionProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogDescription } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
14
src/components/ui/alert-dialog/AlertDialogFooter.vue
Normal file
14
src/components/ui/alert-dialog/AlertDialogFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="alert-dialog-footer" :class="cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/alert-dialog/AlertDialogHeader.vue
Normal file
14
src/components/ui/alert-dialog/AlertDialogHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="alert-dialog-header" :class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
17
src/components/ui/alert-dialog/AlertDialogTitle.vue
Normal file
17
src/components/ui/alert-dialog/AlertDialogTitle.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<AlertDialogTitle data-slot="alert-dialog-title" v-bind="delegatedProps" :class="cn('text-lg font-semibold', props.class)">
|
||||
<slot />
|
||||
</AlertDialogTitle>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogTitleProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogTitle } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
12
src/components/ui/alert-dialog/AlertDialogTrigger.vue
Normal file
12
src/components/ui/alert-dialog/AlertDialogTrigger.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<AlertDialogTrigger data-slot="alert-dialog-trigger" v-bind="props">
|
||||
<slot />
|
||||
</AlertDialogTrigger>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogTriggerProps } from 'reka-ui'
|
||||
import { AlertDialogTrigger } from 'reka-ui'
|
||||
|
||||
const props = defineProps<AlertDialogTriggerProps>()
|
||||
</script>
|
||||
9
src/components/ui/alert-dialog/index.ts
Normal file
9
src/components/ui/alert-dialog/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { default as AlertDialog } from './AlertDialog.vue'
|
||||
export { default as AlertDialogAction } from './AlertDialogAction.vue'
|
||||
export { default as AlertDialogCancel } from './AlertDialogCancel.vue'
|
||||
export { default as AlertDialogContent } from './AlertDialogContent.vue'
|
||||
export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
|
||||
export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
|
||||
export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
|
||||
export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
|
||||
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'
|
||||
24
src/components/ui/badge/Badge.vue
Normal file
24
src/components/ui/badge/Badge.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<Primitive data-slot="badge" :class="cn(badgeVariants({ variant }), props.class)" v-bind="delegatedProps">
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { BadgeVariants } from '.'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { badgeVariants } from '.'
|
||||
|
||||
const props = defineProps<
|
||||
PrimitiveProps & {
|
||||
variant?: BadgeVariants['variant']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
23
src/components/ui/badge/index.ts
Normal file
23
src/components/ui/badge/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Badge } from './Badge.vue'
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
||||
secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||
24
src/components/ui/button/Button.vue
Normal file
24
src/components/ui/button/Button.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<Primitive data-slot="button" :as="as" :as-child="asChild" :class="cn(buttonVariants({ variant, size }), props.class)">
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '.'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '.'
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants['variant']
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button'
|
||||
})
|
||||
</script>
|
||||
35
src/components/ui/button/index.ts
Normal file
35
src/components/ui/button/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Button } from './Button.vue'
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline:
|
||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
link: 'text-primary underline-offset-4 hover:underline'
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||
14
src/components/ui/card/Card.vue
Normal file
14
src/components/ui/card/Card.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="card" :class="cn('bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardAction.vue
Normal file
14
src/components/ui/card/CardAction.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="card-action" :class="cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardContent.vue
Normal file
14
src/components/ui/card/CardContent.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="card-content" :class="cn('px-6', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardDescription.vue
Normal file
14
src/components/ui/card/CardDescription.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<p data-slot="card-description" :class="cn('text-muted-foreground text-sm', props.class)">
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardFooter.vue
Normal file
14
src/components/ui/card/CardFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="card-footer" :class="cn('flex items-center px-6 [.border-t]:pt-6', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
22
src/components/ui/card/CardHeader.vue
Normal file
22
src/components/ui/card/CardHeader.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div
|
||||
data-slot="card-header"
|
||||
:class="
|
||||
cn(
|
||||
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardTitle.vue
Normal file
14
src/components/ui/card/CardTitle.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<h3 data-slot="card-title" :class="cn('leading-none font-semibold', props.class)">
|
||||
<slot />
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
7
src/components/ui/card/index.ts
Normal file
7
src/components/ui/card/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { default as Card } from './Card.vue'
|
||||
export { default as CardAction } from './CardAction.vue'
|
||||
export { default as CardContent } from './CardContent.vue'
|
||||
export { default as CardDescription } from './CardDescription.vue'
|
||||
export { default as CardFooter } from './CardFooter.vue'
|
||||
export { default as CardHeader } from './CardHeader.vue'
|
||||
export { default as CardTitle } from './CardTitle.vue'
|
||||
15
src/components/ui/dialog/Dialog.vue
Normal file
15
src/components/ui/dialog/Dialog.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<DialogRoot v-slot="slotProps" data-slot="dialog" v-bind="forwarded">
|
||||
<slot v-bind="slotProps" />
|
||||
</DialogRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogRootEmits, DialogRootProps } from 'reka-ui'
|
||||
import { DialogRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogRootProps>()
|
||||
const emits = defineEmits<DialogRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
12
src/components/ui/dialog/DialogClose.vue
Normal file
12
src/components/ui/dialog/DialogClose.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<DialogClose data-slot="dialog-close" v-bind="props">
|
||||
<slot />
|
||||
</DialogClose>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogCloseProps } from 'reka-ui'
|
||||
import { DialogClose } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogCloseProps>()
|
||||
</script>
|
||||
49
src/components/ui/dialog/DialogContent.vue
Normal file
49
src/components/ui/dialog/DialogContent.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogContent
|
||||
data-slot="dialog-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose
|
||||
v-if="showCloseButton"
|
||||
data-slot="dialog-close"
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<X />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { X } from 'lucide-vue-next'
|
||||
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import DialogOverlay from './DialogOverlay.vue'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<DialogContentProps & { class?: HTMLAttributes['class']; showCloseButton?: boolean }>(), {
|
||||
showCloseButton: true
|
||||
})
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
19
src/components/ui/dialog/DialogDescription.vue
Normal file
19
src/components/ui/dialog/DialogDescription.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<DialogDescription data-slot="dialog-description" v-bind="forwardedProps" :class="cn('text-muted-foreground text-sm', props.class)">
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogDescriptionProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogDescription, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
12
src/components/ui/dialog/DialogFooter.vue
Normal file
12
src/components/ui/dialog/DialogFooter.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div data-slot="dialog-footer" :class="cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
</script>
|
||||
14
src/components/ui/dialog/DialogHeader.vue
Normal file
14
src/components/ui/dialog/DialogHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="dialog-header" :class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
26
src/components/ui/dialog/DialogOverlay.vue
Normal file
26
src/components/ui/dialog/DialogOverlay.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<DialogOverlay
|
||||
data-slot="dialog-overlay"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogOverlayProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogOverlay } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
51
src/components/ui/dialog/DialogScrollContent.vue
Normal file
51
src/components/ui/dialog/DialogScrollContent.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay
|
||||
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||
>
|
||||
<DialogContent
|
||||
:class="
|
||||
cn(
|
||||
'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
@pointer-down-outside="(event) => {
|
||||
const originalEvent = event.detail.originalEvent;
|
||||
const target = originalEvent.target as HTMLElement;
|
||||
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary">
|
||||
<X class="w-4 h-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogOverlay>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { X } from 'lucide-vue-next'
|
||||
import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
19
src/components/ui/dialog/DialogTitle.vue
Normal file
19
src/components/ui/dialog/DialogTitle.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<DialogTitle data-slot="dialog-title" v-bind="forwardedProps" :class="cn('text-lg leading-none font-semibold', props.class)">
|
||||
<slot />
|
||||
</DialogTitle>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogTitleProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogTitle, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
12
src/components/ui/dialog/DialogTrigger.vue
Normal file
12
src/components/ui/dialog/DialogTrigger.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<DialogTrigger data-slot="dialog-trigger" v-bind="props">
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogTriggerProps } from 'reka-ui'
|
||||
import { DialogTrigger } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogTriggerProps>()
|
||||
</script>
|
||||
10
src/components/ui/dialog/index.ts
Normal file
10
src/components/ui/dialog/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export { default as Dialog } from './Dialog.vue'
|
||||
export { default as DialogClose } from './DialogClose.vue'
|
||||
export { default as DialogContent } from './DialogContent.vue'
|
||||
export { default as DialogDescription } from './DialogDescription.vue'
|
||||
export { default as DialogFooter } from './DialogFooter.vue'
|
||||
export { default as DialogHeader } from './DialogHeader.vue'
|
||||
export { default as DialogOverlay } from './DialogOverlay.vue'
|
||||
export { default as DialogScrollContent } from './DialogScrollContent.vue'
|
||||
export { default as DialogTitle } from './DialogTitle.vue'
|
||||
export { default as DialogTrigger } from './DialogTrigger.vue'
|
||||
35
src/components/ui/input/Input.vue
Normal file
35
src/components/ui/input/Input.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="
|
||||
cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
defaultValue?: string | number
|
||||
modelValue?: string | number
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', payload: string | number): void
|
||||
}>()
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue
|
||||
})
|
||||
</script>
|
||||
1
src/components/ui/input/index.ts
Normal file
1
src/components/ui/input/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Input } from './Input.vue'
|
||||
26
src/components/ui/label/Label.vue
Normal file
26
src/components/ui/label/Label.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<Label
|
||||
data-slot="label"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LabelProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Label } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
1
src/components/ui/label/index.ts
Normal file
1
src/components/ui/label/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Label } from './Label.vue'
|
||||
19
src/components/ui/popover/Popover.vue
Normal file
19
src/components/ui/popover/Popover.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverRootEmits, PopoverRootProps } from "reka-ui"
|
||||
import { PopoverRoot, useForwardPropsEmits } from "reka-ui"
|
||||
|
||||
const props = defineProps<PopoverRootProps>()
|
||||
const emits = defineEmits<PopoverRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverRoot
|
||||
v-slot="slotProps"
|
||||
data-slot="popover"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</PopoverRoot>
|
||||
</template>
|
||||
15
src/components/ui/popover/PopoverAnchor.vue
Normal file
15
src/components/ui/popover/PopoverAnchor.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverAnchorProps } from "reka-ui"
|
||||
import { PopoverAnchor } from "reka-ui"
|
||||
|
||||
const props = defineProps<PopoverAnchorProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverAnchor
|
||||
data-slot="popover-anchor"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</PopoverAnchor>
|
||||
</template>
|
||||
45
src/components/ui/popover/PopoverContent.vue
Normal file
45
src/components/ui/popover/PopoverContent.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverContentEmits, PopoverContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import {
|
||||
PopoverContent,
|
||||
PopoverPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<PopoverContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
{
|
||||
align: "center",
|
||||
sideOffset: 4,
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<PopoverContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverPortal>
|
||||
<PopoverContent
|
||||
data-slot="popover-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md origin-(--reka-popover-content-transform-origin) outline-hidden',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</PopoverContent>
|
||||
</PopoverPortal>
|
||||
</template>
|
||||
15
src/components/ui/popover/PopoverTrigger.vue
Normal file
15
src/components/ui/popover/PopoverTrigger.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverTriggerProps } from "reka-ui"
|
||||
import { PopoverTrigger } from "reka-ui"
|
||||
|
||||
const props = defineProps<PopoverTriggerProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverTrigger
|
||||
data-slot="popover-trigger"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</PopoverTrigger>
|
||||
</template>
|
||||
4
src/components/ui/popover/index.ts
Normal file
4
src/components/ui/popover/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as Popover } from "./Popover.vue"
|
||||
export { default as PopoverAnchor } from "./PopoverAnchor.vue"
|
||||
export { default as PopoverContent } from "./PopoverContent.vue"
|
||||
export { default as PopoverTrigger } from "./PopoverTrigger.vue"
|
||||
27
src/components/ui/progress/Progress.vue
Normal file
27
src/components/ui/progress/Progress.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<ProgressRoot
|
||||
data-slot="progress"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('bg-primary/20 relative h-2 w-full overflow-hidden rounded-full', props.class)"
|
||||
>
|
||||
<ProgressIndicator
|
||||
data-slot="progress-indicator"
|
||||
class="bg-primary h-full w-full flex-1 transition-all"
|
||||
:style="`transform: translateX(-${100 - (props.modelValue ?? 0)}%);`"
|
||||
/>
|
||||
</ProgressRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ProgressRootProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ProgressIndicator, ProgressRoot } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = withDefaults(defineProps<ProgressRootProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
modelValue: 0
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
1
src/components/ui/progress/index.ts
Normal file
1
src/components/ui/progress/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Progress } from './Progress.vue'
|
||||
15
src/components/ui/select/Select.vue
Normal file
15
src/components/ui/select/Select.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<SelectRoot v-slot="slotProps" data-slot="select" v-bind="forwarded">
|
||||
<slot v-bind="slotProps" />
|
||||
</SelectRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectRootEmits, SelectRootProps } from 'reka-ui'
|
||||
import { SelectRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps<SelectRootProps>()
|
||||
const emits = defineEmits<SelectRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
51
src/components/ui/select/SelectContent.vue
Normal file
51
src/components/ui/select/SelectContent.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
data-slot="select-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--reka-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
||||
position === 'popper' &&
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectViewport
|
||||
:class="
|
||||
cn(
|
||||
'p-1',
|
||||
position === 'popper' && 'h-[var(--reka-select-trigger-height)] w-full min-w-[var(--reka-select-trigger-width)] scroll-my-1'
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</SelectViewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectContentEmits, SelectContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { SelectContent, SelectPortal, SelectViewport, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SelectScrollDownButton, SelectScrollUpButton } from '.'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
position: 'popper'
|
||||
})
|
||||
const emits = defineEmits<SelectContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
12
src/components/ui/select/SelectGroup.vue
Normal file
12
src/components/ui/select/SelectGroup.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<SelectGroup data-slot="select-group" v-bind="props">
|
||||
<slot />
|
||||
</SelectGroup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectGroupProps } from 'reka-ui'
|
||||
import { SelectGroup } from 'reka-ui'
|
||||
|
||||
const props = defineProps<SelectGroupProps>()
|
||||
</script>
|
||||
39
src/components/ui/select/SelectItem.vue
Normal file
39
src/components/ui/select/SelectItem.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<SelectItem
|
||||
data-slot="select-item"
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<span class="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<SelectItemIndicator>
|
||||
<slot name="indicator-icon">
|
||||
<Check class="size-4" />
|
||||
</slot>
|
||||
</SelectItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectItemText>
|
||||
<slot />
|
||||
</SelectItemText>
|
||||
</SelectItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectItemProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import { SelectItem, SelectItemIndicator, SelectItemText, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<SelectItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
12
src/components/ui/select/SelectItemText.vue
Normal file
12
src/components/ui/select/SelectItemText.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<SelectItemText data-slot="select-item-text" v-bind="props">
|
||||
<slot />
|
||||
</SelectItemText>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectItemTextProps } from 'reka-ui'
|
||||
import { SelectItemText } from 'reka-ui'
|
||||
|
||||
const props = defineProps<SelectItemTextProps>()
|
||||
</script>
|
||||
14
src/components/ui/select/SelectLabel.vue
Normal file
14
src/components/ui/select/SelectLabel.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<SelectLabel data-slot="select-label" :class="cn('text-muted-foreground px-2 py-1.5 text-xs', props.class)">
|
||||
<slot />
|
||||
</SelectLabel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectLabelProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { SelectLabel } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<SelectLabelProps & { class?: HTMLAttributes['class'] }>()
|
||||
</script>
|
||||
26
src/components/ui/select/SelectScrollDownButton.vue
Normal file
26
src/components/ui/select/SelectScrollDownButton.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<SelectScrollDownButton
|
||||
data-slot="select-scroll-down-button"
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('flex cursor-default items-center justify-center py-1', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<ChevronDown class="size-4" />
|
||||
</slot>
|
||||
</SelectScrollDownButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectScrollDownButtonProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronDown } from 'lucide-vue-next'
|
||||
import { SelectScrollDownButton, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<SelectScrollDownButtonProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
26
src/components/ui/select/SelectScrollUpButton.vue
Normal file
26
src/components/ui/select/SelectScrollUpButton.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<SelectScrollUpButton
|
||||
data-slot="select-scroll-up-button"
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('flex cursor-default items-center justify-center py-1', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<ChevronUp class="size-4" />
|
||||
</slot>
|
||||
</SelectScrollUpButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectScrollUpButtonProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronUp } from 'lucide-vue-next'
|
||||
import { SelectScrollUpButton, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<SelectScrollUpButtonProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
19
src/components/ui/select/SelectSeparator.vue
Normal file
19
src/components/ui/select/SelectSeparator.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<SelectSeparator
|
||||
data-slot="select-separator"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('bg-border pointer-events-none -mx-1 my-1 h-px', props.class)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectSeparatorProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { SelectSeparator } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<SelectSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
34
src/components/ui/select/SelectTrigger.vue
Normal file
34
src/components/ui/select/SelectTrigger.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<SelectTrigger
|
||||
data-slot="select-trigger"
|
||||
:data-size="size"
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
<SelectIcon as-child>
|
||||
<ChevronDown class="size-4 opacity-50" />
|
||||
</SelectIcon>
|
||||
</SelectTrigger>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectTriggerProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ChevronDown } from 'lucide-vue-next'
|
||||
import { SelectIcon, SelectTrigger, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = withDefaults(defineProps<SelectTriggerProps & { class?: HTMLAttributes['class']; size?: 'sm' | 'default' }>(), {
|
||||
size: 'default'
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'size')
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
12
src/components/ui/select/SelectValue.vue
Normal file
12
src/components/ui/select/SelectValue.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<SelectValue data-slot="select-value" v-bind="props">
|
||||
<slot />
|
||||
</SelectValue>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectValueProps } from 'reka-ui'
|
||||
import { SelectValue } from 'reka-ui'
|
||||
|
||||
const props = defineProps<SelectValueProps>()
|
||||
</script>
|
||||
11
src/components/ui/select/index.ts
Normal file
11
src/components/ui/select/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { default as Select } from './Select.vue'
|
||||
export { default as SelectContent } from './SelectContent.vue'
|
||||
export { default as SelectGroup } from './SelectGroup.vue'
|
||||
export { default as SelectItem } from './SelectItem.vue'
|
||||
export { default as SelectItemText } from './SelectItemText.vue'
|
||||
export { default as SelectLabel } from './SelectLabel.vue'
|
||||
export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue'
|
||||
export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue'
|
||||
export { default as SelectSeparator } from './SelectSeparator.vue'
|
||||
export { default as SelectTrigger } from './SelectTrigger.vue'
|
||||
export { default as SelectValue } from './SelectValue.vue'
|
||||
42
src/components/ui/sonner/Sonner.vue
Normal file
42
src/components/ui/sonner/Sonner.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ToasterProps } from "vue-sonner"
|
||||
import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, XIcon } from "lucide-vue-next"
|
||||
import { Toaster as Sonner } from "vue-sonner"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<ToasterProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sonner
|
||||
:class="cn('toaster group', props.class)"
|
||||
:style="{
|
||||
'--normal-bg': 'var(--popover)',
|
||||
'--normal-text': 'var(--popover-foreground)',
|
||||
'--normal-border': 'var(--border)',
|
||||
'--border-radius': 'var(--radius)',
|
||||
}"
|
||||
v-bind="props"
|
||||
>
|
||||
<template #success-icon>
|
||||
<CircleCheckIcon class="size-4" />
|
||||
</template>
|
||||
<template #info-icon>
|
||||
<InfoIcon class="size-4" />
|
||||
</template>
|
||||
<template #warning-icon>
|
||||
<TriangleAlertIcon class="size-4" />
|
||||
</template>
|
||||
<template #error-icon>
|
||||
<OctagonXIcon class="size-4" />
|
||||
</template>
|
||||
<template #loading-icon>
|
||||
<div>
|
||||
<Loader2Icon class="size-4 animate-spin" />
|
||||
</div>
|
||||
</template>
|
||||
<template #close-icon>
|
||||
<XIcon class="size-4" />
|
||||
</template>
|
||||
</Sonner>
|
||||
</template>
|
||||
1
src/components/ui/sonner/index.ts
Normal file
1
src/components/ui/sonner/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Toaster } from "./Sonner.vue"
|
||||
16
src/components/ui/table/Table.vue
Normal file
16
src/components/ui/table/Table.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div data-slot="table-container" class="relative w-full overflow-auto">
|
||||
<table data-slot="table" :class="cn('w-full caption-bottom text-sm', props.class)">
|
||||
<slot />
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/table/TableBody.vue
Normal file
14
src/components/ui/table/TableBody.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<tbody data-slot="table-body" :class="cn('[&_tr:last-child]:border-0', props.class)">
|
||||
<slot />
|
||||
</tbody>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/table/TableCaption.vue
Normal file
14
src/components/ui/table/TableCaption.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<caption data-slot="table-caption" :class="cn('text-muted-foreground mt-4 text-sm', props.class)">
|
||||
<slot />
|
||||
</caption>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
17
src/components/ui/table/TableCell.vue
Normal file
17
src/components/ui/table/TableCell.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<td
|
||||
data-slot="table-cell"
|
||||
:class="cn('p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
29
src/components/ui/table/TableEmpty.vue
Normal file
29
src/components/ui/table/TableEmpty.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<TableRow>
|
||||
<TableCell :class="cn('p-4 whitespace-nowrap align-middle text-sm text-foreground', props.class)" v-bind="delegatedProps">
|
||||
<div class="flex items-center justify-center py-10">
|
||||
<slot />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { cn } from '@/lib/utils'
|
||||
import TableCell from './TableCell.vue'
|
||||
import TableRow from './TableRow.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
colspan?: number
|
||||
}>(),
|
||||
{
|
||||
colspan: 1
|
||||
}
|
||||
)
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
14
src/components/ui/table/TableFooter.vue
Normal file
14
src/components/ui/table/TableFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<tfoot data-slot="table-footer" :class="cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', props.class)">
|
||||
<slot />
|
||||
</tfoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
22
src/components/ui/table/TableHead.vue
Normal file
22
src/components/ui/table/TableHead.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<th
|
||||
data-slot="table-head"
|
||||
:class="
|
||||
cn(
|
||||
'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</th>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/table/TableHeader.vue
Normal file
14
src/components/ui/table/TableHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<thead data-slot="table-header" :class="cn('[&_tr]:border-b', props.class)">
|
||||
<slot />
|
||||
</thead>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/table/TableRow.vue
Normal file
14
src/components/ui/table/TableRow.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<tr data-slot="table-row" :class="cn('hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors', props.class)">
|
||||
<slot />
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
9
src/components/ui/table/index.ts
Normal file
9
src/components/ui/table/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { default as Table } from './Table.vue'
|
||||
export { default as TableBody } from './TableBody.vue'
|
||||
export { default as TableCaption } from './TableCaption.vue'
|
||||
export { default as TableCell } from './TableCell.vue'
|
||||
export { default as TableEmpty } from './TableEmpty.vue'
|
||||
export { default as TableFooter } from './TableFooter.vue'
|
||||
export { default as TableHead } from './TableHead.vue'
|
||||
export { default as TableHeader } from './TableHeader.vue'
|
||||
export { default as TableRow } from './TableRow.vue'
|
||||
8
src/components/ui/table/utils.ts
Normal file
8
src/components/ui/table/utils.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Updater } from '@tanstack/vue-table'
|
||||
|
||||
import type { Ref } from 'vue'
|
||||
import { isFunction } from '@tanstack/vue-table'
|
||||
|
||||
export const valueUpdater = <T>(updaterOrValue: Updater<T>, ref: Ref<T>) => {
|
||||
ref.value = isFunction(updaterOrValue) ? updaterOrValue(ref.value) : updaterOrValue
|
||||
}
|
||||
173
src/composables/useGameConfig.ts
Normal file
173
src/composables/useGameConfig.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from './useI18n'
|
||||
import {
|
||||
BUILDINGS as ORIGINAL_BUILDINGS,
|
||||
SHIPS as ORIGINAL_SHIPS,
|
||||
DEFENSES as ORIGINAL_DEFENSES,
|
||||
TECHNOLOGIES as ORIGINAL_TECHNOLOGIES,
|
||||
OFFICERS as ORIGINAL_OFFICERS
|
||||
} from '@/config/gameConfig'
|
||||
|
||||
import { BuildingType, ShipType, DefenseType, TechnologyType, OfficerType } from '@/types/game'
|
||||
|
||||
import type { BuildingConfig, ShipConfig, DefenseConfig, TechnologyConfig, OfficerConfig } from '@/types/game'
|
||||
|
||||
/**
|
||||
* 提供翻译后的游戏配置对象的 Composable
|
||||
* 将 i18n 系统的翻译覆盖到原始 gameConfig 上
|
||||
*/
|
||||
export const useGameConfig = () => {
|
||||
const { t } = useI18n()
|
||||
|
||||
// 建筑类型枚举值到翻译键的映射
|
||||
const buildingKeyMap: Record<BuildingType, string> = {
|
||||
[BuildingType.MetalMine]: 'metalMine',
|
||||
[BuildingType.CrystalMine]: 'crystalMine',
|
||||
[BuildingType.DeuteriumSynthesizer]: 'deuteriumSynthesizer',
|
||||
[BuildingType.SolarPlant]: 'solarPlant',
|
||||
[BuildingType.RoboticsFactory]: 'roboticsFactory',
|
||||
[BuildingType.NaniteFactory]: 'naniteFactory',
|
||||
[BuildingType.Shipyard]: 'shipyard',
|
||||
[BuildingType.ResearchLab]: 'researchLab',
|
||||
[BuildingType.MetalStorage]: 'metalStorage',
|
||||
[BuildingType.CrystalStorage]: 'crystalStorage',
|
||||
[BuildingType.DeuteriumTank]: 'deuteriumTank',
|
||||
[BuildingType.DarkMatterCollector]: 'darkMatterCollector',
|
||||
[BuildingType.LunarBase]: 'lunarBase',
|
||||
[BuildingType.SensorPhalanx]: 'sensorPhalanx',
|
||||
[BuildingType.JumpGate]: 'jumpGate'
|
||||
}
|
||||
|
||||
// 舰船类型枚举值到翻译键的映射
|
||||
const shipKeyMap: Record<ShipType, string> = {
|
||||
[ShipType.LightFighter]: 'lightFighter',
|
||||
[ShipType.HeavyFighter]: 'heavyFighter',
|
||||
[ShipType.Cruiser]: 'cruiser',
|
||||
[ShipType.Battleship]: 'battleship',
|
||||
[ShipType.SmallCargo]: 'smallCargo',
|
||||
[ShipType.LargeCargo]: 'largeCargo',
|
||||
[ShipType.ColonyShip]: 'colonyShip',
|
||||
[ShipType.Recycler]: 'recycler',
|
||||
[ShipType.EspionageProbe]: 'espionageProbe',
|
||||
[ShipType.DarkMatterHarvester]: 'darkMatterHarvester'
|
||||
}
|
||||
|
||||
// 防御设施类型枚举值到翻译键的映射
|
||||
const defenseKeyMap: Record<DefenseType, string> = {
|
||||
[DefenseType.RocketLauncher]: 'rocketLauncher',
|
||||
[DefenseType.LightLaser]: 'lightLaser',
|
||||
[DefenseType.HeavyLaser]: 'heavyLaser',
|
||||
[DefenseType.GaussCannon]: 'gaussCannon',
|
||||
[DefenseType.IonCannon]: 'ionCannon',
|
||||
[DefenseType.PlasmaTurret]: 'plasmaTurret',
|
||||
[DefenseType.SmallShieldDome]: 'smallShieldDome',
|
||||
[DefenseType.LargeShieldDome]: 'largeShieldDome'
|
||||
}
|
||||
|
||||
// 科技类型枚举值到翻译键的映射
|
||||
const technologyKeyMap: Record<TechnologyType, string> = {
|
||||
[TechnologyType.EnergyTechnology]: 'energyTechnology',
|
||||
[TechnologyType.LaserTechnology]: 'laserTechnology',
|
||||
[TechnologyType.IonTechnology]: 'ionTechnology',
|
||||
[TechnologyType.HyperspaceTechnology]: 'hyperspaceTechnology',
|
||||
[TechnologyType.PlasmaTechnology]: 'plasmaTechnology',
|
||||
[TechnologyType.ComputerTechnology]: 'computerTechnology',
|
||||
[TechnologyType.CombustionDrive]: 'combustionDrive',
|
||||
[TechnologyType.ImpulseDrive]: 'impulseDrive',
|
||||
[TechnologyType.HyperspaceDrive]: 'hyperspaceDrive',
|
||||
[TechnologyType.DarkMatterTechnology]: 'darkMatterTechnology'
|
||||
}
|
||||
|
||||
// 军官类型枚举值到翻译键的映射
|
||||
const officerKeyMap: Record<OfficerType, string> = {
|
||||
[OfficerType.Commander]: 'commander',
|
||||
[OfficerType.Admiral]: 'admiral',
|
||||
[OfficerType.Engineer]: 'engineer',
|
||||
[OfficerType.Geologist]: 'geologist',
|
||||
[OfficerType.Technocrat]: 'technocrat',
|
||||
[OfficerType.DarkMatterSpecialist]: 'darkMatterSpecialist'
|
||||
}
|
||||
|
||||
// 翻译后的建筑配置
|
||||
const BUILDINGS = computed(() => {
|
||||
const translated: Record<BuildingType, BuildingConfig> = {} as Record<BuildingType, BuildingConfig>
|
||||
for (const [key, config] of Object.entries(ORIGINAL_BUILDINGS)) {
|
||||
const buildingType = key as BuildingType
|
||||
const translationKey = buildingKeyMap[buildingType]
|
||||
translated[buildingType] = {
|
||||
...config,
|
||||
name: t(`buildings.${translationKey}`),
|
||||
description: t(`buildingDescriptions.${translationKey}`)
|
||||
}
|
||||
}
|
||||
return translated
|
||||
})
|
||||
|
||||
// 翻译后的舰船配置
|
||||
const SHIPS = computed(() => {
|
||||
const translated: Record<ShipType, ShipConfig> = {} as Record<ShipType, ShipConfig>
|
||||
for (const [key, config] of Object.entries(ORIGINAL_SHIPS)) {
|
||||
const shipType = key as ShipType
|
||||
const translationKey = shipKeyMap[shipType]
|
||||
translated[shipType] = {
|
||||
...config,
|
||||
name: t(`ships.${translationKey}`),
|
||||
description: t(`shipDescriptions.${translationKey}`)
|
||||
}
|
||||
}
|
||||
return translated
|
||||
})
|
||||
|
||||
// 翻译后的防御设施配置
|
||||
const DEFENSES = computed(() => {
|
||||
const translated: Record<DefenseType, DefenseConfig> = {} as Record<DefenseType, DefenseConfig>
|
||||
for (const [key, config] of Object.entries(ORIGINAL_DEFENSES)) {
|
||||
const defenseType = key as DefenseType
|
||||
const translationKey = defenseKeyMap[defenseType]
|
||||
translated[defenseType] = {
|
||||
...config,
|
||||
name: t(`defenses.${translationKey}`),
|
||||
description: t(`defenseDescriptions.${translationKey}`)
|
||||
}
|
||||
}
|
||||
return translated
|
||||
})
|
||||
|
||||
// 翻译后的科技配置
|
||||
const TECHNOLOGIES = computed(() => {
|
||||
const translated: Record<TechnologyType, TechnologyConfig> = {} as Record<TechnologyType, TechnologyConfig>
|
||||
for (const [key, config] of Object.entries(ORIGINAL_TECHNOLOGIES)) {
|
||||
const technologyType = key as TechnologyType
|
||||
const translationKey = technologyKeyMap[technologyType]
|
||||
translated[technologyType] = {
|
||||
...config,
|
||||
name: t(`technologies.${translationKey}`),
|
||||
description: t(`technologyDescriptions.${translationKey}`)
|
||||
}
|
||||
}
|
||||
return translated
|
||||
})
|
||||
|
||||
// 翻译后的军官配置
|
||||
const OFFICERS = computed(() => {
|
||||
const translated: Record<OfficerType, OfficerConfig> = {} as Record<OfficerType, OfficerConfig>
|
||||
for (const [key, config] of Object.entries(ORIGINAL_OFFICERS)) {
|
||||
const officerType = key as OfficerType
|
||||
const translationKey = officerKeyMap[officerType]
|
||||
translated[officerType] = {
|
||||
...config,
|
||||
name: t(`officers.${translationKey}`),
|
||||
description: t(`officerDescriptions.${translationKey}`)
|
||||
}
|
||||
}
|
||||
return translated
|
||||
})
|
||||
|
||||
return {
|
||||
BUILDINGS,
|
||||
SHIPS,
|
||||
DEFENSES,
|
||||
TECHNOLOGIES,
|
||||
OFFICERS
|
||||
}
|
||||
}
|
||||
38
src/composables/useI18n.ts
Normal file
38
src/composables/useI18n.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { computed } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { locales, type Locale } from '@/locales'
|
||||
|
||||
export const useI18n = () => {
|
||||
const gameStore = useGameStore()
|
||||
|
||||
const currentLocale = computed(() => gameStore.locale)
|
||||
|
||||
const messages = computed(() => locales[currentLocale.value])
|
||||
|
||||
// 获取翻译文本的辅助函数
|
||||
const t = (key: string): string => {
|
||||
const keys = key.split('.')
|
||||
let value: any = messages.value
|
||||
|
||||
for (const k of keys) {
|
||||
if (value && typeof value === 'object' && k in value) {
|
||||
value = value[k]
|
||||
} else {
|
||||
return key // 如果找不到翻译,返回原始 key
|
||||
}
|
||||
}
|
||||
|
||||
return typeof value === 'string' ? value : key
|
||||
}
|
||||
|
||||
const setLocale = (locale: Locale) => {
|
||||
gameStore.locale = locale
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
locale: currentLocale,
|
||||
setLocale,
|
||||
messages
|
||||
}
|
||||
}
|
||||
49
src/composables/useTheme.ts
Normal file
49
src/composables/useTheme.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
|
||||
type Theme = 'light' | 'dark'
|
||||
|
||||
const isDark = ref<boolean>(false)
|
||||
|
||||
export const useTheme = () => {
|
||||
const gameStore = useGameStore()
|
||||
|
||||
// 初始化主题
|
||||
onMounted(() => {
|
||||
if (!gameStore.isDark) {
|
||||
// 首次访问,使用系统主题偏好
|
||||
isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
gameStore.isDark = isDark.value ? 'dark' : 'light'
|
||||
} else {
|
||||
// 从 gameStore 读取保存的主题(Pinia会自动从localStorage恢复)
|
||||
const savedTheme = gameStore.isDark as Theme
|
||||
isDark.value = savedTheme === 'dark'
|
||||
}
|
||||
applyTheme()
|
||||
})
|
||||
|
||||
// 监听主题变化
|
||||
watch(isDark, () => {
|
||||
applyTheme()
|
||||
gameStore.isDark = isDark.value ? 'dark' : 'light'
|
||||
})
|
||||
|
||||
// 应用主题
|
||||
const applyTheme = () => {
|
||||
if (isDark.value) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
const toggleTheme = () => {
|
||||
isDark.value = !isDark.value
|
||||
}
|
||||
|
||||
return {
|
||||
isDark,
|
||||
toggleTheme
|
||||
}
|
||||
}
|
||||
570
src/config/gameConfig.ts
Normal file
570
src/config/gameConfig.ts
Normal file
@@ -0,0 +1,570 @@
|
||||
import { BuildingType, TechnologyType, ShipType, DefenseType, OfficerType } from '@/types/game'
|
||||
import type { BuildingConfig, TechnologyConfig, ShipConfig, DefenseConfig, OfficerConfig } from '@/types/game'
|
||||
|
||||
// 建筑配置数据
|
||||
export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
[BuildingType.MetalMine]: {
|
||||
id: BuildingType.MetalMine,
|
||||
name: '金属矿',
|
||||
description: '开采金属资源',
|
||||
baseCost: { metal: 60, crystal: 15, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 1.5,
|
||||
spaceUsage: 3,
|
||||
planetOnly: true
|
||||
},
|
||||
[BuildingType.CrystalMine]: {
|
||||
id: BuildingType.CrystalMine,
|
||||
name: '晶体矿',
|
||||
description: '开采晶体资源',
|
||||
baseCost: { metal: 48, crystal: 24, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 1.6,
|
||||
spaceUsage: 3,
|
||||
planetOnly: true
|
||||
},
|
||||
[BuildingType.DeuteriumSynthesizer]: {
|
||||
id: BuildingType.DeuteriumSynthesizer,
|
||||
name: '重氢合成器',
|
||||
description: '合成重氢资源',
|
||||
baseCost: { metal: 225, crystal: 75, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
baseTime: 20, // 减少建造时间:30→20秒
|
||||
costMultiplier: 1.5,
|
||||
spaceUsage: 4,
|
||||
planetOnly: true
|
||||
},
|
||||
[BuildingType.SolarPlant]: {
|
||||
id: BuildingType.SolarPlant,
|
||||
name: '太阳能电站',
|
||||
description: '提供能源',
|
||||
baseCost: { metal: 75, crystal: 30, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 1.5,
|
||||
spaceUsage: 5
|
||||
},
|
||||
[BuildingType.RoboticsFactory]: {
|
||||
id: BuildingType.RoboticsFactory,
|
||||
name: '机器人工厂',
|
||||
description: '加快建造速度',
|
||||
baseCost: { metal: 400, crystal: 120, deuterium: 200, darkMatter: 0, energy: 0 },
|
||||
baseTime: 40, // 减少建造时间:60→40秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 6
|
||||
},
|
||||
[BuildingType.NaniteFactory]: {
|
||||
id: BuildingType.NaniteFactory,
|
||||
name: '纳米工厂',
|
||||
description: '增加建造队列数量,每级+1队列',
|
||||
baseCost: { metal: 1000000, crystal: 500000, deuterium: 100000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 240, // 减少建造时间:300→240秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 15,
|
||||
requirements: { [BuildingType.RoboticsFactory]: 10 }
|
||||
},
|
||||
[BuildingType.Shipyard]: {
|
||||
id: BuildingType.Shipyard,
|
||||
name: '船坞',
|
||||
description: '建造舰船',
|
||||
baseCost: { metal: 400, crystal: 200, deuterium: 100, darkMatter: 0, energy: 0 },
|
||||
baseTime: 30, // 减少建造时间:60→30秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 8
|
||||
},
|
||||
[BuildingType.ResearchLab]: {
|
||||
id: BuildingType.ResearchLab,
|
||||
name: '研究实验室',
|
||||
description: '研究科技',
|
||||
baseCost: { metal: 200, crystal: 400, deuterium: 200, darkMatter: 0, energy: 0 },
|
||||
baseTime: 30, // 减少建造时间:60→30秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 5
|
||||
},
|
||||
[BuildingType.MetalStorage]: {
|
||||
id: BuildingType.MetalStorage,
|
||||
name: '金属仓库',
|
||||
description: '增加金属存储上限',
|
||||
baseCost: { metal: 1000, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 2
|
||||
},
|
||||
[BuildingType.CrystalStorage]: {
|
||||
id: BuildingType.CrystalStorage,
|
||||
name: '晶体仓库',
|
||||
description: '增加晶体存储上限',
|
||||
baseCost: { metal: 1000, crystal: 500, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 2
|
||||
},
|
||||
[BuildingType.DeuteriumTank]: {
|
||||
id: BuildingType.DeuteriumTank,
|
||||
name: '重氢罐',
|
||||
description: '增加重氢存储上限',
|
||||
baseCost: { metal: 1000, crystal: 1000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
baseTime: 15, // 减少建造时间:30→15秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 2
|
||||
},
|
||||
[BuildingType.DarkMatterCollector]: {
|
||||
id: BuildingType.DarkMatterCollector,
|
||||
name: '暗物质收集器',
|
||||
description: '收集稀有的暗物质资源',
|
||||
baseCost: { metal: 50000, crystal: 100000, deuterium: 50000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 90, // 减少建造时间:120→90秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 10,
|
||||
planetOnly: true
|
||||
},
|
||||
// 月球专属建筑
|
||||
[BuildingType.LunarBase]: {
|
||||
id: BuildingType.LunarBase,
|
||||
name: '月球基地',
|
||||
description: '增加月球可用空间',
|
||||
baseCost: { metal: 20000, crystal: 40000, deuterium: 20000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 45, // 减少建造时间:60→45秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 0, // 月球基地本身不占用空间,反而增加空间
|
||||
moonOnly: true
|
||||
},
|
||||
[BuildingType.SensorPhalanx]: {
|
||||
id: BuildingType.SensorPhalanx,
|
||||
name: '传感器阵列',
|
||||
description: '侦测周围星系的舰队活动',
|
||||
baseCost: { metal: 20000, crystal: 40000, deuterium: 20000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60, // 减少建造时间:90→60秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 10,
|
||||
moonOnly: true
|
||||
},
|
||||
[BuildingType.JumpGate]: {
|
||||
id: BuildingType.JumpGate,
|
||||
name: '跳跃门',
|
||||
description: '瞬间传送舰队到其他月球',
|
||||
baseCost: { metal: 2000000, crystal: 4000000, deuterium: 2000000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 240, // 减少建造时间:300→240秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 20,
|
||||
moonOnly: true
|
||||
}
|
||||
}
|
||||
|
||||
// 科技配置数据
|
||||
export const TECHNOLOGIES: Record<TechnologyType, TechnologyConfig> = {
|
||||
[TechnologyType.EnergyTechnology]: {
|
||||
id: TechnologyType.EnergyTechnology,
|
||||
name: '能源技术',
|
||||
description: '提高能源利用效率',
|
||||
baseCost: { metal: 0, crystal: 800, deuterium: 400, darkMatter: 0, energy: 0 },
|
||||
baseTime: 30, // 减少研究时间:60→30秒
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 1 }
|
||||
},
|
||||
[TechnologyType.LaserTechnology]: {
|
||||
id: TechnologyType.LaserTechnology,
|
||||
name: '激光技术',
|
||||
description: '开发激光武器',
|
||||
baseCost: { metal: 200, crystal: 100, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 1, [TechnologyType.EnergyTechnology]: 2 }
|
||||
},
|
||||
[TechnologyType.IonTechnology]: {
|
||||
id: TechnologyType.IonTechnology,
|
||||
name: '离子技术',
|
||||
description: '开发离子武器',
|
||||
baseCost: { metal: 1000, crystal: 300, deuterium: 100, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 4, [TechnologyType.LaserTechnology]: 5, [TechnologyType.EnergyTechnology]: 4 }
|
||||
},
|
||||
[TechnologyType.HyperspaceTechnology]: {
|
||||
id: TechnologyType.HyperspaceTechnology,
|
||||
name: '超空间技术',
|
||||
description: '研究超空间跳跃',
|
||||
baseCost: { metal: 0, crystal: 4000, deuterium: 2000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 7, [TechnologyType.EnergyTechnology]: 5 }
|
||||
},
|
||||
[TechnologyType.PlasmaTechnology]: {
|
||||
id: TechnologyType.PlasmaTechnology,
|
||||
name: '等离子技术',
|
||||
description: '开发等离子武器',
|
||||
baseCost: { metal: 2000, crystal: 4000, deuterium: 1000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
requirements: {
|
||||
[BuildingType.ResearchLab]: 4,
|
||||
[TechnologyType.EnergyTechnology]: 8,
|
||||
[TechnologyType.LaserTechnology]: 10,
|
||||
[TechnologyType.IonTechnology]: 5
|
||||
}
|
||||
},
|
||||
[TechnologyType.ComputerTechnology]: {
|
||||
id: TechnologyType.ComputerTechnology,
|
||||
name: '计算机技术',
|
||||
description: '增加研究队列数量,每级+1队列',
|
||||
baseCost: { metal: 0, crystal: 400, deuterium: 600, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 1 }
|
||||
},
|
||||
[TechnologyType.CombustionDrive]: {
|
||||
id: TechnologyType.CombustionDrive,
|
||||
name: '燃烧引擎',
|
||||
description: '基础推进系统',
|
||||
baseCost: { metal: 400, crystal: 0, deuterium: 600, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 1, [TechnologyType.EnergyTechnology]: 1 }
|
||||
},
|
||||
[TechnologyType.ImpulseDrive]: {
|
||||
id: TechnologyType.ImpulseDrive,
|
||||
name: '脉冲引擎',
|
||||
description: '高级推进系统',
|
||||
baseCost: { metal: 2000, crystal: 4000, deuterium: 600, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 2, [TechnologyType.EnergyTechnology]: 1 }
|
||||
},
|
||||
[TechnologyType.HyperspaceDrive]: {
|
||||
id: TechnologyType.HyperspaceDrive,
|
||||
name: '超空间引擎',
|
||||
description: '超空间推进系统',
|
||||
baseCost: { metal: 10000, crystal: 20000, deuterium: 6000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 7, [TechnologyType.HyperspaceTechnology]: 3 }
|
||||
},
|
||||
[TechnologyType.DarkMatterTechnology]: {
|
||||
id: TechnologyType.DarkMatterTechnology,
|
||||
name: '暗物质技术',
|
||||
description: '研究暗物质的性质和应用',
|
||||
baseCost: { metal: 100000, crystal: 200000, deuterium: 100000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 180,
|
||||
costMultiplier: 2,
|
||||
requirements: { [BuildingType.ResearchLab]: 8, [TechnologyType.HyperspaceTechnology]: 5 }
|
||||
}
|
||||
}
|
||||
|
||||
// 舰船配置数据
|
||||
export const SHIPS: Record<ShipType, ShipConfig> = {
|
||||
[ShipType.LightFighter]: {
|
||||
id: ShipType.LightFighter,
|
||||
name: '轻型战斗机',
|
||||
description: '基础战斗单位',
|
||||
cost: { metal: 3000, crystal: 1000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 20,
|
||||
cargoCapacity: 50,
|
||||
attack: 50,
|
||||
shield: 10,
|
||||
armor: 400,
|
||||
speed: 12500,
|
||||
fuelConsumption: 20,
|
||||
requirements: { [BuildingType.Shipyard]: 1, [TechnologyType.CombustionDrive]: 1 }
|
||||
},
|
||||
[ShipType.HeavyFighter]: {
|
||||
id: ShipType.HeavyFighter,
|
||||
name: '重型战斗机',
|
||||
description: '强力战斗单位',
|
||||
cost: { metal: 6000, crystal: 4000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 30,
|
||||
cargoCapacity: 100,
|
||||
attack: 150,
|
||||
shield: 25,
|
||||
armor: 1000,
|
||||
speed: 10000,
|
||||
fuelConsumption: 75,
|
||||
requirements: { [BuildingType.Shipyard]: 3, [TechnologyType.ImpulseDrive]: 2 }
|
||||
},
|
||||
[ShipType.Cruiser]: {
|
||||
id: ShipType.Cruiser,
|
||||
name: '巡洋舰',
|
||||
description: '中型战舰',
|
||||
cost: { metal: 20000, crystal: 7000, deuterium: 2000, darkMatter: 0, energy: 0 },
|
||||
buildTime: 60,
|
||||
cargoCapacity: 800,
|
||||
attack: 400,
|
||||
shield: 50,
|
||||
armor: 2700,
|
||||
speed: 15000,
|
||||
fuelConsumption: 300,
|
||||
requirements: { [BuildingType.Shipyard]: 5, [TechnologyType.ImpulseDrive]: 4, [TechnologyType.IonTechnology]: 2 }
|
||||
},
|
||||
[ShipType.Battleship]: {
|
||||
id: ShipType.Battleship,
|
||||
name: '战列舰',
|
||||
description: '重型战舰',
|
||||
cost: { metal: 45000, crystal: 15000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 90,
|
||||
cargoCapacity: 1500,
|
||||
attack: 1000,
|
||||
shield: 200,
|
||||
armor: 6000,
|
||||
speed: 10000,
|
||||
fuelConsumption: 500,
|
||||
requirements: { [BuildingType.Shipyard]: 7, [TechnologyType.HyperspaceDrive]: 4 }
|
||||
},
|
||||
[ShipType.SmallCargo]: {
|
||||
id: ShipType.SmallCargo,
|
||||
name: '小型运输船',
|
||||
description: '运输资源',
|
||||
cost: { metal: 2000, crystal: 2000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 15,
|
||||
cargoCapacity: 5000,
|
||||
attack: 5,
|
||||
shield: 10,
|
||||
armor: 400,
|
||||
speed: 5000,
|
||||
fuelConsumption: 10,
|
||||
requirements: { [BuildingType.Shipyard]: 2, [TechnologyType.CombustionDrive]: 2 }
|
||||
},
|
||||
[ShipType.LargeCargo]: {
|
||||
id: ShipType.LargeCargo,
|
||||
name: '大型运输船',
|
||||
description: '大量运输资源',
|
||||
cost: { metal: 6000, crystal: 6000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 30,
|
||||
cargoCapacity: 25000,
|
||||
attack: 5,
|
||||
shield: 25,
|
||||
armor: 1200,
|
||||
speed: 7500,
|
||||
fuelConsumption: 50,
|
||||
requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.CombustionDrive]: 6 }
|
||||
},
|
||||
[ShipType.ColonyShip]: {
|
||||
id: ShipType.ColonyShip,
|
||||
name: '殖民船',
|
||||
description: '建立新殖民地',
|
||||
cost: { metal: 10000, crystal: 20000, deuterium: 10000, darkMatter: 0, energy: 0 },
|
||||
buildTime: 120,
|
||||
cargoCapacity: 7500,
|
||||
attack: 50,
|
||||
shield: 100,
|
||||
armor: 3000,
|
||||
speed: 2500,
|
||||
fuelConsumption: 1000,
|
||||
requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.ImpulseDrive]: 3 }
|
||||
},
|
||||
[ShipType.Recycler]: {
|
||||
id: ShipType.Recycler,
|
||||
name: '回收船',
|
||||
description: '回收废墟资源',
|
||||
cost: { metal: 10000, crystal: 6000, deuterium: 2000, darkMatter: 0, energy: 0 },
|
||||
buildTime: 60,
|
||||
cargoCapacity: 20000,
|
||||
attack: 1,
|
||||
shield: 10,
|
||||
armor: 1600,
|
||||
speed: 2000,
|
||||
fuelConsumption: 300,
|
||||
requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.CombustionDrive]: 6 }
|
||||
},
|
||||
[ShipType.EspionageProbe]: {
|
||||
id: ShipType.EspionageProbe,
|
||||
name: '间谍探测器',
|
||||
description: '侦察敌方星球',
|
||||
cost: { metal: 0, crystal: 1000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 5,
|
||||
cargoCapacity: 5,
|
||||
attack: 0,
|
||||
shield: 0,
|
||||
armor: 100,
|
||||
speed: 100000000,
|
||||
fuelConsumption: 1,
|
||||
requirements: { [BuildingType.Shipyard]: 3, [TechnologyType.CombustionDrive]: 3 }
|
||||
},
|
||||
[ShipType.DarkMatterHarvester]: {
|
||||
id: ShipType.DarkMatterHarvester,
|
||||
name: '暗物质采集船',
|
||||
description: '专门用于采集暗物质的特殊飞船',
|
||||
cost: { metal: 100000, crystal: 150000, deuterium: 50000, darkMatter: 0, energy: 0 },
|
||||
buildTime: 120,
|
||||
cargoCapacity: 1000, // 暗物质专用储存
|
||||
attack: 10,
|
||||
shield: 50,
|
||||
armor: 2000,
|
||||
speed: 5000,
|
||||
fuelConsumption: 500,
|
||||
requirements: {
|
||||
[BuildingType.Shipyard]: 8,
|
||||
[TechnologyType.HyperspaceDrive]: 5,
|
||||
[TechnologyType.DarkMatterTechnology]: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 防御设施配置数据
|
||||
export const DEFENSES: Record<DefenseType, DefenseConfig> = {
|
||||
[DefenseType.RocketLauncher]: {
|
||||
id: DefenseType.RocketLauncher,
|
||||
name: '火箭发射器',
|
||||
description: '基础防御设施',
|
||||
cost: { metal: 2000, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 10,
|
||||
attack: 80,
|
||||
shield: 20,
|
||||
armor: 200,
|
||||
requirements: { [BuildingType.Shipyard]: 1 }
|
||||
},
|
||||
[DefenseType.LightLaser]: {
|
||||
id: DefenseType.LightLaser,
|
||||
name: '轻型激光炮',
|
||||
description: '激光防御武器',
|
||||
cost: { metal: 1500, crystal: 500, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 12,
|
||||
attack: 100,
|
||||
shield: 25,
|
||||
armor: 200,
|
||||
requirements: { [BuildingType.Shipyard]: 2, [TechnologyType.LaserTechnology]: 3 }
|
||||
},
|
||||
[DefenseType.HeavyLaser]: {
|
||||
id: DefenseType.HeavyLaser,
|
||||
name: '重型激光炮',
|
||||
description: '强力激光武器',
|
||||
cost: { metal: 6000, crystal: 2000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 20,
|
||||
attack: 250,
|
||||
shield: 100,
|
||||
armor: 800,
|
||||
requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.LaserTechnology]: 6 }
|
||||
},
|
||||
[DefenseType.GaussCannon]: {
|
||||
id: DefenseType.GaussCannon,
|
||||
name: '高斯炮',
|
||||
description: '电磁加速武器',
|
||||
cost: { metal: 20000, crystal: 15000, deuterium: 2000, darkMatter: 0, energy: 0 },
|
||||
buildTime: 35,
|
||||
attack: 1100,
|
||||
shield: 200,
|
||||
armor: 3500,
|
||||
requirements: { [BuildingType.Shipyard]: 6, [TechnologyType.EnergyTechnology]: 6 }
|
||||
},
|
||||
[DefenseType.IonCannon]: {
|
||||
id: DefenseType.IonCannon,
|
||||
name: '离子炮',
|
||||
description: '离子武器系统',
|
||||
cost: { metal: 2000, crystal: 6000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 30,
|
||||
attack: 150,
|
||||
shield: 500,
|
||||
armor: 800,
|
||||
requirements: { [BuildingType.Shipyard]: 4, [TechnologyType.IonTechnology]: 4 }
|
||||
},
|
||||
[DefenseType.PlasmaTurret]: {
|
||||
id: DefenseType.PlasmaTurret,
|
||||
name: '等离子炮台',
|
||||
description: '最强防御武器',
|
||||
cost: { metal: 50000, crystal: 50000, deuterium: 30000, darkMatter: 0, energy: 0 },
|
||||
buildTime: 60,
|
||||
attack: 3000,
|
||||
shield: 300,
|
||||
armor: 10000,
|
||||
requirements: { [BuildingType.Shipyard]: 8, [TechnologyType.PlasmaTechnology]: 7 }
|
||||
},
|
||||
[DefenseType.SmallShieldDome]: {
|
||||
id: DefenseType.SmallShieldDome,
|
||||
name: '小型护盾罩',
|
||||
description: '保护星球的能量护盾',
|
||||
cost: { metal: 10000, crystal: 10000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 30,
|
||||
attack: 1,
|
||||
shield: 2000,
|
||||
armor: 2000,
|
||||
requirements: { [BuildingType.Shipyard]: 6, [TechnologyType.EnergyTechnology]: 3 }
|
||||
},
|
||||
[DefenseType.LargeShieldDome]: {
|
||||
id: DefenseType.LargeShieldDome,
|
||||
name: '大型护盾罩',
|
||||
description: '强大的星球护盾',
|
||||
cost: { metal: 50000, crystal: 50000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 60,
|
||||
attack: 1,
|
||||
shield: 10000,
|
||||
armor: 10000,
|
||||
requirements: { [BuildingType.Shipyard]: 6, [TechnologyType.EnergyTechnology]: 6 }
|
||||
}
|
||||
}
|
||||
|
||||
// 军官配置数据
|
||||
export const OFFICERS: Record<OfficerType, OfficerConfig> = {
|
||||
[OfficerType.Commander]: {
|
||||
id: OfficerType.Commander,
|
||||
name: '指挥官',
|
||||
description: '提升建筑速度和管理能力',
|
||||
cost: { metal: 0, crystal: 50000, deuterium: 25000, darkMatter: 0, energy: 0 },
|
||||
weeklyMaintenance: { metal: 0, crystal: 5000, deuterium: 2500, darkMatter: 0, energy: 0 },
|
||||
benefits: {
|
||||
buildingSpeedBonus: 10, // 建筑速度 +10%
|
||||
additionalBuildQueue: 1, // 额外1个建筑队列
|
||||
storageCapacityBonus: 10 // 仓储容量 +10%
|
||||
}
|
||||
},
|
||||
[OfficerType.Admiral]: {
|
||||
id: OfficerType.Admiral,
|
||||
name: '上将',
|
||||
description: '提升舰队作战能力',
|
||||
cost: { metal: 50000, crystal: 25000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
weeklyMaintenance: { metal: 5000, crystal: 2500, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
benefits: {
|
||||
additionalFleetSlots: 2, // 额外2个舰队槽位
|
||||
fleetSpeedBonus: 10, // 舰队速度 +10%
|
||||
fuelConsumptionReduction: 10 // 燃料消耗 -10%
|
||||
}
|
||||
},
|
||||
[OfficerType.Engineer]: {
|
||||
id: OfficerType.Engineer,
|
||||
name: '工程师',
|
||||
description: '增强防御和能量系统',
|
||||
cost: { metal: 40000, crystal: 20000, deuterium: 10000, darkMatter: 0, energy: 0 },
|
||||
weeklyMaintenance: { metal: 4000, crystal: 2000, deuterium: 1000, darkMatter: 0, energy: 0 },
|
||||
benefits: {
|
||||
defenseBonus: 15, // 防御力 +15%
|
||||
energyProductionBonus: 10, // 电量产出 +10%
|
||||
buildingSpeedBonus: 5 // 建筑速度 +5%
|
||||
}
|
||||
},
|
||||
[OfficerType.Geologist]: {
|
||||
id: OfficerType.Geologist,
|
||||
name: '地质学家',
|
||||
description: '提高资源开采效率',
|
||||
cost: { metal: 30000, crystal: 30000, deuterium: 20000, darkMatter: 0, energy: 0 },
|
||||
weeklyMaintenance: { metal: 3000, crystal: 3000, deuterium: 2000, darkMatter: 0, energy: 0 },
|
||||
benefits: {
|
||||
resourceProductionBonus: 15, // 资源产量 +15%
|
||||
storageCapacityBonus: 10 // 仓储容量 +10%
|
||||
}
|
||||
},
|
||||
[OfficerType.Technocrat]: {
|
||||
id: OfficerType.Technocrat,
|
||||
name: '技术专家',
|
||||
description: '加快科技研究速度',
|
||||
cost: { metal: 20000, crystal: 40000, deuterium: 20000, darkMatter: 0, energy: 0 },
|
||||
weeklyMaintenance: { metal: 2000, crystal: 4000, deuterium: 2000, darkMatter: 0, energy: 0 },
|
||||
benefits: {
|
||||
researchSpeedBonus: 15 // 研究速度 +15%
|
||||
}
|
||||
},
|
||||
[OfficerType.DarkMatterSpecialist]: {
|
||||
id: OfficerType.DarkMatterSpecialist,
|
||||
name: '暗物质专家',
|
||||
description: '提升暗物质采集效率',
|
||||
cost: { metal: 50000, crystal: 100000, deuterium: 50000, darkMatter: 100, energy: 0 },
|
||||
weeklyMaintenance: { metal: 5000, crystal: 10000, deuterium: 5000, darkMatter: 10, energy: 0 },
|
||||
benefits: {
|
||||
darkMatterProductionBonus: 25 // 暗物质产量 +25%
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 月球配置
|
||||
export const MOON_CONFIG = {
|
||||
minDebrisField: 100000, // 最小残骸场 (金属+晶体)
|
||||
baseChance: 1, // 基础1%概率
|
||||
maxChance: 20, // 最大20%概率
|
||||
chancePerDebris: 100000, // 每10万资源增加1%概率
|
||||
baseSize: 100, // 月球基础空间
|
||||
lunarBaseSpaceBonus: 3 // 每级月球基地增加的空间
|
||||
}
|
||||
7
src/env.d.ts
vendored
Normal file
7
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
7
src/lib/utils.ts
Normal file
7
src/lib/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ClassValue } from 'clsx'
|
||||
import { clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export const cn = (...inputs: ClassValue[]) => {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
536
src/locales/de.ts
Normal file
536
src/locales/de.ts
Normal file
@@ -0,0 +1,536 @@
|
||||
export default {
|
||||
common: {
|
||||
confirm: 'Bestätigen',
|
||||
cancel: 'Abbrechen',
|
||||
delete: 'Löschen',
|
||||
edit: 'Bearbeiten',
|
||||
save: 'Speichern',
|
||||
close: 'Schließen',
|
||||
back: 'Zurück',
|
||||
next: 'Weiter',
|
||||
previous: 'Vorherige',
|
||||
submit: 'Absenden',
|
||||
reset: 'Zurücksetzen',
|
||||
search: 'Suchen',
|
||||
filter: 'Filtern',
|
||||
loading: 'Laden...',
|
||||
noData: 'Keine Daten',
|
||||
error: 'Fehler',
|
||||
success: 'Erfolg',
|
||||
warning: 'Warnung',
|
||||
info: 'Info',
|
||||
resourceType: 'Ressourcentyp',
|
||||
playerName: 'Kommandant',
|
||||
timeHour: 'Std',
|
||||
timeMinute: 'Min',
|
||||
timeSecond: 'Sek',
|
||||
featureLocked: 'Funktion gesperrt',
|
||||
unlockRequired: 'Gebäude erforderlich',
|
||||
requiredBuilding: 'Erforderliches Gebäude',
|
||||
currentLevel: 'Aktuelles Level',
|
||||
goToBuildings: 'Zu Gebäuden',
|
||||
locked: 'Gesperrt',
|
||||
viewRequirements: 'Anforderungen anzeigen',
|
||||
requirementsNotMet: 'Anforderungen nicht erfüllt',
|
||||
current: 'Aktuell'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: 'Anforderungen nicht erfüllt',
|
||||
insufficientResources: 'Unzureichende Ressourcen',
|
||||
shieldDomeLimit: 'Schildkuppel-Limit erreicht',
|
||||
fleetMissionsFull: 'Flottenmissionsplätze voll',
|
||||
insufficientFleet: 'Unzureichende Flotte',
|
||||
insufficientFuel: 'Unzureichender Treibstoff',
|
||||
planetOnly: 'Dieses Gebäude kann nur auf Planeten gebaut werden',
|
||||
moonOnly: 'Dieses Gebäude kann nur auf Monden gebaut werden',
|
||||
buildQueueFull: 'Bauauftrag voll',
|
||||
insufficientSpace: 'Unzureichender Platz',
|
||||
buildingLevelZero: 'Gebäudelevel ist 0, kann nicht abgerissen werden',
|
||||
researchQueueFull: 'Forschungsauftrag voll',
|
||||
moonExists: 'Mond existiert bereits',
|
||||
insufficientDebris: 'Unzureichendes Trümmerfeld'
|
||||
},
|
||||
nav: {
|
||||
overview: 'Übersicht',
|
||||
buildings: 'Gebäude',
|
||||
research: 'Forschung',
|
||||
shipyard: 'Raumschiffwerft',
|
||||
defense: 'Verteidigung',
|
||||
fleet: 'Flotte',
|
||||
officers: 'Offiziere',
|
||||
simulator: 'Simulator',
|
||||
galaxy: 'Galaxie',
|
||||
messages: 'Nachrichten',
|
||||
settings: 'Einstellungen'
|
||||
},
|
||||
sidebar: {
|
||||
language: 'Sprache',
|
||||
lightMode: 'Heller Modus',
|
||||
darkMode: 'Dunkler Modus',
|
||||
collapse: 'Einklappen',
|
||||
expand: 'Ausklappen'
|
||||
},
|
||||
resources: {
|
||||
metal: 'Metall',
|
||||
crystal: 'Kristall',
|
||||
deuterium: 'Deuterium',
|
||||
darkMatter: 'Dunkle Materie',
|
||||
energy: 'Energie',
|
||||
production: 'Produktion',
|
||||
capacity: 'Kapazität',
|
||||
current: 'Aktuell',
|
||||
max: 'Max. Kapazität',
|
||||
perHour: 'Stunde'
|
||||
},
|
||||
planet: {
|
||||
planet: 'Planet',
|
||||
moon: 'Mond',
|
||||
colony: 'Kolonie',
|
||||
position: 'Position',
|
||||
coordinates: 'Koordinaten',
|
||||
switchToMoon: 'Zum Mond',
|
||||
backToPlanet: 'Zurück zum Planeten',
|
||||
fields: 'Felder',
|
||||
temperature: 'Temperatur',
|
||||
homePlanet: 'Heimatplanet',
|
||||
planetPrefix: 'Planet',
|
||||
moonSuffix: 's Mond',
|
||||
colonyPrefix: 'Kolonie'
|
||||
},
|
||||
player: {
|
||||
points: 'Gesamtpunkte'
|
||||
},
|
||||
buildings: {
|
||||
metalMine: 'Metallmine',
|
||||
crystalMine: 'Kristallmine',
|
||||
deuteriumSynthesizer: 'Deuterium-Synthesizer',
|
||||
solarPlant: 'Solarkraftwerk',
|
||||
roboticsFactory: 'Roboterfabrik',
|
||||
naniteFactory: 'Nanitenfabrik',
|
||||
shipyard: 'Raumschiffwerft',
|
||||
researchLab: 'Forschungslabor',
|
||||
metalStorage: 'Metallspeicher',
|
||||
crystalStorage: 'Kristallspeicher',
|
||||
deuteriumTank: 'Deuteriumtank',
|
||||
darkMatterCollector: 'Dunkle-Materie-Kollektor',
|
||||
lunarBase: 'Mondbasis',
|
||||
sensorPhalanx: 'Sensorphalanx',
|
||||
jumpGate: 'Sprungtor',
|
||||
buildTime: 'Bauzeit',
|
||||
production: 'Produktion',
|
||||
consumption: 'Verbrauch',
|
||||
totalCost: 'Gesamtkosten',
|
||||
totalPoints: 'Gesamtpunkte',
|
||||
levelRange: 'Stufenbereich'
|
||||
},
|
||||
buildingDescriptions: {
|
||||
metalMine: 'Fördert Metallressourcen',
|
||||
crystalMine: 'Fördert Kristallressourcen',
|
||||
deuteriumSynthesizer: 'Synthesiert Deuteriumressourcen',
|
||||
solarPlant: 'Liefert Energie',
|
||||
roboticsFactory: 'Beschleunigt Baugeschwindigkeit',
|
||||
naniteFactory: 'Erhöht Bauauftragskapazität, +1 pro Stufe (max 10)',
|
||||
shipyard: 'Baut Schiffe',
|
||||
researchLab: 'Erforscht Technologien',
|
||||
metalStorage: 'Erhöht Metallspeicherkapazität',
|
||||
crystalStorage: 'Erhöht Kristallspeicherkapazität',
|
||||
deuteriumTank: 'Erhöht Deuteriumspeicherkapazität',
|
||||
darkMatterCollector: 'Sammelt seltene Dunkle-Materie-Ressourcen',
|
||||
lunarBase: 'Erhöht verfügbaren Platz auf dem Mond',
|
||||
sensorPhalanx: 'Erkennt Flottenaktivitäten in umliegenden Systemen',
|
||||
jumpGate: 'Überträgt Flotten sofort zu anderen Monden'
|
||||
},
|
||||
ships: {
|
||||
lightFighter: 'Leichter Jäger',
|
||||
heavyFighter: 'Schwerer Jäger',
|
||||
cruiser: 'Kreuzer',
|
||||
battleship: 'Schlachtschiff',
|
||||
smallCargo: 'Kleiner Transporter',
|
||||
largeCargo: 'Großer Transporter',
|
||||
colonyShip: 'Kolonieschiff',
|
||||
recycler: 'Recycler',
|
||||
espionageProbe: 'Spionagesonde',
|
||||
darkMatterHarvester: 'Dunkle-Materie-Ernter'
|
||||
},
|
||||
shipDescriptions: {
|
||||
lightFighter: 'Grundlegende Kampfeinheit',
|
||||
heavyFighter: 'Schwer gepanzerter Jäger',
|
||||
cruiser: 'Mittleres Kriegsschiff, ausgewogene Offensive und Defensive',
|
||||
battleship: 'Mächtiges Kriegsschiff',
|
||||
smallCargo: 'Transportiert kleine Mengen Ressourcen',
|
||||
largeCargo: 'Transportiert große Mengen Ressourcen',
|
||||
colonyShip: 'Zur Kolonisierung neuer Planeten',
|
||||
recycler: 'Sammelt Trümmerfeld-Ressourcen',
|
||||
espionageProbe: 'Späht feindliche Planeten aus',
|
||||
darkMatterHarvester: 'Spezielles Schiff zum Ernten von Dunkler Materie'
|
||||
},
|
||||
defenses: {
|
||||
rocketLauncher: 'Raketenwerfer',
|
||||
lightLaser: 'Leichtes Lasergeschütz',
|
||||
heavyLaser: 'Schweres Lasergeschütz',
|
||||
gaussCannon: 'Gaußkanone',
|
||||
ionCannon: 'Ionengeschütz',
|
||||
plasmaTurret: 'Plasmawerfer',
|
||||
smallShieldDome: 'Kleine Schildkuppel',
|
||||
largeShieldDome: 'Große Schildkuppel'
|
||||
},
|
||||
defenseDescriptions: {
|
||||
rocketLauncher: 'Grundlegende Verteidigungsanlage',
|
||||
lightLaser: 'Leichte Energiewaffe',
|
||||
heavyLaser: 'Schwere Energiewaffe',
|
||||
gaussCannon: 'Hochgeschwindigkeits-Kinetikwaffe',
|
||||
ionCannon: 'Effektiv gegen Schilde',
|
||||
plasmaTurret: 'Mächtige Verteidigungsanlage',
|
||||
smallShieldDome: 'Kleiner Schild zum Schutz des gesamten Planeten',
|
||||
largeShieldDome: 'Großer Schild zum Schutz des gesamten Planeten'
|
||||
},
|
||||
research: {
|
||||
researchTime: 'Forschungszeit',
|
||||
totalCost: 'Gesamtkosten',
|
||||
totalPoints: 'Gesamtpunkte',
|
||||
levelRange: 'Stufenbereich'
|
||||
},
|
||||
technologies: {
|
||||
energyTechnology: 'Energietechnik',
|
||||
laserTechnology: 'Lasertechnik',
|
||||
ionTechnology: 'Ionentechnik',
|
||||
hyperspaceTechnology: 'Hyperraumtechnik',
|
||||
plasmaTechnology: 'Plasmatechnik',
|
||||
computerTechnology: 'Computertechnologie',
|
||||
combustionDrive: 'Verbrennungsantrieb',
|
||||
impulseDrive: 'Impulsantrieb',
|
||||
hyperspaceDrive: 'Hyperraumantrieb',
|
||||
darkMatterTechnology: 'Dunkle-Materie-Technologie'
|
||||
},
|
||||
technologyDescriptions: {
|
||||
energyTechnology: 'Verbessert Energieeffizienz',
|
||||
laserTechnology: 'Grundlage für Laserwaffen und -verteidigung',
|
||||
ionTechnology: 'Ionenwaffentechnologie',
|
||||
hyperspaceTechnology: 'Hyperraumsprung-Technologie',
|
||||
plasmaTechnology: 'Plasmawaffentechnologie',
|
||||
computerTechnology: 'Erhöht Forschungsauftragskapazität, +1 pro Stufe (max 10)',
|
||||
combustionDrive: 'Grundlegende Antriebstechnologie',
|
||||
impulseDrive: 'Mittlere Antriebstechnologie',
|
||||
hyperspaceDrive: 'Fortgeschrittene Antriebstechnologie',
|
||||
darkMatterTechnology: 'Forschung zu Eigenschaften und Anwendungen von Dunkler Materie'
|
||||
},
|
||||
officers: {
|
||||
commander: 'Kommandant',
|
||||
admiral: 'Admiral',
|
||||
engineer: 'Ingenieur',
|
||||
geologist: 'Geologe',
|
||||
technocrat: 'Technokrat',
|
||||
darkMatterSpecialist: 'Dunkle-Materie-Spezialist'
|
||||
},
|
||||
officerDescriptions: {
|
||||
commander: 'Verbessert Baugeschwindigkeit und Management',
|
||||
admiral: 'Verbessert Flottenkampf und Geschwindigkeit',
|
||||
engineer: 'Verbessert Energie und Verteidigung',
|
||||
geologist: 'Verbessert Ressourcenproduktion',
|
||||
technocrat: 'Verbessert Forschungsgeschwindigkeit und Spionage',
|
||||
darkMatterSpecialist: 'Verbessert Dunkle-Materie-Sammlungseffizienz'
|
||||
},
|
||||
queue: {
|
||||
buildQueue: 'Bauauftrag',
|
||||
researchQueue: 'Forschungsauftrag',
|
||||
building: 'Im Bau',
|
||||
researching: 'In Forschung',
|
||||
remaining: 'Verbleibend',
|
||||
cancel: 'Abbrechen',
|
||||
cancelBuild: 'Bau abbrechen',
|
||||
cancelResearch: 'Forschung abbrechen',
|
||||
confirmCancel: 'Möchten Sie wirklich abbrechen? 50% der Ressourcen werden zurückerstattet.',
|
||||
level: 'Stufe',
|
||||
upgradeToLevel: 'Auf Stufe aufrüsten'
|
||||
},
|
||||
overview: {
|
||||
title: 'Planetenübersicht',
|
||||
resourceOverview: 'Ressourcen',
|
||||
fleetInfo: 'Flotte',
|
||||
currentShips: 'Schiffe auf diesem Planeten'
|
||||
},
|
||||
buildingsView: {
|
||||
title: 'Gebäude',
|
||||
usedSpace: 'Verwendeter Platz',
|
||||
spaceUsage: 'Platzbedarf',
|
||||
level: 'Stufe',
|
||||
upgradeCost: 'Ausbaukosten',
|
||||
buildTime: 'Bauzeit',
|
||||
upgrade: 'Ausbauen',
|
||||
upgradeFailed: 'Ausbau fehlgeschlagen',
|
||||
upgradeFailedMessage: 'Bitte überprüfen Sie, ob Sie genügend Ressourcen, Platz oder keine anderen Bauaufträge haben.',
|
||||
demolish: 'Abreißen',
|
||||
demolishRefund: 'Abriss-Rückerstattung',
|
||||
demolishFailed: 'Abriss fehlgeschlagen',
|
||||
demolishFailedMessage: 'Abriss nicht möglich. Bitte überprüfen Sie, ob die Bauqueue voll ist oder die Gebäudestufe 0 ist.'
|
||||
},
|
||||
researchView: {
|
||||
title: 'Forschung',
|
||||
researchCost: 'Forschungskosten',
|
||||
research: 'Forschen',
|
||||
researchFailed: 'Forschung fehlgeschlagen',
|
||||
researchFailedMessage:
|
||||
'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben, die Voraussetzungen erfüllt sind oder keine anderen Forschungsaufträge vorhanden sind.'
|
||||
},
|
||||
shipyard: {
|
||||
attack: 'Angriff',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
speed: 'Geschwindigkeit',
|
||||
cargoCapacity: 'Ladekapazität',
|
||||
fuelConsumption: 'Treibstoffverbrauch',
|
||||
buildCost: 'Baukosten',
|
||||
buildTime: 'Bauzeit',
|
||||
perUnit: 'Pro Einheit',
|
||||
batchCalculator: 'Batch-Rechner',
|
||||
quantity: 'Menge',
|
||||
totalCost: 'Gesamtkosten',
|
||||
totalTime: 'Gesamtzeit'
|
||||
},
|
||||
shipyardView: {
|
||||
title: 'Raumschiffwerft',
|
||||
attack: 'Angriff',
|
||||
shield: 'Schild',
|
||||
speed: 'Geschwindigkeit',
|
||||
cargoCapacity: 'Ladekapazität',
|
||||
unitCost: 'Stückkosten',
|
||||
buildQuantity: 'Baumenge',
|
||||
totalCost: 'Gesamtkosten',
|
||||
build: 'Bauen',
|
||||
inputError: 'Eingabefehler',
|
||||
inputErrorMessage: 'Bitte Baumenge eingeben!',
|
||||
buildFailed: 'Bau fehlgeschlagen',
|
||||
buildFailedMessage: 'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben oder die Voraussetzungen erfüllt sind.'
|
||||
},
|
||||
defense: {
|
||||
attack: 'Angriff',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
buildCost: 'Baukosten',
|
||||
buildTime: 'Bauzeit',
|
||||
perUnit: 'Pro Einheit',
|
||||
batchCalculator: 'Batch-Rechner',
|
||||
quantity: 'Menge',
|
||||
totalCost: 'Gesamtkosten',
|
||||
totalTime: 'Gesamtzeit'
|
||||
},
|
||||
defenseView: {
|
||||
title: 'Verteidigung',
|
||||
attack: 'Angriff',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
buildTime: 'Bauzeit',
|
||||
seconds: 's',
|
||||
unitCost: 'Stückkosten',
|
||||
buildQuantity: 'Baumenge',
|
||||
totalCost: 'Gesamtkosten',
|
||||
build: 'Bauen',
|
||||
shieldDomeBuilt: 'Schildkuppel bereits gebaut',
|
||||
inputError: 'Eingabefehler',
|
||||
inputErrorMessage: 'Bitte Baumenge eingeben!',
|
||||
buildFailed: 'Bau fehlgeschlagen',
|
||||
buildFailedMessage:
|
||||
'Bitte überprüfen Sie, ob Sie genügend Ressourcen haben oder die Voraussetzungen erfüllt sind. Schildkuppeln können nur einmal gebaut werden.'
|
||||
},
|
||||
fleetView: {
|
||||
title: 'Flottenverwaltung',
|
||||
fleetOverview: 'Flottenübersicht',
|
||||
sendFleet: 'Flotte senden',
|
||||
flightMissions: 'Flugmissionen',
|
||||
currentPlanetFleet: 'Flotte auf diesem Planeten',
|
||||
attack: 'Angriff',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
speed: 'Geschwindigkeit',
|
||||
cargo: 'Fracht',
|
||||
selectFleet: 'Flotte auswählen',
|
||||
selectFleetDescription: 'Wählen Sie die Anzahl der zu sendenden Schiffe',
|
||||
available: 'Verfügbar',
|
||||
all: 'Alle',
|
||||
targetCoordinates: 'Zielkoordinaten',
|
||||
galaxy: 'Galaxie',
|
||||
system: 'System',
|
||||
position: 'Position',
|
||||
missionType: 'Missionstyp',
|
||||
missionInfo: 'Missionsinfo',
|
||||
fuelConsumption: 'Treibstoffverbrauch',
|
||||
flightTime: 'Flugzeit',
|
||||
attackMission: 'Angriff',
|
||||
transport: 'Transport',
|
||||
colonize: 'Kolonisieren',
|
||||
spy: 'Spionage',
|
||||
deploy: 'Stationieren',
|
||||
transportResources: 'Ressourcen transportieren',
|
||||
totalCargoCapacity: 'Gesamtladekapazität',
|
||||
used: 'Verwendet',
|
||||
noFlightMissions: 'Keine Flugmissionen',
|
||||
outbound: 'Hinflug',
|
||||
returning: 'Rückflug',
|
||||
fleetComposition: 'Flottenzusammensetzung',
|
||||
carryingResources: 'Transportierte Ressourcen',
|
||||
arrivalTime: 'Ankunftszeit',
|
||||
returnTime: 'Rückkehrzeit',
|
||||
recallFleet: 'Flotte zurückrufen',
|
||||
sendFailed: 'Senden fehlgeschlagen',
|
||||
sendFailedMessage: 'Bitte überprüfen Sie Flottenanzahl, Treibstoffverfügbarkeit oder Ladekapazitätsgrenzen.',
|
||||
recallFailed: 'Zurückrufen fehlgeschlagen',
|
||||
recallFailedMessage: 'Diese Mission kann nicht zurückgerufen werden.',
|
||||
unknownPlanet: 'Unbekannter Planet',
|
||||
fleetMissionSlots: 'Flottenmissionsplätze'
|
||||
},
|
||||
officersView: {
|
||||
title: 'Offiziere',
|
||||
activated: 'Aktiviert',
|
||||
inactive: 'Inaktiv',
|
||||
activeStatus: 'Aktivierungsstatus',
|
||||
expirationTime: 'Ablaufzeit',
|
||||
remainingTime: 'Verbleibende Zeit',
|
||||
recruitCost: 'Rekrutierungskosten',
|
||||
days: 'Tage',
|
||||
benefitsBonus: 'Vorteile',
|
||||
resourceProduction: 'Ressourcenproduktion',
|
||||
darkMatterProduction: 'Dunkle-Materie-Produktion',
|
||||
energyProduction: 'Energieproduktion',
|
||||
buildingSpeed: 'Baugeschwindigkeit',
|
||||
researchSpeed: 'Forschungsgeschwindigkeit',
|
||||
fleetSpeed: 'Flottengeschwindigkeit',
|
||||
fuelConsumption: 'Treibstoffverbrauch',
|
||||
defense: 'Verteidigung',
|
||||
storageCapacity: 'Lagerkapazität',
|
||||
buildQueue: 'Bauauftrag',
|
||||
fleetSlots: 'Flottenslots',
|
||||
hire: 'Rekrutieren',
|
||||
renew: 'Verlängern',
|
||||
dismiss: 'Entlassen',
|
||||
hireTitle: 'Offizier rekrutieren',
|
||||
hireMessage: 'Möchten Sie wirklich {name} rekrutieren? Gültig für 7 Tage.',
|
||||
renewTitle: 'Offizier verlängern',
|
||||
renewMessage: 'Möchten Sie wirklich {name} für 7 Tage verlängern?',
|
||||
dismissTitle: 'Offizier entlassen',
|
||||
dismissMessage: 'Möchten Sie wirklich {name} entlassen? Es werden keine Kosten zurückerstattet.',
|
||||
hireFailed: 'Rekrutierung fehlgeschlagen',
|
||||
renewFailed: 'Verlängerung fehlgeschlagen',
|
||||
insufficientResources: 'Nicht genug Ressourcen!'
|
||||
},
|
||||
galaxyView: {
|
||||
title: 'Galaxie',
|
||||
selectCoordinates: 'Koordinaten auswählen',
|
||||
galaxy: 'Galaxie',
|
||||
selectGalaxy: 'Galaxie auswählen',
|
||||
system: 'System',
|
||||
selectSystem: 'System auswählen',
|
||||
view: 'Anzeigen',
|
||||
myPlanet: 'Mein Planet',
|
||||
totalPositions: 'Insgesamt 10 Planetenpositionen',
|
||||
mine: 'Mein',
|
||||
hostile: 'Feindlich',
|
||||
emptySlot: 'Leer - Kolonisierbar',
|
||||
scout: 'Spähen',
|
||||
attack: 'Angriff',
|
||||
colonize: 'Kolonisieren',
|
||||
switch: 'Wechseln',
|
||||
scoutPlanetTitle: 'Planet ausspionieren',
|
||||
attackPlanetTitle: 'Planet angreifen',
|
||||
colonizePlanetTitle: 'Planet kolonisieren',
|
||||
scoutPlanetMessage:
|
||||
'Möchten Sie wirklich Spionagesonden senden, um Planet [{coordinates}] auszuspionieren?\n\nBitte gehen Sie zur Flottenseite, um Schiffe auszuwählen und zu senden.',
|
||||
attackPlanetMessage:
|
||||
'Möchten Sie wirklich Planet [{coordinates}] angreifen?\n\nBitte gehen Sie zur Flottenseite, um Schiffe auszuwählen und zu senden.',
|
||||
colonizePlanetMessage:
|
||||
'Möchten Sie wirklich Position [{coordinates}] kolonisieren?\n\nBitte gehen Sie zur Flottenseite, um ein Kolonieschiff zu senden.'
|
||||
},
|
||||
messagesView: {
|
||||
title: 'Nachrichten',
|
||||
battleReports: 'Kampfberichte',
|
||||
spyReports: 'Spionageberichte',
|
||||
noBattleReports: 'Keine Kampfberichte',
|
||||
noSpyReports: 'Keine Spionageberichte',
|
||||
battleReport: 'Kampfbericht',
|
||||
spyReport: 'Spionagebericht',
|
||||
victory: 'Sieg',
|
||||
defeat: 'Niederlage',
|
||||
draw: 'Unentschieden',
|
||||
attackerFleet: 'Angreiferflotte',
|
||||
defenderFleet: 'Verteidigerflotte',
|
||||
defenderDefense: 'Verteidigerverteidigung',
|
||||
attackerLosses: 'Angreiferverluste',
|
||||
defenderLosses: 'Verteidigerverluste',
|
||||
noLosses: 'Keine Verluste',
|
||||
plunder: 'Beute',
|
||||
debrisField: 'Trümmerfeld',
|
||||
resources: 'Ressourcen',
|
||||
fleet: 'Flotte',
|
||||
defense: 'Verteidigung',
|
||||
buildings: 'Gebäude'
|
||||
},
|
||||
simulatorView: {
|
||||
title: 'Kampfsimulator',
|
||||
attacker: 'Angreifer',
|
||||
defender: 'Verteidiger',
|
||||
attackerConfig: 'Angreiferkonfiguration',
|
||||
attackerConfigDesc: 'Angreiferflotte und Technologiestufen konfigurieren',
|
||||
defenderConfig: 'Verteidigerkonfiguration',
|
||||
defenderConfigDesc: 'Verteidigerflotte, Verteidigung und Technologiestufen konfigurieren',
|
||||
fleet: 'Flotte',
|
||||
defenseStructures: 'Verteidigungsanlagen',
|
||||
techLevels: 'Technologiestufen',
|
||||
weapon: 'Waffe',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
defenderResources: 'Verteidigerressourcen (für Beuteberechnung)',
|
||||
startSimulation: 'Simulation starten',
|
||||
reset: 'Zurücksetzen',
|
||||
battleResult: 'Kampfergebnis',
|
||||
attackerVictory: 'Angreifer Sieg',
|
||||
defenderVictory: 'Verteidiger Sieg',
|
||||
draw: 'Unentschieden',
|
||||
afterRounds: 'Nach {rounds} Runden',
|
||||
attackerLosses: 'Angreiferverluste',
|
||||
defenderLosses: 'Verteidigerverluste',
|
||||
noLosses: 'Keine Verluste',
|
||||
attackerRemaining: 'Angreifer verblieben',
|
||||
defenderRemaining: 'Verteidiger verblieben',
|
||||
allDestroyed: 'Alle zerstört',
|
||||
plunderableResources: 'Erbeutbare Ressourcen',
|
||||
debrisField: 'Trümmerfeld',
|
||||
moonChance: 'Mondchance',
|
||||
showRoundDetails: 'Rundendetails anzeigen',
|
||||
hideRoundDetails: 'Rundendetails ausblenden',
|
||||
round: 'Runde {round}',
|
||||
attackerRemainingPower: 'Verbleibende Angreiferkraft',
|
||||
defenderRemainingPower: 'Verbleibende Verteidigerkraft'
|
||||
},
|
||||
settings: {
|
||||
dataManagement: 'Datenverwaltung',
|
||||
dataManagementDesc: 'Spieldaten exportieren, importieren oder löschen',
|
||||
exportData: 'Daten exportieren',
|
||||
exportDataDesc: 'Spielfortschritt als JSON-Datei exportieren',
|
||||
export: 'Exportieren',
|
||||
exporting: 'Exportieren...',
|
||||
exportSuccess: 'Export erfolgreich',
|
||||
exportFailed: 'Export fehlgeschlagen, bitte erneut versuchen',
|
||||
importData: 'Daten importieren',
|
||||
importDataDesc: 'Spielfortschritt aus JSON-Datei wiederherstellen',
|
||||
selectFile: 'Datei auswählen',
|
||||
importSuccess: 'Import erfolgreich',
|
||||
importConfirmTitle: 'Import bestätigen',
|
||||
importConfirmMessage: 'Beim Importieren wird der aktuelle Spielfortschritt überschrieben. Diese Aktion kann nicht rückgängig gemacht werden. Fortfahren?',
|
||||
importFailed: 'Import fehlgeschlagen, bitte Dateiformat überprüfen',
|
||||
clearData: 'Daten löschen',
|
||||
clearDataDesc: 'Alle Spieldaten löschen und zurücksetzen',
|
||||
clear: 'Löschen',
|
||||
clearConfirmTitle: 'Löschen bestätigen',
|
||||
clearConfirmMessage: 'Alle Spieldaten werden gelöscht und von vorne begonnen. Diese Aktion kann nicht rückgängig gemacht werden. Fortfahren?',
|
||||
gameSettings: 'Spieleinstellungen',
|
||||
gameSettingsDesc: 'Spielparameter und Einstellungen anpassen',
|
||||
playerName: 'Spielername',
|
||||
gameSpeed: 'Spielgeschwindigkeit',
|
||||
gameSpeedDesc: 'Aktueller Spielgeschwindigkeitsmultiplikator',
|
||||
about: 'Über',
|
||||
version: 'Version',
|
||||
buildDate: 'Build-Datum',
|
||||
community: 'Community',
|
||||
github: 'GitHub-Repository',
|
||||
qqGroup: 'QQ-Gruppe'
|
||||
}
|
||||
}
|
||||
533
src/locales/en.ts
Normal file
533
src/locales/en.ts
Normal file
@@ -0,0 +1,533 @@
|
||||
export default {
|
||||
common: {
|
||||
confirm: 'Confirm',
|
||||
cancel: 'Cancel',
|
||||
delete: 'Delete',
|
||||
edit: 'Edit',
|
||||
save: 'Save',
|
||||
close: 'Close',
|
||||
back: 'Back',
|
||||
next: 'Next',
|
||||
previous: 'Previous',
|
||||
submit: 'Submit',
|
||||
reset: 'Reset',
|
||||
search: 'Search',
|
||||
filter: 'Filter',
|
||||
loading: 'Loading...',
|
||||
noData: 'No Data',
|
||||
error: 'Error',
|
||||
success: 'Success',
|
||||
warning: 'Warning',
|
||||
info: 'Info',
|
||||
resourceType: 'Resource Type',
|
||||
playerName: 'Commander',
|
||||
timeHour: 'h',
|
||||
timeMinute: 'm',
|
||||
timeSecond: 's',
|
||||
featureLocked: 'Feature Locked',
|
||||
unlockRequired: 'Building Required',
|
||||
requiredBuilding: 'Required Building',
|
||||
currentLevel: 'Current Level',
|
||||
goToBuildings: 'Go to Buildings',
|
||||
locked: 'Locked',
|
||||
viewRequirements: 'View Requirements',
|
||||
requirementsNotMet: 'Requirements Not Met',
|
||||
current: 'Current'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: 'Requirements not met',
|
||||
insufficientResources: 'Insufficient resources',
|
||||
shieldDomeLimit: 'Shield dome limit reached',
|
||||
fleetMissionsFull: 'Fleet mission slots full',
|
||||
insufficientFleet: 'Insufficient fleet',
|
||||
insufficientFuel: 'Insufficient fuel',
|
||||
planetOnly: 'This building can only be built on planets',
|
||||
moonOnly: 'This building can only be built on moons',
|
||||
buildQueueFull: 'Build queue full',
|
||||
insufficientSpace: 'Insufficient space',
|
||||
buildingLevelZero: 'Building level is 0, cannot demolish',
|
||||
researchQueueFull: 'Research queue full',
|
||||
moonExists: 'Moon already exists',
|
||||
insufficientDebris: 'Insufficient debris field'
|
||||
},
|
||||
nav: {
|
||||
overview: 'Overview',
|
||||
buildings: 'Buildings',
|
||||
research: 'Research',
|
||||
shipyard: 'Shipyard',
|
||||
defense: 'Defense',
|
||||
fleet: 'Fleet',
|
||||
officers: 'Officers',
|
||||
simulator: 'Simulator',
|
||||
galaxy: 'Galaxy',
|
||||
messages: 'Messages',
|
||||
settings: 'Settings'
|
||||
},
|
||||
sidebar: {
|
||||
language: 'Language',
|
||||
lightMode: 'Light Mode',
|
||||
darkMode: 'Dark Mode',
|
||||
collapse: 'Collapse',
|
||||
expand: 'Expand'
|
||||
},
|
||||
resources: {
|
||||
metal: 'Metal',
|
||||
crystal: 'Crystal',
|
||||
deuterium: 'Deuterium',
|
||||
darkMatter: 'Dark Matter',
|
||||
energy: 'Energy',
|
||||
production: 'Production',
|
||||
capacity: 'Capacity',
|
||||
current: 'Current',
|
||||
max: 'Max Capacity',
|
||||
perHour: 'hour'
|
||||
},
|
||||
planet: {
|
||||
planet: 'Planet',
|
||||
moon: 'Moon',
|
||||
colony: 'Colony',
|
||||
position: 'Position',
|
||||
coordinates: 'Coordinates',
|
||||
switchToMoon: 'View Moon',
|
||||
backToPlanet: 'Back to Planet',
|
||||
fields: 'Fields',
|
||||
temperature: 'Temperature',
|
||||
homePlanet: 'Home Planet',
|
||||
planetPrefix: 'Planet',
|
||||
moonSuffix: "'s Moon",
|
||||
colonyPrefix: 'Colony'
|
||||
},
|
||||
player: {
|
||||
points: 'Total Points'
|
||||
},
|
||||
buildings: {
|
||||
metalMine: 'Metal Mine',
|
||||
crystalMine: 'Crystal Mine',
|
||||
deuteriumSynthesizer: 'Deuterium Synthesizer',
|
||||
solarPlant: 'Solar Plant',
|
||||
roboticsFactory: 'Robotics Factory',
|
||||
naniteFactory: 'Nanite Factory',
|
||||
shipyard: 'Shipyard',
|
||||
researchLab: 'Research Lab',
|
||||
metalStorage: 'Metal Storage',
|
||||
crystalStorage: 'Crystal Storage',
|
||||
deuteriumTank: 'Deuterium Tank',
|
||||
darkMatterCollector: 'Dark Matter Collector',
|
||||
lunarBase: 'Lunar Base',
|
||||
sensorPhalanx: 'Sensor Phalanx',
|
||||
jumpGate: 'Jump Gate',
|
||||
buildTime: 'Build Time',
|
||||
production: 'Production',
|
||||
consumption: 'Consumption',
|
||||
totalCost: 'Total Cost',
|
||||
totalPoints: 'Total Points',
|
||||
levelRange: 'Level Range'
|
||||
},
|
||||
buildingDescriptions: {
|
||||
metalMine: 'Extracts metal resources',
|
||||
crystalMine: 'Extracts crystal resources',
|
||||
deuteriumSynthesizer: 'Synthesizes deuterium resources',
|
||||
solarPlant: 'Provides energy',
|
||||
roboticsFactory: 'Accelerates construction speed',
|
||||
naniteFactory: 'Increases build queue capacity, +1 per level (max 10)',
|
||||
shipyard: 'Constructs ships',
|
||||
researchLab: 'Researches technologies',
|
||||
metalStorage: 'Increases metal storage capacity',
|
||||
crystalStorage: 'Increases crystal storage capacity',
|
||||
deuteriumTank: 'Increases deuterium storage capacity',
|
||||
darkMatterCollector: 'Collects rare dark matter resources',
|
||||
lunarBase: 'Increases available space on the moon',
|
||||
sensorPhalanx: 'Detects fleet activities in surrounding systems',
|
||||
jumpGate: 'Instantly transfers fleets to other moons'
|
||||
},
|
||||
ships: {
|
||||
lightFighter: 'Light Fighter',
|
||||
heavyFighter: 'Heavy Fighter',
|
||||
cruiser: 'Cruiser',
|
||||
battleship: 'Battleship',
|
||||
smallCargo: 'Small Cargo',
|
||||
largeCargo: 'Large Cargo',
|
||||
colonyShip: 'Colony Ship',
|
||||
recycler: 'Recycler',
|
||||
espionageProbe: 'Espionage Probe',
|
||||
darkMatterHarvester: 'Dark Matter Harvester'
|
||||
},
|
||||
shipDescriptions: {
|
||||
lightFighter: 'Basic combat unit',
|
||||
heavyFighter: 'Heavily armored fighter',
|
||||
cruiser: 'Medium warship, balanced offense and defense',
|
||||
battleship: 'Powerful warship',
|
||||
smallCargo: 'Transports small amounts of resources',
|
||||
largeCargo: 'Transports large amounts of resources',
|
||||
colonyShip: 'Used to colonize new planets',
|
||||
recycler: 'Collects debris field resources',
|
||||
espionageProbe: 'Scouts enemy planets',
|
||||
darkMatterHarvester: 'Special ship for harvesting dark matter'
|
||||
},
|
||||
defenses: {
|
||||
rocketLauncher: 'Rocket Launcher',
|
||||
lightLaser: 'Light Laser',
|
||||
heavyLaser: 'Heavy Laser',
|
||||
gaussCannon: 'Gauss Cannon',
|
||||
ionCannon: 'Ion Cannon',
|
||||
plasmaTurret: 'Plasma Turret',
|
||||
smallShieldDome: 'Small Shield Dome',
|
||||
largeShieldDome: 'Large Shield Dome'
|
||||
},
|
||||
defenseDescriptions: {
|
||||
rocketLauncher: 'Basic defense facility',
|
||||
lightLaser: 'Light energy weapon',
|
||||
heavyLaser: 'Heavy energy weapon',
|
||||
gaussCannon: 'High-speed kinetic weapon',
|
||||
ionCannon: 'Effective against shields',
|
||||
plasmaTurret: 'Powerful defense facility',
|
||||
smallShieldDome: 'Small shield protecting the entire planet',
|
||||
largeShieldDome: 'Large shield protecting the entire planet'
|
||||
},
|
||||
research: {
|
||||
researchTime: 'Research Time',
|
||||
totalCost: 'Total Cost',
|
||||
totalPoints: 'Total Points',
|
||||
levelRange: 'Level Range'
|
||||
},
|
||||
technologies: {
|
||||
energyTechnology: 'Energy Technology',
|
||||
laserTechnology: 'Laser Technology',
|
||||
ionTechnology: 'Ion Technology',
|
||||
hyperspaceTechnology: 'Hyperspace Technology',
|
||||
plasmaTechnology: 'Plasma Technology',
|
||||
computerTechnology: 'Computer Technology',
|
||||
combustionDrive: 'Combustion Drive',
|
||||
impulseDrive: 'Impulse Drive',
|
||||
hyperspaceDrive: 'Hyperspace Drive',
|
||||
darkMatterTechnology: 'Dark Matter Technology'
|
||||
},
|
||||
technologyDescriptions: {
|
||||
energyTechnology: 'Improves energy efficiency',
|
||||
laserTechnology: 'Foundation of laser weapons and defense',
|
||||
ionTechnology: 'Ion weapon technology',
|
||||
hyperspaceTechnology: 'Hyperspace jump technology',
|
||||
plasmaTechnology: 'Plasma weapon technology',
|
||||
computerTechnology: 'Increases research queue capacity, +1 per level (max 10)',
|
||||
combustionDrive: 'Basic propulsion technology',
|
||||
impulseDrive: 'Intermediate propulsion technology',
|
||||
hyperspaceDrive: 'Advanced propulsion technology',
|
||||
darkMatterTechnology: 'Research into dark matter properties and applications'
|
||||
},
|
||||
officers: {
|
||||
commander: 'Commander',
|
||||
admiral: 'Admiral',
|
||||
engineer: 'Engineer',
|
||||
geologist: 'Geologist',
|
||||
technocrat: 'Technocrat',
|
||||
darkMatterSpecialist: 'Dark Matter Specialist'
|
||||
},
|
||||
officerDescriptions: {
|
||||
commander: 'Improves building speed and management',
|
||||
admiral: 'Improves fleet combat and speed',
|
||||
engineer: 'Improves energy and defense',
|
||||
geologist: 'Improves resource production',
|
||||
technocrat: 'Improves research speed and espionage',
|
||||
darkMatterSpecialist: 'Improves dark matter collection efficiency'
|
||||
},
|
||||
queue: {
|
||||
buildQueue: 'Build Queue',
|
||||
researchQueue: 'Research Queue',
|
||||
building: 'Building',
|
||||
researching: 'Researching',
|
||||
remaining: 'Remaining',
|
||||
cancel: 'Cancel',
|
||||
cancelBuild: 'Cancel Build',
|
||||
cancelResearch: 'Cancel Research',
|
||||
confirmCancel: 'Are you sure you want to cancel? 50% of resources will be refunded.',
|
||||
level: 'Level',
|
||||
upgradeToLevel: 'Upgrade to Level'
|
||||
},
|
||||
overview: {
|
||||
title: 'Planet Overview',
|
||||
resourceOverview: 'Resources',
|
||||
fleetInfo: 'Fleet',
|
||||
currentShips: 'Ships on this planet'
|
||||
},
|
||||
buildingsView: {
|
||||
title: 'Buildings',
|
||||
usedSpace: 'Used Space',
|
||||
spaceUsage: 'Space Usage',
|
||||
level: 'Level',
|
||||
upgradeCost: 'Upgrade Cost',
|
||||
buildTime: 'Build Time',
|
||||
upgrade: 'Upgrade',
|
||||
upgradeFailed: 'Upgrade Failed',
|
||||
upgradeFailedMessage: 'Please check if you have enough resources, space, or if there are other build tasks.',
|
||||
demolish: 'Demolish',
|
||||
demolishRefund: 'Demolish Refund',
|
||||
demolishFailed: 'Demolish Failed',
|
||||
demolishFailedMessage: 'Unable to demolish this building. Please check if the build queue is full or the building level is 0.'
|
||||
},
|
||||
researchView: {
|
||||
title: 'Research',
|
||||
researchCost: 'Research Cost',
|
||||
research: 'Research',
|
||||
researchFailed: 'Research Failed',
|
||||
researchFailedMessage: 'Please check if you have enough resources, prerequisites are met, or if there are other research tasks.'
|
||||
},
|
||||
shipyard: {
|
||||
attack: 'Attack',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
speed: 'Speed',
|
||||
cargoCapacity: 'Cargo Capacity',
|
||||
fuelConsumption: 'Fuel Consumption',
|
||||
buildCost: 'Build Cost',
|
||||
buildTime: 'Build Time',
|
||||
perUnit: 'Per Unit',
|
||||
batchCalculator: 'Batch Calculator',
|
||||
quantity: 'Quantity',
|
||||
totalCost: 'Total Cost',
|
||||
totalTime: 'Total Time'
|
||||
},
|
||||
shipyardView: {
|
||||
title: 'Shipyard',
|
||||
attack: 'Attack',
|
||||
shield: 'Shield',
|
||||
speed: 'Speed',
|
||||
cargoCapacity: 'Cargo Capacity',
|
||||
unitCost: 'Unit Cost',
|
||||
buildQuantity: 'Build Quantity',
|
||||
totalCost: 'Total Cost',
|
||||
build: 'Build',
|
||||
inputError: 'Input Error',
|
||||
inputErrorMessage: 'Please enter build quantity!',
|
||||
buildFailed: 'Build Failed',
|
||||
buildFailedMessage: 'Please check if you have enough resources or if prerequisites are met.'
|
||||
},
|
||||
defense: {
|
||||
attack: 'Attack',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
buildCost: 'Build Cost',
|
||||
buildTime: 'Build Time',
|
||||
perUnit: 'Per Unit',
|
||||
batchCalculator: 'Batch Calculator',
|
||||
quantity: 'Quantity',
|
||||
totalCost: 'Total Cost',
|
||||
totalTime: 'Total Time'
|
||||
},
|
||||
defenseView: {
|
||||
title: 'Defense',
|
||||
attack: 'Attack',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
buildTime: 'Build Time',
|
||||
seconds: 's',
|
||||
unitCost: 'Unit Cost',
|
||||
buildQuantity: 'Build Quantity',
|
||||
totalCost: 'Total Cost',
|
||||
build: 'Build',
|
||||
shieldDomeBuilt: 'Shield dome already built',
|
||||
inputError: 'Input Error',
|
||||
inputErrorMessage: 'Please enter build quantity!',
|
||||
buildFailed: 'Build Failed',
|
||||
buildFailedMessage: 'Please check if you have enough resources or if prerequisites are met. Shield domes can only be built once.'
|
||||
},
|
||||
fleetView: {
|
||||
title: 'Fleet Management',
|
||||
fleetOverview: 'Fleet Overview',
|
||||
sendFleet: 'Send Fleet',
|
||||
flightMissions: 'Flight Missions',
|
||||
currentPlanetFleet: 'Current Planet Fleet',
|
||||
attack: 'Attack',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
speed: 'Speed',
|
||||
cargo: 'Cargo',
|
||||
selectFleet: 'Select Fleet',
|
||||
selectFleetDescription: 'Select the number of ships to send',
|
||||
available: 'Available',
|
||||
all: 'All',
|
||||
targetCoordinates: 'Target Coordinates',
|
||||
galaxy: 'Galaxy',
|
||||
system: 'System',
|
||||
position: 'Position',
|
||||
missionType: 'Mission Type',
|
||||
missionInfo: 'Mission Info',
|
||||
fuelConsumption: 'Fuel Consumption',
|
||||
flightTime: 'Flight Time',
|
||||
attackMission: 'Attack',
|
||||
transport: 'Transport',
|
||||
colonize: 'Colonize',
|
||||
spy: 'Spy',
|
||||
deploy: 'Deploy',
|
||||
transportResources: 'Transport Resources',
|
||||
totalCargoCapacity: 'Total Cargo Capacity',
|
||||
used: 'Used',
|
||||
noFlightMissions: 'No flight missions',
|
||||
outbound: 'Outbound',
|
||||
returning: 'Returning',
|
||||
fleetComposition: 'Fleet Composition',
|
||||
carryingResources: 'Carrying Resources',
|
||||
arrivalTime: 'Arrival Time',
|
||||
returnTime: 'Return Time',
|
||||
recallFleet: 'Recall Fleet',
|
||||
sendFailed: 'Send Failed',
|
||||
sendFailedMessage: 'Please check fleet count, fuel availability, or cargo capacity limits.',
|
||||
recallFailed: 'Recall Failed',
|
||||
recallFailedMessage: 'This mission cannot be recalled.',
|
||||
unknownPlanet: 'Unknown Planet',
|
||||
fleetMissionSlots: 'Fleet Mission Slots'
|
||||
},
|
||||
officersView: {
|
||||
title: 'Officers',
|
||||
activated: 'Activated',
|
||||
inactive: 'Inactive',
|
||||
activeStatus: 'Active Status',
|
||||
expirationTime: 'Expiration Time',
|
||||
remainingTime: 'Remaining Time',
|
||||
recruitCost: 'Recruitment Cost',
|
||||
days: 'days',
|
||||
benefitsBonus: 'Benefits Bonus',
|
||||
resourceProduction: 'Resource Production',
|
||||
darkMatterProduction: 'Dark Matter Production',
|
||||
energyProduction: 'Energy Production',
|
||||
buildingSpeed: 'Building Speed',
|
||||
researchSpeed: 'Research Speed',
|
||||
fleetSpeed: 'Fleet Speed',
|
||||
fuelConsumption: 'Fuel Consumption',
|
||||
defense: 'Defense',
|
||||
storageCapacity: 'Storage Capacity',
|
||||
buildQueue: 'Build Queue',
|
||||
fleetSlots: 'Fleet Slots',
|
||||
hire: 'Hire',
|
||||
renew: 'Renew',
|
||||
dismiss: 'Dismiss',
|
||||
hireTitle: 'Hire Officer',
|
||||
hireMessage: 'Are you sure you want to hire {name}? Valid for 7 days.',
|
||||
renewTitle: 'Renew Officer',
|
||||
renewMessage: 'Are you sure you want to renew {name} for 7 days?',
|
||||
dismissTitle: 'Dismiss Officer',
|
||||
dismissMessage: 'Are you sure you want to dismiss {name}? No refunds will be given.',
|
||||
hireFailed: 'Hire Failed',
|
||||
renewFailed: 'Renew Failed',
|
||||
insufficientResources: 'Insufficient resources!'
|
||||
},
|
||||
galaxyView: {
|
||||
title: 'Galaxy',
|
||||
selectCoordinates: 'Select Coordinates',
|
||||
galaxy: 'Galaxy',
|
||||
selectGalaxy: 'Select Galaxy',
|
||||
system: 'System',
|
||||
selectSystem: 'Select System',
|
||||
view: 'View',
|
||||
myPlanet: 'My Planet',
|
||||
totalPositions: '10 planet positions total',
|
||||
mine: 'Mine',
|
||||
hostile: 'Hostile',
|
||||
emptySlot: 'Empty - Colonizable',
|
||||
scout: 'Scout',
|
||||
attack: 'Attack',
|
||||
colonize: 'Colonize',
|
||||
switch: 'Switch',
|
||||
scoutPlanetTitle: 'Scout Planet',
|
||||
attackPlanetTitle: 'Attack Planet',
|
||||
colonizePlanetTitle: 'Colonize Planet',
|
||||
scoutPlanetMessage:
|
||||
'Are you sure you want to send espionage probes to scout planet [{coordinates}]?\n\nPlease go to the fleet page to select ships and send.',
|
||||
attackPlanetMessage: 'Are you sure you want to attack planet [{coordinates}]?\n\nPlease go to the fleet page to select ships and send.',
|
||||
colonizePlanetMessage:
|
||||
'Are you sure you want to colonize position [{coordinates}]?\n\nPlease go to the fleet page to send a colony ship.'
|
||||
},
|
||||
messagesView: {
|
||||
title: 'Messages',
|
||||
battleReports: 'Battle Reports',
|
||||
spyReports: 'Spy Reports',
|
||||
noBattleReports: 'No battle reports',
|
||||
noSpyReports: 'No spy reports',
|
||||
battleReport: 'Battle Report',
|
||||
spyReport: 'Spy Report',
|
||||
victory: 'Victory',
|
||||
defeat: 'Defeat',
|
||||
draw: 'Draw',
|
||||
attackerFleet: 'Attacker Fleet',
|
||||
defenderFleet: 'Defender Fleet',
|
||||
defenderDefense: 'Defender Defense',
|
||||
attackerLosses: 'Attacker Losses',
|
||||
defenderLosses: 'Defender Losses',
|
||||
noLosses: 'No losses',
|
||||
plunder: 'Plunder',
|
||||
debrisField: 'Debris Field',
|
||||
resources: 'Resources',
|
||||
fleet: 'Fleet',
|
||||
defense: 'Defense',
|
||||
buildings: 'Buildings'
|
||||
},
|
||||
simulatorView: {
|
||||
title: 'Battle Simulator',
|
||||
attacker: 'Attacker',
|
||||
defender: 'Defender',
|
||||
attackerConfig: 'Attacker Configuration',
|
||||
attackerConfigDesc: 'Configure attacker fleet and technology levels',
|
||||
defenderConfig: 'Defender Configuration',
|
||||
defenderConfigDesc: 'Configure defender fleet, defense, and technology levels',
|
||||
fleet: 'Fleet',
|
||||
defenseStructures: 'Defense Structures',
|
||||
techLevels: 'Technology Levels',
|
||||
weapon: 'Weapon',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
defenderResources: 'Defender Resources (for plunder calculation)',
|
||||
startSimulation: 'Start Simulation',
|
||||
reset: 'Reset',
|
||||
battleResult: 'Battle Result',
|
||||
attackerVictory: 'Attacker Victory',
|
||||
defenderVictory: 'Defender Victory',
|
||||
draw: 'Draw',
|
||||
afterRounds: 'After {rounds} rounds',
|
||||
attackerLosses: 'Attacker Losses',
|
||||
defenderLosses: 'Defender Losses',
|
||||
noLosses: 'No losses',
|
||||
attackerRemaining: 'Attacker Remaining',
|
||||
defenderRemaining: 'Defender Remaining',
|
||||
allDestroyed: 'All destroyed',
|
||||
plunderableResources: 'Plunderable Resources',
|
||||
debrisField: 'Debris Field',
|
||||
moonChance: 'Moon chance',
|
||||
showRoundDetails: 'Show round details',
|
||||
hideRoundDetails: 'Hide round details',
|
||||
round: 'Round {round}',
|
||||
attackerRemainingPower: 'Attacker remaining power',
|
||||
defenderRemainingPower: 'Defender remaining power'
|
||||
},
|
||||
settings: {
|
||||
dataManagement: 'Data Management',
|
||||
dataManagementDesc: 'Export, import, or clear game data',
|
||||
exportData: 'Export Data',
|
||||
exportDataDesc: 'Export game progress as JSON file',
|
||||
export: 'Export',
|
||||
exporting: 'Exporting...',
|
||||
exportSuccess: 'Export successful',
|
||||
exportFailed: 'Export failed, please try again',
|
||||
importData: 'Import Data',
|
||||
importDataDesc: 'Restore game progress from JSON file',
|
||||
selectFile: 'Select File',
|
||||
importSuccess: 'Import successful',
|
||||
importConfirmTitle: 'Confirm Import',
|
||||
importConfirmMessage: 'Importing will overwrite current game progress. This action cannot be undone. Continue?',
|
||||
importFailed: 'Import failed, please check file format',
|
||||
clearData: 'Clear Data',
|
||||
clearDataDesc: 'Delete all game data and reset',
|
||||
clear: 'Clear',
|
||||
clearConfirmTitle: 'Confirm Clear Data',
|
||||
clearConfirmMessage: 'This will delete all game data and start over. This action cannot be undone. Continue?',
|
||||
gameSettings: 'Game Settings',
|
||||
gameSettingsDesc: 'Adjust game parameters and preferences',
|
||||
playerName: 'Player Name',
|
||||
gameSpeed: 'Game Speed',
|
||||
gameSpeedDesc: 'Current game speed multiplier',
|
||||
about: 'About',
|
||||
version: 'Version',
|
||||
buildDate: 'Build Date',
|
||||
community: 'Community',
|
||||
github: 'GitHub Repository',
|
||||
qqGroup: 'QQ Group'
|
||||
}
|
||||
}
|
||||
53
src/locales/index.ts
Normal file
53
src/locales/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import zhCN from './zh-CN'
|
||||
import zhTW from './zh-TW'
|
||||
import en from './en'
|
||||
import de from './de'
|
||||
import ru from './ru'
|
||||
import ko from './ko'
|
||||
import ja from './ja'
|
||||
|
||||
export type Locale = 'zh-CN' | 'zh-TW' | 'en' | 'de' | 'ru' | 'ko' | 'ja'
|
||||
|
||||
export const locales = { 'zh-CN': zhCN, 'zh-TW': zhTW, en, de, ru, ko, ja }
|
||||
|
||||
export const localeNames: Record<Locale, string> = {
|
||||
'zh-CN': '简体中文',
|
||||
'zh-TW': '繁體中文',
|
||||
en: 'English',
|
||||
de: 'Deutsch',
|
||||
ru: 'Русский',
|
||||
ko: '한국어',
|
||||
ja: '日本語'
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据浏览器语言检测并返回应用支持的语言
|
||||
* @returns 检测到的语言代码
|
||||
*/
|
||||
export const detectBrowserLocale = (): Locale => {
|
||||
// 获取浏览器语言
|
||||
const browserLang = navigator.language || (navigator.languages && navigator.languages[0]) || 'zh-CN'
|
||||
const lang = browserLang.toLowerCase()
|
||||
|
||||
// 映射浏览器语言到应用支持的语言
|
||||
if (lang.startsWith('zh-tw') || lang.startsWith('zh-hant') || lang.startsWith('zh-hk') || lang.startsWith('zh-mo')) {
|
||||
return 'zh-TW'
|
||||
} else if (lang.startsWith('zh')) {
|
||||
return 'zh-CN'
|
||||
} else if (lang.startsWith('ja')) {
|
||||
return 'ja'
|
||||
} else if (lang.startsWith('ko')) {
|
||||
return 'ko'
|
||||
} else if (lang.startsWith('en')) {
|
||||
return 'en'
|
||||
} else if (lang.startsWith('de')) {
|
||||
return 'de'
|
||||
} else if (lang.startsWith('ru')) {
|
||||
return 'ru'
|
||||
}
|
||||
|
||||
// 默认返回简体中文
|
||||
return 'zh-CN'
|
||||
}
|
||||
|
||||
export type TranslationSchema = typeof zhCN
|
||||
531
src/locales/ja.ts
Normal file
531
src/locales/ja.ts
Normal file
@@ -0,0 +1,531 @@
|
||||
export default {
|
||||
common: {
|
||||
confirm: '確認',
|
||||
cancel: 'キャンセル',
|
||||
delete: '削除',
|
||||
edit: '編集',
|
||||
save: '保存',
|
||||
close: '閉じる',
|
||||
back: '戻る',
|
||||
next: '次へ',
|
||||
previous: '前へ',
|
||||
submit: '送信',
|
||||
reset: 'リセット',
|
||||
search: '検索',
|
||||
filter: 'フィルター',
|
||||
loading: '読み込み中...',
|
||||
noData: 'データなし',
|
||||
error: 'エラー',
|
||||
success: '成功',
|
||||
warning: '警告',
|
||||
info: '情報',
|
||||
resourceType: '資源タイプ',
|
||||
playerName: '司令官',
|
||||
timeHour: '時間',
|
||||
timeMinute: '分',
|
||||
timeSecond: '秒',
|
||||
featureLocked: '機能がロックされています',
|
||||
unlockRequired: '建物が必要です',
|
||||
requiredBuilding: '必要な建物',
|
||||
currentLevel: '現在のレベル',
|
||||
goToBuildings: '建物へ移動',
|
||||
locked: 'ロック済み',
|
||||
viewRequirements: '必要条件を表示',
|
||||
requirementsNotMet: '必要条件が満たされていません',
|
||||
current: '現在'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: '前提条件を満たしていません',
|
||||
insufficientResources: '資源が不足しています',
|
||||
shieldDomeLimit: 'シールドドームの上限に達しました',
|
||||
fleetMissionsFull: '艦隊ミッションスロットが満杯です',
|
||||
insufficientFleet: '艦隊が不足しています',
|
||||
insufficientFuel: '燃料が不足しています',
|
||||
planetOnly: 'この建物は惑星でのみ建設できます',
|
||||
moonOnly: 'この建物は月でのみ建設できます',
|
||||
buildQueueFull: '建設キューが満杯です',
|
||||
insufficientSpace: 'スペースが不足しています',
|
||||
buildingLevelZero: '建物レベルが0のため、解体できません',
|
||||
researchQueueFull: '研究キューが満杯です',
|
||||
moonExists: '月は既に存在します',
|
||||
insufficientDebris: '残骸フィールドが不足しています'
|
||||
},
|
||||
nav: {
|
||||
overview: '概要',
|
||||
buildings: '建物',
|
||||
research: '研究',
|
||||
shipyard: '造船所',
|
||||
defense: '防衛',
|
||||
fleet: '艦隊',
|
||||
officers: '士官',
|
||||
simulator: 'シミュレーター',
|
||||
galaxy: '銀河',
|
||||
messages: 'メッセージ',
|
||||
settings: '設定'
|
||||
},
|
||||
sidebar: {
|
||||
language: '言語',
|
||||
lightMode: 'ライトモード',
|
||||
darkMode: 'ダークモード',
|
||||
collapse: 'メニューを閉じる',
|
||||
expand: 'メニューを開く'
|
||||
},
|
||||
resources: {
|
||||
metal: '金属',
|
||||
crystal: 'クリスタル',
|
||||
deuterium: '重水素',
|
||||
darkMatter: 'ダークマター',
|
||||
energy: 'エネルギー',
|
||||
production: '生産量',
|
||||
capacity: '容量',
|
||||
current: '現在の貯蔵量',
|
||||
max: '最大容量',
|
||||
perHour: '時間'
|
||||
},
|
||||
planet: {
|
||||
planet: '惑星',
|
||||
moon: '月',
|
||||
colony: 'コロニー',
|
||||
position: '位置',
|
||||
coordinates: '座標',
|
||||
switchToMoon: '月を表示',
|
||||
backToPlanet: '母星に戻る',
|
||||
fields: 'フィールド',
|
||||
temperature: '温度',
|
||||
homePlanet: '母星',
|
||||
planetPrefix: '惑星',
|
||||
moonSuffix: 'の月',
|
||||
colonyPrefix: 'コロニー'
|
||||
},
|
||||
player: {
|
||||
points: '総ポイント'
|
||||
},
|
||||
buildings: {
|
||||
metalMine: '金属鉱山',
|
||||
crystalMine: 'クリスタル鉱山',
|
||||
deuteriumSynthesizer: '重水素合成装置',
|
||||
solarPlant: '太陽光発電所',
|
||||
roboticsFactory: 'ロボット工場',
|
||||
naniteFactory: 'ナノマシン工場',
|
||||
shipyard: '造船所',
|
||||
researchLab: '研究所',
|
||||
metalStorage: '金属倉庫',
|
||||
crystalStorage: 'クリスタル倉庫',
|
||||
deuteriumTank: '重水素タンク',
|
||||
darkMatterCollector: 'ダークマター採取装置',
|
||||
lunarBase: '月面基地',
|
||||
sensorPhalanx: 'センサーファランクス',
|
||||
jumpGate: 'ジャンプゲート',
|
||||
buildTime: '建設時間',
|
||||
production: '生産量',
|
||||
consumption: '消費',
|
||||
totalCost: '総コスト',
|
||||
totalPoints: '総ポイント',
|
||||
levelRange: 'レベル範囲'
|
||||
},
|
||||
buildingDescriptions: {
|
||||
metalMine: '金属資源を採掘',
|
||||
crystalMine: 'クリスタル資源を採掘',
|
||||
deuteriumSynthesizer: '重水素資源を合成',
|
||||
solarPlant: 'エネルギーを供給',
|
||||
roboticsFactory: '建設速度を向上',
|
||||
naniteFactory: '建設キュー数を増加、レベル毎に+1(最大10)',
|
||||
shipyard: '艦船を建造',
|
||||
researchLab: '技術を研究',
|
||||
metalStorage: '金属の貯蔵上限を増加',
|
||||
crystalStorage: 'クリスタルの貯蔵上限を増加',
|
||||
deuteriumTank: '重水素の貯蔵上限を増加',
|
||||
darkMatterCollector: '希少なダークマター資源を収集',
|
||||
lunarBase: '月の利用可能スペースを増加',
|
||||
sensorPhalanx: '周辺星系の艦隊活動を探知',
|
||||
jumpGate: '他の月へ艦隊を瞬間移動'
|
||||
},
|
||||
ships: {
|
||||
lightFighter: '軽戦闘機',
|
||||
heavyFighter: '重戦闘機',
|
||||
cruiser: '巡洋艦',
|
||||
battleship: '戦艦',
|
||||
smallCargo: '小型輸送船',
|
||||
largeCargo: '大型輸送船',
|
||||
colonyShip: 'コロニーシップ',
|
||||
recycler: 'リサイクラー',
|
||||
espionageProbe: 'スパイプローブ',
|
||||
darkMatterHarvester: 'ダークマター採取船'
|
||||
},
|
||||
shipDescriptions: {
|
||||
lightFighter: '基本戦闘ユニット',
|
||||
heavyFighter: '重装甲戦闘機',
|
||||
cruiser: '中型戦艦、攻守バランス型',
|
||||
battleship: '強力な戦艦',
|
||||
smallCargo: '少量の資源を輸送',
|
||||
largeCargo: '大量の資源を輸送',
|
||||
colonyShip: '新惑星の植民に使用',
|
||||
recycler: 'デブリフィールドの資源を回収',
|
||||
espionageProbe: '敵惑星を偵察',
|
||||
darkMatterHarvester: 'ダークマター採取専用の特殊艦'
|
||||
},
|
||||
defenses: {
|
||||
rocketLauncher: 'ロケットランチャー',
|
||||
lightLaser: 'ライトレーザー',
|
||||
heavyLaser: 'ヘビーレーザー',
|
||||
gaussCannon: 'ガウスキャノン',
|
||||
ionCannon: 'イオンキャノン',
|
||||
plasmaTurret: 'プラズマタレット',
|
||||
smallShieldDome: '小型シールドドーム',
|
||||
largeShieldDome: '大型シールドドーム'
|
||||
},
|
||||
defenseDescriptions: {
|
||||
rocketLauncher: '基本防衛施設',
|
||||
lightLaser: '軽量エネルギー兵器',
|
||||
heavyLaser: '重型エネルギー兵器',
|
||||
gaussCannon: '高速運動エネルギー兵器',
|
||||
ionCannon: 'シールド破壊に効果的',
|
||||
plasmaTurret: '強力な防衛施設',
|
||||
smallShieldDome: '惑星全体を保護する小型シールド',
|
||||
largeShieldDome: '惑星全体を保護する大型シールド'
|
||||
},
|
||||
research: {
|
||||
researchTime: '研究時間',
|
||||
totalCost: '総コスト',
|
||||
totalPoints: '総ポイント',
|
||||
levelRange: 'レベル範囲'
|
||||
},
|
||||
technologies: {
|
||||
energyTechnology: 'エネルギー技術',
|
||||
laserTechnology: 'レーザー技術',
|
||||
ionTechnology: 'イオン技術',
|
||||
hyperspaceTechnology: 'ハイパースペース技術',
|
||||
plasmaTechnology: 'プラズマ技術',
|
||||
computerTechnology: 'コンピューター技術',
|
||||
combustionDrive: '燃焼ドライブ',
|
||||
impulseDrive: 'インパルスドライブ',
|
||||
hyperspaceDrive: 'ハイパースペースドライブ',
|
||||
darkMatterTechnology: 'ダークマター技術'
|
||||
},
|
||||
technologyDescriptions: {
|
||||
energyTechnology: 'エネルギー利用効率を向上',
|
||||
laserTechnology: 'レーザー兵器と防衛の基礎',
|
||||
ionTechnology: 'イオン兵器技術',
|
||||
hyperspaceTechnology: 'ハイパースペースジャンプ技術',
|
||||
plasmaTechnology: 'プラズマ兵器技術',
|
||||
computerTechnology: '研究キュー数を増加、レベル毎に+1(最大10)',
|
||||
combustionDrive: '基本推進技術',
|
||||
impulseDrive: '中級推進技術',
|
||||
hyperspaceDrive: '高級推進技術',
|
||||
darkMatterTechnology: 'ダークマターの性質と応用を研究'
|
||||
},
|
||||
officers: {
|
||||
commander: '司令官',
|
||||
admiral: '提督',
|
||||
engineer: 'エンジニア',
|
||||
geologist: '地質学者',
|
||||
technocrat: '技術専門家',
|
||||
darkMatterSpecialist: 'ダークマター専門家'
|
||||
},
|
||||
officerDescriptions: {
|
||||
commander: '建設速度と管理能力を向上',
|
||||
admiral: '艦隊戦闘力と速度を向上',
|
||||
engineer: 'エネルギーと防御力を向上',
|
||||
geologist: '資源生産量を向上',
|
||||
technocrat: '研究速度と偵察能力を向上',
|
||||
darkMatterSpecialist: 'ダークマター採取効率を向上'
|
||||
},
|
||||
queue: {
|
||||
buildQueue: '建設キュー',
|
||||
researchQueue: '研究キュー',
|
||||
building: '建設中',
|
||||
researching: '研究中',
|
||||
remaining: '残り時間',
|
||||
cancel: 'キャンセル',
|
||||
cancelBuild: '建設キャンセル',
|
||||
cancelResearch: '研究キャンセル',
|
||||
confirmCancel: 'キャンセルしますか?資源の50%が返還されます。',
|
||||
level: 'レベル',
|
||||
upgradeToLevel: 'レベルにアップグレード'
|
||||
},
|
||||
shipyard: {
|
||||
attack: '攻撃力',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
speed: '速度',
|
||||
cargoCapacity: '貨物容量',
|
||||
fuelConsumption: '燃料消費',
|
||||
buildCost: '建設コスト',
|
||||
buildTime: '建設時間',
|
||||
perUnit: 'ユニットあたり',
|
||||
batchCalculator: '一括計算機',
|
||||
quantity: '数量',
|
||||
totalCost: '総コスト',
|
||||
totalTime: '総時間'
|
||||
},
|
||||
overview: {
|
||||
title: '惑星概要',
|
||||
resourceOverview: '資源概要',
|
||||
fleetInfo: '艦隊',
|
||||
currentShips: '現在の惑星の艦船数'
|
||||
},
|
||||
buildingsView: {
|
||||
title: '建物',
|
||||
usedSpace: '使用済みスペース',
|
||||
spaceUsage: 'スペース使用量',
|
||||
level: 'レベル',
|
||||
upgradeCost: 'アップグレードコスト',
|
||||
buildTime: '建設時間',
|
||||
upgrade: 'アップグレード',
|
||||
upgradeFailed: 'アップグレード失敗',
|
||||
upgradeFailedMessage: '資源が十分か、スペースが十分か、または他の建設タスクがないか確認してください。',
|
||||
demolish: '解体',
|
||||
demolishRefund: '解体返還',
|
||||
demolishFailed: '解体失敗',
|
||||
demolishFailedMessage: 'この建物を解体できません。建設キューが満杯か、建物レベルが0でないか確認してください。'
|
||||
},
|
||||
researchView: {
|
||||
title: '研究',
|
||||
researchCost: '研究コスト',
|
||||
research: '研究',
|
||||
researchFailed: '研究失敗',
|
||||
researchFailedMessage: '資源が十分か、前提条件が満たされているか、または他の研究タスクがないか確認してください。'
|
||||
},
|
||||
defense: {
|
||||
attack: '攻撃力',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
buildCost: '建設コスト',
|
||||
buildTime: '建設時間',
|
||||
perUnit: 'ユニットあたり',
|
||||
batchCalculator: '一括計算機',
|
||||
quantity: '数量',
|
||||
totalCost: '総コスト',
|
||||
totalTime: '総時間'
|
||||
},
|
||||
shipyardView: {
|
||||
title: '造船所',
|
||||
attack: '攻撃力',
|
||||
shield: 'シールド',
|
||||
speed: '速度',
|
||||
cargoCapacity: '積載量',
|
||||
unitCost: 'ユニットコスト',
|
||||
buildQuantity: '建造数',
|
||||
totalCost: '総コスト',
|
||||
build: '建造',
|
||||
inputError: '入力エラー',
|
||||
inputErrorMessage: '建造数を入力してください!',
|
||||
buildFailed: '建造失敗',
|
||||
buildFailedMessage: '資源が十分か、前提条件が満たされているか確認してください。'
|
||||
},
|
||||
defenseView: {
|
||||
title: '防衛施設',
|
||||
attack: '攻撃力',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
buildTime: '建設時間',
|
||||
seconds: '秒',
|
||||
unitCost: 'ユニットコスト',
|
||||
buildQuantity: '建造数',
|
||||
totalCost: '総コスト',
|
||||
build: '建造',
|
||||
shieldDomeBuilt: 'シールドドーム建設済み',
|
||||
inputError: '入力エラー',
|
||||
inputErrorMessage: '建造数を入力してください!',
|
||||
buildFailed: '建造失敗',
|
||||
buildFailedMessage: '資源が十分か、前提条件が満たされているか確認してください。シールドドームは1つのみ建設できます。'
|
||||
},
|
||||
fleetView: {
|
||||
title: '艦隊管理',
|
||||
fleetOverview: '艦隊概要',
|
||||
sendFleet: '艦隊派遣',
|
||||
flightMissions: '飛行ミッション',
|
||||
currentPlanetFleet: '現在の惑星艦隊',
|
||||
attack: '攻撃',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
speed: '速度',
|
||||
cargo: '貨物',
|
||||
selectFleet: '艦隊選択',
|
||||
selectFleetDescription: '派遣する艦船数を選択',
|
||||
available: '利用可能',
|
||||
all: '全て',
|
||||
targetCoordinates: '目標座標',
|
||||
galaxy: '銀河',
|
||||
system: '星系',
|
||||
position: '位置',
|
||||
missionType: 'ミッションタイプ',
|
||||
missionInfo: 'ミッション情報',
|
||||
fuelConsumption: '燃料消費',
|
||||
flightTime: '飛行時間',
|
||||
attackMission: '攻撃',
|
||||
transport: '輸送',
|
||||
colonize: '植民',
|
||||
spy: '偵察',
|
||||
deploy: '配備',
|
||||
transportResources: '資源輸送',
|
||||
totalCargoCapacity: '総積載量',
|
||||
used: '使用済み',
|
||||
noFlightMissions: '飛行ミッションなし',
|
||||
outbound: '往路',
|
||||
returning: '帰路',
|
||||
fleetComposition: '艦隊構成',
|
||||
carryingResources: '運搬資源',
|
||||
arrivalTime: '到着時刻',
|
||||
returnTime: '帰還時刻',
|
||||
recallFleet: '艦隊召還',
|
||||
sendFailed: '派遣失敗',
|
||||
sendFailedMessage: '艦隊数、燃料の充足、または積載量の制限を確認してください。',
|
||||
recallFailed: '召還失敗',
|
||||
recallFailedMessage: 'このミッションは召還できません。',
|
||||
unknownPlanet: '未知の惑星',
|
||||
fleetMissionSlots: '艦隊ミッションスロット'
|
||||
},
|
||||
officersView: {
|
||||
title: '士官',
|
||||
activated: 'アクティブ',
|
||||
inactive: '非アクティブ',
|
||||
activeStatus: 'アクティブ状態',
|
||||
expirationTime: '期限切れ時刻',
|
||||
remainingTime: '残り時間',
|
||||
recruitCost: '募集コスト',
|
||||
days: '日',
|
||||
benefitsBonus: '効果ボーナス',
|
||||
resourceProduction: '資源生産量',
|
||||
darkMatterProduction: 'ダークマター生産量',
|
||||
energyProduction: 'エネルギー生産量',
|
||||
buildingSpeed: '建設速度',
|
||||
researchSpeed: '研究速度',
|
||||
fleetSpeed: '艦隊速度',
|
||||
fuelConsumption: '燃料消費',
|
||||
defense: '防御力',
|
||||
storageCapacity: '貯蔵容量',
|
||||
buildQueue: '建設キュー',
|
||||
fleetSlots: '艦隊スロット',
|
||||
hire: '雇用',
|
||||
renew: '更新',
|
||||
dismiss: '解雇',
|
||||
hireTitle: '士官雇用',
|
||||
hireMessage: '{name}を雇用しますか?有効期限は7日間です。',
|
||||
renewTitle: '士官更新',
|
||||
renewMessage: '{name}を7日間更新しますか?',
|
||||
dismissTitle: '士官解雇',
|
||||
dismissMessage: '{name}を解雇しますか?費用は返金されません。',
|
||||
hireFailed: '雇用失敗',
|
||||
renewFailed: '更新失敗',
|
||||
insufficientResources: '資源不足!'
|
||||
},
|
||||
galaxyView: {
|
||||
title: '銀河',
|
||||
selectCoordinates: '座標選択',
|
||||
galaxy: '銀河',
|
||||
selectGalaxy: '銀河を選択',
|
||||
system: '星系',
|
||||
selectSystem: '星系を選択',
|
||||
view: '表示',
|
||||
myPlanet: '自分の惑星',
|
||||
totalPositions: '全10惑星位置',
|
||||
mine: '自分',
|
||||
hostile: '敵対',
|
||||
emptySlot: '空き - 植民可能',
|
||||
scout: '偵察',
|
||||
attack: '攻撃',
|
||||
colonize: '植民',
|
||||
switch: '切り替え',
|
||||
scoutPlanetTitle: '惑星偵察',
|
||||
attackPlanetTitle: '惑星攻撃',
|
||||
colonizePlanetTitle: '惑星植民',
|
||||
scoutPlanetMessage: '惑星[{coordinates}]にスパイプローブを送りますか?\n\n艦隊ページに移動して艦船を選択して派遣してください。',
|
||||
attackPlanetMessage: '惑星[{coordinates}]を攻撃しますか?\n\n艦隊ページに移動して艦船を選択して派遣してください。',
|
||||
colonizePlanetMessage: '位置[{coordinates}]を植民しますか?\n\n艦隊ページに移動してコロニーシップを派遣してください。'
|
||||
},
|
||||
messagesView: {
|
||||
title: 'メッセージセンター',
|
||||
battleReports: '戦闘レポート',
|
||||
spyReports: 'スパイレポート',
|
||||
noBattleReports: '戦闘レポートなし',
|
||||
noSpyReports: 'スパイレポートなし',
|
||||
battleReport: '戦闘レポート',
|
||||
spyReport: 'スパイレポート',
|
||||
victory: '勝利',
|
||||
defeat: '敗北',
|
||||
draw: '引き分け',
|
||||
attackerFleet: '攻撃側艦隊',
|
||||
defenderFleet: '防御側艦隊',
|
||||
defenderDefense: '防御側防衛',
|
||||
attackerLosses: '攻撃側損失',
|
||||
defenderLosses: '防御側損失',
|
||||
noLosses: '損失なし',
|
||||
plunder: '略奪資源',
|
||||
debrisField: 'デブリフィールド',
|
||||
resources: '資源',
|
||||
fleet: '艦隊',
|
||||
defense: '防衛',
|
||||
buildings: '建物'
|
||||
},
|
||||
simulatorView: {
|
||||
title: '戦闘シミュレーター',
|
||||
attacker: '攻撃側',
|
||||
defender: '防御側',
|
||||
attackerConfig: '攻撃側設定',
|
||||
attackerConfigDesc: '攻撃側の艦隊と技術レベルを設定',
|
||||
defenderConfig: '防御側設定',
|
||||
defenderConfigDesc: '防御側の艦隊、防衛、技術レベルを設定',
|
||||
fleet: '艦隊',
|
||||
defenseStructures: '防衛施設',
|
||||
techLevels: '技術レベル',
|
||||
weapon: '武器',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
defenderResources: '防御側資源(略奪計算用)',
|
||||
startSimulation: 'シミュレーション開始',
|
||||
reset: 'リセット',
|
||||
battleResult: '戦闘結果',
|
||||
attackerVictory: '攻撃側勝利',
|
||||
defenderVictory: '防御側勝利',
|
||||
draw: '引き分け',
|
||||
afterRounds: '{rounds}ラウンド後',
|
||||
attackerLosses: '攻撃側損失',
|
||||
defenderLosses: '防御側損失',
|
||||
noLosses: '損失なし',
|
||||
attackerRemaining: '攻撃側残存',
|
||||
defenderRemaining: '防御側残存',
|
||||
allDestroyed: '全て破壊',
|
||||
plunderableResources: '略奪可能資源',
|
||||
debrisField: 'デブリフィールド',
|
||||
moonChance: '月生成確率',
|
||||
showRoundDetails: 'ラウンド詳細表示',
|
||||
hideRoundDetails: 'ラウンド詳細非表示',
|
||||
round: '第{round}ラウンド',
|
||||
attackerRemainingPower: '攻撃側残存火力',
|
||||
defenderRemainingPower: '防御側残存火力'
|
||||
},
|
||||
settings: {
|
||||
dataManagement: 'データ管理',
|
||||
dataManagementDesc: 'ゲームデータのエクスポート、インポート、またはクリア',
|
||||
exportData: 'データエクスポート',
|
||||
exportDataDesc: 'ゲームの進行状況をJSONファイルとしてエクスポート',
|
||||
export: 'エクスポート',
|
||||
exporting: 'エクスポート中...',
|
||||
exportSuccess: 'エクスポート成功',
|
||||
exportFailed: 'エクスポートに失敗しました。もう一度お試しください',
|
||||
importData: 'データインポート',
|
||||
importDataDesc: 'JSONファイルからゲームの進行状況を復元',
|
||||
selectFile: 'ファイルを選択',
|
||||
importSuccess: 'インポート成功',
|
||||
importConfirmTitle: 'インポート確認',
|
||||
importConfirmMessage: 'インポートすると現在のゲームの進行状況が上書きされます。この操作は元に戻せません。続行しますか?',
|
||||
importFailed: 'インポートに失敗しました。ファイル形式を確認してください',
|
||||
clearData: 'データクリア',
|
||||
clearDataDesc: 'すべてのゲームデータを削除してリセット',
|
||||
clear: 'クリア',
|
||||
clearConfirmTitle: 'データクリア確認',
|
||||
clearConfirmMessage: 'すべてのゲームデータが削除され、最初からやり直します。この操作は元に戻せません。続行しますか?',
|
||||
gameSettings: 'ゲーム設定',
|
||||
gameSettingsDesc: 'ゲームパラメータと設定を調整',
|
||||
playerName: 'プレイヤー名',
|
||||
gameSpeed: 'ゲーム速度',
|
||||
gameSpeedDesc: '現在のゲーム速度倍率',
|
||||
about: 'について',
|
||||
version: 'バージョン',
|
||||
buildDate: 'ビルド日',
|
||||
community: 'コミュニティ',
|
||||
github: 'GitHubリポジトリ',
|
||||
qqGroup: 'QQグループ'
|
||||
}
|
||||
}
|
||||
532
src/locales/ko.ts
Normal file
532
src/locales/ko.ts
Normal file
@@ -0,0 +1,532 @@
|
||||
export default {
|
||||
common: {
|
||||
confirm: '확인',
|
||||
cancel: '취소',
|
||||
delete: '삭제',
|
||||
edit: '편집',
|
||||
save: '저장',
|
||||
close: '닫기',
|
||||
back: '돌아가기',
|
||||
next: '다음',
|
||||
previous: '이전',
|
||||
submit: '제출',
|
||||
reset: '초기화',
|
||||
search: '검색',
|
||||
filter: '필터',
|
||||
loading: '로딩 중...',
|
||||
noData: '데이터 없음',
|
||||
error: '오류',
|
||||
success: '성공',
|
||||
warning: '경고',
|
||||
info: '정보',
|
||||
resourceType: '자원 유형',
|
||||
playerName: '사령관',
|
||||
timeHour: '시간',
|
||||
timeMinute: '분',
|
||||
timeSecond: '초',
|
||||
featureLocked: '기능 잠김',
|
||||
unlockRequired: '건물 필요',
|
||||
requiredBuilding: '필요한 건물',
|
||||
currentLevel: '현재 레벨',
|
||||
goToBuildings: '건물로 이동',
|
||||
locked: '잠김',
|
||||
viewRequirements: '요구사항 보기',
|
||||
requirementsNotMet: '요구사항 미충족',
|
||||
current: '현재'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: '전제 조건 미충족',
|
||||
insufficientResources: '자원 부족',
|
||||
shieldDomeLimit: '실드 돔 한도 도달',
|
||||
fleetMissionsFull: '함대 임무 슬롯 가득 참',
|
||||
insufficientFleet: '함대 부족',
|
||||
insufficientFuel: '연료 부족',
|
||||
planetOnly: '이 건물은 행성에서만 지을 수 있습니다',
|
||||
moonOnly: '이 건물은 위성에서만 지을 수 있습니다',
|
||||
buildQueueFull: '건설 대기열 가득 참',
|
||||
insufficientSpace: '공간 부족',
|
||||
buildingLevelZero: '건물 레벨이 0이므로 철거할 수 없습니다',
|
||||
researchQueueFull: '연구 대기열 가득 참',
|
||||
moonExists: '위성이 이미 존재합니다',
|
||||
insufficientDebris: '잔해장 부족'
|
||||
},
|
||||
nav: {
|
||||
overview: '개요',
|
||||
buildings: '건물',
|
||||
research: '연구',
|
||||
shipyard: '조선소',
|
||||
defense: '방어',
|
||||
fleet: '함대',
|
||||
officers: '장교',
|
||||
simulator: '시뮬레이터',
|
||||
galaxy: '은하계',
|
||||
messages: '메시지',
|
||||
settings: '설정'
|
||||
},
|
||||
sidebar: {
|
||||
language: '언어',
|
||||
lightMode: '라이트 모드',
|
||||
darkMode: '다크 모드',
|
||||
collapse: '메뉴 접기',
|
||||
expand: '메뉴 펼치기'
|
||||
},
|
||||
resources: {
|
||||
metal: '금속',
|
||||
crystal: '크리스탈',
|
||||
deuterium: '중수소',
|
||||
darkMatter: '암흑 물질',
|
||||
energy: '에너지',
|
||||
production: '생산량',
|
||||
capacity: '용량',
|
||||
current: '현재 저장량',
|
||||
max: '최대 용량',
|
||||
perHour: '시간'
|
||||
},
|
||||
planet: {
|
||||
planet: '행성',
|
||||
moon: '위성',
|
||||
colony: '식민지',
|
||||
position: '위치',
|
||||
coordinates: '좌표',
|
||||
switchToMoon: '위성 보기',
|
||||
backToPlanet: '모행성으로 돌아가기',
|
||||
fields: '필드',
|
||||
temperature: '온도',
|
||||
homePlanet: '모행성',
|
||||
planetPrefix: '행성',
|
||||
moonSuffix: '의 위성',
|
||||
colonyPrefix: '식민지'
|
||||
},
|
||||
player: {
|
||||
points: '총 점수'
|
||||
},
|
||||
buildings: {
|
||||
metalMine: '금속 광산',
|
||||
crystalMine: '크리스탈 광산',
|
||||
deuteriumSynthesizer: '중수소 합성기',
|
||||
solarPlant: '태양광 발전소',
|
||||
roboticsFactory: '로봇 공장',
|
||||
naniteFactory: '나노 공장',
|
||||
shipyard: '조선소',
|
||||
researchLab: '연구소',
|
||||
metalStorage: '금속 창고',
|
||||
crystalStorage: '크리스탈 창고',
|
||||
deuteriumTank: '중수소 탱크',
|
||||
darkMatterCollector: '암흑 물질 수집기',
|
||||
lunarBase: '달 기지',
|
||||
sensorPhalanx: '센서 팔랑크스',
|
||||
jumpGate: '점프 게이트',
|
||||
buildTime: '건설 시간',
|
||||
production: '생산량',
|
||||
consumption: '소비',
|
||||
totalCost: '총 비용',
|
||||
totalPoints: '총 점수',
|
||||
levelRange: '레벨 범위'
|
||||
},
|
||||
buildingDescriptions: {
|
||||
metalMine: '금속 자원 채굴',
|
||||
crystalMine: '크리스탈 자원 채굴',
|
||||
deuteriumSynthesizer: '중수소 자원 합성',
|
||||
solarPlant: '에너지 제공',
|
||||
roboticsFactory: '건설 속도 향상',
|
||||
naniteFactory: '건설 대기열 수 증가, 레벨당 +1 (최대 10개)',
|
||||
shipyard: '함선 건조',
|
||||
researchLab: '기술 연구',
|
||||
metalStorage: '금속 저장 용량 증가',
|
||||
crystalStorage: '크리스탈 저장 용량 증가',
|
||||
deuteriumTank: '중수소 저장 용량 증가',
|
||||
darkMatterCollector: '희귀한 암흑 물질 자원 수집',
|
||||
lunarBase: '달 가용 공간 증가',
|
||||
sensorPhalanx: '주변 행성계의 함대 활동 감지',
|
||||
jumpGate: '다른 위성으로 함대 순간 이동'
|
||||
},
|
||||
ships: {
|
||||
lightFighter: '경전투기',
|
||||
heavyFighter: '중전투기',
|
||||
cruiser: '순양함',
|
||||
battleship: '전함',
|
||||
smallCargo: '소형 수송선',
|
||||
largeCargo: '대형 수송선',
|
||||
colonyShip: '식민선',
|
||||
recycler: '재활용선',
|
||||
espionageProbe: '정찰기',
|
||||
darkMatterHarvester: '암흑 물질 채취선'
|
||||
},
|
||||
shipDescriptions: {
|
||||
lightFighter: '기본 전투 유닛',
|
||||
heavyFighter: '중장갑 전투기',
|
||||
cruiser: '중형 전함, 공격과 방어 균형',
|
||||
battleship: '강력한 전함',
|
||||
smallCargo: '소량의 자원 운송',
|
||||
largeCargo: '대량의 자원 운송',
|
||||
colonyShip: '새로운 행성 식민에 사용',
|
||||
recycler: '잔해장 자원 수집',
|
||||
espionageProbe: '적 행성 정찰',
|
||||
darkMatterHarvester: '암흑 물질 채취 전용 특수 함선'
|
||||
},
|
||||
defenses: {
|
||||
rocketLauncher: '로켓 발사대',
|
||||
lightLaser: '경량 레이저포',
|
||||
heavyLaser: '중형 레이저포',
|
||||
gaussCannon: '가우스 캐논',
|
||||
ionCannon: '이온 캐논',
|
||||
plasmaTurret: '플라즈마 포탑',
|
||||
smallShieldDome: '소형 실드 돔',
|
||||
largeShieldDome: '대형 실드 돔'
|
||||
},
|
||||
defenseDescriptions: {
|
||||
rocketLauncher: '기본 방어 시설',
|
||||
lightLaser: '경량 에너지 무기',
|
||||
heavyLaser: '중형 에너지 무기',
|
||||
gaussCannon: '고속 운동 에너지 무기',
|
||||
ionCannon: '실드 파괴의 이기',
|
||||
plasmaTurret: '강력한 방어 시설',
|
||||
smallShieldDome: '행성 전체를 보호하는 소형 실드',
|
||||
largeShieldDome: '행성 전체를 보호하는 대형 실드'
|
||||
},
|
||||
research: {
|
||||
researchTime: '연구 시간',
|
||||
totalCost: '총 비용',
|
||||
totalPoints: '총 점수',
|
||||
levelRange: '레벨 범위'
|
||||
},
|
||||
technologies: {
|
||||
energyTechnology: '에너지 기술',
|
||||
laserTechnology: '레이저 기술',
|
||||
ionTechnology: '이온 기술',
|
||||
hyperspaceTechnology: '초공간 기술',
|
||||
plasmaTechnology: '플라즈마 기술',
|
||||
computerTechnology: '컴퓨터 기술',
|
||||
combustionDrive: '연소 엔진',
|
||||
impulseDrive: '임펄스 엔진',
|
||||
hyperspaceDrive: '초공간 엔진',
|
||||
darkMatterTechnology: '암흑 물질 기술'
|
||||
},
|
||||
technologyDescriptions: {
|
||||
energyTechnology: '에너지 이용 효율 향상',
|
||||
laserTechnology: '레이저 무기와 방어의 기초',
|
||||
ionTechnology: '이온 무기 기술',
|
||||
hyperspaceTechnology: '초공간 점프 기술',
|
||||
plasmaTechnology: '플라즈마 무기 기술',
|
||||
computerTechnology: '연구 대기열 수 증가, 레벨당 +1 (최대 10개)',
|
||||
combustionDrive: '기본 추진 기술',
|
||||
impulseDrive: '중급 추진 기술',
|
||||
hyperspaceDrive: '고급 추진 기술',
|
||||
darkMatterTechnology: '암흑 물질의 성질과 응용 연구'
|
||||
},
|
||||
officers: {
|
||||
commander: '사령관',
|
||||
admiral: '제독',
|
||||
engineer: '엔지니어',
|
||||
geologist: '지질학자',
|
||||
technocrat: '기술 전문가',
|
||||
darkMatterSpecialist: '암흑 물질 전문가'
|
||||
},
|
||||
officerDescriptions: {
|
||||
commander: '건설 속도 및 관리 능력 향상',
|
||||
admiral: '함대 전투력 및 속도 향상',
|
||||
engineer: '에너지 및 방어력 향상',
|
||||
geologist: '자원 생산량 향상',
|
||||
technocrat: '연구 속도 및 정찰 능력 향상',
|
||||
darkMatterSpecialist: '암흑 물질 수집 효율 향상'
|
||||
},
|
||||
queue: {
|
||||
buildQueue: '건설 대기열',
|
||||
researchQueue: '연구 대기열',
|
||||
building: '건설 중',
|
||||
researching: '연구 중',
|
||||
remaining: '남은 시간',
|
||||
cancel: '취소',
|
||||
cancelBuild: '건설 취소',
|
||||
cancelResearch: '연구 취소',
|
||||
confirmCancel: '취소하시겠습니까? 자원의 50%가 환불됩니다.',
|
||||
level: '레벨',
|
||||
upgradeToLevel: '레벨로 업그레이드'
|
||||
},
|
||||
overview: {
|
||||
title: '행성 개요',
|
||||
resourceOverview: '자원 개요',
|
||||
fleetInfo: '함대',
|
||||
currentShips: '현재 행성의 함선 수'
|
||||
},
|
||||
buildingsView: {
|
||||
title: '건물',
|
||||
usedSpace: '사용된 공간',
|
||||
spaceUsage: '공간 사용',
|
||||
level: '레벨',
|
||||
upgradeCost: '업그레이드 비용',
|
||||
buildTime: '건설 시간',
|
||||
upgrade: '업그레이드',
|
||||
upgradeFailed: '업그레이드 실패',
|
||||
upgradeFailedMessage: '자원이 충분한지, 공간이 충분한지, 또는 다른 건설 작업이 있는지 확인하세요.',
|
||||
demolish: '철거',
|
||||
demolishRefund: '철거 환불',
|
||||
demolishFailed: '철거 실패',
|
||||
demolishFailedMessage: '이 건물을 철거할 수 없습니다. 건설 대기열이 가득 찼거나 건물 레벨이 0인지 확인하세요.'
|
||||
},
|
||||
researchView: {
|
||||
title: '연구',
|
||||
researchCost: '연구 비용',
|
||||
research: '연구',
|
||||
researchFailed: '연구 실패',
|
||||
researchFailedMessage: '자원이 충분한지, 전제 조건이 충족되었는지, 또는 다른 연구 작업이 있는지 확인하세요.'
|
||||
},
|
||||
shipyard: {
|
||||
attack: '공격력',
|
||||
shield: '쉴드',
|
||||
armor: '장갑',
|
||||
speed: '속도',
|
||||
cargoCapacity: '화물 용량',
|
||||
fuelConsumption: '연료 소비',
|
||||
buildCost: '건설 비용',
|
||||
buildTime: '건설 시간',
|
||||
perUnit: '단위당',
|
||||
batchCalculator: '일괄 계산기',
|
||||
quantity: '수량',
|
||||
totalCost: '총 비용',
|
||||
totalTime: '총 시간'
|
||||
},
|
||||
shipyardView: {
|
||||
title: '조선소',
|
||||
attack: '공격력',
|
||||
shield: '실드',
|
||||
speed: '속도',
|
||||
cargoCapacity: '적재량',
|
||||
unitCost: '단위 비용',
|
||||
buildQuantity: '건조 수량',
|
||||
totalCost: '총 비용',
|
||||
build: '건조',
|
||||
inputError: '입력 오류',
|
||||
inputErrorMessage: '건조 수량을 입력하세요!',
|
||||
buildFailed: '건조 실패',
|
||||
buildFailedMessage: '자원이 충분한지 또는 전제 조건이 충족되었는지 확인하세요.'
|
||||
},
|
||||
defense: {
|
||||
attack: '공격력',
|
||||
shield: '쉴드',
|
||||
armor: '장갑',
|
||||
buildCost: '건설 비용',
|
||||
buildTime: '건설 시간',
|
||||
perUnit: '단위당',
|
||||
batchCalculator: '일괄 계산기',
|
||||
quantity: '수량',
|
||||
totalCost: '총 비용',
|
||||
totalTime: '총 시간'
|
||||
},
|
||||
defenseView: {
|
||||
title: '방어 시설',
|
||||
attack: '공격력',
|
||||
shield: '실드',
|
||||
armor: '장갑',
|
||||
buildTime: '건설 시간',
|
||||
seconds: '초',
|
||||
unitCost: '단위 비용',
|
||||
buildQuantity: '건조 수량',
|
||||
totalCost: '총 비용',
|
||||
build: '건조',
|
||||
shieldDomeBuilt: '실드 돔이 이미 건설됨',
|
||||
inputError: '입력 오류',
|
||||
inputErrorMessage: '건조 수량을 입력하세요!',
|
||||
buildFailed: '건조 실패',
|
||||
buildFailedMessage: '자원이 충분한지 또는 전제 조건이 충족되었는지 확인하세요. 실드 돔은 하나만 건설할 수 있습니다.'
|
||||
},
|
||||
fleetView: {
|
||||
title: '함대 관리',
|
||||
fleetOverview: '함대 개요',
|
||||
sendFleet: '함대 파견',
|
||||
flightMissions: '비행 임무',
|
||||
currentPlanetFleet: '현재 행성 함대',
|
||||
attack: '공격',
|
||||
shield: '실드',
|
||||
armor: '장갑',
|
||||
speed: '속도',
|
||||
cargo: '화물',
|
||||
selectFleet: '함대 선택',
|
||||
selectFleetDescription: '파견할 함선 수 선택',
|
||||
available: '사용 가능',
|
||||
all: '전체',
|
||||
targetCoordinates: '목표 좌표',
|
||||
galaxy: '은하계',
|
||||
system: '행성계',
|
||||
position: '위치',
|
||||
missionType: '임무 유형',
|
||||
missionInfo: '임무 정보',
|
||||
fuelConsumption: '연료 소비',
|
||||
flightTime: '비행 시간',
|
||||
attackMission: '공격',
|
||||
transport: '수송',
|
||||
colonize: '식민',
|
||||
spy: '정찰',
|
||||
deploy: '배치',
|
||||
transportResources: '자원 수송',
|
||||
totalCargoCapacity: '총 적재량',
|
||||
used: '사용됨',
|
||||
noFlightMissions: '비행 임무 없음',
|
||||
outbound: '이동 중',
|
||||
returning: '귀환 중',
|
||||
fleetComposition: '함대 구성',
|
||||
carryingResources: '운반 자원',
|
||||
arrivalTime: '도착 시간',
|
||||
returnTime: '귀환 시간',
|
||||
recallFleet: '함대 소환',
|
||||
sendFailed: '파견 실패',
|
||||
sendFailedMessage: '함대 수, 연료 충분 여부 또는 적재량 한계를 확인하세요.',
|
||||
recallFailed: '소환 실패',
|
||||
recallFailedMessage: '이 임무는 소환할 수 없습니다.',
|
||||
unknownPlanet: '알 수 없는 행성',
|
||||
fleetMissionSlots: '함대 임무 슬롯'
|
||||
},
|
||||
officersView: {
|
||||
title: '장교',
|
||||
activated: '활성화됨',
|
||||
inactive: '비활성',
|
||||
activeStatus: '활성 상태',
|
||||
expirationTime: '만료 시간',
|
||||
remainingTime: '남은 시간',
|
||||
recruitCost: '모집 비용',
|
||||
days: '일',
|
||||
benefitsBonus: '효과 보너스',
|
||||
resourceProduction: '자원 생산량',
|
||||
darkMatterProduction: '암흑 물질 생산량',
|
||||
energyProduction: '에너지 생산량',
|
||||
buildingSpeed: '건설 속도',
|
||||
researchSpeed: '연구 속도',
|
||||
fleetSpeed: '함대 속도',
|
||||
fuelConsumption: '연료 소비',
|
||||
defense: '방어력',
|
||||
storageCapacity: '저장 용량',
|
||||
buildQueue: '건설 대기열',
|
||||
fleetSlots: '함대 슬롯',
|
||||
hire: '고용',
|
||||
renew: '갱신',
|
||||
dismiss: '해고',
|
||||
hireTitle: '장교 고용',
|
||||
hireMessage: '{name}을(를) 고용하시겠습니까? 유효 기간은 7일입니다.',
|
||||
renewTitle: '장교 갱신',
|
||||
renewMessage: '{name}을(를) 7일간 갱신하시겠습니까?',
|
||||
dismissTitle: '장교 해고',
|
||||
dismissMessage: '{name}을(를) 해고하시겠습니까? 비용은 환불되지 않습니다.',
|
||||
hireFailed: '고용 실패',
|
||||
renewFailed: '갱신 실패',
|
||||
insufficientResources: '자원 부족!'
|
||||
},
|
||||
galaxyView: {
|
||||
title: '은하계',
|
||||
selectCoordinates: '좌표 선택',
|
||||
galaxy: '은하계',
|
||||
selectGalaxy: '은하계 선택',
|
||||
system: '행성계',
|
||||
selectSystem: '행성계 선택',
|
||||
view: '보기',
|
||||
myPlanet: '내 행성',
|
||||
totalPositions: '총 10개 행성 위치',
|
||||
mine: '내 것',
|
||||
hostile: '적대',
|
||||
emptySlot: '빈 자리 - 식민 가능',
|
||||
scout: '정찰',
|
||||
attack: '공격',
|
||||
colonize: '식민',
|
||||
switch: '전환',
|
||||
scoutPlanetTitle: '행성 정찰',
|
||||
attackPlanetTitle: '행성 공격',
|
||||
colonizePlanetTitle: '행성 식민',
|
||||
scoutPlanetMessage:
|
||||
'행성 [{coordinates}]을(를) 정찰하기 위해 정찰기를 보내시겠습니까?\n\n함대 페이지로 이동하여 함선을 선택하고 파견하세요.',
|
||||
attackPlanetMessage: '행성 [{coordinates}]을(를) 공격하시겠습니까?\n\n함대 페이지로 이동하여 함선을 선택하고 파견하세요.',
|
||||
colonizePlanetMessage: '위치 [{coordinates}]을(를) 식민하시겠습니까?\n\n함대 페이지로 이동하여 식민선을 파견하세요.'
|
||||
},
|
||||
messagesView: {
|
||||
title: '메시지 센터',
|
||||
battleReports: '전투 보고서',
|
||||
spyReports: '정찰 보고서',
|
||||
noBattleReports: '전투 보고서 없음',
|
||||
noSpyReports: '정찰 보고서 없음',
|
||||
battleReport: '전투 보고서',
|
||||
spyReport: '정찰 보고서',
|
||||
victory: '승리',
|
||||
defeat: '패배',
|
||||
draw: '무승부',
|
||||
attackerFleet: '공격자 함대',
|
||||
defenderFleet: '방어자 함대',
|
||||
defenderDefense: '방어자 방어',
|
||||
attackerLosses: '공격자 손실',
|
||||
defenderLosses: '방어자 손실',
|
||||
noLosses: '손실 없음',
|
||||
plunder: '약탈 자원',
|
||||
debrisField: '잔해장',
|
||||
resources: '자원',
|
||||
fleet: '함대',
|
||||
defense: '방어',
|
||||
buildings: '건물'
|
||||
},
|
||||
simulatorView: {
|
||||
title: '전투 시뮬레이터',
|
||||
attacker: '공격자',
|
||||
defender: '방어자',
|
||||
attackerConfig: '공격자 설정',
|
||||
attackerConfigDesc: '공격자의 함대와 기술 레벨 설정',
|
||||
defenderConfig: '방어자 설정',
|
||||
defenderConfigDesc: '방어자의 함대, 방어 및 기술 레벨 설정',
|
||||
fleet: '함대',
|
||||
defenseStructures: '방어 시설',
|
||||
techLevels: '기술 레벨',
|
||||
weapon: '무기',
|
||||
shield: '실드',
|
||||
armor: '장갑',
|
||||
defenderResources: '방어자 자원 (약탈 계산용)',
|
||||
startSimulation: '시뮬레이션 시작',
|
||||
reset: '초기화',
|
||||
battleResult: '전투 결과',
|
||||
attackerVictory: '공격자 승리',
|
||||
defenderVictory: '방어자 승리',
|
||||
draw: '무승부',
|
||||
afterRounds: '{rounds}회 전투 후',
|
||||
attackerLosses: '공격자 손실',
|
||||
defenderLosses: '방어자 손실',
|
||||
noLosses: '손실 없음',
|
||||
attackerRemaining: '공격자 잔여',
|
||||
defenderRemaining: '방어자 잔여',
|
||||
allDestroyed: '모두 파괴됨',
|
||||
plunderableResources: '약탈 가능 자원',
|
||||
debrisField: '잔해장',
|
||||
moonChance: '위성 생성 확률',
|
||||
showRoundDetails: '라운드 상세 표시',
|
||||
hideRoundDetails: '라운드 상세 숨기기',
|
||||
round: '제 {round} 라운드',
|
||||
attackerRemainingPower: '공격자 잔여 화력',
|
||||
defenderRemainingPower: '방어자 잔여 화력'
|
||||
},
|
||||
settings: {
|
||||
dataManagement: '데이터 관리',
|
||||
dataManagementDesc: '게임 데이터 내보내기, 가져오기 또는 지우기',
|
||||
exportData: '데이터 내보내기',
|
||||
exportDataDesc: '게임 진행 상황을 JSON 파일로 내보내기',
|
||||
export: '내보내기',
|
||||
exporting: '내보내는 중...',
|
||||
exportSuccess: '내보내기 성공',
|
||||
exportFailed: '내보내기 실패, 다시 시도해주세요',
|
||||
importData: '데이터 가져오기',
|
||||
importDataDesc: 'JSON 파일에서 게임 진행 상황 복원',
|
||||
selectFile: '파일 선택',
|
||||
importSuccess: '가져오기 성공',
|
||||
importConfirmTitle: '가져오기 확인',
|
||||
importConfirmMessage: '가져오기를 하면 현재 게임 진행 상황이 덮어쓰기됩니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?',
|
||||
importFailed: '가져오기 실패, 파일 형식을 확인해주세요',
|
||||
clearData: '데이터 지우기',
|
||||
clearDataDesc: '모든 게임 데이터 삭제 및 초기화',
|
||||
clear: '지우기',
|
||||
clearConfirmTitle: '데이터 지우기 확인',
|
||||
clearConfirmMessage: '모든 게임 데이터가 삭제되고 처음부터 시작됩니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?',
|
||||
gameSettings: '게임 설정',
|
||||
gameSettingsDesc: '게임 매개변수 및 설정 조정',
|
||||
playerName: '플레이어 이름',
|
||||
gameSpeed: '게임 속도',
|
||||
gameSpeedDesc: '현재 게임 속도 배율',
|
||||
about: '정보',
|
||||
version: '버전',
|
||||
buildDate: '빌드 날짜',
|
||||
community: '커뮤니티',
|
||||
github: 'GitHub 저장소',
|
||||
qqGroup: 'QQ 그룹'
|
||||
}
|
||||
}
|
||||
536
src/locales/ru.ts
Normal file
536
src/locales/ru.ts
Normal file
@@ -0,0 +1,536 @@
|
||||
export default {
|
||||
common: {
|
||||
confirm: 'Подтвердить',
|
||||
cancel: 'Отмена',
|
||||
delete: 'Удалить',
|
||||
edit: 'Редактировать',
|
||||
save: 'Сохранить',
|
||||
close: 'Закрыть',
|
||||
back: 'Назад',
|
||||
next: 'Далее',
|
||||
previous: 'Предыдущий',
|
||||
submit: 'Отправить',
|
||||
reset: 'Сбросить',
|
||||
search: 'Поиск',
|
||||
filter: 'Фильтр',
|
||||
loading: 'Загрузка...',
|
||||
noData: 'Нет данных',
|
||||
error: 'Ошибка',
|
||||
success: 'Успешно',
|
||||
warning: 'Предупреждение',
|
||||
info: 'Информация',
|
||||
resourceType: 'Тип ресурса',
|
||||
playerName: 'Командир',
|
||||
timeHour: 'ч',
|
||||
timeMinute: 'мин',
|
||||
timeSecond: 'сек',
|
||||
featureLocked: 'Функция заблокирована',
|
||||
unlockRequired: 'Требуется здание',
|
||||
requiredBuilding: 'Необходимое здание',
|
||||
currentLevel: 'Текущий уровень',
|
||||
goToBuildings: 'К зданиям',
|
||||
locked: 'Заблокировано',
|
||||
viewRequirements: 'Просмотр требований',
|
||||
requirementsNotMet: 'Требования не выполнены',
|
||||
current: 'Текущий'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: 'Требования не выполнены',
|
||||
insufficientResources: 'Недостаточно ресурсов',
|
||||
shieldDomeLimit: 'Достигнут лимит щитовых куполов',
|
||||
fleetMissionsFull: 'Слоты миссий флота заполнены',
|
||||
insufficientFleet: 'Недостаточно флота',
|
||||
insufficientFuel: 'Недостаточно топлива',
|
||||
planetOnly: 'Это здание можно построить только на планетах',
|
||||
moonOnly: 'Это здание можно построить только на лунах',
|
||||
buildQueueFull: 'Очередь строительства заполнена',
|
||||
insufficientSpace: 'Недостаточно места',
|
||||
buildingLevelZero: 'Уровень здания 0, нельзя снести',
|
||||
researchQueueFull: 'Очередь исследований заполнена',
|
||||
moonExists: 'Луна уже существует',
|
||||
insufficientDebris: 'Недостаточно обломков'
|
||||
},
|
||||
nav: {
|
||||
overview: 'Обзор',
|
||||
buildings: 'Здания',
|
||||
research: 'Исследования',
|
||||
shipyard: 'Верфь',
|
||||
defense: 'Оборона',
|
||||
fleet: 'Флот',
|
||||
officers: 'Офицеры',
|
||||
simulator: 'Симулятор',
|
||||
galaxy: 'Галактика',
|
||||
messages: 'Сообщения',
|
||||
settings: 'Настройки'
|
||||
},
|
||||
sidebar: {
|
||||
language: 'Язык',
|
||||
lightMode: 'Светлая тема',
|
||||
darkMode: 'Тёмная тема',
|
||||
collapse: 'Свернуть',
|
||||
expand: 'Развернуть'
|
||||
},
|
||||
resources: {
|
||||
metal: 'Металл',
|
||||
crystal: 'Кристалл',
|
||||
deuterium: 'Дейтерий',
|
||||
darkMatter: 'Тёмная материя',
|
||||
energy: 'Энергия',
|
||||
production: 'Производство',
|
||||
capacity: 'Вместимость',
|
||||
current: 'Текущий',
|
||||
max: 'Макс. вместимость',
|
||||
perHour: 'час'
|
||||
},
|
||||
planet: {
|
||||
planet: 'Планета',
|
||||
moon: 'Луна',
|
||||
colony: 'Колония',
|
||||
position: 'Позиция',
|
||||
coordinates: 'Координаты',
|
||||
switchToMoon: 'На луну',
|
||||
backToPlanet: 'Вернуться на планету',
|
||||
fields: 'Поля',
|
||||
temperature: 'Температура',
|
||||
homePlanet: 'Родная планета',
|
||||
planetPrefix: 'Планета',
|
||||
moonSuffix: 'я луна',
|
||||
colonyPrefix: 'Колония'
|
||||
},
|
||||
player: {
|
||||
points: 'Всего очков'
|
||||
},
|
||||
buildings: {
|
||||
metalMine: 'Рудник металла',
|
||||
crystalMine: 'Рудник кристалла',
|
||||
deuteriumSynthesizer: 'Синтезатор дейтерия',
|
||||
solarPlant: 'Солнечная электростанция',
|
||||
roboticsFactory: 'Фабрика роботов',
|
||||
naniteFactory: 'Нанитная фабрика',
|
||||
shipyard: 'Верфь',
|
||||
researchLab: 'Исследовательская лаборатория',
|
||||
metalStorage: 'Хранилище металла',
|
||||
crystalStorage: 'Хранилище кристалла',
|
||||
deuteriumTank: 'Цистерна дейтерия',
|
||||
darkMatterCollector: 'Коллектор тёмной материи',
|
||||
lunarBase: 'Лунная база',
|
||||
sensorPhalanx: 'Сенсорная фаланга',
|
||||
jumpGate: 'Прыжковые ворота',
|
||||
buildTime: 'Время строительства',
|
||||
production: 'Производство',
|
||||
consumption: 'Потребление',
|
||||
totalCost: 'Общая стоимость',
|
||||
totalPoints: 'Общие очки',
|
||||
levelRange: 'Диапазон уровней'
|
||||
},
|
||||
buildingDescriptions: {
|
||||
metalMine: 'Добывает металлические ресурсы',
|
||||
crystalMine: 'Добывает кристаллические ресурсы',
|
||||
deuteriumSynthesizer: 'Синтезирует дейтериевые ресурсы',
|
||||
solarPlant: 'Обеспечивает энергией',
|
||||
roboticsFactory: 'Ускоряет скорость строительства',
|
||||
naniteFactory: 'Увеличивает вместимость очереди строительства, +1 за уровень (макс 10)',
|
||||
shipyard: 'Строит корабли',
|
||||
researchLab: 'Исследует технологии',
|
||||
metalStorage: 'Увеличивает ёмкость хранилища металла',
|
||||
crystalStorage: 'Увеличивает ёмкость хранилища кристалла',
|
||||
deuteriumTank: 'Увеличивает ёмкость хранилища дейтерия',
|
||||
darkMatterCollector: 'Собирает редкие ресурсы тёмной материи',
|
||||
lunarBase: 'Увеличивает доступное пространство на луне',
|
||||
sensorPhalanx: 'Обнаруживает активность флота в окружающих системах',
|
||||
jumpGate: 'Мгновенно переносит флоты на другие луны'
|
||||
},
|
||||
ships: {
|
||||
lightFighter: 'Лёгкий истребитель',
|
||||
heavyFighter: 'Тяжёлый истребитель',
|
||||
cruiser: 'Крейсер',
|
||||
battleship: 'Линкор',
|
||||
smallCargo: 'Малый транспорт',
|
||||
largeCargo: 'Большой транспорт',
|
||||
colonyShip: 'Колонизатор',
|
||||
recycler: 'Переработчик',
|
||||
espionageProbe: 'Шпионский зонд',
|
||||
darkMatterHarvester: 'Сборщик тёмной материи'
|
||||
},
|
||||
shipDescriptions: {
|
||||
lightFighter: 'Базовая боевая единица',
|
||||
heavyFighter: 'Тяжелобронированный истребитель',
|
||||
cruiser: 'Средний боевой корабль, сбалансированная атака и защита',
|
||||
battleship: 'Мощный боевой корабль',
|
||||
smallCargo: 'Транспортирует небольшое количество ресурсов',
|
||||
largeCargo: 'Транспортирует большое количество ресурсов',
|
||||
colonyShip: 'Используется для колонизации новых планет',
|
||||
recycler: 'Собирает ресурсы с поля обломков',
|
||||
espionageProbe: 'Разведывает вражеские планеты',
|
||||
darkMatterHarvester: 'Специальный корабль для сбора тёмной материи'
|
||||
},
|
||||
defenses: {
|
||||
rocketLauncher: 'Ракетная установка',
|
||||
lightLaser: 'Лёгкий лазер',
|
||||
heavyLaser: 'Тяжёлый лазер',
|
||||
gaussCannon: 'Гауссова пушка',
|
||||
ionCannon: 'Ионное орудие',
|
||||
plasmaTurret: 'Плазменная турель',
|
||||
smallShieldDome: 'Малый щитовой купол',
|
||||
largeShieldDome: 'Большой щитовой купол'
|
||||
},
|
||||
defenseDescriptions: {
|
||||
rocketLauncher: 'Базовое оборонительное сооружение',
|
||||
lightLaser: 'Лёгкое энергетическое оружие',
|
||||
heavyLaser: 'Тяжёлое энергетическое оружие',
|
||||
gaussCannon: 'Высокоскоростное кинетическое оружие',
|
||||
ionCannon: 'Эффективно против щитов',
|
||||
plasmaTurret: 'Мощное оборонительное сооружение',
|
||||
smallShieldDome: 'Малый щит, защищающий всю планету',
|
||||
largeShieldDome: 'Большой щит, защищающий всю планету'
|
||||
},
|
||||
research: {
|
||||
researchTime: 'Время исследования',
|
||||
totalCost: 'Общая стоимость',
|
||||
totalPoints: 'Общие очки',
|
||||
levelRange: 'Диапазон уровней'
|
||||
},
|
||||
technologies: {
|
||||
energyTechnology: 'Энергетическая технология',
|
||||
laserTechnology: 'Лазерная технология',
|
||||
ionTechnology: 'Ионная технология',
|
||||
hyperspaceTechnology: 'Гиперпространственная технология',
|
||||
plasmaTechnology: 'Плазменная технология',
|
||||
computerTechnology: 'Компьютерная технология',
|
||||
combustionDrive: 'Реактивный двигатель',
|
||||
impulseDrive: 'Импульсный двигатель',
|
||||
hyperspaceDrive: 'Гиперпространственный двигатель',
|
||||
darkMatterTechnology: 'Технология тёмной материи'
|
||||
},
|
||||
technologyDescriptions: {
|
||||
energyTechnology: 'Улучшает энергоэффективность',
|
||||
laserTechnology: 'Основа лазерного оружия и обороны',
|
||||
ionTechnology: 'Технология ионного оружия',
|
||||
hyperspaceTechnology: 'Технология гиперпространственных прыжков',
|
||||
plasmaTechnology: 'Технология плазменного оружия',
|
||||
computerTechnology: 'Увеличивает вместимость очереди исследований, +1 за уровень (макс 10)',
|
||||
combustionDrive: 'Базовая технология двигателей',
|
||||
impulseDrive: 'Средняя технология двигателей',
|
||||
hyperspaceDrive: 'Продвинутая технология двигателей',
|
||||
darkMatterTechnology: 'Исследование свойств и применения тёмной материи'
|
||||
},
|
||||
officers: {
|
||||
commander: 'Командир',
|
||||
admiral: 'Адмирал',
|
||||
engineer: 'Инженер',
|
||||
geologist: 'Геолог',
|
||||
technocrat: 'Технократ',
|
||||
darkMatterSpecialist: 'Специалист по тёмной материи'
|
||||
},
|
||||
officerDescriptions: {
|
||||
commander: 'Улучшает скорость строительства и управление',
|
||||
admiral: 'Улучшает боевую мощь и скорость флота',
|
||||
engineer: 'Улучшает энергию и оборону',
|
||||
geologist: 'Улучшает производство ресурсов',
|
||||
technocrat: 'Улучшает скорость исследований и шпионаж',
|
||||
darkMatterSpecialist: 'Улучшает эффективность сбора тёмной материи'
|
||||
},
|
||||
queue: {
|
||||
buildQueue: 'Очередь строительства',
|
||||
researchQueue: 'Очередь исследований',
|
||||
building: 'Строится',
|
||||
researching: 'Исследуется',
|
||||
remaining: 'Осталось',
|
||||
cancel: 'Отменить',
|
||||
cancelBuild: 'Отменить строительство',
|
||||
cancelResearch: 'Отменить исследование',
|
||||
confirmCancel: 'Вы уверены, что хотите отменить? 50% ресурсов будет возвращено.',
|
||||
level: 'Уровень',
|
||||
upgradeToLevel: 'Улучшить до уровня'
|
||||
},
|
||||
overview: {
|
||||
title: 'Обзор планеты',
|
||||
resourceOverview: 'Ресурсы',
|
||||
fleetInfo: 'Флот',
|
||||
currentShips: 'Корабли на этой планете'
|
||||
},
|
||||
buildingsView: {
|
||||
title: 'Здания',
|
||||
usedSpace: 'Использовано полей',
|
||||
spaceUsage: 'Использование полей',
|
||||
level: 'Уровень',
|
||||
upgradeCost: 'Стоимость улучшения',
|
||||
buildTime: 'Время строительства',
|
||||
upgrade: 'Улучшить',
|
||||
upgradeFailed: 'Улучшение не удалось',
|
||||
upgradeFailedMessage: 'Пожалуйста, проверьте, достаточно ли у вас ресурсов, места или нет других задач строительства.',
|
||||
demolish: 'Снести',
|
||||
demolishRefund: 'Возврат от сноса',
|
||||
demolishFailed: 'Снос не удался',
|
||||
demolishFailedMessage: 'Невозможно снести это здание. Проверьте, не заполнена ли очередь строительства или уровень здания не равен 0.'
|
||||
},
|
||||
researchView: {
|
||||
title: 'Исследования',
|
||||
researchCost: 'Стоимость исследования',
|
||||
research: 'Исследовать',
|
||||
researchFailed: 'Исследование не удалось',
|
||||
researchFailedMessage:
|
||||
'Пожалуйста, проверьте, достаточно ли у вас ресурсов, выполнены ли предварительные условия или нет других исследовательских задач.'
|
||||
},
|
||||
shipyard: {
|
||||
attack: 'Атака',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
speed: 'Скорость',
|
||||
cargoCapacity: 'Грузоподъёмность',
|
||||
fuelConsumption: 'Расход топлива',
|
||||
buildCost: 'Стоимость постройки',
|
||||
buildTime: 'Время строительства',
|
||||
perUnit: 'За единицу',
|
||||
batchCalculator: 'Калькулятор партий',
|
||||
quantity: 'Количество',
|
||||
totalCost: 'Общая стоимость',
|
||||
totalTime: 'Общее время'
|
||||
},
|
||||
shipyardView: {
|
||||
title: 'Верфь',
|
||||
attack: 'Атака',
|
||||
shield: 'Щит',
|
||||
speed: 'Скорость',
|
||||
cargoCapacity: 'Грузоподъёмность',
|
||||
unitCost: 'Стоимость единицы',
|
||||
buildQuantity: 'Количество для постройки',
|
||||
totalCost: 'Общая стоимость',
|
||||
build: 'Построить',
|
||||
inputError: 'Ошибка ввода',
|
||||
inputErrorMessage: 'Пожалуйста, введите количество для постройки!',
|
||||
buildFailed: 'Постройка не удалась',
|
||||
buildFailedMessage: 'Пожалуйста, проверьте, достаточно ли у вас ресурсов или выполнены ли предварительные условия.'
|
||||
},
|
||||
defense: {
|
||||
attack: 'Атака',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
buildCost: 'Стоимость постройки',
|
||||
buildTime: 'Время строительства',
|
||||
perUnit: 'За единицу',
|
||||
batchCalculator: 'Калькулятор партий',
|
||||
quantity: 'Количество',
|
||||
totalCost: 'Общая стоимость',
|
||||
totalTime: 'Общее время'
|
||||
},
|
||||
defenseView: {
|
||||
title: 'Оборона',
|
||||
attack: 'Атака',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
buildTime: 'Время постройки',
|
||||
seconds: 'с',
|
||||
unitCost: 'Стоимость единицы',
|
||||
buildQuantity: 'Количество для постройки',
|
||||
totalCost: 'Общая стоимость',
|
||||
build: 'Построить',
|
||||
shieldDomeBuilt: 'Щитовой купол уже построен',
|
||||
inputError: 'Ошибка ввода',
|
||||
inputErrorMessage: 'Пожалуйста, введите количество для постройки!',
|
||||
buildFailed: 'Постройка не удалась',
|
||||
buildFailedMessage:
|
||||
'Пожалуйста, проверьте, достаточно ли у вас ресурсов или выполнены ли предварительные условия. Щитовые купола можно построить только один раз.'
|
||||
},
|
||||
fleetView: {
|
||||
title: 'Управление флотом',
|
||||
fleetOverview: 'Обзор флота',
|
||||
sendFleet: 'Отправить флот',
|
||||
flightMissions: 'Полетные миссии',
|
||||
currentPlanetFleet: 'Флот на этой планете',
|
||||
attack: 'Атака',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
speed: 'Скорость',
|
||||
cargo: 'Груз',
|
||||
selectFleet: 'Выбрать флот',
|
||||
selectFleetDescription: 'Выберите количество кораблей для отправки',
|
||||
available: 'Доступно',
|
||||
all: 'Все',
|
||||
targetCoordinates: 'Целевые координаты',
|
||||
galaxy: 'Галактика',
|
||||
system: 'Система',
|
||||
position: 'Позиция',
|
||||
missionType: 'Тип миссии',
|
||||
missionInfo: 'Информация о миссии',
|
||||
fuelConsumption: 'Расход топлива',
|
||||
flightTime: 'Время полета',
|
||||
attackMission: 'Атака',
|
||||
transport: 'Транспорт',
|
||||
colonize: 'Колонизация',
|
||||
spy: 'Разведка',
|
||||
deploy: 'Размещение',
|
||||
transportResources: 'Транспортировка ресурсов',
|
||||
totalCargoCapacity: 'Общая грузоподъёмность',
|
||||
used: 'Использовано',
|
||||
noFlightMissions: 'Нет полетных миссий',
|
||||
outbound: 'Туда',
|
||||
returning: 'Возвращение',
|
||||
fleetComposition: 'Состав флота',
|
||||
carryingResources: 'Перевозимые ресурсы',
|
||||
arrivalTime: 'Время прибытия',
|
||||
returnTime: 'Время возврата',
|
||||
recallFleet: 'Отозвать флот',
|
||||
sendFailed: 'Отправка не удалась',
|
||||
sendFailedMessage: 'Пожалуйста, проверьте количество флота, наличие топлива или ограничения грузоподъёмности.',
|
||||
recallFailed: 'Отзыв не удался',
|
||||
recallFailedMessage: 'Эта миссия не может быть отозвана.',
|
||||
unknownPlanet: 'Неизвестная планета',
|
||||
fleetMissionSlots: 'Слоты миссий флота'
|
||||
},
|
||||
officersView: {
|
||||
title: 'Офицеры',
|
||||
activated: 'Активирован',
|
||||
inactive: 'Неактивен',
|
||||
activeStatus: 'Статус активации',
|
||||
expirationTime: 'Время истечения',
|
||||
remainingTime: 'Оставшееся время',
|
||||
recruitCost: 'Стоимость найма',
|
||||
days: 'дн.',
|
||||
benefitsBonus: 'Бонусы',
|
||||
resourceProduction: 'Производство ресурсов',
|
||||
darkMatterProduction: 'Производство тёмной материи',
|
||||
energyProduction: 'Производство энергии',
|
||||
buildingSpeed: 'Скорость строительства',
|
||||
researchSpeed: 'Скорость исследований',
|
||||
fleetSpeed: 'Скорость флота',
|
||||
fuelConsumption: 'Расход топлива',
|
||||
defense: 'Защита',
|
||||
storageCapacity: 'Вместимость хранилища',
|
||||
buildQueue: 'Очередь строительства',
|
||||
fleetSlots: 'Слоты флота',
|
||||
hire: 'Нанять',
|
||||
renew: 'Продлить',
|
||||
dismiss: 'Уволить',
|
||||
hireTitle: 'Нанять офицера',
|
||||
hireMessage: 'Вы уверены, что хотите нанять {name}? Действует 7 дней.',
|
||||
renewTitle: 'Продлить офицера',
|
||||
renewMessage: 'Вы уверены, что хотите продлить {name} на 7 дней?',
|
||||
dismissTitle: 'Уволить офицера',
|
||||
dismissMessage: 'Вы уверены, что хотите уволить {name}? Средства не возвращаются.',
|
||||
hireFailed: 'Найм не удался',
|
||||
renewFailed: 'Продление не удалось',
|
||||
insufficientResources: 'Недостаточно ресурсов!'
|
||||
},
|
||||
galaxyView: {
|
||||
title: 'Галактика',
|
||||
selectCoordinates: 'Выбрать координаты',
|
||||
galaxy: 'Галактика',
|
||||
selectGalaxy: 'Выбрать галактику',
|
||||
system: 'Система',
|
||||
selectSystem: 'Выбрать систему',
|
||||
view: 'Показать',
|
||||
myPlanet: 'Моя планета',
|
||||
totalPositions: 'Всего 10 позиций планет',
|
||||
mine: 'Моя',
|
||||
hostile: 'Враждебная',
|
||||
emptySlot: 'Пусто - можно колонизировать',
|
||||
scout: 'Разведка',
|
||||
attack: 'Атака',
|
||||
colonize: 'Колонизация',
|
||||
switch: 'Переключить',
|
||||
scoutPlanetTitle: 'Разведать планету',
|
||||
attackPlanetTitle: 'Атаковать планету',
|
||||
colonizePlanetTitle: 'Колонизировать планету',
|
||||
scoutPlanetMessage:
|
||||
'Вы уверены, что хотите отправить шпионские зонды для разведки планеты [{coordinates}]?\n\nПерейдите на страницу флота, чтобы выбрать корабли и отправить.',
|
||||
attackPlanetMessage:
|
||||
'Вы уверены, что хотите атаковать планету [{coordinates}]?\n\nПерейдите на страницу флота, чтобы выбрать корабли и отправить.',
|
||||
colonizePlanetMessage:
|
||||
'Вы уверены, что хотите колонизировать позицию [{coordinates}]?\n\nПерейдите на страницу флота, чтобы отправить колонизационный корабль.'
|
||||
},
|
||||
messagesView: {
|
||||
title: 'Сообщения',
|
||||
battleReports: 'Отчёты о боях',
|
||||
spyReports: 'Отчёты разведки',
|
||||
noBattleReports: 'Нет отчётов о боях',
|
||||
noSpyReports: 'Нет отчётов разведки',
|
||||
battleReport: 'Отчёт о бое',
|
||||
spyReport: 'Отчёт разведки',
|
||||
victory: 'Победа',
|
||||
defeat: 'Поражение',
|
||||
draw: 'Ничья',
|
||||
attackerFleet: 'Флот нападающего',
|
||||
defenderFleet: 'Флот защитника',
|
||||
defenderDefense: 'Оборона защитника',
|
||||
attackerLosses: 'Потери нападающего',
|
||||
defenderLosses: 'Потери защитника',
|
||||
noLosses: 'Без потерь',
|
||||
plunder: 'Добыча',
|
||||
debrisField: 'Поле обломков',
|
||||
resources: 'Ресурсы',
|
||||
fleet: 'Флот',
|
||||
defense: 'Оборона',
|
||||
buildings: 'Здания'
|
||||
},
|
||||
simulatorView: {
|
||||
title: 'Симулятор боя',
|
||||
attacker: 'Нападающий',
|
||||
defender: 'Защитник',
|
||||
attackerConfig: 'Настройки нападающего',
|
||||
attackerConfigDesc: 'Настроить флот и уровни технологий нападающего',
|
||||
defenderConfig: 'Настройки защитника',
|
||||
defenderConfigDesc: 'Настроить флот, оборону и уровни технологий защитника',
|
||||
fleet: 'Флот',
|
||||
defenseStructures: 'Оборонительные сооружения',
|
||||
techLevels: 'Уровни технологий',
|
||||
weapon: 'Оружие',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
defenderResources: 'Ресурсы защитника (для расчёта добычи)',
|
||||
startSimulation: 'Начать симуляцию',
|
||||
reset: 'Сбросить',
|
||||
battleResult: 'Результат боя',
|
||||
attackerVictory: 'Победа нападающего',
|
||||
defenderVictory: 'Победа защитника',
|
||||
draw: 'Ничья',
|
||||
afterRounds: 'После {rounds} раундов',
|
||||
attackerLosses: 'Потери нападающего',
|
||||
defenderLosses: 'Потери защитника',
|
||||
noLosses: 'Без потерь',
|
||||
attackerRemaining: 'Осталось у нападающего',
|
||||
defenderRemaining: 'Осталось у защитника',
|
||||
allDestroyed: 'Всё уничтожено',
|
||||
plunderableResources: 'Доступная добыча',
|
||||
debrisField: 'Поле обломков',
|
||||
moonChance: 'Шанс появления луны',
|
||||
showRoundDetails: 'Показать детали раундов',
|
||||
hideRoundDetails: 'Скрыть детали раундов',
|
||||
round: 'Раунд {round}',
|
||||
attackerRemainingPower: 'Оставшаяся мощь нападающего',
|
||||
defenderRemainingPower: 'Оставшаяся мощь защитника'
|
||||
},
|
||||
settings: {
|
||||
dataManagement: 'Управление данными',
|
||||
dataManagementDesc: 'Экспорт, импорт или очистка игровых данных',
|
||||
exportData: 'Экспорт данных',
|
||||
exportDataDesc: 'Экспортировать прогресс игры в JSON файл',
|
||||
export: 'Экспорт',
|
||||
exporting: 'Экспорт...',
|
||||
exportSuccess: 'Экспорт успешен',
|
||||
exportFailed: 'Экспорт не удался, попробуйте еще раз',
|
||||
importData: 'Импорт данных',
|
||||
importDataDesc: 'Восстановить прогресс игры из JSON файла',
|
||||
selectFile: 'Выбрать файл',
|
||||
importSuccess: 'Импорт успешен',
|
||||
importConfirmTitle: 'Подтвердить импорт',
|
||||
importConfirmMessage: 'Импорт перезапишет текущий прогресс игры. Это действие невозможно отменить. Продолжить?',
|
||||
importFailed: 'Импорт не удался, проверьте формат файла',
|
||||
clearData: 'Очистить данные',
|
||||
clearDataDesc: 'Удалить все игровые данные и сбросить',
|
||||
clear: 'Очистить',
|
||||
clearConfirmTitle: 'Подтвердить очистку данных',
|
||||
clearConfirmMessage: 'Все игровые данные будут удалены и игра начнется заново. Это действие невозможно отменить. Продолжить?',
|
||||
gameSettings: 'Настройки игры',
|
||||
gameSettingsDesc: 'Настроить параметры и предпочтения игры',
|
||||
playerName: 'Имя игрока',
|
||||
gameSpeed: 'Скорость игры',
|
||||
gameSpeedDesc: 'Текущий множитель скорости игры',
|
||||
about: 'О программе',
|
||||
version: 'Версия',
|
||||
buildDate: 'Дата сборки',
|
||||
community: 'Сообщество',
|
||||
github: 'Репозиторий GitHub',
|
||||
qqGroup: 'Группа QQ'
|
||||
}
|
||||
}
|
||||
531
src/locales/zh-CN.ts
Normal file
531
src/locales/zh-CN.ts
Normal file
@@ -0,0 +1,531 @@
|
||||
export default {
|
||||
common: {
|
||||
confirm: '确认',
|
||||
cancel: '取消',
|
||||
delete: '删除',
|
||||
edit: '编辑',
|
||||
save: '保存',
|
||||
close: '关闭',
|
||||
back: '返回',
|
||||
next: '下一步',
|
||||
previous: '上一步',
|
||||
submit: '提交',
|
||||
reset: '重置',
|
||||
search: '搜索',
|
||||
filter: '筛选',
|
||||
loading: '加载中...',
|
||||
noData: '暂无数据',
|
||||
error: '错误',
|
||||
success: '成功',
|
||||
warning: '警告',
|
||||
info: '信息',
|
||||
resourceType: '资源类型',
|
||||
playerName: '指挥官',
|
||||
timeHour: '时',
|
||||
timeMinute: '分',
|
||||
timeSecond: '秒',
|
||||
featureLocked: '功能已锁定',
|
||||
unlockRequired: '需要解锁前置建筑',
|
||||
requiredBuilding: '所需建筑',
|
||||
currentLevel: '当前等级',
|
||||
goToBuildings: '前往建筑页面',
|
||||
locked: '已锁定',
|
||||
viewRequirements: '查看前置条件',
|
||||
requirementsNotMet: '前置条件未满足',
|
||||
current: '当前'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: '不满足前置条件',
|
||||
insufficientResources: '资源不足',
|
||||
shieldDomeLimit: '护盾罩数量限制',
|
||||
fleetMissionsFull: '舰队任务槽位已满',
|
||||
insufficientFleet: '舰队数量不足',
|
||||
insufficientFuel: '燃料不足',
|
||||
planetOnly: '该建筑只能在行星上建造',
|
||||
moonOnly: '该建筑只能在月球上建造',
|
||||
buildQueueFull: '建造队列已满',
|
||||
insufficientSpace: '空间不足',
|
||||
buildingLevelZero: '建筑等级为0,无法拆除',
|
||||
researchQueueFull: '研究队列已满',
|
||||
moonExists: '已存在月球',
|
||||
insufficientDebris: '残骸场不足'
|
||||
},
|
||||
nav: {
|
||||
overview: '总览',
|
||||
buildings: '建筑',
|
||||
research: '研究',
|
||||
shipyard: '船坞',
|
||||
defense: '防御',
|
||||
fleet: '舰队',
|
||||
officers: '军官',
|
||||
simulator: '模拟',
|
||||
galaxy: '星系',
|
||||
messages: '消息',
|
||||
settings: '设置'
|
||||
},
|
||||
sidebar: {
|
||||
language: '语言',
|
||||
lightMode: '日间模式',
|
||||
darkMode: '夜间模式',
|
||||
collapse: '收起菜单',
|
||||
expand: '展开菜单'
|
||||
},
|
||||
resources: {
|
||||
metal: '金属',
|
||||
crystal: '晶体',
|
||||
deuterium: '重氢',
|
||||
darkMatter: '暗物质',
|
||||
energy: '能量',
|
||||
production: '产量',
|
||||
capacity: '容量',
|
||||
current: '当前储量',
|
||||
max: '最大容量',
|
||||
perHour: '小时'
|
||||
},
|
||||
planet: {
|
||||
planet: '星球',
|
||||
moon: '月球',
|
||||
colony: '殖民地',
|
||||
position: '位置',
|
||||
coordinates: '坐标',
|
||||
switchToMoon: '查看月球',
|
||||
backToPlanet: '返回母星',
|
||||
fields: '场地',
|
||||
temperature: '温度',
|
||||
homePlanet: '母星',
|
||||
planetPrefix: '星球',
|
||||
moonSuffix: '的月球',
|
||||
colonyPrefix: '殖民地'
|
||||
},
|
||||
player: {
|
||||
points: '总积分'
|
||||
},
|
||||
buildings: {
|
||||
metalMine: '金属矿',
|
||||
crystalMine: '晶体矿',
|
||||
deuteriumSynthesizer: '重氢合成器',
|
||||
solarPlant: '太阳能电站',
|
||||
roboticsFactory: '机器人工厂',
|
||||
naniteFactory: '纳米工厂',
|
||||
shipyard: '船坞',
|
||||
researchLab: '研究实验室',
|
||||
metalStorage: '金属仓库',
|
||||
crystalStorage: '晶体仓库',
|
||||
deuteriumTank: '重氢罐',
|
||||
darkMatterCollector: '暗物质收集器',
|
||||
lunarBase: '月球基地',
|
||||
sensorPhalanx: '传感器阵列',
|
||||
jumpGate: '跳跃门',
|
||||
buildTime: '建造时间',
|
||||
production: '产量',
|
||||
consumption: '消耗',
|
||||
totalCost: '累积成本',
|
||||
totalPoints: '累积积分',
|
||||
levelRange: '等级范围'
|
||||
},
|
||||
buildingDescriptions: {
|
||||
metalMine: '开采金属资源',
|
||||
crystalMine: '开采晶体资源',
|
||||
deuteriumSynthesizer: '合成重氢资源',
|
||||
solarPlant: '提供能源',
|
||||
roboticsFactory: '加快建造速度',
|
||||
naniteFactory: '增加建造队列数量,每级+1队列(最多10个)',
|
||||
shipyard: '建造舰船',
|
||||
researchLab: '研究科技',
|
||||
metalStorage: '增加金属存储上限',
|
||||
crystalStorage: '增加晶体存储上限',
|
||||
deuteriumTank: '增加重氢存储上限',
|
||||
darkMatterCollector: '收集稀有的暗物质资源',
|
||||
lunarBase: '增加月球可用空间',
|
||||
sensorPhalanx: '侦测周围星系的舰队活动',
|
||||
jumpGate: '瞬间传送舰队到其他月球'
|
||||
},
|
||||
ships: {
|
||||
lightFighter: '轻型战斗机',
|
||||
heavyFighter: '重型战斗机',
|
||||
cruiser: '巡洋舰',
|
||||
battleship: '战列舰',
|
||||
smallCargo: '小型运输船',
|
||||
largeCargo: '大型运输船',
|
||||
colonyShip: '殖民船',
|
||||
recycler: '回收船',
|
||||
espionageProbe: '间谍探测器',
|
||||
darkMatterHarvester: '暗物质采集船'
|
||||
},
|
||||
shipDescriptions: {
|
||||
lightFighter: '基础战斗单位',
|
||||
heavyFighter: '重装战斗机',
|
||||
cruiser: '中型战舰,攻守平衡',
|
||||
battleship: '强力战舰',
|
||||
smallCargo: '运输少量资源',
|
||||
largeCargo: '运输大量资源',
|
||||
colonyShip: '用于殖民新星球',
|
||||
recycler: '收集残骸场资源',
|
||||
espionageProbe: '侦察敌方星球',
|
||||
darkMatterHarvester: '专门用于采集暗物质的特殊飞船'
|
||||
},
|
||||
defenses: {
|
||||
rocketLauncher: '火箭发射器',
|
||||
lightLaser: '轻型激光炮',
|
||||
heavyLaser: '重型激光炮',
|
||||
gaussCannon: '高斯炮',
|
||||
ionCannon: '离子炮',
|
||||
plasmaTurret: '等离子炮塔',
|
||||
smallShieldDome: '小型护盾罩',
|
||||
largeShieldDome: '大型护盾罩'
|
||||
},
|
||||
defenseDescriptions: {
|
||||
rocketLauncher: '基础防御设施',
|
||||
lightLaser: '轻型能量武器',
|
||||
heavyLaser: '重型能量武器',
|
||||
gaussCannon: '高速动能武器',
|
||||
ionCannon: '破坏护盾的利器',
|
||||
plasmaTurret: '强力防御设施',
|
||||
smallShieldDome: '保护整个星球的小型护盾',
|
||||
largeShieldDome: '保护整个星球的大型护盾'
|
||||
},
|
||||
research: {
|
||||
researchTime: '研究时间',
|
||||
totalCost: '累积成本',
|
||||
totalPoints: '累积积分',
|
||||
levelRange: '等级范围'
|
||||
},
|
||||
technologies: {
|
||||
energyTechnology: '能源技术',
|
||||
laserTechnology: '激光技术',
|
||||
ionTechnology: '离子技术',
|
||||
hyperspaceTechnology: '超空间技术',
|
||||
plasmaTechnology: '等离子技术',
|
||||
computerTechnology: '计算机技术',
|
||||
combustionDrive: '燃烧引擎',
|
||||
impulseDrive: '脉冲引擎',
|
||||
hyperspaceDrive: '超空间引擎',
|
||||
darkMatterTechnology: '暗物质技术'
|
||||
},
|
||||
technologyDescriptions: {
|
||||
energyTechnology: '提高能源利用效率',
|
||||
laserTechnology: '激光武器和防御的基础',
|
||||
ionTechnology: '离子武器技术',
|
||||
hyperspaceTechnology: '超空间跳跃技术',
|
||||
plasmaTechnology: '等离子武器技术',
|
||||
computerTechnology: '增加研究队列数量,每级+1队列(最多10个)',
|
||||
combustionDrive: '基础推进技术',
|
||||
impulseDrive: '中级推进技术',
|
||||
hyperspaceDrive: '高级推进技术',
|
||||
darkMatterTechnology: '研究暗物质的性质和应用'
|
||||
},
|
||||
officers: {
|
||||
commander: '指挥官',
|
||||
admiral: '上将',
|
||||
engineer: '工程师',
|
||||
geologist: '地质学家',
|
||||
technocrat: '技术专家',
|
||||
darkMatterSpecialist: '暗物质专家'
|
||||
},
|
||||
officerDescriptions: {
|
||||
commander: '提升建筑速度和管理能力',
|
||||
admiral: '提升舰队战斗力和速度',
|
||||
engineer: '提升能源和防御能力',
|
||||
geologist: '提升资源产量',
|
||||
technocrat: '提升研究速度和间谍能力',
|
||||
darkMatterSpecialist: '提升暗物质采集效率'
|
||||
},
|
||||
queue: {
|
||||
buildQueue: '建造队列',
|
||||
researchQueue: '研究队列',
|
||||
building: '建造中',
|
||||
researching: '研究中',
|
||||
remaining: '剩余时间',
|
||||
cancel: '取消',
|
||||
cancelBuild: '取消建造',
|
||||
cancelResearch: '取消研究',
|
||||
confirmCancel: '确定要取消吗?将返还50%的资源。',
|
||||
level: '等级',
|
||||
upgradeToLevel: '升级到等级'
|
||||
},
|
||||
overview: {
|
||||
title: '星球总览',
|
||||
resourceOverview: '资源概览',
|
||||
fleetInfo: '舰队',
|
||||
currentShips: '当前星球的舰船数量'
|
||||
},
|
||||
buildingsView: {
|
||||
title: '建筑',
|
||||
usedSpace: '已用空间',
|
||||
spaceUsage: '占用空间',
|
||||
level: '等级',
|
||||
upgradeCost: '升级消耗',
|
||||
buildTime: '建造时间',
|
||||
upgrade: '升级',
|
||||
upgradeFailed: '升级失败',
|
||||
upgradeFailedMessage: '请检查资源是否足够、空间是否充足或是否有其他建造任务。',
|
||||
demolish: '拆除',
|
||||
demolishRefund: '拆除返还',
|
||||
demolishFailed: '拆除失败',
|
||||
demolishFailedMessage: '无法拆除该建筑,请检查建造队列是否已满或建筑等级是否为0。'
|
||||
},
|
||||
researchView: {
|
||||
title: '研究',
|
||||
researchCost: '研究消耗',
|
||||
research: '研究',
|
||||
researchFailed: '研究失败',
|
||||
researchFailedMessage: '请检查资源是否足够、前置条件是否满足,或是否有其他研究任务。'
|
||||
},
|
||||
shipyard: {
|
||||
attack: '攻击力',
|
||||
shield: '护盾',
|
||||
armor: '装甲',
|
||||
speed: '速度',
|
||||
cargoCapacity: '载货量',
|
||||
fuelConsumption: '燃料消耗',
|
||||
buildCost: '建造成本',
|
||||
buildTime: '建造时间',
|
||||
perUnit: '每个单位',
|
||||
batchCalculator: '批量建造计算器',
|
||||
quantity: '数量',
|
||||
totalCost: '总成本',
|
||||
totalTime: '总时间'
|
||||
},
|
||||
shipyardView: {
|
||||
title: '船坞',
|
||||
attack: '攻击力',
|
||||
shield: '护盾',
|
||||
speed: '速度',
|
||||
cargoCapacity: '载货量',
|
||||
unitCost: '单位成本',
|
||||
buildQuantity: '建造数量',
|
||||
totalCost: '总成本',
|
||||
build: '建造',
|
||||
inputError: '输入错误',
|
||||
inputErrorMessage: '请输入建造数量!',
|
||||
buildFailed: '建造失败',
|
||||
buildFailedMessage: '请检查资源是否足够或前置条件是否满足。'
|
||||
},
|
||||
defense: {
|
||||
attack: '攻击力',
|
||||
shield: '护盾',
|
||||
armor: '装甲',
|
||||
buildCost: '建造成本',
|
||||
buildTime: '建造时间',
|
||||
perUnit: '每个单位',
|
||||
batchCalculator: '批量建造计算器',
|
||||
quantity: '数量',
|
||||
totalCost: '总成本',
|
||||
totalTime: '总时间'
|
||||
},
|
||||
defenseView: {
|
||||
title: '防御设施',
|
||||
attack: '攻击力',
|
||||
shield: '护盾',
|
||||
armor: '装甲',
|
||||
buildTime: '建造时间',
|
||||
seconds: '秒',
|
||||
unitCost: '单位成本',
|
||||
buildQuantity: '建造数量',
|
||||
totalCost: '总成本',
|
||||
build: '建造',
|
||||
shieldDomeBuilt: '护盾罩已建造',
|
||||
inputError: '输入错误',
|
||||
inputErrorMessage: '请输入建造数量!',
|
||||
buildFailed: '建造失败',
|
||||
buildFailedMessage: '请检查资源是否足够或前置条件是否满足。护盾罩只能建造一个。'
|
||||
},
|
||||
fleetView: {
|
||||
title: '舰队管理',
|
||||
fleetOverview: '舰队总览',
|
||||
sendFleet: '派遣舰队',
|
||||
flightMissions: '飞行任务',
|
||||
currentPlanetFleet: '当前星球舰队',
|
||||
attack: '攻击',
|
||||
shield: '护盾',
|
||||
armor: '装甲',
|
||||
speed: '速度',
|
||||
cargo: '载货',
|
||||
selectFleet: '选择舰队',
|
||||
selectFleetDescription: '选择要派遣的舰船数量',
|
||||
available: '可用',
|
||||
all: '全部',
|
||||
targetCoordinates: '目标坐标',
|
||||
galaxy: '银河系',
|
||||
system: '星系',
|
||||
position: '位置',
|
||||
missionType: '任务类型',
|
||||
missionInfo: '任务信息',
|
||||
fuelConsumption: '燃料消耗',
|
||||
flightTime: '飞行时间',
|
||||
attackMission: '攻击',
|
||||
transport: '运输',
|
||||
colonize: '殖民',
|
||||
spy: '侦察',
|
||||
deploy: '部署',
|
||||
transportResources: '运输资源',
|
||||
totalCargoCapacity: '总载货量',
|
||||
used: '已用',
|
||||
noFlightMissions: '暂无飞行任务',
|
||||
outbound: '前往',
|
||||
returning: '返回',
|
||||
fleetComposition: '舰队组成',
|
||||
carryingResources: '携带资源',
|
||||
arrivalTime: '到达时间',
|
||||
returnTime: '返回时间',
|
||||
recallFleet: '召回舰队',
|
||||
sendFailed: '派遣失败',
|
||||
sendFailedMessage: '请检查舰队数量、燃料是否充足,或载货量是否超出限制。',
|
||||
recallFailed: '召回失败',
|
||||
recallFailedMessage: '该任务无法召回。',
|
||||
unknownPlanet: '未知星球',
|
||||
fleetMissionSlots: '舰队任务槽位'
|
||||
},
|
||||
officersView: {
|
||||
title: '军官',
|
||||
activated: '已激活',
|
||||
inactive: '未激活',
|
||||
activeStatus: '激活状态',
|
||||
expirationTime: '到期时间',
|
||||
remainingTime: '剩余时间',
|
||||
recruitCost: '招募成本',
|
||||
days: '天',
|
||||
benefitsBonus: '效果加成',
|
||||
resourceProduction: '资源产量',
|
||||
darkMatterProduction: '暗物质产量',
|
||||
energyProduction: '电量产出',
|
||||
buildingSpeed: '建筑速度',
|
||||
researchSpeed: '研究速度',
|
||||
fleetSpeed: '舰队速度',
|
||||
fuelConsumption: '燃料消耗',
|
||||
defense: '防御力',
|
||||
storageCapacity: '仓储容量',
|
||||
buildQueue: '建筑队列',
|
||||
fleetSlots: '舰队槽位',
|
||||
hire: '招募',
|
||||
renew: '续约',
|
||||
dismiss: '解雇',
|
||||
hireTitle: '招募军官',
|
||||
hireMessage: '确定要招募 {name} 吗?有效期为7天。',
|
||||
renewTitle: '续约军官',
|
||||
renewMessage: '确定要为 {name} 续约7天吗?',
|
||||
dismissTitle: '解雇军官',
|
||||
dismissMessage: '确定要解雇 {name} 吗?不会返还任何费用。',
|
||||
hireFailed: '招募失败',
|
||||
renewFailed: '续约失败',
|
||||
insufficientResources: '资源不足!'
|
||||
},
|
||||
galaxyView: {
|
||||
title: '星系',
|
||||
selectCoordinates: '选择坐标',
|
||||
galaxy: '银河系',
|
||||
selectGalaxy: '选择银河系',
|
||||
system: '星系',
|
||||
selectSystem: '选择星系',
|
||||
view: '查看',
|
||||
myPlanet: '我的星球',
|
||||
totalPositions: '共10个星球位置',
|
||||
mine: '我的',
|
||||
hostile: '敌对',
|
||||
emptySlot: '空位 - 可殖民',
|
||||
scout: '侦察',
|
||||
attack: '攻击',
|
||||
colonize: '殖民',
|
||||
switch: '切换',
|
||||
scoutPlanetTitle: '侦察星球',
|
||||
attackPlanetTitle: '攻击星球',
|
||||
colonizePlanetTitle: '殖民星球',
|
||||
scoutPlanetMessage: '确定要派遣间谍探测器侦察星球 [{coordinates}] 吗?\n\n请前往舰队页面选择舰船并派遣。',
|
||||
attackPlanetMessage: '确定要攻击星球 [{coordinates}] 吗?\n\n请前往舰队页面选择舰船并派遣。',
|
||||
colonizePlanetMessage: '确定要殖民位置 [{coordinates}] 吗?\n\n请前往舰队页面派遣殖民船。'
|
||||
},
|
||||
messagesView: {
|
||||
title: '消息中心',
|
||||
battleReports: '战斗报告',
|
||||
spyReports: '间谍报告',
|
||||
noBattleReports: '暂无战斗报告',
|
||||
noSpyReports: '暂无间谍报告',
|
||||
battleReport: '战斗报告',
|
||||
spyReport: '间谍报告',
|
||||
victory: '胜利',
|
||||
defeat: '失败',
|
||||
draw: '平局',
|
||||
attackerFleet: '攻击方舰队',
|
||||
defenderFleet: '防守方舰队',
|
||||
defenderDefense: '防守方防御',
|
||||
attackerLosses: '攻击方损失',
|
||||
defenderLosses: '防守方损失',
|
||||
noLosses: '无损失',
|
||||
plunder: '掠夺资源',
|
||||
debrisField: '残骸场',
|
||||
resources: '资源',
|
||||
fleet: '舰队',
|
||||
defense: '防御',
|
||||
buildings: '建筑'
|
||||
},
|
||||
simulatorView: {
|
||||
title: '战斗模拟器',
|
||||
attacker: '攻击方',
|
||||
defender: '防守方',
|
||||
attackerConfig: '攻击方配置',
|
||||
attackerConfigDesc: '设置攻击方的舰队和科技等级',
|
||||
defenderConfig: '防守方配置',
|
||||
defenderConfigDesc: '设置防守方的舰队、防御和科技等级',
|
||||
fleet: '舰队',
|
||||
defenseStructures: '防御设施',
|
||||
techLevels: '科技等级',
|
||||
weapon: '武器',
|
||||
shield: '护盾',
|
||||
armor: '装甲',
|
||||
defenderResources: '防守方资源(用于掠夺计算)',
|
||||
startSimulation: '开始模拟',
|
||||
reset: '重置',
|
||||
battleResult: '战斗结果',
|
||||
attackerVictory: '攻击方胜利',
|
||||
defenderVictory: '防守方胜利',
|
||||
draw: '平局',
|
||||
afterRounds: '经过 {rounds} 回合战斗',
|
||||
attackerLosses: '攻击方损失',
|
||||
defenderLosses: '防守方损失',
|
||||
noLosses: '无损失',
|
||||
attackerRemaining: '攻击方剩余',
|
||||
defenderRemaining: '防守方剩余',
|
||||
allDestroyed: '全部摧毁',
|
||||
plunderableResources: '可掠夺资源',
|
||||
debrisField: '残骸场',
|
||||
moonChance: '月球生成概率',
|
||||
showRoundDetails: '显示回合详情',
|
||||
hideRoundDetails: '隐藏回合详情',
|
||||
round: '第 {round} 回合',
|
||||
attackerRemainingPower: '攻击方剩余火力',
|
||||
defenderRemainingPower: '防守方剩余火力'
|
||||
},
|
||||
settings: {
|
||||
dataManagement: '数据管理',
|
||||
dataManagementDesc: '导出、导入或清除游戏数据',
|
||||
exportData: '导出数据',
|
||||
exportDataDesc: '将游戏进度导出为JSON文件',
|
||||
export: '导出',
|
||||
exporting: '导出中...',
|
||||
exportSuccess: '导出成功',
|
||||
exportFailed: '导出失败,请重试',
|
||||
importData: '导入数据',
|
||||
importDataDesc: '从JSON文件恢复游戏进度',
|
||||
selectFile: '导入',
|
||||
importSuccess: '导入成功',
|
||||
importConfirmTitle: '确认导入数据',
|
||||
importConfirmMessage: '导入数据将覆盖当前游戏进度,此操作不可撤销。确定要继续吗?',
|
||||
importFailed: '导入失败,请检查文件格式',
|
||||
clearData: '清除数据',
|
||||
clearDataDesc: '删除所有游戏数据并重置游戏',
|
||||
clear: '清除',
|
||||
clearConfirmTitle: '确认清除数据',
|
||||
clearConfirmMessage: '这将删除所有游戏数据并重新开始,此操作不可撤销。确定要继续吗?',
|
||||
gameSettings: '游戏设置',
|
||||
gameSettingsDesc: '调整游戏参数和偏好设置',
|
||||
playerName: '玩家名称',
|
||||
gameSpeed: '游戏速度',
|
||||
gameSpeedDesc: '当前游戏速度倍率',
|
||||
about: '关于',
|
||||
version: '版本',
|
||||
buildDate: '构建日期',
|
||||
community: '社区',
|
||||
github: 'GitHub 仓库',
|
||||
qqGroup: 'QQ 交流群'
|
||||
}
|
||||
}
|
||||
531
src/locales/zh-TW.ts
Normal file
531
src/locales/zh-TW.ts
Normal file
@@ -0,0 +1,531 @@
|
||||
export default {
|
||||
common: {
|
||||
confirm: '確認',
|
||||
cancel: '取消',
|
||||
delete: '刪除',
|
||||
edit: '編輯',
|
||||
save: '儲存',
|
||||
close: '關閉',
|
||||
back: '返回',
|
||||
next: '下一步',
|
||||
previous: '上一步',
|
||||
submit: '提交',
|
||||
reset: '重置',
|
||||
search: '搜尋',
|
||||
filter: '篩選',
|
||||
loading: '載入中...',
|
||||
noData: '暫無資料',
|
||||
error: '錯誤',
|
||||
success: '成功',
|
||||
warning: '警告',
|
||||
info: '資訊',
|
||||
resourceType: '資源類型',
|
||||
playerName: '指揮官',
|
||||
timeHour: '時',
|
||||
timeMinute: '分',
|
||||
timeSecond: '秒',
|
||||
featureLocked: '功能已鎖定',
|
||||
unlockRequired: '需要解鎖前置建築',
|
||||
requiredBuilding: '所需建築',
|
||||
currentLevel: '當前等級',
|
||||
goToBuildings: '前往建築頁面',
|
||||
locked: '已鎖定',
|
||||
viewRequirements: '查看前置條件',
|
||||
requirementsNotMet: '前置條件未滿足',
|
||||
current: '當前'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: '不滿足前置條件',
|
||||
insufficientResources: '資源不足',
|
||||
shieldDomeLimit: '護盾罩數量限制',
|
||||
fleetMissionsFull: '艦隊任務槽位已滿',
|
||||
insufficientFleet: '艦隊數量不足',
|
||||
insufficientFuel: '燃料不足',
|
||||
planetOnly: '該建築只能在行星上建造',
|
||||
moonOnly: '該建築只能在月球上建造',
|
||||
buildQueueFull: '建造隊列已滿',
|
||||
insufficientSpace: '空間不足',
|
||||
buildingLevelZero: '建築等級為0,無法拆除',
|
||||
researchQueueFull: '研究隊列已滿',
|
||||
moonExists: '已存在月球',
|
||||
insufficientDebris: '殘骸場不足'
|
||||
},
|
||||
nav: {
|
||||
overview: '總覽',
|
||||
buildings: '建築',
|
||||
research: '研究',
|
||||
shipyard: '船塢',
|
||||
defense: '防禦',
|
||||
fleet: '艦隊',
|
||||
officers: '軍官',
|
||||
simulator: '模擬',
|
||||
galaxy: '星系',
|
||||
messages: '訊息',
|
||||
settings: '設定'
|
||||
},
|
||||
sidebar: {
|
||||
language: '語言',
|
||||
lightMode: '日間模式',
|
||||
darkMode: '夜間模式',
|
||||
collapse: '收起選單',
|
||||
expand: '展開選單'
|
||||
},
|
||||
resources: {
|
||||
metal: '金屬',
|
||||
crystal: '晶體',
|
||||
deuterium: '重氫',
|
||||
darkMatter: '暗物質',
|
||||
energy: '能量',
|
||||
production: '產量',
|
||||
capacity: '容量',
|
||||
current: '當前儲量',
|
||||
max: '最大容量',
|
||||
perHour: '小時'
|
||||
},
|
||||
planet: {
|
||||
planet: '星球',
|
||||
moon: '月球',
|
||||
colony: '殖民地',
|
||||
position: '位置',
|
||||
coordinates: '座標',
|
||||
switchToMoon: '查看月球',
|
||||
backToPlanet: '返回母星',
|
||||
fields: '場地',
|
||||
temperature: '溫度',
|
||||
homePlanet: '母星',
|
||||
planetPrefix: '星球',
|
||||
moonSuffix: '的月球',
|
||||
colonyPrefix: '殖民地'
|
||||
},
|
||||
player: {
|
||||
points: '總積分'
|
||||
},
|
||||
buildings: {
|
||||
metalMine: '金屬礦',
|
||||
crystalMine: '晶體礦',
|
||||
deuteriumSynthesizer: '重氫合成器',
|
||||
solarPlant: '太陽能電站',
|
||||
roboticsFactory: '機器人工廠',
|
||||
naniteFactory: '納米工廠',
|
||||
shipyard: '船塢',
|
||||
researchLab: '研究實驗室',
|
||||
metalStorage: '金屬倉庫',
|
||||
crystalStorage: '晶體倉庫',
|
||||
deuteriumTank: '重氫罐',
|
||||
darkMatterCollector: '暗物質收集器',
|
||||
lunarBase: '月球基地',
|
||||
sensorPhalanx: '傳感器陣列',
|
||||
jumpGate: '跳躍門',
|
||||
buildTime: '建造時間',
|
||||
production: '產量',
|
||||
consumption: '消耗',
|
||||
totalCost: '累積成本',
|
||||
totalPoints: '累積積分',
|
||||
levelRange: '等級範圍'
|
||||
},
|
||||
buildingDescriptions: {
|
||||
metalMine: '開採金屬資源',
|
||||
crystalMine: '開採晶體資源',
|
||||
deuteriumSynthesizer: '合成重氫資源',
|
||||
solarPlant: '提供能源',
|
||||
roboticsFactory: '加快建造速度',
|
||||
naniteFactory: '增加建造佇列數量,每級+1佇列(最多10個)',
|
||||
shipyard: '建造艦船',
|
||||
researchLab: '研究科技',
|
||||
metalStorage: '增加金屬儲存上限',
|
||||
crystalStorage: '增加晶體儲存上限',
|
||||
deuteriumTank: '增加重氫儲存上限',
|
||||
darkMatterCollector: '收集稀有的暗物質資源',
|
||||
lunarBase: '增加月球可用空間',
|
||||
sensorPhalanx: '偵測周圍星系的艦隊活動',
|
||||
jumpGate: '瞬間傳送艦隊到其他月球'
|
||||
},
|
||||
ships: {
|
||||
lightFighter: '輕型戰鬥機',
|
||||
heavyFighter: '重型戰鬥機',
|
||||
cruiser: '巡洋艦',
|
||||
battleship: '戰列艦',
|
||||
smallCargo: '小型運輸船',
|
||||
largeCargo: '大型運輸船',
|
||||
colonyShip: '殖民船',
|
||||
recycler: '回收船',
|
||||
espionageProbe: '間諜探測器',
|
||||
darkMatterHarvester: '暗物質採集船'
|
||||
},
|
||||
shipDescriptions: {
|
||||
lightFighter: '基礎戰鬥單位',
|
||||
heavyFighter: '重裝戰鬥機',
|
||||
cruiser: '中型戰艦,攻守平衡',
|
||||
battleship: '強力戰艦',
|
||||
smallCargo: '運輸少量資源',
|
||||
largeCargo: '運輸大量資源',
|
||||
colonyShip: '用於殖民新星球',
|
||||
recycler: '收集殘骸場資源',
|
||||
espionageProbe: '偵察敵方星球',
|
||||
darkMatterHarvester: '專門用於採集暗物質的特殊飛船'
|
||||
},
|
||||
defenses: {
|
||||
rocketLauncher: '火箭發射器',
|
||||
lightLaser: '輕型激光炮',
|
||||
heavyLaser: '重型激光炮',
|
||||
gaussCannon: '高斯炮',
|
||||
ionCannon: '離子炮',
|
||||
plasmaTurret: '等離子炮塔',
|
||||
smallShieldDome: '小型護盾罩',
|
||||
largeShieldDome: '大型護盾罩'
|
||||
},
|
||||
defenseDescriptions: {
|
||||
rocketLauncher: '基礎防禦設施',
|
||||
lightLaser: '輕型能量武器',
|
||||
heavyLaser: '重型能量武器',
|
||||
gaussCannon: '高速動能武器',
|
||||
ionCannon: '破壞護盾的利器',
|
||||
plasmaTurret: '強力防禦設施',
|
||||
smallShieldDome: '保護整個星球的小型護盾',
|
||||
largeShieldDome: '保護整個星球的大型護盾'
|
||||
},
|
||||
research: {
|
||||
researchTime: '研究時間',
|
||||
totalCost: '累積成本',
|
||||
totalPoints: '累積積分',
|
||||
levelRange: '等級範圍'
|
||||
},
|
||||
technologies: {
|
||||
energyTechnology: '能源技術',
|
||||
laserTechnology: '激光技術',
|
||||
ionTechnology: '離子技術',
|
||||
hyperspaceTechnology: '超空間技術',
|
||||
plasmaTechnology: '等離子技術',
|
||||
computerTechnology: '計算機技術',
|
||||
combustionDrive: '燃燒引擎',
|
||||
impulseDrive: '脈衝引擎',
|
||||
hyperspaceDrive: '超空間引擎',
|
||||
darkMatterTechnology: '暗物質技術'
|
||||
},
|
||||
technologyDescriptions: {
|
||||
energyTechnology: '提高能源利用效率',
|
||||
laserTechnology: '激光武器和防禦的基礎',
|
||||
ionTechnology: '離子武器技術',
|
||||
hyperspaceTechnology: '超空間跳躍技術',
|
||||
plasmaTechnology: '等離子武器技術',
|
||||
computerTechnology: '增加研究佇列數量,每級+1佇列(最多10個)',
|
||||
combustionDrive: '基礎推進技術',
|
||||
impulseDrive: '中級推進技術',
|
||||
hyperspaceDrive: '高級推進技術',
|
||||
darkMatterTechnology: '研究暗物質的性質和應用'
|
||||
},
|
||||
officers: {
|
||||
commander: '指揮官',
|
||||
admiral: '上將',
|
||||
engineer: '工程師',
|
||||
geologist: '地質學家',
|
||||
technocrat: '技術專家',
|
||||
darkMatterSpecialist: '暗物質專家'
|
||||
},
|
||||
officerDescriptions: {
|
||||
commander: '提升建築速度和管理能力',
|
||||
admiral: '提升艦隊戰鬥力和速度',
|
||||
engineer: '提升能源和防禦能力',
|
||||
geologist: '提升資源產量',
|
||||
technocrat: '提升研究速度和間諜能力',
|
||||
darkMatterSpecialist: '提升暗物質採集效率'
|
||||
},
|
||||
queue: {
|
||||
buildQueue: '建造佇列',
|
||||
researchQueue: '研究佇列',
|
||||
building: '建造中',
|
||||
researching: '研究中',
|
||||
remaining: '剩餘時間',
|
||||
cancel: '取消',
|
||||
cancelBuild: '取消建造',
|
||||
cancelResearch: '取消研究',
|
||||
confirmCancel: '確定要取消嗎?將返還50%的資源。',
|
||||
level: '等級',
|
||||
upgradeToLevel: '升級到等級'
|
||||
},
|
||||
overview: {
|
||||
title: '星球總覽',
|
||||
resourceOverview: '資源概覽',
|
||||
fleetInfo: '艦隊資訊',
|
||||
currentShips: '當前星球的艦船數量'
|
||||
},
|
||||
buildingsView: {
|
||||
title: '建築',
|
||||
usedSpace: '已用空間',
|
||||
spaceUsage: '佔用空間',
|
||||
level: '等級',
|
||||
upgradeCost: '升級消耗',
|
||||
buildTime: '建造時間',
|
||||
upgrade: '升級',
|
||||
upgradeFailed: '升級失敗',
|
||||
upgradeFailedMessage: '請檢查資源是否足夠、空間是否充足或是否有其他建造任務。',
|
||||
demolish: '拆除',
|
||||
demolishRefund: '拆除返還',
|
||||
demolishFailed: '拆除失敗',
|
||||
demolishFailedMessage: '無法拆除該建築,請檢查建造隊列是否已滿或建築等級是否為0。'
|
||||
},
|
||||
researchView: {
|
||||
title: '研究',
|
||||
researchCost: '研究消耗',
|
||||
research: '研究',
|
||||
researchFailed: '研究失敗',
|
||||
researchFailedMessage: '請檢查資源是否足夠、前置條件是否滿足,或是否有其他研究任務。'
|
||||
},
|
||||
shipyard: {
|
||||
attack: '攻擊力',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
speed: '速度',
|
||||
cargoCapacity: '載貨量',
|
||||
fuelConsumption: '燃料消耗',
|
||||
buildCost: '建造成本',
|
||||
buildTime: '建造時間',
|
||||
perUnit: '每個單位',
|
||||
batchCalculator: '批量建造計算器',
|
||||
quantity: '數量',
|
||||
totalCost: '總成本',
|
||||
totalTime: '總時間'
|
||||
},
|
||||
shipyardView: {
|
||||
title: '船塢',
|
||||
attack: '攻擊力',
|
||||
shield: '護盾',
|
||||
speed: '速度',
|
||||
cargoCapacity: '載貨量',
|
||||
unitCost: '單位成本',
|
||||
buildQuantity: '建造數量',
|
||||
totalCost: '總成本',
|
||||
build: '建造',
|
||||
inputError: '輸入錯誤',
|
||||
inputErrorMessage: '請輸入建造數量!',
|
||||
buildFailed: '建造失敗',
|
||||
buildFailedMessage: '請檢查資源是否足夠或前置條件是否滿足。'
|
||||
},
|
||||
defense: {
|
||||
attack: '攻擊力',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
buildCost: '建造成本',
|
||||
buildTime: '建造時間',
|
||||
perUnit: '每個單位',
|
||||
batchCalculator: '批量建造計算器',
|
||||
quantity: '數量',
|
||||
totalCost: '總成本',
|
||||
totalTime: '總時間'
|
||||
},
|
||||
defenseView: {
|
||||
title: '防禦設施',
|
||||
attack: '攻擊力',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
buildTime: '建造時間',
|
||||
seconds: '秒',
|
||||
unitCost: '單位成本',
|
||||
buildQuantity: '建造數量',
|
||||
totalCost: '總成本',
|
||||
build: '建造',
|
||||
shieldDomeBuilt: '護盾罩已建造',
|
||||
inputError: '輸入錯誤',
|
||||
inputErrorMessage: '請輸入建造數量!',
|
||||
buildFailed: '建造失敗',
|
||||
buildFailedMessage: '請檢查資源是否足夠或前置條件是否滿足。護盾罩只能建造一個。'
|
||||
},
|
||||
fleetView: {
|
||||
title: '艦隊管理',
|
||||
fleetOverview: '艦隊總覽',
|
||||
sendFleet: '派遣艦隊',
|
||||
flightMissions: '飛行任務',
|
||||
currentPlanetFleet: '當前星球艦隊',
|
||||
attack: '攻擊',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
speed: '速度',
|
||||
cargo: '載貨',
|
||||
selectFleet: '選擇艦隊',
|
||||
selectFleetDescription: '選擇要派遣的艦船數量',
|
||||
available: '可用',
|
||||
all: '全部',
|
||||
targetCoordinates: '目標座標',
|
||||
galaxy: '銀河系',
|
||||
system: '星系',
|
||||
position: '位置',
|
||||
missionType: '任務類型',
|
||||
missionInfo: '任務資訊',
|
||||
fuelConsumption: '燃料消耗',
|
||||
flightTime: '飛行時間',
|
||||
attackMission: '攻擊',
|
||||
transport: '運輸',
|
||||
colonize: '殖民',
|
||||
spy: '偵察',
|
||||
deploy: '部署',
|
||||
transportResources: '運輸資源',
|
||||
totalCargoCapacity: '總載貨量',
|
||||
used: '已用',
|
||||
noFlightMissions: '暫無飛行任務',
|
||||
outbound: '前往',
|
||||
returning: '返回',
|
||||
fleetComposition: '艦隊組成',
|
||||
carryingResources: '攜帶資源',
|
||||
arrivalTime: '到達時間',
|
||||
returnTime: '返回時間',
|
||||
recallFleet: '召回艦隊',
|
||||
sendFailed: '派遣失敗',
|
||||
sendFailedMessage: '請檢查艦隊數量、燃料是否充足,或載貨量是否超出限制。',
|
||||
recallFailed: '召回失敗',
|
||||
recallFailedMessage: '該任務無法召回。',
|
||||
unknownPlanet: '未知星球',
|
||||
fleetMissionSlots: '艦隊任務槽位'
|
||||
},
|
||||
officersView: {
|
||||
title: '軍官',
|
||||
activated: '已啟用',
|
||||
inactive: '未啟用',
|
||||
activeStatus: '啟用狀態',
|
||||
expirationTime: '到期時間',
|
||||
remainingTime: '剩餘時間',
|
||||
recruitCost: '招募成本',
|
||||
days: '天',
|
||||
benefitsBonus: '效果加成',
|
||||
resourceProduction: '資源產量',
|
||||
darkMatterProduction: '暗物質產量',
|
||||
energyProduction: '電量產出',
|
||||
buildingSpeed: '建築速度',
|
||||
researchSpeed: '研究速度',
|
||||
fleetSpeed: '艦隊速度',
|
||||
fuelConsumption: '燃料消耗',
|
||||
defense: '防禦力',
|
||||
storageCapacity: '倉儲容量',
|
||||
buildQueue: '建築佇列',
|
||||
fleetSlots: '艦隊槽位',
|
||||
hire: '招募',
|
||||
renew: '續約',
|
||||
dismiss: '解雇',
|
||||
hireTitle: '招募軍官',
|
||||
hireMessage: '確定要招募 {name} 嗎?有效期為7天。',
|
||||
renewTitle: '續約軍官',
|
||||
renewMessage: '確定要為 {name} 續約7天嗎?',
|
||||
dismissTitle: '解雇軍官',
|
||||
dismissMessage: '確定要解雇 {name} 嗎?不會返還任何費用。',
|
||||
hireFailed: '招募失敗',
|
||||
renewFailed: '續約失敗',
|
||||
insufficientResources: '資源不足!'
|
||||
},
|
||||
galaxyView: {
|
||||
title: '星系',
|
||||
selectCoordinates: '選擇座標',
|
||||
galaxy: '銀河系',
|
||||
selectGalaxy: '選擇銀河系',
|
||||
system: '星系',
|
||||
selectSystem: '選擇星系',
|
||||
view: '查看',
|
||||
myPlanet: '我的星球',
|
||||
totalPositions: '共10個星球位置',
|
||||
mine: '我的',
|
||||
hostile: '敵對',
|
||||
emptySlot: '空位 - 可殖民',
|
||||
scout: '偵察',
|
||||
attack: '攻擊',
|
||||
colonize: '殖民',
|
||||
switch: '切換',
|
||||
scoutPlanetTitle: '偵察星球',
|
||||
attackPlanetTitle: '攻擊星球',
|
||||
colonizePlanetTitle: '殖民星球',
|
||||
scoutPlanetMessage: '確定要派遣間諜探測器偵察星球 [{coordinates}] 嗎?\n\n請前往艦隊頁面選擇艦船並派遣。',
|
||||
attackPlanetMessage: '確定要攻擊星球 [{coordinates}] 嗎?\n\n請前往艦隊頁面選擇艦船並派遣。',
|
||||
colonizePlanetMessage: '確定要殖民位置 [{coordinates}] 嗎?\n\n請前往艦隊頁面派遣殖民船。'
|
||||
},
|
||||
messagesView: {
|
||||
title: '訊息中心',
|
||||
battleReports: '戰鬥報告',
|
||||
spyReports: '間諜報告',
|
||||
noBattleReports: '暫無戰鬥報告',
|
||||
noSpyReports: '暫無間諜報告',
|
||||
battleReport: '戰鬥報告',
|
||||
spyReport: '間諜報告',
|
||||
victory: '勝利',
|
||||
defeat: '失敗',
|
||||
draw: '平局',
|
||||
attackerFleet: '攻擊方艦隊',
|
||||
defenderFleet: '防守方艦隊',
|
||||
defenderDefense: '防守方防禦',
|
||||
attackerLosses: '攻擊方損失',
|
||||
defenderLosses: '防守方損失',
|
||||
noLosses: '無損失',
|
||||
plunder: '掠奪資源',
|
||||
debrisField: '殘骸場',
|
||||
resources: '資源',
|
||||
fleet: '艦隊',
|
||||
defense: '防禦',
|
||||
buildings: '建築'
|
||||
},
|
||||
simulatorView: {
|
||||
title: '戰鬥模擬器',
|
||||
attacker: '攻擊方',
|
||||
defender: '防守方',
|
||||
attackerConfig: '攻擊方配置',
|
||||
attackerConfigDesc: '設置攻擊方的艦隊和科技等級',
|
||||
defenderConfig: '防守方配置',
|
||||
defenderConfigDesc: '設置防守方的艦隊、防禦和科技等級',
|
||||
fleet: '艦隊',
|
||||
defenseStructures: '防禦設施',
|
||||
techLevels: '科技等級',
|
||||
weapon: '武器',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
defenderResources: '防守方資源(用於掠奪計算)',
|
||||
startSimulation: '開始模擬',
|
||||
reset: '重置',
|
||||
battleResult: '戰鬥結果',
|
||||
attackerVictory: '攻擊方勝利',
|
||||
defenderVictory: '防守方勝利',
|
||||
draw: '平局',
|
||||
afterRounds: '經過 {rounds} 回合戰鬥',
|
||||
attackerLosses: '攻擊方損失',
|
||||
defenderLosses: '防守方損失',
|
||||
noLosses: '無損失',
|
||||
attackerRemaining: '攻擊方剩餘',
|
||||
defenderRemaining: '防守方剩餘',
|
||||
allDestroyed: '全部摧毀',
|
||||
plunderableResources: '可掠奪資源',
|
||||
debrisField: '殘骸場',
|
||||
moonChance: '月球生成機率',
|
||||
showRoundDetails: '顯示回合詳情',
|
||||
hideRoundDetails: '隱藏回合詳情',
|
||||
round: '第 {round} 回合',
|
||||
attackerRemainingPower: '攻擊方剩餘火力',
|
||||
defenderRemainingPower: '防守方剩餘火力'
|
||||
},
|
||||
settings: {
|
||||
dataManagement: '資料管理',
|
||||
dataManagementDesc: '匯出、匯入或清除遊戲資料',
|
||||
exportData: '匯出資料',
|
||||
exportDataDesc: '將遊戲進度匯出為JSON檔案',
|
||||
export: '匯出',
|
||||
exporting: '匯出中...',
|
||||
exportSuccess: '匯出成功',
|
||||
exportFailed: '匯出失敗,請重試',
|
||||
importData: '匯入資料',
|
||||
importDataDesc: '從JSON檔案恢復遊戲進度',
|
||||
selectFile: '匯入',
|
||||
importSuccess: '匯入成功',
|
||||
importConfirmTitle: '確認匯入資料',
|
||||
importConfirmMessage: '匯入資料將覆蓋目前遊戲進度,此操作不可撤銷。確定要繼續嗎?',
|
||||
importFailed: '匯入失敗,請檢查檔案格式',
|
||||
clearData: '清除資料',
|
||||
clearDataDesc: '刪除所有遊戲資料並重置遊戲',
|
||||
clear: '清除',
|
||||
clearConfirmTitle: '確認清除資料',
|
||||
clearConfirmMessage: '這將刪除所有遊戲資料並重新開始,此操作不可撤銷。確定要繼續嗎?',
|
||||
gameSettings: '遊戲設定',
|
||||
gameSettingsDesc: '調整遊戲參數和偏好設定',
|
||||
playerName: '玩家名稱',
|
||||
gameSpeed: '遊戲速度',
|
||||
gameSpeedDesc: '目前遊戲速度倍率',
|
||||
about: '關於',
|
||||
version: '版本',
|
||||
buildDate: '建置日期',
|
||||
community: '社群',
|
||||
github: 'GitHub 儲存庫',
|
||||
qqGroup: 'QQ 交流群'
|
||||
}
|
||||
}
|
||||
353
src/logic/battleLogic.ts
Normal file
353
src/logic/battleLogic.ts
Normal file
@@ -0,0 +1,353 @@
|
||||
import type { Fleet, Resources, BattleResult, Officer } from '@/types/game'
|
||||
import { DefenseType, ShipType, OfficerType } from '@/types/game'
|
||||
import { SHIPS, DEFENSES } from '@/config/gameConfig'
|
||||
import * as officerLogic from './officerLogic'
|
||||
|
||||
/**
|
||||
* 战斗单位(舰船或防御)
|
||||
*/
|
||||
interface BattleUnit {
|
||||
type: ShipType | DefenseType
|
||||
count: number
|
||||
attack: number
|
||||
shield: number
|
||||
armor: number
|
||||
isShip: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 战斗方
|
||||
*/
|
||||
interface BattleSide {
|
||||
fleet: BattleUnit[]
|
||||
defense: BattleUnit[]
|
||||
totalShields: number
|
||||
totalArmor: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备战斗方数据
|
||||
*/
|
||||
const prepareBattleSide = (fleet: Partial<Fleet>, defense: Partial<Record<DefenseType, number>>, defenseBonus: number = 0): BattleSide => {
|
||||
const side: BattleSide = {
|
||||
fleet: [],
|
||||
defense: [],
|
||||
totalShields: 0,
|
||||
totalArmor: 0
|
||||
}
|
||||
|
||||
// 添加舰船
|
||||
Object.entries(fleet).forEach(([shipType, count]) => {
|
||||
if (count > 0) {
|
||||
const config = SHIPS[shipType as ShipType]
|
||||
const unit: BattleUnit = {
|
||||
type: shipType as ShipType,
|
||||
count,
|
||||
attack: config.attack,
|
||||
shield: config.shield * (1 + defenseBonus / 100),
|
||||
armor: config.armor * (1 + defenseBonus / 100),
|
||||
isShip: true
|
||||
}
|
||||
side.fleet.push(unit)
|
||||
side.totalShields += unit.shield * count
|
||||
side.totalArmor += unit.armor * count
|
||||
}
|
||||
})
|
||||
|
||||
// 添加防御
|
||||
Object.entries(defense).forEach(([defenseType, count]) => {
|
||||
if (count > 0) {
|
||||
const config = DEFENSES[defenseType as DefenseType]
|
||||
const unit: BattleUnit = {
|
||||
type: defenseType as DefenseType,
|
||||
count,
|
||||
attack: config.attack,
|
||||
shield: config.shield * (1 + defenseBonus / 100),
|
||||
armor: config.armor * (1 + defenseBonus / 100),
|
||||
isShip: false
|
||||
}
|
||||
side.defense.push(unit)
|
||||
side.totalShields += unit.shield * count
|
||||
side.totalArmor += unit.armor * count
|
||||
}
|
||||
})
|
||||
|
||||
return side
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算一方的总攻击力
|
||||
*/
|
||||
const calculateTotalAttack = (side: BattleSide): number => {
|
||||
let total = 0
|
||||
side.fleet.forEach(unit => {
|
||||
total += unit.attack * unit.count
|
||||
})
|
||||
side.defense.forEach(unit => {
|
||||
total += unit.attack * unit.count
|
||||
})
|
||||
return total
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一轮战斗
|
||||
*/
|
||||
const executeBattleRound = (attacker: BattleSide, defender: BattleSide): void => {
|
||||
// 攻击方对防御方造成伤害
|
||||
const attackerDamage = calculateTotalAttack(attacker)
|
||||
applyDamage(defender, attackerDamage)
|
||||
|
||||
// 防御方对攻击方造成伤害
|
||||
const defenderDamage = calculateTotalAttack(defender)
|
||||
applyDamage(attacker, defenderDamage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 对一方施加伤害
|
||||
*/
|
||||
const applyDamage = (side: BattleSide, totalDamage: number): void => {
|
||||
let remainingDamage = totalDamage
|
||||
|
||||
// 先消耗护盾
|
||||
const totalShields = side.totalShields
|
||||
if (totalShields > 0) {
|
||||
const shieldAbsorption = Math.min(remainingDamage, totalShields)
|
||||
remainingDamage -= shieldAbsorption
|
||||
side.totalShields -= shieldAbsorption
|
||||
}
|
||||
|
||||
// 剩余伤害穿透护盾,破坏单位
|
||||
if (remainingDamage > 0) {
|
||||
destroyUnits(side, remainingDamage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据伤害摧毁单位
|
||||
*/
|
||||
const destroyUnits = (side: BattleSide, damage: number): void => {
|
||||
let remainingDamage = damage
|
||||
|
||||
// 随机选择单位摧毁
|
||||
const allUnits = [...side.fleet, ...side.defense]
|
||||
|
||||
while (remainingDamage > 0 && allUnits.some(u => u.count > 0)) {
|
||||
// 随机选择一个有数量的单位
|
||||
const availableUnits = allUnits.filter(u => u.count > 0)
|
||||
if (availableUnits.length === 0) break
|
||||
|
||||
const targetUnit = availableUnits[Math.floor(Math.random() * availableUnits.length)]
|
||||
if (!targetUnit) break // 安全检查
|
||||
|
||||
// 计算破坏概率(伤害 / 装甲)
|
||||
const destructionChance = Math.min(remainingDamage / targetUnit.armor, 1)
|
||||
|
||||
if (Math.random() < destructionChance) {
|
||||
targetUnit.count--
|
||||
side.totalArmor -= targetUnit.armor
|
||||
remainingDamage -= targetUnit.armor
|
||||
} else {
|
||||
// 未破坏,但消耗一部分伤害
|
||||
remainingDamage -= targetUnit.armor * destructionChance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查战斗是否结束
|
||||
*/
|
||||
const isBattleOver = (attacker: BattleSide, defender: BattleSide): boolean => {
|
||||
const attackerHasUnits = attacker.fleet.some(u => u.count > 0) || attacker.defense.some(u => u.count > 0)
|
||||
const defenderHasUnits = defender.fleet.some(u => u.count > 0) || defender.defense.some(u => u.count > 0)
|
||||
|
||||
return !attackerHasUnits || !defenderHasUnits
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算损失
|
||||
*/
|
||||
const calculateLosses = (
|
||||
initialSide: BattleSide,
|
||||
finalSide: BattleSide
|
||||
): { fleet: Partial<Fleet>; defense: Partial<Record<DefenseType, number>> } => {
|
||||
const losses: { fleet: Partial<Fleet>; defense: Partial<Record<DefenseType, number>> } = {
|
||||
fleet: {},
|
||||
defense: {}
|
||||
}
|
||||
|
||||
// 计算舰船损失
|
||||
initialSide.fleet.forEach((initialUnit, index) => {
|
||||
const finalUnit = finalSide.fleet[index]
|
||||
const lost = initialUnit.count - (finalUnit?.count || 0)
|
||||
if (lost > 0) {
|
||||
losses.fleet[initialUnit.type as ShipType] = lost
|
||||
}
|
||||
})
|
||||
|
||||
// 计算防御损失
|
||||
initialSide.defense.forEach((initialUnit, index) => {
|
||||
const finalUnit = finalSide.defense[index]
|
||||
const lost = initialUnit.count - (finalUnit?.count || 0)
|
||||
if (lost > 0) {
|
||||
losses.defense[initialUnit.type as DefenseType] = lost
|
||||
}
|
||||
})
|
||||
|
||||
return losses
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算残骸场
|
||||
*/
|
||||
const calculateDebrisField = (
|
||||
attackerLosses: Partial<Fleet>,
|
||||
defenderLosses: { fleet: Partial<Fleet>; defense: Partial<Record<DefenseType, number>> }
|
||||
): Resources => {
|
||||
const debris: Resources = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
const debrisRate = 0.3 // 30%的残骸回收率
|
||||
|
||||
// 攻击方舰船损失
|
||||
Object.entries(attackerLosses).forEach(([shipType, count]) => {
|
||||
const config = SHIPS[shipType as ShipType]
|
||||
debris.metal += config.cost.metal * count * debrisRate
|
||||
debris.crystal += config.cost.crystal * count * debrisRate
|
||||
})
|
||||
|
||||
// 防御方舰船损失
|
||||
Object.entries(defenderLosses.fleet).forEach(([shipType, count]) => {
|
||||
const config = SHIPS[shipType as ShipType]
|
||||
debris.metal += config.cost.metal * count * debrisRate
|
||||
debris.crystal += config.cost.crystal * count * debrisRate
|
||||
})
|
||||
|
||||
// 防御设施不产生残骸场(或产生较少)
|
||||
|
||||
return debris
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算掠夺资源
|
||||
*/
|
||||
const calculatePlunder = (availableResources: Resources, attackerFleet: Partial<Fleet>, cargoCapacity: number): Resources => {
|
||||
// 计算攻击方剩余运载能力
|
||||
let totalCapacity = 0
|
||||
Object.entries(attackerFleet).forEach(([shipType, count]) => {
|
||||
const config = SHIPS[shipType as ShipType]
|
||||
totalCapacity += config.cargoCapacity * count
|
||||
})
|
||||
|
||||
// 最多掠夺50%的资源
|
||||
const maxPlunder = Math.min(totalCapacity, cargoCapacity)
|
||||
const plunderRate = 0.5
|
||||
|
||||
const plunder: Resources = {
|
||||
metal: Math.min(availableResources.metal * plunderRate, maxPlunder * 0.5),
|
||||
crystal: Math.min(availableResources.crystal * plunderRate, maxPlunder * 0.3),
|
||||
deuterium: Math.min(availableResources.deuterium * plunderRate, maxPlunder * 0.2),
|
||||
darkMatter: 0, // 暗物质无法掠夺
|
||||
energy: 0
|
||||
}
|
||||
|
||||
return plunder
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行战斗模拟
|
||||
*/
|
||||
export const simulateBattle = (
|
||||
attackerFleet: Partial<Fleet>,
|
||||
defenderFleet: Partial<Fleet>,
|
||||
defenderDefense: Partial<Record<DefenseType, number>>,
|
||||
defenderResources: Resources,
|
||||
attackerOfficers: Record<OfficerType, Officer>,
|
||||
defenderOfficers: Record<OfficerType, Officer>
|
||||
): BattleResult => {
|
||||
// 计算军官加成
|
||||
const attackerBonuses = officerLogic.calculateActiveBonuses(attackerOfficers, Date.now())
|
||||
const defenderBonuses = officerLogic.calculateActiveBonuses(defenderOfficers, Date.now())
|
||||
|
||||
// 准备战斗方
|
||||
const initialAttacker = prepareBattleSide(attackerFleet, {}, attackerBonuses.defenseBonus)
|
||||
const initialDefender = prepareBattleSide(defenderFleet, defenderDefense, defenderBonuses.defenseBonus)
|
||||
|
||||
// 复制战斗方用于战斗
|
||||
const attacker = JSON.parse(JSON.stringify(initialAttacker)) as BattleSide
|
||||
const defender = JSON.parse(JSON.stringify(initialDefender)) as BattleSide
|
||||
|
||||
// 战斗回合(最多6回合)
|
||||
let rounds = 0
|
||||
const maxRounds = 6
|
||||
|
||||
while (rounds < maxRounds && !isBattleOver(attacker, defender)) {
|
||||
executeBattleRound(attacker, defender)
|
||||
rounds++
|
||||
}
|
||||
|
||||
// 计算损失
|
||||
const attackerLosses = calculateLosses(initialAttacker, attacker).fleet
|
||||
const defenderLosses = calculateLosses(initialDefender, defender)
|
||||
|
||||
// 判断胜负
|
||||
let winner: 'attacker' | 'defender' | 'draw' = 'draw'
|
||||
const attackerSurvived = attacker.fleet.some(u => u.count > 0)
|
||||
const defenderSurvived = defender.fleet.some(u => u.count > 0) || defender.defense.some(u => u.count > 0)
|
||||
|
||||
if (attackerSurvived && !defenderSurvived) {
|
||||
winner = 'attacker'
|
||||
} else if (!attackerSurvived && defenderSurvived) {
|
||||
winner = 'defender'
|
||||
}
|
||||
|
||||
// 计算残骸场
|
||||
const debrisField = calculateDebrisField(attackerLosses, defenderLosses)
|
||||
|
||||
// 计算掠夺(仅攻击方胜利时)
|
||||
const plunder =
|
||||
winner === 'attacker'
|
||||
? calculatePlunder(defenderResources, attackerFleet, 10000)
|
||||
: { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
|
||||
// 生成战斗报告
|
||||
const battleResult: BattleResult = {
|
||||
id: `battle_${Date.now()}`,
|
||||
timestamp: Date.now(),
|
||||
attackerId: '',
|
||||
defenderId: '',
|
||||
attackerPlanetId: '',
|
||||
defenderPlanetId: '',
|
||||
attackerFleet,
|
||||
defenderFleet,
|
||||
defenderDefense,
|
||||
attackerLosses,
|
||||
defenderLosses,
|
||||
winner,
|
||||
plunder,
|
||||
debrisField
|
||||
}
|
||||
|
||||
return battleResult
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算防御设施修复(防御有70%概率修复)
|
||||
*/
|
||||
export const repairDefense = (
|
||||
defenseBeforeBattle: Partial<Record<DefenseType, number>>,
|
||||
defenseAfterBattle: Partial<Record<DefenseType, number>>
|
||||
): Partial<Record<DefenseType, number>> => {
|
||||
const repaired: Partial<Record<DefenseType, number>> = { ...defenseAfterBattle }
|
||||
|
||||
Object.keys(defenseBeforeBattle).forEach(defenseType => {
|
||||
const before = defenseBeforeBattle[defenseType as DefenseType] || 0
|
||||
const after = defenseAfterBattle[defenseType as DefenseType] || 0
|
||||
const lost = before - after
|
||||
|
||||
if (lost > 0) {
|
||||
// 70%修复概率
|
||||
const repairedCount = Math.floor(lost * 0.7)
|
||||
repaired[defenseType as DefenseType] = after + repairedCount
|
||||
}
|
||||
})
|
||||
|
||||
return repaired
|
||||
}
|
||||
194
src/logic/buildingLogic.ts
Normal file
194
src/logic/buildingLogic.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import type { Planet, Resources, BuildQueueItem } from '@/types/game'
|
||||
import { BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/game'
|
||||
import { BUILDINGS } from '@/config/gameConfig'
|
||||
import * as pointsLogic from './pointsLogic'
|
||||
|
||||
/**
|
||||
* 计算建筑升级成本
|
||||
*/
|
||||
export const calculateBuildingCost = (buildingType: BuildingType, targetLevel: number): Resources => {
|
||||
const config = BUILDINGS[buildingType]
|
||||
const multiplier = Math.pow(config.costMultiplier, targetLevel - 1)
|
||||
return {
|
||||
metal: Math.floor(config.baseCost.metal * multiplier),
|
||||
crystal: Math.floor(config.baseCost.crystal * multiplier),
|
||||
deuterium: Math.floor(config.baseCost.deuterium * multiplier),
|
||||
darkMatter: Math.floor(config.baseCost.darkMatter * multiplier),
|
||||
energy: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算建筑升级时间
|
||||
*/
|
||||
export const calculateBuildingTime = (buildingType: BuildingType, targetLevel: number, buildingSpeedBonus: number = 0): number => {
|
||||
const config = BUILDINGS[buildingType]
|
||||
const multiplier = Math.pow(config.costMultiplier, targetLevel - 1)
|
||||
const baseTime = config.baseTime * multiplier
|
||||
const speedMultiplier = 1 - buildingSpeedBonus / 100
|
||||
return Math.floor(baseTime * speedMultiplier)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算已用空间
|
||||
*/
|
||||
export const calculateUsedSpace = (planet: Planet): number => {
|
||||
let usedSpace = 0
|
||||
Object.entries(planet.buildings).forEach(([buildingType, level]) => {
|
||||
if (level > 0) {
|
||||
const config = BUILDINGS[buildingType as BuildingType]
|
||||
usedSpace += config.spaceUsage * level
|
||||
}
|
||||
})
|
||||
return usedSpace
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查建筑升级条件
|
||||
*/
|
||||
export const checkBuildingRequirements = (
|
||||
buildingType: BuildingType,
|
||||
planet: Planet,
|
||||
technologies: Partial<Record<TechnologyType, number>>
|
||||
): boolean => {
|
||||
const config = BUILDINGS[buildingType]
|
||||
const requirements = (config as any).requirements
|
||||
if (!requirements) return true
|
||||
|
||||
for (const [key, level] of Object.entries(requirements)) {
|
||||
const requiredLevel = level as number
|
||||
if (Object.values(BuildingType).includes(key as BuildingType)) {
|
||||
if ((planet.buildings[key as BuildingType] || 0) < requiredLevel) {
|
||||
return false
|
||||
}
|
||||
} else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
|
||||
if ((technologies[key as TechnologyType] || 0) < requiredLevel) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有足够空间建造
|
||||
*/
|
||||
export const checkSpaceAvailable = (planet: Planet, buildingType: BuildingType): boolean => {
|
||||
const usedSpace = calculateUsedSpace(planet)
|
||||
const buildingConfig = BUILDINGS[buildingType]
|
||||
const requiredSpace = buildingConfig.spaceUsage
|
||||
return usedSpace + requiredSpace <= planet.maxSpace
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建建造队列项
|
||||
*/
|
||||
export const createBuildQueueItem = (buildingType: BuildingType, targetLevel: number, buildTime: number): BuildQueueItem => {
|
||||
const now = Date.now()
|
||||
return {
|
||||
id: `build_${now}`,
|
||||
type: 'building',
|
||||
itemType: buildingType,
|
||||
targetLevel,
|
||||
startTime: now,
|
||||
endTime: now + buildTime * 1000
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理建造完成
|
||||
*/
|
||||
export const completeBuildQueue = (planet: Planet, now: number, onPointsEarned?: (points: number, type: 'building' | 'ship' | 'defense', itemType: string, level?: number, quantity?: number) => void): void => {
|
||||
planet.buildQueue = planet.buildQueue.filter(item => {
|
||||
if (now >= item.endTime) {
|
||||
// 建造完成
|
||||
if (item.type === 'building') {
|
||||
const oldLevel = planet.buildings[item.itemType as BuildingType] || 0
|
||||
const newLevel = item.targetLevel || 0
|
||||
planet.buildings[item.itemType as BuildingType] = newLevel
|
||||
|
||||
// 计算并累积积分
|
||||
if (onPointsEarned && newLevel > oldLevel) {
|
||||
const points = pointsLogic.calculateBuildingPoints(item.itemType as BuildingType, oldLevel, newLevel)
|
||||
onPointsEarned(points, 'building', item.itemType, newLevel)
|
||||
}
|
||||
} else if (item.type === 'ship') {
|
||||
const shipType = item.itemType as ShipType
|
||||
const quantity = item.quantity || 0
|
||||
planet.fleet[shipType] = (planet.fleet[shipType] || 0) + quantity
|
||||
|
||||
// 计算并累积积分
|
||||
if (onPointsEarned && quantity > 0) {
|
||||
const points = pointsLogic.calculateShipPoints(shipType, quantity)
|
||||
onPointsEarned(points, 'ship', item.itemType, undefined, quantity)
|
||||
}
|
||||
} else if (item.type === 'defense') {
|
||||
const defenseType = item.itemType as DefenseType
|
||||
const quantity = item.quantity || 0
|
||||
planet.defense[defenseType] = (planet.defense[defenseType] || 0) + quantity
|
||||
|
||||
// 计算并累积积分
|
||||
if (onPointsEarned && quantity > 0) {
|
||||
const points = pointsLogic.calculateDefensePoints(defenseType, quantity)
|
||||
onPointsEarned(points, 'defense', item.itemType, undefined, quantity)
|
||||
}
|
||||
} else if (item.type === 'demolish') {
|
||||
// 拆除完成,降低建筑等级
|
||||
// 注意:拆除不会扣除积分,积分只增不减
|
||||
const buildingType = item.itemType as BuildingType
|
||||
const currentLevel = planet.buildings[buildingType] || 0
|
||||
planet.buildings[buildingType] = Math.max(0, currentLevel - 1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算拆除返还资源
|
||||
* @param buildingType 建筑类型
|
||||
* @param currentLevel 当前等级
|
||||
* @returns 返还50%的当前等级建造成本
|
||||
*/
|
||||
export const calculateDemolishRefund = (buildingType: BuildingType, currentLevel: number): Resources => {
|
||||
const cost = calculateBuildingCost(buildingType, currentLevel)
|
||||
return {
|
||||
metal: Math.floor(cost.metal * 0.5),
|
||||
crystal: Math.floor(cost.crystal * 0.5),
|
||||
deuterium: Math.floor(cost.deuterium * 0.5),
|
||||
darkMatter: Math.floor(cost.darkMatter * 0.5),
|
||||
energy: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算拆除时间
|
||||
* @param buildingType 建筑类型
|
||||
* @param currentLevel 当前等级
|
||||
* @param buildingSpeedBonus 建筑速度加成
|
||||
* @returns 拆除时间(建造时间的50%)
|
||||
*/
|
||||
export const calculateDemolishTime = (buildingType: BuildingType, currentLevel: number, buildingSpeedBonus: number = 0): number => {
|
||||
const buildTime = calculateBuildingTime(buildingType, currentLevel, buildingSpeedBonus)
|
||||
return Math.floor(buildTime * 0.5)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建拆除队列项
|
||||
* @param buildingType 建筑类型
|
||||
* @param currentLevel 当前等级
|
||||
* @param demolishTime 拆除时间
|
||||
* @returns 拆除队列项
|
||||
*/
|
||||
export const createDemolishQueueItem = (buildingType: BuildingType, currentLevel: number, demolishTime: number): BuildQueueItem => {
|
||||
const now = Date.now()
|
||||
return {
|
||||
id: `demolish_${now}`,
|
||||
type: 'demolish',
|
||||
itemType: buildingType,
|
||||
targetLevel: currentLevel - 1, // 目标等级为当前等级-1
|
||||
startTime: now,
|
||||
endTime: now + demolishTime * 1000
|
||||
}
|
||||
}
|
||||
161
src/logic/buildingValidation.ts
Normal file
161
src/logic/buildingValidation.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import type { Planet, Resources, BuildQueueItem, Officer } from '@/types/game'
|
||||
import { BuildingType, TechnologyType, OfficerType } from '@/types/game'
|
||||
import * as buildingLogic from './buildingLogic'
|
||||
import * as resourceLogic from './resourceLogic'
|
||||
import * as publicLogic from './publicLogic'
|
||||
import * as officerLogic from './officerLogic'
|
||||
import { BUILDINGS } from '@/config/gameConfig'
|
||||
|
||||
/**
|
||||
* 验证建筑升级的所有条件
|
||||
*/
|
||||
export const validateBuildingUpgrade = (
|
||||
planet: Planet,
|
||||
buildingType: BuildingType,
|
||||
technologies: Partial<Record<TechnologyType, number>>,
|
||||
officers: Record<OfficerType, Officer>
|
||||
): {
|
||||
valid: boolean
|
||||
reason?: string
|
||||
} => {
|
||||
const currentLevel = planet.buildings[buildingType] || 0
|
||||
const targetLevel = currentLevel + 1
|
||||
const cost = buildingLogic.calculateBuildingCost(buildingType, targetLevel)
|
||||
const buildingConfig = BUILDINGS[buildingType]
|
||||
|
||||
// 检查星球/月球限制
|
||||
if (buildingConfig.planetOnly && planet.isMoon) {
|
||||
return { valid: false, reason: 'errors.planetOnly' }
|
||||
}
|
||||
if (buildingConfig.moonOnly && !planet.isMoon) {
|
||||
return { valid: false, reason: 'errors.moonOnly' }
|
||||
}
|
||||
|
||||
// 计算军官加成
|
||||
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
|
||||
|
||||
// 检查建造队列是否已满
|
||||
const maxQueue = publicLogic.getMaxBuildQueue(planet, bonuses.additionalBuildQueue)
|
||||
if (planet.buildQueue.length >= maxQueue) {
|
||||
return { valid: false, reason: 'errors.buildQueueFull' }
|
||||
}
|
||||
|
||||
// 检查空间
|
||||
if (!buildingLogic.checkSpaceAvailable(planet, buildingType)) {
|
||||
return { valid: false, reason: 'errors.insufficientSpace' }
|
||||
}
|
||||
|
||||
// 检查资源
|
||||
if (!resourceLogic.checkResourcesAvailable(planet.resources, cost)) {
|
||||
return { valid: false, reason: 'errors.insufficientResources' }
|
||||
}
|
||||
|
||||
// 检查前置条件
|
||||
if (!buildingLogic.checkBuildingRequirements(buildingType, planet, technologies)) {
|
||||
return { valid: false, reason: 'errors.requirementsNotMet' }
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行建筑升级(扣除资源,添加到队列)
|
||||
*/
|
||||
export const executeBuildingUpgrade = (planet: Planet, buildingType: BuildingType, officers: Record<OfficerType, Officer>): BuildQueueItem => {
|
||||
const currentLevel = planet.buildings[buildingType] || 0
|
||||
const targetLevel = currentLevel + 1
|
||||
const cost = buildingLogic.calculateBuildingCost(buildingType, targetLevel)
|
||||
|
||||
// 计算军官加成
|
||||
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
|
||||
const time = buildingLogic.calculateBuildingTime(buildingType, targetLevel, bonuses.buildingSpeedBonus)
|
||||
|
||||
// 扣除资源
|
||||
resourceLogic.deductResources(planet.resources, cost)
|
||||
|
||||
// 创建队列项
|
||||
return buildingLogic.createBuildQueueItem(buildingType, targetLevel, time)
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消建造并计算返还资源
|
||||
*/
|
||||
export const cancelBuildingUpgrade = (_planet: Planet, queueItem: BuildQueueItem): Resources => {
|
||||
const cost = buildingLogic.calculateBuildingCost(queueItem.itemType as BuildingType, queueItem.targetLevel || 1)
|
||||
|
||||
return {
|
||||
metal: Math.floor(cost.metal * 0.5),
|
||||
crystal: Math.floor(cost.crystal * 0.5),
|
||||
deuterium: Math.floor(cost.deuterium * 0.5),
|
||||
darkMatter: Math.floor(cost.darkMatter * 0.5),
|
||||
energy: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找队列项
|
||||
*/
|
||||
export const findQueueItem = (
|
||||
queue: BuildQueueItem[],
|
||||
queueId: string
|
||||
): {
|
||||
item: BuildQueueItem | null
|
||||
index: number
|
||||
} => {
|
||||
const index = queue.findIndex(q => q.id === queueId)
|
||||
if (index === -1) {
|
||||
return { item: null, index: -1 }
|
||||
}
|
||||
return { item: queue[index] || null, index }
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证建筑拆除的所有条件
|
||||
*/
|
||||
export const validateBuildingDemolish = (
|
||||
planet: Planet,
|
||||
buildingType: BuildingType,
|
||||
officers: Record<OfficerType, Officer>
|
||||
): {
|
||||
valid: boolean
|
||||
reason?: string
|
||||
} => {
|
||||
const currentLevel = planet.buildings[buildingType] || 0
|
||||
|
||||
// 检查建筑等级
|
||||
if (currentLevel <= 0) {
|
||||
return { valid: false, reason: 'errors.buildingLevelZero' }
|
||||
}
|
||||
|
||||
// 计算军官加成
|
||||
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
|
||||
|
||||
// 检查建造队列是否已满
|
||||
const maxQueue = publicLogic.getMaxBuildQueue(planet, bonuses.additionalBuildQueue)
|
||||
if (planet.buildQueue.length >= maxQueue) {
|
||||
return { valid: false, reason: 'errors.buildQueueFull' }
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行建筑拆除(返还资源,添加到队列)
|
||||
*/
|
||||
export const executeBuildingDemolish = (planet: Planet, buildingType: BuildingType, officers: Record<OfficerType, Officer>): BuildQueueItem => {
|
||||
const currentLevel = planet.buildings[buildingType] || 0
|
||||
|
||||
// 计算军官加成
|
||||
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
|
||||
const demolishTime = buildingLogic.calculateDemolishTime(buildingType, currentLevel, bonuses.buildingSpeedBonus)
|
||||
|
||||
// 返还50%资源
|
||||
const refund = buildingLogic.calculateDemolishRefund(buildingType, currentLevel)
|
||||
planet.resources.metal += refund.metal
|
||||
planet.resources.crystal += refund.crystal
|
||||
planet.resources.deuterium += refund.deuterium
|
||||
planet.resources.darkMatter += refund.darkMatter
|
||||
|
||||
// 创建拆除队列项
|
||||
return buildingLogic.createDemolishQueueItem(buildingType, currentLevel, demolishTime)
|
||||
}
|
||||
374
src/logic/fleetLogic.ts
Normal file
374
src/logic/fleetLogic.ts
Normal file
@@ -0,0 +1,374 @@
|
||||
import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, Player, Officer } from '@/types/game'
|
||||
import { ShipType, DefenseType, MissionType, BuildingType, OfficerType } from '@/types/game'
|
||||
import * as battleLogic from './battleLogic'
|
||||
import * as moonLogic from './moonLogic'
|
||||
import * as moonValidation from './moonValidation'
|
||||
|
||||
/**
|
||||
* 计算两个星球之间的距离
|
||||
*/
|
||||
export const calculateDistance = (
|
||||
from: { galaxy: number; system: number; position: number },
|
||||
to: { galaxy: number; system: number; position: number }
|
||||
): number => {
|
||||
return Math.sqrt(Math.pow(to.galaxy - from.galaxy, 2) + Math.pow(to.system - from.system, 2) + Math.pow(to.position - from.position, 2))
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算飞行时间
|
||||
*/
|
||||
export const calculateFlightTime = (distance: number, minSpeed: number): number => {
|
||||
return Math.max(10, Math.floor((distance * 10000) / minSpeed)) // 至少10秒
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建舰队任务
|
||||
*/
|
||||
export const createFleetMission = (
|
||||
playerId: string,
|
||||
originPlanetId: string,
|
||||
targetPosition: { galaxy: number; system: number; position: number },
|
||||
missionType: MissionType,
|
||||
fleet: Partial<Fleet>,
|
||||
cargo: Resources,
|
||||
flightTime: number
|
||||
): FleetMission => {
|
||||
const now = Date.now()
|
||||
return {
|
||||
id: `mission_${now}`,
|
||||
playerId,
|
||||
originPlanetId,
|
||||
targetPosition,
|
||||
missionType,
|
||||
fleet,
|
||||
cargo,
|
||||
departureTime: now,
|
||||
arrivalTime: now + flightTime * 1000,
|
||||
returnTime: now + flightTime * 2 * 1000,
|
||||
status: 'outbound'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运输任务到达
|
||||
*/
|
||||
export const processTransportArrival = (mission: FleetMission, targetPlanet: Planet | undefined): void => {
|
||||
if (targetPlanet) {
|
||||
targetPlanet.resources.metal += mission.cargo.metal
|
||||
targetPlanet.resources.crystal += mission.cargo.crystal
|
||||
targetPlanet.resources.deuterium += mission.cargo.deuterium
|
||||
targetPlanet.resources.darkMatter += mission.cargo.darkMatter
|
||||
}
|
||||
mission.status = 'returning'
|
||||
mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理攻击任务到达
|
||||
*/
|
||||
export const processAttackArrival = (
|
||||
mission: FleetMission,
|
||||
targetPlanet: Planet | undefined,
|
||||
attacker: Player,
|
||||
defender: Player | null,
|
||||
allPlanets: Planet[]
|
||||
): { battleResult: BattleResult; moon: Planet | null } | null => {
|
||||
if (!targetPlanet || targetPlanet.ownerId === attacker.id) {
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
}
|
||||
|
||||
// 执行战斗
|
||||
const battleResult = battleLogic.simulateBattle(
|
||||
mission.fleet,
|
||||
targetPlanet.fleet,
|
||||
targetPlanet.defense,
|
||||
targetPlanet.resources,
|
||||
attacker.officers,
|
||||
defender?.officers || ({} as Record<OfficerType, Officer>)
|
||||
)
|
||||
|
||||
// 更新战斗报告ID
|
||||
battleResult.id = `battle_${Date.now()}`
|
||||
battleResult.attackerId = attacker.id
|
||||
battleResult.defenderId = targetPlanet.ownerId || 'unknown'
|
||||
battleResult.attackerPlanetId = mission.originPlanetId
|
||||
battleResult.defenderPlanetId = targetPlanet.id
|
||||
|
||||
// 如果攻击方获胜,掠夺资源已经在战斗模拟中计算
|
||||
mission.cargo = battleResult.plunder
|
||||
|
||||
// 更新舰队 - 计算幸存舰船
|
||||
const survivingFleet: Partial<Fleet> = {}
|
||||
Object.entries(mission.fleet).forEach(([shipType, initialCount]) => {
|
||||
const lost = battleResult.attackerLosses[shipType as ShipType] || 0
|
||||
const surviving = initialCount - lost
|
||||
if (surviving > 0) {
|
||||
survivingFleet[shipType as ShipType] = surviving
|
||||
}
|
||||
})
|
||||
mission.fleet = survivingFleet
|
||||
|
||||
// 更新目标星球舰队和防御
|
||||
Object.entries(battleResult.defenderLosses.fleet).forEach(([shipType, lost]) => {
|
||||
targetPlanet.fleet[shipType as ShipType] = Math.max(0, targetPlanet.fleet[shipType as ShipType] - lost)
|
||||
})
|
||||
|
||||
Object.entries(battleResult.defenderLosses.defense).forEach(([defenseType, lost]) => {
|
||||
targetPlanet.defense[defenseType as DefenseType] = Math.max(0, targetPlanet.defense[defenseType as DefenseType] - lost)
|
||||
})
|
||||
|
||||
// 防御设施修复(70%概率)
|
||||
const defenseBeforeBattle: Partial<Record<DefenseType, number>> = { ...targetPlanet.defense }
|
||||
Object.entries(battleResult.defenderLosses.defense).forEach(([defenseType, lost]) => {
|
||||
defenseBeforeBattle[defenseType as DefenseType] = (defenseBeforeBattle[defenseType as DefenseType] || 0) + lost
|
||||
})
|
||||
targetPlanet.defense = battleLogic.repairDefense(defenseBeforeBattle, targetPlanet.defense) as Record<DefenseType, number>
|
||||
|
||||
// 扣除掠夺的资源
|
||||
targetPlanet.resources.metal -= battleResult.plunder.metal
|
||||
targetPlanet.resources.crystal -= battleResult.plunder.crystal
|
||||
targetPlanet.resources.deuterium -= battleResult.plunder.deuterium
|
||||
|
||||
mission.status = 'returning'
|
||||
|
||||
// 尝试生成月球(如果该位置还没有月球)
|
||||
let moon: Planet | null = null
|
||||
const moonCheck = moonValidation.canCreateMoon(allPlanets, targetPlanet.position, battleResult.debrisField)
|
||||
if (moonCheck.canCreate && moonCheck.chance) {
|
||||
if (moonValidation.shouldGenerateMoon(moonCheck.chance)) {
|
||||
moon = moonLogic.tryGenerateMoon(battleResult.debrisField, targetPlanet.position, targetPlanet.id, targetPlanet.ownerId || 'unknown')
|
||||
}
|
||||
}
|
||||
|
||||
return { battleResult, moon }
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理殖民任务到达
|
||||
*/
|
||||
export const processColonizeArrival = (
|
||||
mission: FleetMission,
|
||||
targetPlanet: Planet | undefined,
|
||||
playerId: string,
|
||||
colonyNameTemplate: string = 'Colony'
|
||||
): Planet | null => {
|
||||
if (targetPlanet) {
|
||||
// 位置已被占用
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
}
|
||||
|
||||
// 创建新殖民地
|
||||
const newPlanet: Planet = {
|
||||
id: `planet_${Date.now()}`,
|
||||
name: `${colonyNameTemplate} ${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`,
|
||||
ownerId: playerId,
|
||||
position: mission.targetPosition,
|
||||
resources: { metal: 500, crystal: 500, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildings: {} as Record<BuildingType, number>,
|
||||
fleet: {
|
||||
[ShipType.LightFighter]: 0,
|
||||
[ShipType.HeavyFighter]: 0,
|
||||
[ShipType.Cruiser]: 0,
|
||||
[ShipType.Battleship]: 0,
|
||||
[ShipType.SmallCargo]: 0,
|
||||
[ShipType.LargeCargo]: 0,
|
||||
[ShipType.ColonyShip]: 0,
|
||||
[ShipType.Recycler]: 0,
|
||||
[ShipType.EspionageProbe]: 0,
|
||||
[ShipType.DarkMatterHarvester]: 0
|
||||
},
|
||||
defense: {
|
||||
[DefenseType.RocketLauncher]: 0,
|
||||
[DefenseType.LightLaser]: 0,
|
||||
[DefenseType.HeavyLaser]: 0,
|
||||
[DefenseType.GaussCannon]: 0,
|
||||
[DefenseType.IonCannon]: 0,
|
||||
[DefenseType.PlasmaTurret]: 0,
|
||||
[DefenseType.SmallShieldDome]: 0,
|
||||
[DefenseType.LargeShieldDome]: 0
|
||||
},
|
||||
buildQueue: [],
|
||||
lastUpdate: Date.now(),
|
||||
maxSpace: 200,
|
||||
isMoon: false
|
||||
}
|
||||
|
||||
Object.values(BuildingType).forEach(building => {
|
||||
newPlanet.buildings[building] = 0
|
||||
})
|
||||
|
||||
// 殖民船被消耗
|
||||
mission.fleet[ShipType.ColonyShip] = (mission.fleet[ShipType.ColonyShip] || 1) - 1
|
||||
mission.status = 'returning'
|
||||
|
||||
return newPlanet
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理间谍任务到达
|
||||
*/
|
||||
export const processSpyArrival = (mission: FleetMission, targetPlanet: Planet | undefined, playerId: string): SpyReport | null => {
|
||||
if (!targetPlanet) {
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
}
|
||||
|
||||
const spyReport: SpyReport = {
|
||||
id: `spy_${Date.now()}`,
|
||||
timestamp: Date.now(),
|
||||
spyId: playerId,
|
||||
targetPlanetId: targetPlanet.id,
|
||||
targetPlayerId: targetPlanet.ownerId || 'unknown',
|
||||
resources: { ...targetPlanet.resources },
|
||||
fleet: { ...targetPlanet.fleet },
|
||||
defense: { ...targetPlanet.defense },
|
||||
buildings: { ...targetPlanet.buildings },
|
||||
technologies: {},
|
||||
detectionChance: 0.3
|
||||
}
|
||||
|
||||
mission.status = 'returning'
|
||||
return spyReport
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理部署任务到达
|
||||
*/
|
||||
export const processDeployArrival = (mission: FleetMission, targetPlanet: Planet | undefined, playerId: string): boolean => {
|
||||
if (!targetPlanet || targetPlanet.ownerId !== playerId) {
|
||||
mission.status = 'returning'
|
||||
return false
|
||||
}
|
||||
|
||||
for (const [shipType, count] of Object.entries(mission.fleet)) {
|
||||
targetPlanet.fleet[shipType as ShipType] += count
|
||||
}
|
||||
|
||||
// 部署任务直接完成,不返回
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理舰队任务返回
|
||||
*/
|
||||
export const processFleetReturn = (mission: FleetMission, originPlanet: Planet): void => {
|
||||
// 舰船返回
|
||||
Object.entries(mission.fleet).forEach(([shipType, count]) => {
|
||||
if (count > 0) {
|
||||
originPlanet.fleet[shipType as ShipType] += count
|
||||
}
|
||||
})
|
||||
|
||||
// 资源返回(掠夺物或运输货物)
|
||||
originPlanet.resources.metal += mission.cargo.metal
|
||||
originPlanet.resources.crystal += mission.cargo.crystal
|
||||
originPlanet.resources.deuterium += mission.cargo.deuterium
|
||||
originPlanet.resources.darkMatter += mission.cargo.darkMatter
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新舰队任务状态
|
||||
*/
|
||||
export const updateFleetMissions = (
|
||||
missions: FleetMission[],
|
||||
planets: Map<string, Planet>,
|
||||
attacker: Player,
|
||||
defender: Player | null,
|
||||
now: number
|
||||
): {
|
||||
completedMissions: string[]
|
||||
battleReports: BattleResult[]
|
||||
spyReports: SpyReport[]
|
||||
newColonies: Planet[]
|
||||
newMoons: Planet[]
|
||||
} => {
|
||||
const completedMissions: string[] = []
|
||||
const battleReports: BattleResult[] = []
|
||||
const spyReports: SpyReport[] = []
|
||||
const newColonies: Planet[] = []
|
||||
const newMoons: Planet[] = []
|
||||
|
||||
// 获取所有星球列表(用于月球生成检查)
|
||||
const allPlanets = Array.from(planets.values())
|
||||
|
||||
missions.forEach(mission => {
|
||||
const originPlanet = attacker.planets.find(p => p.id === mission.originPlanetId)
|
||||
|
||||
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
|
||||
// 任务到达目标
|
||||
const targetKey = `${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`
|
||||
const targetPlanet = planets.get(targetKey)
|
||||
|
||||
switch (mission.missionType) {
|
||||
case MissionType.Transport:
|
||||
processTransportArrival(mission, targetPlanet)
|
||||
break
|
||||
|
||||
case MissionType.Attack:
|
||||
const attackResult = processAttackArrival(mission, targetPlanet, attacker, defender, allPlanets)
|
||||
if (attackResult) {
|
||||
battleReports.push(attackResult.battleResult)
|
||||
if (attackResult.moon) {
|
||||
newMoons.push(attackResult.moon)
|
||||
// 将月球添加到planets map中
|
||||
const moonKey = `${attackResult.moon.position.galaxy}:${attackResult.moon.position.system}:${attackResult.moon.position.position}`
|
||||
planets.set(moonKey, attackResult.moon)
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case MissionType.Colonize:
|
||||
const newColony = processColonizeArrival(mission, targetPlanet, attacker.id)
|
||||
if (newColony) {
|
||||
newColonies.push(newColony)
|
||||
planets.set(targetKey, newColony)
|
||||
}
|
||||
break
|
||||
|
||||
case MissionType.Spy:
|
||||
const spyReport = processSpyArrival(mission, targetPlanet, attacker.id)
|
||||
if (spyReport) {
|
||||
spyReports.push(spyReport)
|
||||
}
|
||||
break
|
||||
|
||||
case MissionType.Deploy:
|
||||
const deployed = processDeployArrival(mission, targetPlanet, attacker.id)
|
||||
if (deployed) {
|
||||
completedMissions.push(mission.id)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) {
|
||||
// 舰队返回
|
||||
if (originPlanet) {
|
||||
processFleetReturn(mission, originPlanet)
|
||||
}
|
||||
completedMissions.push(mission.id)
|
||||
}
|
||||
})
|
||||
|
||||
return { completedMissions, battleReports, spyReports, newColonies, newMoons }
|
||||
}
|
||||
|
||||
/**
|
||||
* 召回舰队
|
||||
*/
|
||||
export const recallFleetMission = (mission: FleetMission, now: number): boolean => {
|
||||
if (mission.status !== 'outbound') return false
|
||||
|
||||
const elapsedTime = now - mission.departureTime
|
||||
|
||||
// 如果还在飞行途中,立即返回
|
||||
if (now < mission.arrivalTime) {
|
||||
mission.status = 'returning'
|
||||
mission.returnTime = now + elapsedTime // 返回时间等于已飞行的时间
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
132
src/logic/gameLogic.ts
Normal file
132
src/logic/gameLogic.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import type { Planet, Player, BuildQueueItem, Officer } from '@/types/game'
|
||||
import { TechnologyType, OfficerType } from '@/types/game'
|
||||
import * as officerLogic from './officerLogic'
|
||||
import * as buildingLogic from './buildingLogic'
|
||||
import * as researchLogic from './researchLogic'
|
||||
import * as resourceLogic from './resourceLogic'
|
||||
import * as pointsLogic from './pointsLogic'
|
||||
|
||||
/**
|
||||
* 初始化玩家数据
|
||||
*/
|
||||
export const initializePlayer = (playerId: string, playerName: string = 'Commander'): Player => {
|
||||
const player: Player = {
|
||||
id: playerId,
|
||||
name: playerName,
|
||||
planets: [],
|
||||
technologies: {} as Record<TechnologyType, number>,
|
||||
officers: {} as Record<OfficerType, Officer>,
|
||||
researchQueue: [],
|
||||
fleetMissions: [],
|
||||
battleReports: [],
|
||||
spyReports: [],
|
||||
points: 0
|
||||
}
|
||||
|
||||
// 初始化科技等级
|
||||
Object.values(TechnologyType).forEach(tech => {
|
||||
player.technologies[tech] = 0
|
||||
})
|
||||
|
||||
// 初始化军官状态
|
||||
Object.values(OfficerType).forEach(officer => {
|
||||
player.officers[officer] = officerLogic.createInactiveOfficer(officer)
|
||||
})
|
||||
|
||||
return player
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要初始化游戏
|
||||
*/
|
||||
export const shouldInitializeGame = (planets: Planet[]): boolean => {
|
||||
return planets.length === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有星球的最后更新时间
|
||||
*/
|
||||
export const updatePlanetsLastUpdate = (planets: Planet[], now: number): void => {
|
||||
planets.forEach(planet => {
|
||||
planet.lastUpdate = now
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成星系位置列表
|
||||
*/
|
||||
export const generateSystemPositions = (
|
||||
_galaxy: number,
|
||||
_system: number,
|
||||
count: number = 10
|
||||
): Array<{ position: number; planet: Planet | null }> => {
|
||||
const result: Array<{ position: number; planet: Planet | null }> = []
|
||||
for (let pos = 1; pos <= count; pos++) {
|
||||
result.push({ position: pos, planet: null })
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机NPC星球位置
|
||||
*/
|
||||
export const generateRandomPosition = (): { galaxy: number; system: number; position: number } => {
|
||||
return {
|
||||
galaxy: Math.floor(Math.random() * 9) + 1,
|
||||
system: Math.floor(Math.random() * 10) + 1,
|
||||
position: Math.floor(Math.random() * 10) + 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成位置键
|
||||
*/
|
||||
export const generatePositionKey = (galaxy: number, system: number, position: number): string => {
|
||||
return `${galaxy}:${system}:${position}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新游戏状态 - 处理所有星球和任务
|
||||
*/
|
||||
export const processGameUpdate = (
|
||||
player: Player,
|
||||
now: number
|
||||
): {
|
||||
updatedResearchQueue: BuildQueueItem[]
|
||||
} => {
|
||||
// 获取军官加成
|
||||
const bonuses = officerLogic.calculateActiveBonuses(player.officers, now)
|
||||
|
||||
// 创建积分回调函数
|
||||
const onPointsEarned = (points: number, _type: string, _itemType: string, _level?: number, _quantity?: number) => {
|
||||
pointsLogic.addPoints(player, points)
|
||||
}
|
||||
|
||||
// 更新所有星球
|
||||
player.planets.forEach(planet => {
|
||||
// 更新资源
|
||||
resourceLogic.updatePlanetResources(planet, now, bonuses)
|
||||
|
||||
// 检查建造队列
|
||||
buildingLogic.completeBuildQueue(planet, now, onPointsEarned)
|
||||
})
|
||||
|
||||
// 检查研究队列
|
||||
const updatedResearchQueue = researchLogic.completeResearchQueue(
|
||||
player.researchQueue,
|
||||
player.technologies,
|
||||
now,
|
||||
onPointsEarned
|
||||
)
|
||||
|
||||
return {
|
||||
updatedResearchQueue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并返回过期的军官列表
|
||||
*/
|
||||
export const checkOfficersExpiration = (officers: Record<OfficerType, Officer>, now: number): void => {
|
||||
officerLogic.checkAndDeactivateExpiredOfficers(officers, now)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user