3f39c56f4b
- 部署脚本 deploy.sh:一键部署到 154.8.162.18,端口3400,Nginx conf.d - 应用名改为「企业用工专家」(登录/注册/侧边栏/员工端等6处) - 移动端适配修复:Attendance/Dashboard/Settings/Contracts 6处grid响应式 - 帮助系统:HelpModal 组件 + TopNav 帮助图标 + RAG语义搜索集成 - 后端 rag.service.ts 新增23条帮助种子数据 + searchHelp() - 前端搜索调用 /ai/rag/help-search,RAG不可用时回退关键词匹配 - 网页图标 favicon.svg(indigo主色+建筑人形图案) - index.html 标题改为「企业用工专家」
102 lines
4.0 KiB
TypeScript
102 lines
4.0 KiB
TypeScript
import { Link, useNavigate } from 'react-router-dom'
|
|
import { ChevronDown, Settings as SettingsIcon, Bell, Menu, HelpCircle } from 'lucide-react'
|
|
import { useState } from 'react'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { useAuthStore } from '../../store/authStore'
|
|
import api from '../../lib/api'
|
|
import Breadcrumb from './Breadcrumb'
|
|
import HelpModal from '../HelpModal'
|
|
|
|
export default function TopNav({ onMenuClick }: { onMenuClick?: () => void }) {
|
|
const navigate = useNavigate()
|
|
const { user, logout } = useAuthStore()
|
|
const [menuOpen, setMenuOpen] = useState(false)
|
|
const [helpOpen, setHelpOpen] = useState(false)
|
|
|
|
const { data: dashboardData } = useQuery<any>({
|
|
queryKey: ['dashboard'],
|
|
queryFn: async () => {
|
|
const res = await api.get('/dashboard') as any
|
|
return res.data
|
|
},
|
|
refetchInterval: 60000,
|
|
})
|
|
const riskCount = dashboardData?.riskSummary?.pending || 0
|
|
|
|
return (
|
|
<header className="sticky top-0 z-30 bg-white border-b border-gray-200">
|
|
<div className="h-14 flex items-center justify-between px-4">
|
|
{/* 左侧:hamburger(移动端)+ 面包屑 */}
|
|
<div className="flex items-center gap-2 min-w-0">
|
|
<button
|
|
onClick={onMenuClick}
|
|
className="md:hidden p-1.5 rounded-md hover:bg-gray-100"
|
|
aria-label="打开菜单"
|
|
>
|
|
<Menu className="w-5 h-5 text-gray-600" />
|
|
</button>
|
|
<Breadcrumb />
|
|
</div>
|
|
|
|
{/* 右侧:帮助 + 通知 + 设置 + 用户菜单 */}
|
|
<div className="flex items-center gap-2 shrink-0">
|
|
<button
|
|
onClick={() => setHelpOpen(true)}
|
|
className="p-1.5 rounded-md hover:bg-gray-100"
|
|
aria-label="帮助"
|
|
>
|
|
<HelpCircle className="w-4 h-4 text-gray-600" />
|
|
</button>
|
|
<HelpModal open={helpOpen} onClose={() => setHelpOpen(false)} />
|
|
<Link to="/settings" className="p-1.5 rounded-md hover:bg-gray-100" aria-label="设置">
|
|
<SettingsIcon className="w-4 h-4 text-gray-600" />
|
|
</Link>
|
|
<Link to="/notifications" className="relative p-1.5 rounded-md hover:bg-gray-100" aria-label="通知">
|
|
<Bell className="w-4 h-4 text-gray-600" />
|
|
{riskCount > 0 && (
|
|
<span className="absolute -top-0.5 -right-0.5 min-w-4 h-4 px-1 bg-danger text-white text-xs rounded-full flex items-center justify-center">
|
|
{riskCount > 99 ? '99+' : riskCount}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
<div className="relative">
|
|
<button
|
|
onClick={() => setMenuOpen(!menuOpen)}
|
|
className="flex items-center gap-1 px-2 py-1.5 rounded-md hover:bg-gray-100"
|
|
aria-expanded={menuOpen}
|
|
aria-haspopup="menu"
|
|
aria-label="用户菜单"
|
|
>
|
|
<span className="text-sm text-gray-700 hidden sm:inline">{user?.name || '用户'}</span>
|
|
<ChevronDown className="w-4 h-4 text-gray-500" />
|
|
</button>
|
|
{menuOpen && (
|
|
<>
|
|
<button className="fixed inset-0 z-10 cursor-default" onClick={() => setMenuOpen(false)} aria-label="关闭菜单" />
|
|
<div className="absolute right-0 mt-1 w-40 bg-white rounded-md shadow-lg border border-gray-200 z-20">
|
|
<Link
|
|
to="/settings"
|
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
onClick={() => setMenuOpen(false)}
|
|
>
|
|
设置
|
|
</Link>
|
|
<button
|
|
onClick={() => {
|
|
logout()
|
|
navigate('/login')
|
|
}}
|
|
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
>
|
|
退出
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|