feat(downloads): fold download plans into download panel (DL-3)
- Waiting plans (approval / disk space / automation off / queued) are
active-segment rows on /download-tasks/board, merged by created_at,
counted, with approve / cancel actions; ?plan= deep links and legacy
/downloads/plans routes redirect to the panel
- Download detail shows "why this download" (source, purpose, replaced
version, reasons); raw evaluation in tech info
- Failed / cancelled / expired plans appear in the entity download
history; rejected / revoked / expired approvals close draft plans
- New GET /acquisition-plans/{id}/why, POST /acquisition-plans/{id}/cancel
- User-facing copy no longer says 下载计划; glossary updated
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c0522f2531
commit
a9147e647d
50 files changed
+1535
-348
No files matched your search
@@ -8,13 +8,14 @@ import {
|
||||
DownloaderInstance,
|
||||
type DownloadStatus,
|
||||
DownloadTask,
|
||||
DownloadWhy,
|
||||
type PlanStatus,
|
||||
} from "@umt/contracts";
|
||||
import { z } from "zod";
|
||||
import { TTL_SECOND } from "~/lib/api.ts";
|
||||
import { apiMutate, apiParsed, cleanParams } from "./parse.ts";
|
||||
|
||||
// 下载任务、下载计划、缓存卷(W2-C:/download-tasks、/acquisition-plans、/download-cache/cleanup、/dashboard)
|
||||
// 下载任务、计划(界面统称"下载")、缓存卷(W2-C:/download-tasks、/acquisition-plans、/download-cache/cleanup、/dashboard)
|
||||
|
||||
const DownloadPage = cursorPage(DownloadTask);
|
||||
const PlanPage = cursorPage(AcquisitionPlan);
|
||||
@@ -27,6 +28,7 @@ export const downloadKeys = {
|
||||
one: (id: string) => ["downloads", "one", id] as const,
|
||||
plans: (status?: PlanStatus) => ["downloads", "plans", status ?? "all"] as const,
|
||||
plan: (id: string) => ["downloads", "plan", id] as const,
|
||||
why: (id: string) => ["downloads", "why", id] as const,
|
||||
cache: () => ["downloads", "cache"] as const,
|
||||
board: (state: DownloadBoardState) => ["downloads", "board", state] as const,
|
||||
};
|
||||
@@ -72,6 +74,19 @@ export function useDownloadBoard(state: DownloadBoardState) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 深链 ?plan=(DL-3):只取这个计划对应的面板行(等待中的计划或它的下载任务);没有行 = 已结束或不存在 */
|
||||
export function useBoardPlanRow(planId: string | null) {
|
||||
return useQuery({
|
||||
queryKey: ["downloads", "board-plan", planId ?? ""],
|
||||
enabled: !!planId,
|
||||
queryFn: ({ signal }) =>
|
||||
apiParsed(DownloadBoard, "/api/v1/download-tasks/board", {
|
||||
params: { planId: planId ?? "", limit: 1 },
|
||||
signal,
|
||||
}).then((b) => b.items[0] ?? null),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDownloadTask(id: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: downloadKeys.one(id ?? ""),
|
||||
@@ -104,6 +119,27 @@ export function usePlan(id: string | null | undefined) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 「为什么下这个」(DL-3):来源、目的、被替换的版本、选中理由要点与原始评估 */
|
||||
export function usePlanWhy(id: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: downloadKeys.why(id ?? ""),
|
||||
enabled: !!id,
|
||||
staleTime: 60 * TTL_SECOND,
|
||||
queryFn: ({ signal }) =>
|
||||
apiParsed(DownloadWhy, "/api/v1/acquisition-plans/:id/why", { pathParams: { id: id ?? "" }, signal }),
|
||||
});
|
||||
}
|
||||
|
||||
/** 取消还没提交的下载(已预留 / 草案);待批准的走拒绝审批,服务端随之取消 */
|
||||
export function useCancelPlan() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) =>
|
||||
apiMutate("/api/v1/acquisition-plans/:id/cancel", { pathParams: { id } }).then((d) => AcquisitionPlan.parse(d)),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: downloadKeys.all }),
|
||||
});
|
||||
}
|
||||
|
||||
/** 暂停(记为用户暂停)/ 恢复;阈值暂停在水位未回落前服务端拒绝恢复 */
|
||||
export function useDownloadAction() {
|
||||
const qc = useQueryClient();
|
||||
@@ -186,8 +222,8 @@ export const PAUSE_REASON_LABEL = {
|
||||
} as const;
|
||||
|
||||
export const PLAN_STATUS_LABEL: Record<PlanStatus, string> = {
|
||||
draft: "草稿",
|
||||
reserved: "已预留",
|
||||
draft: "待批准",
|
||||
reserved: "已预留空间",
|
||||
submitted: "已提交",
|
||||
completed: "已完成",
|
||||
cancelled: "已取消",
|
||||
|
||||
@@ -173,7 +173,7 @@ export function buildTimeline(input: {
|
||||
key: "plan",
|
||||
stage: "plan",
|
||||
at: plan.createdAt,
|
||||
title: `创建下载计划(${plan.purpose === "fill" ? "补缺" : plan.purpose === "upgrade" ? "升级" : "换源"})`,
|
||||
title: `准备下载(${plan.purpose === "fill" ? "补缺" : plan.purpose === "upgrade" ? "升级" : "换源"})`,
|
||||
detail: humanizeReason(plan.statusReason),
|
||||
tone: "neutral",
|
||||
});
|
||||
@@ -182,7 +182,7 @@ export function buildTimeline(input: {
|
||||
key: "plan-status",
|
||||
stage: "plan",
|
||||
at: plan.updatedAt,
|
||||
title: `计划状态:${PLAN_STATUS_LABEL[plan.status]}`,
|
||||
title: `下载准备:${PLAN_STATUS_LABEL[plan.status]}`,
|
||||
detail: humanizeReason(plan.statusReason),
|
||||
tone: plan.status === "failed" ? "destructive" : plan.status === "expired" ? "warning" : "neutral",
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user