Files
UHD_Media_Tool/apps/web/app/components/episode-mapping/preview-table.tsx
T
rootandClaude Opus 5.5 63e94b308e feat(identify-words): 识别词——全局识别词(MP 语法)+ 作品识别词(季集换算),规则页「识别词」页签
- 新表 identify_words(0094):全局一块 + 每部作品一块原文,带版本与审计;episode_mapping_rule 转成文本行后删表
- 全局识别词移植 MP WordsMatcher(屏蔽 / 替换 / 集数偏移 / 替换+偏移 / {[tmdbid=…]}),所有按标题 / 文件名识别的地方解析前套用,命中的行记在解析结果上;保存后后台按改动行重新识别
- 作品识别词:S02 >> S01 EP+24 / S01E13 >> S00E01 / S01 @组 >> 忽略,解析器 + 打印器 + 编译;发布组繁简视为同一个
- 去掉规则自动停用:算出来对不上季表只待确认,要停用就注释掉那一行
- 集数待确认的采用 / 忽略与智能体 apply_rule 改为往作品识别词追加一行(带来历注释)
- 编辑器可视化 / 文本两种模式改同一份原文,保存前试算;作品页「集数映射」用同一个编辑器

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-08 17:03:00 +08:00

88 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { EpisodeMappingPreviewItem } from "@umt/contracts";
import { ArrowRight } from "lucide-react";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { cn, formatDateTime } from "~/lib/utils.ts";
import { canonicalText, methodText, sourceText, VALIDATION } from "./model.ts";
// 模拟预览表格:原始季集 → 当前结果 → 套用草稿后的结果、方式(命中的作品识别词行、全局识别词)、校验;
// 与当前不同的行高亮并标「有变化」。
export function PreviewTable({
items,
hasDraft,
empty = "这部作品还没有绑定的资源。",
}: {
items: EpisodeMappingPreviewItem[];
/** 有草稿时列名写「套用后」,否则写「重算」 */
hasDraft: boolean;
empty?: string;
}) {
if (!items.length) return <p className="px-4 py-6 text-caption text-muted-foreground sm:px-5">{empty}</p>;
return (
<div className="overflow-x-auto">
<table className="w-full min-w-[44rem] text-caption">
<thead>
<tr className="border-b border-border/60 text-left text-meta text-muted-foreground">
<th className="px-4 py-2 font-medium">资源</th>
<th className="px-3 py-2 font-medium">原始</th>
<th className="px-3 py-2 font-medium">当前</th>
<th className="px-3 py-2 font-medium">{hasDraft ? "套用后" : "重算"}</th>
<th className="px-3 py-2 font-medium">方式</th>
<th className="px-4 py-2 font-medium">校验</th>
</tr>
</thead>
<tbody>
{items.map((it) => {
const v = VALIDATION[it.validation];
return (
<tr
key={it.sourceReleaseId}
data-changed={it.changed || undefined}
className={cn("border-b border-border/60 last:border-b-0", it.changed && "bg-foreground/[0.04]")}
>
<td className="max-w-[22rem] px-4 py-2 align-top">
<div className="font-mono text-meta break-all text-foreground">{it.title}</div>
<div className="mt-0.5 text-meta text-muted-foreground">
{[it.releaseGroup, formatDateTime(it.publishedAt)].filter(Boolean).join(" · ")}
</div>
</td>
<td className="px-3 py-2 align-top font-mono tabular-nums">{sourceText(it.preview)}</td>
<td className="px-3 py-2 align-top">
<div className="font-mono tabular-nums">{canonicalText(it.current)}</div>
<div className="text-meta text-muted-foreground">{methodText(it.current)}</div>
</td>
<td className="px-3 py-2 align-top">
<div className="inline-flex items-center gap-1 font-mono tabular-nums">
{it.changed ? <ArrowRight aria-hidden className="h-3 w-3 text-muted-foreground" /> : null}
<span className={cn(it.changed && "font-semibold text-foreground")}>
{canonicalText(it.preview)}
</span>
</div>
{it.changed ? <div className="text-meta text-muted-foreground">有变化</div> : null}
</td>
<td className="max-w-[16rem] px-3 py-2 align-top">
<div>{methodText(it.preview)}</div>
{it.preview.ruleText ? (
<div className="font-mono text-meta break-all text-muted-foreground">
{it.preview.ruleLine ? `第 ${it.preview.ruleLine} 行 ` : ""}
{it.preview.ruleText}
</div>
) : null}
{it.words.length ? (
<div className="text-meta break-all text-muted-foreground">
命中识别词:<span className="font-mono">{it.words.join(";")}</span>
</div>
) : null}
</td>
<td className="px-4 py-2 align-top">
<StatusBadge tone={v.tone}>{v.label}</StatusBadge>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}