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>
<script>
import ModalAddTrackToPlaylist from "./components/ModalAddTrackToPlaylist.vue";
import ModalNewPlaylist from "./components/ModalNewPlaylist.vue";
import Navbar from "./components/Navbar.vue";
import Player from "./components/Player.vue";
import Toast from "./components/Toast.vue";
import { ipcRenderer } from "./electron/ipcRenderer";
import { isAccountLoggedIn } from "@/utils/auth";
import Lyrics from "./views/lyrics.vue";
import { mapState } from "vuex";
import ModalAddTrackToPlaylist from './components/ModalAddTrackToPlaylist.vue';
import ModalNewPlaylist from './components/ModalNewPlaylist.vue';
import Navbar from './components/Navbar.vue';
import Player from './components/Player.vue';
import Toast from './components/Toast.vue';
import { ipcRenderer } from './electron/ipcRenderer';
import { isAccountLoggedIn, isLooseLoggedIn } from '@/utils/auth';
import Lyrics from './views/lyrics.vue';
import { mapState } from 'vuex';
export default {
name: "App",
name: 'App',
components: {
Navbar,
Player,
@ -46,50 +46,60 @@ export default {
};
},
computed: {
...mapState(["showLyrics", "showLibraryDefault", "player"]),
...mapState(['showLyrics', 'showLibraryDefault', 'player']),
isAccountLoggedIn() {
return isAccountLoggedIn();
},
showPlayer() {
return (
[
"mv",
"loginUsername",
"login",
"loginAccount",
"lastfmCallback",
'mv',
'loginUsername',
'login',
'loginAccount',
'lastfmCallback',
].includes(this.$route.name) === false
);
},
enablePlayer() {
return this.player.enabled && this.$route.name !== "lastfmCallback";
return this.player.enabled && this.$route.name !== 'lastfmCallback';
},
showNavbar() {
return this.$route.name !== "lastfmCallback";
return this.$route.name !== 'lastfmCallback';
},
},
created() {
this.showLibraryDefault && this.$router.push("/library");
if (this.isElectron) {
ipcRenderer(this);
}
window.addEventListener("keydown", this.handleKeydown);
this.showLibraryDefault && this.$router.push('/library');
if (this.isElectron) ipcRenderer(this);
window.addEventListener('keydown', this.handleKeydown);
this.fetchData();
},
methods: {
handleKeydown(e) {
if (e.code === "Space") {
if (e.target.tagName === "INPUT") return false;
if (this.$route.name === "mv") return false;
if (e.code === 'Space') {
if (e.target.tagName === 'INPUT') return false;
if (this.$route.name === 'mv') return false;
e.preventDefault();
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>
<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 {
--color-body-bg: #ffffff;
@ -103,7 +113,7 @@ export default {
--color-secondary-bg-for-transparent: rgba(209, 209, 214, 0.28);
}
[data-theme="dark"] {
[data-theme='dark'] {
--color-body-bg: #222222;
--color-text: #ffffff;
--color-primary: #335eea;
@ -121,7 +131,7 @@ export default {
}
#app,
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,
WenQuanYi Micro Hei, sans-serif;
}
@ -195,7 +205,7 @@ main::-webkit-scrollbar {
background: rgba(128, 128, 128, 0.38);
}
[data-theme="dark"] ::-webkit-scrollbar-thumb {
[data-theme='dark'] ::-webkit-scrollbar-thumb {
background: var(--color-secondary-bg);
}
@ -207,7 +217,7 @@ main::-webkit-scrollbar {
transform: translateY(100%);
}
[data-electron="yes"] {
[data-electron='yes'] {
button,
.navigation-links a,
.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) {
return request({
url: "/user/detail",
method: "get",
url: '/user/detail',
method: 'get',
params: {
uid,
timestamp: new Date().getTime(),
},
});
}
@ -22,8 +23,8 @@ export function userDetail(uid) {
*/
export function userAccount() {
return request({
url: "/user/account",
method: "get",
url: '/user/account',
method: 'get',
params: {
timestamp: new Date().getTime(),
},
@ -43,8 +44,8 @@ export function userAccount() {
*/
export function userPlaylist(params) {
return request({
url: "/user/playlist",
method: "get",
url: '/user/playlist',
method: 'get',
params,
});
}
@ -57,8 +58,8 @@ export function userPlaylist(params) {
*/
export function userLikedSongsIDs(uid) {
return request({
url: "/likelist",
method: "get",
url: '/likelist',
method: 'get',
params: {
uid,
timestamp: new Date().getTime(),
@ -74,8 +75,8 @@ export function userLikedSongsIDs(uid) {
*/
export function dailySignin(type = 0) {
return request({
url: "/daily_signin",
method: "post",
url: '/daily_signin',
method: 'post',
params: {
type,
timestamp: new Date().getTime(),
@ -94,8 +95,8 @@ export function dailySignin(type = 0) {
*/
export function likedAlbums() {
return request({
url: "/album/sublist",
method: "get",
url: '/album/sublist',
method: 'get',
params: {
timestamp: new Date().getTime(),
},
@ -108,8 +109,8 @@ export function likedAlbums() {
*/
export function likedArtists() {
return request({
url: "/artist/sublist",
method: "get",
url: '/artist/sublist',
method: 'get',
params: {
timestamp: new Date().getTime(),
},
@ -122,8 +123,8 @@ export function likedArtists() {
*/
export function likedMVs() {
return request({
url: "/mv/sublist",
method: "get",
url: '/mv/sublist',
method: 'get',
params: {
timestamp: new Date().getTime(),
},

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

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

@ -1,18 +1,27 @@
// import store, { state, dispatch, commit } from "@/store";
import { isAccountLoggedIn } from "@/utils/auth";
import { likeATrack } from "@/api/track";
import { isAccountLoggedIn, isLooseLoggedIn } from '@/utils/auth';
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 {
showToast({ state, commit }, text) {
if (state.toast.timer !== null) {
clearTimeout(state.toast.timer);
commit("updateToast", { show: false, text: "", timer: null });
commit('updateToast', { show: false, text: '', timer: null });
}
commit("updateToast", {
commit('updateToast', {
show: true,
text,
timer: setTimeout(() => {
commit("updateToast", {
commit('updateToast', {
show: false,
text: state.toast.text,
timer: null,
@ -20,23 +29,117 @@ export default {
}, 3200),
});
},
likeASong({ state, commit, dispatch }, id) {
likeATrack({ state, commit, dispatch }, id) {
if (!isAccountLoggedIn()) {
dispatch("showToast", "此操作需要登录网易云账号");
dispatch('showToast', '此操作需要登录网易云账号');
return;
}
let like = true;
if (state.liked.songs.includes(id)) like = false;
likeATrack({ id, like }).then(() => {
if (like === false) {
commit(
"updateLikedSongs",
state.liked.songs.filter((d) => d !== id)
);
commit('updateLikedXXX', {
name: 'songs',
data: state.liked.songs.filter(d => d !== id),
});
} else {
let newLikeSongs = state.liked.songs;
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 {
updateLikedSongs(state, trackIDs) {
state.liked.songs = trackIDs;
state.player.sendSelfToIpcMain();
updateLikedXXX(state, { name, data }) {
state.liked[name] = data;
if (name === 'songs') {
state.player.sendSelfToIpcMain();
}
},
changeLang(state, lang) {
state.settings.lang = lang;
@ -23,11 +25,11 @@ export default {
},
togglePlaylistCategory(state, name) {
const index = state.settings.enabledPlaylistCategories.findIndex(
(c) => c === name
c => c === name
);
if (index !== -1) {
state.settings.enabledPlaylistCategories = state.settings.enabledPlaylistCategories.filter(
(c) => c !== name
c => c !== name
);
} else {
state.settings.enabledPlaylistCategories.push(name);

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

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

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

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

Loading…
Cancel
Save