Skip to content

Commit 6f9ef4c

Browse files
committed
fix: address verified review findings
1 parent 8f38c83 commit 6f9ef4c

22 files changed

Lines changed: 445 additions & 121 deletions

src/api/album.ts

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,103 @@
1+
import type { AxiosResponse } from 'axios'
2+
13
import request from '@/utils/http'
24

5+
export interface AlbumArtistResponse {
6+
id?: string | number
7+
name?: string
8+
}
9+
10+
export interface AlbumInfoResponse {
11+
id?: string | number
12+
name?: string
13+
picUrl?: string
14+
size?: number
15+
artist?: AlbumArtistResponse
16+
artists?: AlbumArtistResponse[]
17+
}
18+
19+
export interface AlbumTrackResponse {
20+
id?: string | number
21+
name?: string
22+
platform?: 'netease' | 'qq'
23+
server?: 'netease' | 'qq'
24+
artists?: AlbumArtistResponse[]
25+
ar?: AlbumArtistResponse[]
26+
album?: {
27+
id?: string | number
28+
name?: string
29+
picUrl?: string
30+
artist?: {
31+
img1v1Url?: string
32+
}
33+
}
34+
al?: {
35+
id?: string | number
36+
name?: string
37+
picUrl?: string
38+
artist?: {
39+
img1v1Url?: string
40+
}
41+
}
42+
duration?: number
43+
dt?: number
44+
mvid?: string | number
45+
mv?: string | number
46+
originalId?: string | number
47+
url?: string
48+
mediaId?: string | number
49+
extra?: Record<string, unknown>
50+
}
51+
52+
export interface AlbumDetailResponse {
53+
album?: AlbumInfoResponse
54+
songs?: AlbumTrackResponse[]
55+
}
56+
57+
export interface AlbumSublistResponse {
58+
count?: number
59+
data?: AlbumInfoResponse[]
60+
hasMore?: boolean
61+
}
62+
63+
type HttpResponseData<T> = AxiosResponse<T> | T
64+
65+
function unwrapResponseData<T>(response: HttpResponseData<T>): T {
66+
if (response && typeof response === 'object' && 'data' in response) {
67+
return (response as AxiosResponse<T>).data
68+
}
69+
70+
return response as T
71+
}
72+
373
/**
474
* 获取专辑详情
575
* @param {number} id - 专辑 ID
676
*/
7-
export function getAlbumDetail(id: number) {
8-
return request({
77+
export async function getAlbumDetail(id: number): Promise<AlbumDetailResponse> {
78+
const response = await request<AlbumDetailResponse>({
979
url: '/album',
1080
method: 'get',
1181
params: { id }
1282
})
83+
84+
return unwrapResponseData(response)
1385
}
1486

1587
/**
1688
* 获取当前登录用户收藏的专辑
1789
* @param {number} limit - 数量
1890
* @param {number} offset - 偏移量
1991
*/
20-
export function getAlbumSublist(limit: number = 50, offset: number = 0) {
21-
return request({
92+
export async function getAlbumSublist(
93+
limit: number = 50,
94+
offset: number = 0
95+
): Promise<AlbumSublistResponse> {
96+
const response = await request<AlbumSublistResponse>({
2297
url: '/album/sublist',
2398
method: 'get',
2499
params: { limit, offset }
25100
})
101+
102+
return unwrapResponseData(response)
26103
}

src/components/Playlist.vue

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ type PlaylistItem = {
2626
name: string
2727
}
2828
29+
function createPlaylistItemFingerprint(song: Song): string {
30+
return JSON.stringify({
31+
id: song.id,
32+
name: song.name,
33+
duration: song.duration,
34+
platform: song.platform,
35+
cover: song.album?.picUrl || '',
36+
artists: Array.isArray(song.artists) ? song.artists.map(artist => artist.name) : []
37+
})
38+
}
39+
2940
function normalizePlaylistItem(song: Song): PlaylistItem {
3041
return {
3142
id: song.id,
@@ -41,7 +52,7 @@ function normalizePlaylistItem(song: Song): PlaylistItem {
4152
4253
const renderedCount = ref(0)
4354
const normalizedSongs = shallowRef<PlaylistItem[]>([])
44-
let normalizedSongSources: Song[] = []
55+
let normalizedSongFingerprints: string[] = []
4556
const totalSongCount = computed(() => playerStore.songList.length)
4657
const currentIndex = computed(() => playerStore.currentIndex)
4758
const renderedSongCount = computed(() =>
@@ -69,12 +80,15 @@ const visibleSongs = computed(() =>
6980
7081
function syncNormalizedSongs(songList: Song[]): void {
7182
const nextNormalizedSongs = new Array<PlaylistItem>(songList.length)
72-
let changed = normalizedSongSources.length !== songList.length
83+
const nextFingerprints = new Array<string>(songList.length)
84+
let changed = normalizedSongFingerprints.length !== songList.length
7385
7486
for (let index = 0; index < songList.length; index += 1) {
7587
const song = songList[index]
88+
const fingerprint = createPlaylistItemFingerprint(song)
89+
nextFingerprints[index] = fingerprint
7690
77-
if (normalizedSongSources[index] === song) {
91+
if (normalizedSongFingerprints[index] === fingerprint) {
7892
nextNormalizedSongs[index] = normalizedSongs.value[index]
7993
continue
8094
}
@@ -87,7 +101,7 @@ function syncNormalizedSongs(songList: Song[]): void {
87101
return
88102
}
89103
90-
normalizedSongSources = songList.slice()
104+
normalizedSongFingerprints = nextFingerprints
91105
normalizedSongs.value = nextNormalizedSongs
92106
}
93107

src/components/user/AlbumDetailPanel.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const emit = defineEmits<{
7171
</div>
7272

7373
<SongDetailList
74-
v-else-if="!props.loading && props.songs.length > 0"
74+
v-else
7575
:songs="props.songs"
7676
:fallback-cover="props.album?.picUrl ?? ''"
7777
@play-song="emit('play-song', $event)"

src/components/user/FavoriteAlbumsView.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import type { FavoriteAlbumItem } from '@/composables/useFavoriteAlbums'
44
interface FavoriteAlbumsViewProps {
55
albums: FavoriteAlbumItem[]
66
loading?: boolean
7-
activeAlbumId?: string | null
8-
playingAlbumId?: string | null
7+
activeAlbumId?: FavoriteAlbumItem['id'] | null
8+
playingAlbumId?: FavoriteAlbumItem['id'] | null
99
}
1010
1111
const props = withDefaults(defineProps<FavoriteAlbumsViewProps>(), {
@@ -28,7 +28,11 @@ function handleAlbumPlay(albumId: string | number): void {
2828
}
2929
3030
function isPlayingAlbum(albumId: string | number): boolean {
31-
return String(albumId) === props.playingAlbumId
31+
return props.playingAlbumId !== null && String(albumId) === String(props.playingAlbumId)
32+
}
33+
34+
function isActiveAlbum(albumId: FavoriteAlbumItem['id']): boolean {
35+
return props.activeAlbumId !== null && String(albumId) === String(props.activeAlbumId)
3236
}
3337
3438
function formatArtistName(album: FavoriteAlbumItem): string {
@@ -52,7 +56,7 @@ function formatArtistName(album: FavoriteAlbumItem): string {
5256
v-for="album in props.albums"
5357
:key="album.id"
5458
class="album-card"
55-
:class="{ active: String(album.id) === props.activeAlbumId }"
59+
:class="{ active: isActiveAlbum(album.id) }"
5660
>
5761
<button type="button" class="album-card-hit" @click="handleAlbumOpen(album.id)">
5862
<div class="album-cover">

src/components/user/LikedSongsView.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,11 @@ function readItemHeight(): number {
161161
}
162162
163163
function clampIndex(index: number): number {
164-
if (props.likeSongs.length === 0) {
164+
if (filteredSongs.value.length === 0) {
165165
return 0
166166
}
167167
168-
return Math.min(Math.max(index, 0), props.likeSongs.length - 1)
168+
return Math.min(Math.max(index, 0), filteredSongs.value.length - 1)
169169
}
170170
171171
function syncItemHeight(): void {

src/components/user/PlaylistDetailPanel.vue

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,7 @@ const emit = defineEmits<{
6060
<p>当前歌单暂无可播放歌曲。</p>
6161
</div>
6262

63-
<SongDetailList
64-
v-else-if="!props.loading && props.songs.length > 0"
65-
:songs="props.songs"
66-
@play-song="emit('play-song', $event)"
67-
/>
63+
<SongDetailList v-else :songs="props.songs" @play-song="emit('play-song', $event)" />
6864
</section>
6965
</template>
7066

src/components/user/SongDetailList.vue

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,15 @@ const offsetY = computed(() => startIndex.value * itemHeight.value)
4343
const visibleSongs = computed(() =>
4444
props.songs.slice(startIndex.value, endIndex.value).map((song, offset) => ({
4545
index: startIndex.value + offset,
46-
song
46+
song,
47+
coverUrl: resolveSongCover(song)
48+
}))
49+
)
50+
const staticSongs = computed(() =>
51+
props.songs.map((song, index) => ({
52+
index,
53+
song,
54+
coverUrl: resolveSongCover(song)
4755
}))
4856
)
4957
@@ -217,16 +225,16 @@ onUnmounted(() => {
217225
<div class="detail-viewport" :style="{ height: `${totalHeight}px` }">
218226
<div class="detail-window" :style="{ transform: `translateY(${offsetY}px)` }">
219227
<div
220-
v-for="{ song, index } in visibleSongs"
228+
v-for="{ song, index, coverUrl } in visibleSongs"
221229
:key="`${song.id}-${index}`"
222230
class="detail-song-row"
223231
>
224232
<button type="button" class="detail-song" @click="emitPlaySong(index)">
225233
<span class="detail-song-index">{{ String(index + 1).padStart(2, '0') }}</span>
226234
<img
227-
v-if="resolveSongCover(song)"
235+
v-if="coverUrl"
228236
class="detail-song-cover"
229-
:src="resolveSongCover(song)"
237+
:src="coverUrl"
230238
:alt="song.name"
231239
loading="lazy"
232240
/>
@@ -248,17 +256,17 @@ onUnmounted(() => {
248256

249257
<div v-else class="detail-list detail-list-static">
250258
<button
251-
v-for="(song, index) in props.songs"
259+
v-for="{ song, index, coverUrl } in staticSongs"
252260
:key="`${song.id}-${index}`"
253261
type="button"
254262
class="detail-song"
255263
@click="emitPlaySong(index)"
256264
>
257265
<span class="detail-song-index">{{ String(index + 1).padStart(2, '0') }}</span>
258266
<img
259-
v-if="resolveSongCover(song)"
267+
v-if="coverUrl"
260268
class="detail-song-cover"
261-
:src="resolveSongCover(song)"
269+
:src="coverUrl"
262270
:alt="song.name"
263271
loading="lazy"
264272
/>

src/composables/useFavoriteAlbums.ts

Lines changed: 12 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { computed, ref, type ComputedRef, type Ref } from 'vue'
22

3+
import type {
4+
AlbumDetailResponse,
5+
AlbumInfoResponse as RawAlbumInfo,
6+
AlbumSublistResponse,
7+
AlbumTrackResponse as RawAlbumTrack
8+
} from '@/api/album'
39
import { getAlbumDetail, getAlbumSublist } from '@/api/album'
410
import { createSong, type Song } from '@/platform/music/interface'
511
import { isCanceledRequestError } from '@/utils/http/cancelError'
@@ -24,64 +30,6 @@ export interface UseFavoriteAlbumsReturn {
2430
loadAlbumSongs: (albumId: string | number) => Promise<Song[]>
2531
}
2632

27-
interface RawAlbumArtist {
28-
id?: string | number
29-
name?: string
30-
}
31-
32-
interface RawAlbumInfo {
33-
id?: string | number
34-
name?: string
35-
picUrl?: string
36-
size?: number
37-
artist?: RawAlbumArtist
38-
artists?: RawAlbumArtist[]
39-
}
40-
41-
interface RawAlbumTrack {
42-
id?: string | number
43-
name?: string
44-
platform?: Song['platform']
45-
server?: Song['platform']
46-
artists?: RawAlbumArtist[]
47-
ar?: RawAlbumArtist[]
48-
album?: {
49-
id?: string | number
50-
name?: string
51-
picUrl?: string
52-
artist?: {
53-
img1v1Url?: string
54-
}
55-
}
56-
al?: {
57-
id?: string | number
58-
name?: string
59-
picUrl?: string
60-
artist?: {
61-
img1v1Url?: string
62-
}
63-
}
64-
duration?: number
65-
dt?: number
66-
mvid?: string | number
67-
mv?: string | number
68-
originalId?: string | number
69-
url?: string
70-
mediaId?: string | number
71-
extra?: Record<string, unknown>
72-
}
73-
74-
interface AlbumSublistResponse {
75-
count?: number
76-
data?: RawAlbumInfo[]
77-
hasMore?: boolean
78-
}
79-
80-
interface AlbumDetailResponse {
81-
album?: RawAlbumInfo
82-
songs?: RawAlbumTrack[]
83-
}
84-
8533
const ALBUM_PAGE_SIZE = 50
8634

8735
function formatAlbumArtistName(album: RawAlbumInfo): string {
@@ -217,27 +165,28 @@ export function useFavoriteAlbums(): UseFavoriteAlbumsReturn {
217165
const response = (await task.guard(
218166
getAlbumSublist(ALBUM_PAGE_SIZE, offset)
219167
)) as AlbumSublistResponse
220-
const pageAlbums = extractFavoriteAlbums(response)
168+
const rawPageAlbums = extractFavoriteAlbums(response)
169+
const rawPageCount = rawPageAlbums.length
170+
const pageAlbums = rawPageAlbums
221171
.map(album => normalizeFavoriteAlbum(album))
222172
.filter((album): album is FavoriteAlbumItem => Boolean(album))
223173

224174
nextAlbums.push(...pageAlbums)
225175

226176
const totalCount = Number(response.count)
227177
const reachedEnd =
228-
pageAlbums.length === 0 ||
229-
(Number.isFinite(totalCount) && nextAlbums.length >= totalCount)
178+
rawPageCount === 0 || (Number.isFinite(totalCount) && nextAlbums.length >= totalCount)
230179

231180
const canInferMore =
232-
pageAlbums.length === ALBUM_PAGE_SIZE &&
181+
rawPageCount === ALBUM_PAGE_SIZE &&
233182
(!Number.isFinite(totalCount) || nextAlbums.length < totalCount)
234183

235184
hasMore = (response.hasMore ?? canInferMore) && !reachedEnd
236185
if (!hasMore) {
237186
break
238187
}
239188

240-
offset += pageAlbums.length
189+
offset += rawPageCount
241190
}
242191

243192
task.commit(() => {

0 commit comments

Comments
 (0)