Files
smart-crop-ui/crop-x/src/components/layouts/Navbar.tsx

409 lines
13 KiB
TypeScript

'use client';
import { Book, Menu, Sunset, Trees, Zap } from "lucide-react";
import { Tractor, Map, Clipboard, Package, Brain, Droplets, Settings } from 'lucide-react';
import { MessageBell } from './components/MessageBell';
import { UserProfile } from './components/UserProfile';
import { ThemeToggle } from './ThemeToggle';
import { AuthProvider } from './components/auth/AuthContext';
import { useElementHeight } from '@/hooks/useElementHeight';
import { useViewHeight } from '@/hooks/useViewHeight';
import { usePathname } from 'next/navigation';
import { useRef, useEffect, useState } from 'react';
// 注释掉 Accordion 相关导入,因为不再需要二级菜单
// import {
// Accordion,
// AccordionContent,
// AccordionItem,
// AccordionTrigger,
// } from "@/components/ui/accordion";
import { useLayoutStore } from '@/stores/useLayoutStore';
import { Button } from "@/components/ui/button";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
interface MenuItem {
title: string;
url: string;
description?: string;
icon?: React.ReactNode;
items?: MenuItem[];
}
interface Navbar1Props {
logo?: {
url: string;
src: string;
alt: string;
title: string;
};
menu?: MenuItem[];
auth?: {
login: {
title: string;
url: string;
};
signup: {
title: string;
url: string;
};
};
}
const navbarData = {
logo: {
url: "/",
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/shadcnblockscom-icon.svg",
alt: "Crop-X Logo",
title: "智慧农业生产管理系统",
},
menu: [
{
title: "智能农机管理系统",
url: "/agricultural-machinery",
description: "农机档案、实时监控、精准作业管理",
icon: <Tractor className="size-5 shrink-0" />,
},
{
title: "地块信息管理系统",
url: "/land-information",
description: "地块档案、地图管理、空间分析",
icon: <Map className="size-5 shrink-0" />,
},
{
title: "农事操作管理系统",
url: "/farming-operation",
description: "农事计划、任务管理、操作执行",
icon: <Clipboard className="size-5 shrink-0" />,
},
{
title: "农业资产管理系统",
url: "/agricultural-asset",
description: "基础信息、采购管理、库存管理",
icon: <Package className="size-5 shrink-0" />,
},
{
title: "AI作物模型精准决策系统",
url: "/ai-crop-model",
description: "数据感知、模型应用、智能决策",
icon: <Brain className="size-5 shrink-0" />,
},
{
title: "水肥一体化控制系统",
url: "/water-fertilizer-control",
description: "水肥机管理、智能灌溉、配方管理",
icon: <Droplets className="size-5 shrink-0" />,
},
{
title: "中心配置管理系统",
url: "/central-config",
description: "租户管理、用户管理、系统监控",
icon: <Settings className="size-5 shrink-0" />,
},
{
title: "API 测试示例",
url: "/api-example",
description: "测试和展示 OpenAPI 客户端调用",
icon: <Brain className="size-5 shrink-0" />,
},
],
auth: {
login: { title: "登录", url: "/login" },
signup: { title: "注册", url: "/register" },
},
};
const Navbar1 = () => {
const logo = navbarData.logo
const menu = navbarData.menu
const auth = navbarData.auth
const pathname = usePathname()
const containerStyle = {
maxWidth:"100%",marginLeft:"0px",marginRight:"0px",paddingLeft:"1rem",paddingRight:"0rem"
}
// 检查当前路径是否匹配菜单项
const isMenuActive = (url: string) => {
// 精确匹配
if (pathname === url) return true;
// 检查是否是该菜单下的子路径
if (pathname.startsWith(url + '/')) return true;
return false;
}
// 使用自定义 Hook 计算高度
const { elementRef, updateHeight } = useElementHeight({
immediate: true, // 立即计算高度
onUpdate: (height: number) => {
// 更新 Zustand store 中的状态
const { setNavigatorHeight } = useLayoutStore.getState();
setNavigatorHeight(height);
}
});
// 监听页面高度变化
useViewHeight();
const handleMessageClick = () => {
// 处理消息点击事件,可以跳转到消息中心页面
console.log('Navigate to message center');
};
const handleProfileClick = () => {
// 处理个人中心点击事件
console.log('Navigate to profile page');
};
return (
<AuthProvider>
<section className="py-4" ref={elementRef}>
<div className="container" style = {containerStyle}>
{/* Desktop Menu */}
<nav className="hidden justify-between lg:flex">
<div className="flex items-center gap-6">
{/* Logo */}
<span className="flex items-center gap-2">
<div className="flex items-center gap-3 flex-shrink-0">
<div className="w-10 h-10 bg-green-600 rounded-lg flex items-center justify-center">
<Tractor className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-green-800"></h1>
<p className="text-xs text-muted-foreground">Smart Agriculture Management System</p>
</div>
</div>
</span>
<div className="flex items-center gap-1 overflow-x-auto flex-1 min-w-0" style={{ maxWidth: '70vw' }}>
<style jsx>{`
div::-webkit-scrollbar {
height: 6px;
}
div::-webkit-scrollbar-track {
background: transparent;
}
div::-webkit-scrollbar-thumb {
background-color: #d1d5db;
border-radius: 3px;
}
div::-webkit-scrollbar-thumb:hover {
background-color: #9ca3af;
}
div {
scrollbar-width: thin;
scrollbar-color: #d1d5db transparent;
}
.navigation-menu-container {
max-width: 70vw;
}
`}</style>
<NavigationMenu>
<NavigationMenuList className="flex gap-1 min-w-max">
{menu.map((item) => renderMenuItem(item, isMenuActive))}
</NavigationMenuList>
</NavigationMenu>
</div>
</div>
<div className="flex gap-2" style = {{alignItems:"center"}}>
<ThemeToggle />
<MessageBell onMessageClick={handleMessageClick} />
<UserProfile onProfileClick={handleProfileClick} />
</div>
</nav>
{/* Mobile Menu */}
<div className="block lg:hidden">
<div className="flex items-center justify-between">
{/* Logo */}
<a href={logo.url} className="flex items-center gap-2">
<img
src={logo.src}
className="max-h-8 dark:invert"
alt={logo.alt}
/>
</a>
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" size="icon">
<Menu className="size-4" />
</Button>
</SheetTrigger>
<SheetContent className="overflow-y-auto">
<SheetHeader>
<SheetTitle>
<a href={logo.url} className="flex items-center gap-2">
<img
src={logo.src}
className="max-h-8 dark:invert"
alt={logo.alt}
/>
</a>
</SheetTitle>
</SheetHeader>
<div className="flex flex-col gap-6 p-4">
{/* 简化移动端菜单,不再使用 Accordion */}
<div className="flex w-full flex-col gap-4">
{menu.map((item) => renderMobileMenuItem(item, isMenuActive))}
</div>
<div className="flex flex-col gap-3">
<div className="flex justify-center">
<ThemeToggle />
</div>
<div className="flex justify-center">
<MessageBell onMessageClick={handleMessageClick} />
</div>
<div className="flex justify-center">
<UserProfile onProfileClick={handleProfileClick} />
</div>
</div>
</div>
</SheetContent>
</Sheet>
</div>
</div>
</div>
</section>
</AuthProvider>
);
};
const renderMenuItem = (item: MenuItem, isMenuActive: (url: string) => boolean) => {
// 注释掉二级菜单相关代码,项目不需要二级菜单
// if (item.items) {
// return (
// <NavigationMenuItem key={item.title}>
// <NavigationMenuTrigger className="whitespace-nowrap">{item.title}</NavigationMenuTrigger>
// <NavigationMenuContent className="bg-popover text-popover-foreground">
// {item.items.map((subItem) => (
//
// <NavigationMenuLink asChild key={subItem.title} className="w-80">
// <SubMenuLink item={subItem} />
// </NavigationMenuLink>
// ))}
// </NavigationMenuContent>
// </NavigationMenuItem>
// );
// }
return (
<NavigationMenuItem key={item.title}>
<NavigationMenuLink
href={item.url}
data-menu-item="true"
data-menu-url={item.url}
className={`
inline-flex h-10 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium gap-2 whitespace-nowrap relative
${isMenuActive(item.url)
? 'bg-primary/10'
: 'bg-background hover:bg-muted hover:text-accent-foreground'
}
[&:not([data-active])]:text-foreground
`}
>
{item.icon && (
<span className={`
shrink-0
${isMenuActive(item.url)
? 'text-primary'
: 'text-muted-foreground'
}
hover:text-primary
[&.group-data-[state=open]]:text-muted-foreground
`}>
{item.icon}
</span>
)}
<div className="relative">
<span className={isMenuActive(item.url) ? 'text-primary' : ''}>
{item.title}
</span>
{/* 激活菜单项下方的横条 */}
{isMenuActive(item.url) && (
<div className="absolute -bottom-1 left-0 right-0 h-[0.5px] bg-primary"></div>
)}
</div>
</NavigationMenuLink>
</NavigationMenuItem>
);
};
const renderMobileMenuItem = (item: MenuItem, isMenuActive: (url: string) => boolean) => {
// 注释掉移动端二级菜单相关代码
// if (item.items) {
// return (
// <AccordionItem key={item.title} value={item.title} className="border-b-0">
// <AccordionTrigger className="text-md py-0 font-semibold hover:no-underline gap-2">
// {item.icon && <span className="shrink-0">{item.icon}</span>}
// {item.title}
// </AccordionTrigger>
// <AccordionContent className="mt-2">
// {item.items.map((subItem) => (
// <SubMenuLink key={subItem.title} item={subItem} />
// ))}
// </AccordionContent>
// </AccordionItem>
// );
// }
return (
<a
key={item.title}
href={item.url}
className={`
text-md font-semibold flex items-center gap-2 p-2 rounded-md transition-colors
${isMenuActive(item.url)
? 'bg-primary/10 text-primary'
: 'hover:bg-muted hover:text-accent-foreground'
}
`}
>
{item.icon && (
<span className={`
shrink-0
${isMenuActive(item.url) ? 'text-primary' : 'text-muted-foreground'}
`}>
{item.icon}
</span>
)}
<span className={isMenuActive(item.url) ? 'text-primary' : ''}>
{item.title}
</span>
</a>
);
};
// 注释掉 SubMenuLink 组件,因为不再需要二级菜单
// const SubMenuLink = ({ item }: { item: MenuItem }) => {
// return (
// <a
// className="hover:bg-muted hover:text-accent-foreground flex min-w-80 select-none flex-row gap-4 rounded-md p-3 leading-none no-underline outline-none transition-colors"
// href={item.url}
// >
// <div className="text-foreground">{item.icon}</div>
// <div>
// <div className="text-sm font-semibold">{item.title}</div>
// {item.description && (
// <p className="text-muted-foreground text-sm leading-snug">
// {item.description}
// </p>
// )}
// </div>
// </a>
// );
// };
export { Navbar1 };