Files
TurboHR/frontend/src/components/layout/PortalLayout.tsx
T
selfrelease 456cd91a83 brand: 软件名称从"企业用工专家/TurboHR"统一改为"安职通"
- 登录/注册/忘记密码页标题
- 侧边栏和员工端布局默认名称
- 新手引导欢迎语
- 帮助中心问答
- 验收测试页面标题
- 后端 RAG 知识库
- 部署脚本日志输出

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-15 15:29:13 +08:00

86 lines
3.4 KiB
TypeScript

/**
* 员工端统一布局 — 顶部 Header(Logo + 员工名 + 退出)+ 底部固定 TabBar 导航
* 所有已登录员工端页面共享此布局
*/
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { DollarSign, FileText, ScrollText, LogOut, CalendarCheck, Home } from 'lucide-react'
import Logo from '../../components/ui/Logo'
const tabItems = [
{ path: '/portal/home', label: '首页', icon: Home },
{ path: '/portal/payslip', label: '工资条', icon: DollarSign },
{ path: '/portal/contract', label: '我的合同', icon: FileText },
{ path: '/portal/attendance', label: '我的考勤', icon: CalendarCheck },
{ path: '/portal/policies', label: '规章制度', icon: ScrollText },
]
export default function PortalLayout({ children }: { children: React.ReactNode }) {
const location = useLocation()
const navigate = useNavigate()
const employee = (() => {
try { return JSON.parse(localStorage.getItem('portalEmployee') || '{}') } catch { return {} }
})()
const handleLogout = () => {
localStorage.removeItem('portalToken')
localStorage.removeItem('portalEmployee')
navigate('/portal/login')
}
return (
<div className="min-h-screen bg-gray-50 flex flex-col">
{/* 顶部 Header — 安全区 padding 适配刘海屏 */}
<header className="sticky top-0 z-30 bg-white border-b border-gray-200 pt-safe">
<div className="max-w-md mx-auto h-14 flex items-center justify-between px-4">
<div className="flex items-center gap-2 min-w-0">
<Logo className="w-6 h-6 text-primary flex-shrink-0" />
<span className="text-sm font-bold text-gray-900 truncate"></span>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
{employee.name && (
<span className="text-xs text-gray-500 truncate max-w-[60px]">{employee.name}</span>
)}
<button
onClick={handleLogout}
className="flex items-center gap-1 text-xs text-gray-400 hover:text-gray-600 px-2 py-1.5 rounded-md hover:bg-gray-50"
>
<LogOut className="w-3.5 h-3.5" />
退
</button>
</div>
</div>
</header>
{/* 主内容区 */}
<main className="flex-1 max-w-md mx-auto w-full px-4 py-6 pb-28">
{children}
</main>
{/* 底部固定 TabBar — 安全区 padding 适配 home indicator */}
<nav className="fixed bottom-0 left-0 right-0 z-30 bg-white border-t border-gray-200 pb-safe">
<div className="max-w-md mx-auto flex items-center justify-around h-16">
{tabItems.map((item) => {
const Icon = item.icon
const active = location.pathname === item.path ||
(item.path === '/portal/payslip' && location.pathname.startsWith('/portal/payslip'))
return (
<Link
key={item.path}
to={item.path}
className={`flex flex-col items-center justify-center gap-0.5 flex-1 h-full transition-colors ${
active ? 'text-primary' : 'text-gray-400 hover:text-gray-600'
}`}
>
<Icon className={`w-5 h-5 ${active ? 'fill-primary/10' : ''}`} />
<span className={`text-xs ${active ? 'font-medium' : ''}`}>{item.label}</span>
</Link>
)
})}
</div>
</nav>
</div>
)
}