a603f5548c
- 新增 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)
12 KiB
Executable File
12 KiB
Executable File
前端工程
技术栈
Vite 6 + React 18 + TypeScript + zustand + react-router-dom
| 类别 | 选型 | 说明 |
|---|---|---|
| 构建 | Vite 6 | 开发服务器端口 3000,/api 代理到 localhost:8080 |
| UI 框架 | React 18 | 函数组件 + Hooks |
| 状态管理 | zustand | 轻量,支持 devtools 中间件 |
| 路由 | react-router-dom v6 | SPA 模式 |
| 实时通信 | HTTP 轮询 | 每 1.5s 轮询任务状态(WebSocket 待后端实现) |
| HTTP 请求 | fetch + 封装层 | 统一错误处理、响应解包 |
目录结构
frontend/src/
├── main.tsx # 入口
├── App.tsx # 根组件
├── api/ # API 封装层
│ ├── client.ts # fetch 封装:统一错误处理、响应解包(get/post/put/del)
│ ├── auth.ts # 注册/登录(真实后端调用)
│ ├── project.ts # 工程 CRUD + 风格 + 任务列表(mock 切换)
│ ├── generate.ts # POST /generate、GET /tasks/:taskId、GET /tasks/:taskId/assets
│ ├── prompt.ts # 提示词优化 + extractTags
│ ├── mock.ts # 多工程 Mock 数据 + 模拟 WebSocket 管线进度
│ └── types.ts # API 类型定义
├── stores/ # zustand stores
│ ├── auth.ts # 用户认证状态
│ ├── project.ts # 当前工程详情(含 draft/commit 风格编辑)
│ ├── projectList.ts # 工程列表(画廊页)
│ ├── task.ts # 任务草稿(生成表单状态)
│ ├── generation.ts # 生成状态(提交、轮询、结果)
│ ├── theme.ts # 主题切换(light/dark)
│ └── toast.ts # Toast 通知
├── pages/ # 页面级组件
│ ├── LoginPage.tsx # 登录页
│ ├── RegisterPage.tsx # 注册页
│ ├── HomePage.tsx # 项目画廊(工程列表 + 新建弹窗)
│ ├── ProjectDetailPage.tsx # 三栏工作台(配置 + 生成 + 任务列表)
│ └── ResultPage.tsx # 结果页(素材预览 + 下载)
├── components/ # 可复用组件
│ ├── Layout.tsx # 导航栏 + Outlet + ToastContainer
│ ├── StyleSelector.tsx # 预设风格选择器(pill 按钮)
│ ├── CustomTagsEditor.tsx # 自定义标签编辑器(可添加/删除)
│ ├── PromptEditor.tsx # 三段式提示词编辑器
│ ├── GenerateForm.tsx # 生成表单(素材类型 + 风格覆盖 + 提示词 + 参数)
│ ├── ProgressBar.tsx # 管线进度条
│ ├── AssetPreview.tsx # 素材预览
│ ├── ProjectCard.tsx # 画廊卡片
│ ├── CreateProjectModal.tsx # 新建工程弹窗
│ ├── ProjectConfigPanel.tsx # 左栏:工程配置编辑面板
│ ├── TaskListPanel.tsx # 右栏:任务列表面板
│ ├── StatusBadge.tsx # 状态徽标组件
│ ├── EmptyState.tsx # 空状态占位
│ ├── Skeleton.tsx # 加载骨架屏
│ └── Toast.tsx # Toast 通知容器
├── router/ # 路由定义
│ └── index.tsx
├── styles/ # 全局样式
│ └── global.css # 暗色/亮色主题 CSS 变量 + reset
└── utils/ # 工具函数
└── style.ts # 风格合并、预设分类、自定义标签工具
实现状态
| 模块 | 状态 | 说明 |
|---|---|---|
| API 封装层 | [x] | client.ts 统一 fetch 封装,auth.ts 真实调用,project/generate 使用 mock |
| Mock 层 | [x] | mock.ts 提供多工程 mock 数据 + 模拟 WebSocket 管线进度 |
| 认证流程 | [x] | 注册/登录/退出/token 持久化,真实后端对接 |
| zustand stores | [x] | auth/project/projectList/task/generation/theme/toast 七个 store |
| 页面 | [x] | 5 个页面(Login/Register/Home/ProjectDetail/Result) |
| 组件 | [x] | StyleSelector/CustomTagsEditor/PromptEditor/GenerateForm/ProgressBar/AssetPreview/ProjectCard/CreateProjectModal/ProjectConfigPanel/TaskListPanel/StatusBadge 等 |
| 路由 | [x] | 含 ProtectedRoute 守卫 |
| 自定义标签 | [x] | custom: 前缀存入 kvPairs,零后端改动 |
路由规划
| 路径 | 页面 | 说明 |
|---|---|---|
/ |
HomePage | 项目画廊,展示所有工程 |
/projects/:projectId |
ProjectDetailPage | 三栏工作台(配置 + 生成 + 任务列表) |
/projects/:projectId/tasks/:taskId |
ResultPage | 素材预览 + 下载 |
组件树
graph TD
App --> Router
Router --> HomePage
Router --> ProjectDetailPage
Router --> ResultPage
HomePage --> ProjectCard["ProjectCard(工程卡片)"]
HomePage --> CreateProjectModal["CreateProjectModal(新建弹窗)"]
CreateProjectModal --> StyleSelector
CreateProjectModal --> CustomTagsEditor
ProjectDetailPage --> ProjectConfigPanel["ProjectConfigPanel(左栏配置)"]
ProjectDetailPage --> GenerateForm["GenerateForm(中间上半)"]
ProjectDetailPage --> ProgressBar["ProgressBar(中间下半)"]
ProjectDetailPage --> TaskListPanel["TaskListPanel(右栏任务)"]
ProjectConfigPanel --> StyleSelector2["StyleSelector"]
ProjectConfigPanel --> CustomTagsEditor2["CustomTagsEditor"]
TaskListPanel --> StatusBadge["StatusBadge"]
GenerateForm --> PromptEditor["PromptEditor"]
GenerateForm --> StyleSelector3["StyleSelector(任务覆盖)"]
ResultPage --> AssetPreview["AssetPreview"]
三栏工作台布局
ProjectDetailPage 采用 CSS Grid 三栏布局:
grid-template-columns: 260px 1fr 320px
| 区域 | 宽度 | 内容 |
|---|---|---|
| 左栏 | 260px | 工程名(可编辑)、预设风格选择器、自定义标签编辑器、保存/取消按钮 |
| 中间 | flex-1 | 上半:生成表单;下半:当前任务进度条(提交后显示) |
| 右栏 | 320px | 任务列表(所有状态),15 秒自动刷新 |
核心交互流程
项目管理流程
sequenceDiagram
actor User
participant HP as HomePage
participant API as 后端 API
User->>HP: 进入首页
HP->>API: GET /api/v1/projects
API-->>HP: 工程列表
HP->>HP: 画廊展示 ProjectCard
User->>HP: 点击「新建工程」
HP->>HP: 弹出 CreateProjectModal
User->>HP: 输入名称、选择风格、添加自定义标签
HP->>API: POST /api/v1/projects
API-->>HP: 新工程
HP->>HP: 跳转到 ProjectDetailPage
生成流程
sequenceDiagram
actor User
participant PDP as ProjectDetailPage
participant API as 后端 API
User->>PDP: 填写 prompt、选择素材类型
User->>PDP: 点击提交
PDP->>API: POST /api/v1/generate
API-->>PDP: 返回 taskId
PDP->>PDP: ProgressBar 显示进度
loop 轮询任务状态(1.5s)
PDP->>API: GET /api/v1/tasks/:taskId
API-->>PDP: 任务状态 + 进度
PDP->>PDP: 更新 ProgressBar
end
API-->>PDP: status = completed
PDP->>API: GET /api/v1/tasks/:taskId/assets
API-->>PDP: 素材列表
PDP->>PDP: 显示「查看结果」按钮
风格编辑流程(手动保存)
sequenceDiagram
actor User
participant PCP as ProjectConfigPanel
participant Store as project store
participant API as 后端 API
User->>PCP: 进入工程页
PCP->>Store: startEditing() — 复制 style 到 draftStyle
User->>PCP: 修改预设风格 / 添加自定义标签
PCP->>Store: updateDraft(key, value) — 更新 draftStyle
Store->>Store: hasUnsavedChanges = true
alt 点击保存
User->>PCP: 点击「保存」
PCP->>Store: commitDraft()
Store->>API: PUT /api/v1/projects/:id/style
Store->>API: PUT /api/v1/projects/:id(如名称变更)
Store->>Store: style = draftStyle, hasUnsavedChanges = false
else 点击取消
User->>PCP: 点击「取消」
PCP->>Store: discardDraft()
Store->>Store: draftStyle = style, hasUnsavedChanges = false
end
自定义标签
在预设风格分类之外,用户可添加自定义标签。标签存储在工程风格的 kvPairs 中,使用 custom: 前缀区分:
{
"artStyle": "pixel",
"palette": "warm",
"custom:cel-shading": "true",
"custom:glow-effects": "true"
}
工具函数(utils/style.ts):
isCustomKey(key)— 判断是否为自定义标签getCustomTags(style)— 提取自定义标签列表addCustomTag(style, tag)— 添加标签removeCustomTag(style, tag)— 删除标签
extractTags() 函数在提取标签时自动包含自定义标签值。
状态管理
zustand stores
projectList store — 工程列表(HomePage)
interface ProjectListState {
projects: Project[];
loading: boolean;
loadProjects: () => Promise<void>;
createProject: (name: string, style?: Record<string, string>) => Promise<Project>;
deleteProject: (projectId: string) => Promise<void>;
}
project store — 当前工程详情(ProjectDetailPage)
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>;
startEditing: () => void;
updateDraft: (key: string, value: string) => void;
setDraftStyle: (style: Record<string, string>) => void;
setDraftName: (name: string) => void;
discardDraft: () => void;
commitDraft: () => Promise<void>;
}
task store — 任务草稿
interface TaskState {
prompt: string;
assetType: 'sprite' | 'background' | 'ui' | 'animation';
taskStyle: Record<string, string>;
params: TaskParams;
enableAI: boolean;
optimizedPrompt: string | null;
// ... setters, runOptimize, reset
}
generation store — 生成状态
interface GenerationState {
taskId: string | null;
stage: PipelineStage | null;
progress: number;
status: 'idle' | 'submitting' | 'running' | 'completed' | 'failed';
retryCount: number;
rejectReason: string | null;
assets: Asset[];
error: string | null;
submit: (req: GenerateRequest) => Promise<void>;
reset: () => void;
}
API 类型定义(api/types.ts)
interface Project {
id: string;
name: string;
style: { kvPairs: Record<string, string> };
createdAt: string;
taskCount?: number;
lastActivityAt?: string;
}
interface CreateProjectRequest {
name: string;
style?: Record<string, string>;
}
interface Task {
id: string;
projectId: string;
prompt: string;
assetType: string;
status: 'pending' | 'submitted' | 'running' | 'completed' | 'failed';
stage?: PipelineStage;
progress?: number;
retryCount?: number;
error?: string | null;
createdAt: string;
updatedAt: string;
}
interface Asset {
id: string;
key: string;
url: string;
format: string;
width: number;
height: number;
metadata: {
frameWidth?: number;
frameHeight?: number;
frameCount?: number;
directions?: number;
};
}
预设风格键分类
前端 StyleSelector 以分类标签组织,完整键值表见 预设风格键。
StyleSelector 两种使用场景:
- 工程风格(ProjectConfigPanel / CreateProjectModal):全量编辑,保存到后端
- 任务覆盖(GenerateForm):基于工程风格展示,仅记录差异