feat: support cloud disk

This commit is contained in:
qier222 2021-06-05 21:14:06 +08:00
parent 996904f056
commit 571d0d71f8
No known key found for this signature in database
GPG Key ID: 9C85007ED905F14D
10 changed files with 250 additions and 35 deletions

View File

@ -10,6 +10,7 @@ const table = {
'/album/detail/dynamic': require('../module/album_detail_dynamic'), '/album/detail/dynamic': require('../module/album_detail_dynamic'),
'/recommend/resource': require('../module/recommend_resource'), '/recommend/resource': require('../module/recommend_resource'),
'/playlist/subscribe': require('../module/playlist_subscribe'), '/playlist/subscribe': require('../module/playlist_subscribe'),
'/user/cloud/detail': require('../module/user_cloud_detail'),
'/playlist/catlist': require('../module/playlist_catlist'), '/playlist/catlist': require('../module/playlist_catlist'),
'/playlist/detail': require('../module/playlist_detail'), '/playlist/detail': require('../module/playlist_detail'),
'/login/cellphone': require('../module/login_cellphone'), '/login/cellphone': require('../module/login_cellphone'),
@ -17,6 +18,7 @@ const table = {
'/playlist/create': require('../module/playlist_create'), '/playlist/create': require('../module/playlist_create'),
'/playlist/tracks': require('../module/playlist_tracks'), '/playlist/tracks': require('../module/playlist_tracks'),
'/recommend/songs': require('../module/recommend_songs'), '/recommend/songs': require('../module/recommend_songs'),
'/user/cloud/del': require('../module/user_cloud_del'),
'/toplist/artist': require('../module/toplist_artist'), '/toplist/artist': require('../module/toplist_artist'),
'/artist/sublist': require('../module/artist_sublist'), '/artist/sublist': require('../module/artist_sublist'),
'/login/refresh': require('../module/login_refresh'), '/login/refresh': require('../module/login_refresh'),

View File

@ -95,6 +95,7 @@ export default {
this.$store.dispatch('fetchLikedAlbums'); this.$store.dispatch('fetchLikedAlbums');
this.$store.dispatch('fetchLikedArtists'); this.$store.dispatch('fetchLikedArtists');
this.$store.dispatch('fetchLikedMVs'); this.$store.dispatch('fetchLikedMVs');
this.$store.dispatch('fetchCloudDisk');
} }
}, },
handleScroll() { handleScroll() {

View File

@ -130,3 +130,69 @@ export function likedMVs() {
}, },
}); });
} }
/**
* 上传歌曲到云盘需要登录
*/
export function uploadSong(file) {
let formData = new FormData();
formData.append('songFile', file);
return request({
url: '/cloud',
method: 'post',
params: {
timestamp: new Date().getTime(),
},
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
/**
* 获取云盘歌曲需要登录
* 说明 : 登录后调用此接口 , 可获取云盘数据 , 获取的数据没有对应 url, 需要再调用一 /song/url 获取 url
* - limit : 返回数量 , 默认为 200
* - offset : 偏移数量用于分页 , :( 页数 -1)*200, 其中 200 limit 的值 , 默认为 0
* @param {Object} params
* @param {number} params.limit
* @param {number=} params.offset
*/
export function cloudDisk(params = {}) {
params.timestamp = new Date().getTime();
return request({
url: '/user/cloud',
method: 'get',
params,
});
}
/**
* 获取云盘歌曲详情需要登录
*/
export function cloudDiskTrackDetail(id) {
return request({
url: '/user/cloud/detail',
method: 'get',
params: {
timestamp: new Date().getTime(),
id,
},
});
}
/**
* 删除云盘歌曲需要登录
* @param {Array} id
*/
export function cloudDiskTrackDelete(id) {
return request({
url: '/user/cloud/del',
method: 'get',
params: {
timestamp: new Date().getTime(),
id,
},
});
}

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="arrow-up" class="svg-inline--fa fa-arrow-up fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"></path></svg>

After

Width:  |  Height:  |  Size: 487 B

View File

@ -240,6 +240,8 @@ export default {
this.$router.push({ path: '/library/liked-songs' }); this.$router.push({ path: '/library/liked-songs' });
} else if (this.player.playlistSource.type === 'url') { } else if (this.player.playlistSource.type === 'url') {
this.$router.push({ path: this.player.playlistSource.id }); this.$router.push({ path: this.player.playlistSource.id });
} else if (this.player.playlistSource.type === 'cloudDisk') {
this.$router.push({ path: '/library' });
} else { } else {
this.$router.push({ this.$router.push({
path: path:

View File

@ -1,14 +1,14 @@
<template> <template>
<div class="track-list"> <div class="track-list">
<ContextMenu ref="menu"> <ContextMenu ref="menu">
<div class="item-info"> <div v-show="type !== 'cloudDisk'" class="item-info">
<img :src="rightClickedTrack.al.picUrl | resizeImage(224)" /> <img :src="rightClickedTrackComputed.al.picUrl | resizeImage(224)" />
<div class="info"> <div class="info">
<div class="title">{{ rightClickedTrack.name }}</div> <div class="title">{{ rightClickedTrackComputed.name }}</div>
<div class="subtitle">{{ rightClickedTrack.ar[0].name }}</div> <div class="subtitle">{{ rightClickedTrackComputed.ar[0].name }}</div>
</div> </div>
</div> </div>
<hr /> <hr v-show="type !== 'cloudDisk'" />
<div class="item" @click="play">{{ $t('contextMenu.play') }}</div> <div class="item" @click="play">{{ $t('contextMenu.play') }}</div>
<div class="item" @click="addToQueue">{{ <div class="item" @click="addToQueue">{{
$t('contextMenu.addToQueue') $t('contextMenu.addToQueue')
@ -19,11 +19,19 @@
@click="removeTrackFromQueue" @click="removeTrackFromQueue"
>从队列删除</div >从队列删除</div
> >
<hr /> <hr v-show="type !== 'cloudDisk'" />
<div v-show="!isRightClickedTrackLiked" class="item" @click="like"> <div
v-show="!isRightClickedTrackLiked && type !== 'cloudDisk'"
class="item"
@click="like"
>
{{ $t('contextMenu.saveToMyLikedSongs') }} {{ $t('contextMenu.saveToMyLikedSongs') }}
</div> </div>
<div v-show="isRightClickedTrackLiked" class="item" @click="like"> <div
v-show="isRightClickedTrackLiked && type !== 'cloudDisk'"
class="item"
@click="like"
>
{{ $t('contextMenu.removeFromMyLikedSongs') }} {{ $t('contextMenu.removeFromMyLikedSongs') }}
</div> </div>
<div <div
@ -32,17 +40,27 @@
@click="removeTrackFromPlaylist" @click="removeTrackFromPlaylist"
>从歌单中删除</div >从歌单中删除</div
> >
<div class="item" @click="addTrackToPlaylist">{{ <div
$t('contextMenu.addToPlaylist') v-show="type !== 'cloudDisk'"
}}</div> class="item"
@click="addTrackToPlaylist"
>{{ $t('contextMenu.addToPlaylist') }}</div
>
<div
v-if="extraContextMenuItem.includes('removeTrackFromCloudDisk')"
class="item"
@click="removeTrackFromCloudDisk"
>从云盘中删除</div
>
</ContextMenu> </ContextMenu>
<div :style="listStyles"> <div :style="listStyles">
<TrackListItem <TrackListItem
v-for="(track, index) in tracks" v-for="(track, index) in tracks"
:key="itemKey === 'id' ? track.id : `${track.id}${index}`" :key="itemKey === 'id' ? track.id : `${track.id}${index}`"
:track="track" :track-prop="track"
:highlight-playing-track="highlightPlayingTrack" :highlight-playing-track="highlightPlayingTrack"
@dblclick.native="playThisList(track.id)" @dblclick.native="playThisList(track.id || track.songId)"
@click.right.native="openMenu($event, track, index)" @click.right.native="openMenu($event, track, index)"
/> />
</div> </div>
@ -52,6 +70,7 @@
<script> <script>
import { mapActions, mapMutations, mapState } from 'vuex'; import { mapActions, mapMutations, mapState } from 'vuex';
import { addOrRemoveTrackFromPlaylist } from '@/api/playlist'; import { addOrRemoveTrackFromPlaylist } from '@/api/playlist';
import { cloudDiskTrackDelete } from '@/api/user';
import { isAccountLoggedIn } from '@/utils/auth'; import { isAccountLoggedIn } from '@/utils/auth';
import TrackListItem from '@/components/TrackListItem.vue'; import TrackListItem from '@/components/TrackListItem.vue';
@ -65,9 +84,20 @@ export default {
ContextMenu, ContextMenu,
}, },
props: { props: {
tracks: Array, tracks: {
type: String, type: Array,
id: Number, default: () => {
return [];
},
},
type: {
type: String,
default: 'tracklist',
}, // tracklist | album | playlist | cloudDisk
id: {
type: Number,
default: 0,
},
dbclickTrackFunc: { dbclickTrackFunc: {
type: String, type: String,
default: 'default', default: 'default',
@ -88,6 +118,7 @@ export default {
return [ return [
// 'removeTrackFromPlaylist' // 'removeTrackFromPlaylist'
// 'removeTrackFromQueue' // 'removeTrackFromQueue'
// 'removeTrackFromCloudDisk'
]; ];
}, },
}, },
@ -121,6 +152,16 @@ export default {
isRightClickedTrackLiked() { isRightClickedTrackLiked() {
return this.liked.songs.includes(this.rightClickedTrack?.id); return this.liked.songs.includes(this.rightClickedTrack?.id);
}, },
rightClickedTrackComputed() {
return this.type === 'cloudDisk'
? {
id: 0,
name: '',
ar: [{ name: '' }],
al: { picUrl: '' },
}
: this.rightClickedTrack;
},
}, },
created() { created() {
if (this.type === 'tracklist') { if (this.type === 'tracklist') {
@ -158,11 +199,14 @@ export default {
} else if (this.dbclickTrackFunc === 'playPlaylistByID') { } else if (this.dbclickTrackFunc === 'playPlaylistByID') {
this.player.playPlaylistByID(this.id, trackID); this.player.playPlaylistByID(this.id, trackID);
} else if (this.dbclickTrackFunc === 'playAList') { } else if (this.dbclickTrackFunc === 'playAList') {
let trackIDs = this.tracks.map(t => t.id); let trackIDs = this.tracks.map(t => t.id || t.songId);
this.player.replacePlaylist(trackIDs, this.id, 'artist', trackID); this.player.replacePlaylist(trackIDs, this.id, 'artist', trackID);
} else if (this.dbclickTrackFunc === 'dailyTracks') { } else if (this.dbclickTrackFunc === 'dailyTracks') {
let trackIDs = this.tracks.map(t => t.id); let trackIDs = this.tracks.map(t => t.id);
this.player.replacePlaylist(trackIDs, '/daily/songs', 'url', trackID); this.player.replacePlaylist(trackIDs, '/daily/songs', 'url', trackID);
} else if (this.dbclickTrackFunc === 'playCloudDisk') {
let trackIDs = this.tracks.map(t => t.id || t.songId);
this.player.replacePlaylist(trackIDs, this.id, 'cloudDisk', trackID);
} }
}, },
playThisListDefault(trackID) { playThisListDefault(trackID) {
@ -226,6 +270,23 @@ export default {
this.rightClickedTrackIndex this.rightClickedTrackIndex
); );
}, },
removeTrackFromCloudDisk() {
if (confirm(`确定要从云盘删除 ${this.rightClickedTrack.songName}`)) {
let trackID = this.rightClickedTrack.songId;
cloudDiskTrackDelete(trackID).then(data => {
this.showToast(
data.code === 200 ? '已将此歌曲从云盘删除' : data.message
);
let newCloudDisk = this.liked.cloudDisk.filter(
t => t.songId !== trackID
);
this.$store.commit('updateLikedXXX', {
name: 'cloudDisk',
data: newCloudDisk,
});
});
}
},
}, },
}; };
</script> </script>

View File

@ -13,7 +13,7 @@
:class="{ hover: focus }" :class="{ hover: focus }"
@click="goToAlbum" @click="goToAlbum"
/> />
<div v-if="isAlbum" class="no"> <div v-if="showOrderNumber" class="no">
<button v-show="focus && track.playable && !isPlaying" @click="playTrack"> <button v-show="focus && track.playable && !isPlaying" @click="playTrack">
<svg-icon <svg-icon
icon-class="play" icon-class="play"
@ -37,7 +37,7 @@
<span v-if="isAlbum" class="featured"> <span v-if="isAlbum" class="featured">
<ArtistsInLine <ArtistsInLine
:artists="track.ar" :artists="track.ar"
:exclude="this.$parent.albumObject.artist.name" :exclude="$parent.albumObject.artist.name"
prefix="-" prefix="-"
/></span> /></span>
<span v-if="isAlbum && track.mark === 1318912" class="explicit-symbol" <span v-if="isAlbum && track.mark === 1318912" class="explicit-symbol"
@ -58,11 +58,13 @@
</div> </div>
<div></div> <div></div>
</div> </div>
<div v-if="!isTracklist && !isAlbum" class="album">
<div v-if="showAlbumName" class="album">
<router-link :to="`/album/${album.id}`">{{ album.name }}</router-link> <router-link :to="`/album/${album.id}`">{{ album.name }}</router-link>
<div></div> <div></div>
</div> </div>
<div v-if="!isTracklist" class="actions">
<div v-if="showLikeButton" class="actions">
<button @click="likeThisSong"> <button @click="likeThisSong">
<svg-icon <svg-icon
icon-class="heart" icon-class="heart"
@ -73,7 +75,7 @@
<svg-icon v-show="isLiked" icon-class="heart-solid"></svg-icon> <svg-icon v-show="isLiked" icon-class="heart-solid"></svg-icon>
</button> </button>
</div> </div>
<div v-if="!isTracklist" class="time"> <div v-if="showTrackTime" class="time">
{{ track.dt | formatTime }} {{ track.dt | formatTime }}
</div> </div>
</div> </div>
@ -87,18 +89,26 @@ import { mapState } from 'vuex';
export default { export default {
name: 'TrackListItem', name: 'TrackListItem',
components: { ArtistsInLine, ExplicitSymbol }, components: { ArtistsInLine, ExplicitSymbol },
props: { props: {
track: Object, trackProp: Object,
highlightPlayingTrack: { highlightPlayingTrack: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
}, },
data() { data() {
return { hover: false, trackStyle: {} }; return { hover: false, trackStyle: {} };
}, },
computed: { computed: {
...mapState(['settings']), ...mapState(['settings']),
track() {
return this.type === 'cloudDisk'
? this.trackProp.simpleSong
: this.trackProp;
},
imgUrl() { imgUrl() {
let image = let image =
this.track?.al?.picUrl ?? this.track?.al?.picUrl ??
@ -112,7 +122,7 @@ export default {
return []; return [];
}, },
album() { album() {
return this.track.album || this.track.al; return this.track.album || this.track.al || this.track?.simpleSong?.al;
}, },
translate() { translate() {
let t; let t;
@ -134,17 +144,14 @@ export default {
this.track.alia?.length > 0 this.track.alia?.length > 0
); );
}, },
isTracklist() {
return this.type === 'tracklist';
},
isPlaylist() { isPlaylist() {
return this.type === 'playlist'; return this.type === 'playlist';
}, },
isLiked() { isLiked() {
return this.$parent.liked.songs.includes(this.track.id); return this.$parent.liked.songs.includes(this.track?.id);
}, },
isPlaying() { isPlaying() {
return this.$store.state.player.currentTrack.id === this.track.id; return this.$store.state.player.currentTrack.id === this.track?.id;
}, },
trackClass() { trackClass() {
let trackClass = [this.type]; let trackClass = [this.type];
@ -169,7 +176,20 @@ export default {
? !this.$store.state.settings.enableUnblockNeteaseMusic ? !this.$store.state.settings.enableUnblockNeteaseMusic
: true; : true;
}, },
showLikeButton() {
return this.type !== 'tracklist' && this.type !== 'cloudDisk';
},
showOrderNumber() {
return this.type === 'album';
},
showAlbumName() {
return this.type !== 'album' && this.type !== 'tracklist';
},
showTrackTime() {
return this.type !== 'tracklist';
},
}, },
methods: { methods: {
goToAlbum() { goToAlbum() {
this.$router.push({ path: '/album/' + this.track.al.id }); this.$router.push({ path: '/album/' + this.track.al.id });

View File

@ -9,6 +9,7 @@ import {
likedAlbums, likedAlbums,
likedArtists, likedArtists,
likedMVs, likedMVs,
cloudDisk,
} from '@/api/user'; } from '@/api/user';
export default { export default {
@ -147,4 +148,16 @@ export default {
} }
}); });
}, },
fetchCloudDisk: ({ commit }) => {
if (!isAccountLoggedIn()) return;
return cloudDisk().then(result => {
console.log(result);
if (result.data) {
commit('updateLikedXXX', {
name: 'cloudDisk',
data: result.data,
});
}
});
},
}; };

View File

@ -19,6 +19,7 @@ export default {
albums: [], albums: [],
artists: [], artists: [],
mvs: [], mvs: [],
cloudDisk: [],
}, },
contextMenu: { contextMenu: {
clickObjectID: 0, clickObjectID: 0,

View File

@ -80,14 +80,26 @@
> >
{{ $t('library.mvs') }} {{ $t('library.mvs') }}
</div> </div>
<div
class="tab"
:class="{ active: currentTab === 'cloudDisk' }"
@click="updateCurrentTab('cloudDisk')"
>
云盘
</div>
</div> </div>
<button <button
v-show="currentTab === 'playlists'" v-show="currentTab === 'playlists'"
class="add-playlist" class="tab-button"
icon="plus"
@click="openAddPlaylistModal" @click="openAddPlaylistModal"
><svg-icon icon-class="plus" />{{ $t('library.newPlayList') }}</button ><svg-icon icon-class="plus" />{{ $t('library.newPlayList') }}
> </button>
<button
v-show="currentTab === 'cloudDisk'"
class="tab-button"
@click="selectUploadFiles"
><svg-icon icon-class="arrow-up-alt" /> 上传歌曲
</button>
</div> </div>
<div v-show="currentTab === 'playlists'"> <div v-show="currentTab === 'playlists'">
@ -121,8 +133,26 @@
<div v-show="currentTab === 'mvs'"> <div v-show="currentTab === 'mvs'">
<MvRow :mvs="liked.mvs" /> <MvRow :mvs="liked.mvs" />
</div> </div>
<div v-show="currentTab === 'cloudDisk'">
<TrackList
:id="-8"
:tracks="liked.cloudDisk"
:column-number="3"
type="cloudDisk"
dbclick-track-func="playCloudDisk"
:extra-context-menu-item="['removeTrackFromCloudDisk']"
/>
</div>
</div> </div>
<input
ref="cloudDiskUploadInput"
type="file"
style="display: none"
@change="uploadSongToCloudDisk"
/>
<ContextMenu> <ContextMenu>
<div class="item" @click="changePlaylistFilter('all')">{{ <div class="item" @click="changePlaylistFilter('all')">{{
$t('contextMenu.allPlaylists') $t('contextMenu.allPlaylists')
@ -140,9 +170,10 @@
<script> <script>
import { mapActions, mapMutations, mapState } from 'vuex'; import { mapActions, mapMutations, mapState } from 'vuex';
import { getLyric } from '@/api/track';
import { randomNum, dailyTask } from '@/utils/common'; import { randomNum, dailyTask } from '@/utils/common';
import { isAccountLoggedIn } from '@/utils/auth'; import { isAccountLoggedIn } from '@/utils/auth';
import { uploadSong } from '@/api/user';
import { getLyric } from '@/api/track';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import locale from '@/locale'; import locale from '@/locale';
@ -227,6 +258,7 @@ export default {
this.$store.dispatch('fetchLikedAlbums'); this.$store.dispatch('fetchLikedAlbums');
this.$store.dispatch('fetchLikedArtists'); this.$store.dispatch('fetchLikedArtists');
this.$store.dispatch('fetchLikedMVs'); this.$store.dispatch('fetchLikedMVs');
this.$store.dispatch('fetchCloudDisk');
}, },
playLikedSongs() { playLikedSongs() {
this.$store.state.player.playPlaylistByID( this.$store.state.player.playPlaylistByID(
@ -272,6 +304,22 @@ export default {
this.updateData({ key: 'libraryPlaylistFilter', value: type }); this.updateData({ key: 'libraryPlaylistFilter', value: type });
window.scrollTo({ top: 375, behavior: 'smooth' }); window.scrollTo({ top: 375, behavior: 'smooth' });
}, },
selectUploadFiles() {
this.$refs.cloudDiskUploadInput.click();
},
uploadSongToCloudDisk(e) {
const files = e.target.files;
uploadSong(files[0]).then(result => {
if (result.code === 200) {
let newCloudDisk = this.liked.cloudDisk;
newCloudDisk.unshift(result.privateCloud);
this.$store.commit('updateLikedXXX', {
name: 'cloudDisk',
data: newCloudDisk,
});
}
});
},
}, },
}; };
</script> </script>
@ -427,7 +475,7 @@ h1 {
} }
} }
button.add-playlist { button.tab-button {
color: var(--color-text); color: var(--color-text);
border-radius: 8px; border-radius: 8px;
padding: 0 14px; padding: 0 14px;