mirror of
https://gitee.com/yudaocode/yudao-ui-admin-vben.git
synced 2025-12-30 10:32:25 +00:00
feat:【ele】【ai】音乐的迁移
This commit is contained in:
@@ -27,6 +27,7 @@ export function getKnowledge(id: number) {
|
||||
`/ai/knowledge/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 新增知识库
|
||||
export function createKnowledge(data: AiKnowledgeKnowledgeApi.Knowledge) {
|
||||
return requestClient.post('/ai/knowledge/create', data);
|
||||
|
||||
29
apps/web-ele/src/views/ai/music/index/index.vue
Normal file
29
apps/web-ele/src/views/ai/music/index/index.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Nullable, Recordable } from '@vben/types';
|
||||
|
||||
import { ref, unref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import List from './list/index.vue';
|
||||
import Mode from './mode/index.vue';
|
||||
|
||||
defineOptions({ name: 'AiMusicIndex' });
|
||||
|
||||
const listRef = ref<Nullable<{ generateMusic: (...args: any) => void }>>(null);
|
||||
|
||||
function generateMusic(args: { formData: Recordable<any> }) {
|
||||
unref(listRef)?.generateMusic(args.formData);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<div class="flex h-full items-stretch">
|
||||
<!-- 模式 -->
|
||||
<Mode class="flex-none" @generate-music="generateMusic" />
|
||||
<!-- 音频列表 -->
|
||||
<List ref="listRef" class="flex-auto" />
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, reactive, ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatPast } from '@vben/utils';
|
||||
|
||||
import { ElImage, ElSlider } from 'element-plus';
|
||||
|
||||
defineOptions({ name: 'AiMusicAudioBarIndex' });
|
||||
|
||||
const currentSong = inject<any>('currentSong', {});
|
||||
|
||||
const audioRef = ref<HTMLAudioElement | null>(null);
|
||||
const audioProps = reactive<any>({
|
||||
autoplay: true,
|
||||
paused: false,
|
||||
currentTime: '00:00',
|
||||
duration: '00:00',
|
||||
muted: false,
|
||||
volume: 50,
|
||||
}); // 音频相关属性https://www.runoob.com/tags/ref-av-dom.html
|
||||
|
||||
function toggleStatus(type: string) {
|
||||
audioProps[type] = !audioProps[type];
|
||||
if (type === 'paused' && audioRef.value) {
|
||||
if (audioProps[type]) {
|
||||
audioRef.value.pause();
|
||||
} else {
|
||||
audioRef.value.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新播放位置 */
|
||||
function audioTimeUpdate(args: any) {
|
||||
audioProps.currentTime = formatPast(new Date(args.timeStamp), 'mm:ss');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="b-1 b-l-none h-18 bg-card flex items-center justify-between border border-solid border-rose-100 px-2"
|
||||
>
|
||||
<!-- 歌曲信息 -->
|
||||
<div class="flex gap-2.5">
|
||||
<ElImage
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
class="!w-[45px]"
|
||||
/>
|
||||
<div>
|
||||
<div>{{ currentSong.name }}</div>
|
||||
<div class="text-xs text-gray-400">{{ currentSong.singer }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 音频controls -->
|
||||
<div class="flex items-center gap-3">
|
||||
<IconifyIcon
|
||||
icon="majesticons:back-circle"
|
||||
class="size-5 cursor-pointer text-gray-300"
|
||||
/>
|
||||
<IconifyIcon
|
||||
:icon="
|
||||
audioProps.paused
|
||||
? 'mdi:arrow-right-drop-circle'
|
||||
: 'solar:pause-circle-bold'
|
||||
"
|
||||
class="size-7 cursor-pointer"
|
||||
@click="toggleStatus('paused')"
|
||||
/>
|
||||
<IconifyIcon
|
||||
icon="majesticons:next-circle"
|
||||
class="size-5 cursor-pointer text-gray-300"
|
||||
/>
|
||||
<div class="flex items-center gap-4">
|
||||
<span>{{ audioProps.currentTime }}</span>
|
||||
<ElSlider v-model="audioProps.duration" color="#409eff" class="!w-40" />
|
||||
<span>{{ audioProps.duration }}</span>
|
||||
</div>
|
||||
<!-- 音频 -->
|
||||
<audio
|
||||
v-bind="audioProps"
|
||||
ref="audioRef"
|
||||
controls
|
||||
v-show="!audioProps"
|
||||
@timeupdate="audioTimeUpdate"
|
||||
>
|
||||
<!-- <source :src="audioUrl" /> -->
|
||||
</audio>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<IconifyIcon
|
||||
:icon="audioProps.muted ? 'tabler:volume-off' : 'tabler:volume'"
|
||||
class="size-5 cursor-pointer"
|
||||
@click="toggleStatus('muted')"
|
||||
/>
|
||||
<ElSlider v-model="audioProps.volume" color="#409eff" class="!w-40" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
100
apps/web-ele/src/views/ai/music/index/list/index.vue
Normal file
100
apps/web-ele/src/views/ai/music/index/list/index.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup lang="ts">
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { provide, ref } from 'vue';
|
||||
|
||||
import { ElCol, ElEmpty, ElRow, ElTabPane, ElTabs } from 'element-plus';
|
||||
|
||||
import audioBar from './audioBar/index.vue';
|
||||
import songCard from './songCard/index.vue';
|
||||
import songInfo from './songInfo/index.vue';
|
||||
|
||||
defineOptions({ name: 'AiMusicListIndex' });
|
||||
|
||||
const currentType = ref('mine');
|
||||
const loading = ref(false); // loading 状态
|
||||
const currentSong = ref({}); // 当前音乐
|
||||
const mySongList = ref<Recordable<any>[]>([]);
|
||||
const squareSongList = ref<Recordable<any>[]>([]);
|
||||
|
||||
function generateMusic(formData: Recordable<any>) {
|
||||
loading.value = true;
|
||||
setTimeout(() => {
|
||||
mySongList.value = Array.from({ length: 20 }, (_, index) => {
|
||||
return {
|
||||
id: index,
|
||||
audioUrl: '',
|
||||
videoUrl: '',
|
||||
title: `我走后${index}`,
|
||||
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);
|
||||
}
|
||||
|
||||
function setCurrentSong(music: Recordable<any>) {
|
||||
currentSong.value = music;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
generateMusic,
|
||||
});
|
||||
|
||||
provide('currentSong', currentSong);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-auto overflow-hidden">
|
||||
<ElTabs
|
||||
v-model="currentType"
|
||||
class="flex-auto px-5"
|
||||
tab-position="bottom"
|
||||
>
|
||||
<!-- 我的创作 -->
|
||||
<ElTabPane name="mine" label="我的创作" v-loading="loading">
|
||||
<ElRow v-if="mySongList.length > 0" :gutter="12">
|
||||
<ElCol v-for="song in mySongList" :key="song.id" :span="24">
|
||||
<songCard :song-info="song" @play="setCurrentSong(song)" />
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElEmpty v-else description="暂无音乐" />
|
||||
</ElTabPane>
|
||||
|
||||
<!-- 试听广场 -->
|
||||
<ElTabPane name="square" label="试听广场" v-loading="loading">
|
||||
<ElRow v-if="squareSongList.length > 0" :gutter="12">
|
||||
<ElCol v-for="song in squareSongList" :key="song.id" :span="24">
|
||||
<songCard :song-info="song" @play="setCurrentSong(song)" />
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElEmpty v-else description="暂无音乐" />
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
<!-- songInfo -->
|
||||
<songInfo class="flex-none" />
|
||||
</div>
|
||||
<audioBar class="flex-none" />
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-tabs) {
|
||||
.el-tabs__content {
|
||||
padding: 0 7px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { ElImage } from 'element-plus';
|
||||
|
||||
defineOptions({ name: 'AiMusicSongCardIndex' });
|
||||
|
||||
defineProps({
|
||||
songInfo: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['play']);
|
||||
|
||||
const currentSong = inject<any>('currentSong', {});
|
||||
|
||||
function playSong() {
|
||||
emits('play');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-3 flex rounded p-3">
|
||||
<div class="relative" @click="playSong">
|
||||
<ElImage :src="songInfo.imageUrl" class="w-20 flex-none" />
|
||||
<div
|
||||
class="absolute left-0 top-0 flex h-full w-full cursor-pointer items-center justify-center bg-black bg-opacity-40"
|
||||
>
|
||||
<IconifyIcon
|
||||
:icon="
|
||||
currentSong.id === songInfo.id
|
||||
? 'solar:pause-circle-bold'
|
||||
: 'mdi:arrow-right-drop-circle'
|
||||
"
|
||||
:size="30"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<div>{{ songInfo.title }}</div>
|
||||
<div class="mt-2 line-clamp-2 text-xs">
|
||||
{{ songInfo.desc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import { ElButton, ElCard, ElImage } from 'element-plus';
|
||||
|
||||
defineOptions({ name: 'AiMusicSongInfoIndex' });
|
||||
|
||||
const currentSong = inject<any>('currentSong', {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElCard class="!mb-0 w-40 leading-6">
|
||||
<ElImage :src="currentSong.imageUrl" class="h-full w-full" />
|
||||
|
||||
<div class="">{{ currentSong.title }}</div>
|
||||
<div class="line-clamp-1 text-xs">
|
||||
{{ currentSong.desc }}
|
||||
</div>
|
||||
<div class="text-xs">
|
||||
{{ currentSong.date }}
|
||||
</div>
|
||||
<ElButton size="small" round class="my-2">信息复用</ElButton>
|
||||
<div class="text-xs" v-html="currentSong.lyric"></div>
|
||||
</ElCard>
|
||||
</template>
|
||||
66
apps/web-ele/src/views/ai/music/index/mode/desc.vue
Normal file
66
apps/web-ele/src/views/ai/music/index/mode/desc.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import { ElInput, ElOption, ElSelect, ElSwitch } from 'element-plus';
|
||||
|
||||
import Title from '../title/index.vue';
|
||||
|
||||
defineOptions({ name: 'AiMusicModeDesc' });
|
||||
|
||||
const formData = reactive({
|
||||
desc: '',
|
||||
pure: false,
|
||||
version: '3',
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
formData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Title
|
||||
title="音乐/歌词说明"
|
||||
desc="描述您想要的音乐风格和主题,使用流派和氛围而不是特定的艺术家和歌曲"
|
||||
>
|
||||
<ElInput
|
||||
v-model="formData.desc"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 6, maxRows: 6 }"
|
||||
:maxlength="1200"
|
||||
:show-word-limit="true"
|
||||
placeholder="一首关于糟糕分手的欢快歌曲"
|
||||
/>
|
||||
</Title>
|
||||
|
||||
<Title title="纯音乐" class="mt-5" desc="创建一首没有歌词的歌曲">
|
||||
<template #extra>
|
||||
<ElSwitch v-model="formData.pure" size="small" />
|
||||
</template>
|
||||
</Title>
|
||||
|
||||
<Title
|
||||
title="版本"
|
||||
desc="描述您想要的音乐风格和主题,使用流派和氛围而不是特定的艺术家和歌曲"
|
||||
>
|
||||
<ElSelect v-model="formData.version" class="w-full" placeholder="请选择">
|
||||
<ElOption
|
||||
v-for="item in [
|
||||
{
|
||||
value: '3',
|
||||
label: 'V3',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: 'V2',
|
||||
},
|
||||
]"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</ElSelect>
|
||||
</Title>
|
||||
</div>
|
||||
</template>
|
||||
38
apps/web-ele/src/views/ai/music/index/mode/index.vue
Normal file
38
apps/web-ele/src/views/ai/music/index/mode/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Nullable, Recordable } from '@vben/types';
|
||||
|
||||
import { ref, unref } from 'vue';
|
||||
|
||||
import { ElButton, ElCard, ElRadioButton, ElRadioGroup } from 'element-plus';
|
||||
|
||||
import desc from './desc.vue';
|
||||
import lyric from './lyric.vue';
|
||||
|
||||
defineOptions({ name: 'AiMusicModeIndex' });
|
||||
|
||||
const emits = defineEmits(['generateMusic']);
|
||||
|
||||
const generateMode = ref('lyric');
|
||||
|
||||
const modeRef = ref<Nullable<{ formData: Recordable<any> }>>(null);
|
||||
|
||||
function generateMusic() {
|
||||
emits('generateMusic', { formData: unref(modeRef)?.formData });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElCard class="!mb-0 h-full w-80">
|
||||
<ElRadioGroup v-model="generateMode" class="mb-4">
|
||||
<ElRadioButton value="desc"> 描述模式 </ElRadioButton>
|
||||
<ElRadioButton value="lyric"> 歌词模式 </ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
|
||||
<!-- 描述模式/歌词模式 切换 -->
|
||||
<component :is="generateMode === 'desc' ? desc : lyric" ref="modeRef" />
|
||||
|
||||
<ElButton type="primary" round class="w-full" @click="generateMusic">
|
||||
创作音乐
|
||||
</ElButton>
|
||||
</ElCard>
|
||||
</template>
|
||||
107
apps/web-ele/src/views/ai/music/index/mode/lyric.vue
Normal file
107
apps/web-ele/src/views/ai/music/index/mode/lyric.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElInput,
|
||||
ElOption,
|
||||
ElSelect,
|
||||
ElSpace,
|
||||
ElTag,
|
||||
} from 'element-plus';
|
||||
|
||||
import Title from '../title/index.vue';
|
||||
|
||||
defineOptions({ name: 'AiMusicModeLyric' });
|
||||
|
||||
const tags = ['rock', 'punk', 'jazz', 'soul', 'country', 'kidsmusic', 'pop'];
|
||||
|
||||
const showCustom = ref(false);
|
||||
|
||||
const formData = reactive({
|
||||
lyric: '',
|
||||
style: '',
|
||||
name: '',
|
||||
version: '',
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
formData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="">
|
||||
<Title title="歌词" desc="自己编写歌词或使用Ai生成歌词,两节/8行效果最佳">
|
||||
<ElInput
|
||||
v-model="formData.lyric"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 6, maxRows: 6 }"
|
||||
:maxlength="1200"
|
||||
:show-word-limit="true"
|
||||
placeholder="请输入您自己的歌词"
|
||||
/>
|
||||
</Title>
|
||||
|
||||
<Title title="音乐风格">
|
||||
<ElSpace class="flex-wrap">
|
||||
<ElTag v-for="tag in tags" :key="tag" class="mb-2">
|
||||
{{ tag }}
|
||||
</ElTag>
|
||||
</ElSpace>
|
||||
|
||||
<ElButton
|
||||
:type="showCustom ? 'primary' : 'default'"
|
||||
round
|
||||
size="small"
|
||||
class="mb-2"
|
||||
@click="showCustom = !showCustom"
|
||||
>
|
||||
自定义风格
|
||||
</ElButton>
|
||||
</Title>
|
||||
|
||||
<Title
|
||||
v-show="showCustom"
|
||||
desc="描述您想要的音乐风格,Suno无法识别艺术家的名字,但可以理解流派和氛围"
|
||||
class="mt-3"
|
||||
>
|
||||
<ElInput
|
||||
v-model="formData.style"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 4, maxRows: 4 }"
|
||||
:maxlength="256"
|
||||
show-word-limit
|
||||
placeholder="输入音乐风格(英文)"
|
||||
/>
|
||||
</Title>
|
||||
|
||||
<Title title="音乐/歌曲名称">
|
||||
<ElInput
|
||||
class="w-full"
|
||||
v-model="formData.name"
|
||||
placeholder="请输入音乐/歌曲名称"
|
||||
/>
|
||||
</Title>
|
||||
|
||||
<Title title="版本">
|
||||
<ElSelect v-model="formData.version" class="w-full" placeholder="请选择">
|
||||
<ElOption
|
||||
v-for="item in [
|
||||
{
|
||||
value: '3',
|
||||
label: 'V3',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: 'V2',
|
||||
},
|
||||
]"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</ElSelect>
|
||||
</Title>
|
||||
</div>
|
||||
</template>
|
||||
27
apps/web-ele/src/views/ai/music/index/title/index.vue
Normal file
27
apps/web-ele/src/views/ai/music/index/title/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'AiMusicTitleIndex' });
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
desc: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<div class="flex items-center justify-between text-gray-600">
|
||||
<span>{{ title }}</span>
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
<div class="my-2 text-xs text-gray-400">
|
||||
{{ desc }}
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user