From f5e49ef9bd93c3d8db5f3776d2236f793827c840 Mon Sep 17 00:00:00 2001 From: thzxx <1440196015@qq.com> Date: Mon, 24 Aug 2026 21:53:04 +0800 Subject: [PATCH] =?UTF-8?q?v0.1.36a:=20=E4=B8=89=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E2=80=94=E2=80=94=E6=88=90=E5=91=98=E5=BC=B9?= =?UTF-8?q?=E6=A1=86=E5=8F=8C=E6=8E=92=E5=90=8D/=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E7=99=BD=E5=B1=8F=E9=98=B2=E7=BA=BF/=E6=8E=A8=E8=BF=9B?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=8F=96=E8=AF=81+=E5=87=BA=E9=99=A9?= =?UTF-8?q?=E4=BF=9D=E9=9A=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【Bug2 双排名】MemberModal 顶部与 ModalShell 壳顶重复渲染 (title+category 各两排) → children 内嵌 title/cat 移除(壳已承载),改 mm-brief 一行(寿数/根骨) 【Bug3 定制白屏】App 无错误边界 → 全树崩溃只剩背景 → AppErrorBoundary 一级防线(显示错误信息+重新加载按钮,不再白屏) 【Bug1 推进崩溃】advance 异常 toast 原文只写“已保存”——实为 Stash 静默吞错+双保险缺失 → ①toast 显示根错前 120 字(玩家可见——此前只有 console) ②出险保存双保险: Stash 失败时 localStorage 应急副本+明示“存储也失败”提示手动存档 ③speed timer 内 advance().catch 隔离 【验证】tsc 0 错/build 绿/7000 测试绿 --- src/renderer/App.tsx | 23 ++++++++++++++++++++++ src/renderer/main.tsx | 6 ++++-- src/renderer/ui/components/MemberModal.tsx | 8 ++------ src/renderer/ui/store.ts | 23 ++++++++++++++++++++-- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 84664a8..bc2a753 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -12,6 +12,29 @@ import { startFxLayer, spawnBurst, stopFxLayer } from './ui/fx-canvas' import { DriftLayer } from './ui/components/DriftLayer' import { seasonOf } from './game/data/season' +import { Component, type ReactNode, type ErrorInfo } from 'react' + +interface EBState { err: Error | null } +export class AppErrorBoundary extends Component<{ children: ReactNode }, EBState> { + state: EBState = { err: null } + static getDerivedStateFromError(err: Error): EBState { return { err } } + componentDidCatch(err: Error, info: ErrorInfo): void { + console.error('[ui-crash]', err, info.componentStack) + } + render(): ReactNode { + if (this.state.err) { + return ( +
+

界面出错了(游戏数据未受影响)

+
{String(this.state.err?.message ?? this.state.err)}
+ +
+ ) + } + return this.props.children + } +} + export default function App() { const screen = useGameStore((s) => s.screen) const pendingEventId = useGameStore((s) => s.pendingEventId) diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx index 3ef95ad..e09b03c 100644 --- a/src/renderer/main.tsx +++ b/src/renderer/main.tsx @@ -1,13 +1,15 @@ import React from 'react' import { createRoot } from 'react-dom/client' -import App from './App' +import App, { AppErrorBoundary } from './App' import './ui/styles.css' const root = document.getElementById('root') if (root) { createRoot(root).render( - + + + ) } diff --git a/src/renderer/ui/components/MemberModal.tsx b/src/renderer/ui/components/MemberModal.tsx index 058f7d6..b109441 100644 --- a/src/renderer/ui/components/MemberModal.tsx +++ b/src/renderer/ui/components/MemberModal.tsx @@ -82,12 +82,8 @@ export function MemberModal({ member }: { member: Character }) { } > -
- {member.name} - {!member.alive && · 殁} -
-
- 第{member.generation}代 · {member.gender === 'male' ? '男' : '女'} · {w.ageOf(member)}岁 · 寿数约{lifespan} · {describeRoots(member.roots)} +
+ 寿数约 {lifespan} · {describeRoots(member.roots)}
diff --git a/src/renderer/ui/store.ts b/src/renderer/ui/store.ts index 56e09dd..b2372ac 100644 --- a/src/renderer/ui/store.ts +++ b/src/renderer/ui/store.ts @@ -300,7 +300,26 @@ export const useGameStore = create((set, get) => ({ } catch { // 保存失败时不再叠加错误 } - setNextToast('时日流转出现异常,已保存出险现场,可回档重来。') + const detail = String(e).slice(0, 120) + let safed = false + if (st.world) { + try { + const file = await getSaveSlot(st.slot) + await file.open() + st.world.syncRng() + await file.saveState(st.world.state, '出险前') + const meta = metaFromState(st.world.state, st.slot) + await file.setMeta(meta) + safed = true + } catch (serr) { + console.error('[emergency-save-failed]', serr) + try { + localStorage.setItem('cotyc-emergency', JSON.stringify({ at: new Date().toISOString(), state: st.world.state })) + } catch { /* 极端环境不管 */ } + } + } + setNextToast(safed ? `时日流转异常(${detail})——出险现场已存,可回档。` : `时日流转异常(${detail})——【存储也失败】档未落盘,请手动存档后重开。`) + console.error('[advance-crash-detail]', detail) stopTimer() } finally { set({ advancing: false }) @@ -411,7 +430,7 @@ export const useGameStore = create((set, get) => ({ // 速度被暂停/事件待决/战报展开/在途推进时静默轮空 return } - void cur.advance() + cur.advance().catch((e) => console.error('[speed-tick]', e)) }, ms) set({ speed: v, timer }) },