从零理解:Vue + Coze 如何制作冰球运动员生成器
这个项目是做什么的?
想象你有一个神奇的照相馆:
- 你上传一张自己的照片
- 选择你想要的冰球装备(队服颜色、号码、位置等)
- 选择喜欢的画风(写实、卡通、素描等)
- 点一下"生成"按钮
- 几秒钟后,你就得到了一张穿着冰球装备的艺术照!
这个项目就是实现这个神奇功能的网站。
一、先看看用户能看到什么(界面)
左边:输入区
css
┌─────────────────────────┐
│ [选择文件] 按钮 │ ← 点这个上传照片
│ │
│ [图片预览] │ ← 上传后显示你的照片
│ │
│ 队服编号:[10] │ ← 输入你想要的号码
│ 队服颜色:[红色 ▼] │ ← 下拉选择颜色
│ 位置:[守门员 ▼] │ ← 选择场上位置
│ 持杆:[左手 ▼] │ ← 左手还是右手持杆
│ 风格:[写实 ▼] │ ← 选择画风
│ │
│ [生成] 按钮 │ ← 点这个开始生成
└─────────────────────────┘
右边:输出区
arduino
┌─────────────────────────┐
│ │
│ [生成的图片] │ ← 生成成功后显示
│ │
│ "图片上传中..." │ ← 过程中显示状态
│ "生成中..." │
│ │
└─────────────────────────┘
二、代码是如何工作的?(详细拆解)
第一步:准备工具(导入 Vue 功能)
javascript
import { ref, onMounted } from 'vue'
通俗解释:
ref:就像一个"魔法盒子",把数据放进去,数据变化时页面会自动更新onMounted:页面加载完成后自动执行的函数
第二步:准备变量(存储数据)
javascript
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() 包装的变量,变化时页面会自动更新!
第三步:图片预览功能
javascript
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; // 把网址给预览变量
}
}
通俗解释:
这个过程就像:
- 用户点击"选择文件" → 选择一张图片
FileReader像一个"翻译官",把图片文件翻译成浏览器能显示的格式(Base64)- 翻译完成后,把结果放到
imgPreview盒子里 - 页面看到这个盒子变了,自动显示预览图
为什么需要预览?
- 让用户确认选对了图片
- 提升用户体验(不用等上传就能看到)
第四步:上传文件到 Coze 服务器
javascript
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(重要!)
}
通俗解释:
这个过程就像寄快递:
- 打包 :
FormData像一个快递箱,把文件装进去 - 贴单 :在请求头里加上
Authorization,就像贴快递单,证明你是谁 - 发货 :
fetch发送请求,把"快递"发到 Coze 服务器 - 签收 :服务器收到后,返回一个"取件码"(
file_id) - 验货 :检查
ret.code,如果是 0 表示成功,否则显示错误信息
为什么需要 file_id?
- Coze 服务器存储了你的图片
file_id就是这张图片的"身份证号"- 后面调用工作流时,用这个 ID 告诉 AI"用哪张图片"
第五步:调用 Coze 工作流(核心!)
javascript
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; // 拿到生成图片的网址
}
通俗解释:
这个过程就像点外卖:
-
告诉状态 :
status.value = "图片上传中..."→ 告诉用户"我正在做" -
上传食材 :
uploadFile()→ 先把你的照片传到 Coze(就像把食材送到餐厅) -
写菜单 :
parameters对象 → 告诉厨师(AI)你想要什么: - 用哪张照片(file_id) - 什么风格(写实/乐高/国漫...) - 队服颜色(红/蓝/绿...) - 号码(10 号) - 位置(守门员/前锋/后卫) - 持杆习惯(左手/右手) -
下单 :
fetch(workflowUrl, ...)→ 把菜单发给厨房(Coze 工作流) -
等餐 :
await res.json()→ 等待 AI 处理完成 -
上菜 :
imgUrl.value = data.data→ 拿到生成好的图片网址,显示给用户
第六步:HTML 模板(页面结构)
vue
<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 元素
三、完整流程图解
ini
用户操作 代码执行 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 的"魔法"所在!
javascript
// 不用 ref(普通变量)
let count = 0;
count = 1; // 页面不会变!
// 用 ref(响应式变量)
const count = ref(0);
count.value = 1; // 页面自动更新!
通俗理解 :ref() 给变量装了"监控器",值一变就通知页面更新。
Q2: async/await 是什么?
答:处理"需要等待"的操作。
javascript
// 没有 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 是什么?
答:你的"身份证"。
javascript
const patToken = import.meta.env.VITE_PAT_TOKEN;
- 这是你在 Coze 平台申请的访问令牌
- 每次调用 Coze API 都要带上它
- 证明"我是合法用户"
- 类似银行卡密码,不能泄露!
安全提示 :代码里的注释 // 安全问题 就是在提醒:Token 不应该直接放在前端代码里!
Q4: 工作流是什么?
答:Coze 上的"自动化流程"。
你可以把 Coze 工作流理解为一个"机器人厨师":
- 你告诉它食材(上传的图片 file_id)
- 你告诉它菜谱(各种参数:风格、颜色、号码等)
- 它按照预设的流程处理(AI 生成)
- 最后给你端上菜(返回生成的图片)
工作流 ID :7617731556123328527 就是这个"机器人厨师"的编号。
五、总结:整个项目做了什么?
用一句话概括:
用户上传照片 → 选择参数 → 传到 Coze → AI 生成 → 显示结果
核心步骤:
- 前端界面:让用户选择图片和参数
- 文件上传:把图片传到 Coze,拿到 file_id
- 调用工作流:把 file_id 和参数发给 AI
- 显示结果:AI 生成完成后,显示图片
技术要点:
- Vue 的响应式系统(
ref) - 文件读取(
FileReader) - HTTP 请求(
fetch) - 异步处理(
async/await) - 表单绑定(
v-model)
希望这个详细版本能帮你理解这个项目!如果还有哪里不明白,可以具体问哪个部分。
