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: ,
+ },
+]