fix(web): settings page layout pass (FE-7)

- Section header (h2 + description) and section blocks as h3; unified
  block padding and title size
- Global settings forms use label-left / control-right rows, stacked
  below md; control width fixed by type (number short, text/select
  medium, wide/textarea full)
- Per-block SaveBar pinned at the block end with dirty hint; naming
  template saves moved from the header to the bar
- Console shell uses overflow-x-clip so the sticky header and sticky
  section nav actually stick; anchor jumps re-align while async content
  loads and land below the header
- Section content uses a single minmax(0,1fr) column so wide tables
  scroll inside their card instead of widening the page
- useSettings fills missing keys from DEFAULT_SETTINGS so an older
  backend no longer crashes the metadata section

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 17:16:02 +08:00
1 parent 04cffcd1f1
commit 639fdeacae
13 files changed
+254 -111

No files matched your search

+24 -30
View File
@@ -12,7 +12,7 @@ import { Button } from "~/components/ui/button.tsx";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select.tsx";
import { Switch } from "~/components/ui/switch.tsx";
import { errorMessage } from "~/lib/api.ts";
import { SettingsBlock } from "./settings-block.tsx";
import { SaveBar, SettingsBlock } from "./settings-block.tsx";
// 设置 → 媒体库与目录 → 命名模板:全局模板 + 库根覆盖(作品覆盖在作品详情)。
@@ -61,19 +61,7 @@ function GlobalNaming() {
};
return (
<SettingsBlock
section="library"
anchor="naming"
title="命名模板"
description={NAMING_NOTE}
dirty={dirty}
actions={
<Button size="sm" onClick={onSave} disabled={!dirty || save.isPending || !draft}>
<Save />
{save.isPending ? "保存中…" : "保存"}
</Button>
}
>
<SettingsBlock section="library" anchor="naming" title="命名模板" description={NAMING_NOTE} dirty={dirty}>
{!q.data || !draft ? (
q.isError ? (
<ErrorState plain error={q.error} onRetry={() => q.refetch()} />
@@ -110,6 +98,12 @@ function GlobalNaming() {
aria-label="已有目录的作品固定文件名"
/>
</label>
<SaveBar dirty={dirty}>
<Button size="sm" onClick={onSave} disabled={!dirty || save.isPending || !draft}>
<Save />
{save.isPending ? "保存中…" : "保存"}
</Button>
</SaveBar>
</div>
)}
</SettingsBlock>
@@ -199,22 +193,6 @@ function RootNamingEditor({ root }: { root: LibraryRoot }) {
{NAMING_KIND_LABEL[k]}:{saved[k] ? "库根覆盖" : "沿用全局"}
</StatusBadge>
))}
<div className="ml-auto flex items-center gap-2">
<Button
variant="ghost"
size="sm"
className="hover:text-destructive"
disabled={!hasOverride || save.isPending}
onClick={() => put(null)}
>
<Trash2 />
清除覆盖
</Button>
<Button size="sm" disabled={!dirty || save.isPending} onClick={() => put(overrideBody(draft))}>
<Save />
{save.isPending ? "保存中…" : "保存覆盖"}
</Button>
</div>
</div>
<NamingTemplateEditor
kinds={KINDS}
@@ -229,6 +207,22 @@ function RootNamingEditor({ root }: { root: LibraryRoot }) {
saveErrors={errors}
dirtyKinds={dirtyKinds}
/>
<SaveBar dirty={dirty}>
<Button
variant="ghost"
size="sm"
className="hover:text-destructive"
disabled={!hasOverride || save.isPending}
onClick={() => put(null)}
>
<Trash2 />
清除覆盖
</Button>
<Button size="sm" disabled={!dirty || save.isPending} onClick={() => put(overrideBody(draft))}>
<Save />
{save.isPending ? "保存中…" : "保存覆盖"}
</Button>
</SaveBar>
</div>
);
}