feat: 歌单支持拖动排序
This commit is contained in:
1 parent
38e1b098d2
commit
55381df4f6
8 files changed
+116
-28
No files matched your search
@@ -36,6 +36,7 @@
|
|||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"vue": "3.5.0",
|
"vue": "3.5.0",
|
||||||
"vue-codemirror": "^6.1.1",
|
"vue-codemirror": "^6.1.1",
|
||||||
|
"vue-draggable-plus": "^0.5.3",
|
||||||
"xgplayer": "^3.0.20",
|
"xgplayer": "^3.0.20",
|
||||||
"xgplayer-subtitles": "^3.0.20"
|
"xgplayer-subtitles": "^3.0.20"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+21
@@ -80,6 +80,9 @@ importers:
|
|||||||
vue-codemirror:
|
vue-codemirror:
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1(codemirror@6.0.1(@lezer/common@1.2.1))(vue@3.5.0(typescript@5.5.4))
|
version: 6.1.1(codemirror@6.0.1(@lezer/common@1.2.1))(vue@3.5.0(typescript@5.5.4))
|
||||||
|
vue-draggable-plus:
|
||||||
|
specifier: ^0.5.3
|
||||||
|
version: 0.5.3(@types/sortablejs@1.15.8)
|
||||||
xgplayer:
|
xgplayer:
|
||||||
specifier: ^3.0.20
|
specifier: ^3.0.20
|
||||||
version: 3.0.20(core-js@3.38.1)
|
version: 3.0.20(core-js@3.38.1)
|
||||||
@@ -523,6 +526,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-TRdIPqdsvKmPla44kVy4jv5Nt5vjMfVjbIEke1CRULIrwKNRC4lIiZvNYDJvbUMNCFPNIUcOKhXTyMJrX18IMA==}
|
resolution: {integrity: sha512-TRdIPqdsvKmPla44kVy4jv5Nt5vjMfVjbIEke1CRULIrwKNRC4lIiZvNYDJvbUMNCFPNIUcOKhXTyMJrX18IMA==}
|
||||||
deprecated: This is a stub types definition. pdfjs-dist provides its own type definitions, so you do not need this installed.
|
deprecated: This is a stub types definition. pdfjs-dist provides its own type definitions, so you do not need this installed.
|
||||||
|
|
||||||
|
'@types/sortablejs@1.15.8':
|
||||||
|
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==}
|
||||||
|
|
||||||
'@vicons/ionicons5@0.12.0':
|
'@vicons/ionicons5@0.12.0':
|
||||||
resolution: {integrity: sha512-Iy1EUVRpX0WWxeu1VIReR1zsZLMc4fqpt223czR+Rpnrwu7pt46nbnC2ycO7ItI/uqDLJxnbcMC7FujKs9IfFA==}
|
resolution: {integrity: sha512-Iy1EUVRpX0WWxeu1VIReR1zsZLMc4fqpt223czR+Rpnrwu7pt46nbnC2ycO7ItI/uqDLJxnbcMC7FujKs9IfFA==}
|
||||||
|
|
||||||
@@ -1207,6 +1213,15 @@ packages:
|
|||||||
codemirror: 6.x
|
codemirror: 6.x
|
||||||
vue: 3.x
|
vue: 3.x
|
||||||
|
|
||||||
|
vue-draggable-plus@0.5.3:
|
||||||
|
resolution: {integrity: sha512-dwKDzZ8io3y7k2iuIwVwiGrdiq5C0S7Et7nt5Gz5KjpBS9MtZGFP+L4FJPWGSLYleOT8HmVuCXTZYjGet7wC0g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/sortablejs': ^1.15.0
|
||||||
|
'@vue/composition-api': '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@vue/composition-api':
|
||||||
|
optional: true
|
||||||
|
|
||||||
vue-tsc@2.0.29:
|
vue-tsc@2.0.29:
|
||||||
resolution: {integrity: sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==}
|
resolution: {integrity: sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1682,6 +1697,8 @@ snapshots:
|
|||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@types/sortablejs@1.15.8': {}
|
||||||
|
|
||||||
'@vicons/ionicons5@0.12.0': {}
|
'@vicons/ionicons5@0.12.0': {}
|
||||||
|
|
||||||
'@vitejs/plugin-vue@5.1.2(vite@5.4.0(@types/node@22.1.0)(sass@1.77.8))(vue@3.5.0(typescript@5.5.4))':
|
'@vitejs/plugin-vue@5.1.2(vite@5.4.0(@types/node@22.1.0)(sass@1.77.8))(vue@3.5.0(typescript@5.5.4))':
|
||||||
@@ -2466,6 +2483,10 @@ snapshots:
|
|||||||
codemirror: 6.0.1(@lezer/common@1.2.1)
|
codemirror: 6.0.1(@lezer/common@1.2.1)
|
||||||
vue: 3.5.0(typescript@5.5.4)
|
vue: 3.5.0(typescript@5.5.4)
|
||||||
|
|
||||||
|
vue-draggable-plus@0.5.3(@types/sortablejs@1.15.8):
|
||||||
|
dependencies:
|
||||||
|
'@types/sortablejs': 1.15.8
|
||||||
|
|
||||||
vue-tsc@2.0.29(typescript@5.5.4):
|
vue-tsc@2.0.29(typescript@5.5.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@volar/typescript': 2.4.0-alpha.18
|
'@volar/typescript': 2.4.0-alpha.18
|
||||||
|
|||||||
+4
-4
@@ -135,9 +135,9 @@ provide('rootDirectory', rootDirectory)
|
|||||||
: view.title
|
: view.title
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<div class="tab-point" :style="{ backgroundColor: view.color }"></div>
|
|
||||||
<div class="tab-control">
|
<div class="tab-control">
|
||||||
<n-icon class="tag-control-icon icon" @click="view.switchShow()" v-if="view.allowCopy">
|
<div class="tab-point" :style="{ backgroundColor: view.color }"></div>
|
||||||
|
<n-icon class="tag-control-icon icon" @click="view.switchShow()">
|
||||||
<EyeOutline v-show="view.visible" class="icon-inner" />
|
<EyeOutline v-show="view.visible" class="icon-inner" />
|
||||||
<EyeOffOutline v-show="!view.visible" class="icon-inner" />
|
<EyeOffOutline v-show="!view.visible" class="icon-inner" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@@ -148,7 +148,7 @@ provide('rootDirectory', rootDirectory)
|
|||||||
view.currentFiles,
|
view.currentFiles,
|
||||||
index + 1
|
index + 1
|
||||||
)
|
)
|
||||||
">
|
" v-if="view.allowCopy">
|
||||||
<CopyOutline />
|
<CopyOutline />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-icon class="tag-control-icon icon" @click="deleteView(index)">
|
<n-icon class="tag-control-icon icon" @click="deleteView(index)">
|
||||||
@@ -241,7 +241,7 @@ $tag-width: 170px;
|
|||||||
height: 5px;
|
height: 5px;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border-radius: 66px;
|
border-radius: 66px;
|
||||||
margin-left: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-control {
|
.tab-control {
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory'
|
|||||||
import config from '@/config'
|
import config from '@/config'
|
||||||
import useDataBus from '@/hooks/useDataBus'
|
import useDataBus from '@/hooks/useDataBus'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
disable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const dataBus = useDataBus()
|
const dataBus = useDataBus()
|
||||||
|
|
||||||
const { uploadSizeLimit } = config
|
const { uploadSizeLimit } = config
|
||||||
@@ -57,8 +64,16 @@ const removeDirectory = async (files: File[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const check = (event: DragEvent) => [
|
||||||
|
...(event.dataTransfer?.files ?? []),
|
||||||
|
...(dataBus.get('dragData_vFiles') ?? []),
|
||||||
|
...(dataBus.get('dragData_vDirectory') ?? [])
|
||||||
|
].length !== 0
|
||||||
|
|
||||||
const handleDragOver = async (event: DragEvent) => {
|
const handleDragOver = async (event: DragEvent) => {
|
||||||
|
if (props.disable || check(event) === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (showMask.value === 1) {
|
if (showMask.value === 1) {
|
||||||
return
|
return
|
||||||
@@ -71,6 +86,9 @@ const handleDragLeave = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDrop = async (event: DragEvent) => {
|
const handleDrop = async (event: DragEvent) => {
|
||||||
|
if (props.disable || check(event) === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
showMask.value = 0
|
showMask.value = 0
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export default function useDataBus() {
|
|||||||
const value = dataStorage[key];
|
const value = dataStorage[key];
|
||||||
dataStorage[key] = undefined;
|
dataStorage[key] = undefined;
|
||||||
return value;
|
return value;
|
||||||
|
},
|
||||||
|
get(key: string | number) {
|
||||||
|
const value = dataStorage[key];
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,15 @@ import { createApp, Reactive } from "vue";
|
|||||||
import aplayerListNew from "../src/aplayerListNew.vue";
|
import aplayerListNew from "../src/aplayerListNew.vue";
|
||||||
import apType from "../types/ap";
|
import apType from "../types/ap";
|
||||||
|
|
||||||
|
type aplayerMiaoOption = {
|
||||||
|
onDragItemStart?: () => void,
|
||||||
|
onDragItemEnd?: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对aplayer进行一些自定义的风格优化
|
* 对aplayer进行一些自定义的风格优化
|
||||||
*/
|
*/
|
||||||
const useAplayerMiao = (ap: Reactive<apType>) => {
|
const useAplayerMiao = (ap: Reactive<apType>, options?: aplayerMiaoOption) => {
|
||||||
const container = ap.container as HTMLDivElement;
|
const container = ap.container as HTMLDivElement;
|
||||||
|
|
||||||
// 隐藏旧版歌单
|
// 隐藏旧版歌单
|
||||||
@@ -30,7 +35,7 @@ const useAplayerMiao = (ap: Reactive<apType>) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
container.appendChild(new_list_dom);
|
container.appendChild(new_list_dom);
|
||||||
const app = createApp(aplayerListNew, { ap });
|
const app = createApp(aplayerListNew, { ap, ...(options ?? {}) });
|
||||||
app.mount(new_list_dom);
|
app.mount(new_list_dom);
|
||||||
|
|
||||||
ap.on("destroy", () => {
|
ap.on("destroy", () => {
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ const handleDropVFiles = (files: VirtualFile[]) => {
|
|||||||
|
|
||||||
const aplayer = ref<any>(null);
|
const aplayer = ref<any>(null);
|
||||||
|
|
||||||
|
const disableDropReceiver = ref(false)
|
||||||
|
|
||||||
let ap: apType
|
let ap: apType
|
||||||
let vueApp: any
|
let vueApp: any
|
||||||
|
|
||||||
@@ -59,7 +61,14 @@ onMounted(() => {
|
|||||||
theme: "#60e8a2"
|
theme: "#60e8a2"
|
||||||
}
|
}
|
||||||
ap = reactive<apType>(new APlayer(aplayerOption))
|
ap = reactive<apType>(new APlayer(aplayerOption))
|
||||||
vueApp = useAplayerMiao(ap)
|
vueApp = useAplayerMiao(ap, {
|
||||||
|
onDragItemStart: () => {
|
||||||
|
disableDropReceiver.value = true
|
||||||
|
},
|
||||||
|
onDragItemEnd: () => {
|
||||||
|
disableDropReceiver.value = false
|
||||||
|
},
|
||||||
|
})
|
||||||
ap.on('listremove', (...args: any) => {
|
ap.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.list.audios[index].name
|
||||||
|
|||||||
@@ -1,33 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<ol :style="{ maxHeight: ap.options.listMaxHeight }" :key="refreshKey">
|
<ol :style="{ maxHeight: ap.options.listMaxHeight }" :key="refreshKey" >
|
||||||
<li v-for="(audio, index) in ap.list.audios" :class="activeAudioIndex == index ? 'aplayer-list-light' : ''"
|
<VueDraggable v-model="ap.list.audios" @start="onDragStart" @end="onDragEnd" item-key="id" handle=".aplayer-list-control-btn-reorder">
|
||||||
class="aplayer-list-new" @click="switchAudio(index)" :key="audio.id">
|
<!-- <template #item="{ element, index }"> -->
|
||||||
<span class="aplayer-list-cur" :style="{ backgroundColor: apTheme }"></span>
|
<li v-for="(element, index) in ap.list.audios" :class="activeAudioIndex == index ? 'aplayer-list-light' : ''"
|
||||||
<span class="aplayer-list-index">{{ index + 1 }}</span>
|
class="aplayer-list-new" @click="switchAudio(index)" :key="element.id">
|
||||||
<span class="aplayer-list-title">{{ audio.name }}</span>
|
<span class="aplayer-list-cur" :style="{ backgroundColor: apTheme }"></span>
|
||||||
<span class="aplayer-list-author" v-show="audio.artist">{{ audio.artist }}</span>
|
<span class="aplayer-list-index">{{ index + 1 }}</span>
|
||||||
<div class="aplayer-list-control">
|
<span class="aplayer-list-title">{{ element.name }}</span>
|
||||||
<div class="aplayer-list-control-btn" @click.stop="listAudioUp(index)">
|
<span class="aplayer-list-author" v-show="element.artist">{{ element.artist }}</span>
|
||||||
<ChevronUpOutline class="aplayer-list-control-btn-svg" />
|
<div class="aplayer-list-control">
|
||||||
</div>
|
<div class="aplayer-list-control-btn" @click.stop="listAudioUp(index)" v-if="false">
|
||||||
<div class="aplayer-list-control-btn" @click.stop="listAudioDown(index)">
|
<ChevronUpOutline class="aplayer-list-control-btn-svg" />
|
||||||
<ChevronDownOutline class="aplayer-list-control-btn-svg" />
|
</div>
|
||||||
</div>
|
<div class="aplayer-list-control-btn" @click.stop="listAudioDown(index)" v-if="false">
|
||||||
<div class="aplayer-list-control-btn" @click.stop="listAudioRemove(index)">
|
<ChevronDownOutline class="aplayer-list-control-btn-svg" />
|
||||||
<CloseOutline class="aplayer-list-control-btn-svg" />
|
</div>
|
||||||
</div>
|
<div class="aplayer-list-control-btn" @click.stop="listAudioRemove(index)">
|
||||||
</div>
|
<CloseOutline class="aplayer-list-control-btn-svg" />
|
||||||
</li>
|
</div>
|
||||||
|
<div class="aplayer-list-control-btn aplayer-list-control-btn-reorder">
|
||||||
|
<ReorderFourOutline class="aplayer-list-control-btn-svg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<!-- </template> -->
|
||||||
|
</VueDraggable>
|
||||||
</ol>
|
</ol>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { ChevronUpOutline, ChevronDownOutline, CloseOutline } from '@vicons/ionicons5';
|
import { ChevronUpOutline, ChevronDownOutline, CloseOutline, ReorderFourOutline } from '@vicons/ionicons5';
|
||||||
|
import { VueDraggable } from 'vue-draggable-plus'
|
||||||
import apType, { audioType } from '../types/ap';
|
import apType, { audioType } from '../types/ap';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
ap: apType
|
ap: apType
|
||||||
|
onDragItemStart: () => void,
|
||||||
|
onDragItemEnd: () => void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const refreshKey = ref(Math.random())
|
const refreshKey = ref(Math.random())
|
||||||
@@ -97,7 +107,21 @@ const listAudioDown = (index: number) => {
|
|||||||
const listAudioRemove = (index: number) => {
|
const listAudioRemove = (index: number) => {
|
||||||
props.ap.list.remove(index)
|
props.ap.list.remove(index)
|
||||||
}
|
}
|
||||||
|
const {onDragStart, onDragEnd} = (() => {
|
||||||
|
let curr: audioType
|
||||||
|
return {
|
||||||
|
onDragStart: () => {
|
||||||
|
curr = props.ap.list.audios[props.ap.list.index]
|
||||||
|
},
|
||||||
|
onDragEnd: () => {
|
||||||
|
listRefreshIndex(curr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
// const onDragEnd = (_e: any) => {
|
||||||
|
// const curr = props.ap.list.audios[props.ap.list.index]
|
||||||
|
// listRefreshIndex(curr)
|
||||||
|
// }
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log(props.ap)
|
console.log(props.ap)
|
||||||
@@ -105,6 +129,12 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.aplayer-list-new {
|
||||||
|
height: fit-content !important;
|
||||||
|
.aplayer-list-cur {
|
||||||
|
height: calc( 100% - 8px ) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
.aplayer-list-control {
|
.aplayer-list-control {
|
||||||
float: right;
|
float: right;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|||||||
Reference in new issue
Block a user