Files
WQ_GUI/.qwen/skills/wq_gui_frontend_scaffold/SKILL.md

7.7 KiB
Raw Blame History

name, description, source, extracted_at
name description source extracted_at
WQ_GUI 前端 Vue3 + Element Plus 脚手架 WQ_GUI 项目 frontend/ 目录的 Vite + Vue 3 + TS + Element Plus 最小可运行脚手架,以及 useTaskPoller 与 Element Plus UI 的接线模式 auto-skill 2026-06-02T08:17:33.116Z

WQ_GUI 前端脚手架 (Vue 3 + Element Plus)

适用场景

为 WQ_GUI FastAPI 后端 (127.0.0.1:8000) 搭建一个最小可联调的浏览器控制台。 后端已暴露:

  • POST /api/modeling/train{ task_id, status, kind }
  • POST /api/modeling/predict{ task_id, status, kind }
  • GET /api/tasks/{task_id}TaskRecord(含 PENDING/PROCESSING/SUCCESS/FAILED + 模型指标 / 输出路径)
  • GET /api/algorithms → 算法清单

前端已有 (frontend/src/):

  • api/request.tsaxios 单例 + 响应拦截器自动 unwrapbaseURL 走 VITE_API_BASE_URL 缺省 http://127.0.0.1:8000
  • api/tasks.ts:所有提交 / 查询函数 + 完整 TaskRecord / TaskStatus / TaskKind 类型
  • composables/useTaskPoller.ts:完整轮询 composable支持 3 种用法(静态 / 响应式 taskId / 手动)

1. 一次性补齐的脚手架文件

frontend/ 初始状态只有 src/apisrc/composables,缺整个 Vite 骨架。直接照下面这 7 个文件铺一遍:

frontend/
├── .env.development       # VITE_API_BASE_URL=http://127.0.0.1:8000
├── .gitignore             # node_modules / dist / .vite
├── env.d.ts               # vite/client + ImportMeta + *.vue shim
├── index.html             # 挂载 #app
├── package.json
├── tsconfig.json          # 严格模式 + @ → src + bundler resolution
├── tsconfig.node.json     # 给 vite.config.ts 用
├── vite.config.ts         # @ alias + 0.0.0.0:5173
└── src/
    ├── main.ts
    └── App.vue

锁定版本2026-06 联调通过)

{
  "dependencies": {
    "vue": "^3.4.27",
    "element-plus": "^2.7.5",
    "@element-plus/icons-vue": "^2.3.1",
    "axios": "^1.7.2"
  },
  "devDependencies": {
    "@types/node": "^20.12.12",
    "@vitejs/plugin-vue": "^5.0.4",
    "typescript": "^5.4.5",
    "vite": "^5.2.11",
    "vue-tsc": "^2.0.19"
  }
}

@types/node 必加——vite.config.ts 用了 import { fileURLToPath, URL } from 'node:url',否则 npm run build 类型检查必挂。

tsconfig.json 关键字段

  • "moduleResolution": "bundler"
  • "allowImportingTsExtensions": true(配合 vue-tsc --noEmit
  • "paths": { "@/*": ["src/*"] } + "baseUrl": "."
  • "include": ["src/**/*.vue"]vue-tsc 才会处理 SFC
  • "references": [{ "path": "./tsconfig.node.json" }]

vite.config.ts 关键字段

resolve: {
  alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
},
server: { host: '0.0.0.0', port: 5173 },

0.0.0.0 方便局域网真机调试;端口冲突时 strictPort: false 允许 Vite 自动 +1。


2. main.ts 模板(全量注册 Element Plus

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus)

// 全量注册图标 (<el-icon><Cpu /></el-icon>)
for (const [name, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(name, component)
}
app.mount('#app')

联调期全量注册最省事;后期打包体积大再换 unplugin-vue-components 按需。


3. useTaskPoller 接线模式(双实例)

训练 / 推断是两条独立流水线,各起一个 useTaskPoller 实例。核心套路:把 task_id 包成 ref<string | null>(null)composable 内部 watch自动 start(),无需手动调:

import { ref, watch, computed } from 'vue'
import { submitTrain, submitPredict, type TaskRecord } from './api/tasks'
import { useTaskPoller } from './composables/useTaskPoller'

// —— 训练 ——
const trainTaskId = ref<string | null>(null)
const trainPoller = useTaskPoller(trainTaskId)   // 传 ref 进去, 自动 watch

async function onStartTrain() {
  const { task_id } = await submitTrain({ ... })
  trainTaskId.value = task_id   // 赋值后 watch 触发 start()
}

// —— 推断 ——
const predictTaskId = ref<string | null>(null)
const predictPoller = useTaskPoller(predictTaskId)
const modelId = ref('')

// 训练一成功, model_id 自动填入推断输入框
watch(
  () => trainPoller.result.value?.model_id,
  (newId) => { if (newId) modelId.value = newId },
)

async function onStartPredict() {
  const { task_id } = await submitPredict({ model_id: modelId.value, ... })
  predictTaskId.value = task_id
}

关键点

  • trainPoller.result.value 才是 SUCCESS 后的完整 TaskRecordrecord.value 是任意时刻(含中间态)的最新记录。模板里同时展示用 trainPoller.record.value ?? trainPoller.result.value
  • poller.isPolling.value / poller.status.value / poller.error.value / poller.taskId.value 都是 Ref,模板里必须用 .value(它们是嵌套 refVue 模板不会自动 unwrap)。

4. el-progress 状态映射

PollerStatus = 'idle' | 'PENDING' | 'PROCESSING' | 'SUCCESS' | 'FAILED' el-progressstatus 接受 '' | 'success' | 'warning' | 'exception'

function progressOf(status: string): number {
  switch (status) {
    case 'idle':
    case 'PENDING':   return 10
    case 'PROCESSING':return 60
    case 'SUCCESS':
    case 'FAILED':    return 100
    default:          return 0
  }
}
function progressStatusOf(s: string): '' | 'success' | 'exception' {
  if (s === 'SUCCESS') return 'success'
  if (s === 'FAILED')  return 'exception'
  return ''
}

模板里 v-if="poller.isPolling.value || poller.status.value === 'SUCCESS' || poller.status.value === 'FAILED'" 控制展示。


5. CSS深色控制台风slate 渐变 + 卡片玻璃态)

.app-root {
  min-height: 100vh;
  background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%);
  color: #e2e8f0;
}
.panel {
  background: rgba(30, 41, 59, 0.7) !important;
  border: 1px solid rgba(148, 163, 184, 0.18) !important;
}
.app-main {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* 左训练 / 右推断 */
  gap: 20px;
}
@media (max-width: 960px) { .app-main { grid-template-columns: 1fr; } }

深色背景下 Element Plus 的 el-form-item__label / el-descriptions__label 默认是黑色文字,必须 :deep() 覆盖成浅色。


6. 启动与验证

cd /d D:\111\office\ZHLduijie\1.WQ\WQ_GUI\frontend
npm install
npm run dev

打开 http://127.0.0.1:5173/,联调期望路径:

  1. 左侧「开始训练」→ 立即拿到 task_id + 黄色 轮询中 + 进度条 60%
  2. 后端 SUCCESS → 进度条变绿,下面出现 model_id 标签 + R²/RMSE/MAE
  3. 右侧 model_id 被自动填入 → 「开始推断」→ 走 output_zarr_path 展示
  4. 任何一步 FAILED → 进度条变红 + 后端 error 字段

7. 已知 caveat

  • 第一次 npm install 约 150MB,要耐心等。
  • useTaskPoller 已有 onUnmounted 自动清理,不要再手写 clearInterval
  • request.ts 注释里写明 FastAPI dev 期 allow_origins=["*"]不需要配 Vite proxy;如果未来后端收紧 CORS再在 vite.config.tsserver.proxy['/api']
  • feature_start 后端接受 number | stringel-input v-model 出来是 string直接传给 API 即可,后端会自己判别。
  • v-modelref<number | string>(4) 类型注解是必须的,否则 TS 会推断成 Ref<number>,输入框失焦报错。
  • @element-plus/icons-vue 全量注册后用 <el-icon><Cpu /></el-icon> 调,本期 App.vue 没用到但留着扩展位。