从零搭建 AI 生图前端:Vite 工程化 + 通义千问 Image API 实战
本文带你从零开始,用 Vite 脚手架搭建一个轻量前端项目,通过 HTTP 调用阿里云 DashScope 的通义千问(Qwen)图像生成 API,并深入探讨前端环境变量管理的最佳实践。
项目概览
这是一个基于 Vite 的前端 Demo,核心功能是:
- 用户打开页面
- 前端直接通过
fetch调用阿里云 DashScope 的多模态生成接口 - 传入参考图 + 文字描述,让 Qwen 模型生成一张新图片
- 将生成的图片渲染到页面上
听起来很简单?但这里涉及一个关键问题:API Key 写在前端代码里会直接暴露。那在前端工程化体系下,怎么安全地管理 API Key 呢?这正是本文的重点。
项目结构
csharp
qwen-image-demo/
├── index.html # 入口 HTML
├── package.json # 项目配置
├── .env.local # 环境变量(API Key 存放处)
├── .gitignore # Git 忽略规则
├── public/
│ ├── favicon.svg # 网站图标
│ └── icons.svg # SVG 图标精灵
└── src/
├── main.js # 主逻辑:调用 API + 渲染
├── style.css # 全局样式
└── assets/ # 静态资源
├── hero.png
├── javascript.svg
└── vite.svg
第一步:Vite 初始化
bash
npm create vite@latest qwen-image-demo
cd qwen-image-demo
npm install
package.json 核心配置:
json
{
"name": "qwen-image-demo",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^8.0.12"
}
}
"type": "module" 开启 ES Module 模式,让我们可以直接使用 import.meta.env。
第二步:环境变量管理 ------ 本文核心
错误做法
很多人刚开始会这样写:
js
// 危险!绝对不要这样做!
const apiKey = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
API Key 明文写在前端代码里,任何人打开浏览器 DevTools 就能看到。即使你用了 Webpack/Vite 打包,字符串依然是明文的。
✅ 正确做法:Vite 环境变量
Vite 提供了内置的环境变量机制。在项目根目录 创建 .env.local 文件:
bash
# .env.local
VITE_QWEN_API_KEY=sk-your-api-key-here
两条铁律:
- 变量名必须以
VITE_开头,否则 Vite 不会暴露给客户端代码.env.local必须加入.gitignore,绝对不能提交到 Git 仓库
在代码中使用:
js
const apiKey = import.meta.env.VITE_QWEN_API_KEY;
Vite 在构建时会将 import.meta.env.VITE_* 静态替换为实际值。开发时通过 Vite Dev Server 注入,生产构建时内联到 bundle 中。
Vite 就是前端项目在工程化这块的"大管家" 。当你执行
npm run dev的那一刻,Vite 接管了整个项目:模块热更新、环境变量注入、ESM 原生支持、CSS 处理......全部由它统一调度。
安全边界
这里有一个重要的认知:环境变量最终还是会出现在前端 bundle 中 。VITE_* 变量的设计目的是「避免将密钥硬编码在源码中并提交到 Git」,而不是「让前端密钥对用户不可见」。
真正安全的做法是:
- 生产环境:API Key 只存放在后端,前端通过自己的后端中转请求
- 开发/Demo 阶段 :使用 Vite 环境变量 +
.gitignore防止密钥泄露到代码仓库
第三步:入口 HTML
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Qwen 图像生成 Demo</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
关键点:
<script type="module">启用浏览器原生 ESM,Vite 在开发阶段直接利用这个能力实现极速 HMR#app是挂载点,所有 DOM 操作都围绕它展开
第四步:核心逻辑 main.js
原版代码存在的问题
项目原始代码存在以下不足:
- 缺少 CSS 导入 ------ 写了
style.css但没有在 JS 中import,样式不会生效 - 无错误处理 ------ API 调用失败时没有任何提示,直接白屏
- 无响应校验 ------ 直接链式访问深层属性,任一环节为空都会导致运行时崩溃
- 加载状态缺失 ------ 图片生成需要数秒,用户看不到任何反馈
修正后的完整代码
js
// 导入样式(原版缺失,导致 CSS 不生效)
import './style.css';
const apiKey = import.meta.env.VITE_QWEN_API_KEY;
const root = document.querySelector('#app');
/**
* 调用 DashScope 多模态生成接口生成图片
* @returns {Promise<string>} 生成的图片 URL 或 base64
*/
const generateImage = async () => {
const res = await fetch(
// 替换为你的 DashScope 多模态生成端点,格式见官方文档
'https://<your-dashscope-endpoint>/api/v1/services/aigc/multimodal-generation/generation',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: 'qwen-image-plus', // 模型名称,按实际选用
input: {
messages: [
{
role: 'user',
content: [
{
image: 'https://example.com/ref-1.png' // 参考图 1
},
{
image: 'https://example.com/ref-2.png' // 参考图 2
},
{
image: 'https://example.com/ref-3.png' // 参考图 3
},
{
text: '参考图1的人物穿着图2的衣服,摆出图3的姿势'
}
]
}
]
},
parameters: {
n: 1,
size: '1024*1024'
}
})
}
);
// 新增:HTTP 状态码校验
if (!res.ok) {
const errBody = await res.text();
throw new Error(`API 请求失败 (${res.status}): ${errBody}`);
}
const data = await res.json();
console.log('📦 API 响应:', data);
// 新增:安全访问深层属性
const imageUrl = data?.output?.choices?.[0]?.message?.content?.[0]?.image;
if (!imageUrl) {
throw new Error('响应中未找到生成的图片,请检查 API 返回结构');
}
return imageUrl;
};
/**
* 将图片渲染到页面
*/
const renderImage = (imageUrl) => {
root.innerHTML = `
<div style="padding: 40px 20px;">
<h2>🎨 生成结果</h2>
<img
src="${imageUrl}"
alt="AI 生成的图片"
style="max-width: 400px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15);"
/>
</div>
`;
};
/**
* 渲染错误信息
*/
const renderError = (message) => {
root.innerHTML = `
<div style="padding: 40px 20px; color: #e53e3e;">
<h2>❌ 生成失败</h2>
<p>${message}</p>
</div>
`;
};
/**
* 渲染加载状态
*/
const renderLoading = () => {
root.innerHTML = `
<div style="padding: 40px 20px;">
<h2>⏳ 正在生成图片...</h2>
<p>请稍候,这可能需要几秒钟</p>
</div>
`;
};
// ✅ 主流程:完整的加载 → 结果 / 错误 状态管理
const main = async () => {
try {
renderLoading();
const imageUrl = await generateImage();
renderImage(imageUrl);
} catch (err) {
console.error('生成图片出错:', err);
renderError(err.message);
}
};
main();
第五步:样式
样式文件 src/style.css 定义了 CSS 变量、响应式布局、暗色模式等。有几个值得注意的设计细节:
CSS 变量体系
css
:root {
--text: #6b6375;
--text-h: #08060d;
--bg: #fff;
--border: #e5e4e7;
--accent: #aa3bff;
--accent-bg: rgba(170, 59, 255, 0.1);
--shadow: rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
font: 18px/145% var(--sans);
color-scheme: light dark; /* 声明支持明暗双模式 */
}
暗色模式适配
css
@media (prefers-color-scheme: dark) {
:root {
--text: #9ca3af;
--bg: #16171d;
--accent: #c084fc;
/* ... */
}
}
通过 prefers-color-scheme 媒体查询,自动跟随系统主题切换,无需 JS 干预。
注意 :CSS 必须通过
import './style.css'在 JS 中导入才能被 Vite 打包和处理。这是原版代码遗漏的关键一步。
完整数据流
scss
用户打开页面
│
▼
main.js 执行
│
├── import './style.css' ← Vite 处理 CSS
├── import.meta.env.VITE_* ← Vite 注入环境变量
│
▼
renderLoading() → 显示"正在生成..."
│
▼
generateImage()
│
├── fetch() POST → DashScope API
│ ├── Authorization: Bearer ${apiKey}
│ └── Body: { model, input: { messages }, parameters }
│
├── res.ok? ──No──→ throw Error → renderError()
│
▼
data.output.choices[0].message.content[0].image
│
▼
renderImage() → 页面展示生成的图片
API 请求/响应详解
请求结构
json
{
"model": "qwen-image-plus",
"input": {
"messages": [{
"role": "user",
"content": [
{ "image": "https://example.com/ref-1.png" },
{ "image": "https://example.com/ref-2.png" },
{ "image": "https://example.com/ref-3.png" },
{ "text": "参考图1的人物穿着图2的衣服,摆出图3的姿势" }
]
}]
},
"parameters": {
"n": 1,
"size": "1024*1024"
}
}
model:模型标识,可选qwen-image-plus、qwen-image-max等,以 DashScope 控制台实际可用的模型名为准input.messages[].content[]:多模态输入数组,可混排image和textparameters.n:生成图片数量parameters.size:输出尺寸,格式为宽*高
响应结构
json
{
"output": {
"choices": [{
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": [{
"image": "https://your-cdn.example.com/generated/xxx.png"
}]
}
}]
},
"usage": {
"image_count": 1
},
"request_id": "xxx-xxx-xxx"
}
图片结果路径:output.choices[0].message.content[0].image
.gitignore 配置
gitignore
node_modules
dist
*.local # ← 关键:排除所有 .env.local 文件
# Editor
.vscode/*
.idea
.DS_Store
*.local 这条规则确保 .env.local 永远不会被提交到 Git,从源头保护 API Key。
运行项目
bash
# 开发模式(HMR 热更新)
npm run dev
# 生产构建
npm run build
# 预览生产构建
npm run preview
关键知识点总结
| 知识点 | 说明 |
|---|---|
| Vite 环境变量 | VITE_* 前缀的变量通过 import.meta.env 暴露给前端代码 |
| .env.local | 存放敏感配置,必须 加入 .gitignore |
| Vite 大管家 | Vite 统一管理 HMR、ESM、CSS 处理、环境变量注入、构建优化 |
| 多模态 API | DashScope 的 /multimodal-generation/generation 端点支持图文混合输入 |
| ESM | type="module" + Vite 实现原生 ES Module 开发体验 |
| Defense in Depth | 多层防护:.gitignore + 环境变量 + 后端代理(生产环境) |
写在最后
这个 Demo 虽然小巧,但涵盖了现代前端工程化的几个关键实践:
- Vite 作为构建工具的统一调度能力
- 环境变量在前端的安全管理方式
- 多模态 AI API 的调用范式
- 完整的加载-成功-失败状态管理
把这些基础打牢,无论是接图像生成、语音识别还是大模型对话,前端侧的接入模式都是相通的