16f22e6622
- 新建 api-services-raw.ts 导出原始 axios 方法供特殊端点使用 - 完成 api-services.ts 全领域覆盖(auth/employee/roster/dashboard/attendance/payroll/socialInsurance/commercialInsurance/termination/policies/evidence/audit/calendar/companyFiles/notifications/settings/ai/platform/portal/survey/search) - 迁移所有 47+ 页面文件:pages/、pages/roster/、pages/portal/、pages/platform/、pages/auth/、pages/dashboard/、pages/compliance/ - 移除所有直接 import api from '../../lib/api' 引用 - 修复 Termination.tsx / WorkProcess.tsx 中 string|null 类型错误 - 修复 SocialInsurance.tsx 中 api-services-raw delete 导入名 - 修复 PlatformLogin.tsx 变量遮蔽问题 - tsc --noEmit 零错误,vite build 成功
119 lines
4.8 KiB
TypeScript
119 lines
4.8 KiB
TypeScript
import { Link, useNavigate } from 'react-router-dom'
|
|
import { ChevronDown, Settings as SettingsIcon, Bell, Menu, HelpCircle, Smartphone, ClipboardList } from 'lucide-react'
|
|
import { useState } from 'react'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { useAuthStore } from '../../store/authStore'
|
|
import { dashboardApi } from '../../lib/api-services'
|
|
import Breadcrumb from './Breadcrumb'
|
|
import HelpModal from '../HelpModal'
|
|
import PortalQRModal from '../PortalQRModal'
|
|
import SurveyModal from '../SurveyModal'
|
|
|
|
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 [portalQROpen, setPortalQROpen] = useState(false)
|
|
const [surveyOpen, setSurveyOpen] = useState(false)
|
|
|
|
const { data: dashboardData } = useQuery<any>({
|
|
queryKey: ['dashboard'],
|
|
queryFn: () => dashboardApi.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={() => setPortalQROpen(true)}
|
|
className="p-1.5 rounded-md hover:bg-gray-100"
|
|
aria-label="员工端入口"
|
|
>
|
|
<Smartphone className="w-4 h-4 text-gray-600" />
|
|
</button>
|
|
<PortalQRModal open={portalQROpen} onClose={() => setPortalQROpen(false)} />
|
|
<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)} />
|
|
<button
|
|
onClick={() => setSurveyOpen(true)}
|
|
className="p-1.5 rounded-md hover:bg-gray-100"
|
|
aria-label="功能调查"
|
|
>
|
|
<ClipboardList className="w-4 h-4 text-gray-600" />
|
|
</button>
|
|
<SurveyModal open={surveyOpen} onClose={() => setSurveyOpen(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>
|
|
)
|
|
}
|