diff --git a/apps/web/src/router/ProtectedRoute.tsx b/apps/web/src/router/ProtectedRoute.tsx new file mode 100644 index 000000000..b1f2fa12f --- /dev/null +++ b/apps/web/src/router/ProtectedRoute.tsx @@ -0,0 +1,16 @@ +import React from "react" +import { Navigate } from "react-router-dom" +import { useAuthStore } from "@/store/authStore" + +/** 受保护的路由组件 */ +// eslint-disable-next-line react-refresh/only-export-components +export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { + const isAuthenticated = useAuthStore((state) => state.isAuthenticated) + const hasAccessToken = Boolean(localStorage.getItem("access_token")) + + if (!isAuthenticated || !hasAccessToken) { + return + } + + return <>{children} +} diff --git a/apps/web/src/router/appRoutes.tsx b/apps/web/src/router/appRoutes.tsx new file mode 100644 index 000000000..2858217c3 --- /dev/null +++ b/apps/web/src/router/appRoutes.tsx @@ -0,0 +1,225 @@ +import { Navigate, type RouteObject } from "react-router-dom" +import MainLayout from "@/components/layout/MainLayout" +import { ProtectedRoute } from "./ProtectedRoute" + +/** + * 受保护的 /app 子路由 + * 所有页面使用 lazy 懒加载 + */ +const appChildren: RouteObject[] = [ + { + index: true, + element: , + }, + { + path: "dashboard", + lazy: () => + import("@/pages/dashboard/Dashboard").then((m) => ({ + Component: m.default, + })), + }, + { + path: "assets", + lazy: () => + import("@/pages/assets/AssetLibrary").then((m) => ({ + Component: m.default, + })), + }, + { + path: "titles", + lazy: () => + import("@/pages/titles/TitleLibrary").then((m) => ({ + Component: m.default, + })), + }, + { + path: "voices", + lazy: () => + import("@/pages/voices/VoiceLibrary").then((m) => ({ + Component: m.default, + })), + }, + { + path: "templates", + lazy: () => + import("@/pages/templates/TemplateLibrary").then((m) => ({ + Component: m.default, + })), + }, + { + path: "generate", + lazy: () => + import("@/pages/generate/GeneratePage").then((m) => ({ + Component: m.default, + })), + }, + { + path: "history", + lazy: () => + import("@/pages/history/TaskHistory").then((m) => ({ + Component: m.default, + })), + }, + { + path: "products", + lazy: () => + import("@/pages/products/ProductLibrary").then((m) => ({ + Component: m.default, + })), + }, + { + path: "products/:id", + lazy: () => + import("@/pages/products/ProductDetail").then((m) => ({ + Component: m.default, + })), + }, + { + path: "tasks", + lazy: () => + import("@/pages/tasks/TaskCenter").then((m) => ({ + Component: m.default, + })), + }, + { + path: "editing-planner", + lazy: () => + import("@/pages/editing-planner/EditingPlanner").then((m) => ({ + Component: m.default, + })), + }, + { + path: "my-templates", + lazy: () => + import("@/pages/my-templates/MyTemplates").then((m) => ({ + Component: m.default, + })), + }, + { + path: "voice-clone", + lazy: () => + import("@/pages/voice-clone/VoiceClone").then((m) => ({ + Component: m.default, + })), + }, + { + path: "voice-materials", + lazy: () => + import("@/pages/voice-materials/VoiceMaterialLibrary").then((m) => ({ + Component: m.default, + })), + }, + { + path: "my-voices", + lazy: () => + import("@/pages/my-voices/MyVoices").then((m) => ({ + Component: m.default, + })), + }, + { + path: "accounts", + lazy: () => + import("@/pages/accounts/Accounts").then((m) => ({ + Component: m.default, + })), + }, + { + path: "duplication", + lazy: () => + import("@/pages/duplication/DuplicationUpload").then((m) => ({ + Component: m.default, + })), + }, + { + path: "duplication/results", + lazy: () => + import("@/pages/duplication/DuplicationResults").then((m) => ({ + Component: m.default, + })), + }, + { + path: "duplication/:id", + lazy: () => + import("@/pages/duplication/DuplicationDetail").then((m) => ({ + Component: m.default, + })), + }, + { + path: "subscription", + lazy: () => + import("@/pages/subscription/Plans").then((m) => ({ + Component: m.default, + })), + }, + { + path: "subscription/upgrade", + lazy: () => + import("@/pages/subscription/UpgradeSubscription").then((m) => ({ + Component: m.default, + })), + }, + { + path: "subscription/billing", + lazy: () => + import("@/pages/subscription/Billing").then((m) => ({ + Component: m.default, + })), + }, + { + path: "profile", + lazy: () => + import("@/pages/profile/Settings").then((m) => ({ + Component: m.default, + })), + }, + { + path: "admin", + children: [ + { + index: true, + lazy: () => + import("@/pages/admin/AdminComingSoon").then((m) => ({ + Component: m.default, + })), + }, + { + path: "users", + lazy: () => + import("@/pages/admin/AdminComingSoon").then((m) => ({ + Component: m.default, + })), + }, + { + path: "analytics", + lazy: () => + import("@/pages/admin/AdminComingSoon").then((m) => ({ + Component: m.default, + })), + }, + { + path: "monitor", + lazy: () => + import("@/pages/admin/AdminComingSoon").then((m) => ({ + Component: m.default, + })), + }, + { + path: "logs", + lazy: () => + import("@/pages/admin/AdminComingSoon").then((m) => ({ + Component: m.default, + })), + }, + ], + }, +] + +export const appRoutes: RouteObject = { + path: "/app", + element: ( + + + + ), + children: appChildren, +} diff --git a/apps/web/src/router/index.tsx b/apps/web/src/router/index.tsx index 670c915ce..e13171463 100644 --- a/apps/web/src/router/index.tsx +++ b/apps/web/src/router/index.tsx @@ -3,283 +3,13 @@ * 扁平化路由:去掉 Project 层级,所有资源直接归属用户 */ import { createBrowserRouter, Navigate } from "react-router-dom" -import React from "react" -import MainLayout from "@/components/layout/MainLayout" -import Login from "@/pages/auth/Login" -import Register from "@/pages/auth/Register" -import ForgotPassword from "@/pages/auth/ForgotPassword" -import ResetPassword from "@/pages/auth/ResetPassword" -import WechatCallback from "@/pages/auth/WechatCallback" -import HomePage from "@/pages/home/HomePage" -import { useAuthStore } from "@/store/authStore" - -/** 受保护的路由组件 */ -// eslint-disable-next-line react-refresh/only-export-components -const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { - const isAuthenticated = useAuthStore((state) => state.isAuthenticated) - const hasAccessToken = Boolean(localStorage.getItem("access_token")) - - if (!isAuthenticated || !hasAccessToken) { - return - } - - return <>{children} -} - -/** 首页路由组件:已登录跳 dashboard,未登录显示落地页 */ -// eslint-disable-next-line react-refresh/only-export-components -const HomeRoute: React.FC = () => { - const isAuthenticated = useAuthStore((state) => state.isAuthenticated) - const hasAccessToken = Boolean(localStorage.getItem("access_token")) - - if (isAuthenticated && hasAccessToken) { - return - } - - return -} +import { publicRoutes } from "./publicRoutes" +import { appRoutes } from "./appRoutes" /** 路由配置 */ export const router = createBrowserRouter([ - { - path: "/", - element: , - }, - { - path: "/login", - element: , - }, - { - path: "/register", - element: , - }, - { - path: "/forgot-password", - element: , - }, - { - path: "/reset-password", - element: , - }, - { - path: "/auth/wechat/callback", - element: , - }, - { - path: "/app", - element: ( - - - - ), - children: [ - { - index: true, - element: , - }, - { - path: "dashboard", - lazy: () => - import("@/pages/dashboard/Dashboard").then((m) => ({ - Component: m.default, - })), - }, - { - path: "assets", - lazy: () => - import("@/pages/assets/AssetLibrary").then((m) => ({ - Component: m.default, - })), - }, - { - path: "titles", - lazy: () => - import("@/pages/titles/TitleLibrary").then((m) => ({ - Component: m.default, - })), - }, - { - path: "voices", - lazy: () => - import("@/pages/voices/VoiceLibrary").then((m) => ({ - Component: m.default, - })), - }, - { - path: "templates", - lazy: () => - import("@/pages/templates/TemplateLibrary").then((m) => ({ - Component: m.default, - })), - }, - { - path: "generate", - lazy: () => - import("@/pages/generate/GeneratePage").then((m) => ({ - Component: m.default, - })), - }, - { - path: "history", - lazy: () => - import("@/pages/history/TaskHistory").then((m) => ({ - Component: m.default, - })), - }, - { - path: "products", - lazy: () => - import("@/pages/products/ProductLibrary").then((m) => ({ - Component: m.default, - })), - }, - { - path: "products/:id", - lazy: () => - import("@/pages/products/ProductDetail").then((m) => ({ - Component: m.default, - })), - }, - { - path: "tasks", - lazy: () => - import("@/pages/tasks/TaskCenter").then((m) => ({ - Component: m.default, - })), - }, - { - path: "editing-planner", - lazy: () => - import("@/pages/editing-planner/EditingPlanner").then((m) => ({ - Component: m.default, - })), - }, - { - path: "my-templates", - lazy: () => - import("@/pages/my-templates/MyTemplates").then((m) => ({ - Component: m.default, - })), - }, - { - path: "voice-clone", - lazy: () => - import("@/pages/voice-clone/VoiceClone").then((m) => ({ - Component: m.default, - })), - }, - { - path: "voice-materials", - lazy: () => - import("@/pages/voice-materials/VoiceMaterialLibrary").then((m) => ({ - Component: m.default, - })), - }, - { - path: "my-voices", - lazy: () => - import("@/pages/my-voices/MyVoices").then((m) => ({ - Component: m.default, - })), - }, - { - path: "accounts", - lazy: () => - import("@/pages/accounts/Accounts").then((m) => ({ - Component: m.default, - })), - }, - { - path: "duplication", - lazy: () => - import("@/pages/duplication/DuplicationUpload").then((m) => ({ - Component: m.default, - })), - }, - { - path: "duplication/results", - lazy: () => - import("@/pages/duplication/DuplicationResults").then((m) => ({ - Component: m.default, - })), - }, - { - path: "duplication/:id", - lazy: () => - import("@/pages/duplication/DuplicationDetail").then((m) => ({ - Component: m.default, - })), - }, - { - path: "subscription", - lazy: () => - import("@/pages/subscription/Plans").then((m) => ({ - Component: m.default, - })), - }, - { - path: "subscription/upgrade", - lazy: () => - import("@/pages/subscription/UpgradeSubscription").then((m) => ({ - Component: m.default, - })), - }, - { - path: "subscription/billing", - lazy: () => - import("@/pages/subscription/Billing").then((m) => ({ - Component: m.default, - })), - }, - { - path: "profile", - lazy: () => - import("@/pages/profile/Settings").then((m) => ({ - Component: m.default, - })), - }, - { - path: "admin", - children: [ - { - index: true, - lazy: () => - import("@/pages/admin/AdminComingSoon").then((m) => ({ - Component: m.default, - })), - }, - { - path: "users", - lazy: () => - import("@/pages/admin/AdminComingSoon").then((m) => ({ - Component: m.default, - })), - }, - { - path: "analytics", - lazy: () => - import("@/pages/admin/AdminComingSoon").then((m) => ({ - Component: m.default, - })), - }, - { - path: "monitor", - lazy: () => - import("@/pages/admin/AdminComingSoon").then((m) => ({ - Component: m.default, - })), - }, - { - path: "logs", - lazy: () => - import("@/pages/admin/AdminComingSoon").then((m) => ({ - Component: m.default, - })), - }, - ], - }, - ], - }, + ...publicRoutes, + appRoutes, { path: "*", element: , diff --git a/apps/web/src/router/publicRoutes.tsx b/apps/web/src/router/publicRoutes.tsx new file mode 100644 index 000000000..e610b941d --- /dev/null +++ b/apps/web/src/router/publicRoutes.tsx @@ -0,0 +1,48 @@ +import { Navigate, type RouteObject } from "react-router-dom" +import HomePage from "@/pages/home/HomePage" +import Login from "@/pages/auth/Login" +import Register from "@/pages/auth/Register" +import ForgotPassword from "@/pages/auth/ForgotPassword" +import ResetPassword from "@/pages/auth/ResetPassword" +import WechatCallback from "@/pages/auth/WechatCallback" +import { useAuthStore } from "@/store/authStore" + +/** 首页路由组件:已登录跳 dashboard,未登录显示落地页 */ +// eslint-disable-next-line react-refresh/only-export-components +const HomeRoute: React.FC = () => { + const isAuthenticated = useAuthStore((state) => state.isAuthenticated) + const hasAccessToken = Boolean(localStorage.getItem("access_token")) + + if (isAuthenticated && hasAccessToken) { + return + } + + return +} + +export const publicRoutes: RouteObject[] = [ + { + path: "/", + element: , + }, + { + path: "/login", + element: , + }, + { + path: "/register", + element: , + }, + { + path: "/forgot-password", + element: , + }, + { + path: "/reset-password", + element: , + }, + { + path: "/auth/wechat/callback", + element: , + }, +]