diff --git a/src/locales/de.ts b/src/locales/de.ts index 3ef58e2..7c09158 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -1201,6 +1201,8 @@ export default { diplomacy: { sort: { label: 'Sortieren', + ascending: 'Aufsteigend', + descending: 'Absteigend', reputation: 'Ruf', planets: 'Planeten', difficulty: 'Schwierigkeit', diff --git a/src/locales/en.ts b/src/locales/en.ts index f7c1a0d..9fa0b90 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -1307,6 +1307,8 @@ export default { searchPlaceholder: 'Search NPC name...', sort: { label: 'Sort', + ascending: 'Ascending', + descending: 'Descending', reputation: 'Reputation', planets: 'Planets', difficulty: 'Difficulty', diff --git a/src/locales/es-LA.ts b/src/locales/es-LA.ts index 19d8899..9067712 100644 --- a/src/locales/es-LA.ts +++ b/src/locales/es-LA.ts @@ -1209,6 +1209,8 @@ export default { diplomacy: { sort: { label: 'Ordenar', + ascending: 'Ascendente', + descending: 'Descendente', reputation: 'Reputación', planets: 'Planetas', difficulty: 'Dificultad', diff --git a/src/locales/ja.ts b/src/locales/ja.ts index 14956bb..ab3d6dc 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -1226,6 +1226,8 @@ export default { diplomacy: { sort: { label: '並び替え', + ascending: '昇順', + descending: '降順', reputation: '評判', planets: '惑星', difficulty: '難易度', diff --git a/src/locales/ko.ts b/src/locales/ko.ts index 41b2bb8..c4553cd 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -1176,6 +1176,8 @@ export default { diplomacy: { sort: { label: '정렬', + ascending: '오름차순', + descending: '내림차순', reputation: '평판', planets: '행성', difficulty: '난이도', diff --git a/src/locales/ru.ts b/src/locales/ru.ts index 567a6a9..8823039 100644 --- a/src/locales/ru.ts +++ b/src/locales/ru.ts @@ -1202,6 +1202,8 @@ export default { diplomacy: { sort: { label: 'Сортировка', + ascending: 'По возрастанию', + descending: 'По убыванию', reputation: 'Репутация', planets: 'Планеты', difficulty: 'Сложность', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 6858d40..f6e5996 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -1275,6 +1275,8 @@ export default { searchPlaceholder: '搜索NPC名称...', sort: { label: '排序', + ascending: '升序', + descending: '降序', reputation: '好感度', planets: '星球数量', difficulty: '难度', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 6df86c9..3084b05 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -1195,6 +1195,8 @@ export default { diplomacy: { sort: { label: '排序', + ascending: '升序', + descending: '降序', reputation: '聲望', planets: '星球', difficulty: '難度', diff --git a/src/logic/battleLogic.ts b/src/logic/battleLogic.ts index 4244a1a..1cf52b2 100644 --- a/src/logic/battleLogic.ts +++ b/src/logic/battleLogic.ts @@ -2,6 +2,7 @@ import type { Fleet, Resources, BattleResult, Officer, TechnologyType } from '@/ import { DefenseType, OfficerType } from '@/types/game' import { workerManager } from '@/workers/workerManager' import { MOON_CONFIG } from '@/config/gameConfig' +import { generateId } from '@/utils/id' /** * 执行战斗模拟 @@ -66,7 +67,7 @@ export const simulateBattle = async ( // 生成战斗报告 const battleResult: BattleResult = { - id: `battle_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, + id: generateId('battle'), timestamp: Date.now(), attackerId: '', defenderId: '', diff --git a/src/logic/campaignLogic.ts b/src/logic/campaignLogic.ts index 0820654..b16a509 100644 --- a/src/logic/campaignLogic.ts +++ b/src/logic/campaignLogic.ts @@ -19,6 +19,7 @@ import { type ShipType } from '@/types/game' import { MAIN_CAMPAIGN, getAllQuests, getQuestById, getQuestsByChapter } from '@/config/campaignConfig' +import { generateId } from '@/utils/id' import * as resourceLogic from './resourceLogic' /** @@ -515,7 +516,7 @@ export const createQuestNotification = ( ): QuestNotification => { const quest = getQuestById(questId) return { - id: `quest_notification_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, + id: generateId('quest_notification'), timestamp: Date.now(), questId, questTitleKey: quest?.titleKey || '', diff --git a/src/logic/fleetLogic.ts b/src/logic/fleetLogic.ts index a377037..b6e9c42 100644 --- a/src/logic/fleetLogic.ts +++ b/src/logic/fleetLogic.ts @@ -3,6 +3,7 @@ import type { Locale } from '@/locales' import { ShipType, DefenseType, MissionType, BuildingType, OfficerType, TechnologyType, ExpeditionZone } from '@/types/game' import { FLEET_STORAGE_CONFIG, EXPEDITION_ZONES } from '@/config/gameConfig' import { useGameStore } from '@/stores/gameStore' +import { generateId } from '@/utils/id' import * as battleLogic from './battleLogic' import * as moonLogic from './moonLogic' import * as moonValidation from './moonValidation' @@ -61,7 +62,7 @@ export const createFleetMission = ( ): FleetMission => { const now = Date.now() return { - id: `mission_${now}_${Math.random().toString(36).substring(2, 9)}`, + id: generateId('mission', now), playerId, originPlanetId, // 深拷贝targetPosition,避免多个任务共享同一个引用 @@ -171,7 +172,7 @@ export const processAttackArrival = async ( ) // 更新战斗报告ID - battleResult.id = `battle_${Date.now()}` + battleResult.id = generateId('battle') battleResult.attackerId = attacker.id battleResult.defenderId = targetPlanet.ownerId || 'unknown' battleResult.attackerPlanetId = mission.originPlanetId @@ -267,7 +268,7 @@ export const processNPCAttackArrival = async ( ) // 更新战斗报告ID和参与者信息 - battleResult.id = `battle_${Date.now()}` + battleResult.id = generateId('battle') battleResult.attackerId = npc.id battleResult.defenderId = defender.id battleResult.attackerPlanetId = mission.originPlanetId @@ -414,7 +415,7 @@ export const processColonizeArrival = ( // 创建新殖民地 const newPlanet: Planet = { - id: `planet_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, + id: generateId('planet'), name: `${colonyNameTemplate} ${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`, ownerId: player.id, position: mission.targetPosition, @@ -564,7 +565,7 @@ export const processSpyArrival = ( const wasDetected = Math.random() < detectionChance const spyReport: SpyReport = { - id: `spy_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, + id: generateId('spy'), timestamp: Date.now(), spyId: attacker.id, targetPlanetId: targetPlanet.id, @@ -1050,7 +1051,7 @@ export const processDestroyArrival = async ( ) // 更新战斗报告 - battleResult.id = `battle_${Date.now()}` + battleResult.id = generateId('battle') battleResult.attackerId = attacker.id battleResult.defenderId = targetPlanet.ownerId || 'unknown' battleResult.attackerPlanetId = mission.originPlanetId diff --git a/src/logic/moonLogic.ts b/src/logic/moonLogic.ts index ac8378a..df85588 100644 --- a/src/logic/moonLogic.ts +++ b/src/logic/moonLogic.ts @@ -1,6 +1,7 @@ import type { Planet, Resources } from '@/types/game' import { BuildingType, ShipType, DefenseType } from '@/types/game' import { MOON_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig' +import { generateId } from '@/utils/id' /** * 计算月球生成概率 @@ -79,7 +80,7 @@ export const tryGenerateMoon = ( // 生成月球 const moon: Planet = { - id: `moon_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, + id: generateId('moon'), name: `Moon [${planetPosition.galaxy}:${planetPosition.system}:${planetPosition.position}]`, ownerId: playerId, position: planetPosition, diff --git a/src/logic/planetLogic.ts b/src/logic/planetLogic.ts index a13a5b2..6e8cf45 100644 --- a/src/logic/planetLogic.ts +++ b/src/logic/planetLogic.ts @@ -1,6 +1,7 @@ import type { Planet, Resources } from '@/types/game' import { ShipType, DefenseType, BuildingType } from '@/types/game' import { MOON_CONFIG, PLANET_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig' +import { generateId } from '@/utils/id' import * as oreDepositLogic from './oreDepositLogic' /** @@ -173,7 +174,7 @@ export const createMoon = ( moonSuffix: string = "'s Moon", diameter?: number ): Planet => { - const moonId = `moon_${Date.now()}_${Math.random().toString(36).substring(2, 9)}` + const moonId = generateId('moon') const moon: Planet = { id: moonId, name: `${parentPlanet.name}${moonSuffix}`, diff --git a/src/utils/id.ts b/src/utils/id.ts new file mode 100644 index 0000000..56c6515 --- /dev/null +++ b/src/utils/id.ts @@ -0,0 +1,7 @@ +/** + * 统一生成带前缀的业务ID + * 便于后续集中调整ID规则 + */ +export const generateId = (prefix: string, timestamp: number = Date.now()): string => { + return `${prefix}_${timestamp}_${Math.random().toString(36).slice(2, 9)}` +} diff --git a/src/views/DiplomacyView.vue b/src/views/DiplomacyView.vue index d1d0939..aa3d78a 100644 --- a/src/views/DiplomacyView.vue +++ b/src/views/DiplomacyView.vue @@ -207,7 +207,7 @@ variant="outline" size="icon" @click="sortOrder = sortOrder === 'asc' ? 'desc' : 'asc'" - :title="sortOrder === 'asc' ? 'Ascending' : 'Descending'" + :title="sortOrder === 'asc' ? t('diplomacy.sort.ascending') : t('diplomacy.sort.descending')" > diff --git a/src/views/GMView.vue b/src/views/GMView.vue index fcd5751..0603e40 100644 --- a/src/views/GMView.vue +++ b/src/views/GMView.vue @@ -80,7 +80,7 @@ - + {{ t('gmView.presets') || 'Presets' }} @@ -327,20 +327,81 @@ values: Record } - const presetOverwriteDialogOpen = ref(false) - const pendingPresetToOverwrite = ref<{ - section: any + type GMSectionTabValue = 'buildings' | 'research' | 'ships' | 'defense' | 'officers' + type GMPresetSectionKey = Exclude + + 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 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 + 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(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(() => [ { 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 } } }