perf(P22PROD1-1,P22PROD1-2): 路由懒加载与Vite分包消除大包warning
这个提交包含在:
@@ -0,0 +1,12 @@
|
||||
import { Spinner } from "../ui/spinner";
|
||||
|
||||
export function RoutePageFallback() {
|
||||
return (
|
||||
<div className="flex min-h-[40vh] items-center justify-center">
|
||||
<div className="card-std flex items-center gap-3 px-4 py-3 text-sm text-zinc-500">
|
||||
<Spinner size="sm" />
|
||||
<span>页面加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+55
-47
@@ -1,38 +1,46 @@
|
||||
import { Suspense, type ReactNode } from "react";
|
||||
import { Navigate, Route, Routes } from "react-router-dom";
|
||||
import { AppShellLayout } from "../layouts/AppShellLayout";
|
||||
import { AuthLayout } from "../layouts/AuthLayout";
|
||||
import { AuditLogsPage } from "../pages/AuditLogsPage";
|
||||
import { ApprovalRulesPage } from "../pages/ApprovalRulesPage";
|
||||
import { FeatureFlagsPage } from "../pages/FeatureFlagsPage";
|
||||
import { ChangePasswordPage } from "../pages/ChangePasswordPage";
|
||||
import { BudgetsPage } from "../pages/BudgetsPage";
|
||||
import { ChangePasswordPage } from "../pages/ChangePasswordPage";
|
||||
import { DashboardPage } from "../pages/DashboardPage";
|
||||
import { DepartmentsPage } from "../pages/DepartmentsPage";
|
||||
import { MasterDataPage } from "../pages/MasterDataPage";
|
||||
import { DelegationsPage } from "../pages/DelegationsPage";
|
||||
import { ForbiddenPage } from "../pages/ForbiddenPage";
|
||||
import { MobileApprovalsPage } from "../pages/MobileApprovalsPage";
|
||||
import { MobileAssetScanPage } from "../pages/MobileAssetScanPage";
|
||||
import { MobileCapturePage } from "../pages/MobileCapturePage";
|
||||
import { InvoicePoolPage } from "../pages/InvoicePoolPage";
|
||||
import { LoginPage } from "../pages/LoginPage";
|
||||
import { LeaveRequestPage } from "../pages/LeaveRequestPage";
|
||||
import { NotificationsPage } from "../pages/NotificationsPage";
|
||||
import { NotificationChannelsPage } from "../pages/NotificationChannelsPage";
|
||||
import { ErpIntegrationPage } from "../pages/ErpIntegrationPage";
|
||||
import { PurchaseRequestsPage } from "../pages/PurchaseRequestsPage";
|
||||
import { TravelRequestsPage } from "../pages/TravelRequestsPage";
|
||||
import { AssetRegistryPage } from "../pages/AssetRegistryPage";
|
||||
import { InventoryChecksPage } from "../pages/InventoryChecksPage";
|
||||
import { NotFoundPage } from "../pages/NotFoundPage";
|
||||
import { ProfilePage } from "../pages/ProfilePage";
|
||||
import { ReimbursementDetailPage } from "../pages/ReimbursementDetailPage";
|
||||
import { ReimbursementsPage } from "../pages/ReimbursementsPage";
|
||||
import { UserManagementPage } from "../pages/UserManagementPage";
|
||||
import { WorkflowTaskDetailPage } from "../pages/WorkflowTaskDetailPage";
|
||||
import { WorkflowTasksPage } from "../pages/WorkflowTasksPage";
|
||||
import { RoutePageFallback } from "../components/common/RoutePageFallback";
|
||||
import {
|
||||
ApprovalRulesPage,
|
||||
AssetRegistryPage,
|
||||
AuditLogsPage,
|
||||
DelegationsPage,
|
||||
DepartmentsPage,
|
||||
ErpIntegrationPage,
|
||||
FeatureFlagsPage,
|
||||
InventoryChecksPage,
|
||||
InvoicePoolPage,
|
||||
LeaveRequestPage,
|
||||
MasterDataPage,
|
||||
MobileApprovalsPage,
|
||||
MobileAssetScanPage,
|
||||
MobileCapturePage,
|
||||
NotificationChannelsPage,
|
||||
NotificationsPage,
|
||||
PurchaseRequestsPage,
|
||||
ReimbursementDetailPage,
|
||||
ReimbursementsPage,
|
||||
TravelRequestsPage,
|
||||
UserManagementPage,
|
||||
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>
|
||||
@@ -82,7 +90,7 @@ export function AppRouter() {
|
||||
path="reimbursements"
|
||||
element={
|
||||
<RequirePermission anyOf={["expense.view_own", "expense.view_all"]}>
|
||||
<ReimbursementsPage />
|
||||
{withRouteFallback(<ReimbursementsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -91,7 +99,7 @@ export function AppRouter() {
|
||||
path="reimbursements/:reimbursementId"
|
||||
element={
|
||||
<RequirePermission anyOf={["expense.view_own", "expense.view_all"]}>
|
||||
<ReimbursementDetailPage />
|
||||
{withRouteFallback(<ReimbursementDetailPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -100,7 +108,7 @@ export function AppRouter() {
|
||||
path="leave-requests"
|
||||
element={
|
||||
<RequirePermission anyOf={["leave.view_own", "leave.view_all"]}>
|
||||
<LeaveRequestPage />
|
||||
{withRouteFallback(<LeaveRequestPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -109,7 +117,7 @@ export function AppRouter() {
|
||||
path="purchase-requests"
|
||||
element={
|
||||
<RequirePermission anyOf={["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"]}>
|
||||
<PurchaseRequestsPage />
|
||||
{withRouteFallback(<PurchaseRequestsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -118,7 +126,7 @@ export function AppRouter() {
|
||||
path="travel-requests"
|
||||
element={
|
||||
<RequirePermission anyOf={["travel.create", "travel.view_own", "travel.view_all", "travel.manage", "travel.payment.manage", "integration.manage"]}>
|
||||
<TravelRequestsPage />
|
||||
{withRouteFallback(<TravelRequestsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -127,7 +135,7 @@ export function AppRouter() {
|
||||
path="assets"
|
||||
element={
|
||||
<RequirePermission anyOf={["asset.view_own", "asset.view_all", "asset.manage", "asset.transfer", "asset.inventory"]}>
|
||||
<AssetRegistryPage />
|
||||
{withRouteFallback(<AssetRegistryPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -136,7 +144,7 @@ export function AppRouter() {
|
||||
path="inventory-checks"
|
||||
element={
|
||||
<RequirePermission anyOf={["asset.inventory"]}>
|
||||
<InventoryChecksPage />
|
||||
{withRouteFallback(<InventoryChecksPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -145,7 +153,7 @@ export function AppRouter() {
|
||||
path="workflow/tasks"
|
||||
element={
|
||||
<RequirePermission anyOf={["expense.approve", "leave.approve", "procurement.approve", "travel.approve"]}>
|
||||
<WorkflowTasksPage />
|
||||
{withRouteFallback(<WorkflowTasksPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -154,7 +162,7 @@ export function AppRouter() {
|
||||
path="workflow/tasks/:taskId"
|
||||
element={
|
||||
<RequirePermission anyOf={["expense.approve", "leave.approve", "procurement.approve", "travel.approve"]}>
|
||||
<WorkflowTaskDetailPage />
|
||||
{withRouteFallback(<WorkflowTaskDetailPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -163,7 +171,7 @@ export function AppRouter() {
|
||||
path="invoice-pool"
|
||||
element={
|
||||
<RequirePermission anyOf={["invoice.upload", "invoice.view_own", "invoice.manage"]}>
|
||||
<InvoicePoolPage />
|
||||
{withRouteFallback(<InvoicePoolPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -172,7 +180,7 @@ export function AppRouter() {
|
||||
path="mobile/capture"
|
||||
element={
|
||||
<RequirePermission anyOf={["invoice.upload", "invoice.manage"]}>
|
||||
<MobileCapturePage />
|
||||
{withRouteFallback(<MobileCapturePage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -181,7 +189,7 @@ export function AppRouter() {
|
||||
path="mobile/assets/scan"
|
||||
element={
|
||||
<RequirePermission anyOf={["asset.view_own", "asset.view_all", "asset.manage", "asset.transfer", "asset.inventory"]}>
|
||||
<MobileAssetScanPage />
|
||||
{withRouteFallback(<MobileAssetScanPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -190,18 +198,18 @@ export function AppRouter() {
|
||||
path="mobile/approvals"
|
||||
element={
|
||||
<RequirePermission anyOf={["expense.approve", "leave.approve", "procurement.approve", "travel.approve"]}>
|
||||
<MobileApprovalsPage />
|
||||
{withRouteFallback(<MobileApprovalsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="notifications" element={<NotificationsPage />} />
|
||||
<Route path="notifications" element={withRouteFallback(<NotificationsPage />)} />
|
||||
|
||||
<Route
|
||||
path="audit-logs"
|
||||
element={
|
||||
<RequirePermission anyOf={["system.audit.view"]}>
|
||||
<AuditLogsPage />
|
||||
{withRouteFallback(<AuditLogsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -210,7 +218,7 @@ export function AppRouter() {
|
||||
path="delegations"
|
||||
element={
|
||||
<RequirePermission anyOf={["expense.proxy_manage"]}>
|
||||
<DelegationsPage />
|
||||
{withRouteFallback(<DelegationsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -219,7 +227,7 @@ export function AppRouter() {
|
||||
path="system/users"
|
||||
element={
|
||||
<RequirePermission anyOf={["system.users"]}>
|
||||
<UserManagementPage />
|
||||
{withRouteFallback(<UserManagementPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -228,7 +236,7 @@ export function AppRouter() {
|
||||
path="system/departments"
|
||||
element={
|
||||
<RequirePermission anyOf={["system.users"]}>
|
||||
<DepartmentsPage />
|
||||
{withRouteFallback(<DepartmentsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -237,7 +245,7 @@ export function AppRouter() {
|
||||
path="system/master-data"
|
||||
element={
|
||||
<RequirePermission anyOf={["masterdata.view", "masterdata.manage"]}>
|
||||
<MasterDataPage />
|
||||
{withRouteFallback(<MasterDataPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -246,7 +254,7 @@ export function AppRouter() {
|
||||
path="system/notification-channels"
|
||||
element={
|
||||
<RequirePermission anyOf={["notification.channel.manage"]}>
|
||||
<NotificationChannelsPage />
|
||||
{withRouteFallback(<NotificationChannelsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -255,7 +263,7 @@ export function AppRouter() {
|
||||
path="system/integrations/erp"
|
||||
element={
|
||||
<RequirePermission anyOf={["integration.manage"]}>
|
||||
<ErpIntegrationPage />
|
||||
{withRouteFallback(<ErpIntegrationPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -264,7 +272,7 @@ export function AppRouter() {
|
||||
path="system/approval-rules"
|
||||
element={
|
||||
<RequirePermission anyOf={["system.settings"]}>
|
||||
<ApprovalRulesPage />
|
||||
{withRouteFallback(<ApprovalRulesPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
@@ -273,7 +281,7 @@ export function AppRouter() {
|
||||
path="system/feature-flags"
|
||||
element={
|
||||
<RequirePermission anyOf={["system.settings"]}>
|
||||
<FeatureFlagsPage />
|
||||
{withRouteFallback(<FeatureFlagsPage />)}
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { ApprovalRulesPage as default } from "../../pages/ApprovalRulesPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { AssetRegistryPage as default } from "../../pages/AssetRegistryPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { AuditLogsPage as default } from "../../pages/AuditLogsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { DelegationsPage as default } from "../../pages/DelegationsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { DepartmentsPage as default } from "../../pages/DepartmentsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { ErpIntegrationPage as default } from "../../pages/ErpIntegrationPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { FeatureFlagsPage as default } from "../../pages/FeatureFlagsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { InventoryChecksPage as default } from "../../pages/InventoryChecksPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { InvoicePoolPage as default } from "../../pages/InvoicePoolPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { LeaveRequestPage as default } from "../../pages/LeaveRequestPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { MasterDataPage as default } from "../../pages/MasterDataPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { MobileApprovalsPage as default } from "../../pages/MobileApprovalsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { MobileAssetScanPage as default } from "../../pages/MobileAssetScanPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { MobileCapturePage as default } from "../../pages/MobileCapturePage";
|
||||
@@ -0,0 +1 @@
|
||||
export { NotificationChannelsPage as default } from "../../pages/NotificationChannelsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { NotificationsPage as default } from "../../pages/NotificationsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { PurchaseRequestsPage as default } from "../../pages/PurchaseRequestsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { ReimbursementDetailPage as default } from "../../pages/ReimbursementDetailPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { ReimbursementsPage as default } from "../../pages/ReimbursementsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { TravelRequestsPage as default } from "../../pages/TravelRequestsPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { UserManagementPage as default } from "../../pages/UserManagementPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { WorkflowTaskDetailPage as default } from "../../pages/WorkflowTaskDetailPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { WorkflowTasksPage as default } from "../../pages/WorkflowTasksPage";
|
||||
@@ -0,0 +1,25 @@
|
||||
import { lazy } from "react";
|
||||
|
||||
export const AuditLogsPage = lazy(() => import("./AuditLogsPage"));
|
||||
export const ApprovalRulesPage = lazy(() => import("./ApprovalRulesPage"));
|
||||
export const AssetRegistryPage = lazy(() => import("./AssetRegistryPage"));
|
||||
export const DelegationsPage = lazy(() => import("./DelegationsPage"));
|
||||
export const DepartmentsPage = lazy(() => import("./DepartmentsPage"));
|
||||
export const ErpIntegrationPage = lazy(() => import("./ErpIntegrationPage"));
|
||||
export const FeatureFlagsPage = lazy(() => import("./FeatureFlagsPage"));
|
||||
export const InventoryChecksPage = lazy(() => import("./InventoryChecksPage"));
|
||||
export const InvoicePoolPage = lazy(() => import("./InvoicePoolPage"));
|
||||
export const LeaveRequestPage = lazy(() => import("./LeaveRequestPage"));
|
||||
export const MasterDataPage = lazy(() => import("./MasterDataPage"));
|
||||
export const MobileApprovalsPage = lazy(() => import("./MobileApprovalsPage"));
|
||||
export const MobileAssetScanPage = lazy(() => import("./MobileAssetScanPage"));
|
||||
export const MobileCapturePage = lazy(() => import("./MobileCapturePage"));
|
||||
export const NotificationChannelsPage = lazy(() => import("./NotificationChannelsPage"));
|
||||
export const NotificationsPage = lazy(() => import("./NotificationsPage"));
|
||||
export const PurchaseRequestsPage = lazy(() => import("./PurchaseRequestsPage"));
|
||||
export const ReimbursementDetailPage = lazy(() => import("./ReimbursementDetailPage"));
|
||||
export const ReimbursementsPage = lazy(() => import("./ReimbursementsPage"));
|
||||
export const TravelRequestsPage = lazy(() => import("./TravelRequestsPage"));
|
||||
export const UserManagementPage = lazy(() => import("./UserManagementPage"));
|
||||
export const WorkflowTaskDetailPage = lazy(() => import("./WorkflowTaskDetailPage"));
|
||||
export const WorkflowTasksPage = lazy(() => import("./WorkflowTasksPage"));
|
||||
@@ -2,6 +2,86 @@ import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
|
||||
const PAGE_CHUNK_PATTERNS = {
|
||||
"page-travel": ["TravelRequestsPage"],
|
||||
"page-assets": ["AssetRegistryPage", "InventoryChecksPage"],
|
||||
"page-procurement": ["PurchaseRequestsPage"],
|
||||
"page-workflow": ["WorkflowTasksPage", "WorkflowTaskDetailPage"],
|
||||
"page-system": [
|
||||
"AuditLogsPage",
|
||||
"ApprovalRulesPage",
|
||||
"DelegationsPage",
|
||||
"DepartmentsPage",
|
||||
"ErpIntegrationPage",
|
||||
"FeatureFlagsPage",
|
||||
"InvoicePoolPage",
|
||||
"LeaveRequestPage",
|
||||
"MasterDataPage",
|
||||
"NotificationChannelsPage",
|
||||
"NotificationsPage",
|
||||
"ReimbursementDetailPage",
|
||||
"ReimbursementsPage",
|
||||
"UserManagementPage",
|
||||
],
|
||||
"page-mobile": ["MobileCapturePage", "MobileAssetScanPage", "MobileApprovalsPage"],
|
||||
} as const;
|
||||
|
||||
function resolveManualChunk(id: string) {
|
||||
if (id.includes("node_modules")) {
|
||||
if (["react", "react-dom", "react-router-dom"].some((pkg) => id.includes(`/node_modules/${pkg}/`))) {
|
||||
return "vendor-react";
|
||||
}
|
||||
|
||||
if (id.includes("/node_modules/@tanstack/react-query/")) {
|
||||
return "vendor-query";
|
||||
}
|
||||
|
||||
if (id.includes("/node_modules/@zxing/browser/")) {
|
||||
return "vendor-scan";
|
||||
}
|
||||
|
||||
if (
|
||||
id.includes("/node_modules/@radix-ui/") ||
|
||||
id.includes("/node_modules/class-variance-authority/") ||
|
||||
id.includes("/node_modules/clsx/") ||
|
||||
id.includes("/node_modules/tailwind-merge/") ||
|
||||
id.includes("/node_modules/sonner/")
|
||||
) {
|
||||
return "vendor-ui";
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
id.includes("/src/api/http.ts") ||
|
||||
id.includes("/src/api/types.ts") ||
|
||||
id.includes("/src/api/errorMessages.ts") ||
|
||||
id.includes("/src/api/auth.ts") ||
|
||||
id.includes("/src/auth/") ||
|
||||
id.includes("/src/components/common/") ||
|
||||
id.includes("/src/hooks/") ||
|
||||
id.includes("/src/lib/") ||
|
||||
id.includes("/src/pwa.ts")
|
||||
) {
|
||||
return "app-core";
|
||||
}
|
||||
|
||||
if (id.includes("/src/api/masterData.ts") || id.includes("/src/components/ai/")) {
|
||||
return "app-domain-shared";
|
||||
}
|
||||
|
||||
for (const [chunkName, pageNames] of Object.entries(PAGE_CHUNK_PATTERNS)) {
|
||||
for (const pageName of pageNames) {
|
||||
if (id.includes(`/src/router/lazy/${pageName}.ts`)) {
|
||||
return chunkName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
@@ -89,6 +169,15 @@ export default defineConfig({
|
||||
},
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
return resolveManualChunk(id);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
|
||||
在新工单中引用
屏蔽一个用户