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
}
}
}