fix(web): make unavailable organize targets explicit

This commit is contained in:
root committed 2026-10-13 20:39:08 +08:00
1 parent 407cb7eb36
commit 1475ffcce4
2 files changed
+39 -8

No files matched your search

@@ -321,7 +321,19 @@ export function OrganizeWizard({ source, onClose }: { source: OrganizeSource; on
Number.isFinite(Number(minMb)) && Number.isFinite(Number(minMb)) &&
Number(minMb) >= 0 && Number(minMb) >= 0 &&
Number.isSafeInteger(Math.round(Number(minMb) * MIB)); Number.isSafeInteger(Math.round(Number(minMb) * MIB));
const targetUnavailable =
options.target.mode === "root" &&
rootsQ.isSuccess &&
!roots.some(
(r) =>
options.target.mode === "root" &&
r.id === options.target.rootId &&
r.kind === "library" &&
r.writable &&
r.health !== "offline",
);
const canPreview = const canPreview =
!targetUnavailable &&
(!needsReview || recoveryConfirmed) && (!needsReview || recoveryConfirmed) &&
!ambiguous && !ambiguous &&
!lookupPending && !lookupPending &&
@@ -548,6 +560,11 @@ export function OrganizeWizard({ source, onClose }: { source: OrganizeSource; on
批量重新整理使用本次统一设置。要沿用各记录的洗版与文件覆写,逐条打开重新整理。 批量重新整理使用本次统一设置。要沿用各记录的洗版与文件覆写,逐条打开重新整理。
</p> </p>
) : null} ) : null}
{targetUnavailable ? (
<p role="alert" className="text-caption text-warning">
原目标媒体库不可用,在高级选项中重新选择目标。
</p>
) : null}
{src.recovery ? ( {src.recovery ? (
<div className="space-y-2 text-caption"> <div className="space-y-2 text-caption">
<p className="text-muted-foreground"> <p className="text-muted-foreground">
@@ -580,7 +597,7 @@ export function OrganizeWizard({ source, onClose }: { source: OrganizeSource; on
</div> </div>
) : null} ) : null}
<details <details
open={advancedOpen || ambiguous || (needsReview && !recoveryConfirmed)} open={advancedOpen || ambiguous || targetUnavailable || (needsReview && !recoveryConfirmed)}
onToggle={(e) => setAdvancedOpen(e.currentTarget.open)} onToggle={(e) => setAdvancedOpen(e.currentTarget.open)}
className="rounded-xl border border-border p-3" className="rounded-xl border border-border p-3"
> >
@@ -769,6 +786,9 @@ export function OrganizeWizard({ source, onClose }: { source: OrganizeSource; on
value: "auto", value: "auto",
label: profile ? `自动 · ${profile.name}` : "自动 · 按作品类型与已有目录", label: profile ? `自动 · ${profile.name}` : "自动 · 按作品类型与已有目录",
}, },
...(targetUnavailable
? [{ value: targetValue, label: "原媒体库不可用 · 重新选择", disabled: true }]
: []),
...libRoots.map((r) => ({ value: r.id, label: r.name })), ...libRoots.map((r) => ({ value: r.id, label: r.name })),
{ value: "dir", label: "选择目录…" }, { value: "dir", label: "选择目录…" },
]} ]}
@@ -962,7 +982,19 @@ export function OrganizeWizard({ source, onClose }: { source: OrganizeSource; on
className="min-w-0 space-y-3 border-t border-border p-4 sm:p-5 lg:border-t-0 lg:border-l" className="min-w-0 space-y-3 border-t border-border p-4 sm:p-5 lg:border-t-0 lg:border-l"
aria-label="整理预览" aria-label="整理预览"
> >
<h3 className="text-caption font-medium">整理预览</h3> <div className="flex items-center justify-between gap-2">
<h3 className="text-caption font-medium">整理预览</h3>
<Button
variant="ghost"
size="sm"
onClick={() => {
setAdvancedOpen(true);
setPreviewOpen(false);
}}
>
修改整理选项
</Button>
</div>
{notice ? <p className="text-meta text-warning">{notice}</p> : null} {notice ? <p className="text-meta text-warning">{notice}</p> : null}
{dry.isPending ? ( {dry.isPending ? (
<p role="status" className="flex gap-2 text-caption text-muted-foreground"> <p role="status" className="flex gap-2 text-caption text-muted-foreground">
@@ -60,12 +60,11 @@ export function PreviewStatsBar({
}) { }) {
return ( return (
<div className="flex flex-wrap items-center gap-1.5"> <div className="flex flex-wrap items-center gap-1.5">
<StatusBadge> <span className="text-meta text-muted-foreground">
{stats.groups} 组 · {stats.files} 个文件 {stats.groups} 组 · {stats.files} 个文件 · 可执行 {stats.executable}
</StatusBadge> </span>
<StatusBadge tone={stats.executable ? "success" : "neutral"}>可执行 {stats.executable}</StatusBadge> {stats.conflicts ? <span className="text-meta text-warning">· 冲突 {stats.conflicts}</span> : null}
{stats.conflicts ? <StatusBadge tone="warning">冲突 {stats.conflicts}</StatusBadge> : null} {stats.failed ? <span className="text-meta text-destructive">· 不能执行 {stats.failed}</span> : null}
{stats.failed ? <StatusBadge tone="destructive">不能执行 {stats.failed}</StatusBadge> : null}
<div className="ml-auto flex items-center gap-2 text-caption text-muted-foreground"> <div className="ml-auto flex items-center gap-2 text-caption text-muted-foreground">
<span aria-hidden>只看问题</span> <span aria-hidden>只看问题</span>
<Switch checked={onlyProblems} onCheckedChange={onOnlyProblems} aria-label="只看问题" /> <Switch checked={onlyProblems} onCheckedChange={onOnlyProblems} aria-label="只看问题" />