Files
ChronicleOfTheImmortalClan/src/renderer/ui/panels/GenealogyPanel.tsx
T
thzxx ff6df8054c refactor(0.1.14-P1): 内核归位——game/ 按引擎架构重排(零行为漂移)
结构(旧 game/core+engine → 新 engine/ 域):
- engine/kernel/  时钟/随机/插件协议/fxqueue/timesense/format/urgency/guide/names(原 core)
- engine/narrative/  legacy/报告/列传/谱系/年轴(原 core 叙事族)
- engine/runtime/   World/creation/pcgen/ApiFacade/capabilities/pluginManager/boot/clocks + Systems/*(12 系统)
- engine/sim/     Market(未来 WorldSim 同行)
- 旧 game/core、engine/systems、engine/world.ts 等路径全部废弃(无 re-export 兼容层)

验证:35 套件/967 测试全绿(金钟罩三档零漂移=纯搬迁无行为变化)
typecheck 0 error
2026-08-23 13:23:25 +08:00

129 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useGameStore } from '../store'
import { computeGenealogy } from '../../game/engine/narrative/genealogy'
import { describeRealm } from '../../game/data/realms'
import { Character, FamilyState } from '../../game/types/domain'
import { useState } from 'react'
import { MemberModal } from '../components/MemberModal'
export default function GenealogyPanel() {
const world = useGameStore((s) => s.world)
const revision = useGameStore((s) => s.revision)
const selectedMemberId = useGameStore((s) => s.selectedMemberId)
const setSelected = useGameStore((s) => s.setSelected)
const [deceasedTab, setDeceasedTab] = useState(false)
void revision
if (!world) return null
const w = world
const s = w.state
const rows = computeGenealogy(w)
const deceased = !deceasedTab
? []
: Object.values(s.members).filter((c) => !c.alive).sort((a, b) => (a.deathYear ?? 0) - (b.deathYear ?? 0))
const selected = selectedMemberId ? s.members[selectedMemberId] as Character | undefined : undefined
return (
<div>
<div className="help-text">
宗族谱系:世代分带,夫妻同卷,子孙列后。点任一名字可查详情;染灰者已下世,功绩铭记。
</div>
<div style={{ display: 'flex', gap: 6, marginBottom: 10 }}>
<button
className={`btn btn-sm ${!deceasedTab ? 'btn-primary' : ''}`}
onClick={() => setDeceasedTab(false)}
>
在世谱系
</button>
<button className={`btn btn-sm ${deceasedTab ? 'btn-primary' : ''}`} onClick={() => setDeceasedTab(true)}>
先人簿({Object.values(s.members).filter((c) => !c.alive).length}位)
</button>
</div>
{!deceasedTab &&
rows.map((row) => (
<div key={row.gen} className="genealogy-row">
<div className="genealogy-gen">{row.gen}</div>
<div className="genealogy-body">
{row.units.map((unit, i) => (
<div key={i} className="genealogy-unit">
<div className="genealogy-parents">
{unit.parents[0] && <GeneNode c={unit.parents[0]} w={w} />}
<div className="genealogy-matron">×</div>
{unit.parents[1] && <GeneNode c={unit.parents[1]} w={w} />}
</div>
{unit.children.length > 0 && (
<>
<div className="genealogy-drop" />
<div className="genealogy-children">
{unit.children.map((ch) => (
<GeneNode key={ch.id} c={ch} w={w} />
))}
</div>
</>
)}
</div>
))}
{row.singles.length > 0 && (
<div className="genealogy-singles">
{row.singles.map((c) => (
<GeneNode key={c.id} c={c} w={w} />
))}
</div>
)}
{row.units.length + row.singles.length === 0 && <div className="dim2">此代凋零</div>}
</div>
</div>
))}
{deceasedTab &&
deceased.map((c) => (
<div key={c.id} className="set-row">
<span>
<span className="ch-year" style={{ marginRight: 10 }}>殁于{c.deathYear}</span>
{c.name} · {c.gender === 'male' ? '男' : '女'} · {c.generation}
<span className="dim2" style={{ marginLeft: 8 }}>{c.deathCause ?? '寿终'}</span>
</span>
<button className="btn btn-sm" onClick={() => setSelected(c.id)}></button>
</div>
))}
{selected && <MemberModal member={selected} />}
</div>
)
}
function GeneNode({ c, w }: { c: Character; w: NonNullable<ReturnType<typeof useGameStore.getState>['world']> }) {
const setSelected = useGameStore((s) => s.setSelected)
const isHead = w.state.family.headId === c.id
const rank = c.techniqueRank ?? 0
const post = POST_NAME[c.post ?? ''] ?? ''
return (
<div
className={`gene-node ${c.alive ? '' : 'gene-dead'} ${isHead ? 'gene-head' : ''}`}
onClick={() => setSelected(c.id)}
title={`${c.name} · ${describeRealm(c.realm)} · ${w.ageOf(c)}岁`}
>
<div className="gene-name">
{c.name}
{isHead && <span className="tag head-t" style={{ marginLeft: 4 }}></span>}
{post && <span className="tag" style={{ marginLeft: 4 }}>{post}</span>}
{!c.alive && <span className="tag" style={{ marginLeft: 4 }}></span>}
</div>
<div className="gene-sub">
{c.alive ? `${describeRealm(c.realm)} · ${w.ageOf(c)}岁` : `${c.deathCause ?? '寿终'}${c.deathYear ? `${c.deathYear}年)` : ''}`}
{rank > 0 && <span className="tag gold-t" style={{ marginLeft: 4 }}>{rank}</span>}
</div>
</div>
)
}
const POST_NAME: Record<string, string> = {
head: '家主',
elder: '长老',
guardian: '供奉',
steward: '执事',
master: '掌教'
}
export type { FamilyState }