feat: 新增AI music相关页面以及组件,调整了APPview页面min-height为height,注释了登录时dict接口获取,在contentWrap组件新增了bodystyle属性
							parent
							
								
									7fe8b8b7a9
								
							
						
					
					
						commit
						9516e7184f
					
				|  | @ -10,12 +10,13 @@ const prefixCls = getPrefixCls('content-wrap') | |||
| 
 | ||||
| defineProps({ | ||||
|   title: propTypes.string.def(''), | ||||
|   message: propTypes.string.def('') | ||||
|   message: propTypes.string.def(''), | ||||
|   bodyStyle: propTypes.object.def({ padding: '20px' }) | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ElCard :class="[prefixCls, 'mb-15px']" shadow="never"> | ||||
|   <ElCard :body-style="bodyStyle" :class="[prefixCls, 'mb-15px']" shadow="never"> | ||||
|     <template v-if="title" #header> | ||||
|       <div class="flex items-center"> | ||||
|         <span class="text-16px font-700">{{ title }}</span> | ||||
|  | @ -30,8 +31,6 @@ defineProps({ | |||
|         </div> | ||||
|       </div> | ||||
|     </template> | ||||
|     <div> | ||||
|       <slot></slot> | ||||
|     </div> | ||||
|     <slot></slot> | ||||
|   </ElCard> | ||||
| </template> | ||||
|  |  | |||
|  | @ -38,24 +38,24 @@ provide('reload', reload) | |||
|     :class="[ | ||||
|       'p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]', | ||||
|       { | ||||
|         '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]': | ||||
|         '!h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]': | ||||
|           (fixedHeader && | ||||
|             (layout === 'classic' || layout === 'topLeft' || layout === 'top') && | ||||
|             footer) || | ||||
|           (!tagsView && layout === 'top' && footer), | ||||
|         '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]': | ||||
|         '!h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]': | ||||
|           tagsView && layout === 'top' && footer, | ||||
| 
 | ||||
|         '!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]': | ||||
|         '!h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]': | ||||
|           !fixedHeader && layout === 'classic' && footer, | ||||
| 
 | ||||
|         '!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]': | ||||
|         '!h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]': | ||||
|           !fixedHeader && layout === 'topLeft' && footer, | ||||
| 
 | ||||
|         '!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]': | ||||
|         '!h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]': | ||||
|           fixedHeader && layout === 'cutMenu' && footer, | ||||
| 
 | ||||
|         '!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]': | ||||
|         '!h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]': | ||||
|           !fixedHeader && layout === 'cutMenu' && footer | ||||
|       } | ||||
|     ]" | ||||
|  |  | |||
|  | @ -70,6 +70,26 @@ const remainingRouter: AppRouteRecordRaw[] = [ | |||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     path: '/ai/music', | ||||
|     component: Layout, | ||||
|     redirect: '/index', | ||||
|     name: 'AIMusic', | ||||
|     meta: {}, | ||||
|     children: [ | ||||
|       { | ||||
|         path: 'index', | ||||
|         component: () => import('@/views/ai//music/index.vue'), | ||||
|         name: 'AIMusicIndex', | ||||
|         meta: { | ||||
|           title: 'AI 音乐', | ||||
|           icon: 'ep:home-filled', | ||||
|           noCache: false, | ||||
|           affix: true | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     path: '/user', | ||||
|     component: Layout, | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { store } from '../index' | |||
| import { DictDataVO } from '@/api/system/dict/types' | ||||
| import { CACHE_KEY, useCache } from '@/hooks/web/useCache' | ||||
| const { wsCache } = useCache('sessionStorage') | ||||
| import { getSimpleDictDataList } from '@/api/system/dict/dict.data' | ||||
| // import { getSimpleDictDataList } from '@/api/system/dict/dict.data'
 | ||||
| 
 | ||||
| export interface DictValueType { | ||||
|   value: any | ||||
|  | @ -45,7 +45,8 @@ export const useDictStore = defineStore('dict', { | |||
|         this.dictMap = dictMap | ||||
|         this.isSetDict = true | ||||
|       } else { | ||||
|         const res = await getSimpleDictDataList() | ||||
|         const res = [] | ||||
|         // const res = await getSimpleDictDataList()
 | ||||
|         // 设置数据
 | ||||
|         const dictDataMap = new Map<string, any>() | ||||
|         res.forEach((dictData: DictDataVO) => { | ||||
|  | @ -75,7 +76,8 @@ export const useDictStore = defineStore('dict', { | |||
|     }, | ||||
|     async resetDict() { | ||||
|       wsCache.delete(CACHE_KEY.DICT_CACHE) | ||||
|       const res = await getSimpleDictDataList() | ||||
|       const res = [] | ||||
|       // const res = await getSimpleDictDataList()
 | ||||
|       // 设置数据
 | ||||
|       const dictDataMap = new Map<string, any>() | ||||
|       res.forEach((dictData: DictDataVO) => { | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| <template> | ||||
|   <div class="h-72px bg-[var(--el-bg-color-overlay)] b-solid b-1 b-[var(--el-border-color)] b-l-none">播放器</div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| 
 | ||||
| defineOptions({ name: 'Index' }) | ||||
| 
 | ||||
| </script> | ||||
|  | @ -0,0 +1,94 @@ | |||
| <template> | ||||
|   <div class="flex flex-col h-full"> | ||||
|     <div class="flex-auto flex overflow-hidden"> | ||||
|       <el-tabs v-model="currentType" class="flex-auto px-[var(--app-content-padding)]"> | ||||
|         <!-- 我的创作 --> | ||||
|         <el-tab-pane label="我的创作" v-loading="loading" name="mine"> | ||||
|           <el-row v-if="mySongList.length" :gutter="12"> | ||||
|             <el-col v-for="song in mySongList" :key="song.id" :md="24" :lg="12"> | ||||
|               <songCard v-bind="song"/> | ||||
|             </el-col> | ||||
|           </el-row> | ||||
|           <el-empty v-else description="暂无音乐"/> | ||||
|         </el-tab-pane> | ||||
| 
 | ||||
|         <!-- 试听广场 --> | ||||
|         <el-tab-pane label="试听广场" v-loading="loading" name="square"> | ||||
|           <el-row v-if="squareSongList.length" v-loading="loading" :gutter="12"> | ||||
|             <el-col v-for="song in squareSongList" :key="song.id" :md="24" :lg="12"> | ||||
|               <songCard v-bind="song"/> | ||||
|             </el-col> | ||||
|           </el-row> | ||||
|           <el-empty v-else description="暂无音乐"/> | ||||
|         </el-tab-pane> | ||||
|       </el-tabs> | ||||
|       <!-- songInfo --> | ||||
|       <songInfo v-bind="squareSongList[0]" class="flex-none"/> | ||||
|     </div> | ||||
|     <audioBar class="flex-none"/> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import songCard from './songCard/index.vue' | ||||
| import songInfo from './songInfo/index.vue' | ||||
| import audioBar from './audioBar/index.vue' | ||||
| 
 | ||||
| defineOptions({ name: 'Index' }) | ||||
| 
 | ||||
| const currentType = ref('mine') | ||||
| // loading 状态 | ||||
| const loading = ref(false) | ||||
| 
 | ||||
| const mySongList = ref<Recordable[]>([]) | ||||
| const squareSongList = ref<Recordable[]>([]) | ||||
| 
 | ||||
| /* | ||||
|  *@Description: 调接口生成音乐列表 | ||||
|  *@MethodAuthor: xiaohong | ||||
|  *@Date: 2024-06-27 17:06:44 | ||||
| */ | ||||
| function generateMusic (formData: Recordable) { | ||||
|   console.log(formData); | ||||
|   loading.value = true | ||||
|   setTimeout(() => { | ||||
|     mySongList.value = Array.from({ length: 20 }, (_, index) => { | ||||
|       return { | ||||
|         id: index, | ||||
|         audioUrl: '', | ||||
|         videoUrl: '', | ||||
|         title: '我走后', | ||||
|         imageUrl: 'https://www.carsmp3.com/data/attachment/forum/201909/19/091020q5kgre20fidreqyt.jpg', | ||||
|         desc: 'Metal, symphony, film soundtrack, grand, majesticMetal, dtrack, grand, majestic', | ||||
|         date: '2024年04月30日 14:02:57', | ||||
|         lyric: `<div class="_words_17xen_66"><div>大江东去,浪淘尽,千古风流人物。 | ||||
|           </div><div>故垒西边,人道是,三国周郎赤壁。 | ||||
|           </div><div>乱石穿空,惊涛拍岸,卷起千堆雪。 | ||||
|           </div><div>江山如画,一时多少豪杰。 | ||||
|           </div><div> | ||||
|           </div><div>遥想公瑾当年,小乔初嫁了,雄姿英发。 | ||||
|           </div><div>羽扇纶巾,谈笑间,樯橹灰飞烟灭。 | ||||
|           </div><div>故国神游,多情应笑我,早生华发。 | ||||
|           </div><div>人生如梦,一尊还酹江月。</div></div>` | ||||
|       } | ||||
|     }) | ||||
|     loading.value = false | ||||
|   }, 3000) | ||||
| } | ||||
| 
 | ||||
| defineExpose({ | ||||
|   generateMusic | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| :deep(.el-tabs) { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   .el-tabs__content{ | ||||
|   padding: 0 7px; | ||||
|   overflow: auto; | ||||
|  } | ||||
| } | ||||
| </style> | ||||
|  | @ -0,0 +1,29 @@ | |||
| <template> | ||||
|   <div class="flex bg-[var(--el-bg-color-overlay)] p-12px mb-12px rounded-1"> | ||||
|     <el-image :src="imageUrl" class="flex-none w-80px"/> | ||||
|     <div class="ml-8px"> | ||||
|       <div>{{ title }}</div> | ||||
|       <div class="mt-8px text-12px text-[var(--el-text-color-secondary)] line-clamp-2"> | ||||
|         {{ desc }} | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| 
 | ||||
| defineOptions({ name: 'Index' }) | ||||
| 
 | ||||
| defineProps({ | ||||
|   imageUrl: { | ||||
|     type: String | ||||
|   }, | ||||
|   title: { | ||||
|     type: String | ||||
|   }, | ||||
|   desc: { | ||||
|     type: String | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
| </script> | ||||
|  | @ -0,0 +1,33 @@ | |||
| <template> | ||||
|   <ContentWrap class="w-300px mb-[0!important] line-height-24px"> | ||||
|     <el-image :src="imageUrl"/> | ||||
|     <div class="">{{ title }}</div> | ||||
|     <div class="text-[var(--el-text-color-secondary)] text-12px line-clamp-1">{{ desc }}</div> | ||||
|     <div class="text-[var(--el-text-color-secondary)] text-12px">{{ date }}</div> | ||||
|     <el-button size="small" round class="my-6px">信息复用</el-button> | ||||
|     <div class="text-[var(--el-text-color-secondary)] text-12px" v-html="lyric"></div> | ||||
|   </ContentWrap> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| 
 | ||||
| defineOptions({ name: 'Index' }) | ||||
| 
 | ||||
| defineProps({ | ||||
|   imageUrl: { | ||||
|     type: String | ||||
|   }, | ||||
|   title: { | ||||
|     type: String | ||||
|   }, | ||||
|   desc: { | ||||
|     type: String | ||||
|   }, | ||||
|   date: { | ||||
|     type: String | ||||
|   }, | ||||
|   lyric: { | ||||
|     type: String | ||||
|   } | ||||
| }) | ||||
| </script> | ||||
|  | @ -0,0 +1,55 @@ | |||
| <template> | ||||
|   <div> | ||||
|     <Title title="音乐/歌词说明" desc="描述您想要的音乐风格和主题,使用流派和氛围而不是特定的艺术家和歌曲"> | ||||
|       <el-input | ||||
|         v-model="formData.desc" | ||||
|         :autosize="{ minRows: 6, maxRows: 6}" | ||||
|         resize="none" | ||||
|         type="textarea" | ||||
|         maxlength="1200" | ||||
|         show-word-limit | ||||
|         placeholder="一首关于糟糕分手的欢快歌曲" | ||||
|       /> | ||||
|     </Title> | ||||
| 
 | ||||
|     <Title title="纯音乐" desc="创建一首没有歌词的歌曲"> | ||||
|       <template #extra> | ||||
|         <el-switch v-model="formData.pure" size="small"/> | ||||
|       </template> | ||||
|     </Title> | ||||
| 
 | ||||
|     <Title title="版本" desc="描述您想要的音乐风格和主题,使用流派和氛围而不是特定的艺术家和歌曲"> | ||||
|       <el-select v-model="formData.version" placeholder="请选择"> | ||||
|         <el-option | ||||
|           v-for="item in [{ | ||||
|             value: '3', | ||||
|             label: 'V3' | ||||
|           }, { | ||||
|             value: '2', | ||||
|             label: 'V2' | ||||
|           }]" | ||||
|           :key="item.value" | ||||
|           :label="item.label" | ||||
|           :value="item.value" | ||||
|         /> | ||||
|       </el-select> | ||||
|     </Title> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import Title from '../title/index.vue' | ||||
| 
 | ||||
| defineOptions({ name: 'Desc' }) | ||||
| 
 | ||||
| const formData = reactive({ | ||||
|   desc: '', | ||||
|   pure: false, | ||||
|   version: '3' | ||||
| }) | ||||
| 
 | ||||
| defineExpose({ | ||||
|   formData | ||||
| }) | ||||
| 
 | ||||
| </script> | ||||
|  | @ -0,0 +1,44 @@ | |||
| <template> | ||||
|   <ContentWrap class="w-300px h-full"> | ||||
|     <el-radio-group v-model="generateMode" class="mb-15px"> | ||||
|       <el-radio-button label="desc"> | ||||
|         描述模式 | ||||
|       </el-radio-button> | ||||
|       <el-radio-button label="lyric"> | ||||
|         歌词模式 | ||||
|       </el-radio-button> | ||||
|     </el-radio-group> | ||||
| 
 | ||||
|     <!-- 描述模式/歌词模式 切换 --> | ||||
|     <component :is="generateMode === 'desc' ? desc : lyric" ref="modeRef"/> | ||||
| 
 | ||||
|     <el-button type="primary" round class="w-full" @click="generateMusic"> | ||||
|       创作音乐 | ||||
|     </el-button> | ||||
|   </ContentWrap> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import desc from './desc.vue' | ||||
| import lyric from './lyric.vue' | ||||
| 
 | ||||
| defineOptions({ name: 'Index' }) | ||||
| 
 | ||||
| const emits = defineEmits(['generate-music']) | ||||
| 
 | ||||
| const generateMode = ref('lyric') | ||||
| 
 | ||||
| interface ModeRef { | ||||
|   formData: Recordable | ||||
| } | ||||
| const modeRef = ref<ModeRef | null>(null) | ||||
| 
 | ||||
| /* | ||||
|  *@Description: 根据信息生成音乐 | ||||
|  *@MethodAuthor: xiaohong | ||||
|  *@Date: 2024-06-27 16:40:16 | ||||
| */ | ||||
| function generateMusic () { | ||||
|   emits('generate-music', {formData: unref(modeRef)?.formData.value}) | ||||
| } | ||||
| </script> | ||||
|  | @ -0,0 +1,83 @@ | |||
| <template> | ||||
|   <div class=""> | ||||
|     <Title title="歌词" desc="自己编写歌词或使用Ai生成歌词,两节/8行效果最佳"> | ||||
|       <el-input | ||||
|         v-model="formData.lyric" | ||||
|         :autosize="{ minRows: 6, maxRows: 6}" | ||||
|         resize="none" | ||||
|         type="textarea" | ||||
|         maxlength="1200" | ||||
|         show-word-limit | ||||
|         placeholder="请输入您自己的歌词" | ||||
|       /> | ||||
|     </Title> | ||||
| 
 | ||||
|     <Title title="音乐风格"> | ||||
|       <el-space class="flex-wrap"> | ||||
|         <el-tag v-for="tag in tags" :key="tag" round class="mb-8px">{{tag}}</el-tag> | ||||
|       </el-space> | ||||
| 
 | ||||
|       <el-button | ||||
|         :type="showCustom ? 'primary': 'default'"  | ||||
|         round  | ||||
|         size="small"  | ||||
|         class="mb-6px" | ||||
|         @click="showCustom = !showCustom" | ||||
|       >自定义风格 | ||||
|       </el-button> | ||||
|     </Title> | ||||
| 
 | ||||
|     <Title v-show="showCustom" desc="描述您想要的音乐风格,Suno无法识别艺术家的名字,但可以理解流派和氛围" class="-mt-12px"> | ||||
|       <el-input | ||||
|         v-model="formData.style" | ||||
|         :autosize="{ minRows: 4, maxRows: 4}" | ||||
|         resize="none" | ||||
|         type="textarea" | ||||
|         maxlength="256" | ||||
|         show-word-limit | ||||
|         placeholder="输入音乐风格(英文)" | ||||
|       /> | ||||
|     </Title> | ||||
| 
 | ||||
|     <Title title="音乐/歌曲名称"> | ||||
|       <el-input v-model="formData.name" placeholder="请输入音乐/歌曲名称"/> | ||||
|     </Title> | ||||
| 
 | ||||
|     <Title title="版本"> | ||||
|       <el-select v-model="formData.version" placeholder="请选择"> | ||||
|         <el-option | ||||
|           v-for="item in [{ | ||||
|             value: '3', | ||||
|             label: 'V3' | ||||
|           }, { | ||||
|             value: '2', | ||||
|             label: 'V2' | ||||
|           }]" | ||||
|           :key="item.value" | ||||
|           :label="item.label" | ||||
|           :value="item.value" | ||||
|         /> | ||||
|       </el-select> | ||||
|     </Title> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import Title from '../title/index.vue' | ||||
| defineOptions({ name: 'Lyric' }) | ||||
| 
 | ||||
| const tags = ['rock', 'punk', 'jazz', 'soul', 'country', 'kidsmusic', 'pop'] | ||||
| 
 | ||||
| const showCustom = ref(false) | ||||
| 
 | ||||
| const formData = reactive({ | ||||
|   lyric: '', | ||||
|   style: '', | ||||
|   name: '', | ||||
|   version: '' | ||||
| }) | ||||
| 
 | ||||
| defineExpose({ | ||||
|   formData | ||||
| }) | ||||
| </script> | ||||
|  | @ -0,0 +1,25 @@ | |||
| <template> | ||||
|   <div class="mb-12px"> | ||||
|     <div class="flex text-[var(--el-text-color-primary)] justify-between items-center"> | ||||
|       <span>{{title}}</span> | ||||
|       <slot name="extra"></slot> | ||||
|     </div> | ||||
|     <div class="text-[var(--el-text-color-secondary)] text-12px my-8px"> | ||||
|       {{desc}} | ||||
|     </div> | ||||
|     <slot></slot> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| defineOptions({ name: 'Index' }) | ||||
| 
 | ||||
| defineProps({ | ||||
|   title: { | ||||
|     type: String | ||||
|   }, | ||||
|   desc: { | ||||
|     type: String | ||||
|   } | ||||
| }) | ||||
| </script> | ||||
|  | @ -0,0 +1,21 @@ | |||
| <template> | ||||
|   <div class="flex h-1/1"> | ||||
|     <!-- 模式 --> | ||||
|     <Mode class="flex-none" @generate-music="generateMusic"/> | ||||
|     <!-- 音频列表 --> | ||||
|     <List ref="listRef" class="flex-auto"/> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import Mode from './components/mode/index.vue' | ||||
| import List from './components/list/index.vue' | ||||
| 
 | ||||
| defineOptions({ name: 'Index' }) | ||||
| 
 | ||||
| const listRef = ref<{generateMusic: (...args) => void} | null>(null) | ||||
| 
 | ||||
| function generateMusic (args: {formData: Recordable}) { | ||||
|  unref(listRef)?.generateMusic(args.formData) | ||||
| } | ||||
| </script> | ||||
		Loading…
	
		Reference in New Issue
	
	 罗婷(luot1)
						罗婷(luot1)