'use client';
import * as Collapsible from '@radix-ui/react-collapsible';
import Link from 'next/link';
import type { MenuNode } from '@/lib/api/types/menu';
import { NavTooltip } from '@/components/layout/nav-tooltip';
/** 布局固定「概览」,与 IAM nav 拼成同一棵树(不入库) */
export const LAYOUT_OVERVIEW_MENU_ID = '__layout_overview__';
export function isLayoutOverviewNode(node: MenuNode): boolean {
return node.id === LAYOUT_OVERVIEW_MENU_ID;
}
export const layoutOverviewMenuNode: MenuNode = {
id: LAYOUT_OVERVIEW_MENU_ID,
parent_id: '',
menu_name: '概览',
menu_type: 2,
path: '/dashboard',
icon: '📊',
};
/** 将布局「概览」与 IAM nav 合并为侧栏顶层列表,便于组好完整结构再一次性渲染 */
export function layoutNavRootsFromApi(items: MenuNode[]): MenuNode[] {
return [layoutOverviewMenuNode, ...items.filter((n) => n.menu_type !== 3)];
}
export function normalizeHref(node: MenuNode): string {
if (node.external_link) {
return node.external_link;
}
const p = node.path?.trim();
if (!p) {
return '#';
}
if (p.startsWith('http://') || p.startsWith('https://')) {
return p;
}
return p.startsWith('/') ? p : `/${p}`;
}
export function isActivePath(pathname: string, href: string): boolean {
if (!href || href === '#' || href.startsWith('http://') || href.startsWith('https://')) {
return false;
}
if (pathname === href) {
return true;
}
if (href !== '/' && pathname.startsWith(`${href}/`)) {
return true;
}
return false;
}
export function linkClass(active: boolean): string {
return [
'block rounded-md px-2 py-2 text-sm',
active
? 'bg-neutral-200 font-medium text-neutral-900'
: 'text-neutral-800 hover:bg-neutral-100',
].join(' ');
}
/** 经典树:目录可展开,叶子为链接(用于经典侧栏与图标模式右侧浮层) */
export function NavTreeItem({
node,
depth,
pathname,
onInternalNavigate,
onMenuNavigate,
/** 图标模式浮层:二级及以下项之间额外增加 2px(space-y-0.5) */
iconFlyout = false,
}: {
node: MenuNode;
depth: number;
pathname: string;
onInternalNavigate?: () => void;
onMenuNavigate?: (path: string, title: string) => void;
iconFlyout?: boolean;
}) {
if (node.menu_type === 3) {
return null;
}
const children = (node.children ?? []).filter((c) => c.menu_type !== 3);
const hasChildren = children.length > 0;
const pad = 10 + depth * 10;
const label = (
{node.icon ? (
{node.icon}
) : null}
{node.menu_name}
);
if (hasChildren) {
return (
菜单失败