用 Vue3 + Coze API 打造冰球运动员 AI 生成器:从图片上传到风格化输出

本文将带你从零构建一个基于 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)

核心业务流程

  1. 图片预览 :用户选择图片后,立即在前端显示预览(使用 FileReader + Base64);
  2. 上传图片 :将图片通过 FormData 上传至 Coze 文件服务,获取 file_id
  3. 调用工作流 :携带 file_id 与用户配置参数,调用 Coze 工作流 API;
  4. 展示结果:解析返回的图片 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 冰球明星!🏒🐶

相关推荐
Dragon Wu2 小时前
TailWindCss 核心功能总结
前端·css·前端框架·postcss
SHolmes18542 小时前
给定某日的上班时间段,计算当日的工作时间总时长(Python)
开发语言·前端·python
掘金安东尼2 小时前
顶层元素问题:popover vs. dialog
前端·javascript·面试
掘金安东尼2 小时前
React 的新时代已经到来:你需要知道的一切
前端·javascript·面试
掘金安东尼2 小时前
React 已经改变了,你的 Hooks 也应该改变
前端·vue.js·github
Codebee2 小时前
A2UI vs OOD全栈方案:AI驱动UI的两种技术路径深度解析
前端·架构
掘金安东尼2 小时前
TypeScript 严格性是非单调的:strict-null-checks 和 no-implicit-any 的相互影响
前端·面试
1024肥宅3 小时前
现代 JavaScript 特性:TypeScript 深度解析与实践
前端·javascript·typescript
麦麦大数据3 小时前
F053 投标推荐可视化系统+推荐算法vue+flask+爬虫
vue.js·爬虫·flask·可视化·推荐算法·招投标