From 61754acb878340fbe50b6816d4502588c6323202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Sat, 13 Jun 2026 17:37:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9C=86=E6=A1=8C):=20=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E6=8C=82=E8=B5=B7=E6=8C=89=E9=92=AE=E7=A7=BB=E8=87=B3=E9=A1=B6?= =?UTF-8?q?=E9=83=A8=E6=9A=82=E5=81=9C=E6=8C=89=E9=92=AE=E5=8F=B3=E4=BE=A7?= =?UTF-8?q?=20+=20=E5=AE=9E=E6=97=B6=E7=A0=94=E8=AE=A8=E5=A4=8D=E4=BD=8D?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E9=97=B8=E9=97=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Step2 顶部「暂停/恢复会商」右侧新增「后台挂起」按钮(查看历史时隐藏),移除底部干预栏里的旧入口 - handleConfirmAgents 从第一步开启实时研讨时复位 loadedFromDraftRef,修复看过历史后再开新讨论按钮不出现的问题 - 大屏 dashboard 多组件样式与业务链条编辑页细节调整 Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend-web/src/pages/LightAppListPage.tsx | 307 +++++++++++++++--- .../src/pages/LightAppManagementPage.tsx | 2 +- .../components/ChainFlowDiagram.tsx | 79 ++++- .../components/Step2Panel.tsx | 33 +- .../dashboard/DashboardApp.tsx | 24 +- .../dashboard/components/AnalyticsCenter.tsx | 38 +-- .../dashboard/components/BrandScores.tsx | 70 ++-- .../dashboard/components/ConsensusPanel.tsx | 16 +- .../dashboard/components/DetailView.tsx | 58 ++-- .../dashboard/components/DraftingCard.tsx | 16 +- .../dashboard/components/MetricsHeader.tsx | 10 +- .../dashboard/components/RiskDonut.tsx | 30 +- .../dashboard/components/Roadmap.tsx | 32 +- .../components/SeatDeliveryOverview.tsx | 44 +-- .../dashboard/components/SeatShowcase.tsx | 76 ++--- .../dashboard/dashboard.css | 38 +++ .../pages/BusinessChainEditorPage.tsx | 161 +++++---- .../pages/RoundtablePlanningPage.tsx | 6 +- 18 files changed, 672 insertions(+), 368 deletions(-) diff --git a/frontend-web/src/pages/LightAppListPage.tsx b/frontend-web/src/pages/LightAppListPage.tsx index ff7c75bd..b6bc9d54 100644 --- a/frontend-web/src/pages/LightAppListPage.tsx +++ b/frontend-web/src/pages/LightAppListPage.tsx @@ -1,27 +1,71 @@ /** * 应用列表页 —— 以卡片网格展示「新页跳转(window.open)」类型的轻应用。 * - * 支持搜索 + 分页。点击卡片用 buildLightAppUrl 构造最终地址(追加解析后的路由参数) - * 并 window.open 新开页跳转。仅展示已启用的应用;iframe 类型不在此处(它们注入侧边栏)。 + * 只读启动器:搜索 + 分页;每张卡片预览「拼接后最终路由」(buildLightAppUrl 追加解析后的 + * 登录态 / 主题路由参数),并提供「一键复制URL」「进入应用」(window.open 新开页) 两个动作。 + * 增删改不在本页,走独立的「应用管理」页(admin);iframe 类型也不在此(它们注入侧边栏)。 + * + * 配色(与「业务链条配置」页统一): + * - 浅色:品牌红 `#CF1322`(hover `#A8071A`)。 + * - 深蓝:按《深蓝色 UI 规范》——页面 `#0C1836`、卡片 `#122347`、边框 `#1A2F5A`、 + * 正文 `#C5CFE0`、次级 `#6E88B0`;主操作改用规范主色蓝 `#1677FF`(hover `#0958D9`)。 + * + * 后端 LightApp 无「分类」字段,故不做分类筛选;卡片图标 / 主题色按 appId 哈希派生 + * (`icon` 字段当前未用),仅作视觉区分,不承载语义。 */ import { useEffect, useMemo, useState } from "react"; -import { ExternalLink, Search } from "lucide-react"; +import { + ArrowUpRight, + Bot, + Check, + Code, + Copy, + FileText, + Globe, + Layers, + Notebook, + Search, + SlidersHorizontal, + Terminal, + X, + type LucideIcon, +} from "lucide-react"; import { toast } from "sonner"; -import { Input } from "@/components/ui/input"; import { Pagination } from "@/components/ui/pagination"; -import { useTheme } from "@/contexts/ThemeContext"; +import { cn } from "@/lib/utils"; import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps"; import { buildLightAppUrl } from "@/strategy-components/lib/light-app"; const PAGE_SIZE = 12; -export default function LightAppListPage() { - const { theme } = useTheme(); - const isDark = theme === "dark"; +/** 卡片图标池(按 appId 哈希取一个,仅作视觉区分)。 */ +const ICON_POOL: LucideIcon[] = [Bot, Notebook, Terminal, SlidersHorizontal, Globe, Code, Layers, FileText]; +/** 卡片主题色池:浅色用对应色阶,深蓝统一落到 `#1A2F5A` 容器底 + 规范强调色。 */ +const THEME_POOL = [ + { iconBg: "bg-rose-100 text-rose-600 dark:bg-[#1A2F5A] dark:text-[#FF7875]" }, + { iconBg: "bg-indigo-100 text-indigo-600 dark:bg-[#1A2F5A] dark:text-[#6388FF]" }, + { iconBg: "bg-emerald-100 text-emerald-600 dark:bg-[#1A2F5A] dark:text-[#52C41A]" }, + { iconBg: "bg-amber-100 text-amber-600 dark:bg-[#1A2F5A] dark:text-[#FAAD14]" }, + { iconBg: "bg-blue-100 text-blue-600 dark:bg-[#1A2F5A] dark:text-[#4096FF]" }, + { iconBg: "bg-purple-100 text-purple-600 dark:bg-[#1A2F5A] dark:text-[#9C7BFF]" }, +] as const; + +/** 稳定字符串哈希 → [0, mod) 下标。 */ +function hashIndex(str: string, mod: number): number { + let h = 0; + for (let i = 0; i < str.length; i += 1) { + h = (h * 31 + str.charCodeAt(i)) >>> 0; + } + return mod > 0 ? h % mod : 0; +} + +export default function LightAppListPage() { const [search, setSearch] = useState(""); const [page, setPage] = useState(1); + const [copiedId, setCopiedId] = useState(null); + const { data: apps = [], isLoading } = useLightApps({ appType: "window_open", enabledOnly: true, @@ -47,67 +91,226 @@ export default function LightAppListPage() { window.open(url, "_blank", "noopener,noreferrer"); }; - const shellClass = isDark - ? "h-full overflow-y-auto bg-[linear-gradient(180deg,#081322_0%,#0d1d33_42%,#0a1628_100%)] text-slate-100" - : "h-full overflow-y-auto bg-[linear-gradient(180deg,#fffdfd_0%,#fff7f7_42%,#fafafa_100%)] text-[#222222]"; - const cardClass = isDark - ? "group cursor-pointer rounded-2xl border border-[rgba(67,89,124,0.46)] bg-[rgba(12,29,54,0.94)] p-4 transition-all hover:-translate-y-0.5 hover:border-[rgba(77,141,255,0.6)] hover:shadow-[0_18px_38px_rgba(3,10,22,0.35)]" - : "group cursor-pointer rounded-2xl border border-[#E8E8E8] bg-white p-4 transition-all hover:-translate-y-0.5 hover:border-[#FFA39E] hover:shadow-[0_16px_32px_rgba(245,34,45,0.1)]"; - const mutedText = isDark ? "text-slate-500" : "text-[#A6A6A6]"; - const iconWrap = isDark - ? "bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]" - : "bg-[#FFF1F0] text-[#CF1322]"; + const copyUrl = async (app: LightApp) => { + const url = buildLightAppUrl(app); + if (!url) { + toast.error("该应用地址为空,无法复制"); + return; + } + try { + await navigator.clipboard.writeText(url); + setCopiedId(app.appId); + setTimeout(() => setCopiedId((id) => (id === app.appId ? null : id)), 2000); + } catch { + toast.error("复制失败,请手动选择地址"); + } + }; return ( -
-
+
+
+ {/* 标题 */}
-

- 应用列表 -

-

- 点击卡片在新标签页打开应用。地址会自动附带配置的路由参数(登录态 / 主题)。 +

+ + + + +

+ 应用列表 +

+ + 导航工作台 + +
+

+ 点击卡片将在新标签页中一键直达应用。地址会自动、智能地拼接路由参数(跨域登录态、个性化主题等)。

-
- - setSearch(e.target.value)} - placeholder="搜索应用名称 / 描述" - className="pl-9" - /> + {/* 搜索 */} +
+
+ + setSearch(e.target.value)} + placeholder="键入应用名称、目标地址、或描述,实时过滤检索…" + className="w-full rounded-xl border border-zinc-200 bg-zinc-50/50 py-3 pl-10 pr-10 text-sm transition focus:border-[#CF1322]/60 focus:bg-white focus:outline-none focus:ring-2 focus:ring-[#CF1322]/20 dark:border-[#1A2F5A] dark:bg-[#0C1836] dark:text-[#C5CFE0] dark:placeholder-[#4A6491] dark:focus:border-[#1677FF]/60 dark:focus:bg-[#122347] dark:focus:ring-[#1677FF]/25" + /> + {search && ( + + )} +
+ {/* 结果计数 */} +
+
+ 检索到最终结果:{" "} + {apps.length} 个关联应用 +
+ {search && ( + + )} +
+ + {/* 卡片网格 / 空态 */} {apps.length === 0 ? ( -
- {isLoading ? "加载中…" : "暂无可用应用"} +
+
+ +
+

+ {isLoading ? "正在加载应用…" : search ? "未找到匹配的应用" : "暂无可用应用"} +

+

+ {isLoading + ? "请稍候。" + : search + ? "尝试更换关键词,或清除搜索查看全部应用。" + : "应用由管理员在「应用管理」中登记后会出现在这里。"} +

) : ( <> -
+
{pageApps.map((app) => ( - + void copyUrl(app)} + onOpen={() => openApp(app)} + /> ))}
- + {apps.length > PAGE_SIZE && ( + + )} )} + + {/* 页脚 */} +
+ 应用导航工作台 • 优雅可靠的任务中转入口 +
+
+
+ ); +} + +// ── 单张应用卡 ─────────────────────────────────────────────────────────────── + +function AppCard({ + app, + copied, + onCopy, + onOpen, +}: { + app: LightApp; + copied: boolean; + onCopy: () => void; + onOpen: () => void; +}) { + const Icon = ICON_POOL[hashIndex(app.appId, ICON_POOL.length)]; + const theme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)]; + + const parsedUrl = buildLightAppUrl(app); + const hasParams = (app.routeParams ?? []).some((p) => p.key) && parsedUrl !== app.url; + + return ( +
+ {/* 上:图标 + 标题 + 描述 */} +
+
+
+ +
+
+ +

+ {app.name} +

+ +

+ {app.description || "暂无应用详细描述说明,点击进入下方链接查看详情。"} +

+
+ + {/* 下:URL 预览 + 动作 */} +
+
+
+ 拼接后目标路由终端: + + 自动拼接已激活参 + +
+
+ {parsedUrl || "(地址为空)"} +
+ {hasParams && ( +
+ + 包含动态拼接参数 +
+ )} +
+ +
+ + + +
); diff --git a/frontend-web/src/pages/LightAppManagementPage.tsx b/frontend-web/src/pages/LightAppManagementPage.tsx index fd2b16c5..21a805b7 100644 --- a/frontend-web/src/pages/LightAppManagementPage.tsx +++ b/frontend-web/src/pages/LightAppManagementPage.tsx @@ -528,7 +528,7 @@ export default function LightAppManagementPage() { className="flex h-7 cursor-pointer items-center space-x-1 rounded-[2px] border border-[#DBDBDB] bg-white px-2.5 text-[12px] font-bold text-[#575757] transition-colors hover:border-[#F5222D] hover:text-[#F5222D]" > - + 添加路由参数 + 添加路由参数
diff --git a/frontend-web/src/roundtable-planning/components/ChainFlowDiagram.tsx b/frontend-web/src/roundtable-planning/components/ChainFlowDiagram.tsx index c5a49ebb..808c0d44 100644 --- a/frontend-web/src/roundtable-planning/components/ChainFlowDiagram.tsx +++ b/frontend-web/src/roundtable-planning/components/ChainFlowDiagram.tsx @@ -47,13 +47,26 @@ export interface ChainFlowDiagramProps { } // ── 布局常量(世界坐标,单位 px)───────────────────────────────────────────── -const NODE_W = 188; -const NODE_H = 66; +const NODE_W = 206; +const NODE_H = 84; const COL_GAP = 116; const ROW_GAP = 22; const PAD = 52; const COORDINATOR_ID = "__coordinator__"; +// 节点卡左侧描边(hex,内联上色避免被 border-border 覆盖)/ 角色行配色(按头像配色)。 +// 参考「业务链条配置器」设计:彩色左描边 + 同色角色行。 +const NODE_COLORS: Record = { + blue: { hex: "#3b82f6", role: "text-blue-600 dark:text-blue-300" }, + purple: { hex: "#a855f7", role: "text-purple-600 dark:text-purple-300" }, + emerald: { hex: "#10b981", role: "text-emerald-600 dark:text-emerald-300" }, + amber: { hex: "#f59e0b", role: "text-amber-600 dark:text-amber-300" }, + pink: { hex: "#ec4899", role: "text-pink-600 dark:text-pink-300" }, + human: { hex: "#8b5cf6", role: "text-violet-600 dark:text-violet-300" }, +}; +const nodeColorOf = (t: string) => + NODE_COLORS[t] ?? { hex: "#94a3b8", role: "text-muted-foreground" }; + const MIN_SCALE = 0.25; const MAX_SCALE = 2.6; @@ -119,6 +132,7 @@ export function ChainFlowDiagram({ stages, onNodeClick, className }: ChainFlowDi for (const nb of b.nodes) { edges.push({ id: `${k}:${na.id}->${nb.id}`, + // 直角折线:右出 → 到中线 → 竖直 → 进左侧(尖角,不抹圆)。 d: `M ${na.rightX} ${na.cy} H ${midX} V ${nb.cy} H ${nb.leftX}`, }); } @@ -232,13 +246,13 @@ export function ChainFlowDiagram({ stages, onNodeClick, className }: ChainFlowDi
{ if (e.button !== 0) return; @@ -272,7 +286,7 @@ export function ChainFlowDiagram({ stages, onNodeClick, className }: ChainFlowDi ))} - {/* 节点卡片 */} - {layout.cols.flatMap((col) => + {/* 节点卡片(参考「业务链条配置器」样式:左彩色描边 + 头像 + 名称 + + L-阶段徽标 + 同色角色行 + 底部 Ready 心跳条;图标仍用我方 getAvatar)。 */} + {layout.cols.flatMap((col, colIdx) => col.nodes.map((n) => { const isCoordinator = n.id === COORDINATOR_ID; + const nc = nodeColorOf(n.avatarType); return (
-
{getAvatar(n.avatarType, "h-9 w-9")}
-
-
{n.name}
-
- {isCoordinator ? "按阶段派活 · 综合收口" : n.description || n.id} + {/* 上:头像 + 名称 + 阶段徽标 + 角色行 */} +
+
{getAvatar(n.avatarType, "h-7 w-7")}
+
+
+ {n.name} + + {isCoordinator ? "核心" : `L-${colIdx}`} + +
+

+ {isCoordinator ? "按阶段派活 · 综合收口" : n.description || n.id} +

+ + {/* 下:身份 + Ready 心跳条 */} +
+ {isCoordinator ? "总控协调核心" : "智能体席位"} + + + Ready + +
); @@ -354,6 +389,16 @@ export function ChainFlowDiagram({ stages, onNodeClick, className }: ChainFlowDi ))}
+ {/* 左上状态条(参考设计的「拓扑画布」浮条)。全屏时让位给退出按钮。 */} + {!fullscreen && ( +
+ + 拓扑画布 + + 节点 {layout.allNodes.length} +
+ )} + {/* 工具条:缩小 / 放大 / 适应 / 全屏 */}
zoomBy(1.2)}> diff --git a/frontend-web/src/roundtable-planning/components/Step2Panel.tsx b/frontend-web/src/roundtable-planning/components/Step2Panel.tsx index b1105b3f..128d2b05 100644 --- a/frontend-web/src/roundtable-planning/components/Step2Panel.tsx +++ b/frontend-web/src/roundtable-planning/components/Step2Panel.tsx @@ -2,7 +2,7 @@ * Step 2 主面板 —— 多智能体圆桌研讨。 * * 包含 4 块: - * 1) 顶部 Headline + 推理模型下拉 + 挂起/恢复按钮 + * 1) 顶部 Headline + 推理模型下拉 + 暂停/恢复按钮 + 后台挂起按钮(查看历史时隐藏) * 2) Persona 抽屉(点击席位头像后展开,见 `PersonaDrawer.tsx`) * 3) 对话流(初始化占位 + 气泡列表 + ChainOfThought 折叠卡 + markdown) * 4) 底部澄清条 + 用户干预输入 + 「达成共识」入口 @@ -149,6 +149,8 @@ export interface Step2PanelProps { onProceedToStep3: () => void; /** 「后台挂起」:把当前研讨转入后端自动跑到结果绘制完成。 */ onBackgroundSuspend: () => void; + /** 是否在「查看历史记录」(从草稿/后台作业加载)—— 为 true 时隐藏「后台挂起」按钮。 */ + isViewingHistory?: boolean; onTriggerToast: (msg: string) => void; } @@ -194,6 +196,7 @@ export function Step2Panel(props: Step2PanelProps) { onSavePersona, onProceedToStep3, onBackgroundSuspend, + isViewingHistory, onTriggerToast, } = props; @@ -462,6 +465,19 @@ export function Step2Panel(props: Step2PanelProps) { 暂停会商 )} + {/* 后台挂起:放在暂停/恢复按钮右侧。查看历史记录时隐藏(isViewingHistory), + 重新从第一步进入第二步研讨时(isViewingHistory 为假)正常显示。 */} + {SHOW_BACKGROUND_SUSPEND && !isViewingHistory && ( + + )}
@@ -765,21 +781,6 @@ export function Step2Panel(props: Step2PanelProps) { 干预并发送 - {SHOW_BACKGROUND_SUSPEND && ( - <> -
- - - - )} - -
+
-

{seat.title} · 席位专页

+

{seat.title} · 席位专页

{seat.completeness > 0 && ( - + 达成度: {seat.completeness}% )}
- {seat.analyst && 席任分析官:{seat.analyst}} + {seat.analyst && 席任分析官:{seat.analyst}}
- + Seat node: {String(seat.id).toUpperCase()}
@@ -85,11 +85,11 @@ export function DetailView({ activeTab, onBackToOverview, onNavigate }: DetailVi
{variant !== "release" && ( -
- -

此席位核心交付 · 使命概述

+
+ +

此席位核心交付 · 使命概述

-

+

{seat.description || "(无摘要)"}

@@ -102,20 +102,20 @@ export function DetailView({ activeTab, onBackToOverview, onNavigate }: DetailVi
{seat.metrics.length > 0 && ( -
-

席位评定量化得分

+
+

席位评定量化得分

{seat.metrics.map((met, i) => ( -
+
- {met.label} + {met.label} {met.value} 分
-
+
@@ -125,10 +125,10 @@ export function DetailView({ activeTab, onBackToOverview, onNavigate }: DetailVi )} {seat.risks.length > 0 && ( - -
- -

此专项下属风险与关注点

+ +
+ +

此专项下属风险与关注点

{seat.risks.map((risk) => ( @@ -150,15 +150,15 @@ export function DetailView({ activeTab, onBackToOverview, onNavigate }: DetailVi {(deps.length > 0 || supports.length > 0) && ( -
-

席位依赖关系

+
+

席位依赖关系

{deps.map((d) => ( @@ -167,7 +167,7 @@ export function DetailView({ activeTab, onBackToOverview, onNavigate }: DetailVi @@ -177,19 +177,19 @@ export function DetailView({ activeTab, onBackToOverview, onNavigate }: DetailVi )} {recommended.length > 0 && ( - -
-

研讨协同席位推荐

+ +
+

研讨协同席位推荐

{recommended.map((other) => ( ))}
diff --git a/frontend-web/src/roundtable-planning/dashboard/components/DraftingCard.tsx b/frontend-web/src/roundtable-planning/dashboard/components/DraftingCard.tsx index 84177157..dc1ed7f9 100644 --- a/frontend-web/src/roundtable-planning/dashboard/components/DraftingCard.tsx +++ b/frontend-web/src/roundtable-planning/dashboard/components/DraftingCard.tsx @@ -22,22 +22,16 @@ export function DraftingCard({
- {/* 左上角标(品牌正红 #F5222D,对齐设计原图) */} -
+ {/* 左上角标(浅色品牌正红 #F5222D / 深色主蓝,取色见 dashboard.css 的 .dash-corner) */} +
{/* 右下角标 */} -
+
{/* 极淡绘图网格 overlay */}
{children}
diff --git a/frontend-web/src/roundtable-planning/dashboard/components/MetricsHeader.tsx b/frontend-web/src/roundtable-planning/dashboard/components/MetricsHeader.tsx index feecc67b..1e7c6485 100644 --- a/frontend-web/src/roundtable-planning/dashboard/components/MetricsHeader.tsx +++ b/frontend-web/src/roundtable-planning/dashboard/components/MetricsHeader.tsx @@ -27,24 +27,24 @@ export function MetricsHeader({ onCardClick }: MetricsHeaderProps) { onClick={() => onCardClick?.(metric.id)} >
- + {metric.label}
- + {metric.value} - {metric.unit && {metric.unit}} + {metric.unit && {metric.unit}} {metric.trend && ( {metric.trend.type === "up" ? "▲" : "▼"} diff --git a/frontend-web/src/roundtable-planning/dashboard/components/RiskDonut.tsx b/frontend-web/src/roundtable-planning/dashboard/components/RiskDonut.tsx index d49c5708..0e6ca4f2 100644 --- a/frontend-web/src/roundtable-planning/dashboard/components/RiskDonut.tsx +++ b/frontend-web/src/roundtable-planning/dashboard/components/RiskDonut.tsx @@ -30,13 +30,13 @@ export function RiskDonut({ onCategoryHover }: RiskDonutProps) { return (
-
- -

核心风险分类分布

+
+ +

核心风险分类分布

{empty ? ( -
+
本轮研讨未识别到显式风险项。
@@ -44,11 +44,11 @@ export function RiskDonut({ onCategoryHover }: RiskDonutProps) { <>
{/* 风险总数(去掉百分比圆环,保留大数字,对齐设计原图) */} -
- +
+ {activeCategory ? `${activeCategory.percentage}%` : riskTotal} - + {activeCategory ? activeCategory.name : "项核心风险"}
@@ -61,7 +61,7 @@ export function RiskDonut({ onCategoryHover }: RiskDonutProps) {
handleMouseEnter(cat)} @@ -70,8 +70,8 @@ export function RiskDonut({ onCategoryHover }: RiskDonutProps) {
- {cat.name} - {cat.value}% + {cat.name} + {cat.value}%
@@ -81,20 +81,20 @@ export function RiskDonut({ onCategoryHover }: RiskDonutProps) {
{/* 悬停细项抽屉 */} -
+
{activeCategory ? (
{activeCategory.name}包含细项: -
    +
      {activeCategory.details.slice(0, 3).map((det, i) => (
    • {det}
    • ))}
) : ( -
+
悬停上方分类标签,查看各项底层细节。
@@ -104,9 +104,9 @@ export function RiskDonut({ onCategoryHover }: RiskDonutProps) { )}
-
+
风险按高/中/低等级聚合占比 - + 共 {riskTotal} 项
diff --git a/frontend-web/src/roundtable-planning/dashboard/components/Roadmap.tsx b/frontend-web/src/roundtable-planning/dashboard/components/Roadmap.tsx index c7205c8c..cdcdc781 100644 --- a/frontend-web/src/roundtable-planning/dashboard/components/Roadmap.tsx +++ b/frontend-web/src/roundtable-planning/dashboard/components/Roadmap.tsx @@ -19,12 +19,12 @@ export function Roadmap() { return (
-
-

+
+

执行路线 - 点击阶段卡片展开关键里程碑 + 点击阶段卡片展开关键里程碑

- + 共 {ROADMAP_PHASES.length} 个阶段
@@ -36,42 +36,42 @@ export function Roadmap() { return ( togglePhase(phase.id) : undefined} >
- + {phase.id} -

{phase.title}

+

{phase.title}

{phase.period && ( - + {phase.period} )}
-

{phase.description}

+

{phase.description}

{hasHighlights && isExpanded && ( - + 此阶段关键里程碑:
    {phase.highlights.map((hlt, idx) => ( -
  • - +
  • + {hlt}
  • ))} @@ -80,10 +80,10 @@ export function Roadmap() { )}
-
+
Timeline Segment {phase.id} {hasHighlights && ( -
+
{isExpanded ? "收起" : "详细指标"} {isExpanded ? : }
diff --git a/frontend-web/src/roundtable-planning/dashboard/components/SeatDeliveryOverview.tsx b/frontend-web/src/roundtable-planning/dashboard/components/SeatDeliveryOverview.tsx index 22a37015..c5fcfb4e 100644 --- a/frontend-web/src/roundtable-planning/dashboard/components/SeatDeliveryOverview.tsx +++ b/frontend-web/src/roundtable-planning/dashboard/components/SeatDeliveryOverview.tsx @@ -22,9 +22,9 @@ export function SeatDeliveryOverview({ onSeatSelect, activeSeatId }: SeatDeliver return (
-

+

席位交付总览 - 点击卡片快速切入对应专属席位页 + 点击卡片快速切入对应专属席位页

@@ -36,39 +36,39 @@ export function SeatDeliveryOverview({ onSeatSelect, activeSeatId }: SeatDeliver onSeatSelect(seat.id)} >
-
+
- {seat.title} + {seat.title}
-
+
席位页
{seat.analyst && ( -
+
汇报人: {seat.analyst}
)} -

{seat.description}

+

{seat.description}

{seat.metrics.length > 0 && (
{seat.metrics.slice(0, 2).map((met, i) => (
-
+
{met.label} - {met.value} + {met.value}
-
+
{seat.risks.length > 0 && ( -
+
{seat.risks.slice(0, 3).map((risk) => (
{risk.level === "high" ? ( @@ -110,27 +110,27 @@ export function SeatDeliveryOverview({ onSeatSelect, activeSeatId }: SeatDeliver {summarySeat && ( onSeatSelect(summarySeat.id)} >
-
- +
+
-

{summarySeat.title}

- +

{summarySeat.title}

+ 决策中心终审
-

{summarySeat.description}

+

{summarySeat.description}

-
+
进入 {summarySeat.title} 专页 →
diff --git a/frontend-web/src/roundtable-planning/dashboard/components/SeatShowcase.tsx b/frontend-web/src/roundtable-planning/dashboard/components/SeatShowcase.tsx index 36562340..24d8177c 100644 --- a/frontend-web/src/roundtable-planning/dashboard/components/SeatShowcase.tsx +++ b/frontend-web/src/roundtable-planning/dashboard/components/SeatShowcase.tsx @@ -40,7 +40,7 @@ function allBullets(groups: SeatSection[]): string[] { function EmptyShowcase() { return ( -

该席位暂无结构化汇报内容。

+

该席位暂无结构化汇报内容。

); } @@ -82,7 +82,7 @@ function MatrixShowcase({ groups, color }: { groups: SeatSection[]; color: strin return (
-
+
结构化框架 · 维度拆解
@@ -90,14 +90,14 @@ function MatrixShowcase({ groups, color }: { groups: SeatSection[]; color: strin {groups.map((g, i) => (
-
+
维度 {i + 1}
-
{g.heading || `小节 ${i + 1}`}
- +
{g.heading || `小节 ${i + 1}`}
+ {g.bullets.length} 项要点
@@ -106,28 +106,28 @@ function MatrixShowcase({ groups, color }: { groups: SeatSection[]; color: strin -
-
- 各维度关键交付要点对标 - +
+
+ 各维度关键交付要点对标 + 共 {groups.reduce((n, g) => n + g.bullets.length, 0)} 项
- + - + {groups.flatMap((g, gi) => g.bullets.map((b, bi) => { rowNo += 1; return ( - - + + @@ -151,20 +151,20 @@ function GaugeShowcase({ seat, groups, color }: { seat: SeatCard; groups: SeatSe
- 核心洞察聚焦 · 关键论点拆解 + 核心洞察聚焦 · 关键论点拆解
- 本席位核心论断 -

{seat.description || "(无摘要)"}

+ 本席位核心论断 +

{seat.description || "(无摘要)"}

{bullets.map((b, i) => ( -
+
{i + 1} -

{b}

+

{b}

))} @@ -218,7 +218,7 @@ function CompareShowcase({ groups, color }: { groups: SeatSection[]; color: stri {p.bullets.map((b, j) => (
  • - {b} + {b}
  • ))} @@ -236,12 +236,12 @@ function AuditShowcase({ bullets, color }: { bullets: string[]; color: string }) return ( -
    - +
    + 要点核对台 - + 已确认 {done.length}/{bullets.length}
    @@ -254,19 +254,19 @@ function AuditShowcase({ bullets, color }: { bullets: string[]; color: string }) key={i} onClick={() => toggle(i)} className={`flex cursor-pointer select-none items-start gap-3 rounded border p-3 transition-all duration-300 ${ - checked ? "border-emerald-300 bg-emerald-50/40" : "border-slate-200 bg-white hover:border-slate-300" + checked ? "border-emerald-300 bg-emerald-50/40 dark:border-emerald-500/40 dark:bg-emerald-500/10" : "border-slate-200 bg-white hover:border-slate-300 dark:border-[var(--dash-border)] dark:bg-[var(--dash-card)] dark:hover:border-[var(--dash-border-soft)]" }`} >
    {checked && }
    {b} @@ -284,17 +284,17 @@ function ReleaseShowcase({ seat, bullets }: { seat: SeatCard; bullets: string[] const [signed, setSigned] = useState(false); return (
    - -

    - + +

    + 研讨结果最终发布声明书 (Executive Release)

    -

    {seat.description || "(无总结陈述)"}

    +

    {seat.description || "(无总结陈述)"}

    {bullets.length > 0 && ( -
    - 关键结论与调和事项: -
    +
    + 关键结论与调和事项: +
    {bullets.map((b, i) => (
    @@ -306,11 +306,11 @@ function ReleaseShowcase({ seat, bullets }: { seat: SeatCard; bullets: string[] )} -
    +
    - -
    总台最终数字签署确认栏
    -

    勾选此项即代表会议全席专家一致同意此交付终稿,封盘归档。

    + +
    总台最终数字签署确认栏
    +

    勾选此项即代表会议全席专家一致同意此交付终稿,封盘归档。

    序号 所属维度 关键交付要点
    {String(rowNo).padStart(2, "0")}
    {String(rowNo).padStart(2, "0")} {bi === 0 ? g.heading || `维度 ${gi + 1}` : ""}