Merge branch 'fe-settings'
This commit is contained in:
commit
161e6e732a
13 files changed
+254
-111
No files matched your search
@@ -178,7 +178,7 @@ function ConsoleShell({ session }: { session: SessionInfo }) {
|
||||
const activeLabel = pageTitle(location.pathname) ?? PORTAL_NAME;
|
||||
|
||||
return (
|
||||
<div className="app-scrollbars relative isolate flex min-h-screen overflow-x-hidden bg-background">
|
||||
<div className="app-scrollbars relative isolate flex min-h-screen overflow-x-clip bg-background">
|
||||
<div className="pointer-events-none fixed inset-0 -z-10 bg-page-glow" />
|
||||
<PrismLeak className="-z-10" />
|
||||
|
||||
@@ -245,7 +245,7 @@ function ConsoleShell({ session }: { session: SessionInfo }) {
|
||||
</header>
|
||||
|
||||
{/* 底部留出悬浮按钮的高度,页面末尾的内容能滚到它上面 */}
|
||||
<main className="relative flex-1 overflow-x-hidden px-4 pt-6 pb-24 md:px-6 md:pt-8">
|
||||
<main className="relative flex-1 overflow-x-clip px-4 pt-6 pb-24 md:px-6 md:pt-8">
|
||||
<div key={location.pathname} className="mx-auto max-w-6xl motion-safe:animate-blur-in">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -25,21 +25,37 @@ export default function SettingsPage() {
|
||||
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;
|
||||
}
|
||||
let tries = 0;
|
||||
const go = () => {
|
||||
const el = document.getElementById(anchorId(section.id, anchor));
|
||||
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
else if (tries++ < 10) timer = window.setTimeout(go, 150);
|
||||
const started = performance.now();
|
||||
let stopped = false;
|
||||
let timer = 0;
|
||||
const stop = () => {
|
||||
stopped = true;
|
||||
};
|
||||
let timer = window.setTimeout(go, 0);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [section.id, anchor]);
|
||||
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 (
|
||||
<>
|
||||
@@ -65,7 +81,7 @@ export default function SettingsPage() {
|
||||
|
||||
{/* 宽屏:左侧分区导航 */}
|
||||
<nav aria-label="设置分区" className="hidden lg:block">
|
||||
<ul className="sticky top-20 grid gap-0.5">
|
||||
<ul className="sticky top-20 grid max-h-[calc(100dvh-6rem)] gap-0.5 overflow-y-auto pb-2">
|
||||
{SECTIONS.map((s) => {
|
||||
const active = s.id === section.id;
|
||||
return (
|
||||
@@ -107,11 +123,14 @@ export default function SettingsPage() {
|
||||
</nav>
|
||||
|
||||
<div className="min-w-0">
|
||||
<div className="mb-6 hidden lg:block">
|
||||
<h2 className="flex items-center gap-2 text-title font-semibold text-foreground">{section.label}</h2>
|
||||
<p className="mt-0.5 text-caption text-muted-foreground">{section.description}</p>
|
||||
{/* 分区页头:标题 + 一句说明;窄屏由上方下拉承担,标题只留给读屏 */}
|
||||
<div className="lg:mb-8">
|
||||
<h2 className="sr-only text-title font-semibold text-foreground lg:not-sr-only">{section.label}</h2>
|
||||
<p className="mt-1 hidden text-caption text-muted-foreground lg:block">{section.description}</p>
|
||||
</div>
|
||||
<div key={section.id} className="grid grid-cols-1 gap-8">
|
||||
{section.render()}
|
||||
</div>
|
||||
<div key={section.id}>{section.render()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user