
简介vue3 版 coze workflow.zip 是一套针对自媒体内容创作者与前端开发团队的 Coze 工作流资源包基于 Vue 3 深度适配可帮助读者在最新技术栈上高效完成从内容规划、界面设计到开发发布的全流程。资源共 130 个文件以 Vue 组件为主辅以 TypeScript 类型文件、PNG 图片、CSS/SCSS 样式另含 JSON 配置、HTML 入口、Markdown 说明和 YAML 配置压缩包仅 484KB轻量且结构清晰。目前已有 186 人学习下载。内容涵盖多种实用模板与工具覆盖界面交互、自动化测试、持续集成等环节并附带 empty_file.txt 与 coze-flow-test-feature-flow 示例项目便于理解 Coze 流程的配置与运行机制减少重复劳动使开发者能更专注于内容创意与质量。这套工作流适合需要在 Vue 3 环境中搭建自媒体项目、提升开发效率的前端人员或内容团队使用。1. 为什么要把 Coze 工作流前端搬回 Vue 3 自己控Coze 平台上拖拽节点、连边、跑测试的那套编辑体验并不是只有靠官方页面才能用。把它的视觉语言抽出来做成一套 Vue 3 工作流前端模板是很多做自媒体中台、知识库问答、内容生产工具链的团队正在做的事。手里这份 zip 就是干这个用的结构上有四个 CSS 文件和一个双入口配置上有 ESLint 和 package.json但没有 src 目录也没有复杂业务逻辑。与其说是完整项目不如说是 Coze 风格工作流界面在 Vue 3 下的起点。适合两类人一是想把工作流节点编排直接嵌进自己内容平台的开发者二是刚把技术栈从 Vue 2 往 Vue 3 迁移、想拿真实界面练手的人。2. 资源包拆解package.json、ESLint 与 Vue 3 工作流工程的边界先看清这是不是一个能直接npm run dev的完整工程。zip 里没有 src、没有 vite.config、也没有 node_modules说明它被裁剪过只保留主题样式、入口页和工程约束文件。对开发者来说这反而干净因为你能直接复用样式体系再按自己的目录规范补业务层。常见做法是把它作为 templates 目录铺进 Vite 工程用官方脚手架先建外层骨架再把这份样式和入口并进去。2.1 文件清单与职责先把十个文件的职责划清楚后面动手时才知道哪些能直接用、哪些要改文件职责使用建议package.json记录 Vue 依赖与脚本对齐 Vue/Vite 大版本后再补装依赖.eslintrc.js约束 Vue 3 组件与脚本写法团队已有规范时以本文件为基线扩展.gitignore忽略 node_modules/dist 等目录直接保留index.htmlVite 构建入口用于本地开发和构建产物index-cdn.html外链 CDN 加载 Vue 的演示页只给产品/设计快速看效果variables.css颜色、圆角、阴影等设计令牌主题定制第一优先改这里theme-default.css默认主题完整样式基线和 variables.css 配套改style.css统一 import 其余样式入口在 main.js 里 import 这一份index.css页面级覆盖放布局调整不要在这里再声明设计变量coze-page.jpgCoze 界面截图用作还原度对照基准这个包真正的价值在样式四件套和双入口业务代码需要你自己按 Vue 3 工程结构补。下面看 package.json 里锁了什么。2.2 package.json最小可运行集合这份 package.json 可以直接放进 Vite 工程使用。版本号是常见写法以你下载到的资源内实际声明为准{ name: vue3-coze-workflow, private: true, scripts: { dev: vite, build: vite build, preview: vite preview }, dependencies: { vue: ^3.4.0 }, devDependencies: { vitejs/plugin-vue: ^5.0.0, eslint: ^8.57.0, eslint-plugin-vue: ^9.23.0, vite: ^5.2.0 } }scripts 里只有 dev / build / preview 三个命令说明资源包默认走 Vite。依赖只声明了 vue没带 vue-router 和 pinia意思是做 Coze 工作流画布不一定需要路由节点状态用 Composition API 就能撑住等状态跨组件变复杂了再按npm i pinia引入也不迟。eslint 放在 devDependencies因为构建产物不包含它。用 pnpm 或 npm 安装时如果报版本冲突优先检查 vue 和 vite 的大版本是否匹配Vite 5 配 Vue 3.4 是比较稳的组合。2.3 ESLint 配置对 Vue 3 语法收紧到什么程度下面的 ESLint 配置是 Vue 3 项目里常见的一套收紧规则module.exports { root: true, env: { browser: true, es2021: true }, extends: [ eslint:recommended, plugin:vue/vue3-recommended ], parserOptions: { ecmaVersion: latest, sourceType: module }, rules: { vue/multi-word-component-names: off } }关键在 vue3-recommended 和那条自定义规则。vue3-recommended 会把模板里没使用的属性、未注册的组件全部拦下来适合做 Code Review 的机器前置检查。vue/multi-word-component-names关掉是因为 Coze 节点组件往往就叫 Node.vue、Edge.vue 这类单名词文件按社区默认规范要求双单词命名只会产生噪音。其余规则保持默认不要过度约束。值得注意的是这份配置没有 TypeScript parser。如果后续要迁移script setup langts需要补 typescript-eslint/parser 和 vue-tsc当前资源按纯 JavaScript 处理。2.4 双入口为什么同一个界面要做两份 HTMLindex.html 是 Vite 约定入口开发时由 dev server 托管资源走相对路径。index-cdn.html 是给不装环境的人看的版本里面用外链把 Vue 3 运行时挂到 window 上双击文件就能在浏览器打开。两个入口共用同一套 CSS 文件差异点只在前端框架的加载方式。这种双入口适合团队里设计或运营要提前看效果你不需要先跑一遍npm run build。最终上线请以 index.html 这条链路为准CDN 版本只做原型演示。理解了工程边界下一章进入这套资源包最能打的部分四份 CSS 怎么组织主题。3. 四份 CSS 的分工从 variables.css 到 style.css 的主题覆盖链路很多 Vue 3 后台项目样式乱根因是没分层有人把设计变量埋进组件有人直接改全局选择器。这份 Coze 工作流样式是反例它把样式分为设计令牌、主题默认值、入口聚合、页面覆盖四层改主题时只动前两层。理解这条链路再套到自己写的弹窗、tabs、表格上都能保持一致。3.1 加载顺序决定覆盖方向style.css 是唯一被打包入口内部按顺序 import/* style.css */ import ./variables.css; import ./theme-default.css; import ./index.css;加载顺序如下表后加载的同名声明会覆盖先加载的层级文件内容定位改动时机1variables.css:root 与 [data-theme] 上的 CSS 变量换品牌色、改圆角、调阴影2theme-default.css节点、面板、连线的默认组件样式调整某一类组件的默认外观3index.css页面级布局与临时覆盖只影响当前页面排列4style.css入口聚合自身不写规则新增样式文件时在此注册CSS 变量天然有继承和覆盖特性后加载的 var() 消费不受影响但同名字面量写在哪一层决定覆盖关系。如果你改了 variables.css 里的主色页面却不变先检查是不是 index.css 里写死了同颜色值。命名冲突和加载顺序是这类问题的主要来源。3.2 variables.css 定义设计令牌把颜色、间距、圆角、阴影抽到变量层是这套资源最值得抄的设计。看一段典型的变量定义:root { --coze-color-bg: #f7f8fa; --coze-color-panel: #ffffff; --coze-color-node: #ffffff; --coze-color-primary: #3370ff; --coze-color-success: #00b42a; --coze-color-error: #f53f3f; --coze-color-border: #e5e6eb; --coze-color-text: #1d2129; --coze-radius-md: 8px; --coze-radius-lg: 12px; --coze-shadow-node: 0 2px 8px rgba(0, 0, 0, 0.06); --coze-font-body: PingFang SC, Microsoft YaHei, system-ui, sans-serif; } [data-themedark] { --coze-color-bg: #1d2129; --coze-color-panel: #23272f; --coze-color-node: #2a2f3a; --coze-color-primary: #4080ff; --coze-color-border: #4e5969; --coze-color-text: #e5e6eb; }变量命名统一带 coze 前缀避免和第三方组件库的 --el-color-primary、--ant-color-primary 冲突。这里面对应的是 Coze 工作流编辑器最常用的白底节点、8px 圆角、轻阴影三件套。要换品牌色只需改 --coze-color-primary 一行所有使用该变量的组件同步变化。暗色模式通过[data-themedark]覆盖同一批变量前端在根组件上切换>template div classwf-node div classwf-node__header span{{ node.title }}/span /div div classwf-node__body slot / /div /div /template style scoped .wf-node { background: var(--coze-color-node, #ffffff); border: 1px solid var(--coze-color-border, #e5e6eb); border-radius: var(--coze-radius-md, 8px); box-shadow: var(--coze-shadow-node, 0 2px 8px rgba(0, 0, 0, 0.06)); min-width: 160px; } .wf-node__header { padding: 8px 12px; border-bottom: 1px solid var(--coze-color-border, #e5e6eb); font-size: 13px; color: var(--coze-color-text, #1d2129); } .wf-node__body { padding: 12px; } /style每个 var() 都给了第二参数作为兜底值即使某个变量被意外删除页面还能按近似值渲染。scoped 样式里直接用变量不会破坏组件隔离这是 Vue 3 单文件组件里最省心的写法。如果你在样式表里看到 #3370ff 这类绝对值优先怀疑是不是有人在组件内做了硬编码。3.4 从主题变量到页面样式的覆盖闭环当你要做一个跟 Coze 风格一致的 tabs 或下拉先查 variables.css 有没有对应令牌没有再补而不是新建一个颜色变量。实际操作中我一般在 theme-default.css 里写组件默认样式比如 tabs 的选中态用 var(--coze-color-primary)hover 态用 color-mix 混合一个浅色页面级的偏移量、间距留在 index.css。一条好用的判断标准能被复用的值进 variables.css只和某个页面相关的值放 index.css。这套分层和 Vue 3 组件的 scoped 思想天然匹配主题切换时页面表现不会出现局部不跟随。下一章把样式落到画布组件上用 Composition API 管理节点和连线。4. 用 Composition API 封装 Coze 工作流节点画布与连线逻辑工作流编辑器在前端本质上是一个有向图。每个节点有 id、type、坐标每条边有 source 和 target。Coze 的编辑器还允许看下游节点、断点调试这些能力在 Vue 3 里可以用 reactive 状态加 computed 派生属性实现不依赖重型组件库。4.1 为什么用 reactive 而不是多个 ref节点数组和连线数组是相互关联的两个状态删除节点所有连到它的边都要一起删选中节点下游节点集合要实时重算。如果用离散的 ref需要在多处手动维护一致性。reactive 把 nodes 和 edges 放在同一对象里配合 computed 做派生态数据流更接近后端图模型。这是 Vue 2 与 Vue 3 在处理这类场景时最大的区别Vue 2 的 data 是初始化时递归代理Vue 3 的 reactive 按需代理性能更好写起来也更接近普通对象。4.2 useWorkflow 组合式函数下面这段封装覆盖了节点的增删、连线的去重和下游节点推导是画布逻辑的核心import { reactive, computed } from vue export function useWorkflow(initialNodes [], initialEdges []) { const state reactive({ nodes: initialNodes.map((n) ({ type: llm, x: 0, y: 0, ...n })), edges: initialEdges, selectedNodeId: null }) const selectedNode computed(() state.nodes.find((node) node.id state.selectedNodeId) ) const downstreamNodes computed(() { if (!state.selectedNodeId) return [] const outgoing new Map() state.edges.forEach((edge) { const targets outgoing.get(edge.source) || [] targets.push(edge.target) outgoing.set(edge.source, targets) }) const result [] const queue [state.selectedNodeId] while (queue.length) { const current queue.shift() ;(outgoing.get(current) || []).forEach((target) { if (!result.includes(target)) { result.push(target) queue.push(target) } }) } return result .map((id) state.nodes.find((n) n.id id)) .filter(Boolean) }) function addNode(node) { const id ${node.type}_${Date.now()} state.nodes.push({ id, x: 0, y: 0, ...node }) return id } function removeNode(nodeId) { state.nodes state.nodes.filter((n) n.id ! nodeId) state.edges state.edges.filter((e) e.source ! nodeId e.target ! nodeId) if (state.selectedNodeId nodeId) state.selectedNodeId null } function connect(sourceId, targetId) { if (sourceId targetId) return const existed state.edges.some( (e) e.source sourceId e.target targetId ) if (existed) return state.edges.push({ id: ${sourceId}-${targetId}, source: sourceId, target: targetId }) } return { state, selectedNode, downstreamNodes, addNode, removeNode, connect } }几个细节值得解释。downstreamNodes 用队列做广度优先遍历而不是递归因为真实工作流里节点数量可能上百递归解构深层链路有栈溢出风险队列迭代没有这个顾虑。removeNode 里对 state.nodes 和 state.edges 同时做 filter保证图不会出现悬空边。connect 中先用 some 判断同一条边是否已存在避免重复拖拽连线造成边集合膨胀。id 用${node.type}_${Date.now()}生成可读性高、同类型节点肉眼能区分需要更严格唯一性时可以换成 crypto.randomUUID()。4.3 节点渲染与事件映射画布组件把 useWorkflow 暴露的状态接进模板按节点类型分发到不同子组件。常见节点类型和事件对应关系如下节点类型典型事件Vue 3 组件映射llmmodel-change / on-messageLlmNode.vuecoderun-code / on-outputCodeNode.vueknowledgeretrieve / on-hitKnowledgeNode.vueconditionjudge / on-passConditionNode.vue模板层用动态组件渲染写法如下script setup import { useWorkflow } from ../composables/useWorkflow import LlmNode from ./nodes/LlmNode.vue import CodeNode from ./nodes/CodeNode.vue import KnowledgeNode from ./nodes/KnowledgeNode.vue import ConditionNode from ./nodes/ConditionNode.vue const components { LlmNode, CodeNode, KnowledgeNode, ConditionNode } const { state, addNode, removeNode, connect } useWorkflow() function handleDrop(type) { addNode({ type, title: type.toUpperCase() }) } /script template div classwf-canvas drop.preventhandleDrop($event.dataTransfer.getData(text/plain)) WorkflowNode v-fornode in state.nodes :keynode.id :nodenode :componentcomponents[node.type] :selectednode.id state.selectedNodeId selectstate.selectedNodeId node.id removeremoveNode(node.id) connectconnect($event.source, $event.target) / /div /templatev-for 的 key 直接用节点稳定 id不要用数组下标否则删除中间节点时 Vue 会把剩余节点状态错位复用。component 的 :is 接收组件对象而非字符串配合映射表新增一种节点类型只需要加一个条目。连线还没算坐标真实画布一般把边抽象成 svg path用两个节点中心点做贝塞尔曲线这不影响状态层设计画布逻辑和渲染逻辑在 useWorkflow 中已经分离需要时再增加坐标计算函数即可。5. 让 Vue 3 页面跑通 Coze 对话流鉴权、流式响应与渲染工作流画布搭起来了下一步是让前端真正调用一条 Coze 对话流。Coze 的对话流和工作流都提供 HTTP 接口前端需要处理三件事请求鉴权、流式读取、结果渲染。如果后端没有现成的转发服务最简单的方式是在 Vue 3 工程里加一个 Vite 开发服务器中间件做请求转发生产环境由 Nginx 转发到后端服务。5.1 鉴权信息不要落在前端直接把 Bot Token 写进前端代码等于公开密钥。常见做法是在服务端保存 token前端只带用户身份。下面的思路适用任何 Vue 3 后端仓库这里用 FastAPI 做示意转发到 Coze 对话流接口import os import httpx from fastapi import FastAPI, Request app FastAPI() COZE_API_BASE os.getenv(COZE_API_BASE, https://api.coze.example.com) COZE_TOKEN os.getenv(COZE_TOKEN, ) app.post(/api/coze/chat) async def forward_chat(request: Request): payload await request.json() headers { Authorization: fBearer {COZE_TOKEN}, Content-Type: application/json } async with httpx.AsyncClient(timeout60) as client: resp await client.post( f{COZE_API_BASE}/v1/chat, jsonpayload, headersheaders ) return resp.json()COZE_API_BASE 和 COZE_TOKEN 从环境变量读取提交到 git 的只有 .env.example。前端访问同源/api/coze/chat不存在跨域问题也不会把 token 暴露给浏览器。注意这里的超时设置对话流接口耗时常超过 30 秒timeout 至少要给 60 秒否则 Web 服务网关会先断连。5.2 流式读取用 fetch 处理边读边渲染对话流接口通常以流式格式返回结果Vue 3 侧用 fetch 配合 ReadableStream 逐行解析import { ref } from vue export function useCozeChat() { const loading ref(false) const error ref(null) async function send({ onChunk, ...payload }) { loading.value true error.value null const resp await fetch(/api/coze/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ stream: true, ...payload }) }) if (!resp.ok) { error.value HTTP ${resp.status} throw new Error(error.value) } 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:)) { try { const event JSON.parse(trimmed.slice(5).trim()) onChunk?.(event) } catch (e) { // 单条解析失败时跳过避免中断整个会话 } } } } loading.value false } return { loading, error, send } }这里有个容易踩的坑底层传输不一定按换行对齐一次 chunk 可能只包含半行数据。所以每次读取后先拼进 buffer再按换行符切分把最后一段留在 buffer 里等下一次读取直到 done。onChunk 把解析出的消息增量推给组件页面再以数组形式收集这些片段实现类似打字的流式展示。loading.value 要在整个 while 循环结束后再置为 false不能放在 fetch 之后因为 fetch resolve 只代表响应头到达不代表 body 读完。5.3 组件内做打字机效果在 Vue 3 组件里把流式片段累积成消息列表script setup import { ref } from vue import { useCozeChat } from ../composables/useCozeChat const { send, loading, error } useCozeChat() const messages ref([]) async function handleSend(text) { messages.value.push({ role: user, content: text }) messages.value.push({ role: assistant, content: }) const latest messages.value[messages.value.length - 1] await send({ query: text, onChunk: (chunk) { latest.content chunk.content || } }) } /scriptonChunk 回调修改的是 messages 中最后一个 assistant 消息的 content。Vue 3 的 ref 包裹数组后对元素的属性修改也是响应式的模板里 v-for 渲染的文本会随每次 chunk 自动更新不需要手动操作 DOM。如果你在 Vue 2 里习惯用this.$set这里直接赋值就有效果这是响应式系统重构后的差异。对抗网络抖动时前端可以加一个逾时判断超过 20 秒没有收到任何 chunk 就提示重试或切换工作流。这个参数放在 useCozeChat 的参数对象里不要写死在循环内部方便不同页面单独调整。6. 部署前的三个检查双入口、base 路径与主题兜底最后把部署和验证的细节收拢成几个必查项。6.1 确认最终入口用 index.htmlindex-cdn.html 只是原型演示用的它通过 CDN 外链加载 Vue 运行时无法利用 Vite 构建产物。上线前确认入口文件是 index.html并在项目根目录执行npm run build把构建产物 dist 目录部署到服务器。如果部署到站点子路径还需要在 vite.config 里设置 baseexport default defineConfig({ base: ./, plugins: [vue()] })base 设为 ./ 后资源引用从绝对路径变成相对路径dist/index.html 可以放进任意子目录。Nginx 配 history 路由时需要把未知路径回退到入口location / { try_files $uri $uri/ /index.html; }这个配置对单页应用是刚需否则刷新二级页面会 404。6.2 验证主题是否跟随>grep -rn #3370ff\|#ffffff src --include*.vue --include*.css正常输出只应该出现在 variables.css 和 theme-default.css其他文件出现同名色值就需要改成 var() 引用。coze-page.jpg 是还原度的对照物开发时把它固定在屏幕角落调整 --coze-color-primary、--coze-shadow-node 等变量直到节点卡片与截图视觉接近。这种对比校验比盯着代码想象实际效果可靠得多。这三个检查点分别对应双入口选择错误、子目录部署白屏、主题变量失效是这份资源最容易出问题的地方。把这三步跑通Vue 3 版 Coze 工作流前端就算真正落地了。本文还有配套的精品资源点击获取