feat: 部分完成音乐播放器
This commit is contained in:
1 parent
3b09d4c1cb
commit
1f42785b4a
13 files changed
+659
-37
No files matched your search
@@ -8,7 +8,7 @@ const pluginConfig: registerComponentOption = {
|
||||
name: '音乐',
|
||||
icon: Disc,
|
||||
getComponent: async () =>
|
||||
(await import('./miaoMusicPlayerDemo.vue')).default,
|
||||
(await import('./miaoMusicPlayer.vue')).default,
|
||||
filter: (vDirs, vFiles) => {
|
||||
if (vDirs.length !== 0) {
|
||||
return false
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { createApp, Reactive } from "vue";
|
||||
import aplayerListNew from "../src/aplayerListNew.vue";
|
||||
import aplayerListNew from "../src/components/aplayerListNew.vue";
|
||||
import apType, { audioType } from "../types/ap";
|
||||
|
||||
type aplayerMiaoOption = {
|
||||
export type aplayerMiaoOption = {
|
||||
onDragItemStart?: () => void
|
||||
onDragItemEnd?: () => void
|
||||
onPlayVideo?: (element: audioType) => void
|
||||
onSaveCollection?: (audios: audioType[]) => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+66
-7
@@ -2,14 +2,20 @@
|
||||
<miao-drop-handler @on-virtual-files="handleDropVFiles">
|
||||
<miao-alert-tip-provider ref="miaoMessageRef">
|
||||
<div class="miaoMusic-container">
|
||||
<NScrollbar>
|
||||
<miao-music-main v-if="ap" :ap="ap"></miao-music-main>
|
||||
</NScrollbar>
|
||||
<miao-music-main
|
||||
v-if="ap"
|
||||
:ap="ap"
|
||||
v-model:data="data"
|
||||
:updateLocalData="updateLocalData"
|
||||
@new-collection="saveCurrPlayList"></miao-music-main>
|
||||
<miao-mask
|
||||
:show="isAPlayerListShow"
|
||||
style="z-index: 5"
|
||||
@click="() => ap?.list.hide()"></miao-mask>
|
||||
<div ref="aplayer" style="z-index: 10" />
|
||||
<miao-mask
|
||||
:show="isSavingCollection"
|
||||
@click="isSavingCollection = false"></miao-mask>
|
||||
</div>
|
||||
</miao-alert-tip-provider>
|
||||
</miao-drop-handler>
|
||||
@@ -18,8 +24,16 @@
|
||||
<script setup lang="ts">
|
||||
import useAplayerMiao from './hooks/aplayerMiao'
|
||||
import { VirtualFile } from '@/class/VirtualDirectory'
|
||||
import { App, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import { NScrollbar } from 'naive-ui'
|
||||
import {
|
||||
App,
|
||||
onBeforeMount,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
reactive,
|
||||
ref,
|
||||
toRaw,
|
||||
watch
|
||||
} from 'vue'
|
||||
import './src/APlayer.fix.css'
|
||||
// @ts-ignore
|
||||
import APlayer from 'APlayer'
|
||||
@@ -33,6 +47,9 @@ import usePluginCenter from '@/hooks/usePluginCenter'
|
||||
import VirtualPage from '@/class/VirtualPage'
|
||||
import miaoMask from '@/components/miaoMask.vue'
|
||||
import miaoMusicMain from './src/main.vue'
|
||||
import collectionType from './types/collection'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import localForage from 'localforage'
|
||||
|
||||
const pluginCenter = usePluginCenter()
|
||||
let ap = ref<apType>()
|
||||
@@ -41,6 +58,7 @@ let vueApp: App<Element>
|
||||
const miaoMessageRef = ref<InstanceType<typeof miaoAlertTipProvider>>()
|
||||
const aplayer = ref<any>(null)
|
||||
const isAPlayerListShow = ref(true)
|
||||
const isSavingCollection = ref(false)
|
||||
|
||||
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
|
||||
required: true
|
||||
@@ -50,6 +68,18 @@ const props = defineProps<{
|
||||
view: VirtualPage
|
||||
}>()
|
||||
|
||||
const data = ref<collectionType[]>([])
|
||||
|
||||
watch(
|
||||
data,
|
||||
() => {
|
||||
updateLocalData()
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => currentFiles.value,
|
||||
(value, oldValue) => {
|
||||
@@ -80,6 +110,23 @@ const handleDropVFiles = (files: VirtualFile[]) => {
|
||||
currentFiles.value = uniq([...currentFiles.value, ...files])
|
||||
}
|
||||
|
||||
const saveCurrPlayList = () => {
|
||||
if (ap.value) {
|
||||
data.value.unshift({
|
||||
id: Math.random(),
|
||||
name: `新建歌单-${new Date()}`,
|
||||
intro: 'none',
|
||||
createTime: new Date(),
|
||||
coverUrl: '',
|
||||
audios: cloneDeep(ap.value.list.audios)
|
||||
})
|
||||
miaoMessageRef.value?.alertTip('已创建新的歌单', {
|
||||
timeout: 1000,
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const initAPlayerMiao = () => {
|
||||
const aplayerOption = {
|
||||
container: aplayer.value,
|
||||
@@ -103,7 +150,8 @@ const initAPlayerMiao = () => {
|
||||
) as VirtualFile
|
||||
]
|
||||
)
|
||||
}
|
||||
},
|
||||
onSaveCollection: saveCurrPlayList
|
||||
})
|
||||
ap.value.on('listremove', (...args: any) => {
|
||||
const { index } = args[0] as { index: number }
|
||||
@@ -122,7 +170,18 @@ const initAPlayerMiao = () => {
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const updateLocalData = () => {
|
||||
// console.log('updateLocalData', toRaw(data.value))
|
||||
localForage.setItem('miaoMusic-storage-musicCollections', toRaw(data.value))
|
||||
}
|
||||
const getLocalData = async () => {
|
||||
data.value =
|
||||
(await localForage.getItem('miaoMusic-storage-musicCollections')) ?? []
|
||||
}
|
||||
onBeforeMount(async () => {
|
||||
await getLocalData()
|
||||
})
|
||||
onMounted(async () => {
|
||||
currentFiles.value = [...currentFiles.value.reverse()]
|
||||
initAPlayerMiao()
|
||||
})
|
||||
@@ -331,7 +331,8 @@
|
||||
}
|
||||
|
||||
.aplayer .aplayer-info .aplayer-music .aplayer-title {
|
||||
font-size: 14px
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.aplayer .aplayer-info .aplayer-music .aplayer-author {
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div class="collection-info-container">
|
||||
<div class="collection-info-container-top">
|
||||
<div
|
||||
class="collection-info-container-top-exit"
|
||||
@click="emit('exit')">
|
||||
<ArrowBackOutline></ArrowBackOutline>
|
||||
</div>
|
||||
<div class="collection-info-container-top-title">
|
||||
<span :tooltip="false" v-if="!isEditing">
|
||||
{{ collection.name }}
|
||||
</span>
|
||||
<n-input
|
||||
v-if="isEditing"
|
||||
v-model:value="collection.name"></n-input>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="collection-info-container-top-delete"
|
||||
v-if="isEditing"
|
||||
@click="
|
||||
clickDeleteCount !== 2
|
||||
? clickDeleteCount++
|
||||
: emit('deleteSelf')
|
||||
">
|
||||
<TrashOutline></TrashOutline>
|
||||
<span>
|
||||
{{
|
||||
clickDeleteCount === 0
|
||||
? `删除`
|
||||
: clickDeleteCount === 1
|
||||
? '确认删除'
|
||||
: '再次确认'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collection-info-container-controller">
|
||||
<div
|
||||
class="collection-info-container-controller-play"
|
||||
@click="emit('playAll', collection)">
|
||||
<div class="collection-info-container-controller-play-icon">
|
||||
<CaretForwardOutline></CaretForwardOutline>
|
||||
</div>
|
||||
<div class="collection-info-container-controller-play-text">
|
||||
{{ `全部播放` }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="collection-info-container-controller-right">
|
||||
<div
|
||||
class="collection-info-container-controller-edit"
|
||||
@click="isEditing = !isEditing">
|
||||
<component
|
||||
:is="
|
||||
isEditing ? CheckmarkOutline : CreateOutline
|
||||
"></component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NScrollbar>
|
||||
<div class="collection-info-container-audioList">
|
||||
<VueDraggable
|
||||
v-model="collection.audios"
|
||||
:animation="150"
|
||||
handle=".collection-info-container-audioList-item-controller-item-drag"
|
||||
ghostClass="iSDragging">
|
||||
<div
|
||||
v-for="(audio, index) in collection.audios"
|
||||
class="collection-info-container-audioList-item">
|
||||
<div
|
||||
class="collection-info-container-audioList-item-index">
|
||||
<div>{{ index + 1 }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="collection-info-container-audioList-item-info">
|
||||
<div
|
||||
v-if="!isEditing"
|
||||
class="collection-info-container-audioList-item-info-name">
|
||||
<NEllipsis :tooltip="false">
|
||||
{{ audio.name }}
|
||||
</NEllipsis>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="audio.name"
|
||||
v-if="isEditing"></n-input>
|
||||
</div>
|
||||
<div
|
||||
class="collection-info-container-audioList-item-controller">
|
||||
<PlayOutline
|
||||
class="collection-info-container-audioList-item-controller-item"
|
||||
v-if="!isEditing"
|
||||
@click="
|
||||
emit('playAll', collection, index)
|
||||
"></PlayOutline>
|
||||
<TrashOutline
|
||||
class="collection-info-container-audioList-item-controller-item"
|
||||
v-if="isEditing"
|
||||
@click="handleDelete(index)"></TrashOutline>
|
||||
<ReorderFourOutline
|
||||
v-if="isEditing"
|
||||
class="collection-info-container-audioList-item-controller-item collection-info-container-audioList-item-controller-item-drag"></ReorderFourOutline>
|
||||
</div>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
</NScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NScrollbar, NEllipsis, NInput } from 'naive-ui'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import {
|
||||
ArrowBackOutline,
|
||||
CaretForwardOutline,
|
||||
ReorderFourOutline,
|
||||
PlayOutline,
|
||||
CreateOutline,
|
||||
CheckmarkOutline,
|
||||
TrashOutline
|
||||
} from '@vicons/ionicons5'
|
||||
import collectionType from '../types/collection'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
collection: collectionType
|
||||
}>()
|
||||
|
||||
const isEditing = ref(false)
|
||||
const clickDeleteCount = ref(0)
|
||||
|
||||
watch(isEditing, (v) => v && (clickDeleteCount.value = 0))
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
props.collection.audios.splice(index, 1)
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
exit: []
|
||||
deleteSelf: []
|
||||
playAll: [collection: collectionType, index?: number]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$ig: linear-gradient(90deg, #a1ee5e 0%, #00e3ae 100%);
|
||||
.collection-info-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&-top {
|
||||
height: 70px;
|
||||
padding: 0 15px;
|
||||
background: $ig;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&-exit {
|
||||
width: 30px;
|
||||
aspect-ratio: 1;
|
||||
color: #063b2a;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
svg {
|
||||
width: 25px;
|
||||
aspect-ratio: 1;
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
&:hover {
|
||||
svg {
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-title {
|
||||
display: flex;
|
||||
margin-left: 10px;
|
||||
&-confirm {
|
||||
margin-left: 10px;
|
||||
width: 32px;
|
||||
// color: #ffffff;
|
||||
}
|
||||
span {
|
||||
user-select: none;
|
||||
font-size: larger;
|
||||
}
|
||||
}
|
||||
&-delete {
|
||||
display: flex;
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
color: red;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
& > span {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-controller {
|
||||
height: 50px;
|
||||
padding: 0 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&-play {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& > div {
|
||||
cursor: pointer;
|
||||
}
|
||||
&-icon {
|
||||
height: 30px;
|
||||
width: 45px;
|
||||
background: $ig;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
svg {
|
||||
width: 30px;
|
||||
height: 25px;
|
||||
color: aliceblue;
|
||||
}
|
||||
}
|
||||
&-text {
|
||||
user-select: none;
|
||||
margin-left: 10px;
|
||||
font-size: larger;
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
& > div {
|
||||
margin: 0 10px;
|
||||
cursor: pointer;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
color: #002911;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-audioList {
|
||||
flex: 1;
|
||||
color: #002911;
|
||||
padding-bottom: 88px;
|
||||
&-item {
|
||||
// padding: 0 20px;
|
||||
padding-right: 7px;
|
||||
display: flex;
|
||||
height: 70px;
|
||||
align-items: center;
|
||||
&-index {
|
||||
width: 50px;
|
||||
user-select: none;
|
||||
font-size: 24px;
|
||||
text-align: right;
|
||||
& > div {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
&-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
&-name {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
&-controller {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&-item {
|
||||
width: 30px;
|
||||
cursor: pointer;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.isDragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
+13
-9
@@ -9,7 +9,10 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="aplayer-list-top-buttons">
|
||||
<component :is="AddCircleOutline" class="aplayer-list-top-buttons-icon"></component>
|
||||
<AddCircleOutline
|
||||
v-if="onSaveCollection"
|
||||
class="aplayer-list-top-buttons-icon"
|
||||
@click="onSaveCollection(ap.list.audios)"></AddCircleOutline>
|
||||
</div>
|
||||
<span></span>
|
||||
</div>
|
||||
@@ -43,7 +46,7 @@
|
||||
<div
|
||||
v-if="isVideo(element)"
|
||||
class="aplayer-list-control-btn aplayer-list-control-btn-hover aplayer-list-control-btn-play"
|
||||
@click.stop="onPlayVideo(element)">
|
||||
@click.stop="onPlayVideo && onPlayVideo(element)">
|
||||
<VideocamOutline class="aplayer-list-control-btn-svg" />
|
||||
</div>
|
||||
<div
|
||||
@@ -120,14 +123,14 @@ import {
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
// 与vue-draggable冲突
|
||||
// import miaoLazyDiv from '@/components/miaoLazyDiv.vue';
|
||||
import apType, { audioType } from '../types/ap'
|
||||
import apType, { audioType } from '../../types/ap'
|
||||
import { aplayerMiaoOption } from '../../hooks/aplayerMiao'
|
||||
|
||||
const props = defineProps<{
|
||||
ap: apType
|
||||
onDragItemStart: () => void
|
||||
onDragItemEnd: () => void
|
||||
onPlayVideo: (element: audioType) => void
|
||||
}>()
|
||||
const props = defineProps<
|
||||
{
|
||||
ap: apType
|
||||
} & aplayerMiaoOption
|
||||
>()
|
||||
|
||||
const apRef = ref(props.ap)
|
||||
|
||||
@@ -396,6 +399,7 @@ onMounted(() => {
|
||||
&-icon {
|
||||
width: 20px;
|
||||
aspect-ratio: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
scale: 1.1;
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="collection" @click="emit('onenter')">
|
||||
<div class="collection-cover" @click.stop="emit('onPlay')">
|
||||
<img
|
||||
v-if="item.coverUrl"
|
||||
class="collection-cover-img"
|
||||
:src="item.coverUrl" />
|
||||
<div class="collection-cover-play">
|
||||
<Play></Play>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collection-info">
|
||||
<div class="collection-info-name">
|
||||
<span class="collection-info-name-ellipsis">
|
||||
<span>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="collection-info-stats">
|
||||
<span class="collection-info-stats-time">{{ dateStr }}</span>
|
||||
<span class="collection-info-stats-count">
|
||||
{{ `共${item.audios.length}首` }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import collectionType from '../../types/collection'
|
||||
import dateFormatter from '@/hooks/dateFormatter'
|
||||
import { Play } from '@vicons/ionicons5'
|
||||
|
||||
const props = defineProps<{
|
||||
item: collectionType
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
onPlay: []
|
||||
onenter: []
|
||||
}>()
|
||||
|
||||
const dateStr = computed(() =>
|
||||
dateFormatter(new Date(props.item.createTime), 'yyyy-MM-dd')
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.collection {
|
||||
height: 50px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
transition: height 0.25s ease;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:not(&:last-child) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.collection-cover {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
aspect-ratio: 1;
|
||||
background-color: #60e8a2;
|
||||
margin-right: 5px;
|
||||
.collection-cover-play {
|
||||
width: 25px;
|
||||
height: 15px;
|
||||
background-color: #d1d1d1b2;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
svg {
|
||||
height: 15px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.collection-info {
|
||||
position: relative;
|
||||
width: calc(100% - 60px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
.collection-info-name {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
&-ellipsis {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
span {
|
||||
white-space: nowrap;
|
||||
text-wrap: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
.collection-info-stats {
|
||||
text-wrap: nowrap;
|
||||
span {
|
||||
font-size: smaller;
|
||||
}
|
||||
.collection-info-stats-time {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 29 32">
|
||||
<path
|
||||
d="M9.333 9.333h13.333v4l5.333-5.333-5.333-5.333v4h-16v8h2.667v-5.333zM22.667 22.667h-13.333v-4l-5.333 5.333 5.333 5.333v-4h16v-8h-2.667v5.333z"></path>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,25 +1,133 @@
|
||||
<template>
|
||||
<div class="miaoMusic-main">
|
||||
<div class="fd" v-if="false">
|
||||
{{ fakeData }}
|
||||
</div>
|
||||
<NScrollbar>
|
||||
<div class="miaoMusic-main-collectionsList">
|
||||
<div class="miaoMusic-main-collectionsList-title">自建歌单</div>
|
||||
<VueDraggable
|
||||
v-if="collections"
|
||||
v-model="collections"
|
||||
:animation="150"
|
||||
:delay="100"
|
||||
ghostClass="iSDragging">
|
||||
<collection
|
||||
v-for="item in collections"
|
||||
:item="item"
|
||||
@on-play="handleCollectionsPlay(item)"
|
||||
@onenter="handleCollectionEnter(item)"></collection>
|
||||
</VueDraggable>
|
||||
<n-empty
|
||||
description="空空如也~"
|
||||
size="huge"
|
||||
style="margin-top: 200px"
|
||||
v-if="!data || data?.length === 0">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<HandLeftOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
<template #extra>
|
||||
<n-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="emit('newCollection')">
|
||||
立刻创建
|
||||
</n-button>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
</NScrollbar>
|
||||
<Transition name="slide-fade">
|
||||
<collectionInfo
|
||||
class="miaoMusic-main-collection"
|
||||
:collection="currCollection"
|
||||
v-if="currCollection"
|
||||
@exit="handleCollectionEnter(undefined)"
|
||||
@delete-self="handleCollectionDelete()"
|
||||
@play-all="handleCollectionsPlay"></collectionInfo>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import apType from '../types/ap'
|
||||
import collection from './components/collection.vue'
|
||||
import collectionInfo from './collectionInfo.vue'
|
||||
import collectionType from '../types/collection'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import { NScrollbar, NEmpty, NButton } from 'naive-ui'
|
||||
import { HandLeftOutline } from '@vicons/ionicons5'
|
||||
|
||||
const props = defineProps<{
|
||||
ap: apType
|
||||
}>()
|
||||
|
||||
const fakeData = ref(new Array(10000).fill('我会一直盯着你'))
|
||||
console.log(props.ap)
|
||||
const emit = defineEmits<{
|
||||
newCollection: []
|
||||
}>()
|
||||
|
||||
const collections = defineModel<collectionType[]>('data')
|
||||
const currCollection = ref<collectionType>()
|
||||
const playCollection = (collection: collectionType, index?: number) => {
|
||||
const ap = props.ap
|
||||
console.log({ collection, ap })
|
||||
// clear有bug
|
||||
while (ap.list.audios.length !== 0) {
|
||||
ap.list.remove(0)
|
||||
}
|
||||
ap.list.add(collection.audios)
|
||||
index && ap.list.switch(index)
|
||||
ap.play()
|
||||
}
|
||||
|
||||
const handleCollectionsPlay = (item: collectionType, index?: number) => {
|
||||
playCollection(item, index)
|
||||
}
|
||||
|
||||
const handleCollectionEnter = (item: collectionType | undefined) => {
|
||||
currCollection.value = item
|
||||
}
|
||||
|
||||
const handleCollectionDelete = () => {
|
||||
collections.value &&
|
||||
collections.value.splice(
|
||||
collections.value.findIndex((v) => v === currCollection.value),
|
||||
1
|
||||
)
|
||||
currCollection.value = undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.miaoMusic-main {
|
||||
padding-bottom: 88px;
|
||||
position: relative;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
.miaoMusic-main-collectionsList {
|
||||
padding: 15px 20px 88px;
|
||||
&-title {
|
||||
font-size: larger;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 10px;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.slide-fade-enter-active {
|
||||
transition: all 0.15s ease-out;
|
||||
}
|
||||
|
||||
.slide-fade-leave-active {
|
||||
transition: all 0.15s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
}
|
||||
|
||||
.slide-fade-enter-from,
|
||||
.slide-fade-leave-to {
|
||||
transform: translateX(200px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.isDragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
@@ -18,6 +18,7 @@ type apType = {
|
||||
add: (audios: audioType[]) => void
|
||||
refreshAudioIndex: () => void
|
||||
hide: () => void
|
||||
clear: () => void
|
||||
}
|
||||
container: HTMLDivElement
|
||||
on: (event: string, callback: () => void) => void
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { audioType } from './ap'
|
||||
|
||||
export type collectionItemType = {} & audioType
|
||||
|
||||
export type collectionType = {
|
||||
id: number
|
||||
name: string
|
||||
intro: string
|
||||
createTime: Date
|
||||
coverUrl?: string
|
||||
audios: collectionItemType[]
|
||||
}
|
||||
|
||||
export default collectionType
|
||||
Reference in new issue
Block a user