refactor: 水平对称双翼布局—左翼(偶索引)+中央主干+右翼(奇索引),实线/虚线区分

This commit is contained in:
2026-08-10 13:21:30 +08:00
parent ae520e49d7
commit 87296af139

View File

@ -121,41 +121,51 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, curren
{/* ─── 焦点模式 ─── */} {/* ─── 焦点模式 ─── */}
{!showFullMap && ( {!showFullMap && (
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-6">
{/* 顶部行:遗留分支标记 */}
{legacySubs.length > 0 && ( {legacySubs.length > 0 && (
<div className="mb-2 rounded-lg border border-dashed border-orange-200 bg-orange-50 px-3 py-1.5 text-center"> <div className="rounded-lg border border-dashed border-orange-200 bg-orange-50 px-3 py-1.5 text-center">
<p className="text-[10px] font-medium text-orange-600">⚠ 以下 {legacySubs.length} 个分支属于已完工的历史主线,仍在进行中</p> <p className="text-[10px] font-medium text-orange-600">⚠ {legacySubs.length} 个遗留分支仍在进行中</p>
</div> </div>
)} )}
{/* 每个活跃主线一行:左翼 + 中央 + 右翼 */}
{/* 中央:activeMain 纵向排列 */} {activeMain.map(mainTask => {
<div className="flex flex-col items-center gap-2"> const allSubs = [...activeSubs, ...legacySubs];
{activeMain.map(t => ( const leftSubs = allSubs.filter((_, i) => i % 2 === 0);
<SlimCard key={t.id} task={t} assigneeName={assigneeNames?.[t.assignee_id || ""]} active onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} /> const rightSubs = allSubs.filter((_, i) => i % 2 === 1);
))} return (
{activeMain.length === 0 && <p className="text-xs text-gray-400 py-4">当前无活跃主线任务</p>} <div key={mainTask.id} className="flex flex-row items-center w-full">
</div> {/* 左翼 */}
<div className="flex-1 flex justify-end items-center pr-4 gap-1">
{/* 两侧:activeSubs + legacySubs 与中央同行 */} {leftSubs.map(t => {
<div className="flex flex-wrap items-start justify-center gap-4 mt-2"> const isLegacy = legacySubs.includes(t);
{/* activeSubs(实线) */} return (
{activeSubs.map(t => ( <div key={t.id} className="flex items-center">
<div key={t.id} className="flex flex-col items-center"> <SlimCard task={t} assigneeName={assigneeNames?.[t.assignee_id || ""]} active legacy={isLegacy} onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} />
<div className="h-4 w-0.5 bg-gray-300" /> <div className={`w-6 border-t-2 ${isLegacy ? "border-dashed border-orange-300" : "border-solid border-gray-400"}`} />
<div className="w-8 border-t-2 border-solid border-gray-400" /> </div>
<SlimCard task={t} assigneeName={assigneeNames?.[t.assignee_id || ""]} active onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} /> );
})}
</div>
{/* 中央主干 */}
<div className="shrink-0 z-10">
<SlimCard task={mainTask} assigneeName={assigneeNames?.[mainTask.assignee_id || ""]} active onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} />
</div>
{/* 右翼 */}
<div className="flex-1 flex justify-start items-center pl-4 gap-1">
{rightSubs.map(t => {
const isLegacy = legacySubs.includes(t);
return (
<div key={t.id} className="flex items-center">
<div className={`w-6 border-t-2 ${isLegacy ? "border-dashed border-orange-300" : "border-solid border-gray-400"}`} />
<SlimCard task={t} assigneeName={assigneeNames?.[t.assignee_id || ""]} active legacy={isLegacy} onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} />
</div>
);
})}
</div>
</div> </div>
))} );
{/* legacySubs(虚线+来源标注) */} })}
{legacySubs.map(t => ( {activeMain.length === 0 && <p className="text-xs text-gray-400 py-4">当前无活跃主线任务</p>}
<div key={t.id} className="flex flex-col items-center">
<div className="h-4 w-0.5 bg-orange-200" />
<div className="w-8 border-t-2 border-dashed border-orange-300" />
<SlimCard task={t} assigneeName={assigneeNames?.[t.assignee_id || ""]} active legacy onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} />
</div>
))}
</div>
</div> </div>
)} )}
@ -164,40 +174,50 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, curren
<div className="space-y-6"> <div className="space-y-6">
{allMains.map(mainTask => { {allMains.map(mainTask => {
const children = Array.from(ALL_TASKS).filter(t => t.parent_task_id === mainTask.id); const children = Array.from(ALL_TASKS).filter(t => t.parent_task_id === mainTask.id);
const activeChildren = children.filter(c => active(c.status)); const hasLegacyActive = children.some(c => !isMain(c) && !active(mainTask.status));
const hasLegacyActive = activeChildren.some(c => !isMain(c) && !active(mainTask.status)); const leftChildren = children.filter((_, i) => i % 2 === 0);
const rightChildren = children.filter((_, i) => i % 2 === 1);
return ( return (
<div key={mainTask.id} className="relative"> <div key={mainTask.id} className="relative">
{/* 垂直轴线 */} {/* 垂直轴线贯穿中央 */}
<div className="absolute left-1/2 top-0 bottom-0 w-0.5 bg-gray-200 -translate-x-1/2" /> <div className="absolute left-1/2 top-0 bottom-0 w-0.5 bg-gray-200 -translate-x-1/2 z-0" />
{/* 主干卡片居中 */} {/* 水平一行:左翼 + 中央 + 右翼 */}
<div className="flex justify-center"> <div className="flex flex-row items-center w-full">
<div className="relative z-10"> {/* 左翼分支 */}
<div className="flex-1 flex justify-end items-center pr-4 gap-1">
{leftChildren.map(c => (
<div key={c.id} className="flex items-center">
<SlimCard task={c} assigneeName={assigneeNames?.[c.assignee_id || ""]}
active={active(c.status)} legacy={hasLegacyActive && !active(mainTask.status)}
onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} />
<div className={`w-6 border-t-2 ${hasLegacyActive && !active(mainTask.status) ? "border-dashed border-orange-300" : "border-solid border-gray-400"}`} />
</div>
))}
</div>
{/* 中央主干 */}
<div className="shrink-0 z-10 relative">
<SlimCard task={mainTask} assigneeName={assigneeNames?.[mainTask.assignee_id || ""]} <SlimCard task={mainTask} assigneeName={assigneeNames?.[mainTask.assignee_id || ""]}
active={active(mainTask.status)} onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} /> active={active(mainTask.status)} onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} />
{active(mainTask.status) && ( {active(mainTask.status) && (
<div className="absolute -top-1 -left-1 h-3 w-3 rounded-full bg-green-400 border-2 border-white" /> <div className="absolute -top-1 -left-1 h-3 w-3 rounded-full bg-green-400 border-2 border-white" />
)} )}
</div> </div>
</div> {/* 右翼分支 */}
<div className="flex-1 flex justify-start items-center pl-4 gap-1">
{/* 分支水平排列 */} {rightChildren.map(c => (
{children.length > 0 && ( <div key={c.id} className="flex items-center">
<div className="flex flex-wrap justify-center gap-3 mt-2"> <div className={`w-6 border-t-2 ${hasLegacyActive && !active(mainTask.status) ? "border-dashed border-orange-300" : "border-solid border-gray-400"}`} />
{children.map(c => (
<div key={c.id} className="flex flex-col items-center">
<div className="w-0.5 h-3 bg-gray-300" />
<SlimCard task={c} assigneeName={assigneeNames?.[c.assignee_id || ""]} <SlimCard task={c} assigneeName={assigneeNames?.[c.assignee_id || ""]}
active={active(c.status)} legacy={hasLegacyActive && !active(mainTask.status)} active={active(c.status)} legacy={hasLegacyActive && !active(mainTask.status)}
onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} /> onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} />
</div> </div>
))} ))}
</div> </div>
)} </div>
{/* 区间连接线 */} {/* 区间连接 ▼ */}
{allMains.indexOf(mainTask) < allMains.length - 1 && ( {allMains.indexOf(mainTask) < allMains.length - 1 && (
<div className="flex justify-center py-2"> <div className="flex justify-center py-2">
<span className="text-[10px] text-gray-300">▼</span> <span className="text-[10px] text-gray-300">▼</span>