mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +08:00
feat(本地化与工具): 为排序功能添加升序/降序翻译并统一 ID 生成
- 为所有支持的语言添加排序功能的 "ascending" 和 "descending" 翻译 - 创建统一的 ID 生成工具函数 generateId,用于生成带前缀的业务 ID - 重构多个逻辑模块(battleLogic、moonLogic 等)使用新的 ID 生成函数 - 改进 GM 视图的类型安全,添加预设数据验证和类型保护
This commit is contained in:
@@ -80,7 +80,7 @@
|
||||
<!-- 建筑/科技/舰船/防御/军官 - 统一配置渲染 -->
|
||||
<TabsContent v-for="section in gmSections" :key="section.tabValue" :value="section.tabValue" class="space-y-4">
|
||||
<!-- 预设操作区 -->
|
||||
<Card v-if="section.tabValue !== 'officers'" class="mb-4">
|
||||
<Card v-if="isPresettableSection(section)" class="mb-4">
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-lg">{{ t('gmView.presets') || 'Presets' }}</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -327,20 +327,81 @@
|
||||
values: Record<string, number>
|
||||
}
|
||||
|
||||
const presetOverwriteDialogOpen = ref(false)
|
||||
const pendingPresetToOverwrite = ref<{
|
||||
section: any
|
||||
type GMSectionTabValue = 'buildings' | 'research' | 'ships' | 'defense' | 'officers'
|
||||
type GMPresetSectionKey = Exclude<GMSectionTabValue, 'officers'>
|
||||
|
||||
type GMSection = {
|
||||
tabValue: GMSectionTabValue
|
||||
titleKey: string
|
||||
descKey: string
|
||||
items: string[]
|
||||
max?: number
|
||||
placeholder?: string
|
||||
buttons: { label: string; value: number }[]
|
||||
getItemName: (item: string) => string
|
||||
getValue: (item: string) => number
|
||||
setValue: (item: string, val: number) => void
|
||||
onButtonClick: (item: string, val: number) => void
|
||||
}
|
||||
|
||||
type GMPresetSection = GMSection & {
|
||||
tabValue: GMPresetSectionKey
|
||||
}
|
||||
|
||||
interface PendingPresetOverwrite {
|
||||
section: GMPresetSection
|
||||
name: string
|
||||
values: Record<string, number>
|
||||
existingIndex: number
|
||||
} | null>(null)
|
||||
|
||||
const getPresets = (type: string): GMPreset[] => {
|
||||
const data = localStorage.getItem(`gm_presets_${type}`)
|
||||
return data ? JSON.parse(data) : []
|
||||
}
|
||||
|
||||
const savePresets = (type: string, presets: GMPreset[]) => {
|
||||
// 校验预设结构,避免历史脏数据污染当前视图
|
||||
const isGMPreset = (value: unknown): value is GMPreset => {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
const preset = value as Partial<GMPreset>
|
||||
return typeof preset.id === 'string' && typeof preset.name === 'string' && typeof preset.values === 'object' && preset.values !== null
|
||||
}
|
||||
|
||||
// 只有建筑/科技/舰船/防御页支持预设
|
||||
const isPresettableSection = (section: GMSection): section is GMPresetSection => {
|
||||
return section.tabValue !== 'officers'
|
||||
}
|
||||
|
||||
const presetOverwriteDialogOpen = ref(false)
|
||||
const pendingPresetToOverwrite = ref<PendingPresetOverwrite | null>(null)
|
||||
|
||||
const getPresets = (type: GMPresetSectionKey): GMPreset[] => {
|
||||
const key = `gm_presets_${type}`
|
||||
const data = localStorage.getItem(key)
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
// 兼容旧版本或手动修改导致的损坏数据,避免页面因解析失败崩溃
|
||||
const parsed = JSON.parse(data)
|
||||
if (!Array.isArray(parsed)) {
|
||||
localStorage.removeItem(key)
|
||||
return []
|
||||
}
|
||||
|
||||
const presets = parsed.filter(isGMPreset)
|
||||
// 过滤掉结构不完整的预设,并顺手回写清理后的结果
|
||||
if (presets.length !== parsed.length) {
|
||||
localStorage.setItem(key, JSON.stringify(presets))
|
||||
}
|
||||
|
||||
return presets
|
||||
} catch {
|
||||
localStorage.removeItem(key)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const savePresets = (type: GMPresetSectionKey, presets: GMPreset[]) => {
|
||||
localStorage.setItem(`gm_presets_${type}`, JSON.stringify(presets))
|
||||
}
|
||||
|
||||
@@ -365,7 +426,9 @@
|
||||
defense: getPresets('defense')
|
||||
})
|
||||
|
||||
const handleSavePreset = (section: any) => {
|
||||
const handleSavePreset = (section: GMSection) => {
|
||||
if (!isPresettableSection(section)) return
|
||||
|
||||
const name = presetNames.value[section.tabValue]?.trim()
|
||||
if (!name) {
|
||||
toast.error(t('gmView.presetNameRequired') || '请输入预设名称')
|
||||
@@ -432,7 +495,9 @@
|
||||
pendingPresetToOverwrite.value = null
|
||||
}
|
||||
|
||||
const handleDeletePreset = (section: any) => {
|
||||
const handleDeletePreset = (section: GMSection) => {
|
||||
if (!isPresettableSection(section)) return
|
||||
|
||||
const presetId = selectedPresets.value[section.tabValue]
|
||||
if (!presetId || presetId === 'default') {
|
||||
toast.error(t('gmView.cannotDeleteDefault') || '无法删除默认预设')
|
||||
@@ -450,7 +515,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
const handleApplyPreset = (section: any) => {
|
||||
const handleApplyPreset = (section: GMSection) => {
|
||||
if (!isPresettableSection(section)) return
|
||||
|
||||
const presetId = selectedPresets.value[section.tabValue]
|
||||
if (!presetId) return
|
||||
|
||||
@@ -487,18 +554,17 @@
|
||||
|
||||
// 重新计算最大舰队仓储,确保数据是最新的
|
||||
const maxStorage = calculateMaxFleetStorage(selectedPlanet.value, gameStore.player.technologies)
|
||||
|
||||
const shipTypes = Object.values(ShipType)
|
||||
|
||||
// 将总容量平均分配给每种舰船
|
||||
const storagePerShip = maxStorage / shipTypes.length
|
||||
|
||||
shipTypes.forEach(type => {
|
||||
const usage = SHIPS.value[type]?.storageUsage || 1
|
||||
const storagePerShip = maxStorage / section.items.length
|
||||
|
||||
section.items.forEach(item => {
|
||||
const usage = SHIPS.value[item as ShipType]?.storageUsage || 1
|
||||
// 如果 usage 为 0 (如某些特殊单位),则给予一个默认数量,或者跳过
|
||||
if (usage <= 0) {
|
||||
section.setValue(type, 100) // 防止除以0,给予固定值
|
||||
section.setValue(item, 100) // 防止除以0,给予固定值
|
||||
} else {
|
||||
section.setValue(type, Math.floor(storagePerShip / usage))
|
||||
section.setValue(item, Math.floor(storagePerShip / usage))
|
||||
}
|
||||
})
|
||||
} else if (section.tabValue === 'defense') {
|
||||
@@ -512,7 +578,7 @@
|
||||
// 反弹道导弹占用1个空间,分配一半容量
|
||||
section.setValue(item, Math.floor(halfCapacity))
|
||||
} else if (item === DefenseType.InterplanetaryMissile) {
|
||||
// 星际导弹占用2个空间,分配一半容量
|
||||
// 星际导弹占用1个空间,分配一半容量
|
||||
section.setValue(item, Math.floor(halfCapacity))
|
||||
} else {
|
||||
section.setValue(item, 10000)
|
||||
@@ -606,22 +672,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// GM编辑区块配置 - 统一管理建筑/科技/舰船/防御/军官
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type GMSection = {
|
||||
tabValue: string
|
||||
titleKey: string
|
||||
descKey: string
|
||||
items: string[]
|
||||
max?: number
|
||||
placeholder?: string
|
||||
buttons: { label: string; value: number }[]
|
||||
getItemName: (item: any) => string
|
||||
getValue: (item: any) => number
|
||||
setValue: (item: any, val: number) => void
|
||||
onButtonClick: (item: any, val: number) => void
|
||||
}
|
||||
|
||||
const gmSections = computed<GMSection[]>(() => [
|
||||
{
|
||||
tabValue: 'buildings',
|
||||
@@ -634,17 +684,17 @@
|
||||
{ label: 'Lv 10', value: 10 },
|
||||
{ label: 'Lv 30', value: 30 }
|
||||
],
|
||||
getItemName: (item: BuildingType) => BUILDINGS.value[item].name,
|
||||
getValue: (item: BuildingType) => selectedPlanet.value?.buildings[item] || 0,
|
||||
setValue: (item: BuildingType, val: number) => {
|
||||
getItemName: item => BUILDINGS.value[item as BuildingType].name,
|
||||
getValue: item => selectedPlanet.value?.buildings[item as BuildingType] || 0,
|
||||
setValue: (item, val) => {
|
||||
if (selectedPlanet.value) {
|
||||
selectedPlanet.value.buildings[item] = val
|
||||
selectedPlanet.value.buildings[item as BuildingType] = val
|
||||
updatePlayerPoints()
|
||||
}
|
||||
},
|
||||
onButtonClick: (item: BuildingType, val: number) => {
|
||||
onButtonClick: (item, val) => {
|
||||
if (selectedPlanet.value) {
|
||||
selectedPlanet.value.buildings[item] = val
|
||||
selectedPlanet.value.buildings[item as BuildingType] = val
|
||||
updatePlayerPoints()
|
||||
}
|
||||
}
|
||||
@@ -660,14 +710,14 @@
|
||||
{ label: 'Lv 10', value: 10 },
|
||||
{ label: 'Lv 20', value: 20 }
|
||||
],
|
||||
getItemName: (item: TechnologyType) => TECHNOLOGIES.value[item].name,
|
||||
getValue: (item: TechnologyType) => gameStore.player.technologies[item] || 0,
|
||||
setValue: (item: TechnologyType, val: number) => {
|
||||
gameStore.player.technologies[item] = val
|
||||
getItemName: item => TECHNOLOGIES.value[item as TechnologyType].name,
|
||||
getValue: item => gameStore.player.technologies[item as TechnologyType] || 0,
|
||||
setValue: (item, val) => {
|
||||
gameStore.player.technologies[item as TechnologyType] = val
|
||||
updatePlayerPoints()
|
||||
},
|
||||
onButtonClick: (item: TechnologyType, val: number) => {
|
||||
gameStore.player.technologies[item] = val
|
||||
onButtonClick: (item, val) => {
|
||||
gameStore.player.technologies[item as TechnologyType] = val
|
||||
updatePlayerPoints()
|
||||
}
|
||||
},
|
||||
@@ -682,17 +732,17 @@
|
||||
{ label: '+100', value: 100 },
|
||||
{ label: '+1K', value: 1000 }
|
||||
],
|
||||
getItemName: (item: ShipType) => SHIPS.value[item].name,
|
||||
getValue: (item: ShipType) => selectedPlanet.value?.fleet[item] || 0,
|
||||
setValue: (item: ShipType, val: number) => {
|
||||
getItemName: item => SHIPS.value[item as ShipType].name,
|
||||
getValue: item => selectedPlanet.value?.fleet[item as ShipType] || 0,
|
||||
setValue: (item, val) => {
|
||||
if (selectedPlanet.value) {
|
||||
selectedPlanet.value.fleet[item] = val
|
||||
selectedPlanet.value.fleet[item as ShipType] = val
|
||||
updatePlayerPoints()
|
||||
}
|
||||
},
|
||||
onButtonClick: (item: ShipType, val: number) => {
|
||||
onButtonClick: (item, val) => {
|
||||
if (selectedPlanet.value) {
|
||||
selectedPlanet.value.fleet[item] = (selectedPlanet.value.fleet[item] || 0) + val
|
||||
selectedPlanet.value.fleet[item as ShipType] = (selectedPlanet.value.fleet[item as ShipType] || 0) + val
|
||||
updatePlayerPoints()
|
||||
}
|
||||
}
|
||||
@@ -708,17 +758,17 @@
|
||||
{ label: '+100', value: 100 },
|
||||
{ label: '+1K', value: 1000 }
|
||||
],
|
||||
getItemName: (item: DefenseType) => DEFENSES.value[item].name,
|
||||
getValue: (item: DefenseType) => selectedPlanet.value?.defense[item] || 0,
|
||||
setValue: (item: DefenseType, val: number) => {
|
||||
getItemName: item => DEFENSES.value[item as DefenseType].name,
|
||||
getValue: item => selectedPlanet.value?.defense[item as DefenseType] || 0,
|
||||
setValue: (item, val) => {
|
||||
if (selectedPlanet.value) {
|
||||
selectedPlanet.value.defense[item] = val
|
||||
selectedPlanet.value.defense[item as DefenseType] = val
|
||||
updatePlayerPoints()
|
||||
}
|
||||
},
|
||||
onButtonClick: (item: DefenseType, val: number) => {
|
||||
onButtonClick: (item, val) => {
|
||||
if (selectedPlanet.value) {
|
||||
selectedPlanet.value.defense[item] = (selectedPlanet.value.defense[item] || 0) + val
|
||||
selectedPlanet.value.defense[item as DefenseType] = (selectedPlanet.value.defense[item as DefenseType] || 0) + val
|
||||
updatePlayerPoints()
|
||||
}
|
||||
}
|
||||
@@ -735,27 +785,28 @@
|
||||
{ label: `30${t('gmView.days')}`, value: 30 },
|
||||
{ label: `365${t('gmView.days')}`, value: 365 }
|
||||
],
|
||||
getItemName: (item: OfficerType) => OFFICERS.value[item].name,
|
||||
getValue: (item: OfficerType) => officerDays.value[item] || 0,
|
||||
setValue: (item: OfficerType, val: number) => {
|
||||
officerDays.value[item] = val
|
||||
getItemName: item => OFFICERS.value[item as OfficerType].name,
|
||||
getValue: item => officerDays.value[item as OfficerType] || 0,
|
||||
setValue: (item, val) => {
|
||||
officerDays.value[item as OfficerType] = val
|
||||
},
|
||||
onButtonClick: (item: OfficerType, days: number) => {
|
||||
officerDays.value[item] = days
|
||||
onButtonClick: (item, days) => {
|
||||
const officerType = item as OfficerType
|
||||
officerDays.value[officerType] = days
|
||||
const now = Date.now()
|
||||
const expiresAt = now + days * 24 * 60 * 60 * 1000
|
||||
if (!gameStore.player.officers[item]) {
|
||||
gameStore.player.officers[item] = {
|
||||
type: item,
|
||||
if (!gameStore.player.officers[officerType]) {
|
||||
gameStore.player.officers[officerType] = {
|
||||
type: officerType,
|
||||
active: true,
|
||||
hiredAt: now,
|
||||
expiresAt: expiresAt
|
||||
}
|
||||
} else {
|
||||
gameStore.player.officers[item].expiresAt = expiresAt
|
||||
gameStore.player.officers[item].active = true
|
||||
if (!gameStore.player.officers[item].hiredAt) {
|
||||
gameStore.player.officers[item].hiredAt = now
|
||||
gameStore.player.officers[officerType].expiresAt = expiresAt
|
||||
gameStore.player.officers[officerType].active = true
|
||||
if (!gameStore.player.officers[officerType].hiredAt) {
|
||||
gameStore.player.officers[officerType].hiredAt = now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user