feat(frontend): 构建前端 MVP,实现完整 UI 框架与 mock 数据层
- 安装 zustand + react-router-dom 依赖 - 修复 Vite 代理配置,添加 /auth 路由转发 - 实现 API 封装层(client.ts 统一 fetch、auth.ts 真实调用) - 实现 mock 数据层(mock.ts 模拟 WebSocket 管线进度) - 实现 4 个 zustand stores(auth/project/task/generation) - 实现 5 个组件(StyleSelector/PromptEditor/GenerateForm/ProgressBar/AssetPreview) - 实现 5 个页面(Login/Register/Project/Generate/Result) - 实现路由含 ProtectedRoute 守卫 - 添加暗色主题全局样式 - 更新前端文档与 API 文档
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { useEffect } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useTaskStore } from '../stores/task'
|
||||
import { useGenerationStore } from '../stores/generation'
|
||||
import GenerateForm from '../components/GenerateForm'
|
||||
import ProgressBar from '../components/ProgressBar'
|
||||
|
||||
export default function GeneratePage() {
|
||||
const { projectId = 'proj-default' } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const { prompt, assetType, reset: resetTask } = useTaskStore()
|
||||
const {
|
||||
status,
|
||||
stage,
|
||||
progress,
|
||||
retryCount,
|
||||
rejectReason,
|
||||
taskId,
|
||||
submit,
|
||||
reset: resetGeneration,
|
||||
} = useGenerationStore()
|
||||
|
||||
// 组件卸载时重置生成状态
|
||||
useEffect(() => {
|
||||
return () => resetGeneration()
|
||||
}, [resetGeneration])
|
||||
|
||||
// 完成后自动跳转
|
||||
useEffect(() => {
|
||||
if (status === 'completed' && taskId) {
|
||||
const timer = setTimeout(() => {
|
||||
navigate(`/projects/${projectId}/tasks/${taskId}`)
|
||||
}, 1500)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [status, taskId, projectId, navigate])
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await submit(projectId, prompt, assetType)
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
resetGeneration()
|
||||
resetTask()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container" style={{ paddingTop: 24, paddingBottom: 40 }}>
|
||||
{/* 顶栏 */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 32,
|
||||
}}
|
||||
>
|
||||
<h1 style={{ fontSize: 24 }}>新建生成</h1>
|
||||
<Link
|
||||
to={`/projects/${projectId}`}
|
||||
className="btn-secondary"
|
||||
style={{
|
||||
padding: '8px 20px',
|
||||
fontSize: 13,
|
||||
borderRadius: 'var(--radius)',
|
||||
}}
|
||||
>
|
||||
返回工程
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* 生成表单 */}
|
||||
{status === 'idle' || status === 'submitting' ? (
|
||||
<GenerateForm onSubmit={handleSubmit} submitting={status === 'submitting'} />
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
|
||||
{/* 进度条 */}
|
||||
<ProgressBar
|
||||
stage={stage}
|
||||
progress={progress}
|
||||
status={status}
|
||||
retryCount={retryCount}
|
||||
rejectReason={rejectReason}
|
||||
/>
|
||||
|
||||
{/* 状态提示 */}
|
||||
{status === 'running' && (
|
||||
<p style={{ textAlign: 'center', color: 'var(--text-secondary)' }}>
|
||||
管线执行中,请稍候...
|
||||
</p>
|
||||
)}
|
||||
|
||||
{status === 'completed' && (
|
||||
<p style={{ textAlign: 'center', color: 'var(--success)' }}>
|
||||
✓ 生成完成,正在跳转到结果页...
|
||||
</p>
|
||||
)}
|
||||
|
||||
{status === 'failed' && (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<p style={{ color: 'var(--error)', marginBottom: 16 }}>生成失败</p>
|
||||
<button className="btn-primary" onClick={handleReset}>
|
||||
重新开始
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user