本文将带你从零构建一个基于 Vue3 和 Coze 工作流的趣味 AI 应用------"宠物变冰球运动员"生成器。通过上传一张宠物照片,结合用户自定义的队服编号、颜色、位置等参数,即可生成一张风格化的冰球运动员形象图。
一、项目背景与目标
在 AI 能力逐渐普及的今天,越来越多开发者尝试将大模型能力集成进自己的 Web 应用中。本项目的目标是打造一个轻量、有趣、可分享的前端应用:
- 用户上传宠物照片;
- 自定义冰球队服(编号、颜色)、场上位置(守门员/前锋/后卫)、持杆手(左/右)以及艺术风格(写实、乐高、国漫等);
- 后端调用 Coze 平台的工作流 API,完成图像生成;
- 最终返回生成结果并展示。
这类"趣味换脸/换装"类应用非常适合社交传播,比如冰球协会举办活动时,鼓励用户上传自家宠物照片生成"冰球明星",再分享至朋友圈,既有趣又具传播性。
二、技术栈与核心流程
技术选型
- 前端框架 :Vue 3(
<script setup>+ Composition API) - 状态管理 :
ref响应式变量 - HTTP 请求 :原生
fetch - AI 能力平台 :Coze(提供工作流和文件上传 API)
- 环境变量 :
import.meta.env.VITE_PAT_TOKEN(用于安全存储 PAT Token)
核心业务流程
- 图片预览 :用户选择图片后,立即在前端显示预览(使用
FileReader+ Base64); - 上传图片 :将图片通过
FormData上传至 Coze 文件服务,获取file_id; - 调用工作流 :携带
file_id与用户配置参数,调用 Coze 工作流 API; - 展示结果:解析返回的图片 URL 并渲染。
三、代码详解:从模板到逻辑
1. 模板结构(Template)
xml
<template>
<div class="container">
<div class="input">
<!-- 图片上传与预览 -->
<div class="file-input">
<img :src="imgPreview" alt="" v-if="imgPreview">
<input type="file"
ref="uploadImage"
accept="image/*"
@change="updataImageData"
required>
</div>
<!-- 配置项:队服、位置、风格等 -->
<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>
<!-- 其他颜色... -->
</select>
</div>
</div>
<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>
<!-- 多种艺术风格... -->
</select>
</div>
</div>
<!-- 生成按钮 -->
<div class="generate">
<button @click="generate">生成</button>
</div>
</div>
<!-- 输出区域 -->
<div class="output">
<div class="generated">
<img :src="imgUrl" alt="" v-if="imgUrl">
<div v-if="status">{{ status }}</div>
</div>
</div>
</div>
</template>
✅ 关键点:
- 使用
v-if控制预览图和结果图的显示;accept="image/*"限制仅可选择图片文件;- 所有配置项均通过
v-model双向绑定到响应式变量。
2. 响应式状态声明(Script Setup)
ini
import { ref, onMounted } from 'vue'
const imgPreview = ref('') // 本地预览图(Base64)
const uniform_number = ref(10)
const uniform_color = ref('红')
const position = ref(0)
const shooting_hand = ref('左手') // 注意:实际传给后端的是 0/1,此处为显示用
const style = ref('写实')
// 生成状态与结果
const status = ref('')
const imgUrl = ref('')
// Coze API 配置
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 = '7567272503635771427'
🔒 安全提示 :
VITE_PAT_TOKEN是 Personal Access Token,绝不能硬编码在代码中 !应通过.env文件注入,并确保.gitignore中排除该文件。
3. 图片预览功能:用户体验的关键
javascript
const uploadImage = ref(null)
onMounted(() => {
console.log(uploadImage.value) // 挂载后指向 input DOM
})
// 状态 null -> input DOM ref也可以用来绑定DOM元素
const updataImageData = () => {
const input = uploadImage.value
if (!input.files || input.files.length === 0) return
// 文件对象 html新特性
const file = input.files[0]
const reader = new FileReader() //
reader.readAsDataURL(file)
// readAsDateURL 返回Base64编码的DataURL 可直接用于<img src>
reader.onload = (e) => {
imgPreview.value = e.target.result // // 响应式状态 当拿到图片文件后 立马赋给imgPreview的value 那么此时template中img的src就会接收这个状态 从而响应展示图片
}
}
🌟 为什么需要预览?
- 用户上传的图片可能较大,上传需时间;
- 立即显示预览能提升交互反馈感;
FileReader.readAsDataURL()将图片转为 Base64,无需网络请求即可显示。
4. 上传图片到 Coze:获取 file_id
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])
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
// 当code为0时 表示没有错误 那么这里进行判断 当不为0时 返回错误信息给status.value
}
return ret.data.id // 关键:返回 file_id 供后续工作流使用
}
⚠️ 常见错误排查:
- 若返回
{"code":700012006,"msg":"cannot get access token from Authorization header"},说明patToken未正确设置或格式错误;- 确保请求头为
'Authorization': 'Bearer xxx',注意大小写和空格。
5. 调用 Coze 工作流:生成 AI 图像
javascript
const generate = async () => {
status.value = '图片上传中...'
const file_id = await uploadFile()
if (!file_id) return
status.value = '图片上传成功,正在生成中...'
const parameters = {
picture: JSON.stringify({ file_id }), // 注意:需 stringify
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,
parameters
})
})
const ret = await res.json()
if (ret.code !== 0) {
status.value = ret.msg
return
}
const data = JSON.parse(ret.data) // 注意:Coze 返回的是字符串化的 JSON
imgUrl.value = data.data
status.value = ''
}
❗ 重要细节:
picture字段必须是JSON.stringify({ file_id }),因为 Coze 工作流节点可能期望字符串输入;ret.data是字符串,需再次JSON.parse才能得到真正的结果对象;- 若遇到
{"code":4000,"msg":"The requested API endpoint GET /v1/workflow/run does not exist..."},说明你用了 GET 方法,但该接口只支持 POST!
四、样式与布局(Scoped CSS)
xml
<style scoped>
.container {
display: flex;
flex-direction: row;
height: 100vh;
}
.input {
display: flex;
flex-direction: column;
min-width: 330px;
}
.generated {
width: 400px;
height: 400px;
border: solid 1px black;
display: flex;
justify-content: center;
align-items: center;
}
</style>
✨ 使用
scoped确保样式隔离,避免污染全局;弹性布局实现左右两栏(配置区 + 结果区)。
五、总结与延伸
本项目完整展示了如何将 前端交互 与 AI 工作流 结合:
- 利用 Vue3 的响应式系统管理状态;
- 通过
FileReader实现即时预览; - 使用
fetch + FormData安全上传文件; - 调用 Coze API 实现"上传 → 生成 → 展示"闭环。
最后提醒:
- 务必保护好你的
PAT Token; - 遵守 Coze 的 API 调用频率限制,如果无法响应,可以尝试更换你的Coze API;
- 测试不同风格下的生成效果,优化用户体验。
通过这个小而美的项目,你不仅能掌握 Vue3 的实战技巧,还能深入理解如何将 AI 能力无缝集成到 Web 应用中。快去试试吧,让你的宠物穿上冰球队服,成为下一个 AI 冰球明星!🏒🐶