feat(任务全景): 流转树改为宽屏弹窗并放大字体

- TaskFlowView 增加 size prop(sm默认/lg放大),弹窗模式卡片/字体/按钮全部放大
- TaskTreeViewer 通用 Modal 导出并支持 widthClass/bodyClassName 定制
- AdminTasksPage 内嵌展开改为宽屏 Modal(max-w-6xl),保留接收/转交/驳回操作
- 记录弹窗放大且 z-index 提升到 z-[60] 保证覆盖弹窗
- 修复 TaskRecordResponse 无 note 字段的类型错误,清理未使用 import
This commit is contained in:
2026-08-28 11:19:08 +08:00
parent d92fcc4d4a
commit 7cfaf4b04b
3 changed files with 128 additions and 92 deletions

View File

@ -13,7 +13,7 @@ import {
} from "../../services/taskApi";
import { TaskFlowView } from "../../components/TaskTree/TaskFlowView";
import type { ModalTarget } from "../../components/TaskTree/TaskTreeViewer";
import { ReceiveConfirmModal, RejectModal, TransferModal } from "../../components/TaskTree/TaskTreeViewer";
import { Modal, ReceiveConfirmModal, RejectModal, TransferModal } from "../../components/TaskTree/TaskTreeViewer";
import type { ProductResponse } from "../../types/admin";
import type { ProductScanResponse } from "../../types/api";
import { useToast } from "../../components/ui/Toast";
@ -174,12 +174,11 @@ export default function AdminTasksPage() {
{
key: "actions", label: "操作", colSpan: 2,
render: (p) => {
const productExpanded = activeTreeProductId === p.serial_number;
const isTreeLoading = treeLoading[p.serial_number];
return (
<button onClick={() => toggleProductTree(p.serial_number)} className="flex items-center gap-1 rounded border border-blue-200 px-2.5 py-1 text-xs font-medium text-blue-600 hover:bg-blue-50 transition-colors">
{isTreeLoading ? <Loader2 className="h-3 w-3 animate-spin" /> : productExpanded ? <X className="h-3 w-3" /> : <GitBranch className="h-3 w-3" />}
{productExpanded ? "收起" : "流转树"}
<button onClick={() => openTreeModal(p.serial_number)} className="flex items-center gap-1 rounded border border-blue-200 px-2.5 py-1 text-xs font-medium text-blue-600 hover:bg-blue-50 transition-colors">
{isTreeLoading ? <Loader2 className="h-3 w-3 animate-spin" /> : <GitBranch className="h-3 w-3" />}
流转树
</button>
);
},
@ -221,9 +220,9 @@ export default function AdminTasksPage() {
if (autoSn) {
setKeyword(autoSn);
loadProducts(autoSn).then((data) => {
// 产品加载完成后自动展开流转树
// 产品加载完成后自动打开流转树弹窗
const found = data.find((p: ProductResponse) => p.serial_number === autoSn);
if (found) toggleProductTree(found.serial_number);
if (found) openTreeModal(found.serial_number);
});
} else {
loadProducts(keyword);
@ -234,6 +233,7 @@ export default function AdminTasksPage() {
e?.preventDefault();
setExpandedOrders(new Set());
setTaskTrees({});
setActiveTreeProductId(null);
loadProducts(keyword);
}
@ -362,14 +362,8 @@ export default function AdminTasksPage() {
});
}
// ---- 排他展开(手风琴模式) ----
async function toggleProductTree(serialNumber: string) {
// 点击已展开的树 → 收起
if (activeTreeProductId === serialNumber) {
setActiveTreeProductId(null);
return;
}
// 展开新的 → 自动收起旧的
// ---- 流转树宽屏弹窗(懒加载,缓存到 taskTrees) ----
function openTreeModal(serialNumber: string) {
setActiveTreeProductId(serialNumber);
if (!taskTrees[serialNumber]) {
setTreeLoading((s) => ({ ...s, [serialNumber]: true }));
@ -379,6 +373,9 @@ export default function AdminTasksPage() {
.finally(() => setTreeLoading((s) => ({ ...s, [serialNumber]: false })));
}
}
function closeTreeModal() {
setActiveTreeProductId(null);
}
// ---- 任务操作 ----
const refreshProductTree = useCallback(async (serialNumber: string) => {
@ -695,10 +692,6 @@ export default function AdminTasksPage() {
{/* 产品行 */}
{group.products.map((p) => {
const productExpanded = activeTreeProductId === p.serial_number;
const isTreeLoading = treeLoading[p.serial_number];
const tree = taskTrees[p.serial_number];
return (
<div key={p.id}>
<div className="grid gap-2 border-t border-gray-50 px-5 py-3 items-center text-sm hover:bg-gray-50/50" style={{ gridTemplateColumns: `repeat(${TOTAL_SPAN}, minmax(0, 1fr))` }}>
@ -708,36 +701,6 @@ export default function AdminTasksPage() {
</div>
))}
</div>
{/* 展开的流转树 — 卡片堆叠视图 */}
{productExpanded && tree && (
<div className="border-t border-dashed border-blue-100 bg-gradient-to-b from-blue-50/40 to-white px-5 py-4">
<h4 className="mb-3 flex items-center gap-2 text-xs font-semibold text-gray-500">
<GitBranch className="h-3.5 w-3.5" />
流转卡片 — {p.serial_number}
</h4>
{tree.task_tree && tree.task_tree.length > 0 ? (
<TaskFlowView
tasks={tree.task_tree}
onAction={setModalTarget}
currentUser={currentUser}
assigneeNames={tree.assignee_names}
/>
) : (
<div className="flex flex-col items-center justify-center py-12 text-gray-400">
<GitBranch className="mb-3 h-10 w-10 text-gray-300" />
<p className="text-sm font-medium text-gray-500">暂无流转记录</p>
<p className="mt-1 text-xs text-gray-400">产品刚创建,尚未分配生产任务</p>
</div>
)}
</div>
)}
{productExpanded && isTreeLoading && (
<div className="border-t border-dashed border-gray-100 bg-gray-50/50 px-5 py-12 text-center">
<Loader2 className="mx-auto h-6 w-6 animate-spin text-blue-400" />
<p className="mt-2 text-xs text-gray-400">加载流转树...</p>
</div>
)}
</div>
);
})}
@ -762,6 +725,37 @@ export default function AdminTasksPage() {
</div>
)}
{/* 🔧 流转树宽屏弹窗 */}
{activeTreeProductId && (
<Modal
open
onClose={closeTreeModal}
title={`🔀 流转树 — ${activeTreeProductId}`}
widthClass="max-w-6xl"
bodyClassName="max-h-[90vh] overflow-y-auto"
>
{treeLoading[activeTreeProductId] ? (
<div className="flex justify-center py-16">
<Loader2 className="h-8 w-8 animate-spin text-blue-400" />
</div>
) : taskTrees[activeTreeProductId]?.task_tree?.length ? (
<TaskFlowView
tasks={taskTrees[activeTreeProductId].task_tree}
onAction={setModalTarget}
currentUser={currentUser}
assigneeNames={taskTrees[activeTreeProductId].assignee_names}
size="lg"
/>
) : (
<div className="flex flex-col items-center justify-center py-16 text-gray-400">
<GitBranch className="mb-3 h-12 w-12 text-gray-300" />
<p className="text-sm font-medium text-gray-500">暂无流转记录</p>
<p className="mt-1 text-xs text-gray-400">产品刚创建,尚未分配生产任务</p>
</div>
)}
</Modal>
)}
{/* ---- 弹窗 ---- */}
<ReceiveConfirmModal
open={modalTarget?.action === "receive"}