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
+63 -8
View File
@@ -5,17 +5,28 @@ interface ProjectState {
projectId: string
name: string
style: Record<string, string>
draftStyle: Record<string, string>
draftName: string
hasUnsavedChanges: boolean
loading: boolean
loadProject: (projectId: string) => Promise<void>
loadStyle: (projectId: string) => Promise<void>
updateStyle: (key: string, value: string) => void
saveStyle: () => Promise<void>
startEditing: () => void
updateDraft: (key: string, value: string) => void
setDraftStyle: (style: Record<string, string>) => void
setDraftName: (name: string) => void
discardDraft: () => void
commitDraft: () => Promise<void>
updateName: (name: string) => Promise<void>
}
export const useProjectStore = create<ProjectState>((set, get) => ({
projectId: '',
name: '',
style: {},
draftStyle: {},
draftName: '',
hasUnsavedChanges: false,
loading: false,
loadProject: async (projectId) => {
@@ -25,7 +36,10 @@ export const useProjectStore = create<ProjectState>((set, get) => ({
set({
projectId: project.id,
name: project.name,
draftName: project.name,
style: project.style.kvPairs,
draftStyle: { ...project.style.kvPairs },
hasUnsavedChanges: false,
loading: false,
})
} catch {
@@ -35,15 +49,56 @@ export const useProjectStore = create<ProjectState>((set, get) => ({
loadStyle: async (projectId) => {
const kvPairs = await projectApi.getStyle(projectId)
set({ style: kvPairs })
set({ style: kvPairs, draftStyle: { ...kvPairs }, hasUnsavedChanges: false })
},
updateStyle: (key, value) => {
set(state => ({ style: { ...state.style, [key]: value } }))
startEditing: () => {
const { style, name } = get()
set({ draftStyle: { ...style }, draftName: name, hasUnsavedChanges: false })
},
saveStyle: async () => {
const { projectId, style } = get()
await projectApi.saveStyle(projectId, style)
updateDraft: (key, value) => {
set(state => {
const next = { ...state.draftStyle }
if (next[key] === value) {
delete next[key]
} else {
next[key] = value
}
return {
draftStyle: next,
hasUnsavedChanges: true,
}
})
},
setDraftStyle: (style) => set({ draftStyle: style, hasUnsavedChanges: true }),
setDraftName: (name) => set({ draftName: name, hasUnsavedChanges: true }),
discardDraft: () => {
const { style, name } = get()
set({ draftStyle: { ...style }, draftName: name, hasUnsavedChanges: false })
},
commitDraft: async () => {
const { projectId, draftStyle, draftName, name } = get()
// save style
await projectApi.saveStyle(projectId, draftStyle)
// save name if changed
if (draftName !== name) {
await projectApi.updateProject(projectId, { name: draftName })
}
set({
style: { ...draftStyle },
name: draftName,
hasUnsavedChanges: false,
})
},
updateName: async (name) => {
const { projectId } = get()
await projectApi.updateProject(projectId, { name })
set({ name, draftName: name })
},
}))
+37
View File
@@ -0,0 +1,37 @@
import { create } from 'zustand'
import type { Project } from '../api/types'
import * as projectApi from '../api/project'
interface ProjectListState {
projects: Project[]
loading: boolean
loadProjects: () => Promise<void>
createProject: (name: string, style?: Record<string, string>) => Promise<Project>
deleteProject: (projectId: string) => Promise<void>
}
export const useProjectListStore = create<ProjectListState>((set, get) => ({
projects: [],
loading: false,
loadProjects: async () => {
set({ loading: true })
try {
const projects = await projectApi.listProjects()
set({ projects, loading: false })
} catch {
set({ loading: false })
}
},
createProject: async (name, style) => {
const project = await projectApi.createProject(name, style)
set({ projects: [project, ...get().projects] })
return project
},
deleteProject: async (projectId) => {
await projectApi.deleteProject(projectId)
set({ projects: get().projects.filter(p => p.id !== projectId) })
},
}))