mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-04-22 19:14:29 +08:00
840 lines
28 KiB
Vue
840 lines
28 KiB
Vue
<template>
|
||
<div class="min-h-screen bg-gray-50">
|
||
<!-- 页面头部 -->
|
||
<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="flex-1 text-center text-lg text-gray-900">音乐创作</h1>
|
||
<div class="w-8"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 创作表单 -->
|
||
<div class="p-4 space-y-6">
|
||
<!-- 模式切换 -->
|
||
<div class="bg-white rounded-xl p-4 shadow-sm">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<span class="text-gray-900 font-medium">创作模式</span>
|
||
<van-switch v-model="suno.custom" @change="onModeChange" size="24px" />
|
||
</div>
|
||
<p class="text-sm text-gray-500">
|
||
{{
|
||
suno.custom ? '自定义模式:可设置歌词、风格等详细参数' : '简单模式:通过描述快速生成'
|
||
}}
|
||
</p>
|
||
</div>
|
||
|
||
<!-- 模型选择 -->
|
||
<div class="bg-white rounded-xl p-4 shadow-sm">
|
||
<label class="block text-gray-700 font-medium mb-3">模型版本:</label>
|
||
<CustomSelect
|
||
v-model="suno.data.model"
|
||
:options="suno.models"
|
||
title="选择模型"
|
||
@change="suno.onModelSelect"
|
||
>
|
||
<template #option="{ option, selected }">
|
||
<div class="flex items-center w-full">
|
||
<span class="font-bold text-blue-600 mr-2">{{ option.label }}</span>
|
||
<span class="text-xs text-gray-400">({{ option.value }})</span>
|
||
<span v-if="selected" class="ml-auto text-green-500"
|
||
><i class="iconfont icon-success"></i
|
||
></span>
|
||
</div>
|
||
</template>
|
||
</CustomSelect>
|
||
</div>
|
||
|
||
<!-- 纯音乐开关 -->
|
||
<div class="bg-white rounded-xl p-4 shadow-sm">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<span class="text-gray-900 font-medium">纯音乐</span>
|
||
<p class="text-sm text-gray-500 mt-1">生成不包含人声的音乐</p>
|
||
</div>
|
||
<van-switch v-model="suno.data.instrumental" size="24px" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 自定义模式内容 -->
|
||
<div v-if="suno.custom" class="space-y-6">
|
||
<!-- 歌词输入 -->
|
||
<div v-if="!suno.data.instrumental" class="bg-white rounded-xl p-4 shadow-sm">
|
||
<label class="block text-gray-700 font-medium mb-3">歌词</label>
|
||
<textarea
|
||
v-model="suno.data.lyrics"
|
||
placeholder="请在这里输入你自己写的歌词..."
|
||
class="w-full px-4 py-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||
rows="6"
|
||
maxlength="2000"
|
||
/>
|
||
<div class="flex items-center justify-between mt-3">
|
||
<span class="text-sm text-gray-500">{{ suno.data.lyrics.length }}/2000</span>
|
||
<button
|
||
@click="suno.createLyric"
|
||
:disabled="suno.isGenerating || !suno.data.lyrics"
|
||
class="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium disabled:bg-gray-300 disabled:cursor-not-allowed hover:bg-blue-700 transition-colors flex items-center space-x-2"
|
||
>
|
||
<i v-if="suno.isGenerating" class="iconfont icon-loading animate-spin"></i>
|
||
<span>{{ suno.isGenerating ? '生成中...' : '生成歌词' }}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 音乐风格 -->
|
||
<div class="bg-white rounded-xl p-4 shadow-sm">
|
||
<label class="block text-gray-700 font-medium mb-3">音乐风格</label>
|
||
<textarea
|
||
v-model="suno.data.tags"
|
||
placeholder="请输入音乐风格,多个风格之间用英文逗号隔开..."
|
||
class="w-full px-4 py-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||
rows="3"
|
||
maxlength="120"
|
||
/>
|
||
<div class="flex justify-between items-center mt-2 mb-3">
|
||
<span class="text-sm text-gray-500">{{ suno.data.tags.length }}/120</span>
|
||
</div>
|
||
<!-- 风格标签选择 -->
|
||
<div class="flex flex-wrap gap-2">
|
||
<button
|
||
v-for="tag in suno.tags"
|
||
:key="tag.value"
|
||
@click="suno.selectTag(tag)"
|
||
class="px-3 py-1 text-sm border border-blue-200 text-blue-600 rounded-full hover:bg-blue-50 transition-colors"
|
||
>
|
||
{{ tag.label }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 歌曲名称 -->
|
||
<div class="bg-white rounded-xl p-4 shadow-sm">
|
||
<label class="block text-gray-700 font-medium mb-3">歌曲名称</label>
|
||
<input
|
||
v-model="suno.data.title"
|
||
placeholder="请输入歌曲名称..."
|
||
class="w-full px-4 py-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
maxlength="100"
|
||
/>
|
||
<div class="text-right mt-2">
|
||
<span class="text-sm text-gray-500">{{ suno.data.title.length }}/100</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 简单模式内容 -->
|
||
<div v-else class="bg-white rounded-xl p-4 shadow-sm">
|
||
<label class="block text-gray-700 font-medium mb-3">歌曲描述</label>
|
||
<textarea
|
||
v-model="suno.data.prompt"
|
||
placeholder="例如:一首关于爱情的摇滚歌曲..."
|
||
class="w-full px-4 py-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||
rows="6"
|
||
maxlength="1000"
|
||
/>
|
||
<div class="text-right mt-2">
|
||
<span class="text-sm text-gray-500">{{ suno.data.prompt.length }}/1000</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 续写歌曲 -->
|
||
<div
|
||
v-if="suno.refSong"
|
||
class="bg-white rounded-xl p-4 shadow-sm border-l-4 border-orange-400"
|
||
>
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h3 class="text-gray-900 font-medium flex items-center">
|
||
<i class="iconfont icon-link mr-2 text-orange-500"></i>
|
||
续写歌曲
|
||
</h3>
|
||
<button
|
||
@click="suno.removeRefSong"
|
||
class="px-3 py-1 text-sm bg-red-100 text-red-600 rounded-lg hover:bg-red-200 transition-colors"
|
||
>
|
||
移除
|
||
</button>
|
||
</div>
|
||
<div class="space-y-3">
|
||
<div class="flex justify-between">
|
||
<span class="text-gray-600">歌曲名称:</span>
|
||
<span class="text-gray-900 font-medium">{{ suno.refSong.title }}</span>
|
||
</div>
|
||
<div class="flex justify-between">
|
||
<span class="text-gray-600">歌曲时长:</span>
|
||
<span class="text-gray-900 font-medium">{{ suno.refSong.duration }}秒</span>
|
||
</div>
|
||
<div>
|
||
<label class="block text-gray-700 font-medium mb-2">续写开始时间(秒)</label>
|
||
<input
|
||
v-model="suno.refSong.extend_secs"
|
||
type="number"
|
||
:min="0"
|
||
:max="suno.refSong.duration"
|
||
placeholder="从第几秒开始续写"
|
||
class="w-full px-4 py-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
/>
|
||
<p class="text-sm text-gray-500 mt-1">
|
||
建议从 {{ Math.floor(suno.refSong.duration * 0.8) }}-{{ suno.refSong.duration }}
|
||
秒开始续写
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 生成按钮 -->
|
||
<div class="sticky bottom-4 bg-white rounded-xl p-4 shadow-lg">
|
||
<button
|
||
@click="suno.create"
|
||
:disabled="suno.loading"
|
||
type="button"
|
||
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>
|
||
|
||
<!-- 上传音乐 -->
|
||
<div class="bg-white rounded-xl p-4 shadow-sm">
|
||
<label class="block text-gray-700 font-medium mb-3">上传音乐文件</label>
|
||
<el-upload
|
||
ref="suno.uploadRef"
|
||
:auto-upload="false"
|
||
:show-file-list="false"
|
||
:on-change="suno.handleFileChange"
|
||
:before-upload="suno.beforeUpload"
|
||
accept=".wav,.mp3"
|
||
class="upload-area w-full"
|
||
>
|
||
<template #trigger>
|
||
<button
|
||
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>
|
||
</button>
|
||
</template>
|
||
</el-upload>
|
||
<div class="mt-3 p-3 bg-gray-50 rounded-lg text-sm text-gray-500">
|
||
<div class="upload-tips">
|
||
<p>• 上传你自己的音乐文件,然后进行二次创作</p>
|
||
<p>• 请上传6-60秒的原始音频</p>
|
||
<p>• 检测到人声的音频将仅设为私人音频</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 作品列表 -->
|
||
<div class="p-4">
|
||
<h2 class="text-lg font-semibold text-gray-900 mb-4">我的作品</h2>
|
||
<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 class="flex space-x-4">
|
||
<div class="flex-shrink-0">
|
||
<div class="relative w-16 h-16 rounded-lg overflow-hidden bg-gray-100">
|
||
<el-image
|
||
:src="item.cover_url"
|
||
fit="cover"
|
||
class="w-full h-full"
|
||
:preview-disabled="true"
|
||
>
|
||
<template #error>
|
||
<div class="w-full h-full flex items-center justify-center bg-gray-100">
|
||
<i class="iconfont icon-mp3 text-gray-400 text-xl"></i>
|
||
</div>
|
||
</template>
|
||
</el-image>
|
||
<!-- 音乐播放按钮 -->
|
||
<button
|
||
v-if="item.progress === 100"
|
||
@click="suno.play(item)"
|
||
class="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50 opacity-0 hover:opacity-100 transition-opacity"
|
||
>
|
||
<i class="iconfont icon-play text-white text-xl"></i>
|
||
</button>
|
||
<!-- 进度动画 -->
|
||
<div
|
||
v-if="item.progress < 100 && item.progress !== 101"
|
||
class="absolute inset-0 flex items-center justify-center bg-blue-500 bg-opacity-20"
|
||
>
|
||
<i class="iconfont icon-loading animate-spin text-blue-500 text-xl"></i>
|
||
</div>
|
||
<!-- 失败状态 -->
|
||
<div
|
||
v-if="item.progress === 101"
|
||
class="absolute inset-0 flex items-center justify-center bg-red-500 bg-opacity-20"
|
||
>
|
||
<i class="iconfont icon-warning text-red-500 text-xl"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<div class="flex items-start justify-between">
|
||
<div class="flex-1">
|
||
<h3 class="text-gray-900 font-medium truncate">
|
||
{{ item.title || '未命名歌曲' }}
|
||
</h3>
|
||
<p class="text-gray-500 text-sm mt-1 line-clamp-2">
|
||
{{ item.tags || item.prompt }}
|
||
</p>
|
||
</div>
|
||
<!-- 任务状态 -->
|
||
<div v-if="item.progress < 100" class="flex items-center space-x-2 text-sm">
|
||
<div
|
||
v-if="item.progress === 101"
|
||
class="text-red-600 flex items-center space-x-1"
|
||
>
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
stroke-width="2"
|
||
d="M12 9v2m0 4h.01"
|
||
/>
|
||
</svg>
|
||
<span>失败</span>
|
||
</div>
|
||
<div v-else class="text-blue-600 flex items-center space-x-1">
|
||
<div
|
||
class="w-3 h-3 border border-blue-600 border-t-transparent rounded-full animate-spin"
|
||
></div>
|
||
<span>生成中</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 标签 -->
|
||
<div class="flex items-center space-x-2 mt-2">
|
||
<span
|
||
v-if="item.major_model_version"
|
||
class="px-2 py-1 text-xs bg-blue-100 text-blue-600 rounded-full"
|
||
>
|
||
{{ item.major_model_version }}
|
||
</span>
|
||
<span
|
||
v-if="item.type === 4"
|
||
class="px-2 py-1 text-xs bg-green-100 text-green-600 rounded-full"
|
||
>
|
||
<i class="iconfont icon-upload mr-1"></i>用户上传
|
||
</span>
|
||
<span
|
||
v-if="item.type === 3"
|
||
class="px-2 py-1 text-xs bg-yellow-100 text-yellow-600 rounded-full"
|
||
>
|
||
<i class="iconfont icon-mp3 mr-1"></i>完整歌曲
|
||
</span>
|
||
<span
|
||
v-if="item.ref_song"
|
||
class="px-2 py-1 text-xs bg-purple-100 text-purple-600 rounded-full"
|
||
>
|
||
<i class="iconfont icon-link mr-1"></i>续写
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="flex items-center justify-between mt-4">
|
||
<div class="flex space-x-2">
|
||
<button
|
||
v-if="item.progress === 100"
|
||
@click="suno.play(item)"
|
||
class="px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors flex items-center space-x-1"
|
||
>
|
||
<i class="iconfont icon-play !text-xs"></i>
|
||
<span>播放</span>
|
||
</button>
|
||
<button
|
||
v-if="item.progress === 100"
|
||
@click="suno.download(item)"
|
||
:disabled="item.downloading"
|
||
class="px-3 py-1.5 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition-colors disabled:bg-gray-400 flex items-center space-x-1"
|
||
>
|
||
<svg
|
||
v-if="item.downloading"
|
||
class="w-3 h-3 animate-spin"
|
||
fill="none"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<circle
|
||
class="opacity-25"
|
||
cx="12"
|
||
cy="12"
|
||
r="10"
|
||
stroke="currentColor"
|
||
stroke-width="4"
|
||
/>
|
||
<path
|
||
class="opacity-75"
|
||
fill="currentColor"
|
||
d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||
/>
|
||
</svg>
|
||
<i v-else class="iconfont icon-download !text-xs"></i>
|
||
<span>{{ item.downloading ? '下载中...' : '下载' }}</span>
|
||
</button>
|
||
<button
|
||
v-if="item.progress === 100"
|
||
@click="suno.extend(item)"
|
||
class="px-3 py-1.5 bg-purple-600 text-white text-sm rounded-lg hover:bg-purple-700 transition-colors flex items-center justify-center min-w-[60px]"
|
||
>
|
||
<i class="iconfont icon-link !text-xs mr-1"></i>
|
||
<span>续写</span>
|
||
</button>
|
||
</div>
|
||
<button
|
||
@click="suno.removeJob(item)"
|
||
class="px-3 py-1.5 bg-red-100 text-red-600 text-sm rounded-lg hover:bg-red-200 transition-colors flex items-center space-x-1"
|
||
>
|
||
<i class="iconfont icon-remove !text-xs"></i>
|
||
<span>删除</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 进度条 -->
|
||
<div v-if="item.progress < 100 && item.progress !== 101" class="mt-4">
|
||
<div class="flex justify-between text-sm text-gray-600 mb-1">
|
||
<span>生成进度</span>
|
||
<span>{{ item.progress }}%</span>
|
||
</div>
|
||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||
<div
|
||
class="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
||
:style="{ width: item.progress + '%' }"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 错误信息 -->
|
||
<div
|
||
v-if="item.progress === 101"
|
||
class="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg"
|
||
>
|
||
<div class="flex items-start space-x-2">
|
||
<div>
|
||
<p class="text-red-600 text-sm">{{ item.err_msg || '未知错误' }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 加载更多 -->
|
||
<div v-if="suno.listLoading" class="flex justify-center py-4">
|
||
<svg class="w-6 h-6 animate-spin text-gray-400" fill="none" viewBox="0 0 24 24">
|
||
<circle
|
||
class="opacity-25"
|
||
cx="12"
|
||
cy="12"
|
||
r="10"
|
||
stroke="currentColor"
|
||
stroke-width="4"
|
||
/>
|
||
<path
|
||
class="opacity-75"
|
||
fill="currentColor"
|
||
d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||
/>
|
||
</svg>
|
||
</div>
|
||
|
||
<!-- 没有更多了 -->
|
||
<div v-if="suno.listFinished && !suno.listLoading" class="text-center py-4 text-gray-500">
|
||
没有更多了
|
||
</div>
|
||
</div>
|
||
|
||
<div class="px-4" v-else>
|
||
<van-empty description="暂无数据" image-size="120" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 音乐播放器 -->
|
||
<div
|
||
v-if="suno.showPlayer"
|
||
class="fixed inset-0 z-50 flex items-end justify-center bg-black bg-opacity-50"
|
||
@click="suno.showPlayer = false"
|
||
>
|
||
<div @click.stop class="bg-white rounded-t-2xl w-full max-w-md animate-slide-up">
|
||
<div class="flex items-center justify-between p-4 border-b">
|
||
<h3 class="text-lg font-semibold text-gray-900">正在播放</h3>
|
||
<button @click="suno.showPlayer = false" class="p-2 hover:bg-gray-100 rounded-full">
|
||
<svg
|
||
class="w-5 h-5 text-gray-500"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
stroke-width="2"
|
||
d="M6 18L18 6M6 6l12 12"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div class="p-6">
|
||
<audio
|
||
v-if="suno.currentAudio"
|
||
:src="suno.currentAudio"
|
||
controls
|
||
autoplay
|
||
class="w-full rounded-lg"
|
||
>
|
||
您的浏览器不支持音频播放
|
||
</audio>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import '@/assets/css/mobile/suno.scss'
|
||
import CustomSelect from '@/components/mobile/CustomSelect.vue'
|
||
import { useSunoStore } from '@/store/mobile/suno'
|
||
import { onMounted, onUnmounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { checkSession } from '@/store/cache'
|
||
|
||
const router = useRouter()
|
||
const suno = useSunoStore()
|
||
|
||
// 页面专属方法
|
||
const goBack = () => {
|
||
router.back()
|
||
}
|
||
|
||
const onModeChange = () => {
|
||
if (!suno.custom) {
|
||
suno.removeRefSong()
|
||
}
|
||
}
|
||
|
||
// 滚动监听、定时轮询等副作用
|
||
const handleScroll = () => {
|
||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
|
||
const windowHeight = window.innerHeight
|
||
const documentHeight = document.documentElement.scrollHeight
|
||
if (scrollTop + windowHeight >= documentHeight - 100) {
|
||
suno.loadMore()
|
||
}
|
||
}
|
||
|
||
let tastPullHandler = null
|
||
onMounted(() => {
|
||
checkSession()
|
||
.then(() => {
|
||
suno.fetchData(1)
|
||
tastPullHandler = setInterval(() => {
|
||
if (suno.taskPulling) {
|
||
suno.refreshFirstPage()
|
||
}
|
||
}, 5000)
|
||
window.addEventListener('scroll', handleScroll)
|
||
})
|
||
.catch(() => {
|
||
console.warn('用户未登录')
|
||
})
|
||
})
|
||
onUnmounted(() => {
|
||
if (tastPullHandler) clearInterval(tastPullHandler)
|
||
window.removeEventListener('scroll', handleScroll)
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@use '@/assets/css/mobile/suno.scss';
|
||
|
||
/* Dark 主题样式 - 按照 theme-dark.scss 的模式 */
|
||
:root[data-theme='dark'] .min-h-screen {
|
||
background-color: rgb(13, 20, 53) !important;
|
||
|
||
/* 页面头部 */
|
||
.sticky.top-0 {
|
||
background-color: rgb(31, 41, 55) !important;
|
||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3) !important;
|
||
|
||
.icon-back {
|
||
color: rgb(156, 163, 175) !important;
|
||
}
|
||
|
||
h1 {
|
||
color: rgb(255, 255, 255) !important;
|
||
}
|
||
|
||
button:hover {
|
||
background-color: rgb(75, 85, 99) !important;
|
||
}
|
||
}
|
||
|
||
/* 创作表单 */
|
||
.space-y-6 {
|
||
.bg-white {
|
||
background-color: rgb(31, 41, 55) !important;
|
||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3) !important;
|
||
}
|
||
|
||
.text-gray-900 {
|
||
color: rgb(209, 213, 219) !important;
|
||
}
|
||
|
||
.text-gray-700 {
|
||
color: rgb(209, 213, 219) !important;
|
||
}
|
||
|
||
.text-gray-500 {
|
||
color: rgb(156, 163, 175) !important;
|
||
}
|
||
|
||
.text-gray-600 {
|
||
color: rgb(156, 163, 175) !important;
|
||
}
|
||
|
||
/* 输入框样式 */
|
||
input,
|
||
textarea {
|
||
background-color: rgb(55, 65, 81) !important;
|
||
border-color: rgb(75, 85, 99) !important;
|
||
color: rgb(209, 213, 219) !important;
|
||
|
||
&::placeholder {
|
||
color: rgb(107, 114, 128) !important;
|
||
}
|
||
|
||
&:focus {
|
||
border-color: rgb(139, 92, 246) !important;
|
||
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2) !important;
|
||
}
|
||
}
|
||
|
||
/* 按钮样式 */
|
||
.bg-blue-600 {
|
||
background-color: rgb(37, 99, 235) !important;
|
||
|
||
&:hover:not(:disabled) {
|
||
background-color: rgb(29, 78, 216) !important;
|
||
}
|
||
|
||
&:disabled {
|
||
background-color: rgb(156, 163, 175) !important;
|
||
}
|
||
}
|
||
|
||
.bg-gradient-to-r.from-blue-500.to-purple-600 {
|
||
background: linear-gradient(to right, rgb(59, 130, 246), rgb(147, 51, 234)) !important;
|
||
|
||
&:hover:not(:disabled) {
|
||
background: linear-gradient(to right, rgb(37, 99, 235), rgb(126, 34, 206)) !important;
|
||
}
|
||
|
||
&:disabled {
|
||
background: linear-gradient(to right, rgb(156, 163, 175), rgb(156, 163, 175)) !important;
|
||
}
|
||
}
|
||
|
||
.bg-gradient-to-r.from-purple-500.to-red-300 {
|
||
background: linear-gradient(to right, rgb(147, 51, 234), rgb(252, 165, 165)) !important;
|
||
|
||
&:hover:not(:disabled) {
|
||
background: linear-gradient(to right, rgb(126, 34, 206), rgb(239, 68, 68)) !important;
|
||
}
|
||
}
|
||
|
||
/* 风格标签 */
|
||
.border-blue-200 {
|
||
border-color: rgb(59, 130, 246) !important;
|
||
color: rgb(59, 130, 246) !important;
|
||
|
||
&:hover {
|
||
background-color: rgba(59, 130, 246, 0.1) !important;
|
||
}
|
||
}
|
||
|
||
/* 续写歌曲区域 */
|
||
.border-orange-400 {
|
||
border-left-color: rgb(251, 146, 60) !important;
|
||
}
|
||
|
||
.bg-red-100 {
|
||
background-color: rgba(239, 68, 68, 0.1) !important;
|
||
color: rgb(239, 68, 68) !important;
|
||
|
||
&:hover {
|
||
background-color: rgba(239, 68, 68, 0.2) !important;
|
||
}
|
||
}
|
||
|
||
/* 上传提示区域 */
|
||
.bg-gray-50 {
|
||
background-color: rgb(55, 65, 81) !important;
|
||
}
|
||
|
||
/* 底部生成按钮 */
|
||
.sticky.bottom-4 {
|
||
background-color: rgb(31, 41, 55) !important;
|
||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3) !important;
|
||
}
|
||
}
|
||
|
||
/* 作品列表 */
|
||
.p-4 {
|
||
h2 {
|
||
color: rgb(255, 255, 255) !important;
|
||
}
|
||
|
||
.bg-white {
|
||
background-color: rgb(31, 41, 55) !important;
|
||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3) !important;
|
||
}
|
||
|
||
.bg-gray-100 {
|
||
background-color: rgb(55, 65, 81) !important;
|
||
}
|
||
|
||
.text-gray-900 {
|
||
color: rgb(209, 213, 219) !important;
|
||
}
|
||
|
||
.text-gray-500 {
|
||
color: rgb(156, 163, 175) !important;
|
||
}
|
||
|
||
.text-gray-600 {
|
||
color: rgb(156, 163, 175) !important;
|
||
}
|
||
|
||
.text-gray-400 {
|
||
color: rgb(107, 114, 128) !important;
|
||
}
|
||
|
||
/* 标签样式 */
|
||
.bg-blue-100 {
|
||
background-color: rgba(59, 130, 246, 0.1) !important;
|
||
color: rgb(59, 130, 246) !important;
|
||
}
|
||
|
||
.bg-green-100 {
|
||
background-color: rgba(34, 197, 94, 0.1) !important;
|
||
color: rgb(34, 197, 94) !important;
|
||
}
|
||
|
||
.bg-yellow-100 {
|
||
background-color: rgba(251, 191, 36, 0.1) !important;
|
||
color: rgb(251, 191, 36) !important;
|
||
}
|
||
|
||
.bg-purple-100 {
|
||
background-color: rgba(147, 51, 234, 0.1) !important;
|
||
color: rgb(147, 51, 234) !important;
|
||
}
|
||
|
||
/* 按钮样式 */
|
||
.bg-blue-600 {
|
||
background-color: rgb(37, 99, 235) !important;
|
||
|
||
&:hover {
|
||
background-color: rgb(29, 78, 216) !important;
|
||
}
|
||
}
|
||
|
||
.bg-green-600 {
|
||
background-color: rgb(34, 197, 94) !important;
|
||
|
||
&:hover {
|
||
background-color: rgb(22, 163, 74) !important;
|
||
}
|
||
}
|
||
|
||
.bg-purple-600 {
|
||
background-color: rgb(147, 51, 234) !important;
|
||
|
||
&:hover {
|
||
background-color: rgb(126, 34, 206) !important;
|
||
}
|
||
}
|
||
|
||
.bg-red-100 {
|
||
background-color: rgba(239, 68, 68, 0.1) !important;
|
||
color: rgb(239, 68, 68) !important;
|
||
|
||
&:hover {
|
||
background-color: rgba(239, 68, 68, 0.2) !important;
|
||
}
|
||
}
|
||
|
||
/* 进度条 */
|
||
.bg-gray-200 {
|
||
background-color: rgb(75, 85, 99) !important;
|
||
}
|
||
|
||
.bg-blue-600 {
|
||
background-color: rgb(37, 99, 235) !important;
|
||
}
|
||
|
||
/* 错误信息 */
|
||
.bg-red-50 {
|
||
background-color: rgba(239, 68, 68, 0.1) !important;
|
||
border-color: rgba(239, 68, 68, 0.2) !important;
|
||
}
|
||
|
||
.text-red-600 {
|
||
color: rgb(239, 68, 68) !important;
|
||
}
|
||
|
||
/* 加载状态 */
|
||
.text-blue-600 {
|
||
color: rgb(37, 99, 235) !important;
|
||
}
|
||
|
||
.text-green-500 {
|
||
color: rgb(34, 197, 94) !important;
|
||
}
|
||
|
||
.text-orange-500 {
|
||
color: rgb(251, 146, 60) !important;
|
||
}
|
||
|
||
/* 加载更多 */
|
||
.text-gray-400 {
|
||
color: rgb(107, 114, 128) !important;
|
||
}
|
||
|
||
.text-gray-500 {
|
||
color: rgb(156, 163, 175) !important;
|
||
}
|
||
}
|
||
|
||
/* 音乐播放器弹窗 */
|
||
.fixed.inset-0 {
|
||
.bg-white {
|
||
background-color: rgb(31, 41, 55) !important;
|
||
}
|
||
|
||
.border-b {
|
||
border-bottom-color: rgb(75, 85, 99) !important;
|
||
}
|
||
|
||
h3 {
|
||
color: rgb(255, 255, 255) !important;
|
||
}
|
||
|
||
button {
|
||
&:hover {
|
||
background-color: rgb(75, 85, 99) !important;
|
||
}
|
||
|
||
svg {
|
||
color: rgb(156, 163, 175) !important;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|