mirror of
https://github.com/qier222/YesPlayMusic.git
synced 2025-02-21 13:32:43 +08:00
fix: conflict
This commit is contained in:
commit
309ca88c34
@ -41,6 +41,7 @@
|
||||
"extract-zip": "^2.0.1",
|
||||
"howler": "^2.2.0",
|
||||
"js-cookie": "^2.2.1",
|
||||
"localforage": "^1.9.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pac-proxy-agent": "^4.1.0",
|
||||
"plyr": "^3.6.2",
|
||||
|
BIN
public/img/logos/nyancat.gif
Normal file
BIN
public/img/logos/nyancat.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -166,6 +166,7 @@ button {
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
@ -61,15 +61,15 @@ export function toplistOfArtists(type = null) {
|
||||
/**
|
||||
* 获取歌手 mv
|
||||
* 说明 : 调用此接口 , 传入歌手 id, 可获得歌手 mv 信息 , 具体 mv 播放地址可调 用/mv传入此接口获得的 mvid 来拿到 , 如 : /artist/mv?id=6452,/mv?mvid=5461064
|
||||
* @param {number} id 歌手 id, 可由搜索接口获得
|
||||
* @param {number} params.id 歌手 id, 可由搜索接口获得
|
||||
* @param {number} params.offset
|
||||
* @param {number} params.limit
|
||||
*/
|
||||
export function artistMv(id) {
|
||||
export function artistMv(params) {
|
||||
return request({
|
||||
url: "/artist/mv",
|
||||
method: "get",
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,7 @@ export function dailySignin(type = 0) {
|
||||
method: "post",
|
||||
params: {
|
||||
type,
|
||||
timestamp: new Date().getTime(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -62,3 +62,40 @@
|
||||
.volume-control:hover .vue-slider-process {
|
||||
background-color: #335eea;
|
||||
}
|
||||
|
||||
/* nyancat */
|
||||
|
||||
.nyancat .vue-slider-rail {
|
||||
background-color: rgba(128, 128, 128, 0.18);
|
||||
padding: 2.5px 0px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.nyancat .vue-slider-process {
|
||||
padding: 0px 1px;
|
||||
top: -2px;
|
||||
border-radius: 0;
|
||||
background: -webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
color-stop(0, #f00),
|
||||
color-stop(17%, #f90),
|
||||
color-stop(33%, #ff0),
|
||||
color-stop(50%, #3f0),
|
||||
color-stop(67%, #09f),
|
||||
color-stop(83%, #63f)
|
||||
);
|
||||
}
|
||||
|
||||
.nyancat .vue-slider-dot-handle {
|
||||
background: url("/img/logos/nyancat.gif");
|
||||
background-size: 36px;
|
||||
width: 36px;
|
||||
height: 24px;
|
||||
margin-top: -6px;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
box-sizing: border-box;
|
||||
visibility: visible;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<span class="artist-in-line">
|
||||
<span v-for="(ar, index) in slicedArtists" :key="ar.id">
|
||||
{{ computedPrefix }}
|
||||
<span v-for="(ar, index) in filteredArtists" :key="ar.id">
|
||||
<router-link :to="`/artist/${ar.id}`">{{ ar.name }}</router-link>
|
||||
<span v-if="index !== slicedArtists.length - 1">, </span>
|
||||
<span v-if="index !== filteredArtists.length - 1">, </span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
@ -15,16 +16,22 @@ export default {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
showFirstArtist: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
exclude: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
slicedArtists() {
|
||||
return this.showFirstArtist
|
||||
? this.artists
|
||||
: this.artists.slice(1, this.artists.length);
|
||||
filteredArtists() {
|
||||
return this.artists.filter((a) => a.name !== this.exclude);
|
||||
},
|
||||
computedPrefix() {
|
||||
if (this.filteredArtists.length !== 0) return this.prefix;
|
||||
else return "";
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="context-menu">
|
||||
<div class="context-menu" ref="contextMenu">
|
||||
<div
|
||||
class="menu"
|
||||
tabindex="-1"
|
||||
@ -37,6 +37,7 @@ export default {
|
||||
|
||||
closeMenu() {
|
||||
this.showMenu = false;
|
||||
this.$parent.closeMenu();
|
||||
},
|
||||
|
||||
openMenu(e) {
|
||||
@ -62,10 +63,11 @@ export default {
|
||||
.menu {
|
||||
position: fixed;
|
||||
min-width: 136px;
|
||||
max-width: 240px;
|
||||
list-style: none;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
box-shadow: 0 6px 12px -4px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
@ -77,15 +79,65 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
.menu {
|
||||
background: rgba(46, 46, 46, 0.68);
|
||||
backdrop-filter: blur(16px) contrast(120%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.menu .item {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 7px;
|
||||
cursor: default;
|
||||
color: var(--color-text);
|
||||
&:hover {
|
||||
background: #eaeffd;
|
||||
color: #335eea;
|
||||
background: var(--color-primary-bg);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 4px 10px;
|
||||
background: rgba(128, 128, 128, 0.18);
|
||||
height: 1px;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
padding: 10px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text);
|
||||
cursor: default;
|
||||
img {
|
||||
height: 38px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.info {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 12px;
|
||||
opacity: 0.68;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -51,9 +51,11 @@ export default {
|
||||
this.$router.push({ path: "/mv/" + id, query });
|
||||
},
|
||||
getUrl(mv) {
|
||||
if (mv.cover !== undefined) return mv.cover;
|
||||
if (mv.imgurl16v9 !== undefined) return mv.imgurl16v9;
|
||||
if (mv.coverUrl !== undefined) return mv.coverUrl;
|
||||
if (mv.cover !== undefined) return mv.cover.replace(/^http:/, "https:");
|
||||
if (mv.imgurl16v9 !== undefined)
|
||||
return mv.imgurl16v9.replace(/^http:/, "https:");
|
||||
if (mv.coverUrl !== undefined)
|
||||
return mv.coverUrl.replace(/^http:/, "https:");
|
||||
},
|
||||
getID(mv) {
|
||||
if (mv.id !== undefined) return mv.id;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="player">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar" :class="{ nyancat: settings.nyancatStyle }">
|
||||
<vue-slider
|
||||
v-model="progress"
|
||||
:min="0"
|
||||
|
@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<div class="track-list" :style="listStyles">
|
||||
<ContextMenu ref="menu">
|
||||
<div class="item-info">
|
||||
<img :src="rightClickedTrack.al.picUrl | resizeImage(128)" />
|
||||
<div class="info">
|
||||
<div class="title">{{ rightClickedTrack.name }}</div>
|
||||
<div class="subtitle">{{ rightClickedTrack.ar[0].name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="item" @click="play">Play</div>
|
||||
<div class="item" @click="playNext">Play Next</div>
|
||||
<div
|
||||
@ -60,10 +68,25 @@ export default {
|
||||
type: String,
|
||||
default: "default",
|
||||
},
|
||||
albumObject: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
artist: {
|
||||
name: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rightClickedTrack: null,
|
||||
rightClickedTrack: {
|
||||
id: 0,
|
||||
name: "",
|
||||
ar: [{ name: "" }],
|
||||
al: { picUrl: "" },
|
||||
},
|
||||
listStyles: {},
|
||||
};
|
||||
},
|
||||
@ -90,6 +113,14 @@ export default {
|
||||
this.rightClickedTrack = track;
|
||||
this.$refs.menu.openMenu(e);
|
||||
},
|
||||
closeMenu() {
|
||||
this.rightClickedTrack = {
|
||||
id: 0,
|
||||
name: "",
|
||||
ar: [{ name: "" }],
|
||||
al: { picUrl: "" },
|
||||
};
|
||||
},
|
||||
playThisList(trackID) {
|
||||
if (this.dbclickTrackFunc === "default") {
|
||||
this.playThisListDefault(trackID);
|
||||
|
@ -4,8 +4,8 @@
|
||||
:class="trackClass"
|
||||
:style="trackStyle"
|
||||
:title="track.reason"
|
||||
@mouseover="focus = true"
|
||||
@mouseleave="focus = false"
|
||||
@mouseover="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
>
|
||||
<img
|
||||
:src="imgUrl | resizeImage(224)"
|
||||
@ -34,9 +34,11 @@
|
||||
<div class="container">
|
||||
<div class="title">
|
||||
{{ track.name }}
|
||||
<span class="featured" v-if="isAlbum && track.ar.length > 1">
|
||||
-
|
||||
<ArtistsInLine :artists="track.ar" :showFirstArtist="false"
|
||||
<span class="featured" v-if="isAlbum">
|
||||
<ArtistsInLine
|
||||
:artists="track.ar"
|
||||
:exclude="this.$parent.albumObject.artist.name"
|
||||
prefix="-"
|
||||
/></span>
|
||||
<span v-if="isAlbum && track.mark === 1318912" class="explicit-symbol"
|
||||
><ExplicitSymbol
|
||||
@ -90,7 +92,7 @@ export default {
|
||||
track: Object,
|
||||
},
|
||||
data() {
|
||||
return { focus: false, trackStyle: {} };
|
||||
return { hover: false, trackStyle: {} };
|
||||
},
|
||||
computed: {
|
||||
imgUrl() {
|
||||
@ -125,11 +127,21 @@ export default {
|
||||
let trackClass = [this.type];
|
||||
if (!this.track.playable) trackClass.push("disable");
|
||||
if (this.isPlaying) trackClass.push("playing");
|
||||
if (this.focus) trackClass.push("focus");
|
||||
return trackClass;
|
||||
},
|
||||
accountLogin() {
|
||||
return isAccountLoggedIn();
|
||||
},
|
||||
isMenuOpened() {
|
||||
return this.$parent.rightClickedTrack.id === this.track.id ? true : false;
|
||||
},
|
||||
focus() {
|
||||
return (
|
||||
(this.hover && this.$parent.rightClickedTrack.id === 0) ||
|
||||
this.isMenuOpened
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
goToAlbum() {
|
||||
@ -285,11 +297,13 @@ button {
|
||||
opacity: 0.88;
|
||||
color: var(--color-text);
|
||||
}
|
||||
&:hover {
|
||||
transition: all 0.3s;
|
||||
background: var(--color-secondary-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.track.focus {
|
||||
transition: all 0.3s;
|
||||
background: var(--color-secondary-bg);
|
||||
}
|
||||
|
||||
.track.disable {
|
||||
img {
|
||||
filter: grayscale(1) opacity(0.6);
|
||||
|
@ -19,7 +19,7 @@ export default {
|
||||
library: {
|
||||
sLibrary: "'s Library",
|
||||
likedSongs: "Liked Songs",
|
||||
sLikedSongs: "'s LikedSongs",
|
||||
sLikedSongs: "'s Liked Songs",
|
||||
},
|
||||
explore: {
|
||||
explore: "Explore",
|
||||
@ -91,7 +91,7 @@ export default {
|
||||
artist: "Artists",
|
||||
album: "Albums",
|
||||
song: "Songs",
|
||||
mv: "MVs",
|
||||
mv: "Music Videos",
|
||||
playlist: "Playlists",
|
||||
noResult: "No Results",
|
||||
searchFor: "Search for",
|
||||
|
@ -3,7 +3,7 @@ export default {
|
||||
nav: {
|
||||
home: "首页",
|
||||
explore: "发现",
|
||||
library: "资料库",
|
||||
library: "音乐库",
|
||||
search: "搜索",
|
||||
},
|
||||
home: {
|
||||
@ -14,7 +14,7 @@ export default {
|
||||
charts: "排行榜",
|
||||
},
|
||||
library: {
|
||||
sLibrary: "的资料库",
|
||||
sLibrary: "的音乐库",
|
||||
likedSongs: "我喜欢的歌",
|
||||
sLikedSongs: "喜欢的歌",
|
||||
},
|
||||
|
@ -49,6 +49,14 @@ const routes = [
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/artist/:id/mv",
|
||||
name: "artistMV",
|
||||
component: () => import("@/views/artistMV.vue"),
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/mv/:id",
|
||||
name: "mv",
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { updateMediaSessionMetaData } from "@/utils/mediaSession";
|
||||
import { getTrackDetail, scrobble, getMP3 } from "@/api/track";
|
||||
import { getTrackDetail, scrobble } from "@/api/track";
|
||||
import { isAccountLoggedIn } from "@/utils/auth";
|
||||
import { updateHttps } from "@/utils/common";
|
||||
// import { updateHttps } from "@/utils/common";
|
||||
import localforage from "localforage";
|
||||
import { cacheTrack } from "@/utils/db";
|
||||
|
||||
export default {
|
||||
switchTrack({ state, dispatch, commit }, basicTrack) {
|
||||
getTrackDetail(basicTrack.id).then((data) => {
|
||||
let track = data.songs[0];
|
||||
track.sort = basicTrack.sort;
|
||||
|
||||
// 获取当前的播放时间。初始化为 loading 状态时返回 howler 的实例而不是浮点数时间,比如 1.332
|
||||
let time = state.howler.seek();
|
||||
let currentTime = 0;
|
||||
@ -26,6 +29,7 @@ export default {
|
||||
sourceid: state.player.listInfo.id,
|
||||
time: currentTime,
|
||||
});
|
||||
|
||||
commit("updateCurrentTrack", track);
|
||||
updateMediaSessionMetaData(track);
|
||||
document.title = `${track.name} · ${track.ar[0].name} - YesPlayMusic`;
|
||||
@ -42,11 +46,16 @@ export default {
|
||||
});
|
||||
}
|
||||
if (isAccountLoggedIn()) {
|
||||
getMP3(track.id).then((data) => {
|
||||
// 未知情况下会没有返回数据导致报错,增加防范逻辑
|
||||
if (data.data[0]) {
|
||||
const url = updateHttps(data.data[0].url);
|
||||
commitMP3(url);
|
||||
let tracks = localforage.createInstance({
|
||||
name: "tracks",
|
||||
});
|
||||
tracks.getItem(`${track.id}`).then((t) => {
|
||||
if (t !== null) {
|
||||
commitMP3(URL.createObjectURL(t.mp3));
|
||||
} else {
|
||||
cacheTrack(`${track.id}`).then((t) => {
|
||||
commitMP3(URL.createObjectURL(t.mp3));
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -107,3 +107,89 @@ export function changeAppearance(appearance) {
|
||||
.querySelector('meta[name="theme-color"]')
|
||||
.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",
|
||||
];
|
||||
for (let keyword of keywords) {
|
||||
if (title.includes(keyword) === false) continue;
|
||||
return {
|
||||
title: title
|
||||
.replace(`(${keyword})`, "")
|
||||
.replace(`: ${keyword}`, "")
|
||||
.replace(`[${keyword}]`, "")
|
||||
.replace(`- ${keyword}`, "")
|
||||
.replace(`${keyword}`, ""),
|
||||
subtitle: keyword,
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: title,
|
||||
subtitle: "",
|
||||
};
|
||||
}
|
||||
|
||||
export function splitAlbumTitle(title) {
|
||||
let keywords = [
|
||||
"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}`, ""),
|
||||
subtitle: keyword,
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: title,
|
||||
subtitle: "",
|
||||
};
|
||||
}
|
||||
|
||||
export function bytesToSize(bytes) {
|
||||
var marker = 1024; // Change to 1000 if required
|
||||
var decimal = 2; // Change as required
|
||||
var kiloBytes = marker;
|
||||
var megaBytes = marker * marker;
|
||||
var gigaBytes = marker * marker * marker;
|
||||
|
||||
if (bytes < kiloBytes) return bytes + " Bytes";
|
||||
else if (bytes < megaBytes)
|
||||
return (bytes / kiloBytes).toFixed(decimal) + " KB";
|
||||
else if (bytes < gigaBytes)
|
||||
return (bytes / megaBytes).toFixed(decimal) + " MB";
|
||||
else return (bytes / gigaBytes).toFixed(decimal) + " GB";
|
||||
}
|
||||
|
57
src/utils/db.js
Normal file
57
src/utils/db.js
Normal file
@ -0,0 +1,57 @@
|
||||
import axios from "axios";
|
||||
import localforage from "localforage";
|
||||
import { getMP3 } from "@/api/track";
|
||||
|
||||
export function cacheTrack(id) {
|
||||
let tracks = localforage.createInstance({
|
||||
name: "tracks",
|
||||
});
|
||||
|
||||
// TODO: limit cache songs number
|
||||
// tracks.length().then(function (length) {
|
||||
// if (length > 2) {
|
||||
// tracks.keys().then(function (keys) {
|
||||
// tracks.removeItem(keys[keys.length - 2]);
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// TODO: cache track details
|
||||
return getMP3(id).then((data) => {
|
||||
return axios
|
||||
.get(data.data[0].url.replace(/^http:/, "https:"), {
|
||||
responseType: "blob",
|
||||
})
|
||||
.then((data) => {
|
||||
tracks.setItem(`${id}`, { mp3: data.data });
|
||||
return { mp3: data.data };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function countDBSize(dbName) {
|
||||
let db = localforage.createInstance({
|
||||
name: dbName,
|
||||
});
|
||||
let trackSizes = [];
|
||||
return db
|
||||
.iterate((value) => {
|
||||
trackSizes.push(value.mp3.size);
|
||||
})
|
||||
.then(() => {
|
||||
return {
|
||||
bytes: trackSizes.reduce((s1, s2) => s1 + s2),
|
||||
length: trackSizes.length,
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
export function clearDB(dbName) {
|
||||
let db = localforage.createInstance({
|
||||
name: dbName,
|
||||
});
|
||||
return db.clear();
|
||||
}
|
@ -11,9 +11,8 @@
|
||||
:id="album.id"
|
||||
/>
|
||||
<div class="info">
|
||||
<div class="title">
|
||||
{{ album.name }}
|
||||
</div>
|
||||
<div class="title"> {{ title }}</div>
|
||||
<div class="subtitle" v-if="subtitle !== ''">{{ subtitle }}</div>
|
||||
<div class="artist">
|
||||
<span>{{ album.type | formatAlbumType(album) }} by </span
|
||||
><router-link :to="`/artist/${album.artist.id}`">{{
|
||||
@ -53,7 +52,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TrackList :tracks="tracks" :type="'album'" :id="album.id" />
|
||||
<TrackList
|
||||
:tracks="tracks"
|
||||
:type="'album'"
|
||||
:id="album.id"
|
||||
:albumObject="album"
|
||||
/>
|
||||
<div class="extra-info">
|
||||
<div class="album-time"></div>
|
||||
<div class="release-date">
|
||||
@ -103,6 +107,7 @@ import { getArtistAlbum } from "@/api/artist";
|
||||
import { getTrackDetail } from "@/api/track";
|
||||
import { playAlbumByID } from "@/utils/play";
|
||||
import { getAlbum, albumDynamicDetail, likeAAlbum } from "@/api/album";
|
||||
import { splitSoundtrackAlbumTitle, splitAlbumTitle } from "@/utils/common";
|
||||
import NProgress from "nprogress";
|
||||
import { isAccountLoggedIn } from "@/utils/auth";
|
||||
|
||||
@ -135,6 +140,8 @@ export default {
|
||||
show: false,
|
||||
moreAlbums: [],
|
||||
dynamicDetail: {},
|
||||
subtitle: "",
|
||||
title: "",
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@ -161,7 +168,11 @@ export default {
|
||||
realAlbums.find((a1) => a1.id === a.id) === undefined &&
|
||||
eps.find((a1) => a1.id === a.id) === undefined
|
||||
);
|
||||
return [...realAlbums, ...eps, ...restItems].slice(0, 5);
|
||||
if (realAlbums.length === 0) {
|
||||
return [...realAlbums, ...eps, ...restItems].slice(0, 5);
|
||||
} else {
|
||||
return [...realAlbums, ...restItems].slice(0, 5);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@ -182,10 +193,24 @@ export default {
|
||||
this.dynamicDetail.isSub = !this.dynamicDetail.isSub;
|
||||
});
|
||||
},
|
||||
formatTitle() {
|
||||
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;
|
||||
} else {
|
||||
this.subtitle =
|
||||
splitTitle.subtitle === ""
|
||||
? splitTitle2.subtitle
|
||||
: splitTitle.subtitle;
|
||||
}
|
||||
},
|
||||
loadData(id) {
|
||||
getAlbum(id).then((data) => {
|
||||
this.album = data.album;
|
||||
this.tracks = data.songs;
|
||||
this.formatTitle();
|
||||
NProgress.done();
|
||||
this.show = true;
|
||||
|
||||
@ -230,8 +255,10 @@ export default {
|
||||
.title {
|
||||
font-size: 56px;
|
||||
font-weight: 700;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.artist {
|
||||
font-size: 18px;
|
||||
|
@ -8,9 +8,17 @@
|
||||
<div class="name">{{ artist.name }}</div>
|
||||
<div class="artist">{{ $t("artist.artist") }}</div>
|
||||
<div class="statistics">
|
||||
{{ artist.musicSize }} {{ $t("common.songs") }} ·
|
||||
{{ artist.albumSize }} {{ $t("artist.withAlbums") }} ·
|
||||
{{ artist.mvSize }} {{ $t("artist.videos") }}
|
||||
<a @click="scrollTo('popularTracks')"
|
||||
>{{ artist.musicSize }} {{ $t("common.songs") }}</a
|
||||
>
|
||||
·
|
||||
<a @click="scrollTo('seeMore', 'start')"
|
||||
>{{ artist.albumSize }} {{ $t("artist.withAlbums") }}</a
|
||||
>
|
||||
·
|
||||
<a @click="scrollTo('mvs')"
|
||||
>{{ artist.mvSize }} {{ $t("artist.videos") }}</a
|
||||
>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<ButtonTwoTone @click.native="playPopularSongs()" :iconClass="`play`">
|
||||
@ -57,21 +65,21 @@
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popular-tracks">
|
||||
<div class="popular-tracks" id="popularTracks">
|
||||
<div class="section-title">{{ $t("artist.popularSongs") }}</div>
|
||||
<TrackList
|
||||
:tracks="popularTracks.slice(0, showMorePopTracks ? 24 : 12)"
|
||||
:type="'tracklist'"
|
||||
/>
|
||||
|
||||
<div class="show-more">
|
||||
<div class="show-more" id="seeMore">
|
||||
<button @click="showMorePopTracks = !showMorePopTracks">
|
||||
<span v-show="!showMorePopTracks">{{ $t("artist.showMore") }}</span>
|
||||
<span v-show="showMorePopTracks">{{ $t("artist.showLess") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="albums" v-if="albums.length !== 0">
|
||||
<div class="albums" id="albums" v-if="albums.length !== 0">
|
||||
<div class="section-title">{{ $t("artist.albums") }}</div>
|
||||
<CoverRow
|
||||
:type="'album'"
|
||||
@ -80,8 +88,13 @@
|
||||
:showPlayButton="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mvs" v-if="mvs.length !== 0">
|
||||
<div class="section-title">MVs</div>
|
||||
<div class="mvs" id="mvs" v-if="mvs.length !== 0">
|
||||
<div class="section-title"
|
||||
>MVs
|
||||
<router-link v-show="hasMoreMV" :to="`/artist/${this.artist.id}/mv`">{{
|
||||
$t("home.seeMore")
|
||||
}}</router-link>
|
||||
</div>
|
||||
<MvRow :mvs="mvs" subtitle="publishTime" />
|
||||
</div>
|
||||
<div class="eps" v-if="eps.length !== 0">
|
||||
@ -135,6 +148,7 @@ export default {
|
||||
},
|
||||
showMorePopTracks: false,
|
||||
mvs: [],
|
||||
hasMoreMV: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -163,8 +177,9 @@ export default {
|
||||
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;
|
||||
});
|
||||
},
|
||||
goToAlbum(id) {
|
||||
@ -185,6 +200,12 @@ export default {
|
||||
if (data.code === 200) this.artist.followed = !this.artist.followed;
|
||||
});
|
||||
},
|
||||
scrollTo(div, block = "center") {
|
||||
document.getElementById(div).scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block,
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadData(this.$route.params.id);
|
||||
@ -211,7 +232,7 @@ export default {
|
||||
.artist-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 72px;
|
||||
margin-bottom: 26px;
|
||||
color: var(--color-text);
|
||||
img {
|
||||
height: 192px;
|
||||
@ -255,7 +276,16 @@ export default {
|
||||
opacity: 0.88;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 16px;
|
||||
margin-top: 46px;
|
||||
padding-top: 46px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
a {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
opacity: 0.68;
|
||||
}
|
||||
}
|
||||
|
||||
.latest-release {
|
||||
|
96
src/views/artistMV.vue
Normal file
96
src/views/artistMV.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div v-show="show">
|
||||
<h1>
|
||||
<img class="avatar" :src="artist.img1v1Url | resizeImage(1024)" />{{
|
||||
artist.name
|
||||
}}'s Music Videos
|
||||
</h1>
|
||||
<MvRow :mvs="mvs" subtitle="publishTime" />
|
||||
<div class="load-more">
|
||||
<ButtonTwoTone v-show="hasMore" @click.native="loadMVs" color="grey">{{
|
||||
$t("explore.loadMore")
|
||||
}}</ButtonTwoTone>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { artistMv, getArtist } from "@/api/artist";
|
||||
import NProgress from "nprogress";
|
||||
|
||||
import ButtonTwoTone from "@/components/ButtonTwoTone.vue";
|
||||
import MvRow from "@/components/MvRow.vue";
|
||||
|
||||
export default {
|
||||
name: "artistMV",
|
||||
components: {
|
||||
MvRow,
|
||||
ButtonTwoTone,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: 0,
|
||||
show: false,
|
||||
hasMore: true,
|
||||
artist: {},
|
||||
mvs: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
getArtist(this.id).then((data) => {
|
||||
this.artist = data.artist;
|
||||
});
|
||||
this.loadMVs();
|
||||
},
|
||||
loadMVs() {
|
||||
artistMv({ id: this.id, limit: 100, offset: this.mvs.length + 1 }).then(
|
||||
(data) => {
|
||||
this.mvs.push(...data.mvs);
|
||||
this.hasMore = data.hasMore;
|
||||
NProgress.done();
|
||||
this.show = true;
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
h1 {
|
||||
font-size: 42px;
|
||||
color: var(--color-text);
|
||||
.avatar {
|
||||
height: 44px;
|
||||
margin-right: 12px;
|
||||
vertical-align: -7px;
|
||||
border-radius: 50%;
|
||||
border: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
.load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div v-show="show">
|
||||
<h1>
|
||||
<img class="head" :src="user.profile.avatarUrl | resizeImage" />{{
|
||||
user.profile.nickname
|
||||
<img class="head" :src="data.user.avatarUrl | resizeImage" />{{
|
||||
data.user.nickname
|
||||
}}{{ $t("library.sLibrary") }}
|
||||
</h1>
|
||||
<div class="section-one">
|
||||
@ -129,12 +129,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
user: {
|
||||
profile: {
|
||||
avatarUrl: "",
|
||||
nickname: "",
|
||||
},
|
||||
},
|
||||
playlists: [],
|
||||
hasMorePlaylists: true,
|
||||
likedSongsPlaylist: {
|
||||
@ -153,7 +147,7 @@ export default {
|
||||
created() {
|
||||
NProgress.start();
|
||||
userDetail(this.data.user.userId).then((data) => {
|
||||
this.user = data;
|
||||
this.$store.commit("updateData", { key: "user", value: data.profile });
|
||||
});
|
||||
},
|
||||
activated() {
|
||||
@ -253,18 +247,24 @@ export default {
|
||||
});
|
||||
},
|
||||
loadLikedAlbums() {
|
||||
NProgress.start();
|
||||
likedAlbums().then((data) => {
|
||||
this.albums = data.data;
|
||||
NProgress.done();
|
||||
});
|
||||
},
|
||||
loadLikedArtists() {
|
||||
NProgress.start();
|
||||
likedArtists().then((data) => {
|
||||
this.artists = data.data;
|
||||
NProgress.done();
|
||||
});
|
||||
},
|
||||
loadLikedMVs() {
|
||||
NProgress.start();
|
||||
likedMVs().then((data) => {
|
||||
this.mvs = data.data;
|
||||
NProgress.done();
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -121,7 +121,7 @@ export default {
|
||||
},
|
||||
created() {
|
||||
if (this.$route.name === "likedSongs") {
|
||||
this.loadData(this.data.user.likedSongPlaylistID);
|
||||
this.loadData(this.data.likedSongPlaylistID);
|
||||
} else {
|
||||
this.loadData(this.$route.params.id);
|
||||
}
|
||||
|
@ -74,6 +74,16 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="left">
|
||||
<div class="title"
|
||||
>Cached {{ tracksCache.length }} songs ({{ tracksCache.size }})</div
|
||||
>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button @click="clearCache('tracks')"> Clear Songs Cache </button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="left">
|
||||
<div class="title"> Show Github Icon </div>
|
||||
@ -106,6 +116,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="left">
|
||||
<div class="title" style="transform: scaleX(-1)">🐈️ 🏳️🌈</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="nyancat-style"
|
||||
id="nyancat-style"
|
||||
v-model="nyancatStyle"
|
||||
/>
|
||||
<label for="nyancat-style"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -113,10 +139,19 @@
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { doLogout } from "@/utils/auth";
|
||||
import { changeAppearance } from "@/utils/common";
|
||||
import { changeAppearance, bytesToSize } from "@/utils/common";
|
||||
import { countDBSize, clearDB } from "@/utils/db";
|
||||
|
||||
export default {
|
||||
name: "settings",
|
||||
data() {
|
||||
return {
|
||||
tracksCache: {
|
||||
size: "0KB",
|
||||
length: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["settings", "data"]),
|
||||
lang: {
|
||||
@ -143,11 +178,13 @@ export default {
|
||||
},
|
||||
musicQuality: {
|
||||
get() {
|
||||
if (this.settings.appearance === undefined) return 320000;
|
||||
if (this.settings.musicQuality === undefined) return 320000;
|
||||
return this.settings.musicQuality;
|
||||
},
|
||||
set(value) {
|
||||
if (value === this.settings.musicQuality) return;
|
||||
this.$store.commit("changeMusicQuality", value);
|
||||
this.clearCache("tracks");
|
||||
},
|
||||
},
|
||||
showGithubIcon: {
|
||||
@ -174,12 +211,49 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
nyancatStyle: {
|
||||
get() {
|
||||
if (this.settings.nyancatStyle === undefined) return false;
|
||||
return this.settings.nyancatStyle;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit("updateSettings", {
|
||||
key: "nyancatStyle",
|
||||
value,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
logout() {
|
||||
doLogout();
|
||||
this.$router.push({ name: "home" });
|
||||
},
|
||||
countDBSize(dbName) {
|
||||
countDBSize(dbName).then((data) => {
|
||||
if (data === undefined) {
|
||||
this.tracksCache = {
|
||||
size: "0KB",
|
||||
length: 0,
|
||||
};
|
||||
return;
|
||||
}
|
||||
this.tracksCache.size = bytesToSize(data.bytes);
|
||||
this.tracksCache.length = data.length;
|
||||
});
|
||||
},
|
||||
clearCache(dbName) {
|
||||
// TODO: toast
|
||||
clearDB(dbName).then(() => {
|
||||
this.countDBSize("tracks");
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.countDBSize("tracks");
|
||||
},
|
||||
activated() {
|
||||
this.countDBSize("tracks");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -301,6 +375,21 @@ h2 {
|
||||
background: var(--color-primary-bg);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
color: var(--color-text);
|
||||
background: var(--color-secondary-bg);
|
||||
padding: 8px 12px 8px 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
transition: 0.2s;
|
||||
&:hover {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.94);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.beforeAnimation {
|
||||
|
@ -7017,6 +7017,13 @@ localforage@1.8.1:
|
||||
dependencies:
|
||||
lie "3.1.1"
|
||||
|
||||
localforage@^1.9.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1"
|
||||
integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==
|
||||
dependencies:
|
||||
lie "3.1.1"
|
||||
|
||||
locate-path@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/locate-path/download/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||
@ -11046,7 +11053,7 @@ vue-class-component@^7.1.0:
|
||||
resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-7.2.6.tgz#8471e037b8e4762f5a464686e19e5afc708502e4"
|
||||
integrity sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==
|
||||
|
||||
vue-cli-plugin-electron-builder@^2.0.0-rc.5:
|
||||
vue-cli-plugin-electron-builder@~2.0.0-rc.4:
|
||||
version "2.0.0-rc.5"
|
||||
resolved "https://registry.npm.taobao.org/vue-cli-plugin-electron-builder/download/vue-cli-plugin-electron-builder-2.0.0-rc.5.tgz#87cd8d09877f5f3ae339abc0bedc47d7d2b733ac"
|
||||
integrity sha1-h82NCYd/XzrjOavAvtxH19K3M6w=
|
||||
|
Loading…
x
Reference in New Issue
Block a user