Selaa lähdekoodia

Merge remote-tracking branch 'origin/dev' into dev

YunaiV 10 kuukautta sitten
vanhempi
commit
440ad58a77

+ 4 - 5
src/components/ContentWrap/src/ContentWrap.vue

@@ -10,12 +10,13 @@ const prefixCls = getPrefixCls('content-wrap')
 
 
 defineProps({
 defineProps({
   title: propTypes.string.def(''),
   title: propTypes.string.def(''),
-  message: propTypes.string.def('')
+  message: propTypes.string.def(''),
+  bodyStyle: propTypes.object.def({ padding: '20px' })
 })
 })
 </script>
 </script>
 
 
 <template>
 <template>
-  <ElCard :class="[prefixCls, 'mb-15px']" shadow="never">
+  <ElCard :body-style="bodyStyle" :class="[prefixCls, 'mb-15px']" shadow="never">
     <template v-if="title" #header>
     <template v-if="title" #header>
       <div class="flex items-center">
       <div class="flex items-center">
         <span class="text-16px font-700">{{ title }}</span>
         <span class="text-16px font-700">{{ title }}</span>
@@ -30,8 +31,6 @@ defineProps({
         </div>
         </div>
       </div>
       </div>
     </template>
     </template>
-    <div>
-      <slot></slot>
-    </div>
+    <slot></slot>
   </ElCard>
   </ElCard>
 </template>
 </template>

+ 6 - 6
src/layout/components/AppView.vue

@@ -38,24 +38,24 @@ provide('reload', reload)
     :class="[
     :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)]',
       '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 &&
           (fixedHeader &&
             (layout === 'classic' || layout === 'topLeft' || layout === 'top') &&
             (layout === 'classic' || layout === 'topLeft' || layout === 'top') &&
             footer) ||
             footer) ||
           (!tagsView && 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,
           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,
           !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,
           !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,
           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
           !fixedHeader && layout === 'cutMenu' && footer
       }
       }
     ]"
     ]"

+ 20 - 0
src/router/modules/remaining.ts

@@ -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',
     path: '/user',
     component: Layout,
     component: Layout,

+ 5 - 3
src/store/modules/dict.ts

@@ -4,7 +4,7 @@ import { store } from '../index'
 import { DictDataVO } from '@/api/system/dict/types'
 import { DictDataVO } from '@/api/system/dict/types'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 const { wsCache } = useCache('sessionStorage')
 const { wsCache } = useCache('sessionStorage')
-import { getSimpleDictDataList } from '@/api/system/dict/dict.data'
+// import { getSimpleDictDataList } from '@/api/system/dict/dict.data'
 
 
 export interface DictValueType {
 export interface DictValueType {
   value: any
   value: any
@@ -45,7 +45,8 @@ export const useDictStore = defineStore('dict', {
         this.dictMap = dictMap
         this.dictMap = dictMap
         this.isSetDict = true
         this.isSetDict = true
       } else {
       } else {
-        const res = await getSimpleDictDataList()
+        const res = []
+        // const res = await getSimpleDictDataList()
         // 设置数据
         // 设置数据
         const dictDataMap = new Map<string, any>()
         const dictDataMap = new Map<string, any>()
         res.forEach((dictData: DictDataVO) => {
         res.forEach((dictData: DictDataVO) => {
@@ -75,7 +76,8 @@ export const useDictStore = defineStore('dict', {
     },
     },
     async resetDict() {
     async resetDict() {
       wsCache.delete(CACHE_KEY.DICT_CACHE)
       wsCache.delete(CACHE_KEY.DICT_CACHE)
-      const res = await getSimpleDictDataList()
+      const res = []
+      // const res = await getSimpleDictDataList()
       // 设置数据
       // 设置数据
       const dictDataMap = new Map<string, any>()
       const dictDataMap = new Map<string, any>()
       res.forEach((dictData: DictDataVO) => {
       res.forEach((dictData: DictDataVO) => {

+ 9 - 0
src/views/ai/music/components/list/audioBar/index.vue

@@ -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>

+ 94 - 0
src/views/ai/music/components/list/index.vue

@@ -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" :span="24">
+              <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" :span="24">
+              <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>

+ 29 - 0
src/views/ai/music/components/list/songCard/index.vue

@@ -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>

+ 33 - 0
src/views/ai/music/components/list/songInfo/index.vue

@@ -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>

+ 55 - 0
src/views/ai/music/components/mode/desc.vue

@@ -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>

+ 44 - 0
src/views/ai/music/components/mode/index.vue

@@ -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>

+ 83 - 0
src/views/ai/music/components/mode/lyric.vue

@@ -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>

+ 25 - 0
src/views/ai/music/components/title/index.vue

@@ -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>

+ 21 - 0
src/views/ai/music/index.vue

@@ -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>