🔧 build: support ESM and upgrade to Vite 5

- 临时解决下载歌曲无法正常播放 #113
This commit is contained in:
imsyy
2024-01-09 18:13:01 +08:00
parent 6a1e606d6d
commit 309c323a14
19 changed files with 543 additions and 451 deletions

View File

@@ -1,9 +1,18 @@
# 根配置文件
## 编辑器在查找配置时会停止查找更高层次的配置文件
root = true root = true
# 通配符,匹配所有文件
[*] [*]
# 设置字符集为 UTF-8确保文件中的文本使用 UTF-8 编码
charset = utf-8 charset = utf-8
# 使用空格作为缩进风格
indent_style = space indent_style = space
# 设置每个缩进级别的空格数量为 2
indent_size = 2 indent_size = 2
# 设置行尾换行符为LFLine Feed
end_of_line = lf end_of_line = lf
# 在文件的末尾插入一个新行
insert_final_newline = true insert_final_newline = true
# 删除每一行末尾的尾随空格
trim_trailing_whitespace = true trim_trailing_whitespace = true

View File

@@ -229,7 +229,6 @@ docker run -d --name SPlayer -p 7899:7899 imsyy/splayer:latest
- [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) - [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
- [YesPlayMusic](https://github.com/qier222/YesPlayMusic) - [YesPlayMusic](https://github.com/qier222/YesPlayMusic)
- [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server) - [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server)
- [BlurLyric](https://github.com/Project-And-Factory/BlurLyric)
- [Vue-mmPlayer](https://github.com/maomao1996/Vue-mmPlayer) - [Vue-mmPlayer](https://github.com/maomao1996/Vue-mmPlayer)
## 📢 免责声明 ## 📢 免责声明

1
components.d.ts vendored
View File

@@ -77,7 +77,6 @@ declare module 'vue' {
NSelect: typeof import('naive-ui')['NSelect'] NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton'] NSkeleton: typeof import('naive-ui')['NSkeleton']
NSlider: typeof import('naive-ui')['NSlider'] NSlider: typeof import('naive-ui')['NSlider']
NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin'] NSpin: typeof import('naive-ui')['NSpin']
NSwitch: typeof import('naive-ui')['NSwitch'] NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab'] NTab: typeof import('naive-ui')['NTab']

View File

@@ -1,9 +1,9 @@
import { ipcMain, dialog, app, clipboard, shell } from "electron"; import { ipcMain, dialog, app, clipboard, shell } from "electron";
import { readDirAsync } from "@main/utils/readDirAsync"; import { readDirAsync } from "@main/utils/readDirAsync";
import { parseFile } from "music-metadata"; import { parseFile } from "music-metadata";
import { write } from "node-id3";
import { download } from "electron-dl"; import { download } from "electron-dl";
import getNeteaseMusicUrl from "@main/utils/getNeteaseMusicUrl"; import getNeteaseMusicUrl from "@main/utils/getNeteaseMusicUrl";
import NodeID3 from "node-id3";
import axios from "axios"; import axios from "axios";
import fs from "fs/promises"; import fs from "fs/promises";
@@ -203,6 +203,8 @@ const mainIpcMain = (win) => {
directory: path, directory: path,
filename: `${songName}.${songType}`, filename: `${songName}.${songType}`,
}); });
// 若不为 mp3则不进行元信息写入
if (songType !== "mp3") return true;
// 下载封面 // 下载封面
const coverDownload = await download(win, songData.cover, { const coverDownload = await download(win, songData.cover, {
directory: path, directory: path,
@@ -218,10 +220,10 @@ const mainIpcMain = (win) => {
image: coverDownload.getSavePath(), image: coverDownload.getSavePath(),
}; };
// 保存修改后的元数据 // 保存修改后的元数据
write(songTag, songDownload.getSavePath()); const isSuccess = NodeID3.write(songTag, songDownload.getSavePath());
// 删除封面 // 删除封面
await fs.unlink(coverDownload.getSavePath()); await fs.unlink(coverDownload.getSavePath());
return true; return isSuccess;
} else { } else {
console.log(`目录不存在:${path}`); console.log(`目录不存在:${path}`);
return false; return false;

View File

@@ -1,6 +1,8 @@
import { dialog, shell } from "electron"; import { dialog, shell } from "electron";
import { is } from "@electron-toolkit/utils"; import { is } from "@electron-toolkit/utils";
import { autoUpdater } from "electron-updater"; import pkg from "electron-updater";
const { autoUpdater } = pkg;
// 更新弹窗 // 更新弹窗
const hasNewVersion = (info) => { const hasNewVersion = (info) => {

View File

@@ -8,9 +8,10 @@
"github": "https://github.com/imsyy/SPlayer", "github": "https://github.com/imsyy/SPlayer",
"repository": "github:imsyy/SPlayer", "repository": "github:imsyy/SPlayer",
"engines": { "engines": {
"node": ">=16.16.0" "node": ">=18.16.0",
"npm": ">=9.6.7",
"pnpm": ">=8.14.0"
}, },
"packageManager": "pnpm@8.12.0",
"scripts": { "scripts": {
"format": "prettier --write .", "format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
@@ -23,8 +24,8 @@
"build:linux": "npm run build && electron-builder --linux --config" "build:linux": "npm run build && electron-builder --linux --config"
}, },
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^2.0.0", "@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/utils": "^2.0.1", "@electron-toolkit/utils": "^3.0.0",
"@material/material-color-utilities": "^0.2.7", "@material/material-color-utilities": "^0.2.7",
"NeteaseCloudMusicApi": "^4.14.0", "NeteaseCloudMusicApi": "^4.14.0",
"axios": "^1.6.5", "axios": "^1.6.5",
@@ -33,11 +34,11 @@
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"electron-updater": "^6.1.7", "electron-updater": "^6.1.7",
"express": "^4.18.2", "express": "^4.18.2",
"express-http-proxy": "^1.6.3", "express-http-proxy": "^2.0.0",
"howler": "^2.2.4", "howler": "^2.2.4",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"music-metadata": "7.13.4", "music-metadata": "7.14.0",
"node-id3": "^0.2.6", "node-id3": "^0.2.6",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
@@ -49,22 +50,22 @@
"devDependencies": { "devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2", "@electron-toolkit/eslint-config": "^1.0.2",
"@rushstack/eslint-patch": "^1.6.1", "@rushstack/eslint-patch": "^1.6.1",
"@vitejs/plugin-vue": "^4.6.2", "@vitejs/plugin-vue": "^5.0.2",
"@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-prettier": "^9.0.0",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"electron": "^27.2.1", "electron": "^28.1.2",
"electron-builder": "^24.9.1", "electron-builder": "^24.9.1",
"electron-log": "^5.0.3", "electron-log": "^5.0.3",
"electron-vite": "^1.0.29", "electron-vite": "^2.0.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-plugin-vue": "^9.19.2", "eslint-plugin-vue": "^9.19.2",
"naive-ui": "^2.37.0", "naive-ui": "^2.37.3",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"sass": "^1.69.7", "sass": "^1.69.7",
"terser": "^5.26.0", "terser": "^5.26.0",
"unplugin-auto-import": "^0.16.7", "unplugin-auto-import": "^0.17.3",
"unplugin-vue-components": "^0.25.2", "unplugin-vue-components": "^0.26.0",
"vite": "^4.5.1", "vite": "^5.0.11",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-pwa": "^0.17.4", "vite-plugin-pwa": "^0.17.4",
"vue": "3.4.4" "vue": "3.4.4"

492
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -90,5 +90,6 @@
"password": "M2 17h20v2H2v-2zm1.15-4.05L4 11.47l.85 1.48l1.3-.75l-.85-1.48H7v-1.5H5.3l.85-1.47L4.85 7L4 8.47L3.15 7l-1.3.75l.85 1.47H1v1.5h1.7l-.85 1.48l1.3.75zm6.7-.75l1.3.75l.85-1.48l.85 1.48l1.3-.75l-.85-1.48H15v-1.5h-1.7l.85-1.47l-1.3-.75L12 8.47L11.15 7l-1.3.75l.85 1.47H9v1.5h1.7l-.85 1.48zM23 9.22h-1.7l.85-1.47l-1.3-.75L20 8.47L19.15 7l-1.3.75l.85 1.47H17v1.5h1.7l-.85 1.48l1.3.75l.85-1.48l.85 1.48l1.3-.75l-.85-1.48H23v-1.5z", "password": "M2 17h20v2H2v-2zm1.15-4.05L4 11.47l.85 1.48l1.3-.75l-.85-1.48H7v-1.5H5.3l.85-1.47L4.85 7L4 8.47L3.15 7l-1.3.75l.85 1.47H1v1.5h1.7l-.85 1.48l1.3.75zm6.7-.75l1.3.75l.85-1.48l.85 1.48l1.3-.75l-.85-1.48H15v-1.5h-1.7l.85-1.47l-1.3-.75L12 8.47L11.15 7l-1.3.75l.85 1.47H9v1.5h1.7l-.85 1.48zM23 9.22h-1.7l.85-1.47l-1.3-.75L20 8.47L19.15 7l-1.3.75l.85 1.47H17v1.5h1.7l-.85 1.48l1.3.75l.85-1.48l.85 1.48l1.3-.75l-.85-1.48H23v-1.5z",
"star": "m12 17.27l4.15 2.51c.76.46 1.69-.22 1.49-1.08l-1.1-4.72l3.67-3.18c.67-.58.31-1.68-.57-1.75l-4.83-.41l-1.89-4.46c-.34-.81-1.5-.81-1.84 0L9.19 8.63l-4.83.41c-.88.07-1.24 1.17-.57 1.75l3.67 3.18l-1.1 4.72c-.2.86.73 1.54 1.49 1.08l4.15-2.5z", "star": "m12 17.27l4.15 2.51c.76.46 1.69-.22 1.49-1.08l-1.1-4.72l3.67-3.18c.67-.58.31-1.68-.57-1.75l-4.83-.41l-1.89-4.46c-.34-.81-1.5-.81-1.84 0L9.19 8.63l-4.83.41c-.88.07-1.24 1.17-.57 1.75l3.67 3.18l-1.1 4.72c-.2.86.73 1.54 1.49 1.08l4.15-2.5z",
"record": "M17 18.25v3.25H7v-3.25c0-1.38 2.24-2.5 5-2.5s5 1.12 5 2.5M12 5.5a6.5 6.5 0 0 1 6.5 6.5c0 1.25-.35 2.42-.96 3.41L16 14.04c.32-.61.5-1.31.5-2.04c0-2.5-2-4.5-4.5-4.5s-4.5 2-4.5 4.5c0 .73.18 1.43.5 2.04l-1.54 1.37c-.61-.99-.96-2.16-.96-3.41A6.5 6.5 0 0 1 12 5.5m0-4A10.5 10.5 0 0 1 22.5 12c0 2.28-.73 4.39-1.96 6.11l-1.5-1.35c.92-1.36 1.46-3 1.46-4.76A8.5 8.5 0 0 0 12 3.5A8.5 8.5 0 0 0 3.5 12c0 1.76.54 3.4 1.46 4.76l-1.5 1.35A10.473 10.473 0 0 1 1.5 12A10.5 10.5 0 0 1 12 1.5m0 8a2.5 2.5 0 0 1 2.5 2.5a2.5 2.5 0 0 1-2.5 2.5A2.5 2.5 0 0 1 9.5 12A2.5 2.5 0 0 1 12 9.5Z", "record": "M17 18.25v3.25H7v-3.25c0-1.38 2.24-2.5 5-2.5s5 1.12 5 2.5M12 5.5a6.5 6.5 0 0 1 6.5 6.5c0 1.25-.35 2.42-.96 3.41L16 14.04c.32-.61.5-1.31.5-2.04c0-2.5-2-4.5-4.5-4.5s-4.5 2-4.5 4.5c0 .73.18 1.43.5 2.04l-1.54 1.37c-.61-.99-.96-2.16-.96-3.41A6.5 6.5 0 0 1 12 5.5m0-4A10.5 10.5 0 0 1 22.5 12c0 2.28-.73 4.39-1.96 6.11l-1.5-1.35c.92-1.36 1.46-3 1.46-4.76A8.5 8.5 0 0 0 12 3.5A8.5 8.5 0 0 0 3.5 12c0 1.76.54 3.4 1.46 4.76l-1.5 1.35A10.473 10.473 0 0 1 1.5 12A10.5 10.5 0 0 1 12 1.5m0 8a2.5 2.5 0 0 1 2.5 2.5a2.5 2.5 0 0 1-2.5 2.5A2.5 2.5 0 0 1 9.5 12A2.5 2.5 0 0 1 12 9.5Z",
"storage": "M4 20h16c1.1 0 2-.9 2-2s-.9-2-2-2H4c-1.1 0-2 .9-2 2s.9 2 2 2m0-3h2v2H4zM2 6c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2s-.9-2-2-2H4c-1.1 0-2 .9-2 2m4 1H4V5h2zm-2 7h16c1.1 0 2-.9 2-2s-.9-2-2-2H4c-1.1 0-2 .9-2 2s.9 2 2 2m0-3h2v2H4z" "storage": "M4 20h16c1.1 0 2-.9 2-2s-.9-2-2-2H4c-1.1 0-2 .9-2 2s.9 2 2 2m0-3h2v2H4zM2 6c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2s-.9-2-2-2H4c-1.1 0-2 .9-2 2m4 1H4V5h2zm-2 7h16c1.1 0 2-.9 2-2s-.9-2-2-2H4c-1.1 0-2 .9-2 2s.9 2 2 2m0-3h2v2H4z",
"lrc-text": "M13.8 22H5c-1.7 0-3-1.3-3-3v-1h11.1c-.1.3-.1.7-.1 1c0 1.1.3 2.1.8 3m0-6H5V5c0-1.7 1.3-3 3-3h11c1.7 0 3 1.3 3 3v1h-2V5c0-.6-.4-1-1-1s-1 .4-1 1v8.1c-1.8.3-3.3 1.4-4.2 2.9M8 8h7V6H8zm0 4h6v-2H8zm9 4v6l5-3z"
} }

View File

@@ -49,9 +49,14 @@
<div v-show="playerControlShow" class="menu"> <div v-show="playerControlShow" class="menu">
<div class="left"> <div class="left">
<!-- 歌词模式 --> <!-- 歌词模式 -->
<div v-if="isHasLrc" class="n-icon" @click="pureLyricMode = !pureLyricMode"> <n-icon
<n-text></n-text> v-if="isHasLrc"
</div> :class="['lrc-open', { open: pureLyricMode }]"
size="28"
@click="pureLyricMode = !pureLyricMode"
>
<SvgIcon icon="lrc-text" />
</n-icon>
</div> </div>
<div class="right"> <div class="right">
<!-- 全屏切换 --> <!-- 全屏切换 -->
@@ -406,17 +411,6 @@ onUnmounted(() => {
justify-content: flex-end; justify-content: flex-end;
flex: 1; flex: 1;
} }
.left {
justify-content: flex-start;
.n-icon {
margin-left: 0;
margin-right: 12px;
.n-text {
font-size: 26px;
font-weight: bold;
}
}
}
.n-icon { .n-icon {
margin-left: 12px; margin-left: 12px;
width: 40px; width: 40px;
@@ -441,6 +435,17 @@ onUnmounted(() => {
transform: scale(1); transform: scale(1);
} }
} }
.left {
justify-content: flex-start;
.n-icon {
margin-left: 0;
&.lrc-open {
&.open {
opacity: 0.8;
}
}
}
}
} }
.main-player { .main-player {
display: flex; display: flex;

View File

@@ -779,6 +779,41 @@ const getPlaySongName = () => {
return songName + " - " + songArtist; return songName + " - " + songArtist;
}; };
export const playAllSongs = async (playlist, mode = "normal") => {
try {
// pinia
const music = musicData();
const status = siteStatus();
if (!playlist) return false;
// 关闭心动模式
status.playHeartbeatMode = false;
// 更改模式和歌单
status.playMode = mode;
music.playList = playlist.slice();
// 是否处于歌单内
const songId = music.getPlaySongData?.id;
const existingIndex = playlist.findIndex((song) => song.id === songId);
// 若不处于
if (existingIndex === -1 || !songId) {
console.log("不在歌单内");
music.playSongData = playlist[0];
status.playIndex = 0;
// 初始化播放器
await initPlayer(true);
} else {
console.log("处于歌单内");
music.playSongData = playlist[existingIndex];
status.playIndex = existingIndex;
// 播放
fadePlayOrPause();
}
$message.info("已开始播放", { showIcon: false });
} catch (error) {
console.error("播放全部歌曲出错:", error);
$message.error("播放全部歌曲出现错误");
}
};
/* /*
* 清除定时器 * 清除定时器
*/ */

View File

@@ -17,7 +17,17 @@
<!-- 功能区 --> <!-- 功能区 -->
<n-flex class="menu" justify="space-between"> <n-flex class="menu" justify="space-between">
<n-flex class="left"> <n-flex class="left">
<n-button type="primary" class="play" circle strong secondary @click="playAllSongs"> <n-button
:disabled="userCloudData?.length === 0"
:focusable="false"
type="primary"
class="play"
tag="div"
circle
strong
secondary
@click="playAllSongs(userCloudData)"
>
<template #icon> <template #icon>
<n-icon size="32"> <n-icon size="32">
<SvgIcon icon="play-arrow-rounded" /> <SvgIcon icon="play-arrow-rounded" />
@@ -82,19 +92,14 @@
</template> </template>
<script setup> <script setup>
import { storeToRefs } from "pinia"; import { indexedDBData } from "@/stores";
import { musicData, siteStatus, indexedDBData } from "@/stores";
import { getUserCloud } from "@/api/cloud"; import { getUserCloud } from "@/api/cloud";
import { fuzzySearch } from "@/utils/helper"; import { fuzzySearch } from "@/utils/helper";
import { fadePlayOrPause, initPlayer } from "@/utils/Player"; import { playAllSongs } from "@/utils/Player";
import debounce from "@/utils/debounce"; import debounce from "@/utils/debounce";
import formatData from "@/utils/formatData"; import formatData from "@/utils/formatData";
const music = musicData();
const status = siteStatus();
const indexedDB = indexedDBData(); const indexedDB = indexedDBData();
const { playList, playSongData } = storeToRefs(music);
const { playIndex, playMode, playHeartbeatMode } = storeToRefs(status);
// 云盘数据 // 云盘数据
const userCloudSpace = ref([]); const userCloudSpace = ref([]);
@@ -160,31 +165,6 @@ const localSearch = debounce((val) => {
searchData.value = result; searchData.value = result;
}, 300); }, 300);
// 播放歌单全部歌曲
const playAllSongs = async () => {
if (!userCloudData.value || !Object.keys(userCloudData.value).length) return false;
// 关闭心动模式
playHeartbeatMode.value = false;
// 更改模式和歌单
playMode.value = "normal";
playList.value = userCloudData.value.slice();
// 是否处于歌单内
const songId = playSongData.value?.id;
const existingIndex = userCloudData.value.findIndex((song) => song.id === songId);
// 若不处于
if (existingIndex === -1 || !songId) {
playSongData.value = userCloudData.value[0];
playIndex.value = 0;
// 初始化播放器
await initPlayer(true);
} else {
playSongData.value = userCloudData.value[existingIndex];
playIndex.value = existingIndex;
fadePlayOrPause();
}
$message.info("已开始播放", { showIcon: false });
};
// 云盘扩容 // 云盘扩容
const goBuy = () => { const goBuy = () => {
window.open("https://music.163.com/#/store/product/detail?id=34001"); window.open("https://music.163.com/#/store/product/detail?id=34001");

View File

@@ -13,7 +13,16 @@
</div> </div>
<!-- 操作 --> <!-- 操作 -->
<n-flex class="control"> <n-flex class="control">
<n-button size="large" tag="div" round strong secondary @click="playAllSongs"> <n-button
:disabled="dailySongsData.data?.length === 0"
:focusable="false"
size="large"
tag="div"
round
strong
secondary
@click="playAllSongs(dailySongsData.data)"
>
<template #icon> <template #icon>
<n-icon> <n-icon>
<SvgIcon icon="play-arrow-rounded" /> <SvgIcon icon="play-arrow-rounded" />
@@ -40,16 +49,12 @@
<script setup> <script setup>
import { NIcon } from "naive-ui"; import { NIcon } from "naive-ui";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { musicData, siteData, siteStatus } from "@/stores"; import { siteData } from "@/stores";
import { fadePlayOrPause, initPlayer } from "@/utils/Player"; import { playAllSongs } from "@/utils/Player";
import SvgIcon from "@/components/Global/SvgIcon"; import SvgIcon from "@/components/Global/SvgIcon";
const data = siteData(); const data = siteData();
const music = musicData();
const status = siteStatus();
const { dailySongsData } = storeToRefs(data); const { dailySongsData } = storeToRefs(data);
const { playList, playSongData } = storeToRefs(music);
const { playIndex, playMode, playHeartbeatMode } = storeToRefs(status);
const showTime = ref(false); const showTime = ref(false);
const showTimeOut = ref(null); const showTimeOut = ref(null);
@@ -85,34 +90,6 @@ const moreOptions = computed(() => [
}, },
]); ]);
// 播放歌单全部歌曲
const playAllSongs = async () => {
if (!dailySongsData.value.data) return false;
// 关闭心动模式
playHeartbeatMode.value = false;
// 更改模式和歌单
playMode.value = "normal";
playList.value = dailySongsData.value.data.slice();
// 是否处于歌单内
const songId = music.getPlaySongData?.id;
const existingIndex = dailySongsData.value.data.findIndex((song) => song.id === songId);
// 若不处于
if (existingIndex === -1 || !songId) {
console.log("不在歌单内");
playSongData.value = dailySongsData.value.data[0];
playIndex.value = 0;
// 初始化播放器
await initPlayer(true);
} else {
console.log("处于歌单内");
playSongData.value = dailySongsData.value.data[existingIndex];
playIndex.value = existingIndex;
// 播放
fadePlayOrPause();
}
$message.info("已开始播放", { showIcon: false });
};
onMounted(() => { onMounted(() => {
showTimeOut.value = setTimeout(() => { showTimeOut.value = setTimeout(() => {
showTime.value = true; showTime.value = true;

View File

@@ -105,13 +105,14 @@
<n-flex class="left"> <n-flex class="left">
<n-button <n-button
:disabled="albumData === 'empty'" :disabled="albumData === 'empty'"
:focusable="false"
type="primary" type="primary"
class="play" class="play"
tag="div" tag="div"
circle circle
strong strong
secondary secondary
@click="playAllSongs" @click="playAllSongs(albumData)"
> >
<template #icon> <template #icon>
<n-icon size="32"> <n-icon size="32">
@@ -120,6 +121,7 @@
</template> </template>
</n-button> </n-button>
<n-button <n-button
:focusable="false"
class="like" class="like"
size="large" size="large"
tag="div" tag="div"
@@ -138,7 +140,7 @@
{{ isLikeOrDislike(albumId) ? "收藏专辑" : "取消收藏" }} {{ isLikeOrDislike(albumId) ? "收藏专辑" : "取消收藏" }}
</n-button> </n-button>
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start"> <n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
<n-button class="more" size="large" tag="div" circle strong secondary> <n-button :focusable="false" class="more" size="large" tag="div" circle strong secondary>
<template #icon> <template #icon>
<n-icon> <n-icon>
<SvgIcon icon="format-list-bulleted" /> <SvgIcon icon="format-list-bulleted" />
@@ -193,7 +195,9 @@
</div> </div>
<div v-else class="title"> <div v-else class="title">
<n-text class="key">参数不完整</n-text> <n-text class="key">参数不完整</n-text>
<n-button class="back" strong secondary @click="router.go(-1)"> 返回上一页 </n-button> <n-button :focusable="false" class="back" strong secondary @click="router.go(-1)">
返回上一页
</n-button>
</div> </div>
</template> </template>
@@ -201,12 +205,12 @@
import { NIcon } from "naive-ui"; import { NIcon } from "naive-ui";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { musicData, siteData, siteStatus } from "@/stores"; import { siteData } from "@/stores";
import { getSongDetail } from "@/api/song"; import { getSongDetail } from "@/api/song";
import { getAlbumDetail, likeAlbum } from "@/api/album"; import { getAlbumDetail, likeAlbum } from "@/api/album";
import { formatNumber, fuzzySearch } from "@/utils/helper"; import { formatNumber, fuzzySearch } from "@/utils/helper";
import { getTimestampTime } from "@/utils/timeTools"; import { getTimestampTime } from "@/utils/timeTools";
import { fadePlayOrPause, initPlayer } from "@/utils/Player"; import { playAllSongs } from "@/utils/Player";
import { isLogin } from "@/utils/auth"; import { isLogin } from "@/utils/auth";
import debounce from "@/utils/debounce"; import debounce from "@/utils/debounce";
import formatData from "@/utils/formatData"; import formatData from "@/utils/formatData";
@@ -214,11 +218,7 @@ import SvgIcon from "@/components/Global/SvgIcon";
const router = useRouter(); const router = useRouter();
const data = siteData(); const data = siteData();
const music = musicData();
const status = siteStatus();
const { userLikeData } = storeToRefs(data); const { userLikeData } = storeToRefs(data);
const { playList, playSongData } = storeToRefs(music);
const { playIndex, playMode, playHeartbeatMode } = storeToRefs(status);
// 专辑 ID // 专辑 ID
const albumId = ref(router.currentRoute.value.query.id || null); const albumId = ref(router.currentRoute.value.query.id || null);
@@ -269,34 +269,6 @@ const getAlbumAllData = async (id, justDetail = false) => {
albumData.value = formatData(songsDetail.songs, "song"); albumData.value = formatData(songsDetail.songs, "song");
}; };
// 播放专辑全部歌曲
const playAllSongs = async () => {
if (!albumData.value) return false;
// 关闭心动模式
playHeartbeatMode.value = false;
// 更改模式和歌单
playMode.value = "normal";
playList.value = albumData.value.slice();
// 是否处于专辑内
const songId = playSongData.value?.id;
const existingIndex = albumData.value.findIndex((song) => song.id === songId);
// 若不处于
if (existingIndex === -1 || !songId) {
console.log("不在专辑内");
playSongData.value = albumData.value[0];
playIndex.value = 0;
// 初始化播放器
await initPlayer(true);
} else {
console.log("处于专辑内");
playSongData.value = albumData.value[existingIndex];
playIndex.value = existingIndex;
// 播放
fadePlayOrPause();
}
$message.info("已开始播放", { showIcon: false });
};
// 歌曲模糊搜索 // 歌曲模糊搜索
const localSearch = debounce((val) => { const localSearch = debounce((val) => {
const searchValue = val?.trim(); const searchValue = val?.trim();

View File

@@ -100,13 +100,14 @@
<n-flex class="left"> <n-flex class="left">
<n-button <n-button
:disabled="djData === 'empty'" :disabled="djData === 'empty'"
:focusable="false"
type="primary" type="primary"
class="play" class="play"
tag="div" tag="div"
circle circle
strong strong
secondary secondary
@click="playAllSongs" @click="playAllSongs(djData, 'dj')"
> >
<template #icon> <template #icon>
<n-icon size="32"> <n-icon size="32">
@@ -115,6 +116,7 @@
</template> </template>
</n-button> </n-button>
<n-button <n-button
:focusable="false"
class="like" class="like"
size="large" size="large"
tag="div" tag="div"
@@ -133,7 +135,7 @@
{{ isLikeOrDislike(djId) ? "订阅电台" : "取消订阅" }} {{ isLikeOrDislike(djId) ? "订阅电台" : "取消订阅" }}
</n-button> </n-button>
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start"> <n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
<n-button class="more" size="large" tag="div" circle strong secondary> <n-button :focusable="false" class="more" size="large" tag="div" circle strong secondary>
<template #icon> <template #icon>
<n-icon> <n-icon>
<SvgIcon icon="format-list-bulleted" /> <SvgIcon icon="format-list-bulleted" />
@@ -197,7 +199,9 @@
</div> </div>
<div v-else class="title"> <div v-else class="title">
<n-text class="key">参数不完整</n-text> <n-text class="key">参数不完整</n-text>
<n-button class="back" strong secondary @click="router.go(-1)"> 返回上一页 </n-button> <n-button :focusable="false" class="back" strong secondary @click="router.go(-1)">
返回上一页
</n-button>
</div> </div>
</template> </template>
@@ -205,25 +209,21 @@
import { NIcon } from "naive-ui"; import { NIcon } from "naive-ui";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { musicData, siteData, siteSettings, siteStatus } from "@/stores"; import { siteData, siteSettings } from "@/stores";
import { getDjDetail, getDjProgram, likeDj } from "@/api/dj"; import { getDjDetail, getDjProgram, likeDj } from "@/api/dj";
import { fuzzySearch } from "@/utils/helper"; import { fuzzySearch } from "@/utils/helper";
import { isLogin } from "@/utils/auth"; import { isLogin } from "@/utils/auth";
import { getTimestampTime } from "@/utils/timeTools"; import { getTimestampTime } from "@/utils/timeTools";
import { fadePlayOrPause, initPlayer } from "@/utils/Player"; import { playAllSongs } from "@/utils/Player";
import debounce from "@/utils/debounce"; import debounce from "@/utils/debounce";
import formatData from "@/utils/formatData"; import formatData from "@/utils/formatData";
import SvgIcon from "@/components/Global/SvgIcon"; import SvgIcon from "@/components/Global/SvgIcon";
const router = useRouter(); const router = useRouter();
const data = siteData(); const data = siteData();
const music = musicData();
const status = siteStatus();
const settings = siteSettings(); const settings = siteSettings();
const { userLikeData } = storeToRefs(data); const { userLikeData } = storeToRefs(data);
const { loadSize } = storeToRefs(settings); const { loadSize } = storeToRefs(settings);
const { playList, playSongData } = storeToRefs(music);
const { playIndex, playMode, playHeartbeatMode } = storeToRefs(status);
// 电台数据 // 电台数据
const djId = ref(router.currentRoute.value.query.id); const djId = ref(router.currentRoute.value.query.id);
@@ -290,34 +290,6 @@ const getDjProgramData = async (id, limit = loadSize.value, offset = 0) => {
} }
}; };
// 播放电台全部节目
const playAllSongs = async () => {
if (!djData.value) return false;
// 关闭心动模式
playHeartbeatMode.value = false;
// 更改模式和电台
playMode.value = "dj";
playList.value = djData.value.slice();
// 是否处于电台内
const songId = music.getPlaySongData?.id;
const existingIndex = djData.value.findIndex((song) => song.id === songId);
// 若不处于
if (existingIndex === -1 || !songId) {
console.log("不在电台内");
playSongData.value = djData.value[0];
playIndex.value = 0;
// 初始化播放器
await initPlayer(true);
} else {
console.log("处于电台内");
playSongData.value = djData.value[existingIndex];
playIndex.value = existingIndex;
// 播放
fadePlayOrPause();
}
$message.info("已开始播放", { showIcon: false });
};
// 节目模糊搜索 // 节目模糊搜索
const localSearch = debounce((val) => { const localSearch = debounce((val) => {
const searchValue = val?.trim(); const searchValue = val?.trim();

View File

@@ -112,13 +112,14 @@
<n-flex class="left"> <n-flex class="left">
<n-button <n-button
:disabled="playListData === null || playListData === 'empty' || loadingMsg !== null" :disabled="playListData === null || playListData === 'empty' || loadingMsg !== null"
:focusable="false"
type="primary" type="primary"
class="play" class="play"
tag="div" tag="div"
circle circle
strong strong
secondary secondary
@click="playAllSongs" @click="playAllSongs(playListData)"
> >
<template #icon> <template #icon>
<n-icon size="32"> <n-icon size="32">
@@ -128,6 +129,7 @@
</n-button> </n-button>
<n-button <n-button
v-if="!isUserPLayList" v-if="!isUserPLayList"
:focusable="false"
class="like" class="like"
size="large" size="large"
tag="div" tag="div"
@@ -149,6 +151,7 @@
</n-button> </n-button>
<n-button <n-button
v-else v-else
:focusable="false"
class="like" class="like"
size="large" size="large"
tag="div" tag="div"
@@ -165,7 +168,15 @@
编辑歌单 编辑歌单
</n-button> </n-button>
<n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start"> <n-dropdown :options="moreOptions" trigger="hover" placement="bottom-start">
<n-button class="more" size="large" tag="div" circle strong secondary> <n-button
:focusable="false"
class="more"
size="large"
tag="div"
circle
strong
secondary
>
<template #icon> <template #icon>
<n-icon> <n-icon>
<SvgIcon icon="format-list-bulleted" /> <SvgIcon icon="format-list-bulleted" />
@@ -218,7 +229,9 @@
</div> </div>
<div v-else class="title"> <div v-else class="title">
<n-text class="key">参数不完整</n-text> <n-text class="key">参数不完整</n-text>
<n-button class="back" strong secondary @click="router.go(-1)"> 返回上一页 </n-button> <n-button :focusable="false" class="back" strong secondary @click="router.go(-1)">
返回上一页
</n-button>
</div> </div>
</template> </template>
@@ -226,7 +239,7 @@
import { NIcon } from "naive-ui"; import { NIcon } from "naive-ui";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { musicData, siteData, siteStatus } from "@/stores"; import { siteData } from "@/stores";
import { import {
getPlayListDetail, getPlayListDetail,
getAllPlayList, getAllPlayList,
@@ -238,18 +251,14 @@ import { getSongDetail } from "@/api/song";
import { formatNumber, fuzzySearch } from "@/utils/helper"; import { formatNumber, fuzzySearch } from "@/utils/helper";
import { isLogin } from "@/utils/auth"; import { isLogin } from "@/utils/auth";
import { getTimestampTime } from "@/utils/timeTools"; import { getTimestampTime } from "@/utils/timeTools";
import { fadePlayOrPause, initPlayer } from "@/utils/Player"; import { playAllSongs } from "@/utils/Player";
import debounce from "@/utils/debounce"; import debounce from "@/utils/debounce";
import formatData from "@/utils/formatData"; import formatData from "@/utils/formatData";
import SvgIcon from "@/components/Global/SvgIcon"; import SvgIcon from "@/components/Global/SvgIcon";
const router = useRouter(); const router = useRouter();
const data = siteData(); const data = siteData();
const music = musicData();
const status = siteStatus();
const { userLikeData, userData } = storeToRefs(data); const { userLikeData, userData } = storeToRefs(data);
const { playList, playSongData } = storeToRefs(music);
const { playIndex, playMode, playHeartbeatMode } = storeToRefs(status);
// 歌单 ID // 歌单 ID
const playlistId = ref( const playlistId = ref(
@@ -385,34 +394,6 @@ const getBigPlayListData = async (id, count) => {
loadingMsg.value = null; loadingMsg.value = null;
}; };
// 播放歌单全部歌曲
const playAllSongs = async () => {
if (!playListData.value) return false;
// 关闭心动模式
playHeartbeatMode.value = false;
// 更改模式和歌单
playMode.value = "normal";
playList.value = playListData.value.slice();
// 是否处于歌单内
const songId = music.getPlaySongData?.id;
const existingIndex = playListData.value.findIndex((song) => song.id === songId);
// 若不处于
if (existingIndex === -1 || !songId) {
console.log("不在歌单内");
playSongData.value = playListData.value[0];
playIndex.value = 0;
// 初始化播放器
await initPlayer(true);
} else {
console.log("处于歌单内");
playSongData.value = playListData.value[existingIndex];
playIndex.value = existingIndex;
// 播放
fadePlayOrPause();
}
$message.info("已开始播放", { showIcon: false });
};
// 歌曲模糊搜索 // 歌曲模糊搜索
const localSearch = debounce((val) => { const localSearch = debounce((val) => {
const searchValue = val?.trim(); const searchValue = val?.trim();

View File

@@ -5,7 +5,7 @@
v-if="allAlbumData" v-if="allAlbumData"
:style="{ :style="{
height: `calc(100vh - ${ height: `calc(100vh - ${
Object.keys(music.playSongData)?.length && status.showPlayBar ? 380 : 300 Object.keys(music.playSongData)?.length && status.showPlayBar ? 445 : 365
}px)`, }px)`,
}" }"
class="local-album" class="local-album"

View File

@@ -6,7 +6,7 @@
class="local-artists" class="local-artists"
:style="{ :style="{
height: `calc(100vh - ${ height: `calc(100vh - ${
Object.keys(music.playSongData)?.length && status.showPlayBar ? 380 : 300 Object.keys(music.playSongData)?.length && status.showPlayBar ? 445 : 365
}px)`, }px)`,
}" }"
type="card" type="card"

View File

@@ -26,18 +26,51 @@
GB GB
</div> </div>
</n-flex> </n-flex>
<!-- 标签页 --> <!-- 功能区 -->
<n-tabs v-model:value="tabValue" class="tabs" type="segment" @update:value="tabChange"> <n-flex class="menu" justify="space-between">
<n-tab name="local-songs"> 歌曲 </n-tab> <n-flex class="left">
<n-tab name="local-artists"> 歌手 </n-tab> <n-button
<n-tab name="local-albums"> 专辑 </n-tab> :disabled="!localSongList?.length"
<template #suffix> :focusable="false"
type="primary"
class="play"
tag="div"
circle
strong
secondary
@click="playAllSongs(localSongList)"
>
<template #icon>
<n-icon size="32">
<SvgIcon icon="play-arrow-rounded" />
</n-icon>
</template>
</n-button>
<!-- 目录管理 -->
<n-button
:focusable="false"
class="local-path"
tag="div"
round
strong
secondary
@click="localPathShow = true"
>
<template #icon>
<n-icon>
<SvgIcon icon="folder-cog" />
</n-icon>
</template>
目录管理
</n-button>
</n-flex>
<n-flex class="right">
<!-- 模糊搜索 --> <!-- 模糊搜索 -->
<div v-if="localSongList?.length" class="search">
<n-input <n-input
v-if="localSongList?.length"
v-model:value="searchValue" v-model:value="searchValue"
:input-props="{ autoComplete: false }" :input-props="{ autoComplete: false }"
class="local-search" class="search"
placeholder="搜索" placeholder="搜索"
clearable clearable
@input="localSearch" @input="localSearch"
@@ -48,19 +81,13 @@
</n-icon> </n-icon>
</template> </template>
</n-input> </n-input>
</div> </n-flex>
<!-- 目录管理 --> </n-flex>
<div class="local-path"> <!-- 标签页 -->
<n-button strong secondary @click="localPathShow = true"> <n-tabs v-model:value="tabValue" class="tabs" type="segment" @update:value="tabChange">
<template #icon> <n-tab name="local-songs"> 歌曲 </n-tab>
<n-icon> <n-tab name="local-artists"> 歌手 </n-tab>
<SvgIcon icon="folder-cog" /> <n-tab name="local-albums"> 专辑 </n-tab>
</n-icon>
</template>
目录管理
</n-button>
</div>
</template>
</n-tabs> </n-tabs>
<!-- 路由页面 --> <!-- 路由页面 -->
<Transition name="fade" mode="out-in"> <Transition name="fade" mode="out-in">
@@ -172,6 +199,7 @@
import { musicData, indexedDBData } from "@/stores"; import { musicData, indexedDBData } from "@/stores";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { fuzzySearch } from "@/utils/helper"; import { fuzzySearch } from "@/utils/helper";
import { playAllSongs } from "@/utils/Player";
import debounce from "@/utils/debounce"; import debounce from "@/utils/debounce";
const indexedDB = indexedDBData(); const indexedDB = indexedDBData();
@@ -335,25 +363,40 @@ onBeforeMount(async () => {
} }
} }
} }
.tabs { .menu {
margin-bottom: 20px; flex-wrap: nowrap;
.search {
height: 100%;
margin-right: 16px;
.local-search {
display: flex;
align-items: center; align-items: center;
height: 100%; margin: 20px 0;
border-radius: 6px; .left {
} flex-wrap: nowrap;
align-items: center;
.play {
--n-width: 46px;
--n-height: 46px;
} }
.local-path { .local-path {
height: 100%;
:deep(.n-button) {
--n-height: 100%; --n-height: 100%;
--n-border-radius: 6px; height: 40px;
} }
} }
.right {
.search {
height: 40px;
width: 130px;
display: flex;
align-items: center;
border-radius: 40px;
transition:
width 0.3s,
background-color 0.3s;
&.n-input--focus {
width: 200px;
}
}
}
}
.tabs {
margin-bottom: 20px;
} }
} }
.local-list { .local-list {