feat(episode-mapping): 映射规则接口与模拟预览、待处理确认 / 忽略、作品页集数映射分区与订阅摘要

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-03 19:28:09 +08:00
1 parent fe597187dd
commit 45a114263b
26 files changed
+2770 -9

No files matched your search

+11 -3
View File
@@ -15,6 +15,7 @@ import { PageHeader } from "~/components/console/page-header.tsx";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { TechInfo } from "~/components/console/tech-info.tsx";
import { useListPagination } from "~/components/console/use-list-pagination.ts";
import { EpisodeDecision } from "~/components/episode-mapping/decision.tsx";
import { EntityPicker, type PickedEntity } from "~/components/library/identity-entity-picker.tsx";
import { DECISION_KIND, DECISION_STATUS, opts, SEVERITY } from "~/components/library/labels.ts";
import { Chip, relTime, Tone } from "~/components/library/parts.tsx";
@@ -363,6 +364,8 @@ function DismissButton({ d }: { d: Decision }) {
function DecisionCard({ d }: { d: Decision }) {
const open = OPEN.includes(d.status);
const identity = IDENTITY_KINDS.includes(d.kind);
// 集数待确认:证据与「确认 / 修改 / 忽略」由 EpisodeDecision 渲染
const episode = d.kind === "episode_mapping";
// 身份类:手动 TMDB / 选本地作品两个选项并进「指定作品」
const options = d.options.filter(
(o) =>
@@ -383,11 +386,16 @@ function DecisionCard({ d }: { d: Decision }) {
{d.summary ? (
<p className="mt-1 text-caption break-words text-muted-foreground">{humanizeReason(d.summary)}</p>
) : null}
<GroupReleases d={d} />
{episode ? null : <GroupReleases d={d} />}
{episode ? (
<div className="mt-3">
<EpisodeDecision d={d} open={open} />
</div>
) : null}
<div className="mt-3">
<Evidence d={d} />
</div>
{open ? (
{open && !episode ? (
<div className="mt-3 flex flex-wrap items-center gap-2">
{identity ? <AssignWorkButton d={d} /> : null}
{options.map((o) => (
@@ -404,7 +412,7 @@ function DecisionCard({ d }: { d: Decision }) {
</Link>
) : null}
</div>
) : (
) : open ? null : (
<p className="mt-3 text-meta text-muted-foreground">
{d.resolvedBy ? `${actorText(d.resolvedBy)} ` : ""}于 {formatDateTime(d.resolvedAt)}
{resolution?.optionKey
@@ -48,6 +48,7 @@ import { PaginatedList } from "~/components/console/paginated-list.tsx";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { TechInfo } from "~/components/console/tech-info.tsx";
import { DROPPED_STATUS, droppedReason, PURPOSE_LABEL } from "~/components/downloads/plan-format.ts";
import { EpisodeMappingPanel } from "~/components/episode-mapping/mapping-panel.tsx";
import { evidenceLabel, FieldLockDialog, IdentityDialog, MergeDialog } from "~/components/library/entity-dialogs.tsx";
import {
CONTENT_TYPE,
@@ -1215,6 +1216,7 @@ export default function LibraryDetailPage() {
{e.kind !== "movie" ? <EpisodesPanel detail={d} units={entity.data?.contentUnits ?? []} /> : null}
{e.kind === "movie" ? <MovieVersionsPanel entityId={e.id} /> : null}
<SlotsPanel detail={d} units={entity.data?.contentUnits ?? []} />
{e.kind === "series" || e.kind === "variety" ? <EpisodeMappingPanel entityId={e.id} /> : null}
<div className="grid gap-5 xl:grid-cols-2">
<IdentityPanel detail={d} />
<MetadataPanel detail={d} entity={entity.data} />
@@ -12,6 +12,7 @@ import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
import { KvList } from "~/components/console/kv-list.tsx";
import { consolePath } from "~/components/console/nav.ts";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { SubscriptionMappingSummary } from "~/components/episode-mapping/subscription-summary.tsx";
import { PRIORITY, SEASON_SCOPE, SUB_MODE, SUB_ORIGIN, SUB_STATUS } from "~/components/library/labels.ts";
import { CoverPlaceholder, Panel, relTime, Tone } from "~/components/library/parts.tsx";
import { SlotsPanel } from "~/components/library/subscriptions-slots.tsx";
@@ -215,6 +216,10 @@ export default function SubscriptionDetailPage() {
{sub.mediaEntityId ? <SlotPanel sub={sub} cov={cov} item={item} canFill={tracking} /> : null}
{sub.mediaEntityId && sub.mediaKind !== "movie" ? (
<SubscriptionMappingSummary entityId={sub.mediaEntityId} seasonUnitId={sub.seasonUnitId} />
) : null}
<div className="grid gap-5 lg:grid-cols-2">
<Panel title="订阅信息">
<KvList