李慕婉-仙逆-造相Z-Turbo Vue.js前端项目集成:打造动态AI画廊

张开发
2026/4/18 15:46:48 15 分钟阅读

分享文章

李慕婉-仙逆-造相Z-Turbo Vue.js前端项目集成:打造动态AI画廊
李慕婉-仙逆-造相Z-Turbo Vue.js前端项目集成打造动态AI画廊最近在折腾AI绘画发现李慕婉-仙逆-造相Z-Turbo这个模型挺有意思的生成的中国风图像很有韵味。光在命令行里玩总觉得不过瘾想着要是能有个自己的网页画廊随时生成、随时展示那该多好。正好Vue.js 3的响应式特性和组件化开发用来做这种动态内容展示再合适不过了。今天我就带你从零开始用Vue 3搭建一个专属的动态AI艺术画廊。你不用是Vue专家只要对JavaScript和前端开发有点了解跟着步骤走就能把这个酷炫的项目跑起来。我们会用最新的Vite工具快速创建项目用Axios来和AI模型的API“对话”用Pinia来管理那些正在生成中的图片任务最后再把整个应用部署到静态网站托管服务上。整个过程就像搭积木一步步来最终你会得到一个完全由你掌控的、能不断“生长”的AI艺术画廊。1. 项目初始化与环境搭建万事开头难但用对了工具开头也能很轻松。我们首先把项目的基础架子搭好。1.1 创建Vue 3项目现在创建Vue项目我首推Vite。它速度快、配置简单能让我们更专注于写代码而不是折腾构建工具。打开你的终端比如VS Code的终端或者系统的命令行工具找一个你喜欢的目录执行下面的命令npm create vuelatest这个命令会启动一个交互式的项目创建向导。它会问你几个问题帮你配置项目。我的选择和建议如下项目名称输入ai-art-gallery或者你喜欢的任何名字。是否添加TypeScript选 No。为了教程简洁我们先用纯JavaScript。当然如果你熟悉TS选Yes会让项目更健壮。是否添加JSX支持选 No。是否添加Vue Router选 Yes。虽然我们这个单页应用可能用不到复杂路由但装上以备不时之需是个好习惯。是否添加Pinia选 Yes。这是我们待会儿要用的状态管理库必须装上。是否添加Vitest选 No单元测试可选。是否添加端到端测试选 No。是否添加ESLint选 Yes。它能帮我们保持代码风格一致避免一些低级错误。是否添加Prettier选 Yes。代码格式化工具让代码更美观。选择完成后Vite会自动创建项目文件夹并安装基础依赖。进入项目目录并安装剩余依赖cd ai-art-gallery npm install1.2 安装额外依赖我们的项目还需要两个关键的库Axios一个非常好用的HTTP客户端用来发送请求到AI模型的API。一个UI组件库可选为了快速搭建好看的界面我们可以选用一个UI库。这里我选择Element Plus它对Vue 3支持友好组件丰富。你也可以选择你熟悉的比如Ant Design Vue、Vuetify等。在项目根目录下运行npm install axios element-plus element-plus/icons-vueelement-plus/icons-vue是Element Plus的图标库我们会用到一些图标。1.3 配置Element Plus和Axios安装好后需要简单配置一下。打开src/main.js文件修改内容如下import { createApp } from vue import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css import * as ElementPlusIconsVue from element-plus/icons-vue import App from ./App.vue import router from ./router import ./assets/main.css const app createApp(App) // 注册所有Element Plus图标按需引入更佳这里为方便全注册 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(createPinia()) app.use(router) app.use(ElementPlus) app.mount(#app)这样我们就在Vue应用中全局注册了Element Plus组件和图标。Axios我们通常不会全局注册而是在需要的地方创建实例或直接引入这样更灵活。我们下一步就来封装它。2. 核心逻辑封装API与状态管理这是项目的大脑部分负责和后台AI“沟通”并记住所有生成任务的状态。2.1 创建并封装Axios实例在src目录下新建一个api文件夹然后在里面创建一个request.js文件。这里我们将配置Axios设置基础URL、超时时间、请求拦截器等。// src/api/request.js import axios from axios // 创建一个axios实例 const service axios.create({ // 这里填入你实际调用李慕婉模型API的基地址 // 例如如果你通过某个平台调用可能是 https://api.some-platform.com/v1 // 注意请务必使用你有权访问且合规的API端点 baseURL: import.meta.env.VITE_API_BASE_URL || https://your-ai-api-endpoint.com, timeout: 60000, // 超时时间设为60秒因为图像生成可能较慢 headers: { Content-Type: application/json, // 如果需要认证在这里添加你的API Key // Authorization: Bearer ${import.meta.env.VITE_API_KEY} } }) // 请求拦截器 service.interceptors.request.use( config { // 在发送请求之前可以做一些事情比如统一添加token // const token localStorage.getItem(token) // if (token) { // config.headers.Authorization Bearer ${token} // } return config }, error { // 对请求错误做些什么 console.error(Request error:, error) return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response { // 对响应数据做点什么 return response.data }, error { // 对响应错误做点什么 console.error(Response error:, error.response || error.message) // 可以在这里根据HTTP状态码统一处理错误例如提示用户 return Promise.reject(error) } ) export default service重要提示baseURL和Authorization头信息需要替换成你实际可用的AI模型API信息。为了安全强烈建议将这些敏感信息放在环境变量中。在项目根目录创建.env.local文件VITE_API_BASE_URLhttps://your-real-api-endpoint.com VITE_API_KEYyour_secret_api_key_here然后在request.js中通过import.meta.env.VITE_API_BASE_URL读取。2.2 定义具体的API函数在src/api文件夹下再创建一个ai.js文件。这里定义调用李慕婉模型生成图像的具体函数。// src/api/ai.js import request from ./request.js /** * 调用AI图像生成API * param {Object} params - 生成参数 * param {string} params.prompt - 图像描述文本 * param {string} [params.negative_prompt] - 负面描述文本 * param {number} [params.width512] - 图像宽度 * param {number} [params.height512] - 图像高度 * param {number} [params.steps20] - 生成步数 * param {number} [params.cfg_scale7] - 提示词相关性 * returns {Promise} 返回Promise成功时包含任务ID或图像数据 */ export function generateImage(params) { // 这里的API路径和参数结构需要根据你实际调用的API文档进行调整 return request.post(/generate, { model: limuwan-xianni-z-turbo, // 模型名称根据实际情况修改 prompt: params.prompt, negative_prompt: params.negative_prompt, width: params.width || 512, height: params.height || 512, steps: params.steps || 20, cfg_scale: params.cfg_scale || 7, // 可能还有其他参数如sampler、seed等 }) } /** * 查询生成任务状态如果API是异步的 * param {string} taskId - 任务ID * returns {Promise} 返回Promise包含任务状态和结果如果完成 */ export function getTaskStatus(taskId) { return request.get(/tasks/${taskId}) }2.3 使用Pinia管理生成任务状态Pinia是Vue官方推荐的状态管理库比Vuex更简单。我们用它来管理所有图片生成任务的状态等待中、生成中、已完成、失败以及生成的图片列表。在src/stores目录下新建一个gallery.js文件。// src/stores/gallery.js import { defineStore } from pinia import { ref, computed } from vue import { generateImage, getTaskStatus } from /api/ai.js export const useGalleryStore defineStore(gallery, () { // 状态 const tasks ref([]) // 所有任务 { id, prompt, status, imageUrl, createdAt } const isLoading ref(false) // Getter (计算属性) const completedImages computed(() { return tasks.value.filter(task task.status completed task.imageUrl) }) const pendingTasks computed(() { return tasks.value.filter(task task.status pending || task.status processing) }) // Actions (方法) async function createGenerationTask(prompt, options {}) { isLoading.value true const taskId task_${Date.now()}_${Math.random().toString(36).substr(2, 9)} // 先添加一个“等待中”的任务到列表 const newTask { id: taskId, prompt, status: pending, // pending, processing, completed, failed imageUrl: null, createdAt: new Date().toISOString(), ...options } tasks.value.unshift(newTask) // 新任务放在最前面 try { // 调用API const response await generateImage({ prompt, ...options }) // 假设API是同步返回图片URL的简单情况 // 实际情况可能更复杂需要轮询任务状态 updateTaskStatus(taskId, completed, response.imageUrl) // 如果是异步API这里可以启动一个轮询 // startPollingTask(taskId, response.task_id) } catch (error) { console.error(生成失败:, error) updateTaskStatus(taskId, failed) } finally { isLoading.value false } } function updateTaskStatus(taskId, status, imageUrl null) { const taskIndex tasks.value.findIndex(t t.id taskId) if (taskIndex -1) { tasks.value[taskIndex].status status if (imageUrl) { tasks.value[taskIndex].imageUrl imageUrl } } } // 模拟轮询异步任务如果API支持 async function startPollingTask(localTaskId, remoteTaskId) { updateTaskStatus(localTaskId, processing) const pollInterval setInterval(async () { try { const statusResponse await getTaskStatus(remoteTaskId) if (statusResponse.status completed) { updateTaskStatus(localTaskId, completed, statusResponse.imageUrl) clearInterval(pollInterval) } else if (statusResponse.status failed) { updateTaskStatus(localTaskId, failed) clearInterval(pollInterval) } // 如果还是processing继续轮询 } catch (error) { console.error(轮询任务状态失败:, error) updateTaskStatus(localTaskId, failed) clearInterval(pollInterval) } }, 3000) // 每3秒查询一次 } function removeTask(taskId) { const index tasks.value.findIndex(t t.id taskId) if (index -1) { tasks.value.splice(index, 1) } } return { // 状态 tasks, isLoading, // Getter completedImages, pendingTasks, // Actions createGenerationTask, updateTaskStatus, removeTask } })这个Store管理了整个画廊的核心数据流。createGenerationTask方法是枢纽它发起请求、更新任务状态、处理结果。3. 构建画廊页面与组件大脑有了现在来打造漂亮的界面。我们将创建两个主要组件一个用于输入提示词并生成图片另一个用于展示图片流。3.1 创建生成器组件在src/components目录下新建ImageGenerator.vue。template div classgenerator-container el-card classbox-card template #header div classcard-header span创作新的AI画作/span el-iconMagicStick //el-icon /div /template el-form :modelform label-width80px :disabledisLoading el-form-item label画面描述 required el-input v-modelform.prompt typetextarea :rows4 placeholder用中文详细描述你想要的画面例如水墨风格一位白衣剑仙立于云雾缭绕的山巅远处有飞鹤意境悠远 maxlength500 show-word-limit / /el-form-item el-row :gutter20 el-col :span12 el-form-item label画面宽度 el-slider v-modelform.width :min256 :max1024 :step64 show-input / /el-form-item /el-col el-col :span12 el-form-item label画面高度 el-slider v-modelform.height :min256 :max1024 :step64 show-input / /el-form-item /el-col /el-row el-form-item label负面描述 el-input v-modelform.negativePrompt placeholder描述你不希望在画面中出现的内容可选 / /el-form-item el-form-item el-button typeprimary :loadingisLoading clickhandleGenerate :iconPromotion {{ isLoading ? 生成中... : 开始创作 }} /el-button el-button clickhandleReset重置/el-button /el-form-item /el-form !-- 提示词示例 -- el-collapse classtips-collapse el-collapse-item title 提示词灵感 div classtips-content pstrong中国风人物/strong 古风少女手持团扇站在桃花树下花瓣飘落工笔画风格。/p pstrong山水意境/strong 泼墨山水层峦叠嶂江面一叶扁舟远处有古寺晨雾弥漫。/p pstrong神话传说/strong 青龙翱翔于九天之上雷电交加细节精致玄幻插画风格。/p p尽量描述细节如风格、主体、场景、氛围、光线等效果会更好。/p /div /el-collapse-item /el-collapse /el-card /div /template script setup import { ref, computed } from vue import { ElMessage } from element-plus import { MagicStick, Promotion } from element-plus/icons-vue import { useGalleryStore } from /stores/gallery const galleryStore useGalleryStore() const isLoading computed(() galleryStore.isLoading) const form ref({ prompt: , width: 512, height: 512, negativePrompt: , steps: 20, cfgScale: 7 }) const handleGenerate async () { if (!form.value.prompt.trim()) { ElMessage.warning(请输入画面描述) return } const options { width: form.value.width, height: form.value.height, } if (form.value.negativePrompt) { options.negative_prompt form.value.negativePrompt } await galleryStore.createGenerationTask(form.value.prompt, options) ElMessage.success(创作任务已开始请稍候...) } const handleReset () { form.value { prompt: , width: 512, height: 512, negativePrompt: , steps: 20, cfgScale: 7 } } /script style scoped .generator-container { margin-bottom: 30px; } .card-header { display: flex; justify-content: space-between; align-items: center; font-size: 1.1em; font-weight: bold; } .tips-collapse { margin-top: 20px; } .tips-content p { margin: 8px 0; line-height: 1.5; color: #666; } /style这个组件提供了一个表单让用户输入提示词、调整参数并触发生成任务。3.2 创建画廊展示组件在src/components目录下新建GalleryView.vue。template div classgallery-container !-- 任务状态提示 -- div v-ifpendingTasks.length 0 classpending-section el-alert title正在创作中的作品 typeinfo :closablefalse classmb-20 template #default 共有 {{ pendingTasks.length }} 幅作品正在生成请耐心等待... /template /el-alert div classpending-list div v-fortask in pendingTasks :keytask.id classpending-item el-card shadowhover classtask-card div classtask-content el-icon classloading-icon :size40Loading //el-icon div classtask-info p classtask-prompt{{ task.prompt }}/p p classtask-meta el-tag :typetask.status processing ? warning : info sizesmall {{ task.status processing ? 生成中 : 等待中 }} /el-tag span classtask-time{{ formatTime(task.createdAt) }}/span /p /div /div /el-card /div /div /div !-- 已完成的作品画廊 -- div classcompleted-section h2 v-ifcompletedImages.length 0 classsection-title 我的AI艺术画廊 ({{ completedImages.length }}) /h2 div v-ifcompletedImages.length 0 pendingTasks.length 0 classempty-state el-empty description画廊空空如也快去创作第一幅作品吧 / /div div v-else classimage-grid div v-forimage in completedImages :keyimage.id classimage-item el-card shadowhover classimage-card :body-style{ padding: 0 } div classimage-wrapper el-image :srcimage.imageUrl :preview-src-listcompletedImages.map(img img.imageUrl) fitcover classgallery-image loadinglazy template #error div classimage-error el-iconPicture //el-icon span加载失败/span /div /template /el-image div classimage-overlay div classoverlay-content p classimage-prompt{{ image.prompt }}/p div classimage-actions el-button typeprimary :iconDownload sizesmall clickdownloadImage(image)保存/el-button el-button typedanger :iconDelete sizesmall clickremoveImage(image.id)移除/el-button /div /div /div /div div classcard-footer span classimage-time{{ formatTime(image.createdAt) }}/span /div /el-card /div /div /div /div /template script setup import { computed } from vue import { ElMessage, ElMessageBox } from element-plus import { Loading, Download, Delete, Picture } from element-plus/icons-vue import { useGalleryStore } from /stores/gallery const galleryStore useGalleryStore() const pendingTasks computed(() galleryStore.pendingTasks) const completedImages computed(() galleryStore.completedImages) const formatTime (timeString) { const date new Date(timeString) return date.toLocaleString(zh-CN) } const downloadImage async (image) { if (!image.imageUrl) return try { const response await fetch(image.imageUrl) const blob await response.blob() const url window.URL.createObjectURL(blob) const a document.createElement(a) a.href url a.download ai-art-${image.id.slice(0, 8)}.png document.body.appendChild(a) a.click() window.URL.revokeObjectURL(url) document.body.removeChild(a) ElMessage.success(图片保存成功) } catch (error) { console.error(下载失败:, error) ElMessage.error(图片下载失败) } } const removeImage (taskId) { ElMessageBox.confirm( 确定要从画廊中移除这幅作品吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning, } ).then(() { galleryStore.removeTask(taskId) ElMessage.success(已移除) }).catch(() { // 用户取消 }) } /script style scoped .gallery-container { padding: 20px 0; } .mb-20 { margin-bottom: 20px; } .pending-list { display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 40px; } .pending-item { flex: 1 1 300px; } .task-card { height: 100%; } .task-content { display: flex; align-items: center; padding: 20px; } .loading-icon { margin-right: 20px; color: var(--el-color-primary); animation: rotate 2s linear infinite; } keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .task-info { flex: 1; } .task-prompt { margin: 0 0 10px 0; font-size: 0.95em; color: #333; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .task-meta { display: flex; justify-content: space-between; align-items: center; margin: 0; } .task-time { font-size: 0.85em; color: #999; } .section-title { font-size: 1.5em; margin: 0 0 20px 0; color: #333; border-left: 4px solid var(--el-color-primary); padding-left: 12px; } .empty-state { padding: 60px 0; } .image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; } .image-item { transition: transform 0.3s ease; } .image-item:hover { transform: translateY(-5px); } .image-card { height: 100%; overflow: hidden; } .image-wrapper { position: relative; aspect-ratio: 1 / 1; overflow: hidden; } .gallery-image { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .image-wrapper:hover .gallery-image { transform: scale(1.05); } .image-error { width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: #f5f7fa; color: #909399; } .image-error .el-icon { font-size: 48px; margin-bottom: 10px; } .image-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); color: white; opacity: 0; transition: opacity 0.3s ease; display: flex; align-items: center; justify-content: center; padding: 20px; } .image-wrapper:hover .image-overlay { opacity: 1; } .overlay-content { text-align: center; width: 100%; } .image-prompt { margin: 0 0 15px 0; font-size: 0.9em; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } .image-actions { display: flex; gap: 10px; justify-content: center; } .card-footer { padding: 12px; border-top: 1px solid var(--el-border-color-light); font-size: 0.85em; color: #666; } /style这个组件负责展示两部分内容上方是正在生成的任务列表下方是以网格形式展示已完成的精美图片。鼠标悬停在图片上会有遮罩层效果可以查看提示词和进行操作。3.3 整合到主页面现在我们把这两个组件放到主页上。修改src/views/HomeView.vue如果没有可以修改App.vue或创建一个新的页面组件。template div classhome header classapp-header h1 李慕婉AI艺术画廊/h1 p classsubtitle用文字生成属于你的中国风意境画作/p /header main classmain-content div classcontainer !-- 生成器组件 -- ImageGenerator / !-- 画廊展示组件 -- GalleryView / /div /main footer classapp-footer pPowered by Vue 3 李慕婉-仙逆-造相Z-Turbo | 构建你的动态AI艺术空间/p /footer /div /template script setup import ImageGenerator from /components/ImageGenerator.vue import GalleryView from /components/GalleryView.vue /script style scoped .home { min-height: 100vh; display: flex; flex-direction: column; } .app-header { text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .app-header h1 { margin: 0 0 10px 0; font-size: 2.5em; } .subtitle { margin: 0; font-size: 1.2em; opacity: 0.9; } .main-content { flex: 1; padding: 30px 0; background-color: #f8f9fa; } .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; } .app-footer { text-align: center; padding: 20px; background-color: #2c3e50; color: #ecf0f1; font-size: 0.9em; } /style4. 项目构建与静态部署项目开发完了最后一步是把它构建成静态文件并部署到网上让任何人都能访问。4.1 构建生产版本Vite使得构建过程非常简单。在项目根目录下运行npm run build这个命令会在项目根目录下生成一个dist文件夹里面包含了所有优化过的、可用于生产环境的HTML、CSS和JavaScript文件。4.2 本地预览构建结果在部署之前最好先本地预览一下构建后的效果。你可以使用任何静态文件服务器。一个简单的方法是使用Vite自带的预览功能npm run preview然后打开浏览器访问http://localhost:4173检查一切是否正常。4.3 部署到静态网站托管服务dist文件夹里的内容就是纯粹的静态文件可以部署到任何静态网站托管服务。这里以几个流行的平台为例Vercel (推荐对Vue项目友好)将你的代码推送到GitHub、GitLab或Bitbucket。登录 Vercel。点击 New Project导入你的仓库。Vercel会自动检测到是Vue项目使用默认配置即可。点击 Deploy。几分钟后你会获得一个*.vercel.app的免费域名。Netlify同样将代码推送到Git仓库。登录 Netlify点击 Add new site - Import an existing project。选择你的仓库构建命令填写npm run build发布目录填写dist。点击 Deploy site。GitHub Pages在项目根目录创建deploy.sh脚本或使用gh-pages包。更简单的方式在Vite配置 (vite.config.js) 中设置base: /你的仓库名/。运行npm run build。将dist文件夹的内容推送到仓库的gh-pages分支或docs文件夹。在仓库的Settings - Pages中选择源分支为gh-pages或/docs文件夹。部署完成后你就拥有了一个在线的、动态的AI艺术画廊。你可以把链接分享给朋友让他们也能欣赏或尝试生成画作。5. 总结与后续思考走完这一趟一个功能完整的动态AI画廊前端应用就从无到有地搭建起来了。从用Vite快速初始化项目到用Pinia优雅地管理那些异步的生成任务状态再到用组件化思想拼出用户界面最后打包部署上线每一步都是在把想法变成现实。用Vue 3来做这种实时性要求高的应用确实很舒服。响应式系统让界面和数据同步变得非常自然你只需要关心状态怎么变视图会自动更新。Element Plus这类UI库则把我们从繁琐的样式细节中解放出来能更专注于业务逻辑。整个项目结构清晰api目录管通信stores目录管状态components目录管视图各司其职维护起来也方便。当然这只是个起点。这个画廊还有很多可以玩和可以改进的地方。比如你可以引入vue-router来做更复杂的页面路由给每张生成的画作一个单独的详情页。或者加入用户登录功能把作品保存到云端数据库这样换台设备也能看到自己的画廊。如果AI API支持还可以增加“以图生图”、“图像编辑”这样的高级功能。界面交互上拖拽排序、收藏夹、作品分类这些功能都能让体验更好。最关键的一点是请务必使用合规、你有权访问的AI模型API服务并在调用时遵守相关平台的使用条款。前端代码里涉及API密钥等敏感信息一定要通过环境变量来管理千万不要直接写在代码里提交到公开仓库。希望这个项目能给你带来一些启发。前端技术结合AI能力能创造出很多有意思的东西。最重要的是动手去试在做的过程中你可能会遇到各种问题但解决问题的过程本身就是最好的学习。不妨就从克隆代码、替换成你自己的API端点开始打造一个独一无二的AI艺术空间吧。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章