5a9d440339
1. documentTitle 中 contractType 枚举值转中文
(LABOR→劳务协议、FIXED→固定期限劳动合同等)
2. 签署日期解析改为本地时区构造(new Date(y,m-1,d,12)),
避免 new Date('2026-08-16') 被解析为 UTC 00:00 导致
本地 +8 显示为 08:00:00
3. 签署记录 Tab 增加数字角标显示总记录数
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
249 lines
15 KiB
TypeScript
249 lines
15 KiB
TypeScript
import { lazy, Suspense, useState, useEffect } from 'react'
|
|
import { Routes, Route, Navigate } from 'react-router-dom'
|
|
import { Toaster } from 'sonner'
|
|
import { useAuthStore } from './store/authStore'
|
|
import TopNav from './components/layout/TopNav'
|
|
import SidebarNav from './components/layout/SidebarNav'
|
|
import MobileTabBar from './components/layout/MobileTabBar'
|
|
import OnboardingGuide from './components/OnboardingGuide'
|
|
import PortalLayout from './components/layout/PortalLayout'
|
|
import PageContainer from './components/layout/PageContainer'
|
|
import { CommandPalette } from './components/ui/CommandPalette'
|
|
import { SkeletonPage } from './components/ui/Skeleton'
|
|
import ErrorBoundary from './components/ui/ErrorBoundary'
|
|
|
|
const Login = lazy(() => import('./pages/auth/Login'))
|
|
const Register = lazy(() => import('./pages/auth/Register'))
|
|
const ForgotPassword = lazy(() => import('./pages/auth/ForgotPassword'))
|
|
const Dashboard = lazy(() => import('./pages/Dashboard'))
|
|
const Money = lazy(() => import('./pages/Money'))
|
|
const SocialInsurance = lazy(() => import('./pages/SocialInsurance'))
|
|
const Roster = lazy(() => import('./pages/Roster'))
|
|
const OrgChart = lazy(() => import('./pages/OrgChart'))
|
|
const SupportDashboard = lazy(() => import('./pages/support/SupportDashboard'))
|
|
const Termination = lazy(() => import('./pages/Termination'))
|
|
const AIAssistant = lazy(() => import('./pages/AIAssistant'))
|
|
const Settings = lazy(() => import('./pages/Settings'))
|
|
const Evidence = lazy(() => import('./pages/Evidence'))
|
|
const Policies = lazy(() => import('./pages/Policies'))
|
|
const Attendance = lazy(() => import('./pages/Attendance'))
|
|
const Templates = lazy(() => import('./pages/Templates'))
|
|
const AuditLog = lazy(() => import('./pages/AuditLog'))
|
|
const Notifications = lazy(() => import('./pages/Notifications'))
|
|
const PortalLogin = lazy(() => import('./pages/portal/PortalLogin'))
|
|
const Payslip = lazy(() => import('./pages/portal/Payslip'))
|
|
const MyContract = lazy(() => import('./pages/portal/MyContract'))
|
|
const Onboarding = lazy(() => import('./pages/portal/Onboarding'))
|
|
const ContractConfirm = lazy(() => import('./pages/portal/ContractConfirm'))
|
|
const MyPolicies = lazy(() => import('./pages/portal/MyPolicies'))
|
|
const AutoLogin = lazy(() => import('./pages/portal/AutoLogin'))
|
|
const MedicalPeriodCalculator = lazy(() => import('./pages/tools/MedicalPeriodCalculator'))
|
|
const HealthCheck = lazy(() => import('./pages/tools/HealthCheck'))
|
|
const AnnualValueReport = lazy(() => import('./pages/tools/AnnualValueReport'))
|
|
const CalendarPage = lazy(() => import('./pages/Calendar'))
|
|
const MyAttendance = lazy(() => import('./pages/portal/MyAttendance'))
|
|
const MyLeave = lazy(() => import('./pages/portal/MyLeave'))
|
|
const SpecialStatus = lazy(() => import('./pages/SpecialStatus'))
|
|
const CompanyFiles = lazy(() => import('./pages/CompanyFiles'))
|
|
const LeaveApproval = lazy(() => import('./pages/LeaveApproval'))
|
|
const TrainingRecords = lazy(() => import('./pages/roster/TrainingRecords'))
|
|
const PerformanceRecords = lazy(() => import('./pages/roster/PerformanceRecords'))
|
|
const DisciplinaryRecords = lazy(() => import('./pages/roster/DisciplinaryRecords'))
|
|
|
|
// Sprint 4-5 新增页面
|
|
const EmployeeHome = lazy(() => import('./pages/portal/EmployeeHome'))
|
|
const OnboardingProgress = lazy(() => import('./pages/portal/OnboardingProgress'))
|
|
const ResignationApply = lazy(() => import('./pages/portal/ResignationApply'))
|
|
const MyEsign = lazy(() => import('./pages/portal/MyEsign'))
|
|
const MyRecords = lazy(() => import('./pages/portal/MyRecords'))
|
|
const RiskCenter = lazy(() => import('./pages/compliance/RiskCenter'))
|
|
const SalaryDashboard = lazy(() => import('./pages/SalaryDashboard'))
|
|
const CommercialInsurance = lazy(() => import('./pages/CommercialInsurance'))
|
|
const EmployeeBenefits = lazy(() => import('./pages/EmployeeBenefits'))
|
|
const ESign = lazy(() => import('./pages/ESign'))
|
|
const CommissionBonus = lazy(() => import('./pages/CommissionBonus'))
|
|
|
|
// 平台管理端
|
|
const PlatformLogin = lazy(() => import('./pages/platform/PlatformLogin'))
|
|
const PlatformDashboard = lazy(() => import('./pages/platform/PlatformDashboard'))
|
|
const PlatformOrgs = lazy(() => import('./pages/platform/PlatformOrgs'))
|
|
const PlatformUsers = lazy(() => import('./pages/platform/PlatformUsers'))
|
|
const PlatformSidebar = lazy(() => import('./components/layout/PlatformSidebar'))
|
|
|
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
|
if (!isAuthenticated) return <Navigate to="/login" replace />
|
|
return <>{children}</>
|
|
}
|
|
|
|
function PublicRoute({ children }: { children: React.ReactNode }) {
|
|
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
|
if (isAuthenticated) return <Navigate to="/" replace />
|
|
return <>{children}</>
|
|
}
|
|
|
|
function AdminLayout({ children }: { children: React.ReactNode }) {
|
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
return (
|
|
<div className="flex min-h-screen bg-[#f8f9fb]">
|
|
<SidebarNav mobileOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
|
<div className="flex-1 flex flex-col min-w-0">
|
|
<TopNav onMenuClick={() => setSidebarOpen(true)} />
|
|
<main className="flex-1 py-6 pb-20 md:pb-6">
|
|
<PageContainer>
|
|
<ErrorBoundary>
|
|
<Suspense fallback={<SkeletonPage />}>{children}</Suspense>
|
|
</ErrorBoundary>
|
|
</PageContainer>
|
|
</main>
|
|
<MobileTabBar />
|
|
</div>
|
|
<OnboardingGuide />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function PlatformRoute({ children }: { children: React.ReactNode }) {
|
|
const user = useAuthStore((s) => s.user)
|
|
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
|
if (!isAuthenticated || user?.role !== 'SUPER_ADMIN') return <Navigate to="/platform/login" replace />
|
|
return <>{children}</>
|
|
}
|
|
|
|
function PlatformLayout({ children }: { children: React.ReactNode }) {
|
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
return (
|
|
<div className="flex min-h-screen bg-[#f8f9fb]">
|
|
<PlatformSidebar mobileOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
|
<div className="flex-1 flex flex-col min-w-0">
|
|
<header className="h-14 bg-white border-b border-gray-200 flex items-center px-4 shrink-0">
|
|
<button
|
|
className="md:hidden mr-3 text-gray-400"
|
|
onClick={() => setSidebarOpen(true)}
|
|
aria-label="打开菜单"
|
|
>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
|
</button>
|
|
<span className="text-sm text-gray-500">平台管理后台</span>
|
|
<span className="ml-2 px-2 py-0.5 rounded text-[10px] font-bold bg-primary text-white">ADMIN</span>
|
|
</header>
|
|
<main className="flex-1 py-6 px-4 md:px-6">
|
|
<div className="max-w-7xl mx-auto">
|
|
<ErrorBoundary>
|
|
<Suspense fallback={<SkeletonPage />}>{children}</Suspense>
|
|
</ErrorBoundary>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function PortalLayoutWrapper({ children, showNav = true }: { children: React.ReactNode; showNav?: boolean }) {
|
|
if (!showNav) {
|
|
return (
|
|
<div className="min-h-screen bg-surface pt-safe pb-safe">
|
|
<main className="max-w-md mx-auto py-6 px-4">
|
|
<ErrorBoundary>
|
|
<Suspense fallback={<SkeletonPage />}>{children}</Suspense>
|
|
</ErrorBoundary>
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|
|
return (
|
|
<PortalLayout>
|
|
<ErrorBoundary>
|
|
<Suspense fallback={<SkeletonPage />}>{children}</Suspense>
|
|
</ErrorBoundary>
|
|
</PortalLayout>
|
|
)
|
|
}
|
|
|
|
export default function App() {
|
|
const [cmdOpen, setCmdOpen] = useState(false)
|
|
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
|
|
|
useEffect(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
e.preventDefault()
|
|
if (isAuthenticated) setCmdOpen(true)
|
|
}
|
|
}
|
|
window.addEventListener('keydown', handler)
|
|
return () => window.removeEventListener('keydown', handler)
|
|
}, [isAuthenticated])
|
|
|
|
return (
|
|
<>
|
|
<CommandPalette open={cmdOpen} onClose={() => setCmdOpen(false)} />
|
|
<Routes>
|
|
{/* 管理端认证页面 */}
|
|
<Route path="/login" element={<Suspense fallback={<SkeletonPage />}><PublicRoute><Login /></PublicRoute></Suspense>} />
|
|
<Route path="/register" element={<Suspense fallback={<SkeletonPage />}><PublicRoute><Register /></PublicRoute></Suspense>} />
|
|
<Route path="/forgot-password" element={<Suspense fallback={<SkeletonPage />}><PublicRoute><ForgotPassword /></PublicRoute></Suspense>} />
|
|
|
|
{/* 管理端业务页面 */}
|
|
<Route path="/" element={<ProtectedRoute><AdminLayout><Dashboard /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/roster" element={<ProtectedRoute><AdminLayout><Roster /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/org-chart" element={<ProtectedRoute><AdminLayout><OrgChart /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/support" element={<ProtectedRoute><AdminLayout><SupportDashboard /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/money" element={<ProtectedRoute><AdminLayout><Money /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/social" element={<ProtectedRoute><AdminLayout><SocialInsurance /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/termination" element={<ProtectedRoute><AdminLayout><Termination /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/ai-assistant" element={<ProtectedRoute><AdminLayout><AIAssistant /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/settings" element={<ProtectedRoute><AdminLayout><Settings /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/evidence" element={<ProtectedRoute><AdminLayout><Evidence /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/policies" element={<ProtectedRoute><AdminLayout><Policies /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/attendance" element={<ProtectedRoute><AdminLayout><Attendance /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/calendar" element={<ProtectedRoute><AdminLayout><CalendarPage /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/templates" element={<ProtectedRoute><AdminLayout><Templates /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/audit" element={<ProtectedRoute><AdminLayout><AuditLog /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/notifications" element={<ProtectedRoute><AdminLayout><Notifications /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/tools/medical-period" element={<ProtectedRoute><AdminLayout><MedicalPeriodCalculator /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/tools/health-check" element={<ProtectedRoute><AdminLayout><HealthCheck /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/tools/annual-value" element={<ProtectedRoute><AdminLayout><AnnualValueReport /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/special-status" element={<ProtectedRoute><AdminLayout><SpecialStatus /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/commission-bonus" element={<ProtectedRoute><AdminLayout><CommissionBonus /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/company-files" element={<ProtectedRoute><AdminLayout><CompanyFiles /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/leave-approval" element={<ProtectedRoute><AdminLayout><LeaveApproval /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/training-records" element={<ProtectedRoute><AdminLayout><TrainingRecords /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/performance-records" element={<ProtectedRoute><AdminLayout><PerformanceRecords /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/disciplinary-records" element={<ProtectedRoute><AdminLayout><DisciplinaryRecords /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/risk-center" element={<ProtectedRoute><AdminLayout><RiskCenter /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/salary-dashboard" element={<ProtectedRoute><AdminLayout><SalaryDashboard /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/commercial-insurance" element={<ProtectedRoute><AdminLayout><CommercialInsurance /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/benefits" element={<ProtectedRoute><AdminLayout><EmployeeBenefits /></AdminLayout></ProtectedRoute>} />
|
|
<Route path="/esign" element={<ProtectedRoute><AdminLayout><ESign /></AdminLayout></ProtectedRoute>} />
|
|
|
|
{/* 平台管理端 */}
|
|
<Route path="/platform/login" element={<Suspense fallback={<SkeletonPage />}><PlatformLogin /></Suspense>} />
|
|
<Route path="/platform/dashboard" element={<PlatformRoute><PlatformLayout><PlatformDashboard /></PlatformLayout></PlatformRoute>} />
|
|
<Route path="/platform/orgs" element={<PlatformRoute><PlatformLayout><PlatformOrgs /></PlatformLayout></PlatformRoute>} />
|
|
<Route path="/platform/users" element={<PlatformRoute><PlatformLayout><PlatformUsers /></PlatformLayout></PlatformRoute>} />
|
|
|
|
{/* 员工端 */}
|
|
<Route path="/portal/login" element={<PortalLayoutWrapper showNav={false}><PortalLogin /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/payslip" element={<PortalLayoutWrapper><Payslip /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/contract" element={<PortalLayoutWrapper><MyContract /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/onboarding" element={<PortalLayoutWrapper showNav={false}><Onboarding /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/contract-confirm" element={<PortalLayoutWrapper showNav={false}><ContractConfirm /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/policies" element={<PortalLayoutWrapper><MyPolicies /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/attendance" element={<PortalLayoutWrapper><MyAttendance /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/leave" element={<PortalLayoutWrapper><MyLeave /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/auto-login" element={<PortalLayoutWrapper showNav={false}><AutoLogin /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/home" element={<PortalLayoutWrapper><EmployeeHome /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/onboarding-progress" element={<PortalLayoutWrapper><OnboardingProgress /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/resignation" element={<PortalLayoutWrapper><ResignationApply /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/esign" element={<PortalLayoutWrapper><MyEsign /></PortalLayoutWrapper>} />
|
|
<Route path="/portal/records" element={<PortalLayoutWrapper><MyRecords /></PortalLayoutWrapper>} />
|
|
|
|
{/* 兜底 */}
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
<Toaster position="top-center" richColors closeButton />
|
|
</>
|
|
)
|
|
}
|