add qq music search

master
Janx 2 years ago
parent 3361a5086a
commit affc7849a1

@ -21,8 +21,6 @@ export function getAlbum(id) {
return data; return data;
}); });
}; };
fetchLatest();
return getAlbumFromCache(id).then(result => { return getAlbumFromCache(id).then(result => {
return result ?? fetchLatest(); return result ?? fetchLatest();
}); });

@ -20,7 +20,9 @@ export function search(params) {
return request({ return request({
url: '/search', url: '/search',
method: 'get', method: 'get',
params, params: {
...params,
},
}).then(data => { }).then(data => {
if (data.result?.song !== undefined) if (data.result?.song !== undefined)
data.result.song.songs = mapTrackPlayableStatus(data.result.song.songs); data.result.song.songs = mapTrackPlayableStatus(data.result.song.songs);

@ -42,15 +42,21 @@ export function dailyRecommendPlaylist(params) {
* @param {number} id * @param {number} id
* @param {boolean=} noCache * @param {boolean=} noCache
*/ */
export function getPlaylistDetail(id, noCache = false) { export function getPlaylistDetail(id, noCache = false, server) {
let params = { id }; let params = { id };
if (noCache) params.timestamp = new Date().getTime(); if (noCache) params.timestamp = new Date().getTime();
return request({ return request({
url: '/playlist/detail', url: '/playlist/detail',
method: 'get', method: 'get',
params, params: {
...params,
server,
},
}).then(data => { }).then(data => {
if (data.playlist) { if (data.playlist) {
if (data.playlist.source) {
return data;
}
data.playlist.tracks = mapTrackPlayableStatus( data.playlist.tracks = mapTrackPlayableStatus(
data.playlist.tracks, data.playlist.tracks,
data.privileges || [] data.privileges || []

@ -53,8 +53,6 @@ export function getTrackDetail(ids) {
return data; return data;
}); });
}; };
fetchLatest();
let idsInArray = [String(ids)]; let idsInArray = [String(ids)];
if (typeof ids === 'string') { if (typeof ids === 'string') {
idsInArray = ids.split(','); idsInArray = ids.split(',');
@ -71,25 +69,23 @@ export function getTrackDetail(ids) {
/** /**
* 获取歌词 * 获取歌词
* 说明 : 调用此接口 , 传入音乐 id 可获得对应音乐的歌词 ( 不需要登录 ) * 说明 : 调用此接口 , 传入音乐 id 可获得对应音乐的歌词 ( 不需要登录 )
* @param {number} id - 音乐 id * @param id - 音乐 id
*/ */
export function getLyric(id) { export function getLyric(id, server) {
const fetchLatest = () => { const fetchLatest = () => {
return request({ return request({
url: '/lyric', url: '/lyric',
method: 'get', method: 'get',
params: { params: {
id, id,
server: server ? server : undefined,
}, },
}).then(result => { }).then(result => {
cacheLyric(id, result); cacheLyric(`${server ? server : ''}${id}`, result);
return result; return result;
}); });
}; };
return getLyricFromCache(`${server ? server : ''}${id}`).then(result => {
fetchLatest();
return getLyricFromCache(id).then(result => {
return result ?? fetchLatest(); return result ?? fetchLatest();
}); });
} }

@ -16,7 +16,12 @@
><svg-icon icon-class="play" /> ><svg-icon icon-class="play" />
</button> </button>
</div> </div>
<img :src="imageUrl" :style="imageStyles" loading="lazy" /> <img
:src="imageUrl"
onerror="this.src = 'https://p2.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg'; this.onerror=null;"
:style="imageStyles"
loading="lazy"
/>
<transition v-if="coverHover || alwaysShowShadow" name="fade"> <transition v-if="coverHover || alwaysShowShadow" name="fade">
<div <div
v-show="focus || alwaysShowShadow" v-show="focus || alwaysShowShadow"

@ -7,7 +7,7 @@
:class="{ artist: type === 'artist' }" :class="{ artist: type === 'artist' }"
> >
<Cover <Cover
:id="item.id" :id="parseInt(item.id)"
:image-url="getImageUrl(item)" :image-url="getImageUrl(item)"
:type="type" :type="type"
:play-button-size="type === 'artist' ? 26 : playButtonSize" :play-button-size="type === 'artist' ? 26 : playButtonSize"
@ -99,7 +99,8 @@ export default {
return this.type === 'album' && item.mark === 1056768; return this.type === 'album' && item.mark === 1056768;
}, },
getTitleLink(item) { getTitleLink(item) {
return `/${this.type}/${item.id}`; let server = this.$route.query.server;
return `/${this.type}/${item.id}?${server ? 'server=' + server : ''}`;
}, },
getImageUrl(item) { getImageUrl(item) {
if (item.img1v1Url) { if (item.img1v1Url) {

@ -41,6 +41,9 @@
@blur="inputFocus = false" @blur="inputFocus = false"
/> />
</div> </div>
<a @click="showSearchList">
<svg-icon icon-class="arrow-down" @click="showSearchList" />
</a>
</div> </div>
</div> </div>
<img <img
@ -51,7 +54,12 @@
/> />
</div> </div>
</nav> </nav>
<ContextMenu ref="showSearchList">
<div class="item" @click="toCoSearch('tencent')">
<svg-icon icon-class="settings" />
QQ音乐
</div>
</ContextMenu>
<ContextMenu ref="userProfileMenu"> <ContextMenu ref="userProfileMenu">
<div class="item" @click="toSettings"> <div class="item" @click="toSettings">
<svg-icon icon-class="settings" /> <svg-icon icon-class="settings" />
@ -155,6 +163,15 @@ export default {
showUserProfileMenu(e) { showUserProfileMenu(e) {
this.$refs.userProfileMenu.openMenu(e); this.$refs.userProfileMenu.openMenu(e);
}, },
showSearchList(e) {
this.$refs.showSearchList.openMenu(e);
},
toCoSearch(item) {
this.$router.push({
name: 'coSearch',
query: { server: item, keywords: this.keywords },
});
},
logout() { logout() {
if (!confirm('确定要退出登录吗?')) return; if (!confirm('确定要退出登录吗?')) return;
doLogout(); doLogout();

@ -28,6 +28,7 @@
<img <img
:src="currentTrack.al && currentTrack.al.picUrl | resizeImage(224)" :src="currentTrack.al && currentTrack.al.picUrl | resizeImage(224)"
loading="lazy" loading="lazy"
onerror="this.src = 'https://p2.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg'; this.onerror=null;"
@click="goToAlbum" @click="goToAlbum"
/> />
<div class="track-info" :title="audioSource"> <div class="track-info" :title="audioSource">

@ -62,11 +62,11 @@
<div :style="listStyles"> <div :style="listStyles">
<TrackListItem <TrackListItem
v-for="(track, index) in tracks" v-for="(track, index) in tracks.slice(0, maxSize)"
:key="itemKey === 'id' ? track.id : `${track.id}${index}`" :key="itemKey === 'id' ? track.id : `${track.id}${index}`"
:track-prop="track" :track-prop="track"
:highlight-playing-track="highlightPlayingTrack" :highlight-playing-track="highlightPlayingTrack"
@dblclick.native="playThisList(track.id || track.songId)" @dblclick.native="playThisList(track)"
@click.right.native="openMenu($event, track, index)" @click.right.native="openMenu($event, track, index)"
/> />
</div> </div>
@ -101,8 +101,12 @@ export default {
default: 'tracklist', default: 'tracklist',
}, // tracklist | album | playlist | cloudDisk }, // tracklist | album | playlist | cloudDisk
id: { id: {
type: Number, type: String,
default: 0, default: '0',
},
maxSize: {
type: String,
default: '20',
}, },
dbclickTrackFunc: { dbclickTrackFunc: {
type: String, type: String,
@ -195,7 +199,17 @@ export default {
}; };
this.rightClickedTrackIndex = -1; this.rightClickedTrackIndex = -1;
}, },
playThisListByTrack(track) {
this.showToast('正在进行其他平台播放');
let tracks = this.tracks.filter(_track => {
return _track.playable == 1;
});
this.player.replacePlaylist(tracks, track, 'artist', track);
},
playThisList(trackID) { playThisList(trackID) {
if (trackID.constructor === Object) this.playThisListByTrack(trackID);
trackID = trackID.id || trackID.songId;
console.log(this.dbclickTrackFunc);
if (this.dbclickTrackFunc === 'default') { if (this.dbclickTrackFunc === 'default') {
this.playThisListDefault(trackID); this.playThisListDefault(trackID);
} else if (this.dbclickTrackFunc === 'none') { } else if (this.dbclickTrackFunc === 'none') {

@ -4,6 +4,7 @@
:class="trackClass" :class="trackClass"
:style="trackStyle" :style="trackStyle"
:title="showUnavailableSongInGreyStyle ? track.reason : ''" :title="showUnavailableSongInGreyStyle ? track.reason : ''"
@click="playTrack"
@mouseover="hover = true" @mouseover="hover = true"
@mouseleave="hover = false" @mouseleave="hover = false"
> >
@ -12,7 +13,8 @@
:src="imgUrl" :src="imgUrl"
loading="lazy" loading="lazy"
:class="{ hover: focus }" :class="{ hover: focus }"
@click="goToAlbum" onerror="this.src = 'https://p2.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg'; this.onerror=null;"
@click.stop="goToAlbum"
/> />
<div v-if="showOrderNumber" class="no"> <div v-if="showOrderNumber" class="no">
<button v-show="focus && playable && !isPlaying" @click="playTrack"> <button v-show="focus && playable && !isPlaying" @click="playTrack">
@ -118,6 +120,7 @@ export default {
}, },
imgUrl() { imgUrl() {
let image = let image =
this.track?.picUrl ??
this.track?.al?.picUrl ?? this.track?.al?.picUrl ??
this.track?.album?.picUrl ?? this.track?.album?.picUrl ??
'https://p2.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg'; 'https://p2.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg';
@ -210,10 +213,16 @@ export default {
methods: { methods: {
goToAlbum() { goToAlbum() {
if (this.track.al.id === 0) return; if (this.track.al.id === 0) return;
if (this.track.sourceUrl) {
window.open(this.track.sourceUrl);
return;
}
this.$router.push({ path: '/album/' + this.track.al.id }); this.$router.push({ path: '/album/' + this.track.al.id });
}, },
playTrack() { playTrack() {
this.$parent.playThisList(this.track.id); if (this.track.constructor === Object)
this.$parent.playThisListByTrack(this.track);
else this.$parent.playThisList(this.track.id);
}, },
likeThisSong() { likeThisSong() {
this.$parent.likeATrack(this.track.id); this.$parent.likeATrack(this.track.id);

@ -83,6 +83,11 @@ const routes = [
name: 'searchType', name: 'searchType',
component: () => import('@/views/searchType.vue'), component: () => import('@/views/searchType.vue'),
}, },
{
path: '/coSearch',
name: 'coSearch',
component: () => import('@/views/coSearch.vue'),
},
{ {
path: '/new-album', path: '/new-album',
name: 'newAlbum', name: 'newAlbum',

@ -258,19 +258,15 @@ export default class {
let trackID = this._playNextList.shift(); let trackID = this._playNextList.shift();
return [trackID, this.current]; return [trackID, this.current];
} }
// 循环模式开启,则重新播放当前模式下的相对的下一首 // 循环模式开启,则重新播放当前模式下的相对的下一首
if (this.repeatMode === 'on') { if (this.repeatMode === 'on') {
if (this._reversed && this.current === 0) { if (this._reversed && this.current === 0) {
// 倒序模式,当前歌曲是第一首,则重新播放列表最后一首 // 倒序模式,当前歌曲是第一首,则重新播放列表最后一首
return [this.list[this.list.length - 1], this.list.length - 1]; return [this.list[this.list.length - 1], this.list.length - 1];
} else if (this.list.length === this.current + 1) { } else if (this.list.length === this.current + 1) {
// 正序模式,当前歌曲是最后一首,则重新播放第一首
return [this.list[0], 0]; return [this.list[0], 0];
} }
} }
// 返回 [trackID, index]
return [this.list[next], next]; return [this.list[next], next];
} }
_getPrevTrack() { _getPrevTrack() {
@ -488,6 +484,8 @@ export default class {
if (autoplay && this._currentTrack.name) { if (autoplay && this._currentTrack.name) {
this._scrobble(this.currentTrack, this._howler?.seek()); this._scrobble(this.currentTrack, this._howler?.seek());
} }
if (id.constructor === Object)
return this._replaceCurrentTrackByTrack(id, (autoplay = true));
return getTrackDetail(id).then(data => { return getTrackDetail(id).then(data => {
const track = data.songs[0]; const track = data.songs[0];
this._currentTrack = track; this._currentTrack = track;
@ -500,6 +498,20 @@ export default class {
); );
}); });
} }
_replaceCurrentTrackByTrack(track, autoplay = true) {
if (autoplay && this._currentTrack.name) {
this._scrobble(this.currentTrack, this._howler?.seek());
}
this._currentTrack = track;
if (track.url) {
let replaced = false;
if (track.id === this.currentTrackID) {
this._playAudioSource(track.url, autoplay);
replaced = true;
}
return replaced;
}
}
/** /**
* @returns 是否成功加载音频并使用加载完成的音频替换了howler实例 * @returns 是否成功加载音频并使用加载完成的音频替换了howler实例
*/ */
@ -553,7 +565,7 @@ export default class {
}); });
} }
_loadSelfFromLocalStorage() { _loadSelfFromLocalStorage() {
const player = JSON.parse(localStorage.getItem('player')); let player = JSON.parse(localStorage.getItem('player'));
if (!player) return; if (!player) return;
for (const [key, value] of Object.entries(player)) { for (const [key, value] of Object.entries(player)) {
this[key] = value; this[key] = value;
@ -766,7 +778,6 @@ export default class {
if (excludeSaveKeys.includes(key)) continue; if (excludeSaveKeys.includes(key)) continue;
player[key] = value; player[key] = value;
} }
localStorage.setItem('player', JSON.stringify(player)); localStorage.setItem('player', JSON.stringify(player));
} }
@ -833,7 +844,6 @@ export default class {
} }
this._howler?._sounds[0]._node.setSinkId(store.state.settings.outputDevice); this._howler?._sounds[0]._node.setSinkId(store.state.settings.outputDevice);
} }
replacePlaylist( replacePlaylist(
trackIDs, trackIDs,
playlistSourceID, playlistSourceID,
@ -842,6 +852,7 @@ export default class {
) { ) {
this._isPersonalFM = false; this._isPersonalFM = false;
if (!this._enabled) this._enabled = true; if (!this._enabled) this._enabled = true;
console.log(trackIDs);
this.list = trackIDs; this.list = trackIDs;
this.current = 0; this.current = 0;
this._playlistSource = { this._playlistSource = {
@ -859,6 +870,7 @@ export default class {
playAlbumByID(id, trackID = 'first') { playAlbumByID(id, trackID = 'first') {
getAlbum(id).then(data => { getAlbum(id).then(data => {
let trackIDs = data.songs.map(t => t.id); let trackIDs = data.songs.map(t => t.id);
console.log('playAlbumByID');
this.replacePlaylist(trackIDs, id, 'album', trackID); this.replacePlaylist(trackIDs, id, 'album', trackID);
}); });
} }
@ -868,12 +880,14 @@ export default class {
); );
getPlaylistDetail(id, noCache).then(data => { getPlaylistDetail(id, noCache).then(data => {
let trackIDs = data.playlist.trackIds.map(t => t.id); let trackIDs = data.playlist.trackIds.map(t => t.id);
console.log('getPlaylistDetail');
this.replacePlaylist(trackIDs, id, 'playlist', trackID); this.replacePlaylist(trackIDs, id, 'playlist', trackID);
}); });
} }
playArtistByID(id, trackID = 'first') { playArtistByID(id, trackID = 'first') {
getArtist(id).then(data => { getArtist(id).then(data => {
let trackIDs = data.hotSongs.map(t => t.id); let trackIDs = data.hotSongs.map(t => t.id);
console.log('playArtistByID');
this.replacePlaylist(trackIDs, id, 'artist', trackID); this.replacePlaylist(trackIDs, id, 'artist', trackID);
}); });
} }
@ -891,6 +905,7 @@ export default class {
const songId = data.playlist.trackIds[randomId].id; const songId = data.playlist.trackIds[randomId].id;
intelligencePlaylist({ id: songId, pid: id }).then(result => { intelligencePlaylist({ id: songId, pid: id }).then(result => {
let trackIDs = result.data.map(t => t.id); let trackIDs = result.data.map(t => t.id);
console.log('playIntelligenceListById');
this.replacePlaylist(trackIDs, id, 'playlist', trackID); this.replacePlaylist(trackIDs, id, 'playlist', trackID);
}); });
}); });

@ -20,19 +20,23 @@ export function isTrackPlayable(track) {
if (isAccountLoggedIn() && store.state.data.user.vipType === 11) { if (isAccountLoggedIn() && store.state.data.user.vipType === 11) {
result.playable = true; result.playable = true;
} else { } else {
console.log('VIP Only');
result.playable = false; result.playable = false;
result.reason = 'VIP Only'; result.reason = 'VIP Only';
} }
} else if (track.fee === 4 || track.privilege?.fee === 4) { } else if (track.fee === 4 || track.privilege?.fee === 4) {
console.log('付费专辑');
result.playable = false; result.playable = false;
result.reason = '付费专辑'; result.reason = '付费专辑';
} else if ( } else if (
track.noCopyrightRcmd !== null && track.noCopyrightRcmd !== null &&
track.noCopyrightRcmd !== undefined track.noCopyrightRcmd !== undefined
) { ) {
console.log('无版权');
result.playable = false; result.playable = false;
result.reason = '无版权'; result.reason = '无版权';
} else if (track.privilege?.st < 0 && isAccountLoggedIn()) { } else if (track.privilege?.st < 0 && isAccountLoggedIn()) {
console.log('VIP Only');
result.playable = false; result.playable = false;
result.reason = '已下架'; result.reason = '已下架';
} }

@ -133,7 +133,8 @@ export function cacheLyric(id, lyrics) {
} }
export function getLyricFromCache(id) { export function getLyricFromCache(id) {
return db.lyric.get(Number(id)).then(result => { return db.lyric.get(id).then(result => {
console.log(result);
if (!result) return undefined; if (!result) return undefined;
return result.lyrics; return result.lyrics;
}); });

@ -1,7 +1,7 @@
import router from '@/router'; import router from '@/router';
import { doLogout, getCookie } from '@/utils/auth'; import { doLogout, getCookie } from '@/utils/auth';
import axios from 'axios'; import axios from 'axios';
let baseURL = ''; let baseURL = '/api';
// Web 和 Electron 跑在不同端口避免同时启动时冲突 // Web 和 Electron 跑在不同端口避免同时启动时冲突
if (process.env.IS_ELECTRON) { if (process.env.IS_ELECTRON) {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
@ -13,7 +13,7 @@ if (process.env.IS_ELECTRON) {
baseURL = process.env.VUE_APP_NETEASE_API_URL; baseURL = process.env.VUE_APP_NETEASE_API_URL;
} }
const service = axios.create({ const service = axios.create({
baseURL: baseURL, baseURL,
withCredentials: true, withCredentials: true,
timeout: 15000, timeout: 15000,
}); });

@ -37,7 +37,11 @@
</div> </div>
<div class="cover"> <div class="cover">
<div class="cover-container"> <div class="cover-container">
<img :src="imageUrl" loading="lazy" /> <img
:src="imageUrl"
loading="lazy"
onerror="this.src='https://p2.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg'; this.onerror=null;"
/>
<div <div
class="shadow" class="shadow"
:style="{ backgroundImage: `url(${imageUrl})` }" :style="{ backgroundImage: `url(${imageUrl})` }"
@ -362,7 +366,11 @@ export default {
}, },
watch: { watch: {
currentTrack() { currentTrack() {
if (this.currentTrack.source) {
this.getLyric('tencent');
} else {
this.getLyric(); this.getLyric();
}
this.getCoverColor(); this.getCoverColor();
}, },
showLyrics(show) { showLyrics(show) {
@ -440,9 +448,8 @@ export default {
this.player.playNextTrack(); this.player.playNextTrack();
} }
}, },
getLyric() { getLyric(server) {
if (!this.currentTrack.id) return; return getLyric(this.currentTrack.id, server).then(data => {
return getLyric(this.currentTrack.id).then(data => {
if (!data?.lrc?.lyric) { if (!data?.lrc?.lyric) {
this.lyric = []; this.lyric = [];
this.tlyric = []; this.tlyric = [];
@ -912,7 +919,6 @@ export default {
.lyrics-page.no-lyric { .lyrics-page.no-lyric {
.left-side { .left-side {
transition: all 0.5s; transition: all 0.5s;
transform: translateX(27vh);
margin-right: 0; margin-right: 0;
} }
} }
@ -960,7 +966,6 @@ export default {
.slide-fade-enter, .slide-fade-enter,
.slide-fade-leave-to { .slide-fade-leave-to {
transform: translateX(27vh);
opacity: 0; opacity: 0;
} }
</style> </style>

@ -5,8 +5,8 @@
class="playlist-info" class="playlist-info"
> >
<Cover <Cover
:id="playlist.id" :id="parseInt(playlist.id)"
:image-url="playlist.coverImgUrl | resizeImage(1024)" :image-url="playlist.picUrl || playlist.coverImgUrl | resizeImage(1024)"
:show-play-button="true" :show-play-button="true"
:always-show-shadow="true" :always-show-shadow="true"
:click-cover-to-play="true" :click-cover-to-play="true"
@ -44,8 +44,11 @@
{{ playlist.updateTime | formatDate }} · {{ playlist.trackCount }} {{ playlist.updateTime | formatDate }} · {{ playlist.trackCount }}
{{ $t('common.songs') }} {{ $t('common.songs') }}
</div> </div>
<div class="description" @click="toggleFullDescription"> <div
{{ playlist.description }} class="description"
@click="toggleFullDescription"
v-html="playlist.description"
>
</div> </div>
<div class="buttons"> <div class="buttons">
<ButtonTwoTone icon-class="play" @click.native="playPlaylistByID()"> <ButtonTwoTone icon-class="play" @click.native="playPlaylistByID()">
@ -164,9 +167,10 @@
</div> </div>
<TrackList <TrackList
:id="playlist.id"
:tracks="filteredTracks" :tracks="filteredTracks"
max-size="20"
type="playlist" type="playlist"
dbclick-track-func="none"
:extra-context-menu-item=" :extra-context-menu-item="
isUserOwnPlaylist ? ['removeTrackFromPlaylist'] : [] isUserOwnPlaylist ? ['removeTrackFromPlaylist'] : []
" "
@ -447,7 +451,7 @@ export default {
}, },
loadData(id, next = undefined) { loadData(id, next = undefined) {
this.id = id; this.id = id;
getPlaylistDetail(this.id, true) getPlaylistDetail(this.id, true, this.$route.query.server || undefined)
.then(data => { .then(data => {
this.playlist = data.playlist; this.playlist = data.playlist;
this.tracks = data.playlist.tracks; this.tracks = data.playlist.tracks;

@ -14,7 +14,8 @@ module.exports = {
proxy: { proxy: {
'^/api': { '^/api': {
target: target:
'https://service-osrz4um8-1257251314.gz.apigw.tencentcs.com/release/', // 'https://service-osrz4um8-1257251314.gz.apigw.tencentcs.com/release/',
'http://127.0.0.1:3000/',
changeOrigin: true, changeOrigin: true,
pathRewrite: { pathRewrite: {
'^/api': '/', '^/api': '/',

Loading…
Cancel
Save