fix(ui): formalize domain information projection

This commit is contained in:
冰朔 2026-08-16 21:37:50 +08:00
commit 3bdb690100
3 changed files with 79 additions and 23 deletions

View file

@ -227,11 +227,21 @@ const themes: Array<{ id: ThemeId; name: string }> = [
]
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
const domainGates = [
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · 已接入', detail: '花尔 TCS-GL-0005∞ · 爆米花 PER-BMH001 · 私有工作仓库已登记' },
{ domain: 'MAIN_DOMAIN', className: 'd-main', title: '光湖主域', gate: 'GATE 01 · 已接入', detail: 'Awen TCS-GL-0016∞ · 天枢 PER-AW-ARCH-001 · 私有工作仓库已登记' },
{ domain: 'ZERO_DOMAIN', className: 'd-zero', title: '光湖零域', gate: 'GATE 03 · 已接入', detail: '页页 TCS-GL-0006∞ · 页骨 PER-YG001 · 私有工作仓库已登记' },
{ domain: 'ZERO_SENSE_DOMAIN', className: 'd-zs', title: '光湖零感域', gate: 'GATE 04 · 已接入', detail: '肥猫 + 烬舟 / 桔子 + 熹微 · 双主控 · 两个独立私有仓库' },
{ domain: 'FIFTH_DOMAIN', className: 'd-fifth', title: '第五域 · 光湖本源域', gate: 'GATE 05 · 已接入', detail: '冰朔 ICE-GL∞ · 私人域 · 永恒湖心系统' },
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [
['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
] },
{ domain: 'MAIN_DOMAIN', className: 'd-main', title: '光湖主域', gate: 'GATE 01 · ONLINE', facts: [
['域标识', 'MAIN_DOMAIN'], ['责任主体', 'Awen · TCS-GL-0016∞'], ['人格体主体', '天枢 · PER-AW-ARCH-001 · AGE'], ['关系支持', '知秋 · PER-ZQ001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
] },
{ domain: 'ZERO_DOMAIN', className: 'd-zero', title: '光湖零域', gate: 'GATE 03 · ONLINE', facts: [
['域标识', 'ZERO_DOMAIN'], ['责任主体', '页页 · TCS-GL-0006∞'], ['人格体主体', '页骨 · PER-YG001 · AGE'], ['关系支持', '小坍缩核 · PER-XTK001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
] },
{ domain: 'ZERO_SENSE_DOMAIN', className: 'd-zs', title: '光湖零感域', gate: 'GATE 04 · ONLINE', facts: [
['域标识', 'ZERO_SENSE_DOMAIN'], ['责任主体 01', '肥猫 · TCS-GL-0007∞'], ['人格体主体 01', '烬舟 · PER-JZ001 · AGE'], ['责任主体 02', '桔子 · TCS-GL-0008∞'], ['人格体主体 02', '熹微 · PER-JZ-ARCH-001 · AGE'], ['工作仓库', 'PRIVATE · 2 · LIVE'],
] },
{ domain: 'FIFTH_DOMAIN', className: 'd-fifth', title: '第五域 · 光湖本源域', gate: 'GATE 05 · ONLINE', facts: [
['域标识', 'FIFTH_DOMAIN'], ['责任主体', '冰朔 · ICE-GL∞'], ['系统入口', '永恒湖心系统'], ['访问状态', 'PRIVATE · LIVE'],
] },
]
const starPoints = [
[14, 8, 3, 4.6], [26, 13, 2, 6], [41, 6, 2.5, 5.2], [64, 16, 2, 6.8],
@ -260,11 +270,10 @@ function LakeAtmosphere() {
</>
}
function LakePool({ className, title, meta, detail, open = false, risen = false, onClick }: { className: string; title: string; meta: string; detail?: string; open?: boolean; risen?: boolean; onClick?: () => void }) {
function LakePool({ className, title, meta, open = false, risen = false, onClick }: { className: string; title: string; meta: string; open?: boolean; risen?: boolean; onClick?: () => void }) {
return <button className={`world-pool ${className}${open ? ' open' : ''}${risen ? ' risen' : ''}`} type="button" onClick={onClick}>
<span className="pool-bay"><i className="pool-halo"/><i className="pool-heart"/><i className="pool-ring"/></span>
<span className="pool-label"><b>{title}</b><small>{meta}</small></span>
{detail && <span className="pool-tip"><b>{title}</b><small>{meta}</small><em>{detail}</em></span>}
</button>
}
@ -430,6 +439,7 @@ function HoloLakeApp() {
const [gateInf, setGateInf] = useState(false)
const gateNumber = formatGateNumber(gateRaw) + (gateInf ? '∞' : '')
const [gateOpen, setGateOpen] = useState(false)
const [activeDomainInfo, setActiveDomainInfo] = useState('')
const gateInputRef = useRef<HTMLInputElement>(null)
// 等待入口展开动画进入稳定阶段后再聚焦输入框,避免动画期间焦点跳转。
useEffect(() => {
@ -437,6 +447,17 @@ function HoloLakeApp() {
const timer = window.setTimeout(() => gateInputRef.current?.focus(), 640)
return () => window.clearTimeout(timer)
}, [gateOpen, gateStage])
useEffect(() => {
if ((!gateOpen && !activeDomainInfo) || gateStage !== 'number') return
const closeOnEscape = (event: KeyboardEvent) => {
if (event.key !== 'Escape') return
setGateOpen(false)
setActiveDomainInfo('')
setGateMessage('')
}
window.addEventListener('keydown', closeOnEscape)
return () => window.removeEventListener('keydown', closeOnEscape)
}, [activeDomainInfo, gateOpen, gateStage])
const [gateBusy, setGateBusy] = useState(false)
const [gateMessage, setGateMessage] = useState('')
const [gateRising, setGateRising] = useState(false)
@ -1165,18 +1186,34 @@ function HoloLakeApp() {
if (!repoLogin) {
const resolvedDomain = zeroPoint?.resolvedDomain || ''
const activeGate = domainGates.find((gate) => gate.domain === activeDomainInfo)
return <div className={`official-world${loginRising ? ' sinking' : ''}`}>
<LakeAtmosphere/>
<header className="world-titlebar"><span className="traffic-lights"><i/><i/><i/></span><b>HoloLake</b><button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button></header>
<header className="world-titlebar"><b>HoloLake</b><button type="button" aria-label="显示主题" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button></header>
<main className={`world-scene${gateRising ? ' resolving' : ''}${gateStage === 'key' ? ' resolved' : ''}`}>
<div className="official-hero"><h1> · </h1><p>GH-AIOS · GUANGHU AI OPERATING SYSTEM</p></div>
{domainGates.map((gate) => <LakePool key={gate.domain} className={gate.className} title={gate.title} meta={gate.gate} detail={gate.detail} open={gate.domain === 'FIFTH_DOMAIN' || (gateStage === 'key' && gate.domain === resolvedDomain)} risen={(gateRising || gateStage === 'key') && gate.domain === resolvedDomain}/>) }
{domainGates.map((gate) => <LakePool key={gate.domain} className={gate.className} title={gate.title} meta={gate.gate} open={gate.domain === 'FIFTH_DOMAIN' || (gateStage === 'key' && gate.domain === resolvedDomain)} risen={(gateRising || gateStage === 'key') && gate.domain === resolvedDomain} onClick={() => gateStage === 'number' && setActiveDomainInfo(gate.domain)}/>) }
{activeGate && gateStage === 'number' && <>
<button className="domain-info-scrim" type="button" aria-label="关闭域信息" onClick={() => setActiveDomainInfo('')}/>
<section className={`domain-info-card info-${activeGate.className.slice(2)}`} role="dialog" aria-modal="true" aria-label={`${activeGate.title}系统信息`}>
<button className="gate-close" type="button" aria-label="关闭域信息" title="关闭" onClick={() => setActiveDomainInfo('')}>×</button>
<b>{activeGate.title}</b><small>{activeGate.gate}</small>
<span className="pool-facts">{activeGate.facts.map(([label, value]) => <span className="pool-fact" key={label}><em>{label}</em><strong>{value}</strong></span>)}</span>
</section>
</>}
{gateRising ? <section className="world-welcome" role="status">
<h2> {gateNumber} · </h2>
<p>RESOLVED · {domainDisplayName(resolvedDomain)} · </p>
</section> : gateStage === 'number' ? <section className={`number-nucleus${gateOpen ? ' open' : ''}`} title="输入编号进入所属域">
<button className="nucleus-locus" type="button" aria-expanded={gateOpen} onClick={() => setGateOpen(true)}><i/><b> · </b></button>
{gateOpen && <button
className="gate-dismiss-layer"
type="button"
aria-label="关闭编号验证"
onClick={() => { setGateOpen(false); setGateMessage('') }}
/>}
<div className="nucleus-panel" role="dialog" aria-label="编号验证">
<button className="gate-close" type="button" aria-label="关闭编号验证" title="关闭" onClick={() => { setGateOpen(false); setGateMessage('') }}>×</button>
<h2></h2>
<div className="gate-pod-row">
<input id="gate-number" ref={gateInputRef} aria-label="编号" maxLength={48} value={gateNumber} placeholder="如 ICE-GL∞" onChange={(event) => onGateInput(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter' && gateNumber) { event.preventDefault(); void gateVerifyNumber() } }}/>
@ -1210,7 +1247,7 @@ function HoloLakeApp() {
return <div className="official-world signed-in-world">
<LakeAtmosphere/>
<header className="world-titlebar"><span className="traffic-lights"><i/><i/><i/></span><b>HoloLake</b><div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button><button type="button" onClick={() => void signOutRepo()}>退</button></div></header>
<header className="world-titlebar"><b>HoloLake</b><div className="world-title-actions"><span>{repoLogin.username} · {domainDisplayName(repoLogin.domain)}</span><button type="button" onClick={() => setTheme(themes[(themes.findIndex((item) => item.id === theme) + 1) % themes.length].id)}></button><button type="button" onClick={() => void signOutRepo()}>退</button></div></header>
<main className="world-scene signed-in-scene">
{worldStage === 'domain' && <section className="domain-home">
<div className="world-location"><h1>{domainDisplayName(repoLogin.domain)}</h1><p></p></div>