feat: 新增Android平台支持及构建流程

集成Android平台相关目录与配置文件,包含Gradle构建脚本、资源文件、启动图标、Java入口、Proguard规则等,完善.gitignore以排除Android构建产物。更新CI流程,支持自动构建并发布Android APK。移除README中项目结构说明,简化文档。
This commit is contained in:
谦君
2025-12-20 00:48:36 +08:00
parent 20fb2bb6a4
commit 1368bb4445
97 changed files with 7859 additions and 335 deletions

View File

@@ -157,7 +157,7 @@
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty'
import { Siren, Eye, Sword, Shield, Globe } from 'lucide-vue-next'
import { Siren, Eye, Sword, Shield, Globe, Recycle } from 'lucide-vue-next'
import { MissionType } from '@/types/game'
import type { IncomingFleetAlert } from '@/types/game'
import { formatDate, formatTime } from '@/utils/format'
@@ -200,6 +200,8 @@
return Eye
case MissionType.Attack:
return Sword
case MissionType.Recycle:
return Recycle
default:
return Siren
}
@@ -212,6 +214,8 @@
return 'text-purple-500'
case MissionType.Attack:
return 'text-red-500'
case MissionType.Recycle:
return 'text-amber-500'
default:
return 'text-yellow-500'
}
@@ -229,6 +233,8 @@
return t('enemyAlert.missionType.spy')
case MissionType.Attack:
return t('enemyAlert.missionType.attack')
case MissionType.Recycle:
return t('enemyAlert.missionType.recycle')
default:
return t('enemyAlert.missionType.unknown')
}
@@ -241,6 +247,8 @@
return t('enemyAlert.warning.spy')
case MissionType.Attack:
return t('enemyAlert.warning.attack')
case MissionType.Recycle:
return t('enemyAlert.warning.recycle')
default:
return t('enemyAlert.warning.unknown')
}

View File

@@ -0,0 +1,72 @@
<template>
<div v-if="showWarning" class="bg-destructive/10 border-b border-destructive/20">
<div class="px-4 sm:px-6 py-2 flex items-center justify-between gap-3">
<!-- 警告图标和信息 -->
<div class="flex items-center gap-2 flex-1 min-w-0">
<Zap class="h-5 w-5 text-destructive flex-shrink-0 animate-pulse" />
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold text-destructive">
{{ t('energy.lowWarning') }}
</p>
<p class="text-xs text-muted-foreground">
{{ detailMessage }}
</p>
</div>
</div>
<!-- 建造电站按钮 -->
<Button @click="goToBuildSolarPlant" variant="outline" size="sm" class="flex-shrink-0">
{{ t('energy.buildSolarPlant') }}
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useGameStore } from '@/stores/gameStore'
import { Button } from '@/components/ui/button'
import { Zap } from 'lucide-vue-next'
import { useI18n } from '@/composables/useI18n'
import * as resourceLogic from '@/logic/resourceLogic'
import * as officerLogic from '@/logic/officerLogic'
const gameStore = useGameStore()
const router = useRouter()
const { t } = useI18n()
// 获取当前星球
const planet = computed(() => gameStore.currentPlanet)
// 计算能量产量
const energyProduction = computed(() => {
if (!planet.value) return 0
const now = Date.now()
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, now)
return resourceLogic.calculateEnergyProduction(planet.value, { energyProductionBonus: bonuses.energyProductionBonus })
})
// 计算能量消耗
const energyConsumption = computed(() => {
if (!planet.value) return 0
return resourceLogic.calculateEnergyConsumption(planet.value)
})
// 是否显示警告(电力产量 < 消耗)
const showWarning = computed(() => {
if (!planet.value) return false
return energyProduction.value < energyConsumption.value
})
// 详细消息
const detailMessage = computed(() => {
const deficit = Math.ceil(energyConsumption.value - energyProduction.value)
return t('energy.deficitDetail', { deficit: deficit.toString() })
})
// 跳转到建筑页面建造太阳能电站
const goToBuildSolarPlant = () => {
router.push('/buildings')
}
</script>

View File

@@ -4,12 +4,20 @@
<CardHeader>
<div class="flex items-start justify-between">
<div class="flex-1">
<CardTitle class="flex items-center gap-2">
<CardTitle class="flex items-center gap-2 flex-wrap">
{{ npc.name }}
<span v-if="npc.note" class="text-muted-foreground font-normal">({{ npc.note }})</span>
<Badge :variant="statusBadgeVariant">
{{ statusText }}
</Badge>
<!-- NPC难度等级徽章 -->
<Badge
v-if="npc.difficultyLevel"
:variant="difficultyBadgeVariant"
:class="difficultyLevelColor"
>
Lv.{{ npc.difficultyLevel }}
</Badge>
</CardTitle>
<CardDescription class="mt-1">
{{ npc.planets.length }} {{ t('diplomacy.planets') }}
@@ -208,6 +216,28 @@
return 'text-muted-foreground'
})
// NPC难度等级颜色
const difficultyLevelColor = computed(() => {
const level = props.npc.difficultyLevel
if (!level) return 'text-muted-foreground'
if (level <= 1) return 'text-green-600 dark:text-green-400' // 新手
if (level <= 2) return 'text-lime-600 dark:text-lime-400' // 简单
if (level <= 3) return 'text-yellow-600 dark:text-yellow-400' // 普通
if (level <= 4) return 'text-orange-600 dark:text-orange-400' // 困难
if (level <= 5) return 'text-red-600 dark:text-red-400' // 专家
if (level <= 6) return 'text-purple-600 dark:text-purple-400' // 大师
return 'text-pink-600 dark:text-pink-400' // 传奇及以上
})
// NPC难度等级Badge样式
const difficultyBadgeVariant = computed((): 'default' | 'secondary' | 'destructive' | 'outline' => {
const level = props.npc.difficultyLevel
if (!level) return 'outline'
if (level <= 2) return 'secondary'
if (level <= 4) return 'default'
return 'destructive'
})
// 最近的外交事件
const recentEvent = computed(() => {
if (!props.relation?.history || props.relation.history.length === 0) return null

View File

@@ -18,6 +18,15 @@
<div class="flex items-center gap-2">
<span class="font-medium truncate">{{ npc.name }}</span>
<span v-if="npc.note" class="text-muted-foreground text-sm truncate">({{ npc.note }})</span>
<!-- NPC难度等级徽章 -->
<Badge
v-if="npc.difficultyLevel"
:variant="difficultyBadgeVariant"
class="text-xs"
:class="difficultyLevelColor"
>
Lv.{{ npc.difficultyLevel }}
</Badge>
</div>
<div class="text-xs text-muted-foreground">
{{ npc.planets.length }} {{ t('diplomacy.planets') }}
@@ -68,9 +77,18 @@
'bg-gray-400': status === RelationStatus.Neutral
}"
/>
<div class="flex-1 min-w-0">
<div class="flex-1 min-w-0 flex items-center gap-1 flex-wrap">
<span class="font-medium truncate">{{ npc.name }}</span>
<span v-if="npc.note" class="text-muted-foreground text-sm ml-1">({{ npc.note }})</span>
<span v-if="npc.note" class="text-muted-foreground text-sm">({{ npc.note }})</span>
<!-- NPC难度等级徽章 (移动端) -->
<Badge
v-if="npc.difficultyLevel"
:variant="difficultyBadgeVariant"
class="text-xs"
:class="difficultyLevelColor"
>
Lv.{{ npc.difficultyLevel }}
</Badge>
</div>
<ChevronDown class="h-4 w-4 text-muted-foreground transition-transform flex-shrink-0" :class="{ 'rotate-180': isExpanded }" />
</div>
@@ -214,6 +232,28 @@
return 'text-muted-foreground'
})
// NPC难度等级颜色
const difficultyLevelColor = computed(() => {
const level = props.npc.difficultyLevel
if (!level) return 'text-muted-foreground'
if (level <= 1) return 'text-green-600 dark:text-green-400' // 新手
if (level <= 2) return 'text-lime-600 dark:text-lime-400' // 简单
if (level <= 3) return 'text-yellow-600 dark:text-yellow-400' // 普通
if (level <= 4) return 'text-orange-600 dark:text-orange-400' // 困难
if (level <= 5) return 'text-red-600 dark:text-red-400' // 专家
if (level <= 6) return 'text-purple-600 dark:text-purple-400' // 大师
return 'text-pink-600 dark:text-pink-400' // 传奇及以上
})
// NPC难度等级Badge样式
const difficultyBadgeVariant = computed((): 'default' | 'secondary' | 'destructive' | 'outline' => {
const level = props.npc.difficultyLevel
if (!level) return 'outline'
if (level <= 2) return 'secondary'
if (level <= 4) return 'default'
return 'destructive'
})
// 最近的外交事件
const recentEvent = computed(() => {
if (!props.relation?.history || props.relation.history.length === 0) return null

View File

@@ -173,7 +173,8 @@
const getQueueProgress = (item: BuildQueueItem): number => {
const elapsed = currentTime.value - item.startTime
const total = item.endTime - item.startTime
return Math.min(100, (elapsed / total) * 100)
if (total <= 0) return 100
return Math.max(0, Math.min(100, (elapsed / total) * 100))
}
// 统一的取消处理