feat(web): organize records page parity with MP history (HIS-1)

Poster, source -> target, size, downloader; type/time filters; status
counts; group by work; row menu; batch select, delete and reorganize;
delete confirm dialog with dry-run summary; detail delete and download info.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 23:57:19 +08:00
1 parent 51e82f1cd9
commit 03c651085f
8 files changed
+898 -110

No files matched your search

+30 -1
View File
@@ -1,4 +1,11 @@
import { keepPreviousData, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
keepPreviousData,
useInfiniteQuery,
useMutation,
useQueries,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import {
cursorPage,
type FileEntry,
@@ -281,6 +288,28 @@ export function useRecordSources(recordId: string | null) {
});
}
/** HIS-1 批量重新整理:多条记录的源路径一并换算(合并 items / unresolved) */
export function useRecordSourcesMany(recordIds: string[]) {
return useQueries({
queries: recordIds.map((id) => ({
queryKey: ["files", "record-sources", id],
retry: false,
queryFn: ({ signal }: { signal: AbortSignal }) =>
call(FilesRecordSourcesResult, "/files/record-sources", { params: { recordId: id }, signal }),
})),
combine: combineRecordSources,
});
}
// 模块级函数:引用稳定,结果不变时 useQueries 不重算(避免下游 useMemo 每次渲染都变)
function combineRecordSources(rs: { isPending: boolean; error: unknown; data?: FilesRecordSourcesResult }[]) {
return {
isPending: rs.some((r) => r.isPending),
error: rs.find((r) => r.error)?.error ?? null,
data: rs.every((r) => r.data) ? rs.map((r) => r.data!) : undefined,
};
}
/** 作品选择器里的本地作品 */
export type LocalEntityHit = { id: string; title: string; year: number | null; kind: string; alias: string | null };