refactor: 前端重构为项目+任务双核心模型
- 新增 HomePage 项目画廊页,支持新建/删除工程 - 新增 ProjectDetailPage 三栏工作台(配置+生成+任务列表) - 新增 CustomTagsEditor 支持自定义风格标签(custom: 前缀存入 kvPairs) - 重构 project store 为 draft/commit 模式,支持手动保存/取消 - 新增 projectList store 管理工程列表 - 扩展 mock.ts 为多工程数据结构 - 扩展 api/project.ts 新增项目 CRUD API(mock-gated) - 简化 ResultPage,仅展示素材预览 - 删除 GeneratePage、ProjectPage 及未使用的 hooks - 更新 docs/ 文档(frontend.md、api.md、style-keys.md、_index.md)
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useProjectStore } from '../stores/project'
|
||||
import { useTaskStore } from '../stores/task'
|
||||
import { useGenerationStore } from '../stores/generation'
|
||||
import { useToastStore } from '../stores/toast'
|
||||
import { extractTags } from '../api/prompt'
|
||||
import { mergeStyles } from '../utils/style'
|
||||
import type { GenerateRequest } from '../api/types'
|
||||
import ProjectConfigPanel from '../components/ProjectConfigPanel'
|
||||
import GenerateForm from '../components/GenerateForm'
|
||||
import ProgressBar from '../components/ProgressBar'
|
||||
import TaskListPanel from '../components/TaskListPanel'
|
||||
|
||||
export default function ProjectDetailPage() {
|
||||
const { projectId = '' } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const addToast = useToastStore(s => s.addToast)
|
||||
|
||||
const { style: projectStyle } = useProjectStore()
|
||||
const taskStore = useTaskStore()
|
||||
const {
|
||||
status,
|
||||
stage,
|
||||
progress,
|
||||
taskId,
|
||||
statusText,
|
||||
retryCount,
|
||||
rejectReason,
|
||||
submit,
|
||||
reset: resetGeneration,
|
||||
} = useGenerationStore()
|
||||
|
||||
// 重置生成状态(切换工程时)
|
||||
useEffect(() => {
|
||||
resetGeneration()
|
||||
taskStore.reset()
|
||||
}, [projectId]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// 失败时 toast
|
||||
useEffect(() => {
|
||||
if (status === 'failed') {
|
||||
const errText = useGenerationStore.getState().error || '未知错误'
|
||||
addToast({ type: 'error', message: `素材生成失败:${errText}` })
|
||||
}
|
||||
}, [status, addToast])
|
||||
|
||||
const handleSubmit = async (finalPrompt: string) => {
|
||||
const { taskStyle, params, enableAI, optimizedPrompt } = taskStore
|
||||
const mergedStyle = mergeStyles(projectStyle, taskStyle)
|
||||
const tags = extractTags(mergedStyle)
|
||||
|
||||
const req: GenerateRequest = {
|
||||
projectId,
|
||||
prompt: enableAI && optimizedPrompt ? optimizedPrompt : finalPrompt,
|
||||
assetType: taskStore.assetType,
|
||||
tags,
|
||||
projectStyle,
|
||||
taskStyle,
|
||||
resolution: params.resolution,
|
||||
directions: params.frames?.directions,
|
||||
framesPerDir: params.frames?.framesPerDirection,
|
||||
format: params.format,
|
||||
}
|
||||
|
||||
await submit(req)
|
||||
}
|
||||
|
||||
const handleViewResult = () => {
|
||||
if (taskId) navigate(`/projects/${projectId}/tasks/${taskId}`)
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
resetGeneration()
|
||||
taskStore.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '260px 1fr 320px',
|
||||
height: 'calc(100vh - 56px)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* 左栏:工程配置 */}
|
||||
<aside
|
||||
style={{
|
||||
borderRight: '1px solid var(--border)',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<ProjectConfigPanel />
|
||||
</aside>
|
||||
|
||||
{/* 中间:生成表单 + 进度 */}
|
||||
<main
|
||||
className="page-enter"
|
||||
style={{ overflow: 'auto', padding: '24px 32px' }}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32, maxWidth: 720 }}>
|
||||
{/* 上半:生成表单 */}
|
||||
<section>
|
||||
<h2 style={{ fontSize: 18, marginBottom: 20 }}>新建生成</h2>
|
||||
<GenerateForm onSubmit={handleSubmit} submitting={status === 'submitting'} />
|
||||
</section>
|
||||
|
||||
{/* 下半:当前任务进度 */}
|
||||
{status !== 'idle' && (
|
||||
<section className="card">
|
||||
<h2 style={{ fontSize: 16, marginBottom: 16 }}>生成进度</h2>
|
||||
<ProgressBar
|
||||
stage={stage}
|
||||
progress={progress}
|
||||
status={status}
|
||||
retryCount={retryCount}
|
||||
rejectReason={rejectReason}
|
||||
/>
|
||||
|
||||
{status === 'running' && (
|
||||
<p style={{ textAlign: 'center', color: 'var(--text-secondary)', marginTop: 12 }}>
|
||||
{statusText || '管线执行中,请稍候...'}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{status === 'completed' && (
|
||||
<div style={{ textAlign: 'center', marginTop: 16 }}>
|
||||
<p style={{ color: 'var(--success)', marginBottom: 12 }}>
|
||||
生成完成
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: 12, justifyContent: 'center' }}>
|
||||
<button className="btn-primary" onClick={handleViewResult}>
|
||||
查看结果
|
||||
</button>
|
||||
<button className="btn-secondary" onClick={handleReset}>
|
||||
继续生成
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'failed' && (
|
||||
<div style={{ textAlign: 'center', marginTop: 16 }}>
|
||||
<p style={{ color: 'var(--error)', marginBottom: 12 }}>
|
||||
{useGenerationStore.getState().error || '生成失败'}
|
||||
</p>
|
||||
<button className="btn-primary" onClick={handleReset}>
|
||||
重新开始
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* 右栏:任务列表 */}
|
||||
<aside
|
||||
style={{
|
||||
borderLeft: '1px solid var(--border)',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<TaskListPanel />
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user