From 3c17fa3356bf9d67ccffd87b0d89933aa693eb84 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 30 Sep 2026 00:00:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=96=87=E4=BB=B6=E4=B8=8E=E4=BD=9C?= =?UTF-8?q?=E5=93=81=E9=A1=B5=E7=9A=84=E4=BD=8E=E4=BC=98=E5=85=88=E7=BA=A7?= =?UTF-8?q?=E9=97=AE=E9=A2=98(FIX-7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 文件列表「主文件」只认视频扩展名(字幕自成资产时位置 is_primary 也为真) - 行内悬浮操作只在鼠标悬浮时出现,选中行不再常驻遮挡大小/时间 - 种子详情按识别状态说人话,解析结果 JSON 跟随技术信息默认收起 - 作品页空收录版本写「没设收录要求」,版本行写「库里有 N 个版本」 Co-Authored-By: Claude Opus 5.5 --- apps/server/src/modules/file-manager/list.ts | 3 ++- apps/server/test/file-manager/browse.test.ts | 23 +++++++++++++++++++ apps/web/app/components/files/file-list.tsx | 13 ++++------- apps/web/app/components/library/versions.tsx | 4 +++- .../workbench/release-detail-sheet.tsx | 15 ++++++++++-- apps/web/app/routes/console/library.$id.tsx | 4 ++-- 6 files changed, 48 insertions(+), 14 deletions(-) diff --git a/apps/server/src/modules/file-manager/list.ts b/apps/server/src/modules/file-manager/list.ts index bef1264f..3452b90a 100644 --- a/apps/server/src/modules/file-manager/list.ts +++ b/apps/server/src/modules/file-manager/list.ts @@ -375,7 +375,8 @@ export async function annotate( fileKind, status, escape: r.escape, - asset: a?.link ?? null, + // is_primary 是"资产的主位置",字幕等附属文件自成资产时也为 true;「主文件」只认视频扩展名(FIX-7) + asset: a ? { ...a.link, isPrimary: a.link.isPrimary && fileKind === "video" } : null, download: dl ? { taskId: dl.taskId, status: dl.status, progress: dl.progress, hnr: dl.hnr } : null, media: a?.media ?? null, dirSummary: null, diff --git a/apps/server/test/file-manager/browse.test.ts b/apps/server/test/file-manager/browse.test.ts index 4dea049a..0199f73d 100644 --- a/apps/server/test/file-manager/browse.test.ts +++ b/apps/server/test/file-manager/browse.test.ts @@ -78,6 +78,29 @@ describe.skipIf(skip)("files 浏览与守卫", () => { expect(dir.dirSummary).toMatchObject({ videoCount: 2, registeredCount: 1 }); }); + it("主文件只认视频:整理登记成独立资产的字幕(is_primary = true)不标主文件(FIX-7)", async () => { + await mkdir(join(h.lib, "Sub (2014)")); + await writeFile(join(h.lib, "Sub (2014)", "Sub (2014) - 1080p.mkv"), "video"); + await writeFile(join(h.lib, "Sub (2014)", "Sub (2014) - 1080p.chi.zh-cn.srt"), "1\n"); + const e = await seedEntity(h.db, { kind: "movie", title: "Sub", year: 2014 }); + const v = await seedAsset(h.db, h, { entityId: e, rel: "Sub (2014)/Sub (2014) - 1080p.mkv" }); + const srt = await seedAsset(h.db, h, { + entityId: e, + rel: "Sub (2014)/Sub (2014) - 1080p.chi.zh-cn.srt", + kind: "subtitle", + }); + // 与 organize.ts 一致:附属文件的资产位置也是该资产的主位置,并挂在视频位置下 + await h.db + .updateTable("asset_location") + .set({ is_primary: true, sidecar_of_location_id: v.locationId }) + .where("id", "=", srt.locationId) + .execute(); + const r = await h.api("GET", `/files?${q({ root: h.libRoot, path: "Sub (2014)" })}`); + const byName = Object.fromEntries(r.body.data.entries.map((x: { name: string }) => [x.name, x])); + expect(byName["Sub (2014) - 1080p.mkv"].asset).toMatchObject({ isPrimary: true }); + expect(byName["Sub (2014) - 1080p.chi.zh-cn.srt"].asset).toMatchObject({ isPrimary: false }); + }); + it("下载根:纳管下载中的内容标 downloading", async () => { const r = await h.api("GET", `/files?${q({ root: h.dlRoot })}`); const e = r.body.data.entries.find((x: { name: string }) => x.name === "Show.S01.1080p"); diff --git a/apps/web/app/components/files/file-list.tsx b/apps/web/app/components/files/file-list.tsx index 30646513..135c1c73 100644 --- a/apps/web/app/components/files/file-list.tsx +++ b/apps/web/app/components/files/file-list.tsx @@ -515,9 +515,7 @@ export function FileList({ {sizeCell(e)} {relTime(e.mtime)} - {onAction && !e.escape ? ( - - ) : null} + {onAction && !e.escape ? : null} {menuBtn} @@ -561,15 +559,13 @@ export const DRAG_MIME = "application/x-umt-files"; const DESKTOP_COLS = "grid-cols-[28px_minmax(0,1fr)_120px_5.5rem_32px] lg:grid-cols-[28px_minmax(0,1fr)_120px_5.5rem_6.5rem_32px]"; -/** 行内 hover / 聚焦时浮出的 2 个主操作(§5.1),盖在大小/时间列上方 */ +/** 行内 hover 时浮出的 2 个主操作(§5.1),盖在大小/时间列上方 */ function HoverOps({ e, - focused, onAction, opState, }: { e: FileEntry; - focused: boolean; onAction: (e: FileEntry, op: MenuKey) => void; opState?: (e: FileEntry, op: MenuKey) => string | null; }) { @@ -577,9 +573,10 @@ function HoverOps({ return ( {ops.map((op) => { diff --git a/apps/web/app/components/library/versions.tsx b/apps/web/app/components/library/versions.tsx index 677221d6..38ed06e8 100644 --- a/apps/web/app/components/library/versions.tsx +++ b/apps/web/app/components/library/versions.tsx @@ -254,7 +254,9 @@ export function EpisodeVersionList({ v={lead} aside={ versions.length > 1 ? ( - {versions.length} 个版本 + + 库里有 {versions.length} 个版本 + ) : null } /> diff --git a/apps/web/app/components/workbench/release-detail-sheet.tsx b/apps/web/app/components/workbench/release-detail-sheet.tsx index cb55b918..3d555fd6 100644 --- a/apps/web/app/components/workbench/release-detail-sheet.tsx +++ b/apps/web/app/components/workbench/release-detail-sheet.tsx @@ -13,6 +13,7 @@ import { KvList } from "~/components/console/kv-list.tsx"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { Button } from "~/components/ui/button.tsx"; import { errorMessage } from "~/lib/api.ts"; +import { useUiPrefs } from "~/lib/ui-prefs.ts"; import { formatBytes, formatDateTime } from "~/lib/utils.ts"; import { DownloadDialog } from "./download-dialog.tsx"; import { absTime, categoryLabel, countText, ORIGIN_LABEL, relTime, snatchedOf } from "./release-fields.ts"; @@ -28,13 +29,21 @@ const DECISION_LABEL = { deferred: { label: "延后:已有计划在途", tone: "warning" }, } as const; +/** 没关联作品时按识别状态说一句(原码不上屏) */ +const UNLINKED_TEXT: Record = { + unresolved: "还没识别作品。", + ambiguous: "有多个可能的作品,没有自动关联。", + unidentifiable: "没识别出作品。", + rejected: "识别结果已被排除。", +}; + /** 本地库存 + 候选理由(来自作品详情:Slot / CandidateEvaluation),不从标题猜 */ function LocalInventory({ release, entry }: { release: WorkbenchRelease; entry: InventoryEntry | undefined }) { const detail = useEntityDetail(release.entity?.id); if (!release.entity) { return (

- 未关联本地作品({release.resolutionStatus})。 + {UNLINKED_TEXT[release.resolutionStatus] ?? "未关联本地作品。"} 去指定作品 @@ -123,6 +132,7 @@ export function ReleaseDetailSheet({ const refresh = useRefreshReleaseDetail(); const entity = useEntityDetail(release?.entity?.id); const [dl, setDl] = useState(false); + const techOpen = useUiPrefs().techInfoExpanded; const r = detail.data?.release ?? release; // 按需抓详情页:打开抽屉时没有缓存的详情页扩展内容就抓一次(服务端缓存 24 小时,同一资源本次会话只自动抓一次) @@ -296,7 +306,8 @@ export function ReleaseDetailSheet({ ) : null} - + {/* 原始解析 JSON 属于技术信息:默认收起,跟随设置 → 界面「技术信息默认展开」 */} + )} {r ? : null} diff --git a/apps/web/app/routes/console/library.$id.tsx b/apps/web/app/routes/console/library.$id.tsx index e974fd00..256974cc 100644 --- a/apps/web/app/routes/console/library.$id.tsx +++ b/apps/web/app/routes/console/library.$id.tsx @@ -513,8 +513,8 @@ function SlotsPanel({ detail, units }: { detail: LibraryDetail; units: ContentUn 去订阅