mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 16:05:12 +08:00
feat: 新增NPC与外交逻辑,优化UI组件结构
重构并精简了部分UI组件,移除冗余弹窗与详情组件,新增NPC相关逻辑(npcBehaviorLogic、npcGrowthLogic、npcStore等)及外交逻辑(diplomaticLogic、DiplomacyView)。完善分页、标签、复选框等通用UI组件。优化战报弹窗,调整README下载链接为相对路径,修复部分国际化内容。
This commit is contained in:
@@ -6,297 +6,330 @@
|
||||
<h1 class="text-2xl sm:text-3xl font-bold">{{ t('fleetView.title') }}</h1>
|
||||
|
||||
<!-- 标签切换 -->
|
||||
<div class="flex gap-2 border-b">
|
||||
<Button @click="activeTab = 'fleet'" :variant="activeTab === 'fleet' ? 'default' : 'ghost'" class="rounded-b-none">
|
||||
{{ t('fleetView.fleetOverview') }}
|
||||
</Button>
|
||||
<Button @click="activeTab = 'send'" :variant="activeTab === 'send' ? 'default' : 'ghost'" class="rounded-b-none">
|
||||
{{ t('fleetView.sendFleet') }}
|
||||
</Button>
|
||||
<Button @click="activeTab = 'missions'" :variant="activeTab === 'missions' ? 'default' : 'ghost'" class="rounded-b-none">
|
||||
{{ t('fleetView.flightMissions') }}
|
||||
<Badge v-if="gameStore.player.fleetMissions.length > 0" variant="secondary" class="ml-1">
|
||||
{{ gameStore.player.fleetMissions.length }}
|
||||
</Badge>
|
||||
</Button>
|
||||
</div>
|
||||
<Tabs v-model="activeTab" class="w-full">
|
||||
<TabsList class="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="fleet">{{ t('fleetView.fleetOverview') }}</TabsTrigger>
|
||||
<TabsTrigger value="send">{{ t('fleetView.sendFleet') }}</TabsTrigger>
|
||||
<TabsTrigger value="missions">
|
||||
{{ t('fleetView.flightMissions') }}
|
||||
<Badge v-if="gameStore.player.fleetMissions.length > 0" variant="destructive" class="ml-1">
|
||||
{{ gameStore.player.fleetMissions.length }}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<!-- 舰队总览 -->
|
||||
<div v-if="activeTab === 'fleet'">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.currentPlanetFleet') }}</CardTitle>
|
||||
<CardDescription>
|
||||
{{ planet.name }} [{{ planet.position.galaxy }}:{{ planet.position.system }}:{{ planet.position.position }}]
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div v-for="(count, shipType) in planet.fleet" :key="shipType" class="p-3 sm:p-4 border rounded-lg space-y-2">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 class="font-semibold text-sm sm:text-base">{{ SHIPS[shipType].name }}</h3>
|
||||
<p class="text-xl sm:text-2xl font-bold">{{ formatNumber(count) }}</p>
|
||||
<!-- 舰队总览 -->
|
||||
<TabsContent value="fleet" class="mt-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.currentPlanetFleet') }}</CardTitle>
|
||||
<CardDescription>
|
||||
{{ planet.name }} [{{ planet.position.galaxy }}:{{ planet.position.system }}:{{ planet.position.position }}]
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div v-for="(count, shipType) in planet.fleet" :key="shipType" class="p-3 sm:p-4 border rounded-lg space-y-2">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 class="font-semibold text-sm sm:text-base">{{ SHIPS[shipType].name }}</h3>
|
||||
<p class="text-xl sm:text-2xl font-bold">{{ formatNumber(count) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs sm:text-sm text-muted-foreground space-y-1">
|
||||
<p>{{ t('fleetView.attack') }}: {{ SHIPS[shipType].attack }}</p>
|
||||
<p>{{ t('fleetView.shield') }}: {{ SHIPS[shipType].shield }}</p>
|
||||
<p>{{ t('fleetView.armor') }}: {{ SHIPS[shipType].armor }}</p>
|
||||
<p>{{ t('fleetView.speed') }}: {{ formatNumber(SHIPS[shipType].speed) }}</p>
|
||||
<p>{{ t('fleetView.cargo') }}: {{ formatNumber(SHIPS[shipType].cargoCapacity) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs sm:text-sm text-muted-foreground space-y-1">
|
||||
<p>{{ t('fleetView.attack') }}: {{ SHIPS[shipType].attack }}</p>
|
||||
<p>{{ t('fleetView.shield') }}: {{ SHIPS[shipType].shield }}</p>
|
||||
<p>{{ t('fleetView.armor') }}: {{ SHIPS[shipType].armor }}</p>
|
||||
<p>{{ t('fleetView.speed') }}: {{ formatNumber(SHIPS[shipType].speed) }}</p>
|
||||
<p>{{ t('fleetView.cargo') }}: {{ formatNumber(SHIPS[shipType].cargoCapacity) }}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 派遣舰队 -->
|
||||
<TabsContent value="send" class="mt-4 space-y-4">
|
||||
<!-- 舰队任务槽位信息 -->
|
||||
<Card>
|
||||
<CardContent class="py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-medium">{{ t('fleetView.fleetMissionSlots') }}:</span>
|
||||
<span class="text-sm font-bold">{{ gameStore.player.fleetMissions.length }} / {{ maxFleetMissions }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 选择舰队 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.selectFleet') }}</CardTitle>
|
||||
<CardDescription>{{ t('fleetView.selectFleetDescription') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||
<div v-for="(count, shipType) in planet.fleet" :key="shipType" class="space-y-2">
|
||||
<Label :for="`ship-${shipType}`" class="text-xs sm:text-sm">
|
||||
{{ SHIPS[shipType].name }} ({{ t('fleetView.available') }}: {{ count }})
|
||||
</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
:id="`ship-${shipType}`"
|
||||
v-model.number="selectedFleet[shipType]"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="count"
|
||||
placeholder="0"
|
||||
class="text-sm"
|
||||
/>
|
||||
<Button @click="selectedFleet[shipType] = count" variant="outline" size="sm">{{ t('fleetView.all') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 派遣舰队 -->
|
||||
<div v-if="activeTab === 'send'" class="space-y-4">
|
||||
<!-- 舰队任务槽位信息 -->
|
||||
<Card>
|
||||
<CardContent class="py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm font-medium">{{ t('fleetView.fleetMissionSlots') }}:</span>
|
||||
<span class="text-sm font-bold">{{ gameStore.player.fleetMissions.length }} / {{ maxFleetMissions }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<!-- 目标坐标 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.targetCoordinates') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-3 gap-2 sm:gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="galaxy" class="text-xs sm:text-sm">{{ t('fleetView.galaxy') }}</Label>
|
||||
<Input id="galaxy" v-model.number="targetPosition.galaxy" type="number" min="1" max="9" placeholder="1" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="system" class="text-xs sm:text-sm">{{ t('fleetView.system') }}</Label>
|
||||
<Input id="system" v-model.number="targetPosition.system" type="number" min="1" max="10" placeholder="1" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="position" class="text-xs sm:text-sm">{{ t('fleetView.position') }}</Label>
|
||||
<Input id="position" v-model.number="targetPosition.position" type="number" min="1" max="10" placeholder="1" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 选择舰队 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.selectFleet') }}</CardTitle>
|
||||
<CardDescription>{{ t('fleetView.selectFleetDescription') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||
<div v-for="(count, shipType) in planet.fleet" :key="shipType" class="space-y-2">
|
||||
<Label :for="`ship-${shipType}`" class="text-xs sm:text-sm">
|
||||
{{ SHIPS[shipType].name }} ({{ t('fleetView.available') }}: {{ count }})
|
||||
</Label>
|
||||
<div class="flex gap-2">
|
||||
<!-- 任务类型 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.missionType') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
<Button
|
||||
v-for="mission in availableMissions"
|
||||
:key="mission.type"
|
||||
@click="selectedMission = mission.type"
|
||||
:variant="selectedMission === mission.type ? 'default' : 'outline'"
|
||||
class="justify-start"
|
||||
>
|
||||
<component :is="mission.icon" class="h-4 w-4 mr-2" />
|
||||
{{ mission.name }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 运输资源(仅运输任务) -->
|
||||
<Card v-if="selectedMission === MissionType.Transport">
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.transportResources') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<!-- 赠送模式切换(仅当目标是NPC星球时显示) -->
|
||||
<div v-if="targetNpc" class="mb-4 p-3 border rounded-lg bg-muted/50">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Checkbox id="gift-mode" :default-value="isGiftMode" />
|
||||
<Label for="gift-mode" class="flex items-center gap-2 cursor-pointer">
|
||||
<Gift class="h-4 w-4" />
|
||||
{{ t('fleetView.giftMode') }}
|
||||
</Label>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">{{ t('fleetView.giftModeDescription') }} {{ targetNpc.name }}</p>
|
||||
<div v-if="isGiftMode && (cargo.metal > 0 || cargo.crystal > 0 || cargo.deuterium > 0)" class="mt-2 text-xs">
|
||||
<span class="text-muted-foreground">{{ t('fleetView.estimatedReputationGain') }}:</span>
|
||||
<span class="ml-1 font-semibold text-green-600 dark:text-green-400">+{{ calculateGiftReputation() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="cargo-metal" class="text-xs sm:text-sm flex items-center gap-2">
|
||||
<ResourceIcon type="metal" size="sm" />
|
||||
{{ t('resources.metal') }} ({{ t('fleetView.available') }}: {{ formatNumber(planet.resources.metal) }})
|
||||
</Label>
|
||||
<Input id="cargo-metal" v-model.number="cargo.metal" type="number" min="0" :max="planet.resources.metal" placeholder="0" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="cargo-crystal" class="text-xs sm:text-sm flex items-center gap-2">
|
||||
<ResourceIcon type="crystal" size="sm" />
|
||||
{{ t('resources.crystal') }} ({{ t('fleetView.available') }}: {{ formatNumber(planet.resources.crystal) }})
|
||||
</Label>
|
||||
<Input
|
||||
:id="`ship-${shipType}`"
|
||||
v-model.number="selectedFleet[shipType]"
|
||||
id="cargo-crystal"
|
||||
v-model.number="cargo.crystal"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="count"
|
||||
:max="planet.resources.crystal"
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="cargo-deuterium" class="text-xs sm:text-sm flex items-center gap-2">
|
||||
<ResourceIcon type="deuterium" size="sm" />
|
||||
{{ t('resources.deuterium') }} ({{ t('fleetView.available') }}: {{ formatNumber(planet.resources.deuterium) }})
|
||||
</Label>
|
||||
<Input
|
||||
id="cargo-deuterium"
|
||||
v-model.number="cargo.deuterium"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="planet.resources.deuterium"
|
||||
placeholder="0"
|
||||
class="text-sm"
|
||||
/>
|
||||
<Button @click="selectedFleet[shipType] = count" variant="outline" size="sm">{{ t('fleetView.all') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p class="text-xs sm:text-sm text-muted-foreground mt-2">
|
||||
{{ t('fleetView.totalCargoCapacity') }}: {{ formatNumber(getTotalCargoCapacity()) }} | {{ t('fleetView.used') }}:
|
||||
{{ formatNumber(getTotalCargo()) }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 目标坐标 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.targetCoordinates') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-3 gap-2 sm:gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="galaxy" class="text-xs sm:text-sm">{{ t('fleetView.galaxy') }}</Label>
|
||||
<Input id="galaxy" v-model.number="targetPosition.galaxy" type="number" min="1" max="9" placeholder="1" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="system" class="text-xs sm:text-sm">{{ t('fleetView.system') }}</Label>
|
||||
<Input id="system" v-model.number="targetPosition.system" type="number" min="1" max="10" placeholder="1" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="position" class="text-xs sm:text-sm">{{ t('fleetView.position') }}</Label>
|
||||
<Input id="position" v-model.number="targetPosition.position" type="number" min="1" max="10" placeholder="1" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 任务类型 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.missionType') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
<Button
|
||||
v-for="mission in availableMissions"
|
||||
:key="mission.type"
|
||||
@click="selectedMission = mission.type"
|
||||
:variant="selectedMission === mission.type ? 'default' : 'outline'"
|
||||
class="justify-start"
|
||||
>
|
||||
<component :is="mission.icon" class="h-4 w-4 mr-2" />
|
||||
{{ mission.name }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 运输资源(仅运输任务) -->
|
||||
<Card v-if="selectedMission === MissionType.Transport">
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.transportResources') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="cargo-metal" class="text-xs sm:text-sm flex items-center gap-2">
|
||||
<ResourceIcon type="metal" size="sm" />
|
||||
{{ t('resources.metal') }} ({{ t('fleetView.available') }}: {{ formatNumber(planet.resources.metal) }})
|
||||
</Label>
|
||||
<Input id="cargo-metal" v-model.number="cargo.metal" type="number" min="0" :max="planet.resources.metal" placeholder="0" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="cargo-crystal" class="text-xs sm:text-sm flex items-center gap-2">
|
||||
<ResourceIcon type="crystal" size="sm" />
|
||||
{{ t('resources.crystal') }} ({{ t('fleetView.available') }}: {{ formatNumber(planet.resources.crystal) }})
|
||||
</Label>
|
||||
<Input
|
||||
id="cargo-crystal"
|
||||
v-model.number="cargo.crystal"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="planet.resources.crystal"
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="cargo-deuterium" class="text-xs sm:text-sm flex items-center gap-2">
|
||||
<!-- 任务信息 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.missionInfo') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div class="flex justify-between text-xs sm:text-sm">
|
||||
<span class="text-muted-foreground">{{ t('fleetView.fuelConsumption') }}:</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<ResourceIcon type="deuterium" size="sm" />
|
||||
{{ t('resources.deuterium') }} ({{ t('fleetView.available') }}: {{ formatNumber(planet.resources.deuterium) }})
|
||||
</Label>
|
||||
<Input
|
||||
id="cargo-deuterium"
|
||||
v-model.number="cargo.deuterium"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="planet.resources.deuterium"
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs sm:text-sm text-muted-foreground mt-2">
|
||||
{{ t('fleetView.totalCargoCapacity') }}: {{ formatNumber(getTotalCargoCapacity()) }} | {{ t('fleetView.used') }}:
|
||||
{{ formatNumber(getTotalCargo()) }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 任务信息 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('fleetView.missionInfo') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div class="flex justify-between text-xs sm:text-sm">
|
||||
<span class="text-muted-foreground">{{ t('fleetView.fuelConsumption') }}:</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<ResourceIcon type="deuterium" size="sm" />
|
||||
<span :class="getFuelConsumption() > planet.resources.deuterium ? 'text-red-600 dark:text-red-400 font-medium' : ''">
|
||||
{{ formatNumber(getFuelConsumption()) }}
|
||||
<span :class="getFuelConsumption() > planet.resources.deuterium ? 'text-red-600 dark:text-red-400 font-medium' : ''">
|
||||
{{ formatNumber(getFuelConsumption()) }}
|
||||
</span>
|
||||
<span class="text-muted-foreground">/ {{ formatNumber(planet.resources.deuterium) }}</span>
|
||||
</span>
|
||||
<span class="text-muted-foreground">/ {{ formatNumber(planet.resources.deuterium) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="Object.values(selectedFleet).some(c => c > 0)" class="flex justify-between text-xs sm:text-sm">
|
||||
<span class="text-muted-foreground">{{ t('fleetView.flightTime') }}:</span>
|
||||
<span>{{ formatTime(getFlightTime()) }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 派遣按钮 -->
|
||||
<Button @click="handleSendFleet" :disabled="!canSendFleet()" class="w-full" size="lg">{{ t('fleetView.sendFleet') }}</Button>
|
||||
</div>
|
||||
|
||||
<!-- 飞行任务 -->
|
||||
<div v-if="activeTab === 'missions'" class="space-y-4">
|
||||
<Card v-if="gameStore.player.fleetMissions.length === 0">
|
||||
<CardContent class="py-8 text-center text-muted-foreground">{{ t('fleetView.noFlightMissions') }}</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card v-for="mission in gameStore.player.fleetMissions" :key="mission.id">
|
||||
<CardHeader>
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<CardTitle class="text-base sm:text-lg">{{ getMissionName(mission.missionType) }}</CardTitle>
|
||||
<CardDescription class="text-xs sm:text-sm">
|
||||
{{ getPlanetName(mission.originPlanetId) }} → [{{ mission.targetPosition.galaxy }}:{{ mission.targetPosition.system }}:{{
|
||||
mission.targetPosition.position
|
||||
}}]
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Badge :variant="mission.status === 'outbound' ? 'default' : 'secondary'">
|
||||
{{ mission.status === 'outbound' ? t('fleetView.outbound') : t('fleetView.returning') }}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-3">
|
||||
<!-- 舰队组成 -->
|
||||
<div>
|
||||
<p class="text-xs sm:text-sm font-medium mb-2">{{ t('fleetView.fleetComposition') }}:</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Badge v-for="(count, shipType) in mission.fleet" :key="shipType" variant="outline">
|
||||
{{ SHIPS[shipType].name }}: {{ count }}
|
||||
<div v-if="Object.values(selectedFleet).some(c => c > 0)" class="flex justify-between text-xs sm:text-sm">
|
||||
<span class="text-muted-foreground">{{ t('fleetView.flightTime') }}:</span>
|
||||
<span>{{ formatTime(getFlightTime()) }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 派遣按钮 -->
|
||||
<Button @click="handleSendFleet" :disabled="!canSendFleet()" class="w-full" size="lg">{{ t('fleetView.sendFleet') }}</Button>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 飞行任务 -->
|
||||
<TabsContent value="missions" class="mt-4 space-y-4">
|
||||
<Card v-if="gameStore.player.fleetMissions.length === 0">
|
||||
<CardContent class="py-8 text-center text-muted-foreground">{{ t('fleetView.noFlightMissions') }}</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card v-for="mission in gameStore.player.fleetMissions" :key="mission.id">
|
||||
<CardHeader>
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<CardTitle class="text-base sm:text-lg">{{ getMissionName(mission.missionType) }}</CardTitle>
|
||||
<CardDescription class="text-xs sm:text-sm">
|
||||
{{ getPlanetName(mission.originPlanetId) }} → [{{ mission.targetPosition.galaxy }}:{{ mission.targetPosition.system }}:{{
|
||||
mission.targetPosition.position
|
||||
}}]
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Badge :variant="mission.status === 'outbound' ? 'default' : 'secondary'">
|
||||
{{ mission.status === 'outbound' ? t('fleetView.outbound') : t('fleetView.returning') }}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 携带资源 -->
|
||||
<div v-if="mission.cargo.metal > 0 || mission.cargo.crystal > 0 || mission.cargo.deuterium > 0 || mission.cargo.darkMatter > 0">
|
||||
<p class="text-xs sm:text-sm font-medium mb-2">{{ t('fleetView.carryingResources') }}:</p>
|
||||
<div class="flex flex-wrap gap-2 text-xs">
|
||||
<span v-if="mission.cargo.metal > 0" class="flex items-center gap-1">
|
||||
<ResourceIcon type="metal" size="sm" />
|
||||
{{ formatNumber(mission.cargo.metal) }}
|
||||
</span>
|
||||
<span v-if="mission.cargo.crystal > 0" class="flex items-center gap-1">
|
||||
<ResourceIcon type="crystal" size="sm" />
|
||||
{{ formatNumber(mission.cargo.crystal) }}
|
||||
</span>
|
||||
<span v-if="mission.cargo.deuterium > 0" class="flex items-center gap-1">
|
||||
<ResourceIcon type="deuterium" size="sm" />
|
||||
{{ formatNumber(mission.cargo.deuterium) }}
|
||||
</span>
|
||||
<span v-if="mission.cargo.darkMatter > 0" class="flex items-center gap-1">
|
||||
<ResourceIcon type="darkMatter" size="sm" />
|
||||
{{ formatNumber(mission.cargo.darkMatter) }}
|
||||
</span>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-3">
|
||||
<!-- 舰队组成 -->
|
||||
<div>
|
||||
<p class="text-xs sm:text-sm font-medium mb-2">{{ t('fleetView.fleetComposition') }}:</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Badge v-for="(count, shipType) in mission.fleet" :key="shipType" variant="outline">
|
||||
{{ SHIPS[shipType].name }}: {{ count }}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between text-xs sm:text-sm">
|
||||
<span>{{ mission.status === 'outbound' ? t('fleetView.arrivalTime') : t('fleetView.returnTime') }}:</span>
|
||||
<span>{{ formatTime(getRemainingTime(mission)) }}</span>
|
||||
<!-- 携带资源 -->
|
||||
<div v-if="mission.cargo.metal > 0 || mission.cargo.crystal > 0 || mission.cargo.deuterium > 0 || mission.cargo.darkMatter > 0">
|
||||
<p class="text-xs sm:text-sm font-medium mb-2">{{ t('fleetView.carryingResources') }}:</p>
|
||||
<div class="flex flex-wrap gap-2 text-xs">
|
||||
<span v-if="mission.cargo.metal > 0" class="flex items-center gap-1">
|
||||
<ResourceIcon type="metal" size="sm" />
|
||||
{{ formatNumber(mission.cargo.metal) }}
|
||||
</span>
|
||||
<span v-if="mission.cargo.crystal > 0" class="flex items-center gap-1">
|
||||
<ResourceIcon type="crystal" size="sm" />
|
||||
{{ formatNumber(mission.cargo.crystal) }}
|
||||
</span>
|
||||
<span v-if="mission.cargo.deuterium > 0" class="flex items-center gap-1">
|
||||
<ResourceIcon type="deuterium" size="sm" />
|
||||
{{ formatNumber(mission.cargo.deuterium) }}
|
||||
</span>
|
||||
<span v-if="mission.cargo.darkMatter > 0" class="flex items-center gap-1">
|
||||
<ResourceIcon type="darkMatter" size="sm" />
|
||||
{{ formatNumber(mission.cargo.darkMatter) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getMissionProgress(mission)" />
|
||||
</div>
|
||||
|
||||
<!-- 操作 -->
|
||||
<div class="flex gap-2">
|
||||
<Button v-if="mission.status === 'outbound'" @click="handleRecallFleet(mission.id)" variant="outline" size="sm" class="w-full">
|
||||
{{ t('fleetView.recallFleet') }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<!-- 进度条 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between text-xs sm:text-sm">
|
||||
<span>{{ mission.status === 'outbound' ? t('fleetView.arrivalTime') : t('fleetView.returnTime') }}:</span>
|
||||
<span>{{ formatTime(getRemainingTime(mission)) }}</span>
|
||||
</div>
|
||||
<Progress :model-value="getMissionProgress(mission)" />
|
||||
</div>
|
||||
|
||||
<!-- 操作 -->
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
v-if="mission.status === 'outbound'"
|
||||
@click="handleRecallFleet(mission.id)"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="w-full"
|
||||
>
|
||||
{{ t('fleetView.recallFleet') }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<!-- 提示对话框 -->
|
||||
<AlertDialog ref="alertDialog" />
|
||||
<AlertDialog :open="alertDialogOpen" @update:open="alertDialogOpen = $event">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{{ alertDialogTitle }}</AlertDialogTitle>
|
||||
<AlertDialogDescription class="whitespace-pre-line">
|
||||
{{ alertDialogMessage }}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogAction>{{ t('common.confirm') }}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useUniverseStore } from '@/stores/universeStore'
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useGameConfig } from '@/composables/useGameConfig'
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||
@@ -304,30 +337,46 @@
|
||||
import { ShipType, MissionType, BuildingType } from '@/types/game'
|
||||
import type { Fleet, Resources } from '@/types/game'
|
||||
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 { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import ResourceIcon from '@/components/ResourceIcon.vue'
|
||||
import AlertDialog from '@/components/AlertDialog.vue'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import UnlockRequirement from '@/components/UnlockRequirement.vue'
|
||||
import { Sword, Package, Rocket as RocketIcon, Eye, Users, Recycle, Skull } from 'lucide-vue-next'
|
||||
import { Sword, Package, Rocket as RocketIcon, Eye, Users, Recycle, Skull, Gift } from 'lucide-vue-next'
|
||||
import { formatNumber, formatTime } from '@/utils/format'
|
||||
import * as shipValidation from '@/logic/shipValidation'
|
||||
import * as fleetLogic from '@/logic/fleetLogic'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as diplomaticLogic from '@/logic/diplomaticLogic'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const universeStore = useUniverseStore()
|
||||
const npcStore = useNPCStore()
|
||||
const { t } = useI18n()
|
||||
const { SHIPS } = useGameConfig()
|
||||
const planet = computed(() => gameStore.currentPlanet)
|
||||
const alertDialog = ref<InstanceType<typeof AlertDialog> | null>(null)
|
||||
|
||||
// AlertDialog 状态
|
||||
const alertDialogOpen = ref(false)
|
||||
const alertDialogTitle = ref('')
|
||||
const alertDialogMessage = ref('')
|
||||
|
||||
// 当前时间(响应式)
|
||||
const currentTime = ref(Date.now())
|
||||
@@ -372,7 +421,7 @@
|
||||
currentTime.value = Date.now()
|
||||
}, 1000) // 每秒更新一次
|
||||
|
||||
const { galaxy, system, position, mission } = route.query
|
||||
const { galaxy, system, position, mission, gift } = route.query
|
||||
|
||||
// 如果有参数,填充数据
|
||||
if (galaxy || system || position) {
|
||||
@@ -388,6 +437,10 @@
|
||||
selectedMission.value = MissionType.Attack
|
||||
} else if (mission === 'colonize') {
|
||||
selectedMission.value = MissionType.Colonize
|
||||
} else if (gift === '1') {
|
||||
// 如果有gift参数,设置为运输任务并启用赠送模式
|
||||
selectedMission.value = MissionType.Transport
|
||||
isGiftMode.value = true
|
||||
}
|
||||
|
||||
// 自动切换到派遣舰队标签
|
||||
@@ -405,6 +458,26 @@
|
||||
}
|
||||
})
|
||||
|
||||
// 检查目标是否为NPC星球
|
||||
const targetNpc = computed(() => {
|
||||
return npcStore.npcs.find(npc =>
|
||||
npc.planets.some(
|
||||
p =>
|
||||
p.position.galaxy === targetPosition.value.galaxy &&
|
||||
p.position.system === targetPosition.value.system &&
|
||||
p.position.position === targetPosition.value.position
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
// 是否为赠送模式
|
||||
const isGiftMode = ref(false)
|
||||
|
||||
// 计算赠送的预估好感度增加值
|
||||
const calculateGiftReputation = (): number => {
|
||||
return diplomaticLogic.calculateGiftReputationGain(cargo.value)
|
||||
}
|
||||
|
||||
// 可用任务类型
|
||||
const availableMissions = computed(() => [
|
||||
{ type: MissionType.Attack, name: t('fleetView.attackMission'), icon: Sword },
|
||||
@@ -467,7 +540,8 @@
|
||||
if (!hasShips) return { valid: false, errorKey: 'fleetView.noShipsSelected' }
|
||||
|
||||
// 检查是否派遣到自己的星球
|
||||
if (planet.value) {
|
||||
// 回收任务和部署任务除外(回收残骸可能在同位置,部署可能到自己的月球)
|
||||
if (planet.value && selectedMission.value !== MissionType.Recycle && selectedMission.value !== MissionType.Deploy) {
|
||||
const isSamePlanet =
|
||||
targetPosition.value.galaxy === planet.value.position.galaxy &&
|
||||
targetPosition.value.system === planet.value.position.system &&
|
||||
@@ -541,6 +615,13 @@
|
||||
cargo,
|
||||
flightTime
|
||||
)
|
||||
|
||||
// 如果是赠送模式,标记任务
|
||||
if (missionType === MissionType.Transport && isGiftMode.value && targetNpc.value) {
|
||||
mission.isGift = true
|
||||
mission.giftTargetNpcId = targetNpc.value.id
|
||||
}
|
||||
|
||||
gameStore.player.fleetMissions.push(mission)
|
||||
return true
|
||||
}
|
||||
@@ -552,10 +633,9 @@
|
||||
// 验证是否可以派遣
|
||||
const validation = canSendFleet()
|
||||
if (!validation.valid) {
|
||||
alertDialog.value?.show({
|
||||
title: t('fleetView.sendFailed'),
|
||||
message: validation.errorKey ? t(validation.errorKey) : t('fleetView.sendFailedMessage')
|
||||
})
|
||||
alertDialogTitle.value = t('fleetView.sendFailed')
|
||||
alertDialogMessage.value = validation.errorKey ? t(validation.errorKey) : t('fleetView.sendFailedMessage')
|
||||
alertDialogOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -580,12 +660,12 @@
|
||||
selectedFleet.value[key as ShipType] = 0
|
||||
})
|
||||
cargo.value = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
isGiftMode.value = false
|
||||
activeTab.value = 'missions'
|
||||
} else {
|
||||
alertDialog.value?.show({
|
||||
title: t('fleetView.sendFailed'),
|
||||
message: t('fleetView.sendFailedMessage')
|
||||
})
|
||||
alertDialogTitle.value = t('fleetView.sendFailed')
|
||||
alertDialogMessage.value = t('fleetView.sendFailedMessage')
|
||||
alertDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,10 +679,9 @@
|
||||
const handleRecallFleet = (missionId: string) => {
|
||||
const success = recallFleet(missionId)
|
||||
if (!success) {
|
||||
alertDialog.value?.show({
|
||||
title: t('fleetView.recallFailed'),
|
||||
message: t('fleetView.recallFailedMessage')
|
||||
})
|
||||
alertDialogTitle.value = t('fleetView.recallFailed')
|
||||
alertDialogMessage.value = t('fleetView.recallFailedMessage')
|
||||
alertDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user