feat(web): 作品页与订阅选季的剧集组轨道和季卡片

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 15:45:12 +08:00
1 parent 265340751c
commit 7d5ba35280
9 files changed
+827 -138

No files matched your search

@@ -13,6 +13,7 @@ import { ExternalLink, Search, X } from "lucide-react";
import { type ReactNode, useEffect, useId, useMemo, useState } from "react";
import { Link } from "react-router";
import { toast } from "sonner";
import { useEpisodeGroups } from "~/api/episode-mapping.ts";
import { useEntityFromCandidate } from "~/api/files.ts";
import { downloaders, useSiteOptions } from "~/api/instances.ts";
import { useEntityDetail, useLibraryRoots } from "~/api/library.ts";
@@ -29,6 +30,8 @@ import {
import { FormField } from "~/components/console/form-field.tsx";
import { consolePath } from "~/components/console/nav.ts";
import { AbsoluteNumberingHint } from "~/components/episode-mapping/subscription-summary.tsx";
import { SubscriptionSeasonPicker } from "~/components/library/episode-structure.tsx";
import { hasStructureChoice } from "~/components/library/episode-structure-model.ts";
import { PRIORITY, SEASON_SCOPE, SUB_MODE } from "~/components/library/labels.ts";
import { ChipToggleGroup } from "~/components/library/parts.tsx";
import { Button } from "~/components/ui/button.tsx";
@@ -333,6 +336,9 @@ export function SubscriptionFormDialog({
const isMovie = kind === "movie";
const detail = useEntityDetail(entityId && !isMovie ? entityId : undefined);
// 作品有剧集组可选时,新建订阅用「季集结构轨道 + 季卡片」选季(学 MP);编辑时季不能改,仍是下拉
const epGroups = useEpisodeGroups(entityId && !isMovie && !editing ? entityId : undefined);
const seasonCards = !editing && !!entityId && hasStructureChoice(epGroups.data);
const seasons = (detail.data?.contentUnits ?? [])
.filter((u) => u.kind === "season" && u.seasonNumber !== null)
.sort((a, b) => (a.seasonNumber ?? 0) - (b.seasonNumber ?? 0));
@@ -537,27 +543,38 @@ export function SubscriptionFormDialog({
/>
</FormField>
</div>
{!isMovie && seasonCards && entityId ? (
<FormField label="季" hint="按作品当前的季集结构订阅;换剧集组等于改作品设置,会先确认">
<SubscriptionSeasonPicker
entityId={entityId}
value={d.seasonUnitId}
onChange={(v) => patch({ seasonUnitId: v })}
/>
</FormField>
) : null}
{isMovie ? null : (
<div className="grid gap-3 sm:grid-cols-2">
<FormField
label="季"
htmlFor={f("season")}
hint={
editing ? "季建好后不能改;要换季就新建订阅" : entityId ? undefined : "作品加入媒体库后可选季"
}
>
<Pick
id={f("season")}
value={d.seasonUnitId}
onChange={(v) => patch({ seasonUnitId: v })}
placeholder="全部季"
options={seasons.map((s) => ({
value: s.id,
label: s.seasonNumber === 0 ? "特别篇" : `第 ${s.seasonNumber} 季`,
}))}
disabled={editing || !entityId}
/>
</FormField>
{seasonCards ? null : (
<FormField
label="季"
htmlFor={f("season")}
hint={
editing ? "季建好后不能改;要换季就新建订阅" : entityId ? undefined : "作品加入媒体库后可选季"
}
>
<Pick
id={f("season")}
value={d.seasonUnitId}
onChange={(v) => patch({ seasonUnitId: v })}
placeholder="全部季"
options={seasons.map((s) => ({
value: s.id,
label: s.seasonNumber === 0 ? "特别篇" : `第 ${s.seasonNumber} 季`,
}))}
disabled={editing || !entityId}
/>
</FormField>
)}
<FormField label="季范围" htmlFor={f("scope")}>
<Pick
id={f("scope")}