ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vue3接入Coze Workflow:打造可视化工作流调度前端

Vue3接入Coze Workflow:打造可视化工作流调度前端 简介基于Vue 3技术栈适配的coze工作流工具包面向自媒体内容创作者、前端开发者及内容型团队旨在解决内容平台从界面搭建到自动化测试、持续集成等环节的效率问题。压缩包共一百三十个文件体积约484KB核心是八十个Vue组件并配有十五个TypeScript文件、CSS/SCSS样式配置、JSON配置、HTML模板等分别支撑组件逻辑、类型检查、视觉主题与页面框架。目前已有186人学习下载。工作流内置了基于Composition API的可复用模板与示例流程同时预置ESLint、包管理器配置和自动化测试框架方便工程化落地还提供可用于验证功能的测试特性流程项目便于二次开发。无论是个人博主还是协作团队都可借鉴其中的模块划分与实践经验快速搭建并维护自媒体平台减少重复劳动将精力放在内容创意与质量提升上适合中高级前端开发者直接应用。1. coze workflow 压缩包里那一堆 vue3 代码到底在解决什么问题拿到一个vue3 版coze workflow.zip很多人的第一反应是把它当成一个“不用再写后端”的万能工程包直接解压跑。但实际上这类压缩包解决的问题非常具体coze 工作流本身有编排能力、有节点执行日志却没有一个适合自己业务方使用的操作界面而 vue3 工程恰好承担的是“workflow 的调度台 配置面板 运行时状态看板”三重角色。它不是在替代 coze而是把 coze 的 workflow 引擎当成一个可调用的后端服务再用 vue3 的组件化能力把工作流的启动、暂停、错误重试、节点状态渲染都变成浏览器里可交互的东西。这个定位决定了整套代码的结构最外层是 vue3 工程中间是 coze workflow 的 API 封装层最底层是节点协议和运行时状态协议。国内团队拿到这个 zip 最常见的落地场景是给没有前端经验的运营同学做一个“投喂 Excel、点一下按钮就能跑完整个 agent 工作流”的内部工具或者是把 coze workflow 嵌进已有的 vue3 后台管理系统里当作一个可视化编排模块。适合看这篇文章的读者是已经用过 coze 或类似平台能理解 workflow 和 agent 的区别但还没想清楚 vue3 前端该以什么姿势接入 coze 工作流传参、收结果、画节点的人。2. 拆解 vue3 版 coze workflow 的工程骨架与选型理由2.1 为什么这类 zip 几乎都是 vite vue3 ts 的组合下载过几个同类压缩包之后会发现解压出来的 package.json 里基本都是vue: ^3.4.0、vite: ^5.x、typescript: ^5.x这个组合很少有纯 js 的版本。这不是巧合而是因为 coze workflow 的请求参数和响应体是严格的分层结构字段嵌套很深用 TypeScript 的 interface 把请求 payload 钉死比靠记忆拼对象字段要可靠得多。vite 则负责把开发服务器的秒级热更新带给 workflow 调试场景——你改一个节点参数浏览器里立刻能看到效果这对反复调 workflow 的行为参数非常关键。vue3 的Composition API在这里也派上了用场。coze workflow 的一次运行涉及多个异步阶段开始执行、节点运行中、节点完成、整个工作流完成。如果用 options API这些状态要散落在data、methods、watch三个地方很难跟踪而setup函数里可以用ref和computed把运行过程变成一个连续的状态流。这也是 vue3 版压缩包和 vue2 版最大的分水岭vue2 里模拟 workflow 状态通常靠 event bus 或 vuexvue3 里直接用一个自定义组合式函数useWorkflowRunner就能搞定。2.2 一个标准 zip 内部的目录结构长什么样我见过多个不同团队出的vue3 版coze workflow包目录结构虽然不完全一致但基本都遵循一个原则把 coze 相关的代码隔离在src/core里业务页面只和src/core暴露出来的接口打交道。这样一个区域是“平台相关”的另一个是“业务相关”的。典型结构如下vue3-coze-workflow/ ├── src/ │ ├── core/ │ │ ├── types/ │ │ │ ├── workflow.ts # coze workflow 协议类型 │ │ │ └── node.ts # 节点类型与节点输出类型 │ │ ├── api/ │ │ │ └── cozeClient.ts # coze API 封装统一处理鉴权和请求 │ │ ├── runner/ │ │ │ └── useWorkflowRunner.ts # 组合式函数管理运行状态机 │ │ └── visual/ │ │ └── nodeStyle.ts # 节点类型到样式/图标的映射 │ ├── views/ │ │ ├── WorkflowList.vue # 工作流列表页 │ │ ├── WorkflowEditor.vue # 工作流配置页 │ │ └── WorkflowRunLog.vue # 运行日志页 │ ├── stores/ │ │ └── workflowStore.ts # pinia 状态管理 │ └── App.vue ├── .env.development # 开发环境变量指向 coze 测试空间 ├── .env.production # 生产环境变量指向 coze 正式空间 └── vite.config.ts注意types/workflow.ts这个文件在整个工程里地位很高。它存在的意义是让前端 vscode 在写模板时自动提示出node_id、workflow_id、conversation_id这些字段减少把workflow_id和conversation_id混用的低级错误。如果你拿到的 zip 里没有这个文件说明封装者图省事把参数直接放在请求函数里建议你自己补上后面调试会舒服很多。2.3 把 coze 的 workflow 协议在 vue3 里钉死coze 的 workflow 接口从形态上看和一般 HTTP API 有一点非常不同它的一次完整调用通常包含“启动执行”和“获取执行结果”两步或者直接使用支持流式返回的接口一连接就陆续吐出各节点的执行事件。为了在 vue3 前端统一处理这两类情况我一般会把协议类型定义成下面这样// src/core/types/workflow.ts export interface CozeWorkflowStartRequest { workflow_id: string; parameters: Recordstring, unknown; conversation_id?: string; is_async: boolean; } export interface CozeWorkflowNodeEvent { event: node_start | node_end | workflow_finished | error; node_id: string; node_title?: string; output?: unknown; cost_ms?: number; } export interface WorkflowRunState { status: idle | running | success | failed; currentNodeId: string | null; nodeOutputs: Recordstring, unknown; errorMsg: string; }这段类型里最值得关注的是WorkflowRunState它不是 coze 协议的一部分而是你自己根据前端需要抽象出来的运行状态模型。status四个值对应了页面顶部状态条的四种颜色currentNodeId用来在画布上高亮正在执行的节点nodeOutputs是每个节点执行完成后的输出快照。把协议层和视图层之间加这个中间类型前端代码就不会直接散落着node_start这种让人摸不着头脑的字符串。提示参数名以 coze 开放平台的真实接口为准这里展示的是同类 zip 常见的简化逻辑。不同版本平台对async模式的支持程度不一样is_async字段在有的版本里叫async接入前要顺手在 coze 平台的 API 调试页里点开确认一下别被旧代码带偏。3. 在 vue3 里写一个能直接对话 coze workflow 的调用层3.1 先想清楚用普通 HTTP 还是 SSE 流式接入vue3 版 coze workflow源码包最常见的坑是封装层把 coze 接口当普通 axios 请求来用——发个 POST 然后等 JSON 返回。这样做对于简短的 workflow 没问题一旦 workflow 里有多个 agent 节点或需要调用外部工具一个流程可能要跑几十秒前端页面就只能 blank 在那里转圈用户会忍不住刷新。所以靠谱的做法是支持 SSEServer-Sent Events。coze 平台本身在实时返回上大多支持流式前端用EventSource或者fetchReadableStream都能接。我倾向于用fetchReadableStream而不是EventSource原因有两个第一EventSource只能发 GET 请求但 coze workflow 启动通常要求 POST避免把一长串参数塞在 URL 里第二fetch流式读取时能顺手读取 HTTP 状态码身份过期时能立刻跳登录而不是等流结束才发现挂掉。// src/core/api/cozeClient.ts export async function runWorkflowStream( baseUrl: string, token: string, workflowId: string, parameters: Recordstring, unknown, onEvent: (event: CozeWorkflowNodeEvent) void ): Promisevoid { const response await fetch(${baseUrl}/v1/workflow/run, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${token} }, body: JSON.stringify({ workflow_id: workflowId, parameters, stream: true }) }); if (!response.ok) { throw new Error(coze workflow request failed: ${response.status}); } const reader response.body!.getReader(); const decoder new TextDecoder(); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop()!; for (const line of lines) { if (line.startsWith(data:)) { const event JSON.parse(line.slice(5).trim()) as CozeWorkflowNodeEvent; onEvent(event); } } } }这段代码的核心逻辑是把 SSE 推送的按行数据切分成事件。服务端推送的每一行以data:开头后面跟一段 JSONsplit(\n)配合buffer变量是为了应对一次网络包只读到半行数据的情况。node_start和node_end事件都会从onEvent回调里冒出来页面就能据此实时更新节点高亮。decoder.decode第二参数的stream: true是关键它能正确处理多字节中文字符跨包的问题否则工作流输出里带 emoji 或中文时会出现乱码。3.2 用 pinia 管理 workflow 的运行状态而不是把状态放在组件里在 vue3 项目里接入多页 workflow 时一个很容易踩的坑是WorkflowEditor.vue和WorkflowRunLog.vue是两个独立路由但共用同一次工作流运行的上下文。如果每个页面各自发请求就做不到一边看节点高亮一边看日志面板的联动效果。正确做法是把当前运行状态放进 pinia store让所有页面都是同一个运行状态的消费者。// src/stores/workflowStore.ts import { defineStore } from pinia; import { ref, computed } from vue; export const useWorkflowStore defineStore(workflow, () { const runState refWorkflowRunState({ status: idle, currentNodeId: null, nodeOutputs: {}, errorMsg: }); const startRun async (workflowId: string, params: Recordstring, unknown) { runState.value.status running; runState.value.errorMsg ; runState.value.nodeOutputs {}; await runWorkflowStream( import.meta.env.VITE_COZE_API_URL, import.meta.env.VITE_COZE_TOKEN, workflowId, params, (event) { if (event.event node_start) { runState.value.currentNodeId event.node_id; } if (event.event node_end) { runState.value.nodeOutputs[event.node_id] event.output; } if (event.event workflow_finished) { runState.value.status success; } if (event.event error) { runState.value.status failed; runState.value.errorMsg String(event.output || unknown error); } } ).catch(err { runState.value.status failed; runState.value.errorMsg err.message; }); }; const reset () { runState.value { status: idle, currentNodeId: null, nodeOutputs: {}, errorMsg: }; }; return { runState, startRun, reset }; });这段 store 用了 vue3 的 setup store 写法好处是ref、computed和普通函数可以随便混用代码量比 options 写法少三分之一。注意startRun内部没有用同步await压制事件处理而是边流转边更新runState这样界面上的节点高亮不是一次性跳到最后而是逐个节点地走过去。用户能看到当前卡在哪个环节是 coze 平台的真实行为也是前端能复现工作流运行过程的核心价值。3.3 异常处理哪些错误值得 vue3 前端挽回coze workflow 调用失败的类型比普通 HTTP 接口更多样。我总结了一个适合前端做分支处理的错误表格错误现象可能原因vue3 侧动作HTTP 401 时响应体为空token 过期或注销清空本地存储并跳登录页SSE 流中途断开工作流执行超过平台单次限制提示稍后查询历史记录事件里带error字段且 output 为空节点内部异常展示所在节点标题并终止高亮请求发出后 5 秒无任何事件参数类型不匹配或 workflow_id 失效直接终止并显示“参数校验失败”这些情况不是靠finally统一收尾就能解决的前端的用户提示要有区分度。比如执行超时和中途断流虽然都表现为“卡住”前者是等待时间过长后者我们看到的是status从running直接变failed提示文案要分别做。我会在useWorkflowRunner.ts里加一个最长等待计时器收到第一个事件时清零缓存计时器这也是一般 zip 包容易缺的一部分。4. 把 workflow 节点在 vue3 页面上画出来并回填运行状态4.1 节点画布选型grid 布局加 SVG 连线是性价比最高的方案vue3 版 coze workflow的压缩包里最让人有“高级感”的部分就是可视化画布。但直接上全套图编辑器的方案比如牵扯到复杂的自绘节点、自由缩放连线对多数业务来说太重了。一个内部工具的画布真正需要的功能其实是三点节点能排布、节点能选中、运行时有高亮。用vue-grid-layout或新版grid-layout当作节点摆放底座再用一个svg层画连线是整个方案里成本最低的做法。script setup langts import GridLayout from grid-layout; import { computed } from vue; import { useWorkflowStore } from /stores/workflowStore; const props defineProps{ nodes: WorkflowNode[]; edges: WorkflowEdge[]; }(); const store useWorkflowStore(); const layout computed(() props.nodes.map((node, index) ({ x: node.x ?? (index % 3) * 3, y: node.y ?? Math.floor(index / 3) * 3, w: 3, h: 3, i: node.id }))); /script这里computed的作用是让布局数组从原始节点数据中自动生成不要手工维护两个数组否则拖动节点后数据不同步会很难查。w: 3和h: 3是设计稿里节点卡片的固定最小尺寸如果后续要支持不同节点类型的不同大小可以把这两个值从节点数据里读取。4.2 节点展示与运行时状态高亮联动真正让画布“活”起来的是把 store 里的currentNodeId映射到节点卡片的 class 上。这一步很简单但和节点的视觉设计强相关下边的代码展示最直接的做法template div classworkflow-canvas grid-layout v-model:layoutlayout :col-num12 :row-height30 :is-draggablefalse :is-resizablefalse grid-item v-fornode in nodes :keynode.id :inode.id classworkflow-node :class{ is-running: node.id store.runState.currentNodeId, is-done: !!store.runState.nodeOutputs[node.id], is-error: node.id store.runState.currentNodeId store.runState.status failed } span{{ node.title }}/span span v-ifstore.runState.nodeOutputs[node.id] classnode-status 已完成 {{ (store.runState.nodeOutputs[node.id] as any).cost_ms ?? }} /span /grid-item /grid-layout /div /template这段模板里的is-running和is-done状态直接读取了runState.currentNodeId和nodeOutputs。CSS 里通常会把is-running设为黄色边框加呼吸灯动画is-done变为绿色边框is-error变为红色闪烁。这样用户在 workflow 跑起来以后不需要看日志也能一眼看出流程卡在哪个 agent 节点上。如果你发现日志里显示节点已完成但画布没变化基本是event.node_id和前端node.id对不上这是不同 zip 包最常见的“状态回填失效”原因可以在v-for里把node.id打印出来对比。4.2.1 用 Vue 的 computed 生成可拖拽的自适应节点尺寸有些压缩包里还带自适应分辨率的功能本质是把节点的像素尺寸变成可响应式。grid-layout本身支持根据容器宽度自动换算所以只要你在节点数据里存的是相对栅格值0 到 12 之间的网格数页面窗口变宽时节点会自动排布。要注意的是想实现这个效果就必须保证节点数据里的x、y、w、h全部有默认值否则首次渲染会闪一下再跳位。这个跳动问题在网速慢时特别明显解决办法是渲染前先对这些字段做一次归零处理。4.3 用动态组件渲染不同类型的节点详情coze workflow 的节点类型很多普通代码节点、大模型节点、agent 节点、条件分支节点。它们最明显的差异在配置面板。如果你在 vue3 里用v-if堆五种分支页面会很难维护。更好的做法是把每种配置面板做成独立组件然后用动态组件component :is...渲染。例如在WorkflowNodeConfig.vue里template component :isconfigComponentMap[node.type] :nodeactiveNode updatehandleNodeUpdate / /template script setup langts import CodeNodeConfig from ./nodes/CodeNodeConfig.vue; import LLMNodeConfig from ./nodes/LLMNodeConfig.vue; import AgentNodeConfig from ./nodes/AgentNodeConfig.vue; const configComponentMap: Recordstring, Component { code: CodeNodeConfig, llm: LLMNodeConfig, agent: AgentNodeConfig }; /script这里configComponentMap是一个把字符串节点类型映射到组件的对象新增一种节点时只需扩展 map 和新增一个子组件改动范围非常小。和热词里常提的 “agent 和 workflow” 区别在这里也能体现出来agent 节点内部有自己独立的提示词和工具列表所以它的配置组件通常比普通节点大很多在 vue3 里用动态组件可以避免把 agent 节点的复杂配置混进通用节点的代码里。5. 让 zip 里的工程直接能跑的几个实用配置技巧5.1 多环境切换不靠改代码靠 .env 文件拿到 zip 后第一件事不是急着npm run dev而是检查.env.development和.env.production它们决定你跑起来后连的是 coze 的哪个空间。开发用测试空间生产用正式空间这是最基本的隔离要求。vite 对.env文件的支持是默认的变量名必须以VITE_开头才能暴露给前端代码# .env.development VITE_COZE_API_URLhttps://api.coze.cn VITE_COZE_BOT_IDyour_bot_id VITE_COZE_WORKFLOW_IDyour_workflow_id VITE_COZE_TOKENyour_development_tokenimport.meta.env.VITE_COZE_API_URL在 vue3 工程里可以直接读取。注意VITE_COZE_TOKEN不建议直接明文放在.env.development里提交到 git 仓库更好的是把它放到本地未跟踪的.env.local文件里.gitignore要记得加上.env.local避免密钥泄露到团队仓库。生产环境的 token 更不建议写在前端代码里应该在 coze 控制台开启服务端签名校验前端只保留一个“临时换取 token”的接口。5.2 生产部署时把代理配好避免跨域和路径问题本地开发时npm run dev通常不会遇到跨域因为 vite 的 proxy 能把/coze前缀转发到 coze 平台但生产环境常常是把前端 build 之后的 dist 放到 nginx 里此时如果没有反向代理配置前端的 API 请求会直接发到你的业务域名的/coze/...路径下造成 404 或者 405 错误。需要在vite.config.ts里先确认转发关系// vite.config.ts 片段 export default defineConfig({ server: { proxy: { /coze: { target: https://api.coze.cn, changeOrigin: true, rewrite: (path) path.replace(/^\/coze/, ) } } } });changeOrigin: true表示把请求头的Host改成目标域名这是后端校验来源时的关键。在 vue3 开发环境下你的fetch(/coze/v1/workflow/run)会被代理转发到https://api.coze.cn/v1/workflow/run而生产环境的 nginx 也要做同样的location /coze/反向代理并且要额外处理路径里的特殊字符否则请求参数里带中文时会被 nginx 默认拦截成 400。5.3 节点画布深色模式与高分辨率屏适配coze workflow 的平台原版界面是浅色为主但很多 vue3 后台管理系统天生是深色皮肤。把节点画布嵌进去的时候最突兀的就是白色卡片浮在深色界面上。这里我建议不要自己写一堆深色样式而是用 CSS 变量把节点卡片的底色、边框、文字全部抽象出来.workflow-node { background: var(--wf-node-bg, #ffffff); border: 1px solid var(--wf-node-border, #e5e7eb); color: var(--wf-node-text, #1f2937); }然后在工程入口根据当前主题给:root设置变量值深色和浅色只换变量不重复写组件样式。关于高分屏适配核心是grid-layout的rowHeight用固定像素在 2k 屏幕上节点会显得偏小所以要在挂载时根据window.innerWidth重置一次布局的缩放比例。这个技巧虽然消耗不了多少代码但如果没做整个画布在 4k 屏上就会像手机界面放大一样模糊且拥挤。5.4 验证 zip 工程是否完整的三个检查点最后分享一个三分钟验证法。解压缩后不要急着瞎跑先检查三项第一package.json里是否有vue、pinia、vue-router三个依赖缺一个说明这不是完整 vue3 工程骨架第二看src/core/types/里有没有前文提到的WorkflowRunState类型如果所有节点状态都用字符串硬编码那么它的运行日志页一定是常年需要人工迁就的第三把入口main.ts里的createApp打开看一眼是否use(createPinia())没装 pinia 的 zip 大概率还是 vue2 时代的思维后面接 coze 流式数据时会很别扭。这三项都通过再接上你自己的 coze 空间和 token就能跑通从节点配置到运行高亮的完整链路。本文还有配套的精品资源点击获取
返回列表