feat(ui): 新建首页落地页
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Failing after 193h58m42s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 193h58m51s
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Failing after 193h58m42s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 193h58m51s
- 新增公开落地页 HomePage(/),包含 5 个区域:HeroSection、FeatureSection、WorkflowSection、PricingSection、CTASection - 受保护路由基础路径从 / 迁移至 /app,使 / 可作为公开首页 - 新增 HomeRoute 组件:已登录用户自动跳转 /app/dashboard,未登录用户展示落地页 - 级联更新所有导航路径:Sidebar、Header、PageHead 全部迁移至 /app 前缀 - 零 antd 直接导入,全部使用 CSS 变量 + V21 Design System - 响应式适配:768px / 480px 断点 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,69 +40,69 @@ const NAV_ITEMS: NavItem[] = [
|
||||
{
|
||||
key: "dashboard",
|
||||
label: "概览",
|
||||
path: "/dashboard",
|
||||
path: "/app/dashboard",
|
||||
icon: <DashboardOutlined />,
|
||||
},
|
||||
{ key: "assets", label: "素材库", path: "/assets", icon: <FileOutlined /> },
|
||||
{ key: "assets", label: "素材库", path: "/app/assets", icon: <FileOutlined /> },
|
||||
{
|
||||
key: "titles",
|
||||
label: "标题库",
|
||||
path: "/titles",
|
||||
path: "/app/titles",
|
||||
icon: <FileTextOutlined />,
|
||||
},
|
||||
{ key: "voices", label: "配音库", path: "/voices", icon: <AudioOutlined /> },
|
||||
{ key: "voices", label: "配音库", path: "/app/voices", icon: <AudioOutlined /> },
|
||||
{
|
||||
key: "voice-clone",
|
||||
label: "我的音色",
|
||||
path: "/voice-clone",
|
||||
path: "/app/voice-clone",
|
||||
icon: <AudioOutlined />,
|
||||
},
|
||||
{
|
||||
key: "accounts",
|
||||
label: "账号管理",
|
||||
path: "/accounts",
|
||||
path: "/app/accounts",
|
||||
icon: <AppstoreOutlined />,
|
||||
},
|
||||
{
|
||||
key: "templates",
|
||||
label: "模板库",
|
||||
path: "/templates",
|
||||
path: "/app/templates",
|
||||
icon: <AppstoreOutlined />,
|
||||
},
|
||||
{
|
||||
key: "editing-planner",
|
||||
label: "剪辑编辑器",
|
||||
path: "/editing-planner",
|
||||
path: "/app/editing-planner",
|
||||
icon: <EditOutlined />,
|
||||
},
|
||||
{
|
||||
key: "my-templates",
|
||||
label: "我的模板",
|
||||
path: "/my-templates",
|
||||
path: "/app/my-templates",
|
||||
icon: <FolderOutlined />,
|
||||
},
|
||||
{
|
||||
key: "generate",
|
||||
label: "一键生成",
|
||||
path: "/generate",
|
||||
path: "/app/generate",
|
||||
icon: <VideoCameraOutlined />,
|
||||
},
|
||||
{
|
||||
key: "history",
|
||||
label: "任务历史",
|
||||
path: "/history",
|
||||
path: "/app/history",
|
||||
icon: <HistoryOutlined />,
|
||||
},
|
||||
{
|
||||
key: "products",
|
||||
label: "成品库",
|
||||
path: "/products",
|
||||
path: "/app/products",
|
||||
icon: <TrophyOutlined />,
|
||||
},
|
||||
{
|
||||
key: "duplication",
|
||||
label: "查重",
|
||||
path: "/duplication",
|
||||
path: "/app/duplication",
|
||||
icon: <ScanOutlined />,
|
||||
},
|
||||
];
|
||||
@@ -120,13 +120,13 @@ const Header: React.FC = () => {
|
||||
key: "profile",
|
||||
icon: <UserOutlined />,
|
||||
label: "个人设置",
|
||||
onClick: () => navigate("/profile"),
|
||||
onClick: () => navigate("/app/profile"),
|
||||
},
|
||||
{
|
||||
key: "subscription",
|
||||
icon: <SettingOutlined />,
|
||||
label: "订阅管理",
|
||||
onClick: () => navigate("/subscription"),
|
||||
onClick: () => navigate("/app/subscription"),
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
@@ -139,8 +139,8 @@ const Header: React.FC = () => {
|
||||
|
||||
/** 判断导航项是否激活 */
|
||||
const isActive = (path: string) => {
|
||||
if (path === "/dashboard") {
|
||||
return location.pathname === "/" || location.pathname === "/dashboard";
|
||||
if (path === "/app/dashboard") {
|
||||
return location.pathname === "/app" || location.pathname === "/app/dashboard";
|
||||
}
|
||||
return location.pathname.startsWith(path);
|
||||
};
|
||||
@@ -151,7 +151,7 @@ const Header: React.FC = () => {
|
||||
<button
|
||||
className="xx-brand"
|
||||
type="button"
|
||||
onClick={() => navigate("/dashboard")}
|
||||
onClick={() => navigate("/app/dashboard")}
|
||||
>
|
||||
<span className="xx-logo">🦐</span>
|
||||
<span className="xx-brand-text">小虾自动剪辑</span>
|
||||
|
||||
@@ -42,39 +42,39 @@ export interface PageHeadProps {
|
||||
/* ── 路由 → 标题映射(用于自动生成面包屑) ────────────────── */
|
||||
|
||||
const ROUTE_TITLE_MAP: Record<string, string> = {
|
||||
"/dashboard": "首页",
|
||||
"/generate": "一键生成",
|
||||
"/assets": "素材库",
|
||||
"/voices": "配音库",
|
||||
"/titles": "标题库",
|
||||
"/products": "成片库",
|
||||
"/templates": "模板库",
|
||||
"/history": "任务历史",
|
||||
"/admin": "控制台",
|
||||
"/admin/users": "用户管理",
|
||||
"/admin/analytics": "数据分析",
|
||||
"/admin/monitor": "系统监控",
|
||||
"/admin/logs": "系统日志",
|
||||
"/subscription": "订阅管理",
|
||||
"/subscription/upgrade": "升级订阅",
|
||||
"/subscription/billing": "账单管理",
|
||||
"/profile": "个人设置",
|
||||
"/editing-planner": "剪辑规划",
|
||||
"/my-templates": "我的模板",
|
||||
"/voice-clone": "我的音色",
|
||||
"/accounts": "账号管理",
|
||||
"/duplication": "查重",
|
||||
"/duplication/results": "查重结果",
|
||||
"/app/dashboard": "首页",
|
||||
"/app/generate": "一键生成",
|
||||
"/app/assets": "素材库",
|
||||
"/app/voices": "配音库",
|
||||
"/app/titles": "标题库",
|
||||
"/app/products": "成片库",
|
||||
"/app/templates": "模板库",
|
||||
"/app/history": "任务历史",
|
||||
"/app/admin": "控制台",
|
||||
"/app/admin/users": "用户管理",
|
||||
"/app/admin/analytics": "数据分析",
|
||||
"/app/admin/monitor": "系统监控",
|
||||
"/app/admin/logs": "系统日志",
|
||||
"/app/subscription": "订阅管理",
|
||||
"/app/subscription/upgrade": "升级订阅",
|
||||
"/app/subscription/billing": "账单管理",
|
||||
"/app/profile": "个人设置",
|
||||
"/app/editing-planner": "剪辑规划",
|
||||
"/app/my-templates": "我的模板",
|
||||
"/app/voice-clone": "我的音色",
|
||||
"/app/accounts": "账号管理",
|
||||
"/app/duplication": "查重",
|
||||
"/app/duplication/results": "查重结果",
|
||||
};
|
||||
|
||||
/* ── 自动生成面包屑 ─────────────────────────────────────── */
|
||||
|
||||
/** 根据当前路径生成面包屑 */
|
||||
const generateBreadcrumb = (pathname: string): BreadcrumbItem[] => {
|
||||
const items: BreadcrumbItem[] = [{ label: "首页", path: "/dashboard" }];
|
||||
const items: BreadcrumbItem[] = [{ label: "首页", path: "/app/dashboard" }];
|
||||
|
||||
// 首页本身不需要面包屑
|
||||
if (pathname === "/" || pathname === "/dashboard") {
|
||||
if (pathname === "/app" || pathname === "/app/dashboard") {
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -125,8 +125,8 @@ const PageHead: React.FC<PageHeadProps> = ({
|
||||
const showBreadcrumb =
|
||||
!hideBreadcrumb &&
|
||||
breadcrumbItems.length > 1 &&
|
||||
location.pathname !== "/" &&
|
||||
location.pathname !== "/dashboard";
|
||||
location.pathname !== "/app" &&
|
||||
location.pathname !== "/app/dashboard";
|
||||
|
||||
return (
|
||||
<header className="xx-page-head">
|
||||
|
||||
@@ -48,13 +48,13 @@ const MENU_GROUPS: SidebarMenuGroup[] = [
|
||||
{
|
||||
key: "dashboard",
|
||||
label: "首页",
|
||||
path: "/dashboard",
|
||||
path: "/app/dashboard",
|
||||
icon: <DashboardOutlined />,
|
||||
},
|
||||
{
|
||||
key: "generate",
|
||||
label: "一键生成",
|
||||
path: "/generate",
|
||||
path: "/app/generate",
|
||||
icon: <VideoCameraOutlined />,
|
||||
},
|
||||
],
|
||||
@@ -65,43 +65,43 @@ const MENU_GROUPS: SidebarMenuGroup[] = [
|
||||
{
|
||||
key: "assets",
|
||||
label: "素材库",
|
||||
path: "/assets",
|
||||
path: "/app/assets",
|
||||
icon: <FileOutlined />,
|
||||
},
|
||||
{
|
||||
key: "voices",
|
||||
label: "配音库",
|
||||
path: "/voices",
|
||||
path: "/app/voices",
|
||||
icon: <AudioOutlined />,
|
||||
},
|
||||
{
|
||||
key: "voice-clone",
|
||||
label: "我的音色",
|
||||
path: "/voice-clone",
|
||||
path: "/app/voice-clone",
|
||||
icon: <AudioOutlined />,
|
||||
},
|
||||
{
|
||||
key: "accounts",
|
||||
label: "账号管理",
|
||||
path: "/accounts",
|
||||
path: "/app/accounts",
|
||||
icon: <AppstoreOutlined />,
|
||||
},
|
||||
{
|
||||
key: "titles",
|
||||
label: "标题库",
|
||||
path: "/titles",
|
||||
path: "/app/titles",
|
||||
icon: <FileTextOutlined />,
|
||||
},
|
||||
{
|
||||
key: "products",
|
||||
label: "成片库",
|
||||
path: "/products",
|
||||
path: "/app/products",
|
||||
icon: <TrophyOutlined />,
|
||||
},
|
||||
{
|
||||
key: "templates",
|
||||
label: "模板库",
|
||||
path: "/templates",
|
||||
path: "/app/templates",
|
||||
icon: <AppstoreOutlined />,
|
||||
},
|
||||
],
|
||||
@@ -112,19 +112,19 @@ const MENU_GROUPS: SidebarMenuGroup[] = [
|
||||
{
|
||||
key: "history",
|
||||
label: "任务历史",
|
||||
path: "/history",
|
||||
path: "/app/history",
|
||||
icon: <HistoryOutlined />,
|
||||
},
|
||||
{
|
||||
key: "admin",
|
||||
label: "控制台",
|
||||
path: "/admin",
|
||||
path: "/app/admin",
|
||||
icon: <ControlOutlined />,
|
||||
},
|
||||
{
|
||||
key: "subscription",
|
||||
label: "订阅管理",
|
||||
path: "/subscription",
|
||||
path: "/app/subscription",
|
||||
icon: <CrownOutlined />,
|
||||
},
|
||||
],
|
||||
@@ -133,8 +133,8 @@ const MENU_GROUPS: SidebarMenuGroup[] = [
|
||||
|
||||
/** 判断菜单项是否激活 */
|
||||
const isMenuItemActive = (pathname: string, path: string): boolean => {
|
||||
if (path === "/dashboard") {
|
||||
return pathname === "/" || pathname === "/dashboard";
|
||||
if (path === "/app/dashboard") {
|
||||
return pathname === "/app" || pathname === "/app/dashboard";
|
||||
}
|
||||
return pathname.startsWith(path);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* 首页落地页 — V21 Design System
|
||||
*
|
||||
* 5 个区域:HeroSection / FeatureSection / WorkflowSection
|
||||
* / PricingSection / CTASection
|
||||
*
|
||||
* 零 antd 直接导入,全部使用 CSS 变量
|
||||
*/
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "@/components/ui";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import "./home-page.css";
|
||||
|
||||
/* ── HeroSection ─────────────────────────────────────────── */
|
||||
|
||||
const HeroSection: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<section className="hp-hero">
|
||||
<div className="hp-hero-inner">
|
||||
{/* 左侧文案 */}
|
||||
<div className="hp-hero-content">
|
||||
<span className="hp-hero-tag">🦐 小虾智剪 · AI智能视频创作平台</span>
|
||||
<h1 className="hp-hero-title">
|
||||
上传素材,AI自动剪辑
|
||||
<br />
|
||||
一键生成短视频
|
||||
</h1>
|
||||
<p className="hp-hero-desc">
|
||||
基于先进的 AI 技术,自动识别视频亮点,智能剪辑、配音、加字幕。
|
||||
30 秒内将长视频转化为适合各平台传播的精品短视频。
|
||||
</p>
|
||||
<div className="hp-hero-actions">
|
||||
<Button buttonType="primary" buttonSize="lg" onClick={() => navigate("/register")}>
|
||||
立即免费开始
|
||||
</Button>
|
||||
<Button buttonType="secondary" buttonSize="lg" onClick={() => navigate("/pricing")}>
|
||||
查看定价方案
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧视觉 */}
|
||||
<div className="hp-hero-visual">
|
||||
<div className="hp-hero-video">
|
||||
<div className="hp-hero-video-inner">
|
||||
<span className="hp-hero-video-placeholder">🎬</span>
|
||||
</div>
|
||||
<button className="hp-hero-play" type="button" aria-label="播放演示视频">
|
||||
▶
|
||||
</button>
|
||||
</div>
|
||||
<div className="hp-hero-info">
|
||||
<div className="hp-hero-badges">
|
||||
<span className="hp-hero-badge">✨ AI智能剪辑</span>
|
||||
<span className="hp-hero-badge">⚡ 30秒生成</span>
|
||||
</div>
|
||||
<span className="hp-hero-pill">可发布</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── FeatureSection ──────────────────────────────────────── */
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "🤖",
|
||||
title: "AI 智能剪辑",
|
||||
desc: "自动识别视频高光片段,智能去除冗余内容,一键生成精彩短视频。",
|
||||
},
|
||||
{
|
||||
icon: "🎙️",
|
||||
title: "AI 配音克隆",
|
||||
desc: "克隆您的声音,支持多种音色风格,自动生成自然流畅的配音。",
|
||||
},
|
||||
{
|
||||
icon: "📝",
|
||||
title: "智能字幕标题",
|
||||
desc: "自动语音识别生成精准字幕,AI 创作吸睛标题,提升内容传播力。",
|
||||
},
|
||||
{
|
||||
icon: "📱",
|
||||
title: "多平台一键发布",
|
||||
desc: "支持抖音、快手、小红书、微信视频号等主流平台,一键同步发布。",
|
||||
},
|
||||
];
|
||||
|
||||
const FeatureSection: React.FC = () => {
|
||||
return (
|
||||
<section className="hp-features">
|
||||
<div className="hp-section-inner">
|
||||
<h2 className="hp-section-title">核心功能</h2>
|
||||
<p className="hp-section-desc">
|
||||
从素材上传到视频发布,全流程 AI 赋能,让短视频创作更简单
|
||||
</p>
|
||||
<div className="hp-feature-grid">
|
||||
{FEATURES.map((f) => (
|
||||
<div key={f.title} className="hp-feature-card">
|
||||
<div className="hp-feature-icon">{f.icon}</div>
|
||||
<h3 className="hp-feature-title">{f.title}</h3>
|
||||
<p className="hp-feature-desc">{f.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── WorkflowSection ─────────────────────────────────────── */
|
||||
|
||||
const STEPS = [
|
||||
{ icon: "📤", title: "上传素材", desc: "拖拽或选择视频素材,支持批量上传" },
|
||||
{ icon: "🧠", title: "AI 处理", desc: "AI 自动分析、剪辑、配音、加字幕" },
|
||||
{ icon: "👀", title: "预览调整", desc: "在线预览生成结果,支持微调编辑" },
|
||||
{ icon: "🚀", title: "一键发布", desc: "多平台同步发布,追踪数据表现" },
|
||||
];
|
||||
|
||||
const WorkflowSection: React.FC = () => {
|
||||
return (
|
||||
<section className="hp-workflow">
|
||||
<div className="hp-section-inner">
|
||||
<h2 className="hp-section-title">工作流程</h2>
|
||||
<p className="hp-section-desc">
|
||||
四步完成短视频创作,从素材到发布仅需 30 秒
|
||||
</p>
|
||||
<div className="hp-step-grid">
|
||||
{STEPS.map((step, idx) => (
|
||||
<div key={step.title} className="hp-step-card">
|
||||
<div className="hp-step-number">{idx + 1}</div>
|
||||
<div className="hp-step-icon">{step.icon}</div>
|
||||
<h3 className="hp-step-title">{step.title}</h3>
|
||||
<p className="hp-step-desc">{step.desc}</p>
|
||||
{idx < STEPS.length - 1 && <div className="hp-step-arrow">→</div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── PricingSection ──────────────────────────────────────── */
|
||||
|
||||
const PLANS = [
|
||||
{
|
||||
name: "基础版",
|
||||
price: "免费",
|
||||
period: "",
|
||||
desc: "适合个人体验,快速上手",
|
||||
features: ["每月 5 次 AI 生成", "720p 视频导出", "基础模板库", "1 个平台账号绑定"],
|
||||
highlighted: false,
|
||||
cta: "免费开始",
|
||||
},
|
||||
{
|
||||
name: "专业版",
|
||||
price: "¥99",
|
||||
period: "/月",
|
||||
desc: "适合内容创作者,高效产出",
|
||||
features: [
|
||||
"每月 100 次 AI 生成",
|
||||
"1080p 视频导出",
|
||||
"全部模板库",
|
||||
"4 个平台账号绑定",
|
||||
"AI 配音克隆",
|
||||
"优先客服支持",
|
||||
],
|
||||
highlighted: true,
|
||||
cta: "立即订阅",
|
||||
},
|
||||
{
|
||||
name: "企业版",
|
||||
price: "¥399",
|
||||
period: "/月",
|
||||
desc: "适合团队与企业,规模化运营",
|
||||
features: [
|
||||
"无限次 AI 生成",
|
||||
"4K 视频导出",
|
||||
"全部模板 + 定制模板",
|
||||
"无限平台账号绑定",
|
||||
"团队协作管理",
|
||||
"API 接入支持",
|
||||
"专属客户经理",
|
||||
],
|
||||
highlighted: false,
|
||||
cta: "联系销售",
|
||||
},
|
||||
];
|
||||
|
||||
const PricingSection: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<section className="hp-pricing">
|
||||
<div className="hp-section-inner">
|
||||
<h2 className="hp-section-title">定价方案</h2>
|
||||
<p className="hp-section-desc">选择适合您的方案,随时升级或取消</p>
|
||||
<div className="hp-pricing-grid">
|
||||
{PLANS.map((plan) => (
|
||||
<div
|
||||
key={plan.name}
|
||||
className={`hp-pricing-card${plan.highlighted ? " hp-pricing-card--highlight" : ""}`}
|
||||
>
|
||||
{plan.highlighted && <div className="hp-pricing-badge">推荐</div>}
|
||||
<h3 className="hp-pricing-name">{plan.name}</h3>
|
||||
<div className="hp-pricing-price">
|
||||
{plan.price}
|
||||
{plan.period && <span className="hp-pricing-period">{plan.period}</span>}
|
||||
</div>
|
||||
<p className="hp-pricing-desc">{plan.desc}</p>
|
||||
<ul className="hp-pricing-features">
|
||||
{plan.features.map((f) => (
|
||||
<li key={f}>
|
||||
<span className="hp-pricing-check">✓</span>
|
||||
{f}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Button
|
||||
buttonType={plan.highlighted ? "primary" : "secondary"}
|
||||
onClick={() => navigate("/register")}
|
||||
>
|
||||
{plan.cta}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── CTASection ──────────────────────────────────────────── */
|
||||
|
||||
const CTASection: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<section className="hp-cta">
|
||||
<div className="hp-cta-inner">
|
||||
<h2 className="hp-cta-title">开始用 AI 创作短视频</h2>
|
||||
<p className="hp-cta-desc">
|
||||
免费注册,立即体验 AI 智能视频创作。无需信用卡,零风险上手。
|
||||
</p>
|
||||
<Button buttonType="primary" buttonSize="lg" onClick={() => navigate("/register")}>
|
||||
免费注册
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
/* ── 主页面 ─────────────────────────────────────────────── */
|
||||
|
||||
const HomePage: React.FC = () => {
|
||||
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 已登录用户自动跳转到 dashboard
|
||||
React.useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
navigate("/app/dashboard", { replace: true });
|
||||
}
|
||||
}, [isAuthenticated, navigate]);
|
||||
|
||||
return (
|
||||
<div className="hp-page">
|
||||
{/* 顶部导航栏 */}
|
||||
<header className="hp-nav">
|
||||
<div className="hp-nav-inner">
|
||||
<button className="hp-nav-brand" type="button" onClick={() => navigate("/")}>
|
||||
<span className="hp-nav-logo">🦐</span>
|
||||
<span className="hp-nav-brand-text">小虾智剪</span>
|
||||
</button>
|
||||
<div className="hp-nav-actions">
|
||||
<Button buttonType="ghost" buttonSize="sm" onClick={() => navigate("/login")}>
|
||||
登录
|
||||
</Button>
|
||||
<Button buttonType="primary" buttonSize="sm" onClick={() => navigate("/register")}>
|
||||
免费注册
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 5 个区域 */}
|
||||
<HeroSection />
|
||||
<FeatureSection />
|
||||
<WorkflowSection />
|
||||
<PricingSection />
|
||||
<CTASection />
|
||||
|
||||
{/* 底部 */}
|
||||
<footer className="hp-footer">
|
||||
<p>© 2026 小虾智剪 · AI智能视频创作平台</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
@@ -0,0 +1,583 @@
|
||||
/**
|
||||
* 首页落地页 — V21 Design System
|
||||
*
|
||||
* 5 个区域:HeroSection / FeatureSection / WorkflowSection
|
||||
* / PricingSection / CTASection
|
||||
* 全部使用 CSS 变量,零 antd 直接导入
|
||||
*/
|
||||
|
||||
/* ── 页面容器 ────────────────────────────────────────────── */
|
||||
|
||||
.hp-page {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-base, #fafafa);
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ── 顶部导航 ────────────────────────────────────────────── */
|
||||
|
||||
.hp-nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border-bottom: 1px solid var(--line, rgba(0, 0, 0, 0.06));
|
||||
}
|
||||
|
||||
.hp-nav-inner {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hp-nav-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hp-nav-logo {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.hp-nav-brand-text {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
}
|
||||
|
||||
.hp-nav-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ── HeroSection ─────────────────────────────────────────── */
|
||||
|
||||
.hp-hero {
|
||||
padding: 120px 24px 60px;
|
||||
background: linear-gradient(180deg, rgba(99, 102, 241, 0.04) 0%, transparent 60%);
|
||||
}
|
||||
|
||||
.hp-hero-inner {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 420px;
|
||||
gap: 48px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hp-hero-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hp-hero-tag {
|
||||
display: inline-flex;
|
||||
align-self: flex-start;
|
||||
padding: 6px 16px;
|
||||
border-radius: var(--radius-sm, 8px);
|
||||
background: rgba(99, 102, 241, 0.08);
|
||||
color: var(--primary, #6366f1);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.hp-hero-title {
|
||||
font-size: 44px;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
margin: 0;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.hp-hero-desc {
|
||||
font-size: 17px;
|
||||
line-height: 1.7;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
margin: 0;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.hp-hero-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Hero 右侧视觉 */
|
||||
.hp-hero-visual {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.hp-hero-video {
|
||||
position: relative;
|
||||
border-radius: var(--radius-lg, 16px);
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
aspect-ratio: 16 / 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 20px 60px rgba(99, 102, 241, 0.18);
|
||||
}
|
||||
|
||||
.hp-hero-video-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hp-hero-video-placeholder {
|
||||
font-size: 64px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.hp-hero-play {
|
||||
position: absolute;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: var(--primary, #6366f1);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.hp-hero-play:hover {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 6px 28px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.hp-hero-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.hp-hero-badges {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.hp-hero-badge {
|
||||
padding: 6px 14px;
|
||||
border-radius: var(--radius-sm, 8px);
|
||||
background: var(--bg-surface, #fff);
|
||||
border: 1px solid var(--line, rgba(0, 0, 0, 0.06));
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hp-hero-pill {
|
||||
padding: 5px 14px;
|
||||
border-radius: 20px;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: #059669;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── 通用 Section 样式 ──────────────────────────────────── */
|
||||
|
||||
.hp-section-inner {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.hp-section-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
margin: 0 0 12px;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
}
|
||||
|
||||
.hp-section-desc {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
margin: 0 0 48px;
|
||||
}
|
||||
|
||||
/* ── FeatureSection ──────────────────────────────────────── */
|
||||
|
||||
.hp-features {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.hp-feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.hp-feature-card {
|
||||
padding: 32px 24px;
|
||||
border-radius: var(--radius-lg, 16px);
|
||||
background: var(--bg-surface, #fff);
|
||||
border: 1px solid var(--line, rgba(0, 0, 0, 0.06));
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.hp-feature-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.hp-feature-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto 16px;
|
||||
border-radius: var(--radius-md, 12px);
|
||||
background: rgba(99, 102, 241, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.hp-feature-title {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
}
|
||||
|
||||
.hp-feature-desc {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── WorkflowSection ─────────────────────────────────────── */
|
||||
|
||||
.hp-workflow {
|
||||
padding: 80px 0;
|
||||
background: linear-gradient(180deg, transparent 0%, rgba(99, 102, 241, 0.03) 50%, transparent 100%);
|
||||
}
|
||||
|
||||
.hp-step-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hp-step-card {
|
||||
position: relative;
|
||||
padding: 32px 24px;
|
||||
border-radius: var(--radius-lg, 16px);
|
||||
background: var(--bg-surface, #fff);
|
||||
border: 1px solid var(--line, rgba(0, 0, 0, 0.06));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hp-step-number {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary, #6366f1);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hp-step-icon {
|
||||
font-size: 36px;
|
||||
margin: 8px 0 12px;
|
||||
}
|
||||
|
||||
.hp-step-title {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
}
|
||||
|
||||
.hp-step-desc {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hp-step-arrow {
|
||||
position: absolute;
|
||||
right: -18px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 20px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
opacity: 0.4;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ── PricingSection ──────────────────────────────────────── */
|
||||
|
||||
.hp-pricing {
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.hp-pricing-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 24px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.hp-pricing-card {
|
||||
position: relative;
|
||||
padding: 36px 28px;
|
||||
border-radius: var(--radius-lg, 16px);
|
||||
background: var(--bg-surface, #fff);
|
||||
border: 1px solid var(--line, rgba(0, 0, 0, 0.06));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.hp-pricing-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.hp-pricing-card--highlight {
|
||||
border-color: var(--primary, #6366f1);
|
||||
box-shadow: 0 8px 32px rgba(99, 102, 241, 0.12);
|
||||
}
|
||||
|
||||
.hp-pricing-badge {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: 20px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 20px;
|
||||
background: var(--primary, #6366f1);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hp-pricing-name {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
}
|
||||
|
||||
.hp-pricing-price {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.hp-pricing-period {
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
}
|
||||
|
||||
.hp-pricing-desc {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hp-pricing-features {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hp-pricing-features li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
}
|
||||
|
||||
.hp-pricing-check {
|
||||
color: #10b981;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ── CTASection ──────────────────────────────────────────── */
|
||||
|
||||
.hp-cta {
|
||||
padding: 80px 24px;
|
||||
}
|
||||
|
||||
.hp-cta-inner {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
padding: 60px 40px;
|
||||
border-radius: var(--radius-lg, 16px);
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.06) 0%, rgba(139, 92, 246, 0.06) 100%);
|
||||
border: 1px solid var(--line, rgba(0, 0, 0, 0.06));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.hp-cta-title {
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: var(--text-primary, #1a1a2e);
|
||||
}
|
||||
|
||||
.hp-cta-desc {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── 底部 ────────────────────────────────────────────────── */
|
||||
|
||||
.hp-footer {
|
||||
padding: 32px 24px;
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--line, rgba(0, 0, 0, 0.06));
|
||||
}
|
||||
|
||||
.hp-footer p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
}
|
||||
|
||||
/* ── 响应式 768px ────────────────────────────────────────── */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hp-hero-inner {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.hp-hero-visual {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hp-hero-title {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.hp-hero {
|
||||
padding: 100px 24px 48px;
|
||||
}
|
||||
|
||||
.hp-feature-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.hp-step-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.hp-step-arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hp-pricing-grid {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hp-section-title {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.hp-cta-inner {
|
||||
padding: 40px 24px;
|
||||
}
|
||||
|
||||
.hp-cta-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 响应式 480px ────────────────────────────────────────── */
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.hp-hero-title {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.hp-hero-desc {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.hp-hero-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hp-hero-badges {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.hp-feature-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hp-step-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hp-nav-actions {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ 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";
|
||||
|
||||
/** 受保护的路由组件 */
|
||||
@@ -24,8 +25,24 @@ const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
/** 首页路由组件:已登录跳 dashboard,未登录显示落地页 */
|
||||
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 />,
|
||||
@@ -43,7 +60,7 @@ export const router = createBrowserRouter([
|
||||
element: <ResetPassword />,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
path: "/app",
|
||||
element: (
|
||||
<ProtectedRoute>
|
||||
<MainLayout />
|
||||
@@ -52,7 +69,7 @@ export const router = createBrowserRouter([
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/dashboard" replace />,
|
||||
element: <Navigate to="/app/dashboard" replace />,
|
||||
},
|
||||
{
|
||||
path: "dashboard",
|
||||
|
||||
Reference in New Issue
Block a user