chore: format codes

master
qier222 4 years ago
parent 6922c716e2
commit 9351f6bc89
No known key found for this signature in database
GPG Key ID: 9C85007ED905F14D

@ -1,15 +1,15 @@
// node module
const fs = require("fs");
const https = require("https");
const resolve = require("path").resolve;
const join = require("path").resolve;
const extract = require("extract-zip");
const fs = require('fs');
const https = require('https');
const resolve = require('path').resolve;
const join = require('path').resolve;
const extract = require('extract-zip');
// 函数参数
const dest = resolve(__dirname, "../");
const fileName = "NeteaseCloudMusicApi-master.zip";
const dest = resolve(__dirname, '../');
const fileName = 'NeteaseCloudMusicApi-master.zip';
const options = {
hostname: "github.91chifun.workers.dev",
hostname: 'github.91chifun.workers.dev',
path: `//https://github.com/Binaryify/NeteaseCloudMusicApi/archive/master.zip`,
};
@ -27,13 +27,13 @@ function fix2(number) {
async function download(options, fileName, callback) {
return await new Promise((resolve, reject) => {
const destPath = join(__dirname, "../" + fileName);
const destPath = join(__dirname, '../' + fileName);
// Check if exist
if (fs.existsSync(destPath)) return resolve(destPath);
const file = fs.createWriteStream(destPath);
const request = https.get(options, (res) => {
let len = res.headers && parseInt(res.headers["content-length"], 10);
const request = https.get(options, res => {
let len = res.headers && parseInt(res.headers['content-length'], 10);
let cur = 0;
// 1048576 - bytes in 1Megabyte
const MEGA = 1048576;
@ -43,10 +43,10 @@ async function download(options, fileName, callback) {
}
if (!len) {
console.log(
"Downloading, but can not get content-length, please be patient."
'Downloading, but can not get content-length, please be patient.'
);
}
res.on("data", (chunk) => {
res.on('data', chunk => {
if (len) {
cur += chunk.length;
console.log(
@ -56,22 +56,22 @@ async function download(options, fileName, callback) {
);
}
});
res.on("end", () => {
callback("Downloading complete!");
res.on('end', () => {
callback('Downloading complete!');
});
res.pipe(file);
file.on("finish", () => {
file.on('finish', () => {
file.close(() => {
callback("File wrote complete!");
callback('File wrote complete!');
resolve(destPath);
});
});
file.on("error", (err) => {
file.on('error', err => {
fs.unlink(destPath);
reject(err);
});
request.on("error", (err) => {
console.log("Error: " + err.message);
request.on('error', err => {
console.log('Error: ' + err.message);
});
});
});
@ -82,21 +82,21 @@ async function unzip(source, target) {
await extract(source, {
dir: target,
});
console.log("Extraction complete");
console.log('Extraction complete');
return true;
} catch (err) {
// handle any errors
if (err.message === "end of central directory record signature not found") {
console.log("Not a full_downloaded zip file, removed!");
if (err.message === 'end of central directory record signature not found') {
console.log('Not a full_downloaded zip file, removed!');
fs.unlinkSync(source);
}
return false;
}
}
// Download process
download(options, fileName, (text) => {
download(options, fileName, text => {
console.log(text);
}).then((path) => {
}).then(path => {
console.log(path);
// Unzip process
return unzip(path, dest);

@ -1,5 +1,5 @@
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
/**
* 获取专辑内容
@ -8,12 +8,12 @@ import { mapTrackPlayableStatus } from "@/utils/common";
*/
export function getAlbum(id) {
return request({
url: "/album",
method: "get",
url: '/album',
method: 'get',
params: {
id,
},
}).then((data) => {
}).then(data => {
data.songs = mapTrackPlayableStatus(data.songs);
return data;
});
@ -32,8 +32,8 @@ export function getAlbum(id) {
*/
export function newAlbums(params) {
return request({
url: "/album/new",
method: "get",
url: '/album/new',
method: 'get',
params,
});
}
@ -46,8 +46,8 @@ export function newAlbums(params) {
*/
export function albumDynamicDetail(id) {
return request({
url: "/album/detail/dynamic",
method: "get",
url: '/album/detail/dynamic',
method: 'get',
params: { id, timestamp: new Date().getTime() },
});
}
@ -63,8 +63,8 @@ export function albumDynamicDetail(id) {
*/
export function likeAAlbum(params) {
return request({
url: "/album/sub",
method: "post",
url: '/album/sub',
method: 'post',
params,
});
}

@ -1,5 +1,5 @@
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
/**
* 获取歌手单曲
@ -8,13 +8,13 @@ import { mapTrackPlayableStatus } from "@/utils/common";
*/
export function getArtist(id) {
return request({
url: "/artists",
method: "get",
url: '/artists',
method: 'get',
params: {
id,
timestamp: new Date().getTime(),
},
}).then((data) => {
}).then(data => {
data.hotSongs = mapTrackPlayableStatus(data.hotSongs);
return data;
});
@ -33,8 +33,8 @@ export function getArtist(id) {
*/
export function getArtistAlbum(params) {
return request({
url: "/artist/album",
method: "get",
url: '/artist/album',
method: 'get',
params,
});
}
@ -51,8 +51,8 @@ export function getArtistAlbum(params) {
*/
export function toplistOfArtists(type = null) {
return request({
url: "/toplist/artist",
method: "get",
url: '/toplist/artist',
method: 'get',
params: {
type,
},
@ -67,8 +67,8 @@ export function toplistOfArtists(type = null) {
*/
export function artistMv(params) {
return request({
url: "/artist/mv",
method: "get",
url: '/artist/mv',
method: 'get',
params,
});
}
@ -84,8 +84,8 @@ export function artistMv(params) {
*/
export function followAArtist(params) {
return request({
url: "/artist/sub",
method: "post",
url: '/artist/sub',
method: 'post',
params,
});
}
@ -98,8 +98,8 @@ export function followAArtist(params) {
*/
export function similarArtists(id) {
return request({
url: "/simi/artist",
method: "post",
url: '/simi/artist',
method: 'post',
params: { id },
});
}

@ -1,4 +1,4 @@
import request from "@/utils/request";
import request from '@/utils/request';
/**
* 手机登录
@ -14,8 +14,8 @@ import request from "@/utils/request";
*/
export function loginWithPhone(params) {
return request({
url: "/login/cellphone",
method: "post",
url: '/login/cellphone',
method: 'post',
params,
});
}
@ -31,8 +31,8 @@ export function loginWithPhone(params) {
*/
export function loginWithEmail(params) {
return request({
url: "/login",
method: "post",
url: '/login',
method: 'post',
params,
});
}
@ -44,8 +44,8 @@ export function loginWithEmail(params) {
*/
export function refreshCookie() {
return request({
url: "/login/refresh",
method: "post",
url: '/login/refresh',
method: 'post',
});
}
@ -55,7 +55,7 @@ export function refreshCookie() {
*/
export function logout() {
return request({
url: "/logout",
method: "post",
url: '/logout',
method: 'post',
});
}

@ -1,20 +1,20 @@
// Last.fm API documents 👉 https://www.last.fm/api
import axios from "axios";
import md5 from "crypto-js/md5";
import axios from 'axios';
import md5 from 'crypto-js/md5';
const apiKey = process.env.VUE_APP_LASTFM_API_KEY;
const apiSharedSecret = process.env.VUE_APP_LASTFM_API_SHARED_SECRET;
const baseUrl = window.location.origin;
const url = "https://ws.audioscrobbler.com/2.0/";
const url = 'https://ws.audioscrobbler.com/2.0/';
const sign = (params) => {
const sign = params => {
const sortParamsKeys = Object.keys(params).sort();
const sortedParams = sortParamsKeys.reduce((acc, key) => {
acc[key] = params[key];
return acc;
}, {});
let signature = "";
let signature = '';
for (const [key, value] of Object.entries(sortedParams)) {
signature += `${key}${value}`;
}
@ -33,10 +33,10 @@ export function authGetSession(token) {
).toString();
return axios({
url,
method: "GET",
method: 'GET',
params: {
method: "auth.getSession",
format: "json",
method: 'auth.getSession',
format: 'json',
api_key: apiKey,
api_sig: signature,
token,
@ -46,34 +46,34 @@ export function authGetSession(token) {
export function trackUpdateNowPlaying(params) {
params.api_key = apiKey;
params.method = "track.updateNowPlaying";
params.sk = JSON.parse(localStorage.getItem("lastfm"))["key"];
params.method = 'track.updateNowPlaying';
params.sk = JSON.parse(localStorage.getItem('lastfm'))['key'];
const signature = sign(params);
return axios({
url,
method: "POST",
method: 'POST',
params: {
...params,
api_sig: signature,
format: "json",
format: 'json',
},
});
}
export function trackScrobble(params) {
params.api_key = apiKey;
params.method = "track.scrobble";
params.sk = JSON.parse(localStorage.getItem("lastfm"))["key"];
params.method = 'track.scrobble';
params.sk = JSON.parse(localStorage.getItem('lastfm'))['key'];
const signature = sign(params);
return axios({
url,
method: "POST",
method: 'POST',
params: {
...params,
api_sig: signature,
format: "json",
format: 'json',
},
});
}

@ -1,4 +1,4 @@
import request from "@/utils/request";
import request from '@/utils/request';
/**
* 获取 mv 数据
@ -9,8 +9,8 @@ import request from "@/utils/request";
*/
export function mvDetail(mvid) {
return request({
url: "/mv/detail",
method: "get",
url: '/mv/detail',
method: 'get',
params: {
mvid,
timestamp: new Date().getTime(),
@ -30,8 +30,8 @@ export function mvDetail(mvid) {
*/
export function mvUrl(params) {
return request({
url: "/mv/url",
method: "get",
url: '/mv/url',
method: 'get',
params,
});
}
@ -43,8 +43,8 @@ export function mvUrl(params) {
*/
export function simiMv(mvid) {
return request({
url: "/simi/mv",
method: "get",
url: '/simi/mv',
method: 'get',
params: { mvid },
});
}
@ -62,8 +62,8 @@ export function simiMv(mvid) {
export function likeAMV(params) {
params.timestamp = new Date().getTime();
return request({
url: "/mv/sub",
method: "post",
url: '/mv/sub',
method: 'post',
params,
});
}

@ -1,5 +1,5 @@
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
/**
* 搜索
@ -18,10 +18,10 @@ import { mapTrackPlayableStatus } from "@/utils/common";
*/
export function search(params) {
return request({
url: "/search",
method: "get",
url: '/search',
method: 'get',
params,
}).then((data) => {
}).then(data => {
if (data.result?.song !== undefined)
data.result.song.songs = mapTrackPlayableStatus(data.result.song.songs);
return data;
@ -30,8 +30,8 @@ export function search(params) {
export function personalFM() {
return request({
url: "/personal_fm",
method: "get",
url: '/personal_fm',
method: 'get',
params: {
timestamp: new Date().getTime(),
},
@ -40,8 +40,8 @@ export function personalFM() {
export function fmTrash(id) {
return request({
url: "/fm_trash",
method: "post",
url: '/fm_trash',
method: 'post',
params: {
timestamp: new Date().getTime(),
id,

@ -1,5 +1,5 @@
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
/**
* 推荐歌单
@ -11,8 +11,8 @@ import { mapTrackPlayableStatus } from "@/utils/common";
*/
export function recommendPlaylist(params) {
return request({
url: "/personalized",
method: "get",
url: '/personalized',
method: 'get',
params,
});
}
@ -24,8 +24,8 @@ export function recommendPlaylist(params) {
*/
export function dailyRecommendPlaylist(params) {
return request({
url: "/recommend/resource",
method: "get",
url: '/recommend/resource',
method: 'get',
params,
});
}
@ -43,10 +43,10 @@ export function getPlaylistDetail(id, noCache = false) {
let params = { id };
if (noCache) params.timestamp = new Date().getTime();
return request({
url: "/playlist/detail",
method: "get",
url: '/playlist/detail',
method: 'get',
params,
}).then((data) => {
}).then(data => {
data.playlist.tracks = mapTrackPlayableStatus(
data.playlist.tracks,
data.privileges || []
@ -67,8 +67,8 @@ export function getPlaylistDetail(id, noCache = false) {
*/
export function highQualityPlaylist(params) {
return request({
url: "/top/playlist/highquality",
method: "get",
url: '/top/playlist/highquality',
method: 'get',
params,
});
}
@ -86,8 +86,8 @@ export function highQualityPlaylist(params) {
*/
export function topPlaylist(params) {
return request({
url: "/top/playlist",
method: "get",
url: '/top/playlist',
method: 'get',
params,
});
}
@ -98,8 +98,8 @@ export function topPlaylist(params) {
*/
export function playlistCatlist() {
return request({
url: "/playlist/catlist",
method: "get",
url: '/playlist/catlist',
method: 'get',
});
}
@ -109,8 +109,8 @@ export function playlistCatlist() {
*/
export function toplists() {
return request({
url: "/toplist",
method: "get",
url: '/toplist',
method: 'get',
});
}
@ -126,8 +126,8 @@ export function toplists() {
export function subscribePlaylist(params) {
params.timestamp = new Date().getTime();
return request({
url: "/playlist/subscribe",
method: "post",
url: '/playlist/subscribe',
method: 'post',
params,
});
}
@ -140,8 +140,8 @@ export function subscribePlaylist(params) {
*/
export function deletePlaylist(id) {
return request({
url: "/playlist/delete",
method: "post",
url: '/playlist/delete',
method: 'post',
params: { id },
});
}
@ -160,8 +160,8 @@ export function deletePlaylist(id) {
export function createPlaylist(params) {
params.timestamp = new Date().getTime();
return request({
url: "/playlist/create",
method: "post",
url: '/playlist/create',
method: 'post',
params,
});
}
@ -178,8 +178,8 @@ export function createPlaylist(params) {
export function addOrRemoveTrackFromPlaylist(params) {
params.timestamp = new Date().getTime();
return request({
url: "/playlist/tracks",
method: "post",
url: '/playlist/tracks',
method: 'post',
params,
});
}
@ -193,10 +193,10 @@ export function addOrRemoveTrackFromPlaylist(params) {
*/
export function dailyRecommendTracks() {
return request({
url: "/recommend/songs",
method: "post",
url: '/recommend/songs',
method: 'post',
params: { timestamp: new Date().getTime() },
}).then((result) => {
}).then(result => {
result.data.dailySongs = mapTrackPlayableStatus(
result.data.dailySongs,
result.data.privileges

@ -1,6 +1,6 @@
import store from "@/store";
import request from "@/utils/request";
import { mapTrackPlayableStatus } from "@/utils/common";
import store from '@/store';
import request from '@/utils/request';
import { mapTrackPlayableStatus } from '@/utils/common';
/**
* 获取音乐 url
* 说明 : 使用歌单详情接口后 , 能得到的音乐的 id, 但不能得到的音乐 url, 调用此接口, 传入的音乐 id( 可多个 , 用逗号隔开 ), 可以获取对应的音乐的 url,
@ -13,8 +13,8 @@ export function getMP3(id) {
? store.state.settings.musicQuality
: 320000;
return request({
url: "/song/url",
method: "get",
url: '/song/url',
method: 'get',
params: {
id,
br,
@ -28,12 +28,12 @@ export function getMP3(id) {
*/
export function getTrackDetail(ids) {
return request({
url: "/song/detail",
method: "get",
url: '/song/detail',
method: 'get',
params: {
ids,
},
}).then((data) => {
}).then(data => {
data.songs = mapTrackPlayableStatus(data.songs, data.privileges);
return data;
});
@ -46,8 +46,8 @@ export function getTrackDetail(ids) {
export function getLyric(id) {
return request({
url: "/lyric",
method: "get",
url: '/lyric',
method: 'get',
params: {
id,
},
@ -60,8 +60,8 @@ export function getLyric(id) {
*/
export function topSong(type) {
return request({
url: "/top/song",
method: "get",
url: '/top/song',
method: 'get',
params: {
type,
},
@ -79,8 +79,8 @@ export function topSong(type) {
export function likeATrack(params) {
params.timestamp = new Date().getTime();
return request({
url: "/like",
method: "get",
url: '/like',
method: 'get',
params,
});
}
@ -99,8 +99,8 @@ export function likeATrack(params) {
export function scrobble(params) {
params.timestamp = new Date().getTime();
return request({
url: "/scrobble",
method: "get",
url: '/scrobble',
method: 'get',
params,
});
}

@ -265,17 +265,17 @@ a.plyr__control::before {
display: none;
}
.plyr [data-plyr="airplay"],
.plyr [data-plyr="captions"],
.plyr [data-plyr="fullscreen"],
.plyr [data-plyr="pip"] {
.plyr [data-plyr='airplay'],
.plyr [data-plyr='captions'],
.plyr [data-plyr='fullscreen'],
.plyr [data-plyr='pip'] {
display: none;
}
.plyr--airplay-supported [data-plyr="airplay"],
.plyr--captions-enabled [data-plyr="captions"],
.plyr--fullscreen-enabled [data-plyr="fullscreen"],
.plyr--pip-supported [data-plyr="pip"] {
.plyr--airplay-supported [data-plyr='airplay'],
.plyr--captions-enabled [data-plyr='captions'],
.plyr--fullscreen-enabled [data-plyr='fullscreen'],
.plyr--pip-supported [data-plyr='pip'] {
display: inline-block;
}
@ -288,11 +288,11 @@ a.plyr__control::before {
transition: transform 0.3s ease;
}
.plyr__menu .plyr__control[aria-expanded="true"] svg {
.plyr__menu .plyr__control[aria-expanded='true'] svg {
transform: rotate(90deg);
}
.plyr__menu .plyr__control[aria-expanded="true"] .plyr__tooltip {
.plyr__menu .plyr__control[aria-expanded='true'] .plyr__tooltip {
display: none;
}
@ -327,7 +327,7 @@ a.plyr__control::before {
border: var(--plyr-menu-arrow-size, 4px) solid transparent;
border-top-color: rgba(255, 255, 255, 0.9);
border-top-color: var(--plyr-menu-background, rgba(255, 255, 255, 0.9));
content: "";
content: '';
height: 0;
position: absolute;
right: calc(((18px / 2) + calc(10px * 0.7)) - (4px / 2));
@ -341,18 +341,18 @@ a.plyr__control::before {
width: 0;
}
.plyr__menu__container [role="menu"] {
.plyr__menu__container [role='menu'] {
padding: calc(10px * 0.7);
padding: calc(var(--plyr-control-spacing, 10px) * 0.7);
}
.plyr__menu__container [role="menuitem"],
.plyr__menu__container [role="menuitemradio"] {
.plyr__menu__container [role='menuitem'],
.plyr__menu__container [role='menuitemradio'] {
margin-top: 2px;
}
.plyr__menu__container [role="menuitem"]:first-child,
.plyr__menu__container [role="menuitemradio"]:first-child {
.plyr__menu__container [role='menuitem']:first-child,
.plyr__menu__container [role='menuitemradio']:first-child {
margin-top: 0;
}
@ -386,7 +386,7 @@ a.plyr__control::before {
.plyr__menu__container .plyr__control::after {
border: 4px solid transparent;
border: var(--plyr-menu-item-arrow-size, 4px) solid transparent;
content: "";
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
@ -441,7 +441,7 @@ a.plyr__control::before {
background: var(--plyr-menu-back-border-color, #dcdfe5);
box-shadow: 0 1px 0 #fff;
box-shadow: 0 1px 0 var(--plyr-menu-back-border-shadow-color, #fff);
content: "";
content: '';
height: 1px;
left: 0;
margin-top: calc(calc(10px * 0.7) / 2);
@ -457,19 +457,19 @@ a.plyr__control::before {
border-right-color: currentColor;
}
.plyr__menu__container .plyr__control[role="menuitemradio"] {
.plyr__menu__container .plyr__control[role='menuitemradio'] {
padding-left: calc(10px * 0.7);
padding-left: calc(var(--plyr-control-spacing, 10px) * 0.7);
}
.plyr__menu__container .plyr__control[role="menuitemradio"]::after,
.plyr__menu__container .plyr__control[role="menuitemradio"]::before {
.plyr__menu__container .plyr__control[role='menuitemradio']::after,
.plyr__menu__container .plyr__control[role='menuitemradio']::before {
border-radius: 100%;
}
.plyr__menu__container .plyr__control[role="menuitemradio"]::before {
.plyr__menu__container .plyr__control[role='menuitemradio']::before {
background: rgba(0, 0, 0, 0.1);
content: "";
content: '';
display: block;
flex-shrink: 0;
height: 16px;
@ -479,7 +479,7 @@ a.plyr__control::before {
width: 16px;
}
.plyr__menu__container .plyr__control[role="menuitemradio"]::after {
.plyr__menu__container .plyr__control[role='menuitemradio']::after {
background: #fff;
border: 0;
height: 6px;
@ -492,7 +492,7 @@ a.plyr__control::before {
}
.plyr__menu__container
.plyr__control[role="menuitemradio"][aria-checked="true"]::before {
.plyr__control[role='menuitemradio'][aria-checked='true']::before {
background: #00b3ff;
background: var(
--plyr-control-toggle-checked-background,
@ -501,14 +501,14 @@ a.plyr__control::before {
}
.plyr__menu__container
.plyr__control[role="menuitemradio"][aria-checked="true"]::after {
.plyr__control[role='menuitemradio'][aria-checked='true']::after {
opacity: 1;
transform: translateY(-50%) scale(1);
}
.plyr__menu__container
.plyr__control[role="menuitemradio"].plyr__tab-focus::before,
.plyr__menu__container .plyr__control[role="menuitemradio"]:hover::before {
.plyr__control[role='menuitemradio'].plyr__tab-focus::before,
.plyr__menu__container .plyr__control[role='menuitemradio']:hover::before {
background: rgba(35, 40, 47, 0.1);
}
@ -524,7 +524,7 @@ a.plyr__control::before {
pointer-events: none;
}
.plyr--full-ui input[type="range"] {
.plyr--full-ui input[type='range'] {
-webkit-appearance: none;
background: 0 0;
border: 0;
@ -547,7 +547,7 @@ a.plyr__control::before {
width: 100%;
}
.plyr--full-ui input[type="range"]::-webkit-slider-runnable-track {
.plyr--full-ui input[type='range']::-webkit-slider-runnable-track {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@ -566,7 +566,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui input[type="range"]::-webkit-slider-thumb {
.plyr--full-ui input[type='range']::-webkit-slider-thumb {
background: #fff;
background: var(--plyr-range-thumb-background, #fff);
border: 0;
@ -596,7 +596,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui input[type="range"]::-moz-range-track {
.plyr--full-ui input[type='range']::-moz-range-track {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@ -608,7 +608,7 @@ a.plyr__control::before {
user-select: none;
}
.plyr--full-ui input[type="range"]::-moz-range-thumb {
.plyr--full-ui input[type='range']::-moz-range-thumb {
background: #fff;
background: var(--plyr-range-thumb-background, #fff);
border: 0;
@ -628,7 +628,7 @@ a.plyr__control::before {
width: var(--plyr-range-thumb-height, 13px);
}
.plyr--full-ui input[type="range"]::-moz-range-progress {
.plyr--full-ui input[type='range']::-moz-range-progress {
background: currentColor;
border-radius: calc(5px / 2);
border-radius: calc(var(--plyr-range-track-height, 5px) / 2);
@ -636,7 +636,7 @@ a.plyr__control::before {
height: var(--plyr-range-track-height, 5px);
}
.plyr--full-ui input[type="range"]::-ms-track {
.plyr--full-ui input[type='range']::-ms-track {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@ -650,7 +650,7 @@ a.plyr__control::before {
color: transparent;
}
.plyr--full-ui input[type="range"]::-ms-fill-upper {
.plyr--full-ui input[type='range']::-ms-fill-upper {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@ -663,7 +663,7 @@ a.plyr__control::before {
user-select: none;
}
.plyr--full-ui input[type="range"]::-ms-fill-lower {
.plyr--full-ui input[type='range']::-ms-fill-lower {
background: 0 0;
border: 0;
border-radius: calc(5px / 2);
@ -677,7 +677,7 @@ a.plyr__control::before {
background: currentColor;
}
.plyr--full-ui input[type="range"]::-ms-thumb {
.plyr--full-ui input[type='range']::-ms-thumb {
background: #fff;
background: var(--plyr-range-thumb-background, #fff);
border: 0;
@ -698,20 +698,20 @@ a.plyr__control::before {
margin-top: 0;
}
.plyr--full-ui input[type="range"]::-ms-tooltip {
.plyr--full-ui input[type='range']::-ms-tooltip {
display: none;
}
.plyr--full-ui input[type="range"]:focus {
.plyr--full-ui input[type='range']:focus {
outline: 0;
}
.plyr--full-ui input[type="range"]::-moz-focus-outer {
.plyr--full-ui input[type='range']::-moz-focus-outer {
border: 0;
}
.plyr--full-ui
input[type="range"].plyr__tab-focus::-webkit-slider-runnable-track {
input[type='range'].plyr__tab-focus::-webkit-slider-runnable-track {
outline-color: #00b3ff;
outline-color: var(
--plyr-tab-focus-color,
@ -722,7 +722,7 @@ a.plyr__control::before {
outline-width: 3px;
}
.plyr--full-ui input[type="range"].plyr__tab-focus::-moz-range-track {
.plyr--full-ui input[type='range'].plyr__tab-focus::-moz-range-track {
outline-color: #00b3ff;
outline-color: var(
--plyr-tab-focus-color,
@ -733,7 +733,7 @@ a.plyr__control::before {
outline-width: 3px;
}
.plyr--full-ui input[type="range"].plyr__tab-focus::-ms-track {
.plyr--full-ui input[type='range'].plyr__tab-focus::-ms-track {
outline-color: #00b3ff;
outline-color: var(
--plyr-tab-focus-color,
@ -769,7 +769,7 @@ a.plyr__control::before {
}
.plyr__time + .plyr__time::before {
content: "\2044";
content: '\2044';
margin-right: 10px;
margin-right: var(--plyr-control-spacing, 10px);
}
@ -821,7 +821,7 @@ a.plyr__control::before {
var(--plyr-tooltip-background, rgba(255, 255, 255, 0.9));
bottom: calc(4px * -1);
bottom: calc(var(--plyr-tooltip-arrow-size, 4px) * -1);
content: "";
content: '';
height: 0;
left: 50%;
position: absolute;
@ -906,7 +906,7 @@ a.plyr__control::before {
position: relative;
}
.plyr__progress input[type="range"],
.plyr__progress input[type='range'],
.plyr__progress__buffer {
margin-left: calc(13px * -0.5);
margin-left: calc(var(--plyr-range-thumb-height, 13px) * -0.5);
@ -916,7 +916,7 @@ a.plyr__control::before {
width: calc(100% + var(--plyr-range-thumb-height, 13px));
}
.plyr__progress input[type="range"] {
.plyr__progress input[type='range'] {
position: relative;
z-index: 2;
}
@ -1024,7 +1024,7 @@ a.plyr__control::before {
width: 20%;
}
.plyr__volume input[type="range"] {
.plyr__volume input[type='range'] {
margin-left: calc(10px / 2);
margin-left: calc(var(--plyr-control-spacing, 10px) / 2);
margin-right: calc(10px / 2);
@ -1054,7 +1054,7 @@ a.plyr__control::before {
.plyr--audio .plyr__control.plyr__tab-focus,
.plyr--audio .plyr__control:hover,
.plyr--audio .plyr__control[aria-expanded="true"] {
.plyr--audio .plyr__control[aria-expanded='true'] {
background: #00b3ff;
background: var(
--plyr-audio-control-background-hover,
@ -1064,7 +1064,7 @@ a.plyr__control::before {
color: var(--plyr-audio-control-color-hover, #fff);
}
.plyr--full-ui.plyr--audio input[type="range"]::-webkit-slider-runnable-track {
.plyr--full-ui.plyr--audio input[type='range']::-webkit-slider-runnable-track {
background-color: rgba(193, 200, 209, 0.6);
background-color: var(
--plyr-audio-range-track-background,
@ -1072,7 +1072,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui.plyr--audio input[type="range"]::-moz-range-track {
.plyr--full-ui.plyr--audio input[type='range']::-moz-range-track {
background-color: rgba(193, 200, 209, 0.6);
background-color: var(
--plyr-audio-range-track-background,
@ -1080,7 +1080,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui.plyr--audio input[type="range"]::-ms-track {
.plyr--full-ui.plyr--audio input[type='range']::-ms-track {
background-color: rgba(193, 200, 209, 0.6);
background-color: var(
--plyr-audio-range-track-background,
@ -1088,7 +1088,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui.plyr--audio input[type="range"]:active::-webkit-slider-thumb {
.plyr--full-ui.plyr--audio input[type='range']:active::-webkit-slider-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(35, 40, 47, 0.1);
box-shadow: var(
@ -1100,7 +1100,7 @@ a.plyr__control::before {
var(--plyr-audio-range-thumb-active-shadow-color, rgba(35, 40, 47, 0.1));
}
.plyr--full-ui.plyr--audio input[type="range"]:active::-moz-range-thumb {
.plyr--full-ui.plyr--audio input[type='range']:active::-moz-range-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(35, 40, 47, 0.1);
box-shadow: var(
@ -1112,7 +1112,7 @@ a.plyr__control::before {
var(--plyr-audio-range-thumb-active-shadow-color, rgba(35, 40, 47, 0.1));
}
.plyr--full-ui.plyr--audio input[type="range"]:active::-ms-thumb {
.plyr--full-ui.plyr--audio input[type='range']:active::-ms-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(35, 40, 47, 0.1);
box-shadow: var(
@ -1207,7 +1207,7 @@ a.plyr__control::before {
.plyr--video .plyr__control.plyr__tab-focus,
.plyr--video .plyr__control:hover,
.plyr--video .plyr__control[aria-expanded="true"] {
.plyr--video .plyr__control[aria-expanded='true'] {
background: #00b3ff;
background: var(
--plyr-video-control-background-hover,
@ -1258,7 +1258,7 @@ a.plyr__control::before {
display: block;
}
.plyr--full-ui.plyr--video input[type="range"]::-webkit-slider-runnable-track {
.plyr--full-ui.plyr--video input[type='range']::-webkit-slider-runnable-track {
background-color: rgba(255, 255, 255, 0.25);
background-color: var(
--plyr-video-range-track-background,
@ -1266,7 +1266,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui.plyr--video input[type="range"]::-moz-range-track {
.plyr--full-ui.plyr--video input[type='range']::-moz-range-track {
background-color: rgba(255, 255, 255, 0.25);
background-color: var(
--plyr-video-range-track-background,
@ -1274,7 +1274,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui.plyr--video input[type="range"]::-ms-track {
.plyr--full-ui.plyr--video input[type='range']::-ms-track {
background-color: rgba(255, 255, 255, 0.25);
background-color: var(
--plyr-video-range-track-background,
@ -1282,7 +1282,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui.plyr--video input[type="range"]:active::-webkit-slider-thumb {
.plyr--full-ui.plyr--video input[type='range']:active::-webkit-slider-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(255, 255, 255, 0.5);
box-shadow: var(
@ -1297,7 +1297,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui.plyr--video input[type="range"]:active::-moz-range-thumb {
.plyr--full-ui.plyr--video input[type='range']:active::-moz-range-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(255, 255, 255, 0.5);
box-shadow: var(
@ -1312,7 +1312,7 @@ a.plyr__control::before {
);
}
.plyr--full-ui.plyr--video input[type="range"]:active::-ms-thumb {
.plyr--full-ui.plyr--video input[type='range']:active::-ms-thumb {
box-shadow: 0 1px 1px rgba(35, 40, 47, 0.15), 0 0 0 1px rgba(35, 40, 47, 0.2),
0 0 0 3px rgba(255, 255, 255, 0.5);
box-shadow: var(
@ -1713,7 +1713,7 @@ a.plyr__control::before {
var(--plyr-tooltip-background, rgba(255, 255, 255, 0.9));
bottom: calc(4px * -1);
bottom: calc(var(--plyr-tooltip-arrow-size, 4px) * -1);
content: "";
content: '';
height: 0;
left: 50%;
position: absolute;

@ -89,7 +89,7 @@
}
.nyancat .vue-slider-dot-handle {
background: url("/img/logos/nyancat.gif");
background: url('/img/logos/nyancat.gif');
background-size: 36px;
width: 36px;
height: 24px;
@ -101,7 +101,7 @@
}
.nyancat-stop .vue-slider-dot-handle {
background-image: url("/img/logos/nyancat-stop.png");
background-image: url('/img/logos/nyancat-stop.png');
transition: 300ms;
}
@ -126,10 +126,10 @@
display: none;
}
body[data-theme="dark"] .lyrics-page .vue-slider-process {
body[data-theme='dark'] .lyrics-page .vue-slider-process {
background-color: #fafafa;
}
body[data-theme="dark"] .lyrics-page .vue-slider-dot-handle {
body[data-theme='dark'] .lyrics-page .vue-slider-dot-handle {
background-color: #fff;
}

@ -1,8 +1,7 @@
import Vue from "vue";
import SvgIcon from "@/components/SvgIcon";
import Vue from 'vue';
import SvgIcon from '@/components/SvgIcon';
Vue.component("svg-icon", SvgIcon);
const requireAll = (requireContext) =>
requireContext.keys().map(requireContext);
const req = require.context("./", true, /\.svg$/);
Vue.component('svg-icon', SvgIcon);
const requireAll = requireContext => requireContext.keys().map(requireContext);
const req = require.context('./', true, /\.svg$/);
requireAll(req);

@ -1,4 +1,4 @@
"use strict";
'use strict';
import {
app,
protocol,
@ -7,20 +7,20 @@ import {
dialog,
globalShortcut,
nativeTheme,
} from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
import { startNeteaseMusicApi } from "./electron/services";
import { initIpcMain } from "./electron/ipcMain.js";
import { createMenu } from "./electron/menu";
import { createTray } from "@/electron/tray";
import { createTouchBar } from "./electron/touchBar";
import { createDockMenu } from "./electron/dockMenu";
import { registerGlobalShortcut } from "./electron/globalShortcut";
import { autoUpdater } from "electron-updater";
import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer";
import express from "express";
import expressProxy from "express-http-proxy";
import Store from "electron-store";
} from 'electron';
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import { startNeteaseMusicApi } from './electron/services';
import { initIpcMain } from './electron/ipcMain.js';
import { createMenu } from './electron/menu';
import { createTray } from '@/electron/tray';
import { createTouchBar } from './electron/touchBar';
import { createDockMenu } from './electron/dockMenu';
import { registerGlobalShortcut } from './electron/globalShortcut';
import { autoUpdater } from 'electron-updater';
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
import express from 'express';
import expressProxy from 'express-http-proxy';
import Store from 'electron-store';
class Background {
constructor() {
@ -28,19 +28,19 @@ class Background {
this.tray = null;
this.store = new Store({
windowWidth: {
width: { type: "number", default: 1440 },
height: { type: "number", default: 840 },
width: { type: 'number', default: 1440 },
height: { type: 'number', default: 840 },
},
});
this.neteaseMusicAPI = null;
this.expressApp = null;
this.willQuitApp = process.platform === "darwin" ? false : true;
this.willQuitApp = process.platform === 'darwin' ? false : true;
this.init();
}
init() {
console.log("initializing");
console.log('initializing');
// Make sure the app is singleton.
if (!app.requestSingleInstanceLock()) return app.quit();
@ -53,7 +53,7 @@ class Background {
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: "app", privileges: { secure: true, standard: true } },
{ scheme: 'app', privileges: { secure: true, standard: true } },
]);
// handle app events
@ -65,45 +65,45 @@ class Background {
try {
await installExtension(VUEJS_DEVTOOLS);
} catch (e) {
console.error("Vue Devtools failed to install:", e.toString());
console.error('Vue Devtools failed to install:', e.toString());
}
// Exit cleanly on request from parent process in development mode.
if (process.platform === "win32") {
process.on("message", (data) => {
if (data === "graceful-exit") {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit();
}
});
} else {
process.on("SIGTERM", () => {
process.on('SIGTERM', () => {
app.quit();
});
}
}
createExpressApp() {
console.log("creating express app");
console.log('creating express app');
const expressApp = express();
expressApp.use("/", express.static(__dirname + "/"));
expressApp.use("/api", expressProxy("http://127.0.0.1:10754"));
expressApp.use('/', express.static(__dirname + '/'));
expressApp.use('/api', expressProxy('http://127.0.0.1:10754'));
this.expressApp = expressApp.listen(27232);
}
createWindow() {
console.log("creating app window");
console.log('creating app window');
const appearance = this.store.get("settings.appearance");
const appearance = this.store.get('settings.appearance');
this.window = new BrowserWindow({
width: this.store.get("window.width") || 1440,
height: this.store.get("window.height") || 840,
width: this.store.get('window.width') || 1440,
height: this.store.get('window.height') || 840,
minWidth: 1080,
minHeight: 720,
titleBarStyle: "hiddenInset",
frame: process.platform !== "win32",
title: "YesPlayMusic",
titleBarStyle: 'hiddenInset',
frame: process.platform !== 'win32',
title: 'YesPlayMusic',
webPreferences: {
webSecurity: false,
nodeIntegration: true,
@ -111,11 +111,11 @@ class Background {
contextIsolation: false,
},
backgroundColor:
((appearance === undefined || appearance === "auto") &&
((appearance === undefined || appearance === 'auto') &&
nativeTheme.shouldUseDarkColors) ||
appearance === "dark"
? "#222"
: "#fff",
appearance === 'dark'
? '#222'
: '#fff',
});
// hide menu bar on Microsoft Windows and Linux
@ -126,46 +126,46 @@ class Background {
this.window.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
if (!process.env.IS_TEST) this.window.webContents.openDevTools();
} else {
createProtocol("app");
this.window.loadURL("http://localhost:27232");
createProtocol('app');
this.window.loadURL('http://localhost:27232');
}
}
checkForUpdates() {
autoUpdater.checkForUpdatesAndNotify();
const showNewVersionMessage = (info) => {
const showNewVersionMessage = info => {
dialog
.showMessageBox({
title: "发现新版本 v" + info.version,
message: "发现新版本 v" + info.version,
detail: "是否前往 GitHub 下载新版本安装包?",
buttons: ["下载", "取消"],
type: "question",
title: '发现新版本 v' + info.version,
message: '发现新版本 v' + info.version,
detail: '是否前往 GitHub 下载新版本安装包?',
buttons: ['下载', '取消'],
type: 'question',
noLink: true,
})
.then((result) => {
.then(result => {
if (result.response === 0) {
shell.openExternal(
"https://github.com/qier222/YesPlayMusic/releases"
'https://github.com/qier222/YesPlayMusic/releases'
);
}
});
};
autoUpdater.on("update-available", (info) => {
autoUpdater.on('update-available', info => {
showNewVersionMessage(info);
});
}
handleWindowEvents() {
this.window.once("ready-to-show", () => {
console.log("windows ready-to-show event");
this.window.once('ready-to-show', () => {
console.log('windows ready-to-show event');
this.window.show();
});
this.window.on("close", (e) => {
console.log("windows close event");
this.window.on('close', e => {
console.log('windows close event');
if (this.willQuitApp) {
/* the user tried to quit the app */
this.window = null;
@ -177,31 +177,31 @@ class Background {
}
});
this.window.on("resize", () => {
this.window.on('resize', () => {
let { height, width } = this.window.getBounds();
this.store.set("window", { height, width });
this.store.set('window', { height, width });
});
this.window.on("minimize", () => {
this.window.on('minimize', () => {
if (
["win32", "linux"].includes(process.platform) &&
this.store.get("settings.minimizeToTray")
['win32', 'linux'].includes(process.platform) &&
this.store.get('settings.minimizeToTray')
) {
this.window.hide();
}
});
this.window.webContents.on("new-window", function (e, url) {
this.window.webContents.on('new-window', function (e, url) {
e.preventDefault();
console.log("open url");
const excludeHosts = ["www.last.fm"];
const exclude = excludeHosts.find((host) => url.includes(host));
console.log('open url');
const excludeHosts = ['www.last.fm'];
const exclude = excludeHosts.find(host => url.includes(host));
if (exclude) {
const newWindow = new BrowserWindow({
width: 800,
height: 600,
titleBarStyle: "default",
title: "YesPlayMusic",
titleBarStyle: 'default',
title: 'YesPlayMusic',
webPreferences: {
webSecurity: false,
nodeIntegration: true,
@ -217,14 +217,14 @@ class Background {
}
handleAppEvents() {
app.on("ready", async () => {
app.on('ready', async () => {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
console.log("app ready event");
console.log('app ready event');
// for development
if (process.env.NODE_ENV !== "production") {
if (process.env.NODE_ENV !== 'production') {
this.initDevtools();
}
@ -242,7 +242,7 @@ class Background {
createMenu(this.window);
// create tray
if (["win32", "linux"].includes(process.platform)) {
if (['win32', 'linux'].includes(process.platform)) {
this.tray = createTray(this.window);
}
@ -253,15 +253,15 @@ class Background {
this.window.setTouchBar(createTouchBar(this.window));
// register global shortcuts
if (this.store.get("settings.enableGlobalShortcut")) {
if (this.store.get('settings.enableGlobalShortcut')) {
registerGlobalShortcut(this.window);
}
});
app.on("activate", () => {
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
console.log("app activate event");
console.log('app activate event');
if (this.window === null) {
this.createWindow();
} else {
@ -269,21 +269,21 @@ class Background {
}
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on("before-quit", () => {
app.on('before-quit', () => {
this.willQuitApp = true;
});
app.on("quit", () => {
app.on('quit', () => {
this.expressApp.close();
});
app.on("will-quit", () => {
app.on('will-quit', () => {
// unregister all global shortcuts
globalShortcut.unregisterAll();
});

@ -10,7 +10,7 @@
<script>
export default {
name: "ArtistInLine",
name: 'ArtistInLine',
props: {
artists: {
type: Array,
@ -18,20 +18,20 @@ export default {
},
exclude: {
type: String,
default: "",
default: '',
},
prefix: {
type: String,
default: "",
default: '',
},
},
computed: {
filteredArtists() {
return this.artists.filter((a) => a.name !== this.exclude);
return this.artists.filter(a => a.name !== this.exclude);
},
computedPrefix() {
if (this.filteredArtists.length !== 0) return this.prefix;
else return "";
else return '';
},
},
};

@ -4,7 +4,7 @@
<script>
export default {
name: "ButtonIcon",
name: 'ButtonIcon',
};
</script>

@ -2,7 +2,7 @@
<button :style="buttonStyle" :class="color">
<svg-icon
v-if="iconClass !== null"
:iconClass="iconClass"
:icon-class="iconClass"
:style="{ marginRight: iconButton ? '0px' : '8px' }"
/>
<slot></slot>
@ -11,7 +11,7 @@
<script>
export default {
name: "ButtonTwoTone",
name: 'ButtonTwoTone',
props: {
iconClass: {
type: String,
@ -27,32 +27,32 @@ export default {
},
color: {
type: String,
default: "blue",
default: 'blue',
},
backgroundColor: {
type: String,
default: "",
default: '',
},
textColor: {
type: String,
default: "",
default: '',
},
shape: {
type: String,
default: "square",
default: 'square',
},
},
computed: {
buttonStyle() {
let styles = {
borderRadius: this.shape === "round" ? "50%" : "8px",
borderRadius: this.shape === 'round' ? '50%' : '8px',
padding: `8px ${this.horizontalPadding}px`,
// height: "38px",
width: this.shape === "round" ? "38px" : "auto",
width: this.shape === 'round' ? '38px' : 'auto',
};
if (this.backgroundColor !== "")
if (this.backgroundColor !== '')
styles.backgroundColor = this.backgroundColor;
if (this.textColor !== "") styles.color = this.textColor;
if (this.textColor !== '') styles.color = this.textColor;
return styles;
},
},

@ -1,12 +1,12 @@
<template>
<div class="context-menu" ref="contextMenu">
<div ref="contextMenu" class="context-menu">
<div
v-if="showMenu"
ref="menu"
class="menu"
tabindex="-1"
ref="menu"
v-if="showMenu"
@blur="closeMenu"
:style="{ top: top, left: left }"
@blur="closeMenu"
@click="closeMenu"
>
<slot></slot>
@ -16,12 +16,12 @@
<script>
export default {
name: "ContextMenu",
name: 'ContextMenu',
data() {
return {
showMenu: false,
top: "0px",
left: "0px",
top: '0px',
left: '0px',
};
},
methods: {
@ -31,8 +31,8 @@ export default {
let largestWidth = window.innerWidth - this.$refs.menu.offsetWidth - 25;
if (top > largestHeight) top = largestHeight;
if (left > largestWidth) left = largestWidth;
this.top = top + "px";
this.left = left + "px";
this.top = top + 'px';
this.left = left + 'px';
},
closeMenu() {
@ -82,7 +82,7 @@ export default {
}
}
[data-theme="dark"] {
[data-theme='dark'] {
.menu {
background: rgba(36, 36, 36, 0.78);
backdrop-filter: blur(16px) contrast(120%);

@ -1,9 +1,9 @@
<template>
<div
class="cover"
:class="{ 'cover-hover': coverHover }"
@mouseover="focus = true"
@mouseleave="focus = false"
:class="{ 'cover-hover': coverHover }"
@click="clickCoverToPlay ? play() : goTo()"
>
<div class="cover-container">
@ -11,16 +11,16 @@
<button
v-show="focus"
class="play-button"
@click.stop="play()"
:style="playButtonStyles"
@click.stop="play()"
><svg-icon icon-class="play" />
</button>
</div>
<img :src="imageUrl" :style="imageStyles" />
<transition name="fade" v-if="coverHover || alwaysShowShadow">
<transition v-if="coverHover || alwaysShowShadow" name="fade">
<div
class="shadow"
v-show="focus || alwaysShowShadow"
class="shadow"
:style="shadowStyles"
></div>
</transition>
@ -52,22 +52,22 @@ export default {
imageStyles() {
let styles = {};
if (this.fixedSize !== 0) {
styles.width = this.fixedSize + "px";
styles.height = this.fixedSize + "px";
styles.width = this.fixedSize + 'px';
styles.height = this.fixedSize + 'px';
}
if (this.type === "artist") styles.borderRadius = "50%";
if (this.type === 'artist') styles.borderRadius = '50%';
return styles;
},
playButtonStyles() {
let styles = {};
styles.width = this.playButtonSize + "%";
styles.height = this.playButtonSize + "%";
styles.width = this.playButtonSize + '%';
styles.height = this.playButtonSize + '%';
return styles;
},
shadowStyles() {
let styles = {};
styles.backgroundImage = `url(${this.imageUrl})`;
if (this.type === "artist") styles.borderRadius = "50%";
if (this.type === 'artist') styles.borderRadius = '50%';
return styles;
},
},

@ -1,19 +1,19 @@
<template>
<div class="cover-row" :style="rowStyles">
<div
class="item"
v-for="item in items"
:key="item.id"
class="item"
:class="{ artist: type === 'artist' }"
>
<Cover
:imageUrl="getImageUrl(item)"
:type="type"
:id="item.id"
:playButtonSize="type === 'artist' ? 26 : playButtonSize"
:image-url="getImageUrl(item)"
:type="type"
:play-button-size="type === 'artist' ? 26 : playButtonSize"
/>
<div class="text">
<div class="info" v-if="showPlayCount">
<div v-if="showPlayCount" class="info">
<span class="play-count"
><svg-icon icon-class="play" />{{
item.playCount | formatPlayCount
@ -21,15 +21,15 @@
</span>
</div>
<div class="title" :style="{ fontSize: subTextFontSize }">
<span class="explicit-symbol" v-if="isExplicit(item)"
<span v-if="isExplicit(item)" class="explicit-symbol"
><ExplicitSymbol
/></span>
<span class="lock-icon" v-if="isPrivacy(item)">
<span v-if="isPrivacy(item)" class="lock-icon">
<svg-icon icon-class="lock"
/></span>
<router-link :to="getTitleLink(item)">{{ item.name }}</router-link>
</div>
<div class="info" v-if="type !== 'artist' && subText !== 'none'">
<div v-if="type !== 'artist' && subText !== 'none'" class="info">
<span v-html="getSubText(item)"></span>
</div>
</div>
@ -38,11 +38,11 @@
</template>
<script>
import Cover from "@/components/Cover.vue";
import ExplicitSymbol from "@/components/ExplicitSymbol.vue";
import Cover from '@/components/Cover.vue';
import ExplicitSymbol from '@/components/ExplicitSymbol.vue';
export default {
name: "CoverRow",
name: 'CoverRow',
components: {
Cover,
ExplicitSymbol,
@ -50,64 +50,64 @@ export default {
props: {
items: { type: Array, required: true },
type: { type: String, required: true },
subText: { type: String, default: "null" },
subTextFontSize: { type: String, default: "16px" },
subText: { type: String, default: 'null' },
subTextFontSize: { type: String, default: '16px' },
showPlayCount: { type: Boolean, default: false },
columnNumber: { type: Number, default: 5 },
gap: { type: String, default: "44px 24px" },
gap: { type: String, default: '44px 24px' },
playButtonSize: { type: Number, default: 22 },
},
computed: {
rowStyles() {
return {
"grid-template-columns": `repeat(${this.columnNumber}, 1fr)`,
'grid-template-columns': `repeat(${this.columnNumber}, 1fr)`,
gap: this.gap,
};
},
},
methods: {
getSubText(item) {
if (this.subText === "copywriter") return item.copywriter;
if (this.subText === "description") return item.description;
if (this.subText === "updateFrequency") return item.updateFrequency;
if (this.subText === "creator") return "by " + item.creator.nickname;
if (this.subText === "releaseYear")
if (this.subText === 'copywriter') return item.copywriter;
if (this.subText === 'description') return item.description;
if (this.subText === 'updateFrequency') return item.updateFrequency;
if (this.subText === 'creator') return 'by ' + item.creator.nickname;
if (this.subText === 'releaseYear')
return new Date(item.publishTime).getFullYear();
if (this.subText === "artist") {
if (this.subText === 'artist') {
if (item.artist !== undefined)
return `<a href="/#/artist/${item.artist.id}">${item.artist.name}</a>`;
if (item.artists !== undefined)
return `<a href="/#/artist/${item.artists[0].id}">${item.artists[0].name}</a>`;
}
if (this.subText === "albumType+releaseYear") {
if (this.subText === 'albumType+releaseYear') {
let albumType = item.type;
if (item.type === "EP/Single") {
albumType = item.size === 1 ? "Single" : "EP";
} else if (item.type === "Single") {
albumType = "Single";
} else if (item.type === "专辑") {
albumType = "Album";
if (item.type === 'EP/Single') {
albumType = item.size === 1 ? 'Single' : 'EP';
} else if (item.type === 'Single') {
albumType = 'Single';
} else if (item.type === '专辑') {
albumType = 'Album';
}
return `${albumType} · ${new Date(item.publishTime).getFullYear()}`;
}
if (this.subText === "appleMusic") return "by Apple Music";
if (this.subText === 'appleMusic') return 'by Apple Music';
},
isPrivacy(item) {
return this.type === "playlist" && item.privacy === 10;
return this.type === 'playlist' && item.privacy === 10;
},
isExplicit(item) {
return this.type === "album" && item.mark === 1056768;
return this.type === 'album' && item.mark === 1056768;
},
getTitleLink(item) {
return `/${this.type}/${item.id}`;
},
getImageUrl(item) {
if (item.img1v1Url) {
let img1v1ID = item.img1v1Url.split("/");
let img1v1ID = item.img1v1Url.split('/');
img1v1ID = img1v1ID[img1v1ID.length - 1];
if (img1v1ID === "5639395138885805.jpg") {
if (img1v1ID === '5639395138885805.jpg') {
// img1v1Url 😅😅😅
return "https://p2.music.126.net/VnZiScyynLG7atLIZ2YPkw==/18686200114669622.jpg?param=512y512";
return 'https://p2.music.126.net/VnZiScyynLG7atLIZ2YPkw==/18686200114669622.jpg?param=512y512';
}
}
let img = item.img1v1Url || item.picUrl || item.coverImgUrl;

@ -18,24 +18,24 @@
</template>
<script>
import { mapMutations, mapState, mapActions } from "vuex";
import { dailyRecommendTracks } from "@/api/playlist";
import { isAccountLoggedIn } from "@/utils/auth";
import sample from "lodash/sample";
import { mapMutations, mapState, mapActions } from 'vuex';
import { dailyRecommendTracks } from '@/api/playlist';
import { isAccountLoggedIn } from '@/utils/auth';
import sample from 'lodash/sample';
const defaultCovers = [
"https://p2.music.126.net/0-Ybpa8FrDfRgKYCTJD8Xg==/109951164796696795.jpg",
"https://p2.music.126.net/QxJA2mr4hhb9DZyucIOIQw==/109951165422200291.jpg",
"https://p1.music.126.net/AhYP9TET8l-VSGOpWAKZXw==/109951165134386387.jpg",
'https://p2.music.126.net/0-Ybpa8FrDfRgKYCTJD8Xg==/109951164796696795.jpg',
'https://p2.music.126.net/QxJA2mr4hhb9DZyucIOIQw==/109951165422200291.jpg',
'https://p1.music.126.net/AhYP9TET8l-VSGOpWAKZXw==/109951165134386387.jpg',
];
export default {
name: "DailyTracksCard",
name: 'DailyTracksCard',
data() {
return { useAnimation: false };
},
computed: {
...mapState(["dailyTracks"]),
...mapState(['dailyTracks']),
coverUrl() {
return `${
this.dailyTracks[0]?.al.picUrl || sample(defaultCovers)
@ -46,29 +46,29 @@ export default {
if (this.dailyTracks.length === 0) this.loadDailyTracks();
},
methods: {
...mapActions(["showToast"]),
...mapMutations(["updateDailyTracks"]),
...mapActions(['showToast']),
...mapMutations(['updateDailyTracks']),
loadDailyTracks() {
if (!isAccountLoggedIn()) return;
dailyRecommendTracks()
.then((result) => {
.then(result => {
this.updateDailyTracks(result.data.dailySongs);
})
.catch(() => {});
},
goToDailyTracks() {
this.$router.push({ name: "dailySongs" });
this.$router.push({ name: 'dailySongs' });
},
playDailyTracks() {
if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号");
this.showToast('此操作需要登录网易云账号');
return;
}
let trackIDs = this.dailyTracks.map((t) => t.id);
let trackIDs = this.dailyTracks.map(t => t.id);
this.$store.state.player.replacePlaylist(
trackIDs,
"/daily/songs",
"url",
'/daily/songs',
'url',
this.dailyTracks[0].id
);
},

@ -3,10 +3,10 @@
</template>
<script>
import SvgIcon from "@/components/SvgIcon.vue";
import SvgIcon from '@/components/SvgIcon.vue';
export default {
name: "ExplicitSymbol",
name: 'ExplicitSymbol',
components: {
SvgIcon,
},
@ -23,8 +23,8 @@ export default {
},
created() {
this.svgStyle = {
height: this.size + "px",
width: this.size + "px",
height: this.size + 'px',
width: this.size + 'px',
};
},
};

@ -12,17 +12,17 @@
</div>
<div class="controls">
<div class="buttons">
<button-icon @click.native="moveToFMTrash" title="不喜欢"
><svg-icon icon-class="thumbs-down" id="thumbs-down"
<button-icon title="不喜欢" @click.native="moveToFMTrash"
><svg-icon id="thumbs-down" icon-class="thumbs-down"
/></button-icon>
<button-icon
:title="$t(isPlaying ? 'player.pause' : 'player.play')"
class="play"
@click.native="play"
:title="$t(isPlaying ? 'player.pause' : 'player.play')"
>
<svg-icon :iconClass="isPlaying ? 'pause' : 'play'"
<svg-icon :icon-class="isPlaying ? 'pause' : 'play'"
/></button-icon>
<button-icon @click.native="next" :title="$t('player.next')"
<button-icon :title="$t('player.next')" @click.native="next"
><svg-icon icon-class="next" /></button-icon
></div>
<div class="card-name"><svg-icon icon-class="fm" />私人FM</div>
@ -32,15 +32,15 @@
</template>
<script>
import ButtonIcon from "@/components/ButtonIcon.vue";
import ArtistsInLine from "@/components/ArtistsInLine.vue";
import { mapState } from "vuex";
import ButtonIcon from '@/components/ButtonIcon.vue';
import ArtistsInLine from '@/components/ArtistsInLine.vue';
import { mapState } from 'vuex';
export default {
name: "FMCard",
name: 'FMCard',
components: { ButtonIcon, ArtistsInLine },
computed: {
...mapState(["player"]),
...mapState(['player']),
track() {
return this.player.personalFMTrack;
},
@ -60,7 +60,7 @@ export default {
},
goToAlbum() {
if (this.track.album.id === 0) return;
this.$router.push({ path: "/album/" + this.track.album.id });
this.$router.push({ path: '/album/' + this.track.album.id });
},
moveToFMTrash() {
this.player.moveToFMTrash();

@ -1,6 +1,6 @@
<template>
<div class="shade" @click="clickOutside" v-show="show">
<div class="modal" @click.stop :style="modalStyles">
<div v-show="show" class="shade" @click="clickOutside">
<div class="modal" :style="modalStyles" @click.stop>
<div class="header">
<div class="title">{{ title }}</div>
<button class="close" @click="close"
@ -8,7 +8,7 @@
/></button>
</div>
<div class="content"><slot></slot></div>
<div class="footer" v-if="showFooter">
<div v-if="showFooter" class="footer">
<!-- <button>取消</button>
<button class="primary">确定</button> -->
<slot name="footer"></slot>
@ -19,13 +19,13 @@
<script>
export default {
name: "Modal",
name: 'Modal',
props: {
show: Boolean,
close: Function,
title: {
type: String,
default: "Title",
default: 'Title',
},
showFooter: {
type: Boolean,
@ -33,7 +33,7 @@ export default {
},
width: {
type: String,
default: "50vw",
default: '50vw',
},
clickOutsideHide: {
type: Boolean,
@ -173,7 +173,7 @@ export default {
}
}
[data-theme="dark"] {
[data-theme='dark'] {
.shade {
background: rgba(0, 0, 0, 0.38);
color: var(--color-text);

@ -3,7 +3,7 @@
class="add-track-to-playlist-modal"
:show="show"
:close="close"
:showFooter="false"
:show-footer="false"
title="添加到歌单"
width="25vw"
>
@ -12,9 +12,9 @@
><svg-icon icon-class="plus" />新建歌单</div
>
<div
class="playlist"
v-for="playlist in ownPlaylists"
:key="playlist.id"
class="playlist"
@click="addTrackToPlaylist(playlist.id)"
>
<img :src="playlist.coverImgUrl | resizeImage(224)" />
@ -28,13 +28,13 @@
</template>
<script>
import { mapActions, mapMutations, mapState } from "vuex";
import Modal from "@/components/Modal.vue";
import { userPlaylist } from "@/api/user";
import { addOrRemoveTrackFromPlaylist } from "@/api/playlist";
import { mapActions, mapMutations, mapState } from 'vuex';
import Modal from '@/components/Modal.vue';
import { userPlaylist } from '@/api/user';
import { addOrRemoveTrackFromPlaylist } from '@/api/playlist';
export default {
name: "ModalAddTrackToPlaylist",
name: 'ModalAddTrackToPlaylist',
components: {
Modal,
},
@ -44,22 +44,22 @@ export default {
};
},
computed: {
...mapState(["modals", "data"]),
...mapState(['modals', 'data']),
show: {
get() {
return this.modals.addTrackToPlaylistModal.show;
},
set(value) {
this.updateModal({
modalName: "addTrackToPlaylistModal",
key: "show",
modalName: 'addTrackToPlaylistModal',
key: 'show',
value,
});
},
},
ownPlaylists() {
return this.playlists.filter(
(p) =>
p =>
p.creator.userId === this.data.user.userId &&
p.id !== this.data.likedSongPlaylistID
);
@ -69,8 +69,8 @@ export default {
this.getUserPlaylists();
},
methods: {
...mapMutations(["updateModal"]),
...mapActions(["showToast"]),
...mapMutations(['updateModal']),
...mapActions(['showToast']),
close() {
this.show = false;
},
@ -79,19 +79,19 @@ export default {
timestamp: new Date().getTime(),
limit: 1000,
uid: this.data.user.userId,
}).then((data) => {
}).then(data => {
this.playlists = data.playlist;
});
},
addTrackToPlaylist(playlistID) {
addOrRemoveTrackFromPlaylist({
op: "add",
op: 'add',
pid: playlistID,
tracks: this.modals.addTrackToPlaylistModal.selectedTrackID,
}).then((data) => {
}).then(data => {
if (data.body.code === 200) {
this.show = false;
this.showToast("已添加到歌单");
this.showToast('已添加到歌单');
} else {
this.showToast(data.body.message);
}
@ -99,14 +99,14 @@ export default {
},
newPlaylist() {
this.updateModal({
modalName: "newPlaylistModal",
key: "afterCreateAddTrackID",
modalName: 'newPlaylistModal',
key: 'afterCreateAddTrackID',
value: this.modals.addTrackToPlaylistModal.selectedTrackID,
});
this.close();
this.updateModal({
modalName: "newPlaylistModal",
key: "show",
modalName: 'newPlaylistModal',
key: 'show',
value: true,
});
},

@ -8,16 +8,16 @@
>
<template slot="default">
<input
v-model="title"
type="text"
placeholder="歌单标题"
maxlength="40"
v-model="title"
/>
<div class="checkbox">
<input
type="checkbox"
id="checkbox-private"
v-model="privatePlaylist"
type="checkbox"
/>
<label for="checkbox-private">设置为隐私歌单</label>
</div>
@ -29,57 +29,57 @@
</template>
<script>
import Modal from "@/components/Modal.vue";
import { mapMutations, mapState } from "vuex";
import { createPlaylist, addOrRemoveTrackFromPlaylist } from "@/api/playlist";
import Modal from '@/components/Modal.vue';
import { mapMutations, mapState } from 'vuex';
import { createPlaylist, addOrRemoveTrackFromPlaylist } from '@/api/playlist';
export default {
name: "ModalNewPlaylist",
name: 'ModalNewPlaylist',
components: {
Modal,
},
data() {
return {
title: "",
title: '',
privatePlaylist: false,
};
},
computed: {
...mapState(["modals"]),
...mapState(['modals']),
show: {
get() {
return this.modals.newPlaylistModal.show;
},
set(value) {
this.updateModal({
modalName: "newPlaylistModal",
key: "show",
modalName: 'newPlaylistModal',
key: 'show',
value,
});
},
},
},
methods: {
...mapMutations(["updateModal"]),
...mapMutations(['updateModal']),
close() {
this.show = false;
this.title = "";
this.title = '';
this.privatePlaylist = false;
this.resetAfterCreateAddTrackID();
},
createPlaylist() {
let params = { name: this.title };
if (this.private) params.type = 10;
createPlaylist(params).then((data) => {
createPlaylist(params).then(data => {
if (data.code === 200) {
if (this.modals.newPlaylistModal.afterCreateAddTrackID !== 0) {
addOrRemoveTrackFromPlaylist({
op: "add",
op: 'add',
pid: data.id,
tracks: this.modals.newPlaylistModal.afterCreateAddTrackID,
}).then((data) => {
}).then(data => {
if (data.body.code === 200) {
this.showToast("已添加到歌单");
this.showToast('已添加到歌单');
} else {
this.showToast(data.body.message);
}
@ -87,14 +87,14 @@ export default {
});
}
this.close();
this.showToast("成功创建歌单");
this.showToast('成功创建歌单');
}
});
},
resetAfterCreateAddTrackID() {
this.updateModal({
modalName: "newPlaylistModal",
key: "AfterCreateAddTrackID",
modalName: 'newPlaylistModal',
key: 'AfterCreateAddTrackID',
value: 0,
});
},
@ -110,7 +110,7 @@ export default {
input {
margin-bottom: 12px;
}
input[type="text"] {
input[type='text'] {
width: calc(100% - 24px);
flex: 1;
background: var(--color-secondary-bg-for-transparent);
@ -125,12 +125,12 @@ export default {
background: var(--color-primary-bg-for-transparent);
opacity: 1;
}
[data-theme="light"] &:focus {
[data-theme='light'] &:focus {
color: var(--color-primary);
}
}
.checkbox {
input[type="checkbox" i] {
input[type='checkbox' i] {
margin: 3px 3px 3px 4px;
}
display: flex;

@ -1,6 +1,6 @@
<template>
<div class="mv-row" :class="{ 'without-padding': withoutPadding }">
<div class="mv" v-for="mv in mvs" :key="getID(mv)">
<div v-for="mv in mvs" :key="getID(mv)" class="mv">
<div
class="cover"
@mouseover="hoverVideoID = getID(mv)"
@ -10,8 +10,8 @@
<img :src="getUrl(mv)" />
<transition name="fade">
<div
class="shadow"
v-show="hoverVideoID === getID(mv)"
class="shadow"
:style="{ background: 'url(' + getUrl(mv) + ')' }"
></div>
</transition>
@ -28,12 +28,12 @@
<script>
export default {
name: "CoverVideo",
name: 'CoverVideo',
props: {
mvs: Array,
subtitle: {
type: String,
default: "artist",
default: 'artist',
},
withoutPadding: { type: Boolean, default: false },
},
@ -48,11 +48,11 @@ export default {
if (this.$parent.player !== undefined) {
query = { autoplay: this.$parent.player.playing };
}
this.$router.push({ path: "/mv/" + id, query });
this.$router.push({ path: '/mv/' + id, query });
},
getUrl(mv) {
let url = mv.imgurl16v9 ?? mv.cover ?? mv.coverUrl;
return url.replace(/^http:/, "https:") + "?param=464y260";
return url.replace(/^http:/, 'https:') + '?param=464y260';
},
getID(mv) {
if (mv.id !== undefined) return mv.id;
@ -63,8 +63,8 @@ export default {
if (mv.title !== undefined) return mv.title;
},
getSubtitle(mv) {
if (this.subtitle === "artist") {
let artistName = "null";
if (this.subtitle === 'artist') {
let artistName = 'null';
let artistID = 0;
if (mv.artistName !== undefined) {
artistName = mv.artistName;
@ -74,7 +74,7 @@ export default {
artistID = mv.creator[0].userId;
}
return `<a href="/#/artist/${artistID}">${artistName}</a>`;
} else if (this.subtitle === "publishTime") {
} else if (this.subtitle === 'publishTime') {
return mv.publishTime;
}
},

@ -10,11 +10,11 @@
></div>
<div
class="button max-restore codicon"
@click="windowMaxRestore"
:class="{
'codicon-chrome-restore': !isWindowMaximized,
'codicon-chrome-maximize': isWindowMaximized,
}"
@click="windowMaxRestore"
></div>
<div
class="button close codicon codicon-chrome-close"
@ -32,17 +32,17 @@
</div>
<div class="navigation-links">
<router-link to="/" :class="{ active: this.$route.name === 'home' }">{{
$t("nav.home")
$t('nav.home')
}}</router-link>
<router-link
to="/explore"
:class="{ active: this.$route.name === 'explore' }"
>{{ $t("nav.explore") }}</router-link
>{{ $t('nav.explore') }}</router-link
>
<router-link
to="/library"
:class="{ active: this.$route.name === 'library' }"
>{{ $t("nav.library") }}</router-link
>{{ $t('nav.library') }}</router-link
>
</div>
<div class="right-part">
@ -52,8 +52,8 @@
<div class="input">
<input
ref="searchInput"
:placeholder="inputFocus ? '' : $t('nav.search')"
v-model="keywords"
:placeholder="inputFocus ? '' : $t('nav.search')"
@keydown.enter="doSearch"
@focus="inputFocus = true"
@blur="inputFocus = false"
@ -68,43 +68,43 @@
<ContextMenu ref="userProfileMenu">
<div class="item" @click="toSettings">
<svg-icon icon-class="settings" />
{{ $t("library.userProfileMenu.settings") }}
{{ $t('library.userProfileMenu.settings') }}
</div>
<div class="item" @click="toLogin" v-if="!isLooseLoggedIn">
<div v-if="!isLooseLoggedIn" class="item" @click="toLogin">
<svg-icon icon-class="login" />
{{ $t("login.login") }}
{{ $t('login.login') }}
</div>
<div class="item" @click="logout" v-if="isLooseLoggedIn">
<div v-if="isLooseLoggedIn" class="item" @click="logout">
<svg-icon icon-class="logout" />
{{ $t("library.userProfileMenu.logout") }}
{{ $t('library.userProfileMenu.logout') }}
</div>
<hr />
<div class="item" @click="toGitHub">
<svg-icon icon-class="github" />
{{ $t("nav.github") }}
{{ $t('nav.github') }}
</div>
</ContextMenu>
</div>
</template>
<script>
import { mapState } from "vuex";
import { isLooseLoggedIn, doLogout } from "@/utils/auth";
import { mapState } from 'vuex';
import { isLooseLoggedIn, doLogout } from '@/utils/auth';
// import icons for win32 title bar
// icons by https://github.com/microsoft/vscode-codicons
import "vscode-codicons/dist/codicon.css";
import 'vscode-codicons/dist/codicon.css';
import ContextMenu from "@/components/ContextMenu.vue";
import ButtonIcon from "@/components/ButtonIcon.vue";
import ContextMenu from '@/components/ContextMenu.vue';
import ButtonIcon from '@/components/ButtonIcon.vue';
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;
export default {
name: "Navbar",
name: 'Navbar',
components: {
ButtonIcon,
ContextMenu,
@ -112,44 +112,44 @@ export default {
data() {
return {
inputFocus: false,
langs: ["zh-CN", "en", "tr"],
keywords: "",
langs: ['zh-CN', 'en', 'tr'],
keywords: '',
isWindowMaximized: false,
};
},
computed: {
...mapState(["settings", "data"]),
...mapState(['settings', 'data']),
isLooseLoggedIn() {
return isLooseLoggedIn();
},
avatarUrl() {
return this.data?.user?.avatarUrl && this.isLooseLoggedIn
? `${this.data?.user?.avatarUrl}?param=512y512`
: "http://s4.music.126.net/style/web2/img/default/default_avatar.jpg?param=60y60";
: 'http://s4.music.126.net/style/web2/img/default/default_avatar.jpg?param=60y60';
},
},
created() {
if (process.env.IS_ELECTRON === true) {
ipcRenderer.on("isMaximized", (event, value) => {
ipcRenderer.on('isMaximized', (event, value) => {
this.isWindowMaximized = value;
});
}
},
methods: {
go(where) {
if (where === "back") this.$router.go(-1);
if (where === 'back') this.$router.go(-1);
else this.$router.go(1);
},
doSearch() {
if (!this.keywords) return;
if (
this.$route.name === "search" &&
this.$route.name === 'search' &&
this.$route.params.keywords === this.keywords
) {
return;
}
this.$router.push({
name: "search",
name: 'search',
params: { keywords: this.keywords },
});
},
@ -157,31 +157,31 @@ export default {
this.$refs.userProfileMenu.openMenu(e);
},
logout() {
if (!confirm("确定要退出登录吗?")) return;
if (!confirm('确定要退出登录吗?')) return;
doLogout();
this.$router.push({ name: "home" });
this.$router.push({ name: 'home' });
},
toSettings() {
this.$router.push({ name: "settings" });
this.$router.push({ name: 'settings' });
},
toGitHub() {
window.open("https://github.com/qier222/YesPlayMusic");
window.open('https://github.com/qier222/YesPlayMusic');
},
toLogin() {
if (process.env.IS_ELECTRON === true) {
this.$router.push({ name: "loginAccount" });
this.$router.push({ name: 'loginAccount' });
} else {
this.$router.push({ name: "login" });
this.$router.push({ name: 'login' });
}
},
windowMinimize() {
ipcRenderer.send("minimize");
ipcRenderer.send('minimize');
},
windowMaxRestore() {
ipcRenderer.send("maximizeOrUnmaximize");
ipcRenderer.send('maximizeOrUnmaximize');
},
windowClose() {
ipcRenderer.send("close");
ipcRenderer.send('close');
},
},
};
@ -224,7 +224,7 @@ nav {
display: none;
}
[data-electron-os="win32"] {
[data-electron-os='win32'] {
nav {
padding-top: 20px;
-webkit-app-region: no-drag;
@ -244,7 +244,7 @@ nav {
.title {
padding: 8px;
font-size: 12px;
font-family: "Segoe UI", "Microsoft YaHei UI", "Microsoft YaHei",
font-family: 'Segoe UI', 'Microsoft YaHei UI', 'Microsoft YaHei',
sans-serif;
}
.controls {
@ -278,7 +278,7 @@ nav {
}
}
}
&[data-theme="dark"] .win32-titlebar {
&[data-theme='dark'] .win32-titlebar {
--hover: #191919;
--active: #333333;
}
@ -387,7 +387,7 @@ nav {
}
}
[data-theme="dark"] {
[data-theme='dark'] {
.search-box {
.active {
input,

@ -6,7 +6,7 @@
<script>
export default {
name: "SvgIcon",
name: 'SvgIcon',
props: {
iconClass: {
type: String,
@ -14,7 +14,7 @@ export default {
},
className: {
type: String,
default: "",
default: '',
},
},
computed: {
@ -23,9 +23,9 @@ export default {
},
svgClass() {
if (this.className) {
return "svg-icon " + this.className;
return 'svg-icon ' + this.className;
} else {
return "svg-icon";
return 'svg-icon';
}
},
},

@ -1,16 +1,16 @@
<template>
<transition name="fade">
<div class="toast" v-show="toast.show">{{ toast.text }}</div>
<div v-show="toast.show" class="toast">{{ toast.text }}</div>
</transition>
</template>
<script>
import { mapState } from "vuex";
import { mapState } from 'vuex';
export default {
name: "Toast",
name: 'Toast',
computed: {
...mapState(["toast"]),
...mapState(['toast']),
},
};
</script>
@ -33,7 +33,7 @@ export default {
z-index: 100;
}
[data-theme="dark"] {
[data-theme='dark'] {
.toast {
background: rgba(46, 46, 46, 0.68);
backdrop-filter: blur(16px) contrast(120%);

@ -8,12 +8,12 @@
@mouseleave="hover = false"
>
<img
:src="imgUrl"
v-if="!isAlbum"
@click="goToAlbum"
:src="imgUrl"
:class="{ hover: focus }"
@click="goToAlbum"
/>
<div class="no" v-if="isAlbum">
<div v-if="isAlbum" class="no">
<button v-show="focus && track.playable && !isPlaying" @click="playTrack">
<svg-icon
icon-class="play"
@ -34,7 +34,7 @@
<div class="container">
<div class="title">
{{ track.name }}
<span class="featured" v-if="isAlbum">
<span v-if="isAlbum" class="featured">
<ArtistsInLine
:artists="track.ar"
:exclude="this.$parent.albumObject.artist.name"
@ -44,7 +44,7 @@
><ExplicitSymbol
/></span>
</div>
<div class="artist" v-if="!isAlbum">
<div v-if="!isAlbum" class="artist">
<span
v-if="track.mark === 1318912"
class="explicit-symbol before-artist"
@ -55,11 +55,11 @@
</div>
<div></div>
</div>
<div class="album" v-if="!isTracklist && !isAlbum">
<div v-if="!isTracklist && !isAlbum" class="album">
<router-link :to="`/album/${album.id}`">{{ album.name }}</router-link>
<div></div>
</div>
<div class="actions" v-if="!isTracklist">
<div v-if="!isTracklist" class="actions">
<button @click="likeThisSong">
<svg-icon
icon-class="heart"
@ -67,10 +67,10 @@
visibility: focus && !isLiked ? 'visible' : 'hidden',
}"
></svg-icon>
<svg-icon icon-class="heart-solid" v-show="isLiked"></svg-icon>
<svg-icon v-show="isLiked" icon-class="heart-solid"></svg-icon>
</button>
</div>
<div class="time" v-if="!isTracklist">
<div v-if="!isTracklist" class="time">
{{ track.dt | formatTime }}
</div>
</div>

@ -1,24 +1,24 @@
const { Menu } = require("electron");
const { Menu } = require('electron');
export function createDockMenu(win) {
return Menu.buildFromTemplate([
{
label: "Play",
label: 'Play',
click() {
win.webContents.send("play");
win.webContents.send('play');
},
},
{ type: "separator" },
{ type: 'separator' },
{
label: "Next",
label: 'Next',
click() {
win.webContents.send("next");
win.webContents.send('next');
},
},
{
label: "Previous",
label: 'Previous',
click() {
win.webContents.send("previous");
win.webContents.send('previous');
},
},
]);

@ -1,22 +1,22 @@
const { globalShortcut } = require("electron");
const { globalShortcut } = require('electron');
export function registerGlobalShortcut(win) {
globalShortcut.register("Alt+CommandOrControl+P", () => {
win.webContents.send("play");
globalShortcut.register('Alt+CommandOrControl+P', () => {
win.webContents.send('play');
});
globalShortcut.register("Alt+CommandOrControl+Right", () => {
win.webContents.send("next");
globalShortcut.register('Alt+CommandOrControl+Right', () => {
win.webContents.send('next');
});
globalShortcut.register("Alt+CommandOrControl+Left", () => {
win.webContents.send("previous");
globalShortcut.register('Alt+CommandOrControl+Left', () => {
win.webContents.send('previous');
});
globalShortcut.register("Alt+CommandOrControl+Up", () => {
win.webContents.send("increaseVolume");
globalShortcut.register('Alt+CommandOrControl+Up', () => {
win.webContents.send('increaseVolume');
});
globalShortcut.register("Alt+CommandOrControl+Down", () => {
win.webContents.send("decreaseVolume");
globalShortcut.register('Alt+CommandOrControl+Down', () => {
win.webContents.send('decreaseVolume');
});
globalShortcut.register("Alt+CommandOrControl+L", () => {
win.webContents.send("like");
globalShortcut.register('Alt+CommandOrControl+L', () => {
win.webContents.send('like');
});
}

@ -1,48 +1,48 @@
import { app, dialog, globalShortcut, ipcMain } from "electron";
import match from "@njzy/unblockneteasemusic";
import { registerGlobalShortcut } from "@/electron/globalShortcut";
import { app, dialog, globalShortcut, ipcMain } from 'electron';
import match from '@njzy/unblockneteasemusic';
import { registerGlobalShortcut } from '@/electron/globalShortcut';
const client = require("discord-rich-presence")("818936529484906596");
const client = require('discord-rich-presence')('818936529484906596');
export function initIpcMain(win, store) {
ipcMain.on("unblock-music", (event, track) => {
ipcMain.on('unblock-music', (event, track) => {
// 兼容 unblockneteasemusic 所使用的 api 字段
track.alias = track.alia || [];
track.duration = track.dt || 0;
track.album = track.al || [];
track.artists = track.ar || [];
const matchPromise = match(track.id, ["qq", "kuwo", "migu"], track);
const matchPromise = match(track.id, ['qq', 'kuwo', 'migu'], track);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject("timeout");
reject('timeout');
}, 3000);
});
Promise.race([matchPromise, timeoutPromise])
.then((res) => {
.then(res => {
event.returnValue = res;
})
.catch((err) => {
console.log("unblock music error: ", err);
.catch(err => {
console.log('unblock music error: ', err);
event.returnValue = null;
});
});
ipcMain.on("close", (e) => {
if (process.platform == "darwin") {
ipcMain.on('close', e => {
if (process.platform == 'darwin') {
win.hide();
}
e.preventDefault(); //阻止默认行为
dialog
.showMessageBox({
type: "info",
title: "Information",
type: 'info',
title: 'Information',
cancelId: 2,
defaultId: 0,
message: "确定要关闭吗?",
buttons: ["最小化", "直接退出"],
message: '确定要关闭吗?',
buttons: ['最小化', '直接退出'],
})
.then((result) => {
.then(result => {
if (result.response == 0) {
e.preventDefault(); //阻止默认行为
win.minimize(); //调用 最小化实例方法
@ -52,25 +52,25 @@ export function initIpcMain(win, store) {
app.exit(); //exit()直接关闭客户端不会执行quit();
}
})
.catch((err) => {
.catch(err => {
console.log(err);
});
});
ipcMain.on("minimize", () => {
ipcMain.on('minimize', () => {
win.minimize();
});
ipcMain.on("maximizeOrUnmaximize", () => {
ipcMain.on('maximizeOrUnmaximize', () => {
const isMaximized = win.isMaximized();
isMaximized ? win.unmaximize() : win.maximize();
win.webContents.send("isMaximized", isMaximized);
win.webContents.send('isMaximized', isMaximized);
});
ipcMain.on("settings", (event, options) => {
store.set("settings", options);
ipcMain.on('settings', (event, options) => {
store.set('settings', options);
const isRegisterShortcut = globalShortcut.isRegistered(
"Alt+CommandOrControl+P"
'Alt+CommandOrControl+P'
);
if (options.enableGlobalShortcut) {
!isRegisterShortcut && registerGlobalShortcut(win);
@ -79,27 +79,27 @@ export function initIpcMain(win, store) {
}
});
ipcMain.on("playDiscordPresence", (event, track) => {
ipcMain.on('playDiscordPresence', (event, track) => {
client.updatePresence({
details: track.name + " - " + track.ar.map((ar) => ar.name).join(","),
details: track.name + ' - ' + track.ar.map(ar => ar.name).join(','),
state: track.al.name,
endTimestamp: Date.now() + track.dt,
largeImageKey: "logo",
largeImageText: "Listening " + track.name,
smallImageKey: "play",
smallImageText: "Playing",
largeImageKey: 'logo',
largeImageText: 'Listening ' + track.name,
smallImageKey: 'play',
smallImageText: 'Playing',
instance: true,
});
});
ipcMain.on("pauseDiscordPresence", (event, track) => {
ipcMain.on('pauseDiscordPresence', (event, track) => {
client.updatePresence({
details: track.name + " - " + track.ar.map((ar) => ar.name).join(","),
details: track.name + ' - ' + track.ar.map(ar => ar.name).join(','),
state: track.al.name,
largeImageKey: "logo",
largeImageText: "YesPlayMusic",
smallImageKey: "pause",
smallImageText: "Pause",
largeImageKey: 'logo',
largeImageText: 'YesPlayMusic',
smallImageKey: 'pause',
smallImageText: 'Pause',
instance: true,
});
});

@ -1,8 +1,8 @@
const { app, Menu } = require("electron");
const { app, Menu } = require('electron');
// import { autoUpdater } from "electron-updater"
// const version = app.getVersion();
const isMac = process.platform === "darwin";
const isMac = process.platform === 'darwin';
export function createMenu(win) {
let menu = null;
@ -12,142 +12,142 @@ export function createMenu(win) {
{
label: app.name,
submenu: [
{ role: "about" },
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ type: "separator" },
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ type: 'separator' },
{
label: "Preferences...",
accelerator: (() => (isMac ? "CmdOrCtrl+," : "Ctrl+,"))(),
label: 'Preferences...',
accelerator: (() => (isMac ? 'CmdOrCtrl+,' : 'Ctrl+,'))(),
click: () => {
win.webContents.send("changeRouteTo", "/settings");
win.webContents.send('changeRouteTo', '/settings');
},
role: "preferences",
role: 'preferences',
},
{ type: "separator" },
{ role: "hide" },
{ role: "hideothers" },
{ role: "unhide" },
{ type: "separator" },
{ role: "quit" },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' },
],
},
]
: []),
{
label: "Edit",
label: 'Edit',
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac
? [
{ role: "delete" },
{ role: "selectAll" },
{ type: "separator" },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: "Speech",
submenu: [{ role: "startspeaking" }, { role: "stopspeaking" }],
label: 'Speech',
submenu: [{ role: 'startspeaking' }, { role: 'stopspeaking' }],
},
]
: [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]),
: [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }]),
{
label: "Search",
accelerator: "CmdOrCtrl+F",
label: 'Search',
accelerator: 'CmdOrCtrl+F',
click: () => {
win.webContents.send("search");
win.webContents.send('search');
},
},
],
},
{
label: "Controls",
label: 'Controls',
submenu: [
{
label: "Play",
label: 'Play',
click: () => {
win.webContents.send("play");
win.webContents.send('play');
},
},
{
label: "Next",
accelerator: "CmdOrCtrl+Right",
label: 'Next',
accelerator: 'CmdOrCtrl+Right',
click: () => {
win.webContents.send("next");
win.webContents.send('next');
},
},
{
label: "Previous",
accelerator: "CmdOrCtrl+Left",
label: 'Previous',
accelerator: 'CmdOrCtrl+Left',
click: () => {
win.webContents.send("previous");
win.webContents.send('previous');
},
},
{
label: "Increase Volume",
accelerator: "CmdOrCtrl+Up",
label: 'Increase Volume',
accelerator: 'CmdOrCtrl+Up',
click: () => {
win.webContents.send("increaseVolume");
win.webContents.send('increaseVolume');
},
},
{
label: "Decrease Volume",
accelerator: "CmdOrCtrl+Down",
label: 'Decrease Volume',
accelerator: 'CmdOrCtrl+Down',
click: () => {
win.webContents.send("decreaseVolume");
win.webContents.send('decreaseVolume');
},
},
{
label: "Like",
accelerator: "CmdOrCtrl+L",
label: 'Like',
accelerator: 'CmdOrCtrl+L',
click: () => {
win.webContents.send("like");
win.webContents.send('like');
},
},
{
label: "Repeat",
accelerator: "Alt+R",
label: 'Repeat',
accelerator: 'Alt+R',
click: () => {
win.webContents.send("repeat");
win.webContents.send('repeat');
},
},
{
label: "Shuffle",
accelerator: "Alt+S",
label: 'Shuffle',
accelerator: 'Alt+S',
click: () => {
win.webContents.send("shuffle");
win.webContents.send('shuffle');
},
},
],
},
{
label: "Window",
label: 'Window',
submenu: [
{ role: "close" },
{ role: "minimize" },
{ role: "zoom" },
{ role: "reload" },
{ role: "forcereload" },
{ role: "toggledevtools" },
{ type: "separator" },
{ role: "togglefullscreen" },
{ role: 'close' },
{ role: 'minimize' },
{ role: 'zoom' },
{ role: 'reload' },
{ role: 'forcereload' },
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'togglefullscreen' },
...(isMac
? [
{ type: "separator" },
{ role: "front" },
{ type: "separator" },
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{
role: "window",
id: "window",
label: "YesPlayMusic",
type: "checkbox",
role: 'window',
id: 'window',
label: 'YesPlayMusic',
type: 'checkbox',
checked: true,
click: () => {
const current = menu.getMenuItemById("window");
const current = menu.getMenuItemById('window');
if (current.checked === false) {
win.hide();
} else {
@ -156,29 +156,29 @@ export function createMenu(win) {
},
},
]
: [{ role: "close" }]),
: [{ role: 'close' }]),
],
},
{
label: "Help",
label: 'Help',
submenu: [
{
label: "GitHub",
label: 'GitHub',
click: async () => {
const { shell } = require("electron");
await shell.openExternal("https://github.com/qier222/YesPlayMusic");
const { shell } = require('electron');
await shell.openExternal('https://github.com/qier222/YesPlayMusic');
},
},
{
label: "Electron",
label: 'Electron',
click: async () => {
const { shell } = require("electron");
await shell.openExternal("https://electronjs.org");
const { shell } = require('electron');
await shell.openExternal('https://electronjs.org');
},
},
{
label: "开发者工具",
accelerator: "F12",
label: '开发者工具',
accelerator: 'F12',
click: () => {
win.webContents.openDevTools();
},

@ -1,6 +1,6 @@
const { TouchBar, nativeImage, ipcMain } = require("electron");
const { TouchBar, nativeImage, ipcMain } = require('electron');
const { TouchBarButton, TouchBarSpacer } = TouchBar;
const path = require("path");
const path = require('path');
export function createTouchBar(window) {
const renderer = window.webContents;
@ -11,72 +11,72 @@ export function createTouchBar(window) {
function getNativeIcon(name) {
return nativeImage.createFromPath(
// eslint-disable-next-line no-undef
path.join(__static, "img/touchbar/", name)
path.join(__static, 'img/touchbar/', name)
);
}
const previousPage = new TouchBarButton({
click: () => {
renderer.send("routerGo", "back");
renderer.send('routerGo', 'back');
},
icon: getNativeIcon("page_prev.png"),
icon: getNativeIcon('page_prev.png'),
});
const nextPage = new TouchBarButton({
click: () => {
renderer.send("routerGo", "forward");
renderer.send('routerGo', 'forward');
},
icon: getNativeIcon("page_next.png"),
icon: getNativeIcon('page_next.png'),
});
const searchButton = new TouchBarButton({
click: () => {
renderer.send("search");
renderer.send('search');
},
icon: getNativeIcon("search.png"),
icon: getNativeIcon('search.png'),
});
const playButton = new TouchBarButton({
click: () => {
renderer.send("play");
renderer.send('play');
},
icon: getNativeIcon("play.png"),
icon: getNativeIcon('play.png'),
});
const previousTrackButton = new TouchBarButton({
click: () => {
renderer.send("previous");
renderer.send('previous');
},
icon: getNativeIcon("backward.png"),
icon: getNativeIcon('backward.png'),
});
const nextTrackButton = new TouchBarButton({
click: () => {
renderer.send("next");
renderer.send('next');
},
icon: getNativeIcon("forward.png"),
icon: getNativeIcon('forward.png'),
});
const likeButton = new TouchBarButton({
click: () => {
renderer.send("like");
renderer.send('like');
},
icon: getNativeIcon("like.png"),
icon: getNativeIcon('like.png'),
});
const nextUpButton = new TouchBarButton({
click: () => {
renderer.send("nextUp");
renderer.send('nextUp');
},
icon: getNativeIcon("next_up.png"),
icon: getNativeIcon('next_up.png'),
});
ipcMain.on("player", (e, { playing, likedCurrentTrack }) => {
ipcMain.on('player', (e, { playing, likedCurrentTrack }) => {
playButton.icon =
playing === true ? getNativeIcon("pause.png") : getNativeIcon("play.png");
playing === true ? getNativeIcon('pause.png') : getNativeIcon('play.png');
likeButton.icon = likedCurrentTrack
? getNativeIcon("like_fill.png")
: getNativeIcon("like.png");
? getNativeIcon('like_fill.png')
: getNativeIcon('like.png');
});
const touchBar = new TouchBar({
@ -84,11 +84,11 @@ export function createTouchBar(window) {
previousPage,
nextPage,
searchButton,
new TouchBarSpacer({ size: "flexible" }),
new TouchBarSpacer({ size: 'flexible' }),
previousTrackButton,
playButton,
nextTrackButton,
new TouchBarSpacer({ size: "flexible" }),
new TouchBarSpacer({ size: 'flexible' }),
likeButton,
nextUpButton,
],

@ -1,79 +1,79 @@
/* global __static */
import path from "path";
import { app, nativeImage, Tray, Menu } from "electron";
import path from 'path';
import { app, nativeImage, Tray, Menu } from 'electron';
export function createTray(win) {
let icon = nativeImage
.createFromPath(path.join(__static, "img/icons/menu@88.png"))
.createFromPath(path.join(__static, 'img/icons/menu@88.png'))
.resize({
height: 20,
width: 20,
});
let tray = new Tray(icon);
tray.setToolTip("YesPlayMusic");
tray.setToolTip('YesPlayMusic');
tray.on("click", () => {
tray.on('click', () => {
win.show();
});
tray.on("right-click", () => {
tray.on('right-click', () => {
const contextMenu = Menu.buildFromTemplate([
{
label: "播放/暂停",
label: '播放/暂停',
icon: nativeImage.createFromPath(
path.join(__static, "img/icons/play.png")
path.join(__static, 'img/icons/play.png')
),
click: () => {
win.webContents.send("play");
win.webContents.send('play');
},
},
{
label: "上一首",
label: '上一首',
icon: nativeImage.createFromPath(
path.join(__static, "img/icons/left.png")
path.join(__static, 'img/icons/left.png')
),
accelerator: "CmdOrCtrl+Left",
accelerator: 'CmdOrCtrl+Left',
click: () => {
win.webContents.send("previous");
win.webContents.send('previous');
},
},
{
label: "下一首",
label: '下一首',
icon: nativeImage.createFromPath(
path.join(__static, "img/icons/right.png")
path.join(__static, 'img/icons/right.png')
),
accelerator: "CmdOrCtrl+Right",
accelerator: 'CmdOrCtrl+Right',
click: () => {
win.webContents.send("next");
win.webContents.send('next');
},
},
{
label: "循环播放",
label: '循环播放',
icon: nativeImage.createFromPath(
path.join(__static, "img/icons/repeat.png")
path.join(__static, 'img/icons/repeat.png')
),
accelerator: "Alt+R",
accelerator: 'Alt+R',
click: () => {
win.webContents.send("repeat");
win.webContents.send('repeat');
},
},
{
label: "加入喜欢",
label: '加入喜欢',
icon: nativeImage.createFromPath(
path.join(__static, "img/icons/like.png")
path.join(__static, 'img/icons/like.png')
),
accelerator: "CmdOrCtrl+L",
accelerator: 'CmdOrCtrl+L',
click: () => {
win.webContents.send("like");
win.webContents.send('like');
},
},
{
label: "退出",
label: '退出',
icon: nativeImage.createFromPath(
path.join(__static, "img/icons/exit.png")
path.join(__static, 'img/icons/exit.png')
),
accelerator: "CmdOrCtrl+W",
accelerator: 'CmdOrCtrl+W',
click: () => {
app.exit();
},

@ -1,10 +1,10 @@
import Vue from "vue";
import VueI18n from "vue-i18n";
import store from "@/store";
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import store from '@/store';
import en from "./lang/en.js";
import zhCN from "./lang/zh-CN.js";
import tr from "./lang/tr.js";
import en from './lang/en.js';
import zhCN from './lang/zh-CN.js';
import tr from './lang/tr.js';
Vue.use(VueI18n);
@ -12,7 +12,7 @@ const i18n = new VueI18n({
locale: store.state.settings.lang,
messages: {
en,
"zh-CN": zhCN,
'zh-CN': zhCN,
tr,
},
silentTranslationWarn: true,

@ -1,81 +1,81 @@
export default {
common: {
play: "PLAY",
songs: "Songs",
play: 'PLAY',
songs: 'Songs',
},
nav: {
home: "Home",
explore: "Explore",
library: "Library",
search: "Search",
github: "GitHub Repo",
home: 'Home',
explore: 'Explore',
library: 'Library',
search: 'Search',
github: 'GitHub Repo',
},
footer: {
settings: "Settings",
settings: 'Settings',
},
home: {
recommendPlaylist: "Recommended Playlists",
recommendArtist: "Recommended Artists",
newAlbum: "Latest Albums",
seeMore: "SEE MORE",
charts: "Charts",
recommendPlaylist: 'Recommended Playlists',
recommendArtist: 'Recommended Artists',
newAlbum: 'Latest Albums',
seeMore: 'SEE MORE',
charts: 'Charts',
},
library: {
sLibrary: "'s Library",
likedSongs: "Liked Songs",
likedSongs: 'Liked Songs',
sLikedSongs: "'s Liked Songs",
playlists: "Playlists",
albums: "Albums",
artists: "Artists",
mvs: "MVs",
newPlayList: "New Playlist",
playlists: 'Playlists',
albums: 'Albums',
artists: 'Artists',
mvs: 'MVs',
newPlayList: 'New Playlist',
userProfileMenu: {
settings: "Settings",
logout: "Logout",
settings: 'Settings',
logout: 'Logout',
},
},
explore: {
explore: "Explore",
loadMore: "Load More",
explore: 'Explore',
loadMore: 'Load More',
},
artist: {
latestRelease: "Latest Releases",
popularSongs: "Popular Songs",
showMore: "SHOW MORE",
showLess: "SHOW LESS",
EPsSingles: "EPs & Singles",
albums: "Albums",
withAlbums: "Albums",
artist: "Artist",
videos: "Music Videos",
following: "Following",
follow: "Follow",
latestRelease: 'Latest Releases',
popularSongs: 'Popular Songs',
showMore: 'SHOW MORE',
showLess: 'SHOW LESS',
EPsSingles: 'EPs & Singles',
albums: 'Albums',
withAlbums: 'Albums',
artist: 'Artist',
videos: 'Music Videos',
following: 'Following',
follow: 'Follow',
},
album: {
released: "Released",
released: 'Released',
},
playlist: {
playlist: "Playlists",
updatedAt: "Updated at",
search: "Search in playlist",
playlist: 'Playlists',
updatedAt: 'Updated at',
search: 'Search in playlist',
},
login: {
accessToAll: "Access to all data",
loginText: "Login to Netease",
search: "Search account",
readonly: "Only access to public data",
usernameLogin: "Username Login",
searchHolder: "Your account username",
accessToAll: 'Access to all data',
loginText: 'Login to Netease',
search: 'Search account',
readonly: 'Only access to public data',
usernameLogin: 'Username Login',
searchHolder: 'Your account username',
enterTip: "Press 'enter' to search",
choose: "Choose your account",
confirm: "Confirm",
countryCode: "Country code",
phone: "Phone",
email: "Email address",
password: "Password",
login: "Login",
loginWithEmail: "Login with Email",
loginWithPhone: "Login with Phone",
choose: 'Choose your account',
confirm: 'Confirm',
countryCode: 'Country code',
phone: 'Phone',
email: 'Email address',
password: 'Password',
login: 'Login',
loginWithEmail: 'Login with Email',
loginWithPhone: 'Login with Phone',
notice: `YesPlayMusic promises not to save any of your account information to the cloud.<br />
Your password will be MD5 encrypted locally and then transmitted to NetEase Music API.<br />
YesPlayMusic is not the official website of NetEase Music, please consider carefully before entering account information. You can also go to <a href="https://github.com/qier222/YesPlayMusic">YesPlayMusic's GitHub repository</a> to build and use the self-hosted NetEase Music API.`,
@ -83,85 +83,85 @@ export default {
YesPlayMusic promises not to save any of your account information to the cloud.<br />`,
},
mv: {
moreVideo: "More Videos",
moreVideo: 'More Videos',
},
next: {
nowPlaying: "Now Playing",
nextUp: "Next Up",
nowPlaying: 'Now Playing',
nextUp: 'Next Up',
},
player: {
like: "Like",
previous: "Previous Song",
next: "Next Song",
repeat: "Repeat",
repeatTrack: "Repeat Track",
shuffle: "Shuffle",
play: "Play",
pause: "Pause",
mute: "Mute",
nextUp: "Next Up",
like: 'Like',
previous: 'Previous Song',
next: 'Next Song',
repeat: 'Repeat',
repeatTrack: 'Repeat Track',
shuffle: 'Shuffle',
play: 'Play',
pause: 'Pause',
mute: 'Mute',
nextUp: 'Next Up',
},
modal: {
close: "Close",
close: 'Close',
},
search: {
artist: "Artists",
album: "Albums",
song: "Songs",
mv: "Music Videos",
playlist: "Playlists",
noResult: "No Results",
searchFor: "Search for",
artist: 'Artists',
album: 'Albums',
song: 'Songs',
mv: 'Music Videos',
playlist: 'Playlists',
noResult: 'No Results',
searchFor: 'Search for',
},
settings: {
settings: "Settings",
logout: "LOGOUT",
language: "Languages",
settings: 'Settings',
logout: 'LOGOUT',
language: 'Languages',
musicQuality: {
text: "Music Quality",
low: "Low",
medium: "Medium",
high: "High",
lossless: "Lossless",
text: 'Music Quality',
low: 'Low',
medium: 'Medium',
high: 'High',
lossless: 'Lossless',
},
cacheLimit: {
text: "Songs Cache limit",
none: "None",
text: 'Songs Cache limit',
none: 'None',
},
lyricFontSize: {
text: "Lyric Font Size",
small: "Small",
medium: "Medium",
large: "Large(Default)",
xlarge: "X-Large",
text: 'Lyric Font Size',
small: 'Small',
medium: 'Medium',
large: 'Large(Default)',
xlarge: 'X-Large',
},
deviceSelector: "Audio Output Device",
permissionRequired: "Microphone Permission Required",
deviceSelector: 'Audio Output Device',
permissionRequired: 'Microphone Permission Required',
appearance: {
text: "Appearance",
auto: "Auto",
light: "Light",
dark: "Dark",
text: 'Appearance',
auto: 'Auto',
light: 'Light',
dark: 'Dark',
},
automaticallyCacheSongs: "Automatically cache songs",
clearSongsCache: "Clear Songs Cache",
cacheCount: "Cached {song} songs ({size})",
showLyricsTranslation: "Show lyrics translation",
showLyricsDynamicBackground: "Show lyrics dynamic background",
minimizeToTray: "Minimize to tray",
showPlaylistsByAppleMusic: "Show playlists by Apple Music",
enableDiscordRichPresence: "Enable Discord Rich Presence",
enableGlobalShortcut: "Enable Global Shortcut",
showLibraryDefault: "Show library default",
automaticallyCacheSongs: 'Automatically cache songs',
clearSongsCache: 'Clear Songs Cache',
cacheCount: 'Cached {song} songs ({size})',
showLyricsTranslation: 'Show lyrics translation',
showLyricsDynamicBackground: 'Show lyrics dynamic background',
minimizeToTray: 'Minimize to tray',
showPlaylistsByAppleMusic: 'Show playlists by Apple Music',
enableDiscordRichPresence: 'Enable Discord Rich Presence',
enableGlobalShortcut: 'Enable Global Shortcut',
showLibraryDefault: 'Show library default',
},
contextMenu: {
play: "Play",
playNext: "Play Next",
saveToMyLikedSongs: "Save to my Liked Songs",
removeFromMyLikedSongs: "Remove from my Liked Songs",
play: 'Play',
playNext: 'Play Next',
saveToMyLikedSongs: 'Save to my Liked Songs',
removeFromMyLikedSongs: 'Remove from my Liked Songs',
},
toast: {
savedToMyLikedSongs: "Saved to my Liked Songs",
removedFromMyLikedSongs: "Removed from my Liked Songs",
savedToMyLikedSongs: 'Saved to my Liked Songs',
removedFromMyLikedSongs: 'Removed from my Liked Songs',
},
};

@ -1,81 +1,81 @@
export default {
common: {
play: "OYNAT",
songs: "Müzikler",
play: 'OYNAT',
songs: 'Müzikler',
},
nav: {
home: "Anasayfa",
explore: "Keşfet",
library: "Kitaplık",
search: "Ara",
github: "GitHub Repo",
home: 'Anasayfa',
explore: 'Keşfet',
library: 'Kitaplık',
search: 'Ara',
github: 'GitHub Repo',
},
footer: {
settings: "Ayarlar",
settings: 'Ayarlar',
},
home: {
recommendPlaylist: "Önerilen Çalma Listeier",
recommendArtist: "Önerilen Sanatçılar",
newAlbum: "Son Çıkan Albümler",
seeMore: "DAHA FAZLASI",
charts: "Listeler",
recommendPlaylist: 'Önerilen Çalma Listeier',
recommendArtist: 'Önerilen Sanatçılar',
newAlbum: 'Son Çıkan Albümler',
seeMore: 'DAHA FAZLASI',
charts: 'Listeler',
},
library: {
sLibrary: "'in Kütüphanesi",
likedSongs: "Beğenilen Müzikler",
likedSongs: 'Beğenilen Müzikler',
sLikedSongs: "'in Beğendiği Müzikler",
playlists: "Çalma Listeleri",
albums: "Albümler",
artists: "Sanatçılar",
mvs: "MVs",
newPlayList: "Yeni Çalma Listesi",
playlists: 'Çalma Listeleri',
albums: 'Albümler',
artists: 'Sanatçılar',
mvs: 'MVs',
newPlayList: 'Yeni Çalma Listesi',
userProfileMenu: {
settings: "Ayarlar",
logout: ıkış Yap",
settings: 'Ayarlar',
logout: ıkış Yap',
},
},
explore: {
explore: "Keşfet",
loadMore: "Daha Fazlası",
explore: 'Keşfet',
loadMore: 'Daha Fazlası',
},
artist: {
latestRelease: "Son Çıkanlar",
popularSongs: "Popüler Müzikler",
showMore: "Daha Fazlası",
showLess: "Daha Azı",
EPsSingles: "EPs & Singles",
albums: "Albümler",
withAlbums: "Albümler",
artist: "Sanatçı",
videos: "Müzik Videoları",
following: "Takip Ediyor",
follow: "Takip Et",
latestRelease: 'Son Çıkanlar',
popularSongs: 'Popüler Müzikler',
showMore: 'Daha Fazlası',
showLess: 'Daha Azı',
EPsSingles: 'EPs & Singles',
albums: 'Albümler',
withAlbums: 'Albümler',
artist: 'Sanatçı',
videos: 'Müzik Videoları',
following: 'Takip Ediyor',
follow: 'Takip Et',
},
album: {
released: "Yayınlandı",
released: 'Yayınlandı',
},
playlist: {
playlist: "Çalma Listeleri",
updatedAt: "Tarihinde Güncellendş",
search: "Çalma Listesinde Ara",
playlist: 'Çalma Listeleri',
updatedAt: 'Tarihinde Güncellendş',
search: 'Çalma Listesinde Ara',
},
login: {
accessToAll: "Tüm verilere eriş",
accessToAll: 'Tüm verilere eriş',
loginText: "Netease'e giriş yap",
search: "Hesap ara",
readonly: "Sadece halka açık verilere erişir",
usernameLogin: "Kullanıcı adı giriş",
searchHolder: "Hesabının kullanıcı adı",
search: 'Hesap ara',
readonly: 'Sadece halka açık verilere erişir',
usernameLogin: 'Kullanıcı adı giriş',
searchHolder: 'Hesabının kullanıcı adı',
enterTip: "Aramak için 'enter'e basınız",
choose: "Hesabını seç",
confirm: "Onayla",
countryCode: "Ülke kodu",
phone: "Telefon",
email: "Email adresi",
password: "Şifre",
login: "Giriş Yap",
loginWithEmail: "Email ile giriş yap",
loginWithPhone: "Phone ile giriş yap",
choose: 'Hesabını seç',
confirm: 'Onayla',
countryCode: 'Ülke kodu',
phone: 'Telefon',
email: 'Email adresi',
password: 'Şifre',
login: 'Giriş Yap',
loginWithEmail: 'Email ile giriş yap',
loginWithPhone: 'Phone ile giriş yap',
notice: `YesPlayMusic hesabınızın hiçbir bilgisini kaydetmeyeceğine dair söz veriyor<br />
Şifren MD5 şifreleme ile yerel olarak şifrelenir ve daha sonra NetEase Müzik API'sine gönderilir<br />
YesPlayMusic, NetEase Music'in resmi websitesi değildir, lütfen hesap bilgilerinizi girmeden önce dikkatlice düşününüz. Aynı zamanda, Kendi NetEase Musix API'nızı host etmek için <a href="https://github.com/qier222/YesPlayMusic">YesPlayMusic'in GitHub Repo'suna</a> gidebilirsiniz.`,
@ -83,84 +83,84 @@ export default {
Şifren MD5 şifreleme ile yerel olarak şifrelenir ve daha sonra NetEase Müzik API'sine gönderilir<br />`,
},
mv: {
moreVideo: "Daha Fazla Video",
moreVideo: 'Daha Fazla Video',
},
next: {
nowPlaying: "Şuan çalıyor",
nextUp: "Sıradaki",
nowPlaying: 'Şuan çalıyor',
nextUp: 'Sıradaki',
},
player: {
like: "Beğen",
previous: "Önceki Müzik",
next: "Sonraki Müzik",
repeat: "Tekrarla",
repeatTrack: "Parçayı Tekrarla",
shuffle: "Karıştır",
play: "Oynat",
pause: "Durdur",
mute: "Sesi kapat",
nextUp: "Sıradaki",
like: 'Beğen',
previous: 'Önceki Müzik',
next: 'Sonraki Müzik',
repeat: 'Tekrarla',
repeatTrack: 'Parçayı Tekrarla',
shuffle: 'Karıştır',
play: 'Oynat',
pause: 'Durdur',
mute: 'Sesi kapat',
nextUp: 'Sıradaki',
},
modal: {
close: "Kapat",
close: 'Kapat',
},
search: {
artist: "Sanatçılar",
album: "Albümler",
song: "Müzikler",
mv: "Müzik Videoları",
playlist: "Çalma Listeleri",
noResult: "Sonuç Bulunamadı",
searchFor: "Search for",
artist: 'Sanatçılar',
album: 'Albümler',
song: 'Müzikler',
mv: 'Müzik Videoları',
playlist: 'Çalma Listeleri',
noResult: 'Sonuç Bulunamadı',
searchFor: 'Search for',
},
settings: {
settings: "Ayarlar",
logout: "ÇIKIŞ YAP",
language: "Diller",
settings: 'Ayarlar',
logout: 'ÇIKIŞ YAP',
language: 'Diller',
musicQuality: {
text: "Müzik Kalitesi",
low: "Düşük",
medium: "Orta",
high: "Yüksek",
lossless: "Kaliteli",
text: 'Müzik Kalitesi',
low: 'Düşük',
medium: 'Orta',
high: 'Yüksek',
lossless: 'Kaliteli',
},
cacheLimit: {
text: "Şarkılar Önbellek sınırı",
none: "Yok",
text: 'Şarkılar Önbellek sınırı',
none: 'Yok',
},
lyricFontSize: {
text: "Şarkı Sözleri Yazı Boyutu",
small: "Küçük",
medium: "Orta",
large: "Büyük(Varsayılan)",
xlarge: "Çok-Büyük",
text: 'Şarkı Sözleri Yazı Boyutu',
small: 'Küçük',
medium: 'Orta',
large: 'Büyük(Varsayılan)',
xlarge: 'Çok-Büyük',
},
deviceSelector: "Ses Çıkış Cihazı",
permissionRequired: "Mikrofon izni gerekiyor",
deviceSelector: 'Ses Çıkış Cihazı',
permissionRequired: 'Mikrofon izni gerekiyor',
appearance: {
text: "Görünüş",
auto: "Otomatik",
light: "Aydınlık",
dark: "Karanlık",
text: 'Görünüş',
auto: 'Otomatik',
light: 'Aydınlık',
dark: 'Karanlık',
},
automaticallyCacheSongs: "Müzikleri otomatik çerezle",
clearSongsCache: "Müzik çerezlerini temizle",
cacheCount: "Çerezlenen {song} Müzikler ({size})",
showLyricsTranslation: "Müzik sözlerinin çevirilerini göster",
showLyricsDynamicBackground: "Dinamik arkaplanda müzik sözlerini gsöter",
minimizeToTray: "Küçült",
automaticallyCacheSongs: 'Müzikleri otomatik çerezle',
clearSongsCache: 'Müzik çerezlerini temizle',
cacheCount: 'Çerezlenen {song} Müzikler ({size})',
showLyricsTranslation: 'Müzik sözlerinin çevirilerini göster',
showLyricsDynamicBackground: 'Dinamik arkaplanda müzik sözlerini gsöter',
minimizeToTray: 'Küçült',
showPlaylistsByAppleMusic: "Apple Music'in Çalma Listelerini Göster",
enableDiscordRichPresence: "Discord gösterimini aktifleştir",
showLibraryDefault: "Kitaplık Varsayılanını göster",
enableDiscordRichPresence: 'Discord gösterimini aktifleştir',
showLibraryDefault: 'Kitaplık Varsayılanını göster',
},
contextMenu: {
play: "Oynat",
playNext: "Sonrakini Oynat",
saveToMyLikedSongs: "Beğendiğim Müziklere Kaydet",
removeFromMyLikedMüzikler: "Beğendiğim Müziklerden Kaldır",
play: 'Oynat',
playNext: 'Sonrakini Oynat',
saveToMyLikedSongs: 'Beğendiğim Müziklere Kaydet',
removeFromMyLikedMüzikler: 'Beğendiğim Müziklerden Kaldır',
},
toast: {
savedToMyLikedSongs: "Beğendiğim Müziklere Kaydet",
removedFromMyLikedSongs: "Beğendiğim Müziklerden Kaldır",
savedToMyLikedSongs: 'Beğendiğim Müziklere Kaydet',
removedFromMyLikedSongs: 'Beğendiğim Müziklerden Kaldır',
},
};

@ -1,78 +1,78 @@
export default {
common: {
play: "播放",
songs: "首歌",
play: '播放',
songs: '首歌',
},
nav: {
home: "首页",
explore: "发现",
library: "音乐库",
search: "搜索",
github: "GitHub 仓库",
home: '首页',
explore: '发现',
library: '音乐库',
search: '搜索',
github: 'GitHub 仓库',
},
home: {
recommendPlaylist: "推荐歌单",
recommendArtist: "推荐艺人",
newAlbum: "新专速递",
seeMore: "查看全部",
charts: "排行榜",
recommendPlaylist: '推荐歌单',
recommendArtist: '推荐艺人',
newAlbum: '新专速递',
seeMore: '查看全部',
charts: '排行榜',
},
library: {
sLibrary: "的音乐库",
likedSongs: "我喜欢的音乐",
sLikedSongs: "喜欢的音乐",
playlists: "歌单",
albums: "专辑",
artists: "艺人",
mvs: "MV",
newPlayList: "新建歌单",
sLibrary: '的音乐库',
likedSongs: '我喜欢的音乐',
sLikedSongs: '喜欢的音乐',
playlists: '歌单',
albums: '专辑',
artists: '艺人',
mvs: 'MV',
newPlayList: '新建歌单',
userProfileMenu: {
settings: "设置",
logout: "登出",
settings: '设置',
logout: '登出',
},
},
explore: {
explore: "发现",
loadMore: "加载更多",
explore: '发现',
loadMore: '加载更多',
},
artist: {
latestRelease: "最新发布",
popularSongs: "热门歌曲",
showMore: "显示更多",
showLess: "收起",
EPsSingles: "EP和单曲",
albums: "专辑",
withAlbums: "张专辑",
artist: "艺人",
videos: "个MV",
following: "已关注",
follow: "关注",
latestRelease: '最新发布',
popularSongs: '热门歌曲',
showMore: '显示更多',
showLess: '收起',
EPsSingles: 'EP和单曲',
albums: '专辑',
withAlbums: '张专辑',
artist: '艺人',
videos: '个MV',
following: '已关注',
follow: '关注',
},
album: {
released: "发行于",
released: '发行于',
},
playlist: {
playlist: "歌单",
updatedAt: "最后更新于",
search: "搜索歌单音乐",
playlist: '歌单',
updatedAt: '最后更新于',
search: '搜索歌单音乐',
},
login: {
accessToAll: "可访问全部数据",
loginText: "登录网易云账号",
search: "搜索网易云账号",
readonly: "只能读取账号公开数据",
usernameLogin: "用户名登录",
searchHolder: "请输入你的网易云用户名",
enterTip: "按 Enter 搜索",
choose: "在列表中选中你的账号",
confirm: "确认",
countryCode: "国际区号",
phone: "手机号",
email: "邮箱",
password: "密码",
login: "登录",
loginWithEmail: "使用邮箱登录",
loginWithPhone: "使用手机号登录",
accessToAll: '可访问全部数据',
loginText: '登录网易云账号',
search: '搜索网易云账号',
readonly: '只能读取账号公开数据',
usernameLogin: '用户名登录',
searchHolder: '请输入你的网易云用户名',
enterTip: '按 Enter 搜索',
choose: '在列表中选中你的账号',
confirm: '确认',
countryCode: '国际区号',
phone: '手机号',
email: '邮箱',
password: '密码',
login: '登录',
loginWithEmail: '使用邮箱登录',
loginWithPhone: '使用手机号登录',
notice: `YesPlayMusic 承诺不会保存你的任何账号信息到云端。<br />
你的密码会在本地进行 MD5 加密后再传输到网易云 API<br />
YesPlayMusic 并非网易云官方网站输入账号信息前请慎重考虑 你也可以前往
@ -84,85 +84,85 @@ export default {
YesPlayMusic 不会传输你的账号数据到任何非网易云音乐官方的服务器<br />`,
},
mv: {
moreVideo: "更多视频",
moreVideo: '更多视频',
},
next: {
nowPlaying: "正在播放",
nextUp: "即将播放",
nowPlaying: '正在播放',
nextUp: '即将播放',
},
player: {
like: "喜欢",
previous: "上一首",
next: "下一首",
repeat: "循环播放",
repeatTrack: "单曲循环",
shuffle: "随机播放",
play: "播放",
pause: "暂停",
mute: "静音",
nextUp: "播放列表",
like: '喜欢',
previous: '上一首',
next: '下一首',
repeat: '循环播放',
repeatTrack: '单曲循环',
shuffle: '随机播放',
play: '播放',
pause: '暂停',
mute: '静音',
nextUp: '播放列表',
},
modal: {
close: "关闭",
close: '关闭',
},
search: {
artist: "艺人",
album: "专辑",
song: "歌曲",
mv: "视频",
playlist: "歌单",
noResult: "暂无结果",
searchFor: "搜索",
artist: '艺人',
album: '专辑',
song: '歌曲',
mv: '视频',
playlist: '歌单',
noResult: '暂无结果',
searchFor: '搜索',
},
settings: {
settings: "设置",
logout: "登出",
language: "语言",
settings: '设置',
logout: '登出',
language: '语言',
musicQuality: {
text: "音质选择",
low: "普通",
medium: "较高",
high: "极高",
lossless: "无损",
text: '音质选择',
low: '普通',
medium: '较高',
high: '极高',
lossless: '无损',
},
cacheLimit: {
text: "歌曲缓存上限",
none: "无限制",
text: '歌曲缓存上限',
none: '无限制',
},
lyricFontSize: {
text: "歌词字体大小",
small: "小",
medium: "中",
large: "大(默认)",
xlarge: "超大",
text: '歌词字体大小',
small: '小',
medium: '中',
large: '大(默认)',
xlarge: '超大',
},
deviceSelector: "音频输出设备",
permissionRequired: "需要麦克风权限",
deviceSelector: '音频输出设备',
permissionRequired: '需要麦克风权限',
appearance: {
text: "外观",
auto: "自动",
light: "浅色",
dark: "深色",
text: '外观',
auto: '自动',
light: '浅色',
dark: '深色',
},
automaticallyCacheSongs: "自动缓存歌曲",
clearSongsCache: "清除歌曲缓存",
cacheCount: "已缓存 {song} 首 ({size})",
showLyricsTranslation: "显示歌词翻译",
showLyricsDynamicBackground: "显示歌词动态背景",
minimizeToTray: "最小化到托盘",
showPlaylistsByAppleMusic: "首页显示来自 Apple Music 的歌单",
enableDiscordRichPresence: "启用 Discord Rich Presence",
enableGlobalShortcut: "启用全局快捷键",
showLibraryDefault: "启动后显示音乐库",
automaticallyCacheSongs: '自动缓存歌曲',
clearSongsCache: '清除歌曲缓存',
cacheCount: '已缓存 {song} 首 ({size})',
showLyricsTranslation: '显示歌词翻译',
showLyricsDynamicBackground: '显示歌词动态背景',
minimizeToTray: '最小化到托盘',
showPlaylistsByAppleMusic: '首页显示来自 Apple Music 的歌单',
enableDiscordRichPresence: '启用 Discord Rich Presence',
enableGlobalShortcut: '启用全局快捷键',
showLibraryDefault: '启动后显示音乐库',
},
contextMenu: {
play: "播放",
playNext: "下一首播放",
saveToMyLikedSongs: "添加到我喜欢的音乐",
removeFromMyLikedSongs: "从喜欢的音乐中删除",
play: '播放',
playNext: '下一首播放',
saveToMyLikedSongs: '添加到我喜欢的音乐',
removeFromMyLikedSongs: '从喜欢的音乐中删除',
},
toast: {
savedToMyLikedSongs: "已添加到我喜欢的音乐",
removedFromMyLikedSongs: "已从喜欢的音乐中删除",
savedToMyLikedSongs: '已添加到我喜欢的音乐',
removedFromMyLikedSongs: '已从喜欢的音乐中删除',
},
};

@ -1,33 +1,33 @@
import Vue from "vue";
import VueAnalytics from "vue-analytics";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import i18n from "@/locale";
import "@/assets/icons";
import "@/utils/filters";
import "./registerServiceWorker";
import { dailyTask } from "@/utils/common";
import Vue from 'vue';
import VueAnalytics from 'vue-analytics';
import App from './App.vue';
import router from './router';
import store from './store';
import i18n from '@/locale';
import '@/assets/icons';
import '@/utils/filters';
import './registerServiceWorker';
import { dailyTask } from '@/utils/common';
window.resetApp = () => {
localStorage.clear();
indexedDB.deleteDatabase("yesplaymusic");
document.cookie.split(";").forEach(function (c) {
indexedDB.deleteDatabase('yesplaymusic');
document.cookie.split(';').forEach(function (c) {
document.cookie = c
.replace(/^ +/, "")
.replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
.replace(/^ +/, '')
.replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
});
return "已重置应用请刷新页面按Ctrl/Command + R";
return '已重置应用请刷新页面按Ctrl/Command + R';
};
console.log(
"如出现问题,可尝试在本页输入 %cresetApp()%c 然后按回车重置应用。",
"background: #eaeffd;color:#335eea;padding: 4px 6px;border-radius:3px;",
"background:unset;color:unset;"
'如出现问题,可尝试在本页输入 %cresetApp()%c 然后按回车重置应用。',
'background: #eaeffd;color:#335eea;padding: 4px 6px;border-radius:3px;',
'background:unset;color:unset;'
);
Vue.use(VueAnalytics, {
id: "UA-180189423-1",
id: 'UA-180189423-1',
router,
});
@ -39,5 +39,5 @@ new Vue({
i18n,
store,
router,
render: (h) => h(App),
}).$mount("#app");
render: h => h(App),
}).$mount('#app');

@ -1,6 +1,6 @@
/* eslint-disable no-console */
import { register } from "register-service-worker";
import { register } from 'register-service-worker';
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
@ -27,6 +27,6 @@ register(`${process.env.BASE_URL}service-worker.js`, {
// );
},
error(error) {
console.error("Error during service worker registration:", error);
console.error('Error during service worker registration:', error);
},
});

@ -1,135 +1,135 @@
import Vue from "vue";
import VueRouter from "vue-router";
import NProgress from "nprogress";
import "@/assets/css/nprogress.css";
import { isLooseLoggedIn, isAccountLoggedIn } from "@/utils/auth";
import Vue from 'vue';
import VueRouter from 'vue-router';
import NProgress from 'nprogress';
import '@/assets/css/nprogress.css';
import { isLooseLoggedIn, isAccountLoggedIn } from '@/utils/auth';
NProgress.configure({ showSpinner: false, trickleSpeed: 100 });
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "home",
component: () => import("@/views/home.vue"),
path: '/',
name: 'home',
component: () => import('@/views/home.vue'),
meta: {
keepAlive: true,
},
},
{
path: "/login",
name: "login",
component: () => import("@/views/login.vue"),
path: '/login',
name: 'login',
component: () => import('@/views/login.vue'),
},
{
path: "/login/username",
name: "loginUsername",
component: () => import("@/views/loginUsername.vue"),
path: '/login/username',
name: 'loginUsername',
component: () => import('@/views/loginUsername.vue'),
},
{
path: "/login/account",
name: "loginAccount",
component: () => import("@/views/loginAccount.vue"),
path: '/login/account',
name: 'loginAccount',
component: () => import('@/views/loginAccount.vue'),
},
{
path: "/playlist/:id",
name: "playlist",
component: () => import("@/views/playlist.vue"),
path: '/playlist/:id',
name: 'playlist',
component: () => import('@/views/playlist.vue'),
},
{
path: "/album/:id",
name: "album",
component: () => import("@/views/album.vue"),
path: '/album/:id',
name: 'album',
component: () => import('@/views/album.vue'),
},
{
path: "/artist/:id",
name: "artist",
component: () => import("@/views/artist.vue"),
path: '/artist/:id',
name: 'artist',
component: () => import('@/views/artist.vue'),
meta: {
keepAlive: true,
},
},
{
path: "/artist/:id/mv",
name: "artistMV",
component: () => import("@/views/artistMV.vue"),
path: '/artist/:id/mv',
name: 'artistMV',
component: () => import('@/views/artistMV.vue'),
meta: {
keepAlive: true,
},
},
{
path: "/mv/:id",
name: "mv",
component: () => import("@/views/mv.vue"),
path: '/mv/:id',
name: 'mv',
component: () => import('@/views/mv.vue'),
},
{
path: "/next",
name: "next",
component: () => import("@/views/next.vue"),
path: '/next',
name: 'next',
component: () => import('@/views/next.vue'),
meta: {
keepAlive: true,
},
},
{
path: "/search/:keywords?",
name: "search",
component: () => import("@/views/search.vue"),
path: '/search/:keywords?',
name: 'search',
component: () => import('@/views/search.vue'),
meta: {
keepAlive: true,
},
},
{
path: "/search/:keywords/:type",
name: "searchType",
component: () => import("@/views/searchType.vue"),
path: '/search/:keywords/:type',
name: 'searchType',
component: () => import('@/views/searchType.vue'),
},
{
path: "/new-album",
name: "newAlbum",
component: () => import("@/views/newAlbum.vue"),
path: '/new-album',
name: 'newAlbum',
component: () => import('@/views/newAlbum.vue'),
},
{
path: "/explore",
name: "explore",
component: () => import("@/views/explore.vue"),
path: '/explore',
name: 'explore',
component: () => import('@/views/explore.vue'),
meta: {
keepAlive: true,
},
},
{
path: "/library",
name: "library",
component: () => import("@/views/library.vue"),
path: '/library',
name: 'library',
component: () => import('@/views/library.vue'),
meta: {
requireLogin: true,
keepAlive: true,
},
},
{
path: "/library/liked-songs",
name: "likedSongs",
component: () => import("@/views/playlist.vue"),
path: '/library/liked-songs',
name: 'likedSongs',
component: () => import('@/views/playlist.vue'),
meta: {
requireLogin: true,
},
},
{
path: "/settings",
name: "settings",
component: () => import("@/views/settings.vue"),
path: '/settings',
name: 'settings',
component: () => import('@/views/settings.vue'),
},
{
path: "/daily/songs",
name: "dailySongs",
component: () => import("@/views/dailyTracks.vue"),
path: '/daily/songs',
name: 'dailySongs',
component: () => import('@/views/dailyTracks.vue'),
meta: {
requireAccountLogin: true,
},
},
{
path: "/lastfm/callback",
name: "lastfmCallback",
component: () => import("@/views/lastfmCallback.vue"),
path: '/lastfm/callback',
name: 'lastfmCallback',
component: () => import('@/views/lastfmCallback.vue'),
},
];
const router = new VueRouter({
@ -145,7 +145,7 @@ const router = new VueRouter({
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch((err) => err);
return originalPush.call(this, location).catch(err => err);
};
router.beforeEach((to, from, next) => {
@ -154,7 +154,7 @@ router.beforeEach((to, from, next) => {
if (isAccountLoggedIn()) {
next();
} else {
next({ path: "/login/account" });
next({ path: '/login/account' });
}
}
if (to.meta.requireLogin) {
@ -162,9 +162,9 @@ router.beforeEach((to, from, next) => {
next();
} else {
if (process.env.IS_ELECTRON === true) {
next({ path: "/login/account" });
next({ path: '/login/account' });
} else {
next({ path: "/login" });
next({ path: '/login' });
}
}
} else {
@ -172,10 +172,10 @@ router.beforeEach((to, from, next) => {
}
});
router.afterEach((to) => {
router.afterEach(to => {
if (
to.matched.some((record) => !record.meta.keepAlive) &&
!["settings", "dailySongs", "lastfmCallback"].includes(to.name)
to.matched.some(record => !record.meta.keepAlive) &&
!['settings', 'dailySongs', 'lastfmCallback'].includes(to.name)
) {
NProgress.start();
}

@ -1,13 +1,13 @@
import Vue from "vue";
import Vuex from "vuex";
import state from "./state";
import mutations from "./mutations";
import actions from "./actions";
import { changeAppearance } from "@/utils/common";
import Player from "@/utils/Player";
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import mutations from './mutations';
import actions from './actions';
import { changeAppearance } from '@/utils/common';
import Player from '@/utils/Player';
// vuex 自定义插件
import saveToLocalStorage from "./plugins/localStorage";
import { getSendSettingsPlugin } from "./plugins/sendSettings";
import saveToLocalStorage from './plugins/localStorage';
import { getSendSettingsPlugin } from './plugins/sendSettings';
Vue.use(Vuex);
@ -26,22 +26,22 @@ const options = {
const store = new Vuex.Store(options);
if ([undefined, null].includes(store.state.settings.lang)) {
const defaultLang = "en";
const defaultLang = 'en';
const langMapper = new Map()
.set("zh", "zh-CN")
.set("en", "en")
.set("tr", "tr");
.set('zh', 'zh-CN')
.set('en', 'en')
.set('tr', 'tr');
store.state.settings.lang =
langMapper.get(navigator.language.slice(0, 2)) || defaultLang;
localStorage.setItem("settings", JSON.stringify(store.state.settings));
localStorage.setItem('settings', JSON.stringify(store.state.settings));
}
changeAppearance(store.state.settings.appearance);
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", () => {
if (store.state.settings.appearance === "auto") {
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => {
if (store.state.settings.appearance === 'auto') {
changeAppearance(store.state.settings.appearance);
}
});
@ -51,7 +51,7 @@ player = new Proxy(player, {
set(target, prop, val) {
// console.log({ prop, val });
target[prop] = val;
if (prop === "_howler") return true;
if (prop === '_howler') return true;
target.saveSelfToLocalStorage();
target.sendSelfToIpcMain();
return true;

@ -1,18 +1,18 @@
import { playlistCategories } from "@/utils/staticData";
import { playlistCategories } from '@/utils/staticData';
console.log("[debug][initLocalStorage.js]");
console.log('[debug][initLocalStorage.js]');
const enabledPlaylistCategories = playlistCategories
.filter((c) => c.enable)
.map((c) => c.name);
.filter(c => c.enable)
.map(c => c.name);
let localStorage = {
player: {},
settings: {
lang: null,
appearance: "auto",
appearance: 'auto',
musicQuality: 320000,
lyricFontSize: 28,
outputDevice: "default",
outputDevice: 'default',
showPlaylistsByAppleMusic: true,
enableUnblockNeteaseMusic: true,
automaticallyCacheSongs: false,

@ -1,7 +1,7 @@
export default (store) => {
export default store => {
store.subscribe((mutation, state) => {
// console.log(mutation);
localStorage.setItem("settings", JSON.stringify(state.settings));
localStorage.setItem("data", JSON.stringify(state.data));
localStorage.setItem('settings', JSON.stringify(state.settings));
localStorage.setItem('data', JSON.stringify(state.data));
});
};

@ -1,11 +1,11 @@
export function getSendSettingsPlugin() {
const electron = window.require("electron");
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;
return (store) => {
return store => {
store.subscribe((mutation, state) => {
// console.log(mutation);
if (mutation.type !== "updateSettings") return;
ipcRenderer.send("settings", {
if (mutation.type !== 'updateSettings') return;
ipcRenderer.send('settings', {
minimizeToTray: state.settings.minimizeToTray,
enableGlobalShortcut: state.settings.enableGlobalShortcut,
appearance: state.settings.appearance,

@ -1,35 +1,35 @@
import Cookies from "js-cookie";
import { logout } from "@/api/auth";
import store from "@/store";
import Cookies from 'js-cookie';
import { logout } from '@/api/auth';
import store from '@/store';
export function doLogout() {
logout();
// 网易云的接口会自动移除该 cookies
// Cookies.remove("MUSIC_U");
// 更新状态仓库中的用户信息
store.commit("updateData", { key: "user", value: {} });
store.commit('updateData', { key: 'user', value: {} });
// 更新状态仓库中的登录状态
store.commit("updateData", { key: "loginMode", value: null });
store.commit('updateData', { key: 'loginMode', value: null });
// 更新状态仓库中的喜欢列表
store.commit("updateData", { key: "likedSongPlaylistID", value: undefined });
store.commit('updateData', { key: 'likedSongPlaylistID', value: undefined });
}
// MUSIC_U 只有在账户登录的情况下才有
export function isLoggedIn() {
return Cookies.get("MUSIC_U") !== undefined ? true : false;
return Cookies.get('MUSIC_U') !== undefined ? true : false;
}
// 账号登录
export function isAccountLoggedIn() {
return (
Cookies.get("MUSIC_U") !== undefined &&
store.state.data.loginMode === "account"
Cookies.get('MUSIC_U') !== undefined &&
store.state.data.loginMode === 'account'
);
}
// 用户名搜索(用户数据为只读)
export function isUsernameLoggedIn() {
return store.state.data.loginMode === "username";
return store.state.data.loginMode === 'username';
}
// 账户登录或者用户名搜索都判断为登录,宽松检查
@ -38,15 +38,15 @@ export function isLooseLoggedIn() {
}
export function getMusicU(string) {
const temp = string.split(";");
const temp = string.split(';');
if (!temp.length) {
return undefined;
}
const MUSIC_U = temp.find((item) => item.includes("MUSIC_U"));
const MUSIC_U = temp.find(item => item.includes('MUSIC_U'));
if (MUSIC_U) {
return MUSIC_U.split("=")[1];
return MUSIC_U.split('=')[1];
}
return "";
return '';
}
export function setMusicU(key, value) {
@ -54,8 +54,8 @@ export function setMusicU(key, value) {
}
export function setCookies(string) {
const cookies = string.split(";;");
cookies.map((cookie) => {
const cookies = string.split(';;');
cookies.map(cookie => {
document.cookie = cookie;
console.log(cookie);
});

@ -1,13 +1,13 @@
import { isAccountLoggedIn } from "./auth";
import { refreshCookie } from "@/api/auth";
import { dailySignin } from "@/api/user";
import dayjs from "dayjs";
import store from "@/store";
import { isAccountLoggedIn } from './auth';
import { refreshCookie } from '@/api/auth';
import { dailySignin } from '@/api/user';
import dayjs from 'dayjs';
import store from '@/store';
export function isTrackPlayable(track) {
let result = {
playable: true,
reason: "",
reason: '',
};
// cloud storage judgement logic
if (isAccountLoggedIn() && track?.privilege?.cs) {
@ -18,27 +18,27 @@ export function isTrackPlayable(track) {
result.playable = true;
} else {
result.playable = false;
result.reason = "VIP Only";
result.reason = 'VIP Only';
}
} else if (track.fee === 4 || track.privilege?.fee === 4) {
result.playable = false;
result.reason = "付费专辑";
result.reason = '付费专辑';
} else if (
track.noCopyrightRcmd !== null &&
track.noCopyrightRcmd !== undefined
) {
result.playable = false;
result.reason = "无版权";
result.reason = '无版权';
} else if (track.privilege?.st < 0 && isAccountLoggedIn()) {
result.playable = false;
result.reason = "已下架";
result.reason = '已下架';
}
return result;
}
export function mapTrackPlayableStatus(tracks, privileges = []) {
return tracks.map((t) => {
const privilege = privileges.find((item) => item.id === t.id) || {};
return tracks.map(t => {
const privilege = privileges.find(item => item.id === t.id) || {};
if (t.privilege) {
Object.assign(t.privilege, privilege);
} else {
@ -63,13 +63,13 @@ export function randomNum(minNum, maxNum) {
}
export function shuffleAList(list) {
let sortsList = list.map((t) => t.sort);
let sortsList = list.map(t => t.sort);
for (let i = 1; i < sortsList.length; i++) {
const random = Math.floor(Math.random() * (i + 1));
[sortsList[i], sortsList[random]] = [sortsList[random], sortsList[i]];
}
let newSorts = {};
list.map((track) => {
list.map(track => {
newSorts[track.id] = sortsList.pop();
});
return newSorts;
@ -88,8 +88,8 @@ export function throttle(fn, time) {
}
export function updateHttps(url) {
if (!url) return "";
return url.replace(/^http:/, "https:");
if (!url) return '';
return url.replace(/^http:/, 'https:');
}
export function dailyTask() {
@ -98,9 +98,9 @@ export function dailyTask() {
isAccountLoggedIn() &&
(lastDate === undefined || lastDate !== dayjs().date())
) {
console.log("execute dailyTask");
store.commit("updateData", {
key: "lastRefreshCookieDate",
console.log('execute dailyTask');
store.commit('updateData', {
key: 'lastRefreshCookieDate',
value: dayjs().date(),
});
refreshCookie();
@ -110,85 +110,85 @@ export function dailyTask() {
}
export function changeAppearance(appearance) {
if (appearance === "auto" || appearance === undefined) {
appearance = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
if (appearance === 'auto' || appearance === undefined) {
appearance = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
document.body.setAttribute("data-theme", appearance);
document.body.setAttribute('data-theme', appearance);
document
.querySelector('meta[name="theme-color"]')
.setAttribute("content", appearance === "dark" ? "#222" : "#fff");
.setAttribute('content', appearance === 'dark' ? '#222' : '#fff');
}
export function splitSoundtrackAlbumTitle(title) {
let keywords = [
"Music from the Original Motion Picture Score",
"The Original Motion Picture Soundtrack",
"Original MGM Motion Picture Soundtrack",
"Complete Original Motion Picture Score",
"Original Music From The Motion Picture",
"Music From The Disney+ Original Movie",
"Original Music From The Netflix Film",
"Original Score to the Motion Picture",
"Original Motion Picture Soundtrack",
"Soundtrack from the Motion Picture",
"Original Television Soundtrack",
"Original Motion Picture Score",
"Music From the Motion Picture",
"Music From The Motion Picture",
"Complete Motion Picture Score",
"Music from the Motion Picture",
"Original Videogame Soundtrack",
"La Bande Originale du Film",
"Music from the Miniseries",
"Bande Originale du Film",
"Die Original Filmmusik",
"Original Soundtrack",
"Complete Score",
"Original Score",
'Music from the Original Motion Picture Score',
'The Original Motion Picture Soundtrack',
'Original MGM Motion Picture Soundtrack',
'Complete Original Motion Picture Score',
'Original Music From The Motion Picture',
'Music From The Disney+ Original Movie',
'Original Music From The Netflix Film',
'Original Score to the Motion Picture',
'Original Motion Picture Soundtrack',
'Soundtrack from the Motion Picture',
'Original Television Soundtrack',
'Original Motion Picture Score',
'Music From the Motion Picture',
'Music From The Motion Picture',
'Complete Motion Picture Score',
'Music from the Motion Picture',
'Original Videogame Soundtrack',
'La Bande Originale du Film',
'Music from the Miniseries',
'Bande Originale du Film',
'Die Original Filmmusik',
'Original Soundtrack',
'Complete Score',
'Original Score',
];
for (let keyword of keywords) {
if (title.includes(keyword) === false) continue;
return {
title: title
.replace(`(${keyword})`, "")
.replace(`: ${keyword}`, "")
.replace(`[${keyword}]`, "")
.replace(`- ${keyword}`, "")
.replace(`${keyword}`, ""),
.replace(`(${keyword})`, '')
.replace(`: ${keyword}`, '')
.replace(`[${keyword}]`, '')
.replace(`- ${keyword}`, '')
.replace(`${keyword}`, ''),
subtitle: keyword,
};
}
return {
title: title,
subtitle: "",
subtitle: '',
};
}
export function splitAlbumTitle(title) {
let keywords = [
"Bonus Tracks Edition",
"Complete Edition",
"Deluxe Edition",
"Deluxe Version",
"Tour Edition",
'Bonus Tracks Edition',
'Complete Edition',
'Deluxe Edition',
'Deluxe Version',
'Tour Edition',
];
for (let keyword of keywords) {
if (title.includes(keyword) === false) continue;
return {
title: title
.replace(`(${keyword})`, "")
.replace(`: ${keyword}`, "")
.replace(`[${keyword}]`, "")
.replace(`- ${keyword}`, "")
.replace(`${keyword}`, ""),
.replace(`(${keyword})`, '')
.replace(`: ${keyword}`, '')
.replace(`[${keyword}]`, '')
.replace(`- ${keyword}`, '')
.replace(`${keyword}`, ''),
subtitle: keyword,
};
}
return {
title: title,
subtitle: "",
subtitle: '',
};
}
@ -201,17 +201,17 @@ export function bytesToSize(bytes) {
let lang = store.state.settings.lang;
if (bytes < kiloBytes) return bytes + (lang === "en" ? " Bytes" : "字节");
if (bytes < kiloBytes) return bytes + (lang === 'en' ? ' Bytes' : '字节');
else if (bytes < megaBytes)
return (bytes / kiloBytes).toFixed(decimal) + " KB";
return (bytes / kiloBytes).toFixed(decimal) + ' KB';
else if (bytes < gigaBytes)
return (bytes / megaBytes).toFixed(decimal) + " MB";
else return (bytes / gigaBytes).toFixed(decimal) + " GB";
return (bytes / megaBytes).toFixed(decimal) + ' MB';
else return (bytes / gigaBytes).toFixed(decimal) + ' GB';
}
export function 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}`;
}

@ -1,26 +1,25 @@
import axios from "axios";
import Dexie from "dexie";
import store from "@/store";
import axios from 'axios';
import Dexie from 'dexie';
import store from '@/store';
// import pkg from "../../package.json";
const db = new Dexie("yesplaymusic");
const db = new Dexie('yesplaymusic');
db.version(2)
.stores({
trackSources: "&id",
trackSources: '&id',
})
.upgrade((tx) =>
.upgrade(tx =>
tx
.table("trackSources")
.table('trackSources')
.toCollection()
.modify(
(track) =>
!track.createTime && (track.createTime = new Date().getTime())
track => !track.createTime && (track.createTime = new Date().getTime())
)
);
db.version(1).stores({
trackSources: "&id",
trackSources: '&id',
});
let tracksCacheBytes = 0;
@ -33,7 +32,7 @@ async function deleteExcessCache() {
return;
}
try {
const delCache = await db.trackSources.orderBy("createTime").first();
const delCache = await db.trackSources.orderBy('createTime').first();
await db.trackSources.delete(delCache.id);
tracksCacheBytes -= delCache.source.byteLength;
console.debug(
@ -41,18 +40,18 @@ async function deleteExcessCache() {
);
deleteExcessCache();
} catch (error) {
console.debug("[debug][db.js] deleteExcessCacheFailed", error);
console.debug('[debug][db.js] deleteExcessCacheFailed', error);
}
}
export function cacheTrackSource(trackInfo, url, bitRate, from = "netease") {
export function cacheTrackSource(trackInfo, url, bitRate, from = 'netease') {
const name = trackInfo.name;
const artist = trackInfo.ar[0]?.name || trackInfo.artists[0]?.name;
return axios
.get(url, {
responseType: "arraybuffer",
responseType: 'arraybuffer',
})
.then((response) => {
.then(response => {
db.trackSources.put({
id: trackInfo.id,
source: response.data,
@ -70,7 +69,7 @@ export function cacheTrackSource(trackInfo, url, bitRate, from = "netease") {
}
export function getTrackSource(id) {
return db.trackSources.get(Number(id)).then((track) => {
return db.trackSources.get(Number(id)).then(track => {
if (!track) return null;
console.debug(
`[debug][db.js] get track from cache 👉 ${track.name} by ${track.artist}`
@ -82,7 +81,7 @@ export function getTrackSource(id) {
export function countDBSize() {
const trackSizes = [];
return db.trackSources
.each((track) => {
.each(track => {
trackSizes.push(track.source.byteLength);
})
.then(() => {
@ -99,7 +98,7 @@ export function countDBSize() {
}
export function clearDB() {
return new Promise((resolve) => {
return new Promise(resolve => {
db.tables.forEach(function (table) {
table.clear();
});

@ -1,63 +1,63 @@
import Vue from "vue";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import relativeTime from "dayjs/plugin/relativeTime";
import locale from "@/locale";
import Vue from 'vue';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
import locale from '@/locale';
Vue.filter("formatTime", (Milliseconds, format = "HH:MM:SS") => {
if (!Milliseconds) return "";
Vue.filter('formatTime', (Milliseconds, format = 'HH:MM:SS') => {
if (!Milliseconds) return '';
dayjs.extend(duration);
dayjs.extend(relativeTime);
let time = dayjs.duration(Milliseconds);
let hours = time.hours().toString();
let mins = time.minutes().toString();
let seconds = time.seconds().toString().padStart(2, "0");
let seconds = time.seconds().toString().padStart(2, '0');
if (format === "HH:MM:SS") {
return hours !== "0"
? `${hours}:${mins.padStart(2, "0")}:${seconds}`
if (format === 'HH:MM:SS') {
return hours !== '0'
? `${hours}:${mins.padStart(2, '0')}:${seconds}`
: `${mins}:${seconds}`;
} else if (format === "Human") {
const hoursUnit = locale.locale === "zh-CN" ? "小时" : "hr";
const minitesUnit = locale.locale === "zh-CN" ? "分钟" : "min";
return hours !== "0"
} else if (format === 'Human') {
const hoursUnit = locale.locale === 'zh-CN' ? '小时' : 'hr';
const minitesUnit = locale.locale === 'zh-CN' ? '分钟' : 'min';
return hours !== '0'
? `${hours} ${hoursUnit} ${mins} ${minitesUnit}`
: `${mins} ${minitesUnit}`;
}
});
Vue.filter("formatDate", (timestamp, format = "MMM D, YYYY") => {
if (!timestamp) return "";
if (locale.locale === "zh-CN") format = "YYYY年MM月DD日";
Vue.filter('formatDate', (timestamp, format = 'MMM D, YYYY') => {
if (!timestamp) return '';
if (locale.locale === 'zh-CN') format = 'YYYY年MM月DD日';
return dayjs(timestamp).format(format);
});
Vue.filter("formatAlbumType", (type, album) => {
if (!type) return "";
if (type === "EP/Single") {
return album.size === 1 ? "Single" : "EP";
} else if (type === "Single") {
return "Single";
} else if (type === "专辑") {
return "Album";
Vue.filter('formatAlbumType', (type, album) => {
if (!type) return '';
if (type === 'EP/Single') {
return album.size === 1 ? 'Single' : 'EP';
} else if (type === 'Single') {
return 'Single';
} else if (type === '专辑') {
return 'Album';
} else {
return type;
}
});
Vue.filter("resizeImage", (imgUrl, size = 512) => {
if (!imgUrl) return "";
Vue.filter('resizeImage', (imgUrl, size = 512) => {
if (!imgUrl) return '';
let httpsImgUrl = imgUrl;
if (imgUrl.slice(0, 5) !== "https") {
httpsImgUrl = "https" + imgUrl.slice(4);
if (imgUrl.slice(0, 5) !== 'https') {
httpsImgUrl = 'https' + imgUrl.slice(4);
}
return `${httpsImgUrl}?param=${size}y${size}`;
});
Vue.filter("formatPlayCount", (count) => {
if (!count) return "";
if (locale.locale === "zh-CN") {
Vue.filter('formatPlayCount', count => {
if (!count) return '';
if (locale.locale === 'zh-CN') {
if (count > 100000000) {
return `${Math.floor((count / 100000000) * 100) / 100}亿`; // 2.32 亿
}
@ -82,7 +82,7 @@ Vue.filter("formatPlayCount", (count) => {
}
});
Vue.filter("toHttps", (url) => {
if (!url) return "";
return url.replace(/^http:/, "https:");
Vue.filter('toHttps', url => {
if (!url) return '';
return url.replace(/^http:/, 'https:');
});

@ -2,29 +2,29 @@
export function lyricParser(lrc) {
return {
lyric: parseLyric(lrc?.lrc?.lyric || ""),
tlyric: parseLyric(lrc?.tlyric?.lyric || ""),
lyric: parseLyric(lrc?.lrc?.lyric || ''),
tlyric: parseLyric(lrc?.tlyric?.lyric || ''),
lyricuser: lrc.lyricUser,
transuser: lrc.transUser,
};
}
export function parseLyric(lrc) {
const lyrics = lrc.split("\n");
const lyrics = lrc.split('\n');
const lrcObj = [];
for (let i = 0; i < lyrics.length; i++) {
const lyric = decodeURIComponent(lyrics[i]);
const timeReg = /\[\d*:\d*((\.|:)\d*)*\]/g;
const timeRegExpArr = lyric.match(timeReg);
if (!timeRegExpArr) continue;
const content = lyric.replace(timeReg, "");
const content = lyric.replace(timeReg, '');
for (let k = 0, h = timeRegExpArr.length; k < h; k++) {
const t = timeRegExpArr[k];
const min = Number(String(t.match(/\[\d*/i)).slice(1));
const sec = Number(String(t.match(/:\d*/i)).slice(1));
const ms = Number(t.match(/\d*\]/i)[0].slice(0, 2)) / 100;
const time = min * 60 + sec + ms;
if (content !== "") {
if (content !== '') {
lrcObj.push({ time: time, rawTime: timeRegExpArr[0], content });
}
}

@ -15,11 +15,11 @@ const nativeAlert = (() => {
if (process.env.IS_ELECTRON === true) {
const {
remote: { dialog },
} = require("electron");
} = require('electron');
if (dialog) {
return (message) => {
return message => {
var options = {
type: "warning",
type: 'warning',
detail: message,
};
dialog.showMessageBoxSync(null, options);

@ -1,41 +1,41 @@
export const byAppleMusic = [
{
coverImgUrl:
"https://p2.music.126.net/GvYQoflE99eoeGi9jG4Bsw==/109951165375336156.jpg",
name: "Happy Hits",
'https://p2.music.126.net/GvYQoflE99eoeGi9jG4Bsw==/109951165375336156.jpg',
name: 'Happy Hits',
id: 5278068783,
},
{
coverImgUrl:
"https://p2.music.126.net/5CJeYN35LnzRDsv5Lcs0-Q==/109951165374966765.jpg",
name: "\u4e2d\u563b\u5408\u74a7",
'https://p2.music.126.net/5CJeYN35LnzRDsv5Lcs0-Q==/109951165374966765.jpg',
name: '\u4e2d\u563b\u5408\u74a7',
id: 5277771961,
},
{
coverImgUrl:
"https://p1.music.126.net/cPaBXr1wZSg86ddl47AK7Q==/109951165375130918.jpg",
name: "Heartbreak Pop",
'https://p1.music.126.net/cPaBXr1wZSg86ddl47AK7Q==/109951165375130918.jpg',
name: 'Heartbreak Pop',
id: 5277965913,
},
{
coverImgUrl:
"https://p2.music.126.net/FDtX55P2NjccDna-LBj9PA==/109951165375065973.jpg",
name: "Festival Bangers",
'https://p2.music.126.net/FDtX55P2NjccDna-LBj9PA==/109951165375065973.jpg',
name: 'Festival Bangers',
id: 5277969451,
},
{
coverImgUrl:
"https://p2.music.126.net/hC0q2dGbOWHVfg4nkhIXPg==/109951165374881177.jpg",
name: "Bedtime Beats",
'https://p2.music.126.net/hC0q2dGbOWHVfg4nkhIXPg==/109951165374881177.jpg',
name: 'Bedtime Beats',
id: 5277778542,
},
];
export const playlistCategories = [
{
name: "全部",
name: '全部',
enable: true,
bigCat: "static",
bigCat: 'static',
},
// {
// name: "For You",
@ -43,9 +43,9 @@ export const playlistCategories = [
// bigCat: "static",
// },
{
name: "推荐歌单",
name: '推荐歌单',
enable: true,
bigCat: "static",
bigCat: 'static',
},
// {
// name: "最新专辑",
@ -53,368 +53,368 @@ export const playlistCategories = [
// bigCat: "static",
// },
{
name: "精品歌单",
name: '精品歌单',
enable: true,
bigCat: "static",
bigCat: 'static',
},
{
name: "官方",
name: '官方',
enable: true,
bigCat: "static",
bigCat: 'static',
},
{
name: "排行榜",
name: '排行榜',
enable: true,
bigCat: "static",
bigCat: 'static',
},
{
name: "华语",
name: '华语',
enable: false,
bigCat: "语种",
bigCat: '语种',
},
{
name: "欧美",
name: '欧美',
enable: true,
bigCat: "语种",
bigCat: '语种',
},
{
name: "日语",
name: '日语',
enable: false,
bigCat: "语种",
bigCat: '语种',
},
{
name: "韩语",
name: '韩语',
enable: false,
bigCat: "语种",
bigCat: '语种',
},
{
name: "粤语",
name: '粤语',
enable: false,
bigCat: "语种",
bigCat: '语种',
},
{
name: "流行",
name: '流行',
enable: true,
bigCat: "风格",
bigCat: '风格',
},
{
name: "摇滚",
name: '摇滚',
enable: true,
bigCat: "风格",
bigCat: '风格',
},
{
name: "民谣",
name: '民谣',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "电子",
name: '电子',
enable: true,
bigCat: "风格",
bigCat: '风格',
},
{
name: "舞曲",
name: '舞曲',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "说唱",
name: '说唱',
enable: true,
bigCat: "风格",
bigCat: '风格',
},
{
name: "轻音乐",
name: '轻音乐',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "爵士",
name: '爵士',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "乡村",
name: '乡村',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "R&B/Soul",
name: 'R&B/Soul',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "古典",
name: '古典',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "民族",
name: '民族',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "英伦",
name: '英伦',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "金属",
name: '金属',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "朋克",
name: '朋克',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "蓝调",
name: '蓝调',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "雷鬼",
name: '雷鬼',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "世界音乐",
name: '世界音乐',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "拉丁",
name: '拉丁',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "New Age",
name: 'New Age',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "古风",
name: '古风',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "后摇",
name: '后摇',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "Bossa Nova",
name: 'Bossa Nova',
enable: false,
bigCat: "风格",
bigCat: '风格',
},
{
name: "清晨",
name: '清晨',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "夜晚",
name: '夜晚',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "学习",
name: '学习',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "工作",
name: '工作',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "午休",
name: '午休',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "下午茶",
name: '下午茶',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "地铁",
name: '地铁',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "驾车",
name: '驾车',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "运动",
name: '运动',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "旅行",
name: '旅行',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "散步",
name: '散步',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "酒吧",
name: '酒吧',
enable: false,
bigCat: "场景",
bigCat: '场景',
},
{
name: "怀旧",
name: '怀旧',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "清新",
name: '清新',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "浪漫",
name: '浪漫',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "伤感",
name: '伤感',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "治愈",
name: '治愈',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "放松",
name: '放松',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "孤独",
name: '孤独',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "感动",
name: '感动',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "兴奋",
name: '兴奋',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "快乐",
name: '快乐',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "安静",
name: '安静',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "思念",
name: '思念',
enable: false,
bigCat: "情感",
bigCat: '情感',
},
{
name: "综艺",
name: '综艺',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "影视原声",
name: '影视原声',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "ACG",
name: 'ACG',
enable: true,
bigCat: "主题",
bigCat: '主题',
},
{
name: "儿童",
name: '儿童',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "校园",
name: '校园',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "游戏",
name: '游戏',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "70后",
name: '70后',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "80后",
name: '80后',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "90后",
name: '90后',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "网络歌曲",
name: '网络歌曲',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "KTV",
name: 'KTV',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "经典",
name: '经典',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "翻唱",
name: '翻唱',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "吉他",
name: '吉他',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "钢琴",
name: '钢琴',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "器乐",
name: '器乐',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "榜单",
name: '榜单',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
{
name: "00后",
name: '00后',
enable: false,
bigCat: "主题",
bigCat: '主题',
},
];

@ -1,30 +1,30 @@
import initLocalStorage from "@/store/initLocalStorage.js";
import pkg from "../../package.json";
import initLocalStorage from '@/store/initLocalStorage.js';
import pkg from '../../package.json';
const updateSetting = () => {
const parsedSettings = JSON.parse(localStorage.getItem("settings"));
const parsedSettings = JSON.parse(localStorage.getItem('settings'));
const settings = {
...initLocalStorage.settings,
...parsedSettings,
};
localStorage.setItem("settings", JSON.stringify(settings));
localStorage.setItem('settings', JSON.stringify(settings));
};
const updateData = () => {
const parsedData = JSON.parse(localStorage.getItem("data"));
const parsedData = JSON.parse(localStorage.getItem('data'));
const data = {
...parsedData,
};
localStorage.setItem("data", JSON.stringify(data));
localStorage.setItem('data', JSON.stringify(data));
};
const updatePlayer = () => {
let parsedData = JSON.parse(localStorage.getItem("player"));
let appVersion = localStorage.getItem("appVersion");
let parsedData = JSON.parse(localStorage.getItem('player'));
let appVersion = localStorage.getItem('appVersion');
if (appVersion === `"0.2.5"`) parsedData = {}; // 0.2.6版本重构了player
const data = {
_repeatMode: "off",
_repeatMode: 'off',
_shuffle: false,
_list: [],
_current: 0,
@ -38,12 +38,12 @@ const updatePlayer = () => {
_shuffledCurrent: 0,
...parsedData,
};
localStorage.setItem("player", JSON.stringify(data));
localStorage.setItem('player', JSON.stringify(data));
};
const removeOldStuff = () => {
// remove old indexedDB databases created by localforage
indexedDB.deleteDatabase("tracks");
indexedDB.deleteDatabase('tracks');
};
export default function () {
@ -51,5 +51,5 @@ export default function () {
updateData();
updatePlayer();
removeOldStuff();
localStorage.setItem("appVersion", JSON.stringify(pkg.version));
localStorage.setItem('appVersion', JSON.stringify(pkg.version));
}

@ -1,21 +1,21 @@
<template>
<div class="album" v-show="show">
<div v-show="show" class="album">
<div class="playlist-info">
<Cover
:imageUrl="album.picUrl | resizeImage(1024)"
:showPlayButton="true"
:alwaysShowShadow="true"
:clickCoverToPlay="true"
:fixedSize="288"
type="album"
:id="album.id"
:coverHover="false"
:playButtonSize="18"
:image-url="album.picUrl | resizeImage(1024)"
:show-play-button="true"
:always-show-shadow="true"
:click-cover-to-play="true"
:fixed-size="288"
type="album"
:cover-hover="false"
:play-button-size="18"
@click.right.native="openMenu"
/>
<div class="info">
<div class="title" @click.right="openMenu"> {{ title }}</div>
<div class="subtitle" v-if="subtitle !== ''" @click.right="openMenu">{{
<div v-if="subtitle !== ''" class="subtitle" @click.right="openMenu">{{
subtitle
}}</div>
<div class="artist">
@ -28,42 +28,42 @@
<span v-else>Compilation by Various Artists</span>
</div>
<div class="date-and-count">
<span class="explicit-symbol" v-if="album.mark === 1056768"
<span v-if="album.mark === 1056768" class="explicit-symbol"
><ExplicitSymbol
/></span>
<span :title="album.publishTime | formatDate">{{
new Date(album.publishTime).getFullYear()
}}</span>
<span> · {{ album.size }} {{ $t("common.songs") }}</span
<span> · {{ album.size }} {{ $t('common.songs') }}</span
>,
{{ albumTime | formatTime("Human") }}
{{ albumTime | formatTime('Human') }}
</div>
<div class="description" @click="showFullDescription = true">
{{ album.description }}
</div>
<div class="buttons" style="margin-top: 32px">
<ButtonTwoTone
icon-class="play"
@click.native="playAlbumByID(album.id)"
iconClass="play"
>
{{ $t("common.play") }}
{{ $t('common.play') }}
</ButtonTwoTone>
<ButtonTwoTone
:iconClass="dynamicDetail.isSub ? 'heart-solid' : 'heart'"
:iconButton="true"
:horizontalPadding="0"
:icon-class="dynamicDetail.isSub ? 'heart-solid' : 'heart'"
:icon-button="true"
:horizontal-padding="0"
:color="dynamicDetail.isSub ? 'blue' : 'grey'"
:textColor="dynamicDetail.isSub ? '#335eea' : ''"
:backgroundColor="
:text-color="dynamicDetail.isSub ? '#335eea' : ''"
:background-color="
dynamicDetail.isSub ? 'var(--color-secondary-bg)' : ''
"
@click.native="likeAlbum"
>
</ButtonTwoTone>
<ButtonTwoTone
iconClass="more"
:iconButton="true"
:horizontalPadding="0"
icon-class="more"
:icon-button="true"
:horizontal-padding="0"
color="grey"
@click.native="openMenu"
>
@ -72,22 +72,22 @@
</div>
</div>
<TrackList
:id="album.id"
:tracks="tracks"
:type="'album'"
:id="album.id"
:albumObject="album"
:album-object="album"
/>
<div class="extra-info">
<div class="album-time"></div>
<div class="release-date">
{{ $t("album.released") }}
{{ album.publishTime | formatDate("MMMM D, YYYY") }}
{{ $t('album.released') }}
{{ album.publishTime | formatDate('MMMM D, YYYY') }}
</div>
<div class="copyright" v-if="album.company !== null">
<div v-if="album.company !== null" class="copyright">
© {{ album.company }}
</div>
</div>
<div class="more-by" v-if="filteredMoreAlbums.length !== 0">
<div v-if="filteredMoreAlbums.length !== 0" class="more-by">
<div class="section-title">
More by
<router-link :to="`/artist/${album.artist.id}`"
@ -98,15 +98,15 @@
<CoverRow
type="album"
:items="filteredMoreAlbums"
subText="albumType+releaseYear"
sub-text="albumType+releaseYear"
/>
</div>
</div>
<Modal
:show="showFullDescription"
:close="() => (showFullDescription = false)"
:showFooter="false"
:clickOutsideHide="true"
:show-footer="false"
:click-outside-hide="true"
title="专辑介绍"
>
<p class="description-fulltext">
@ -114,9 +114,9 @@
</p>
</Modal>
<ContextMenu ref="albumMenu">
<div class="item">{{ $t("contextMenu.playNext") }}</div>
<div class="item">{{ $t('contextMenu.playNext') }}</div>
<div class="item" @click="likeAlbum(true)">{{
dynamicDetail.isSub ? "从音乐库删除" : "保存到音乐库"
dynamicDetail.isSub ? '从音乐库删除' : '保存到音乐库'
}}</div>
<div class="item">添加到歌单</div>
</ContextMenu>
@ -124,24 +124,24 @@
</template>
<script>
import { mapMutations, mapActions, mapState } from "vuex";
import { getArtistAlbum } from "@/api/artist";
import { getTrackDetail } from "@/api/track";
import { getAlbum, albumDynamicDetail, likeAAlbum } from "@/api/album";
import { splitSoundtrackAlbumTitle, splitAlbumTitle } from "@/utils/common";
import NProgress from "nprogress";
import { isAccountLoggedIn } from "@/utils/auth";
import { mapMutations, mapActions, mapState } from 'vuex';
import { getArtistAlbum } from '@/api/artist';
import { getTrackDetail } from '@/api/track';
import { getAlbum, albumDynamicDetail, likeAAlbum } from '@/api/album';
import { splitSoundtrackAlbumTitle, splitAlbumTitle } from '@/utils/common';
import NProgress from 'nprogress';
import { isAccountLoggedIn } from '@/utils/auth';
import ExplicitSymbol from "@/components/ExplicitSymbol.vue";
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import ContextMenu from "@/components/ContextMenu.vue";
import TrackList from "@/components/TrackList.vue";
import CoverRow from "@/components/CoverRow.vue";
import Cover from "@/components/Cover.vue";
import Modal from "@/components/Modal.vue";
import ExplicitSymbol from '@/components/ExplicitSymbol.vue';
import ButtonTwoTone from '@/components/ButtonTwoTone.vue';
import ContextMenu from '@/components/ContextMenu.vue';
import TrackList from '@/components/TrackList.vue';
import CoverRow from '@/components/CoverRow.vue';
import Cover from '@/components/Cover.vue';
import Modal from '@/components/Modal.vue';
export default {
name: "Album",
name: 'Album',
components: {
Cover,
ButtonTwoTone,
@ -151,11 +151,16 @@ export default {
Modal,
ContextMenu,
},
beforeRouteUpdate(to, from, next) {
NProgress.start();
this.loadData(to.params.id);
next();
},
data() {
return {
album: {
id: 0,
picUrl: "",
picUrl: '',
artist: {
id: 0,
},
@ -165,27 +170,27 @@ export default {
show: false,
moreAlbums: [],
dynamicDetail: {},
subtitle: "",
title: "",
subtitle: '',
title: '',
};
},
computed: {
...mapState(["player", "data"]),
...mapState(['player', 'data']),
albumTime() {
let time = 0;
this.tracks.map((t) => (time = time + t.dt));
this.tracks.map(t => (time = time + t.dt));
return time;
},
filteredMoreAlbums() {
let moreAlbums = this.moreAlbums.filter((a) => a.id !== this.album.id);
let realAlbums = moreAlbums.filter((a) => a.type === "专辑");
let moreAlbums = this.moreAlbums.filter(a => a.id !== this.album.id);
let realAlbums = moreAlbums.filter(a => a.type === '专辑');
let eps = moreAlbums.filter(
(a) => a.type === "EP" || (a.type === "EP/Single" && a.size > 1)
a => a.type === 'EP' || (a.type === 'EP/Single' && a.size > 1)
);
let restItems = moreAlbums.filter(
(a) =>
realAlbums.find((a1) => a1.id === a.id) === undefined &&
eps.find((a1) => a1.id === a.id) === undefined
a =>
realAlbums.find(a1 => a1.id === a.id) === undefined &&
eps.find(a1 => a1.id === a.id) === undefined
);
if (realAlbums.length === 0) {
return [...realAlbums, ...eps, ...restItems].slice(0, 5);
@ -198,31 +203,31 @@ export default {
this.loadData(this.$route.params.id);
},
methods: {
...mapMutations(["appendTrackToPlayerList"]),
...mapActions(["playFirstTrackOnList", "playTrackOnListByID", "showToast"]),
playAlbumByID(id, trackID = "first") {
...mapMutations(['appendTrackToPlayerList']),
...mapActions(['playFirstTrackOnList', 'playTrackOnListByID', 'showToast']),
playAlbumByID(id, trackID = 'first') {
this.$store.state.player.playAlbumByID(id, trackID);
},
likeAlbum(toast = false) {
if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号");
this.showToast('此操作需要登录网易云账号');
return;
}
likeAAlbum({
id: this.album.id,
t: this.dynamicDetail.isSub ? 0 : 1,
})
.then((data) => {
.then(data => {
if (data.code === 200) {
this.dynamicDetail.isSub = !this.dynamicDetail.isSub;
if (toast === true)
this.showToast(
this.dynamicDetail.isSub ? "已保存到音乐库" : "已从音乐库删除"
this.dynamicDetail.isSub ? '已保存到音乐库' : '已从音乐库删除'
);
}
console.log(data);
})
.catch((error) => {
.catch(error => {
this.showToast(`${error.response.data.message || error}`);
});
},
@ -230,17 +235,17 @@ export default {
let splitTitle = splitSoundtrackAlbumTitle(this.album.name);
let splitTitle2 = splitAlbumTitle(splitTitle.title);
this.title = splitTitle2.title;
if (splitTitle.subtitle !== "" && splitTitle2.subtitle !== "") {
this.subtitle = splitTitle.subtitle + " · " + splitTitle2.subtitle;
if (splitTitle.subtitle !== '' && splitTitle2.subtitle !== '') {
this.subtitle = splitTitle.subtitle + ' · ' + splitTitle2.subtitle;
} else {
this.subtitle =
splitTitle.subtitle === ""
splitTitle.subtitle === ''
? splitTitle2.subtitle
: splitTitle.subtitle;
}
},
loadData(id) {
getAlbum(id).then((data) => {
getAlbum(id).then(data => {
this.album = data.album;
this.tracks = data.songs;
this.formatTitle();
@ -248,19 +253,17 @@ export default {
this.show = true;
// to get explicit mark
let trackIDs = this.tracks.map((t) => t.id);
getTrackDetail(trackIDs.join(",")).then((data) => {
let trackIDs = this.tracks.map(t => t.id);
getTrackDetail(trackIDs.join(',')).then(data => {
this.tracks = data.songs;
});
// get more album by this artist
getArtistAlbum({ id: this.album.artist.id, limit: 100 }).then(
(data) => {
this.moreAlbums = data.hotAlbums;
}
);
getArtistAlbum({ id: this.album.artist.id, limit: 100 }).then(data => {
this.moreAlbums = data.hotAlbums;
});
});
albumDynamicDetail(id).then((data) => {
albumDynamicDetail(id).then(data => {
this.dynamicDetail = data;
});
},
@ -268,11 +271,6 @@ export default {
this.$refs.albumMenu.openMenu(e);
},
},
beforeRouteUpdate(to, from, next) {
NProgress.start();
this.loadData(to.params.id);
next();
},
};
</script>

@ -1,46 +1,46 @@
<template>
<div class="artist" v-show="show">
<div v-show="show" class="artist">
<div class="artist-info">
<div class="head">
<img :src="artist.img1v1Url | resizeImage(1024)" />
</div>
<div>
<div class="name">{{ artist.name }}</div>
<div class="artist">{{ $t("artist.artist") }}</div>
<div class="artist">{{ $t('artist.artist') }}</div>
<div class="statistics">
<a @click="scrollTo('popularTracks')"
>{{ artist.musicSize }} {{ $t("common.songs") }}</a
>{{ artist.musicSize }} {{ $t('common.songs') }}</a
>
·
<a @click="scrollTo('seeMore', 'start')"
>{{ artist.albumSize }} {{ $t("artist.withAlbums") }}</a
>{{ artist.albumSize }} {{ $t('artist.withAlbums') }}</a
>
·
<a @click="scrollTo('mvs')"
>{{ artist.mvSize }} {{ $t("artist.videos") }}</a
>{{ artist.mvSize }} {{ $t('artist.videos') }}</a
>
</div>
<div class="buttons">
<ButtonTwoTone @click.native="playPopularSongs()" :iconClass="`play`">
{{ $t("common.play") }}
<ButtonTwoTone :icon-class="play" @click.native="playPopularSongs()">
{{ $t('common.play') }}
</ButtonTwoTone>
<ButtonTwoTone @click.native="followArtist" color="grey">
<span v-if="artist.followed">{{ $t("artist.following") }}</span>
<span v-else>{{ $t("artist.follow") }}</span>
<ButtonTwoTone color="grey" @click.native="followArtist">
<span v-if="artist.followed">{{ $t('artist.following') }}</span>
<span v-else>{{ $t('artist.follow') }}</span>
</ButtonTwoTone>
</div>
</div>
</div>
<div class="latest-release">
<div class="section-title">{{ $t("artist.latestRelease") }}</div>
<div class="section-title">{{ $t('artist.latestRelease') }}</div>
<div class="release">
<div class="container">
<Cover
:imageUrl="latestRelease.picUrl | resizeImage"
type="album"
:id="latestRelease.id"
:fixedSize="128"
:playButtonSize="30"
:image-url="latestRelease.picUrl | resizeImage"
type="album"
:fixed-size="128"
:play-button-size="30"
/>
<div class="info">
<div class="name">
@ -53,60 +53,60 @@
</div>
<div class="type">
{{ latestRelease.type | formatAlbumType(latestRelease) }} ·
{{ latestRelease.size }} {{ $t("common.songs") }}
{{ latestRelease.size }} {{ $t('common.songs') }}
</div>
</div>
</div>
<div></div>
</div>
</div>
<div class="popular-tracks" id="popularTracks">
<div class="section-title">{{ $t("artist.popularSongs") }}</div>
<div id="popularTracks" class="popular-tracks">
<div class="section-title">{{ $t('artist.popularSongs') }}</div>
<TrackList
:tracks="popularTracks.slice(0, showMorePopTracks ? 24 : 12)"
:type="'tracklist'"
/>
<div class="show-more" id="seeMore">
<div id="seeMore" class="show-more">
<button @click="showMorePopTracks = !showMorePopTracks">
<span v-show="!showMorePopTracks">{{ $t("artist.showMore") }}</span>
<span v-show="showMorePopTracks">{{ $t("artist.showLess") }}</span>
<span v-show="!showMorePopTracks">{{ $t('artist.showMore') }}</span>
<span v-show="showMorePopTracks">{{ $t('artist.showLess') }}</span>
</button>
</div>
</div>
<div class="albums" id="albums" v-if="albums.length !== 0">
<div class="section-title">{{ $t("artist.albums") }}</div>
<div v-if="albums.length !== 0" id="albums" class="albums">
<div class="section-title">{{ $t('artist.albums') }}</div>
<CoverRow
:type="'album'"
:items="albums"
:subText="'releaseYear'"
:showPlayButton="true"
:sub-text="'releaseYear'"
:show-play-button="true"
/>
</div>
<div class="mvs" id="mvs" v-if="mvs.length !== 0">
<div v-if="mvs.length !== 0" id="mvs" class="mvs">
<div class="section-title"
>MVs
<router-link v-show="hasMoreMV" :to="`/artist/${this.artist.id}/mv`">{{
$t("home.seeMore")
$t('home.seeMore')
}}</router-link>
</div>
<MvRow :mvs="mvs" subtitle="publishTime" />
</div>
<div class="eps" v-if="eps.length !== 0">
<div class="section-title">{{ $t("artist.EPsSingles") }}</div>
<div v-if="eps.length !== 0" class="eps">
<div class="section-title">{{ $t('artist.EPsSingles') }}</div>
<CoverRow
:type="'album'"
:items="eps"
:subText="'albumType+releaseYear'"
:showPlayButton="true"
:sub-text="'albumType+releaseYear'"
:show-play-button="true"
/>
</div>
<div class="similar-artists" v-if="similarArtists.length !== 0">
<div v-if="similarArtists.length !== 0" class="similar-artists">
<div class="section-title">相似艺人</div>
<CoverRow
type="artist"
:columnNumber="6"
:column-number="6"
gap="36px 28px"
:items="similarArtists.slice(0, 12)"
/>
@ -115,42 +115,48 @@
</template>
<script>
import { mapMutations, mapActions, mapState } from "vuex";
import { mapMutations, mapActions, mapState } from 'vuex';
import {
getArtist,
getArtistAlbum,
artistMv,
followAArtist,
similarArtists,
} from "@/api/artist";
import { isAccountLoggedIn } from "@/utils/auth";
import NProgress from "nprogress";
} from '@/api/artist';
import { isAccountLoggedIn } from '@/utils/auth';
import NProgress from 'nprogress';
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import TrackList from "@/components/TrackList.vue";
import CoverRow from "@/components/CoverRow.vue";
import Cover from "@/components/Cover.vue";
import MvRow from "@/components/MvRow.vue";
import ButtonTwoTone from '@/components/ButtonTwoTone.vue';
import TrackList from '@/components/TrackList.vue';
import CoverRow from '@/components/CoverRow.vue';
import Cover from '@/components/Cover.vue';
import MvRow from '@/components/MvRow.vue';
export default {
name: "Artist",
name: 'Artist',
components: { Cover, ButtonTwoTone, TrackList, CoverRow, MvRow },
beforeRouteUpdate(to, from, next) {
NProgress.start();
this.artist.img1v1Url =
'https://p1.music.126.net/VnZiScyynLG7atLIZ2YPkw==/18686200114669622.jpg';
this.loadData(to.params.id, next);
},
data() {
return {
show: false,
artist: {
img1v1Url:
"https://p1.music.126.net/VnZiScyynLG7atLIZ2YPkw==/18686200114669622.jpg",
'https://p1.music.126.net/VnZiScyynLG7atLIZ2YPkw==/18686200114669622.jpg',
},
popularTracks: [],
albumsData: [],
latestRelease: {
picUrl: "",
picUrl: '',
publishTime: 0,
id: 0,
name: "",
type: "",
size: "",
name: '',
type: '',
size: '',
},
showMorePopTracks: false,
mvs: [],
@ -159,91 +165,85 @@ export default {
};
},
computed: {
...mapState(["player"]),
...mapState(['player']),
albums() {
return this.albumsData.filter((a) => a.type === "专辑");
return this.albumsData.filter(a => a.type === '专辑');
},
eps() {
return this.albumsData.filter((a) =>
["EP/Single", "EP", "Single"].includes(a.type)
return this.albumsData.filter(a =>
['EP/Single', 'EP', 'Single'].includes(a.type)
);
},
},
created() {
this.loadData(this.$route.params.id);
},
activated() {
if (this.show) {
if (this.artist.id.toString() !== this.$route.params.id) {
this.show = false;
NProgress.start();
this.loadData(this.$route.params.id);
}
}
},
methods: {
...mapMutations(["appendTrackToPlayerList"]),
...mapActions(["playFirstTrackOnList", "playTrackOnListByID"]),
...mapMutations(['appendTrackToPlayerList']),
...mapActions(['playFirstTrackOnList', 'playTrackOnListByID']),
loadData(id, next = undefined) {
getArtist(id).then((data) => {
getArtist(id).then(data => {
this.artist = data.artist;
this.popularTracks = data.hotSongs;
if (next !== undefined) next();
NProgress.done();
this.show = true;
});
getArtistAlbum({ id: id, limit: 200 }).then((data) => {
getArtistAlbum({ id: id, limit: 200 }).then(data => {
this.albumsData = data.hotAlbums;
this.latestRelease = data.hotAlbums[0];
});
artistMv({ id }).then((data) => {
artistMv({ id }).then(data => {
this.mvs = data.mvs;
this.hasMoreMV = data.hasMore;
});
similarArtists(id).then((data) => {
similarArtists(id).then(data => {
this.similarArtists = data.artists;
});
},
goToAlbum(id) {
this.$router.push({
name: "album",
name: 'album',
params: { id },
});
},
playPopularSongs(trackID = "first") {
let trackIDs = this.popularTracks.map((t) => t.id);
playPopularSongs(trackID = 'first') {
let trackIDs = this.popularTracks.map(t => t.id);
this.$store.state.player.replacePlaylist(
trackIDs,
this.artist.id,
"artist",
'artist',
trackID
);
},
followArtist() {
if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号");
this.showToast('此操作需要登录网易云账号');
return;
}
followAArtist({
id: this.artist.id,
t: this.artist.followed ? 0 : 1,
}).then((data) => {
}).then(data => {
if (data.code === 200) this.artist.followed = !this.artist.followed;
});
},
scrollTo(div, block = "center") {
scrollTo(div, block = 'center') {
document.getElementById(div).scrollIntoView({
behavior: "smooth",
behavior: 'smooth',
block,
});
},
},
created() {
this.loadData(this.$route.params.id);
},
activated() {
if (this.show) {
if (this.artist.id.toString() !== this.$route.params.id) {
this.show = false;
NProgress.start();
this.loadData(this.$route.params.id);
}
}
},
beforeRouteUpdate(to, from, next) {
NProgress.start();
this.artist.img1v1Url =
"https://p1.music.126.net/VnZiScyynLG7atLIZ2YPkw==/18686200114669622.jpg";
this.loadData(to.params.id, next);
},
};
</script>

@ -7,26 +7,32 @@
</h1>
<MvRow :mvs="mvs" subtitle="publishTime" />
<div class="load-more">
<ButtonTwoTone v-show="hasMore" @click.native="loadMVs" color="grey">{{
$t("explore.loadMore")
<ButtonTwoTone v-show="hasMore" color="grey" @click.native="loadMVs">{{
$t('explore.loadMore')
}}</ButtonTwoTone>
</div>
</div>
</template>
<script>
import { artistMv, getArtist } from "@/api/artist";
import NProgress from "nprogress";
import { artistMv, getArtist } from '@/api/artist';
import NProgress from 'nprogress';
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import MvRow from "@/components/MvRow.vue";
import ButtonTwoTone from '@/components/ButtonTwoTone.vue';
import MvRow from '@/components/MvRow.vue';
export default {
name: "artistMV",
name: 'artistMV',
components: {
MvRow,
ButtonTwoTone,
},
beforeRouteUpdate(to, from, next) {
NProgress.start();
this.id = to.params.id;
this.loadData();
next();
},
data() {
return {
id: 0,
@ -36,16 +42,30 @@ export default {
mvs: [],
};
},
created() {
this.id = this.$route.params.id;
this.loadData();
},
activated() {
if (this.$route.params.id !== this.id) {
this.id = this.$route.params.id;
this.mvs = [];
this.artist = {};
this.show = false;
this.hasMore = true;
this.loadData();
}
},
methods: {
loadData() {
getArtist(this.id).then((data) => {
getArtist(this.id).then(data => {
this.artist = data.artist;
});
this.loadMVs();
},
loadMVs() {
artistMv({ id: this.id, limit: 100, offset: this.mvs.length }).then(
(data) => {
data => {
this.mvs.push(...data.mvs);
this.hasMore = data.hasMore;
NProgress.done();
@ -54,26 +74,6 @@ export default {
);
},
},
created() {
this.id = this.$route.params.id;
this.loadData();
},
activated() {
if (this.$route.params.id !== this.id) {
this.id = this.$route.params.id;
this.mvs = [];
this.artist = {};
this.show = false;
this.hasMore = true;
this.loadData();
}
},
beforeRouteUpdate(to, from, next) {
NProgress.start();
this.id = to.params.id;
this.loadData();
next();
},
};
</script>

@ -8,20 +8,20 @@
<TrackList
:tracks="dailyTracks"
type="playlist"
dbclickTrackFunc="dailyTracks"
dbclick-track-func="dailyTracks"
/>
</div>
</template>
<script>
import { mapMutations, mapState } from "vuex";
import NProgress from "nprogress";
import { dailyRecommendTracks } from "@/api/playlist";
import { mapMutations, mapState } from 'vuex';
import NProgress from 'nprogress';
import { dailyRecommendTracks } from '@/api/playlist';
import TrackList from "@/components/TrackList.vue";
import TrackList from '@/components/TrackList.vue';
export default {
name: "dailyTracks",
name: 'dailyTracks',
components: {
TrackList,
},
@ -30,6 +30,9 @@ export default {
show: false,
};
},
computed: {
...mapState(['player', 'data', 'dailyTracks']),
},
created() {
if (this.dailyTracks.length === 0) {
NProgress.start();
@ -38,13 +41,10 @@ export default {
this.show = true;
}
},
computed: {
...mapState(["player", "data", "dailyTracks"]),
},
methods: {
...mapMutations(["updateDailyTracks"]),
...mapMutations(['updateDailyTracks']),
loadDailyTracks() {
dailyRecommendTracks().then((result) => {
dailyRecommendTracks().then(result => {
this.updateDailyTracks(result.data.dailySongs);
NProgress.done();
this.show = true;

@ -1,6 +1,6 @@
<template>
<div class="explore">
<h1>{{ $t("explore.explore") }}</h1>
<h1>{{ $t('explore.explore') }}</h1>
<div class="buttons">
<div
v-for="category in settings.enabledPlaylistCategories"
@ -57,29 +57,29 @@
color="grey"
:loading="loadingMore"
@click.native="getPlaylist"
>{{ $t("explore.loadMore") }}</ButtonTwoTone
>{{ $t('explore.loadMore') }}</ButtonTwoTone
>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
import NProgress from "nprogress";
import { mapState, mapMutations } from 'vuex';
import NProgress from 'nprogress';
import {
topPlaylist,
highQualityPlaylist,
recommendPlaylist,
toplists,
} from "@/api/playlist";
import { playlistCategories } from "@/utils/staticData";
} from '@/api/playlist';
import { playlistCategories } from '@/utils/staticData';
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import CoverRow from "@/components/CoverRow.vue";
import SvgIcon from "@/components/SvgIcon.vue";
import ButtonTwoTone from '@/components/ButtonTwoTone.vue';
import CoverRow from '@/components/CoverRow.vue';
import SvgIcon from '@/components/SvgIcon.vue';
export default {
name: "Explore",
name: 'Explore',
components: {
CoverRow,
ButtonTwoTone,
@ -99,38 +99,38 @@ export default {
return {
show: false,
playlists: [],
activeCategory: "全部",
activeCategory: '全部',
loadingMore: false,
showLoadMoreButton: false,
hasMore: true,
allBigCats: ["语种", "风格", "场景", "情感", "主题"],
allBigCats: ['语种', '风格', '场景', '情感', '主题'],
showCatOptions: false,
};
},
computed: {
...mapState(["settings"]),
...mapState(['settings']),
subText() {
if (this.activeCategory === "排行榜") return "updateFrequency";
if (this.activeCategory === "推荐歌单") return "copywriter";
return "none";
if (this.activeCategory === '排行榜') return 'updateFrequency';
if (this.activeCategory === '推荐歌单') return 'copywriter';
return 'none';
},
},
activated() {
this.loadData();
},
methods: {
...mapMutations(["togglePlaylistCategory"]),
...mapMutations(['togglePlaylistCategory']),
loadData() {
if (!this.show) NProgress.start();
this.activeCategory =
this.$route.query.category === undefined
? "全部"
? '全部'
: this.$route.query.category;
this.getPlaylist();
},
goToCategory(Category) {
this.showCatOptions = false;
this.$router.push({ path: "/explore?category=" + Category });
this.$router.push({ path: '/explore?category=' + Category });
},
updatePlaylist(playlists) {
this.playlists.push(...playlists);
@ -141,19 +141,19 @@ export default {
},
getPlaylist() {
this.loadingMore = true;
if (this.activeCategory === "推荐歌单") {
if (this.activeCategory === '推荐歌单') {
return this.getRecommendPlayList();
}
if (this.activeCategory === "精品歌单") {
if (this.activeCategory === '精品歌单') {
return this.getHighQualityPlaylist();
}
if (this.activeCategory === "排行榜") {
if (this.activeCategory === '排行榜') {
return this.getTopLists();
}
return this.getTopPlayList();
},
getRecommendPlayList() {
recommendPlaylist({ limit: 100 }).then((data) => {
recommendPlaylist({ limit: 100 }).then(data => {
this.playlists = [];
this.updatePlaylist(data.result);
});
@ -162,13 +162,13 @@ export default {
let playlists = this.playlists;
let before =
playlists.length !== 0 ? playlists[playlists.length - 1].updateTime : 0;
highQualityPlaylist({ limit: 50, before }).then((data) => {
highQualityPlaylist({ limit: 50, before }).then(data => {
this.updatePlaylist(data.playlists);
this.hasMore = data.more;
});
},
getTopLists() {
toplists().then((data) => {
toplists().then(data => {
this.playlists = [];
this.updatePlaylist(data.list);
});
@ -177,13 +177,13 @@ export default {
topPlaylist({
cat: this.activeCategory,
offset: this.playlists.length,
}).then((data) => {
}).then(data => {
this.updatePlaylist(data.playlists);
this.hasMore = data.more;
});
},
getCatsByBigCat(name) {
return playlistCategories.filter((c) => c.bigCat === name);
return playlistCategories.filter(c => c.bigCat === name);
},
toggleCat(name) {
this.togglePlaylistCategory(name);

@ -1,25 +1,25 @@
<template>
<div class="home" v-show="show">
<div class="index-row" v-if="settings.showPlaylistsByAppleMusic !== false">
<div v-show="show" class="home">
<div v-if="settings.showPlaylistsByAppleMusic !== false" class="index-row">
<div class="title"> by Apple Music </div>
<CoverRow
:type="'playlist'"
:items="byAppleMusic"
:subText="'appleMusic'"
:imageSize="1024"
sub-text="appleMusic"
:image-size="1024"
/>
</div>
<div class="index-row">
<div class="title">
{{ $t("home.recommendPlaylist") }}
{{ $t('home.recommendPlaylist') }}
<router-link to="/explore?category=推荐歌单">{{
$t("home.seeMore")
$t('home.seeMore')
}}</router-link>
</div>
<CoverRow
:type="'playlist'"
:items="recommendPlaylist.items"
:subText="'copywriter'"
sub-text="copywriter"
/>
</div>
<div class="index-row">
@ -30,51 +30,55 @@
</div>
</div>
<div class="index-row">
<div class="title">{{ $t("home.recommendArtist") }}</div>
<div class="title">{{ $t('home.recommendArtist') }}</div>
<CoverRow
type="artist"
:columnNumber="6"
:column-number="6"
:items="recommendArtists.items"
/>
</div>
<div class="index-row">
<div class="title">
{{ $t("home.newAlbum") }}
<router-link to="/new-album">{{ $t("home.seeMore") }}</router-link>
{{ $t('home.newAlbum') }}
<router-link to="/new-album">{{ $t('home.seeMore') }}</router-link>
</div>
<CoverRow type="album" :items="newReleasesAlbum.items" subText="artist" />
<CoverRow
type="album"
:items="newReleasesAlbum.items"
sub-text="artist"
/>
</div>
<div class="index-row">
<div class="title">
{{ $t("home.charts") }}
{{ $t('home.charts') }}
<router-link to="/explore?category=排行榜">{{
$t("home.seeMore")
$t('home.seeMore')
}}</router-link>
</div>
<CoverRow
type="playlist"
:items="topList.items"
:subText="'updateFrequency'"
:imageSize="1024"
sub-text="updateFrequency"
:image-size="1024"
/>
</div>
</div>
</template>
<script>
import { toplists, recommendPlaylist } from "@/api/playlist";
import { toplistOfArtists } from "@/api/artist";
import { byAppleMusic } from "@/utils/staticData";
import { countDBSize } from "@/utils/db";
import { newAlbums } from "@/api/album";
import NProgress from "nprogress";
import { mapState } from "vuex";
import CoverRow from "@/components/CoverRow.vue";
import FMCard from "@/components/FMCard.vue";
import DailyTracksCard from "@/components/DailyTracksCard.vue";
import { toplists, recommendPlaylist } from '@/api/playlist';
import { toplistOfArtists } from '@/api/artist';
import { byAppleMusic } from '@/utils/staticData';
import { countDBSize } from '@/utils/db';
import { newAlbums } from '@/api/album';
import NProgress from 'nprogress';
import { mapState } from 'vuex';
import CoverRow from '@/components/CoverRow.vue';
import FMCard from '@/components/FMCard.vue';
import DailyTracksCard from '@/components/DailyTracksCard.vue';
export default {
name: "Home",
name: 'Home',
components: { CoverRow, FMCard, DailyTracksCard },
data() {
return {
@ -92,28 +96,31 @@ export default {
};
},
computed: {
...mapState(["settings"]),
...mapState(['settings']),
byAppleMusic() {
return byAppleMusic;
},
},
activated() {
this.loadData();
},
methods: {
loadData() {
if (!this.show) NProgress.start();
recommendPlaylist({
limit: 10,
}).then((data) => {
}).then(data => {
this.recommendPlaylist.items = data.result;
NProgress.done();
this.show = true;
});
newAlbums({
area: "EA",
area: 'EA',
limit: 10,
}).then((data) => {
}).then(data => {
this.newReleasesAlbum.items = data.albums;
});
toplistOfArtists(2).then((data) => {
toplistOfArtists(2).then(data => {
let indexs = [];
while (indexs.length < 6) {
let tmp = ~~(Math.random() * 100);
@ -124,17 +131,14 @@ export default {
indexs.includes(index)
);
});
toplists().then((data) => {
this.topList.items = data.list.filter((l) =>
toplists().then(data => {
this.topList.items = data.list.filter(l =>
this.topList.ids.includes(l.id)
);
});
countDBSize();
},
},
activated() {
this.loadData();
},
};
</script>

@ -6,36 +6,36 @@
<img src="/img/logos/lastfm.png" />
</div>
<div class="message">{{ message }}</div>
<button @click="close" v-show="done"> </button>
<button v-show="done" @click="close"> </button>
</div>
</template>
<script>
import { authGetSession } from "@/api/lastfm";
import { authGetSession } from '@/api/lastfm';
export default {
name: "lastfmCallback",
name: 'LastfmCallback',
data() {
return { message: "请稍等...", done: false };
return { message: '请稍等...', done: false };
},
created() {
const token = new URLSearchParams(window.location.search).get("token");
const token = new URLSearchParams(window.location.search).get('token');
if (!token) {
this.message = "连接失败请重试或联系开发者无Token";
this.message = '连接失败请重试或联系开发者无Token';
this.done = true;
return;
}
console.log(token);
authGetSession(token).then((result) => {
authGetSession(token).then(result => {
console.log(result);
if (!result.data.session) {
this.message = "连接失败请重试或联系开发者无Session";
this.message = '连接失败请重试或联系开发者无Session';
this.done = true;
return;
}
localStorage.setItem("lastfm", JSON.stringify(result.data.session));
this.$store.commit("updateLastfm", result.data.session);
this.message = "已成功连接到 Last.fm";
localStorage.setItem('lastfm', JSON.stringify(result.data.session));
this.$store.commit('updateLastfm', result.data.session);
this.message = '已成功连接到 Last.fm';
this.done = true;
});
},

@ -14,8 +14,8 @@
>
<div class="container" :class="{ active: activeCard === 1 }">
<div class="title-info">
<div class="title">{{ $t("login.loginText") }}</div>
<div class="info">{{ $t("login.accessToAll") }}</div>
<div class="title">{{ $t('login.loginText') }}</div>
<div class="info">{{ $t('login.accessToAll') }}</div>
</div>
<svg-icon icon-class="arrow-right"></svg-icon>
</div>
@ -28,8 +28,8 @@
>
<div class="container" :class="{ active: activeCard === 2 }">
<div class="title-info">
<div class="title">{{ $t("login.search") }}</div>
<div class="info">{{ $t("login.readonly") }}</div>
<div class="title">{{ $t('login.search') }}</div>
<div class="info">{{ $t('login.readonly') }}</div>
</div>
<svg-icon icon-class="arrow-right"></svg-icon>
</div>
@ -39,12 +39,12 @@
</template>
<script>
import NProgress from "nprogress";
import NProgress from 'nprogress';
import SvgIcon from "@/components/SvgIcon.vue";
import SvgIcon from '@/components/SvgIcon.vue';
export default {
name: "Login",
name: 'Login',
components: {
SvgIcon,
},
@ -58,7 +58,7 @@ export default {
},
methods: {
goTo(path) {
this.$router.push({ path: "/login/" + path });
this.$router.push({ path: '/login/' + path });
},
},
};

@ -3,26 +3,26 @@
<div class="section-1">
<img src="/img/logos/netease-music.png" />
</div>
<div class="title">{{ $t("login.loginText") }}</div>
<div class="title">{{ $t('login.loginText') }}</div>
<div class="section-2">
<div class="input-box" v-show="mode === 'phone'">
<div v-show="mode === 'phone'" class="input-box">
<div class="container" :class="{ active: inputFocus === 'phone' }">
<svg-icon icon-class="mobile" />
<div class="inputs">
<input
id="countryCode"
v-model="countryCode"
:placeholder="
inputFocus === 'phone' ? '' : $t('login.countryCode')
"
v-model="countryCode"
@focus="inputFocus = 'phone'"
@blur="inputFocus = ''"
@keyup.enter="login"
/>
<input
id="phoneNumber"
:placeholder="inputFocus === 'phone' ? '' : $t('login.phone')"
v-model="phoneNumber"
:placeholder="inputFocus === 'phone' ? '' : $t('login.phone')"
@focus="inputFocus = 'phone'"
@blur="inputFocus = ''"
@keyup.enter="login"
@ -30,15 +30,15 @@
</div>
</div>
</div>
<div class="input-box" v-show="mode === 'email'">
<div v-show="mode === 'email'" class="input-box">
<div class="container" :class="{ active: inputFocus === 'email' }">
<svg-icon icon-class="mail" />
<div class="inputs">
<input
type="email"
id="email"
:placeholder="inputFocus === 'email' ? '' : $t('login.email')"
v-model="email"
type="email"
:placeholder="inputFocus === 'email' ? '' : $t('login.email')"
@focus="inputFocus = 'email'"
@blur="inputFocus = ''"
@keyup.enter="login"
@ -51,12 +51,12 @@
<svg-icon icon-class="lock" />
<div class="inputs">
<input
type="password"
id="password"
v-model="password"
type="password"
:placeholder="
inputFocus === 'password' ? '' : $t('login.password')
"
v-model="password"
@focus="inputFocus = 'password'"
@blur="inputFocus = ''"
@keyup.enter="login"
@ -66,8 +66,8 @@
</div>
</div>
<div class="confirm">
<button @click="login" v-show="!processing">
{{ $t("login.login") }}
<button v-show="!processing" @click="login">
{{ $t('login.login') }}
</button>
<button v-show="processing" class="loading" disabled>
<span></span>
@ -77,10 +77,10 @@
</div>
<div class="other-login">
<a v-show="mode === 'phone'" @click="mode = 'email'">{{
$t("login.loginWithEmail")
$t('login.loginWithEmail')
}}</a>
<a v-show="mode === 'email'" @click="mode = 'phone'">{{
$t("login.loginWithPhone")
$t('login.loginWithPhone')
}}</a>
</div>
<div
@ -91,25 +91,25 @@
</template>
<script>
import NProgress from "nprogress";
import { loginWithPhone, loginWithEmail } from "@/api/auth";
import { setCookies } from "@/utils/auth";
import md5 from "crypto-js/md5";
import { mapMutations } from "vuex";
import nativeAlert from "@/utils/nativeAlert";
import NProgress from 'nprogress';
import { loginWithPhone, loginWithEmail } from '@/api/auth';
import { setCookies } from '@/utils/auth';
import md5 from 'crypto-js/md5';
import { mapMutations } from 'vuex';
import nativeAlert from '@/utils/nativeAlert';
export default {
name: "Login",
name: 'Login',
data() {
return {
processing: false,
mode: "email",
countryCode: "+86",
phoneNumber: "",
email: "",
password: "",
smsCode: "",
inputFocus: "",
mode: 'email',
countryCode: '+86',
phoneNumber: '',
email: '',
password: '',
smsCode: '',
inputFocus: '',
};
},
computed: {
@ -118,20 +118,20 @@ export default {
},
},
created() {
if (this.$route.query.mode === "phone") {
this.mode = "phone";
if (this.$route.query.mode === 'phone') {
this.mode = 'phone';
}
NProgress.done();
},
methods: {
...mapMutations(["updateData"]),
...mapMutations(['updateData']),
validatePhone() {
if (
this.countryCode === "" ||
this.phone === "" ||
this.password === ""
this.countryCode === '' ||
this.phone === '' ||
this.password === ''
) {
nativeAlert("国家区号或手机号不正确");
nativeAlert('国家区号或手机号不正确');
this.processing = false;
return false;
}
@ -140,27 +140,27 @@ export default {
validateEmail() {
const emailReg = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (
this.email === "" ||
this.password === "" ||
this.email === '' ||
this.password === '' ||
!emailReg.test(this.email)
) {
nativeAlert("邮箱不正确");
nativeAlert('邮箱不正确');
return false;
}
return true;
},
login() {
if (this.mode === "phone") {
if (this.mode === 'phone') {
this.processing = this.validatePhone();
if (!this.processing) return;
loginWithPhone({
countrycode: this.countryCode.replace("+", "").replace(/\s/g, ""),
phone: this.phoneNumber.replace(/\s/g, ""),
password: "fakePassword",
countrycode: this.countryCode.replace('+', '').replace(/\s/g, ''),
phone: this.phoneNumber.replace(/\s/g, ''),
password: 'fakePassword',
md5_password: md5(this.password).toString(),
})
.then(this.handleLoginResponse)
.catch((error) => {
.catch(error => {
this.processing = false;
nativeAlert(`发生错误,请检查你的账号密码是否正确\n${error}`);
});
@ -168,12 +168,12 @@ export default {
this.processing = this.validateEmail();
if (!this.processing) return;
loginWithEmail({
email: this.email.replace(/\s/g, ""),
password: "fakePassword",
email: this.email.replace(/\s/g, ''),
password: 'fakePassword',
md5_password: md5(this.password).toString(),
})
.then(this.handleLoginResponse)
.catch((error) => {
.catch(error => {
this.processing = false;
nativeAlert(`发生错误,请检查你的账号密码是否正确\n${error}`);
});
@ -186,13 +186,13 @@ export default {
}
if (data.code === 200) {
setCookies(data.cookie);
this.updateData({ key: "user", value: data.profile });
this.updateData({ key: "loginMode", value: "account" });
this.$router.push({ path: "/library" });
this.updateData({ key: 'user', value: data.profile });
this.updateData({ key: 'loginMode', value: 'account' });
this.$router.push({ path: '/library' });
} else {
this.processing = false;
console.log(data.msg);
nativeAlert(data.msg ?? data.message ?? "账号或密码错误,请检查");
nativeAlert(data.msg ?? data.message ?? '账号或密码错误,请检查');
}
},
},

@ -1,15 +1,15 @@
<template>
<div class="login">
<div>
<div class="title">{{ $t("login.usernameLogin") }}</div>
<div class="sestion">
<div class="title">{{ $t('login.usernameLogin') }}</div>
<div class="section">
<div class="search-box">
<div class="container">
<svg-icon icon-class="search" />
<div class="input">
<input
:placeholder="$t('login.searchHolder')"
v-model="keyword"
:placeholder="$t('login.searchHolder')"
@keydown.enter="throttleSearch"
/>
</div>
@ -17,17 +17,17 @@
</div>
</div>
<div class="sestion">
<div class="name" v-show="activeUser.nickname === undefined">
{{ $t("login.enterTip") }}
<div v-show="activeUser.nickname === undefined" class="name">
{{ $t('login.enterTip') }}
</div>
<div class="name" v-show="activeUser.nickname !== undefined">
{{ $t("login.choose") }}
<div v-show="activeUser.nickname !== undefined" class="name">
{{ $t('login.choose') }}
</div>
<div class="user-list">
<div
class="user"
v-for="user in result"
:key="user.id"
class="user"
:class="{ active: user.nickname === activeUser.nickname }"
@click="activeUser = user"
>
@ -39,32 +39,32 @@
</div>
</div>
<ButtonTwoTone
@click.native="confirm"
v-show="activeUser.nickname !== undefined"
@click.native="confirm"
>
{{ $t("login.confirm") }}
{{ $t('login.confirm') }}
</ButtonTwoTone>
</div>
</div>
</template>
<script>
import { mapMutations } from "vuex";
import NProgress from "nprogress";
import { search } from "@/api/others";
import { userPlaylist } from "@/api/user";
import { throttle } from "@/utils/common";
import { mapMutations } from 'vuex';
import NProgress from 'nprogress';
import { search } from '@/api/others';
import { userPlaylist } from '@/api/user';
import { throttle } from '@/utils/common';
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import ButtonTwoTone from '@/components/ButtonTwoTone.vue';
export default {
name: "loginUsername",
name: 'LoginUsername',
components: {
ButtonTwoTone,
},
data() {
return {
keyword: "",
keyword: '',
result: [],
activeUser: {},
};
@ -73,26 +73,26 @@ export default {
NProgress.done();
},
methods: {
...mapMutations(["updateData"]),
...mapMutations(['updateData']),
search() {
if (!this.keyword) return;
search({ keywords: this.keyword, limit: 9, type: 1002 }).then((data) => {
search({ keywords: this.keyword, limit: 9, type: 1002 }).then(data => {
this.result = data.result.userprofiles;
this.activeUser = this.result[0];
});
},
confirm() {
this.updateData({ key: "user", value: this.activeUser });
this.updateData({ key: "loginMode", value: "username" });
this.updateData({ key: 'user', value: this.activeUser });
this.updateData({ key: 'loginMode', value: 'username' });
userPlaylist({
uid: this.activeUser.userId,
limit: 1,
}).then((data) => {
}).then(data => {
this.updateData({
key: "likedSongPlaylistID",
key: 'likedSongPlaylistID',
value: data.playlist[0].id,
});
this.$router.push({ path: "/library" });
this.$router.push({ path: '/library' });
});
},
throttleSearch: throttle(function () {

@ -215,7 +215,7 @@ export default {
return this.player.currentTrack?.al?.picUrl + '?param=1024y1024';
},
bgImageUrl() {
return this.player.currentTrack?.al?.picUrl + "?param=500y500";
return this.player.currentTrack?.al?.picUrl + '?param=500y500';
},
progress: {
get() {

@ -13,8 +13,8 @@
{{ mv.data.name }}
<div class="like-button">
<button-icon @click.native="likeMV">
<svg-icon icon-class="heart-solid" v-if="mv.subed"></svg-icon>
<svg-icon icon-class="heart" v-else></svg-icon>
<svg-icon v-if="mv.subed" icon-class="heart-solid"></svg-icon>
<svg-icon v-else icon-class="heart"></svg-icon>
</button-icon>
</div>
</div>
@ -25,108 +25,108 @@
</div>
</div>
<div class="more-video">
<div class="section-title">{{ $t("mv.moreVideo") }}</div>
<div class="section-title">{{ $t('mv.moreVideo') }}</div>
<MvRow :mvs="simiMvs" />
</div>
</div>
</template>
<script>
import { mvDetail, mvUrl, simiMv, likeAMV } from "@/api/mv";
import { isAccountLoggedIn } from "@/utils/auth";
import NProgress from "nprogress";
import "@/assets/css/plyr.css";
import Plyr from "plyr";
import { mvDetail, mvUrl, simiMv, likeAMV } from '@/api/mv';
import { isAccountLoggedIn } from '@/utils/auth';
import NProgress from 'nprogress';
import '@/assets/css/plyr.css';
import Plyr from 'plyr';
import ButtonIcon from "@/components/ButtonIcon.vue";
import MvRow from "@/components/MvRow.vue";
import { mapActions } from "vuex";
import ButtonIcon from '@/components/ButtonIcon.vue';
import MvRow from '@/components/MvRow.vue';
import { mapActions } from 'vuex';
export default {
name: "mv",
name: 'mv',
components: {
MvRow,
ButtonIcon,
},
beforeRouteUpdate(to, from, next) {
this.getData(to.params.id);
next();
},
data() {
return {
mv: {
url: "",
url: '',
data: {
name: "",
artistName: "",
playCount: "",
publishTime: "",
name: '',
artistName: '',
playCount: '',
publishTime: '',
},
},
player: null,
simiMvs: [],
};
},
mounted() {
let videoOptions = {
settings: ['quality'],
autoplay: false,
quality: {
default: 1080,
options: [1080, 720, 480, 240],
},
};
if (this.$route.query.autoplay === 'true') videoOptions.autoplay = true;
this.player = new Plyr(this.$refs.videoPlayer, videoOptions);
this.player.volume = this.$store.state.player.volume;
this.player.on('playing', () => {
this.$store.state.player.pause();
});
this.getData(this.$route.params.id);
console.log('网易云你这mv音频码率也太糊了吧🙄');
},
methods: {
...mapActions(["showToast"]),
...mapActions(['showToast']),
getData(id) {
mvDetail(id).then((data) => {
mvDetail(id).then(data => {
this.mv = data;
let requests = data.data.brs.map((br) => {
let requests = data.data.brs.map(br => {
return mvUrl({ id, r: br.br });
});
Promise.all(requests).then((results) => {
let sources = results.map((result) => {
Promise.all(requests).then(results => {
let sources = results.map(result => {
return {
src: result.data.url.replace(/^http:/, "https:"),
type: "video/mp4",
src: result.data.url.replace(/^http:/, 'https:'),
type: 'video/mp4',
size: result.data.r,
};
});
this.player.source = {
type: "video",
type: 'video',
title: this.mv.data.name,
sources: sources,
poster: this.mv.data.cover.replace(/^http:/, "https:"),
poster: this.mv.data.cover.replace(/^http:/, 'https:'),
};
NProgress.done();
});
});
simiMv(id).then((data) => {
simiMv(id).then(data => {
this.simiMvs = data.mvs;
});
},
likeMV() {
if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号");
this.showToast('此操作需要登录网易云账号');
return;
}
likeAMV({
mvid: this.mv.data.id,
t: this.mv.subed ? 0 : 1,
}).then((data) => {
}).then(data => {
if (data.code === 200) this.mv.subed = !this.mv.subed;
});
},
},
mounted() {
let videoOptions = {
settings: ["quality"],
autoplay: false,
quality: {
default: 1080,
options: [1080, 720, 480, 240],
},
};
if (this.$route.query.autoplay === "true") videoOptions.autoplay = true;
this.player = new Plyr(this.$refs.videoPlayer, videoOptions);
this.player.volume = this.$store.state.player.volume;
this.player.on("playing", () => {
this.$store.state.player.pause();
});
this.getData(this.$route.params.id);
console.log("网易云你这mv音频码率也太糊了吧🙄");
},
beforeRouteUpdate(to, from, next) {
this.getData(to.params.id);
next();
},
};
</script>
<style lang="scss" scoped>

@ -1,12 +1,12 @@
<template>
<div class="newAlbum">
<h1>{{ $t("home.newAlbum") }}</h1>
<h1>{{ $t('home.newAlbum') }}</h1>
<div class="playlist-row">
<div class="playlists">
<CoverRow
type="album"
:items="albums"
subText="artist"
sub-text="artist"
:show-play-button="true"
/>
</div>
@ -15,25 +15,25 @@
</template>
<script>
import { newAlbums } from "@/api/album";
import NProgress from "nprogress";
import { newAlbums } from '@/api/album';
import NProgress from 'nprogress';
import CoverRow from "@/components/CoverRow.vue";
import CoverRow from '@/components/CoverRow.vue';
export default {
components: {
CoverRow,
},
data() {
return {
albums: [],
};
},
components: {
CoverRow,
},
created() {
newAlbums({
area: "EA",
area: 'EA',
limit: 100,
}).then((data) => {
}).then(data => {
this.albums = data.albums;
NProgress.done();
});

@ -1,37 +1,37 @@
<template>
<div class="next-tracks">
<h1>{{ $t("next.nowPlaying") }}</h1>
<h1>{{ $t('next.nowPlaying') }}</h1>
<TrackList
:tracks="[currentTrack]"
type="playlist"
dbclickTrackFunc="none"
dbclick-track-func="none"
/>
<h1 v-show="playNextList.length > 0"></h1>
<TrackList
v-show="playNextList.length > 0"
:tracks="playNextTracks"
type="playlist"
:highlightPlayingTrack="false"
dbclickTrackFunc="playTrackOnListByID"
itemKey="id+index"
v-show="playNextList.length > 0"
:highlight-playing-track="false"
dbclick-track-func="playTrackOnListByID"
item-key="id+index"
/>
<h1>{{ $t("next.nextUp") }}</h1>
<h1>{{ $t('next.nextUp') }}</h1>
<TrackList
:tracks="filteredTracks"
type="playlist"
:highlightPlayingTrack="false"
dbclickTrackFunc="playTrackOnListByID"
:highlight-playing-track="false"
dbclick-track-func="playTrackOnListByID"
/>
</div>
</template>
<script>
import { mapState, mapActions } from "vuex";
import { getTrackDetail } from "@/api/track";
import TrackList from "@/components/TrackList.vue";
import { mapState, mapActions } from 'vuex';
import { getTrackDetail } from '@/api/track';
import TrackList from '@/components/TrackList.vue';
export default {
name: "Next",
name: 'Next',
components: {
TrackList,
},
@ -41,7 +41,7 @@ export default {
};
},
computed: {
...mapState(["player"]),
...mapState(['player']),
currentTrack() {
return this.player.currentTrack;
},
@ -53,14 +53,14 @@ export default {
this.player.current + 1,
this.player.current + 100
);
return this.tracks.filter((t) => trackIDs.includes(t.id));
return this.tracks.filter(t => trackIDs.includes(t.id));
},
playNextList() {
return this.player.playNextList;
},
playNextTracks() {
return this.playNextList.map((tid) => {
return this.tracks.find((t) => t.id === tid);
return this.playNextList.map(tid => {
return this.tracks.find(t => t.id === tid);
});
},
},
@ -75,8 +75,11 @@ export default {
this.loadTracks();
},
},
activated() {
this.loadTracks();
},
methods: {
...mapActions(["playTrackOnListByID"]),
...mapActions(['playTrackOnListByID']),
loadTracks() {
// 100
let trackIDs = this.player.list.slice(
@ -88,21 +91,18 @@ export default {
trackIDs.push(...this.playNextList);
//
let loadedTrackIDs = this.tracks.map((t) => t.id);
let loadedTrackIDs = this.tracks.map(t => t.id);
if (trackIDs.length > 0) {
getTrackDetail(trackIDs.join(",")).then((data) => {
getTrackDetail(trackIDs.join(',')).then(data => {
let newTracks = data.songs.filter(
(t) => !loadedTrackIDs.includes(t.id)
t => !loadedTrackIDs.includes(t.id)
);
this.tracks.push(...newTracks);
});
}
},
},
activated() {
this.loadTracks();
},
};
</script>

@ -1,31 +1,30 @@
<template>
<div v-show="show">
<div
class="playlist-info"
v-if="specialPlaylistInfo === undefined && !isLikeSongsPage"
class="playlist-info"
>
<Cover
:imageUrl="playlist.coverImgUrl | resizeImage(1024)"
:showPlayButton="true"
:alwaysShowShadow="true"
:clickCoverToPlay="true"
:fixedSize="288"
type="playlist"
:id="playlist.id"
:coverHover="false"
:playButtonSize="18"
:image-url="playlist.coverImgUrl | resizeImage(1024)"
:show-play-button="true"
:always-show-shadow="true"
:click-cover-to-play="true"
:fixed-size="288"
type="playlist"
:cover-hover="false"
:play-button-size="18"
@click.right.native="openMenu"
/>
<div class="info">
<div class="title" @click.right="openMenu"
><span class="lock-icon" v-if="playlist.privacy === 10">
><span v-if="playlist.privacy === 10" class="lock-icon">
<svg-icon icon-class="lock" /></span
>{{ playlist.name }}</div
>
<div class="artist">
Playlist by
<span
style="font-weight: 600"
v-if="
[
5277771961,
@ -35,6 +34,7 @@
5278068783,
].includes(playlist.id)
"
style="font-weight: 600"
>Apple Music</span
>
<a
@ -45,48 +45,48 @@
>
</div>
<div class="date-and-count">
{{ $t("playlist.updatedAt") }}
{{ $t('playlist.updatedAt') }}
{{ playlist.updateTime | formatDate }} · {{ playlist.trackCount }}
{{ $t("common.songs") }}
{{ $t('common.songs') }}
</div>
<div class="description" @click="showFullDescription = true">
{{ playlist.description }}
</div>
<div class="buttons">
<ButtonTwoTone @click.native="playPlaylistByID()" :iconClass="`play`">
{{ $t("common.play") }}
<ButtonTwoTone icon-class="play" @click.native="playPlaylistByID()">
{{ $t('common.play') }}
</ButtonTwoTone>
<ButtonTwoTone
v-if="playlist.creator.userId !== data.user.userId"
:iconClass="playlist.subscribed ? 'heart-solid' : 'heart'"
:iconButton="true"
:horizontalPadding="0"
:icon-class="playlist.subscribed ? 'heart-solid' : 'heart'"
:icon-button="true"
:horizontal-padding="0"
:color="playlist.subscribed ? 'blue' : 'grey'"
:textColor="playlist.subscribed ? '#335eea' : ''"
:backgroundColor="
:text-color="playlist.subscribed ? '#335eea' : ''"
:background-color="
playlist.subscribed ? 'var(--color-secondary-bg)' : ''
"
@click.native="likePlaylist"
>
</ButtonTwoTone>
<ButtonTwoTone
iconClass="more"
:iconButton="true"
:horizontalPadding="0"
icon-class="more"
:icon-button="true"
:horizontal-padding="0"
color="grey"
@click.native="openMenu"
>
</ButtonTwoTone>
</div>
</div>
<div class="search-box" v-if="displaySearchInPlaylist">
<div v-if="displaySearchInPlaylist" class="search-box">
<div class="container" :class="{ active: inputFocus }">
<svg-icon icon-class="search" />
<div class="input">
<input
:placeholder="inputFocus ? '' : $t('playlist.search')"
v-model.trim="inputSearchKeyWords"
v-focus="displaySearchInPlaylist"
:placeholder="inputFocus ? '' : $t('playlist.search')"
@input="inputDebounce()"
@focus="inputFocus = true"
@blur="inputFocus = false"
@ -95,7 +95,7 @@
</div>
</div>
</div>
<div class="special-playlist" v-if="specialPlaylistInfo !== undefined">
<div v-if="specialPlaylistInfo !== undefined" class="special-playlist">
<div
class="title"
:class="specialPlaylistInfo.gradient"
@ -111,29 +111,29 @@
<div class="buttons">
<ButtonTwoTone
class="play-button"
@click.native="playPlaylistByID()"
iconClass="play"
icon-class="play"
color="grey"
@click.native="playPlaylistByID()"
>
{{ $t("common.play") }}
{{ $t('common.play') }}
</ButtonTwoTone>
<ButtonTwoTone
v-if="playlist.creator.userId !== data.user.userId"
:iconClass="playlist.subscribed ? 'heart-solid' : 'heart'"
:iconButton="true"
:horizontalPadding="0"
:icon-class="playlist.subscribed ? 'heart-solid' : 'heart'"
:icon-button="true"
:horizontal-padding="0"
:color="playlist.subscribed ? 'blue' : 'grey'"
:textColor="playlist.subscribed ? '#335eea' : ''"
:backgroundColor="
:text-color="playlist.subscribed ? '#335eea' : ''"
:background-color="
playlist.subscribed ? 'var(--color-secondary-bg)' : ''
"
@click.native="likePlaylist"
>
</ButtonTwoTone>
<ButtonTwoTone
iconClass="more"
:iconButton="true"
:horizontalPadding="0"
icon-class="more"
:icon-button="true"
:horizontal-padding="0"
color="grey"
@click.native="openMenu"
>
@ -141,19 +141,19 @@
</div>
</div>
<div class="user-info" v-if="isLikeSongsPage">
<div v-if="isLikeSongsPage" class="user-info">
<h1>
<img class="avatar" :src="data.user.avatarUrl | resizeImage" />{{
data.user.nickname
}}{{ $t("library.sLikedSongs") }}
}}{{ $t('library.sLikedSongs') }}
</h1>
</div>
<TrackList
:tracks="filteredTracks"
:type="'playlist'"
:id="playlist.id"
:extraContextMenuItem="
:tracks="filteredTracks"
type="playlist"
:extra-context-menu-item="
isUserOwnPlaylist ? ['removeTrackFromPlaylist'] : []
"
/>
@ -161,27 +161,27 @@
<Modal
:show="showFullDescription"
:close="() => (showFullDescription = false)"
:showFooter="false"
:clickOutsideHide="true"
:show-footer="false"
:click-outside-hide="true"
title="歌单介绍"
>{{ playlist.description }}</Modal
>
<ContextMenu ref="playlistMenu">
<div class="item">{{ $t("contextMenu.playNext") }}</div>
<div class="item">{{ $t('contextMenu.playNext') }}</div>
<div class="item" @click="likePlaylist(true)">{{
playlist.subscribed ? "从音乐库删除" : "保存到音乐库"
playlist.subscribed ? '从音乐库删除' : '保存到音乐库'
}}</div>
<div class="item" @click="searchInPlaylist()"></div>
<div
class="item"
v-if="playlist.creator.userId === data.user.userId"
class="item"
@click="editPlaylist"
>编辑歌单信息</div
>
<div
class="item"
v-if="playlist.creator.userId === data.user.userId"
class="item"
@click="deletePlaylist"
>删除歌单</div
>
@ -190,112 +190,112 @@
</template>
<script>
import { mapMutations, mapActions, mapState } from "vuex";
import NProgress from "nprogress";
import { mapMutations, mapActions, mapState } from 'vuex';
import NProgress from 'nprogress';
import {
getPlaylistDetail,
subscribePlaylist,
deletePlaylist,
} from "@/api/playlist";
import { getTrackDetail } from "@/api/track";
import { isAccountLoggedIn } from "@/utils/auth";
import nativeAlert from "@/utils/nativeAlert";
} from '@/api/playlist';
import { getTrackDetail } from '@/api/track';
import { isAccountLoggedIn } from '@/utils/auth';
import nativeAlert from '@/utils/nativeAlert';
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import ContextMenu from "@/components/ContextMenu.vue";
import TrackList from "@/components/TrackList.vue";
import Cover from "@/components/Cover.vue";
import Modal from "@/components/Modal.vue";
import ButtonTwoTone from '@/components/ButtonTwoTone.vue';
import ContextMenu from '@/components/ContextMenu.vue';
import TrackList from '@/components/TrackList.vue';
import Cover from '@/components/Cover.vue';
import Modal from '@/components/Modal.vue';
const specialPlaylist = {
2829816518: {
name: "欧美私人订制",
gradient: "gradient-pink-purple-blue",
name: '欧美私人订制',
gradient: 'gradient-pink-purple-blue',
},
2890490211: {
name: "助眠鸟鸣声",
gradient: "gradient-green",
name: '助眠鸟鸣声',
gradient: 'gradient-green',
},
5089855855: {
name: "夜的胡思乱想",
gradient: "gradient-moonstone-blue",
name: '夜的胡思乱想',
gradient: 'gradient-moonstone-blue',
},
2888212971: {
name: "全球百大DJ",
gradient: "gradient-orange-red",
name: '全球百大DJ',
gradient: 'gradient-orange-red',
},
2829733864: {
name: "睡眠伴侣",
gradient: "gradient-midnight-blue",
name: '睡眠伴侣',
gradient: 'gradient-midnight-blue',
},
2829844572: {
name: "洗澡时听的歌",
gradient: "gradient-yellow",
name: '洗澡时听的歌',
gradient: 'gradient-yellow',
},
2920647537: {
name: "还是会想你",
gradient: "gradient-dark-blue-midnight-blue",
name: '还是会想你',
gradient: 'gradient-dark-blue-midnight-blue',
},
2890501416: {
name: "助眠白噪声",
gradient: "gradient-sky-blue",
name: '助眠白噪声',
gradient: 'gradient-sky-blue',
},
5217150082: {
name: "摇滚唱片行",
gradient: "gradient-yellow-red",
name: '摇滚唱片行',
gradient: 'gradient-yellow-red',
},
2829961453: {
name: "古风音乐大赏",
gradient: "gradient-fog",
name: '古风音乐大赏',
gradient: 'gradient-fog',
},
4923261701: {
name: "Trance",
gradient: "gradient-light-red-light-blue ",
name: 'Trance',
gradient: 'gradient-light-red-light-blue ',
},
5212729721: {
name: "欧美点唱机",
gradient: "gradient-indigo-pink-yellow",
name: '欧美点唱机',
gradient: 'gradient-indigo-pink-yellow',
},
3103434282: {
name: "甜蜜少女心",
gradient: "gradient-pink",
name: '甜蜜少女心',
gradient: 'gradient-pink',
},
2829896389: {
name: "日系私人订制",
gradient: "gradient-yellow-pink",
name: '日系私人订制',
gradient: 'gradient-yellow-pink',
},
2829779628: {
name: "运动随身听",
gradient: "gradient-orange-red",
name: '运动随身听',
gradient: 'gradient-orange-red',
},
2860654884: {
name: "独立女声精选",
gradient: "gradient-sharp-blue",
name: '独立女声精选',
gradient: 'gradient-sharp-blue',
},
898150: {
name: "浪漫婚礼专用",
gradient: "gradient-pink",
name: '浪漫婚礼专用',
gradient: 'gradient-pink',
},
2638104052: {
name: "牛奶泡泡浴",
gradient: "gradient-fog",
name: '牛奶泡泡浴',
gradient: 'gradient-fog',
},
5317236517: {
name: "后朋克精选",
gradient: "gradient-pink-purple-blue",
name: '后朋克精选',
gradient: 'gradient-pink-purple-blue',
},
2821115454: {
name: "一周原创发现",
gradient: "gradient-blue-purple",
name: '一周原创发现',
gradient: 'gradient-blue-purple',
},
3136952023: {
name: "私人雷达",
gradient: "gradient-radar",
name: '私人雷达',
gradient: 'gradient-radar',
},
};
export default {
name: "Playlist",
name: 'Playlist',
components: {
Cover,
ButtonTwoTone,
@ -303,14 +303,21 @@ export default {
Modal,
ContextMenu,
},
directives: {
focus: {
inserted: function (el) {
el.focus();
},
},
},
data() {
return {
show: false,
playlist: {
id: 0,
coverImgUrl: "",
coverImgUrl: '',
creator: {
userId: "",
userId: '',
},
trackIds: [],
},
@ -319,26 +326,16 @@ export default {
loadingMore: false,
lastLoadedTrackIndex: 9,
displaySearchInPlaylist: false,
searchKeyWords: "", // 使
inputSearchKeyWords: "", //
searchKeyWords: '', // 使
inputSearchKeyWords: '', //
inputFocus: false,
debounceTimeout: null,
};
},
created() {
if (this.$route.name === "likedSongs") {
this.loadData(this.data.likedSongPlaylistID);
} else {
this.loadData(this.$route.params.id);
}
},
destroyed() {
window.removeEventListener("scroll", this.handleScroll, true);
},
computed: {
...mapState(["player", "data"]),
...mapState(['player', 'data']),
isLikeSongsPage() {
return this.$route.name === "likedSongs";
return this.$route.name === 'likedSongs';
},
specialPlaylistInfo() {
return specialPlaylist[this.playlist.id];
@ -351,7 +348,7 @@ export default {
},
filteredTracks() {
return this.tracks.filter(
(track) =>
track =>
(track.name &&
track.name
.toLowerCase()
@ -361,7 +358,7 @@ export default {
.toLowerCase()
.includes(this.searchKeyWords.toLowerCase())) ||
track.ar.find(
(artist) =>
artist =>
artist.name &&
artist.name
.toLowerCase()
@ -370,35 +367,45 @@ export default {
);
},
},
created() {
if (this.$route.name === 'likedSongs') {
this.loadData(this.data.likedSongPlaylistID);
} else {
this.loadData(this.$route.params.id);
}
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll, true);
},
methods: {
...mapMutations(["appendTrackToPlayerList"]),
...mapActions(["playFirstTrackOnList", "playTrackOnListByID", "showToast"]),
playPlaylistByID(trackID = "first") {
let trackIDs = this.playlist.trackIds.map((t) => t.id);
...mapMutations(['appendTrackToPlayerList']),
...mapActions(['playFirstTrackOnList', 'playTrackOnListByID', 'showToast']),
playPlaylistByID(trackID = 'first') {
let trackIDs = this.playlist.trackIds.map(t => t.id);
this.$store.state.player.replacePlaylist(
trackIDs,
this.playlist.id,
"playlist",
'playlist',
trackID
);
},
likePlaylist(toast = false) {
if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号");
this.showToast('此操作需要登录网易云账号');
return;
}
subscribePlaylist({
id: this.playlist.id,
t: this.playlist.subscribed ? 2 : 1,
}).then((data) => {
}).then(data => {
if (data.code === 200) {
this.playlist.subscribed = !this.playlist.subscribed;
if (toast === true)
this.showToast(
this.playlist.subscribed ? "已保存到音乐库" : "已从音乐库删除"
this.playlist.subscribed ? '已保存到音乐库' : '已从音乐库删除'
);
}
getPlaylistDetail(this.id, true).then((data) => {
getPlaylistDetail(this.id, true).then(data => {
this.playlist = data.playlist;
});
});
@ -406,7 +413,7 @@ export default {
loadData(id, next = undefined) {
this.id = id;
getPlaylistDetail(this.id, true)
.then((data) => {
.then(data => {
this.playlist = data.playlist;
this.tracks = data.playlist.tracks;
NProgress.done();
@ -414,7 +421,7 @@ export default {
this.show = true;
this.lastLoadedTrackIndex = data.playlist.tracks.length - 1;
if (this.playlist.trackCount > this.tracks.length) {
window.addEventListener("scroll", this.handleScroll, true);
window.addEventListener('scroll', this.handleScroll, true);
}
return data;
})
@ -433,15 +440,15 @@ export default {
)
return t;
});
trackIDs = trackIDs.map((t) => t.id);
getTrackDetail(trackIDs.join(",")).then((data) => {
trackIDs = trackIDs.map(t => t.id);
getTrackDetail(trackIDs.join(',')).then(data => {
this.tracks.push(...data.songs);
this.lastLoadedTrackIndex += trackIDs.length;
this.loadingMore = false;
});
},
handleScroll(e) {
let dom = document.querySelector("html");
let dom = document.querySelector('html');
let scrollHeight = Math.max(dom.scrollHeight, dom.scrollHeight);
let scrollTop = e.target.scrollingElement.scrollTop;
let clientHeight =
@ -461,39 +468,39 @@ export default {
},
deletePlaylist() {
if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号");
this.showToast('此操作需要登录网易云账号');
return;
}
let confirmation = confirm(`确定要删除歌单 ${this.playlist.name}`);
if (confirmation === true) {
deletePlaylist(this.playlist.id).then((data) => {
deletePlaylist(this.playlist.id).then(data => {
if (data.code === 200) {
nativeAlert(`已删除歌单 ${this.playlist.name}`);
this.$router.go(-1);
} else {
nativeAlert("发生错误");
nativeAlert('发生错误');
}
});
}
},
editPlaylist() {
nativeAlert("此功能开发中");
nativeAlert('此功能开发中');
},
searchInPlaylist() {
this.displaySearchInPlaylist = !this.displaySearchInPlaylist;
if (this.displaySearchInPlaylist == false) {
this.searchKeyWords = "";
this.inputSearchKeyWords = "";
this.searchKeyWords = '';
this.inputSearchKeyWords = '';
} else {
this.loadMore(500);
}
},
removeTrack(trackID) {
if (!isAccountLoggedIn()) {
this.showToast("此操作需要登录网易云账号");
this.showToast('此操作需要登录网易云账号');
return;
}
this.tracks = this.tracks.filter((t) => t.id !== trackID);
this.tracks = this.tracks.filter(t => t.id !== trackID);
},
inputDebounce() {
if (this.debounceTimeout) clearTimeout(this.debounceTimeout);
@ -502,13 +509,6 @@ export default {
}, 600);
},
},
directives: {
focus: {
inserted: function (el) {
el.focus();
},
},
},
};
</script>
@ -647,7 +647,7 @@ export default {
background-image: linear-gradient(to left, #92fe9d 0%, #00c9ff 100%);
}
[data-theme="dark"] {
[data-theme='dark'] {
.gradient-radar {
background-image: linear-gradient(to left, #92fe9d 0%, #00c9ff 100%);
}
@ -824,7 +824,7 @@ export default {
}
}
[data-theme="dark"] {
[data-theme='dark'] {
.search-box {
.active {
input,

@ -1,83 +1,83 @@
<template>
<div class="search" v-show="show">
<div class="row" v-show="artists.length > 0 || albums.length > 0">
<div class="artists" v-show="artists.length > 0">
<div class="section-title" v-show="artists.length > 0"
>{{ $t("search.artist")
<div v-show="show" class="search">
<div v-show="artists.length > 0 || albums.length > 0" class="row">
<div v-show="artists.length > 0" class="artists">
<div v-show="artists.length > 0" class="section-title"
>{{ $t('search.artist')
}}<router-link :to="`/search/${keywords}/artists`">{{
$t("home.seeMore")
$t('home.seeMore')
}}</router-link></div
>
<CoverRow
type="artist"
:columnNumber="3"
:column-number="3"
:items="artists.slice(0, 3)"
gap="34px 24px"
/>
</div>
<div class="albums">
<div class="section-title" v-show="albums.length > 0"
>{{ $t("search.album")
<div v-show="albums.length > 0" class="section-title"
>{{ $t('search.album')
}}<router-link :to="`/search/${keywords}/albums`">{{
$t("home.seeMore")
$t('home.seeMore')
}}</router-link></div
>
<CoverRow
type="album"
:items="albums.slice(0, 3)"
subText="artist"
:columnNumber="3"
subTextFontSize="14px"
sub-text="artist"
:column-number="3"
sub-text-font-size="14px"
gap="34px 24px"
:playButtonSize="26"
:play-button-size="26"
/>
</div>
</div>
<div class="tracks" v-show="tracks.length > 0">
<div v-show="tracks.length > 0" class="tracks">
<div class="section-title"
>{{ $t("search.song")
>{{ $t('search.song')
}}<router-link :to="`/search/${keywords}/tracks`">{{
$t("home.seeMore")
$t('home.seeMore')
}}</router-link></div
>
<TrackList :tracks="tracks" type="tracklist" />
</div>
<div class="music-videos" v-show="musicVideos.length > 0">
<div v-show="musicVideos.length > 0" class="music-videos">
<div class="section-title"
>{{ $t("search.mv")
>{{ $t('search.mv')
}}<router-link :to="`/search/${keywords}/music-videos`">{{
$t("home.seeMore")
$t('home.seeMore')
}}</router-link></div
>
<MvRow :mvs="musicVideos.slice(0, 5)" />
</div>
<div class="playlists" v-show="playlists.length > 0">
<div v-show="playlists.length > 0" class="playlists">
<div class="section-title"
>{{ $t("search.playlist")
>{{ $t('search.playlist')
}}<router-link :to="`/search/${keywords}/playlists`">{{
$t("home.seeMore")
$t('home.seeMore')
}}</router-link></div
>
<CoverRow
type="playlist"
:items="playlists.slice(0, 12)"
subText="title"
:columnNumber="6"
subTextFontSize="14px"
sub-text="title"
:column-number="6"
sub-text-font-size="14px"
gap="34px 24px"
:playButtonSize="26"
:play-button-size="26"
/>
</div>
<div class="no-results" v-show="!haveResult">
<div v-show="!haveResult" class="no-results">
<div
><svg-icon icon-class="search" />
{{
keywords.length === 0 ? "输入关键字搜索" : $t("search.noResult")
keywords.length === 0 ? '输入关键字搜索' : $t('search.noResult')
}}</div
>
</div>
@ -85,16 +85,16 @@
</template>
<script>
import { getTrackDetail } from "@/api/track";
import { search } from "@/api/others";
import NProgress from "nprogress";
import { getTrackDetail } from '@/api/track';
import { search } from '@/api/others';
import NProgress from 'nprogress';
import TrackList from "@/components/TrackList.vue";
import MvRow from "@/components/MvRow.vue";
import CoverRow from "@/components/CoverRow.vue";
import TrackList from '@/components/TrackList.vue';
import MvRow from '@/components/MvRow.vue';
import CoverRow from '@/components/CoverRow.vue';
export default {
name: "Search",
name: 'Search',
components: {
TrackList,
MvRow,
@ -112,7 +112,7 @@ export default {
},
computed: {
keywords() {
return this.$route.params.keywords ?? "";
return this.$route.params.keywords ?? '';
},
haveResult() {
return (
@ -125,12 +125,21 @@ export default {
);
},
},
watch: {
keywords: function (newKeywords) {
if (newKeywords.length === 0) return;
this.getData();
},
},
created() {
this.getData();
},
methods: {
playTrackInSearchResult(id) {
let track = this.tracks.find((t) => t.id === id);
let track = this.tracks.find(t => t.id === id);
this.$store.state.player.appendTrackToPlayerList(track, true);
},
search(type = "all") {
search(type = 'all') {
const typeTable = {
all: 1018,
musicVideos: 1004,
@ -143,7 +152,7 @@ export default {
keywords: this.keywords,
type: typeTable[type],
limit: 16,
}).then((result) => {
}).then(result => {
return { result: result.result, type };
});
},
@ -151,32 +160,32 @@ export default {
NProgress.start();
this.show = false;
const requestAll = (requests) => {
const requestAll = requests => {
const keywords = this.keywords;
Promise.all(requests).then((results) => {
Promise.all(requests).then(results => {
if (keywords != this.keywords) return;
results.map((result) => {
results.map(result => {
const searchType = result.type;
if (result.result === undefined) return;
result = result.result;
switch (searchType) {
case "all":
case 'all':
this.result = result;
break;
case "musicVideos":
case 'musicVideos':
this.musicVideos = result.mvs ?? [];
break;
case "artists":
case 'artists':
this.artists = result.artists ?? [];
break;
case "albums":
case 'albums':
this.albums = result.albums ?? [];
break;
case "tracks":
case 'tracks':
this.tracks = result.songs ?? [];
this.getTracksDetail();
break;
case "playlists":
case 'playlists':
this.playlists = result.playlists ?? [];
break;
}
@ -187,32 +196,23 @@ export default {
};
const requests = [
this.search("artists"),
this.search("albums"),
this.search("tracks"),
this.search('artists'),
this.search('albums'),
this.search('tracks'),
];
const requests2 = [this.search("musicVideos"), this.search("playlists")];
const requests2 = [this.search('musicVideos'), this.search('playlists')];
requestAll(requests);
requestAll(requests2);
},
getTracksDetail() {
const trackIDs = this.tracks.map((t) => t.id);
const trackIDs = this.tracks.map(t => t.id);
if (trackIDs.length === 0) return;
getTrackDetail(trackIDs.join(",")).then((result) => {
getTrackDetail(trackIDs.join(',')).then(result => {
this.tracks = result.songs;
});
},
},
created() {
this.getData();
},
watch: {
keywords: function (newKeywords) {
if (newKeywords.length === 0) return;
this.getData();
},
},
};
</script>

@ -1,57 +1,57 @@
<template>
<div class="search" v-show="show">
<div v-show="show" class="search">
<h1>
<span>{{ $t("search.searchFor") }}{{ typeNameTable[type] }}</span> "{{
<span>{{ $t('search.searchFor') }}{{ typeNameTable[type] }}</span> "{{
keywords
}}"
</h1>
<div v-if="type === 'artists'">
<CoverRow type="artist" :items="result" :columnNumber="6" />
<CoverRow type="artist" :items="result" :column-number="6" />
</div>
<div v-if="type === 'albums'">
<CoverRow
type="album"
:items="result"
subText="artist"
subTextFontSize="14px"
sub-text="artist"
sub-text-font-size="14px"
/>
</div>
<div v-if="type === 'tracks'">
<TrackList
:tracks="result"
type="playlist"
dbclickTrackFunc="playAList"
dbclick-track-func="playAList"
/>
</div>
<div v-if="type === 'musicVideos'">
<MvRow :mvs="result" />
</div>
<div v-if="type === 'playlists'">
<CoverRow type="playlist" :items="result" subText="title" />
<CoverRow type="playlist" :items="result" sub-text="title" />
</div>
<div class="load-more">
<ButtonTwoTone v-show="hasMore" @click.native="fetchData" color="grey">{{
$t("explore.loadMore")
<ButtonTwoTone v-show="hasMore" color="grey" @click.native="fetchData">{{
$t('explore.loadMore')
}}</ButtonTwoTone>
</div>
</div>
</template>
<script>
import { getTrackDetail } from "@/api/track";
import { search } from "@/api/others";
import { camelCase } from "change-case";
import NProgress from "nprogress";
import { getTrackDetail } from '@/api/track';
import { search } from '@/api/others';
import { camelCase } from 'change-case';
import NProgress from 'nprogress';
import TrackList from "@/components/TrackList.vue";
import MvRow from "@/components/MvRow.vue";
import CoverRow from "@/components/CoverRow.vue";
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
import TrackList from '@/components/TrackList.vue';
import MvRow from '@/components/MvRow.vue';
import CoverRow from '@/components/CoverRow.vue';
import ButtonTwoTone from '@/components/ButtonTwoTone.vue';
export default {
name: "Search",
name: 'Search',
components: {
TrackList,
MvRow,
@ -70,14 +70,17 @@ export default {
},
typeNameTable() {
return {
musicVideos: "MV",
tracks: "歌曲",
albums: "专辑",
artists: "艺人",
playlists: "歌单",
musicVideos: 'MV',
tracks: '歌曲',
albums: '专辑',
artists: '艺人',
playlists: '歌单',
};
},
},
created() {
this.fetchData();
},
methods: {
fetchData() {
const typeTable = {
@ -91,30 +94,30 @@ export default {
keywords: this.keywords,
type: typeTable[this.type],
offset: this.result.length,
}).then((result) => {
}).then(result => {
result = result.result;
this.hasMore = result.hasMore ?? true;
switch (this.type) {
case "musicVideos":
case 'musicVideos':
this.result.push(...result.mvs);
if (result.mvCount <= this.result.length) {
this.hasMore = false;
}
break;
case "artists":
case 'artists':
this.result.push(...result.artists);
break;
case "albums":
case 'albums':
this.result.push(...result.albums);
if (result.albumCount <= this.result.length) {
this.hasMore = false;
}
break;
case "tracks":
case 'tracks':
this.result.push(...result.songs);
this.getTracksDetail();
break;
case "playlists":
case 'playlists':
this.result.push(...result.playlists);
break;
}
@ -123,16 +126,13 @@ export default {
});
},
getTracksDetail() {
const trackIDs = this.result.map((t) => t.id);
const trackIDs = this.result.map(t => t.id);
if (trackIDs.length === 0) return;
getTrackDetail(trackIDs.join(",")).then((result) => {
getTrackDetail(trackIDs.join(',')).then(result => {
this.result = result.songs;
});
},
},
created() {
this.fetchData();
},
};
</script>

@ -21,14 +21,14 @@
<div class="right">
<button @click="logout">
<svg-icon icon-class="logout" />
{{ $t("settings.logout") }}
{{ $t('settings.logout') }}
</button>
</div>
</div>
<h2>{{ $t("settings.settings") }}</h2>
<h2>{{ $t('settings.settings') }}</h2>
<div class="item">
<div class="left">
<div class="title"> {{ $t("settings.language") }} </div>
<div class="title"> {{ $t('settings.language') }} </div>
</div>
<div class="right">
<select v-model="lang">
@ -40,44 +40,44 @@
</div>
<div class="item">
<div class="left">
<div class="title"> {{ $t("settings.appearance.text") }} </div>
<div class="title"> {{ $t('settings.appearance.text') }} </div>
</div>
<div class="right">
<select v-model="appearance">
<option value="auto">{{ $t("settings.appearance.auto") }}</option>
<option value="auto">{{ $t('settings.appearance.auto') }}</option>
<option value="light"
>🌞 {{ $t("settings.appearance.light") }}</option
>🌞 {{ $t('settings.appearance.light') }}</option
>
<option value="dark"
>🌚 {{ $t("settings.appearance.dark") }}</option
>🌚 {{ $t('settings.appearance.dark') }}</option
>
</select>
</div>
</div>
<div class="item">
<div class="left">
<div class="title"> {{ $t("settings.musicQuality.text") }} </div>
<div class="title"> {{ $t('settings.musicQuality.text') }} </div>
</div>
<div class="right">
<select v-model="musicQuality">
<option value="128000">
{{ $t("settings.musicQuality.low") }} - 128Kbps
{{ $t('settings.musicQuality.low') }} - 128Kbps
</option>
<option value="192000">
{{ $t("settings.musicQuality.medium") }} - 192Kbps
{{ $t('settings.musicQuality.medium') }} - 192Kbps
</option>
<option value="320000">
{{ $t("settings.musicQuality.high") }} - 320Kbps
{{ $t('settings.musicQuality.high') }} - 320Kbps
</option>
<option value="999000">
{{ $t("settings.musicQuality.lossless") }} - FLAC
{{ $t('settings.musicQuality.lossless') }} - FLAC
</option>
</select>
</div>
</div>
<div v-if="isElectron" class="item">
<div class="left">
<div class="title"> {{ $t("settings.deviceSelector") }} </div>
<div class="title"> {{ $t('settings.deviceSelector') }} </div>
</div>
<div class="right">
<select v-model="outputDevice" :disabled="withoutAudioPrivilege">
@ -95,7 +95,7 @@
<div v-if="isElectron" class="item">
<div class="left">
<div class="title">
{{ $t("settings.automaticallyCacheSongs") }}
{{ $t('settings.automaticallyCacheSongs') }}
</div>
</div>
<div class="right">
@ -112,12 +112,12 @@
</div>
<div v-if="isElectron" class="item">
<div class="left">
<div class="title"> {{ $t("settings.cacheLimit.text") }} </div>
<div class="title"> {{ $t('settings.cacheLimit.text') }} </div>
</div>
<div class="right">
<select v-model="cacheLimit">
<option :value="false">
{{ $t("settings.cacheLimit.none") }}
{{ $t('settings.cacheLimit.none') }}
</option>
<option :value="512"> 500MB </option>
<option :value="1024"> 1GB </option>
@ -130,7 +130,7 @@
<div class="left">
<div class="title">
{{
$t("settings.cacheCount", {
$t('settings.cacheCount', {
song: tracksCache.length,
size: tracksCache.size,
})
@ -139,13 +139,13 @@
</div>
<div class="right">
<button @click="clearCache()">
{{ $t("settings.clearSongsCache") }}
{{ $t('settings.clearSongsCache') }}
</button>
</div>
</div>
<div class="item">
<div class="left">
<div class="title">{{ $t("settings.showLyricsTranslation") }}</div>
<div class="title">{{ $t('settings.showLyricsTranslation') }}</div>
</div>
<div class="right">
<div class="toggle">
@ -162,7 +162,7 @@
<div class="item">
<div class="left">
<div class="title">{{
$t("settings.showLyricsDynamicBackground")
$t('settings.showLyricsDynamicBackground')
}}</div>
</div>
<div class="right">
@ -179,28 +179,28 @@
</div>
<div class="item">
<div class="left">
<div class="title"> {{ $t("settings.lyricFontSize.text") }} </div>
<div class="title"> {{ $t('settings.lyricFontSize.text') }} </div>
</div>
<div class="right">
<select v-model="lyricFontSize">
<option value="16">
{{ $t("settings.lyricFontSize.small") }} - 16px
{{ $t('settings.lyricFontSize.small') }} - 16px
</option>
<option value="22">
{{ $t("settings.lyricFontSize.medium") }} - 22px
{{ $t('settings.lyricFontSize.medium') }} - 22px
</option>
<option value="28">
{{ $t("settings.lyricFontSize.large") }} - 28px
{{ $t('settings.lyricFontSize.large') }} - 28px
</option>
<option value="36">
{{ $t("settings.lyricFontSize.xlarge") }} - 36px
{{ $t('settings.lyricFontSize.xlarge') }} - 36px
</option>
</select>
</div>
</div>
<div v-if="isElectron && !isMac" class="item">
<div class="left">
<div class="title">{{ $t("settings.minimizeToTray") }}</div>
<div class="title">{{ $t('settings.minimizeToTray') }}</div>
</div>
<div class="right">
<div class="toggle">
@ -221,7 +221,7 @@
{{
isLastfmConnected
? `已连接到 Last.fm (${lastfm.name})`
: "连接 Last.fm "
: '连接 Last.fm '
}}</div
>
</div>
@ -235,7 +235,7 @@
<div class="item">
<div class="left">
<div class="title"> {{ $t("settings.showLibraryDefault") }}</div>
<div class="title"> {{ $t('settings.showLibraryDefault') }}</div>
</div>
<div class="right">
<div class="toggle">
@ -277,7 +277,7 @@
<div class="item">
<div class="left">
<div class="title">
{{ $t("settings.showPlaylistsByAppleMusic") }}</div
{{ $t('settings.showPlaylistsByAppleMusic') }}</div
>
</div>
<div class="right">
@ -295,7 +295,7 @@
<div v-if="isElectron" class="item">
<div class="left">
<div class="title">
{{ $t("settings.enableDiscordRichPresence") }}</div
{{ $t('settings.enableDiscordRichPresence') }}</div
>
</div>
<div class="right">
@ -312,7 +312,7 @@
</div>
<div v-if="isElectron" class="item">
<div class="left">
<div class="title"> {{ $t("settings.enableGlobalShortcut") }}</div>
<div class="title"> {{ $t('settings.enableGlobalShortcut') }}</div>
</div>
<div class="right">
<div class="toggle">
@ -355,32 +355,32 @@
</template>
<script>
import { mapState } from "vuex";
import { isLooseLoggedIn, doLogout } from "@/utils/auth";
import { auth as lastfmAuth } from "@/api/lastfm";
import { changeAppearance, bytesToSize } from "@/utils/common";
import { countDBSize, clearDB } from "@/utils/db";
import pkg from "../../package.json";
import { mapState } from 'vuex';
import { isLooseLoggedIn, doLogout } from '@/utils/auth';
import { auth as lastfmAuth } from '@/api/lastfm';
import { changeAppearance, bytesToSize } from '@/utils/common';
import { countDBSize, clearDB } from '@/utils/db';
import pkg from '../../package.json';
export default {
name: "Settings",
name: 'Settings',
data() {
return {
tracksCache: {
size: "0KB",
size: '0KB',
length: 0,
},
allOutputDevices: [
{
deviceId: "default",
label: "settings.permissionRequired",
deviceId: 'default',
label: 'settings.permissionRequired',
},
],
withoutAudioPrivilege: true,
};
},
computed: {
...mapState(["player", "settings", "data", "lastfm"]),
...mapState(['player', 'settings', 'data', 'lastfm']),
isElectron() {
return process.env.IS_ELECTRON;
},
@ -400,17 +400,17 @@ export default {
},
set(lang) {
this.$i18n.locale = lang;
this.$store.commit("changeLang", lang);
this.$store.commit('changeLang', lang);
},
},
appearance: {
get() {
if (this.settings.appearance === undefined) return "auto";
if (this.settings.appearance === undefined) return 'auto';
return this.settings.appearance;
},
set(value) {
this.$store.commit("updateSettings", {
key: "appearance",
this.$store.commit('updateSettings', {
key: 'appearance',
value,
});
changeAppearance(value);
@ -423,7 +423,7 @@ export default {
},
set(value) {
if (value === this.settings.musicQuality) return;
this.$store.commit("changeMusicQuality", value);
this.$store.commit('changeMusicQuality', value);
this.clearCache();
},
},
@ -433,26 +433,26 @@ export default {
return this.settings.lyricFontSize;
},
set(value) {
this.$store.commit("changeLyricFontSize", value);
this.$store.commit('changeLyricFontSize', value);
},
},
outputDevice: {
get() {
if (this.withoutAudioPrivilege === true) this.getAllOutputDevices();
const isValidDevice = this.allOutputDevices.find(
(device) => device.deviceId === this.settings.outputDevice
device => device.deviceId === this.settings.outputDevice
);
if (
this.settings.outputDevice === undefined ||
isValidDevice === undefined
)
return "default"; // Default deviceId
return 'default'; // Default deviceId
return this.settings.outputDevice;
},
set(deviceId) {
if (deviceId === this.settings.outputDevice || deviceId === undefined)
return;
this.$store.commit("changeOutputDevice", deviceId);
this.$store.commit('changeOutputDevice', deviceId);
this.player.setOutputDevice();
},
},
@ -461,8 +461,8 @@ export default {
return this.settings.enableUnblockNeteaseMusic || true;
},
set(value) {
this.$store.commit("updateSettings", {
key: "enableUnblockNeteaseMusic",
this.$store.commit('updateSettings', {
key: 'enableUnblockNeteaseMusic',
value,
});
},
@ -473,8 +473,8 @@ export default {
return this.settings.showPlaylistsByAppleMusic;
},
set(value) {
this.$store.commit("updateSettings", {
key: "showPlaylistsByAppleMusic",
this.$store.commit('updateSettings', {
key: 'showPlaylistsByAppleMusic',
value,
});
},
@ -485,8 +485,8 @@ export default {
return this.settings.nyancatStyle;
},
set(value) {
this.$store.commit("updateSettings", {
key: "nyancatStyle",
this.$store.commit('updateSettings', {
key: 'nyancatStyle',
value,
});
},
@ -497,8 +497,8 @@ export default {
return this.settings.automaticallyCacheSongs;
},
set(value) {
this.$store.commit("updateSettings", {
key: "automaticallyCacheSongs",
this.$store.commit('updateSettings', {
key: 'automaticallyCacheSongs',
value,
});
if (value === false) {
@ -511,8 +511,8 @@ export default {
return this.settings.showLyricsTranslation;
},
set(value) {
this.$store.commit("updateSettings", {
key: "showLyricsTranslation",
this.$store.commit('updateSettings', {
key: 'showLyricsTranslation',
value,
});
},
@ -522,8 +522,8 @@ export default {
return this.settings.showLyricsDynamicBackground;
},
set(value) {
this.$store.commit("updateSettings", {
key: "showLyricsDynamicBackground",
this.$store.commit('updateSettings', {
key: 'showLyricsDynamicBackground',
value,
});
},
@ -533,8 +533,8 @@ export default {
return this.settings.minimizeToTray;
},
set(value) {
this.$store.commit("updateSettings", {
key: "minimizeToTray",
this.$store.commit('updateSettings', {
key: 'minimizeToTray',
value,
});
},
@ -544,8 +544,8 @@ export default {
return this.settings.enableDiscordRichPresence;
},
set(value) {
this.$store.commit("updateSettings", {
key: "enableDiscordRichPresence",
this.$store.commit('updateSettings', {
key: 'enableDiscordRichPresence',
value,
});
},
@ -555,8 +555,8 @@ export default {
return this.settings.enableGlobalShortcut;
},
set(value) {
this.$store.commit("updateSettings", {
key: "enableGlobalShortcut",
this.$store.commit('updateSettings', {
key: 'enableGlobalShortcut',
value,
});
},
@ -566,8 +566,8 @@ export default {
return this.settings.showLibraryDefault || false;
},
set(value) {
this.$store.commit("updateSettings", {
key: "showLibraryDefault",
this.$store.commit('updateSettings', {
key: 'showLibraryDefault',
value,
});
},
@ -577,8 +577,8 @@ export default {
return this.settings.cacheLimit || false;
},
set(value) {
this.$store.commit("updateSettings", {
key: "cacheLimit",
this.$store.commit('updateSettings', {
key: 'cacheLimit',
value,
});
},
@ -588,27 +588,27 @@ export default {
},
},
created() {
this.countDBSize("tracks");
this.countDBSize('tracks');
},
activated() {
this.countDBSize("tracks");
this.countDBSize('tracks');
},
methods: {
getAllOutputDevices() {
navigator.mediaDevices.enumerateDevices().then((devices) => {
this.allOutputDevices = devices.filter((device) => {
return device.kind == "audiooutput";
navigator.mediaDevices.enumerateDevices().then(devices => {
this.allOutputDevices = devices.filter(device => {
return device.kind == 'audiooutput';
});
if (
this.allOutputDevices.length > 0 &&
this.allOutputDevices[0].label !== ""
this.allOutputDevices[0].label !== ''
) {
this.withoutAudioPriviledge = false;
} else {
this.allOutputDevices = [
{
deviceId: "default",
label: "settings.permissionRequired",
deviceId: 'default',
label: 'settings.permissionRequired',
},
];
}
@ -616,13 +616,13 @@ export default {
},
logout() {
doLogout();
this.$router.push({ name: "home" });
this.$router.push({ name: 'home' });
},
countDBSize() {
countDBSize().then((data) => {
countDBSize().then(data => {
if (data === undefined) {
this.tracksCache = {
size: "0KB",
size: '0KB',
length: 0,
};
return;
@ -639,16 +639,16 @@ export default {
lastfmConnect() {
lastfmAuth();
let lastfmChecker = setInterval(() => {
const session = localStorage.getItem("lastfm");
const session = localStorage.getItem('lastfm');
if (session) {
this.$store.commit("updateLastfm", JSON.parse(session));
this.$store.commit('updateLastfm', JSON.parse(session));
clearInterval(lastfmChecker);
}
}, 1000);
},
lastfmDisconnect() {
localStorage.removeItem("lastfm");
this.$store.commit("updateLastfm", {});
localStorage.removeItem('lastfm');
this.$store.commit('updateLastfm', {});
},
},
};
@ -835,7 +835,7 @@ h2 {
border-radius: 8px;
}
.toggle input + label:before {
content: "";
content: '';
position: absolute;
display: block;
-webkit-transition: 0.2s cubic-bezier(0.24, 0, 0.5, 1);
@ -847,7 +847,7 @@ h2 {
border-radius: 8px;
}
.toggle input + label:after {
content: "";
content: '';
position: absolute;
display: block;
box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.02), 0 4px 0px 0 hsla(0, 0%, 0%, 0.01),

Loading…
Cancel
Save