feat: 优化8BF业务链流程图与深色配置弹窗
This commit is contained in:
1 parent
1e21e559fb
commit
1555d5344b
5 files changed
+395
-13
No files matched your search
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Check, Loader2, Save, Settings2, X } from "lucide-react";
|
||||
import { Loader2, Save, Settings2, X } from "lucide-react";
|
||||
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { PositionBusinessChainFlow } from "@/position-roundtable/components/PositionBusinessChainFlow";
|
||||
import RoundtablePlanningPage from "@/roundtable-planning/pages/RoundtablePlanningPage";
|
||||
import { listChains } from "@/roundtable-planning/api/chains";
|
||||
import type { BusinessChain } from "@/roundtable-planning/lib/business-chain";
|
||||
@@ -34,6 +35,10 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
const [selectedChainId, setSelectedChainId] = useState("");
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const configuredChain = useMemo(
|
||||
() => chains.find((chain) => chain.id === mapping?.chainId) ?? null,
|
||||
[chains, mapping?.chainId],
|
||||
);
|
||||
|
||||
const loadConfiguration = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
@@ -61,6 +66,10 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
}, [loadConfiguration]);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!isAdmin) {
|
||||
setError("仅管理员可以配置 8BF 业务链条");
|
||||
return;
|
||||
}
|
||||
const selected = chains.find((chain) => chain.id === selectedChainId);
|
||||
if (!selected) {
|
||||
setError("请选择一条已发布的业务链");
|
||||
@@ -81,16 +90,10 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [chains, selectedChainId]);
|
||||
}, [chains, isAdmin, selectedChainId]);
|
||||
|
||||
const toolbarSlot = (
|
||||
<>
|
||||
<div className="eightbf-roundtable__chain-status" aria-live="polite">
|
||||
<span className="eightbf-roundtable__chain-status-label">8BF 业务链</span>
|
||||
<span className="eightbf-roundtable__chain-status-value">
|
||||
{isLoading ? "加载中…" : mapping?.chainTitle || "待管理员配置"}
|
||||
</span>
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -101,7 +104,7 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
配置业务链
|
||||
</button>
|
||||
)}
|
||||
{isSettingsOpen && (
|
||||
{isAdmin && isSettingsOpen && (
|
||||
<div className="eightbf-roundtable__settings-mask" role="presentation">
|
||||
<section
|
||||
className="eightbf-roundtable__settings-dialog"
|
||||
@@ -137,7 +140,7 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
{error && <p className="eightbf-roundtable__settings-error">{error}</p>}
|
||||
{error && <p className="eightbf-roundtable__settings-error" role="alert">{error}</p>}
|
||||
<div className="eightbf-roundtable__settings-actions">
|
||||
<button type="button" onClick={() => setIsSettingsOpen(false)}>取消</button>
|
||||
<button type="button" onClick={() => void handleSave()} disabled={isSaving}>
|
||||
@@ -148,7 +151,6 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && mapping?.chainId && <Check className="eightbf-roundtable__configured-icon h-4 w-4" aria-label="已配置" />}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -159,6 +161,13 @@ export default function EightBfRoundtablePage({ compactChrome = false }: { compa
|
||||
lockBusinessChain
|
||||
storageTaskId={storageTaskId}
|
||||
toolbarSlot={toolbarSlot}
|
||||
entryRailBusinessChainSlot={
|
||||
<PositionBusinessChainFlow
|
||||
chain={configuredChain}
|
||||
loading={isLoading}
|
||||
className="eightbf-roundtable__business-chain-flow"
|
||||
/>
|
||||
}
|
||||
rootClassName="eightbf-roundtable"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -21,4 +21,10 @@
|
||||
.eightbf-roundtable .eightbf-roundtable__chain-status-value { color: #134e4a; font-weight: 600; }
|
||||
.eightbf-roundtable .eightbf-roundtable__configured-icon { color: var(--eightbf-accent); }
|
||||
|
||||
/* 8BF 左上业务链区域:复用岗位会商的纵向节点、点阵画布与放大查看交互。 */
|
||||
.eightbf-roundtable .eightbf-roundtable__business-chain-flow {
|
||||
height: 500px;
|
||||
min-height: 460px;
|
||||
}
|
||||
|
||||
/* 可在此继续覆盖第 1 步的任务理解面板与业务链流程图样式。 */
|
||||
@@ -31,4 +31,49 @@
|
||||
.eightbf-roundtable .eightbf-roundtable__settings-actions button:last-child { display: inline-flex; align-items: center; gap: 6px; background: #0f766e; color: #fff; }
|
||||
.eightbf-roundtable .eightbf-roundtable__settings-actions button:disabled { cursor: not-allowed; opacity: .65; }
|
||||
|
||||
/* 管理员业务链配置弹窗:深色主题下与会商工作区使用同一套深蓝表面与高对比文字。 */
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-mask {
|
||||
background: rgb(2 12 30 / 72%);
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-dialog {
|
||||
border: 1px solid rgb(69 195 243 / 42%);
|
||||
background: #082b57;
|
||||
box-shadow: 0 24px 56px rgb(0 0 0 / 45%);
|
||||
}
|
||||
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-kicker { color: #65d6c6; }
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-heading h2 { color: #f1f7ff; }
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-close { color: #a9bddb; }
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-close:hover { color: #fff; }
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-tip { color: #b9c9df; }
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-field { color: #e6effc; }
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-field select {
|
||||
border-color: rgb(143 196 255 / 48%);
|
||||
background: #0d3768;
|
||||
color: #f1f7ff;
|
||||
color-scheme: dark;
|
||||
}
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-field select:focus {
|
||||
border-color: #65d6c6;
|
||||
outline: 2px solid rgb(101 214 198 / 28%);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-field option { background: #0d3768; color: #f1f7ff; }
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-error { color: #fca5a5; }
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-actions button:first-child {
|
||||
color: #b9c9df;
|
||||
}
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-actions button:first-child:hover {
|
||||
background: rgb(143 196 255 / 12%);
|
||||
color: #fff;
|
||||
}
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-actions button:last-child {
|
||||
background: #167d74;
|
||||
}
|
||||
.dark .eightbf-roundtable .eightbf-roundtable__settings-actions button:last-child:hover {
|
||||
background: #21958a;
|
||||
}
|
||||
|
||||
/* 可在此继续覆盖第 3 步报告、流程图与放大弹窗的样式。 */
|
||||
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* 岗位会商风格的只读业务链流程图。
|
||||
*
|
||||
* 页面无关:8BF 等独立会商页可以直接复用岗位会商的纵向流程图视觉与交互,
|
||||
* 不会读取或写入岗位会商的会话、岗位节点状态。
|
||||
*/
|
||||
import { useRef, useState } from "react";
|
||||
import { CheckCircle2, Circle, Search } from "lucide-react";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import type { BusinessChain, ChainSeat } from "@/roundtable-planning/lib/business-chain";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const NODE_COLORS = ["#4f7df3", "#a778f5", "#65b4e8", "#f0a245", "#e675a8"];
|
||||
const FLOW_NODE_WIDTH = 184;
|
||||
const FLOW_NODE_GAP = 12;
|
||||
|
||||
type FlowStage = {
|
||||
goal: string | null;
|
||||
seats: ChainSeat[];
|
||||
};
|
||||
|
||||
export interface PositionBusinessChainFlowProps {
|
||||
chain: BusinessChain | null;
|
||||
loading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function getFlowStages(chain: BusinessChain): FlowStage[] {
|
||||
const seatsById = new Map(chain.seats.map((seat) => [seat.agent_id, seat]));
|
||||
if (chain.stages?.length) {
|
||||
return chain.stages
|
||||
.map((ids, index) => ({
|
||||
goal: chain.stageGoals?.[index] ?? null,
|
||||
seats: ids
|
||||
.map((id) => seatsById.get(id))
|
||||
.filter((seat): seat is ChainSeat => Boolean(seat)),
|
||||
}))
|
||||
.filter((stage) => stage.seats.length > 0);
|
||||
}
|
||||
return chain.seats.map((seat) => ({ goal: null, seats: [seat] }));
|
||||
}
|
||||
|
||||
export function PositionBusinessChainFlow({
|
||||
chain,
|
||||
loading = false,
|
||||
className,
|
||||
}: PositionBusinessChainFlowProps) {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"position-business-chain-flow flex h-full min-h-[460px] w-full flex-col overflow-hidden rounded-2xl border border-[#ece7e4] bg-white p-3 shadow-[0_4px_20px_rgba(0,0,0,0.02)] dark:border-[#2b8ffe]/30 dark:bg-[#042b59]/55 dark:shadow-none",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="mb-3 flex shrink-0 items-center justify-between gap-2 border-b border-slate-200 pb-3 dark:border-[#2b8ffe]/40">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="h-6 w-1 shrink-0 rounded-l-[2px] rounded-br-[2px] bg-[linear-gradient(167deg,#f15959_0%,#cc1925_100%)] dark:bg-[linear-gradient(167deg,#a2e1f9_0%,#45c3f3_100%)]"
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<h2 className="truncate text-base font-semibold text-slate-900 dark:text-white">业务链条</h2>
|
||||
<p className="truncate text-[11px] text-slate-400 dark:text-[#8aa0c4]" aria-live="polite">
|
||||
{chain?.title ?? (loading ? "正在加载管理员配置…" : "等待管理员配置")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{chain && (
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-[#f0c8c8] bg-[#fff7f7] text-[#cc1925] transition-colors hover:border-[#cc1925] hover:bg-[#fff0f0] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#cc1925]/30 dark:border-[#2b8ffe]/45 dark:bg-[#082d5f] dark:text-[#8fc4ff]"
|
||||
title="查看完整流程图"
|
||||
aria-label="查看完整流程图"
|
||||
onClick={() => setDialogOpen(true)}
|
||||
>
|
||||
<Search className="size-[18px]" strokeWidth={2.5} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!chain ? (
|
||||
<div className="flex min-h-0 flex-1 items-center justify-center rounded-lg border border-dashed border-[#e8d0cc] px-5 text-center text-xs leading-6 text-slate-400 dark:border-[#2b8ffe]/30 dark:text-[#8aa0c4]">
|
||||
{loading ? "正在加载业务链条…" : "管理员尚未配置 8BF 业务链条"}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<BusinessChainCanvas chain={chain} className="min-h-0 flex-1" />
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogContent className="flex h-[86vh] w-[96vw] max-w-[1180px] flex-col gap-0 overflow-hidden p-0">
|
||||
<DialogHeader className="shrink-0 border-b px-5 py-4 dark:border-[#2b8ffe]/25">
|
||||
<DialogTitle className="text-base font-semibold">{chain.title} · 业务链条流程图</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="min-h-0 flex-1 p-4">
|
||||
<BusinessChainCanvas chain={chain} expanded className="h-full" />
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function BusinessChainCanvas({
|
||||
chain,
|
||||
expanded = false,
|
||||
className,
|
||||
}: {
|
||||
chain: BusinessChain;
|
||||
expanded?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const [view, setView] = useState({ tx: 0, ty: 0 });
|
||||
const [panning, setPanning] = useState(false);
|
||||
const dragRef = useRef<{ x: number; y: number; tx: number; ty: number } | null>(null);
|
||||
const stages = getFlowStages(chain);
|
||||
const nodeWidth = expanded ? 250 : FLOW_NODE_WIDTH;
|
||||
const nodeGap = expanded ? 18 : FLOW_NODE_GAP;
|
||||
let colorIndex = 0;
|
||||
|
||||
const endPan = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragRef.current) return;
|
||||
dragRef.current = null;
|
||||
setPanning(false);
|
||||
try {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
} catch {
|
||||
// The browser may have already released the pointer capture.
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-lg border border-slate-100 bg-slate-50/70 touch-none dark:border-[#2b8ffe]/25 dark:bg-[#032650]/75",
|
||||
panning ? "cursor-grabbing" : "cursor-grab",
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
backgroundImage: "radial-gradient(rgba(148,163,184,.55) 1px, transparent 1px)",
|
||||
backgroundSize: "16px 16px",
|
||||
}}
|
||||
title="按住鼠标拖动画布;双击复位"
|
||||
onDoubleClick={() => setView({ tx: 0, ty: 0 })}
|
||||
onPointerDown={(event) => {
|
||||
if (event.button !== 0) return;
|
||||
dragRef.current = { x: event.clientX, y: event.clientY, tx: view.tx, ty: view.ty };
|
||||
setPanning(true);
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
}}
|
||||
onPointerMove={(event) => {
|
||||
const drag = dragRef.current;
|
||||
if (!drag) return;
|
||||
setView({ tx: drag.tx + event.clientX - drag.x, ty: drag.ty + event.clientY - drag.y });
|
||||
}}
|
||||
onPointerUp={endPan}
|
||||
onPointerCancel={endPan}
|
||||
>
|
||||
<div
|
||||
className="flex min-h-full min-w-max flex-col items-center px-5 py-6"
|
||||
style={{ transform: `translate(${view.tx}px, ${view.ty}px)` }}
|
||||
>
|
||||
<FlowNode
|
||||
title="任务理解智能体"
|
||||
description="任务理解岗"
|
||||
accent={NODE_COLORS[0]}
|
||||
completed
|
||||
width={nodeWidth}
|
||||
/>
|
||||
{stages.map((stage, stageIndex) => {
|
||||
const stageWidth = Math.max(
|
||||
nodeWidth,
|
||||
stage.seats.length * (nodeWidth + nodeGap) - nodeGap,
|
||||
);
|
||||
return (
|
||||
<div key={`${stage.goal ?? "stage"}-${stageIndex}`} className="flex flex-col items-center">
|
||||
<FlowConnector
|
||||
branches={stage.seats.length}
|
||||
width={stageWidth}
|
||||
nodeWidth={nodeWidth}
|
||||
nodeGap={nodeGap}
|
||||
/>
|
||||
<div className="flex items-start justify-center" style={{ gap: nodeGap }}>
|
||||
{stage.seats.map((seat) => {
|
||||
const accent = NODE_COLORS[(colorIndex++ + 1) % NODE_COLORS.length]!;
|
||||
return (
|
||||
<FlowNode
|
||||
key={seat.agent_id}
|
||||
title={seat.name || seat.agent_id}
|
||||
description={stage.goal?.trim() || seat.description?.trim() || "业务链席位"}
|
||||
accent={accent}
|
||||
width={nodeWidth}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<FlowConnector branches={1} width={nodeWidth} nodeWidth={nodeWidth} nodeGap={nodeGap} />
|
||||
<FlowNode
|
||||
title="方案总结智能体"
|
||||
description="内置总结节点"
|
||||
accent="#8b83f5"
|
||||
width={nodeWidth}
|
||||
/>
|
||||
<FlowConnector branches={1} width={nodeWidth} nodeWidth={nodeWidth} nodeGap={nodeGap} />
|
||||
<FlowNode
|
||||
title="行动规划智能体"
|
||||
description="内置规划节点"
|
||||
accent="#78bce6"
|
||||
width={nodeWidth}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FlowConnector({
|
||||
branches,
|
||||
width,
|
||||
nodeWidth,
|
||||
nodeGap,
|
||||
}: {
|
||||
branches: number;
|
||||
width: number;
|
||||
nodeWidth: number;
|
||||
nodeGap: number;
|
||||
}) {
|
||||
const connectorStyle = { backgroundColor: "rgba(148,163,184,.88)" };
|
||||
if (branches <= 1) {
|
||||
return (
|
||||
<span className="relative mb-1 h-8 w-0.5 rounded-full" style={connectorStyle}>
|
||||
<span className="absolute bottom-1 left-1/2 size-2 -translate-x-1/2 rotate-45 border-b-2 border-r-2 border-slate-400" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const nodeCenter = nodeWidth / 2;
|
||||
return (
|
||||
<div className="relative mb-1 h-9" style={{ width }}>
|
||||
<span className="absolute left-1/2 top-0 h-3.5 w-0.5 -translate-x-1/2 rounded-full" style={connectorStyle} />
|
||||
<span className="absolute top-3.5 h-0.5 rounded-full" style={{ left: nodeCenter, right: nodeCenter, ...connectorStyle }} />
|
||||
{Array.from({ length: branches }).map((_, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="absolute top-3.5 bottom-0 w-0.5 -translate-x-1/2 rounded-full"
|
||||
style={{
|
||||
left: `${((index * (nodeWidth + nodeGap) + nodeCenter) / width) * 100}%`,
|
||||
...connectorStyle,
|
||||
}}
|
||||
>
|
||||
<span className="absolute bottom-1 left-1/2 size-2 -translate-x-1/2 rotate-45 border-b-2 border-r-2 border-slate-400" />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FlowNode({
|
||||
title,
|
||||
description,
|
||||
accent,
|
||||
completed = false,
|
||||
width,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
accent: string;
|
||||
completed?: boolean;
|
||||
width: number;
|
||||
}) {
|
||||
const StatusIcon = completed ? CheckCircle2 : Circle;
|
||||
return (
|
||||
<article
|
||||
className="relative overflow-hidden rounded-xl border border-slate-200 bg-white p-3 pr-10 shadow-sm dark:border-[#2b8ffe]/40 dark:bg-[#0a2d58]"
|
||||
style={{ width, borderLeftWidth: 5, borderLeftColor: accent }}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute right-2 top-2 flex size-6 items-center justify-center rounded-full border bg-white shadow-sm dark:bg-[#0d3768]",
|
||||
completed
|
||||
? "border-emerald-500 text-emerald-500"
|
||||
: "border-slate-300 text-slate-400 dark:border-[#8aa0c4] dark:text-[#8aa0c4]",
|
||||
)}
|
||||
>
|
||||
<StatusIcon className="size-3.5" strokeWidth={2.5} />
|
||||
</span>
|
||||
<p className="truncate text-sm font-extrabold leading-5 text-slate-900 dark:text-[#e8edf5]" title={title}>
|
||||
{title}
|
||||
</p>
|
||||
<p className="mt-0.5 truncate text-xs font-semibold leading-4" style={{ color: accent }} title={description}>
|
||||
{description}
|
||||
</p>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -296,6 +296,8 @@ export default function RoundtablePlanningPage({
|
||||
storageTaskId,
|
||||
/** 供独立业务页在原页头部插入自己的操作入口。 */
|
||||
toolbarSlot,
|
||||
/** 独立业务页在第一步左上角替换默认业务链选择区的内容。 */
|
||||
entryRailBusinessChainSlot,
|
||||
/** 独立业务页的根节点样式类,避免修改原会商样式。 */
|
||||
rootClassName,
|
||||
}: {
|
||||
@@ -304,6 +306,7 @@ export default function RoundtablePlanningPage({
|
||||
lockBusinessChain?: boolean;
|
||||
storageTaskId?: string | null;
|
||||
toolbarSlot?: ReactNode;
|
||||
entryRailBusinessChainSlot?: ReactNode;
|
||||
rootClassName?: string;
|
||||
}) {
|
||||
const { theme } = useTheme();
|
||||
@@ -2170,6 +2173,7 @@ export default function RoundtablePlanningPage({
|
||||
entryRailBusinessCode={prefillRef.current?.businessCode ?? null}
|
||||
lockedChainId={lockedChainId}
|
||||
lockBusinessChain={lockBusinessChain}
|
||||
entryRailBusinessChainSlot={entryRailBusinessChainSlot}
|
||||
/>
|
||||
|
||||
{/* MIDDLE VIEWPORT */}
|
||||
@@ -2729,6 +2733,8 @@ interface LeftSidebarProps {
|
||||
lockBusinessChain?: boolean;
|
||||
/** 独立页面锁定显示的业务链。 */
|
||||
lockedChainId?: string | null;
|
||||
/** 第一阶段替换默认业务链选择区的自定义内容。 */
|
||||
entryRailBusinessChainSlot?: ReactNode;
|
||||
}
|
||||
|
||||
function Step2SandboxSidebarSync({
|
||||
@@ -2779,6 +2785,7 @@ function LeftSidebar({
|
||||
entryRailBusinessCode,
|
||||
lockBusinessChain,
|
||||
lockedChainId,
|
||||
entryRailBusinessChainSlot,
|
||||
}: LeftSidebarProps) {
|
||||
// 整片脱敏:左侧面板(任务概览/参与角色/人工参与等)的所有文本——标题、动态内容、
|
||||
// 嵌套子组件文本一并脱敏。消息列表与沙箱是另外的容器,不挂此 Hook,保持原文。
|
||||
@@ -2921,6 +2928,7 @@ function LeftSidebar({
|
||||
businessCode={entryRailBusinessCode ?? null}
|
||||
lockBusinessChain={lockBusinessChain}
|
||||
lockedChainId={lockedChainId ?? null}
|
||||
businessChainSlot={entryRailBusinessChainSlot}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
@@ -2975,6 +2983,7 @@ function RoundtableEntryRail({
|
||||
businessCode,
|
||||
lockedChainId,
|
||||
lockBusinessChain = false,
|
||||
businessChainSlot,
|
||||
}: {
|
||||
selectedChainId: string | null;
|
||||
onSelectChain: (chain: BusinessChain) => void;
|
||||
@@ -2989,6 +2998,7 @@ function RoundtableEntryRail({
|
||||
/** 固定业务链时,左侧只展示这条链的流程图及放大入口。 */
|
||||
lockedChainId?: string | null;
|
||||
lockBusinessChain?: boolean;
|
||||
businessChainSlot?: ReactNode;
|
||||
}) {
|
||||
const [chains, setChains] = useState<BusinessChain[]>([]);
|
||||
const [chainSectionExpanded, setChainSectionExpanded] = useState(true);
|
||||
@@ -3035,7 +3045,7 @@ function RoundtableEntryRail({
|
||||
|
||||
useEffect(() => {
|
||||
// 深链接模式不展示业务链选择 → 不必拉公共链条。
|
||||
if (deeplink) {
|
||||
if (deeplink || lockBusinessChain) {
|
||||
void refresh();
|
||||
return;
|
||||
}
|
||||
@@ -3068,7 +3078,12 @@ function RoundtableEntryRail({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-3">
|
||||
<div
|
||||
className={cn(
|
||||
"flex min-h-0 flex-1 flex-col gap-3",
|
||||
lockBusinessChain && "overflow-y-auto pr-1 custom-scrollbar",
|
||||
)}
|
||||
>
|
||||
{/* 深链接(taskId)模式:左上角展示「当前任务」(rwfx/3qfx)或「当前行动」(xdfx),替代业务链选择。 */}
|
||||
{deeplink ? (
|
||||
isXdfx ? (
|
||||
@@ -3110,6 +3125,8 @@ function RoundtableEntryRail({
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : lockBusinessChain && businessChainSlot ? (
|
||||
businessChainSlot
|
||||
) : (
|
||||
/* 业务链(公共列表,内部滚动) */
|
||||
<div className="bg-white border border-[#ECE7E4] rounded-2xl p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] transition-shadow duration-300 hover:shadow-[0_4px_24px_rgba(45,37,34,0.04)]">
|
||||
|
||||
Reference in new issue
Block a user