1.6.0更新

This commit is contained in:
谦君
2026-01-06 03:00:02 +08:00
parent 1ad051cd6d
commit 9e7560cc4b
50 changed files with 2374 additions and 468 deletions

View File

@@ -57,9 +57,15 @@
<!-- 防守方配置 -->
<TabsContent value="defender" class="mt-4">
<Card>
<CardHeader>
<CardTitle>{{ t('simulatorView.defenderConfig') }}</CardTitle>
<CardDescription>{{ t('simulatorView.defenderConfigDesc') }}</CardDescription>
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
<div class="space-y-1">
<CardTitle>{{ t('simulatorView.defenderConfig') }}</CardTitle>
<CardDescription>{{ t('simulatorView.defenderConfigDesc') }}</CardDescription>
</div>
<Button variant="outline" size="sm" @click="showSpyReportSelector = true" :disabled="!gameStore.player?.spyReports?.length">
<FileDown class="h-4 w-4 mr-2" />
{{ t('simulatorView.importFromSpyReport') }}
</Button>
</CardHeader>
<CardContent class="space-y-4">
<!-- 舰队配置 -->
@@ -147,28 +153,80 @@
<!-- 战斗结果对话框 -->
<BattleReportDialog v-model:open="showResultDialog" :report="simulationResult" />
<!-- 侦查报告选择对话框 -->
<Dialog v-model:open="showSpyReportSelector">
<DialogContent class="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{{ t('simulatorView.selectSpyReport') }}</DialogTitle>
</DialogHeader>
<div class="space-y-2">
<div v-if="!sortedSpyReports.length" class="text-center py-8 text-muted-foreground">
{{ t('simulatorView.noSpyReports') }}
</div>
<div
v-for="report in sortedSpyReports"
:key="report.id"
@click="importFromSpyReport(report)"
class="p-3 border rounded-lg cursor-pointer hover:bg-accent transition-colors"
>
<div class="flex justify-between items-start">
<div>
<div class="font-medium">{{ report.targetPlanetName }}</div>
<div class="text-sm text-muted-foreground">
[{{ report.targetPosition.galaxy }}:{{ report.targetPosition.system }}:{{ report.targetPosition.position }}]
</div>
</div>
<div class="text-sm text-muted-foreground">
{{ formatTime(report.timestamp) }}
</div>
</div>
<div class="mt-2 flex gap-4 text-xs">
<span class="flex items-center gap-1">
<ResourceIcon type="metal" size="sm" />
{{ formatNumber(report.resources.metal) }}
</span>
<span class="flex items-center gap-1">
<ResourceIcon type="crystal" size="sm" />
{{ formatNumber(report.resources.crystal) }}
</span>
<span class="flex items-center gap-1">
<ResourceIcon type="deuterium" size="sm" />
{{ formatNumber(report.resources.deuterium) }}
</span>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { ref, toRaw } from 'vue'
import { ref, toRaw, computed } from 'vue'
import { useI18n } from '@/composables/useI18n'
import { useGameConfig } from '@/composables/useGameConfig'
import { useGameStore } from '@/stores/gameStore'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { ShipType, DefenseType } from '@/types/game'
import type { Fleet, BattleResult } from '@/types/game'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { ShipType, DefenseType, TechnologyType } from '@/types/game'
import type { Fleet, BattleResult, SpyReport } from '@/types/game'
import { workerManager } from '@/workers/workerManager'
import ResourceIcon from '@/components/common/ResourceIcon.vue'
import BattleReportDialog from '@/components/dialogs/BattleReportDialog.vue'
import { Sword, Shield, Zap, RotateCcw } from 'lucide-vue-next'
import { Sword, Shield, Zap, RotateCcw, FileDown } from 'lucide-vue-next'
import * as planetLogic from '@/logic/planetLogic'
const { t } = useI18n()
const { SHIPS, DEFENSES } = useGameConfig()
const gameStore = useGameStore()
// 侦查报告选择对话框状态
const showSpyReportSelector = ref(false)
// 科技类型配置
const techTypes = ['weapon', 'shield', 'armor'] as const
@@ -249,7 +307,8 @@
// 使用 Worker 执行战斗模拟
const result = await workerManager.simulateBattle({
attacker: attackerSide,
defender: defenderSide
defender: defenderSide,
maxRounds: gameStore.battleToFinish ? 100 : 6
})
// 计算掠夺和残骸场
@@ -302,4 +361,65 @@
simulationResult.value = null
showResultDialog.value = false
}
// 按时间排序的侦查报告
const sortedSpyReports = computed(() => {
return [...(gameStore.player?.spyReports || [])].sort((a, b) => b.timestamp - a.timestamp)
})
// 格式化时间
const formatTime = (timestamp: number) => {
return new Date(timestamp).toLocaleString()
}
// 格式化数字
const formatNumber = (num: number) => {
if (num >= 1e9) return (num / 1e9).toFixed(1) + 'B'
if (num >= 1e6) return (num / 1e6).toFixed(1) + 'M'
if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K'
return num.toString()
}
// 从侦查报告导入数据
const importFromSpyReport = (report: SpyReport) => {
// 先重置防守方数据
defenderFleet.value = initializeFleet()
defenderDefense.value = initializeDefense()
// 填入资源
if (report.resources) {
defenderResources.value = {
metal: report.resources.metal || 0,
crystal: report.resources.crystal || 0,
deuterium: report.resources.deuterium || 0,
darkMatter: report.resources.darkMatter || 0,
energy: 0
}
}
// 填入舰队
if (report.fleet) {
Object.entries(report.fleet).forEach(([key, value]) => {
defenderFleet.value[key as keyof Fleet] = value || 0
})
}
// 填入防御
if (report.defense) {
Object.entries(report.defense).forEach(([key, value]) => {
defenderDefense.value[key as DefenseType] = value || 0
})
}
// 填入科技
if (report.technologies) {
defenderTech.value.weapon = report.technologies[TechnologyType.WeaponsTechnology] || 0
defenderTech.value.shield = report.technologies[TechnologyType.ShieldingTechnology] || 0
defenderTech.value.armor = report.technologies[TechnologyType.ArmourTechnology] || 0
}
// 关闭对话框并切换到防守方标签
showSpyReportSelector.value = false
activeTab.value = 'defender'
}
</script>

View File

@@ -10,8 +10,8 @@
<TabsList :class="['grid', 'w-full', showJumpGateTab ? 'grid-cols-3' : 'grid-cols-2']">
<TabsTrigger v-for="tab in visibleTabs" :key="tab.value" :value="tab.value">
{{ t(`fleetView.${tab.labelKey}`) }}
<Badge v-if="tab.value === 'missions' && gameStore.player.fleetMissions.length > 0" variant="destructive" class="ml-1">
{{ gameStore.player.fleetMissions.length }}
<Badge v-if="tab.value === 'missions' && totalMissionsCount > 0" variant="destructive" class="ml-1">
{{ totalMissionsCount }}
</Badge>
<Badge v-if="tab.value === 'jumpGate' && jumpGateReady" variant="default" class="ml-1"></Badge>
</TabsTrigger>
@@ -294,13 +294,14 @@
<!-- 飞行任务 -->
<TabsContent value="missions" class="mt-4 space-y-4">
<Empty v-if="gameStore.player.fleetMissions.length === 0" class="border rounded-lg">
<Empty v-if="totalMissionsCount === 0" class="border rounded-lg">
<EmptyContent>
<RocketIcon class="h-10 w-10 text-muted-foreground" />
<EmptyDescription>{{ t('fleetView.noFlightMissions') }}</EmptyDescription>
</EmptyContent>
</Empty>
<!-- 舰队任务 -->
<Card v-for="mission in gameStore.player.fleetMissions" :key="mission.id">
<CardHeader>
<div class="flex justify-between items-start">
@@ -378,6 +379,44 @@
</div>
</CardContent>
</Card>
<!-- 导弹攻击任务 -->
<Card v-for="missileAttack in flyingMissileAttacks" :key="missileAttack.id">
<CardHeader>
<div class="flex justify-between items-start">
<div>
<CardTitle class="text-base sm:text-lg flex items-center gap-2">
<Crosshair class="h-4 w-4 text-destructive" />
{{ t('galaxyView.missileAttackTitle') }}
</CardTitle>
<CardDescription class="text-xs sm:text-sm">
{{ getPlanetName(missileAttack.originPlanetId) }} [{{ missileAttack.targetPosition.galaxy }}:{{ missileAttack.targetPosition.system }}:{{
missileAttack.targetPosition.position
}}]
</CardDescription>
</div>
<Badge variant="destructive">
{{ t('fleetView.outbound') }}
</Badge>
</div>
</CardHeader>
<CardContent class="space-y-3">
<!-- 导弹数量 -->
<div>
<p class="text-xs sm:text-sm font-medium mb-2">{{ t('galaxyView.missileCount') }}:</p>
<Badge variant="outline">{{ missileAttack.missileCount }}</Badge>
</div>
<!-- 进度条 -->
<div class="space-y-2">
<div class="flex justify-between text-xs sm:text-sm">
<span>{{ t('fleetView.arrivalTime') }}:</span>
<span>{{ formatTime(getMissileRemainingTime(missileAttack)) }}</span>
</div>
<Progress :model-value="getMissileProgress(missileAttack)" />
</div>
</CardContent>
</Card>
</TabsContent>
<!-- 跳跃门 -->
@@ -591,7 +630,8 @@
Clock,
Check,
Globe,
Moon
Moon,
Crosshair
} from 'lucide-vue-next'
import { formatNumber, formatTime } from '@/utils/format'
import * as shipValidation from '@/logic/shipValidation'
@@ -628,6 +668,16 @@
return publicLogic.getMaxFleetMissions(bonuses.additionalFleetSlots, computerTechLevel)
})
// 飞行中的导弹攻击
const flyingMissileAttacks = computed(() => {
return gameStore.player.missileAttacks?.filter(m => m.status === 'flying') || []
})
// 总任务数量(舰队任务 + 导弹攻击)
const totalMissionsCount = computed(() => {
return gameStore.player.fleetMissions.length + flyingMissileAttacks.value.length
})
const activeTab = ref<'send' | 'missions' | 'jumpGate'>('send')
// Tab 配置
@@ -837,6 +887,8 @@
selectedMission.value = MissionType.Attack
} else if (mission === 'colonize') {
selectedMission.value = MissionType.Colonize
} else if (mission === 'recycle') {
selectedMission.value = MissionType.Recycle
} else if (gift === '1') {
// 如果有gift参数设置为运输任务并启用赠送模式
selectedMission.value = MissionType.Transport
@@ -1413,4 +1465,18 @@
return Math.max(0, Math.min(100, (elapsed / total) * 100))
}
}
// 获取导弹任务剩余时间
const getMissileRemainingTime = (missileAttack: any): number => {
const now = currentTime.value
return Math.max(0, (missileAttack.arrivalTime - now) / 1000)
}
// 获取导弹任务进度
const getMissileProgress = (missileAttack: any): number => {
const now = currentTime.value
const total = missileAttack.arrivalTime - missileAttack.launchTime
const elapsed = now - missileAttack.launchTime
return Math.max(0, Math.min(100, (elapsed / total) * 100))
}
</script>

View File

@@ -66,7 +66,7 @@
{{ t('galaxyView.myPlanets') }}
</Button>
</PopoverTrigger>
<PopoverContent class="w-72 p-2" align="start">
<PopoverContent class="w-87 p-2 max-h-80 overflow-y-auto" align="start">
<div class="space-y-1">
<div class="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
{{ t('galaxyView.selectPlanetToView') }}
@@ -114,7 +114,7 @@
{{ highlightedNpc.name }} ({{ highlightedNpc.planets.length }})
</Button>
</PopoverTrigger>
<PopoverContent class="w-72 p-2" align="start">
<PopoverContent class="w-72 p-2 max-h-96 overflow-y-auto" align="start">
<div class="space-y-1">
<div class="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
{{ t('galaxyView.selectPlanetToView') }}

View File

@@ -149,11 +149,11 @@
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
transform: translate3d(0, -10px, 0);
}
to {
opacity: 1;
transform: translateY(0);
transform: translate3d(0, 0, 0);
}
}
</style>

View File

@@ -22,8 +22,47 @@
<div v-if="planet.isMoon" class="mt-2">
<Button @click="switchToParentPlanet" variant="outline" size="sm">{{ t('planet.backToPlanet') }}</Button>
</div>
<!-- 放弃殖民地按钮 -->
<div v-if="canShowAbandonButton" class="mt-4">
<Button @click="showAbandonDialog = true" variant="destructive" size="sm">
{{ t('planet.abandonColony') }}
</Button>
</div>
</div>
<!-- 放弃殖民地确认对话框 -->
<AlertDialog :open="showAbandonDialog" @update:open="showAbandonDialog = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{ t('planet.confirmAbandon') }}</AlertDialogTitle>
<AlertDialogDescription class="whitespace-pre-line">
{{ t('planet.abandonWarning', { name: planet.name }) }}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{{ t('common.cancel') }}</AlertDialogCancel>
<AlertDialogAction @click="handleAbandonColony" class="bg-destructive text-destructive-foreground hover:bg-destructive/90">
{{ t('planet.confirmAbandonButton') }}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<!-- 错误提示对话框 -->
<AlertDialog :open="showErrorDialog" @update:open="showErrorDialog = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{ t('planet.abandonFailed') }}</AlertDialogTitle>
<AlertDialogDescription>
{{ errorMessage }}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction>{{ t('common.confirm') }}</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<!-- 资源管理 -->
<Card>
<CardHeader>
@@ -195,25 +234,45 @@
import { useGameStore } from '@/stores/gameStore'
import { useI18n } from '@/composables/useI18n'
import { useGameConfig } from '@/composables/useGameConfig'
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from '@/components/ui/alert-dialog'
import ResourceIcon from '@/components/common/ResourceIcon.vue'
import { formatNumber, getResourceColor } from '@/utils/format'
import { scaleNumber } from '@/utils/speed'
import type { Planet } from '@/types/game'
import { TechnologyType } from '@/types/game'
import * as publicLogic from '@/logic/publicLogic'
import * as resourceLogic from '@/logic/resourceLogic'
import * as planetLogic from '@/logic/planetLogic'
const gameStore = useGameStore()
const { t } = useI18n()
const { SHIPS } = useGameConfig()
const planet = computed(() => gameStore.currentPlanet)
// 获取科技加成
const techBonuses = computed(() => ({
mineralResearchLevel: gameStore.player.technologies[TechnologyType.MineralResearch] || 0,
crystalResearchLevel: gameStore.player.technologies[TechnologyType.CrystalResearch] || 0,
fuelResearchLevel: gameStore.player.technologies[TechnologyType.FuelResearch] || 0
}))
const production = computed(() =>
planet.value ? publicLogic.getResourceProduction(planet.value, gameStore.player.officers, gameStore.gameSpeed) : null
planet.value ? publicLogic.getResourceProduction(planet.value, gameStore.player.officers, gameStore.gameSpeed, techBonuses.value) : null
)
const capacity = computed(() => (planet.value ? publicLogic.getResourceCapacity(planet.value, gameStore.player.officers) : null))
@@ -226,7 +285,7 @@
// 资源产量详细breakdown
const productionBreakdown = computed(() => {
if (!planet.value) return null
return resourceLogic.calculateProductionBreakdown(planet.value, gameStore.player.officers, Date.now(), gameStore.gameSpeed)
return resourceLogic.calculateProductionBreakdown(planet.value, gameStore.player.officers, Date.now(), gameStore.gameSpeed, techBonuses.value)
})
// 资源消耗详细breakdown
@@ -286,4 +345,45 @@
gameStore.currentPlanetId = planet.value.parentPlanetId
}
}
// 放弃殖民地相关
const showAbandonDialog = ref(false)
const showErrorDialog = ref(false)
const errorMessage = ref('')
// 是否显示放弃按钮(非主星才显示)
const canShowAbandonButton = computed(() => {
if (!planet.value) return false
// 找到主星(第一个非月球星球)
const mainPlanet = gameStore.player.planets.find(p => !p.isMoon)
// 当前星球不是主星时才显示放弃按钮
return mainPlanet && mainPlanet.id !== planet.value.id
})
// 处理放弃殖民地
const handleAbandonColony = () => {
if (!planet.value) return
const check = planetLogic.canAbandonColony(gameStore.player.planets, planet.value.id)
if (!check.canAbandon) {
showAbandonDialog.value = false
errorMessage.value = check.reason ? t(check.reason) : t('planet.abandonFailed')
showErrorDialog.value = true
return
}
// 记录当前星球ID用于后续切换
const abandonedPlanetId = planet.value.id
// 执行放弃
gameStore.player.planets = planetLogic.abandonColony(gameStore.player.planets, abandonedPlanetId)
// 切换到主星
const mainPlanet = gameStore.player.planets.find(p => !p.isMoon)
if (mainPlanet) {
gameStore.currentPlanetId = mainPlanet.id
}
showAbandonDialog.value = false
}
</script>

View File

@@ -175,7 +175,8 @@
techType,
currentLevel,
gameStore.player.officers,
gameStore.player.technologies
gameStore.player.technologies,
gameStore.player.planets
)
gameStore.player.researchQueue.push(queueItem)
return true
@@ -333,11 +334,33 @@
const getResearchTime = (techType: TechnologyType): number => {
if (!planet.value) return 0
const currentLevel = getTechLevel(techType)
const researchLabLevel = planet.value.buildings['researchLab'] || 0
const intergalacticResearchNetworkLevel = player.value.technologies[TechnologyType.IntergalacticResearchNetwork] || 0
// 计算有效研究实验室等级(考虑星际研究网络)
let researchLabLevel: number
if (intergalacticResearchNetworkLevel > 0) {
researchLabLevel = researchLogic.calculateEffectiveLabLevel(
gameStore.player.planets,
planet.value.id,
intergalacticResearchNetworkLevel
)
} else {
researchLabLevel = planet.value.buildings['researchLab'] || 0
}
const energyTechLevel = player.value.technologies['energyTechnology'] || 0
const universityLevel = planet.value.buildings['university'] || 0
const bonuses = officerLogic.calculateActiveBonuses(player.value.officers, gameStore.gameTime)
return researchLogic.calculateTechnologyTime(techType, currentLevel, bonuses.researchSpeedBonus, researchLabLevel, energyTechLevel)
return researchLogic.calculateTechnologyTime(
techType,
currentLevel,
bonuses.researchSpeedBonus,
researchLabLevel,
energyTechLevel,
1,
universityLevel
)
}
// 检查是否可以添加到等待队列

View File

@@ -114,6 +114,15 @@
{{ gameStore.isPaused ? t('settings.resume') : t('settings.pause') }}
</Button>
</div>
<!-- 战斗模式 -->
<div class="flex items-center justify-between p-4 border rounded-lg">
<div class="space-y-1">
<h3 class="font-medium">{{ t('settings.battleMode') }}</h3>
<p class="text-sm text-muted-foreground">{{ t('settings.battleModeDesc') }}</p>
</div>
<Switch :checked="gameStore.battleToFinish" @update:checked="(val: boolean) => (gameStore.battleToFinish = val)" />
</div>
</CardContent>
</Card>
@@ -581,12 +590,22 @@
const fileName = `${pkg.name}-${new Date().toISOString().slice(0, 10)}-${Date.now()}.json`
const jsonString = JSON.stringify(exportData, null, 2)
// Android 保存到 Documents 目录
// Android 保存到公共 Downloads 目录
if (Capacitor.isNativePlatform()) {
// 检查并请求存储权限
const permStatus = await Filesystem.checkPermissions()
if (permStatus.publicStorage !== 'granted') {
const reqResult = await Filesystem.requestPermissions()
if (reqResult.publicStorage !== 'granted') {
toast.error(t('settings.storagePermissionDenied'))
return
}
}
const result = await Filesystem.writeFile({
path: fileName,
path: `Download/${fileName}`,
data: jsonString,
directory: Directory.Documents,
directory: Directory.ExternalStorage,
encoding: Encoding.UTF8
})
toast.success(t('settings.exportSuccessWithPath', { path: result.uri }))

View File

@@ -101,6 +101,20 @@
/>
</div>
<!-- 拆除数量输入 -->
<div v-if="(planet.fleet[shipType] || 0) > 0" class="space-y-2">
<Label :for="`scrap-quantity-${shipType}`" class="text-xs sm:text-sm text-destructive">{{ t('shipyardView.scrapQuantity') }}</Label>
<Input
:id="`scrap-quantity-${shipType}`"
v-model.number="scrapQuantities[shipType]"
type="number"
min="0"
:max="planet.fleet[shipType] || 0"
placeholder="0"
class="text-sm"
/>
</div>
<div v-if="quantities[shipType] > 0" class="text-xs sm:text-sm space-y-1.5 sm:space-y-2 p-2.5 sm:p-3 bg-muted rounded-lg">
<p class="font-medium text-muted-foreground">{{ t('shipyardView.totalCost') }}:</p>
<div class="space-y-1 sm:space-y-1.5">
@@ -133,6 +147,36 @@
>
{{ t('queue.addToWaiting') }}
</Button>
<!-- 拆除返还资源显示 -->
<div v-if="scrapQuantities[shipType] > 0" class="text-xs sm:text-sm space-y-1.5 sm:space-y-2 p-2.5 sm:p-3 bg-destructive/10 rounded-lg border border-destructive/30">
<p class="font-medium text-destructive">{{ t('shipyardView.scrapRefund') }}:</p>
<div class="space-y-1 sm:space-y-1.5">
<div
v-for="resourceType in costResourceTypes"
:key="resourceType.key"
v-show="resourceType.key !== 'darkMatter' || getScrapRefund(shipType).darkMatter > 0"
class="flex items-center gap-1.5 sm:gap-2"
>
<ResourceIcon :type="resourceType.key" size="sm" />
<span class="text-xs">{{ t(`resources.${resourceType.key}`) }}:</span>
<span class="font-medium text-xs sm:text-sm text-green-500">
+{{ formatNumber(getScrapRefund(shipType)[resourceType.key]) }}
</span>
</div>
</div>
</div>
<!-- 拆除按钮 -->
<Button
v-if="(planet.fleet[shipType] || 0) > 0"
@click="handleScrap(shipType, $event)"
:disabled="!canScrap(shipType)"
variant="destructive"
class="w-full"
>
{{ t('shipyardView.scrap') }}
</Button>
</div>
</CardContent>
</Card>
@@ -243,6 +287,25 @@
[ShipType.Deathstar]: 0
})
// 每种舰船的拆除数量
const scrapQuantities = ref<Record<ShipType, number>>({
[ShipType.LightFighter]: 0,
[ShipType.HeavyFighter]: 0,
[ShipType.Cruiser]: 0,
[ShipType.Battleship]: 0,
[ShipType.Battlecruiser]: 0,
[ShipType.Bomber]: 0,
[ShipType.Destroyer]: 0,
[ShipType.SmallCargo]: 0,
[ShipType.LargeCargo]: 0,
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.SolarSatellite]: 0,
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
})
const buildShip = (shipType: ShipType, quantity: number): { success: boolean; reason?: string } => {
if (!gameStore.currentPlanet) return { success: false }
const validation = shipValidation.validateShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.technologies)
@@ -380,4 +443,54 @@
waitingQueueLogic.addToBuildWaitingQueue(planet.value, item)
quantities.value[shipType] = 0
}
// 计算拆除返还资源
const getScrapRefund = (shipType: ShipType) => {
const quantity = scrapQuantities.value[shipType]
return shipLogic.calculateShipScrapRefund(shipType, quantity)
}
// 检查是否可以拆除
const canScrap = (shipType: ShipType): boolean => {
if (!planet.value) return false
const quantity = scrapQuantities.value[shipType]
if (quantity <= 0) return false
const available = planet.value.fleet[shipType] || 0
return available >= quantity
}
// 拆除舰船
const handleScrap = (shipType: ShipType, _event: MouseEvent) => {
// 防抖:防止快速点击
if (isProcessing.value) return
isProcessing.value = true
setTimeout(() => {
isProcessing.value = false
}, DEBOUNCE_DELAY)
const quantity = scrapQuantities.value[shipType]
if (quantity <= 0) {
alertDialogTitle.value = t('shipyardView.inputError')
alertDialogMessage.value = t('shipyardView.inputErrorMessage')
alertDialogOpen.value = true
return
}
if (!gameStore.currentPlanet) return
const validation = shipValidation.validateShipScrap(gameStore.currentPlanet, shipType, quantity)
if (!validation.valid) {
alertDialogTitle.value = t('shipyardView.scrapFailed')
alertDialogMessage.value = validation.reason ? t(validation.reason) : t('shipyardView.scrapFailedMessage')
alertDialogOpen.value = true
return
}
// 执行拆除
const queueItem = shipValidation.executeShipScrap(gameStore.currentPlanet, shipType, quantity, gameStore.player.officers)
gameStore.currentPlanet.buildQueue.push(queueItem)
scrapQuantities.value[shipType] = 0
}
</script>