refactor: 水平对称双翼布局—左翼(偶索引)+中央主干+右翼(奇索引),实线/虚线区分
This commit is contained in:
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user