diff --git a/frontend-web/.env.development b/frontend-web/.env.development
index b78f859a..3837c075 100644
--- a/frontend-web/.env.development
+++ b/frontend-web/.env.development
@@ -8,7 +8,7 @@ VITE_USE_REAL_DATA=true
VITE_MOCK_COP_TASK_DETAIL=true
# 研判情报报告(大屏绘制智能体深链 selectSqReport)数据源:true 走本地样例假数据(真实接口未就绪时调试用);联调通过后置 false。
-VITE_MOCK_SQ_REPORT=false
+VITE_MOCK_SQ_REPORT=true
# 深链任务详情(cop-task-detail:任务名称/描述)数据源:true 走本地假数据(真实接口未就绪时调试用);联调通过后置 false。
VITE_MOCK_COP_TASK_DETAIL=true
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/Decision.tsx b/frontend-web/src/roundtable-planning/dashboard-agent/Decision.tsx
new file mode 100644
index 00000000..fcf8d0a0
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/Decision.tsx
@@ -0,0 +1,318 @@
+/**
+ * 「大屏绘制智能体」决心建议页 —— 任务信息 / 作战主张 / 打击目标 / 实施要点 / 决心总结
+ * (按设计图 dp-6.png 一比一实现)。
+ *
+ * **dashboard-agent 独占**:作为 DashboardApp `pages` 插槽里「决心建议」Tab 的内容,
+ * 不影响多智能体会商(Step3)大屏多页的样式。当前为「静态页面」,文案为占位内容,
+ * 动态数据后续接入。版式包含:
+ * 1. 任务信息 + 任务行动方向 / 作战主张(3 个编号方案)
+ * 2. 打击目标 / 目标建议(表)+ 实施要点
+ * 3. 决心建议总结(总结段 + 统计块)
+ */
+import { ClipboardList, Crosshair, Target, ListChecks, Flag, Clock, ShieldCheck, AlertTriangle, type LucideIcon } from "lucide-react";
+
+import { Card, CardTitle, Table, Td, Tag, NoData, type Tone } from "@/roundtable-planning/dashboard-agent/ui";
+import type { DecisionData } from "@/roundtable-planning/dashboard-agent/sq-report";
+
+// ── 占位数据 ──────────────────────────────────────────────────────────────────
+
+const TASK_META: Array<{ label: string; value: string }> = [
+ { label: "任务标题", value: "夺控 XX 关键地域,压制敌防御体系" },
+ { label: "任务类型", value: "进攻作战" },
+ { label: "任务时限", value: "5 小时内" },
+ { label: "任务方向", value: "XX 方向" },
+];
+const TASK_INTRO =
+ "根据上级指示,我方对敌防御体系实施进攻,夺控 XX 关键地域,破坏敌指挥控制能力与防御部署,压制敌纵深支援能力,为后续行动创造有利条件。";
+
+interface Proposal {
+ no: number;
+ title: string;
+ bullets: string[];
+ rec: string;
+ recTone: Tone;
+}
+const PROPOSALS: Proposal[] = [
+ {
+ no: 1,
+ title: "主攻突破,速战速决",
+ bullets: ["集中优势兵力突击敌防御薄弱点", "快速突破并夺控要点", "压制敌纵深火力支援能力"],
+ rec: "高",
+ recTone: "red",
+ },
+ {
+ no: 2,
+ title: "稳固推进,逐步压制",
+ bullets: ["先期压制敌火力与指挥体系", "分段推进,扩大战果", "稳固圈占,巩固防御"],
+ rec: "中",
+ recTone: "amber",
+ },
+ {
+ no: 3,
+ title: "迂回包抄,分割合围",
+ bullets: ["迂回敌防御侧翼实施包抄", "切割敌防御部署", "围点打援,歼敌纵深兵力"],
+ rec: "中",
+ recTone: "amber",
+ },
+];
+
+interface TargetRow {
+ no: number;
+ name: string;
+ type: string;
+ importance: string;
+ impTone: Tone;
+ priority: string;
+ priTone: Tone;
+ effect: string;
+ method: string;
+ timing: string;
+}
+const TARGETS: TargetRow[] = [
+ { no: 1, name: "敌前沿防御阵地", type: "防御工事", importance: "高", impTone: "red", priority: "高", priTone: "red", effect: "摧毁瘫痪,降低防御能力", method: "精确打击", timing: "D+0~0+3h" },
+ { no: 2, name: "敌指挥控制节点", type: "指挥所", importance: "高", impTone: "red", priority: "高", priTone: "red", effect: "压制致瘫", method: "火力打击", timing: "D+0~0+5h" },
+ { no: 3, name: "敌炮兵阵地", type: "火力群", importance: "高", impTone: "red", priority: "中", priTone: "amber", effect: "压制摧毁", method: "远程火力", timing: "D+0~0+4h" },
+ { no: 4, name: "敌防空火力点", type: "防空阵地", importance: "中", impTone: "amber", priority: "高", priTone: "red", effect: "前期压制夺取制空", method: "电子+火力", timing: "D+0~0+2h" },
+ { no: 5, name: "敌纵深兵力", type: "增援部队", importance: "中", impTone: "amber", priority: "中", priTone: "amber", effect: "迟滞阻断", method: "火力封锁", timing: "D+0~0+18h" },
+];
+
+const KEY_POINTS = [
+ "集中优势兵力与火力,确保主攻方向突破能力。",
+ "先期制电、压制网络,为突击与夺控创造信息优势。",
+ "强化诸军兵种协同,提升整体作战效能。",
+ "严格协同空地火力与地面行动,形成体系打击合力。",
+ "做好预备兵力与增援应对预案,保持持续作战能力。",
+ "注重战场舆情与目标善后,及时修订完善方案。",
+];
+
+const SUMMARY =
+ "建议采取「主攻突破,速战速决」的作战行动方向,集中优势兵力与火力,优先打击敌防御阵地、敌指挥控制节点与敌纵深兵力,迅速压制敌防御体系,夺控 XX 关键地域,并同步破坏敌纵深支援能力,形成战役全局有利态势。";
+
+interface SummaryStat {
+ icon: LucideIcon;
+ label: string;
+ value: string;
+}
+const SUMMARY_STATS: SummaryStat[] = [
+ { icon: Crosshair, label: "推荐行动方向", value: "主攻突破,速战速决" },
+ { icon: Clock, label: "建议处置时机", value: "D+0 ~ D+6h" },
+ { icon: Target, label: "关键目标数量", value: "5 个" },
+ { icon: ShieldCheck, label: "重点保障", value: "火力压制 / 空中协同 / 机动补给" },
+ { icon: AlertTriangle, label: "风险提示", value: "敌防空反制 / 增援压力 / 地形限制" },
+];
+
+// ── 主组件(有真实数据走真实渲染,否则回退静态占位) ─────────────────────────────
+
+export function Decision({ data }: { data?: DecisionData | null }) {
+ return data ? : ;
+}
+
+function DecisionPlaceholder() {
+ return (
+
+ {/* 1. 任务信息 + 作战主张 */}
+
+
+
+
+
+ {TASK_META.map((m) => (
+
+
- {m.label}
+ - {m.value}
+
+ ))}
+
+
+
+
+
+
+
+
+ {PROPOSALS.map((p) => (
+
+
+
+ {p.no}
+
+
{p.title}
+
+
+ {p.bullets.map((b) => (
+ -
+
+ {b}
+
+ ))}
+
+
+ 推荐程度
+ {p.rec}
+
+
+ ))}
+
+
+
+
+ {/* 2. 打击目标 + 实施要点 */}
+
+
+
+
+
+ {TARGETS.map((t) => (
+
+ | {t.no} |
+ {t.name} |
+ {t.type} |
+
+ {t.importance}
+ |
+
+ {t.priority}
+ |
+ {t.effect} |
+ {t.method} |
+ {t.timing} |
+
+ ))}
+
+
+
+
+
+
+
+ {KEY_POINTS.map((p, i) => (
+ -
+
+ {i + 1}
+
+ {p}
+
+ ))}
+
+
+
+
+ {/* 3. 决心建议总结 */}
+
+
+
+
+ {SUMMARY}
+
+
+ {SUMMARY_STATS.map((s) => (
+
+
+
+ {s.label}
+
+
{s.value}
+
+ ))}
+
+
+
+
+ );
+}
+
+// ── 真实数据渲染 ──────────────────────────────────────────────────────────────
+function DecisionReal({ data }: { data: DecisionData }) {
+ return (
+
+ {/* 任务信息 + 作战目的 */}
+
+
+
+
+ {data.taskTitle && (
+
+
任务标题
+
{data.taskTitle}
+
+ )}
+ {data.taskIntro && (
+
+
任务简介
+
{data.taskIntro}
+
+ )}
+
+
+
+ {data.objectives.length > 0 && (
+
+
+
+ {data.objectives.map((o, i) => (
+ -
+ {i + 1}
+ {o}
+
+ ))}
+
+
+ )}
+
+
+ {/* 打击目标 + 实施要点 */}
+
+ {data.targets.length > 0 && (
+
+
+
+
+ {data.targets.map((t, i) => (
+
+ | {t.name || "—"} |
+ {t.desc || "—"} |
+ {t.reason || "—"} |
+ {t.impact || "—"} |
+ {t.basis || "—"} |
+
+ ))}
+
+
+
+ )}
+
+ {data.keyPoints.length > 0 && (
+
+
+
+ {data.keyPoints.map((p, i) => (
+ -
+ {i + 1}
+ {p}
+
+ ))}
+
+
+ )}
+
+
+ {/* 决心建议总结 */}
+ {data.summary && (
+
+
+
+
+ )}
+
+ );
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/Disposal.tsx b/frontend-web/src/roundtable-planning/dashboard-agent/Disposal.tsx
new file mode 100644
index 00000000..b7440f2c
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/Disposal.tsx
@@ -0,0 +1,245 @@
+/**
+ * 「大屏绘制智能体」处置建议页 —— 处置总结 / 目标 / 处置建议表 / 关键事态
+ * (按设计图 dp-7.png 一比一实现)。
+ *
+ * **dashboard-agent 独占**:作为 DashboardApp `pages` 插槽里「处置建议」Tab 的内容,
+ * 不影响多智能体会商(Step3)大屏多页的样式。当前为「静态页面」,文案为占位内容,
+ * 动态数据后续接入。版式包含:
+ * 1. 处置建议总结 + 目标(5 个处置目标卡)
+ * 2. 处置建议(大表)+ 关键事态(编号事件列表)
+ */
+import {
+ ClipboardCheck,
+ ClipboardList,
+ Flag,
+ ShieldAlert,
+ Megaphone,
+ Users,
+ Gavel,
+ Handshake,
+ AlertTriangle,
+ Star,
+ type LucideIcon,
+} from "lucide-react";
+
+import { Card, CardTitle, FooterNote, Table, Td, Tag, NoData, type Tone } from "@/roundtable-planning/dashboard-agent/ui";
+import type { DisposalData } from "@/roundtable-planning/dashboard-agent/sq-report";
+
+// ── 占位数据 ──────────────────────────────────────────────────────────────────
+
+const SUMMARY_ITEMS: Array<{ label: string; text: string }> = [
+ { label: "总体判断", text: "当前事件可控但需密切关注,舆情存在扩散与升级风险,需快速反应、综合施策。" },
+ { label: "主体策略", text: "以澄清事实、权威发声为主,依法依规处置,统筹线上线下,压缩谣言空间。" },
+ { label: "处置重点", text: "统一对外口径,加强舆论引导,强化协同联动,做好重点群体与重点平台工作。" },
+ { label: "风险防控", text: "警惕谣言扩散、情绪对立与境外炒作,防范局部聚集与事态升级,守牢安全底线。" },
+];
+
+interface GoalCard {
+ icon: LucideIcon;
+ title: string;
+ text: string;
+}
+const GOALS: GoalCard[] = [
+ { icon: ShieldAlert, title: "阻止威胁扩张", text: "及时防御与压制扩张,减少不实信息蔓延与影响扩散。" },
+ { icon: Megaphone, title: "澄清事实,统一口径", text: "权威发布、统一口径,及时回应社会关切。" },
+ { icon: Users, title: "维护社会稳定", text: "引导社会情绪,防范对立与局部聚集,维护大局稳定。" },
+ { icon: Gavel, title: "依法严正应对", text: "对违法违规行为依法处置,严正立场、坚守底线。" },
+ { icon: Handshake, title: "提升协同效能", text: "强化多部门协同联动,形成处置合力与快速反应能力。" },
+];
+
+interface DisposalRow {
+ no: number;
+ content: string;
+ timing: string;
+ subject: string;
+ caliber: string;
+ method: string;
+ effect: string;
+ basis: string;
+}
+const DISPOSALS: DisposalRow[] = [
+ { no: 1, content: "澄清事实,发布权威信息", timing: "立即", subject: "宣传部门 / 权威发布机构", caliber: "事实 / 立场", method: "权威发布、多渠道同步", effect: "统一口径,稳定舆论", basis: "公开透明" },
+ { no: 2, content: "管控舆情,做强引导", timing: "持续", subject: "网信部门 / 平台", caliber: "引导 / 疏导", method: "加强议题设置与正向引导", effect: "压制负面,提升正能量", basis: "依法管理" },
+ { no: 3, content: "防范网络,加强溯源处置", timing: "快速", subject: "公安部门 / 平台", caliber: "溯源 / 处置", method: "监测溯源、依法处置", effect: "切断扩散链条", basis: "合法合规" },
+ { no: 4, content: "协同处置,打击违法行为", timing: "视情", subject: "多部门联动", caliber: "联动 / 执法", method: "联合执法、分级处置", effect: "形成有效震慑", basis: "严正应对" },
+ { no: 5, content: "防范升级,维护稳定", timing: "持续", subject: "综合部门", caliber: "稳控 / 防控", method: "重点防控、预案处置", effect: "守牢稳定底线", basis: "安全第一" },
+];
+
+interface KeyEvent {
+ no: number;
+ title: string;
+ time: string;
+ impact: string;
+ impactTone: Tone;
+}
+const EVENTS: KeyEvent[] = [
+ { no: 1, title: "不实信息在社交平台扩散", time: "05-16 09:30", impact: "高", impactTone: "red" },
+ { no: 2, title: "部分舆情持续发酵引发讨论", time: "05-16 08:40", impact: "中", impactTone: "amber" },
+ { no: 3, title: "相关话题持续热度上升", time: "05-16 10:05", impact: "中", impactTone: "amber" },
+ { no: 4, title: "线下出现小范围聚集活动", time: "05-16 10:20", impact: "高", impactTone: "red" },
+ { no: 5, title: "涉事相关单位回应迟缓", time: "05-16 09:50", impact: "中", impactTone: "amber" },
+];
+
+// ── 主组件(有真实数据走真实渲染,否则回退静态占位) ─────────────────────────────
+
+export function Disposal({ data }: { data?: DisposalData | null }) {
+ return data ? : ;
+}
+
+function DisposalPlaceholder() {
+ return (
+
+ {/* 1. 处置建议总结 + 目标 */}
+
+ {/* 处置建议总结 */}
+
+
+
+
+
+ 综合处置建议
+ 稳健(取义)
+
+
+ 建议执行优先级
+
+ {Array.from({ length: 5 }).map((_, i) => (
+
+ ))}
+
+
+
+
+
+ {/* 目标 */}
+
+
+
+ {GOALS.map((g) => (
+
+
+
+
+
{g.title}
+
{g.text}
+
+ ))}
+
+
+
+
+ {/* 2. 处置建议表 + 关键事态 */}
+
+ {/* 处置建议 */}
+
+
+
+
+ {DISPOSALS.map((d) => (
+
+ | {d.no} |
+ {d.content} |
+ {d.timing} |
+ {d.subject} |
+ {d.caliber} |
+ {d.method} |
+ {d.effect} |
+ {d.basis} |
+
+ ))}
+
+
+
+ 上述处置应在法治框架内规范实施,统筹线上线下,确保快速反应、综合施策。
+
+
+
+ {/* 关键事态 */}
+
+
+
+ {EVENTS.map((e) => (
+ -
+
+ {e.no}
+
+
+
{e.title}
+
+ {e.time}
+ 影响
+ {e.impact}
+
+
+
+ ))}
+
+ 关键事态需密切跟踪,及时研判处置。
+
+
+
+ );
+}
+
+// ── 真实数据渲染 ──────────────────────────────────────────────────────────────
+function DisposalReal({ data }: { data: DisposalData }) {
+ return (
+
+ {/* 目标 + 处置总结 */}
+ {(data.goal || data.summary) && (
+
+ {data.goal && (
+
+
+
+
+ )}
+ {data.summary && (
+
+
+
+
+ )}
+
+ )}
+
+ {/* 处置建议表 */}
+ {data.items.length > 0 && (
+
+
+
+
+ {data.items.map((d, i) => (
+
+ | {i + 1} |
+ {d.content || "—"} |
+ {d.method || "—"} |
+ {d.tone || "—"} |
+ {d.effect || "—"} |
+ {d.subject || "—"} |
+ {d.basis || "—"} |
+
+ ))}
+
+
+
+ )}
+
+ );
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/EnemyDetail.tsx b/frontend-web/src/roundtable-planning/dashboard-agent/EnemyDetail.tsx
new file mode 100644
index 00000000..89dfff1d
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/EnemyDetail.tsx
@@ -0,0 +1,334 @@
+/**
+ * 「大屏绘制智能体」敌情页 —— 敌情详情版式(按设计图 dp-2.png 一比一实现)。
+ *
+ * **dashboard-agent 独占**:作为 DashboardApp `pages` 插槽里「敌情」Tab 的内容,
+ * 不影响多智能体会商(Step3)大屏多页的样式。当前为「静态页面」,文案为占位内容,
+ * 动态数据后续接入。版式包含:
+ * 1. 敌方目标表 + 关键事件表(上排两栏)
+ * 2. 对手叙事表(整行)
+ * 3. 重点关注对象表 + 传播渠道分析 + 风险热度研判(下排三栏)
+ */
+import { Target, CalendarClock, Users, Crosshair, Share2, Activity, ArrowUp } from "lucide-react";
+
+import { Card, CardTitle, HeadBadge, Tag, Table, Td, FooterNote, NoData, type Tone } from "@/roundtable-planning/dashboard-agent/ui";
+import type { EnemyData } from "@/roundtable-planning/dashboard-agent/sq-report";
+
+// ── 占位数据 ──────────────────────────────────────────────────────────────────
+
+interface EnemyTarget {
+ no: number;
+ name: string;
+ count: string;
+ gap: string;
+ gapTone: Tone;
+ note: string;
+}
+const TARGETS: EnemyTarget[] = [
+ { no: 1, name: "战略级", count: "2 个", gap: "+2", gapTone: "red", note: "查实战略意义重大的目标节点" },
+ { no: 2, name: "战役级", count: "3 个", gap: "+1", gapTone: "red", note: "重要枢纽与指挥控制节点" },
+ { no: 3, name: "战术级", count: "5 个", gap: "-2", gapTone: "green", note: "前沿火力点 / 机动单元" },
+ { no: 4, name: "支援级", count: "4 个", gap: "持平", gapTone: "slate", note: "后勤与通信保障目标" },
+ { no: 5, name: "关键人员", count: "78 人", gap: "+3", gapTone: "red", note: "专家与高级指挥人员,重点关注" },
+];
+
+interface KeyEvent {
+ time: string;
+ title: string;
+ brief: string;
+ impact: string;
+ impactTone: Tone;
+}
+const EVENTS: KeyEvent[] = [
+ { time: "05-16 09:30", title: "对我方侦察活动加强", brief: "敌多个 APT 组织对我重点系统进行扫描探测", impact: "高", impactTone: "red" },
+ { time: "05-16 08:45", title: "北部方向兵力调动", brief: "北部出现兵力机动迹象,向前沿地域集结", impact: "高", impactTone: "red" },
+ { time: "05-15 21:10", title: "增设通信节点", brief: "增设若干临时通信节点,提升指挥保障", impact: "中", impactTone: "amber" },
+ { time: "05-15 13:05", title: "网络渗透扫描", brief: "针对我政务与能源系统的渗透扫描频度上升", impact: "高", impactTone: "red" },
+];
+
+interface Narrative {
+ name: string;
+ view: string;
+ intent: string;
+ channel: string;
+ audience: string;
+ heat: string;
+ trend: string;
+ trendTone: Tone;
+}
+const NARRATIVES: Narrative[] = [
+ { name: "主导叙事", view: "渲染我方为地区紧张根源", intent: "争夺道义高地、动员国际舆论", channel: "境外主流媒体 / 社交平台", audience: "国际受众 / 中立国家", heat: "高", trend: "上升", trendTone: "red" },
+ { name: "配套舆论", view: "夸大我方军事活动威胁", intent: "制造焦虑、离间区域关系", channel: "本地社交媒体矩阵", audience: "周边民众", heat: "中", trend: "上升", trendTone: "red" },
+ { name: "情绪动员", view: "煽动对立与不满情绪", intent: "削弱我方内部凝聚力", channel: "短视频 / 论坛", audience: "青年群体", heat: "中", trend: "持平", trendTone: "slate" },
+];
+
+interface FocusObject {
+ no: number;
+ name: string;
+ category: string;
+ relevance: number;
+ relTone: Tone;
+ status: string;
+ statusTone: Tone;
+}
+const FOCUS: FocusObject[] = [
+ { no: 1, name: "红方网络安全组织", category: "组织", relevance: 88, relTone: "red", status: "高度活跃", statusTone: "red" },
+ { no: 2, name: "东方某无人机部队", category: "部队", relevance: 82, relTone: "red", status: "活跃", statusTone: "amber" },
+ { no: 3, name: "北部战区通信枢纽", category: "设施", relevance: 76, relTone: "amber", status: "监控中", statusTone: "amber" },
+ { no: 4, name: "某舆论账号矩阵", category: "媒体", relevance: 70, relTone: "amber", status: "活跃", statusTone: "amber" },
+ { no: 5, name: "海外某情报机构", category: "机构", relevance: 65, relTone: "slate", status: "持续观察", statusTone: "slate" },
+];
+
+interface Channel {
+ name: string;
+ pct: number;
+ desc: string;
+}
+const CHANNELS: Channel[] = [
+ { name: "即时通讯类", pct: 38, desc: "封闭式扩散,溯源难度大" },
+ { name: "社交媒体类", pct: 27, desc: "扩散速度快、覆盖面广" },
+ { name: "论坛 / 贴吧", pct: 16, desc: "话题沉淀、舆论发酵" },
+ { name: "视频平台", pct: 12, desc: "情绪煽动性强" },
+ { name: "其他渠道", pct: 7, desc: "小范围定向传播" },
+];
+
+const RISK_POINTS = [
+ "网络攻击与渗透风险偏高,关键系统面临持续探测。",
+ "信息渗透与认知作战风险上升,舆论环境复杂。",
+ "前沿方向摩擦风险存在,需防范局部突发事件。",
+];
+const RISK_CONCLUSION = "当前风险整体呈上升态势,须重点防范网络与认知域,强化预警与协同处置。";
+
+// ── 主组件(有真实数据走真实渲染,否则回退静态占位) ─────────────────────────────
+
+export function EnemyDetail({ data }: { data?: EnemyData | null }) {
+ return data ? : ;
+}
+
+function EnemyPlaceholder() {
+ return (
+
+ {/* 1. 上排:敌方目标 + 关键事件 */}
+
+ {/* 敌方目标 */}
+
+
+
+
+ >
+ }
+ />
+
+
+ {TARGETS.map((t) => (
+
+ | {t.no} |
+ {t.name} |
+ {t.count} |
+
+ {t.gap}
+ |
+ {t.note} |
+
+ ))}
+
+
+
+
+ {/* 关键事件 */}
+
+
+
+
+ {EVENTS.map((e) => (
+
+ | {e.time} |
+ {e.title} |
+ {e.brief} |
+
+ {e.impact}
+ |
+
+ ))}
+
+
+
+ 近期事件以网络空间动作与军事活动配合为主,整体风险趋升,需持续跟踪重点节点。
+
+
+
+
+ {/* 2. 对手叙事(整行) */}
+
+
+
+
+ {NARRATIVES.map((n) => (
+
+ | {n.name} |
+ {n.view} |
+ {n.intent} |
+ {n.channel} |
+ {n.audience} |
+
+ {n.heat}
+ |
+
+ {n.trend}
+ |
+
+ ))}
+
+
+
+ 对手叙事以「去式化行动」「外部归因」为核心,须以精准事实与权威信息及时反制,压缩其传播空间。
+
+
+
+ {/* 3. 下排:重点关注对象 + 传播渠道分析 + 风险热度研判 */}
+
+ {/* 重点关注对象 */}
+
+
+
+
+ {FOCUS.map((f) => (
+
+ | {f.no} |
+ {f.name} |
+ {f.category} |
+
+
+ {f.relevance}%
+
+ |
+
+ {f.status}
+ |
+
+ ))}
+
+
+
+
+ {/* 传播渠道分析 */}
+
+
+
+ {CHANNELS.map((c) => (
+ -
+
+ {c.name}
+ {c.pct}%
+
+
+ {c.desc}
+
+ ))}
+
+
+
+ {/* 风险热度研判 */}
+
+
+
+
+
总体风险等级
+
+
+ 上升 1 级
+
+
+
主要风险点
+
+ {RISK_POINTS.map((p) => (
+ -
+
+ {p}
+
+ ))}
+
+
+ {RISK_CONCLUSION}
+
+
+
+ );
+}
+
+// ── 真实数据渲染 ──────────────────────────────────────────────────────────────
+function EnemyReal({ data }: { data: EnemyData }) {
+ return (
+
+
+ {data.targets.length > 0 && (
+
+ } />
+
+
+ {data.targets.map((t, i) => (
+
+ | {t.name} |
+ {t.category || "—"} |
+ {t.note || "—"} |
+
+ ))}
+
+
+
+ )}
+
+ {data.events.length > 0 && (
+
+
+
+ {data.events.map((g, i) => (
+
+
+ {g.topic || "事件"}
+ {g.count}
+
+
+ {g.items.map((e, j) => (
+ -
+ {e.time}
+
+
{e.title}
+ {e.brief &&
{e.brief}
}
+
+
+ ))}
+
+
+ ))}
+
+
+ )}
+
+
+ {data.narratives.length > 0 && (
+
+
+
+
+ {data.narratives.map((n, i) => (
+
+ | {n.topic || "—"} |
+ {n.narrator || "—"} |
+ {n.intention || "—"} |
+ {n.type || "—"} |
+ {n.summary || "—"} |
+
+ ))}
+
+
+
+ )}
+
+ );
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/EnvironmentDetail.tsx b/frontend-web/src/roundtable-planning/dashboard-agent/EnvironmentDetail.tsx
new file mode 100644
index 00000000..5fd94717
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/EnvironmentDetail.tsx
@@ -0,0 +1,383 @@
+/**
+ * 「大屏绘制智能体」战场环境页 —— 信息环境 / 人文环境 / 综合研判(按设计图 dp-4.png 一比一实现)。
+ *
+ * **dashboard-agent 独占**:作为 DashboardApp `pages` 插槽里「战场环境」Tab 的内容,
+ * 不影响多智能体会商(Step3)大屏多页的样式。当前为「静态页面」,文案为占位内容,
+ * 动态数据后续接入。版式包含:
+ * 1. 顶部 5 个环境指数卡
+ * 2. 信息环境(5 栏维度卡:媒体生态 / 平台分布 / 基础设施 / 监管治理 / 受众习惯)
+ * 3. 人文环境(4 栏维度卡:社会 / 经济 / 舆论 / 文化)
+ * 4. 环境综合研判 + 信息来源与参考
+ */
+import {
+ Activity,
+ AlertTriangle,
+ Network,
+ AlertOctagon,
+ BarChart3,
+ Tv,
+ LayoutGrid,
+ Server,
+ ShieldCheck,
+ Users,
+ Building2,
+ Coins,
+ MessageSquare,
+ BookOpen,
+ Lightbulb,
+ FileText,
+ TrendingUp,
+ TrendingDown,
+ Minus,
+ type LucideIcon,
+} from "lucide-react";
+
+import { Card, CardTitle, FooterNote, Table, Td, Tag, NoData, type Tone } from "@/roundtable-planning/dashboard-agent/ui";
+import type { EnvData, EnvSection } from "@/roundtable-planning/dashboard-agent/sq-report";
+
+// ── 占位数据 ──────────────────────────────────────────────────────────────────
+
+type Trend = "up" | "down" | "flat";
+
+interface Kpi {
+ label: string;
+ value: string;
+ icon: LucideIcon;
+ delta: string;
+ trend: Trend;
+}
+const KPIS: Kpi[] = [
+ { label: "信息活跃度指数", value: "78.6", icon: Activity, delta: "+3.4", trend: "up" },
+ { label: "舆论风险指数", value: "64.2", icon: AlertTriangle, delta: "+5.1", trend: "up" },
+ { label: "网络连接通畅率", value: "58.7%", icon: Network, delta: "+2.3", trend: "up" },
+ { label: "虚假信息占比", value: "23.6%", icon: AlertOctagon, delta: "-1.8", trend: "down" },
+ { label: "关键平台覆盖率", value: "92.4%", icon: BarChart3, delta: "+0.6", trend: "up" },
+];
+
+interface EnvRow {
+ label: string;
+ value: string;
+ tone: Tone;
+}
+interface EnvField {
+ icon: LucideIcon;
+ title: string;
+ rows: EnvRow[];
+ assessLabel: string;
+ assessment: string;
+}
+
+const INFO_ENV: EnvField[] = [
+ {
+ icon: Tv,
+ title: "媒体生态分布",
+ rows: [
+ { label: "主流媒体", value: "高", tone: "red" },
+ { label: "自媒体", value: "高", tone: "red" },
+ { label: "社交平台", value: "高", tone: "red" },
+ { label: "境外媒体", value: "中", tone: "amber" },
+ { label: "传统媒体", value: "中", tone: "amber" },
+ ],
+ assessLabel: "总体评估",
+ assessment: "媒体生态多元,信息传播活跃。",
+ },
+ {
+ icon: LayoutGrid,
+ title: "平台分布",
+ rows: [
+ { label: "即时通讯", value: "高", tone: "red" },
+ { label: "社交媒体", value: "高", tone: "red" },
+ { label: "视频平台", value: "高", tone: "red" },
+ { label: "论坛社区", value: "中", tone: "amber" },
+ { label: "新闻门户", value: "中", tone: "amber" },
+ ],
+ assessLabel: "总体评估",
+ assessment: "平台覆盖广泛,传播路径多样。",
+ },
+ {
+ icon: Server,
+ title: "基础设施状况",
+ rows: [
+ { label: "网络覆盖", value: "良好", tone: "green" },
+ { label: "带宽容量", value: "充足", tone: "green" },
+ { label: "移动网络", value: "良好", tone: "green" },
+ { label: "数据中心", value: "稳定", tone: "green" },
+ { label: "电力保障", value: "稳定", tone: "green" },
+ ],
+ assessLabel: "总体评估",
+ assessment: "基础设施完备,保障能力强。",
+ },
+ {
+ icon: ShieldCheck,
+ title: "信息监管与治理",
+ rows: [
+ { label: "内容审核", value: "较强", tone: "green" },
+ { label: "谣言治理", value: "较强", tone: "green" },
+ { label: "平台自律", value: "一般", tone: "slate" },
+ { label: "跨境监管", value: "较弱", tone: "amber" },
+ ],
+ assessLabel: "总体评估",
+ assessment: "治理体系较完善,执行力度持续提升。",
+ },
+ {
+ icon: Users,
+ title: "受众信息接触习惯",
+ rows: [
+ { label: "移动端为主", value: "高", tone: "red" },
+ { label: "短视频主导", value: "高", tone: "red" },
+ { label: "信息甄别能力", value: "中", tone: "amber" },
+ { label: "权威信息依赖", value: "中", tone: "amber" },
+ ],
+ assessLabel: "总体评估",
+ assessment: "触达渠道广泛,注意力相对分散。",
+ },
+];
+
+const HUMAN_ENV: EnvField[] = [
+ {
+ icon: Building2,
+ title: "社会环境",
+ rows: [
+ { label: "社会稳定度", value: "良好", tone: "green" },
+ { label: "社会矛盾", value: "可控", tone: "amber" },
+ { label: "民族宗教", value: "稳定", tone: "green" },
+ { label: "城乡发展", value: "一般", tone: "slate" },
+ ],
+ assessLabel: "评估结论",
+ assessment: "社会大局稳定,整体可控。",
+ },
+ {
+ icon: Coins,
+ title: "经济基础",
+ rows: [
+ { label: "经济发展水平", value: "良好", tone: "green" },
+ { label: "产业结构", value: "一般", tone: "slate" },
+ { label: "就业形势", value: "一般", tone: "slate" },
+ { label: "居民收入", value: "一般", tone: "slate" },
+ ],
+ assessLabel: "评估结论",
+ assessment: "经济运行平稳,韧性较强。",
+ },
+ {
+ icon: MessageSquare,
+ title: "社会舆论",
+ rows: [
+ { label: "主流舆论引导", value: "有效", tone: "green" },
+ { label: "网络情绪", value: "一般", tone: "amber" },
+ { label: "热点话题", value: "活跃", tone: "red" },
+ { label: "意见多样性", value: "较强", tone: "amber" },
+ ],
+ assessLabel: "评估结论",
+ assessment: "舆论总体平稳,热点多发。",
+ },
+ {
+ icon: BookOpen,
+ title: "文化环境",
+ rows: [
+ { label: "文化认同", value: "较强", tone: "green" },
+ { label: "价值观主流", value: "稳定", tone: "green" },
+ { label: "文化多元", value: "一般", tone: "slate" },
+ { label: "文化渗透风险", value: "中", tone: "amber" },
+ ],
+ assessLabel: "评估结论",
+ assessment: "文化主流稳定,需防外来渗透。",
+ },
+];
+
+const REVIEW = [
+ "信息环境高度活跃,传播速度快、覆盖面广,主流舆论引导总体有效,但虚假信息与噪声并存。",
+ "网络基础设施完备、平台多元,为信息传播与舆论引导提供良好支撑,需防范境外渗透。",
+ "社会大局稳定,经济运行平稳,人文环境总体向好,为我方舆论工作提供有利条件。",
+ "综合研判:当前战场信息环境总体有利,须强化内容供给与精准引导,压缩虚假信息空间。",
+];
+
+interface SourceRow {
+ name: string;
+ type: string;
+ trust: string;
+ trustTone: Tone;
+ updated: string;
+}
+const SOURCES: SourceRow[] = [
+ { name: "全网舆情监测平台", type: "舆情", trust: "高", trustTone: "green", updated: "2025-05-16 10:20" },
+ { name: "媒体监测系统", type: "媒体", trust: "高", trustTone: "green", updated: "2025-05-16 10:00" },
+ { name: "社交平台数据接口", type: "社交", trust: "中", trustTone: "amber", updated: "2025-05-16 09:40" },
+ { name: "行业研究报告", type: "第三方", trust: "中", trustTone: "amber", updated: "2025-05-15 18:00" },
+ { name: "公开统计数据", type: "官方", trust: "高", trustTone: "green", updated: "2025-05-14 16:00" },
+];
+
+const TREND_ICON: Record = { up: TrendingUp, down: TrendingDown, flat: Minus };
+const TREND_COLOR: Record = { up: "text-red-600", down: "text-emerald-600", flat: "text-slate-400" };
+const VAL_COLOR: Record = {
+ red: "text-red-600",
+ amber: "text-amber-600",
+ green: "text-emerald-600",
+ blue: "text-sky-600",
+ slate: "text-slate-400 dark:text-[var(--dash-text-dim)]",
+};
+
+// ── 主组件(有真实数据走真实渲染,否则回退静态占位) ─────────────────────────────
+
+export function EnvironmentDetail({ data }: { data?: EnvData | null }) {
+ return data ? : ;
+}
+
+function EnvPlaceholder() {
+ return (
+
+ {/* 1. 顶部 5 个环境指数卡 */}
+
+ {KPIS.map((k) => {
+ const TrendIco = TREND_ICON[k.trend];
+ return (
+
+
+
+
+
+ {k.label}
+
+
+ {k.value}
+
+
+ 较昨日
+
+
+ {k.delta}
+
+
+
+ );
+ })}
+
+
+ {/* 2. 信息环境(5 栏) */}
+
信息环境
+
+ {INFO_ENV.map((f) => (
+
+ ))}
+
+
+ {/* 3. 人文环境(4 栏) */}
+
人文环境
+
+ {HUMAN_ENV.map((f) => (
+
+ ))}
+
+
+ {/* 4. 环境综合研判 + 信息来源与参考 */}
+
+
+
+
+ {REVIEW.map((r, i) => (
+ -
+
+ {i + 1}
+
+ {r}
+
+ ))}
+
+
+
+
+
+
+
+ {SOURCES.map((s) => (
+
+ | {s.name} |
+ {s.type} |
+
+ {s.trust}
+ |
+ {s.updated} |
+
+ ))}
+
+
+ 可信度分为高 / 中 / 低,时效性以最新更新时间为准,综合研判时按权重折算。
+
+
+
+ );
+}
+
+/** 环境维度卡(红色标题 + 键值行 + 评估脚注)。 */
+function EnvCard({ field }: { field: EnvField }) {
+ return (
+
+
+
+
+ {field.rows.map((r) => (
+
+
- {r.label}
+ - {r.value}
+
+ ))}
+
+
+ {field.assessment}
+
+ );
+}
+
+/** 行级区段标题(红色竖条 + 文字)。 */
+function SectionLabel({ children }: { children: React.ReactNode }) {
+ return (
+
+
+ {children}
+
+ );
+}
+
+// ── 真实数据渲染 ──────────────────────────────────────────────────────────────
+function EnvReal({ data }: { data: EnvData }) {
+ return (
+
+ {data.info.length > 0 && (
+ <>
+
信息环境
+
+ {data.info.map((s, i) => (
+
+ ))}
+
+ >
+ )}
+ {data.human.length > 0 && (
+ <>
+
人文环境
+
+ {data.human.map((s, i) => (
+
+ ))}
+
+ >
+ )}
+
+ );
+}
+
+/** 环境文本维度卡(标题 + 段落要点)。 */
+function EnvTextCard({ section }: { section: EnvSection }) {
+ return (
+
+
+
+ {section.paragraphs.map((p, i) => (
+ -
+
+ {p}
+
+ ))}
+
+
+ );
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/Judgement.tsx b/frontend-web/src/roundtable-planning/dashboard-agent/Judgement.tsx
new file mode 100644
index 00000000..0642bc62
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/Judgement.tsx
@@ -0,0 +1,360 @@
+/**
+ * 「大屏绘制智能体」情况判断页 —— 敌我优劣 / 条件限制 / 风险应对 / 综合研判
+ * (按设计图 dp-5.png 一比一实现)。
+ *
+ * **dashboard-agent 独占**:作为 DashboardApp `pages` 插槽里「情况判断」Tab 的内容,
+ * 不影响多智能体会商(Step3)大屏多页的样式。当前为「静态页面」,文案为占位内容,
+ * 动态数据后续接入。版式包含:
+ * 1. 敌我优劣势分析(敌方优势/劣势 + 我方优势/劣势 四张要点卡)
+ * 2. 条件与限制(资源/法理/政治/时间 四张约束键值卡)
+ * 3. 风险与应对(风险表)+ 判断结论(综合研判)
+ */
+import { Swords, ShieldX, ShieldCheck, ShieldAlert, Package, Scale, Landmark, Clock, AlertTriangle, Gavel, type LucideIcon } from "lucide-react";
+
+import { Card, CardTitle, FooterNote, Table, Td, Tag, Val, NoData, type Tone } from "@/roundtable-planning/dashboard-agent/ui";
+import type { JudgeData } from "@/roundtable-planning/dashboard-agent/sq-report";
+
+// ── 占位数据 ──────────────────────────────────────────────────────────────────
+
+interface BulletCardData {
+ icon: LucideIcon;
+ title: string;
+ items: string[];
+}
+const SWOT: BulletCardData[] = [
+ {
+ icon: Swords,
+ title: "敌方优势",
+ items: [
+ "火力密度大,远程打击能力强",
+ "信息系统较完善,情报获取渠道多",
+ "机动能力强,可快速集结兵力",
+ "体系作战能力较强,协同较成熟",
+ ],
+ },
+ {
+ icon: ShieldX,
+ title: "敌方劣势",
+ items: [
+ "地形适应性差,山地作战能力不足",
+ "部队协同较弱,指挥链路较长",
+ "兵力部署分散,防御纵深不足",
+ "后勤补给线长,维护保障压力大",
+ ],
+ },
+ {
+ icon: ShieldCheck,
+ title: "我方优势",
+ items: [
+ "占据有利地形,防御体系完备",
+ "群众基础好,保障支援能力强",
+ "部队士气高昂,战斗意志坚定",
+ "熟悉地形地貌,机动灵活",
+ ],
+ },
+ {
+ icon: ShieldAlert,
+ title: "我方劣势",
+ items: [
+ "远程火力相对不足,打击体系待完善",
+ "部分装备老化,信息化程度待提升",
+ "防空反导能力有限,制空压力较大",
+ "特定方向兵力薄弱,需重点加强",
+ ],
+ },
+];
+
+interface KvRow {
+ label: string;
+ value: string;
+ tone: Tone;
+}
+interface ConstraintData {
+ icon: LucideIcon;
+ title: string;
+ rows: KvRow[];
+ footer: string;
+}
+const CONSTRAINTS: ConstraintData[] = [
+ {
+ icon: Package,
+ title: "资源约束",
+ rows: [
+ { label: "弹药储备", value: "充足", tone: "green" },
+ { label: "燃油保障", value: "适中", tone: "amber" },
+ { label: "医疗资源", value: "紧张", tone: "red" },
+ { label: "兵员补充", value: "适中", tone: "amber" },
+ { label: "通信物资", value: "适中", tone: "amber" },
+ ],
+ footer: "资源总体可支撑当前作战,医疗与持续保障需重点关注。",
+ },
+ {
+ icon: Scale,
+ title: "法理约束",
+ rows: [
+ { label: "作战法理依据", value: "符合", tone: "green" },
+ { label: "国际人道法", value: "合规", tone: "green" },
+ { label: "平民保护责任", value: "严格", tone: "red" },
+ { label: "舆论道义争取", value: "有利", tone: "green" },
+ ],
+ footer: "行动总体合法合规,应在法理框架内规范实施。",
+ },
+ {
+ icon: Landmark,
+ title: "政治约束",
+ rows: [
+ { label: "政治敏感度", value: "高", tone: "red" },
+ { label: "对外关系影响", value: "中", tone: "amber" },
+ { label: "国内舆论支持", value: "较高", tone: "green" },
+ { label: "国际舆论环境", value: "一般", tone: "slate" },
+ ],
+ footer: "政治考量需贯穿全程,注重舆论与外交配合。",
+ },
+ {
+ icon: Clock,
+ title: "时间约束",
+ rows: [
+ { label: "现有作战窗口", value: "36 小时内", tone: "red" },
+ { label: "兵力集结时限", value: "48 小时内", tone: "amber" },
+ { label: "后勤展开时间", value: "72 小时内", tone: "amber" },
+ { label: "上级决心时限", value: "60 小时内", tone: "amber" },
+ ],
+ footer: "时间窗口偏紧,须加快决策与力量展开。",
+ },
+];
+
+interface RiskRow {
+ item: string;
+ level: string;
+ levelTone: Tone;
+ impact: string;
+ plan: string;
+}
+const RISKS: RiskRow[] = [
+ { item: "敌火力突袭风险", level: "高", levelTone: "red", impact: "前沿阵地与人员装备损失", plan: "强化伪装防护与机动疏散" },
+ { item: "敌增援兵力介入", level: "中", levelTone: "amber", impact: "局部兵力对比变化", plan: "预置阻援力量,封控通道" },
+ { item: "通信干扰破坏", level: "中", levelTone: "amber", impact: "指挥效率下降", plan: "启用备份链路与抗扰手段" },
+ { item: "网络攻击渗透", level: "高", levelTone: "red", impact: "信息系统受损", plan: "加固防护,分区隔离" },
+ { item: "舆论造谣攻击", level: "低", levelTone: "green", impact: "局部认知影响", plan: "及时澄清,权威发声" },
+];
+
+interface StatBlock {
+ value: string;
+ caption: string;
+}
+const CONCLUSION_STATS: StatBlock[] = [
+ { value: "我方占优", caption: "敌我对比" },
+ { value: "中高", caption: "整体风险" },
+ { value: "积极进取", caption: "决策倾向" },
+];
+const CONCLUSION_POINTS = [
+ "当前总体态势有利,敌方兵力行动受牵制。",
+ "我方占据地利与人和,具备掌握主动的条件。",
+];
+const CONCLUSION = "总体态势有利,敌方行动受牵制;建议积极把握主动,稳中求进、择机而动。";
+
+// ── 主组件(有真实数据走真实渲染,否则回退静态占位) ─────────────────────────────
+
+export function Judgement({ data }: { data?: JudgeData | null }) {
+ return data ? : ;
+}
+
+function JudgePlaceholder() {
+ return (
+
+ {/* 1. 敌我优劣势分析 */}
+
敌我优劣势分析
+
+ {SWOT.map((c) => (
+
+
+
+ {c.items.map((it) => (
+ -
+
+ {it}
+
+ ))}
+
+
+ ))}
+
+
+ {/* 2. 条件与限制 */}
+
条件与限制
+
+ {CONSTRAINTS.map((c) => (
+
+
+
+
+ {c.rows.map((r) => (
+
+
- {r.label}
+ -
+ {r.value}
+
+
+ ))}
+
+
+ {c.footer}
+
+ ))}
+
+
+ {/* 3. 风险与应对 + 判断结论 */}
+
风险与应对
+
+ {/* 风险与应对表 */}
+
+
+
+
+ {RISKS.map((r) => (
+
+ | {r.item} |
+
+ {r.level}
+ |
+ {r.impact} |
+ {r.plan} |
+
+ ))}
+
+
+
+
+ {/* 判断结论(综合研判) */}
+
+
+
+
+ {CONCLUSION_STATS.map((s) => (
+
+
{s.value}
+
{s.caption}
+
+ ))}
+
+
+ {CONCLUSION_POINTS.map((p) => (
+ -
+
+ {p}
+
+ ))}
+
+
+ {CONCLUSION}
+
+
+
+ );
+}
+
+/** 红色编号区段标题(① 敌我优劣势分析 …)。 */
+function NumberedHeading({ n, children }: { n: number; children: React.ReactNode }) {
+ return (
+
+
+ {n}
+
+
{children}
+
+ );
+}
+
+// ── 真实数据渲染 ──────────────────────────────────────────────────────────────
+function SwotCard({ title, icon, items, dot }: { title: string; icon: LucideIcon; items: Array<{ title: string; content: string }>; dot: Tone }) {
+ const dotColor = dot === "green" ? "bg-emerald-500" : "bg-red-600";
+ return (
+
+
+
+ {items.map((it, i) => (
+ -
+
+
+ {it.title && {it.title}:}
+ {it.content}
+
+
+ ))}
+
+
+ );
+}
+
+function JudgeReal({ data }: { data: JudgeData }) {
+ const hasSwot = data.strengths.length > 0 || data.weaknesses.length > 0;
+ const hasRisk = data.risks.length > 0 || data.responses.length > 0;
+ return (
+
+ {hasSwot && (
+ <>
+
优劣势分析
+
+ {data.strengths.length > 0 && }
+ {data.weaknesses.length > 0 && }
+
+ >
+ )}
+
+ {data.constraints.length > 0 && (
+ <>
+
条件与限制
+
+ {data.constraints.map((c, i) => (
+
+
+
+ {c.items.map((it, j) => (
+ -
+
+ {it}
+
+ ))}
+
+
+ ))}
+
+ >
+ )}
+
+ {hasRisk && (
+ <>
+
风险与应对
+
+ {data.risks.length > 0 && (
+
+
+
+ {data.risks.map((r, i) => (
+ -
+
+ {r}
+
+ ))}
+
+
+ )}
+ {data.responses.length > 0 && (
+
+
+
+ {data.responses.map((r, i) => (
+ -
+ {i + 1}
+ {r}
+
+ ))}
+
+
+ )}
+
+ >
+ )}
+
+ );
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/OurSituation.tsx b/frontend-web/src/roundtable-planning/dashboard-agent/OurSituation.tsx
new file mode 100644
index 00000000..c5b3ff34
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/OurSituation.tsx
@@ -0,0 +1,330 @@
+/**
+ * 「大屏绘制智能体」我情页 —— 我方口径 / 叙事 / 任务版式(按设计图 dp-3.png 一比一实现)。
+ *
+ * **dashboard-agent 独占**:作为 DashboardApp `pages` 插槽里「我情」Tab 的内容,
+ * 不影响多智能体会商(Step3)大屏多页的样式。当前为「静态页面」,文案为占位内容,
+ * 动态数据后续接入。版式包含:
+ * 1. 口径 + 叙事 + 上级要求(上排三栏)
+ * 2. 重点发布日历 + 口径关键词 + 传播任务分工速览(下排三栏)
+ */
+import { MessageSquareQuote, BookText, Star, CalendarDays, Tags, Megaphone } from "lucide-react";
+
+import {
+ Card,
+ CardTitle,
+ CardFooterLink,
+ Tag,
+ Table,
+ Td,
+ NoData,
+ type Tone,
+} from "@/roundtable-planning/dashboard-agent/ui";
+import type { OursData } from "@/roundtable-planning/dashboard-agent/sq-report";
+
+// ── 占位数据 ──────────────────────────────────────────────────────────────────
+
+const TONE_META: Array<{ label: string; value: string }> = [
+ { label: "创建时间", value: "2025-05-16 10:00" },
+ { label: "适用范围", value: "对外宣传、媒体回应、公开发布" },
+ { label: "发布层级", value: "公开" },
+];
+const TONE_POINTS = [
+ "坚定维护国家主权、安全与领土完整,立场坚定不移。",
+ "我方始终致力于维护地区和平稳定与共同发展。",
+ "任何外部势力干涉地区事务的行径都不得人心。",
+ "呼吁有关各方保持克制,通过对话协商解决分歧。",
+];
+
+interface NarrativeRow {
+ no: number;
+ title: string;
+ point: string;
+ time: string;
+}
+const NARRATIVES: NarrativeRow[] = [
+ { no: 1, title: "维护正当权益", point: "正当维权行为合法合理", time: "05-16 10:00" },
+ { no: 2, title: "致力地区和平", point: "始终是和平稳定的建设者", time: "05-16 09:00" },
+ { no: 3, title: "倡导对话合作", point: "反对集团对抗与零和博弈", time: "05-15 18:30" },
+ { no: 4, title: "揭批外部干涉", point: "外部势力是紧张根源", time: "05-15 14:00" },
+];
+
+const ORDERS = [
+ "坚定政治方向,主动发声,紧贴宣传节奏。",
+ "强化媒体引导,及时回应社会关切。",
+ "严守宣传纪律,确保信息口径统一。",
+ "加强协同联动,形成对外宣传合力。",
+];
+const ORDER_META: Array<{ label: string; value: string }> = [
+ { label: "下发时间", value: "2025-05-16 08:00" },
+ { label: "下发单位", value: "联合作战指挥部宣传工作组" },
+];
+
+interface CalendarItem {
+ time: string;
+ event: string;
+ status: string;
+ tone: Tone;
+}
+const TODAY_PLAN: CalendarItem[] = [
+ { time: "09:00", event: "例行新闻发布会", status: "已发布", tone: "green" },
+ { time: "14:00", event: "重点新闻通稿", status: "待发布", tone: "amber" },
+ { time: "18:00", event: "对外答问口径", status: "待发布", tone: "amber" },
+];
+const UPCOMING: CalendarItem[] = [
+ { time: "05-17 09:00", event: "专题新闻发布会", status: "待发布", tone: "amber" },
+ { time: "05-18 10:00", event: "白皮书发布", status: "筹备中", tone: "slate" },
+];
+
+interface KeywordGroup {
+ name: string;
+ tone: Tone;
+ words: string[];
+}
+const KEYWORDS: KeywordGroup[] = [
+ { name: "主权安全", tone: "red", words: ["主权", "领土完整", "国家安全", "核心利益"] },
+ { name: "和平发展", tone: "green", words: ["和平", "稳定", "合作", "共同发展"] },
+ { name: "立场态度", tone: "blue", words: ["坚决", "克制", "正当", "合法合理"] },
+];
+
+interface TaskRow {
+ dept: string;
+ task: string;
+ deadline: string;
+}
+const TASKS: TaskRow[] = [
+ { dept: "新闻发布组", task: "组织发布会与新闻通稿", deadline: "当日" },
+ { dept: "媒体协调组", task: "协调媒体报道口径", deadline: "持续" },
+ { dept: "网络宣传组", task: "网络平台内容推送与监看", deadline: "实时" },
+ { dept: "对外传播组", task: "多语种对外发声", deadline: "当日" },
+];
+
+// ── 主组件(有真实数据走真实渲染,否则回退静态占位) ─────────────────────────────
+
+export function OurSituation({ data }: { data?: OursData | null }) {
+ return data ? : ;
+}
+
+function OursPlaceholder() {
+ return (
+
+ {/* 1. 上排:口径 + 叙事 + 上级要求 */}
+
+ {/* 口径 */}
+
+
+
+
+ {TONE_META.map((m) => (
+
+
- {m.label}
+ - {m.value}
+
+ ))}
+
+
+ {TONE_POINTS.map((p) => (
+ -
+
+ {p}
+
+ ))}
+
+
+ 查看完整口径
+
+
+ {/* 叙事 */}
+
+
+
+
+ {NARRATIVES.map((n) => (
+
+ | {n.no} |
+ {n.title} |
+ {n.point} |
+ {n.time} |
+
+ ))}
+
+
+ 查看更多叙事
+
+
+ {/* 上级要求 */}
+
+
+
+ {ORDERS.map((o, i) => (
+ -
+
+ {i + 1}
+
+ {o}
+
+ ))}
+
+
+ {ORDER_META.map((m) => (
+
+
- {m.label}
+ - {m.value}
+
+ ))}
+
+
+
+
+ {/* 2. 下排:重点发布日历 + 口径关键词 + 传播任务分工速览 */}
+
+ {/* 重点发布日历 */}
+
+
+
+
+
+
+ 查看完整日历
+
+
+ {/* 口径关键词 */}
+
+
+
+ {KEYWORDS.map((g) => (
+
+
+ {g.name}
+
+
+ {g.words.map((w) => (
+
+ {w}
+
+ ))}
+
+
+ ))}
+
+ 查看更多关键词
+
+
+ {/* 传播任务分工速览 */}
+
+
+
+
+ {TASKS.map((t) => (
+
+ | {t.dept} |
+ {t.task} |
+ {t.deadline} |
+
+ ))}
+
+
+ 查看完整分工
+
+
+
+ );
+}
+
+/** 发布日历小节(标题 + 时间/事件/状态行)。 */
+function CalendarSection({ title, items }: { title: string; items: CalendarItem[] }) {
+ return (
+
+
{title}
+
+ {items.map((it) => (
+ -
+ {it.time}
+ {it.event}
+ {it.status}
+
+ ))}
+
+
+ );
+}
+
+// ── 真实数据渲染 ──────────────────────────────────────────────────────────────
+function OursReal({ data }: { data: OursData }) {
+ const keywords = Array.from(new Set(data.narratives.flatMap((n) => n.tags))).slice(0, 24);
+ return (
+
+
+ {/* 口径 */}
+ {data.speeches.length > 0 && (
+
+
+
+
+ )}
+
+ {/* 上级要求 */}
+ {data.orders.length > 0 && (
+
+
+
+ {data.orders.map((o, i) => (
+ -
+ {i + 1}
+ {o}
+
+ ))}
+
+
+ )}
+
+
+ {/* 叙事 */}
+ {data.narratives.length > 0 && (
+
+
+
+
+ )}
+
+ {/* 口径关键词(取自叙事标签) */}
+ {keywords.length > 0 && (
+
+
+
+ {keywords.map((w) => (
+ {w}
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/SituationOverview.tsx b/frontend-web/src/roundtable-planning/dashboard-agent/SituationOverview.tsx
new file mode 100644
index 00000000..2e908899
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/SituationOverview.tsx
@@ -0,0 +1,231 @@
+/**
+ * 「大屏绘制智能体」总览页 —— **由六类战情数据聚合**而成(敌情/我情/战场环境/
+ * 情况判断/决心建议/处置建议),版式参考设计图 dp-1.png。
+ *
+ * **dashboard-agent 独占组件**:作为 DashboardApp `pages` 插槽里「总览」Tab 的内容,
+ * **不影响多智能体会商(Step3)大屏多页的样式**。
+ *
+ * 数据来自 `parseSqReport` 的整份 `SqReport`:
+ * - 顶部指标 = 各类目数量;关键战情摘要 / 三栏概览 / 判断·决策·处置 = 各类目要点提炼。
+ * - 接口返回为空(无任何类目)→ 显示「暂无数据」。
+ */
+import {
+ Target,
+ Zap,
+ Megaphone,
+ BookText,
+ ShieldAlert,
+ FileText,
+ Crosshair,
+ Shield,
+ Mountain,
+ Lightbulb,
+ Flag,
+ type LucideIcon,
+} from "lucide-react";
+
+import { Card, CardTitle, FooterNote, NoData } from "@/roundtable-planning/dashboard-agent/ui";
+import type { SqReport } from "@/roundtable-planning/dashboard-agent/sq-report";
+
+// ── 聚合 ──────────────────────────────────────────────────────────────────────
+
+function eventCount(r: SqReport): number {
+ return (r.enemy?.events ?? []).reduce((s, g) => s + (g.count || g.items.length || 0), 0);
+}
+
+interface OverviewColumn {
+ title: string;
+ icon: LucideIcon;
+ rows: Array<{ label: string; value: string }>;
+ focusTitle: string;
+ focus: string[];
+}
+
+// ── 主组件 ────────────────────────────────────────────────────────────────────
+
+export function SituationOverview({ data }: { data?: SqReport | null }) {
+ const r = data;
+ const hasAny =
+ !!r && (!!r.enemy || !!r.ours || !!r.environment || !!r.judgement || !!r.decision || !!r.disposal);
+ if (!hasAny) return ;
+
+ // 顶部指标(各类目数量)
+ const metrics: Array<{ label: string; value: number; icon: LucideIcon }> = [
+ { label: "敌方目标", value: r!.enemy?.targets.length ?? 0, icon: Target },
+ { label: "关键事件", value: eventCount(r!), icon: Zap },
+ { label: "对手叙事", value: r!.enemy?.narratives.length ?? 0, icon: Megaphone },
+ { label: "我方口径", value: r!.ours?.speeches.length ?? 0, icon: BookText },
+ { label: "风险项", value: r!.judgement?.risks.length ?? 0, icon: ShieldAlert },
+ { label: "处置建议", value: r!.disposal?.items.length ?? 0, icon: FileText },
+ ];
+
+ // 关键战情摘要(取各类目最有代表性的一句)
+ const summary: Array<{ label: string; text: string }> = [
+ r!.decision?.summary && { label: "决心研判", text: r!.decision.summary },
+ r!.disposal?.summary && { label: "处置研判", text: r!.disposal.summary },
+ r!.enemy?.narratives[0]?.summary && { label: "对手叙事", text: r!.enemy!.narratives[0].summary },
+ r!.judgement?.risks[0] && { label: "主要风险", text: r!.judgement!.risks[0] },
+ r!.ours?.speeches[0]?.summary && { label: "我方口径", text: r!.ours!.speeches[0].summary },
+ ].filter(Boolean) as Array<{ label: string; text: string }>;
+
+ // 三栏概览
+ const columns: OverviewColumn[] = [];
+ if (r!.enemy) {
+ columns.push({
+ title: "敌情概览",
+ icon: Crosshair,
+ rows: [
+ { label: "敌方目标", value: `${r!.enemy.targets.length} 个` },
+ { label: "关键事件", value: `${eventCount(r!)} 起` },
+ { label: "对手叙事", value: `${r!.enemy.narratives.length} 条` },
+ ],
+ focusTitle: "重点事件",
+ focus: r!.enemy.events.map((e) => e.topic).filter(Boolean).slice(0, 3),
+ });
+ }
+ if (r!.ours) {
+ columns.push({
+ title: "我情概览",
+ icon: Shield,
+ rows: [
+ { label: "口径", value: `${r!.ours.speeches.length} 条` },
+ { label: "叙事", value: `${r!.ours.narratives.length} 条` },
+ { label: "上级要求", value: `${r!.ours.orders.length} 项` },
+ ],
+ focusTitle: "口径要点",
+ focus: r!.ours.speeches.map((s) => s.title).filter(Boolean).slice(0, 3),
+ });
+ }
+ if (r!.environment) {
+ columns.push({
+ title: "战场环境概览",
+ icon: Mountain,
+ rows: [
+ { label: "信息环境", value: `${r!.environment.info.length} 维度` },
+ { label: "人文环境", value: `${r!.environment.human.length} 维度` },
+ ],
+ focusTitle: "环境维度",
+ focus: [...r!.environment.info, ...r!.environment.human].map((s) => s.title).slice(0, 4),
+ });
+ }
+
+ const judgePoints = (r!.judgement?.risks ?? []).slice(0, 3);
+ const decisionPoints = (r!.decision?.objectives ?? []).slice(0, 3);
+ const disposalPoints = (r!.disposal?.items ?? []).map((i) => i.content).filter(Boolean).slice(0, 3);
+
+ return (
+
+ {/* 1. 顶部指标 */}
+
+ {metrics.map((m) => (
+
+
+
+
+
+ {m.label}
+
+ {m.value}
+
+ ))}
+
+
+ {/* 2. 关键战情摘要 */}
+ {summary.length > 0 && (
+
+
+
+
+ )}
+
+ {/* 3. 三栏概览 */}
+ {columns.length > 0 && (
+
+ {columns.map((col) => (
+
+
+
+
+ {col.rows.map((row) => (
+
+
- {row.label}
+ - {row.value}
+
+ ))}
+
+ {col.focus.length > 0 && (
+
+
{col.focusTitle}
+
+ {col.focus.map((f, i) => (
+ -
+
+ {f}
+
+ ))}
+
+
+ )}
+
+
+ ))}
+
+ )}
+
+ {/* 4. 情况判断 / 决策建议 / 处置建议 */}
+ {(judgePoints.length > 0 || decisionPoints.length > 0 || disposalPoints.length > 0) && (
+
+ {judgePoints.length > 0 && (
+
+ )}
+ {decisionPoints.length > 0 && (
+
+ )}
+ {disposalPoints.length > 0 && (
+
+ )}
+
+ )}
+
+ );
+}
+
+/** 底部要点卡(编号要点 + 可选脚注)。 */
+function SummaryCard({
+ icon,
+ title,
+ points,
+ footerLabel,
+ footer,
+}: {
+ icon: LucideIcon;
+ title: string;
+ points: string[];
+ footerLabel: string;
+ footer?: string;
+}) {
+ return (
+
+
+
+ {points.map((p, i) => (
+ -
+ {i + 1}
+ {p}
+
+ ))}
+
+ {footer && {footer}}
+
+ );
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/sq-report.ts b/frontend-web/src/roundtable-planning/dashboard-agent/sq-report.ts
new file mode 100644
index 00000000..b0d7bff8
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/sq-report.ts
@@ -0,0 +1,289 @@
+/**
+ * 「大屏绘制智能体」战情报告(selectSqReport)→ 各页面展示模型 的适配器。
+ *
+ * 接口经 `fetchDashboardInputJson` → `extractSqReport` 后得到:
+ * `{ taskId, source, categories: { [类目]: 反序列化内容 } }`
+ * 类目键(categoryType 去掉「 JSON」后缀):敌情目标 / 敌情关键事件 / 敌情对手叙事 /
+ * 我情口径 / 我情叙事 / 我情上级要求 / 信息环境 / 人文环境 / 优劣势分析 /
+ * 条件与限制 / 风险及应对 / 决心建议 / 处置建议 / 行动方案。
+ *
+ * 本模块把这些**异构、自由文本**的内容防御式地适配成每个页面要用的结构化模型;
+ * 任一类目缺失/为空 → 对应页面 data 为 null,页面回退到静态占位(先做对接,编辑后续再说)。
+ */
+
+// ── 防御式取值 ────────────────────────────────────────────────────────────────
+function asArray(v: unknown): unknown[] {
+ return Array.isArray(v) ? v : [];
+}
+function asObj(v: unknown): Record {
+ return v && typeof v === "object" && !Array.isArray(v) ? (v as Record) : {};
+}
+function asStr(v: unknown): string {
+ if (typeof v === "string") return v;
+ if (v == null) return "";
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
+ return "";
+}
+function strArr(v: unknown): string[] {
+ return asArray(v).map(asStr).filter((s) => s.trim().length > 0);
+}
+
+/** 取 categories 容器(输入即 fetchDashboardInputJson 的返回)。 */
+export function getSqCategories(input: unknown): Record | null {
+ const o = asObj(input);
+ const cats = asObj(o.categories);
+ return Object.keys(cats).length > 0 ? cats : null;
+}
+
+// ── 各页面展示模型 ────────────────────────────────────────────────────────────
+
+export interface EnemyData {
+ targets: Array<{ name: string; category: string; note: string }>;
+ events: Array<{ topic: string; count: number; items: Array<{ time: string; title: string; brief: string }> }>;
+ narratives: Array<{ topic: string; narrator: string; intention: string; type: string; summary: string }>;
+}
+
+export interface OursData {
+ speeches: Array<{ title: string; summary: string; direction: string; time: string }>;
+ narratives: Array<{ title: string; intent: string; tags: string[]; content: string }>;
+ orders: string[];
+}
+
+export interface EnvSection {
+ title: string;
+ paragraphs: string[];
+}
+export interface EnvData {
+ info: EnvSection[];
+ human: EnvSection[];
+}
+
+export interface JudgeData {
+ strengths: Array<{ title: string; content: string }>;
+ weaknesses: Array<{ title: string; content: string }>;
+ constraints: Array<{ title: string; items: string[] }>;
+ risks: string[];
+ responses: string[];
+}
+
+export interface DecisionData {
+ taskTitle: string;
+ taskIntro: string;
+ objectives: string[];
+ targets: Array<{ name: string; desc: string; reason: string; impact: string; basis: string }>;
+ keyPoints: string[];
+ summary: string;
+}
+
+export interface DisposalData {
+ goal: string;
+ summary: string;
+ items: Array<{ content: string; method: string; tone: string; effect: string; subject: string; basis: string }>;
+}
+
+export interface SqReport {
+ enemy: EnemyData | null;
+ ours: OursData | null;
+ environment: EnvData | null;
+ judgement: JudgeData | null;
+ decision: DecisionData | null;
+ disposal: DisposalData | null;
+}
+
+// ── 适配函数(每个返回 null 表示该类目无可用数据) ─────────────────────────────
+
+function adaptEnemy(cats: Record): EnemyData | null {
+ // 敌方目标:组织 + 人物实体
+ const tgt = asObj(cats["敌情目标"]);
+ const targets = [...asArray(tgt.organization), ...asArray(tgt.human)]
+ .map((e) => {
+ const o = asObj(e);
+ return { name: asStr(o.name), category: asStr(o.text) || asStr(o.etype), note: asStr(o.reason) };
+ })
+ .filter((t) => t.name);
+
+ // 关键事件:按 topic 分组
+ const events = asArray(cats["敌情关键事件"])
+ .map((g) => {
+ const o = asObj(g);
+ const items = asArray(o.events)
+ .map((ev) => {
+ const e = asObj(ev);
+ return { time: asStr(e.date) || asStr(e.publish_time), title: asStr(e.title), brief: asStr(e.content) };
+ })
+ .filter((i) => i.title);
+ return { topic: asStr(o.topic), count: typeof o.count === "number" ? o.count : items.length, items };
+ })
+ .filter((g) => g.topic || g.items.length > 0);
+
+ // 对手叙事
+ const narratives = asArray(cats["敌情对手叙事"])
+ .map((n) => {
+ const o = asObj(n);
+ return {
+ topic: asStr(o.topic) || asStr(o.event_name),
+ narrator: asStr(o.narrator) || asStr(o.entity),
+ intention: asStr(o.intention) || asStr(o.intent_of_the_opponent),
+ type: asStr(o.topicType) || asStr(o.narrative_topic_types),
+ summary: asStr(o.summary) || asStr(o.event_summary),
+ };
+ })
+ .filter((n) => n.topic || n.summary);
+
+ if (targets.length === 0 && events.length === 0 && narratives.length === 0) return null;
+ return { targets, events, narratives };
+}
+
+function adaptOurs(cats: Record): OursData | null {
+ const speech = asObj(cats["我情口径"]);
+ const speeches = [
+ ...asArray(speech.selfSpeech).map((s) => {
+ const o = asObj(s);
+ return {
+ title: asStr(o.trendSpeechTitle) || asStr(o.trendSpeechTitleMini),
+ summary: asStr(o.trendSpeechLike) || asStr(o.trendSpeechContent),
+ direction: asStr(o.narrativeDirection),
+ time: asStr(o.trendSpeechTime),
+ };
+ }),
+ ...asArray(speech.selfConsistents).map((s) => {
+ const o = asObj(s);
+ return {
+ title: asStr(o.trendConsistentTitleMini) || asStr(o.trendConsistentTitle),
+ summary: asStr(o.trendConsistentLike) || asStr(o.trendConsistentContent),
+ direction: asStr(o.narrativeDirection),
+ time: asStr(o.trendConsistentTime),
+ };
+ }),
+ ].filter((s) => s.title || s.summary);
+
+ const narratives = asArray(cats["我情叙事"])
+ .map((n) => {
+ const o = asObj(n);
+ return {
+ title: asStr(o["标题"]),
+ intent: asStr(o["意图"]),
+ tags: strArr(o["标签"]),
+ content: asStr(o["内容"]),
+ };
+ })
+ .filter((n) => n.title || n.content);
+
+ // 上级要求:字符串(含编号),按行拆分、去掉行首编号
+ const orderRaw = cats["我情上级要求"];
+ const orderText = typeof orderRaw === "string" ? orderRaw : asStr(orderRaw);
+ const orders = orderText
+ .split(/\r?\n/)
+ .map((l) => l.trim().replace(/^[0-9]+[.、)]\s*/, ""))
+ .filter((l) => l.length > 0);
+
+ if (speeches.length === 0 && narratives.length === 0 && orders.length === 0) return null;
+ return { speeches, narratives, orders };
+}
+
+function adaptEnv(cats: Record): EnvData | null {
+ const toSections = (raw: unknown): EnvSection[] =>
+ Object.entries(asObj(raw))
+ .filter(([k]) => k !== "type")
+ .map(([title, v]) => ({ title, paragraphs: strArr(v) }))
+ .filter((s) => s.paragraphs.length > 0);
+ const info = toSections(cats["信息环境"]);
+ const human = toSections(cats["人文环境"]);
+ if (info.length === 0 && human.length === 0) return null;
+ return { info, human };
+}
+
+function adaptJudge(cats: Record): JudgeData | null {
+ const sw = asObj(cats["优劣势分析"]);
+ const mapTC = (v: unknown) =>
+ asArray(v)
+ .map((x) => {
+ const o = asObj(x);
+ return { title: asStr(o["title"]), content: asStr(o["content"]) };
+ })
+ .filter((x) => x.title || x.content);
+ const strengths = mapTC(sw["优势"]);
+ const weaknesses = mapTC(sw["劣势"]);
+
+ const con = asObj(cats["条件与限制"]);
+ const constraints = Object.entries(con)
+ .map(([title, v]) => ({ title: title.replace(/分析与?/, ""), items: strArr(v) }))
+ .filter((c) => c.items.length > 0);
+
+ const risk = asObj(cats["风险及应对"]);
+ const risks = strArr(risk["风险分析"]);
+ const responses = strArr(risk["应对方案"]);
+
+ if (strengths.length === 0 && weaknesses.length === 0 && constraints.length === 0 && risks.length === 0 && responses.length === 0)
+ return null;
+ return { strengths, weaknesses, constraints, risks, responses };
+}
+
+function adaptDecision(cats: Record): DecisionData | null {
+ const root = asObj(cats["决心建议"]);
+ const core = asObj(root["决心建议"]);
+ const targets = asArray(core["打击目标"])
+ .map((t) => {
+ const o = asObj(t);
+ return {
+ name: asStr(o["目标名称"]),
+ desc: asStr(o["目标描述"]),
+ reason: asStr(o["选择理由"]),
+ impact: asStr(o["潜在影响"]),
+ basis: asStr(o["推荐依据"]),
+ };
+ })
+ .filter((t) => t.name || t.desc);
+ const data: DecisionData = {
+ taskTitle: asStr(core["任务标题"]),
+ taskIntro: asStr(core["任务简介"]),
+ objectives: strArr(core["作战目的"]),
+ targets,
+ keyPoints: strArr(root["实施要点"]),
+ summary: asStr(root["决心建议总结"]),
+ };
+ if (!data.taskTitle && !data.taskIntro && data.objectives.length === 0 && targets.length === 0 && data.keyPoints.length === 0 && !data.summary)
+ return null;
+ return data;
+}
+
+function adaptDisposal(cats: Record): DisposalData | null {
+ const root = asObj(cats["处置建议"]);
+ const items = asArray(root["处置建议"])
+ .map((it) => {
+ const o = asObj(it);
+ const subj = asObj(o["处置主体"]);
+ return {
+ content: asStr(o["建议内容"]),
+ method: asStr(o["处置方式与步骤"]),
+ tone: asStr(o["口径基调"]),
+ effect: asStr(o["预期效果"]),
+ subject: asStr(subj["主责单位"]),
+ basis: asStr(o["立场与原则"]),
+ };
+ })
+ .filter((i) => i.content || i.method);
+ const data: DisposalData = {
+ goal: asStr(root["目标"]),
+ summary: asStr(root["处置建议总结"]),
+ items,
+ };
+ if (!data.goal && !data.summary && items.length === 0) return null;
+ return data;
+}
+
+/** 把 fetchDashboardInputJson 的返回解析成各页面展示模型(无 categories → 全 null)。 */
+export function parseSqReport(input: unknown): SqReport {
+ const cats = getSqCategories(input);
+ if (!cats) {
+ return { enemy: null, ours: null, environment: null, judgement: null, decision: null, disposal: null };
+ }
+ return {
+ enemy: adaptEnemy(cats),
+ ours: adaptOurs(cats),
+ environment: adaptEnv(cats),
+ judgement: adaptJudge(cats),
+ decision: adaptDecision(cats),
+ disposal: adaptDisposal(cats),
+ };
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard-agent/ui.tsx b/frontend-web/src/roundtable-planning/dashboard-agent/ui.tsx
new file mode 100644
index 00000000..5e20b994
--- /dev/null
+++ b/frontend-web/src/roundtable-planning/dashboard-agent/ui.tsx
@@ -0,0 +1,187 @@
+/**
+ * 「大屏绘制智能体」战情页公共 UI 片段(卡片 / 标题 / 更多按钮 / 色块徽标 / 表格)。
+ *
+ * **dashboard-agent 独占**:仅供战情系列静态页(SituationOverview / EnemyDetail / …)
+ * 复用,不影响会商共享的 DashboardApp / dashboard 组件群。配色对齐设计图(白底 + 红色强调)。
+ */
+import { ChevronRight, Inbox, type LucideIcon } from "lucide-react";
+
+/** 空数据占位(接口返回为空 / 该类目缺失时显示)。 */
+export function NoData({ label = "暂无数据" }: { label?: string }) {
+ return (
+
+
+ {label}
+
+ );
+}
+
+/** 卡片外框(白底 + slate 细边 + 轻阴影)。 */
+export function Card({ className = "", children }: { className?: string; children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+
+/** 卡片标题(红色图标 + 标题 + 可选徽标 + 可选右侧操作 + 底部分隔线)。 */
+export function CardTitle({
+ icon: Icon,
+ title,
+ badges,
+ action,
+}: {
+ icon: LucideIcon;
+ title: string;
+ badges?: React.ReactNode;
+ action?: React.ReactNode;
+}) {
+ return (
+
+
+
+
+
+
+ {title}
+
+ {badges &&
{badges}
}
+
+ {action}
+
+ );
+}
+
+/** 标题徽标药丸(如「组织目标 12 个」,数字红色高亮)。 */
+export function HeadBadge({ label, value }: { label: string; value: string }) {
+ return (
+
+ {label}
+ {value}
+
+ );
+}
+
+/** 「更多 >」操作按钮(标题右侧,可自定义文案)。 */
+export function MoreLink({ label = "更多", onClick }: { label?: string; onClick?: () => void }) {
+ return (
+
+ );
+}
+
+/** 卡片底部「查看完整… >」链接条(顶部分隔线 + 红色链接)。 */
+export function CardFooterLink({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) {
+ return (
+
+
+
+ );
+}
+
+export type Tone = "red" | "amber" | "green" | "blue" | "slate";
+
+const TONE_CLASS: Record = {
+ red: "bg-red-50 text-red-600 dark:bg-red-500/15",
+ amber: "bg-amber-50 text-amber-600 dark:bg-amber-500/15",
+ green: "bg-emerald-50 text-emerald-600 dark:bg-emerald-500/15",
+ blue: "bg-sky-50 text-sky-600 dark:bg-sky-500/15",
+ slate: "bg-slate-100 text-slate-500 dark:bg-white/10 dark:text-[var(--dash-text-sec)]",
+};
+
+/** 语义色块徽标(高/中/低、上升/持平/下降、偏高 等)。 */
+export function Tag({ tone, children }: { tone: Tone; children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+
+const VAL_TEXT: Record = {
+ red: "text-red-600",
+ amber: "text-amber-600",
+ green: "text-emerald-600",
+ blue: "text-sky-600",
+ slate: "text-slate-400 dark:text-[var(--dash-text-dim)]",
+};
+
+/** 语义色取值文字(键值行右侧的 高/中/充足/紧张 等,以颜色表强弱)。 */
+export function Val({ tone, children }: { tone: Tone; children: React.ReactNode }) {
+ return {children};
+}
+
+/** 紧凑表格(表头灰字 + 行细分隔线,对齐设计图密集表格)。 */
+export function Table({ head, children }: { head: string[]; children: React.ReactNode }) {
+ return (
+
+
+
+ {head.map((h) => (
+ |
+ {h}
+ |
+ ))}
+
+
+ {children}
+
+ );
+}
+
+/** 表格数据单元(统一内边距 + 细分隔线 + 顶对齐)。 */
+export function Td({ className = "", children }: { className?: string; children?: React.ReactNode }) {
+ return (
+
+ {children}
+ |
+ );
+}
+
+/** 卡片底部「说明 / 动态」提示条(左侧色条 + 浅底)。 */
+export function FooterNote({
+ label,
+ tone = "red",
+ children,
+}: {
+ label: string;
+ tone?: Tone;
+ children: React.ReactNode;
+}) {
+ const border = tone === "green" ? "border-emerald-500" : tone === "blue" ? "border-sky-500" : "border-red-500";
+ const labelColor =
+ tone === "green"
+ ? "text-emerald-700 dark:text-emerald-400"
+ : tone === "blue"
+ ? "text-sky-700 dark:text-sky-400"
+ : "text-red-600";
+ const bg =
+ tone === "green" ? "bg-emerald-50 dark:bg-emerald-500/10" : tone === "blue" ? "bg-sky-50 dark:bg-sky-500/10" : "bg-red-50 dark:bg-red-500/10";
+ return (
+
+ {label}:
+ {children}
+
+ );
+}
diff --git a/frontend-web/src/roundtable-planning/dashboard/DashboardApp.tsx b/frontend-web/src/roundtable-planning/dashboard/DashboardApp.tsx
index 5a003174..91816cd3 100644
--- a/frontend-web/src/roundtable-planning/dashboard/DashboardApp.tsx
+++ b/frontend-web/src/roundtable-planning/dashboard/DashboardApp.tsx
@@ -8,7 +8,7 @@
* - 不传 data 时回落参考 demo(DEMO_DATA)。
*/
import './dashboard.css';
-import { useEffect, useState } from 'react';
+import { useEffect, useState, type ReactNode } from 'react';
import { TabType } from './types';
import {
DashboardDataProvider,
@@ -31,23 +31,48 @@ import {
const TAB_ICONS = [LayoutGrid, Users, Megaphone, ShieldAlert, FileText, Activity];
-export function DashboardApp({ data = DEMO_DATA }: { data?: DashboardDataValue }) {
- const [activeTab, setActiveTab] = useState('overview');
+/** 自定义页(dashboard-agent 战情多页用):一个 Tab = id + 标签 + 图标 + 正文。 */
+export interface DashboardPage {
+ id: string;
+ label: string;
+ icon?: ReactNode;
+ content: ReactNode;
+}
- // 数据切换(切历史记录/重绘 → data 引用变化)时复位到总览:避免停留在上一份数据
- // 钻入的某个席位详情页,而新数据里无同名席位导致「加载席位数据失败」。
+export function DashboardApp({
+ data = DEMO_DATA,
+ pages,
+}: {
+ data?: DashboardDataValue;
+ /**
+ * 可选自定义页集:传入时整套 Tab(标签/图标)与正文都由它驱动,替换默认的
+ * 「总览 + 各席位」(dashboard-agent 用它注入战情多页)。会商 Step3 不传 → 行为零变化。
+ */
+ pages?: DashboardPage[];
+}) {
+ const usingPages = !!pages && pages.length > 0;
+ const firstTab: TabType = usingPages ? pages![0].id : 'overview';
+ const [activeTab, setActiveTab] = useState(firstTab);
+
+ // 数据/页集切换(切历史记录/重绘 → data 引用变化)时复位到首个 Tab:避免停留在上一份
+ // 数据钻入的某个席位详情页,而新数据里无同名席位导致「加载席位数据失败」。
useEffect(() => {
- setActiveTab('overview');
- }, [data]);
+ setActiveTab(firstTab);
+ }, [data, firstTab]);
- // 导航 Tab:总览 + 各席位(由数据动态生成,原参考为写死的固定 6 项)。
- const tabs: TabType[] = ['overview', ...data.SEAT_CARDS.map((s) => s.id)];
+ // 导航 Tab:自定义页集 → 各页 id;否则总览 + 各席位(由数据动态生成)。
+ const tabs: TabType[] = usingPages
+ ? pages!.map((p) => p.id)
+ : ['overview', ...data.SEAT_CARDS.map((s) => s.id)];
- const getTabLabel = (tab: TabType) =>
- tab === 'overview' ? '总览' : data.SEAT_CARDS.find((c) => c.id === tab)?.title ?? tab;
+ const getTabLabel = (tab: TabType) => {
+ if (usingPages) return pages!.find((p) => p.id === tab)?.label ?? tab;
+ return tab === 'overview' ? '总览' : data.SEAT_CARDS.find((c) => c.id === tab)?.title ?? tab;
+ };
const getTabIcon = (tab: TabType) => {
const cls = 'w-4 h-4';
+ if (usingPages) return pages!.find((p) => p.id === tab)?.icon ?? ;
if (tab === 'overview') return ;
const idx = data.SEAT_CARDS.findIndex((c) => c.id === tab);
const Ico = TAB_ICONS[(idx >= 0 ? idx : 0) % TAB_ICONS.length];
@@ -109,7 +134,18 @@ export function DashboardApp({ data = DEMO_DATA }: { data?: DashboardDataValue }
{/* Body */}
- {activeTab === 'overview' ? (
+ {usingPages ? (
+
+ {pages!.find((p) => p.id === activeTab)?.content}
+
+ ) : activeTab === 'overview' ? (
(null);
+ // 战情报告解析结果(selectSqReport → 各页面展示模型);解析失败/无数据 → null(页面回退占位)。
+ const [sqReport, setSqReport] = useState(null);
const dashboard = useStep3Dashboard({
intentReady: null,
@@ -113,7 +133,8 @@ export default function DashboardAgentPage() {
} = dashboard;
const [loading, setLoading] = useState(true);
- const [view, setView] = useState("chat");
+ // 静态阶段默认进「大屏页面」,直接展示静态版式(见 STATIC_PREVIEW)。
+ const [view, setView] = useState(STATIC_PREVIEW ? "page" : "chat");
const [renderMode, setRenderMode] = useState("app");
// 真实接口请求失败/无数据时的提示(真实模式不拿假数据兜底,改为提示+重试)。
const [inputError, setInputError] = useState(null);
@@ -152,6 +173,12 @@ export default function DashboardAgentPage() {
fetchDashboardInputJson(taskId).catch(() => null),
]);
inputRef.current = input;
+ // 解析战情报告 → 各页面展示模型(容错:异常/无数据均不抛错,页面回退占位)。
+ try {
+ setSqReport(parseSqReport(input));
+ } catch {
+ setSqReport(null);
+ }
const savedDialogues = (session?.transcript as Step2Dialogue[] | undefined) ?? [];
const validReport = session?.reportJson && parseReportJson(session.reportJson);
if (validReport) {
@@ -161,14 +188,20 @@ export default function DashboardAgentPage() {
} else if (savedDialogues.length > 0) {
// 有历史聊天但无有效结果 → 进生成过程展示历史,不自动重跑(点「重新绘制」可重生成)。
hydrateTranscriptRef.current(savedDialogues, session?.reportJson ?? null);
- setView("chat");
+ // 静态阶段仍停在「大屏页面」看版式(历史对话可点「编辑」查看)。
+ setView(STATIC_PREVIEW ? "page" : "chat");
} else {
// 无任何记录 → 接口返回了真实数据才启动模型抽取;否则提示(真实模式不拿假数据兜底)。
- setView("chat");
- if (input) {
- startRef.current();
+ // 静态阶段(STATIC_PREVIEW):不自动抽取,停在「大屏页面」展示静态版式。
+ if (STATIC_PREVIEW) {
+ setView("page");
} else {
- setInputError("未获取到任务数据:接口请求失败或未返回内容,请稍后重试。");
+ setView("chat");
+ if (input) {
+ startRef.current();
+ } else {
+ setInputError("未获取到任务数据:接口请求失败或未返回内容,请稍后重试。");
+ }
}
}
} finally {
@@ -218,6 +251,30 @@ export default function DashboardAgentPage() {
);
const hasResult = !!dashboardData || !!dashboardHtml;
+ // 展示层脱敏:所有页面文本经 desensitizeDeep 深度替换后再渲染(与会商大屏一致)。
+ const safeReport = useMemo(() => {
+ if (!sqReport) return null;
+ try {
+ return desensitizeDeep(sqReport) as SqReport;
+ } catch {
+ return sqReport; // 脱敏异常不影响展示
+ }
+ }, [sqReport]);
+
+ // 战情多页:总览 + 六个详情页;详情页传入对应的(脱敏后)真实数据(无则页面回退占位)。
+ const dashPages: DashboardPage[] = useMemo(
+ () => [
+ { id: "overview", label: "总览", icon: , content: },
+ { id: "enemy", label: "敌情", icon: , content: },
+ { id: "ours", label: "我情", icon: , content: },
+ { id: "field", label: "战场环境", icon: , content: },
+ { id: "judge", label: "情况判断", icon: , content: },
+ { id: "decision", label: "决心建议", icon: , content: },
+ { id: "disposal", label: "处置建议", icon: , content: },
+ ],
+ [safeReport],
+ );
+
// 网页渲染:blob URL 作 iframe src(:target 导航需独立文档 URL,见 Step3DashboardMode 注释)。
const [htmlBlobUrl, setHtmlBlobUrl] = useState(null);
useEffect(() => {
@@ -392,13 +449,27 @@ export default function DashboardAgentPage() {
{/* 主体 */}
{loading ? (
+ // 加载框:请求战情报告期间显示,拿到数据后再渲染大屏(避免先闪占位再跳真实数据)。
正在请求任务数据…
-
拉取接口返回后将自动开始智能体抽取绘制
+
拉取接口返回后展示大屏
+ ) : view === "page" ? (
+ renderMode === "html" && htmlBlobUrl ? (
+
+ ) : (
+
+
+
+ )
) : inputError && dialogues.length === 0 ? (
@@ -414,19 +485,6 @@ export default function DashboardAgentPage() {
- ) : view === "page" ? (
- renderMode === "html" && htmlBlobUrl ? (
-
- ) : (
-
-
-
- )
) : (