从零理解:Vue + Coze 如何制作冰球运动员生成器

0 阅读6分钟

从零理解:Vue + Coze 如何制作冰球运动员生成器

这个项目是做什么的?

想象你有一个神奇的照相馆:

  1. 你上传一张自己的照片
  2. 选择你想要的冰球装备(队服颜色、号码、位置等)
  3. 选择喜欢的画风(写实、卡通、素描等)
  4. 点一下"生成"按钮
  5. 几秒钟后,你就得到了一张穿着冰球装备的艺术照!

这个项目就是实现这个神奇功能的网站。


一、先看看用户能看到什么(界面)

左边:输入区

┌─────────────────────────┐
│  [选择文件] 按钮         │ ← 点这个上传照片
│                         │
│  [图片预览]             │ ← 上传后显示你的照片
│                         │
│  队服编号:[10]         │ ← 输入你想要的号码
│  队服颜色:[红色 ▼]     │ ← 下拉选择颜色
│  位置:[守门员 ▼]       │ ← 选择场上位置
│  持杆:[左手 ▼]         │ ← 左手还是右手持杆
│  风格:[写实 ▼]         │ ← 选择画风
│                         │
│       [生成] 按钮       │ ← 点这个开始生成
└─────────────────────────┘

右边:输出区

┌─────────────────────────┐
│                         │
│    [生成的图片]         │ ← 生成成功后显示
│                         │
│   "图片上传中..."       │ ← 过程中显示状态
│   "生成中..."           │
│                         │
└─────────────────────────┘

image.png

二、代码是如何工作的?(详细拆解)

第一步:准备工具(导入 Vue 功能)

import { ref, onMounted } from 'vue'

通俗解释

  • ref:就像一个"魔法盒子",把数据放进去,数据变化时页面会自动更新
  • onMounted:页面加载完成后自动执行的函数

第二步:准备变量(存储数据)

const uniform_number = ref(10);      // 队服号码,默认 10 号
const uniform_color = ref('红');     // 队服颜色,默认红色
const position = ref(0);             // 位置,0=守门员
const shooting_hand = ref(0);        // 持杆,0=左手
const style = ref('写实');           // 风格,默认写实

const status = ref('');              // 当前状态(显示给用户看)
const imgUrl = ref('');              // 生成图片的网址
const imgPreview = ref('');          // 预览图片的网址
const uploadImage = ref(null);       // 上传按钮的 DOM 对象

通俗解释: 这些变量就像一个个"小盒子":

  • 有的盒子装用户选择的参数(号码、颜色等)
  • 有的盒子装状态信息(正在上传、生成中等)
  • 有的盒子装图片网址(预览图、生成图)

关键点:用 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;  // 把网址给预览变量
  }
}

通俗解释

这个过程就像:

  1. 用户点击"选择文件" → 选择一张图片
  2. FileReader 像一个"翻译官",把图片文件翻译成浏览器能显示的格式(Base64)
  3. 翻译完成后,把结果放到 imgPreview 盒子里
  4. 页面看到这个盒子变了,自动显示预览图

为什么需要预览?

  • 让用户确认选对了图片
  • 提升用户体验(不用等上传就能看到)

第四步:上传文件到 Coze 服务器

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]);  // 把文件放进数据包
  
  // 发送 HTTP 请求
  const res = await fetch(uploadUrl, {
    method: 'POST',  // 用 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;  // 返回文件 ID(重要!)
}

通俗解释

这个过程就像寄快递:

  1. 打包FormData 像一个快递箱,把文件装进去
  2. 贴单:在请求头里加上 Authorization,就像贴快递单,证明你是谁
  3. 发货fetch 发送请求,把"快递"发到 Coze 服务器
  4. 签收:服务器收到后,返回一个"取件码"(file_id
  5. 验货:检查 ret.code,如果是 0 表示成功,否则显示错误信息

为什么需要 file_id?

  • Coze 服务器存储了你的图片
  • file_id 就是这张图片的"身份证号"
  • 后面调用工作流时,用这个 ID 告诉 AI"用哪张图片"

第五步:调用 Coze 工作流(核心!)

const generate = async () => {
  status.value = "图片上传中..."  // 告诉用户:开始了
  const file_id = await uploadFile();  // 先上传图片
  if(!file_id) return;  // 上传失败就不继续了
  
  status.value = '图片上传成功,正在生成...'  // 告诉用户:上传好了
  
  // 准备参数(告诉 AI 你想要什么)
  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 // 持杆习惯
  }

  // 调用工作流
  const res = await fetch(workflowUrl, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${patToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      workflow_id: workflowId,  // 工作流 ID(告诉 Coze 用哪个工作流)
      parameters  // 刚才准备的参数
    })
  })
  
  const ret = await res.json();
  if(ret.code !== 0) { 
    status.value = ret.msg;  // 出错了显示错误
    return;
  }
  
  const data = JSON.parse(ret.data);
  console.log(data);
  status.value = '';  // 清空状态
  imgUrl.value = data.data;  // 拿到生成图片的网址
}

通俗解释

这个过程就像点外卖:

  1. 告诉状态status.value = "图片上传中..." → 告诉用户"我正在做"

  2. 上传食材uploadFile() → 先把你的照片传到 Coze(就像把食材送到餐厅)

  3. 写菜单parameters 对象 → 告诉厨师(AI)你想要什么: - 用哪张照片(file_id) - 什么风格(写实/乐高/国漫...) - 队服颜色(红/蓝/绿...) - 号码(10 号) - 位置(守门员/前锋/后卫) - 持杆习惯(左手/右手)

  4. 下单fetch(workflowUrl, ...) → 把菜单发给厨房(Coze 工作流)

  5. 等餐await res.json() → 等待 AI 处理完成

  6. 上菜imgUrl.value = data.data → 拿到生成好的图片网址,显示给用户


第六步:HTML 模板(页面结构)

<template>
  <div class="container">
    <!-- 左边:输入区 -->
    <div class="input">
      <!-- 文件上传 -->
      <input 
        type="file" 
        ref="uploadImage" 
        accept="image/*"
        @change="updateImageData"
      />
      
      <!-- 图片预览 -->
      <img :src="imgPreview" v-if="imgPreview"/>
      
      <!-- 各种选择框 -->
      <label>队服编号:</label>
      <input type="number" v-model="uniform_number"/>
      
      <label>队服颜色:</label>
      <select v-model="uniform_color">
        <option value="红">红</option>
        <option value="蓝">蓝</option>
        <!-- ... -->
      </select>
      
      <!-- 生成按钮 -->
      <button @click="generate">生成</button>
    </div>
    
    <!-- 右边:输出区 -->
    <div class="output">
      <img :src="imgUrl" v-if="imgUrl"/>
      <div v-if="status">{{ status }}</div>
    </div>
  </div>
</template>

通俗解释

这段代码定义了页面长什么样:

关键语法

  • v-model="uniform_number":双向绑定 → 输入框的值变了,变量自动变;变量变了,输入框自动变
  • :src="imgPreview":动态绑定 → imgPreview 是什么网址,图片就显示什么
  • v-if="imgPreview":条件显示 → 只有 imgPreview 有值时才显示图片
  • @click="generate":点击事件 → 点按钮就执行 generate 函数
  • ref="uploadImage":引用 → 让 JavaScript 能找到这个 DOM 元素

三、完整流程图解

用户操作                    代码执行                    Coze 服务器
   │                          │                           │
   ├─ 1. 选择图片 ──────────→ updateImageData()           │
   │                          │                           │
   │                          ├─ FileReader 读取          │
   │                          ├─ 转成 Base64              │
   │                          └─ 更新 imgPreview ───────→ │
   │                                                      │
   ├─ 2. 选择参数 ──────────→ v-model 自动绑定            │
   │    (号码、颜色、风格)     到各个变量                   │
   │                                                      │
   ├─ 3. 点击生成 ──────────→ generate()                  │
   │                          │                           │
   │                          ├─ status = "上传中"        │
   │                          │                           │
   │                          ├─ uploadFile() ──────────→ │
   │                          │   POST /files/upload      │
   │                          │   [发送图片]              │
   │                          │                           │
   │                          │ ←────────────────────────┤
   │                          │   { code: 0, data: { id }}│
   │                          │   [返回 file_id]          │
   │                          │                           │
   │                          ├─ status = "生成中"        │
   │                          │                           │
   │                          ├─ fetch(workflowUrl) ─────→│
   │                          │   POST /workflow/run      │
   │                          │   [发送参数 + file_id]    │
   │                          │                           │
   │                          │                          AI 处理中...
   │                          │                          (生成图片)
   │                          │                           │
   │                          │ ←────────────────────────┤
   │                          │   { code: 0, data: {...}} │
   │                          │   [返回生成图片 URL]      │
   │                          │                           │
   │                          ├─ imgUrl = data.data       │
   │                          ├─ status = ""              │
   │                          │                           │
   │ ←────────────────────────┤                           │
   显示生成图片                页面自动更新

四、常见问题解答

Q1: 为什么要用 ref()

:Vue 的"魔法"所在!

// 不用 ref(普通变量)
let count = 0;
count = 1;  // 页面不会变!

// 用 ref(响应式变量)
const count = ref(0);
count.value = 1;  // 页面自动更新!

通俗理解ref() 给变量装了"监控器",值一变就通知页面更新。


Q2: async/await 是什么?

:处理"需要等待"的操作。

// 没有 await(会出问题)
const res = fetch(url);  // 还没等服务器返回,就继续往下执行
const data = res.json();  // 报错!res 还没准备好

// 有 await(正确做法)
const res = await fetch(url);  // 等等等...等到服务器返回
const data = await res.json();  // 再等等等...等到解析完成
// 现在 data 准备好了,继续执行

通俗理解await 就像"请稍候",等这个操作做完才继续。


Q3: patToken 是什么?

:你的"身份证"。

const patToken = import.meta.env.VITE_PAT_TOKEN;
  • 这是你在 Coze 平台申请的访问令牌
  • 每次调用 Coze API 都要带上它
  • 证明"我是合法用户"
  • 类似银行卡密码,不能泄露!

安全提示:代码里的注释 // 安全问题 就是在提醒:Token 不应该直接放在前端代码里!


Q4: 工作流是什么?

:Coze 上的"自动化流程"。

你可以把 Coze 工作流理解为一个"机器人厨师":

  1. 你告诉它食材(上传的图片 file_id)
  2. 你告诉它菜谱(各种参数:风格、颜色、号码等)
  3. 它按照预设的流程处理(AI 生成)
  4. 最后给你端上菜(返回生成的图片)

工作流 ID7617731556123328527 就是这个"机器人厨师"的编号。


五、总结:整个项目做了什么?

用一句话概括:

用户上传照片 → 选择参数 → 传到 Coze → AI 生成 → 显示结果

核心步骤

  1. 前端界面:让用户选择图片和参数
  2. 文件上传:把图片传到 Coze,拿到 file_id
  3. 调用工作流:把 file_id 和参数发给 AI
  4. 显示结果:AI 生成完成后,显示图片

技术要点

  • Vue 的响应式系统(ref
  • 文件读取(FileReader
  • HTTP 请求(fetch
  • 异步处理(async/await
  • 表单绑定(v-model

希望这个详细版本能帮你理解这个项目!如果还有哪里不明白,可以具体问哪个部分。