Vue3实战:构建可复用的Coze工作流客户端
2026/9/15 13:05:36 网站建设 项目流程

简介:面向自媒体内容创作者与前端开发者的 Vue 3 版 Coze 工作流合集,覆盖内容规划、设计、开发到发布全流程,帮助独立博主或团队在最新技术栈上高效搭建自媒体项目,尤其适合需要将创意快速落地的场景。资源包共 130 个文件,压缩后仅 484KB,主体为 80 个 Vue 组件,配合 TypeScript 类型定义、CSS/SCSS 样式、HTML 入口页面及 JSON 配置等,结构简洁清晰,便于按模块裁剪与复用。已有 186 人学习下载,适合具备一定 Vue 基础、希望借助成熟工作流提升开发效率的开发者。其中不仅包含界面交互模板、自动化测试与持续集成配置,还提供了可实际操作的功能流程示例,便于理解工作流的运行机制;借助 Vue 3 Composition API 的组织方式,可灵活管理组件状态与逻辑复用,减少重复劳动,快速搭建出契合自身需求的自媒体开发环境。

1. 为什么需要一个 Vue3 版的 Coze workflow 客户端

一个 Coze workflow 发布之后,真正拿它交付业务的时候,多数团队要的不是把用户导到扣子平台里点按钮,而是把它嵌进自己的后台管理系统:用户在工单页点一下,内部系统要自动调用这个工作流跑完流程,结果直接渲染在页面表格里。这就是 vue3 版 coze workflow 这件事的实质——用 Vue3 写一个最小可用的前端,把 Coze 工作流的节点编排、参数入口和结果输出搬进自己的产品界面。

它解决的核心问题是:让 workflow 不再只是一个独立页面里的演示功能,而是一个可以被异步调用、按参数返回结果的技术端点。适合那些已经搭好 workflow、需要前端落地页的开发者,也适合想在一套 Vue3 后台里统一管理多个 Bot 和对话流的架构师。后面所有内容都围绕一个目标展开:在本地把 Vue3 前端跑起来,然后让它和 Coze workflow API 完成一次真实对话。

2. Coze workflow 的运行机制与 API 结构

2.1 agent 和 workflow,什么时候该用 workflow 而不是让模型自由发挥

Coze 平台提供 agent 和 workflow 两种主要编排方式,很多人一开始分不清。agent 适合开放式的问答场景,大模型自己决定调用哪些工具、按什么顺序执行,写起来快,但结果不可控,返佣、超时、脏数据这些问题都很难在事后排查。workflow 则相反,它把处理路径固化成一串节点,每个节点做什么、输入输出是什么,在设计阶段就已经确定。

我的经验是,凡是涉及结构化数据的场景——按单据号查状态、把 markdown 转 word、批量审核内容、多系统数据汇总——都优先用 workflow。它的可观测性远好于 agent:每个节点的输入输出都可以单独追踪,账单能算清楚,出问题能定位到具体节点。表格里这几点值得对比:

维度workflowagent
执行路径预先编排,节点顺序固定大模型在运行中动态决定
适合场景结构化任务、多系统串接、必须复现结果开放问答、需要临场推理
调试成本单节点断点,结果可复现相同输入结果可能不同
输出控制由结束节点明确定义需要额外指令约束格式

所以当你拿到一个既有的 Coze workflow 时,前端要做的事情其实很纯粹:按它开始节点定义的参数把用户输入送进去,再把结束节点的输出渲染出来。不要在前端里尝试重新解释业务逻辑,那部分越薄越好。

2.2 workflow/run 的请求参数与返回结构

Coze 开放平台对外暴露的 workflow 调用接口是POST /v1/workflow/run,Vue3 这一侧的所有封装最终都落在这个端点上。这个接口的鉴权方式是 Bearer Token,也就是请求头里的Authorization: Bearer {token},token 在平台个人访问令牌页面生成。请求体里最关键的四个字段如下:

字段类型必填说明
bot_idstring工作流对应的 Bot ID,在 Bot 编排页的发布信息里复制
user_idstring业务侧的用户标识,Coze 用它做调用日志检索
parametersobject按需工作流开始节点定义的输入参数,键名要与节点变量一致
is_asyncboolean是否异步执行,前端交互一般用同步模式

非流式调用的返回值结构比较固定。HTTP 状态 200 不代表业务成功,判断结果的字段是codecode为 0 才表示执行成功,失败时msg里会有具体错误信息。真正要展示的内容都在data字段里,注意这个字段的值是一个被 JSON 字符串化的对象,前端必须先JSON.parse再使用。比如工作流结束节点的输出是{ "answer": "..." },那么data会变成"{\"answer\":\"...\"}",直接取data.answer会拿到 undefined。

2.3 流式响应与普通响应的选择

如果你只是做一个内部工具,用普通响应就够了,实现简单,一个axios.post就能拿到完整结果。但如果你面对的是对话类 workflow,或者用户需要长时间等待多节点执行,就必须考虑流式响应。流式模式下 Coze 服务端返回的是text/event-stream,前端打开一个可读流,逐段读取模型生成的内容,做出打字机效果,这在体验上比让用户盯着 loading 转圈强很多。

流式响应的事件类型主要有两类:messagedonemessage事件里的data是一个字符串化的 JSON,其中content字段是本次增量文本;done事件表示整个流程结束。需要注意的是,流式响应并非全部文本一次性到达,中间可能穿插节点开始、结束这些事件,前端解析时要按event字段分流,不能把每个data:都当作最终答案拼接,否则会把节点元信息也混进对话内容里。

3. 用 Vite 创建 Vue3 项目并接通 workflow API

3.1 创建 Vue3 项目与最小依赖安装

这一步用 Vite 完成。标题里的 vue3 版最终要落在一个能跑的项目里,而 Vite 是当前 Vue3 项目最常规的脚手架,创建命令如下:

npm create vite@latest coze-workflow-vue -- --template vue-ts cd coze-workflow-vue npm install npm install element-plus axios pinia

--template vue-ts会把项目模板指定为 Vue3 加 TypeScript,后面的 workflow 调用逻辑需要定义类型,TS 能帮你在编译期就发现参数名写错的问题。element-plus不是必须项,但你如果要做后台管理系统风格的界面,直接用它是成本最低的方案。axios用于普通 HTTP 调用,pinia用来管理对话状态,后续内容都会以这两个库为基础。

项目生成后,先到src/main.ts里挂上 pinia 和 Element Plus,再定义一个src/types/coze.ts文件,把 Coze 接口相关类型放在一起。

// src/types/coze.ts export interface CozeWorkflowRequest { bot_id: string user_id: string parameters: Record<string, string | number | boolean> } export interface CozeWorkflowResponse { code: number msg: string data: string } export interface CozeStreamEvent { event: 'message' | 'done' | 'error' data?: string }

CozeWorkflowResponse.data特意保留为string而不是object,原因就是上一节说的——Coze 返回的 data 是字符串化的,在代码里明确这个类型,使用前就不会忘记做一次JSON.parse

3.2 用 TypeScript 封装 workflow 调用模块

创建一个src/api/coze.ts作为调用入口,底部导出两个函数:runWorkflow走普通请求,streamWorkflow走流式。先看普通请求的封装,这是整个接入过程的最小可运行代码。

// src/api/coze.ts import axios from 'axios' import type { CozeWorkflowRequest, CozeWorkflowResponse } from '../types/coze' const BASE_URL = 'https://api.coze.cn/v1/workflow/run' const TOKEN = import.meta.env.VITE_COZE_TOKEN export async function runWorkflow( params: CozeWorkflowRequest, timeoutMs = 15000 ): Promise<CozeWorkflowResponse> { const { data } = await axios.post<CozeWorkflowResponse>( BASE_URL, params, { headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json', }, timeout: timeoutMs, } ) if (data.code !== 0) { throw new Error(`workflow 执行失败: ${data.msg}`) } return data }

这里把timeout单独暴露给调用方是刻意的。workflow 的节点数量决定执行耗时,三五个节点的轻量流程通常在两秒内返回,但包含大模型生成节点的流程很可能超过十秒。把超时时间做成参数,UI 层就可以根据具体工作流的复杂度来设置,不需要为最慢的场景统一调大所有请求的超时时间。

VITE_COZE_TOKEN这个环境变量从.env文件读取。项目根目录下新建.env.local,写入VITE_COZE_TOKEN=你的token,Vite 启动时会自动加载。注意VITE_前缀不能省,没有这个前缀的变量不会暴露给前端代码。

3.3 用 fetch 解析流式输出,实现打字机效果

流式调用不能使用 axios,因为 axios 对text/event-stream的支持不完整,对增量数据的处理需要自己拼 buffer。最干净的方式是直接用浏览器原生 fetch,把响应体当作一个ReadableStream来消费。这里给出一个按行解析的版本。

export async function streamWorkflow( params: CozeWorkflowRequest, onDelta: (chunk: string) => void, timeoutMs = 60000 ): Promise<void> { const controller = new AbortController() const timer = setTimeout(() => controller.abort(), timeoutMs) try { const resp = await fetch(BASE_URL, { method: 'POST', headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify(params), signal: controller.signal, }) if (!resp.ok || !resp.body) { throw new Error(`workflow 请求异常: ${resp.status}`) } const reader = resp.body.getReader() const decoder = new TextDecoder('utf-8') 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) { const trimmed = line.trim() if (!trimmed.startsWith('data:')) continue const payload = JSON.parse(trimmed.slice(5).trim()) if (payload.event === 'message') { const inner = JSON.parse(payload.data ?? '{}') if (typeof inner.content === 'string') { onDelta(inner.content) } } if (payload.event === 'error') { throw new Error(JSON.stringify(payload)) } } } } finally { clearTimeout(timer) } }

这段代码的三个关键点。第一,line.startsWith('data:')限定只处理 SSE 数据行,兼容event:字段交错出现。第二,返回的data字段是 JSON 字符串,inner才是真正的模型输出对象,里面才有content。第三,controller.abort()会在超时时打断整个流式链路,避免用户在 workflow 卡住时无限等待。调用方只需要把onDelta里拿到的字符串片段追加到响应式消息上,打字机效果自然就出现了。

4. 把 workflow 对话流接进 Vue3 组件:状态与 UI 编排

4.1 用 Pinia 管理会话消息与运行状态

对话流在界面上表现为一组消息,核心状态只有两个:消息数组以及“是否正在运行”。Pinia 的 store 设计应该保持精简,不要把 workflow 参数都塞进去,那是 API 层的事情。下面这个 store 足够撑起一个单轮直出的 workflow 页面。

// src/stores/chat.ts import { defineStore } from 'pinia' export interface ChatMessage { role: 'user' | 'assistant' content: string } export const useChatStore = defineStore('chat', { state: () => ({ messages: [] as ChatMessage[], running: false, }), actions: { async send(text: string) { const userMessage: ChatMessage = { role: 'user', content: text } const botMessage: ChatMessage = { role: 'assistant', content: '' } this.messages.push(userMessage, botMessage) this.running = true try { await streamWorkflow( { bot_id: import.meta.env.VITE_COZE_BOT_ID, user_id: `web-${Date.now()}`, parameters: { query: text }, }, (delta) => { botMessage.content += delta } ) } finally { this.running = false } }, }, })

user_id这里用时间戳临时生成,生产环境应该换成登录态里的真实用户 ID。Coze 的日志系统会按 user_id 聚合调用记录,如果所有人共享一个固定值,出问题时就无法区分具体是哪个用户触发的失败。parameters里的query是开始节点上定义的变量名,你在 Coze 编排页给节点起的变量名是什么,这里就写什么,不能自己另起名字。

4.2 消息列表与输入框的组件组织

组件拆分建议按三条路径展开:ChatView.vue负责整体布局,MessageList.vue负责渲染消息流,ChatInput.vue负责采集输入与触发发送。父组件只持有 store 引用,不需要自己维护 props 下发。

<!-- src/views/ChatView.vue --> <script setup lang="ts"> import { useChatStore } from '../stores/chat' import MessageList from '../components/MessageList.vue' import ChatInput from '../components/ChatInput.vue' const chat = useChatStore() </script> <template> <div class="chat-container"> <MessageList :messages="chat.messages" /> <ChatInput :disabled="chat.running" @send="chat.send" /> </div> </template>

MessageList里渲染每条消息时,用一个v-for遍历messages,根据role决定气泡靠左还是靠右。assistant消息的容器必须使用white-space: pre-wrap,否则大模型返给用户的换行和缩进会全部消失。还有一个容易忽略的问题:流式追加内容时,Vue 默认会把每次内容变更当成一次 DOM 更新,如果你的消息体很长,需要在MessageList的根节点上监听滚动,让新内容出现时列表自动滚到底部。这里不要用定时器轮询scrollTop,直接在消息 push 后的 next tick 里用scrollIntoView即可。

4.3 把 workflow 节点状态可视化成步骤卡片

很多运营类 workflow 由十几个串行节点组成,用户点击执行后,界面最好能展示“当前已经跑到哪一步”。做法是在 workflow 的开始节点里把节点名称作为参数传入,同时 Coze 在流式响应里会返回节点执行事件,前端监听这些事件并更新一个步骤卡片列表。

<script setup lang="ts"> const stepState = ref<Record<string, 'pending' | 'running' | 'done'>>({}) function markNodeRunning(nodeName: string) { stepState.value[nodeName] = 'running' } </script> <template> <div class="step-cards"> <div v-for="(state, name) in stepState" :key="name" class="step-card" :data-state="state"> {{ name }} </div> </div> </template>

这里的核心价值是让等待过程可见,用户知道工作流在哪个环节耗时,而不是面对一个空洞的转圈图标。实现时需要你在streamWorkflow的解析逻辑里把event对应的节点名称事件也回调出来,仅处理你关心的几个节点即可,不要试图渲染全部系统内部事件,那会把界面搞成一张调试日志表。

4.4 流式失败时的降级策略

流式接口偶发断流是常态。设置一个降级开关:如果 8 秒内一个message事件都没收到,自动重新发起一次普通请求,用非流式结果补全消息内容。这里的关键是前端在发起 flow 请求前记录开始时间,第一个块到达时清除定时器。这个策略不需要复杂的状态机,一个setTimeout加一个receivedFirstChunk布尔值就能实现,但它是实际使用中保证页面可用性最重要的一道保险。

5. workflow 接入的鉴权、部署与验证

5.1 在服务端加一层转发,别把 token 发给浏览器

上一章代码里把VITE_COZE_TOKEN直接写在环境变量里,这只适合本地开发。真正部署时,前端代码里的环境变量会被打包进 JS 文件,浏览器用户打开开发者工具就能看到你的 token,这会带来调用额度被刷的风险。上线前必须移除前端 token,改为在服务端保留凭证。

常见做法是在 Node.js 服务端加一个/api/coze路由,前端只请求你自己的域名,由服务端把请求转发到 Coze。Node 侧示例:

// server/coze.js import express from 'express' import process from 'process' const router = express.Router() router.post('/run', async (req, res) => { const upstream = await fetch('https://api.coze.cn/v1/workflow/run', { method: 'POST', headers: { Authorization: `Bearer ${process.env.COZE_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ bot_id: process.env.COZE_BOT_ID, user_id: req.body.userId, parameters: req.body.parameters, }), }) res.status(upstream.status) for await (const chunk of upstream.body) res.write(chunk) res.end() }) export default router

token 只存在于服务端环境变量里,前端代码无论如何也读不到。这一层还能顺便做请求频率限制、IP 白名单、调用日志记录。不要省这一步,工作流一个节点的成本可能是一次大模型调用,token 泄露直接等于钱包泄露。

5.2 Vue3 项目在 win 服务器 Nginx 上的路由回退配置

Vue3 项目部署时最容易在服务器上遇到 404,原因是 SPA 路由刷新时 Nginx 会把/chat这类路径当成文件去找。vite build 之后把dist目录放到服务器上,再给站点加一条路由回退配置即可:

location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; }

try_files先检查路径是否存在物理文件,没有就回退到 index.html,交给 Vue Router 接管。如果你的页面里嵌了 iframe,注意给 iframe 的父容器显式设置宽高,否则 Vue3 组件里外层 div 的点击事件可能因为 iframe 把事件吞掉而无法触发,这类问题多检查一下嵌套层级的pointer-events和遮挡关系,比从框架代码里找原因更快。

5.3 三个最常踩的 workflow 对接坑与验证命令

对接流程跑不通,九成问题出在三个地方。第一是bot_id填错,从 Coze 平台复制时带了空格或换行,这种错误接口会直接返回bot not found。第二是parameters里的键名和开始节点变量名不一致,Coze 对多余参数不报错,但你的业务字段永远不会出现在后续节点里,表现就是节点输出为空,排查起来很隐蔽。第三是流式模式判断失误,接口返回的 content-type 是text/event-stream,前端如果用 axios 接收,拿到的 response.data 会被解析成字符串而不是对象。

逐层排查时先别急着打开页面调 UI,直接用 curl 验证一条链路:

curl -sS -X POST 'https://api.coze.cn/v1/workflow/run' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{"bot_id":"YOUR_BOT_ID","user_id":"debug-user","parameters":{"query":"你好"}}'

能看到code: 0并且data里有可解析的 JSON,再回到 Vue3 项目里查前端问题。如果 curl 这一步都报鉴权错误,先回平台重新生成一次 token,确认复制时没有隐藏字符。这条链路通了,剩下的工作只是把流式解析的 UI 细节打磨到位。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询