feat(web): 刮削设置分区、作品详情刮削状态与文件管理「刮削」(SCR-1)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f38eca419e
commit
df033130f5
10 files changed
+313
-23
No files matched your search
@@ -986,13 +986,21 @@ export function CopyPathDialog({ root, entry, onClose }: { root: FileRoot; entry
|
||||
);
|
||||
}
|
||||
|
||||
// ─── 刷新元数据(MP 的"刮削") ─────────────────────────────────────
|
||||
// ─── 刷新元数据 / 刮削 ─────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 刷新元数据(§7.2):只对已登记项;多选时按作品去重(POST /files/scrape-batch)。
|
||||
* NFO / 图片投影还没实现,不显示这两项。
|
||||
* write = 刮削(SCR-1):同一接口 write=true,按作品排 scrapeMedia,把 NFO 与图片写进媒体库目录(覆盖方式按设置)。
|
||||
*/
|
||||
export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[]; onClose: () => void }) {
|
||||
export function RefreshMetadataDialog({
|
||||
items,
|
||||
write = false,
|
||||
onClose,
|
||||
}: {
|
||||
items: FileEntry[];
|
||||
write?: boolean | undefined;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const batch = useScrapeBatch();
|
||||
const key = useMemo(() => newIdempotencyKey(), []);
|
||||
const [result, setResult] = useState<FilesScrapeBatchResult | null>(null);
|
||||
@@ -1000,12 +1008,15 @@ export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[];
|
||||
const single = entities.length === 1 ? entities[0] : null;
|
||||
const run = () =>
|
||||
batch.mutate(
|
||||
{ idempotencyKey: key, mediaEntityIds: entities.map((a) => a?.mediaEntityId as string) },
|
||||
{ idempotencyKey: key, mediaEntityIds: entities.map((a) => a?.mediaEntityId as string), write },
|
||||
{
|
||||
onSuccess: (r) => {
|
||||
setResult(r);
|
||||
const skipped = r.skipped.length ? `;${r.skipped.length} 个未提交` : "";
|
||||
(r.skipped.length ? toast.warning : toast.success)(
|
||||
`已提交 ${r.submitted.length} 个作品,锁定字段不变${r.skipped.length ? `;${r.skipped.length} 个未提交` : ""}`,
|
||||
write
|
||||
? `已提交 ${r.submitted.length} 个作品的刮削${skipped}`
|
||||
: `已提交 ${r.submitted.length} 个作品,锁定字段不变${skipped}`,
|
||||
);
|
||||
},
|
||||
onError: (e) => toast.error(filesErrorText(e, "提交失败")),
|
||||
@@ -1015,8 +1026,15 @@ export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[];
|
||||
<Dialog open onOpenChange={(o) => !o && onClose()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>刷新元数据{single ? ` · ${single.title}` : ` · ${entities.length} 个作品`}</DialogTitle>
|
||||
<DialogDescription>按字段刷新,锁定的字段和路径不变。即 MP 的「刮削」。</DialogDescription>
|
||||
<DialogTitle>
|
||||
{write ? "刮削" : "刷新元数据"}
|
||||
{single ? ` · ${single.title}` : ` · ${entities.length} 个作品`}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{write
|
||||
? "把 NFO 和图片写进媒体库目录,已有文件按设置只补缺失或全部覆盖。结果在作品详情的「刮削」里。"
|
||||
: "按字段从 TMDB 刷新,锁定的字段和路径不变。不写媒体库目录里的文件。"}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{!single && !result ? (
|
||||
<ul className="max-h-40 space-y-0.5 overflow-auto text-caption">
|
||||
@@ -1034,13 +1052,18 @@ export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[];
|
||||
<div key={s.mediaEntityId} className="rounded-lg border border-border/60 px-3 py-2">
|
||||
<p className="flex items-center justify-between gap-2">
|
||||
<span className="truncate">{s.title}</span>
|
||||
<Link className="shrink-0 text-meta underline" to={`/console/engine/${s.taskId}`}>
|
||||
查看运行
|
||||
<Link
|
||||
className="shrink-0 text-meta underline"
|
||||
to={write ? `/console/library/${s.mediaEntityId}` : `/console/engine/${s.taskId}`}
|
||||
>
|
||||
{write ? "查看结果" : "查看运行"}
|
||||
</Link>
|
||||
</p>
|
||||
<p className="text-meta text-muted-foreground">
|
||||
{s.lockedFields.length ? `锁定字段:${s.lockedFields.join("、")}` : "没有锁定字段"}
|
||||
</p>
|
||||
{write ? null : (
|
||||
<p className="text-meta text-muted-foreground">
|
||||
{s.lockedFields.length ? `锁定字段:${s.lockedFields.join("、")}` : "没有锁定字段"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{result.skipped.map((s) => (
|
||||
@@ -1064,7 +1087,7 @@ export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[];
|
||||
{result ? null : (
|
||||
<Button onClick={run} disabled={batch.isPending || !entities.length}>
|
||||
{batch.isPending ? <LoaderCircle className="animate-spin" /> : <RefreshCw />}
|
||||
刷新
|
||||
{write ? "刮削" : "刷新"}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
|
||||
Reference in new issue
Block a user