import { useQuery } from "@tanstack/react-query"; import { AssetOperation, DownloadTask, Task } from "@umt/contracts"; import { ArrowLeft } from "lucide-react"; import { Link, Navigate, useNavigate, useParams } from "react-router"; import { z } from "zod"; import { useEngineRun } from "~/api/engine.ts"; import { apiParsed, isMissingEndpoint } from "~/api/parse.ts"; import { DetailSection } from "~/components/console/detail-sheet.tsx"; import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { PageHeader } from "~/components/console/page-header.tsx"; import type { StatusTone } from "~/components/console/status-badge.tsx"; import { Timeline } from "~/components/console/timeline.tsx"; import { TASK_KIND_LABEL, TaskActions, TaskStatusLine as TaskStatusLine2, TaskSummary, } from "~/components/downloads/business-tasks.tsx"; import { runTitle } from "~/components/tasks/labels.ts"; import { RunActions, TaskDetailBody, TaskStatusLine } from "~/components/tasks/task-detail.tsx"; import { Button } from "~/components/ui/button.tsx"; import { TTL_SECOND } from "~/lib/api.ts"; export const meta = () => [{ title: "任务 · UHD 媒体自动化控制台" }]; const OP_TONE: Record = { planned: "neutral", started: "neutral", succeeded: "success", failed: "destructive", skipped: "neutral", compensated: "warning", }; const OP_STATUS: Record = { planned: "计划", started: "进行中", succeeded: "成功", failed: "失败", skipped: "跳过", compensated: "已回滚", }; const OP_PHASE: Record = { decision: "判定", prepare: "准备", transfer: "传输", verify: "校验", commit: "提交", cleanup: "清理", compensate: "回滚", }; const OP_KIND: Record = { copy: "复制", move: "移动", rename: "改名", mkdir: "建目录", hardlink: "硬链接", delete_source: "删除源文件", delete_target: "删除目标文件", verify: "校验", register: "登记入库", qb_pause: "暂停下载", qb_resume: "恢复下载", qb_remove: "移除下载", qb_set_tags: "改下载标签", other: "其他", }; function useTask(id: string, enabled: boolean) { return useQuery({ enabled, queryKey: ["tasks", "one", id], retry: false, queryFn: ({ signal }) => apiParsed(Task, "/api/v1/tasks/:id", { pathParams: { id }, signal }), refetchInterval: (q) => q.state.data && ["pending", "running", "waiting"].includes(q.state.data.status) ? 5 * TTL_SECOND : false, }); } /** 旧链接 /console/tasks/:id 指向下载任务:业务任务查不到时再查下载任务,查到就跳到下载详情 */ function useLegacyDownload(id: string, enabled: boolean) { return useQuery({ queryKey: ["downloads", "legacy-probe", id], enabled, retry: false, queryFn: ({ signal }) => apiParsed(DownloadTask, "/api/v1/download-tasks/:id", { pathParams: { id }, signal }), }); } function Operations({ taskId }: { taskId: string }) { const ops = useQuery({ queryKey: ["tasks", "operations", taskId], queryFn: ({ signal }) => apiParsed(z.object({ items: z.array(AssetOperation) }), "/api/v1/tasks/:id/operations", { pathParams: { id: taskId }, signal, }), }); if (ops.isPending) return ; if (ops.error) return void ops.refetch()} />; if (!ops.data.items.length) return

这个任务没有文件操作。

; return ( ({ id: o.id, at: o.startedAt ?? o.createdAt, tone: OP_TONE[o.status], title: `${OP_PHASE[o.phase]} · ${OP_KIND[o.kind]}:${OP_STATUS[o.status]}`, meta: o.attempts > 1 ? `${o.attempts} 次` : undefined, description: o.organizationRecordId ? ( 整理记录 {o.organizationRecordId.slice(0, 8)} ) : o.downloadTaskId ? ( 下载任务 {o.downloadTaskId.slice(0, 8)} ) : undefined, }))} /> ); } const back = ( ); /** * /console/tasks/:id:id 先当引擎运行查(统一任务);查不到再当旧的业务任务 ID * (有引擎运行就跳过去,没有就显示旧视图),最后当下载任务 ID 跳到下载详情。 */ export default function TaskPage() { const { id = "" } = useParams(); const navigate = useNavigate(); const run = useEngineRun(id); const runMissing = !!run.error && isMissingEndpoint(run.error); const task = useTask(id, runMissing); const notFound = !!task.error && isMissingEndpoint(task.error); const legacy = useLegacyDownload(id, notFound); if (run.data) { const r = run.data; const open = (rid: string) => navigate(`/console/tasks/${encodeURIComponent(rid)}`); return ( <> } actions={ <> {back} } />
); } if (run.error && !runMissing) return ( <> void run.refetch()} /> ); if (task.data?.engineRunId) return ; if (notFound && legacy.data) return ; const t = task.data; return ( <> : undefined} actions={ <> {back} {t ? : null} } /> {run.isPending || task.isPending || (notFound && legacy.isPending) ? (
) : !t ? ( void task.refetch()} /> ) : (
)} ); }