Vue3 调用 Coze 工作流:从上传宠物照到生成冰球明星的完整技术解析

引言

"你家的猫,也能打冰球?"

不是玩笑------这是一次前端与 AI 工作流的完美邂逅。

在当今 AI 应用爆发的时代,开发者不再满足于调用单一模型 API,而是通过 工作流(Workflow) 编排多个能力节点,实现复杂业务逻辑。而前端作为用户交互的第一线,如何优雅地集成这些 AI 能力,成为现代 Web 开发的重要课题。

本文将带你深入剖析一个真实项目:使用 Vue3 前端调用 Coze 平台的工作流 API,上传一张宠物照片,生成穿着定制队服、手持冰球杆的运动员形象图 。我们将逐行解读 App.vue 源码,解释每一个 API 调用、每一段逻辑设计,并结合完整的 Coze 工作流图解,还原整个数据流转过程。文章内容严格引用原始代码(一字不变),确保技术细节 100% 准确。


一、项目背景与目标

AI 应用之冰球前端应用 vue3:冰球协会,上传宠物照片,生成运动员的形象照片。

这个应用的核心功能非常明确:

  • 用户上传一张宠物(或人物)照片;
  • 选择冰球队服编号、颜色、场上位置、持杆手、艺术风格等参数;
  • 点击"生成",系统调用 AI 工作流;
  • 返回一张合成后的"冰球运动员"图像。

而这一切的实现,完全依赖于 Coze 平台提供的工作流 API。前端负责收集输入、上传文件、发起请求、展示结果------典型的"轻前端 + 重 AI 后端"架构。


二、App.vue 整体结构概览

App.vue 是一个标准的 Vue3 单文件组件(SFC),采用 <script setup> 语法糖,结合 Composition API 实现响应式逻辑。整体分为三部分:

  1. <template> :用户界面(UI)
  2. <script setup> :业务逻辑(JS)
  3. <style scoped> :样式(CSS)

我们先从模板入手,理解用户看到什么、能做什么。


三、模板(Template)详解:用户交互层

3.1 文件上传与预览

ini 复制代码
<div class="file-input">
  <input 
    type="file" 
    ref="uploadImage" 
    accept="image/*" 
    @change="updateImageData" required />
</div>
<img :src="imgPreview" alt="" v-if="imgPreview"/>
  • <input type="file">:原生文件选择器,限制只接受图片(accept="image/*")。
  • ref="uploadImage":通过 ref 获取该 DOM 元素,便于 JS 中读取文件。
  • @change="updateImageData":当用户选择文件后,立即触发 updateImageData 方法,生成本地预览。
  • imgPreview 是一个响应式变量,用于显示 Data URL 格式的预览图,无需上传即可看到效果。

用户体验亮点:即使图片很大、上传很慢,用户也能立刻确认自己选对了图。

3.2 表单参数设置

接下来是两组设置项,全部使用 v-model 双向绑定:

第一组:队服信息

xml 复制代码
<div class="settings">
  <div class="selection">
    <label>队服编号:</label>
    <input type="number" v-model="uniform_number"/>
  </div>
  <div class="selection">
    <label>队服颜色:</label>
    <select v-model="uniform_color">
      <option value="红">红</option>
      <option value="蓝">蓝</option>
      <option value="绿">绿</option>
      <option value="白">白</option>
      <option value="黑">黑</option>
    </select>
  </div>
</div>
  • uniform_number:默认值为 10(见 script 部分),支持任意数字。
  • uniform_color:限定五种颜色,值为中文字符串(如 "红")。

第二组:角色与风格

xml 复制代码
<div class="settings">
  <div class="selection">
    <label>位置:</label>
    <select v-model="position">
      <option value="0">守门员</option>
      <option value="1">前锋</option>
      <option value="2">后卫</option>
    </select>
  </div>
  <div class="selection">
    <label>持杆:</label>
    <select v-model="shooting_hand">
      <option value="0">左手</option>
      <option value="1">右手</option>
    </select>
  </div>
  <div class="selection">
    <label>风格:</label>
    <select v-model="style">
      <option value="写实">写实</option>
      <option value="乐高">乐高</option>
      <option value="国漫">国漫</option>
      <option value="日漫">日漫</option>
      <option value="油画">油画</option>
      <option value="涂鸦">涂鸦</option>
      <option value="素描">素描</option>
    </select>
  </div>
</div>
  • positionshooting_hand 的值虽然是数字字符串("0"/"1"/"2"),但前端显示为中文,兼顾可读性与后端兼容性。
  • style 提供 7 种艺术风格,极大增强趣味性和分享欲。

3.3 生成按钮与输出区域

ini 复制代码
<div class="generate">
  <button @click="generate">生成</button>
</div>

点击后触发 generate() 函数,启动整个 AI 生成流程。

输出区域:

ini 复制代码
<div class="output">
  <div class="generated">
    <img :src="imgUrl" alt="" v-if="imgUrl"/>
    <div v-if="status">{{ status }}</div>
  </div>
</div>
  • imgUrl:存储 Coze 返回的生成图 URL。
  • status:动态显示当前状态(如"上传中..."、"生成失败"等),避免用户焦虑。

💡 设计哲学:状态反馈是良好 UX 的核心。没有反馈的"生成"按钮,等于黑盒。


四、脚本逻辑(Script Setup)深度解析

现在进入最核心的部分------JavaScript 逻辑。

4.1 环境配置与常量定义

ini 复制代码
import { ref, onMounted } from 'vue'

const patToken = import.meta.env.VITE_PAT_TOKEN;
const uploadUrl = 'https://api.coze.cn/v1/files/upload';
const workflowUrl = 'https://api.coze.cn/v1/workflow/run';
const workflow_id = '7584046136391630898';
  • import.meta.env.VITE_PAT_TOKEN:Vite 提供的环境变量注入机制。.env 文件中应包含:

    ini 复制代码
    VITE_PAT_TOKEN=cztei_lvNwngHgch9rxNlx4KiXuky3UjfW9iqCZRe17KDXjh22RLL8sPLsb8Vl10R3IHJsW
  • uploadUrl:Coze 官方文件上传接口(文档)。

  • workflowUrl:触发工作流的入口(文档)。

  • workflow_id:在 Coze 控制台创建的工作流唯一 ID,内部已配置好图像生成逻辑(如调用文生图模型、叠加队服等)。

⚠️ 安全警告:将 PAT Token 放在前端仅适用于演示或内部工具。生产环境应通过后端代理 API,避免 Token 泄露。

4.2 响应式状态声明

ini 复制代码
const uniform_number = ref(10);
const uniform_color = ref('红');
const position = ref(0);
const shooting_hand = ref(0);
const style = ref('写实');

const status = ref('');
const imageUrl = ref('');
  • 所有表单字段均为 ref 响应式对象,确保视图自动更新。
  • status 初始为空,后续将显示:"图片上传中..." → "图片上传成功, 正在生成..." → 成功清空 或 错误信息。
  • imageUrl 初始为空,生成成功后赋值为图片 URL。

4.3 核心函数 1:图片预览(updateImageData)

ini 复制代码
const uploadImage = ref(null);
const imgPreview = ref('');

const updateImageData = () => {
  const input = uploadImage.value;
  if (!input.files || input.files.length === 0) {
    return;
  }
  const file = input.files[0];
  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = (e) => {
    imgPreview.value = e.target.result;
  };
}
  • uploadImage 是对 <input> 元素的引用。
  • 使用 FileReaderreadAsDataURL 方法,将文件转为 Base64 编码的 Data URL。
  • onload 回调中,将结果赋给 imgPreview,触发 <img> 标签渲染。

优势:纯前端实现,零网络请求,秒级响应。

4.4 核心函数 2:文件上传(uploadFile)

ini 复制代码
const uploadFile = async () => {
  const formData = new FormData();
  const input = uploadImage.value;
  if (!input.files || input.files.length <= 0) return;
  formData.append('file', input.files[0]);

  const res = await fetch(uploadUrl, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${patToken}`
    },
    body: formData
  });

  const ret = await res.json();
  console.log(ret);
  if (ret.code !== 0) {
    status.value = ret.msg;
    return;
  }
  return ret.data.id;
}

逐行解析:

  1. 构造 FormData

    • new FormData() 是浏览器原生 API,用于构建 multipart/form-data 请求体,专为文件上传设计。
    • formData.append('file', file):Coze 要求字段名为 file
  2. 发送 POST 请求

    • URL:https://api.coze.cn/v1/files/upload

    • Headers:

      • Authorization: Bearer <token>:Coze 使用 Bearer Token 认证。
    • Body:formData 自动设置正确 Content-Type(含 boundary)。

  3. 处理响应

    • 成功时返回:

      css 复制代码
      { "code": 0, "msg": "success", "data": { "id": "file_xxx", ... } }
    • 失败时 code !== 0msg 包含错误原因(如 Token 无效、文件过大等)。

    • 函数返回 file_id(如 "file_abc123"),供下一步使用。

关键点 :Coze 的文件上传是独立步骤,必须先上传获取 file_id,才能在工作流中引用。


五、核心函数 3:调用工作流(generate)

这是整个应用的"大脑"。我们结合 Coze 工作流图,深入分析其逻辑与数据流。

ini 复制代码
const generate = async () => {
  status.value = "图片上传中...";
  const file_id = await uploadFile();
  if (!file_id) return;

  status.value = "图片上传成功, 正在生成...";

  const parameters = {
    picture: JSON.stringify({ file_id }),
    style: style.value,
    uniform_color: uniform_color.value,
    uniform_number: uniform_number.value,
    position: position.value,
    shooting_hand: shooting_hand.value,
  };

  try {
    const res = await fetch(workflowUrl, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${patToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ workflow_id, parameters })
    });

    const ret = await res.json();
    console.log("Workflow API response:", ret);
    if (ret.code !== 0) {
      status.value = ret.msg;
      return;
    }

    // 检查返回数据结构
    console.log("Return data:", ret.data);
    console.log("Return data type:", typeof ret.data);

    // 尝试解析数据
    let data;
    if (typeof ret.data === 'string') {
      try {
        data = JSON.parse(ret.data);
        console.log("Parsed data:", data);
      } catch (e) {
        console.error("JSON parse error:", e);
        status.value = "数据解析错误";
        return;
      }
    } else {
      data = ret.data;
    }

    // 检查data.data是否存在
    if (data && data.data) {
      console.log("Generated image URL:", data.data);
      status.value = '';
      imageUrl.value = data.data;
    } else {
      console.error("Invalid data structure, missing 'data' field:", data);
      status.value = "返回数据结构错误";
    }
  } catch (error) {
    console.error("Generate error:", error);
    status.value = "生成失败,请检查网络连接";
  }
}

逻辑拆解(结合 Coze 工作流图)

Coze 工作流结构(图解说明)

图注

  1. 开始节点 :接收 picture, style, uniform_number, position, shooting_hand, uniform_color 等参数。
  2. 分支一imgUnderstand_1(图像理解)→ 分析上传图片内容(如动物种类、姿态)。
  3. 分支二代码 节点 → 根据 position, shooting_hand, style 等生成描述文本(如"一只狗,右手持杆,身穿红色10号队服,站在冰球场上")。
  4. 大模型节点:将图像理解结果与描述文本合并,生成最终提示词(prompt)。
  5. 图像生成节点:调用文生图模型(如豆包·1.5·Pro·32k),生成新图像。
  6. 结束节点:输出生成图的 URL。

前端代码的对应关系

前端参数 Coze 输入字段 用途
picture picture 图片文件 ID,传入 imgUnderstand_1图像生成 节点
style style 传递给 代码 节点,决定艺术风格
uniform_number uniform_number 用于生成描述
position position 决定角色动作(如守门员蹲姿)
shooting_hand shooting_hand 决定持杆手
uniform_color uniform_color 用于生成队服颜色

💡 关键点:前端只需提供原始参数,Coze 工作流内部完成所有逻辑编排。


数据流全过程

  1. 前端上传文件 → 得到 file_id

  2. 前端组装参数 → 发送至 /workflow/run

  3. Coze 工作流执行

    • imgUnderstand_1:分析图片内容 → 输出 text, url, content
    • 代码 节点:根据参数生成描述 → 如 "一只猫,身穿蓝色10号队服,右手持杆,站在冰球场上,风格为乐高"
    • 大模型 节点:合并图像理解结果与描述 → 生成最终 prompt
    • 图像生成 节点:调用模型生成图像 → 返回 data 字段(URL)
  4. 前端接收响应

    • ret.data 是字符串 → 尝试 JSON.parse
    • 若是对象 → 直接取 data.data
    • 最终赋值给 imageUrl

为什么需要双重解析?

因为 Coze 的"图像生成"节点可能直接返回 URL 字符串,也可能返回 { data: "url" } 结构。前端必须兼容两种情况。


六、样式(Style)简析

xml 复制代码
<style scoped>
.container {
  display: flex;
  flex-direction: row;
  align-items: start;
  justify-content: start;
  height: 100vh;
  font-size: .85rem;
}
.generated {
  width: 400px;
  height: 400px;
  border: solid 1px black;
  display: flex;
  justify-content: center;
  align-items: center;
}
.output img {
  width: 100%;
}
</style>
  • 使用 Flex 布局,左右分栏(输入区固定宽度,输出区自适应)。
  • .generated 容器固定 400x400,图片居中显示,无论原始比例如何都不变形。
  • scoped 确保样式仅作用于当前组件,避免污染全局。

七、项目运行

在项目终端运行命令 :npm run dev

运行界面如下:

选择图片及风格等内容后,点击开始生成,运行结果如图:


总结:为什么这个项目值得学习?

  1. 真实场景:不是 Hello World,而是完整产品逻辑。

  2. 技术全面

    • Vue3 Composition API
    • 文件上传与预览
    • Fetch API 与错误处理
    • 环境变量管理
    • 响应式状态驱动 UI
  3. AI 集成范式:展示了如何将复杂 AI 能力封装为简单 API,前端只需"填参数 + 拿结果"。

  4. 用户体验优先:状态提示、本地预览、错误反馈一应俱全。

安全与部署建议

  • 后端代理所有 Coze API 调用

    • 前端 → 自己的后端(/api/generate)
    • 后端 → Coze(携带安全存储的 Token)
  • 限制工作流权限:Coze 的 PAT Token 应仅授予必要权限。

  • 添加速率限制:防止滥用。

最终,技术的意义在于创造快乐。

当你上传一张狗子的照片,看到它穿上红色10号球衣、右手持杆、以"乐高"风格站在冰场上------

你会笑,会分享,会说:"AI 真酷!"

而这,正是我们写代码的初心。

完整项目源码:lesson_zp/ai/app/iceball: AI + 全栈学习仓库 - Gitee.com

相关推荐
mldong3 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排4 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60614 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角5 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!5 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰6 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi7 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen8 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马8 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒9 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端