refactor: library page

master
qier222 4 years ago
parent b537081f2a
commit 603e39f362
No known key found for this signature in database
GPG Key ID: 9C85007ED905F14D

@ -20,18 +20,18 @@
</template> </template>
<script> <script>
import ModalAddTrackToPlaylist from "./components/ModalAddTrackToPlaylist.vue"; import ModalAddTrackToPlaylist from './components/ModalAddTrackToPlaylist.vue';
import ModalNewPlaylist from "./components/ModalNewPlaylist.vue"; import ModalNewPlaylist from './components/ModalNewPlaylist.vue';
import Navbar from "./components/Navbar.vue"; import Navbar from './components/Navbar.vue';
import Player from "./components/Player.vue"; import Player from './components/Player.vue';
import Toast from "./components/Toast.vue"; import Toast from './components/Toast.vue';
import { ipcRenderer } from "./electron/ipcRenderer"; import { ipcRenderer } from './electron/ipcRenderer';
import { isAccountLoggedIn } from "@/utils/auth"; import { isAccountLoggedIn, isLooseLoggedIn } from '@/utils/auth';
import Lyrics from "./views/lyrics.vue"; import Lyrics from './views/lyrics.vue';
import { mapState } from "vuex"; import { mapState } from 'vuex';
export default { export default {
name: "App", name: 'App',
components: { components: {
Navbar, Navbar,
Player, Player,
@ -46,50 +46,60 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(["showLyrics", "showLibraryDefault", "player"]), ...mapState(['showLyrics', 'showLibraryDefault', 'player']),
isAccountLoggedIn() { isAccountLoggedIn() {
return isAccountLoggedIn(); return isAccountLoggedIn();
}, },
showPlayer() { showPlayer() {
return ( return (
[ [
"mv", 'mv',
"loginUsername", 'loginUsername',
"login", 'login',
"loginAccount", 'loginAccount',
"lastfmCallback", 'lastfmCallback',
].includes(this.$route.name) === false ].includes(this.$route.name) === false
); );
}, },
enablePlayer() { enablePlayer() {
return this.player.enabled && this.$route.name !== "lastfmCallback"; return this.player.enabled && this.$route.name !== 'lastfmCallback';
}, },
showNavbar() { showNavbar() {
return this.$route.name !== "lastfmCallback"; return this.$route.name !== 'lastfmCallback';
}, },
}, },
created() { created() {
this.showLibraryDefault && this.$router.push("/library"); this.showLibraryDefault && this.$router.push('/library');
if (this.isElectron) { if (this.isElectron) ipcRenderer(this);
ipcRenderer(this); window.addEventListener('keydown', this.handleKeydown);
} this.fetchData();
window.addEventListener("keydown", this.handleKeydown);
}, },
methods: { methods: {
handleKeydown(e) { handleKeydown(e) {
if (e.code === "Space") { if (e.code === 'Space') {
if (e.target.tagName === "INPUT") return false; if (e.target.tagName === 'INPUT') return false;
if (this.$route.name === "mv") return false; if (this.$route.name === 'mv') return false;
e.preventDefault(); e.preventDefault();
this.player.play(); this.player.play();
} }
}, },
fetchData() {
if (!isLooseLoggedIn()) return;
this.$store.dispatch('fetchLikedSongs');
this.$store.dispatch('fetchLikedSongsWithDetails');
this.$store.dispatch('fetchLikedPlaylist');
if (isAccountLoggedIn()) {
this.$store.dispatch('fetchLikedAlbums');
this.$store.dispatch('fetchLikedArtists');
this.$store.dispatch('fetchLikedMVs');
}
},
}, },
}; };
</script> </script>
<style lang="scss"> <style lang="scss">
@import url("https://fonts.googleapis.com/css2?family=Barlow:ital,wght@0,500;0,600;0,700;0,800;0,900;1,500;1,600;1,700;1,800;1,900&display=swap"); @import url('https://fonts.googleapis.com/css2?family=Barlow:ital,wght@0,500;0,600;0,700;0,800;0,900;1,500;1,600;1,700;1,800;1,900&display=swap');
:root { :root {
--color-body-bg: #ffffff; --color-body-bg: #ffffff;
@ -103,7 +113,7 @@ export default {
--color-secondary-bg-for-transparent: rgba(209, 209, 214, 0.28); --color-secondary-bg-for-transparent: rgba(209, 209, 214, 0.28);
} }
[data-theme="dark"] { [data-theme='dark'] {
--color-body-bg: #222222; --color-body-bg: #222222;
--color-text: #ffffff; --color-text: #ffffff;
--color-primary: #335eea; --color-primary: #335eea;
@ -121,7 +131,7 @@ export default {
} }
#app, #app,
input { input {
font-family: "Barlow", -apple-system, BlinkMacSystemFont, Helvetica Neue, font-family: 'Barlow', -apple-system, BlinkMacSystemFont, Helvetica Neue,
PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC,
WenQuanYi Micro Hei, sans-serif; WenQuanYi Micro Hei, sans-serif;
} }
@ -195,7 +205,7 @@ main::-webkit-scrollbar {
background: rgba(128, 128, 128, 0.38); background: rgba(128, 128, 128, 0.38);
} }
[data-theme="dark"] ::-webkit-scrollbar-thumb { [data-theme='dark'] ::-webkit-scrollbar-thumb {
background: var(--color-secondary-bg); background: var(--color-secondary-bg);
} }
@ -207,7 +217,7 @@ main::-webkit-scrollbar {
transform: translateY(100%); transform: translateY(100%);
} }
[data-electron="yes"] { [data-electron='yes'] {
button, button,
.navigation-links a, .navigation-links a,
.playlist-info .description { .playlist-info .description {

@ -1,4 +1,4 @@
import request from "@/utils/request"; import request from '@/utils/request';
/** /**
* 获取用户详情 * 获取用户详情
@ -8,10 +8,11 @@ import request from "@/utils/request";
*/ */
export function userDetail(uid) { export function userDetail(uid) {
return request({ return request({
url: "/user/detail", url: '/user/detail',
method: "get", method: 'get',
params: { params: {
uid, uid,
timestamp: new Date().getTime(),
}, },
}); });
} }
@ -22,8 +23,8 @@ export function userDetail(uid) {
*/ */
export function userAccount() { export function userAccount() {
return request({ return request({
url: "/user/account", url: '/user/account',
method: "get", method: 'get',
params: { params: {
timestamp: new Date().getTime(), timestamp: new Date().getTime(),
}, },
@ -43,8 +44,8 @@ export function userAccount() {
*/ */
export function userPlaylist(params) { export function userPlaylist(params) {
return request({ return request({
url: "/user/playlist", url: '/user/playlist',
method: "get", method: 'get',
params, params,
}); });
} }
@ -57,8 +58,8 @@ export function userPlaylist(params) {
*/ */
export function userLikedSongsIDs(uid) { export function userLikedSongsIDs(uid) {
return request({ return request({
url: "/likelist", url: '/likelist',
method: "get", method: 'get',
params: { params: {
uid, uid,
timestamp: new Date().getTime(), timestamp: new Date().getTime(),
@ -74,8 +75,8 @@ export function userLikedSongsIDs(uid) {
*/ */
export function dailySignin(type = 0) { export function dailySignin(type = 0) {
return request({ return request({
url: "/daily_signin", url: '/daily_signin',
method: "post", method: 'post',
params: { params: {
type, type,
timestamp: new Date().getTime(), timestamp: new Date().getTime(),
@ -94,8 +95,8 @@ export function dailySignin(type = 0) {
*/ */
export function likedAlbums() { export function likedAlbums() {
return request({ return request({
url: "/album/sublist", url: '/album/sublist',
method: "get", method: 'get',
params: { params: {
timestamp: new Date().getTime(), timestamp: new Date().getTime(),
}, },
@ -108,8 +109,8 @@ export function likedAlbums() {
*/ */
export function likedArtists() { export function likedArtists() {
return request({ return request({
url: "/artist/sublist", url: '/artist/sublist',
method: "get", method: 'get',
params: { params: {
timestamp: new Date().getTime(), timestamp: new Date().getTime(),
}, },
@ -122,8 +123,8 @@ export function likedArtists() {
*/ */
export function likedMVs() { export function likedMVs() {
return request({ return request({
url: "/mv/sublist", url: '/mv/sublist',
method: "get", method: 'get',
params: { params: {
timestamp: new Date().getTime(), timestamp: new Date().getTime(),
}, },

@ -11,14 +11,14 @@
<vue-slider <vue-slider
v-model="player.progress" v-model="player.progress"
:min="0" :min="0"
:max="player.currentTrackDuration" :max="player.currentTrackDuration + 1"
:interval="1" :interval="1"
:drag-on-click="true" :drag-on-click="true"
:duration="0" :duration="0"
:dot-size="12" :dot-size="12"
:height="2" :height="2"
:tooltip-formatter="formatTrackTime" :tooltip-formatter="formatTrackTime"
@drag-end="player.seek" :lazy="true"
></vue-slider> ></vue-slider>
</div> </div>
<div class="controls"> <div class="controls">
@ -167,20 +167,20 @@
</template> </template>
<script> <script>
import { mapState, mapMutations, mapActions } from "vuex"; import { mapState, mapMutations, mapActions } from 'vuex';
import "@/assets/css/slider.css"; import '@/assets/css/slider.css';
import ButtonIcon from "@/components/ButtonIcon.vue"; import ButtonIcon from '@/components/ButtonIcon.vue';
import VueSlider from "vue-slider-component"; import VueSlider from 'vue-slider-component';
export default { export default {
name: "Player", name: 'Player',
components: { components: {
ButtonIcon, ButtonIcon,
VueSlider, VueSlider,
}, },
computed: { computed: {
...mapState(["player", "settings", "data"]), ...mapState(['player', 'settings', 'data']),
currentTrack() { currentTrack() {
return this.player.currentTrack; return this.player.currentTrack;
}, },
@ -196,47 +196,47 @@ export default {
return this.player.playing; return this.player.playing;
}, },
audioSource() { audioSource() {
return this.player._howler?._src.includes("kuwo.cn") return this.player._howler?._src.includes('kuwo.cn')
? "音源来自酷我音乐" ? '音源来自酷我音乐'
: ""; : '';
}, },
}, },
methods: { methods: {
...mapMutations(["toggleLyrics"]), ...mapMutations(['toggleLyrics']),
...mapActions(["showToast", "likeASong"]), ...mapActions(['showToast', 'likeASong']),
goToNextTracksPage() { goToNextTracksPage() {
if (this.player.isPersonalFM) return; if (this.player.isPersonalFM) return;
this.$route.name === "next" this.$route.name === 'next'
? this.$router.go(-1) ? this.$router.go(-1)
: this.$router.push({ name: "next" }); : this.$router.push({ name: 'next' });
}, },
formatTrackTime(value) { formatTrackTime(value) {
if (!value) return ""; if (!value) return '';
let min = ~~((value / 60) % 60); let min = ~~((value / 60) % 60);
let sec = (~~(value % 60)).toString().padStart(2, "0"); let sec = (~~(value % 60)).toString().padStart(2, '0');
return `${min}:${sec}`; return `${min}:${sec}`;
}, },
goToList() { goToList() {
if (this.player.playlistSource.id === this.data.likedSongPlaylistID) { if (this.player.playlistSource.id === this.data.likedSongPlaylistID) {
this.$router.push({ path: "/library/liked-songs" }); this.$router.push({ path: '/library/liked-songs' });
} else if (this.player.playlistSource.type === "url") { } else if (this.player.playlistSource.type === 'url') {
this.$router.push({ path: this.player.playlistSource.id }); this.$router.push({ path: this.player.playlistSource.id });
} else { } else {
this.$router.push({ this.$router.push({
path: path:
"/" + '/' +
this.player.playlistSource.type + this.player.playlistSource.type +
"/" + '/' +
this.player.playlistSource.id, this.player.playlistSource.id,
}); });
} }
}, },
goToAlbum() { goToAlbum() {
if (this.player.currentTrack.al.id === 0) return; if (this.player.currentTrack.al.id === 0) return;
this.$router.push({ path: "/album/" + this.player.currentTrack.al.id }); this.$router.push({ path: '/album/' + this.player.currentTrack.al.id });
}, },
goToArtist(id) { goToArtist(id) {
this.$router.push({ path: "/artist/" + id }); this.$router.push({ path: '/artist/' + id });
}, },
}, },
}; };

@ -9,14 +9,14 @@
</div> </div>
</div> </div>
<hr /> <hr />
<div class="item" @click="play">{{ $t("contextMenu.play") }}</div> <div class="item" @click="play">{{ $t('contextMenu.play') }}</div>
<div class="item" @click="playNext">{{ $t("contextMenu.playNext") }}</div> <div class="item" @click="playNext">{{ $t('contextMenu.playNext') }}</div>
<hr /> <hr />
<div class="item" @click="like" v-show="!isRightClickedTrackLiked"> <div class="item" @click="like" v-show="!isRightClickedTrackLiked">
{{ $t("contextMenu.saveToMyLikedSongs") }} {{ $t('contextMenu.saveToMyLikedSongs') }}
</div> </div>
<div class="item" @click="like" v-show="isRightClickedTrackLiked"> <div class="item" @click="like" v-show="isRightClickedTrackLiked">
{{ $t("contextMenu.removeFromMyLikedSongs") }} {{ $t('contextMenu.removeFromMyLikedSongs') }}
</div> </div>
<div <div
v-if="extraContextMenuItem.includes('removeTrackFromPlaylist')" v-if="extraContextMenuItem.includes('removeTrackFromPlaylist')"
@ -40,16 +40,16 @@
</template> </template>
<script> <script>
import { mapActions, mapMutations, mapState } from "vuex"; import { mapActions, mapMutations, mapState } from 'vuex';
import { likeATrack } from "@/api/track"; import { likeATrack } from '@/api/track';
import { addOrRemoveTrackFromPlaylist } from "@/api/playlist"; import { addOrRemoveTrackFromPlaylist } from '@/api/playlist';
import { isAccountLoggedIn } from "@/utils/auth"; import { isAccountLoggedIn } from '@/utils/auth';
import TrackListItem from "@/components/TrackListItem.vue"; import TrackListItem from '@/components/TrackListItem.vue';
import ContextMenu from "@/components/ContextMenu.vue"; import ContextMenu from '@/components/ContextMenu.vue';
export default { export default {
name: "TrackList", name: 'TrackList',
components: { components: {
TrackListItem, TrackListItem,
ContextMenu, ContextMenu,
@ -60,14 +60,14 @@ export default {
id: Number, id: Number,
dbclickTrackFunc: { dbclickTrackFunc: {
type: String, type: String,
default: "default", default: 'default',
}, },
albumObject: { albumObject: {
type: Object, type: Object,
default: () => { default: () => {
return { return {
artist: { artist: {
name: "", name: '',
}, },
}; };
}, },
@ -88,38 +88,38 @@ export default {
}, },
itemKey: { itemKey: {
type: String, type: String,
default: "id", default: 'id',
}, },
}, },
data() { data() {
return { return {
rightClickedTrack: { rightClickedTrack: {
id: 0, id: 0,
name: "", name: '',
ar: [{ name: "" }], ar: [{ name: '' }],
al: { picUrl: "" }, al: { picUrl: '' },
}, },
listStyles: {}, listStyles: {},
}; };
}, },
created() { created() {
if (this.type === "tracklist") { if (this.type === 'tracklist') {
this.listStyles = { this.listStyles = {
display: "grid", display: 'grid',
gap: "4px", gap: '4px',
gridTemplateColumns: `repeat(${this.columnNumber}, 1fr)`, gridTemplateColumns: `repeat(${this.columnNumber}, 1fr)`,
}; };
} }
}, },
computed: { computed: {
...mapState(["liked"]), ...mapState(['liked']),
isRightClickedTrackLiked() { isRightClickedTrackLiked() {
return this.liked.songs.includes(this.rightClickedTrack?.id); return this.liked.songs.includes(this.rightClickedTrack?.id);
}, },
}, },
methods: { methods: {
...mapMutations(["updateLikedSongs", "updateModal"]), ...mapMutations(['updateLikedSongs', 'updateModal']),
...mapActions(["nextTrack", "showToast"]), ...mapActions(['nextTrack', 'showToast']),
openMenu(e, track) { openMenu(e, track) {
this.rightClickedTrack = track; this.rightClickedTrack = track;
this.$refs.menu.openMenu(e); this.$refs.menu.openMenu(e);
@ -127,49 +127,49 @@ export default {
closeMenu() { closeMenu() {
this.rightClickedTrack = { this.rightClickedTrack = {
id: 0, id: 0,
name: "", name: '',
ar: [{ name: "" }], ar: [{ name: '' }],
al: { picUrl: "" }, al: { picUrl: '' },
}; };
}, },
playThisList(trackID) { playThisList(trackID) {
if (this.dbclickTrackFunc === "default") { if (this.dbclickTrackFunc === 'default') {
this.playThisListDefault(trackID); this.playThisListDefault(trackID);
} else if (this.dbclickTrackFunc === "none") { } else if (this.dbclickTrackFunc === 'none') {
// do nothing // do nothing
} else if (this.dbclickTrackFunc === "playTrackOnListByID") { } else if (this.dbclickTrackFunc === 'playTrackOnListByID') {
this.$store.state.player.playTrackOnListByID(trackID); this.$store.state.player.playTrackOnListByID(trackID);
} else if (this.dbclickTrackFunc === "playPlaylistByID") { } else if (this.dbclickTrackFunc === 'playPlaylistByID') {
this.$store.state.player.playPlaylistByID(this.id, trackID); this.$store.state.player.playPlaylistByID(this.id, trackID);
} else if (this.dbclickTrackFunc === "playAList") { } else if (this.dbclickTrackFunc === 'playAList') {
let trackIDs = this.tracks.map((t) => t.id); let trackIDs = this.tracks.map(t => t.id);
this.$store.state.player.replacePlaylist( this.$store.state.player.replacePlaylist(
trackIDs, trackIDs,
this.id, this.id,
"artist", 'artist',
trackID trackID
); );
} else if (this.dbclickTrackFunc === "dailyTracks") { } else if (this.dbclickTrackFunc === 'dailyTracks') {
let trackIDs = this.tracks.map((t) => t.id); let trackIDs = this.tracks.map(t => t.id);
this.$store.state.player.replacePlaylist( this.$store.state.player.replacePlaylist(
trackIDs, trackIDs,
"/daily/songs", '/daily/songs',
"url", 'url',
trackID trackID
); );
} }
}, },
playThisListDefault(trackID) { playThisListDefault(trackID) {
if (this.type === "playlist") { if (this.type === 'playlist') {
this.$store.state.player.playPlaylistByID(this.id, trackID); this.$store.state.player.playPlaylistByID(this.id, trackID);
} else if (this.type === "album") { } else if (this.type === 'album') {
this.$store.state.player.playAlbumByID(this.id, trackID); this.$store.state.player.playAlbumByID(this.id, trackID);
} else if (this.type === "tracklist") { } else if (this.type === 'tracklist') {
let trackIDs = this.tracks.map((t) => t.id); let trackIDs = this.tracks.map(t => t.id);
this.$store.state.player.replacePlaylist( this.$store.state.player.replacePlaylist(
trackIDs, trackIDs,
this.id, this.id,
"artist", 'artist',
trackID trackID
); );
} }
@ -188,19 +188,19 @@ export default {
}, },
likeASong(id) { likeASong(id) {
if (!isAccountLoggedIn()) { if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号"); this.showToast('此操作需要登录网易云账号');
return; return;
} }
let like = true; let like = true;
let likedSongs = this.liked.songs; let likedSongs = this.liked.songs;
if (likedSongs.includes(id)) like = false; if (likedSongs.includes(id)) like = false;
likeATrack({ id, like }).then((data) => { likeATrack({ id, like }).then(data => {
if (data.code !== 200) return; if (data.code !== 200) return;
if (like === false) { if (like === false) {
this.showToast(this.$t("toast.removedFromMyLikedSongs")); this.showToast(this.$t('toast.removedFromMyLikedSongs'));
this.updateLikedSongs(likedSongs.filter((d) => d !== id)); this.updateLikedSongs(likedSongs.filter(d => d !== id));
} else { } else {
this.showToast(this.$t("toast.savedToMyLikedSongs")); this.showToast(this.$t('toast.savedToMyLikedSongs'));
likedSongs.push(id); likedSongs.push(id);
this.updateLikedSongs(likedSongs); this.updateLikedSongs(likedSongs);
} }
@ -208,34 +208,34 @@ export default {
}, },
addTrackToPlaylist() { addTrackToPlaylist() {
if (!isAccountLoggedIn()) { if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号"); this.showToast('此操作需要登录网易云账号');
return; return;
} }
this.updateModal({ this.updateModal({
modalName: "addTrackToPlaylistModal", modalName: 'addTrackToPlaylistModal',
key: "show", key: 'show',
value: true, value: true,
}); });
this.updateModal({ this.updateModal({
modalName: "addTrackToPlaylistModal", modalName: 'addTrackToPlaylistModal',
key: "selectedTrackID", key: 'selectedTrackID',
value: this.rightClickedTrack.id, value: this.rightClickedTrack.id,
}); });
}, },
removeTrackFromPlaylist() { removeTrackFromPlaylist() {
if (!isAccountLoggedIn()) { if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号"); this.showToast('此操作需要登录网易云账号');
return; return;
} }
if (confirm(`确定要从歌单删除 ${this.rightClickedTrack.name}`)) { if (confirm(`确定要从歌单删除 ${this.rightClickedTrack.name}`)) {
let trackID = this.rightClickedTrack.id; let trackID = this.rightClickedTrack.id;
addOrRemoveTrackFromPlaylist({ addOrRemoveTrackFromPlaylist({
op: "del", op: 'del',
pid: this.id, pid: this.id,
tracks: trackID, tracks: trackID,
}).then((data) => { }).then(data => {
this.showToast( this.showToast(
data.body.code === 200 ? "已从歌单中删除" : data.body.message data.body.code === 200 ? '已从歌单中删除' : data.body.message
); );
this.$parent.removeTrack(trackID); this.$parent.removeTrack(trackID);
}); });

@ -1,18 +1,27 @@
// import store, { state, dispatch, commit } from "@/store"; // import store, { state, dispatch, commit } from "@/store";
import { isAccountLoggedIn } from "@/utils/auth"; import { isAccountLoggedIn, isLooseLoggedIn } from '@/utils/auth';
import { likeATrack } from "@/api/track"; import { likeATrack } from '@/api/track';
import { getPlaylistDetail } from '@/api/playlist';
import { getTrackDetail } from '@/api/track';
import {
userPlaylist,
userLikedSongsIDs,
likedAlbums,
likedArtists,
likedMVs,
} from '@/api/user';
export default { export default {
showToast({ state, commit }, text) { showToast({ state, commit }, text) {
if (state.toast.timer !== null) { if (state.toast.timer !== null) {
clearTimeout(state.toast.timer); clearTimeout(state.toast.timer);
commit("updateToast", { show: false, text: "", timer: null }); commit('updateToast', { show: false, text: '', timer: null });
} }
commit("updateToast", { commit('updateToast', {
show: true, show: true,
text, text,
timer: setTimeout(() => { timer: setTimeout(() => {
commit("updateToast", { commit('updateToast', {
show: false, show: false,
text: state.toast.text, text: state.toast.text,
timer: null, timer: null,
@ -20,23 +29,117 @@ export default {
}, 3200), }, 3200),
}); });
}, },
likeASong({ state, commit, dispatch }, id) { likeATrack({ state, commit, dispatch }, id) {
if (!isAccountLoggedIn()) { if (!isAccountLoggedIn()) {
dispatch("showToast", "此操作需要登录网易云账号"); dispatch('showToast', '此操作需要登录网易云账号');
return; return;
} }
let like = true; let like = true;
if (state.liked.songs.includes(id)) like = false; if (state.liked.songs.includes(id)) like = false;
likeATrack({ id, like }).then(() => { likeATrack({ id, like }).then(() => {
if (like === false) { if (like === false) {
commit( commit('updateLikedXXX', {
"updateLikedSongs", name: 'songs',
state.liked.songs.filter((d) => d !== id) data: state.liked.songs.filter(d => d !== id),
); });
} else { } else {
let newLikeSongs = state.liked.songs; let newLikeSongs = state.liked.songs;
newLikeSongs.push(id); newLikeSongs.push(id);
commit("updateLikedSongs", newLikeSongs); commit('updateLikedXXX', {
name: 'songs',
data: newLikeSongs,
});
}
dispatch('fetchLikedSongsWithDetails');
});
},
fetchLikedSongs: ({ state, commit }) => {
if (!isLooseLoggedIn()) return;
console.debug('[debug][actions.js] fetchLikedSongs');
if (isAccountLoggedIn()) {
return userLikedSongsIDs({ uid: state.data.user.userId }).then(result => {
if (result.ids) {
commit('updateLikedXXX', {
name: 'songs',
data: result.ids,
});
}
});
} else {
// TODO:搜索ID登录的用户
}
},
fetchLikedSongsWithDetails: ({ state, commit }) => {
console.debug('[debug][actions.js] fetchLikedSongsWithDetails');
return getPlaylistDetail(state.data.likedSongPlaylistID, true).then(
result => {
return getTrackDetail(
result.playlist.trackIds
.slice(0, 12)
.map(t => t.id)
.join(',')
).then(result => {
commit('updateLikedXXX', {
name: 'songsWithDetails',
data: result.songs,
});
});
}
);
},
fetchLikedPlaylist: ({ state, commit }) => {
if (!isLooseLoggedIn()) return;
console.debug('[debug][actions.js] fetchLikedPlaylist');
if (isAccountLoggedIn()) {
return userPlaylist({
uid: state.data.user.userId,
limit: 2000, // 最多只加载2000个歌单等有用户反馈问题再修
timestamp: new Date().getTime(),
}).then(result => {
if (result.playlist) {
commit('updateLikedXXX', {
name: 'playlists',
data: result.playlist,
});
}
});
} else {
// TODO:搜索ID登录的用户
}
},
fetchLikedAlbums: ({ commit }) => {
if (!isAccountLoggedIn()) return;
console.debug('[debug][actions.js] fetchLikedAlbums');
return likedAlbums({ limit: 2000 }).then(result => {
if (result.data) {
commit('updateLikedXXX', {
name: 'albums',
data: result.data,
});
}
});
},
fetchLikedArtists: ({ commit }) => {
if (!isAccountLoggedIn()) return;
console.debug('[debug][actions.js] fetchLikedArtists');
return likedArtists().then(result => {
if (result.data) {
commit('updateLikedXXX', {
name: 'artists',
data: result.data,
});
}
});
},
fetchLikedMVs: ({ commit }) => {
if (!isAccountLoggedIn()) return;
console.debug('[debug][actions.js] fetchLikedMVs');
return likedMVs().then(result => {
if (result.data) {
commit('updateLikedXXX', {
name: 'mvs',
data: result.data,
});
} }
}); });
}, },

@ -1,7 +1,9 @@
export default { export default {
updateLikedSongs(state, trackIDs) { updateLikedXXX(state, { name, data }) {
state.liked.songs = trackIDs; state.liked[name] = data;
if (name === 'songs') {
state.player.sendSelfToIpcMain(); state.player.sendSelfToIpcMain();
}
}, },
changeLang(state, lang) { changeLang(state, lang) {
state.settings.lang = lang; state.settings.lang = lang;
@ -23,11 +25,11 @@ export default {
}, },
togglePlaylistCategory(state, name) { togglePlaylistCategory(state, name) {
const index = state.settings.enabledPlaylistCategories.findIndex( const index = state.settings.enabledPlaylistCategories.findIndex(
(c) => c === name c => c === name
); );
if (index !== -1) { if (index !== -1) {
state.settings.enabledPlaylistCategories = state.settings.enabledPlaylistCategories.filter( state.settings.enabledPlaylistCategories = state.settings.enabledPlaylistCategories.filter(
(c) => c !== name c => c !== name
); );
} else { } else {
state.settings.enabledPlaylistCategories.push(name); state.settings.enabledPlaylistCategories.push(name);

@ -1,11 +1,11 @@
import initLocalStorage from "./initLocalStorage"; import initLocalStorage from './initLocalStorage';
import pkg from "../../package.json"; import pkg from '../../package.json';
import updateApp from "@/utils/updateApp"; import updateApp from '@/utils/updateApp';
if (localStorage.getItem("appVersion") === null) { if (localStorage.getItem('appVersion') === null) {
localStorage.setItem("settings", JSON.stringify(initLocalStorage.settings)); localStorage.setItem('settings', JSON.stringify(initLocalStorage.settings));
localStorage.setItem("data", JSON.stringify(initLocalStorage.data)); localStorage.setItem('data', JSON.stringify(initLocalStorage.data));
localStorage.setItem("appVersion", pkg.version); localStorage.setItem('appVersion', pkg.version);
} }
updateApp(); updateApp();
@ -14,6 +14,11 @@ export default {
showLyrics: false, showLyrics: false,
liked: { liked: {
songs: [], songs: [],
songsWithDetails: [], // 只有前12首
playlists: [],
albums: [],
artists: [],
mvs: [],
}, },
contextMenu: { contextMenu: {
clickObjectID: 0, clickObjectID: 0,
@ -21,7 +26,7 @@ export default {
}, },
toast: { toast: {
show: false, show: false,
text: "", text: '',
timer: null, timer: null,
}, },
modals: { modals: {
@ -35,8 +40,8 @@ export default {
}, },
}, },
dailyTracks: [], dailyTracks: [],
lastfm: JSON.parse(localStorage.getItem("lastfm")) || {}, lastfm: JSON.parse(localStorage.getItem('lastfm')) || {},
player: JSON.parse(localStorage.getItem("player")), player: JSON.parse(localStorage.getItem('player')),
settings: JSON.parse(localStorage.getItem("settings")), settings: JSON.parse(localStorage.getItem('settings')),
data: JSON.parse(localStorage.getItem("data")), data: JSON.parse(localStorage.getItem('data')),
}; };

@ -1,17 +1,17 @@
import { getTrackDetail, scrobble, getMP3 } from "@/api/track"; import { getTrackDetail, scrobble, getMP3 } from '@/api/track';
import shuffle from "lodash/shuffle"; import shuffle from 'lodash/shuffle';
import { Howler, Howl } from "howler"; import { Howler, Howl } from 'howler';
import { cacheTrackSource, getTrackSource } from "@/utils/db"; import { cacheTrackSource, getTrackSource } from '@/utils/db';
import { getAlbum } from "@/api/album"; import { getAlbum } from '@/api/album';
import { getPlaylistDetail } from "@/api/playlist"; import { getPlaylistDetail } from '@/api/playlist';
import { getArtist } from "@/api/artist"; import { getArtist } from '@/api/artist';
import { personalFM, fmTrash } from "@/api/others"; import { personalFM, fmTrash } from '@/api/others';
import store from "@/store"; import store from '@/store';
import { isAccountLoggedIn } from "@/utils/auth"; import { isAccountLoggedIn } from '@/utils/auth';
import { trackUpdateNowPlaying, trackScrobble } from "@/api/lastfm"; import { trackUpdateNowPlaying, trackScrobble } from '@/api/lastfm';
const electron = const electron =
process.env.IS_ELECTRON === true ? window.require("electron") : null; process.env.IS_ELECTRON === true ? window.require('electron') : null;
const ipcRenderer = const ipcRenderer =
process.env.IS_ELECTRON === true ? electron.ipcRenderer : null; process.env.IS_ELECTRON === true ? electron.ipcRenderer : null;
@ -21,7 +21,7 @@ export default class {
this._playing = false; // 是否正在播放中 this._playing = false; // 是否正在播放中
this._progress = 0; // 当前播放歌曲的进度 this._progress = 0; // 当前播放歌曲的进度
this._enabled = false; // 是否启用Player this._enabled = false; // 是否启用Player
this._repeatMode = "off"; // off | on | one this._repeatMode = 'off'; // off | on | one
this._shuffle = false; // true | false this._shuffle = false; // true | false
this._volume = 1; // 0 to 1 this._volume = 1; // 0 to 1
this._volumeBeforeMuted = 1; // 用于保存静音前的音量 this._volumeBeforeMuted = 1; // 用于保存静音前的音量
@ -31,7 +31,7 @@ export default class {
this._current = 0; // 当前播放歌曲在播放列表里的index this._current = 0; // 当前播放歌曲在播放列表里的index
this._shuffledList = []; // 被随机打乱的播放列表,随机播放模式下会使用此播放列表 this._shuffledList = []; // 被随机打乱的播放列表,随机播放模式下会使用此播放列表
this._shuffledCurrent = 0; // 当前播放歌曲在随机列表里面的index this._shuffledCurrent = 0; // 当前播放歌曲在随机列表里面的index
this._playlistSource = { type: "album", id: 123 }; // 当前播放列表的信息 this._playlistSource = { type: 'album', id: 123 }; // 当前播放列表的信息
this._currentTrack = { id: 86827685 }; // 当前播放歌曲的详细信息 this._currentTrack = { id: 86827685 }; // 当前播放歌曲的详细信息
this._playNextList = []; // 当这个list不为空时会优先播放这个list的歌 this._playNextList = []; // 当这个list不为空时会优先播放这个list的歌
this._isPersonalFM = false; // 是否是私人FM模式 this._isPersonalFM = false; // 是否是私人FM模式
@ -40,7 +40,7 @@ export default class {
// howler (https://github.com/goldfire/howler.js) // howler (https://github.com/goldfire/howler.js)
this._howler = null; this._howler = null;
Object.defineProperty(this, "_howler", { Object.defineProperty(this, '_howler', {
enumerable: false, enumerable: false,
}); });
@ -48,7 +48,7 @@ export default class {
this._init(); this._init();
// for debug // for debug
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === 'development') {
window.player = this; window.player = this;
} }
} }
@ -58,7 +58,7 @@ export default class {
} }
set repeatMode(mode) { set repeatMode(mode) {
if (this._isPersonalFM) return; if (this._isPersonalFM) return;
if (!["off", "on", "one"].includes(mode)) { if (!['off', 'on', 'one'].includes(mode)) {
console.warn("repeatMode: invalid args, must be 'on' | 'off' | 'one'"); console.warn("repeatMode: invalid args, must be 'on' | 'off' | 'one'");
return; return;
} }
@ -70,7 +70,7 @@ export default class {
set shuffle(shuffle) { set shuffle(shuffle) {
if (this._isPersonalFM) return; if (this._isPersonalFM) return;
if (shuffle !== true && shuffle !== false) { if (shuffle !== true && shuffle !== false) {
console.warn("shuffle: invalid args, must be Boolean"); console.warn('shuffle: invalid args, must be Boolean');
return; return;
} }
this._shuffle = shuffle; this._shuffle = shuffle;
@ -148,11 +148,11 @@ export default class {
if (this._enabled) { if (this._enabled) {
// 恢复当前播放歌曲 // 恢复当前播放歌曲
this._replaceCurrentTrack(this._currentTrack.id, false).then(() => { this._replaceCurrentTrack(this._currentTrack.id, false).then(() => {
this._howler?.seek(localStorage.getItem("playerCurrentTrackTime") ?? 0); this._howler?.seek(localStorage.getItem('playerCurrentTrackTime') ?? 0);
setInterval( setInterval(
() => () =>
localStorage.setItem( localStorage.setItem(
"playerCurrentTrackTime", 'playerCurrentTrackTime',
this._howler?.seek() this._howler?.seek()
), ),
1000 1000
@ -164,7 +164,7 @@ export default class {
// 初始化私人FM // 初始化私人FM
if (this._personalFMTrack.id === 0 || this._personalFMNextTrack.id === 0) { if (this._personalFMTrack.id === 0 || this._personalFMNextTrack.id === 0) {
personalFM().then((result) => { personalFM().then(result => {
this._personalFMTrack = result.data[0]; this._personalFMTrack = result.data[0];
this._personalFMNextTrack = result.data[1]; this._personalFMNextTrack = result.data[1];
return this._personalFMTrack; return this._personalFMTrack;
@ -185,7 +185,7 @@ export default class {
} }
// 当歌曲是列表最后一首 && 循环模式开启 // 当歌曲是列表最后一首 && 循环模式开启
if (this.list.length === this.current + 1 && this.repeatMode === "on") { if (this.list.length === this.current + 1 && this.repeatMode === 'on') {
return [this.list[0], 0]; return [this.list[0], 0];
} }
@ -194,7 +194,7 @@ export default class {
} }
_getPrevTrack() { _getPrevTrack() {
// 当歌曲是列表第一首 && 循环模式开启 // 当歌曲是列表第一首 && 循环模式开启
if (this.current === 0 && this.repeatMode === "on") { if (this.current === 0 && this.repeatMode === 'on') {
return [this.list[this.list.length - 1], this.list.length - 1]; return [this.list[this.list.length - 1], this.list.length - 1];
} }
@ -202,10 +202,10 @@ export default class {
return [this.list[this.current - 1], this.current - 1]; return [this.list[this.current - 1], this.current - 1];
} }
async _shuffleTheList(firstTrackID = this._currentTrack.id) { async _shuffleTheList(firstTrackID = this._currentTrack.id) {
let list = this._list.filter((tid) => tid !== firstTrackID); let list = this._list.filter(tid => tid !== firstTrackID);
if (firstTrackID === "first") list = this._list; if (firstTrackID === 'first') list = this._list;
this._shuffledList = shuffle(list); this._shuffledList = shuffle(list);
if (firstTrackID !== "first") this._shuffledList.unshift(firstTrackID); if (firstTrackID !== 'first') this._shuffledList.unshift(firstTrackID);
} }
async _scrobble(track, time, completed = false) { async _scrobble(track, time, completed = false) {
console.debug( console.debug(
@ -238,19 +238,19 @@ export default class {
this._howler = new Howl({ this._howler = new Howl({
src: [source], src: [source],
html5: true, html5: true,
format: ["mp3", "flac"], format: ['mp3', 'flac'],
}); });
if (autoplay) { if (autoplay) {
this.play(); this.play();
document.title = `${this._currentTrack.name} · ${this._currentTrack.ar[0].name} - YesPlayMusic`; document.title = `${this._currentTrack.name} · ${this._currentTrack.ar[0].name} - YesPlayMusic`;
} }
this.setOutputDevice(); this.setOutputDevice();
this._howler.once("end", () => { this._howler.once('end', () => {
this._nextTrackCallback(); this._nextTrackCallback();
}); });
} }
_getAudioSourceFromCache(id) { _getAudioSourceFromCache(id) {
return getTrackSource(id).then((t) => { return getTrackSource(id).then(t => {
if (!t) return null; if (!t) return null;
const source = URL.createObjectURL(new Blob([t.source])); const source = URL.createObjectURL(new Blob([t.source]));
return source; return source;
@ -258,18 +258,18 @@ export default class {
} }
_getAudioSourceFromNetease(track) { _getAudioSourceFromNetease(track) {
if (isAccountLoggedIn()) { if (isAccountLoggedIn()) {
return getMP3(track.id).then((result) => { return getMP3(track.id).then(result => {
if (!result.data[0]) return null; if (!result.data[0]) return null;
if (!result.data[0].url) return null; if (!result.data[0].url) return null;
if (result.data[0].freeTrialInfo !== null) return null; // 跳过只能试听的歌曲 if (result.data[0].freeTrialInfo !== null) return null; // 跳过只能试听的歌曲
const source = result.data[0].url.replace(/^http:/, "https:"); const source = result.data[0].url.replace(/^http:/, 'https:');
if (store.state.settings.automaticallyCacheSongs) { if (store.state.settings.automaticallyCacheSongs) {
cacheTrackSource(track, source, result.data[0].br); cacheTrackSource(track, source, result.data[0].br);
} }
return source; return source;
}); });
} else { } else {
return new Promise((resolve) => { return new Promise(resolve => {
resolve(`https://music.163.com/song/media/outer/url?id=${track.id}`); resolve(`https://music.163.com/song/media/outer/url?id=${track.id}`);
}); });
} }
@ -282,42 +282,42 @@ export default class {
) { ) {
return null; return null;
} }
const source = ipcRenderer.sendSync("unblock-music", track); const source = ipcRenderer.sendSync('unblock-music', track);
if (store.state.settings.automaticallyCacheSongs && source?.url) { if (store.state.settings.automaticallyCacheSongs && source?.url) {
// TODO: 将unblockMusic字样换成真正的来源比如酷我咪咕等 // TODO: 将unblockMusic字样换成真正的来源比如酷我咪咕等
cacheTrackSource(track, source.url, 128000, "unblockMusic"); cacheTrackSource(track, source.url, 128000, 'unblockMusic');
} }
return source?.url; return source?.url;
} }
_getAudioSource(track) { _getAudioSource(track) {
return this._getAudioSourceFromCache(String(track.id)) return this._getAudioSourceFromCache(String(track.id))
.then((source) => { .then(source => {
return source ?? this._getAudioSourceFromNetease(track); return source ?? this._getAudioSourceFromNetease(track);
}) })
.then((source) => { .then(source => {
return source ?? this._getAudioSourceFromUnblockMusic(track); return source ?? this._getAudioSourceFromUnblockMusic(track);
}); });
} }
_replaceCurrentTrack( _replaceCurrentTrack(
id, id,
autoplay = true, autoplay = true,
ifUnplayableThen = "playNextTrack" ifUnplayableThen = 'playNextTrack'
) { ) {
if (autoplay && this._currentTrack.name) { if (autoplay && this._currentTrack.name) {
this._scrobble(this.currentTrack, this._howler?.seek()); this._scrobble(this.currentTrack, this._howler?.seek());
} }
return getTrackDetail(id).then((data) => { return getTrackDetail(id).then(data => {
let track = data.songs[0]; let track = data.songs[0];
this._currentTrack = track; this._currentTrack = track;
this._updateMediaSessionMetaData(track); this._updateMediaSessionMetaData(track);
return this._getAudioSource(track).then((source) => { return this._getAudioSource(track).then(source => {
if (source) { if (source) {
this._playAudioSource(source, autoplay); this._playAudioSource(source, autoplay);
this._cacheNextTrack(); this._cacheNextTrack();
return source; return source;
} else { } else {
store.dispatch("showToast", `无法播放 ${track.name}`); store.dispatch('showToast', `无法播放 ${track.name}`);
ifUnplayableThen === "playNextTrack" ifUnplayableThen === 'playNextTrack'
? this.playNextTrack() ? this.playNextTrack()
: this.playPrevTrack(); : this.playPrevTrack();
} }
@ -329,72 +329,72 @@ export default class {
? this._personalFMNextTrack.id ? this._personalFMNextTrack.id
: this._getNextTrack()[0]; : this._getNextTrack()[0];
if (!nextTrackID) return; if (!nextTrackID) return;
getTrackDetail(nextTrackID).then((data) => { getTrackDetail(nextTrackID).then(data => {
let track = data.songs[0]; let track = data.songs[0];
this._getAudioSource(track); this._getAudioSource(track);
}); });
} }
_loadSelfFromLocalStorage() { _loadSelfFromLocalStorage() {
const player = JSON.parse(localStorage.getItem("player")); const player = JSON.parse(localStorage.getItem('player'));
if (!player) return; if (!player) return;
for (const [key, value] of Object.entries(player)) { for (const [key, value] of Object.entries(player)) {
this[key] = value; this[key] = value;
} }
} }
_initMediaSession() { _initMediaSession() {
if ("mediaSession" in navigator) { if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler("play", () => { navigator.mediaSession.setActionHandler('play', () => {
this.play(); this.play();
}); });
navigator.mediaSession.setActionHandler("pause", () => { navigator.mediaSession.setActionHandler('pause', () => {
this.pause(); this.pause();
}); });
navigator.mediaSession.setActionHandler("previoustrack", () => { navigator.mediaSession.setActionHandler('previoustrack', () => {
this.playPrevTrack(); this.playPrevTrack();
}); });
navigator.mediaSession.setActionHandler("nexttrack", () => { navigator.mediaSession.setActionHandler('nexttrack', () => {
this.playNextTrack(); this.playNextTrack();
}); });
navigator.mediaSession.setActionHandler("stop", () => { navigator.mediaSession.setActionHandler('stop', () => {
this.pause(); this.pause();
}); });
navigator.mediaSession.setActionHandler("seekto", (event) => { navigator.mediaSession.setActionHandler('seekto', event => {
this.seek(event.seekTime); this.seek(event.seekTime);
this._updateMediaSessionPositionState(); this._updateMediaSessionPositionState();
}); });
navigator.mediaSession.setActionHandler("seekbackward", (event) => { navigator.mediaSession.setActionHandler('seekbackward', event => {
this.seek(this.seek() - (event.seekOffset || 10)); this.seek(this.seek() - (event.seekOffset || 10));
this._updateMediaSessionPositionState(); this._updateMediaSessionPositionState();
}); });
navigator.mediaSession.setActionHandler("seekforward", (event) => { navigator.mediaSession.setActionHandler('seekforward', event => {
this.seek(this.seek() + (event.seekOffset || 10)); this.seek(this.seek() + (event.seekOffset || 10));
this._updateMediaSessionPositionState(); this._updateMediaSessionPositionState();
}); });
} }
} }
_updateMediaSessionMetaData(track) { _updateMediaSessionMetaData(track) {
if ("mediaSession" in navigator === false) { if ('mediaSession' in navigator === false) {
return; return;
} }
let artists = track.ar.map((a) => a.name); let artists = track.ar.map(a => a.name);
navigator.mediaSession.metadata = new window.MediaMetadata({ navigator.mediaSession.metadata = new window.MediaMetadata({
title: track.name, title: track.name,
artist: artists.join(","), artist: artists.join(','),
album: track.al.name, album: track.al.name,
artwork: [ artwork: [
{ {
src: track.al.picUrl + "?param=512y512", src: track.al.picUrl + '?param=512y512',
type: "image/jpg", type: 'image/jpg',
sizes: "512x512", sizes: '512x512',
}, },
], ],
}); });
} }
_updateMediaSessionPositionState() { _updateMediaSessionPositionState() {
if ("mediaSession" in navigator === false) { if ('mediaSession' in navigator === false) {
return; return;
} }
if ("setPositionState" in navigator.mediaSession) { if ('setPositionState' in navigator.mediaSession) {
navigator.mediaSession.setPositionState({ navigator.mediaSession.setPositionState({
duration: ~~(this.currentTrack.dt / 1000), duration: ~~(this.currentTrack.dt / 1000),
playbackRate: 1.0, playbackRate: 1.0,
@ -404,14 +404,14 @@ export default class {
} }
_nextTrackCallback() { _nextTrackCallback() {
this._scrobble(this._currentTrack, 0, true); this._scrobble(this._currentTrack, 0, true);
if (!this.isPersonalFM && this.repeatMode === "one") { if (!this.isPersonalFM && this.repeatMode === 'one') {
this._replaceCurrentTrack(this._currentTrack.id); this._replaceCurrentTrack(this._currentTrack.id);
} else { } else {
this.playNextTrack(); this.playNextTrack();
} }
} }
_loadPersonalFMNextTrack() { _loadPersonalFMNextTrack() {
return personalFM().then((result) => { return personalFM().then(result => {
this._personalFMNextTrack = result.data[0]; this._personalFMNextTrack = result.data[0];
return this._personalFMNextTrack; return this._personalFMNextTrack;
}); });
@ -425,7 +425,7 @@ export default class {
} }
let copyTrack = { ...track }; let copyTrack = { ...track };
copyTrack.dt -= seekTime * 1000; copyTrack.dt -= seekTime * 1000;
ipcRenderer.send("playDiscordPresence", copyTrack); ipcRenderer.send('playDiscordPresence', copyTrack);
} }
_pauseDiscordPresence(track) { _pauseDiscordPresence(track) {
if ( if (
@ -434,7 +434,7 @@ export default class {
) { ) {
return null; return null;
} }
ipcRenderer.send("pauseDiscordPresence", track); ipcRenderer.send('pauseDiscordPresence', track);
} }
currentTrackID() { currentTrackID() {
@ -467,23 +467,23 @@ export default class {
const [trackID, index] = this._getPrevTrack(); const [trackID, index] = this._getPrevTrack();
if (trackID === undefined) return false; if (trackID === undefined) return false;
this.current = index; this.current = index;
this._replaceCurrentTrack(trackID, true, "playPrevTrack"); this._replaceCurrentTrack(trackID, true, 'playPrevTrack');
return true; return true;
} }
saveSelfToLocalStorage() { saveSelfToLocalStorage() {
let player = {}; let player = {};
for (let [key, value] of Object.entries(this)) { for (let [key, value] of Object.entries(this)) {
if (key === "_playing") continue; if (key === '_playing') continue;
player[key] = value; player[key] = value;
} }
localStorage.setItem("player", JSON.stringify(player)); localStorage.setItem('player', JSON.stringify(player));
} }
pause() { pause() {
this._howler?.pause(); this._howler?.pause();
this._playing = false; this._playing = false;
document.title = "YesPlayMusic"; document.title = 'YesPlayMusic';
this._pauseDiscordPresence(this._currentTrack); this._pauseDiscordPresence(this._currentTrack);
} }
play() { play() {
@ -536,7 +536,7 @@ export default class {
trackIDs, trackIDs,
playlistSourceID, playlistSourceID,
playlistSourceType, playlistSourceType,
autoPlayTrackID = "first" autoPlayTrackID = 'first'
) { ) {
this._isPersonalFM = false; this._isPersonalFM = false;
if (!this._enabled) this._enabled = true; if (!this._enabled) this._enabled = true;
@ -547,37 +547,37 @@ export default class {
id: playlistSourceID, id: playlistSourceID,
}; };
if (this.shuffle) this._shuffleTheList(autoPlayTrackID); if (this.shuffle) this._shuffleTheList(autoPlayTrackID);
if (autoPlayTrackID === "first") { if (autoPlayTrackID === 'first') {
this._replaceCurrentTrack(this.list[0]); this._replaceCurrentTrack(this.list[0]);
} else { } else {
this.current = trackIDs.indexOf(autoPlayTrackID); this.current = trackIDs.indexOf(autoPlayTrackID);
this._replaceCurrentTrack(autoPlayTrackID); this._replaceCurrentTrack(autoPlayTrackID);
} }
} }
playAlbumByID(id, trackID = "first") { playAlbumByID(id, trackID = 'first') {
getAlbum(id).then((data) => { getAlbum(id).then(data => {
let trackIDs = data.songs.map((t) => t.id); let trackIDs = data.songs.map(t => t.id);
this.replacePlaylist(trackIDs, id, "album", trackID); this.replacePlaylist(trackIDs, id, 'album', trackID);
}); });
} }
playPlaylistByID(id, trackID = "first", noCache = false) { playPlaylistByID(id, trackID = 'first', noCache = false) {
console.debug( console.debug(
`[debug][Player.js] playPlaylistByID 👉 id:${id} trackID:${trackID} noCache:${noCache}` `[debug][Player.js] playPlaylistByID 👉 id:${id} trackID:${trackID} noCache:${noCache}`
); );
getPlaylistDetail(id, noCache).then((data) => { getPlaylistDetail(id, noCache).then(data => {
let trackIDs = data.playlist.trackIds.map((t) => t.id); let trackIDs = data.playlist.trackIds.map(t => t.id);
this.replacePlaylist(trackIDs, id, "playlist", trackID); this.replacePlaylist(trackIDs, id, 'playlist', trackID);
}); });
} }
playArtistByID(id, trackID = "first") { playArtistByID(id, trackID = 'first') {
getArtist(id).then((data) => { getArtist(id).then(data => {
let trackIDs = data.hotSongs.map((t) => t.id); let trackIDs = data.hotSongs.map(t => t.id);
this.replacePlaylist(trackIDs, id, "artist", trackID); this.replacePlaylist(trackIDs, id, 'artist', trackID);
}); });
} }
playTrackOnListByID(id, listName = "default") { playTrackOnListByID(id, listName = 'default') {
if (listName === "default") { if (listName === 'default') {
this._current = this._list.findIndex((t) => t === id); this._current = this._list.findIndex(t => t === id);
} }
this._replaceCurrentTrack(id); this._replaceCurrentTrack(id);
} }
@ -604,19 +604,19 @@ export default class {
sendSelfToIpcMain() { sendSelfToIpcMain() {
if (process.env.IS_ELECTRON !== true) return false; if (process.env.IS_ELECTRON !== true) return false;
ipcRenderer.send("player", { ipcRenderer.send('player', {
playing: this.playing, playing: this.playing,
likedCurrentTrack: store.state.liked.songs.includes(this.currentTrack.id), likedCurrentTrack: store.state.liked.songs.includes(this.currentTrack.id),
}); });
} }
switchRepeatMode() { switchRepeatMode() {
if (this._repeatMode === "on") { if (this._repeatMode === 'on') {
this.repeatMode = "one"; this.repeatMode = 'one';
} else if (this._repeatMode === "one") { } else if (this._repeatMode === 'one') {
this.repeatMode = "off"; this.repeatMode = 'off';
} else { } else {
this.repeatMode = "on"; this.repeatMode = 'on';
} }
} }
switchShuffle() { switchShuffle() {

@ -3,7 +3,7 @@
<h1> <h1>
<img class="avatar" :src="data.user.avatarUrl | resizeImage" />{{ <img class="avatar" :src="data.user.avatarUrl | resizeImage" />{{
data.user.nickname data.user.nickname
}}{{ $t("library.sLibrary") }} }}{{ $t('library.sLibrary') }}
</h1> </h1>
<div class="section-one"> <div class="section-one">
<div class="liked-songs" @click="goToLikedSongsList"> <div class="liked-songs" @click="goToLikedSongsList">
@ -11,17 +11,17 @@
<p> <p>
<span <span
v-for="(line, index) in pickedLyric" v-for="(line, index) in pickedLyric"
:key="`${line}${index}`"
v-show="line !== ''" v-show="line !== ''"
:key="`${line}${index}`"
>{{ line }}<br >{{ line }}<br
/></span> /></span>
</p> </p>
</div> </div>
<div class="bottom"> <div class="bottom">
<div class="titles"> <div class="titles">
<div class="title">{{ $t("library.likedSongs") }}</div> <div class="title">{{ $t('library.likedSongs') }}</div>
<div class="sub-title"> <div class="sub-title">
{{ likedSongsPlaylist.trackCount }} {{ $t("common.songs") }} {{ liked.songs.length }} {{ $t('common.songs') }}
</div> </div>
</div> </div>
<button @click.stop="playLikedSongs"> <button @click.stop="playLikedSongs">
@ -31,16 +31,16 @@
</div> </div>
<div class="songs"> <div class="songs">
<TrackList <TrackList
:tracks="likedSongs" :id="liked.playlist ? liked.playlist[0].id : 0"
:type="'tracklist'" :tracks="liked.songsWithDetails"
:id="likedSongsPlaylist.id" :column-number="3"
dbclickTrackFunc="playPlaylistByID" type="tracklist"
:columnNumber="3" dbclick-track-func="playPlaylistByID"
/> />
</div> </div>
</div> </div>
<div class="section-two" id="liked"> <div id="liked" class="section-two">
<div class="tabs-row"> <div class="tabs-row">
<div class="tabs"> <div class="tabs">
<div <div
@ -48,157 +48,104 @@
:class="{ active: currentTab === 'playlists' }" :class="{ active: currentTab === 'playlists' }"
@click="updateCurrentTab('playlists')" @click="updateCurrentTab('playlists')"
> >
{{ $t("library.playlists") }} {{ $t('library.playlists') }}
</div> </div>
<div <div
class="tab" class="tab"
:class="{ active: currentTab === 'albums' }" :class="{ active: currentTab === 'albums' }"
@click="updateCurrentTab('albums')" @click="updateCurrentTab('albums')"
> >
{{ $t("library.albums") }} {{ $t('library.albums') }}
</div> </div>
<div <div
class="tab" class="tab"
:class="{ active: currentTab === 'artists' }" :class="{ active: currentTab === 'artists' }"
@click="updateCurrentTab('artists')" @click="updateCurrentTab('artists')"
> >
{{ $t("library.artists") }} {{ $t('library.artists') }}
</div> </div>
<div <div
class="tab" class="tab"
:class="{ active: currentTab === 'mvs' }" :class="{ active: currentTab === 'mvs' }"
@click="updateCurrentTab('mvs')" @click="updateCurrentTab('mvs')"
> >
{{ $t("library.mvs") }} {{ $t('library.mvs') }}
</div> </div>
</div> </div>
<button <button
v-show="currentTab === 'playlists'"
class="add-playlist" class="add-playlist"
icon="plus" icon="plus"
v-show="currentTab === 'playlists'"
@click="openAddPlaylistModal" @click="openAddPlaylistModal"
><svg-icon icon-class="plus" />{{ $t("library.newPlayList") }}</button ><svg-icon icon-class="plus" />{{ $t('library.newPlayList') }}</button
> >
</div> </div>
<div v-show="currentTab === 'playlists'"> <div v-show="currentTab === 'playlists'">
<div v-if="playlists.length > 1"> <div v-if="liked.playlists.length > 1">
<CoverRow <CoverRow
:items="playlists.slice(1)" :items="liked.playlists.slice(1)"
type="playlist" type="playlist"
subText="creator" sub-text="creator"
:showPlayButton="true" :show-play-button="true"
/> />
</div> </div>
</div> </div>
<div v-show="currentTab === 'albums'"> <div v-show="currentTab === 'albums'">
<CoverRow <CoverRow
:items="albums" :items="liked.albums"
type="album" type="album"
subText="artist" sub-text="artist"
:showPlayButton="true" :show-play-button="true"
/> />
</div> </div>
<div v-show="currentTab === 'artists'"> <div v-show="currentTab === 'artists'">
<CoverRow :items="artists" type="artist" :showPlayButton="true" /> <CoverRow
:items="liked.artists"
type="artist"
:show-play-button="true"
/>
</div> </div>
<div v-show="currentTab === 'mvs'"> <div v-show="currentTab === 'mvs'">
<MvRow :mvs="mvs" /> <MvRow :mvs="liked.mvs" />
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapActions, mapMutations, mapState } from "vuex"; import { mapActions, mapMutations, mapState } from 'vuex';
import { getTrackDetail, getLyric } from "@/api/track"; import { getLyric } from '@/api/track';
import { import { randomNum, dailyTask } from '@/utils/common';
userDetail, import { isAccountLoggedIn } from '@/utils/auth';
userAccount, import NProgress from 'nprogress';
userPlaylist,
likedAlbums,
likedArtists,
likedMVs,
} from "@/api/user";
import { randomNum, dailyTask } from "@/utils/common";
import { getPlaylistDetail } from "@/api/playlist";
import { isAccountLoggedIn } from "@/utils/auth";
import NProgress from "nprogress";
import TrackList from "@/components/TrackList.vue"; import TrackList from '@/components/TrackList.vue';
import CoverRow from "@/components/CoverRow.vue"; import CoverRow from '@/components/CoverRow.vue';
import SvgIcon from "@/components/SvgIcon.vue"; import SvgIcon from '@/components/SvgIcon.vue';
import MvRow from "@/components/MvRow.vue"; import MvRow from '@/components/MvRow.vue';
export default { export default {
name: "Library", name: 'Library',
components: { SvgIcon, CoverRow, TrackList, MvRow }, components: { SvgIcon, CoverRow, TrackList, MvRow },
data() { data() {
return { return {
show: false, show: false,
playlists: [],
hasMorePlaylists: true,
likedSongsPlaylist: {
id: 0,
trackCount: 0,
},
likedSongs: [], likedSongs: [],
likedSongIDs: [],
lyric: undefined, lyric: undefined,
currentTab: "playlists", currentTab: 'playlists',
albums: [],
artists: [],
mvs: [],
}; };
}, },
created() {
NProgress.start();
if (isAccountLoggedIn()) {
userAccount().then((result) => {
this.$store.commit("updateData", {
key: "user",
value: result.profile,
});
});
} else {
userDetail(this.data.user.userId).then((result) => {
this.$store.commit("updateData", {
key: "user",
value: result.profile,
});
});
}
},
activated() {
if (!this.data.likedSongPlaylistID) {
userPlaylist({
uid: this.data.user.userId,
limit: 1,
}).then((data) => {
this.updateData({
key: "likedSongPlaylistID",
value: data.playlist[0].id,
});
this.loadData();
});
} else {
this.loadData();
}
dailyTask();
},
computed: { computed: {
...mapState(["data"]), ...mapState(['data', 'liked']),
likedSongsInState() {
return this.$store.state.liked.songs;
},
pickedLyric() { pickedLyric() {
if (this.lyric === undefined) return ""; if (this.lyric === undefined) return '';
let lyric = this.lyric.split("\n"); let lyric = this.lyric.split('\n');
lyric = lyric.filter((l) => { lyric = lyric.filter(l => {
if (l.includes("作词") || l.includes("作曲")) { if (l.includes('作词') || l.includes('作曲')) {
return false; return false;
} }
return true; return true;
@ -208,132 +155,76 @@ export default {
lineIndex = randomNum(0, lyric.length - 1); lineIndex = randomNum(0, lyric.length - 1);
} }
return [ return [
lyric[lineIndex].split("]")[1], lyric[lineIndex].split(']')[1],
lyric[lineIndex + 1].split("]")[1], lyric[lineIndex + 1].split(']')[1],
lyric[lineIndex + 2].split("]")[1], lyric[lineIndex + 2].split(']')[1],
]; ];
}, },
}, },
created() {
NProgress.start();
},
activated() {
if (this.liked.songsWithDetails.length > 0) {
NProgress.done();
this.show = true;
this.getRandomLyric();
} else {
this.$store.dispatch('fetchLikedSongsWithDetails').then(() => {
NProgress.done();
this.show = true;
this.getRandomLyric();
});
}
this.$store.dispatch('fetchLikedSongs');
this.$store.dispatch('fetchLikedPlaylist');
this.$store.dispatch('fetchLikedAlbums');
this.$store.dispatch('fetchLikedArtists');
this.$store.dispatch('fetchLikedMVs');
dailyTask();
},
methods: { methods: {
...mapActions(["showToast"]), ...mapActions(['showToast']),
...mapMutations(["updateModal", "updateData"]), ...mapMutations(['updateModal', 'updateData']),
playLikedSongs() { playLikedSongs() {
this.$store.state.player.playPlaylistByID( this.$store.state.player.playPlaylistByID(
this.playlists[0].id, this.liked.playlists[0].id,
"first", 'first',
true true
); );
}, },
updateCurrentTab(tab) { updateCurrentTab(tab) {
if (!isAccountLoggedIn() && tab !== "playlists") { if (!isAccountLoggedIn() && tab !== 'playlists') {
this.showToast("此操作需要登录网易云账号"); this.showToast('此操作需要登录网易云账号');
return; return;
} }
this.currentTab = tab; this.currentTab = tab;
document document
.getElementById("liked") .getElementById('liked')
.scrollIntoView({ block: "start", behavior: "smooth" }); .scrollIntoView({ block: 'start', behavior: 'smooth' });
if (tab === "albums") {
if (this.albums.length === 0) this.loadLikedAlbums();
} else if (tab === "artists") {
if (this.artists.length === 0) this.loadLikedArtists();
} else if (tab === "mvs") {
if (this.mvs.length === 0) this.loadLikedMVs();
}
}, },
goToLikedSongsList() { goToLikedSongsList() {
this.$router.push({ path: "/library/liked-songs" }); this.$router.push({ path: '/library/liked-songs' });
},
loadData() {
if (this.hasMorePlaylists && this.currentTab === "playlists") {
this.getUserPlaylists();
}
if (this.currentTab === "albums") {
this.loadLikedAlbums();
} else if (this.currentTab === "artists") {
this.loadLikedArtists();
} else if (this.currentTab === "mvs") {
this.loadLikedMVs();
}
this.getLikedSongs();
},
getUserPlaylists(replace = false) {
userPlaylist({
uid: this.data.user.userId,
offset: this.playlists.length === 0 ? 0 : this.playlists.length - 1,
timestamp: new Date().getTime(),
}).then((data) => {
if (replace) {
this.playlists = data.playlist;
} else {
this.playlists.push(...data.playlist);
}
this.hasMorePlaylists = data.more;
});
},
getLikedSongs(getLyric = true) {
getPlaylistDetail(this.data.likedSongPlaylistID, true).then((data) => {
this.likedSongsPlaylist = data.playlist;
if (data.playlist.trackIds.length === 0) {
NProgress.done();
this.show = true;
return;
}
let TrackIDs = data.playlist.trackIds.slice(0, 12).map((t) => t.id);
this.likedSongIDs = TrackIDs;
getTrackDetail(this.likedSongIDs.join(",")).then((data) => {
this.likedSongs = data.songs;
NProgress.done();
this.show = true;
});
if (getLyric) this.getRandomLyric();
});
}, },
getRandomLyric() { getRandomLyric() {
getLyric( getLyric(
this.likedSongIDs[randomNum(0, this.likedSongIDs.length - 1)] this.liked.songs[randomNum(0, this.liked.songs.length - 1)]
).then((data) => { ).then(data => {
if (data.lrc !== undefined) this.lyric = data.lrc.lyric; if (data.lrc !== undefined) this.lyric = data.lrc.lyric;
}); });
}, },
loadLikedAlbums() {
NProgress.start();
likedAlbums().then((data) => {
this.albums = data.data;
NProgress.done();
});
},
loadLikedArtists() {
NProgress.start();
likedArtists().then((data) => {
this.artists = data.data;
NProgress.done();
});
},
loadLikedMVs() {
NProgress.start();
likedMVs().then((data) => {
this.mvs = data.data;
NProgress.done();
});
},
openAddPlaylistModal() { openAddPlaylistModal() {
if (!isAccountLoggedIn()) { if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号"); this.showToast('此操作需要登录网易云账号');
return; return;
} }
this.updateModal({ this.updateModal({
modalName: "newPlaylistModal", modalName: 'newPlaylistModal',
key: "show", key: 'show',
value: true, value: true,
}); });
}, },
}, },
watch: {
likedSongsInState() {
this.getLikedSongs(false);
},
},
}; };
</script> </script>

@ -69,19 +69,19 @@
</div> </div>
</div> </div>
<div class="progress-bar"> <div class="progress-bar">
<span>{{ formatTrackTime(player.progress) || "0:00" }}</span> <span>{{ formatTrackTime(player.progress) || '0:00' }}</span>
<div class="slider"> <div class="slider">
<vue-slider <vue-slider
v-model="player.progress" v-model="player.progress"
:min="0" :min="0"
:max="player.currentTrackDuration" :max="player.currentTrackDuration + 1"
:interval="1" :interval="1"
:drag-on-click="true" :drag-on-click="true"
:duration="0" :duration="0"
:dot-size="12" :dot-size="12"
:height="2" :height="2"
:tooltip-formatter="formatTrackTime" :tooltip-formatter="formatTrackTime"
@drag-end="player.seek" :lazy="true"
></vue-slider> ></vue-slider>
</div> </div>
<span>{{ formatTrackTime(player.currentTrackDuration) }}</span> <span>{{ formatTrackTime(player.currentTrackDuration) }}</span>
@ -184,15 +184,15 @@
// The lyrics page of Apple Music is so gorgeous, so I copy the design. // The lyrics page of Apple Music is so gorgeous, so I copy the design.
// Some of the codes are from https://github.com/sl1673495/vue-netease-music // Some of the codes are from https://github.com/sl1673495/vue-netease-music
import { mapState, mapMutations, mapActions } from "vuex"; import { mapState, mapMutations, mapActions } from 'vuex';
import VueSlider from "vue-slider-component"; import VueSlider from 'vue-slider-component';
import { formatTrackTime } from "@/utils/common"; import { formatTrackTime } from '@/utils/common';
import { getLyric } from "@/api/track"; import { getLyric } from '@/api/track';
import { lyricParser } from "@/utils/lyrics"; import { lyricParser } from '@/utils/lyrics';
import ButtonIcon from "@/components/ButtonIcon.vue"; import ButtonIcon from '@/components/ButtonIcon.vue';
export default { export default {
name: "Lyrics", name: 'Lyrics',
components: { components: {
VueSlider, VueSlider,
ButtonIcon, ButtonIcon,
@ -207,12 +207,12 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(["player", "settings", "showLyrics"]), ...mapState(['player', 'settings', 'showLyrics']),
currentTrack() { currentTrack() {
return this.player.currentTrack; return this.player.currentTrack;
}, },
imageUrl() { imageUrl() {
return this.player.currentTrack?.al?.picUrl + "?param=1024y1024"; return this.player.currentTrack?.al?.picUrl + '?param=1024y1024';
}, },
lyricWithTranslation() { lyricWithTranslation() {
let ret = []; let ret = [];
@ -222,7 +222,7 @@ export default {
); );
// content // content
if (lyricFiltered.length) { if (lyricFiltered.length) {
lyricFiltered.forEach((l) => { lyricFiltered.forEach(l => {
const { rawTime, time, content } = l; const { rawTime, time, content } = l;
const lyricItem = { time, content, contents: [content] }; const lyricItem = { time, content, contents: [content] };
const sameTimeTLyric = this.tlyric.find( const sameTimeTLyric = this.tlyric.find(
@ -259,10 +259,10 @@ export default {
artist() { artist() {
return this.currentTrack?.ar return this.currentTrack?.ar
? this.currentTrack.ar[0] ? this.currentTrack.ar[0]
: { id: 0, name: "unknown" }; : { id: 0, name: 'unknown' };
}, },
album() { album() {
return this.currentTrack?.al || { id: 0, name: "unknown" }; return this.currentTrack?.al || { id: 0, name: 'unknown' };
}, },
}, },
watch: { watch: {
@ -284,11 +284,11 @@ export default {
clearInterval(this.lyricsInterval); clearInterval(this.lyricsInterval);
}, },
methods: { methods: {
...mapMutations(["toggleLyrics"]), ...mapMutations(['toggleLyrics']),
...mapActions(["likeASong"]), ...mapActions(['likeASong']),
getLyric() { getLyric() {
if (!this.currentTrack.id) return; if (!this.currentTrack.id) return;
return getLyric(this.currentTrack.id).then((data) => { return getLyric(this.currentTrack.id).then(data => {
if (!data?.lrc?.lyric) { if (!data?.lrc?.lyric) {
this.lyric = []; this.lyric = [];
this.tlyric = []; this.tlyric = [];
@ -327,8 +327,8 @@ export default {
const el = document.getElementById(`line${this.highlightLyricIndex}`); const el = document.getElementById(`line${this.highlightLyricIndex}`);
if (el) if (el)
el.scrollIntoView({ el.scrollIntoView({
behavior: "smooth", behavior: 'smooth',
block: "center", block: 'center',
}); });
} }
}, 50); }, 50);
@ -341,7 +341,7 @@ export default {
} else if (line.contents[0] !== undefined) { } else if (line.contents[0] !== undefined) {
return `<span>${line.contents[0]}</span>`; return `<span>${line.contents[0]}</span>`;
} }
return "unknown"; return 'unknown';
}, },
moveToFMTrash() { moveToFMTrash() {
this.player.moveToFMTrash(); this.player.moveToFMTrash();
@ -367,7 +367,7 @@ export default {
--brightness-dynamic-background: 150%; --brightness-dynamic-background: 150%;
} }
[data-theme="dark"] .dynamic-background { [data-theme='dark'] .dynamic-background {
--contrast-dynamic-background: 125%; --contrast-dynamic-background: 125%;
--brightness-dynamic-background: 50%; --brightness-dynamic-background: 50%;
} }

Loading…
Cancel
Save