feat(web): MP-style media detail with seasons and episodes, shared by library and TMDB preview (MD-1)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 17:28:16 +08:00
1 parent 9cd5a28943
commit 97ac59c7ac
9 files changed
+1250 -190

No files matched your search

+12
View File
@@ -8,6 +8,7 @@ import {
SearchResponse,
type SearchScope,
TmdbMediaDetail,
TmdbSeasonDetailView,
} from "@umt/contracts";
import { api } from "~/lib/api.ts";
import { getParsed, parseOrThrow } from "./library.ts";
@@ -20,6 +21,7 @@ export const searchKeys = {
aggregate: (q: string, scope: SearchScope, limit: number) => ["search", "aggregate", q, scope, limit] as const,
releases: (f: ReleaseSearchFilters) => ["search", "releases", f] as const,
tmdb: (type: string, id: string, ms: boolean) => ["search", "tmdb", type, id, ms] as const,
tmdbSeason: (id: string, season: number) => ["search", "tmdb-season", id, season] as const,
mediaServers: (ref: MediaServerRef) => ["search", "media-servers", ref] as const,
};
@@ -114,6 +116,16 @@ export function useTmdbMedia(type: string | undefined, id: string | undefined) {
});
}
/** 仅 TMDB 剧集的季详情与每集信息(MD-1,季折叠展开时才拉) */
export function useTmdbSeason(id: string | undefined, season: number, enabled: boolean) {
return useQuery({
queryKey: searchKeys.tmdbSeason(id ?? "", season),
enabled: enabled && !!id && /^\d+$/.test(id),
queryFn: ({ signal }) => getParsed(TmdbSeasonDetailView, `/media/tmdb/tv/${id}/season/${season}`, {}, signal),
staleTime: 5 * 60_000,
});
}
export type MediaServerRef = { mediaEntityId: string } | { tmdbType: "movie" | "tv"; tmdbId: string };
/** Emby 库存对比(按本地作品或 TMDB 条目);实例失败 / 超时为 unknown,不等于缺失 */