前言:AI 多模态时代,前端能做的不只是"调接口"

2026 年了,“前端调 AI API"这件事已经不是什么新鲜事。但如果你的认知还停留在"前端发个fetch、拿个文本回显到页面上”——那你可能错过了多模态这个更有想象力的方向。

f1693c6406b3ab706655a49a2531b7f2.png

多模态(Multimodal) 是 2025-2026 年 AI 领域最大的突破之一。模型不再只是"输入文字、输出文字",而是能理解图片、生成图片、甚至图文混合输入。这意味着:

  • 你给模型两张穿搭图 + 一句文字描述 → 它生成一张融合后的效果图
  • 你给模型一张产品图 + 一段风格描述 → 它生成电商场景图
  • 你给模型一张草图 + 一段文字 → 它生成渲染后的成品

这张图片正是我调用多模态生成的结果!!!

在这里插入图片描述

本文将从零开始,带你完成一个完整的多模态生图前端项目:

  1. 阿里百炼平台(Bailian)获取 API Key
  2. 使用 Vite 搭建前端项目脚手架
  3. .env.local 安全管理 API Key(前端环境密钥保护的核心方案)
  4. 配置 Vite 代理转发解决跨域(CORS)问题
  5. 调用 Qwen Image 2.0 Pro(通义万象)多模态生图 API
  6. 实现完整的 错误处理 和图片渲染逻辑

读完本文,你将掌握前端工程化 + AI 多模态的完整开发链路。

本文是「AI 全栈路线」系列第三篇——前两篇分别覆盖了 [Node.js + DeepSeek 后端调用] 和 [Python + AI 实战],本篇聚焦前端工程化视角下的多模态 AI 调用


一、先拿到钥匙:阿里百炼平台获取 API Key

1.1 百炼是什么?

阿里百炼(Bailian) 是阿里云推出的大模型服务平台,上面汇集了阿里系的全线 AI 模型能力:

能力类型代表性模型说明
文本对话Qwen3(通义千问)对标 GPT-4 的国产大模型
多模态生图Qwen Image 2.0 Pro本文主角——图文混合输入、可控生成
语音识别ParaformerASR 语音转文字
向量化Text EmbeddingRAG 场景必备

百炼的定位:一个平台管理所有模型,一套 API Key 调用所有能力。

1.2 获取 API Key

进入 百炼控制台 API Key 页面

  1. 登录阿里云账号(支付宝扫码即可)
  2. 点击「创建 API Key」
  3. 复制生成的 Key(形如 sk-ws-H.xxxxxxxx

⚠️ 重要:API Key 只会显示一次,请立即保存到安全位置。这个 Key 代表你的账户身份,泄露意味着别人可以盗刷你的额度。

百炼的控制台还提供了模型广场(按分类浏览所有可用模型)、在线体验(无需写代码直接测试模型效果)和用量监控——这些功能在你选型和调试模型时会经常用到。


二、项目初始化:Vite 脚手架 + 依赖

2.1 为什么用 Vite?

前端发展到 2026 年,Vite 已经成为事实标准的项目构建工具。它的核心优势:

  • 原生 ESM 开发服务器——冷启动毫秒级
  • HMR(热模块替换)——改代码浏览器秒级更新
  • 内置代理配置——一行配置解决开发环境跨域
  • .env 开箱即用——环境变量管理零配置

对于 AI 前端项目,Vite 还有一个关键价值:它通过 import.meta.env 机制,保证 API Key 在构建时注入但不会暴露到生产代码中。 这一点后面会详细展开。

2.2 创建项目

npm init vite@latest qwen-image-demo
# 选择 Vanilla + JavaScript

cd qwen-image-demo
npm install

生成的项目结构:

qwen-image-demo/
├── .env.local          # API Key 存放处(不提交 Git)
├── .gitignore          # 声明忽略文件
├── index.html          # HTML 入口
├── package.json        # 项目配置
├── vite.config.js      # Vite 配置文件
├── public/             # 静态资源(不参与构建处理)
└── src/
    └── main.js         # 业务入口

依赖只有一个:

{
  "devDependencies": {
    "vite": "^8.0.12"
  }
}

零运行时依赖。 你会发现整个项目没有装 openai、没有装 axios、也没有装任何 AI SDK——我们用原生 fetch + JSON 直接调用 DashScope API。如果你刻意要学,永远从最原始的方式开始,不要一上来就被框架包一层。


三、API Key 安全管理:前端环境的关键防护方案

3.1 前端 Key 的困境

后端项目可以通过 dotenvprocess.env 管理密钥,Key 存储在服务器上,用户看不见。

前端不一样——所有代码最终都会跑到用户浏览器里。 如果你写了:

// ❌ 绝对不要这样做!你的 Key 等于公开发布
const apiKey = 'sk-ws-H.xxxxxxxxxxxxxx';

那你的 API Key 就相当于公开了——任何人打开浏览器 DevTools → Sources 面板就能看到。额度被盗刷只是时间问题。

3.2 .env.local + import.meta.env:Vite 的安全方案

Vite 提供了环境变量机制,格式要求:变量名必须以 VITE_ 开头

创建 .env.local

# .env.local
VITE_QWEN_API_KEY=sk-ws-H.RPRRIMR.nBdD...你的真实Key

代码中访问:

// src/main.js
const apiKey = import.meta.env.VITE_QWEN_API_KEY;
// 构建时 Vite 会把它替换成真实值

核心机制

.env.local 文件
    ↓ (Vite 构建时读取)
被内联替换到 import.meta.env.VITE_QWEN_API_KEY
    ↓
业务代码中拿到的是字符串值

🔑 关键理解import.meta.env 不是运行时读取文件——它是构建时静态替换。Vite 在打包时把你的 .env.local 里的值直接内联到代码中。这意味着:

  • 开发时你能用这个 Key 调试
  • 生产构建时,如果你没在部署环境配置这个变量,构建出来的代码里就没有这个 Key
  • .env.local 已经在 .gitignore 中被忽略(Vite 脚手架默认配置),不会提交到 Git

3.3 安全边界:代理才是最终的防线

不过要诚实地说:即使是 import.meta.env,在开发模式下 Key 依然会出现在浏览器内存中。 更安全的架构是:

浏览器 → Vite Dev Server(代理)→ DashScope API
         ↑ 持有 API Key
         浏览器不直接接触 Key

这个方案的核心是——API Key 只存在 Vite Dev Server 这一层,浏览器发出的请求本身不带 Key,由代理层注入。 本文会同时展示两种方案,帮助你理解各自适用场景。

3.4 .gitignore 保护

Vite 脚手架生成的 .gitignore 已经包含了关键规则:

node_modules
dist
*.local          # ← 这条规则保证了 .env.local 不会被提交

*.local 这个通配符匹配任何以 .local 结尾的文件。 所以 .env.local.env.development.local 等都会被 Git 忽略。本地和远程的边界清晰分离——这是工程化的基本素养。


四、Vite 代理配置:解决跨域问题

4.1 问题:浏览器直接请求会被 CORS 拦截

DashScope API 的地址是 https://dashscope.aliyuncs.com。如果你从前端直接 fetch 这个地址:

// ❌ 浏览器会报 CORS 错误
fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation')

控制台会提示:

Access to fetch at 'https://dashscope.aliyuncs.com/...' from origin 'http://localhost:5173'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header...

原因:浏览器的同源策略(Same-Origin Policy)禁止一个 origin(localhost:5173)向另一个 origin(dashscope.aliyuncs.com)发请求,除非目标服务器明确返回了允许跨域的响应头。阿里云 API 服务器不会给 localhost 发这个头——否则任何人都能在浏览器里直接调它了。

4.2 解决方案:Vite Server Proxy

Vite 的 server.proxy 配置可以在开发服务器上架一座桥:

// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    proxy: {
      '/api/dashscope': {
        target: 'https://dashscope.aliyuncs.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api\/dashscope/, ''),
      },
    },
  },
})

这段配置做了什么? 逐行解释:

配置项说明
'/api/dashscope'匹配所有以 /api/dashscope 开头的请求路径
target将匹配到的请求转发到这个地址
changeOrigin: true把请求头中的 Origin 改成 target 的域名(避免目标服务器拒绝)
rewrite路径重写——把 /api/dashscope 前缀去掉后再发给目标

请求流转过程

浏览器发起:
  fetch('/api/dashscope/api/v1/services/aigc/multimodal-generation/generation')
       ↓
Vite Dev Server 拦截(匹配到 /api/dashscope 前缀)
       ↓
去掉前缀 /api/dashscope,变成:
  /api/v1/services/aigc/multimodal-generation/generation
       ↓
转发到:https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation
       ↓
拿到响应 → 返回给浏览器

结果:从浏览器的视角看,请求是发给 localhost:5173(同源),不存在跨域问题。代理层在服务端帮你走了跨域请求——服务端不受浏览器同源策略限制。

💡 这是前端工程化的经典模式:Vite 就是前端项目在工程化这块的"大管家"——Dev Server、HMR、构建打包、代理转发、环境变量,全部由它接管。npm run dev 一启动,Vite 就接管了整个项目的开发环境。


五、核心实战:调用 Qwen Image 多模态生图 API

5.1 理解多模态生图

传统的文生图(Text-to-Image)只支持一段文字 → 一张图。多模态生图支持多张参考图 + 文字描述 → 一张融合图

用本文示例来理解:

  • 输入:图1(一个女生)+ 图2(一条黑裙子)+ 图3(一个坐姿)+ 文字描述"图1的女生穿着图2中的黑色裙子按图3的姿势坐下"
  • 输出:一张融合了三张参考图 + 文字指令的生成图

这个能力背后是 Qwen Image 2.0 Pro 模型——阿里通义万象系列的最新版本,专门做可控的多模态图像生成。

5.2 完整代码

// src/main.js

// 从 Vite 环境变量中获取 API Key(构建时内联替换)
const apiKey = import.meta.env.VITE_QWEN_API_KEY;

// 获取页面挂载点
const root = document.querySelector('#app');

// 核心函数:调用多模态生图 API
const generateImage = async () => {
  const res = await fetch(
    // 注意:这里请求的是 Vite 代理路径,不是直接请求 dashscope
    '/api/dashscope/api/v1/services/aigc/multimodal-generation/generation',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        // API Key 通过 Authorization 头携带
        'Authorization': `Bearer ${apiKey}`,
      },
      // 请求体:序列化为 JSON 字符串后以二进制传输
      body: JSON.stringify({
        "model": "qwen-image-2.0-pro",
        "input": {
          "messages": [
            {
              "role": "user",
              "content": [
                {
                  // 图1:参考人物
                  "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/thtclx/input1.png"
                },
                {
                  // 图2:参考服饰
                  "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/iclsnx/input2.png"
                },
                {
                  // 图3:参考姿势
                  "image": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250925/gborgw/input3.png"
                },
                {
                  // 文字指令
                  "text": "图1的女生穿着图2中的黑色裙子按图3的姿势坐下"
                }
              ]
            }
          ]
        },
        "parameters": {
          "n": 1,               // 生成几张图
          "size": "1024*1536"   // 输出尺寸(宽*高)
        }
      })
    }
  );

  // 解析 JSON 响应
  const data = await res.json();

  // 错误处理:HTTP 状态码非 2xx 时给用户反馈
  if (!res.ok) {
    root.innerHTML = `<p style="color:red">请求失败(${res.status}): ${data.message || '请稍后再试'}</p>`;
    return '';
  }

  // 从深层嵌套结构中取出生成的图片 URL
  return data.output.choices[0].message.content[0].image;
}

// 渲染图片到页面
const renderImage = (imageUrl) => {
  root.innerHTML = `<img src="${imageUrl}" alt="AI生成的图片" />`;
}

// 单点入口
const main = async () => {
  const imageUrl = await generateImage();
  if (imageUrl) {
    renderImage(imageUrl);
  }
};

main();

5.3 逐层解析请求结构

请求路径
/api/dashscope/api/v1/services/aigc/multimodal-generation/generation

这不是 DashScope 的真实地址——它会被 Vite 代理拦截,去掉 /api/dashscope 前缀后转发到:

https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation
请求体结构

多模态生图 API 的 messages 结构比文本对话更丰富——content 是一个数组,元素可以是 image(图片 URL)或 text(文字指令):

"content": [
  { "image": "https://..." },   // 多张参考图片
  { "image": "https://..." },
  { "image": "https://..." },
  { "text": "图1的女生穿着图2中的黑色裙子按图3的姿势坐下" } // 文字指令
]

模型会根据所有 content 元素的内容综合理解你的意图。 图片 + 文字的混合输入,是多模态和传统文生图最本质的区别。

参数说明
参数说明本文取值
model模型名称qwen-image-2.0-pro
input.messages[].content[].image参考图片 URL阿里云文档的示例图
input.messages[].content[].text文字指令描述如何使用参考图
parameters.n生成图片数量1
parameters.size输出尺寸1024*1536(竖版)

六、错误处理:从 404 到 CORS 的踩坑实录

6.1 问题:API 返回非成功状态时前端直接崩溃

在最初版本中,代码拿到 res.json() 后就直接取 data.output.choices[0]...。但如果 API 返回了错误(如 401 认证失败、400 参数错误、404 地址写错),data.outputundefined——代码直接抛 TypeError 白屏。

6.2 修复:if (!res.ok) 守卫

const data = await res.json();

// ✅ 在访问深层属性之前,先检查 HTTP 状态
if (!res.ok) {
  root.innerHTML = `<p style="color:red">请求失败(${res.status}): ${data.message || '请稍后再试'}</p>`;
  return '';
}

// 只有请求成功时才访问 output 下的数据结构
return data.output.choices[0].message.content[0].image;

res.ok 是什么? fetch 返回的 Response 对象上有一个 ok 属性——当 HTTP 状态码在 200-299 范围内时为 true,否则为 false。这是判断请求是否成功的标准方式。

这条守卫语句覆盖了所有这些常见错误场景:

场景HTTP 状态码res.ok用户看到
API Key 错误401false“请求失败(401): Invalid API Key”
参数格式错误400false“请求失败(400): …”
路径写错404false“请求失败(404): …”
请求成功200true正常渲染图片

6.3 开发过程中的常见坑

  1. CORS 错误No 'Access-Control-Allow-Origin' header

    • 原因:浏览器直接请求跨域地址
    • 解决:Vite proxy 配置(见第四章)
    • 注意:Vite proxy 仅在 npm run dev 开发模式生效,生产环境需要在部署层配置反向代理
  2. 404 Not Found

    • 原因:请求路径写错,常见的是代理前缀和目标路径拼接错误
    • 排查:在 Vite 配置中加 configure 打印日志确认实际请求路径
    • 解决:确保 rewrite 规则正确去掉前缀
  3. 401 Unauthorized

    • 原因:API Key 错误或过期
    • 排查:确认 .env.local 中的 Key 是最新的、格式正确(sk- 开头)
    • 注意:百炼的 Key 和灵积(DashScope)旧版 Key 格式不同

七、异步流程控制:async/await 让代码像说话一样

7.1 为什么用 async/await

看三种写法的对比:

// ❌ 回调写法——嵌套地狱
fetch(url, options, (err, res) => {
  if (err) return;
  res.json((err, data) => {
    if (err) return;
    renderImage(data);  // 回调套回调
  });
});

// ⚠️ Promise 链——可读性中等
fetch(url, options)
  .then(res => res.json())
  .then(data => renderImage(data))
  .catch(err => console.error(err));

// ✅ async/await——像读小说一样自上而下
const res = await fetch(url, options);
const data = await res.json();
renderImage(data);

await 不会阻塞浏览器 UI 线程——它只暂停当前 async 函数的执行,事件循环照常运行。所以 await 等待 API 返回期间,页面不会卡住。

7.2 本文中的完整异步流程

const main = async () => {
  const imageUrl = await generateImage();   // ← 卡在这里等生图结果
  // generateImage 内部:
  //   await fetch(...)       ← 等 HTTP 响应
  //   然后 await res.json()  ← 等 JSON 解析
  
  if (imageUrl) {
    renderImage(imageUrl);                  // ← 拿到结果后才渲染
  }
};

main();

执行时间线

0ms    → main() 执行
1ms    → generateImage() 内部发起 fetch 请求
...    → 等待网络往返 + 模型推理(可能 3-15 秒)
N ms   → 拿到响应 → 解析 JSON → 取出图片 URL → 返回
N+1ms  → renderImage(imageUrl) → 图片显示在页面上
整个过程 UI 线程不阻塞,用户操作正常响应

八、完整的数据流转:从百炼 Key 到页面图片

回顾整个项目的全链路:

┌─────────────────────────────────────────────────────┐
│ 1. 阿里百炼控制台                                      │
│    https://bailian.console.aliyun.com                │
│    → 创建 API Key → 复制 sk-ws-...                   │
└────────────────────────┬────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────┐
│ 2. .env.local (本地文件,不提交 Git)                   │
│    VITE_QWEN_API_KEY=sk-ws-...                       │
└────────────────────────┬────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────┐
│ 3. Vite 构建层                                        │
│    import.meta.env.VITE_QWEN_API_KEY                 │
│    → 构建时静态替换为真实值                            │
└────────────────────────┬────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────┐
│ 4. 浏览器 → Vite Dev Server (localhost:5173)          │
│    fetch('/api/dashscope/...')                        │
│    携带 Header: Authorization: Bearer sk-ws-...       │
└────────────────────────┬────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────┐
│ 5. Vite Proxy 拦截                                    │
│    匹配 /api/dashscope 前缀                           │
│    → 去掉前缀 → 转发到 dashscope.aliyuncs.com         │
└────────────────────────┬────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────┐
│ 6. DashScope API(阿里云端)                           │
│    Qwen Image 2.0 Pro 模型推理                       │
│    输入:3张参考图 + 文字指令                          │
│    输出:融合后的图片 URL                              │
└────────────────────────┬────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────┐
│ 7. 响应返回 → 错误检查 (!res.ok)                       │
│    → 解析 JSON → 取出 images[0].url                  │
│    → innerHTML 渲染到 #app                           │
│    → 用户看到生成的图片 ✅                             │
└─────────────────────────────────────────────────────┘

七个环节,每一步都有它的工程意义。 不是"能跑就行"——而是要理解每一步为什么这样设计。


九、延伸的一些知识:前端 AI 开发的更多可能方向

掌握了这个基础骨架后,可以沿着这些方向深入:

方向说明关联技术
流式生成图片生成过程中的进度展示SSE / WebSocket
历史记录保存生成历史,支持回看localStorage / IndexedDB
参数调优面板让用户在页面上调整 sizen、negative prompt 等参数UI 控件 + 动态构造 body
图片上传替代 URL支持用户本地上传图片作为参考图File API + OSS 上传后传 URL
生产部署Nginx 反向代理替代 Vite proxyNginx + 环境变量管理
多模型切换同时调用多个生图模型做对比抽象 API 层 + 策略模式

十、总结:前端工程化 + AI 的核心套路

本文用一整个实战项目串起了这些知识点:

技术要点清单

  1. 阿里百炼平台(bailian.console.aliyun.com)——获取 API Key 的入口,阿里全系 AI 模型的管理中心
  2. Vite 项目脚手架 —— npm init vite,零配置启动前端工程
  3. .env.local + import.meta.env —— 前端环境变量的安全管理方案,VITE_ 前缀是关键
  4. Vite Server Proxy —— server.proxy 配置解决 CORS 跨域问题,rewrite 做路径重写
  5. Qwen Image 2.0 Pro 多模态 API —— messagescontent 数组中混合 image + text,实现图文融合生图
  6. fetch + JSON —— 不需要任何 AI SDK,原生 API 直接调用
  7. !res.ok 错误守卫 —— 在访问深层嵌套数据前先检查 HTTP 状态,防止白屏
  8. async/await 异步控制 —— 让异步代码像同步代码一样自上而下可读

核心心智模型

百炼获取Key

.env.local 安全存储

Vite import.meta.env 注入

fetch 调用 API

Vite Proxy 转发跨域

DashScope 模型推理

错误检查 res.ok

渲染结果到页面

🔥 前端工程化 + AI = 这个时代前端开发者的核心竞争力。

Vite 是你的"大管家"——它接管 Dev Server、环境变量、构建打包、代理转发。AI API 是你的"能力放大器"——以前一个设计师团队花一周做的图,现在一个前端工程师 10 秒调一次 API 就搞定了。

不是 AI 取代前端,是会用 AI 的前端取代不会用的。


如果这篇文章对你有帮助,欢迎点赞、收藏、评论。有问题也可以在评论区交流!

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐