Files
ChronicleOfTheImmortalClan/src/renderer/ui/panels/ChroniclePanel.tsx
T
Hermes Agent 967ef1240d v0.1.8: 万物皆是插件 + 全应用审计修复(60+ 缺陷歼灭)
插件协议:
- CotycPlugin Manifest(dependencies/conflicts/protected/install/uninstall)
- PluginManager:安装序确定性/依赖拒绝/异常回滚/追踪型上下文(卸载全摘钩子)
- 三核心插件常驻(Systems/Data/Events);插件清单/安装移除/启停广播
- EventPoolRegistry 多池合并;facade about/query('plugins')/plugin 订阅
- 设置页插件清单(含卸载按钮、核心保护);examplePlugin 开发范本入仓

全应用审计修复(引擎 39 + UI/主进程 40 双路清单,P0→P2 歼灭):
- P0:单亲 rollRoots 崩/插件事件软锁(findEvent 查池)/fire 覆盖幸存事件/
  facade 未赋值(act 目录 6/24 生效→全量接通)/packOverride 死实现真接管/
  4 能力卡无接线(trib/apprentice/tournament/season)/读档 rowid 方言崩溃/
  buildApprentice 空候选崩/插件安装无回滚/trait 加成从未消费/DEV 双跑破坏时序
- P1:定时器幸存/advance in-flight 闸/battle 暂停/tournament 点将真传/
  史书 memo 依赖/面板 hooks 顺序违规/远征 squad 残留 id/跨档状态残留/
  toast 自清除/读档错误提示/导入后重载世界/pendingEvent 读档回填
- P2:版本三处统一/NewGame 随机收编/学费错误文案/姓氏池去重/
  功法品阶错位(凡阶哨兵)/功法定价对齐/market 白名单校验/
  战利功法去重/回声漏封缄/clock 迭代快照/core 事件池保护/
  渡劫陨落走真突破/寄读 desc 诚实化/doas 死代码清理等 30 项

测试 239→256;金钟罩复验(防御批零漂移)
2026-08-23 09:58:15 +08:00

106 lines
4.3 KiB
TypeScript

import { useGameStore } from '../store'
import { useMemo, useState } from 'react'
import { ChronicleEntry } from '../../game/types/domain'
const CAT_NAME: Record<string, string> = {
birth: '诞庆',
marriage: '姻缘',
death: '祭丧',
breakthrough: '突破',
battle: '战事',
trade: '商贸',
diplomacy: '外交',
exploration: '寻宝',
building: '营造',
event: '奇遇',
misc: '家常'
}
export default function ChroniclePanel() {
const world = useGameStore((s) => s.world)
const revision = useGameStore((s) => s.revision)
void revision
const [filter, setFilter] = useState<string>('all')
const [battleId, setBattleId] = useState<string | null>(null)
const entries = useMemo(() => {
if (!world) return []
const all = [...world.state.chronicle]
const filtered = filter === 'all' ? all : all.filter((e) => e.category === filter)
return filtered.slice().sort((a, b) => (b.year - a.year) || (b.month - a.month))
}, [world, filter, revision])
if (!world) return null
const battles = world.state.battles.slice().reverse()
const battle = battles.find((b) => b.id === battleId)
return (
<div>
<div className="help-text">
族史一卷:由系统自动记下的家族大事。倒序排列,可回看任意一年的兴衰细节。
</div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 10 }}>
<button className={`btn btn-sm ${filter === 'all' ? 'btn-primary' : ''}`} onClick={() => setFilter('all')}>全部</button>
{Object.entries(CAT_NAME).map(([k, v]) => (
<button key={k} className={`btn btn-sm ${filter === k ? 'btn-primary' : ''}`} onClick={() => setFilter(k)}>
{v}
</button>
))}
</div>
<div className="chronicle-list">
{entries.map((e) => (
<div key={e.id} className={`ch-item ${e.important ? 'gold' : ''}`}>
<span className="ch-year">{e.year}{e.month}</span>
<span>
<span className="tag" style={{ marginRight: 8 }}>{CAT_NAME[e.category] ?? e.category}</span>
{e.text}
</span>
</div>
))}
{entries.length === 0 && <div className="dim2">暂无记载。</div>}
</div>
<div className="card-title" style={{ fontSize: '1.02rem', marginTop: 22 }}>战报匣(全文留存)</div>
{battle ? (
<div className="mission-card" style={{ maxWidth: 720 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<b>{battle.title}</b>
<span className="dim2" style={{ fontSize: '0.85rem' }}>
{battle.year}{battle.month} ·{' '}
<span className={battle.winner === 'player' ? 'good' : battle.winner === 'enemy' ? 'bad' : 'warn'}>
{battle.winner === 'player' ? '我方得胜' : battle.winner === 'enemy' ? '我方败北' : '平分秋色'}
</span>
</span>
<button className="btn btn-sm" style={{ marginLeft: 'auto' }} onClick={() => setBattleId(null)}>
收起
</button>
</div>
<div className="dim" style={{ fontSize: '0.92rem', lineHeight: 1.8, marginTop: 8 }}>
{battle.lines.slice(1).map((l, i) => (
<div key={i}>· {l}</div>
))}
{battle.losses.length > 0 && (
<div className="bad">我方折损:{battle.losses.join('、')}</div>
)}
</div>
</div>
) : battles.length === 0 ? (
<div className="dim2" style={{ marginBottom: 6 }}>尚无战事可录。</div>
) : (
<div style={{ maxWidth: 720 }}>
{battles.map((b) => (
<div key={b.id} className="set-row" style={{ maxWidth: 720 }}>
<span>
<span className="ch-year" style={{ marginRight: 10 }}>{b.year}{b.month}</span>
{b.title}
<span className={`tag ${b.winner === 'player' ? 'good' : b.winner === 'enemy' ? 'bad' : 'warn'}`} style={{ marginLeft: 8 }}>
{b.winner === 'player' ? '胜' : b.winner === 'enemy' ? '负' : '平'}
</span>
</span>
<button className="btn btn-sm" onClick={() => setBattleId(b.id)}></button>
</div>
))}
</div>
)}
</div>
)
}