手机端视频生成页面调整完成

This commit is contained in:
GeekMaster
2025-08-08 12:47:12 +08:00
parent 4e237c9560
commit 0ce5df6811
25 changed files with 762 additions and 679 deletions

View File

@@ -308,15 +308,15 @@
<!-- 提交按钮 -->
<div class="submit-btn flex justify-center pt-4">
<el-button
type="primary"
<button
@click="store.submitTask"
:loading="store.submitting"
class="w-full"
size="large"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
>
立即生成 ({{ store.currentPowerCost }} <i class="iconfont icon-vip2 !text-xs"></i>)
</el-button>
<i v-if="store.submitting" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span>立即生成 ({{ store.currentPowerCost }}算力)</span>
</button>
</div>
</div>
</div>

View File

@@ -201,13 +201,15 @@
<!-- 生成按钮 -->
<div class="setting-card">
<button @click="store.create" :disabled="store.loading" class="create-btn">
<button
@click="store.create"
:disabled="store.loading"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="store.loading" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span
>{{ store.loading ? '创作中...' : store.btnText }} ({{ store.sunoPower }}
<i class="iconfont icon-vip2 !text-xs"></i>
)</span
>{{ store.loading ? '创作中...' : store.btnText }} ({{ store.sunoPower }}算力)</span
>
</button>
</div>
@@ -233,7 +235,6 @@
<!-- 上传区域 -->
<el-upload
class="custom-upload"
drag
:auto-upload="true"
:show-file-list="false"
:http-request="store.uploadAudio"
@@ -241,11 +242,13 @@
:limit="1"
>
<template #trigger>
<div class="p-2">
<el-button class="upload-btn" size="large" type="primary">
<div class="w-full py-2">
<button
class="w-full py-3 bg-gradient-to-r from-orange-300 to-purple-500 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-orange-300 hover:to-red-500 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i class="iconfont icon-upload mr-2"></i>
<span>上传音乐</span>
</el-button>
</button>
</div>
</template>
</el-upload>

View File

@@ -22,17 +22,13 @@
</div>
<!-- 提示词生成按钮 -->
<div class="param-line pt">
<el-button
class="generate-btn"
@click="store.generatePrompt"
:loading="store.isGenerating"
size="small"
color="#5865f2"
style="width: 100%"
>
<i class="iconfont icon-chuangzuo"></i>
生成AI视频提示词
<div class="flex justify-end pt-1">
<el-button @click="store.generatePrompt" type="primary" :loading="store.isGenerating">
<span v-if="!store.isGenerating">
<i class="iconfont icon-chuangzuo"></i>
生成提示词
</span>
<span v-else>生成中...</span>
</el-button>
</div>
@@ -126,16 +122,28 @@
</div>
<!-- 算力显示 -->
<el-row class="text-info">
<el-text type="primary"
>当前可用算力<el-text type="warning">{{ store.availablePower }}</el-text></el-text
<div
class="power-info flex items-center justify-between mb-4 mt-3 p-3 rounded-lg bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200 shadow-sm"
>
<div class="flex items-center space-x-2">
<el-icon color="#f59e42" size="20"><i class="iconfont icon-lightning"></i></el-icon>
<span class="font-medium text-gray-700">当前可用算力</span>
<span class="font-bold text-lg text-yellow-500">{{ store.availablePower }}</span>
</div>
<el-tooltip content="算力用于生成视频,每次生成会消耗对应算力" placement="left">
<el-icon color="#a78bfa" size="18"><InfoFilled /></el-icon>
</el-tooltip>
</div>
<div class="flex justify-center">
<button
@click="store.createLumaVideo"
:loading="store.generating"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
>
</el-row>
<!-- 生成按钮 -->
<div class="submit-btn">
<el-button type="primary" :dark="false" @click="store.createLumaVideo" round>
立即生成 ({{ store.lumaPowerCost }}<i class="iconfont icon-vip2"></i>)
</el-button>
<i v-if="store.generating" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span>立即生成 ({{ store.lumaPowerCost }}算力)</span>
</button>
</div>
</div>
</el-tab-pane>
@@ -398,17 +406,17 @@
</div>
<!-- 提示词生成按钮 -->
<div class="param-line pt">
<div class="flex justify-end">
<el-button
class="generate-btn"
@click="store.generatePrompt"
:loading="store.isGenerating"
size="small"
color="#5865f2"
style="width: 100%"
>
<i class="iconfont icon-chuangzuo"></i>
生成专业视频提示词
<span v-if="!store.isGenerating">
<i class="iconfont icon-chuangzuo"></i> 生成提示词
</span>
<span v-else>生成中...</span>
</el-button>
</div>
@@ -429,23 +437,31 @@
</div>
<!-- 算力显示 -->
<el-row class="text-info">
<el-text type="primary"
>当前可用算力<el-text type="warning">{{ store.availablePower }}</el-text></el-text
>
</el-row>
<!-- 算力显示 -->
<div
class="power-info flex items-center justify-between mb-4 mt-2 p-3 rounded-lg bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200 shadow-sm"
>
<div class="flex items-center space-x-2">
<el-icon color="#f59e42" size="20"><i class="iconfont icon-lightning"></i></el-icon>
<span class="font-medium text-gray-700">当前可用算力</span>
<span class="font-bold text-lg text-yellow-500">{{ store.availablePower }}</span>
</div>
<el-tooltip content="算力用于生成视频,每次生成会消耗对应算力" placement="left">
<el-icon color="#a78bfa" size="18"><InfoFilled /></el-icon>
</el-tooltip>
</div>
<!-- 生成按钮 -->
<div class="submit-btn">
<el-button
type="primary"
:dark="false"
<div class="flex justify-center">
<button
@click="store.createKelingVideo"
round
:loading="store.generating"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2 text-base"
>
立即生成 ({{ store.kelingPowerCost }}<i class="iconfont icon-vip2"></i>)
</el-button>
<i v-if="store.generating" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span>立即生成 ({{ store.kelingPowerCost }}算力)</span>
</button>
</div>
</div>
</el-tab-pane>
@@ -599,12 +615,11 @@
</div>
<!-- 视频预览对话框 -->
<black-dialog
:show="store.showDialog"
<el-dialog
v-model="store.showDialog"
title="预览视频"
hide-footer
@cancal="store.showDialog = false"
@update:show="store.showDialog = $event"
@close="store.showDialog = false"
width="auto"
>
<video
@@ -614,16 +629,14 @@
:autoplay="true"
loop="loop"
muted="muted"
v-show="store.showDialog"
>
您的浏览器不支持视频播放
</video>
</black-dialog>
</el-dialog>
</div>
</template>
<script setup>
import BlackDialog from '@/components/ui/BlackDialog.vue'
import Generating from '@/components/ui/Generating.vue'
import { useVideoStore } from '@/store/video'
import { CircleCloseFilled, InfoFilled, Plus } from '@element-plus/icons-vue'

View File

@@ -73,15 +73,15 @@
</template>
<script setup>
import AppCard from '@/components/mobile/AppCard.vue'
import EmptyState from '@/components/mobile/EmptyState.vue'
import CustomTabPane from '@/components/ui/CustomTabPane.vue'
import CustomTabs from '@/components/ui/CustomTabs.vue'
import AppCard from './components/AppCard.vue'
import EmptyState from './components/EmptyState.vue'
import { checkSession } from '@/store/cache'
import { httpGet, httpPost } from '@/utils/http'
import { arrayContains, removeArrayItem, showLoginDialog, substr } from '@/utils/libs'
import { showNotify } from 'vant'
import { onMounted, ref, computed, nextTick } from 'vue'
import { computed, nextTick, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()

View File

@@ -6,36 +6,33 @@
@update:model-value="activeTab = $event"
@tab-click="onTabChange"
>
<CustomTabPane name="mj" label="MJ绘画">
<CustomTabPane name="mj">
<template #label>
<i class="iconfont icon-mj mr-1"></i>
<span>Midjourney</span>
</template>
<div class="tab-content">
<image-mj />
</div>
</CustomTabPane>
<CustomTabPane name="sd" label="SD绘画">
<CustomTabPane name="sd">
<template #label>
<i class="iconfont icon-sd mr-1"></i>
<span>StableDiffusion</span>
</template>
<div class="tab-content">
<image-sd />
</div>
</CustomTabPane>
<CustomTabPane name="dalle" label="DALL·E">
<CustomTabPane name="dalle">
<template #label>
<i class="iconfont icon-dalle mr-1"></i>
<span>Dalle</span>
</template>
<div class="tab-content">
<image-dall />
</div>
</CustomTabPane>
<CustomTabPane name="suno" label="音乐创作">
<div class="tab-content">
<suno-create />
</div>
</CustomTabPane>
<CustomTabPane name="video" label="视频生成">
<div class="tab-content">
<video-create />
</div>
</CustomTabPane>
<CustomTabPane name="jimeng" label="即梦AI">
<div class="tab-content">
<jimeng-create />
</div>
</CustomTabPane>
</CustomTabs>
</div>
</div>
@@ -44,16 +41,12 @@
<script setup>
import CustomTabPane from '@/components/ui/CustomTabPane.vue'
import CustomTabs from '@/components/ui/CustomTabs.vue'
import { httpGet } from '@/utils/http'
import ImageDall from '@/views/mobile/pages/ImageDall.vue'
import ImageMj from '@/views/mobile/pages/ImageMj.vue'
import ImageSd from '@/views/mobile/pages/ImageSd.vue'
import { Button, Field, Image, showNotify } from 'vant'
import { h, onMounted, ref, watch } from 'vue'
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
// 删除 SunoCreate、VideoCreate、JimengCreate 相关 setup 代码和渲染逻辑
const route = useRoute()
const router = useRouter()
const activeTab = ref(route.query.tab || 'mj')
@@ -62,22 +55,8 @@ const activeMenu = ref({
mj: false,
sd: false,
dall: false,
suno: false,
video: false,
jimeng: false,
})
// 监听路由参数变化
watch(
() => route.query.tab,
(newTab) => {
if (newTab && activeMenu.value[newTab]) {
activeTab.value = newTab
}
},
{ immediate: true }
)
// Tab切换处理
const onTabChange = (name) => {
router.replace({
@@ -85,48 +64,6 @@ const onTabChange = (name) => {
query: { ...route.query, tab: name },
})
}
onMounted(() => {
fetchMenus()
})
const fetchMenus = () => {
httpGet('/api/menu/list')
.then((res) => {
menus.value = res.data
activeMenu.value = {
mj: menus.value.some((item) => item.url === '/mj'),
sd: menus.value.some((item) => item.url === '/sd'),
dall: menus.value.some((item) => item.url === '/dalle'),
suno: menus.value.some((item) => item.url === '/suno'),
video: menus.value.some((item) => item.url === '/video'),
jimeng: menus.value.some((item) => item.url === '/jimeng'),
}
// 如果没有指定tab默认选择第一个可用的
if (!route.query.tab) {
const firstAvailable = Object.keys(activeMenu.value).find((key) => activeMenu.value[key])
if (firstAvailable) {
activeTab.value = firstAvailable
}
} else {
// 如果当前选中的tab不可用选择第一个可用的
if (!activeMenu.value[route.query.tab]) {
const firstAvailable = Object.keys(activeMenu.value).find((key) => activeMenu.value[key])
if (firstAvailable) {
activeTab.value = firstAvailable
router.replace({
path: route.path,
query: { ...route.query, tab: firstAvailable },
})
}
}
}
})
.catch((e) => {
console.error('获取菜单失败:', e.message)
})
}
</script>
<style lang="scss" scoped>

View File

@@ -1,13 +1,16 @@
<template>
<div class="jimeng-create">
<!-- 页面头部 -->
<div class="jimeng-create__header">
<div class="jimeng-create__header-content">
<button @click="goBack" class="jimeng-create__header-back-btn">
<i class="iconfont icon-back"></i>
<div class="sticky top-0 z-40 bg-white shadow-sm">
<div class="flex items-center px-4 h-14">
<button
@click="goBack"
class="flex items-center justify-center w-8 h-8 rounded-full hover:bg-gray-100 transition-colors"
>
<i class="iconfont icon-back text-gray-600"></i>
</button>
<h1 class="jimeng-create__header-title">即梦AI</h1>
<div class="jimeng-create__header-spacer"></div>
<h1 class="flex-1 text-center text-lg text-gray-900">即梦AI</h1>
<div class="w-8"></div>
</div>
</div>
@@ -18,6 +21,7 @@
v-model="jimengStore.activeCategory"
@update:modelValue="jimengStore.switchCategory"
>
<!-- 文生图 -->
<CustomTabPane
v-for="category in jimengStore.categories"
:key="category.key"
@@ -27,422 +31,512 @@
<template #label>
<span>{{ category.name }}</span>
</template>
<!-- 生成模式切换 -->
<div class="jimeng-create__mode-section">
<div class="jimeng-create__mode-section-content">
<div>
<span class="jimeng-create__mode-section-title">生成模式</span>
<p class="jimeng-create__mode-section-description">图生图人像写真</p>
</div>
<el-switch
v-model="jimengStore.useImageInput"
@change="jimengStore.switchInputMode"
size="default"
/>
</div>
</div>
<div class="space-y-6">
<!-- 提示词输入 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="请输入图片描述,越详细越好"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 图片尺寸 -->
<CustomSelect
v-model="jimengStore.textToImageParams.size"
:options="
jimengStore.imageSizeOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="图片尺寸"
title="选择尺寸"
/>
<!-- 创意度 -->
<div class="jimeng-create__form-section">
<div class="jimeng-create__slider-section">
<div class="jimeng-create__slider-section-header">
<label>创意度</label>
<el-tooltip content="创意度越高,影响文本描述的程度越高" placement="top">
<i class="iconfont icon-info"></i>
</el-tooltip>
</div>
<el-slider
v-model="jimengStore.textToImageParams.scale"
:min="1"
:max="10"
:step="0.5"
/>
</div>
</div>
<!-- 智能优化提示词 -->
<div class="jimeng-create__form-section">
<div class="jimeng-create__switch-section">
<span>智能优化提示词</span>
<el-switch v-model="jimengStore.textToImageParams.use_pre_llm" size="default" />
</div>
</div>
</div>
<!-- 图生图参数 -->
<template v-if="jimengStore.activeFunction === 'image_to_image'">
<!-- 生成模式切换 -->
<div class="jimeng-create__mode-section">
<div class="jimeng-create__mode-section-content">
<div>
<span class="jimeng-create__mode-section-title">生成模式</span>
<p class="jimeng-create__mode-section-description">图生图人像写真</p>
</div>
<el-switch
v-model="jimengStore.useImageInput"
@change="jimengStore.switchInputMode"
size="default"
/>
</div>
</div>
<div class="space-y-6">
<!-- 上传图片 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">上传图片</label>
<div class="jimeng-create__upload">
<input
ref="imageToImageInput"
type="file"
accept=".jpg,.png,.jpeg"
@change="
(e) =>
jimengStore.onImageUpload({
file: e.target.files[0],
name: e.target.files[0]?.name,
})
"
class="hidden"
/>
<div
@click="$refs.imageToImageInput?.click()"
class="jimeng-create__upload-content"
>
<i
v-if="!jimengStore.imageToImageParams.image_input.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageToImageParams.image_input.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-preview">
<el-image
:src="
jimengStore.imageToImageParams.image_input[0]?.url ||
jimengStore.imageToImageParams.image_input[0]?.content
"
fit="cover"
class="w-32 h-32 rounded"
/>
<button
@click.stop="jimengStore.imageToImageParams.image_input = []"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 提示词输入 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的图片效果"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 图片尺寸 -->
<CustomSelect
v-model="jimengStore.imageToImageParams.size"
:options="
jimengStore.imageSizeOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="图片尺寸"
title="选择尺寸"
/>
</div>
</template>
<!-- 图像编辑参数 -->
<template
v-if="
category.key === 'image_generation' && jimengStore.activeFunction === 'image_edit'
"
>
<div class="space-y-6">
<!-- 上传图片 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">上传图片</label>
<div class="jimeng-create__upload">
<input
ref="imageEditInput"
type="file"
accept=".jpg,.png,.jpeg"
@change="
(e) =>
jimengStore.onImageUpload({
file: e.target.files[0],
name: e.target.files[0]?.name,
})
"
class="hidden"
/>
<div
@click="$refs.imageEditInput?.click()"
class="jimeng-create__upload-content"
>
<i
v-if="!jimengStore.imageEditParams.image_urls.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageEditParams.image_urls.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-preview">
<el-image
:src="
jimengStore.imageEditParams.image_urls[0]?.url ||
jimengStore.imageEditParams.image_urls[0]?.content
"
fit="cover"
class="w-32 h-32 rounded"
/>
<button
@click.stop="jimengStore.imageEditParams.image_urls = []"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 编辑提示词 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">编辑提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的编辑效果"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 编辑强度 -->
<div class="jimeng-create__form-section">
<div class="jimeng-create__slider-section">
<label>编辑强度</label>
<el-slider
v-model="jimengStore.imageEditParams.scale"
:min="0"
:max="1"
:step="0.1"
/>
</div>
</div>
</div>
</template>
<!-- 图像特效参数 -->
<template
v-if="
category.key === 'image_generation' &&
jimengStore.activeFunction === 'image_effects'
"
>
<div class="space-y-6">
<!-- 上传图片 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">上传图片</label>
<div class="jimeng-create__upload">
<input
ref="imageEffectsInput"
type="file"
accept=".jpg,.png,.jpeg"
@change="
(e) =>
jimengStore.onImageUpload({
file: e.target.files[0],
name: e.target.files[0]?.name,
})
"
class="hidden"
/>
<div
@click="$refs.imageEffectsInput?.click()"
class="jimeng-create__upload-content"
>
<i
v-if="!jimengStore.imageEffectsParams.image_input1.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageEffectsParams.image_input1.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-preview">
<el-image
:src="
jimengStore.imageEffectsParams.image_input1[0]?.url ||
jimengStore.imageEffectsParams.image_input1[0]?.content
"
fit="cover"
class="w-32 h-32 rounded"
/>
<button
@click.stop="jimengStore.imageEffectsParams.image_input1 = []"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 特效模板 -->
<CustomSelect
v-model="jimengStore.imageEffectsParams.template_id"
:options="
jimengStore.imageEffectsTemplateOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="特效模板"
title="选择特效模板"
/>
<!-- 输出尺寸 -->
<CustomSelect
v-model="jimengStore.imageEffectsParams.size"
:options="
jimengStore.imageSizeOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="输出尺寸"
title="选择尺寸"
/>
</div>
</template>
<!-- 文生视频参数 -->
<template
v-if="
category.key === 'video_generation' &&
jimengStore.activeFunction === 'text_to_video'
"
>
<!-- 生成模式切换 -->
<div class="jimeng-create__mode-section">
<div class="jimeng-create__mode-section-content">
<div>
<span class="jimeng-create__mode-section-title">生成模式</span>
<p class="jimeng-create__mode-section-description">图生视频</p>
</div>
<el-switch
v-model="jimengStore.useImageInput"
@change="jimengStore.switchInputMode"
size="default"
/>
</div>
</div>
<div class="space-y-6">
<!-- 提示词输入 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的视频内容"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 视频比例 -->
<CustomSelect
v-model="jimengStore.textToVideoParams.aspect_ratio"
:options="
jimengStore.videoAspectRatioOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="视频比例"
title="选择比例"
/>
</div>
</template>
<!-- 图生视频参数 -->
<template
v-if="
category.key === 'video_generation' &&
jimengStore.activeFunction === 'image_to_video'
"
>
<!-- 生成模式切换 -->
<div class="jimeng-create__mode-section">
<div class="jimeng-create__mode-section-content">
<div>
<span class="jimeng-create__mode-section-title">生成模式</span>
<p class="jimeng-create__mode-section-description">图生视频</p>
</div>
<el-switch
v-model="jimengStore.useImageInput"
@change="jimengStore.switchInputMode"
size="default"
/>
</div>
</div>
<div class="space-y-6">
<!-- 上传图片 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">上传图片最多2张</label>
<div class="jimeng-create__upload">
<input
ref="imageToVideoInput"
type="file"
accept=".jpg,.png,.jpeg"
multiple
@change="(e) => jimengStore.handleMultipleImageUpload(e)"
class="hidden"
/>
<div
@click="$refs.imageToVideoInput?.click()"
class="jimeng-create__upload-content"
>
<i
v-if="!jimengStore.imageToVideoParams.image_urls.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageToVideoParams.image_urls.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-multiple">
<div
v-for="(image, index) in jimengStore.imageToVideoParams.image_urls"
:key="index"
class="jimeng-create__upload-multiple-item"
>
<el-image
:src="image?.url || image?.content"
fit="cover"
class="w-24 h-24 rounded"
/>
<button
@click.stop="jimengStore.removeImage(index)"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
<div
v-if="jimengStore.imageToVideoParams.image_urls.length < 2"
@click.stop="$refs.imageToVideoInput?.click()"
class="jimeng-create__upload-multiple-add"
>
<i class="iconfont icon-plus"></i>
</div>
</div>
</div>
</div>
</div>
<!-- 提示词输入 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的视频效果"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 视频比例 -->
<CustomSelect
v-model="jimengStore.imageToVideoParams.aspect_ratio"
:options="
jimengStore.videoAspectRatioOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="视频比例"
title="选择比例"
/>
</div>
</template>
</CustomTabPane>
</CustomTabs>
</div>
<!-- 生成模式切换 -->
<div
v-if="
jimengStore.activeCategory === 'image_generation' ||
jimengStore.activeCategory === 'video_generation'
"
class="jimeng-create__mode-section"
>
<div class="jimeng-create__mode-section-content">
<div>
<span class="jimeng-create__mode-section-title">生成模式</span>
<p class="jimeng-create__mode-section-description">
{{
jimengStore.activeCategory === 'image_generation' ? '图生图人像写真' : '图生视频'
}}
</p>
</div>
<el-switch
v-model="jimengStore.useImageInput"
@change="jimengStore.switchInputMode"
size="default"
/>
</div>
</div>
<!-- 文生图 -->
<div v-if="jimengStore.activeFunction === 'text_to_image'" class="space-y-6">
<!-- 提示词输入 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="请输入图片描述,越详细越好"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 图片尺寸 -->
<CustomSelect
v-model="jimengStore.textToImageParams.size"
:options="
jimengStore.imageSizeOptions.map((opt) => ({ label: opt.label, value: opt.value }))
"
label="图片尺寸"
title="选择尺寸"
/>
<!-- 创意度 -->
<div class="jimeng-create__form-section">
<div class="jimeng-create__slider-section">
<div class="jimeng-create__slider-section-header">
<label>创意度</label>
<el-tooltip content="创意度越高,影响文本描述的程度越高" placement="top">
<i class="iconfont icon-info"></i>
</el-tooltip>
</div>
<el-slider
v-model="jimengStore.textToImageParams.scale"
:min="1"
:max="10"
:step="0.5"
/>
</div>
</div>
<!-- 智能优化提示词 -->
<div class="jimeng-create__form-section">
<div class="jimeng-create__switch-section">
<span>智能优化提示词</span>
<el-switch v-model="jimengStore.textToImageParams.use_pre_llm" size="default" />
</div>
</div>
</div>
<!-- 图生图 -->
<div v-if="jimengStore.activeFunction === 'image_to_image'" class="space-y-6">
<!-- 上传图片 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">上传图片</label>
<div class="jimeng-create__upload">
<input
ref="imageToImageInput"
type="file"
accept=".jpg,.png,.jpeg"
@change="
(e) =>
jimengStore.onImageUpload({
file: e.target.files[0],
name: e.target.files[0]?.name,
})
"
class="hidden"
/>
<div @click="$refs.imageToImageInput?.click()" class="jimeng-create__upload-content">
<i
v-if="!jimengStore.imageToImageParams.image_input.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageToImageParams.image_input.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-preview">
<el-image
:src="
jimengStore.imageToImageParams.image_input[0]?.url ||
jimengStore.imageToImageParams.image_input[0]?.content
"
fit="cover"
class="w-32 h-32 rounded"
/>
<button
@click.stop="jimengStore.imageToImageParams.image_input = []"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 提示词输入 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的图片效果"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 图片尺寸 -->
<CustomSelect
v-model="jimengStore.imageToImageParams.size"
:options="
jimengStore.imageSizeOptions.map((opt) => ({ label: opt.label, value: opt.value }))
"
label="图片尺寸"
title="选择尺寸"
/>
</div>
<!-- 图像编辑 -->
<div v-if="jimengStore.activeFunction === 'image_edit'" class="space-y-6">
<!-- 上传图片 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">上传图片</label>
<div class="jimeng-create__upload">
<input
ref="imageEditInput"
type="file"
accept=".jpg,.png,.jpeg"
@change="
(e) =>
jimengStore.onImageUpload({
file: e.target.files[0],
name: e.target.files[0]?.name,
})
"
class="hidden"
/>
<div @click="$refs.imageEditInput?.click()" class="jimeng-create__upload-content">
<i
v-if="!jimengStore.imageEditParams.image_urls.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageEditParams.image_urls.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-preview">
<el-image
:src="
jimengStore.imageEditParams.image_urls[0]?.url ||
jimengStore.imageEditParams.image_urls[0]?.content
"
fit="cover"
class="w-32 h-32 rounded"
/>
<button
@click.stop="jimengStore.imageEditParams.image_urls = []"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 编辑提示词 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">编辑提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的编辑效果"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 编辑强度 -->
<div class="jimeng-create__form-section">
<div class="jimeng-create__slider-section">
<label>编辑强度</label>
<el-slider v-model="jimengStore.imageEditParams.scale" :min="0" :max="1" :step="0.1" />
</div>
</div>
</div>
<!-- 图像特效 -->
<div v-if="jimengStore.activeFunction === 'image_effects'" class="space-y-6">
<!-- 上传图片 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">上传图片</label>
<div class="jimeng-create__upload">
<input
ref="imageEffectsInput"
type="file"
accept=".jpg,.png,.jpeg"
@change="
(e) =>
jimengStore.onImageUpload({
file: e.target.files[0],
name: e.target.files[0]?.name,
})
"
class="hidden"
/>
<div @click="$refs.imageEffectsInput?.click()" class="jimeng-create__upload-content">
<i
v-if="!jimengStore.imageEffectsParams.image_input1.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageEffectsParams.image_input1.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-preview">
<el-image
:src="
jimengStore.imageEffectsParams.image_input1[0]?.url ||
jimengStore.imageEffectsParams.image_input1[0]?.content
"
fit="cover"
class="w-32 h-32 rounded"
/>
<button
@click.stop="jimengStore.imageEffectsParams.image_input1 = []"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
</div>
</div>
</div>
<!-- 特效模板 -->
<CustomSelect
v-model="jimengStore.imageEffectsParams.template_id"
:options="
jimengStore.imageEffectsTemplateOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="特效模板"
title="选择特效模板"
/>
<!-- 输出尺寸 -->
<CustomSelect
v-model="jimengStore.imageEffectsParams.size"
:options="
jimengStore.imageSizeOptions.map((opt) => ({ label: opt.label, value: opt.value }))
"
label="输出尺寸"
title="选择尺寸"
/>
</div>
<!-- 文生视频 -->
<div v-if="jimengStore.activeFunction === 'text_to_video'" class="space-y-6">
<!-- 提示词输入 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的视频内容"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 视频比例 -->
<CustomSelect
v-model="jimengStore.textToVideoParams.aspect_ratio"
:options="
jimengStore.videoAspectRatioOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="视频比例"
title="选择比例"
/>
</div>
<!-- 图生视频 -->
<div v-if="jimengStore.activeFunction === 'image_to_video'" class="space-y-6">
<!-- 上传图片 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">上传图片最多2张</label>
<div class="jimeng-create__upload">
<input
ref="imageToVideoInput"
type="file"
accept=".jpg,.png,.jpeg"
multiple
@change="(e) => jimengStore.handleMultipleImageUpload(e)"
class="hidden"
/>
<div @click="$refs.imageToVideoInput?.click()" class="jimeng-create__upload-content">
<i
v-if="!jimengStore.imageToVideoParams.image_urls.length"
class="jimeng-create__upload-icon iconfont icon-upload"
></i>
<span
v-if="!jimengStore.imageToVideoParams.image_urls.length"
class="jimeng-create__upload-text"
>上传图片</span
>
<div v-else class="jimeng-create__upload-multiple">
<div
v-for="(image, index) in jimengStore.imageToVideoParams.image_urls"
:key="index"
class="jimeng-create__upload-multiple-item"
>
<el-image
:src="image?.url || image?.content"
fit="cover"
class="w-24 h-24 rounded"
/>
<button
@click.stop="jimengStore.removeImage(index)"
class="jimeng-create__upload-remove-btn"
>
<i class="iconfont icon-close"></i>
</button>
</div>
<div
v-if="jimengStore.imageToVideoParams.image_urls.length < 2"
@click.stop="$refs.imageToVideoInput?.click()"
class="jimeng-create__upload-multiple-add"
>
<i class="iconfont icon-plus"></i>
</div>
</div>
</div>
</div>
</div>
<!-- 提示词输入 -->
<div class="jimeng-create__form-section">
<label class="jimeng-create__form-section-label">提示词</label>
<textarea
v-model="jimengStore.currentPrompt"
placeholder="描述你想要的视频效果"
class="jimeng-create__form-section-textarea"
rows="4"
maxlength="2000"
/>
<div class="jimeng-create__form-section-counter">
<span>{{ jimengStore.currentPrompt.length }}/2000</span>
</div>
</div>
<!-- 视频比例 -->
<CustomSelect
v-model="jimengStore.imageToVideoParams.aspect_ratio"
:options="
jimengStore.videoAspectRatioOptions.map((opt) => ({
label: opt.label,
value: opt.value,
}))
"
label="视频比例"
title="选择比例"
/>
</div>
<!-- 生成按钮 -->
<div class="jimeng-create__submit-btn">
@@ -539,19 +633,19 @@
</p>
</div>
<!-- 任务状态 -->
<div
v-if="item.status !== 'completed'"
class="jimeng-create__works-item-info-status"
>
<div v-if="item.status !== 'success'" class="jimeng-create__works-item-info-status">
<div
v-if="item.status === 'failed'"
class="jimeng-create__works-item-info-status--failed"
>
<i class="iconfont icon-warning"></i>
<span>失败</span>
<el-tag type="danger">任务失败</el-tag>
</div>
<div v-else class="jimeng-create__works-item-info-status--processing">
<div class="loading-spinner"></div>
<div
v-else
class="flex items-center jimeng-create__works-item-info-status--processing"
>
<div class="loading-spinner mr-1"></div>
<span>生成中</span>
</div>
</div>
@@ -670,13 +764,13 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useJimengStore } from '@/store/mobile/jimeng'
import CustomSelect from '@/components/mobile/CustomSelect.vue'
import CustomTabs from '@/components/ui/CustomTabs.vue'
import CustomTabPane from '@/components/ui/CustomTabPane.vue'
import CustomTabs from '@/components/ui/CustomTabs.vue'
import { checkSession } from '@/store/cache'
import { useJimengStore } from '@/store/mobile/jimeng'
import { onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const jimengStore = useJimengStore()

View File

@@ -9,7 +9,7 @@
>
<i class="iconfont icon-back text-gray-600"></i>
</button>
<h1 class="flex-1 text-center text-lg font-semibold text-gray-900">音乐创作</h1>
<h1 class="flex-1 text-center text-lg text-gray-900">音乐创作</h1>
<div class="w-8"></div>
</div>
</div>
@@ -189,9 +189,10 @@
<button
@click="suno.create"
:disabled="suno.loading"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="suno.loading" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span>{{ suno.loading ? '创作中...' : suno.btnText }}({{ suno.sunoPowerCost }}算力)</span>
</button>
</div>
@@ -210,7 +211,7 @@
>
<template #trigger>
<button
class="w-full py-3 bg-gradient-to-r from-purple-500 to-red-300 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
class="w-full py-3 bg-gradient-to-r from-purple-500 to-red-300 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i class="iconfont icon-upload mr-2"></i>
<span>上传音乐</span>
@@ -488,12 +489,12 @@
</template>
<script setup>
import '@/assets/css/mobile/suno.scss'
import CustomSelect from '@/components/mobile/CustomSelect.vue'
import { useSunoStore } from '@/store/mobile/suno'
import { showConfirmDialog } from 'vant'
import { onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useSunoStore } from '@/store/mobile/suno'
import CustomSelect from '@/views/mobile/components/CustomSelect.vue'
import { showConfirmDialog } from 'vant'
import '@/assets/css/mobile/suno.scss'
const router = useRouter()
const suno = useSunoStore()

View File

@@ -9,7 +9,7 @@
>
<i class="iconfont icon-back text-gray-600"></i>
</button>
<h1 class="flex-1 text-center text-lg font-semibold text-gray-900">视频创作</h1>
<h1 class="flex-1 text-center text-lg text-gray-900">视频创作</h1>
<div class="w-8"></div>
</div>
</div>
@@ -186,7 +186,7 @@
<button
@click="video.createLumaVideo"
:disabled="video.generating"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="video.generating" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
@@ -413,7 +413,7 @@
<button
@click="video.createKelingVideo"
:disabled="video.generating"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="video.generating" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
@@ -595,13 +595,12 @@
</template>
<script setup>
import '@/assets/css/mobile/video.scss'
import CustomSelect from '@/components/mobile/CustomSelect.vue'
import { useVideoStore } from '@/store/mobile/video'
import { showConfirmDialog } from 'vant'
import { onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useVideoStore } from '@/store/mobile/video'
import CustomSelect from '@/views/mobile/components/CustomSelect.vue'
import { showConfirmDialog } from 'vant'
import '@/assets/css/mobile/video.scss'
import CustomSelectOption from '../components/CustomSelectOption.vue'
const router = useRouter()
const video = useVideoStore()

View File

@@ -1,6 +1,6 @@
<template>
<div class="mobile-sd">
<van-form @submit="generate">
<van-form>
<van-cell-group class="px-3 pt-3 pb-4">
<div>
<van-field
@@ -74,12 +74,16 @@
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"
/>
<div class="text-line pt-6">
<el-tag>绘图消耗{{ dallPower }}算力当前算力{{ power }}</el-tag>
</div>
<div class="text-line">
<van-button round block type="primary" native-type="submit"> 立即生成 </van-button>
<div class="sticky bottom-4 bg-white rounded-xl p-4 shadow-sm">
<button
@click="generate"
:disabled="loading"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="loading" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span>{{ loading ? '创作中...' : '立即生成' }}({{ dallPower }}算力)</span>
</button>
</div>
</van-cell-group>
</van-form>

View File

@@ -1,6 +1,6 @@
<template>
<div class="mobile-mj">
<van-form @submit="generate">
<van-form>
<div class="text-line">图片比例</div>
<div class="text-line">
<van-row :gutter="10">
@@ -191,16 +191,16 @@
</van-collapse>
</div>
<div class="text-line pt-6">
<el-tag
>绘图消耗{{ mjPower }}算力U/V 操作消耗{{ mjActionPower }}算力当前算力{{
power
}}</el-tag
<div class="sticky bottom-4 bg-white rounded-xl p-4 shadow-sm">
<button
@click="generate"
:disabled="loading"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
</div>
<div class="text-line">
<van-button round block type="primary" native-type="submit"> 立即生成 </van-button>
<i v-if="loading" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span>{{ loading ? '创作中...' : '立即生成' }}({{ mjPower }}算力)</span>
</button>
</div>
</van-form>
@@ -735,5 +735,5 @@ const tabChange = (tab) => {
</script>
<style lang="scss">
@use '../../../assets/css/mobile/image-mj.scss' as *;
@use '@/assets/css/mobile/image-mj.scss' as *;
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="mobile-sd">
<van-form @submit="generate">
<van-form>
<van-cell-group class="px-3 pt-3 pb-4">
<div>
<van-field
@@ -130,12 +130,16 @@
</van-collapse-item>
</van-collapse>
<div class="text-line pt-6">
<el-tag>绘图消耗{{ sdPower }}算力当前算力{{ power }}</el-tag>
</div>
<div class="text-line">
<van-button round block type="primary" native-type="submit"> 立即生成 </van-button>
<div class="sticky bottom-4 bg-white rounded-xl p-4 shadow-sm">
<button
@click="generate"
:disabled="loading"
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
>
<i v-if="loading" class="iconfont icon-loading animate-spin"></i>
<i v-else class="iconfont icon-chuangzuo"></i>
<span>{{ loading ? '创作中...' : '立即生成' }}({{ sdPower }}算力)</span>
</button>
</div>
</van-cell-group>
</van-form>
@@ -563,5 +567,5 @@ const showInfo = (message) => {
</script>
<style lang="scss" scoped>
@use '../../../assets/css/mobile/image-sd.scss' as *;
@use '@/assets/css/mobile/image-sd.scss' as *;
</style>