- 新增 AttendanceRecord 与 AttendancePolicy 模型 - 上下班打卡、今日状态、月度统计端点 - 考勤管理页面(我的/团队/设置三 Tab) - 移动端快速打卡入口 - 管理员补卡与审计日志 - 单测覆盖打卡幂等、迟到计算、工时计算、权限边界
524 行
15 KiB
TypeScript
524 行
15 KiB
TypeScript
import { Suspense, type ReactNode } from "react";
|
|
import { Navigate, Route, Routes } from "react-router-dom";
|
|
import { RoutePageFallback } from "../components/common/RoutePageFallback";
|
|
import { AppShellLayout } from "../layouts/AppShellLayout";
|
|
import { AuthLayout } from "../layouts/AuthLayout";
|
|
import { BudgetsPage } from "../pages/BudgetsPage";
|
|
import { ChangePasswordPage } from "../pages/ChangePasswordPage";
|
|
import { DashboardPage } from "../pages/DashboardPage";
|
|
import { ForbiddenPage } from "../pages/ForbiddenPage";
|
|
import { LoginPage } from "../pages/LoginPage";
|
|
import { NotFoundPage } from "../pages/NotFoundPage";
|
|
import { ProfilePage } from "../pages/ProfilePage";
|
|
import {
|
|
ApOperationsPage,
|
|
ApprovalRulesPage,
|
|
AnnouncementsPage,
|
|
AttendancePage,
|
|
AssetRegistryPage,
|
|
AuditLogsPage,
|
|
CutoverConsolePage,
|
|
EmployeeProfilePage,
|
|
DelegationsPage,
|
|
DepartmentsPage,
|
|
ErpIntegrationPage,
|
|
ExpenseStandardsPage,
|
|
FeatureFlagsPage,
|
|
InventoryChecksPage,
|
|
InvoicePoolPage,
|
|
LeaveRequestPage,
|
|
MasterDataPage,
|
|
MobileApprovalsPage,
|
|
MobileAssetScanPage,
|
|
MobileCapturePage,
|
|
MobileMePage,
|
|
MobilePortalPage,
|
|
NotificationChannelsPage,
|
|
NotificationsPage,
|
|
OrganizationPage,
|
|
PayrollPage,
|
|
PerformancePage,
|
|
PaymentBatchesPage,
|
|
PeoplePage,
|
|
PeopleStatsPage,
|
|
PortalAnnouncementsPage,
|
|
PortalHomePage,
|
|
ProjectsPage,
|
|
ProjectDetailPage,
|
|
ContractsRegistryPage,
|
|
SystemSettingsPage,
|
|
TeamPresencePage,
|
|
TodosPage,
|
|
PurchaseRequestsPage,
|
|
ReimbursementDetailPage,
|
|
ReimbursementDashboardPage,
|
|
ReimbursementPrintPage,
|
|
ReimbursementTemplatesPage,
|
|
ReimbursementsPage,
|
|
TravelRequestsPage,
|
|
UserManagementPage,
|
|
WeeklyReportsPage,
|
|
WorkspaceSetupPage,
|
|
WorkflowDefinitionsPage,
|
|
WorkflowTaskDetailPage,
|
|
WorkflowTasksPage,
|
|
} from "./lazy";
|
|
import { GuestOnly, RequireAuth, RequirePermission } from "./guards";
|
|
|
|
function withRouteFallback(node: ReactNode) {
|
|
return <Suspense fallback={<RoutePageFallback />}>{node}</Suspense>;
|
|
}
|
|
|
|
export function AppRouter() {
|
|
return (
|
|
<Routes>
|
|
<Route
|
|
path="/login"
|
|
element={
|
|
<GuestOnly>
|
|
<AuthLayout />
|
|
</GuestOnly>
|
|
}
|
|
>
|
|
<Route index element={<LoginPage />} />
|
|
</Route>
|
|
|
|
<Route
|
|
path="/change-password"
|
|
element={
|
|
<RequireAuth>
|
|
<AuthLayout />
|
|
</RequireAuth>
|
|
}
|
|
>
|
|
<Route index element={<ChangePasswordPage />} />
|
|
</Route>
|
|
|
|
<Route
|
|
path="/"
|
|
element={
|
|
<RequireAuth>
|
|
<AppShellLayout />
|
|
</RequireAuth>
|
|
}
|
|
>
|
|
<Route index element={<Navigate to="/portal" replace />} />
|
|
<Route path="portal" element={withRouteFallback(<PortalHomePage />)} />
|
|
<Route path="announcements" element={withRouteFallback(<AnnouncementsPage />)} />
|
|
<Route path="team" element={withRouteFallback(<TeamPresencePage />)} />
|
|
<Route
|
|
path="projects"
|
|
element={
|
|
<RequirePermission anyOf={["project.view_own", "project.view_all", "project.manage", "system.settings"]}>
|
|
{withRouteFallback(<ProjectsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="projects/:projectId"
|
|
element={
|
|
<RequirePermission anyOf={["project.view_own", "project.view_all", "project.manage", "system.settings"]}>
|
|
{withRouteFallback(<ProjectDetailPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="contracts-registry"
|
|
element={
|
|
<RequirePermission anyOf={["project.view_own", "project.view_all", "project.manage", "system.settings"]}>
|
|
{withRouteFallback(<ContractsRegistryPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route path="weekly-reports" element={withRouteFallback(<WeeklyReportsPage />)} />
|
|
<Route path="todos" element={withRouteFallback(<TodosPage />)} />
|
|
<Route path="dashboard" element={<DashboardPage />} />
|
|
|
|
<Route
|
|
path="budgets"
|
|
element={
|
|
<RequirePermission anyOf={["budget.view", "budget.manage"]}>
|
|
<BudgetsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="attendance"
|
|
element={withRouteFallback(<AttendancePage />)}
|
|
/>
|
|
|
|
<Route
|
|
path="reimbursements"
|
|
element={
|
|
<RequirePermission anyOf={["expense.view_own", "expense.view_all"]}>
|
|
{withRouteFallback(<ReimbursementsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="reimbursements/dashboard"
|
|
element={
|
|
<RequirePermission anyOf={["expense.view_all"]}>
|
|
{withRouteFallback(<ReimbursementDashboardPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/reimbursement-templates"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings"]}>
|
|
{withRouteFallback(<ReimbursementTemplatesPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="reimbursements/:reimbursementId"
|
|
element={
|
|
<RequirePermission anyOf={["expense.view_own", "expense.view_all"]}>
|
|
{withRouteFallback(<ReimbursementDetailPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="reimbursements/:reimbursementId/print"
|
|
element={
|
|
<RequirePermission anyOf={["expense.view_own", "expense.view_all"]}>
|
|
{withRouteFallback(<ReimbursementPrintPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="leave-requests"
|
|
element={
|
|
<RequirePermission anyOf={["leave.view_own", "leave.view_all"]}>
|
|
{withRouteFallback(<LeaveRequestPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="purchase-requests"
|
|
element={
|
|
<RequirePermission
|
|
anyOf={["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"]}
|
|
>
|
|
{withRouteFallback(<PurchaseRequestsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="travel-requests"
|
|
element={
|
|
<RequirePermission
|
|
anyOf={[
|
|
"travel.create",
|
|
"travel.view_own",
|
|
"travel.view_all",
|
|
"travel.manage",
|
|
"travel.payment.manage",
|
|
"integration.manage",
|
|
]}
|
|
>
|
|
{withRouteFallback(<TravelRequestsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="payment-batches"
|
|
element={
|
|
<RequirePermission anyOf={["payment.batch.manage", "risk.review.manage"]}>
|
|
{withRouteFallback(<PaymentBatchesPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="ap-operations"
|
|
element={
|
|
<RequirePermission anyOf={["payment.batch.manage", "risk.review.manage"]}>
|
|
{withRouteFallback(<ApOperationsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="assets"
|
|
element={
|
|
<RequirePermission
|
|
anyOf={["asset.view_own", "asset.view_all", "asset.manage", "asset.transfer", "asset.inventory"]}
|
|
>
|
|
{withRouteFallback(<AssetRegistryPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="inventory-checks"
|
|
element={
|
|
<RequirePermission anyOf={["asset.inventory"]}>
|
|
{withRouteFallback(<InventoryChecksPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="workflow/tasks"
|
|
element={
|
|
<RequirePermission anyOf={["expense.approve", "leave.approve", "procurement.approve", "travel.approve"]}>
|
|
{withRouteFallback(<WorkflowTasksPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="workflow/tasks/:taskId"
|
|
element={
|
|
<RequirePermission anyOf={["expense.approve", "leave.approve", "procurement.approve", "travel.approve"]}>
|
|
{withRouteFallback(<WorkflowTaskDetailPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="invoice-pool"
|
|
element={
|
|
<RequirePermission anyOf={["invoice.upload", "invoice.view_own", "invoice.manage"]}>
|
|
{withRouteFallback(<InvoicePoolPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route path="mobile" element={withRouteFallback(<MobilePortalPage />)} />
|
|
|
|
<Route path="mobile/me" element={withRouteFallback(<MobileMePage />)} />
|
|
|
|
<Route
|
|
path="mobile/capture"
|
|
element={
|
|
<RequirePermission anyOf={["invoice.upload", "invoice.manage"]}>
|
|
{withRouteFallback(<MobileCapturePage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="mobile/assets/scan"
|
|
element={
|
|
<RequirePermission
|
|
anyOf={["asset.view_own", "asset.view_all", "asset.manage", "asset.transfer", "asset.inventory"]}
|
|
>
|
|
{withRouteFallback(<MobileAssetScanPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="mobile/approvals"
|
|
element={
|
|
<RequirePermission anyOf={["expense.approve", "leave.approve", "procurement.approve", "travel.approve"]}>
|
|
{withRouteFallback(<MobileApprovalsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route path="notifications" element={withRouteFallback(<NotificationsPage />)} />
|
|
|
|
<Route
|
|
path="audit-logs"
|
|
element={
|
|
<RequirePermission anyOf={["system.audit.view"]}>
|
|
{withRouteFallback(<AuditLogsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="delegations"
|
|
element={
|
|
<RequirePermission anyOf={["expense.proxy_manage"]}>
|
|
{withRouteFallback(<DelegationsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="payroll"
|
|
element={
|
|
<RequirePermission anyOf={["system.users"]}>
|
|
{withRouteFallback(<PayrollPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="performance"
|
|
element={
|
|
<RequirePermission anyOf={["system.users"]}>
|
|
{withRouteFallback(<PerformancePage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="organization"
|
|
element={
|
|
<RequirePermission anyOf={["system.users", "report.dashboard.view"]}>
|
|
{withRouteFallback(<OrganizationPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="people"
|
|
element={
|
|
<RequirePermission anyOf={["system.users", "report.dashboard.view"]}>
|
|
{withRouteFallback(<PeoplePage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="people/stats"
|
|
element={
|
|
<RequirePermission anyOf={["system.users", "report.dashboard.view"]}>
|
|
{withRouteFallback(<PeopleStatsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route path="people/:employeeId" element={withRouteFallback(<EmployeeProfilePage />)} />
|
|
|
|
<Route
|
|
path="system/users"
|
|
element={
|
|
<RequirePermission anyOf={["system.users"]}>
|
|
{withRouteFallback(<UserManagementPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/departments"
|
|
element={
|
|
<RequirePermission anyOf={["system.users"]}>
|
|
{withRouteFallback(<DepartmentsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/master-data"
|
|
element={
|
|
<RequirePermission anyOf={["masterdata.view", "masterdata.manage"]}>
|
|
{withRouteFallback(<MasterDataPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/notification-channels"
|
|
element={
|
|
<RequirePermission anyOf={["notification.channel.manage"]}>
|
|
{withRouteFallback(<NotificationChannelsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/integrations/erp"
|
|
element={
|
|
<RequirePermission anyOf={["integration.manage"]}>
|
|
{withRouteFallback(<ErpIntegrationPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/workspace"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings"]}>
|
|
{withRouteFallback(<WorkspaceSetupPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/settings"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings", "system.users", "masterdata.view", "masterdata.manage", "notification.channel.manage", "integration.manage"]}>
|
|
{withRouteFallback(<SystemSettingsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/approval-rules"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings"]}>
|
|
{withRouteFallback(<ApprovalRulesPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/expense-standards"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings"]}>
|
|
{withRouteFallback(<ExpenseStandardsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/feature-flags"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings"]}>
|
|
{withRouteFallback(<FeatureFlagsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/announcements"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings"]}>
|
|
{withRouteFallback(<PortalAnnouncementsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/workflow-definitions"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings"]}>
|
|
{withRouteFallback(<WorkflowDefinitionsPage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="system/cutover"
|
|
element={
|
|
<RequirePermission anyOf={["system.settings"]}>
|
|
{withRouteFallback(<CutoverConsolePage />)}
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route path="settings/profile" element={<ProfilePage />} />
|
|
</Route>
|
|
|
|
<Route path="/403" element={<ForbiddenPage />} />
|
|
<Route path="*" element={<NotFoundPage />} />
|
|
</Routes>
|
|
);
|
|
}
|