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,109 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useProjectStore } from '../stores/project'
|
||||
import { useToastStore } from '../stores/toast'
|
||||
import StyleSelector from './StyleSelector'
|
||||
import CustomTagsEditor from './CustomTagsEditor'
|
||||
import Skeleton from './Skeleton'
|
||||
|
||||
export default function ProjectConfigPanel() {
|
||||
const { projectId = '' } = useParams()
|
||||
const {
|
||||
draftName,
|
||||
draftStyle,
|
||||
hasUnsavedChanges,
|
||||
loading,
|
||||
loadProject,
|
||||
loadStyle,
|
||||
startEditing,
|
||||
updateDraft,
|
||||
setDraftStyle,
|
||||
setDraftName,
|
||||
discardDraft,
|
||||
commitDraft,
|
||||
} = useProjectStore()
|
||||
const addToast = useToastStore(s => s.addToast)
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId) {
|
||||
loadProject(projectId)
|
||||
loadStyle(projectId)
|
||||
startEditing()
|
||||
}
|
||||
}, [projectId, loadProject, loadStyle, startEditing])
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await commitDraft()
|
||||
addToast({ type: 'success', message: '配置已保存' })
|
||||
} catch {
|
||||
addToast({ type: 'error', message: '保存失败' })
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ padding: 16 }}>
|
||||
<Skeleton variant="text" lines={6} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<div style={{ padding: '16px 16px 12px', borderBottom: '1px solid var(--border)' }}>
|
||||
<input
|
||||
type="text"
|
||||
value={draftName}
|
||||
onChange={e => setDraftName(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
padding: '4px 0',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
outline: 'none',
|
||||
borderBottom: '2px solid transparent',
|
||||
}}
|
||||
onFocus={e => (e.target.style.borderBottomColor = 'var(--accent)')}
|
||||
onBlur={e => (e.target.style.borderBottomColor = 'transparent')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: '12px 16px' }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8, color: 'var(--text-secondary)' }}>
|
||||
工程风格
|
||||
</div>
|
||||
<StyleSelector value={draftStyle} onChange={updateDraft} compact />
|
||||
<CustomTagsEditor style={draftStyle} onChange={setDraftStyle} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
padding: 12,
|
||||
borderTop: '1px solid var(--border)',
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={handleSave}
|
||||
disabled={!hasUnsavedChanges}
|
||||
style={{ flex: 1, padding: '8px 0', fontSize: 13 }}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
<button
|
||||
className="btn-secondary"
|
||||
onClick={discardDraft}
|
||||
disabled={!hasUnsavedChanges}
|
||||
style={{ flex: 1, padding: '8px 0', fontSize: 13 }}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user