让你的宠物闪耀橄榄球场!
上传一张照片,选择号码与位置,AI 即刻生成你家猫狗身穿战袍、驰骋冰场的拟人化形象。本文将带你用 Coze 多模态工作流 + Vue 3,从零打造这个趣味 AI 应用。
1. 构建你的AI COZE 工作流
具体关于 Coze 账号注册、工作流创建及 PAT Token 获取等操作步骤,可参考我的指南:
从 TRAE 脚手架 到 Coze 智能体 :打造支持 RAG 的编程教育客服系统 掘金
Coze 智能体创建步骤指南
完成iceball_player工作流的设计
开始节点:定义输入接口(图片必填,其他可选)
在设计工作流的「开始节点」时,需明确数据接口结构:图片输入是必填项,而其他参数(如球衣号码、场上位置、风格类型等)可根据实际需求自由定义和扩展。

代码节点:参数校验与默认值填充
在工作流中添加一个代码节点,用编程逻辑对传入的参数进行校验:若用户未提供某项输入(如球衣号码、位置或风格),则自动赋予合理的默认值,确保后续流程稳定运行。
添加校验和加入默认值的代码

代码部分
const random = (start: number, end: number) => {
const p = Math.random();
return Math.floor(start * (1 - p) + end * p);
}
async function main({ params }: Args): Promise<Output> {
if (params.position == null) params.position = random(0, 3);
if (params.shooting_hand == null) params.shooting_hand = random(0, 2);
const style = params.style || '写实';
const uniform_number:string = (params.uniform_number || 10).toString();
const uniform_color = params.uniform_color || '红';
const position = params.position == 0 ? '守门员': (params.position == 1 ? '前锋': '后卫');
const shooting_hand = params.shooting_hand == 0 ? '左手': '右手';
const empty_hand = params.shooting_hand ? '左手': '右手';
// 构建输出对象
const ret = {
style,
uniform_number,
uniform_color,
position,
shooting_hand,
};
return ret;
}
图片理解节点:解析宠物视觉特征
这个节点用于分析用户上传的宠物图片,提取其关键视觉特征(如品种、毛色、面部朝向、体型等),为后续生成拟人化冰球运动员形象提供语义依据。

特征提取节点:结构化关键属性
该节点并不直接处理图像,而是基于前一个 imgUnderstand_1 节点输出的文本描述(例如“一只橘色短毛猫,正面朝向,大眼睛,表情慵懒”),从中结构化地提取关键宠物特征,如品种、毛色、姿态、面部特征等。这些提取出的字段将作为后续生成环节的精细化控制参数,使 AI 输出更贴合宠物的真实形象。

提示词
你是动物学家,负责丛动物描述中,提取出该动物(主要是外表)里最有图特性的特征,例如特征的肤色、表情、神态、动作等等。
图像生成节点:融合提示词生成拟人化形象
图像生成步骤是整个流程的核心,它依赖于之前节点处理得到的各种风格偏好、图片理解内容、特征提取信息以及正向和负向提示词。这些输入共同作用,以创建最终的图像作品。
首先,需要考虑从前面代码节点获取的风格倾向,这决定了图像的整体艺术风格。接着,利用图片理解节点提供的主题描述来定义图像的基本场景。然后,根据特征节点提供的细节信息调整图像的具体元素。最后,通过正向提示词突出期望的视觉效果,并用负向提示词避免不想要的特性。结合所有这些要素,即可生成理想的图像。

正向提示词
用动物的形象和特征,将该动物**拟人**为一名宠物儿童冰球员,生成{{style}}风格的冰球球员照片,球员身穿{{uniform_color}}色队服,佩戴同色的冰球头盔,队服号码为{{uniform_number}}号,球员位置是{{position}},用{{shooting_hand}}握着球杆,另一只手空着。该照片图像风格为{{style}}。
# 动物形象描述
{{description}}
# 独特外貌特征
{{details}}
# 注意
- 照片中应强化动物独特的外貌特征,以增加辨识度
- 如果球员位置是守门员,画面中应该有冰球球门
负向提示词
球员双手各握一根球杆
球员未佩戴头盔
球员吃东西
画面中出现除了冰球之外的其他球类
地点不在冰球赛场
球员四足站立

总结
至此,我们已完成「萌宠冰球运动员生成器」工作流的核心搭建:从用户上传图片,到理解内容、提取特征、填充默认参数,再到融合风格与提示词完成最终图像生成。每一步都环环相扣——前端负责体验,工作流负责智能。这不仅是一个趣味 AI 应用的实现,更是一套可复用的“前端 + Coze 多模态工作流”开发范式。接下来,只需将工作流 ID 与前端对接,就能让每一只萌宠闪耀 NHL 冰场!
工作流预览
最终结果


二、搭建 Vue 3 前端交互界面
为了让用户能轻松上传宠物照片并生成专属冰球明星,我们需要构建一个直观、响应迅速的前端界面。整个过程分为几个清晰的步骤,每一步都为最终体验服务。
第一步:用 Vite 快速初始化 Vue 3 项目
我们从零开始,使用 Vite 官方脚手架创建一个轻量级 Vue 3 应用:
npm init vite
在交互提示中:
-
Project name:输入项目名称(如
pet-iceball)
-
Framework:选择
Vue
-
Variant:选择
JavaScript(也可选 TypeScript,本文以 JS 为例)
创建完成后,进入目录并安装依赖:
cd pet-iceball
npm install
此时项目结构简洁清晰:
src/
├── main.js // 应用入口
├── App.vue // 根组件
└── style.css // 全局样式
运行 npm run dev,若浏览器成功打开默认欢迎页,说明开发环境已就绪!
第二步:实现图片上传与即时本地预览
用户上传图片后若没有视觉反馈,很容易误以为“没反应”。因此,我们在调用 AI 之前,先实现纯前端的图片预览——让用户立刻看到自己选的是哪只毛孩子!
在 App.vue 中搭建基础 UI
<template>
<div class="container">
<div class="input-section">
<input
type="file"
ref="uploadImage"
accept="image/*"
@change="updateImageData"
/>
<img :src="imgPreview" alt="预览图" v-if="imgPreview" class="preview" />
</div>
</div>
</template>
使用 FileReader 实现预览逻辑
<script setup>
import { ref } from 'vue'
const uploadImage = ref(null)
const imgPreview = ref('')
const updateImageData = () => {
const input = uploadImage.value
if (!input?.files?.length) return
const file = input.files[0]
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (e) => {
imgPreview.value = e.target.result // 触发 Vue 响应式更新
}
}
</script>
✅ 为什么有效?
-
FileReader.readAsDataURL() 将文件转为 Base64 字符串,可直接用于 <img> 标签;
-
imgPreview 是响应式变量,赋值后 Vue 自动更新视图;
- 整个过程不涉及网络请求,秒级响应,体验流畅。
第三步:用响应式状态统一管理用户反馈
为了让用户清楚知道当前处于“上传中”、“生成中”还是“出错了”,我们引入两个核心状态:
const status = ref('') // 显示操作状态或错误信息
const resultUrl = ref('') // 存放 Coze 返回的生成图片 URL
并在模板中绑定:
<template>
<!-- ... -->
<button @click="generate" :disabled="!imgPreview">生成冰球明星</button>
<p v-if="status" class="status">{{ status }}</p>
<div class="output" v-if="resultUrl">
<img :src="resultUrl" alt="AI 生成结果" class="result-image" />
</div>
</template>
这样,无论后台处理耗时多久,用户都能获得明确反馈,避免“点击无响应”的困惑。
第四步:安全、规范地调用 Coze 工作流
要让前端真正驱动 AI 生成,必须严格遵循 Coze 的 API 调用流程:先上传文件获取 file_id,再触发工作流。
1. 配置 PAT Token(安全第一!)
在项目根目录创建 .env.local 文件:
VITE_PAT_TOKEN=your_coze_pat_token_here
⚠️ 务必将其加入 .gitignore,防止泄露!
Vite 会自动将 VITE_ 开头的变量注入客户端代码。
在代码中读取:
const patToken = import.meta.env.VITE_PAT_TOKEN
2. 上传图片到 Coze 文件服务
const uploadFile = async () => {
status.value = '图片上传中...'
const file = uploadImage.value?.files?.[0]
if (!file) return null
const formData = new FormData()
formData.append('file', file) // 字段名必须为 'file'
try {
const res = await fetch('https://api.coze.cn/v1/files/upload', {
method: 'POST',
headers: { Authorization: `Bearer ${patToken}` },
body: formData
})
const data = await res.json()
if (data.code !== 0) throw new Error(data.msg || '上传失败')
return data.data.id // 返回 Coze 分配的 file_id
} catch (err) {
status.value = err.message
return null
}
}
3. 调用工作流生成图像
const generate = async () => {
const fileId = await uploadFile()
if (!fileId) return
status.value = '正在生成冰球明星...'
// 构造参数对象,字段名必须与工作流「开始节点」完全一致
const parameters = {
picture: JSON.stringify({ file_id: fileId }), // 注意:必须是字符串化的 JSON
style: style.value,
uniform_number: uniform_number.value,
uniform_color: uniform_color.value,
position: position.value,
shooting_hand: shooting_hand.value
}
const res = await fetch('https://api.coze.cn/v1/workflow/run', {
method: 'POST',
headers: {
Authorization: `Bearer ${patToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
workflow_id: 'your_workflow_id_here', // 替换为你的实际 ID
parameters
})
})
const ret = await res.json()
if (ret.code !== 0) {
status.value = ret.msg
return
}
// Coze 的 data 字段是 JSON 字符串,需二次解析
const result = JSON.parse(ret.data)
resultUrl.value = result.data // 最终图片 URL
status.value = ''
}
第五步:绑定用户自定义选项
为了让用户自由定制冰球明星形象,我们在 <script setup> 中声明所有可配置项:
const uniform_number = ref(10)
const uniform_color = ref('红')
const position = ref(0) // 0:守门员, 1:前锋, 2:后卫
const shooting_hand = ref(0) // 0:左手, 1:右手
const style = ref('写实')
然后在模板中添加对应的 <select> 或 <input> 元素(例如):
<select v-model="position">
<option :value="0">守门员</option>
<option :value="1">前锋</option>
<option :value="2">后卫</option>
</select>
这些响应式变量会自动同步到 parameters 中,确保用户的选择准确传递给 Coze 工作流。
全部代码:
<template>
<div class="container">
<div class="input">
<div class="file-input">
<input
type="file"
ref="uploadImage"
accept="image/*"
@change="updateImageData"
required
/>
</div>
<img :src="imgPreview" alt="" v-if="imgPreview"/>
<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>
<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>
<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>
<script setup>
import { ref ,onMounted} from 'vue'
//script + setup 是vue3最好的代码组织方式
//composition API 组合
//直接在script setup中定义函数
//标记一个DOM对象 如果要做就用ref
//未挂载时是null 挂载后是DOM对象 template 中的ref绑定的对象
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 = '7584046346609917971'
const uniform_number = ref(10)//队服编号
const uniform_color = ref('red')//队服颜色
const position = ref(0)//位置
const shooting_hand = ref(0)//持杆
const style = ref('写实')//风格
//数据状态
const status = ref('')//空 -> 上传中 -> 生成中 -> 成功
const imgUrl = ref('')//生成的图片url
//生成图片模块
const generate = async()=>{
status.value = "图片上传中..."
const file_id = await uploadFile()
if(!file_id) return;
status.value = "图片上传成功,正在生成中..."
//woekflow调用
const parameters = {
picture:JSON.stringify({
file_id//安全问题
}),
style:style.value,
uniform_number:uniform_number.value,
uniform_color:uniform_color.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;//msg 错误消息
return
}
const data = JSON.parse(ret.data);
console.log(ret.data);
status.value = "图片生成成功"
imgUrl.value = data.data//更新响应式对象
}
//先上传到coze服务器
const uploadFile = async ()=>{
//post 有请求体 http 协议
const formData = new FormData();//收集表单提交数据
const input = uploadImage.value
if(!input.files || input.files.length <= 0) return;
formData.append('file',input.files[0])//请求体里加上文件
//向coze发送http请求 上传
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;//msg 错误消息
return
}
return ret.data.id;
}
//图片预览模块
const uploadImage = ref(null)
const imgPreview = ref('')//声明响应式对象
// null -> dom对象 变化
//挂载了
onMounted(()=>{
// console.log(uploadImage.value);
})
const updateImageData =()=>{
//html5 文件对象
// console.log(uploadImage.value.files[0]);
const input = uploadImage.value
if(!input.files || input.files[0] === 0){
return
}
const file = input.files[0]//文件对象 html5 新特性
console.log(file);
//FileReader 文件读取对象
const reader = new FileReader();
reader.readAsDataURL(file);//url 异步的
reader.onload = (e) => {//读完了
imgPreview.value = e.target.result//更新响应式对象
}
}
</script>
<style scoped>
.container {
display: flex;
flex-direction: row;
align-items: start;
justify-content: start;
height: 100vh;
font-size: .85rem;
}
.preview {
max-width: 300px;
margin-bottom: 20px;
}
.settings {
display: flex;
flex-direction: row;
align-items: start;
justify-content: start;
margin-top: 1rem;
}
.selection {
width: 100%;
text-align: left;
}
.selection input {
width: 50px;
}
.input {
display: flex;
flex-direction: column;
min-width: 330px;
}
.file-input {
display: flex;
margin-bottom: 16px;
}
.output {
margin-top: 10px;
min-height: 300px;
width: 100%;
text-align: left;
}
button {
padding: 10px;
min-width: 200px;
margin-left: 6px;
border: solid 1px black;
}
.generate {
width: 100%;
margin-top: 16px;
}
.generated {
width: 400px;
height: 400px;
border: solid 1px black;
position: relative;
display: flex;
justify-content: center;
/* 水平居中 */
align-items: center;
/* 垂直居中 */
}
.output img {
width: 100%;
}
</style>
通过以上步骤,我们完成了从前端交互到 AI 能力调用的完整链路。整个过程既保证了用户体验的流畅性,又严格遵循了 Coze 的 API 规范,为后续功能扩展(如保存历史、分享结果等)奠定了坚实基础。
总结
这个“萌宠冰球运动员生成器”看似充满魔法,实则建立在一套清晰、可复现的开发逻辑之上。我们没有一上来就对接 AI,而是采用 “先体验,再能力;先本地,再云端” 的渐进式策略,把复杂问题拆解为四个关键阶段,每一步都稳扎稳打:
-
从骨架开始:用 Vite + Vue 3 快速搭建一个现代化前端项目,利用 Composition API 和响应式系统,为后续功能提供干净、可维护的基础。
-
让用户立刻看到反馈:在调用任何 AI 之前,先实现本地图片预览。借助
FileReader,用户一选图,界面马上响应——这种“所见即所得”的体验,是建立信任的第一步。
-
用状态驱动一切:通过
ref() 声明 status 和 imgUrl 等核心状态,让 UI 自动跟随数据变化。无论是加载提示、错误信息还是最终结果,都由数据说了算,逻辑清晰、维护简单。
-
安全、规范地接入 AI:严格遵循 Coze 的 API 要求——先用
FormData 上传文件获取 file_id,再以正确格式调用工作流,并对嵌套的 JSON 响应做双重解析。每一步都考虑了认证、格式、错误兜底,确保链路可靠。
这套方法论的价值远不止于本项目。它提供了一种通用模式:先构建直观的前端交互,再逐步注入 AI 能力。既降低了开发门槛,又保障了用户体验的连贯性与可控感。
最终,你不仅得到了一只穿着冰球战袍的橘猫,更掌握了一条通往“低代码 + 高创意”AI 应用的可行路径。