+
{main ? "主线" : "分支"}
+
{task.task_name}
+
+ {cfg.label}
+ {assigneeName || task.assignee_id || "—"}
{/* 单行时间 */}
-
+
⏰ {fmtTime(task.created_at).split(" ")[0]}
{task.completed_at ? ` → ${fmtTime(task.completed_at).split(" ")[0]}` : " → 至今"}
- {legacy &&
源自: {assigneeName || (findParent(task)?.assignee_id) || "历史任务"}
}
- {isNestedSpawn &&
协助: {findParent(task)?.assignee_id || "—"}
}
+ {legacy &&
源自: {assigneeName || (findParent(task)?.assignee_id) || "历史任务"}
}
+ {isNestedSpawn &&
协助: {findParent(task)?.assignee_id || "—"}
}
{/* 操作按钮 */}
{active && isOwner && (
-
- {task.status?.toUpperCase() === TASK_STATUS.PENDING &&
}
- {task.status?.toUpperCase() !== TASK_STATUS.PENDING &&
}
-
+
+ {task.status?.toUpperCase() === TASK_STATUS.PENDING && }
+ {task.status?.toUpperCase() !== TASK_STATUS.PENDING && }
+
)}
{active && !isOwner && isManager && (
-
-
-
+
+
+
)}
{/* 记录 */}
{task.records && task.records.length > 0 && (
-
{ e.stopPropagation(); onViewRecords?.(task); }} className="mt-1 cursor-pointer rounded bg-blue-50 px-1.5 py-0.5 text-[8px] text-blue-600 hover:bg-blue-100">
-
{task.records.length}条
+
{ e.stopPropagation(); onViewRecords?.(task); }} className={`${sz.record} cursor-pointer rounded bg-blue-50 text-blue-600 hover:bg-blue-100`}>
+ {task.records.length}条
)}
@@ -111,9 +147,10 @@ interface TaskFlowViewProps {
tasks: TaskResponse[]; onAction: (t: ModalTarget) => void;
currentUser?: { username?: string; role?: string } | null;
assigneeNames?: Record
;
+ size?: "sm" | "lg";
}
-export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, currentUser, assigneeNames }: TaskFlowViewProps) {
+export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, currentUser, assigneeNames, size = "sm" }: TaskFlowViewProps) {
const [showFullMap, setShowFullMap] = useState(false);
const [recordsTask, setRecordsTask] = useState(null);
@@ -139,12 +176,13 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, curren
}, [tasks]);
// 🚀 递归渲染分支节点 — 每个节点从自己的 childMap 获取直系子孙,保持树结构不断裂
- const renderBranch = (node: TaskResponse, side: 'left' | 'right', isLegacy: boolean, rootMainId: string): JSX.Element => {
+ const renderBranch = (node: TaskResponse, side: 'left' | 'right', isLegacy: boolean, rootMainId: string): React.JSX.Element => {
const kids = childMap[node.id] || [];
+ const big = size === "lg";
const arrow = side === 'left'
- ? ()
- : ();
- const card = ;
+ ? ()
+ : ();
+ const card = ;
const kidsContainer = kids.length > 0 ? (
{kids.map(k => renderBranch(k, side, isLegacy, rootMainId))}
@@ -178,7 +216,7 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, curren
{/* 模式切换 */}
@@ -195,7 +233,7 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, curren
return (
-
+
{/* 左翼 — 递归渲染,子子孙孙向外延伸 */}
@@ -204,9 +242,9 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, curren
{/* 中央 */}
+ assigneeName={assigneeNames?.[mainTask.assignee_id || ""]} onAction={onAction} currentUser={currentUser} onViewRecords={setRecordsTask} size={size} />
{active(mainTask.status) && (
-
+
)}
{/* 右翼 — 递归渲染,子子孙孙向外延伸 */}
@@ -217,14 +255,14 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, curren
{visibleMains.indexOf(mainTask) < visibleMains.length - 1 && (
- ▼
+ ▼
)}
);
})}
{visibleMains.length === 0 && (
-
+
{showFullMap ? "暂无流转记录" : "当前无活跃主线任务"}
)}
@@ -232,17 +270,17 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, curren
{/* 记录弹窗 */}
{recordsTask && (
-
+
setRecordsTask(null)} />
-
-
提交记录 — {recordsTask.task_name}
+
+
提交记录 — {recordsTask.task_name}
{(recordsTask.records || []).length === 0 ?
暂无记录
:
{[...recordsTask.records!].reverse().map((r, i) => (
-
{fmtTime(r.created_at)}
- {(r.note || r.remark) &&
{r.note || r.remark}
}
- {(() => { const imgs = parseImages(r.images); if (!imgs.length) return null; return
{imgs.map((img, j) =>
})
window.open(imageUrl(img))} />)}
; })()}
+
{fmtTime(r.created_at)}
+ {r.remark &&
{r.remark}
}
+ {(() => { const imgs = parseImages(r.images); if (!imgs.length) return null; return
{imgs.map((img, j) =>
})
window.open(imageUrl(img))} />)}
; })()}
))}
}
diff --git a/frontend/src/components/TaskTree/TaskTreeViewer.tsx b/frontend/src/components/TaskTree/TaskTreeViewer.tsx
index c518044..9acf958 100644
--- a/frontend/src/components/TaskTree/TaskTreeViewer.tsx
+++ b/frontend/src/components/TaskTree/TaskTreeViewer.tsx
@@ -25,16 +25,20 @@ import { getStatusConfig } from "../../constants/task";
// 通用 Modal 容器
// ============================================================
-const Modal = memo(function Modal({
+export const Modal = memo(function Modal({
open,
onClose,
title,
children,
+ widthClass = "max-w-md",
+ bodyClassName = "",
}: {
open: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
+ widthClass?: string;
+ bodyClassName?: string;
}) {
if (!open) return null;
@@ -46,7 +50,7 @@ const Modal = memo(function Modal({
onClick={onClose}
/>
{/* 弹窗 */}
-
+
{title}
);
})}
@@ -762,6 +725,37 @@ export default function AdminTasksPage() {
)}
+ {/* 🔧 流转树宽屏弹窗 */}
+ {activeTreeProductId && (
+
+ {treeLoading[activeTreeProductId] ? (
+
+
+
+ ) : taskTrees[activeTreeProductId]?.task_tree?.length ? (
+
+ ) : (
+
+
+
暂无流转记录
+
产品刚创建,尚未分配生产任务
+
+ )}
+
+ )}
+
{/* ---- 弹窗 ---- */}