feat(library): download history lists adopted tasks and organize records

- Migration 0053: download_task.media_entity_id (FK, set null on delete),
  backfilled from plan slot / source release / latest organize record.
- Submitted and adopted tasks carry the entity; entity merge follows it.
- GET /library/{id}: downloads also matched by dt.media_entity_id (adopted
  takeover tasks), plus organizationRecords (incl. manual path organizes);
  library list / inventory counts use it too.
- Web detail: "接管" marker and a list of records without a download task.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 20:54:10 +08:00
1 parent 74f1566985
commit 05d1f76b9f
9 files changed
+159 -11

No files matched your search

+30 -2
View File
@@ -815,7 +815,9 @@ function DownloadsPanel({ detail }: { detail: LibraryDetail }) {
<p className="text-meta text-muted-foreground">
{d.plan
? `${d.plan.purpose === "upgrade" ? "升级" : d.plan.purpose === "fill" ? "补缺" : "换源"}计划 · `
: ""}
: d.downloadTask.origin === "adopted"
? "接管 · "
: ""}
{formatDateTime(d.downloadTask.createdAt)}
</p>
</div>
@@ -872,6 +874,9 @@ function DownloadsPanel({ detail }: { detail: LibraryDetail }) {
),
},
];
// 没有下载任务的整理记录(文件管理按路径手动整理等),下载任务的记录已在上表
const listed = new Set(detail.downloads.map((d) => d.downloadTask.id));
const manual = (detail.organizationRecords ?? []).filter((r) => !r.downloadTaskId || !listed.has(r.downloadTaskId));
return (
<Panel title="下载历史与校验结果" bodyClassName="p-0 sm:p-0">
<DataTable
@@ -879,8 +884,31 @@ function DownloadsPanel({ detail }: { detail: LibraryDetail }) {
columns={columns}
rows={detail.downloads}
rowKey={(d) => d.downloadTask.id}
empty={{ title: "还没有下载记录" }}
empty={{ title: manual.length ? "没有下载任务,只有手动整理记录" : "还没有下载记录" }}
/>
{manual.length ? (
<div className="border-t border-border/60 px-4 py-3 sm:px-5">
<p className="mb-2 text-meta text-muted-foreground">其他整理记录</p>
<ul className="space-y-1.5">
{manual.map((r) => (
<li key={r.id} className="flex min-w-0 flex-wrap items-center gap-2">
<Tone v={ORG_STATUS[r.status]} />
<Link
to={consolePath("organize", r.id)}
className="min-w-0 max-w-full truncate font-mono text-caption text-foreground hover:underline sm:max-w-md"
title={r.sourcePath ?? r.id}
>
{r.targetPath ?? r.sourcePath ?? r.id}
</Link>
<span className="text-meta text-muted-foreground tabular-nums">
{r.trigger === "manual" ? "手动" : r.trigger === "agent" ? "Agent" : "自动"} · {r.fileSucceeded}/
{r.fileTotal} 个文件 · {formatDateTime(r.createdAt)}
</span>
</li>
))}
</ul>
</div>
) : null}
</Panel>
);
}