Compare commits

...

9 Commits

Author SHA1 Message Date
imsyy
1b6ebd9c7c feat: 支持解锁配置 2025-11-21 17:41:34 +08:00
imsyy
7721251a98 feat: 优化播放流程 2025-11-21 11:57:53 +08:00
底层用户
320047ca9c Merge pull request #586 from SUBearH/SUBearH-patch-2
为歌单页面添加歌单创建时间的显示
2025-11-21 11:54:55 +08:00
底层用户
73be8d8657 Merge pull request #585 from SUBearH/SUBearH-patch-1
Increase max badge value from 999 to 9999
2025-11-21 11:53:29 +08:00
imsyy
d527b076dc 🐞 fix: 优化播放处理 2025-11-21 11:03:36 +08:00
SUBear
7bf9c6d7bc Merge pull request #1 from SUBearH/SUBearH-patch-2-1
Fix conditional rendering for createTime in playlist
2025-11-21 02:51:05 +08:00
SUBear
4dfd897401 Fix conditional rendering for createTime in playlist 2025-11-21 02:48:29 +08:00
SUBear
f0a6526fd1 Fix conditional rendering for createTime display 2025-11-21 02:47:02 +08:00
SUBear
6d4f78413b Increase max badge value from 999 to 9999 2025-11-21 02:36:59 +08:00
49 changed files with 485 additions and 273 deletions

View File

@@ -30,6 +30,14 @@
- 欢迎各位大佬 `Star` 😍
## 💬 交流群
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=2-cVSf1bE0AvAehCib00qFEFdUvPaJ_k&jump_from=webapi&authKey=1NEhib9+GsmsXVo2rCc0IbRaVHeeRXJJ0gbsyKDcIwDdAzYySOubkFCvkV32+7Cw" target="_blank">
![交流群](/screenshots/welcome.png)
</a>
## 👀 Demo
- [SPlayer](https://music.imsyy.top/)

1
components.d.ts vendored
View File

@@ -146,6 +146,7 @@ declare module 'vue' {
SongList: typeof import('./src/components/List/SongList.vue')['default']
SongListCard: typeof import('./src/components/Card/SongListCard.vue')['default']
SongListMenu: typeof import('./src/components/Menu/SongListMenu.vue')['default']
SongUnlockManager: typeof import('./src/components/Modal/SongUnlockManager.vue')['default']
SvgIcon: typeof import('./src/components/Global/SvgIcon.vue')['default']
TextContainer: typeof import('./src/components/Global/TextContainer.vue')['default']
UpdateApp: typeof import('./src/components/Modal/UpdateApp.vue')['default']

View File

@@ -49,6 +49,7 @@
"@pixi/filter-color-matrix": "^7.4.3",
"@pixi/sprite": "^7.4.3",
"@vueuse/core": "^13.9.0",
"@vueuse/integrations": "^14.0.0",
"axios": "^1.13.2",
"axios-retry": "^4.5.0",
"change-case": "^5.4.4",
@@ -72,6 +73,7 @@
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"plyr": "^3.8.3",
"sortablejs": "^1",
"vue-virt-list": "^1.6.1"
},
"devDependencies": {

91
pnpm-lock.yaml generated
View File

@@ -60,6 +60,9 @@ importers:
'@vueuse/core':
specifier: ^13.9.0
version: 13.9.0(vue@3.5.24(typescript@5.9.3))
'@vueuse/integrations':
specifier: ^14.0.0
version: 14.0.0(async-validator@4.2.5)(axios@1.13.2)(change-case@5.4.4)(qrcode@1.5.4)(sortablejs@1.15.6)(vue@3.5.24(typescript@5.9.3))
axios:
specifier: ^1.13.2
version: 1.13.2
@@ -129,6 +132,9 @@ importers:
plyr:
specifier: ^3.8.3
version: 3.8.3
sortablejs:
specifier: ^1
version: 1.15.6
vue-virt-list:
specifier: ^1.6.1
version: 1.6.1(vue@3.5.24(typescript@5.9.3))
@@ -1514,14 +1520,69 @@ packages:
peerDependencies:
vue: ^3.5.0
'@vueuse/core@14.0.0':
resolution: {integrity: sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ==}
peerDependencies:
vue: ^3.5.0
'@vueuse/integrations@14.0.0':
resolution: {integrity: sha512-5A0X7q9qyLtM3xyghq5nK/NEESf7cpcZlkQgXTMuW4JWiAMYxc1ImdhhGrk4negFBsq3ejvAlRmLdNrkcTzk1Q==}
peerDependencies:
async-validator: ^4
axios: ^1
change-case: ^5
drauu: ^0.4
focus-trap: ^7
fuse.js: ^7
idb-keyval: ^6
jwt-decode: ^4
nprogress: ^0.2
qrcode: ^1.5
sortablejs: ^1
universal-cookie: ^7 || ^8
vue: ^3.5.0
peerDependenciesMeta:
async-validator:
optional: true
axios:
optional: true
change-case:
optional: true
drauu:
optional: true
focus-trap:
optional: true
fuse.js:
optional: true
idb-keyval:
optional: true
jwt-decode:
optional: true
nprogress:
optional: true
qrcode:
optional: true
sortablejs:
optional: true
universal-cookie:
optional: true
'@vueuse/metadata@13.9.0':
resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==}
'@vueuse/metadata@14.0.0':
resolution: {integrity: sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA==}
'@vueuse/shared@13.9.0':
resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==}
peerDependencies:
vue: ^3.5.0
'@vueuse/shared@14.0.0':
resolution: {integrity: sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw==}
peerDependencies:
vue: ^3.5.0
'@xmldom/xmldom@0.8.11':
resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==}
engines: {node: '>=10.0.0'}
@@ -3932,6 +3993,9 @@ packages:
resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==}
engines: {node: '>=0.10.0'}
sortablejs@1.15.6:
resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -5876,12 +5940,37 @@ snapshots:
'@vueuse/shared': 13.9.0(vue@3.5.24(typescript@5.9.3))
vue: 3.5.24(typescript@5.9.3)
'@vueuse/core@14.0.0(vue@3.5.24(typescript@5.9.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 14.0.0
'@vueuse/shared': 14.0.0(vue@3.5.24(typescript@5.9.3))
vue: 3.5.24(typescript@5.9.3)
'@vueuse/integrations@14.0.0(async-validator@4.2.5)(axios@1.13.2)(change-case@5.4.4)(qrcode@1.5.4)(sortablejs@1.15.6)(vue@3.5.24(typescript@5.9.3))':
dependencies:
'@vueuse/core': 14.0.0(vue@3.5.24(typescript@5.9.3))
'@vueuse/shared': 14.0.0(vue@3.5.24(typescript@5.9.3))
vue: 3.5.24(typescript@5.9.3)
optionalDependencies:
async-validator: 4.2.5
axios: 1.13.2
change-case: 5.4.4
qrcode: 1.5.4
sortablejs: 1.15.6
'@vueuse/metadata@13.9.0': {}
'@vueuse/metadata@14.0.0': {}
'@vueuse/shared@13.9.0(vue@3.5.24(typescript@5.9.3))':
dependencies:
vue: 3.5.24(typescript@5.9.3)
'@vueuse/shared@14.0.0(vue@3.5.24(typescript@5.9.3))':
dependencies:
vue: 3.5.24(typescript@5.9.3)
'@xmldom/xmldom@0.8.11': {}
abbrev@1.1.1: {}
@@ -8598,6 +8687,8 @@ snapshots:
dependencies:
is-plain-obj: 1.1.0
sortablejs@1.15.6: {}
source-map-js@1.2.1: {}
source-map-support@0.5.21:

BIN
screenshots/welcome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -1,5 +1,6 @@
import { isElectron } from "@/utils/env";
import { songLevelData } from "@/utils/meta";
import { SongUnlockServer } from "@/utils/songManager";
import request from "@/utils/request";
// 获取歌曲详情
@@ -47,16 +48,12 @@ export const songUrl = (
};
// 获取解锁歌曲 URL
export const unlockSongUrl = (
id: number,
keyword: string,
server: "netease" | "kuwo" | "bodian",
) => {
const params = server === "netease" ? { id } : { keyword };
export const unlockSongUrl = (id: number, keyword: string, server: SongUnlockServer) => {
const params = server === SongUnlockServer.NETEASE ? { id } : { keyword };
return request({
baseURL: "/api/unblock",
url: `/${server}`,
params,
params: { ...params, noCookie: true },
});
};

View File

@@ -158,7 +158,7 @@ import { openJumpArtist } from "@/utils/modal";
import { toLikeSong } from "@/utils/auth";
import { isObject } from "lodash-es";
import { formatTimestamp, msToTime } from "@/utils/time";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
import blob from "@/utils/blob";
import { isElectron } from "@/utils/env";
@@ -174,6 +174,7 @@ const props = defineProps<{
}>();
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const musicStore = useMusicStore();
const statusStore = useStatusStore();

View File

@@ -34,9 +34,10 @@ import { openCreatePlaylist } from "@/utils/modal";
import { debounce } from "lodash-es";
import { isLogin } from "@/utils/auth";
import { isElectron } from "@/utils/env";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const musicStore = useMusicStore();
const statusStore = useStatusStore();

View File

@@ -218,6 +218,9 @@ const likeComment = debounce(async (data: CommentType) => {
color: var(--primary-hex);
}
}
.text {
white-space: pre-wrap;
}
}
.reply {
width: 100%;
@@ -226,6 +229,9 @@ const likeComment = debounce(async (data: CommentType) => {
font-size: 13px;
margin-top: 6px;
background-color: rgba(var(--primary), 0.12);
.text {
white-space: pre-wrap;
}
}
.meta {
padding-top: 12px;

View File

@@ -121,8 +121,8 @@ import { formatSongsList } from "@/utils/format";
import { songDetail } from "@/api/song";
import { playlistAllSongs } from "@/api/playlist";
import { radioAllProgram } from "@/api/radio";
import { usePlayer } from "@/utils/player";
import CoverMenu from "@/components/Menu/CoverMenu.vue";
import player from "@/utils/player";
import { formatTimestamp } from "@/utils/time";
interface Props {
@@ -145,6 +145,7 @@ const emit = defineEmits<{
}>();
const router = useRouter();
const player = usePlayer();
const musicStore = useMusicStore();
const statusStore = useStatusStore();

View File

@@ -129,8 +129,8 @@ import { VirtList } from "vue-virt-list";
import { entries, isEmpty } from "lodash-es";
import { sortOptions } from "@/utils/meta";
import { renderIcon } from "@/utils/helper";
import { usePlayer } from "@/utils/player";
import SongListMenu from "@/components/Menu/SongListMenu.vue";
import player from "@/utils/player";
const props = withDefaults(
defineProps<{
@@ -177,6 +177,7 @@ const emit = defineEmits<{
removeSong: [id: number[]];
}>();
const player = usePlayer();
const musicStore = useMusicStore();
const statusStore = useStatusStore();

View File

@@ -31,11 +31,12 @@ import { deleteSongs, isLogin } from "@/utils/auth";
import { songUrl } from "@/api/song";
import { dailyRecommendDislike } from "@/api/rec";
import { formatSongsList } from "@/utils/format";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const emit = defineEmits<{ removeSong: [index: number[]] }>();
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const localStore = useLocalStore();
const statusStore = useStatusStore();

View File

@@ -66,8 +66,9 @@
<script setup lang="ts">
import { useStatusStore } from "@/stores";
import { convertSecondsToTime } from "@/utils/time";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const statusStore = useStatusStore();
// 自定义时长

View File

@@ -32,7 +32,8 @@
<script setup lang="ts">
import { useStatusStore } from "@/stores";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const statusStore = useStatusStore();
</script>

View File

@@ -39,8 +39,9 @@
<script setup lang="ts">
import { isElectron } from "@/utils/env";
import { useStatusStore } from "@/stores";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const statusStore = useStatusStore();
type PresetKey = keyof typeof presetList;

View File

@@ -0,0 +1,62 @@
<template>
<div class="song-unlock-manager">
<n-alert title="免责声明" type="info">
本功能仅作为测试使用资源来自网络若侵犯到您的权益请及时联系我们删除
</n-alert>
<div ref="sortableRef" class="sortable-list">
<n-card
v-for="item in settingStore.songUnlockServer"
:key="item.key"
:content-style="{
display: 'flex',
alignItems: 'center',
gap: '12px',
padding: '16px',
}"
class="sortable-item"
>
<SvgIcon :depth="3" name="Menu" />
<n-text class="name">{{ item.key }}</n-text>
<n-switch v-model:value="item.enabled" :round="false" />
</n-card>
</div>
</div>
</template>
<script setup lang="ts">
import { useSettingStore } from "@/stores";
import { useSortable } from "@vueuse/integrations/useSortable";
const settingStore = useSettingStore();
const sortableRef = ref<HTMLElement | null>(null);
// 拖拽
useSortable(sortableRef, settingStore.songUnlockServer, {
animation: 150,
handle: ".n-icon",
});
</script>
<style scoped lang="scss">
.sortable-list {
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 12px;
.sortable-item {
border-radius: 8px;
.n-icon {
font-size: 16px;
cursor: move;
}
.name {
font-size: 16px;
line-height: normal;
}
.n-switch {
margin-left: auto;
}
}
}
</style>

View File

@@ -79,8 +79,9 @@
import { useStatusStore, useMusicStore, useSettingStore } from "@/stores";
import { isElectron } from "@/utils/env";
import { throttle } from "lodash-es";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const musicStore = useMusicStore();
const statusStore = useStatusStore();
const settingStore = useSettingStore();

View File

@@ -34,12 +34,13 @@
<script setup lang="ts">
import { LyricPlayer } from "@applemusic-like-lyrics/vue";
import { LyricLine } from "@applemusic-like-lyrics/core";
import { type LyricLine } from "@applemusic-like-lyrics/lyric";
import { useMusicStore, useSettingStore, useStatusStore } from "@/stores";
import { getLyricLanguage } from "@/utils/format";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
import LyricMenu from "./LyricMenu.vue";
const player = usePlayer();
const musicStore = useMusicStore();
const statusStore = useStatusStore();
const settingStore = useSettingStore();

View File

@@ -171,11 +171,12 @@
import { LyricWord } from "@applemusic-like-lyrics/lyric";
import { NScrollbar } from "naive-ui";
import { useMusicStore, useSettingStore, useStatusStore } from "@/stores";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
import { getLyricLanguage } from "@/utils/format";
import { isElectron } from "@/utils/env";
import LyricMenu from "./LyricMenu.vue";
const player = usePlayer();
const musicStore = useMusicStore();
const statusStore = useStatusStore();
const settingStore = useSettingStore();

View File

@@ -117,8 +117,9 @@
<script setup lang="ts">
import { useStatusStore, useDataStore } from "@/stores";
import type { VirtualListInst } from "naive-ui";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const dataStore = useDataStore();
const statusStore = useStatusStore();

View File

@@ -199,9 +199,10 @@ import {
openJumpArtist,
openPlaylistAdd,
} from "@/utils/modal";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const musicStore = useMusicStore();
const statusStore = useStatusStore();

View File

@@ -79,8 +79,9 @@
import { useMusicStore, useStatusStore } from "@/stores";
import { coverLoaded } from "@/utils/helper";
import { debounce, isObject } from "lodash-es";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const musicStore = useMusicStore();
const statusStore = useStatusStore();

View File

@@ -146,10 +146,11 @@ const loadMoreComment = () => {
watch(
() => songId.value,
() => {
if (!isShowComment.value) {
commentData.value = [];
return;
}
commentData.value = [];
commentHotData.value = [];
commentPage.value = 1;
commentHasMore.value = true;
if (!isShowComment.value) return;
getHotCommentData();
getAllComment();
},

View File

@@ -95,8 +95,9 @@ import { useMusicStore, useStatusStore, useDataStore } from "@/stores";
import { msToTime } from "@/utils/time";
import { openDownloadSong, openPlaylistAdd } from "@/utils/modal";
import { toLikeSong } from "@/utils/auth";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const dataStore = useDataStore();
const musicStore = useMusicStore();
const statusStore = useStatusStore();

View File

@@ -47,7 +47,7 @@
v-if="!statusStore.personalFmMode"
:value="dataStore.playList?.length ?? 0"
:show="settingStore.showPlaylistCount"
:max="999"
:max="9999"
:style="{
marginRight: settingStore.showPlaylistCount ? '12px' : null,
}"
@@ -65,8 +65,9 @@ import { useMusicStore, useStatusStore, useDataStore, useSettingStore } from "@/
import { openAutoClose, openChangeRate, openEqualizer } from "@/utils/modal";
import { isElectron } from "@/utils/env";
import { renderIcon } from "@/utils/helper";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const dataStore = useDataStore();
const musicStore = useMusicStore();
const statusStore = useStatusStore();

View File

@@ -19,10 +19,11 @@
<script setup lang="ts">
import { useStatusStore } from "@/stores";
import { msToTime } from "@/utils/time";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
withDefaults(defineProps<{ showTooltip?: boolean }>(), { showTooltip: true });
const player = usePlayer();
const statusStore = useStatusStore();
// 拖动时的临时值

View File

@@ -39,12 +39,13 @@
<script setup lang="ts">
import { useStatusStore, useDataStore, useSettingStore } from "@/stores";
import { searchDefault } from "@/api/search";
import SearchInpMenu from "@/components/Menu/SearchInpMenu.vue";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
import { songDetail } from "@/api/song";
import { formatSongsList } from "@/utils/format";
import SearchInpMenu from "@/components/Menu/SearchInpMenu.vue";
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const statusStore = useStatusStore();
const settingStore = useSettingStore();

View File

@@ -36,6 +36,21 @@
</n-card>
</n-collapse-transition>
</div>
<div class="set-list">
<n-h3 prefix="bar"> 社区与资讯 </n-h3>
<n-flex class="link">
<n-card
v-for="(item, index) in communityData"
:key="index"
class="link-item"
hoverable
@click="openLink(item.url)"
>
<SvgIcon :name="item.icon" :size="26" />
<n-text class="name"> {{ item.name }} </n-text>
</n-card>
</n-flex>
</div>
<div class="set-list">
<n-h3 prefix="bar"> 历史版本 </n-h3>
<n-collapse-transition :show="oldVersion?.length > 0">
@@ -59,21 +74,6 @@
</n-collapse>
</n-collapse-transition>
</div>
<div class="set-list">
<n-h3 prefix="bar"> 社区与资讯 </n-h3>
<n-flex class="link">
<n-card
v-for="(item, index) in communityData"
:key="index"
class="link-item"
hoverable
@click="openLink(item.url)"
>
<SvgIcon :name="item.icon" :size="26" />
<n-text class="name"> {{ item.name }} </n-text>
</n-card>
</n-flex>
</div>
</div>
</template>
@@ -89,6 +89,11 @@ const statusStore = useStatusStore();
// 社区数据
const communityData = [
{
name: "加入交流群",
url: "https://qm.qq.com/cgi-bin/qm/qr?k=2-cVSf1bE0AvAehCib00qFEFdUvPaJ_k&jump_from=webapi&authKey=1NEhib9+GsmsXVo2rCc0IbRaVHeeRXJJ0gbsyKDcIwDdAzYySOubkFCvkV32+7Cw",
icon: "QQ",
},
{
name: "GitHub",
url: packageJson.github,

View File

@@ -246,14 +246,12 @@
</n-card>
</div>
<div class="set-list">
<n-h3 prefix="bar">
歌词内容
</n-h3>
<n-h3 prefix="bar"> 歌词内容 </n-h3>
<n-card class="set-item">
<div class="label">
<n-text class="name">
启用在线 TTML 歌词
<n-tag type="warning" size="small" round style="display: inline; vertical-align: middle;">Beta</n-tag>
<n-tag type="warning" size="small" round> Beta </n-tag>
</n-text>
<n-text class="tip" :depth="3">
是否从 AMLL TTML DB 获取歌词如有TTML
@@ -545,10 +543,11 @@ import { cloneDeep, isEqual } from "lodash-es";
import { isElectron } from "@/utils/env";
import { openLyricExclude } from "@/utils/modal";
import { LyricConfig } from "@/types/desktop-lyric";
import defaultDesktopLyricConfig from "@/assets/data/lyricConfig";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
import { SelectOption } from "naive-ui";
import defaultDesktopLyricConfig from "@/assets/data/lyricConfig";
const player = usePlayer();
const statusStore = useStatusStore();
const settingStore = useSettingStore();

View File

@@ -66,13 +66,6 @@
</div>
<n-switch v-model:value="settingStore.playSongDemo" class="set" :round="false" />
</n-card>
<n-card v-if="isElectron" class="set-item">
<div class="label">
<n-text class="name">音乐解锁</n-text>
<n-text class="tip" :depth="3">在无法正常播放时进行替换可能会与原曲不符</n-text>
</div>
<n-switch v-model:value="settingStore.useSongUnlock" class="set" :round="false" />
</n-card>
<n-card v-if="isElectron" class="set-item">
<div class="label">
<n-text class="name">音频输出设备</n-text>
@@ -87,6 +80,35 @@
/>
</n-card>
</div>
<div v-if="isElectron" class="set-list">
<n-h3 prefix="bar">
音乐解锁
<n-tag type="warning" size="small" round>Beta</n-tag>
</n-h3>
<n-card class="set-item">
<div class="label">
<n-text class="name">音乐解锁</n-text>
<n-text class="tip" :depth="3"> 在无法正常播放时进行替换可能会与原曲不符 </n-text>
</div>
<n-switch v-model:value="settingStore.useSongUnlock" class="set" :round="false" />
</n-card>
<!-- 音源配置 -->
<n-card class="set-item">
<div class="label">
<n-text class="name">音源配置</n-text>
<n-text class="tip" :depth="3"> 配置歌曲解锁的音源顺序或是否启用 </n-text>
</div>
<n-button
:disabled="!settingStore.useSongUnlock"
type="primary"
strong
secondary
@click="openSongUnlockManager"
>
配置
</n-button>
</n-card>
</div>
<div class="set-list">
<n-h3 prefix="bar"> 播放器 </n-h3>
<n-card class="set-item">
@@ -227,8 +249,10 @@ import { isLogin } from "@/utils/auth";
import { renderOption } from "@/utils/helper";
import { isElectron } from "@/utils/env";
import { uniqBy } from "lodash";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
import { openSongUnlockManager } from "@/utils/modal";
const player = usePlayer();
const settingStore = useSettingStore();
// 输出设备数据

View File

@@ -1,5 +1,6 @@
import { defineStore } from "pinia";
import { keywords, regexes } from "@/assets/data/exclude";
import { SongUnlockServer } from "@/utils/songManager";
export interface SettingState {
/** 明暗模式 */
@@ -99,6 +100,8 @@ export interface SettingState {
songVolumeFadeTime: number;
/** 是否使用解灰 */
useSongUnlock: boolean;
/** 歌曲解锁音源 */
songUnlockServer: { key: SongUnlockServer; enabled: boolean }[];
/** 显示倒计时 */
countDownShow: boolean;
/** 显示歌词条 */
@@ -201,6 +204,11 @@ export const useSettingStore = defineStore("setting", {
songVolumeFade: true,
songVolumeFadeTime: 300,
useSongUnlock: true,
songUnlockServer: [
{ key: SongUnlockServer.BODIAN, enabled: true },
{ key: SongUnlockServer.GEQUBAO, enabled: true },
{ key: SongUnlockServer.NETEASE, enabled: true },
],
countDownShow: true,
barLyricShow: true,
playerType: "cover",

View File

@@ -3,7 +3,6 @@
padding: 0;
user-select: none;
box-sizing: border-box;
-webkit-user-drag: none;
::after {
box-sizing: border-box;
}
@@ -18,6 +17,12 @@ body {
overflow: hidden;
}
img,
video,
audio {
-webkit-user-drag: none;
}
#app {
width: 100%;
height: 100%;

View File

@@ -3,13 +3,14 @@ import { useEventListener } from "@vueuse/core";
import { openUserAgreement } from "@/utils/modal";
import { debounce } from "lodash-es";
import { isElectron } from "./env";
import { usePlayer } from "@/utils/player";
import packageJson from "@/../package.json";
import player from "@/utils/player";
import log from "./log";
// 应用初始化时需要执行的操作
const init = async () => {
// init pinia-data
const player = usePlayer();
const dataStore = useDataStore();
const statusStore = useStatusStore();
const settingStore = useSettingStore();
@@ -58,6 +59,7 @@ const initEventListener = () => {
// 键盘事件
const keyDownEvent = debounce((event: KeyboardEvent) => {
const player = usePlayer();
const shortcutStore = useShortcutStore();
const target = event.target as HTMLElement;
// 排除元素

View File

@@ -2,7 +2,7 @@ import { isElectron } from "./env";
import { openSetting, openUpdateApp } from "./modal";
import { useMusicStore, useDataStore, useStatusStore } from "@/stores";
import { toLikeSong } from "./auth";
import player from "./player";
import { usePlayer } from "./player";
import { cloneDeep } from "lodash-es";
import { getPlayerInfo } from "./player-utils/song";
import { SettingType } from "@/types/main";
@@ -17,6 +17,7 @@ const closeUpdateStatus = () => {
const initIpc = () => {
try {
if (!isElectron) return;
const player = usePlayer();
// 播放
window.electron.ipcRenderer.on("play", () => player.play());
// 暂停

View File

@@ -105,59 +105,51 @@ class LyricManager {
const isStale = () => this.activeLyricReq !== req || musicStore.playSong?.id !== id;
// 处理 TTML 歌词
const adoptTTML = async () => {
try {
if (!settingStore.enableTTMLLyric) return;
const ttmlContent = await songLyricTTML(id);
if (isStale()) return;
if (!ttmlContent || typeof ttmlContent !== "string") return;
const parsed = parseTTML(ttmlContent);
const lines = parsed?.lines || [];
if (!lines.length) return;
result.yrcData = lines;
ttmlAdopted = true;
} catch (err) {
throw err;
}
if (!settingStore.enableTTMLLyric) return;
const ttmlContent = await songLyricTTML(id);
if (isStale()) return;
if (!ttmlContent || typeof ttmlContent !== "string") return;
const parsed = parseTTML(ttmlContent);
const lines = parsed?.lines || [];
if (!lines.length) return;
result.yrcData = lines;
ttmlAdopted = true;
};
// 处理 LRC 歌词
const adoptLRC = async () => {
try {
const data = await songLyric(id);
if (isStale()) return;
if (!data || data.code !== 200) return;
let lrcLines: LyricLine[] = [];
let yrcLines: LyricLine[] = [];
// 普通歌词
if (data?.lrc?.lyric) {
lrcLines = parseLrc(data.lrc.lyric) || [];
// 普通歌词翻译
if (data?.tlyric?.lyric)
lrcLines = this.alignLyrics(lrcLines, parseLrc(data.tlyric.lyric), "translatedLyric");
// 普通歌词音译
if (data?.romalrc?.lyric)
lrcLines = this.alignLyrics(lrcLines, parseLrc(data.romalrc.lyric), "romanLyric");
}
// 逐字歌词
if (data?.yrc?.lyric) {
yrcLines = parseYrc(data.yrc.lyric) || [];
// 逐字歌词翻译
if (data?.ytlrc?.lyric)
yrcLines = this.alignLyrics(yrcLines, parseLrc(data.ytlrc.lyric), "translatedLyric");
// 逐字歌词音译
if (data?.yromalrc?.lyric)
yrcLines = this.alignLyrics(yrcLines, parseLrc(data.yromalrc.lyric), "romanLyric");
}
if (lrcLines.length) result.lrcData = lrcLines;
// 如果没有 TTML则采用 网易云 YRC
if (!result.yrcData.length && yrcLines.length) {
result.yrcData = yrcLines;
}
// 先返回一次,避免 TTML 请求过慢
const lyricData = this.handleLyricExclude(result);
this.setFinalLyric(lyricData, req);
} catch (err) {
throw err;
const data = await songLyric(id);
if (isStale()) return;
if (!data || data.code !== 200) return;
let lrcLines: LyricLine[] = [];
let yrcLines: LyricLine[] = [];
// 普通歌词
if (data?.lrc?.lyric) {
lrcLines = parseLrc(data.lrc.lyric) || [];
// 普通歌词翻译
if (data?.tlyric?.lyric)
lrcLines = this.alignLyrics(lrcLines, parseLrc(data.tlyric.lyric), "translatedLyric");
// 普通歌词音译
if (data?.romalrc?.lyric)
lrcLines = this.alignLyrics(lrcLines, parseLrc(data.romalrc.lyric), "romanLyric");
}
// 逐字歌词
if (data?.yrc?.lyric) {
yrcLines = parseYrc(data.yrc.lyric) || [];
// 逐字歌词翻译
if (data?.ytlrc?.lyric)
yrcLines = this.alignLyrics(yrcLines, parseLrc(data.ytlrc.lyric), "translatedLyric");
// 逐字歌词音译
if (data?.yromalrc?.lyric)
yrcLines = this.alignLyrics(yrcLines, parseLrc(data.yromalrc.lyric), "romanLyric");
}
if (lrcLines.length) result.lrcData = lrcLines;
// 如果没有 TTML则采用 网易云 YRC
if (!result.yrcData.length && yrcLines.length) {
result.yrcData = yrcLines;
}
// 先返回一次,避免 TTML 请求过慢
const lyricData = this.handleLyricExclude(result);
this.setFinalLyric(lyricData, req);
};
// 设置 TTML
await Promise.allSettled([adoptTTML(), adoptLRC()]);

View File

@@ -20,6 +20,7 @@ import ExcludeLyrics from "@/components/Modal/ExcludeLyrics.vue";
import ChangeRate from "@/components/Modal/ChangeRate.vue";
import AutoClose from "@/components/Modal/AutoClose.vue";
import Equalizer from "@/components/Modal/Equalizer.vue";
import SongUnlockManager from "@/components/Modal/SongUnlockManager.vue";
import { NScrollbar } from "naive-ui";
// 用户协议
@@ -319,3 +320,17 @@ export const openDescModal = (content: string, title: string = "歌单简介") =
},
});
};
/** 打开音源管理弹窗 */
export const openSongUnlockManager = () => {
window.$modal.create({
preset: "card",
transformOrigin: "center",
autoFocus: false,
style: { width: "500px" },
title: "音源管理",
content: () => {
return h(SongUnlockManager);
},
});
};

View File

@@ -84,23 +84,34 @@ export const getUnlockSongUrl = async (songData: SongType): Promise<string | nul
const artist = Array.isArray(songData.artists) ? songData.artists[0].name : songData.artists;
const keyWord = songData.name + "-" + artist;
if (!songId || !keyWord) return null;
const servers: any[] = [
"bodian",
"netease",
];
// 尝试解锁
const promises = servers.map(server => unlockSongUrl(songId, keyWord, server));
const results = await Promise.allSettled(promises);
// 解析结果
for (const result of results) {
if (
result.status === "fulfilled" &&
result.value.code === 200 &&
result.value.url
) {
return result.value.url;
// 获取音源列表
const settingStore = useSettingStore();
const servers = settingStore.songUnlockServer
.filter((server) => server.enabled)
.map((server) => server.key);
if (servers.length === 0) return null;
// 并发请求
const promises = servers.map((server) =>
unlockSongUrl(songId, keyWord, server)
.then((result) => ({
server,
result,
success: result.code === 200 && !!result.url,
}))
.catch((err) => {
console.error(`Unlock failed with server ${server}:`, err);
return { server, result: null, success: false };
}),
);
// 按优先级顺序处理结果
for (const p of promises) {
try {
const item = await p;
if (item.success && item.result) {
return item.result.url;
}
} catch {
continue;
}
}
return null;

View File

@@ -3,6 +3,7 @@ import type { MessageReactive } from "naive-ui";
import { Howl, Howler } from "howler";
import { cloneDeep } from "lodash-es";
import { useMusicStore, useStatusStore, useDataStore, useSettingStore } from "@/stores";
import { useIntervalFn } from "@vueuse/core";
import { calculateProgress } from "./time";
import { shuffleArray, runIdle } from "./helper";
import { heartRateList } from "@/api/playlist";
@@ -23,88 +24,23 @@ import audioContextManager from "@/utils/player-utils/context";
import lyricManager from "./lyricManager";
import blob from "./blob";
// 播放器核心
// Howler.js
/* *允许播放格式 */
const allowPlayFormat = ["mp3", "flac", "webm", "ogg", "wav"];
/**
* 播放器核心
* Howler.js 音频库
*/
class Player {
/** 播放器 */
private player: Howl;
/** 定时器 */
private playerInterval: ReturnType<typeof setInterval> | undefined;
/** 自动关闭定时器 */
private autoCloseInterval: ReturnType<typeof setInterval> | undefined;
/** 频谱数据 */
private audioContext: AudioContext | null = null;
private analyser: AnalyserNode | null = null;
private dataArray: Uint8Array<ArrayBuffer> | null = null;
/** 其他数据 */
private message: MessageReactive | null = null;
/** 预载下一首歌曲播放地址缓存(仅存 URL不创建 Howl */
private nextPrefetch: { id: number; url: string | null; ublock: boolean } | null = null;
/** 并发控制:当前播放会话与初始化/切曲状态 */
private playSessionId: number = 0;
/** 是否正在切换歌曲 */
private switching: boolean = false;
/** 当前曲目重试信息(按歌曲维度计数) */
private retryInfo: { songId: number; count: number } = { songId: 0, count: 0 };
constructor() {
// 创建播放器实例
this.player = new Howl({ src: [""], format: allowPlayFormat, autoplay: false });
// 初始化媒体会话
this.initMediaSession();
// 挂载全局
window.$player = this;
}
/**
* 新建会话并返回会话 id
*/
private newSession(): number {
this.playSessionId += 1;
return this.playSessionId;
}
/**
* 检查传入会话是否过期
*/
private isStale(sessionId: number): boolean {
return sessionId !== this.playSessionId;
}
/**
* 重置底层播放器与定时器(幂等)
*/
private resetPlayerCore() {
try {
// 仅卸载当前播放器实例
if (this.player) {
this.player.stop();
this.player.off();
this.player.unload();
}
} catch {
/* empty */
}
this.cleanupAllTimers();
}
/**
* 处理播放状态
*/
private handlePlayStatus() {
const musicStore = useMusicStore();
const statusStore = useStatusStore();
const settingStore = useSettingStore();
const currentSessionId = this.playSessionId;
// 清理定时器
clearInterval(this.playerInterval);
// 更新播放状态
this.playerInterval = setInterval(() => {
// 检查会话是否过期
if (currentSessionId !== this.playSessionId) {
clearInterval(this.playerInterval);
return;
}
if (!this.player.playing()) return;
private readonly playerInterval = useIntervalFn(
() => {
if (!this.player?.playing()) return;
const musicStore = useMusicStore();
const statusStore = useStatusStore();
const settingStore = useSettingStore();
const currentTime = this.getSeek();
const duration = this.getDuration();
// 计算进度条距离
@@ -130,7 +66,47 @@ class Player {
window.electron.ipcRenderer.send("set-bar", progress);
}
}
}, 250);
},
250,
{ immediate: false },
);
/** 自动关闭定时器 */
private autoCloseInterval: ReturnType<typeof setInterval> | undefined;
/** 频谱数据 */
private audioContext: AudioContext | null = null;
private analyser: AnalyserNode | null = null;
private dataArray: Uint8Array<ArrayBuffer> | null = null;
/** 其他数据 */
private message: MessageReactive | null = null;
/** 预载下一首歌曲播放地址缓存(仅存 URL不创建 Howl */
private nextPrefetch: { id: number; url: string | null; ublock: boolean } | null = null;
/** 当前曲目重试信息(按歌曲维度计数) */
private retryInfo: { songId: number; count: number } = { songId: 0, count: 0 };
constructor() {
// 创建播放器实例
this.player = new Howl({ src: [""], format: allowPlayFormat, autoplay: false });
// 初始化媒体会话
this.initMediaSession();
// 挂载全局
window.$player = this;
}
/**
* 重置底层播放器与定时器(幂等)
*/
private resetPlayerCore() {
try {
// 仅卸载当前播放器实例
if (this.player) {
this.player.stop();
this.player.off();
this.player.unload();
}
Howler.unload();
} catch {
/* empty */
}
this.cleanupAllTimers();
}
/**
* 预载下一首歌曲的播放地址(优先官方,失败则并发尝试解灰)
@@ -215,19 +191,10 @@ class Player {
const settingStore = useSettingStore();
// 播放信息
const { id, path, type } = musicStore.playSong;
const currentSessionId = this.playSessionId;
// 检查会话是否过期
if (currentSessionId !== this.playSessionId) {
console.log("🚫 Session expired, skipping player creation");
return;
}
// 统一重置底层播放器
this.resetPlayerCore();
// 二次检查会话
if (currentSessionId !== this.playSessionId) {
console.log("🚫 Session expired after cleanup, aborting");
return;
}
// 创建播放器
this.player = new Howl({
src,
@@ -250,8 +217,7 @@ class Player {
// else resetSongLyric();
// 获取歌词数据
lyricManager.handleLyric(id, path);
// 定时获取状态
if (!this.playerInterval) this.handlePlayStatus();
// 新增播放历史
if (type !== "radio") dataStore.setHistory(musicStore.playSong);
// 获取歌曲封面主色
@@ -279,10 +245,8 @@ class Player {
const playSongData = getPlaySongData();
// 获取配置
const { seek } = options;
const currentSessionId = this.playSessionId;
// 初次加载
this.player.once("load", () => {
if (currentSessionId !== this.playSessionId) return;
// 允许跨域
if (settingStore.showSpectrums) {
const audioDom = this.getAudioDom();
@@ -325,8 +289,8 @@ class Player {
});
// 播放
this.player.on("play", () => {
if (currentSessionId !== this.playSessionId) return;
window.document.title = getPlayerInfo() || "SPlayer";
this.playerInterval.resume();
// 重置重试计数
try {
const current = getPlaySongData();
@@ -344,8 +308,8 @@ class Player {
});
// 暂停
this.player.on("pause", () => {
if (currentSessionId !== this.playSessionId) return;
if (!isElectron) window.document.title = "SPlayer";
this.playerInterval.pause();
// ipc
if (isElectron) {
window.electron.ipcRenderer.send("play-status-change", false);
@@ -354,7 +318,7 @@ class Player {
});
// 结束
this.player.on("end", () => {
if (currentSessionId !== this.playSessionId) return;
this.playerInterval.pause();
// statusStore.playStatus = false;
console.log("⏹️ song end:", playSongData);
@@ -374,13 +338,11 @@ class Player {
});
// 错误
this.player.on("loaderror", (sourceid, err: unknown) => {
if (currentSessionId !== this.playSessionId) return;
const code = typeof err === "number" ? err : undefined;
this.handlePlaybackError(code);
console.error("❌ song error:", sourceid, playSongData, err);
});
this.player.on("playerror", (sourceid, err: unknown) => {
if (currentSessionId !== this.playSessionId) return;
const code = typeof err === "number" ? err : undefined;
this.handlePlaybackError(code);
console.error("❌ song play error:", sourceid, playSongData, err);
@@ -507,7 +469,6 @@ class Player {
}
// 超过次数:切到下一首或清空
this.retryInfo.count = 0;
this.switching = false;
if (dataStore.playList.length > 1) {
window.$message.error("当前歌曲播放失败,已跳至下一首");
await this.nextOrPrev("next");
@@ -589,7 +550,6 @@ class Player {
const musicStore = useMusicStore();
const statusStore = useStatusStore();
const settingStore = useSettingStore();
const sessionId = this.newSession();
try {
// 获取播放数据
@@ -609,7 +569,6 @@ class Player {
// 本地歌曲
if (path) {
if (this.isStale(sessionId)) return;
try {
await this.createPlayer(`file://${path}`, autoPlay, seek);
await this.parseLocalMusicInfo(path);
@@ -658,20 +617,18 @@ class Player {
if (!playerUrl) {
window.$message.error("该歌曲暂无音源,跳至下一首");
this.switching = false;
await this.nextOrPrev("next");
return;
}
} catch (err) {
console.error("❌ 获取歌曲地址出错:", err);
window.$message.error("获取歌曲地址失败,跳至下一首");
this.switching = false;
await this.nextOrPrev("next");
return;
}
// 有有效 URL 才创建播放器
if (playerUrl && !this.isStale(sessionId)) {
if (playerUrl) {
try {
await this.createPlayer(playerUrl, autoPlay, seek);
} catch (err) {
@@ -682,10 +639,7 @@ class Player {
} catch (err) {
console.error("❌ 初始化音乐播放器出错:", err);
window.$message.error("播放遇到错误,尝试下一首");
this.switching = false;
await this.nextOrPrev("next");
} finally {
this.switching = false;
}
}
/**
@@ -725,7 +679,7 @@ class Player {
// 播放器未加载完成或不存在
if (!this.player || this.player.state() !== "loaded") {
if (changeStatus) statusStore.playStatus = false;
window.$message.warning("播放器未加载完成,请稍后重试");
return;
}
// 立即设置播放状态
@@ -758,12 +712,6 @@ class Player {
const dataStore = useDataStore();
const musicStore = useMusicStore();
try {
if (this.switching) {
console.log("🔄 Already switching, ignoring request");
return;
}
this.switching = true;
// 立即更新UI状态防止用户重复点击
statusStore.playLoading = true;
statusStore.playStatus = false;
@@ -818,16 +766,12 @@ class Player {
// 重置播放进度(切换歌曲时必须重置)
statusStore.currentTime = 0;
statusStore.progress = 0;
// 暂停当前播放
await this.pause(false);
// 初始化播放器不传入seek参数确保从头开始播放
await this.initPlayer(play, 0);
} catch (error) {
console.error("Error in nextOrPrev:", error);
statusStore.playLoading = false;
throw error;
} finally {
this.switching = false;
}
}
/**
@@ -1046,7 +990,6 @@ class Player {
// 查找索引(在处理后的列表中查找)
statusStore.playIndex = processedData.findIndex((item) => item.id === song.id);
// 播放
await this.pause(false);
await this.initPlayer();
}
} else {
@@ -1055,7 +998,6 @@ class Player {
? Math.floor(Math.random() * processedData.length)
: 0;
// 播放
await this.pause(false);
await this.initPlayer();
}
// 更改播放歌单
@@ -1095,11 +1037,6 @@ class Player {
const dataStore = useDataStore();
const statusStore = useStatusStore();
try {
if (this.switching) {
console.log("🔄 Already switching, ignoring request");
return;
}
this.switching = true;
// 立即更新UI状态防止用户重复点击
statusStore.playLoading = true;
statusStore.playStatus = false;
@@ -1118,8 +1055,6 @@ class Player {
statusStore.currentTime = 0;
statusStore.progress = 0;
statusStore.lyricIndex = -1;
// 暂停当前播放
await this.pause(false);
// 清理定时器,防止旧定时器继续运行
this.cleanupAllTimers();
// 清理并播放不传入seek参数确保从头开始播放
@@ -1128,8 +1063,6 @@ class Player {
console.error("Error in togglePlayIndex:", error);
statusStore.playLoading = false;
throw error;
} finally {
this.switching = false;
}
}
/**
@@ -1469,9 +1402,8 @@ class Player {
*/
private cleanupAllTimers() {
// 清理播放状态定时器
if (this.playerInterval) {
clearInterval(this.playerInterval);
this.playerInterval = undefined;
if (this.playerInterval.isActive.value) {
this.playerInterval.pause();
}
// 清理自动关闭定时器
if (this.autoCloseInterval) {
@@ -1494,4 +1426,15 @@ class Player {
}
}
export default new Player();
// export default new Player();
let _player: Player | null = null;
/**
* 获取播放器实例
* @returns Player
*/
export const usePlayer = (): Player => {
if (!_player) _player = new Player();
return _player;
};

View File

@@ -1,3 +1,13 @@
/**
* 歌曲解锁服务器
*/
export enum SongUnlockServer {
NETEASE = "netease",
BODIAN = "bodian",
// KUWO = "kuwo",
GEQUBAO = "gequbao",
}
class SongManager {}
export default new SongManager();

View File

@@ -16,7 +16,7 @@ import { artistAllSongs } from "@/api/artist";
import { songDetail } from "@/api/song";
import { formatSongsList } from "@/utils/format";
import { debounce } from "lodash-es";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const props = defineProps<{
id: number;
@@ -26,6 +26,8 @@ const emit = defineEmits<{
scroll: [e: Event];
}>();
const player = usePlayer();
// 歌曲数据
const loading = ref<boolean>(true);
const hasMore = ref<boolean>(true);

View File

@@ -97,8 +97,9 @@ import { userCloud } from "@/api/cloud";
import { formatSongsList } from "@/utils/format";
import { fuzzySearch, renderIcon } from "@/utils/helper";
import { openBatchList } from "@/utils/modal";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const router = useRouter();
const dataStore = useDataStore();

View File

@@ -54,8 +54,9 @@ import { updateDailySongsData } from "@/utils/auth";
import { formatTimestamp } from "@/utils/time";
import { renderIcon } from "@/utils/helper";
import { openBatchList } from "@/utils/modal";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const musicStore = useMusicStore();
// 更新日期

View File

@@ -58,8 +58,9 @@
<script setup lang="ts">
import { useDataStore } from "@/stores";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const player = usePlayer();
const dataStore = useDataStore();
// 清空最近播放

View File

@@ -181,10 +181,11 @@ import { useDataStore, useStatusStore } from "@/stores";
import { debounce } from "lodash-es";
import { formatTimestamp } from "@/utils/time";
import { openDescModal, openJumpArtist } from "@/utils/modal";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
import { toLikeAlbum } from "@/utils/auth";
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const statusStore = useStatusStore();

View File

@@ -54,7 +54,7 @@
<SvgIcon name="Update" :depth="3" />
<n-text>{{ formatTimestamp(playlistDetailData.updateTime) }}</n-text>
</div>
<div v-else-if="playlistDetailData.createTime" class="item">
<div v-if="playlistDetailData.createTime" class="item">
<SvgIcon name="Time" :depth="3" />
<n-text>{{ formatTimestamp(playlistDetailData.createTime) }}</n-text>
</div>
@@ -176,9 +176,10 @@ import { useDataStore, useStatusStore } from "@/stores";
import { openBatchList, openDescModal, openUpdatePlaylist } from "@/utils/modal";
import { formatTimestamp } from "@/utils/time";
import { isLogin, updateUserLikePlaylist } from "@/utils/auth";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const statusStore = useStatusStore();

View File

@@ -71,7 +71,7 @@
<SvgIcon name="Update" :depth="3" />
<n-text>{{ formatTimestamp(playlistDetailData.updateTime) }}</n-text>
</div>
<div v-else-if="playlistDetailData.createTime" class="item">
<div v-if="playlistDetailData.createTime" class="item">
<SvgIcon name="Time" :depth="3" />
<n-text>{{ formatTimestamp(playlistDetailData.createTime) }}</n-text>
</div>
@@ -226,9 +226,10 @@ import { debounce } from "lodash-es";
import { useDataStore, useStatusStore } from "@/stores";
import { openBatchList, openDescModal, openUpdatePlaylist } from "@/utils/modal";
import { formatTimestamp } from "@/utils/time";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const statusStore = useStatusStore();

View File

@@ -179,12 +179,13 @@ import { renderToolbar } from "@/utils/meta";
import { debounce } from "lodash-es";
import { useDataStore, useStatusStore } from "@/stores";
import { radioAllProgram, radioDetail } from "@/api/radio";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
import { formatTimestamp } from "@/utils/time";
import { toSubRadio } from "@/utils/auth";
import { openDescModal } from "@/utils/modal";
const router = useRouter();
const player = usePlayer();
const dataStore = useDataStore();
const statusStore = useStatusStore();

View File

@@ -151,9 +151,10 @@ import { formatSongsList } from "@/utils/format";
import { uniqBy, flattenDeep, debounce } from "lodash-es";
import { changeLocalMusicPath, fuzzySearch, renderIcon } from "@/utils/helper";
import { openBatchList } from "@/utils/modal";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
const router = useRouter();
const player = usePlayer();
const localStore = useLocalStore();
const settingStore = useSettingStore();

View File

@@ -132,13 +132,14 @@ import { formatCommentList, formatCoverList } from "@/utils/format";
import { isArray, isEmpty } from "lodash-es";
import { formatNumber } from "@/utils/helper";
import { getComment } from "@/api/comment";
import player from "@/utils/player";
import { usePlayer } from "@/utils/player";
// Plyr
import Plyr from "plyr";
import "plyr/dist/plyr.css";
import { formatTimestamp } from "@/utils/time";
const router = useRouter();
const player = usePlayer();
const statusStore = useStatusStore();
// 是否激活