refactor(web): 技术信息折叠铺到媒体库 / 审计 / 运行状态,清理正常态徽章与解释型页描述(DEAI-2)

- 媒体库详情:作品 UUID 进技术信息;「实际路径与库根」改「文件位置」,正常态(已核验、
  可访问、主位置)不加徽章;外部身份显示「TMDB 90228 · 你确认的 / 自动匹配」,修订号进悬停;
  元数据字段来源默认收起(「显示来源」,跟随界面设置);别名默认只列中英文与手动加的;
  「内容单元」→「季与集」,「临时单元」→「编号待确认」;侧栏与页标题统一「媒体库」
- 审计:动作列不再重复英文码,对象列显示中文类型 / 能力名,ID 只在详情;补 capability.denied
- 运行状态:进程、已下线实例、趋势图与最大表收进折叠区;图例同名序列加序号,不再拼接
- 站点卡:账号正常与登录方式不加徽章,健康不显示毫秒,限流移出卡片
- 文件管理:目录「正常」徽章与重复的剩余空间去掉;已登记 → 已入库,临时文件 → 未完成
- 规则:「修订」列改「最后修改」;不支持的关键字合成一行;页签改「默认规则 / 自动收新作品 / 已忽略」;
  默认规则说明去掉「带层」等算法描述
- 站点 Passkey 与登录用通行密钥区分命名;命令面板提示去重
- 删解释型页描述(审计、搜索、下载、下载计划、RSS、规则、媒体库、运行状态、站点、任务、计划、整理)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 20:58:38 +08:00
1 parent 5a717e414c
commit 57ce40168d
31 files changed
+253 -209

No files matched your search

+1 -1
View File
@@ -1049,7 +1049,7 @@ export function RefreshMetadataDialog({ items, onClose }: { items: FileEntry[];
{s.reason === "not_registered"
? "没有已入库文件"
: s.reason === "no_entity"
? "不属于任何已登记作品"
? "不属于媒体库里的作品"
: s.reason === "not_found"
? "作品不存在"
: s.reason}
+2 -8
View File
@@ -2,7 +2,7 @@ import { ChevronRight, Download, Folder, FolderOpen, HardDrive, LoaderCircle, Tr
import { useEffect, useState } from "react";
import { type FileRoot, useDirChildren } from "~/api/files.ts";
import { healthLabel } from "~/lib/labels.ts";
import { cn, formatBytes } from "~/lib/utils.ts";
import { cn } from "~/lib/utils.ts";
import { groupRoots, joinRel, segments } from "./model.ts";
type Loc = { root: string; path: string };
@@ -140,19 +140,13 @@ function RootNode({
{h.tone !== "neutral" ? <TriangleAlert className="h-3 w-3" /> : null}
{h.label}
</span>
) : (
<span className="inline-flex items-center gap-1 text-success">
<span aria-hidden className="h-1.5 w-1.5 rounded-full bg-success" />
正常
</span>
)}
) : null}
{root.pendingCount ? (
<span className="shrink-0 text-warning">
未识别 {root.pendingCount}
{root.pendingPartial ? "+" : ""}
</span>
) : null}
{root.usage ? <span className="truncate">剩余 {formatBytes(root.usage.freeBytes, 1)}</span> : null}
{!root.writable && root.kind === "library" ? <span>只读</span> : null}
</span>
</span>
@@ -123,7 +123,7 @@ export function FileDetail({
value:
e.type === "dir"
? e.dirSummary
? `${e.dirSummary.videoCount}${e.dirSummary.partial ? "+" : ""} 个视频 · ${e.dirSummary.registeredCount} 个已登记`
? `${e.dirSummary.videoCount}${e.dirSummary.partial ? "+" : ""} 个视频 · ${e.dirSummary.registeredCount} 个已入库`
: "—"
: formatBytes(e.size, 2),
},
+2 -2
View File
@@ -89,7 +89,7 @@ const base = {
describe("状态与禁用原因", () => {
it("状态徽标带文字,H&R 未满足为 warning,逃逸为 destructive", () => {
expect(entryStatusView({ ...base, status: "registered" })?.label).toBe("已登记");
expect(entryStatusView({ ...base, status: "registered" })?.label).toBe("已入库");
expect(
entryStatusView({
...base,
@@ -228,7 +228,7 @@ describe("v2 菜单与副行", () => {
type: "dir" as const,
dirSummary: { videoCount: 13, registeredCount: 0, partial: false },
})?.text,
).toBe("13 视频 · 0 已登记");
).toBe("13 个视频 · 未入库");
// 没有 parsedHint(服务端解析不出标题)时不显示副行,前端不自己解析
expect(entrySubline({ ...video, parsedHint: null })).toBeNull();
});
+10 -7
View File
@@ -19,14 +19,14 @@ export function entryStatusView(e: Pick<FileEntry, "status" | "download" | "esca
const s: FileStatus = e.status;
switch (s) {
case "temp":
return { label: "临时文件", tone: "neutral", hint: "下载器或整理过程中的未完成文件" };
return { label: "未完成", tone: "neutral", hint: "下载器或整理过程中的未完成文件" };
case "downloading":
return {
label: e.download?.progress != null ? `下载中 ${Math.floor(e.download.progress * 100)}%` : "下载中",
tone: "neutral",
};
case "registered":
return { label: "已登记", tone: "success" };
return { label: "已入库", tone: "success" };
case "seeding":
return e.download?.hnr === "unsatisfied"
? { label: "做种中·H&R 未满足", tone: "warning", hint: "H&R 满足前不能改名、删除或移动" }
@@ -42,9 +42,9 @@ export const FILTER_CHIPS = [
{ value: "", label: "全部" },
{ value: "video", label: "视频" },
{ value: "unidentified", label: "未识别" },
{ value: "registered", label: "已登记" },
{ value: "registered", label: "已入库" },
{ value: "downloading", label: "下载中" },
{ value: "temp", label: "临时文件" },
{ value: "temp", label: "未完成下载" },
] as const;
export const SORT_OPTIONS = [
@@ -81,7 +81,7 @@ export function opDisabledReason(op: FileOp, e: FileEntry, root: Pick<FileRoot,
case "rename":
if (!rootWritable) return "只读目录,不能改名";
if (seeding) return "做种中,H&R 满足前不能改名";
if (e.status === "temp") return "临时文件不能改名";
if (e.status === "temp") return "未完成的文件不能改名";
return null;
case "move":
if (!rootWritable) return "只读目录,不能移出。可以复制";
@@ -93,7 +93,7 @@ export function opDisabledReason(op: FileOp, e: FileEntry, root: Pick<FileRoot,
return null;
case "organize":
case "identify":
if (e.status === "temp") return "临时文件不能整理";
if (e.status === "temp") return "未完成的文件不能整理";
if (e.type === "file" && e.fileKind !== "video") return "只有视频或目录能整理";
return null;
case "scrape":
@@ -449,7 +449,10 @@ export function entrySubline(e: FileEntry): { text: string; tone: "link" | "hint
if (e.type === "dir" && e.dirSummary) {
const s = e.dirSummary;
if (!s.videoCount && !s.registeredCount) return null;
return { text: `${s.videoCount}${s.partial ? "+" : ""} 视频 · ${s.registeredCount} 已登记`, tone: "meta" };
return {
text: `${s.videoCount}${s.partial ? "+" : ""} 个视频${s.registeredCount ? ` · ${s.registeredCount} 个已入库` : s.videoCount ? " · 未入库" : ""}`,
tone: "meta",
};
}
if (e.parsedHint) return { text: e.parsedHint, tone: "hint" };
return null;