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:
2026-05-25 16:15:31 +08:00
parent 2bc0ec2eaa
commit a603f5548c
26 changed files with 1397 additions and 644 deletions
@@ -0,0 +1,115 @@
import { useState } from 'react'
import StyleSelector from './StyleSelector'
import CustomTagsEditor from './CustomTagsEditor'
interface CreateProjectModalProps {
open: boolean
onClose: () => void
onSubmit: (name: string, style: Record<string, string>) => Promise<void>
}
export default function CreateProjectModal({
open,
onClose,
onSubmit,
}: CreateProjectModalProps) {
const [name, setName] = useState('')
const [style, setStyle] = useState<Record<string, string>>({})
const [submitting, setSubmitting] = useState(false)
if (!open) return null
const handleStyleChange = (key: string, value: string) => {
setStyle(prev =>
prev[key] === value
? (() => {
const { [key]: _, ...rest } = prev
return rest
})()
: { ...prev, [key]: value }
)
}
const handleSubmit = async () => {
if (!name.trim()) return
setSubmitting(true)
try {
await onSubmit(name.trim(), style)
setName('')
setStyle({})
onClose()
} finally {
setSubmitting(false)
}
}
return (
<div
style={{
position: 'fixed',
inset: 0,
zIndex: 1000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
onClick={onClose}
>
<div
style={{
position: 'absolute',
inset: 0,
background: 'rgba(0, 0, 0, 0.5)',
}}
/>
<div
className="card"
style={{
position: 'relative',
width: '100%',
maxWidth: 520,
maxHeight: '80vh',
overflow: 'auto',
zIndex: 1,
}}
onClick={e => e.stopPropagation()}
>
<h2 style={{ fontSize: 18, marginBottom: 20 }}>新建工程</h2>
<label style={{ display: 'block', marginBottom: 16 }}>
<span style={{ display: 'block', fontSize: 13, fontWeight: 600, marginBottom: 6, color: 'var(--text-secondary)' }}>
工程名称
</span>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="输入工程名称"
style={{ width: '100%' }}
/>
</label>
<div style={{ marginBottom: 16 }}>
<span style={{ display: 'block', fontSize: 13, fontWeight: 600, marginBottom: 8, color: 'var(--text-secondary)' }}>
风格配置
</span>
<StyleSelector value={style} onChange={handleStyleChange} compact />
<CustomTagsEditor style={style} onChange={setStyle} />
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
<button className="btn-secondary" onClick={onClose} disabled={submitting}>
取消
</button>
<button
className="btn-primary"
onClick={handleSubmit}
disabled={!name.trim() || submitting}
>
{submitting ? '创建中...' : '创建'}
</button>
</div>
</div>
</div>
)
}