refactor frontend

This commit is contained in:
bridge
2025-12-04 21:33:14 +08:00
parent 880e83c53e
commit ef0ff24783
11 changed files with 253 additions and 219 deletions

67
web/src/stores/socket.ts Normal file
View File

@@ -0,0 +1,67 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { gameSocket } from '../api/socket';
import { useWorldStore } from './world';
import { useUiStore } from './ui';
import type { TickPayloadDTO } from '../types/api';
export const useSocketStore = defineStore('socket', () => {
const isConnected = ref(false);
const lastError = ref<string | null>(null);
let cleanupMessage: (() => void) | undefined;
let cleanupStatus: (() => void) | undefined;
function init() {
if (cleanupStatus) return; // Already initialized
const worldStore = useWorldStore();
const uiStore = useUiStore();
// Listen for status
cleanupStatus = gameSocket.onStatusChange((connected) => {
isConnected.value = connected;
if (connected) {
lastError.value = null;
}
});
// Listen for ticks
cleanupMessage = gameSocket.on((data: any) => {
if (data.type === 'tick') {
const payload = data as TickPayloadDTO;
// Update World
worldStore.handleTick(payload);
// UI Cache Invalidations
uiStore.clearHoverCache();
// Refresh Detail if open (Silent update)
if (uiStore.selectedTarget) {
uiStore.refreshDetail();
}
}
});
// Connect socket
gameSocket.connect();
}
function disconnect() {
if (cleanupMessage) cleanupMessage();
if (cleanupStatus) cleanupStatus();
cleanupMessage = undefined;
cleanupStatus = undefined;
gameSocket.disconnect();
isConnected.value = false;
}
return {
isConnected,
lastError,
init,
disconnect
};
});

View File

@@ -3,6 +3,7 @@ import { ref, shallowRef, computed } from 'vue';
import type { AvatarSummary, GameEvent, MapMatrix, RegionSummary, CelestialPhenomenon } from '../types/core';
import type { TickPayloadDTO, InitialStateDTO } from '../types/api';
import { gameApi } from '../api/game';
import { processNewEvents, mergeAndSortEvents } from '../utils/eventHelper';
export const useWorldStore = defineStore('world', () => {
// --- State ---
@@ -65,50 +66,8 @@ export const useWorldStore = defineStore('world', () => {
function addEvents(rawEvents: any[]) {
if (!rawEvents || rawEvents.length === 0) return;
// 转换 DTO -> Domain
// 增加临时索引 _seq 记录原始逻辑顺序,用于同时间戳事件的排序
const newEvents: GameEvent[] = rawEvents.map((e, index) => ({
id: e.id,
text: e.text,
content: e.content,
year: e.year ?? year.value,
month: e.month ?? month.value,
timestamp: (e.year ?? year.value) * 12 + (e.month ?? month.value),
relatedAvatarIds: e.related_avatar_ids || [],
isMajor: e.is_major,
isStory: e.is_story,
_seq: index
} as GameEvent & { _seq: number }));
// 排序并保留最新的 N 条
const MAX_EVENTS = 300;
const combined = [...newEvents, ...events.value];
combined.sort((a, b) => {
// 1. 先按时间戳升序(最旧的月在上面)
const ta = a.timestamp;
const tb = b.timestamp;
if (tb !== ta) {
return ta - tb;
}
// 2. 时间相同时,按原始逻辑顺序升序(先发生的在上面)
// 旧事件通常没有 _seq (undefined),视为最旧 (-1)
const seqA = (a as any)._seq ?? -1;
const seqB = (b as any)._seq ?? -1;
// 如果都是旧事件,保持相对顺序 (Stable)
if (seqA === -1 && seqB === -1) return 0;
return seqA - seqB;
});
// 保留最新的 N 条 (因为是升序,最新的在最后,所以取最后 N 条)
if (combined.length > MAX_EVENTS) {
events.value = combined.slice(-MAX_EVENTS);
} else {
events.value = combined;
}
const newEvents = processNewEvents(rawEvents, year.value, month.value);
events.value = mergeAndSortEvents(events.value, newEvents);
}
function handleTick(payload: TickPayloadDTO) {
@@ -116,19 +75,6 @@ export const useWorldStore = defineStore('world', () => {
setTime(payload.year, payload.month);
// 检查并处理死亡事件,移除已死亡的角色
// if (payload.events && Array.isArray(payload.events)) {
// const deathEvents = (payload.events as any[]).filter((e: any) => {
// const c = e.content || '';
// return c.includes('身亡') || c.includes('老死');
// });
//
// if (deathEvents.length > 0) {
// // 旧逻辑:主动删除死人。现在改为软删除,后端会在 avatars 更新中推送 is_dead 状态,
// // 所以这里不再需要主动操作。前端展示层根据 is_dead 决定是否隐藏。
// }
// }
if (payload.avatars) updateAvatars(payload.avatars);
if (payload.events) addEvents(payload.events);
if (payload.phenomenon !== undefined) {