mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 16:05:12 +08:00
feat: 初始化项目结构与核心功能
引入项目基础目录结构,包含多语言支持、主要页面与组件、核心游戏逻辑、UI 组件库、加密与本地持久化、自动化 Docker 构建流程、GitHub issue 模板(中英文)、README(中英文)、LICENSE 及开发配置文件。实现 OGame 单机版主要功能模块,为后续开发和扩展奠定基础。
This commit is contained in:
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user