32e78f31e2
CI/CD Pipeline / Frontend Lint (push) Failing after 51h43m2s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 51h43m2s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1710h18m13s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1710h18m15s
CI/CD Pipeline / Build Production Runtime Images (push) Failing after 1710h18m17s
CI/CD Pipeline / Deploy Staging (push) Failing after 1710h18m17s
CI/CD Pipeline / Deploy Production (push) Failing after 1710h49m42s
- 新建 VoiceMaterialLibrary.tsx:卡片/列表双布局、上传、试听、编辑、删除 - 元信息:名称、音色描述、性别(男/女/童声/中性)、风格标签 - V21 设计系统样式(voice-materials.css),含性别色带、响应式适配 - 路由 /app/voice-materials,导航 + PageHead 面包屑配置 - 当前阶段:静态页面 + Mock 数据,等后端 API 就绪后对接 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
285 lines
7.2 KiB
TypeScript
285 lines
7.2 KiB
TypeScript
/**
|
|
* Phase 1 路由重构
|
|
* 扁平化路由:去掉 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 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 <Navigate to="/login" replace />;
|
|
}
|
|
|
|
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 <Navigate to="/app/dashboard" replace />;
|
|
}
|
|
|
|
return <HomePage />;
|
|
};
|
|
|
|
/** 路由配置 */
|
|
export const router = createBrowserRouter([
|
|
{
|
|
path: "/",
|
|
element: <HomeRoute />,
|
|
},
|
|
{
|
|
path: "/login",
|
|
element: <Login />,
|
|
},
|
|
{
|
|
path: "/register",
|
|
element: <Register />,
|
|
},
|
|
{
|
|
path: "/forgot-password",
|
|
element: <ForgotPassword />,
|
|
},
|
|
{
|
|
path: "/reset-password",
|
|
element: <ResetPassword />,
|
|
},
|
|
{
|
|
path: "/app",
|
|
element: (
|
|
<ProtectedRoute>
|
|
<MainLayout />
|
|
</ProtectedRoute>
|
|
),
|
|
children: [
|
|
{
|
|
index: true,
|
|
element: <Navigate to="/app/dashboard" replace />,
|
|
},
|
|
{
|
|
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: "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: "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,
|
|
})),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
path: "*",
|
|
element: <Navigate to="/" replace />,
|
|
},
|
|
]);
|
|
|
|
export default router;
|