feat: 歌单加了个顶部栏

This commit is contained in:
539943419 committed 2024-09-29 17:14:02 +08:00
1 parent ae476413df
commit 2e75a35d10
9 files changed
+296 -77

No files matched your search

+1 -2
View File
@@ -21,8 +21,7 @@
| 长期计划 | | 长期计划 |
|---| |---|
| 重构virtualDirectory,使其不用直接拥有父文件夹 | | 无 |
| 重构virtualDirectory,使其不用直接拥有父文件夹 |
## 碎碎念: ## 碎碎念:
+1
View File
@@ -89,6 +89,7 @@ defineExpose({ alertTip });
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
overflow: hidden;
.miao-message-provider-container { .miao-message-provider-container {
position: absolute; position: absolute;
@@ -39,7 +39,7 @@ const useAplayerMiao = (ap: Reactive<apType>, options?: aplayerMiaoOption) => {
const app = createApp(aplayerListNew, { ap, ...(options ?? {}) }); const app = createApp(aplayerListNew, { ap, ...(options ?? {}) });
app.mount(new_list_dom); app.mount(new_list_dom);
console.log({ap}) // console.log({ap})
ap.on("destroy", () => { ap.on("destroy", () => {
app.unmount(); app.unmount();
@@ -1,113 +1,145 @@
<template> <template>
<miao-drop-handler @on-virtual-files="handleDropVFiles"> <miao-drop-handler @on-virtual-files="handleDropVFiles">
<miao-alertTip-provider ref="miaoMessageRef"> <miao-alert-tip-provider ref="miaoMessageRef">
<div class="miaoMusic-container"> <div class="miaoMusic-container">
<NScrollbar> <NScrollbar>
<div class="miaoMusic-main"> <miao-music-main v-if="ap" :ap="ap"></miao-music-main>
</div>
</NScrollbar> </NScrollbar>
<div ref="aplayer" /> <miao-mask
:show="isAPlayerListShow"
style="z-index: 5"
@click="() => ap?.list.hide()"></miao-mask>
<div ref="aplayer" style="z-index: 10" />
</div> </div>
</miao-alertTip-provider> </miao-alert-tip-provider>
</miao-drop-handler> </miao-drop-handler>
</template> </template>
<script setup lang="ts"> <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 './src/APlayer.fix.css'
// @ts-ignore
import APlayer from 'APlayer'
import config from '@/config'
import difference from 'lodash/difference'
import uniq from 'lodash/uniq'
import apType from './types/ap'
import miaoDropHandler from '@/components/miaoDropHandler.vue' import miaoDropHandler from '@/components/miaoDropHandler.vue'
import miaoAlertTipProvider from '@/components/miaoAlertTipProvider.vue' import miaoAlertTipProvider from '@/components/miaoAlertTipProvider.vue'
import useAplayerMiao from './hooks/aplayerMiao'; import usePluginCenter from '@/hooks/usePluginCenter'
import { VirtualFile } from '@/class/VirtualDirectory'; import VirtualPage from '@/class/VirtualPage'
import { App, onMounted, onUnmounted, reactive, ref, watch } from 'vue'; import miaoMask from '@/components/miaoMask.vue'
import { NScrollbar } from 'naive-ui'; import miaoMusicMain from './src/main.vue'
import "./src/APlayer.fix.css"
// @ts-ignore
import APlayer from 'APlayer';
import config from '@/config';
import difference from 'lodash/difference';
import uniq from 'lodash/uniq'
import apType from './types/ap';
import usePluginCenter from '@/hooks/usePluginCenter';
const pluginCenter = usePluginCenter() const pluginCenter = usePluginCenter()
let ap = ref<apType>()
let vueApp: App<Element>
const miaoMessageRef = ref<InstanceType<typeof miaoAlertTipProvider>>() const miaoMessageRef = ref<InstanceType<typeof miaoAlertTipProvider>>()
const aplayer = ref<any>(null)
const isAPlayerListShow = ref(true)
const currentFiles = defineModel<VirtualFile[]>('currentFiles', { const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
required: true required: true
}); })
watch(() => currentFiles.value, (value, oldValue) => { const props = defineProps<{
view: VirtualPage
}>()
watch(
() => currentFiles.value,
(value, oldValue) => {
const diff = difference(value, oldValue) const diff = difference(value, oldValue)
if (diff.length > 0) { if (diff.length > 0) {
const _s = `等${diff.length}个资源` const _s = `等${diff.length}个资源`
miaoMessageRef.value?.alertTip(`已添加 ${diff[0].name}${diff.length !== 1 ? _s : ''}`, { miaoMessageRef.value?.alertTip(
`已添加 ${diff[0].name}${diff.length !== 1 ? _s : ''}`,
{
type: 'success', type: 'success',
timeout: 3000 timeout: 3000
})
ap.list.add(getMusicList(diff))
} }
}) )
ap?.value?.list.add(getMusicList(diff))
}
}
)
const getMusicList = (mp3VFiles: VirtualFile[]) => mp3VFiles.map((v, index) => ({ const getMusicList = (mp3VFiles: VirtualFile[]) =>
mp3VFiles.map((v, index) => ({
url: `${config.baseUrl}${v.url}`, url: `${config.baseUrl}${v.url}`,
name: v.name, name: v.name,
artist: ' ', artist: ' ',
index, index
})); }))
const handleDropVFiles = (files: VirtualFile[]) => { const handleDropVFiles = (files: VirtualFile[]) => {
currentFiles.value = uniq([...currentFiles.value, ...files]); currentFiles.value = uniq([...currentFiles.value, ...files])
}; }
const aplayer = ref<any>(null); const initAPlayerMiao = () => {
let ap: apType
let vueApp: App<Element>
onMounted(() => {
currentFiles.value = [...currentFiles.value.reverse()]
const aplayerOption = { const aplayerOption = {
container: aplayer.value, container: aplayer.value,
audio: getMusicList(currentFiles.value), audio: getMusicList(currentFiles.value),
autoplay: true, autoplay: true,
volume: 1, volume: 1,
theme: "#60e8a2", theme: '#60e8a2',
listMaxHeight: '500px' listMaxHeight: '500px'
} }
ap = reactive<apType>(new APlayer(aplayerOption)) ap.value = reactive<apType>(new APlayer(aplayerOption))
vueApp = useAplayerMiao(ap, { vueApp = useAplayerMiao(ap.value, {
onDragItemStart: () => { onDragItemStart: () => {},
}, onDragItemEnd: () => {},
onDragItemEnd: () => { onPlayVideo: (element) => {
}, pluginCenter.usePlugin(
onPlayVideo: element => { 'miaoVideoPlayer',
pluginCenter.usePlugin('miaoVideoPlayer', [], [currentFiles.value.find(v=>v.name === element.name) as VirtualFile]) [],
[
currentFiles.value.find(
(v) => v.name === element.name
) as VirtualFile
]
)
} }
}) })
ap.on('listremove', (...args: any) => { ap.value.on('listremove', (...args: any) => {
const { index } = args[0] as { index: number } const { index } = args[0] as { index: number }
const _name = ap.list.audios[index].name const _name = ap?.value?.list.audios[index].name
currentFiles.value = currentFiles.value.filter(v => v.name !== _name) currentFiles.value = currentFiles.value.filter((v) => v.name !== _name)
})
ap.value.on('play', () => {
const name = ap.value?.list.audios[ap.value.list.index].name as string
props.view.setTitle(name)
})
ap.value.on('listshow', () => {
isAPlayerListShow.value = true
})
ap.value.on('listhide', () => {
isAPlayerListShow.value = false
})
}
onMounted(() => {
currentFiles.value = [...currentFiles.value.reverse()]
initAPlayerMiao()
}) })
});
onUnmounted(() => { onUnmounted(() => {
vueApp.unmount() vueApp.unmount()
ap.destroy() ap?.value?.destroy()
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.miaoMusic-container { .miaoMusic-container {
position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.miaoMusic-main {
padding-bottom: 88px;
}
} }
</style> </style>
<style lang="scss"> <style lang="scss">
@@ -117,5 +149,9 @@ onUnmounted(() => {
position: absolute; position: absolute;
bottom: 10px; bottom: 10px;
width: -webkit-fill-available; width: -webkit-fill-available;
.aplayer-icon-order,
.aplayer-icon-loop {
display: none !important;
}
} }
</style> </style>
@@ -3,7 +3,7 @@
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
margin: 5px; margin: 5px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .07), 0 1px 5px 0 rgba(0, 0, 0, .1); box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .07), 0 1px 5px 0 rgba(0, 0, 0, .1);
border-radius: 2px; border-radius: 5px;
overflow: hidden; overflow: hidden;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
@@ -22,10 +22,10 @@
height: 100% height: 100%
} }
.aplayer svg circle, /* .aplayer svg circle,
.aplayer svg path { .aplayer svg path {
fill: #fff fill: #fff
} } */
.aplayer.aplayer-withlist .aplayer-info { .aplayer.aplayer-withlist .aplayer-info {
border-bottom: 1px solid #e9e9e9 border-bottom: 1px solid #e9e9e9
@@ -1,4 +1,18 @@
<template> <template>
<div class="aplayer-list-top">
<div @click="loopModeToggle" class="aplayer-list-top-loopMode">
<component
:is="loopModeMap[loopMode].icon"
class="aplayer-list-top-loopMode-icon"></component>
<span class="aplayer-list-top-loopMode-name">
{{ loopModeMap[loopMode].name }}
</span>
</div>
<div class="aplayer-list-top-buttons">
<component :is="AddCircleOutline" class="aplayer-list-top-buttons-icon"></component>
</div>
<span></span>
</div>
<ol <ol
:style="{ maxHeight: ap.options.listMaxHeight }" :style="{ maxHeight: ap.options.listMaxHeight }"
:class="[ :class="[
@@ -35,7 +49,13 @@
<div <div
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="switchAudio(index)"> @click.stop="switchAudio(index)">
<PlayOutline class="aplayer-list-control-btn-svg" /> <component
:is="
activeAudioIndex == index && !isPaused
? PauseOutline
: PlayOutline
"
class="aplayer-list-control-btn-svg"></component>
</div> </div>
<div <div
class="aplayer-list-control-btn aplayer-list-control-btn-hover" class="aplayer-list-control-btn aplayer-list-control-btn-hover"
@@ -88,8 +108,15 @@ import {
ReorderFourOutline, ReorderFourOutline,
PlayOutline, PlayOutline,
HeartOutline, HeartOutline,
VideocamOutline VideocamOutline,
PauseOutline,
ListCircleOutline,
ListOutline,
HeadsetOutline,
LogoOctocat,
AddCircleOutline
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
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';
@@ -102,6 +129,8 @@ const props = defineProps<{
onPlayVideo: (element: audioType) => void onPlayVideo: (element: audioType) => void
}>() }>()
const apRef = ref(props.ap)
const ol = ref<HTMLOListElement>() const ol = ref<HTMLOListElement>()
const refreshKey = ref(Math.random()) const refreshKey = ref(Math.random())
@@ -115,6 +144,17 @@ const apTheme = computed(
) )
const activeAudioIndex = computed(() => props.ap.list.index) const activeAudioIndex = computed(() => props.ap.list.index)
const isPaused = ref(false)
props.ap.on('play', () => {
nextTick(() => {
isPaused.value = false
})
})
props.ap.on('pause', () => {
isPaused.value = true
})
props.ap.on('listswitch', (...args: any) => { props.ap.on('listswitch', (...args: any) => {
// 手动触发更新,不然监听不到 // 手动触发更新,不然监听不到
@@ -136,6 +176,12 @@ props.ap.on('listadd', refresh)
const switchAudio = (index: number) => { const switchAudio = (index: number) => {
if (index === activeAudioIndex.value) { if (index === activeAudioIndex.value) {
if (props.ap.paused) {
props.ap.play()
} else {
props.ap.pause()
}
props.ap.paused = props.ap.paused
return return
} }
props.ap.list.switch(index) props.ap.list.switch(index)
@@ -207,9 +253,52 @@ const isVideo = (element: audioType) => {
} }
return false return false
} }
const loopModeMap = {
0: { name: '列表循环', icon: ListCircleOutline },
1: { name: '单曲循环', icon: HeadsetOutline },
2: { name: '列表播放', icon: ListOutline },
3: { name: '随机播放', icon: LogoOctocat }
}
const loopMode = computed(() => {
if (apRef.value.options.order === 'list') {
switch (apRef.value.options.loop) {
case 'all':
return 0
case 'one':
return 1
case 'none':
return 2
}
}
return 3
})
const loopModeToggle = () => {
const ops = apRef.value.options
if (ops.order === 'random') {
ops.order = 'list'
} else {
switch (ops.loop) {
case 'all': {
ops.loop = 'one'
break
}
case 'one': {
ops.loop = 'none'
break
}
case 'none': {
ops.loop = 'all'
ops.order = 'random'
break
}
}
}
}
onMounted(() => { onMounted(() => {
// console.log(props.ap) console.log(props.ap)
}) })
</script> </script>
@@ -259,6 +348,60 @@ onMounted(() => {
} }
} }
} }
.aplayer-list-top {
height: 30px;
// width: auto;
// max-width: 100%;
position: relative;
border-bottom: 1px solid #e9e9e9;
display: flex;
justify-items: baseline;
align-items: center;
padding: 0 10px;
.aplayer-list-top-loopMode {
text-align: center;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
float: left;
// background-color: #f9f9f9;
scale: 1;
transition: scale 0.15s ease;
&-icon {
width: 20px;
aspect-ratio: 1;
transform: translateY(1px);
fill: #000;
path {
fill: #000 !important;
}
}
&-name {
margin-left: 5px;
}
&:hover {
scale: 1.1;
}
}
.aplayer-list-top-buttons {
display: inline-flex;
position: absolute;
right: 10px;
align-items: center;
justify-content: center;
scale: 1;
transition: scale 0.15s ease;
&-icon {
width: 20px;
aspect-ratio: 1;
}
&:hover {
scale: 1.1;
}
}
}
</style> </style>
<style lang="scss"> <style lang="scss">
@@ -0,0 +1,11 @@
<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>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class="miaoMusic-main">
<div class="fd" v-if="false">
{{ fakeData }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import apType from '../types/ap'
const props = defineProps<{
ap: apType
}>()
const fakeData = ref(new Array(10000).fill('我会一直盯着你'))
console.log(props.ap)
</script>
<style lang="scss" scoped>
.miaoMusic-main {
padding-bottom: 88px;
}
</style>
+18 -14
View File
@@ -9,6 +9,7 @@ export type audioType = {
} }
type apType = { type apType = {
paused: boolean
list: { list: {
audios: audioType[] audios: audioType[]
index: number index: number
@@ -16,25 +17,28 @@ type apType = {
remove: (index: number) => void remove: (index: number) => void
add: (audios: audioType[]) => void add: (audios: audioType[]) => void
refreshAudioIndex: () => void refreshAudioIndex: () => void
hide: () => void
} }
container: HTMLDivElement container: HTMLDivElement
on: (event: string, callback: () => void) => void on: (event: string, callback: () => void) => void
options: { options: {
audio: audioType[], audio: audioType[]
autoplay: boolean, autoplay: boolean
"volume": number, volume: number
"theme": string, theme: string
"mini": boolean, mini: boolean
"fixed": boolean, fixed: boolean
"mutex": boolean, mutex: boolean
"lrcType": number, lrcType: number
"preload": "auto", preload: 'auto'
"loop": "all", loop: 'all' | 'one' | 'none'
"order": "list", order: 'list' | 'random'
"listMaxHeight": string, listMaxHeight: string
"storageName": "aplayer-setting" storageName: 'aplayer-setting'
}, }
destroy: () => {} destroy: () => {}
pause: () => void
play: () => void
} }
export default apType export default apType