Compare commits

..

2 Commits

Author SHA1 Message Date
xiaoxia 3562b33ad0 Merge branch 'develop' into refactor/voice-clone-api
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 7s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m12s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m45s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m21s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 43s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 24s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m4s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m42s
AI Code Review / AI Code Review (pull_request) Successful in 1m39s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 42s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m39s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 15m19s
CI/CD Pipeline / CI Gate (pull_request) 失败: CI/CD Pipeline / Validate - Code Quality (pull_request) [frontend-only]
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 22s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 10s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1220h39m50s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1221h14m12s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1221h14m13s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1221h14m13s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1221h14m14s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1222h59m27s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1222h59m28s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1222h59m30s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1222h59m31s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1222h59m32s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1222h59m33s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1224h56m35s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1224h56m39s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1225h29m22s
2026-07-27 12:35:00 +08:00
xiaoxia 01ca228c07 refactor(voice-clone): API层按模块拆分(208行→入口28行, -87%)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 13s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 27s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m4s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 59s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 32s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m33s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m7s
AI Code Review / AI Code Review (pull_request) Successful in 1m38s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m12s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 25s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 42s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 3m14s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 26s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1245h47m52s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1245h56m2s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1245h56m4s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1246h5m57s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1246h13m27s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1246h16m47s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1246h13m29s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1246h16m49s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1246h16m51s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1246h22m28s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1246h22m30s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1246h22m32s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1246h49m28s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1246h28m44s
将 voice-clone.ts 单文件拆分为目录结构:
- types.ts — 类型定义
- utils.ts — toVoiceClone 映射 + formatDuration
- clones.ts — 克隆 CRUD + 状态 + 重试
- index.ts — 统一入口 re-export

导入路径 @/api/voice-clone 保持向后兼容。
2026-07-26 16:56:23 +08:00
12 changed files with 505 additions and 503 deletions
-208
View File
@@ -1,208 +0,0 @@
/**
* 音色克隆 API
* 任务 3.11:替换 Mock 数据,对接后端真实 API(3.05)
* 任务 3.15:新增 progress 字段用于进度展示
*/
import apiClient from "./client"
/* ── 前端兼容类型 ─────────────────────────────────────── */
/** 克隆音色状态(前端展示用) */
export type VoiceCloneStatus = "ready" | "processing" | "failed"
/** 克隆音色条目(前端展示用) */
export interface VoiceClone {
id: string
name: string
description: string
duration_seconds: number
status: VoiceCloneStatus
/** 克隆进度 0-100,仅 processing 状态时有值 */
progress: number
sample_url?: string
language: string
gender: string
error_message: string | null
created_at: string
updated_at: string
}
/** 创建克隆请求(前端简化版) */
export interface CreateVoiceCloneRequest {
name: string
audio_url: string
description?: string
}
/* ── 后端 API 类型 ────────────────────────────────────── */
/** 音色克隆元数据(克隆时附带的扩展信息) */
export interface VoiceCloneMetadata {
/** 语音时长(秒) */
duration?: number
/** 采样率(Hz) */
sample_rate?: number
/** 音色 ID(克隆完成后分配) */
voice_id?: string
/** 其他扩展字段 */
[key: string]: unknown
}
/** 后端克隆档案响应 */
export interface VoiceCloneProfile {
id: string
user_id: string
name: string
description: string
source_audio_url: string
voice_id: string | null
voice_model: string
language: string
gender: string
status: "pending" | "processing" | "ready" | "failed"
error_message: string | null
retry_count: number
max_retries: number
metadata_: VoiceCloneMetadata | null
created_at: string
updated_at: string
}
/** 后端克隆列表响应 */
export interface ListVoiceCloneResponse {
items: VoiceCloneProfile[]
total: number
}
/** 后端克隆状态响应 */
export interface VoiceCloneStatusResponse {
id: string
status: "pending" | "processing" | "ready" | "failed"
error_message: string | null
voice_id: string | null
retry_count: number
}
/** 后端创建克隆请求(完整版) */
export interface CreateVoiceCloneRequestFull {
name: string
description?: string
source_audio_url: string
voice_model?: string
language?: string
gender?: string
max_retries?: number
metadata_?: VoiceCloneMetadata
}
/* ── 辅助函数 ─────────────────────────────────────────── */
/**
* 将后端 VoiceCloneProfile 转换为前端 VoiceClone
* 后端 status "pending" 映射为前端 "processing"
*/
export const toVoiceClone = (profile: VoiceCloneProfile): VoiceClone => ({
id: profile.id,
name: profile.name,
description: profile.description || "",
duration_seconds: 0,
status: profile.status === "pending" ? "processing" : profile.status,
progress: 0,
sample_url: profile.source_audio_url || undefined,
language: profile.language || "",
gender: profile.gender || "",
error_message: profile.error_message || null,
created_at: profile.created_at,
updated_at: profile.updated_at,
})
/** 格式化时长 */
export const formatDuration = (seconds: number): string => {
const m = Math.floor(seconds / 60)
const s = seconds % 60
return `${m}:${String(s).padStart(2, "0")}`
}
/* ── 查询参数 ─────────────────────────────────────────── */
export interface VoiceCloneListParams {
status?: string
skip?: number
limit?: number
}
/* ── API 函数 ─────────────────────────────────────────── */
/** 获取克隆音色列表(返回前端兼容数组) */
export const getVoiceClones = async (params?: VoiceCloneListParams): Promise<VoiceClone[]> => {
const searchParams = new URLSearchParams()
if (params?.status) searchParams.set("status", params.status)
if (params?.skip !== undefined) searchParams.set("skip", String(params.skip))
if (params?.limit !== undefined) searchParams.set("limit", String(params.limit))
const qs = searchParams.toString()
const response = await apiClient.get<ListVoiceCloneResponse>(`/voice-clones${qs ? `?${qs}` : ""}`)
return response.data.items.map(toVoiceClone)
}
/** 获取克隆音色列表(返回完整响应含 total) */
export const getVoiceClonesWithTotal = async (
params?: VoiceCloneListParams,
): Promise<ListVoiceCloneResponse> => {
const searchParams = new URLSearchParams()
if (params?.status) searchParams.set("status", params.status)
if (params?.skip !== undefined) searchParams.set("skip", String(params.skip))
if (params?.limit !== undefined) searchParams.set("limit", String(params.limit))
const qs = searchParams.toString()
const response = await apiClient.get<ListVoiceCloneResponse>(`/voice-clones${qs ? `?${qs}` : ""}`)
return response.data
}
/** 获取单个克隆音色详情 */
export const getVoiceCloneDetail = async (id: string): Promise<VoiceCloneProfile> => {
const response = await apiClient.get<VoiceCloneProfile>(`/voice-clones/${id}`)
return response.data
}
/** 创建克隆音色 */
export const createVoiceClone = async (
data: CreateVoiceCloneRequest,
): Promise<VoiceCloneProfile> => {
const payload: CreateVoiceCloneRequestFull = {
name: data.name,
description: data.description,
source_audio_url: data.audio_url,
}
const response = await apiClient.post<VoiceCloneProfile>("/voice-clones", payload)
return response.data
}
/** 删除克隆音色 */
export const deleteVoiceClone = async (id: string): Promise<void> => {
await apiClient.delete(`/voice-clones/${id}`)
}
/** 更新克隆音色名称(stub — 后端暂无 PATCH 端点) */
export const updateVoiceClone = async (
id: string,
data: Partial<Pick<VoiceClone, "name">>,
): Promise<VoiceClone> => {
// 后端暂未提供更新端点,暂用详情接口模拟
const response = await apiClient.get<VoiceCloneProfile>(`/voice-clones/${id}`)
return toVoiceClone({
...response.data,
...data,
updated_at: new Date().toISOString(),
})
}
/** 获取克隆状态 */
export const getVoiceCloneStatus = async (id: string): Promise<VoiceCloneStatusResponse> => {
const response = await apiClient.get<VoiceCloneStatusResponse>(`/voice-clones/${id}/status`)
return response.data
}
/** 重试克隆 */
export const retryVoiceClone = async (id: string): Promise<VoiceCloneProfile> => {
const response = await apiClient.post<VoiceCloneProfile>(`/voice-clones/${id}/retry`)
return response.data
}
+87
View File
@@ -0,0 +1,87 @@
/**
* 音色克隆 API 函数
*/
import apiClient from "../client"
import { toVoiceClone } from "./utils"
import type {
VoiceClone,
VoiceCloneProfile,
CreateVoiceCloneRequest,
CreateVoiceCloneRequestFull,
VoiceCloneListParams,
ListVoiceCloneResponse,
VoiceCloneStatusResponse,
} from "./types"
/** 获取克隆音色列表(返回前端兼容数组) */
export const getVoiceClones = async (params?: VoiceCloneListParams): Promise<VoiceClone[]> => {
const searchParams = new URLSearchParams()
if (params?.status) searchParams.set("status", params.status)
if (params?.skip !== undefined) searchParams.set("skip", String(params.skip))
if (params?.limit !== undefined) searchParams.set("limit", String(params.limit))
const qs = searchParams.toString()
const response = await apiClient.get<ListVoiceCloneResponse>(`/voice-clones${qs ? `?${qs}` : ""}`)
return response.data.items.map(toVoiceClone)
}
/** 获取克隆音色列表(返回完整响应含 total) */
export const getVoiceClonesWithTotal = async (
params?: VoiceCloneListParams,
): Promise<ListVoiceCloneResponse> => {
const searchParams = new URLSearchParams()
if (params?.status) searchParams.set("status", params.status)
if (params?.skip !== undefined) searchParams.set("skip", String(params.skip))
if (params?.limit !== undefined) searchParams.set("limit", String(params.limit))
const qs = searchParams.toString()
const response = await apiClient.get<ListVoiceCloneResponse>(`/voice-clones${qs ? `?${qs}` : ""}`)
return response.data
}
/** 获取单个克隆音色详情 */
export const getVoiceCloneDetail = async (id: string): Promise<VoiceCloneProfile> => {
const response = await apiClient.get<VoiceCloneProfile>(`/voice-clones/${id}`)
return response.data
}
/** 创建克隆音色 */
export const createVoiceClone = async (
data: CreateVoiceCloneRequest,
): Promise<VoiceCloneProfile> => {
const payload: CreateVoiceCloneRequestFull = {
name: data.name,
description: data.description,
source_audio_url: data.audio_url,
}
const response = await apiClient.post<VoiceCloneProfile>("/voice-clones", payload)
return response.data
}
/** 删除克隆音色 */
export const deleteVoiceClone = async (id: string): Promise<void> => {
await apiClient.delete(`/voice-clones/${id}`)
}
/** 更新克隆音色名称(stub — 后端暂无 PATCH 端点) */
export const updateVoiceClone = async (
id: string,
data: Partial<Pick<VoiceClone, "name">>,
): Promise<VoiceClone> => {
const response = await apiClient.get<VoiceCloneProfile>(`/voice-clones/${id}`)
return toVoiceClone({
...response.data,
...data,
updated_at: new Date().toISOString(),
})
}
/** 获取克隆状态 */
export const getVoiceCloneStatus = async (id: string): Promise<VoiceCloneStatusResponse> => {
const response = await apiClient.get<VoiceCloneStatusResponse>(`/voice-clones/${id}/status`)
return response.data
}
/** 重试克隆 */
export const retryVoiceClone = async (id: string): Promise<VoiceCloneProfile> => {
const response = await apiClient.post<VoiceCloneProfile>(`/voice-clones/${id}/retry`)
return response.data
}
+32
View File
@@ -0,0 +1,32 @@
/**
* 音色克隆 API — 目录化入口
* 保持与原 voice-clone.ts 相同导出,向后兼容
*/
// 类型
export type {
VoiceCloneStatus,
VoiceClone,
CreateVoiceCloneRequest,
VoiceCloneMetadata,
VoiceCloneProfile,
ListVoiceCloneResponse,
VoiceCloneStatusResponse,
CreateVoiceCloneRequestFull,
VoiceCloneListParams,
} from "./types"
// 工具函数
export { toVoiceClone, formatDuration } from "./utils"
// API 函数
export {
getVoiceClones,
getVoiceClonesWithTotal,
getVoiceCloneDetail,
createVoiceClone,
deleteVoiceClone,
updateVoiceClone,
getVoiceCloneStatus,
retryVoiceClone,
} from "./clones"
+92
View File
@@ -0,0 +1,92 @@
/**
* 音色克隆类型定义
*/
/** 克隆音色状态(前端展示用) */
export type VoiceCloneStatus = "ready" | "processing" | "failed"
/** 克隆音色条目(前端展示用) */
export interface VoiceClone {
id: string
name: string
description: string
duration_seconds: number
status: VoiceCloneStatus
/** 克隆进度 0-100,仅 processing 状态时有值 */
progress: number
sample_url?: string
language: string
gender: string
error_message: string | null
created_at: string
updated_at: string
}
/** 创建克隆请求(前端简化版) */
export interface CreateVoiceCloneRequest {
name: string
audio_url: string
description?: string
}
/** 音色克隆元数据 */
export interface VoiceCloneMetadata {
duration?: number
sample_rate?: number
voice_id?: string
[key: string]: unknown
}
/** 后端克隆档案响应 */
export interface VoiceCloneProfile {
id: string
user_id: string
name: string
description: string
source_audio_url: string
voice_id: string | null
voice_model: string
language: string
gender: string
status: "pending" | "processing" | "ready" | "failed"
error_message: string | null
retry_count: number
max_retries: number
metadata_: VoiceCloneMetadata | null
created_at: string
updated_at: string
}
/** 后端克隆列表响应 */
export interface ListVoiceCloneResponse {
items: VoiceCloneProfile[]
total: number
}
/** 后端克隆状态响应 */
export interface VoiceCloneStatusResponse {
id: string
status: "pending" | "processing" | "ready" | "failed"
error_message: string | null
voice_id: string | null
retry_count: number
}
/** 后端创建克隆请求(完整版) */
export interface CreateVoiceCloneRequestFull {
name: string
description?: string
source_audio_url: string
voice_model?: string
language?: string
gender?: string
max_retries?: number
metadata_?: VoiceCloneMetadata
}
/** 查询参数 */
export interface VoiceCloneListParams {
status?: string
skip?: number
limit?: number
}
+30
View File
@@ -0,0 +1,30 @@
/**
* 音色克隆工具函数
*/
import type { VoiceCloneProfile, VoiceClone } from "./types"
/**
* 将后端 VoiceCloneProfile 转换为前端 VoiceClone
* 后端 status "pending" 映射为前端 "processing"
*/
export const toVoiceClone = (profile: VoiceCloneProfile): VoiceClone => ({
id: profile.id,
name: profile.name,
description: profile.description || "",
duration_seconds: 0,
status: profile.status === "pending" ? "processing" : profile.status,
progress: 0,
sample_url: profile.source_audio_url || undefined,
language: profile.language || "",
gender: profile.gender || "",
error_message: profile.error_message || null,
created_at: profile.created_at,
updated_at: profile.updated_at,
})
/** 格式化时长 */
export const formatDuration = (seconds: number): string => {
const m = Math.floor(seconds / 60)
const s = seconds % 60
return `${m}:${String(s).padStart(2, "0")}`
}
+264 -8
View File
@@ -8,15 +8,249 @@
*/
import React from "react"
import { useNavigate } from "react-router-dom"
import { Button } from "@/components/ui"
import { useAuthStore } from "@/store/authStore"
import { NavBar, Footer } from "./sections/Layout"
import { HeroSection } from "./sections/HeroSection"
import { FeatureSection } from "./sections/FeatureSection"
import { WorkflowSection } from "./sections/WorkflowSection"
import { PricingSection } from "./sections/PricingSection"
import { CTASection } from "./sections/CTASection"
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()
@@ -30,13 +264,35 @@ const HomePage: React.FC = () => {
return (
<div className="hp-page">
<NavBar />
{/* 顶部导航栏 */}
<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 />
{/* 底部 */}
<footer className="hp-footer">
<p>© 2026 小虾智剪 · AI智能视频创作平台</p>
</footer>
</div>
)
}
@@ -1,20 +0,0 @@
import React from "react"
import { useNavigate } from "react-router-dom"
import { Button } from "@/components/ui"
/** CTA 行动号召区域 */
export 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>
)
}
@@ -1,45 +0,0 @@
import React from "react"
const FEATURES = [
{
icon: "🤖",
title: "AI 智能剪辑",
desc: "自动识别视频高光片段,智能去除冗余内容,智能剪辑精彩短视频。",
},
{
icon: "🎙️",
title: "AI 配音克隆",
desc: "克隆您的声音,支持多种音色风格,自动生成自然流畅的配音。",
},
{
icon: "📝",
title: "智能字幕标题",
desc: "自动语音识别生成精准字幕,AI 创作吸睛标题,提升内容传播力。",
},
{
icon: "📱",
title: "多平台一键发布",
desc: "支持抖音、快手、小红书、微信视频号等主流平台,一键同步发布。",
},
]
/** 核心功能区域 */
export 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>
)
}
@@ -1,55 +0,0 @@
import React from "react"
import { useNavigate } from "react-router-dom"
import { Button } from "@/components/ui"
/** Hero 首屏区域 */
export 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>
)
}
@@ -1,34 +0,0 @@
import React from "react"
import { useNavigate } from "react-router-dom"
import { Button } from "@/components/ui"
/** 顶部导航栏 */
export const NavBar: React.FC = () => {
const navigate = useNavigate()
return (
<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>
)
}
/** 页脚 */
export const Footer: React.FC = () => (
<footer className="hp-footer">
<p>© 2026 小虾智剪 · AI智能视频创作平台</p>
</footer>
)
@@ -1,102 +0,0 @@
import React from "react"
import { useNavigate } from "react-router-dom"
import { Button } from "@/components/ui"
interface Plan {
name: string
price: string
period: string
desc: string
features: string[]
highlighted: boolean
cta: string
}
const PLANS: Plan[] = [
{
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: "联系销售",
},
]
/** 定价方案区域 */
export 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>
)
}
@@ -1,31 +0,0 @@
import React from "react"
const STEPS = [
{ icon: "📤", title: "上传素材", desc: "拖拽或选择视频素材,支持批量上传" },
{ icon: "🧠", title: "AI 处理", desc: "AI 自动分析、剪辑、配音、加字幕" },
{ icon: "👀", title: "预览调整", desc: "在线预览生成结果,支持微调编辑" },
{ icon: "🚀", title: "一键发布", desc: "多平台同步发布,追踪数据表现" },
]
/** 工作流程区域 */
export 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>
)
}