Compare commits

3 Commits
Author SHA1 Message Date
539943419 1f42785b4a feat: 部分完成音乐播放器 2024-09-30 17:07:11 +08:00
539943419 3b09d4c1cb feat: 引入rollup-plugin-visualizer 2024-09-30 09:58:33 +08:00
539943419 948b5f23d1 fix: 修复歌单显示不全的问题 2024-09-30 09:39:32 +08:00
15 changed files with 813 additions and 48 deletions

No files matched your search

+2 -1
View File
@@ -27,4 +27,5 @@ serve
miao-directory.exe
miao-directory-amd64-linux
miao-directory-amd64-win.exe
test
test
stats.html
+2
View File
@@ -29,6 +29,7 @@
"axios": "^1.7.3",
"codemirror": "^6.0.1",
"copy-to-clipboard": "^3.3.3",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"markdown-it": "^14.1.0",
"naive-ui": "^2.39.0",
@@ -47,6 +48,7 @@
"@types/node": "^22.1.0",
"@types/pdfjs-dist": "^2.10.378",
"@vitejs/plugin-vue": "^5.1.2",
"rollup-plugin-visualizer": "^5.12.0",
"typescript": "^5.5.3",
"vite": "^5.4.0",
"vite-plugin-singlefile": "^2.0.2",
+169 -8
View File
@@ -59,6 +59,9 @@ importers:
copy-to-clipboard:
specifier: ^3.3.3
version: 3.3.3
localforage:
specifier: ^1.10.0
version: 1.10.0
lodash:
specifier: ^4.17.21
version: 4.17.21
@@ -108,6 +111,9 @@ importers:
'@vitejs/plugin-vue':
specifier: ^5.1.2
version: 5.1.2(vite@5.4.0(@types/node@22.1.0)(sass@1.77.8))(vue@3.5.0(typescript@5.5.4))
rollup-plugin-visualizer:
specifier: ^5.12.0
version: 5.12.0(rollup@4.20.0)
typescript:
specifier: ^5.5.3
version: 5.5.4
@@ -611,6 +617,10 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
@@ -670,9 +680,20 @@ packages:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
codemirror@6.0.1:
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-support@1.1.3:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
hasBin: true
@@ -740,6 +761,10 @@ packages:
resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==}
engines: {node: '>=8'}
define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
engines: {node: '>=8'}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -784,6 +809,10 @@ packages:
engines: {node: '>=12'}
hasBin: true
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
esniff@2.0.1:
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
engines: {node: '>=0.10'}
@@ -837,6 +866,10 @@ packages:
engines: {node: '>=10'}
deprecated: This package is no longer supported.
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -860,6 +893,9 @@ packages:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
immutable@4.3.7:
resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
@@ -874,6 +910,11 @@ packages:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-docker@2.2.1:
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
engines: {node: '>=8'}
hasBin: true
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -890,9 +931,19 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
is-wsl@2.2.0:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
lie@3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
linkify-it@5.0.0:
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:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
@@ -1004,6 +1055,10 @@ packages:
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
open@8.4.2:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
@@ -1055,11 +1110,25 @@ packages:
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rollup-plugin-visualizer@5.12.0:
resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==}
engines: {node: '>=14'}
hasBin: true
peerDependencies:
rollup: 2.x || 3.x || 4.x
peerDependenciesMeta:
rollup:
optional: true
rollup@4.20.0:
resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -1104,6 +1173,10 @@ packages:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
source-map@0.7.4:
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
engines: {node: '>= 8'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -1253,6 +1326,10 @@ packages:
wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -1266,9 +1343,21 @@ packages:
peerDependencies:
core-js: '>=3.12.1'
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
snapshots:
'@babel/helper-string-parser@7.24.8': {}
@@ -1817,8 +1906,11 @@ snapshots:
- supports-color
optional: true
ansi-regex@5.0.1:
optional: true
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
anymatch@3.1.3:
dependencies:
@@ -1899,6 +1991,12 @@ snapshots:
chownr@2.0.0:
optional: true
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
codemirror@6.0.1(@lezer/common@1.2.1):
dependencies:
'@codemirror/autocomplete': 6.18.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.33.0)(@lezer/common@1.2.1)
@@ -1911,6 +2009,12 @@ snapshots:
transitivePeerDependencies:
- '@lezer/common'
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
color-support@1.1.3:
optional: true
@@ -1972,6 +2076,8 @@ snapshots:
mimic-response: 2.1.0
optional: true
define-lazy-prop@2.0.0: {}
delayed-stream@1.0.0: {}
delegate@3.2.0: {}
@@ -1984,8 +2090,7 @@ snapshots:
downloadjs@1.4.7: {}
emoji-regex@8.0.0:
optional: true
emoji-regex@8.0.0: {}
entities@4.5.0: {}
@@ -2035,6 +2140,8 @@ snapshots:
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
escalade@3.2.0: {}
esniff@2.0.1:
dependencies:
d: 1.0.2
@@ -2093,6 +2200,8 @@ snapshots:
wide-align: 1.1.5
optional: true
get-caller-file@2.0.5: {}
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -2122,6 +2231,8 @@ snapshots:
- supports-color
optional: true
immediate@3.0.6: {}
immutable@4.3.7: {}
inflight@1.0.6:
@@ -2137,10 +2248,11 @@ snapshots:
dependencies:
binary-extensions: 2.3.0
is-docker@2.2.1: {}
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0:
optional: true
is-fullwidth-code-point@3.0.0: {}
is-glob@4.0.3:
dependencies:
@@ -2148,10 +2260,22 @@ snapshots:
is-number@7.0.0: {}
is-wsl@2.2.0:
dependencies:
is-docker: 2.2.1
lie@3.1.1:
dependencies:
immediate: 3.0.6
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
localforage@1.10.0:
dependencies:
lie: 3.1.1
lodash-es@4.17.21: {}
lodash@4.17.21: {}
@@ -2279,6 +2403,12 @@ snapshots:
wrappy: 1.0.2
optional: true
open@8.4.2:
dependencies:
define-lazy-prop: 2.0.0
is-docker: 2.2.1
is-wsl: 2.2.0
path-browserify@1.0.1: {}
path-is-absolute@1.0.1:
@@ -2330,11 +2460,22 @@ snapshots:
regenerator-runtime@0.14.1: {}
require-directory@2.1.1: {}
rimraf@3.0.2:
dependencies:
glob: 7.2.3
optional: true
rollup-plugin-visualizer@5.12.0(rollup@4.20.0):
dependencies:
open: 8.4.2
picomatch: 2.3.1
source-map: 0.7.4
yargs: 17.7.2
optionalDependencies:
rollup: 4.20.0
rollup@4.20.0:
dependencies:
'@types/estree': 1.0.5
@@ -2393,12 +2534,13 @@ snapshots:
source-map-js@1.2.0: {}
source-map@0.7.4: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
optional: true
string_decoder@1.3.0:
dependencies:
@@ -2408,7 +2550,6 @@ snapshots:
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
optional: true
style-mod@4.1.2: {}
@@ -2531,6 +2672,12 @@ snapshots:
string-width: 4.2.3
optional: true
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrappy@1.0.2:
optional: true
@@ -2548,5 +2695,19 @@ snapshots:
eventemitter3: 4.0.7
xgplayer-subtitles: 3.0.20(core-js@3.38.1)
y18n@5.0.8: {}
yallist@4.0.0:
optional: true
yargs-parser@21.1.1: {}
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
+1 -1
View File
@@ -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
}
/**
@@ -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>
@@ -9,12 +9,15 @@
</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>
<ol
:style="{ maxHeight: ap.options.listMaxHeight }"
:style="{ maxHeight: `calc( ${ap.options.listMaxHeight} - 30px )` }"
:class="[
!isDragging ? 'miaoMusic-ol-isDragging' : 'miaoMusic-ol-notDragging'
]"
@@ -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>
+114 -6
View File
@@ -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>
+1
View File
@@ -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
+3 -1
View File
@@ -2,12 +2,14 @@ import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
// import { viteSingleFile } from 'vite-plugin-singlefile'
// import { visualizer } from 'rollup-plugin-visualizer'
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [
vue()
vue(),
// visualizer()
// viteSingleFile()
],
resolve: {