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