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

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

@@ -334,6 +334,12 @@ func (h *JimengHandler) Remove(c *gin.Context) {
return
}
// 正在运行中的任务不能删除
if job.Status == model.JMTaskStatusGenerating || job.Status == model.JMTaskStatusInQueue {
resp.ERROR(c, "正在运行中的任务不能删除,否则无法退回算力")
return
}
tx := h.DB.Begin()
if err := tx.Where("id = ? AND user_id = ?", jobId, user.Id).Delete(&model.JimengJob{}).Error; err != nil {
logger.Errorf("delete jimeng job failed: %v", err)

View File

@@ -1,17 +1,9 @@
@font-face {
font-family: "OPlusSans3-Regular";
src: url("../fonts/OPlusSans3-Regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "OPlusSans3-Medium";
src: url("../fonts/OPlusSans3-Medium.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
$font-regular: "OPlusSans3-Regular", "PingFangSC-Regular", "Roboto", "sans-serif";
$font-medium: "OPlusSans3-Medium", "PingFangSC-Medium", "Roboto", "sans-serif";
$font-regular: 'PingFangSC-Regular', 'Roboto', 'sans-serif', 'ui-sans-serif', '-apple-system',
'system-ui', 'Segoe UI', 'Helvetica', 'Apple Color Emoji', 'Arial', 'sans-serif', 'Segoe UI Emoji',
'Segoe UI Symbol';
$font-medium: 'PingFangSC-Medium', 'Roboto', 'sans-serif', 'ui-sans-serif', '-apple-system',
'system-ui', 'Segoe UI', 'Helvetica', 'Apple Color Emoji', 'Arial', 'sans-serif', 'Segoe UI Emoji',
'Segoe UI Symbol';
.font-regular {
font-family: $font-regular;
@@ -21,4 +13,4 @@ $font-medium: "OPlusSans3-Medium", "PingFangSC-Medium", "Roboto", "sans-serif";
.font-medium {
font-family: $font-medium;
font-weight: 500;
}
}

View File

@@ -1,6 +1,6 @@
.mobile-mj {
.text-line {
padding: 6px;
padding: 6px 0;
font-size: 14px;
.van-row {
@@ -91,7 +91,8 @@
padding: 0;
position: relative;
.van-image, .task-in-queue {
.van-image,
.task-in-queue {
min-height: 100px;
}
@@ -213,4 +214,4 @@
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
.mobile-sd {
.text-line {
padding: 0 6px;
padding: 0;
font-size: 14px;
.van-row {
@@ -98,7 +98,8 @@
padding: 0;
position: relative;
.van-image, .task-in-queue {
.van-image,
.task-in-queue {
min-height: 100px;
}
@@ -201,4 +202,4 @@
}
}
}
}
}

View File

@@ -498,40 +498,10 @@
.custom-upload {
width: 100%;
:deep(.el-upload-dragger) {
:deep(.el-upload) {
width: 100%;
height: auto;
border: 2px dashed var(--el-border-color);
border-radius: 12px;
background: var(--el-bg-color);
transition: all 0.3s ease;
cursor: pointer;
padding: 0;
overflow: hidden;
&:hover {
border-color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}
&.is-dragover {
border-color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
transform: scale(1.02);
}
}
.upload-btn {
background: var(--el-color-primary) !important;
border-color: var(--el-color-primary) !important;
width: 100%;
&:hover {
background: var(--el-color-primary-dark-2) !important;
border-color: var(--el-color-primary-dark-2) !important;
}
}
}

View File

@@ -1,10 +1,17 @@
@use 'font.scss' as *;
:root[data-theme="light"] {
:root[data-theme='light'] {
--text-fb: #000;
--text-color: #5b62ce; // 主要的文本颜色
--normal-color: rgba(43, 54, 116, 1); // 普通颜色
--theme-textcolor-normal: #5b62ce;
p, h1, h2, h3, h4, h5, h6, article {
p,
h1,
h2,
h3,
h4,
h5,
h6,
article {
font-family: $font-regular;
}
html,
@@ -55,4 +62,4 @@
--quote-text-color: #333;
// 面板背景
--panel-bg: linear-gradient(135deg, #f5eafe 0%, #e9e6fc 100%);
}
}

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4125778 */
src: url('iconfont.woff2?t=1752831319382') format('woff2'),
url('iconfont.woff?t=1752831319382') format('woff'),
url('iconfont.ttf?t=1752831319382') format('truetype');
src: url('iconfont.woff2?t=1754626711656') format('woff2'),
url('iconfont.woff?t=1754626711656') format('woff'),
url('iconfont.ttf?t=1754626711656') format('truetype');
}
.iconfont {
@@ -13,6 +13,26 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-back-bold:before {
content: "\e654";
}
.icon-back-circle:before {
content: "\e653";
}
.icon-back:before {
content: "\e6c8";
}
.icon-openai:before {
content: "\e652";
}
.icon-suanli:before {
content: "\e651";
}
.icon-jimeng2:before {
content: "\eabc";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,41 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "27025075",
"name": "返回",
"font_class": "back-bold",
"unicode": "e654",
"unicode_decimal": 58964
},
{
"icon_id": "6237605",
"name": "返回",
"font_class": "back-circle",
"unicode": "e653",
"unicode_decimal": 58963
},
{
"icon_id": "18952076",
"name": "返回",
"font_class": "back",
"unicode": "e6c8",
"unicode_decimal": 59080
},
{
"icon_id": "33483666",
"name": "openai",
"font_class": "openai",
"unicode": "e652",
"unicode_decimal": 58962
},
{
"icon_id": "25677845",
"name": "算力",
"font_class": "suanli",
"unicode": "e651",
"unicode_decimal": 58961
},
{
"icon_id": "42693930",
"name": "即梦AI-02",
@@ -434,7 +469,7 @@
},
{
"icon_id": "39584617",
"name": "MidJourney-copy",
"name": "MidJourney",
"font_class": "MidJourney",
"unicode": "e60e",
"unicode_decimal": 58894

Binary file not shown.

View File

@@ -1,8 +1,8 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog'
import { httpGet, httpPost } from '@/utils/http'
import { showMessageError, showMessageOK, showLoading, closeLoading } from '@/utils/dialog'
import { defineStore } from 'pinia'
import { showConfirmDialog } from 'vant'
import { computed, ref } from 'vue'
export const useJimengStore = defineStore('mobile-jimeng', () => {
// 响应式数据
@@ -209,7 +209,7 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
}
listLoading.value = true
return httpGet('/api/jimeng/list', { page: page.value, page_size: pageSize.value })
return httpPost('/api/jimeng/jobs', { page: page.value, page_size: pageSize.value })
.then((res) => {
total.value = res.data.total
let needPull = false

View File

@@ -441,8 +441,6 @@ export const useVideoStore = defineStore('video', () => {
}
isGenerating.value = true
showLoading('正在生成视频脚本...')
try {
const res = await httpPost('/api/prompt/video', { prompt })
if (activeVideoType.value === 'luma') {
@@ -450,10 +448,8 @@ export const useVideoStore = defineStore('video', () => {
} else {
kelingParams.prompt = res.data
}
closeLoading()
} catch (error) {
showMessageError('生成提示词失败:' + error.message)
closeLoading()
} finally {
isGenerating.value = false
}

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>