6.7 KiB
6.7 KiB
平台化架构设计
目标
将 Painting / Video 两套硬编码分支重构为统一的平台描述符架构,使加入新平台只需新建一个文件夹并实现标准接口,零改动 dialogBox。
背景
当前 dialogBox/index.vue(792 行)通过 v-if="type === 'Painting'" / v-if="type === 'Video'" 承载两套完全不同的逻辑:
- 模型列表:Painting 走后端 API,Video 走静态 JSON
- 参数 schema:Painting 走本地 JS 文件,Video 走远程 workflow JSON
- UI 控件:Painting 有 proportion/dimension/quality/quantity,Video 有 pattern/proportion/time
- 任务 body:Painting 扁平 modelParams,Video
{ workflowId, nodeInfoList }
后续模型参数后端化后,所有平台将统一为"API 获取模型列表 + API 获取参数 schema"模式。
核心设计:平台描述符模式
每个平台封装为一个文件夹,导出 definePlatform() 工厂函数,返回标准接口对象。dialogBox 退化为纯渲染引擎。
平台接口
// src/platforms/<name>/index.js
export function definePlatform() {
// 响应式状态(各平台自定义)
const model = ref(defaultValue)
const modelType = ref(defaultValue)
const state = reactive({ ... })
// 模型选择器组件
const ModelSelector = markRaw(Component)
// 参数控件列表(有序,决定渲染顺序)
const controls = [
{
name: 'proportion',
component: markRaw(ProportionComponent),
show: (config) => config?.params?.some(p => p.ui === 'proportion'),
props: (config) => ({ /* 额外 props */ }),
},
// ...
]
// 图片上传器(可选)
const ImageUploader = markRaw(Component) | null
return {
id: 'painting', // 平台标识
label: 'AI绘画2026', // 显示标题
ModelSelector, // 模型选择器组件
controls, // 有序控件列表
ImageUploader, // 图片上传器组件(可选)
state, // 平台自定义响应式状态
model, // 当前模型 ref
modelType, // 当前模型类型 ref
async loadModels() { }, // 获取模型列表
async loadConfig(modelName) { }, // 获取模型参数配置
buildTaskBody(state) { }, // 构造请求 body
getDefaultModel() { }, // 默认模型名称
isImageRequired(state) { }, // 是否必须上传图片
}
}
控件绑定约定
dialogBox 渲染控件时自动处理 name 与 state 的 v-model 绑定:
modelValue→state[name]onUpdate:modelValue→state[name] = v
控件 descriptor 仅需定义 show 条件(基于 modelConfig 上下文)和额外 props。
平台注册表
// src/platforms/registry.js
import { definePaintingPlatform } from './painting/index.js'
import { defineVideoPlatform } from './video/index.js'
const registry = { Painting, Video }
export function createPlatform(type) {
const factory = registry[type]
if (!factory) throw new Error(`未找到平台: ${type}`)
return factory()
}
dialogBox 角色变化
dialogBox 接收 type prop → 调用 createPlatform(type) → 获得 descriptor → 据 descriptor 渲染一切:
- 渲染
<component :is="platform.ModelSelector"> - 遍历
platform.controls,show返回 true 的渲染<component :is> handleStart()委托给platform.buildTaskBody(state)→ 调用taskPolling.generate(body)watch(model)委托给platform.loadConfig(name)
数据流
flowchart TD
subgraph dialogBox["dialogBox 编排层"]
A["platform.loadModels()"] --> B[模型选择器渲染]
B --> C["watch(model) → platform.loadConfig(name)"]
C --> D[计算 visibleControls]
D --> E["v-for 渲染 controls(自动 v-model)"]
E --> F["handleStart → platform.buildTaskBody()"]
F --> G["taskPolling.generate(body)"]
end
subgraph platform["platform 包"]
H["loadModels() → API"]
I["loadConfig(name) → API"]
J["buildTaskBody() → 扁平 body"]
end
G --> K[POST /suanli/v1/tasks]
K --> L[轮询 → displayStore 更新虚拟滚动列表]
目录结构
src/
├── platforms/ # 平台包(新增)
│ ├── painting/
│ │ ├── index.js # definePlatform()
│ │ ├── modelSelector.vue
│ │ ├── imageUploader.vue
│ │ └── controls/
│ │ ├── proportion.vue
│ │ ├── dimension.vue
│ │ ├── quality.vue
│ │ └── quantity.vue
│ ├── video/
│ │ ├── index.js
│ │ ├── modelSelector.vue
│ │ ├── imageUploader.vue
│ │ └── controls/
│ │ ├── pattern.vue
│ │ ├── proportion.vue
│ │ └── time.vue
│ └── registry.js
│
├── components/
│ ├── dialogBox/index.vue # 精简后 ~200 行
│ ├── Popover/ # 共享基础组件(不变)
│ ├── Select/ # 共享基础组件(不变)
│ ├── Img/ # 共享基础组件(不变)
│ └── virtual-scroller/ # 共享基础组件(不变)
│
├── apis/ # API 层(不变)
├── utils/
│ ├── taskPolling.js # 任务轮询(不变)
│ ├── request.js # Axios(不变)
│ └── modelApi.js # 平台 API 封装
│
├── config/
│ ├── models/ # 逐步废弃
│ ├── runninghub/ # 逐步废弃
│ └── plugins.js # 不变
迁移路径
| 步骤 | 内容 | 影响范围 |
|---|---|---|
| 1 | 新建 src/platforms/ + registry.js,不删旧代码 |
纯新增 |
| 2 | Painting 迁入 descriptor,dialogBox 切换读取路径 | dialogBox 精简 |
| 3 | Video 迁入 descriptor | dialogBox 继续精简 |
| 4 | 删除旧代码:config/models/、config/runninghub/、modelConfig.js |
清理 |
| 5 | 后端化:各平台 loadConfig() 改为调 API |
仅改 descriptor 内部 |
每步独立提交,方便回滚。
不变更的部分
taskPolling.js:任务创建和轮询逻辑通用,不变displayStore:虚拟滚动列表状态通用,不变Popover、Select、Img、virtual-scroller:共享 UI 组件home/index.vue:仍然传typeprop,不变apis/、request.js:HTTP 层不变config/plugins.js、router、stores:不变