import { settingsSectionForLegacyTab } from "@umt/contracts"; import { useEffect } from "react"; import { Link, redirect, useLocation, useNavigate } from "react-router"; import { PageHeader } from "~/components/console/page-header.tsx"; import { parseSettingsHash, SECTIONS, settingsHash } from "~/components/settings/sections.tsx"; import { anchorId } from "~/components/settings/settings-block.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select.tsx"; import { cn } from "~/lib/utils.ts"; export const meta = () => [{ title: "设置 · UHD 媒体自动化控制台" }]; /** 旧链接 `?tab=xxx` → `#分区[/节]`(console-ia.md §3.3) */ export function clientLoader({ request }: { request: Request }) { const url = new URL(request.url); const tab = url.searchParams.get("tab"); if (!tab) return null; const hit = settingsSectionForLegacyTab(tab); url.searchParams.delete("tab"); const rest = url.searchParams.toString(); return redirect(`${url.pathname}${rest ? `?${rest}` : ""}${hit ? settingsHash(hit.section, hit.anchor) : ""}`); } export default function SettingsPage() { const location = useLocation(); const navigate = useNavigate(); const { section, anchor } = parseSettingsHash(location.hash); // 切分区回到顶部;带节锚点时对齐到该节。节内容多是异步加载的(上游列表、能力清单), // 上方内容变高会把目标挤下去,所以在 2.5 秒内反复校正,直到用户自己滚动为止。 // biome-ignore lint/correctness/useExhaustiveDependencies: location.key 让重复点同一锚点也能再跳一次 useEffect(() => { if (!anchor) { window.scrollTo({ top: 0 }); return; } const started = performance.now(); let stopped = false; let timer = 0; const stop = () => { stopped = true; }; const events = ["wheel", "touchstart", "keydown", "pointerdown"] as const; for (const e of events) window.addEventListener(e, stop, { passive: true }); const align = () => { if (stopped) return; const el = document.getElementById(anchorId(section.id, anchor)); if (el) { const margin = Number.parseFloat(getComputedStyle(el).scrollMarginTop) || 0; if (Math.abs(el.getBoundingClientRect().top - margin) > 2) el.scrollIntoView({ block: "start" }); } if (performance.now() - started < 2500) timer = window.setTimeout(align, 120); }; timer = window.setTimeout(align, 0); return () => { window.clearTimeout(timer); for (const e of events) window.removeEventListener(e, stop); }; }, [section.id, anchor, location.key]); return ( <>
{/* 窄屏:顶部下拉 */}

{section.description}

{/* 宽屏:左侧分区导航 */}
{/* 分区页头:标题 + 一句说明;窄屏由上方下拉承担,标题只留给读屏 */}

{section.label}

{section.description}

{section.render()}
); }