mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-05-10 03:34:26 +08:00
手机端即梦页面功能完成
This commit is contained in:
@@ -153,12 +153,7 @@ func (h *JimengHandler) CreateTask(c *gin.Context) {
|
|||||||
"seed": req.Seed,
|
"seed": req.Seed,
|
||||||
"scale": req.Scale,
|
"scale": req.Scale,
|
||||||
}
|
}
|
||||||
if len(req.ImageUrls) > 0 {
|
params["image_urls"] = []string{req.ImageInput}
|
||||||
params["image_urls"] = req.ImageUrls
|
|
||||||
}
|
|
||||||
if len(req.BinaryDataBase64) > 0 {
|
|
||||||
params["binary_data_base64"] = req.BinaryDataBase64
|
|
||||||
}
|
|
||||||
case "image_effects":
|
case "image_effects":
|
||||||
powerCost = h.getPowerFromConfig(model.JMTaskTypeImageEffects)
|
powerCost = h.getPowerFromConfig(model.JMTaskTypeImageEffects)
|
||||||
taskType = model.JMTaskTypeImageEffects
|
taskType = model.JMTaskTypeImageEffects
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* 来自 SunoCreate.vue 的样式,已迁移至此,供移动端页面使用 */
|
/* 来自 SunoCreate.vue 的样式,已迁移至此,供移动端页面使用 */
|
||||||
|
|
||||||
// 自定义动画
|
/* 自定义动画 */
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
animation: scale-up 0.3s ease-out;
|
animation: scale-up 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文本截断
|
/* 文本截断 */
|
||||||
.line-clamp-2 {
|
.line-clamp-2 {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
@@ -69,47 +69,56 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 滚动监听自动加载更多
|
/* 滚动监听自动加载更多 */
|
||||||
.scroll-container {
|
.scroll-container {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色模式适配
|
/* 深色模式适配 */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.bg-gray-50 {
|
.bg-gray-50 {
|
||||||
background-color: #1f2937;
|
background-color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-white {
|
.bg-white {
|
||||||
background-color: #374151;
|
background-color: #374151;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-gray-900 {
|
.text-gray-900 {
|
||||||
color: #f9fafb;
|
color: #f9fafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-gray-700 {
|
.text-gray-700 {
|
||||||
color: #d1d5db;
|
color: #d1d5db;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-gray-600 {
|
.text-gray-600 {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-gray-500 {
|
.text-gray-500 {
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-gray-200 {
|
.border-gray-200 {
|
||||||
border-color: #4b5563;
|
border-color: #4b5563;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-gray-100:hover {
|
.bg-gray-100:hover {
|
||||||
background-color: #4b5563;
|
background-color: #4b5563;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// el-upload 组件样式定制
|
/* el-upload 组件样式定制 */
|
||||||
.upload-area {
|
.upload-area {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
:deep(.el-upload) {
|
:deep(.el-upload) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-button) {
|
:deep(.el-button) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -137,13 +137,13 @@ export const useJimengStore = defineStore('jimeng', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const imageEditParams = reactive({
|
const imageEditParams = reactive({
|
||||||
image_urls: '',
|
image_input: '',
|
||||||
scale: 0.5,
|
scale: 0.5,
|
||||||
seed: -1,
|
seed: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const imageEffectsParams = reactive({
|
const imageEffectsParams = reactive({
|
||||||
image_input1: '',
|
image_input: '',
|
||||||
template_id: '',
|
template_id: '',
|
||||||
size: '1328x1328',
|
size: '1328x1328',
|
||||||
})
|
})
|
||||||
@@ -154,7 +154,7 @@ export const useJimengStore = defineStore('jimeng', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const imageToVideoParams = reactive({
|
const imageToVideoParams = reactive({
|
||||||
image_urls: [],
|
image_input: [],
|
||||||
aspect_ratio: '16:9',
|
aspect_ratio: '16:9',
|
||||||
seed: -1,
|
seed: -1,
|
||||||
})
|
})
|
||||||
@@ -374,7 +374,7 @@ export const useJimengStore = defineStore('jimeng', () => {
|
|||||||
break
|
break
|
||||||
case 'image_to_image':
|
case 'image_to_image':
|
||||||
Object.assign(requestData, {
|
Object.assign(requestData, {
|
||||||
image_input: imageToImageParams.image_input,
|
image_input: imageToImageParams.image_input[0],
|
||||||
width: parseInt(imageToImageParams.size.split('x')[0]),
|
width: parseInt(imageToImageParams.size.split('x')[0]),
|
||||||
height: parseInt(imageToImageParams.size.split('x')[1]),
|
height: parseInt(imageToImageParams.size.split('x')[1]),
|
||||||
gpen: imageToImageParams.gpen,
|
gpen: imageToImageParams.gpen,
|
||||||
@@ -386,14 +386,14 @@ export const useJimengStore = defineStore('jimeng', () => {
|
|||||||
break
|
break
|
||||||
case 'image_edit':
|
case 'image_edit':
|
||||||
Object.assign(requestData, {
|
Object.assign(requestData, {
|
||||||
image_urls: [imageEditParams.image_urls],
|
image_input: imageEditParams.image_input[0],
|
||||||
scale: imageEditParams.scale,
|
scale: imageEditParams.scale,
|
||||||
seed: imageEditParams.seed,
|
seed: imageEditParams.seed,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'image_effects':
|
case 'image_effects':
|
||||||
Object.assign(requestData, {
|
Object.assign(requestData, {
|
||||||
image_input: imageEffectsParams.image_input1,
|
image_input: imageEffectsParams.image_input[0],
|
||||||
template_id: imageEffectsParams.template_id,
|
template_id: imageEffectsParams.template_id,
|
||||||
width: parseInt(imageEffectsParams.size.split('x')[0]),
|
width: parseInt(imageEffectsParams.size.split('x')[0]),
|
||||||
height: parseInt(imageEffectsParams.size.split('x')[1]),
|
height: parseInt(imageEffectsParams.size.split('x')[1]),
|
||||||
@@ -408,7 +408,7 @@ export const useJimengStore = defineStore('jimeng', () => {
|
|||||||
break
|
break
|
||||||
case 'image_to_video':
|
case 'image_to_video':
|
||||||
Object.assign(requestData, {
|
Object.assign(requestData, {
|
||||||
image_urls: imageToVideoParams.image_urls,
|
image_urls: imageToVideoParams.image_input,
|
||||||
aspect_ratio: imageToVideoParams.aspect_ratio,
|
aspect_ratio: imageToVideoParams.aspect_ratio,
|
||||||
seed: imageToVideoParams.seed,
|
seed: imageToVideoParams.seed,
|
||||||
})
|
})
|
||||||
@@ -498,62 +498,6 @@ export const useJimengStore = defineStore('jimeng', () => {
|
|||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 画同款功能
|
|
||||||
const drawSame = (item) => {
|
|
||||||
// 联动功能开关
|
|
||||||
if (item.type === 'text_to_image' || item.type === 'image_to_image') {
|
|
||||||
activeCategory.value = 'image_generation'
|
|
||||||
useImageInput.value = item.type === 'image_to_image'
|
|
||||||
} else if (item.type === 'text_to_video' || item.type === 'image_to_video') {
|
|
||||||
activeCategory.value = 'video_generation'
|
|
||||||
useImageInput.value = item.type === 'image_to_video'
|
|
||||||
} else if (item.type === 'image_edit') {
|
|
||||||
activeCategory.value = 'image_editing'
|
|
||||||
} else if (item.type === 'image_effects') {
|
|
||||||
activeCategory.value = 'image_effects'
|
|
||||||
}
|
|
||||||
switchFunction(item.type)
|
|
||||||
nextTick(() => {
|
|
||||||
currentPrompt.value = item.prompt
|
|
||||||
})
|
|
||||||
if (item.type === 'text_to_image') {
|
|
||||||
if (item.width && item.height) {
|
|
||||||
textToImageParams.size = `${item.width}x${item.height}`
|
|
||||||
}
|
|
||||||
if (item.scale) textToImageParams.scale = item.scale
|
|
||||||
if (item.seed) textToImageParams.seed = item.seed
|
|
||||||
if (item.use_pre_llm !== undefined) textToImageParams.use_pre_llm = item.use_pre_llm
|
|
||||||
} else if (item.type === 'image_to_image') {
|
|
||||||
if (item.image_input) imageToImageParams.image_input = item.image_input
|
|
||||||
if (item.width && item.height) {
|
|
||||||
imageToImageParams.size = `${item.width}x${item.height}`
|
|
||||||
}
|
|
||||||
if (item.gpen) imageToImageParams.gpen = item.gpen
|
|
||||||
if (item.skin) imageToImageParams.skin = item.skin
|
|
||||||
if (item.skin_unifi) imageToImageParams.skin_unifi = item.skin_unifi
|
|
||||||
if (item.gen_mode) imageToImageParams.gen_mode = item.gen_mode
|
|
||||||
if (item.seed) imageToImageParams.seed = item.seed
|
|
||||||
} else if (item.type === 'image_edit') {
|
|
||||||
if (item.image_urls) imageEditParams.image_urls = item.image_urls
|
|
||||||
if (item.scale) imageEditParams.scale = item.scale
|
|
||||||
if (item.seed) imageEditParams.seed = item.seed
|
|
||||||
} else if (item.type === 'image_effects') {
|
|
||||||
if (item.image_input1) imageEffectsParams.image_input1 = item.image_input1
|
|
||||||
if (item.template_id) imageEffectsParams.template_id = item.template_id
|
|
||||||
if (item.width && item.height) {
|
|
||||||
imageEffectsParams.size = `${item.width}x${item.height}`
|
|
||||||
}
|
|
||||||
} else if (item.type === 'text_to_video') {
|
|
||||||
if (item.aspect_ratio) textToVideoParams.aspect_ratio = item.aspect_ratio
|
|
||||||
if (item.seed) textToVideoParams.seed = item.seed
|
|
||||||
} else if (item.type === 'image_to_video') {
|
|
||||||
if (item.image_urls) imageToVideoParams.image_urls = item.image_urls
|
|
||||||
if (item.aspect_ratio) imageToVideoParams.aspect_ratio = item.aspect_ratio
|
|
||||||
if (item.seed) imageToVideoParams.seed = item.seed
|
|
||||||
}
|
|
||||||
showMessageOK('已填入全部参数,可直接生成同款')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面卸载时清理轮询
|
// 页面卸载时清理轮询
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
stopPolling()
|
stopPolling()
|
||||||
@@ -616,7 +560,6 @@ export const useJimengStore = defineStore('jimeng', () => {
|
|||||||
removeJob,
|
removeJob,
|
||||||
playVideo,
|
playVideo,
|
||||||
cleanup,
|
cleanup,
|
||||||
drawSame,
|
|
||||||
|
|
||||||
// 工具函数
|
// 工具函数
|
||||||
substr,
|
substr,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { showMessageError, showMessageOK } from '@/utils/dialog'
|
import { showMessageError, showMessageOK } from '@/utils/dialog'
|
||||||
import { httpGet, httpPost } from '@/utils/http'
|
import { httpDownload, httpGet, httpPost } from '@/utils/http'
|
||||||
|
import { replaceImg } from '@/utils/libs'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { showConfirmDialog } from 'vant'
|
import { showConfirmDialog } from 'vant'
|
||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
@@ -144,13 +145,13 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const imageEditParams = reactive({
|
const imageEditParams = reactive({
|
||||||
image_urls: '',
|
image_input: '',
|
||||||
scale: 0.5,
|
scale: 0.5,
|
||||||
seed: -1,
|
seed: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const imageEffectsParams = reactive({
|
const imageEffectsParams = reactive({
|
||||||
image_input1: '',
|
image_input: '',
|
||||||
template_id: '',
|
template_id: '',
|
||||||
size: '1328x1328',
|
size: '1328x1328',
|
||||||
})
|
})
|
||||||
@@ -245,7 +246,7 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
|
|||||||
break
|
break
|
||||||
case 'image_to_image':
|
case 'image_to_image':
|
||||||
Object.assign(requestData, {
|
Object.assign(requestData, {
|
||||||
image_input: imageToImageParams.image_input,
|
image_input: imageToImageParams.image_input[0],
|
||||||
width: parseInt(imageToImageParams.size.split('x')[0]),
|
width: parseInt(imageToImageParams.size.split('x')[0]),
|
||||||
height: parseInt(imageToImageParams.size.split('x')[1]),
|
height: parseInt(imageToImageParams.size.split('x')[1]),
|
||||||
gpen: imageToImageParams.gpen,
|
gpen: imageToImageParams.gpen,
|
||||||
@@ -257,14 +258,14 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
|
|||||||
break
|
break
|
||||||
case 'image_edit':
|
case 'image_edit':
|
||||||
Object.assign(requestData, {
|
Object.assign(requestData, {
|
||||||
image_urls: [imageEditParams.image_urls],
|
image_input: imageEditParams.image_input[0],
|
||||||
scale: imageEditParams.scale,
|
scale: imageEditParams.scale,
|
||||||
seed: imageEditParams.seed,
|
seed: imageEditParams.seed,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'image_effects':
|
case 'image_effects':
|
||||||
Object.assign(requestData, {
|
Object.assign(requestData, {
|
||||||
image_input: imageEffectsParams.image_input1,
|
image_input: imageEffectsParams.image_input[0],
|
||||||
template_id: imageEffectsParams.template_id,
|
template_id: imageEffectsParams.template_id,
|
||||||
width: parseInt(imageEffectsParams.size.split('x')[0]),
|
width: parseInt(imageEffectsParams.size.split('x')[0]),
|
||||||
height: parseInt(imageEffectsParams.size.split('x')[1]),
|
height: parseInt(imageEffectsParams.size.split('x')[1]),
|
||||||
@@ -279,7 +280,7 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
|
|||||||
break
|
break
|
||||||
case 'image_to_video':
|
case 'image_to_video':
|
||||||
Object.assign(requestData, {
|
Object.assign(requestData, {
|
||||||
image_urls: imageToVideoParams.image_urls,
|
image_urls: imageToVideoParams.image_input,
|
||||||
aspect_ratio: imageToVideoParams.aspect_ratio,
|
aspect_ratio: imageToVideoParams.aspect_ratio,
|
||||||
seed: imageToVideoParams.seed,
|
seed: imageToVideoParams.seed,
|
||||||
})
|
})
|
||||||
@@ -312,11 +313,13 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
|
|||||||
total.value = res.data.total
|
total.value = res.data.total
|
||||||
let needPull = false
|
let needPull = false
|
||||||
const items = []
|
const items = []
|
||||||
for (let v of res.data.items) {
|
if (res.data.items) {
|
||||||
if (v.status === 'in_queue' || v.status === 'generating') {
|
for (let v of res.data.items) {
|
||||||
needPull = true
|
if (v.status === 'in_queue' || v.status === 'generating') {
|
||||||
|
needPull = true
|
||||||
|
}
|
||||||
|
items.push(v)
|
||||||
}
|
}
|
||||||
items.push(v)
|
|
||||||
}
|
}
|
||||||
listLoading.value = false
|
listLoading.value = false
|
||||||
taskPulling.value = needPull
|
taskPulling.value = needPull
|
||||||
@@ -347,20 +350,33 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
|
|||||||
showMediaDialog.value = true
|
showMediaDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadFile = (item) => {
|
const downloadFile = async (item) => {
|
||||||
|
const url = replaceImg(item.video_url || item.img_url)
|
||||||
|
const downloadURL = `${import.meta.env.VITE_API_HOST}/api/download?url=${url}`
|
||||||
|
const urlObj = new URL(url)
|
||||||
|
const fileName = urlObj.pathname.split('/').pop()
|
||||||
|
|
||||||
item.downloading = true
|
item.downloading = true
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = item.img_url || item.video_url
|
try {
|
||||||
link.download = item.title || 'file'
|
const response = await httpDownload(downloadURL)
|
||||||
document.body.appendChild(link)
|
const blob = new Blob([response.data])
|
||||||
link.click()
|
const link = document.createElement('a')
|
||||||
document.body.removeChild(link)
|
link.href = URL.createObjectURL(blob)
|
||||||
item.downloading = false
|
link.download = fileName
|
||||||
showMessageSuccess('开始下载')
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(link.href)
|
||||||
|
item.downloading = false
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('下载失败')
|
||||||
|
item.downloading = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const retryTask = (id) => {
|
const retryTask = (id) => {
|
||||||
return httpPost('/api/jimeng/retry', { id })
|
return httpGet('/api/jimeng/retry', { id })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showMessageOK('重试任务成功')
|
showMessageOK('重试任务成功')
|
||||||
fetchData(1)
|
fetchData(1)
|
||||||
@@ -425,60 +441,6 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
|
|||||||
currentMediaUrl.value = ''
|
currentMediaUrl.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:画同款功能
|
|
||||||
const drawSame = (item) => {
|
|
||||||
// 设置当前提示词
|
|
||||||
currentPrompt.value = item.prompt
|
|
||||||
|
|
||||||
// 根据任务类型设置相应的参数
|
|
||||||
switch (item.type) {
|
|
||||||
case 'text_to_image':
|
|
||||||
activeCategory.value = 'image_generation'
|
|
||||||
useImageInput.value = false
|
|
||||||
// 设置图片尺寸(如果有的话)
|
|
||||||
if (item.width && item.height) {
|
|
||||||
textToImageParams.size = `${item.width}x${item.height}`
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'image_to_image':
|
|
||||||
activeCategory.value = 'image_generation'
|
|
||||||
useImageInput.value = true
|
|
||||||
// 设置图片尺寸(如果有的话)
|
|
||||||
if (item.width && item.height) {
|
|
||||||
imageToImageParams.size = `${item.width}x${item.height}`
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'image_edit':
|
|
||||||
activeCategory.value = 'image_editing'
|
|
||||||
break
|
|
||||||
case 'image_effects':
|
|
||||||
activeCategory.value = 'image_effects'
|
|
||||||
// 设置特效模板(如果有的话)
|
|
||||||
if (item.template_id) {
|
|
||||||
imageEffectsParams.template_id = item.template_id
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'text_to_video':
|
|
||||||
activeCategory.value = 'video_generation'
|
|
||||||
useImageInput.value = false
|
|
||||||
// 设置视频比例(如果有的话)
|
|
||||||
if (item.aspect_ratio) {
|
|
||||||
textToVideoParams.aspect_ratio = item.aspect_ratio
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'image_to_video':
|
|
||||||
activeCategory.value = 'video_generation'
|
|
||||||
useImageInput.value = true
|
|
||||||
// 设置视频比例(如果有的话)
|
|
||||||
if (item.aspect_ratio) {
|
|
||||||
imageToVideoParams.aspect_ratio = item.aspect_ratio
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
showMessageOK('已设置画同款参数')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增:复制提示词功能
|
// 新增:复制提示词功能
|
||||||
const copyPrompt = (prompt) => {
|
const copyPrompt = (prompt) => {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
@@ -556,7 +518,6 @@ export const useJimengStore = defineStore('mobile-jimeng', () => {
|
|||||||
stopTaskPolling,
|
stopTaskPolling,
|
||||||
closeMediaDialog,
|
closeMediaDialog,
|
||||||
fetchPowerConfig,
|
fetchPowerConfig,
|
||||||
drawSame,
|
|
||||||
copyPrompt,
|
copyPrompt,
|
||||||
copyErrorMsg,
|
copyErrorMsg,
|
||||||
init,
|
init,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getSystemInfo } from '@/store/cache'
|
import { getSystemInfo } from '@/store/cache'
|
||||||
import { closeLoading, showLoading, showToastMessage } from '@/utils/dialog'
|
import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog'
|
||||||
import { httpDownload, httpGet, httpPost } from '@/utils/http'
|
import { httpDownload, httpGet, httpPost } from '@/utils/http'
|
||||||
import { replaceImg } from '@/utils/libs'
|
import { replaceImg } from '@/utils/libs'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
@@ -70,7 +70,7 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
}
|
}
|
||||||
const selectTag = (tag) => {
|
const selectTag = (tag) => {
|
||||||
if (data.tags.length + tag.value.length >= 119) {
|
if (data.tags.length + tag.value.length >= 119) {
|
||||||
showToastMessage('标签长度超出限制', 'error')
|
showMessageError('标签长度超出限制')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const currentTags = data.tags.split(',').filter((t) => t.trim())
|
const currentTags = data.tags.split(',').filter((t) => t.trim())
|
||||||
@@ -81,7 +81,7 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
}
|
}
|
||||||
const createLyric = () => {
|
const createLyric = () => {
|
||||||
if (data.lyrics === '') {
|
if (data.lyrics === '') {
|
||||||
showToastMessage('请输入歌词描述', 'error')
|
showMessageError('请输入歌词描述')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isGenerating.value = true
|
isGenerating.value = true
|
||||||
@@ -91,10 +91,10 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
data.title = lines.shift().replace(/\*/g, '')
|
data.title = lines.shift().replace(/\*/g, '')
|
||||||
lines.shift()
|
lines.shift()
|
||||||
data.lyrics = lines.join('\n')
|
data.lyrics = lines.join('\n')
|
||||||
showToastMessage('歌词生成成功', 'success')
|
showMessageOK('歌词生成成功')
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
showToastMessage('歌词生成失败:' + e.message, 'error')
|
showMessageError('歌词生成失败:' + e.message)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isGenerating.value = false
|
isGenerating.value = false
|
||||||
@@ -109,7 +109,7 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
const beforeUpload = (file) => {
|
const beforeUpload = (file) => {
|
||||||
const isLt10M = file.size / 1024 / 1024 < 10
|
const isLt10M = file.size / 1024 / 1024 < 10
|
||||||
if (!isLt10M) {
|
if (!isLt10M) {
|
||||||
showToastMessage('文件大小不能超过 10MB!', 'error')
|
showMessageError('文件大小不能超过 10MB!')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -127,7 +127,7 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
fetchData(1)
|
fetchData(1)
|
||||||
showToastMessage('歌曲上传成功', 'success')
|
showMessageOK('歌曲上传成功')
|
||||||
removeRefSong()
|
removeRefSong()
|
||||||
uploadFiles.value = []
|
uploadFiles.value = []
|
||||||
if (uploadRef.value) {
|
if (uploadRef.value) {
|
||||||
@@ -135,14 +135,14 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
showToastMessage('歌曲上传失败:' + e.message, 'error')
|
showMessageError('歌曲上传失败:' + e.message)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeLoading()
|
closeLoading()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
showToastMessage('文件上传失败:' + e.message, 'error')
|
showMessageError('文件上传失败:' + e.message)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeLoading()
|
closeLoading()
|
||||||
@@ -155,21 +155,21 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
data.extend_secs = refSong.value ? refSong.value.extend_secs : 0
|
data.extend_secs = refSong.value ? refSong.value.extend_secs : 0
|
||||||
if (refSong.value) {
|
if (refSong.value) {
|
||||||
if (data.extend_secs > refSong.value.duration) {
|
if (data.extend_secs > refSong.value.duration) {
|
||||||
showToastMessage('续写开始时间不能超过原歌曲长度', 'error')
|
showMessageError('续写开始时间不能超过原歌曲长度')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if (custom.value) {
|
} else if (custom.value) {
|
||||||
if (data.lyrics === '') {
|
if (data.lyrics === '') {
|
||||||
showToastMessage('请输入歌词', 'error')
|
showMessageError('请输入歌词')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (data.title === '') {
|
if (data.title === '') {
|
||||||
showToastMessage('请输入歌曲标题', 'error')
|
showMessageError('请输入歌曲标题')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (data.prompt === '') {
|
if (data.prompt === '') {
|
||||||
showToastMessage('请输入歌曲描述', 'error')
|
showMessageError('请输入歌曲描述')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,10 +178,10 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
fetchData(1)
|
fetchData(1)
|
||||||
taskPulling.value = true
|
taskPulling.value = true
|
||||||
showToastMessage('创建任务成功', 'success')
|
showMessageOK('创建任务成功')
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
showToastMessage('创建任务失败:' + e.message, 'error')
|
showMessageError('创建任务失败:' + e.message)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -219,7 +219,7 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
listLoading.value = false
|
listLoading.value = false
|
||||||
showToastMessage('获取作品列表失败:' + e.message, 'error')
|
showMessageError('获取作品列表失败:' + e.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const loadMore = () => {
|
const loadMore = () => {
|
||||||
@@ -279,7 +279,7 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
item.downloading = false
|
item.downloading = false
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
showToastMessage('下载失败', 'error')
|
showMessageError('下载失败')
|
||||||
item.downloading = false
|
item.downloading = false
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -296,11 +296,11 @@ export const useSunoStore = defineStore('suno', () => {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
httpGet('/api/suno/remove', { id: item.id })
|
httpGet('/api/suno/remove', { id: item.id })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showToastMessage('任务删除成功', 'success')
|
showMessageOK('任务删除成功')
|
||||||
fetchData(1)
|
fetchData(1)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
showToastMessage('任务删除失败', 'error')
|
showMessageError('任务删除失败')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export function showMessageOK(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showMessageSuccess(message) {
|
||||||
|
showMessageOK(message)
|
||||||
|
}
|
||||||
|
|
||||||
export function showMessageInfo(message) {
|
export function showMessageInfo(message) {
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
showToast(message)
|
showToast(message)
|
||||||
@@ -56,24 +60,3 @@ export function showLoading(message = '正在处理...') {
|
|||||||
export function closeLoading() {
|
export function closeLoading() {
|
||||||
closeToast()
|
closeToast()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义 Toast 消息系统
|
|
||||||
export function showToastMessage(message, type = 'info', duration = 3000) {
|
|
||||||
const toast = document.createElement('div')
|
|
||||||
toast.className = `fixed top-20 left-1/2 transform -translate-x-1/2 z-50 px-4 py-2 rounded-lg text-white font-medium ${
|
|
||||||
type === 'error' ? 'bg-red-500' : type === 'success' ? 'bg-green-500' : 'bg-blue-500'
|
|
||||||
} animate-fade-in`
|
|
||||||
toast.textContent = message
|
|
||||||
document.body.appendChild(toast)
|
|
||||||
|
|
||||||
if (duration > 0) {
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.classList.add('animate-fade-out')
|
|
||||||
setTimeout(() => {
|
|
||||||
if (document.body.contains(toast)) {
|
|
||||||
document.body.removeChild(toast)
|
|
||||||
}
|
|
||||||
}, 300)
|
|
||||||
}, duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ import MarkdownIt from 'markdown-it'
|
|||||||
import emoji from 'markdown-it-emoji'
|
import emoji from 'markdown-it-emoji'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { isMobile } from '@/utils/libs'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -146,6 +147,10 @@ const md = new MarkdownIt({
|
|||||||
}).use(emoji)
|
}).use(emoji)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (isMobile()) {
|
||||||
|
router.push('/mobile/index')
|
||||||
|
return
|
||||||
|
}
|
||||||
getSystemInfo()
|
getSystemInfo()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
title.value = res.data.title
|
title.value = res.data.title
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="param-line">
|
<div class="param-line">
|
||||||
<ImageUpload
|
<ImageUpload
|
||||||
v-model="store.imageEditParams.image_urls"
|
v-model="store.imageEditParams.image_input"
|
||||||
:max-count="1"
|
:max-count="1"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
/>
|
/>
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="param-line">
|
<div class="param-line">
|
||||||
<ImageUpload
|
<ImageUpload
|
||||||
v-model="store.imageEffectsParams.image_input1"
|
v-model="store.imageEffectsParams.image_input"
|
||||||
:max-count="1"
|
:max-count="1"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
/>
|
/>
|
||||||
@@ -271,7 +271,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="param-line">
|
<div class="param-line">
|
||||||
<ImageUpload
|
<ImageUpload
|
||||||
v-model="store.imageToVideoParams.image_urls"
|
v-model="store.imageToVideoParams.image_input"
|
||||||
:max-count="2"
|
:max-count="2"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
/>
|
/>
|
||||||
@@ -387,7 +387,7 @@
|
|||||||
preload="auto"
|
preload="auto"
|
||||||
loop="loop"
|
loop="loop"
|
||||||
muted="muted"
|
muted="muted"
|
||||||
class="preview-video w-full h-full"
|
class="w-full h-full object-cover"
|
||||||
>
|
>
|
||||||
您的浏览器不支持视频播放
|
您的浏览器不支持视频播放
|
||||||
</video>
|
</video>
|
||||||
@@ -457,15 +457,6 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="ml-1">
|
|
||||||
<el-tooltip content="画同款" placement="top">
|
|
||||||
<i
|
|
||||||
class="iconfont icon-image-list cursor-pointer"
|
|
||||||
@click="store.drawSame(item)"
|
|
||||||
></i>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<template v-if="item.status === 'failed'">
|
<template v-if="item.status === 'failed'">
|
||||||
<span class="ml-1" v-if="item.status === 'failed'">
|
<span class="ml-1" v-if="item.status === 'failed'">
|
||||||
<el-tooltip content="重试" placement="top">
|
<el-tooltip content="重试" placement="top">
|
||||||
@@ -531,16 +522,19 @@
|
|||||||
|
|
||||||
<!-- 视频预览对话框 -->
|
<!-- 视频预览对话框 -->
|
||||||
<el-dialog v-model="store.showDialog" title="视频预览" center>
|
<el-dialog v-model="store.showDialog" title="视频预览" center>
|
||||||
<video
|
<div class="flex justify-center items-center">
|
||||||
:src="store.currentVideoUrl"
|
<video
|
||||||
autoplay="true"
|
:src="store.currentVideoUrl"
|
||||||
controls
|
autoplay
|
||||||
preload="auto"
|
controls
|
||||||
loop="loop"
|
preload="auto"
|
||||||
muted="muted"
|
loop
|
||||||
>
|
muted
|
||||||
您的浏览器不支持视频播放
|
style="max-height: calc(100vh - 100px); max-width: 100vw; object-fit: cover"
|
||||||
</video>
|
>
|
||||||
|
您的浏览器不支持视频播放
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -55,149 +55,268 @@ onMounted(() => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.login-page {
|
.login-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: var(--theme-bg-all);
|
||||||
|
background-image: var(--panel-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.back-home-btn {
|
.back-home-btn {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--line-box);
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--theme-text-color-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
background: var(--hover-deep-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--line-box);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 24px;
|
top: 0;
|
||||||
left: 24px;
|
left: 0;
|
||||||
z-index: 10;
|
right: 0;
|
||||||
font-size: 22px;
|
height: 4px;
|
||||||
color: #fff;
|
background: var(--btnColor);
|
||||||
background: rgba(0, 0, 0, 0.15);
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
.back-home-btn:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.back-home-btn {
|
|
||||||
top: 12px;
|
|
||||||
left: 12px;
|
|
||||||
font-size: 20px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.van-theme-dark) .back-home-btn {
|
|
||||||
color: #fff;
|
|
||||||
background: rgba(0, 0, 0, 0.35);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.login-container {
|
.login-header {
|
||||||
width: 100%;
|
text-align: center;
|
||||||
max-width: 480px;
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
.login-card {
|
.login-title {
|
||||||
background: var(--el-bg-color);
|
font-size: 28px;
|
||||||
border-radius: 16px;
|
font-weight: 600;
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
color: var(--theme-text-color-primary);
|
||||||
overflow: hidden;
|
margin: 0 0 8px 0;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
.login-header {
|
.login-subtitle {
|
||||||
background: linear-gradient(135deg, var(--el-color-primary), #8b5cf6);
|
font-size: 16px;
|
||||||
color: white;
|
color: var(--theme-text-color-secondary);
|
||||||
padding: 40px 30px;
|
margin: 0;
|
||||||
text-align: center;
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.login-title {
|
.login-content {
|
||||||
font-size: 28px;
|
:deep(.login-dialog) {
|
||||||
font-weight: 600;
|
.form {
|
||||||
margin: 0 0 8px 0;
|
.block {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
.el-input__wrapper {
|
||||||
|
background: var(--el-fill-color-blank);
|
||||||
|
border: 1px solid var(--line-box);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.is-focus {
|
||||||
|
border-color: var(--border-active);
|
||||||
|
box-shadow: 0 0 0 3px rgba(91, 98, 206, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
color: var(--theme-text-color-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__prefix {
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.login-subtitle {
|
.btn-row {
|
||||||
|
margin-top: 32px;
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--btnColor);
|
||||||
|
border: none;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
opacity: 0.9;
|
font-weight: 500;
|
||||||
margin: 0;
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 16px rgba(91, 98, 206, 0.3);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 24px rgba(91, 98, 206, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-content {
|
.text {
|
||||||
padding: 40px 30px;
|
margin-top: 24px;
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--btn-bg);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.forget {
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色主题适配
|
// 移动端适配
|
||||||
:deep(.van-theme-dark) {
|
|
||||||
.login-page {
|
|
||||||
.login-card {
|
|
||||||
background: var(--el-bg-color-overlay);
|
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动端响应式设计
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.login-page {
|
.login-page {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: var(--van-background);
|
}
|
||||||
|
|
||||||
.login-container {
|
.back-home-btn {
|
||||||
max-width: 100%;
|
top: 16px;
|
||||||
|
left: 16px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
.login-card {
|
.iconfont {
|
||||||
border-radius: 20px;
|
font-size: 18px;
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.login-header {
|
.login-card {
|
||||||
padding: 30px 20px;
|
padding: 32px 24px;
|
||||||
background: linear-gradient(135deg, var(--van-primary-color), #8b5cf6);
|
border-radius: 16px;
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
.login-title {
|
.login-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-subtitle {
|
.login-subtitle {
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-content {
|
||||||
|
:deep(.login-dialog) {
|
||||||
|
.form {
|
||||||
|
.block {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
|
||||||
|
.el-input {
|
||||||
|
.el-input__wrapper {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-content {
|
.btn-row {
|
||||||
padding: 30px 20px;
|
margin-top: 28px;
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
height: 46px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 小屏幕移动端优化
|
// 小屏幕手机适配
|
||||||
@media (max-width: 375px) {
|
@media (max-width: 480px) {
|
||||||
.login-page {
|
.login-card {
|
||||||
padding: 12px;
|
padding: 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.login-container {
|
.login-title {
|
||||||
.login-card {
|
font-size: 22px;
|
||||||
.login-header {
|
}
|
||||||
padding: 24px 16px;
|
|
||||||
|
|
||||||
.login-title {
|
.login-subtitle {
|
||||||
font-size: 22px;
|
font-size: 14px;
|
||||||
}
|
|
||||||
|
|
||||||
.login-subtitle {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-content {
|
|
||||||
padding: 24px 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -58,158 +58,398 @@ onMounted(() => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.register-page {
|
.register-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: var(--theme-bg-all);
|
||||||
|
background-image: var(--panel-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.back-home-btn {
|
.back-home-btn {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--line-box);
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--theme-text-color-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
background: var(--hover-deep-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--line-box);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 24px;
|
top: 0;
|
||||||
left: 24px;
|
left: 0;
|
||||||
z-index: 10;
|
right: 0;
|
||||||
font-size: 22px;
|
height: 4px;
|
||||||
color: #fff;
|
background: var(--btnColor);
|
||||||
background: rgba(0, 0, 0, 0.15);
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
.back-home-btn:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.back-home-btn {
|
|
||||||
top: 12px;
|
|
||||||
left: 12px;
|
|
||||||
font-size: 20px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.van-theme-dark) .back-home-btn {
|
|
||||||
color: #fff;
|
|
||||||
background: rgba(0, 0, 0, 0.35);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.register-container {
|
.register-header {
|
||||||
width: 100%;
|
text-align: center;
|
||||||
max-width: 480px;
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
.register-card {
|
.register-title {
|
||||||
background: var(--el-bg-color);
|
font-size: 28px;
|
||||||
border-radius: 16px;
|
font-weight: 600;
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
color: var(--theme-text-color-primary);
|
||||||
overflow: hidden;
|
margin: 0 0 8px 0;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
.register-header {
|
.register-subtitle {
|
||||||
background: linear-gradient(135deg, #10b981, #059669);
|
font-size: 16px;
|
||||||
color: white;
|
color: var(--theme-text-color-secondary);
|
||||||
padding: 40px 30px;
|
margin: 0;
|
||||||
text-align: center;
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.register-title {
|
.register-content {
|
||||||
font-size: 28px;
|
:deep(.login-dialog) {
|
||||||
font-weight: 600;
|
.form {
|
||||||
margin: 0 0 8px 0;
|
.block {
|
||||||
}
|
margin-bottom: 20px;
|
||||||
|
|
||||||
.register-subtitle {
|
.el-input {
|
||||||
font-size: 16px;
|
.el-input__wrapper {
|
||||||
opacity: 0.9;
|
background: var(--el-fill-color-blank);
|
||||||
margin: 0;
|
border: 1px solid var(--line-box);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.is-focus {
|
||||||
|
border-color: var(--border-active);
|
||||||
|
box-shadow: 0 0 0 3px rgba(91, 98, 206, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
color: var(--theme-text-color-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__prefix,
|
||||||
|
.el-input__suffix {
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.register-content {
|
.btn-row {
|
||||||
padding: 40px 30px;
|
margin-top: 32px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
&[type="primary"],
|
||||||
|
&.register-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--btnColor);
|
||||||
|
border: none;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 16px rgba(91, 98, 206, 0.3);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 24px rgba(91, 98, 206, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-top: 24px;
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--btn-bg);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.forget {
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码输入框样式
|
||||||
|
.verify-code {
|
||||||
|
.el-row {
|
||||||
|
.el-col:first-child {
|
||||||
|
.el-input__wrapper {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-col:last-child {
|
||||||
|
.el-button {
|
||||||
|
height: 40px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-left: none;
|
||||||
|
background: var(--btn-bg);
|
||||||
|
color: var(--theme-text-color-primary);
|
||||||
|
border: 1px solid var(--line-box);
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--hover-deep-color);
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 邀请码输入框样式
|
||||||
|
.invite-code {
|
||||||
|
.el-input__wrapper {
|
||||||
|
background: var(--quote-bg-color);
|
||||||
|
border-color: var(--border-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 协议条款样式
|
||||||
|
.agreement {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--theme-text-color-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
.el-checkbox {
|
||||||
|
margin-right: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
:deep(.el-checkbox__inner) {
|
||||||
|
border-color: var(--line-box);
|
||||||
|
background: var(--el-fill-color-blank);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--border-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-checkbox__input.is-checked) {
|
||||||
|
.el-checkbox__inner {
|
||||||
|
background: var(--btnColor);
|
||||||
|
border-color: var(--border-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--text-color-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深色主题适配
|
// 移动端适配
|
||||||
:deep(.van-theme-dark) {
|
|
||||||
.register-page {
|
|
||||||
.register-card {
|
|
||||||
background: var(--el-bg-color-overlay);
|
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动端响应式设计
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.register-page {
|
.register-page {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: var(--van-background);
|
}
|
||||||
|
|
||||||
.back-home-btn {
|
.back-home-btn {
|
||||||
top: 16px;
|
top: 16px;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
font-size: 20px;
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.register-container {
|
.register-card {
|
||||||
max-width: 100%;
|
padding: 32px 24px;
|
||||||
|
border-radius: 16px;
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
.register-card {
|
.register-title {
|
||||||
border-radius: 20px;
|
font-size: 24px;
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
}
|
||||||
|
|
||||||
.register-header {
|
.register-subtitle {
|
||||||
padding: 30px 20px;
|
font-size: 15px;
|
||||||
background: linear-gradient(135deg, #10b981, #059669);
|
}
|
||||||
|
|
||||||
.register-title {
|
.register-content {
|
||||||
font-size: 24px;
|
:deep(.login-dialog) {
|
||||||
}
|
.form {
|
||||||
|
.block {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
|
||||||
.register-subtitle {
|
.el-input {
|
||||||
font-size: 14px;
|
.el-input__wrapper {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.register-content {
|
.btn-row {
|
||||||
padding: 30px 20px;
|
margin-top: 28px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
&[type="primary"],
|
||||||
|
&.register-btn {
|
||||||
|
height: 46px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-code {
|
||||||
|
.el-row {
|
||||||
|
.el-col:last-child {
|
||||||
|
.el-button {
|
||||||
|
height: 38px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement {
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 小屏幕移动端优化
|
// 小屏幕手机适配
|
||||||
@media (max-width: 375px) {
|
@media (max-width: 480px) {
|
||||||
.register-page {
|
.register-card {
|
||||||
padding: 12px;
|
padding: 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.back-home-btn {
|
.register-title {
|
||||||
top: 12px;
|
font-size: 22px;
|
||||||
left: 12px;
|
}
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-container {
|
.register-subtitle {
|
||||||
.register-card {
|
font-size: 14px;
|
||||||
.register-header {
|
}
|
||||||
padding: 24px 16px;
|
|
||||||
|
|
||||||
.register-title {
|
.register-content {
|
||||||
font-size: 22px;
|
:deep(.login-dialog) {
|
||||||
}
|
.form {
|
||||||
|
.verify-code {
|
||||||
|
.el-row {
|
||||||
|
.el-col:first-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.el-col:last-child {
|
||||||
|
padding-left: 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
.register-subtitle {
|
.el-button {
|
||||||
font-size: 13px;
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
border-left: 1px solid var(--line-box);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.register-content {
|
.agreement {
|
||||||
padding: 24px 16px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<!-- 图生图参数 -->
|
<!-- 图生图参数 -->
|
||||||
<div class="bg-white rounded-xl p-4 shadow-sm mb-3" v-if="jimengStore.useImageInput">
|
<div class="bg-white rounded-xl p-4 shadow-sm mb-3" v-if="jimengStore.useImageInput">
|
||||||
<ImageUpload
|
<ImageUpload
|
||||||
v-model="jimengStore.imageToImageParams.image_input[0]"
|
v-model="jimengStore.imageToImageParams.image_input"
|
||||||
:max-count="1"
|
:max-count="1"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
/>
|
/>
|
||||||
@@ -220,16 +220,13 @@
|
|||||||
<div class="bg-white rounded-xl p-4 shadow-sm mb-3">
|
<div class="bg-white rounded-xl p-4 shadow-sm mb-3">
|
||||||
<div class="flex justify-between items-center w-full">
|
<div class="flex justify-between items-center w-full">
|
||||||
<label class="text-gray-700 font-semibold">使用图片辅助生成:</label>
|
<label class="text-gray-700 font-semibold">使用图片辅助生成:</label>
|
||||||
<el-switch v-model="jimengStore.textToVideoParams.use_image_input" size="default" />
|
<el-switch v-model="jimengStore.useImageInput" size="default" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="bg-white rounded-xl p-4 shadow-sm mb-3" v-if="jimengStore.useImageInput">
|
||||||
class="bg-white rounded-xl p-4 shadow-sm mb-3"
|
|
||||||
v-if="jimengStore.textToVideoParams.use_image_input"
|
|
||||||
>
|
|
||||||
<ImageUpload
|
<ImageUpload
|
||||||
v-model="jimengStore.textToVideoParams.image_input"
|
v-model="jimengStore.imageToVideoParams.image_input"
|
||||||
:max-count="2"
|
:max-count="2"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
/>
|
/>
|
||||||
@@ -268,7 +265,7 @@
|
|||||||
<!-- 作品列表 -->
|
<!-- 作品列表 -->
|
||||||
<div class="jimeng-create__works">
|
<div class="jimeng-create__works">
|
||||||
<h2 class="jimeng-create__works-title">我的作品</h2>
|
<h2 class="jimeng-create__works-title">我的作品</h2>
|
||||||
<div class="jimeng-create__works-list space-y-4">
|
<div class="jimeng-create__works-list space-y-4" v-if="jimengStore.currentList.length > 0">
|
||||||
<div
|
<div
|
||||||
v-for="item in jimengStore.currentList"
|
v-for="item in jimengStore.currentList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -290,6 +287,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-image>
|
</el-image>
|
||||||
|
<div
|
||||||
|
v-else-if="item.video_url"
|
||||||
|
class="jimeng-create__works-item-thumb-placeholder relative"
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
:src="item.video_url"
|
||||||
|
preload="auto"
|
||||||
|
loop="loop"
|
||||||
|
muted="muted"
|
||||||
|
class="w-full h-full object-cover"
|
||||||
|
>
|
||||||
|
您的浏览器不支持视频播放
|
||||||
|
</video>
|
||||||
|
<div
|
||||||
|
class="video-mask absolute top-0 left-0 w-full h-full flex justify-center items-center"
|
||||||
|
@click="jimengStore.playMedia(item)"
|
||||||
|
>
|
||||||
|
<div class="play-btn">
|
||||||
|
<img src="/images/play.svg" alt="播放" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-else class="jimeng-create__works-item-thumb-placeholder">
|
<div v-else class="jimeng-create__works-item-thumb-placeholder">
|
||||||
<i
|
<i
|
||||||
:class="
|
:class="
|
||||||
@@ -297,18 +316,6 @@
|
|||||||
"
|
"
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<!-- 播放/查看按钮 -->
|
|
||||||
<button
|
|
||||||
v-if="item.status === 'completed'"
|
|
||||||
@click="jimengStore.playMedia(item)"
|
|
||||||
class="jimeng-create__works-item-thumb-overlay"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
:class="
|
|
||||||
item.type.includes('video') ? 'iconfont icon-play' : 'iconfont icon-eye'
|
|
||||||
"
|
|
||||||
></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 失败状态 -->
|
<!-- 失败状态 -->
|
||||||
<div
|
<div
|
||||||
@@ -371,50 +378,44 @@
|
|||||||
|
|
||||||
<!-- 快捷操作按钮 -->
|
<!-- 快捷操作按钮 -->
|
||||||
<div class="jimeng-create__works-item-quick-actions">
|
<div class="jimeng-create__works-item-quick-actions">
|
||||||
<!-- 复制提示词 -->
|
<span v-if="item.status === 'success'" class="flex">
|
||||||
<button
|
<!-- 复制提示词 -->
|
||||||
v-if="item.prompt"
|
|
||||||
@click="jimengStore.copyPrompt(item.prompt)"
|
|
||||||
class="jimeng-create__works-item-quick-action-btn"
|
|
||||||
title="复制提示词"
|
|
||||||
>
|
|
||||||
<i class="iconfont icon-copy"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<span v-if="item.status === 'success'">
|
|
||||||
<!-- 画同款 -->
|
|
||||||
<button
|
<button
|
||||||
@click="jimengStore.drawSame(item)"
|
v-if="item.prompt"
|
||||||
|
@click="jimengStore.copyPrompt(item.prompt)"
|
||||||
class="jimeng-create__works-item-quick-action-btn"
|
class="jimeng-create__works-item-quick-action-btn"
|
||||||
title="画同款"
|
title="复制提示词"
|
||||||
>
|
>
|
||||||
<i class="iconfont icon-image-list"></i>
|
<i class="iconfont icon-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 下载 -->
|
<!-- 下载 -->
|
||||||
<button
|
<button
|
||||||
v-if="item.status === 'completed' && (item.img_url || item.video_url)"
|
v-if="item.status === 'success' && (item.img_url || item.video_url)"
|
||||||
@click="jimengStore.downloadFile(item)"
|
@click="jimengStore.downloadFile(item)"
|
||||||
:disabled="item.downloading"
|
:disabled="item.downloading"
|
||||||
class="jimeng-create__works-item-quick-action-btn"
|
class="p-2 text-blue-500"
|
||||||
title="下载"
|
|
||||||
>
|
>
|
||||||
<i v-if="item.downloading" class="iconfont icon-loading animate-spin"></i>
|
<i v-if="item.downloading" class="iconfont icon-loading animate-spin"></i>
|
||||||
<i v-else class="iconfont icon-download"></i></button
|
<i v-else class="iconfont icon-download"></i>
|
||||||
></span>
|
<span class="ml-1">下载</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
<!-- 重试 -->
|
<!-- 重试 -->
|
||||||
<button
|
<button
|
||||||
v-if="item.status === 'failed'"
|
v-if="item.status === 'failed'"
|
||||||
@click="jimengStore.retryTask(item.id)"
|
@click="jimengStore.retryTask(item.id)"
|
||||||
class="jimeng-create__works-item-quick-action-btn"
|
class="p-2 text-green-500"
|
||||||
title="重试"
|
|
||||||
>
|
>
|
||||||
<i class="iconfont icon-refresh"></i>
|
<i class="iconfont icon-refresh"></i>
|
||||||
|
<span class="ml-1">重试</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 删除 -->
|
<!-- 删除 -->
|
||||||
<button @click="jimengStore.removeJob(item)" class="p-2">
|
<button @click="jimengStore.removeJob(item)" class="p-2 text-red-500">
|
||||||
<i class="iconfont icon-remove"></i> 删除
|
<i class="iconfont icon-remove"></i>
|
||||||
|
<span class="ml-1">删除</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -424,7 +425,9 @@
|
|||||||
class="jimeng-create__works-item-error"
|
class="jimeng-create__works-item-error"
|
||||||
>
|
>
|
||||||
<div class="jimeng-create__works-item-error-content">
|
<div class="jimeng-create__works-item-error-content">
|
||||||
<span class="jimeng-create__works-item-error-text">{{ item.err_msg }}</span>
|
<span class="jimeng-create__works-item-error-text line-clamp-3">{{
|
||||||
|
item.err_msg
|
||||||
|
}}</span>
|
||||||
<button
|
<button
|
||||||
@click="jimengStore.copyErrorMsg(item.err_msg)"
|
@click="jimengStore.copyErrorMsg(item.err_msg)"
|
||||||
class="jimeng-create__works-item-error-copy-btn"
|
class="jimeng-create__works-item-error-copy-btn"
|
||||||
@@ -449,6 +452,10 @@
|
|||||||
没有更多了
|
没有更多了
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4" v-else>
|
||||||
|
<van-empty description="暂无数据" image-size="120" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 媒体预览弹窗 -->
|
<!-- 媒体预览弹窗 -->
|
||||||
@@ -461,21 +468,15 @@
|
|||||||
<div class="jimeng-create__media-dialog-header">
|
<div class="jimeng-create__media-dialog-header">
|
||||||
<h3>媒体预览</h3>
|
<h3>媒体预览</h3>
|
||||||
<button @click="jimengStore.closeMediaDialog">
|
<button @click="jimengStore.closeMediaDialog">
|
||||||
<i class="iconfont icon-close"></i>
|
<i class="iconfont icon-error"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="jimeng-create__media-dialog-body">
|
<div class="jimeng-create__media-dialog-body">
|
||||||
<img
|
|
||||||
v-if="jimengStore.currentMediaUrl && !jimengStore.currentMediaUrl.includes('video')"
|
|
||||||
:src="jimengStore.currentMediaUrl"
|
|
||||||
class="w-full max-h-[60vh] object-contain rounded-lg"
|
|
||||||
/>
|
|
||||||
<video
|
<video
|
||||||
v-else-if="jimengStore.currentMediaUrl"
|
|
||||||
:src="jimengStore.currentMediaUrl"
|
:src="jimengStore.currentMediaUrl"
|
||||||
controls
|
controls
|
||||||
autoplay
|
autoplay
|
||||||
class="w-full max-h-[60vh] rounded-lg"
|
class="w-full max-h-[60vh] rounded-lg object-cover"
|
||||||
>
|
>
|
||||||
您的浏览器不支持视频播放
|
您的浏览器不支持视频播放
|
||||||
</video>
|
</video>
|
||||||
@@ -535,5 +536,5 @@ const goBack = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '@/assets/css/mobile/jimeng.scss';
|
@use '@/assets/css/mobile/jimeng.scss';
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -16,9 +16,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-prompt" v-else>
|
<div class="login-prompt" v-else>
|
||||||
<el-button type="primary" size="large" @click="router.push('/mobile/login')"
|
<button
|
||||||
>立即登录</el-button
|
class="py-3 px-5 bg-gradient-to-r from-green-400 to-blue-400 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-green-500 hover:to-blue-500 transition-all duration-200 flex items-center justify-center space-x-2"
|
||||||
|
@click="router.push('/login')"
|
||||||
>
|
>
|
||||||
|
立即登录
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -278,7 +281,6 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
isLogin.value = false
|
isLogin.value = false
|
||||||
showLoginDialog(router)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,7 @@
|
|||||||
<!-- 作品列表 -->
|
<!-- 作品列表 -->
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">我的作品</h2>
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">我的作品</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4" v-if="suno.list.length > 0">
|
||||||
<div v-for="item in suno.list" :key="item.id" class="bg-white rounded-xl p-4 shadow-sm">
|
<div v-for="item in suno.list" :key="item.id" class="bg-white rounded-xl p-4 shadow-sm">
|
||||||
<div class="flex space-x-4">
|
<div class="flex space-x-4">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
@@ -447,6 +447,10 @@
|
|||||||
没有更多了
|
没有更多了
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4" v-else>
|
||||||
|
<van-empty description="暂无数据" image-size="120" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 音乐播放器 -->
|
<!-- 音乐播放器 -->
|
||||||
@@ -496,6 +500,7 @@ import CustomSelect from '@/components/mobile/CustomSelect.vue'
|
|||||||
import { useSunoStore } from '@/store/mobile/suno'
|
import { useSunoStore } from '@/store/mobile/suno'
|
||||||
import { onMounted, onUnmounted } from 'vue'
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { checkSession } from '@/store/cache'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const suno = useSunoStore()
|
const suno = useSunoStore()
|
||||||
@@ -523,13 +528,19 @@ const handleScroll = () => {
|
|||||||
|
|
||||||
let tastPullHandler = null
|
let tastPullHandler = null
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
suno.fetchData(1)
|
checkSession()
|
||||||
tastPullHandler = setInterval(() => {
|
.then(() => {
|
||||||
if (suno.taskPulling) {
|
suno.fetchData(1)
|
||||||
suno.refreshFirstPage()
|
tastPullHandler = setInterval(() => {
|
||||||
}
|
if (suno.taskPulling) {
|
||||||
}, 5000)
|
suno.refreshFirstPage()
|
||||||
window.addEventListener('scroll', handleScroll)
|
}
|
||||||
|
}, 5000)
|
||||||
|
window.addEventListener('scroll', handleScroll)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.warn('用户未登录')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (tastPullHandler) clearInterval(tastPullHandler)
|
if (tastPullHandler) clearInterval(tastPullHandler)
|
||||||
@@ -538,128 +549,5 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 自定义动画 */
|
@use '@/assets/css/mobile/suno.scss';
|
||||||
@keyframes fade-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-out {
|
|
||||||
from {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slide-up {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes scale-up {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-fade-in {
|
|
||||||
animation: fade-in 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-fade-out {
|
|
||||||
animation: fade-out 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-slide-up {
|
|
||||||
animation: slide-up 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-scale-up {
|
|
||||||
animation: scale-up 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 文本截断 */
|
|
||||||
.line-clamp-2 {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动监听自动加载更多 */
|
|
||||||
.scroll-container {
|
|
||||||
height: 100vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 深色模式适配 */
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.bg-gray-50 {
|
|
||||||
background-color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-white {
|
|
||||||
background-color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-900 {
|
|
||||||
color: #f9fafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-700 {
|
|
||||||
color: #d1d5db;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-600 {
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-500 {
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gray-200 {
|
|
||||||
border-color: #4b5563;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-100:hover {
|
|
||||||
background-color: #4b5563;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* el-upload 组件样式定制 */
|
|
||||||
.upload-area {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
:deep(.el-upload) {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-button) {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -438,7 +438,7 @@
|
|||||||
<!-- 作品列表 -->
|
<!-- 作品列表 -->
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">我的作品</h2>
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">我的作品</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4" v-if="video.currentList.length > 0">
|
||||||
<div
|
<div
|
||||||
v-for="item in video.currentList"
|
v-for="item in video.currentList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -573,6 +573,10 @@
|
|||||||
没有更多了
|
没有更多了
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4" v-else>
|
||||||
|
<van-empty description="暂无数据" image-size="120" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 视频预览弹窗 -->
|
<!-- 视频预览弹窗 -->
|
||||||
@@ -611,6 +615,7 @@ import { useVideoStore } from '@/store/mobile/video'
|
|||||||
import { showConfirmDialog } from 'vant'
|
import { showConfirmDialog } from 'vant'
|
||||||
import { onMounted, onUnmounted } from 'vue'
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { checkSession } from '@/store/cache'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const video = useVideoStore()
|
const video = useVideoStore()
|
||||||
@@ -623,13 +628,17 @@ const goBack = () => {
|
|||||||
// 定时轮询等副作用
|
// 定时轮询等副作用
|
||||||
let tastPullHandler = null
|
let tastPullHandler = null
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
video.fetchData(1)
|
checkSession()
|
||||||
video.fetchUserPower()
|
.then(() => {
|
||||||
tastPullHandler = setInterval(() => {
|
|
||||||
if (video.taskPulling) {
|
|
||||||
video.fetchData(1)
|
video.fetchData(1)
|
||||||
}
|
video.fetchUserPower()
|
||||||
}, 5000)
|
tastPullHandler = setInterval(() => {
|
||||||
|
if (video.taskPulling) {
|
||||||
|
video.fetchData(1)
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (tastPullHandler) clearInterval(tastPullHandler)
|
if (tastPullHandler) clearInterval(tastPullHandler)
|
||||||
|
|||||||
Reference in New Issue
Block a user