feat: 部分完成音乐播放器
This commit is contained in:
1 parent
3b09d4c1cb
commit
1f42785b4a
13 files changed
+657
-35
No files matched your search
@@ -29,6 +29,7 @@
|
|||||||
"axios": "^1.7.3",
|
"axios": "^1.7.3",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"naive-ui": "^2.39.0",
|
"naive-ui": "^2.39.0",
|
||||||
|
|||||||
Generated
+22
@@ -59,6 +59,9 @@ importers:
|
|||||||
copy-to-clipboard:
|
copy-to-clipboard:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
|
localforage:
|
||||||
|
specifier: ^1.10.0
|
||||||
|
version: 1.10.0
|
||||||
lodash:
|
lodash:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
@@ -890,6 +893,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
immediate@3.0.6:
|
||||||
|
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||||
|
|
||||||
immutable@4.3.7:
|
immutable@4.3.7:
|
||||||
resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
|
resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
|
||||||
|
|
||||||
@@ -929,9 +935,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
lie@3.1.1:
|
||||||
|
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
|
||||||
|
|
||||||
linkify-it@5.0.0:
|
linkify-it@5.0.0:
|
||||||
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
||||||
|
|
||||||
|
localforage@1.10.0:
|
||||||
|
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
|
||||||
|
|
||||||
lodash-es@4.17.21:
|
lodash-es@4.17.21:
|
||||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||||
|
|
||||||
@@ -2219,6 +2231,8 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
immediate@3.0.6: {}
|
||||||
|
|
||||||
immutable@4.3.7: {}
|
immutable@4.3.7: {}
|
||||||
|
|
||||||
inflight@1.0.6:
|
inflight@1.0.6:
|
||||||
@@ -2250,10 +2264,18 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-docker: 2.2.1
|
is-docker: 2.2.1
|
||||||
|
|
||||||
|
lie@3.1.1:
|
||||||
|
dependencies:
|
||||||
|
immediate: 3.0.6
|
||||||
|
|
||||||
linkify-it@5.0.0:
|
linkify-it@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
uc.micro: 2.1.0
|
uc.micro: 2.1.0
|
||||||
|
|
||||||
|
localforage@1.10.0:
|
||||||
|
dependencies:
|
||||||
|
lie: 3.1.1
|
||||||
|
|
||||||
lodash-es@4.17.21: {}
|
lodash-es@4.17.21: {}
|
||||||
|
|
||||||
lodash@4.17.21: {}
|
lodash@4.17.21: {}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const pluginConfig: registerComponentOption = {
|
|||||||
name: '音乐',
|
name: '音乐',
|
||||||
icon: Disc,
|
icon: Disc,
|
||||||
getComponent: async () =>
|
getComponent: async () =>
|
||||||
(await import('./miaoMusicPlayerDemo.vue')).default,
|
(await import('./miaoMusicPlayer.vue')).default,
|
||||||
filter: (vDirs, vFiles) => {
|
filter: (vDirs, vFiles) => {
|
||||||
if (vDirs.length !== 0) {
|
if (vDirs.length !== 0) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { createApp, Reactive } from "vue";
|
import { createApp, Reactive } from "vue";
|
||||||
import aplayerListNew from "../src/aplayerListNew.vue";
|
import aplayerListNew from "../src/components/aplayerListNew.vue";
|
||||||
import apType, { audioType } from "../types/ap";
|
import apType, { audioType } from "../types/ap";
|
||||||
|
|
||||||
type aplayerMiaoOption = {
|
export type aplayerMiaoOption = {
|
||||||
onDragItemStart?: () => void
|
onDragItemStart?: () => void
|
||||||
onDragItemEnd?: () => void
|
onDragItemEnd?: () => void
|
||||||
onPlayVideo?: (element: audioType) => void
|
onPlayVideo?: (element: audioType) => void
|
||||||
|
onSaveCollection?: (audios: audioType[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+66
-7
@@ -2,14 +2,20 @@
|
|||||||
<miao-drop-handler @on-virtual-files="handleDropVFiles">
|
<miao-drop-handler @on-virtual-files="handleDropVFiles">
|
||||||
<miao-alert-tip-provider ref="miaoMessageRef">
|
<miao-alert-tip-provider ref="miaoMessageRef">
|
||||||
<div class="miaoMusic-container">
|
<div class="miaoMusic-container">
|
||||||
<NScrollbar>
|
<miao-music-main
|
||||||
<miao-music-main v-if="ap" :ap="ap"></miao-music-main>
|
v-if="ap"
|
||||||
</NScrollbar>
|
:ap="ap"
|
||||||
|
v-model:data="data"
|
||||||
|
:updateLocalData="updateLocalData"
|
||||||
|
@new-collection="saveCurrPlayList"></miao-music-main>
|
||||||
<miao-mask
|
<miao-mask
|
||||||
:show="isAPlayerListShow"
|
:show="isAPlayerListShow"
|
||||||
style="z-index: 5"
|
style="z-index: 5"
|
||||||
@click="() => ap?.list.hide()"></miao-mask>
|
@click="() => ap?.list.hide()"></miao-mask>
|
||||||
<div ref="aplayer" style="z-index: 10" />
|
<div ref="aplayer" style="z-index: 10" />
|
||||||
|
<miao-mask
|
||||||
|
:show="isSavingCollection"
|
||||||
|
@click="isSavingCollection = false"></miao-mask>
|
||||||
</div>
|
</div>
|
||||||
</miao-alert-tip-provider>
|
</miao-alert-tip-provider>
|
||||||
</miao-drop-handler>
|
</miao-drop-handler>
|
||||||
@@ -18,8 +24,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useAplayerMiao from './hooks/aplayerMiao'
|
import useAplayerMiao from './hooks/aplayerMiao'
|
||||||
import { VirtualFile } from '@/class/VirtualDirectory'
|
import { VirtualFile } from '@/class/VirtualDirectory'
|
||||||
import { App, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
import {
|
||||||
import { NScrollbar } from 'naive-ui'
|
App,
|
||||||
|
onBeforeMount,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
toRaw,
|
||||||
|
watch
|
||||||
|
} from 'vue'
|
||||||
import './src/APlayer.fix.css'
|
import './src/APlayer.fix.css'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import APlayer from 'APlayer'
|
import APlayer from 'APlayer'
|
||||||
@@ -33,6 +47,9 @@ import usePluginCenter from '@/hooks/usePluginCenter'
|
|||||||
import VirtualPage from '@/class/VirtualPage'
|
import VirtualPage from '@/class/VirtualPage'
|
||||||
import miaoMask from '@/components/miaoMask.vue'
|
import miaoMask from '@/components/miaoMask.vue'
|
||||||
import miaoMusicMain from './src/main.vue'
|
import miaoMusicMain from './src/main.vue'
|
||||||
|
import collectionType from './types/collection'
|
||||||
|
import cloneDeep from 'lodash/cloneDeep'
|
||||||
|
import localForage from 'localforage'
|
||||||
|
|
||||||
const pluginCenter = usePluginCenter()
|
const pluginCenter = usePluginCenter()
|
||||||
let ap = ref<apType>()
|
let ap = ref<apType>()
|
||||||
@@ -41,6 +58,7 @@ let vueApp: App<Element>
|
|||||||
const miaoMessageRef = ref<InstanceType<typeof miaoAlertTipProvider>>()
|
const miaoMessageRef = ref<InstanceType<typeof miaoAlertTipProvider>>()
|
||||||
const aplayer = ref<any>(null)
|
const aplayer = ref<any>(null)
|
||||||
const isAPlayerListShow = ref(true)
|
const isAPlayerListShow = ref(true)
|
||||||
|
const isSavingCollection = ref(false)
|
||||||
|
|
||||||
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
|
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
|
||||||
required: true
|
required: true
|
||||||
@@ -50,6 +68,18 @@ const props = defineProps<{
|
|||||||
view: VirtualPage
|
view: VirtualPage
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const data = ref<collectionType[]>([])
|
||||||
|
|
||||||
|
watch(
|
||||||
|
data,
|
||||||
|
() => {
|
||||||
|
updateLocalData()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => currentFiles.value,
|
() => currentFiles.value,
|
||||||
(value, oldValue) => {
|
(value, oldValue) => {
|
||||||
@@ -80,6 +110,23 @@ const handleDropVFiles = (files: VirtualFile[]) => {
|
|||||||
currentFiles.value = uniq([...currentFiles.value, ...files])
|
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 initAPlayerMiao = () => {
|
||||||
const aplayerOption = {
|
const aplayerOption = {
|
||||||
container: aplayer.value,
|
container: aplayer.value,
|
||||||
@@ -103,7 +150,8 @@ const initAPlayerMiao = () => {
|
|||||||
) as VirtualFile
|
) as VirtualFile
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
onSaveCollection: saveCurrPlayList
|
||||||
})
|
})
|
||||||
ap.value.on('listremove', (...args: any) => {
|
ap.value.on('listremove', (...args: any) => {
|
||||||
const { index } = args[0] as { index: number }
|
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()]
|
currentFiles.value = [...currentFiles.value.reverse()]
|
||||||
initAPlayerMiao()
|
initAPlayerMiao()
|
||||||
})
|
})
|
||||||
@@ -331,7 +331,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.aplayer .aplayer-info .aplayer-music .aplayer-title {
|
.aplayer .aplayer-info .aplayer-music .aplayer-title {
|
||||||
font-size: 14px
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aplayer .aplayer-info .aplayer-music .aplayer-author {
|
.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>
|
||||||
+12
-8
@@ -9,7 +9,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="aplayer-list-top-buttons">
|
<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>
|
</div>
|
||||||
<span></span>
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +46,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="isVideo(element)"
|
v-if="isVideo(element)"
|
||||||
class="aplayer-list-control-btn aplayer-list-control-btn-hover aplayer-list-control-btn-play"
|
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" />
|
<VideocamOutline class="aplayer-list-control-btn-svg" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -120,14 +123,14 @@ import {
|
|||||||
import { VueDraggable } from 'vue-draggable-plus'
|
import { VueDraggable } from 'vue-draggable-plus'
|
||||||
// 与vue-draggable冲突
|
// 与vue-draggable冲突
|
||||||
// import miaoLazyDiv from '@/components/miaoLazyDiv.vue';
|
// 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<{
|
const props = defineProps<
|
||||||
|
{
|
||||||
ap: apType
|
ap: apType
|
||||||
onDragItemStart: () => void
|
} & aplayerMiaoOption
|
||||||
onDragItemEnd: () => void
|
>()
|
||||||
onPlayVideo: (element: audioType) => void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const apRef = ref(props.ap)
|
const apRef = ref(props.ap)
|
||||||
|
|
||||||
@@ -396,6 +399,7 @@ onMounted(() => {
|
|||||||
&-icon {
|
&-icon {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
scale: 1.1;
|
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>
|
<template>
|
||||||
<div class="miaoMusic-main">
|
<div class="miaoMusic-main">
|
||||||
<div class="fd" v-if="false">
|
<NScrollbar>
|
||||||
{{ fakeData }}
|
<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>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import apType from '../types/ap'
|
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<{
|
const props = defineProps<{
|
||||||
ap: apType
|
ap: apType
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const fakeData = ref(new Array(10000).fill('我会一直盯着你'))
|
const emit = defineEmits<{
|
||||||
console.log(props.ap)
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.miaoMusic-main {
|
.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>
|
</style>
|
||||||
@@ -18,6 +18,7 @@ type apType = {
|
|||||||
add: (audios: audioType[]) => void
|
add: (audios: audioType[]) => void
|
||||||
refreshAudioIndex: () => void
|
refreshAudioIndex: () => void
|
||||||
hide: () => void
|
hide: () => void
|
||||||
|
clear: () => void
|
||||||
}
|
}
|
||||||
container: HTMLDivElement
|
container: HTMLDivElement
|
||||||
on: (event: string, callback: () => void) => void
|
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