feat: 歌单加了个顶部栏
This commit is contained in:
1 parent
ae476413df
commit
2e75a35d10
9 files changed
+306
-87
No files matched your search
@@ -21,8 +21,7 @@
|
||||
|
||||
| 长期计划 |
|
||||
|---|
|
||||
| 重构virtualDirectory,使其不用直接拥有父文件夹 |
|
||||
| 重构virtualDirectory,使其不用直接拥有父文件夹 |
|
||||
| 无 |
|
||||
|
||||
## 碎碎念:
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ defineExpose({ alertTip });
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.miao-message-provider-container {
|
||||
position: absolute;
|
||||
|
||||
@@ -39,7 +39,7 @@ const useAplayerMiao = (ap: Reactive<apType>, options?: aplayerMiaoOption) => {
|
||||
const app = createApp(aplayerListNew, { ap, ...(options ?? {}) });
|
||||
app.mount(new_list_dom);
|
||||
|
||||
console.log({ap})
|
||||
// console.log({ap})
|
||||
|
||||
ap.on("destroy", () => {
|
||||
app.unmount();
|
||||
|
||||
@@ -1,113 +1,145 @@
|
||||
<template>
|
||||
<miao-drop-handler @on-virtual-files="handleDropVFiles">
|
||||
<miao-alertTip-provider ref="miaoMessageRef">
|
||||
<miao-alert-tip-provider ref="miaoMessageRef">
|
||||
<div class="miaoMusic-container">
|
||||
<NScrollbar>
|
||||
<div class="miaoMusic-main">
|
||||
</div>
|
||||
<miao-music-main v-if="ap" :ap="ap"></miao-music-main>
|
||||
</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>
|
||||
</miao-alertTip-provider>
|
||||
</miao-alert-tip-provider>
|
||||
</miao-drop-handler>
|
||||
</template>
|
||||
|
||||
<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 miaoAlertTipProvider from '@/components/miaoAlertTipProvider.vue'
|
||||
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 usePluginCenter from '@/hooks/usePluginCenter';
|
||||
import usePluginCenter from '@/hooks/usePluginCenter'
|
||||
import VirtualPage from '@/class/VirtualPage'
|
||||
import miaoMask from '@/components/miaoMask.vue'
|
||||
import miaoMusicMain from './src/main.vue'
|
||||
|
||||
const pluginCenter = usePluginCenter()
|
||||
let ap = ref<apType>()
|
||||
let vueApp: App<Element>
|
||||
|
||||
const miaoMessageRef = ref<InstanceType<typeof miaoAlertTipProvider>>()
|
||||
const aplayer = ref<any>(null)
|
||||
const isAPlayerListShow = ref(true)
|
||||
|
||||
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
|
||||
required: true
|
||||
});
|
||||
|
||||
watch(() => currentFiles.value, (value, oldValue) => {
|
||||
const diff = difference(value, oldValue)
|
||||
if (diff.length > 0) {
|
||||
const _s = `等${diff.length}个资源`
|
||||
miaoMessageRef.value?.alertTip(`已添加 ${diff[0].name}${diff.length !== 1 ? _s : ''}`, {
|
||||
type: 'success',
|
||||
timeout: 3000
|
||||
})
|
||||
ap.list.add(getMusicList(diff))
|
||||
}
|
||||
})
|
||||
|
||||
const getMusicList = (mp3VFiles: VirtualFile[]) => mp3VFiles.map((v, index) => ({
|
||||
url: `${config.baseUrl}${v.url}`,
|
||||
name: v.name,
|
||||
artist: ' ',
|
||||
index,
|
||||
}));
|
||||
const props = defineProps<{
|
||||
view: VirtualPage
|
||||
}>()
|
||||
|
||||
watch(
|
||||
() => currentFiles.value,
|
||||
(value, oldValue) => {
|
||||
const diff = difference(value, oldValue)
|
||||
if (diff.length > 0) {
|
||||
const _s = `等${diff.length}个资源`
|
||||
miaoMessageRef.value?.alertTip(
|
||||
`已添加 ${diff[0].name}${diff.length !== 1 ? _s : ''}`,
|
||||
{
|
||||
type: 'success',
|
||||
timeout: 3000
|
||||
}
|
||||
)
|
||||
ap?.value?.list.add(getMusicList(diff))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const getMusicList = (mp3VFiles: VirtualFile[]) =>
|
||||
mp3VFiles.map((v, index) => ({
|
||||
url: `${config.baseUrl}${v.url}`,
|
||||
name: v.name,
|
||||
artist: ' ',
|
||||
index
|
||||
}))
|
||||
|
||||
const handleDropVFiles = (files: VirtualFile[]) => {
|
||||
currentFiles.value = uniq([...currentFiles.value, ...files]);
|
||||
};
|
||||
currentFiles.value = uniq([...currentFiles.value, ...files])
|
||||
}
|
||||
|
||||
const aplayer = ref<any>(null);
|
||||
|
||||
let ap: apType
|
||||
let vueApp: App<Element>
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
currentFiles.value = [...currentFiles.value.reverse()]
|
||||
const initAPlayerMiao = () => {
|
||||
const aplayerOption = {
|
||||
container: aplayer.value,
|
||||
audio: getMusicList(currentFiles.value),
|
||||
autoplay: true,
|
||||
volume: 1,
|
||||
theme: "#60e8a2",
|
||||
theme: '#60e8a2',
|
||||
listMaxHeight: '500px'
|
||||
}
|
||||
ap = reactive<apType>(new APlayer(aplayerOption))
|
||||
vueApp = useAplayerMiao(ap, {
|
||||
onDragItemStart: () => {
|
||||
},
|
||||
onDragItemEnd: () => {
|
||||
},
|
||||
onPlayVideo: element => {
|
||||
pluginCenter.usePlugin('miaoVideoPlayer', [], [currentFiles.value.find(v=>v.name === element.name) as VirtualFile])
|
||||
ap.value = reactive<apType>(new APlayer(aplayerOption))
|
||||
vueApp = useAplayerMiao(ap.value, {
|
||||
onDragItemStart: () => {},
|
||||
onDragItemEnd: () => {},
|
||||
onPlayVideo: (element) => {
|
||||
pluginCenter.usePlugin(
|
||||
'miaoVideoPlayer',
|
||||
[],
|
||||
[
|
||||
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 _name = ap.list.audios[index].name
|
||||
currentFiles.value = currentFiles.value.filter(v => v.name !== _name)
|
||||
const _name = ap?.value?.list.audios[index].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(() => {
|
||||
vueApp.unmount()
|
||||
ap.destroy()
|
||||
ap?.value?.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.miaoMusic-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.miaoMusic-main {
|
||||
padding-bottom: 88px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
@@ -117,5 +149,9 @@ onUnmounted(() => {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
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;
|
||||
margin: 5px;
|
||||
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;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
@@ -22,10 +22,10 @@
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.aplayer svg circle,
|
||||
/* .aplayer svg circle,
|
||||
.aplayer svg path {
|
||||
fill: #fff
|
||||
}
|
||||
} */
|
||||
|
||||
.aplayer.aplayer-withlist .aplayer-info {
|
||||
border-bottom: 1px solid #e9e9e9
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
<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
|
||||
:style="{ maxHeight: ap.options.listMaxHeight }"
|
||||
:class="[
|
||||
@@ -35,7 +49,13 @@
|
||||
<div
|
||||
class="aplayer-list-control-btn aplayer-list-control-btn-hover aplayer-list-control-btn-play"
|
||||
@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
|
||||
class="aplayer-list-control-btn aplayer-list-control-btn-hover"
|
||||
@@ -88,8 +108,15 @@ import {
|
||||
ReorderFourOutline,
|
||||
PlayOutline,
|
||||
HeartOutline,
|
||||
VideocamOutline
|
||||
VideocamOutline,
|
||||
PauseOutline,
|
||||
ListCircleOutline,
|
||||
ListOutline,
|
||||
HeadsetOutline,
|
||||
LogoOctocat,
|
||||
AddCircleOutline
|
||||
} from '@vicons/ionicons5'
|
||||
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
// 与vue-draggable冲突
|
||||
// import miaoLazyDiv from '@/components/miaoLazyDiv.vue';
|
||||
@@ -102,6 +129,8 @@ const props = defineProps<{
|
||||
onPlayVideo: (element: audioType) => void
|
||||
}>()
|
||||
|
||||
const apRef = ref(props.ap)
|
||||
|
||||
const ol = ref<HTMLOListElement>()
|
||||
|
||||
const refreshKey = ref(Math.random())
|
||||
@@ -115,6 +144,17 @@ const apTheme = computed(
|
||||
)
|
||||
|
||||
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) => {
|
||||
// 手动触发更新,不然监听不到
|
||||
@@ -136,6 +176,12 @@ props.ap.on('listadd', refresh)
|
||||
|
||||
const switchAudio = (index: number) => {
|
||||
if (index === activeAudioIndex.value) {
|
||||
if (props.ap.paused) {
|
||||
props.ap.play()
|
||||
} else {
|
||||
props.ap.pause()
|
||||
}
|
||||
props.ap.paused = props.ap.paused
|
||||
return
|
||||
}
|
||||
props.ap.list.switch(index)
|
||||
@@ -207,9 +253,52 @@ const isVideo = (element: audioType) => {
|
||||
}
|
||||
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(() => {
|
||||
// console.log(props.ap)
|
||||
console.log(props.ap)
|
||||
})
|
||||
</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 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>
|
||||
@@ -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>
|
||||
@@ -9,6 +9,7 @@ export type audioType = {
|
||||
}
|
||||
|
||||
type apType = {
|
||||
paused: boolean
|
||||
list: {
|
||||
audios: audioType[]
|
||||
index: number
|
||||
@@ -16,25 +17,28 @@ type apType = {
|
||||
remove: (index: number) => void
|
||||
add: (audios: audioType[]) => void
|
||||
refreshAudioIndex: () => void
|
||||
hide: () => void
|
||||
}
|
||||
container: HTMLDivElement
|
||||
on: (event: string, callback: () => void) => void
|
||||
options: {
|
||||
audio: audioType[],
|
||||
autoplay: boolean,
|
||||
"volume": number,
|
||||
"theme": string,
|
||||
"mini": boolean,
|
||||
"fixed": boolean,
|
||||
"mutex": boolean,
|
||||
"lrcType": number,
|
||||
"preload": "auto",
|
||||
"loop": "all",
|
||||
"order": "list",
|
||||
"listMaxHeight": string,
|
||||
"storageName": "aplayer-setting"
|
||||
},
|
||||
audio: audioType[]
|
||||
autoplay: boolean
|
||||
volume: number
|
||||
theme: string
|
||||
mini: boolean
|
||||
fixed: boolean
|
||||
mutex: boolean
|
||||
lrcType: number
|
||||
preload: 'auto'
|
||||
loop: 'all' | 'one' | 'none'
|
||||
order: 'list' | 'random'
|
||||
listMaxHeight: string
|
||||
storageName: 'aplayer-setting'
|
||||
}
|
||||
destroy: () => {}
|
||||
pause: () => void
|
||||
play: () => void
|
||||
}
|
||||
|
||||
export default apType
|
||||
Reference in new issue
Block a user