ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
89 lines
3.3 KiB
TypeScript
89 lines
3.3 KiB
TypeScript
"use client";
|
||
|
||
import { type ReactNode, useEffect, useState } from "react";
|
||
|
||
import { usePathname } from "next/navigation";
|
||
|
||
import { useTranslations } from "next-intl";
|
||
|
||
import { Separator } from "@/components/ui/separator";
|
||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||
import { useCurrentUser } from "@/hooks/use-current-user";
|
||
import { api } from "@/lib/api";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
import { AccountSwitcher } from "./sidebar/account-switcher";
|
||
import { LayoutControls } from "./sidebar/layout-controls";
|
||
import { SearchDialog } from "./sidebar/search-dialog";
|
||
import { ThemeSwitcher } from "./sidebar/theme-switcher";
|
||
import { UpdateBadge } from "./update-badge";
|
||
|
||
// 任务详情页保持原样:它自带头部/Tabs 与内边距,这里不再叠加全局头部和 padding。
|
||
function isFullBleed(pathname: string) {
|
||
const p = (() => {
|
||
try {
|
||
return decodeURIComponent(pathname);
|
||
} catch {
|
||
return pathname;
|
||
}
|
||
})();
|
||
// 静态导出开了 trailingSlash,列表页自身的 pathname 是 "/function/tasks/",
|
||
// 先去掉尾斜杠再判前缀,否则列表页会被误判成详情页而丢掉全局头部。
|
||
const normalized = p.replace(/\/+$/, "");
|
||
return normalized.startsWith("/function/tasks/");
|
||
}
|
||
|
||
export function MainContent({ children }: { children: ReactNode }) {
|
||
const currentUser = useCurrentUser();
|
||
const pathname = usePathname();
|
||
const t = useTranslations("header");
|
||
const [version, setVersion] = useState("");
|
||
|
||
useEffect(() => {
|
||
api
|
||
.health()
|
||
.then((h) => setVersion((h.version ?? "").replace(/^v(?=\d)/, "")))
|
||
.catch(() => setVersion(""));
|
||
}, []);
|
||
|
||
if (isFullBleed(pathname)) {
|
||
return <>{children}</>;
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<header
|
||
className={cn(
|
||
"flex h-12 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12",
|
||
"[html[data-navbar-style=sticky]_&]:sticky [html[data-navbar-style=sticky]_&]:top-0 [html[data-navbar-style=sticky]_&]:z-50 [html[data-navbar-style=sticky]_&]:overflow-hidden [html[data-navbar-style=sticky]_&]:rounded-t-[inherit] [html[data-navbar-style=sticky]_&]:bg-background/50 [html[data-navbar-style=sticky]_&]:backdrop-blur-md",
|
||
)}
|
||
>
|
||
<div className="flex w-full items-center justify-between px-4 lg:px-6">
|
||
<div className="flex items-center gap-1 lg:gap-2">
|
||
<SidebarTrigger className="-ml-1" />
|
||
<Separator
|
||
orientation="vertical"
|
||
className="mx-2 data-[orientation=vertical]:h-4 data-[orientation=vertical]:self-center"
|
||
/>
|
||
<SearchDialog />
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
{version && (
|
||
<span className="font-medium text-muted-foreground text-xs tabular-nums">
|
||
{t("version", { version })}
|
||
</span>
|
||
)}
|
||
<UpdateBadge />
|
||
<LayoutControls />
|
||
<ThemeSwitcher />
|
||
<AccountSwitcher users={[currentUser]} />
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div className="min-h-0 min-w-0 flex-1 overflow-x-hidden p-4 has-data-[content-padding=false]:p-0 md:p-6 md:has-data-[content-padding=false]:p-0">
|
||
{children}
|
||
</div>
|
||
</>
|
||
);
|
||
}
|