feat(v2): 纠偏第三批——深色导航、三模块独立入口与轨迹复盘

覆盖 AC-53/54/55(BR-50)。审计 P1-2「新界面未接入、原落地页与导航未改」
由此关闭:导航改为原型 token 的深色壳,三个模块各有独立路由与入口。

界面:
- navigation.css:导航改用原型 token(nav.bg #102B27 / active #244D41 /
  muted #B1C6BE),身份选择器在深色底上改用 dark 变体(否则白底白字);
  工作区画布保持浅色 #f5f7f9——"深色导航+浅色工作区"是两件事。
  保留可读性守卫锁定的两条规则(导航项 min-height:42px/font-size:14px、
  身份选择器 min-height:42px),原有 7 项守卫全部继续通过。
- AgentNav:品牌改为「兴邦金租/智审工作台」,加「业务工作区」分组标签,
  三个模块入口(AI 预审 / 历史经验加工 / 轨迹复盘)指向各自路由;
  权限仍按角色裁剪,身份仍来自服务端。
- 新增 /prescreen-workbench(AI 预审)、/trajectory-center(轨迹复盘)两个模块路由;
  / 从旧聊天落地页改为业务工作区首页,呈现三个模块入口;原落地页内容保留在
  /assistant,没有被删掉(AC-53 要求的是不再"只保留"旧布局)。
- TrajectoryPanel:消费既有只读轨迹端点,失败/跳过步骤照常列出并带错误码,
  成本缺失显示"未记录(不填 0)",并展示服务端对轨迹范围的说明;
  会议结果只读展示(录入属第四批)。

复用而非复制:三个模块路由都 import 既有的 PrescreenPanel / ImportBatchPanel /
ExperienceCenter,没有在路由里再写一份实现。

守卫:新增 module-shell-ui(已登记 verify.py,共 13 项检查),机械核对
深色 token、三入口存在、已合并旧页仍为重定向、按角色裁剪、
?project= 上下文与不硬编码项目 id、轨迹面板不渲染推理字段,
并做前后端路径一致性断言(界面调用的端点必须真的在后端声明过)。
深色底对比度按 WCAG 公式在守卫内**实算**(阈值 AA 4.5:1,实测最低 8.37:1),
不在文档里声称"应该够清楚"。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-10-01 00:50:16 +08:00
co-authored by Claude Code
parent 01c88af501
commit ae89462756
10 changed files with 622 additions and 15 deletions
+78 -3
View File
@@ -1,5 +1,80 @@
import BusinessAssistant from "@/components/xingbang/BusinessAssistant";
"use client";
import {useEffect, useState} from "react";
import {ArrowRight, GitBranch, History, ShieldCheck} from "lucide-react";
import AgentNav from "@/components/xingbang/AgentNav";
import {messageOf} from "@/lib/prescreen";
import "./data-access/workspace.css";
export default function Home() {
return <BusinessAssistant/>;
/** 业务工作区首页(AC-53 / BR-50)。
*
* 三个模块**各有独立入口**:AI 预审 / 历史经验加工 / 轨迹复盘,指向各自的路由。
* 深色导航 + 浅色工作区取自已确认原型;不再以旧版聊天落地页作为唯一首屏。
* 本页只负责导航与项目上下文,不在这里叠加任何模块面板。
*/
const MODULES = [
{key: "prescreen", href: "/prescreen-workbench", icon: ShieldCheck, name: "AI 预审",
summary: "按已发布方案执行受限编排,给出有依据的意见、缺证与未完成项。",
detail: "概率状态与依据 · 动态维度 · 推进沙盘"},
{key: "experience", href: "/experience-center", icon: History, name: "历史经验加工",
summary: "从历史过程恢复时间线,用真实模型提炼核查方法,经专家修订与独立审核发布。",
detail: "来源时间线 · 候选治理 · 卡片入方案"},
{key: "trajectory", href: "/trajectory-center", icon: GitBranch, name: "轨迹复盘",
summary: "只读回放每一次运行的步骤与版本依据,与会议结果对照。",
detail: "步骤与依据 · 失败保留 · 会议结果对照"},
];
export default function BusinessHome() {
const [identity, setIdentity] = useState<any>(null);
const [projectId, setProjectId] = useState("");
const [error, setError] = useState("");
function headers(json = true) {
const token = sessionStorage.getItem("xingbang-access-token");
const role = sessionStorage.getItem("xingbang-demo-role");
return {...(json ? {"Content-Type": "application/json"} : {}),
...(token ? {Authorization: `Bearer ${token}`} : {}),
...(role ? {"X-Xingbang-Demo-Role": role} : {})};
}
useEffect(() => {
setProjectId(new URLSearchParams(location.search).get("project") || "");
fetch("/api/risk-agent/access/context", {headers: headers(false)})
.then(async (response) => {
if (!response.ok) throw Error(messageOf(await response.json().catch(() => ({})), "无法读取身份"));
setIdentity(await response.json());
})
.catch((e: any) => setError(e.message));
}, []);
const withProject = (href: string) => projectId ? `${href}?project=${encodeURIComponent(projectId)}` : href;
return <div className="ra-app">
<AgentNav active="home" project={projectId || undefined} identity={identity || undefined}/>
<main className="ra-main">
<div className="ra-heading">
<div>
<div className="ra-eyebrow">业务工作区</div>
<h1>兴邦金租 · 智审工作台</h1>
<p>三个模块各有独立入口;项目上下文在模块之间保持。所有数值与结论都标明来源,未接入的能力会如实显示未完成。</p>
</div>
</div>
{error && <div role="alert" className="ra-alert error">{error}</div>}
{projectId && <div className="ra-alert">当前项目上下文:<b>{projectId}</b>(在模块之间保持)</div>}
<div className="xb-project-grid">
{MODULES.map((item) => <a className="xb-project-card" key={item.key} href={withProject(item.href)}>
<span>{item.detail}</span>
<h2><item.icon size={18} aria-hidden="true"/> {item.name}</h2>
<p>{item.summary}</p>
<footer><small>进入模块</small><ArrowRight size={15} aria-hidden="true"/></footer>
</a>)}
</div>
<section className="xb-panel xb-wide">
<h2>项目工作台</h2>
<p>需要按项目办理材料、报告与评审结论时,进入项目工作台。</p>
<div className="xb-actions"><a className="ra-button" href={withProject("/xingbang-workbench")}>进入项目工作台</a></div>
</section>
</main>
</div>;
}