--- name: WQ_GUI 前端 Vue3 + Element Plus 脚手架 description: WQ_GUI 项目 frontend/ 目录的 Vite + Vue 3 + TS + Element Plus 最小可运行脚手架,以及 useTaskPoller 与 Element Plus UI 的接线模式 source: auto-skill extracted_at: '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.ts`:axios 单例 + 响应拦截器自动 unwrap,baseURL 走 `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/api` 和 `src/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 联调通过) ```json { "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` 关键字段 ```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) ```ts 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) // 全量注册图标 () for (const [name, component] of Object.entries(ElementPlusIconsVue)) { app.component(name, component) } app.mount('#app') ``` 联调期**全量注册最省事**;后期打包体积大再换 `unplugin-vue-components` 按需。 --- ## 3. useTaskPoller 接线模式(双实例) 训练 / 推断是**两条独立流水线**,各起一个 `useTaskPoller` 实例。核心套路:把 `task_id` 包成 `ref(null)`,composable 内部 `watch` 会**自动 start()**,无需手动调: ```ts import { ref, watch, computed } from 'vue' import { submitTrain, submitPredict, type TaskRecord } from './api/tasks' import { useTaskPoller } from './composables/useTaskPoller' // —— 训练 —— const trainTaskId = ref(null) const trainPoller = useTaskPoller(trainTaskId) // 传 ref 进去, 自动 watch async function onStartTrain() { const { task_id } = await submitTrain({ ... }) trainTaskId.value = task_id // 赋值后 watch 触发 start() } // —— 推断 —— const predictTaskId = ref(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 后的完整 `TaskRecord`;`record.value` 是任意时刻(含中间态)的最新记录。模板里同时展示用 `trainPoller.record.value ?? trainPoller.result.value`。 - `poller.isPolling.value` / `poller.status.value` / `poller.error.value` / `poller.taskId.value` 都是 `Ref`,模板里必须用 `.value`(它们是嵌套 ref,**Vue 模板不会自动 unwrap**)。 --- ## 4. el-progress 状态映射 `PollerStatus = 'idle' | 'PENDING' | 'PROCESSING' | 'SUCCESS' | 'FAILED'` `el-progress` 的 `status` 接受 `'' | 'success' | 'warning' | 'exception'`。 ```ts 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 渐变 + 卡片玻璃态) ```css .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. 启动与验证 ```bat 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.ts` 加 `server.proxy['/api']`。 - `feature_start` 后端接受 `number | string`;el-input v-model 出来是 string,**直接传给 API 即可**,后端会自己判别。 - `v-model` 绑 `ref(4)` 类型注解是必须的,否则 TS 会推断成 `Ref`,输入框失焦报错。 - `@element-plus/icons-vue` 全量注册后用 `` 调,本期 App.vue 没用到但留着扩展位。