feat(web): 作品页与订阅选季的剧集组轨道和季卡片
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
265340751c
commit
7d5ba35280
9 files changed
+827
-138
No files matched your search
@@ -0,0 +1,67 @@
|
||||
import type { EpisodeGroupList, EpisodeStructureSeason } from "@umt/contracts";
|
||||
|
||||
// 季集结构(TMDB 默认季 / 剧集组)的界面纯函数:轨道选项、季卡片文案、切换确认文案。学 MoviePilot 的订阅选季对话框:
|
||||
// 横向轨道选「TMDB 默认 / 各剧集组」,下面按所选结构显示季卡片(海报、季名、首播年、集数、已入库)。
|
||||
|
||||
export interface StructureOption {
|
||||
/** 剧集组 id;null = TMDB 默认季 */
|
||||
groupId: string | null;
|
||||
label: string;
|
||||
/** 第二行:类型 · 季数 · 集数 */
|
||||
meta: string | null;
|
||||
current: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_STRUCTURE_LABEL = "TMDB 默认季";
|
||||
|
||||
/** 轨道选项:TMDB 默认季在最前,其后按 TMDB 顺序列剧集组;当前选择打标 */
|
||||
export function structureOptions(list: EpisodeGroupList | undefined): StructureOption[] {
|
||||
const cur = list?.current?.groupId ?? null;
|
||||
const out: StructureOption[] = [{ groupId: null, label: DEFAULT_STRUCTURE_LABEL, meta: null, current: cur === null }];
|
||||
for (const g of list?.groups ?? []) {
|
||||
out.push({
|
||||
groupId: g.id,
|
||||
label: g.name,
|
||||
meta: [g.typeLabel, `${g.groupCount} 季`, `${g.episodeCount} 集`].filter(Boolean).join(" · "),
|
||||
current: cur === g.id,
|
||||
});
|
||||
}
|
||||
// 当前选的剧集组已从 TMDB 删除:仍列出来,免得界面上看不到当前状态
|
||||
if (cur && !out.some((o) => o.groupId === cur))
|
||||
out.push({ groupId: cur, label: list?.current?.name ?? cur, meta: "TMDB 上已不存在", current: true });
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 有剧集组可选时才显示轨道 */
|
||||
export const hasStructureChoice = (list: EpisodeGroupList | undefined) =>
|
||||
!!list?.applicable && (list.groups.length > 0 || !!list.current);
|
||||
|
||||
export function seasonTitle(s: Pick<EpisodeStructureSeason, "seasonNumber" | "name">): string {
|
||||
return s.name ?? (s.seasonNumber === 0 ? "特别篇" : `第 ${s.seasonNumber} 季`);
|
||||
}
|
||||
|
||||
/** 季卡片第二行:首播年 · 集数 */
|
||||
export function seasonLine(s: Pick<EpisodeStructureSeason, "airDate" | "episodeCount">): string {
|
||||
return [s.airDate ? s.airDate.slice(0, 4) : null, `${s.episodeCount} 集`].filter(Boolean).join(" · ");
|
||||
}
|
||||
|
||||
/** 已入库 x/y;一集都没有时返回 null(只列非零项) */
|
||||
export function inLibraryText(s: Pick<EpisodeStructureSeason, "inLibrary" | "episodeCount">): string | null {
|
||||
return s.inLibrary > 0 ? `已入库 ${s.inLibrary}/${s.episodeCount}` : null;
|
||||
}
|
||||
|
||||
const pad = (n: number) => String(n).padStart(2, "0");
|
||||
|
||||
/** 剧集组里的集与 TMDB 默认季集号不同时给「原 S01E14」对照 */
|
||||
export function originText(
|
||||
seasonNumber: number,
|
||||
e: { episodeNumber: number; tmdbSeason: number; tmdbEpisode: number },
|
||||
): string | null {
|
||||
if (e.tmdbSeason === seasonNumber && e.tmdbEpisode === e.episodeNumber) return null;
|
||||
return `原 S${pad(e.tmdbSeason)}E${pad(e.tmdbEpisode)}`;
|
||||
}
|
||||
|
||||
/** 切换确认的说明:改的是作品设置,影响季表、集数映射、订阅与刮削 */
|
||||
export function switchDescription(target: string): string {
|
||||
return `作品改按「${target}」分季:季与集按 TMDB 原季集迁到新位置,已入库文件跟着走;集数映射、订阅和刮削都按新季集,已绑定的资源会重新换算。之后可以再切回来。`;
|
||||
}
|
||||
Reference in new issue
Block a user