feat: handle empty play queue gracefully and enhance UI responsiveness
This commit is contained in:
		
							parent
							
								
									09a99f75ae
								
							
						
					
					
						commit
						1239dbbf86
					
				| 
						 | 
				
			
			@ -302,6 +302,9 @@ function makePlayQueueListDismiss() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
function getCurrentTrack() {
 | 
			
		||||
	if (playQueueStore.list.length === 0) {
 | 
			
		||||
		return null
 | 
			
		||||
	}
 | 
			
		||||
	if (playQueueStore.playMode.shuffle) {
 | 
			
		||||
		return playQueueStore.list[playQueueStore.shuffleList[playQueueStore.currentIndex]]
 | 
			
		||||
	} else {
 | 
			
		||||
| 
						 | 
				
			
			@ -350,7 +353,9 @@ function toggleMoreOptions() {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(() => [preferences.presentLyrics, getCurrentTrack().song.lyricUrl], (newValue, oldValue) => {
 | 
			
		||||
watch(() => [preferences.presentLyrics, getCurrentTrack()?.song.lyricUrl], (newValue, oldValue) => {
 | 
			
		||||
	if (!getCurrentTrack()) { return }
 | 
			
		||||
 | 
			
		||||
	const [showLyrics, hasLyricUrl] = newValue
 | 
			
		||||
	const [prevShowLyrics, _prevHasLyricUrl] = oldValue || [false, null]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -436,319 +441,335 @@ watch(() => playQueueStore.currentIndex, () => {
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	<!-- Background remains unchanged -->
 | 
			
		||||
	<div class="z-0 absolute top-0 left-0 w-screen h-screen overflow-hidden" v-if="getCurrentTrack().album?.coverDeUrl">
 | 
			
		||||
		<img class="w-full h-full blur-2xl object-cover scale-110" :src="getCurrentTrack().album?.coverDeUrl" />
 | 
			
		||||
		<div class="w-full h-full absolute top-0 left-0 bg-neutral-900/5" />
 | 
			
		||||
	</div>
 | 
			
		||||
	<div v-if="getCurrentTrack() !== null">
 | 
			
		||||
		<!-- Background remains unchanged -->
 | 
			
		||||
		<div class="z-0 absolute top-0 left-0 w-screen h-screen overflow-hidden"
 | 
			
		||||
			v-if="getCurrentTrack()?.album?.coverDeUrl">
 | 
			
		||||
			<img class="w-full h-full blur-2xl object-cover scale-110" :src="getCurrentTrack()?.album?.coverDeUrl" />
 | 
			
		||||
			<div class="w-full h-full absolute top-0 left-0 bg-neutral-900/5" />
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
	<!-- Main content area - new centered flex layout -->
 | 
			
		||||
	<div class="absolute top-0 left-0 flex justify-center h-screen w-screen overflow-y-auto z-10 select-none">
 | 
			
		||||
		<div class="flex items-center justify-center gap-16 h-fit my-auto" ref="mainContainer">
 | 
			
		||||
		<!-- Main content area - new centered flex layout -->
 | 
			
		||||
		<div class="absolute top-0 left-0 flex justify-center h-screen w-screen overflow-y-auto z-10 select-none">
 | 
			
		||||
			<div class="flex items-center justify-center gap-16 h-fit my-auto" ref="mainContainer">
 | 
			
		||||
 | 
			
		||||
			<!-- Controller area -->
 | 
			
		||||
			<div class="flex flex-col w-96 gap-4" ref="controllerRef">
 | 
			
		||||
				<!-- Album cover with enhanced hover effect -->
 | 
			
		||||
				<div ref="albumCover" class="relative">
 | 
			
		||||
					<img :src="getCurrentTrack().album?.coverUrl" class="rounded-2xl shadow-2xl border border-white/20 w-96 h-96
 | 
			
		||||
				<!-- Controller area -->
 | 
			
		||||
				<div class="flex flex-col w-96 gap-4" ref="controllerRef">
 | 
			
		||||
					<!-- Album cover with enhanced hover effect -->
 | 
			
		||||
					<div ref="albumCover" class="relative">
 | 
			
		||||
						<img :src="getCurrentTrack()?.album?.coverUrl" class="rounded-2xl shadow-2xl border border-white/20 w-96 h-96
 | 
			
		||||
							transition-transform duration-300
 | 
			
		||||
							" :class="playQueueStore.isPlaying ? 'scale-100' : 'scale-85'" />
 | 
			
		||||
				</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
				<!-- Song info with enhanced styling -->
 | 
			
		||||
				<div class="flex justify-between items-center gap-2" ref="songInfo">
 | 
			
		||||
					<div class="relative flex-auto w-0">
 | 
			
		||||
						<!-- ...existing song info code... -->
 | 
			
		||||
						<div class="">
 | 
			
		||||
							<div class="text-black/90 blur-lg text-lg font-medium truncate w-80">
 | 
			
		||||
								{{ getCurrentTrack().song.name }}
 | 
			
		||||
					<!-- Song info with enhanced styling -->
 | 
			
		||||
					<div class="flex justify-between items-center gap-2" ref="songInfo">
 | 
			
		||||
						<div class="relative flex-auto w-0">
 | 
			
		||||
							<!-- ...existing song info code... -->
 | 
			
		||||
							<div class="">
 | 
			
		||||
								<div class="text-black/90 blur-lg text-lg font-medium truncate w-80">
 | 
			
		||||
									{{ getCurrentTrack()?.song.name }}
 | 
			
		||||
								</div>
 | 
			
		||||
								<div class="text-black/90 blur-lg text-base truncate w-80">
 | 
			
		||||
									{{ getCurrentTrack()?.song.artists ?? [] }} —
 | 
			
		||||
									{{ getCurrentTrack()?.album?.name ?? '未知专辑' }}
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="text-black/90 blur-lg text-base truncate w-80">
 | 
			
		||||
								{{ getCurrentTrack().song.artists ?? [] }} —
 | 
			
		||||
								{{ getCurrentTrack().album?.name ?? '未知专辑' }}
 | 
			
		||||
 | 
			
		||||
							<div class="absolute top-0">
 | 
			
		||||
								<div class="text-white text-lg font-medium truncate w-80">
 | 
			
		||||
									{{ getCurrentTrack()?.song.name }}
 | 
			
		||||
								</div>
 | 
			
		||||
								<div class="text-white/75 text-base truncate w-80">
 | 
			
		||||
									{{ artistsOrganize(getCurrentTrack()?.song.artists ?? []) }} —
 | 
			
		||||
									{{ getCurrentTrack()?.album?.name ?? '未知专辑' }}
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="absolute top-0">
 | 
			
		||||
							<div class="text-white text-lg font-medium truncate w-80">
 | 
			
		||||
								{{ getCurrentTrack().song.name }}
 | 
			
		||||
						<button
 | 
			
		||||
							class="h-10 w-10 flex justify-center items-center rounded-full backdrop-blur-3xl transition-all duration-200 hover:scale-110"
 | 
			
		||||
							ref="favoriteButton"
 | 
			
		||||
							@click="() => { const track = getCurrentTrack(); if (track !== null) favourites.toggleFavourite(track) }"
 | 
			
		||||
							:class="getCurrentTrack() && favourites.isFavourite(getCurrentTrack()!.song.cid) ? 'bg-neutral-200/90' : 'bg-black/10 hover:bg-black/20'">
 | 
			
		||||
							<span
 | 
			
		||||
								:class="getCurrentTrack() !== null && favourites.isFavourite(getCurrentTrack()!.song.cid) ? 'text-neutral-700' : 'text-white'">
 | 
			
		||||
								<StarFilledIcon v-if="getCurrentTrack() && favourites.isFavourite(getCurrentTrack()!.song.cid)"
 | 
			
		||||
									:size="6" />
 | 
			
		||||
								<StarEmptyIcon v-else :size="6" />
 | 
			
		||||
							</span>
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<!-- Progress section -->
 | 
			
		||||
					<div class="flex flex-col gap-1" ref="progressSection">
 | 
			
		||||
						<!-- ...existing progress bar code... -->
 | 
			
		||||
						<div
 | 
			
		||||
							class="w-full p-[0.125rem] bg-white/20 shadow-[0_.125rem_1rem_0_#00000010] rounded-full backdrop-blur-3xl">
 | 
			
		||||
							<div class="w-full" ref="progressBarContainer">
 | 
			
		||||
								<div class="w-2 h-2 bg-white rounded-full shadow-md" ref="progressBarThumb" />
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="text-white/75 text-base truncate w-80">
 | 
			
		||||
								{{ artistsOrganize(getCurrentTrack().song.artists ?? []) }} —
 | 
			
		||||
								{{ getCurrentTrack().album?.name ?? '未知专辑' }}
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="w-full flex justify-between">
 | 
			
		||||
							<!-- ...existing time display code... -->
 | 
			
		||||
							<div class="font-medium flex-1 text-left relative">
 | 
			
		||||
								<span
 | 
			
		||||
									class="text-black blur-lg absolute top-0">{{ timeFormatter(Math.floor(playQueueStore.currentTime)) }}</span>
 | 
			
		||||
								<span
 | 
			
		||||
									class="text-white/90 absolute top-0">{{ timeFormatter(Math.floor(playQueueStore.currentTime)) }}</span>
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="text-xs text-center relative flex-1">
 | 
			
		||||
								<span class="text-black blur-lg absolute top-0">{{ formatDetector() }}</span>
 | 
			
		||||
								<span class="text-white">{{ formatDetector() }}</span>
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="flex flex-1">
 | 
			
		||||
								<div class="flex-1" />
 | 
			
		||||
								<button
 | 
			
		||||
									class="text-white/90 font-medium text-right relative transition-colors duration-200 hover:text-white"
 | 
			
		||||
									@click="preferences.displayTimeLeft = !preferences.displayTimeLeft">
 | 
			
		||||
									<span
 | 
			
		||||
										class="text-black blur-lg absolute top-0">{{ `${preferences.displayTimeLeft ? '-' : ''}${timeFormatter(preferences.displayTimeLeft ? Math.floor(playQueueStore.duration) - Math.floor(playQueueStore.currentTime) : playQueueStore.duration)}` }}</span>
 | 
			
		||||
									<span>{{ `${preferences.displayTimeLeft ? '-' : ''}${timeFormatter(preferences.displayTimeLeft ? Math.floor(playQueueStore.duration) - Math.floor(playQueueStore.currentTime) : playQueueStore.duration)}` }}</span>
 | 
			
		||||
								</button>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<!-- Control buttons -->
 | 
			
		||||
					<div class="w-full flex justify-between items-center" ref="controlButtons">
 | 
			
		||||
						<div class="flex-1 text-left flex gap-1">
 | 
			
		||||
							<button class="h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 relative"
 | 
			
		||||
								ref="volumeButton" @click="toggleVolumeControl">
 | 
			
		||||
								<div class="w-6 h-6 relative">
 | 
			
		||||
									<span class="text-black blur-md absolute top-0 left-0 transition-all duration-200 hover:scale-110">
 | 
			
		||||
										<SpeakerIcon :size="6" />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-white absolute top-0 left-0 transition-all duration-200 hover:scale-110">
 | 
			
		||||
										<SpeakerIcon :size="6" />
 | 
			
		||||
									</span>
 | 
			
		||||
								</div>
 | 
			
		||||
 | 
			
		||||
								<transition name="volume-control-fade">
 | 
			
		||||
									<div v-if="presentVolumeControl" @click.stop
 | 
			
		||||
										class="absolute bg-black/60 backdrop-blur-3xl rounded-md shadow-2xl border border-[#ffffff39] w-64 h-10 bottom-10 left-[-0.3rem] flex items-center justify-between px-4 z-50">
 | 
			
		||||
										<div class="w-full py-[0.125rem] px-[0.25rem] bg-white/20 rounded-full" ref="volumeSliderContainer">
 | 
			
		||||
											<div class="w-2 h-2 bg-white rounded-full shadow-md cursor-pointer" ref="volumeSliderThumb" />
 | 
			
		||||
										</div>
 | 
			
		||||
										<!-- <span class="text-white text-xs w-8 text-right flex-shrink-0">{{ Math.round(volume * 100) }}%</span> -->
 | 
			
		||||
									</div>
 | 
			
		||||
								</transition>
 | 
			
		||||
							</button>
 | 
			
		||||
							<button
 | 
			
		||||
								class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
 | 
			
		||||
								@click="makePlayQueueListPresent" ref="queueButton">
 | 
			
		||||
								<div class="w-6 h-6 relative">
 | 
			
		||||
									<span class="text-black blur-md absolute top-0 left-0">
 | 
			
		||||
										<MusicListIcon :size="6" />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-white absolute top-0 left-0">
 | 
			
		||||
										<MusicListIcon :size="6" />
 | 
			
		||||
									</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex-2 text-center align-center justify-center gap-2 flex">
 | 
			
		||||
							<button
 | 
			
		||||
								class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200 hover:scale-105"
 | 
			
		||||
								@click="playPrevious" ref="prevButton">
 | 
			
		||||
								<div class="w-8 h-8 relative">
 | 
			
		||||
									<span class="text-black/80 blur-lg absolute top-0 left-0">
 | 
			
		||||
										<RewindIcon :size="8" />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-white absolute top-0 left-0">
 | 
			
		||||
										<RewindIcon :size="8" />
 | 
			
		||||
									</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</button>
 | 
			
		||||
 | 
			
		||||
							<button
 | 
			
		||||
								class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200"
 | 
			
		||||
								@click="handlePlayPause" ref="playButton">
 | 
			
		||||
								<!-- ...existing play/pause icon code... -->
 | 
			
		||||
								<div v-if="playQueueStore.isPlaying">
 | 
			
		||||
									<div v-if="playQueueStore.isBuffering" class="w-6 h-6 relative">
 | 
			
		||||
										<span class="text-black/80 blur-lg absolute top-0 left-0">
 | 
			
		||||
											<LoadingIndicator :size="6" />
 | 
			
		||||
										</span>
 | 
			
		||||
										<span class="text-white absolute top-0 left-0">
 | 
			
		||||
											<LoadingIndicator :size="6" />
 | 
			
		||||
										</span>
 | 
			
		||||
									</div>
 | 
			
		||||
									<div v-else class="w-8 h-8 relative">
 | 
			
		||||
										<span class="text-black blur-md absolute top-0 left-0">
 | 
			
		||||
											<PauseIcon :size="8" />
 | 
			
		||||
										</span>
 | 
			
		||||
										<span class="text-white absolute top-0 left-0">
 | 
			
		||||
											<PauseIcon :size="8" />
 | 
			
		||||
										</span>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
								<div v-else>
 | 
			
		||||
									<div class="w-8 h-8 relative">
 | 
			
		||||
										<span class="text-black/80 blur-lg absolute top-0 left-0">
 | 
			
		||||
											<PlayIcon :size="8" />
 | 
			
		||||
										</span>
 | 
			
		||||
										<span class="text-white absolute top-0 left-0">
 | 
			
		||||
											<PlayIcon :size="8" />
 | 
			
		||||
										</span>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</button>
 | 
			
		||||
 | 
			
		||||
							<button
 | 
			
		||||
								class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200 hover:scale-105"
 | 
			
		||||
								@click="playNext" ref="nextButton">
 | 
			
		||||
								<div class="w-8 h-8 relative">
 | 
			
		||||
									<span class="text-black/80 blur-lg absolute top-0 left-0">
 | 
			
		||||
										<ForwardIcon :size="8" />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-white absolute top-0 left-0">
 | 
			
		||||
										<ForwardIcon :size="8" />
 | 
			
		||||
									</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="flex-1 text-right flex gap-1">
 | 
			
		||||
							<div class="flex-1" />
 | 
			
		||||
							<!-- Lyrics button with tooltip only on hover -->
 | 
			
		||||
							<div @mouseenter="showLyricsTooltip = true" @mouseleave="showLyricsTooltip = false" class="relative">
 | 
			
		||||
								<button
 | 
			
		||||
									class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
 | 
			
		||||
									ref="lyricsButton" @click="preferences.presentLyrics = !preferences.presentLyrics">
 | 
			
		||||
									<div class="w-6 h-6 relative">
 | 
			
		||||
										<span class="text-white absolute top-0 left-0 z-10">
 | 
			
		||||
											<ChatBubbleQuoteFullIcon :size="6" v-if="preferences.presentLyrics" />
 | 
			
		||||
											<ChatBubbleQuoteIcon :size="6" v-else />
 | 
			
		||||
										</span>
 | 
			
		||||
										<span class="text-black/40 blur-md absolute top-0 left-0 z-0">
 | 
			
		||||
											<ChatBubbleQuoteFullIcon :size="6" v-if="preferences.presentLyrics" />
 | 
			
		||||
											<ChatBubbleQuoteIcon :size="6" v-else />
 | 
			
		||||
										</span>
 | 
			
		||||
									</div>
 | 
			
		||||
								</button>
 | 
			
		||||
								<!-- Show tooltip only on hover, with transition -->
 | 
			
		||||
								<transition name="lyrics-tooltip-fade">
 | 
			
		||||
									<div v-if="showLyricsTooltip && getCurrentTrack() === null && !getCurrentTrack()!.song.lyricUrl"
 | 
			
		||||
										class="absolute bottom-10 w-60 left-[-7rem] bg-black/60 backdrop-blur-3xl rounded-md p-2 text-xs flex flex-col text-left shadow-2xl border border-[#ffffff39]">
 | 
			
		||||
										<div class="font-semibold text-white">这首曲目不提供歌词文本</div>
 | 
			
		||||
										<div class="text-white/60">启用歌词时,将会在下一首有歌词的曲目中显示歌词文本。</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								</transition>
 | 
			
		||||
							</div>
 | 
			
		||||
							<button
 | 
			
		||||
								class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all"
 | 
			
		||||
								@click="toggleMoreOptions">
 | 
			
		||||
								<div class="w-6 h-6 relative">
 | 
			
		||||
									<span class="text-black blur-sm absolute top-0 left-0 hover:scale-110 transition-all">
 | 
			
		||||
										<EllipsisHorizontalIcon :size="6" />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-white absolute top-0 left-0 hover:scale-110 transition-all">
 | 
			
		||||
										<EllipsisHorizontalIcon :size="6" />
 | 
			
		||||
									</span>
 | 
			
		||||
 | 
			
		||||
									<dialog :open="showMoreOptions" @click.self="toggleMoreOptions" ref="moreOptionsDialog"
 | 
			
		||||
										class="bottom-8 left-[-13.3rem] w-60 rounded-md overflow-hidden bg-black/60 backdrop-blur-3xl shadow-2xl border border-[#ffffff39]">
 | 
			
		||||
										<ul class="my-2 flex flex-col gap-1">
 | 
			
		||||
											<li>
 | 
			
		||||
												<button
 | 
			
		||||
													class="flex px-2 py-1 hover:bg-white/10 w-full text-left disabled:opacity-70 cursor-not-allowed transition-colors duration-150"
 | 
			
		||||
													disabled>
 | 
			
		||||
													<MuscialNoteSparklingIcon :size="4" class="text-white mr-2" />
 | 
			
		||||
													<div class="flex-col">
 | 
			
		||||
														<div class="text-white text-sm">音频质量</div>
 | 
			
		||||
														<div class="text-white/60 text-xs">改不了,海猫没给这个选项!</div>
 | 
			
		||||
													</div>
 | 
			
		||||
												</button>
 | 
			
		||||
											</li>
 | 
			
		||||
 | 
			
		||||
											<li>
 | 
			
		||||
												<button
 | 
			
		||||
													class="flex px-2 py-1 hover:bg-white/10 w-full text-left disabled:opacity-70 cursor-not-allowed transition-colors duration-150"
 | 
			
		||||
													disabled>
 | 
			
		||||
													<CastEmptyIcon :size="4" class="text-white mr-2" />
 | 
			
		||||
													<div class="flex-col">
 | 
			
		||||
														<div class="text-white text-sm">Chromecast 投放</div>
 | 
			
		||||
														<div class="text-white/60 text-xs">下个版本!</div>
 | 
			
		||||
													</div>
 | 
			
		||||
												</button>
 | 
			
		||||
											</li>
 | 
			
		||||
										</ul>
 | 
			
		||||
									</dialog>
 | 
			
		||||
								</div>
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<!-- Lyrics section - full screen height -->
 | 
			
		||||
				<div class="w-[40rem] h-screen" ref="lyricsSection" v-if="presentLyrics">
 | 
			
		||||
					<ScrollingLyrics :lrcSrc="getCurrentTrack()?.song.lyricUrl ?? undefined" class="h-full"
 | 
			
		||||
						ref="scrollingLyrics" />
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- Queue list dialog with enhanced animations -->
 | 
			
		||||
		<dialog :open="presentQueueListDialog" class="z-20 w-screen h-screen" @click="makePlayQueueListDismiss"
 | 
			
		||||
			ref="playQueueDialogContainer" style="background-color: transparent;">
 | 
			
		||||
			<div
 | 
			
		||||
				class="w-96 h-screen bg-neutral-900/80 shadow-[0_0_16px_0_rgba(0,0,0,0.5)] backdrop-blur-2xl pt-8 flex flex-col transform -translate-x-96"
 | 
			
		||||
				@click.stop ref="playQueueDialog">
 | 
			
		||||
				<div class="flex justify-between mx-8 mb-4">
 | 
			
		||||
					<div class="text-white font-medium text-2xl">播放队列</div>
 | 
			
		||||
					<button
 | 
			
		||||
						class="h-10 w-10 flex justify-center items-center rounded-full backdrop-blur-3xl transition-all duration-200 hover:scale-110"
 | 
			
		||||
						ref="favoriteButton" @click="favourites.toggleFavourite(getCurrentTrack())"
 | 
			
		||||
						:class="favourites.isFavourite(getCurrentTrack().song.cid) ? 'bg-neutral-200/90' : 'bg-black/10 hover:bg-black/20'">
 | 
			
		||||
						<span :class="favourites.isFavourite(getCurrentTrack().song.cid) ? 'text-neutral-700' : 'text-white'">
 | 
			
		||||
							<StarFilledIcon v-if="favourites.isFavourite(getCurrentTrack().song.cid)" :size="6" />
 | 
			
		||||
							<StarEmptyIcon v-else :size="6" />
 | 
			
		||||
						</span>
 | 
			
		||||
						class="text-white w-9 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:bg-neutral-700/80 hover:scale-110"
 | 
			
		||||
						@click="makePlayQueueListDismiss">
 | 
			
		||||
						<XIcon :size="4" />
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<!-- Progress section -->
 | 
			
		||||
				<div class="flex flex-col gap-1" ref="progressSection">
 | 
			
		||||
					<!-- ...existing progress bar code... -->
 | 
			
		||||
					<div
 | 
			
		||||
						class="w-full p-[0.125rem] bg-white/20 shadow-[0_.125rem_1rem_0_#00000010] rounded-full backdrop-blur-3xl">
 | 
			
		||||
						<div class="w-full" ref="progressBarContainer">
 | 
			
		||||
							<div class="w-2 h-2 bg-white rounded-full shadow-md" ref="progressBarThumb" />
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="w-full flex justify-between">
 | 
			
		||||
						<!-- ...existing time display code... -->
 | 
			
		||||
						<div class="font-medium flex-1 text-left relative">
 | 
			
		||||
							<span
 | 
			
		||||
								class="text-black blur-lg absolute top-0">{{ timeFormatter(Math.floor(playQueueStore.currentTime)) }}</span>
 | 
			
		||||
							<span
 | 
			
		||||
								class="text-white/90 absolute top-0">{{ timeFormatter(Math.floor(playQueueStore.currentTime)) }}</span>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="text-xs text-center relative flex-1">
 | 
			
		||||
							<span class="text-black blur-lg absolute top-0">{{ formatDetector() }}</span>
 | 
			
		||||
							<span class="text-white">{{ formatDetector() }}</span>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="flex flex-1">
 | 
			
		||||
							<div class="flex-1" />
 | 
			
		||||
							<button
 | 
			
		||||
								class="text-white/90 font-medium text-right relative transition-colors duration-200 hover:text-white"
 | 
			
		||||
								@click="preferences.displayTimeLeft = !preferences.displayTimeLeft">
 | 
			
		||||
								<span
 | 
			
		||||
									class="text-black blur-lg absolute top-0">{{ `${preferences.displayTimeLeft ? '-' : ''}${timeFormatter(preferences.displayTimeLeft ? Math.floor(playQueueStore.duration) - Math.floor(playQueueStore.currentTime) : playQueueStore.duration)}` }}</span>
 | 
			
		||||
								<span>{{ `${preferences.displayTimeLeft ? '-' : ''}${timeFormatter(preferences.displayTimeLeft ? Math.floor(playQueueStore.duration) - Math.floor(playQueueStore.currentTime) : playQueueStore.duration)}` }}</span>
 | 
			
		||||
							</button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				<div class="flex gap-2 mx-8 mb-4">
 | 
			
		||||
					<button
 | 
			
		||||
						class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
 | 
			
		||||
						:class="playQueueStore.playMode.shuffle ? 'bg-[#ffffffaa] text-neutral-700' : 'text-white bg-neutral-800/80'"
 | 
			
		||||
						@click="toggleShuffle">
 | 
			
		||||
						<ShuffleIcon :size="4" />
 | 
			
		||||
					</button>
 | 
			
		||||
					<button
 | 
			
		||||
						class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
 | 
			
		||||
						:class="playQueueStore.playMode.repeat === 'off' ? 'text-white bg-neutral-800/80' : 'bg-[#ffffffaa] text-neutral-700'"
 | 
			
		||||
						@click="toggleRepeat">
 | 
			
		||||
						<CycleTwoArrowsIcon :size="4" v-if="playQueueStore.playMode.repeat !== 'single'" />
 | 
			
		||||
						<CycleTwoArrowsWithNumOneIcon :size="4" v-else />
 | 
			
		||||
					</button>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<!-- Control buttons -->
 | 
			
		||||
				<div class="w-full flex justify-between items-center" ref="controlButtons">
 | 
			
		||||
					<div class="flex-1 text-left flex gap-1">
 | 
			
		||||
						<button class="h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 relative"
 | 
			
		||||
							ref="volumeButton" @click="toggleVolumeControl">
 | 
			
		||||
							<div class="w-6 h-6 relative">
 | 
			
		||||
								<span class="text-black blur-md absolute top-0 left-0 transition-all duration-200 hover:scale-110">
 | 
			
		||||
									<SpeakerIcon :size="6" />
 | 
			
		||||
								</span>
 | 
			
		||||
								<span class="text-white absolute top-0 left-0 transition-all duration-200 hover:scale-110">
 | 
			
		||||
									<SpeakerIcon :size="6" />
 | 
			
		||||
								</span>
 | 
			
		||||
							</div>
 | 
			
		||||
				<hr class="border-[#ffffff39]" />
 | 
			
		||||
 | 
			
		||||
							<transition name="volume-control-fade">
 | 
			
		||||
								<div v-if="presentVolumeControl" @click.stop
 | 
			
		||||
									class="absolute bg-black/60 backdrop-blur-3xl rounded-md shadow-2xl border border-[#ffffff39] w-64 h-10 bottom-10 left-[-0.3rem] flex items-center justify-between px-4 z-50">
 | 
			
		||||
									<div class="w-full py-[0.125rem] px-[0.25rem] bg-white/20 rounded-full" ref="volumeSliderContainer">
 | 
			
		||||
										<div class="w-2 h-2 bg-white rounded-full shadow-md cursor-pointer" ref="volumeSliderThumb" />
 | 
			
		||||
									</div>
 | 
			
		||||
									<!-- <span class="text-white text-xs w-8 text-right flex-shrink-0">{{ Math.round(volume * 100) }}%</span> -->
 | 
			
		||||
								</div>
 | 
			
		||||
							</transition>
 | 
			
		||||
						</button>
 | 
			
		||||
						<button
 | 
			
		||||
							class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
 | 
			
		||||
							@click="makePlayQueueListPresent" ref="queueButton">
 | 
			
		||||
							<div class="w-6 h-6 relative">
 | 
			
		||||
								<span class="text-black blur-md absolute top-0 left-0">
 | 
			
		||||
									<MusicListIcon :size="6" />
 | 
			
		||||
								</span>
 | 
			
		||||
								<span class="text-white absolute top-0 left-0">
 | 
			
		||||
									<MusicListIcon :size="6" />
 | 
			
		||||
								</span>
 | 
			
		||||
							</div>
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="flex-2 text-center align-center justify-center gap-2 flex">
 | 
			
		||||
						<button
 | 
			
		||||
							class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200 hover:scale-105"
 | 
			
		||||
							@click="playPrevious" ref="prevButton">
 | 
			
		||||
							<div class="w-8 h-8 relative">
 | 
			
		||||
								<span class="text-black/80 blur-lg absolute top-0 left-0">
 | 
			
		||||
									<RewindIcon :size="8" />
 | 
			
		||||
								</span>
 | 
			
		||||
								<span class="text-white absolute top-0 left-0">
 | 
			
		||||
									<RewindIcon :size="8" />
 | 
			
		||||
								</span>
 | 
			
		||||
							</div>
 | 
			
		||||
						</button>
 | 
			
		||||
 | 
			
		||||
						<button
 | 
			
		||||
							class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200"
 | 
			
		||||
							@click="handlePlayPause" ref="playButton">
 | 
			
		||||
							<!-- ...existing play/pause icon code... -->
 | 
			
		||||
							<div v-if="playQueueStore.isPlaying">
 | 
			
		||||
								<div v-if="playQueueStore.isBuffering" class="w-6 h-6 relative">
 | 
			
		||||
									<span class="text-black/80 blur-lg absolute top-0 left-0">
 | 
			
		||||
										<LoadingIndicator :size="6" />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-white absolute top-0 left-0">
 | 
			
		||||
										<LoadingIndicator :size="6" />
 | 
			
		||||
									</span>
 | 
			
		||||
								</div>
 | 
			
		||||
								<div v-else class="w-8 h-8 relative">
 | 
			
		||||
									<span class="text-black blur-md absolute top-0 left-0">
 | 
			
		||||
										<PauseIcon :size="8" />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-white absolute top-0 left-0">
 | 
			
		||||
										<PauseIcon :size="8" />
 | 
			
		||||
									</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
							<div v-else>
 | 
			
		||||
								<div class="w-8 h-8 relative">
 | 
			
		||||
									<span class="text-black/80 blur-lg absolute top-0 left-0">
 | 
			
		||||
										<PlayIcon :size="8" />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-white absolute top-0 left-0">
 | 
			
		||||
										<PlayIcon :size="8" />
 | 
			
		||||
									</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</button>
 | 
			
		||||
 | 
			
		||||
						<button
 | 
			
		||||
							class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200 hover:scale-105"
 | 
			
		||||
							@click="playNext" ref="nextButton">
 | 
			
		||||
							<div class="w-8 h-8 relative">
 | 
			
		||||
								<span class="text-black/80 blur-lg absolute top-0 left-0">
 | 
			
		||||
									<ForwardIcon :size="8" />
 | 
			
		||||
								</span>
 | 
			
		||||
								<span class="text-white absolute top-0 left-0">
 | 
			
		||||
									<ForwardIcon :size="8" />
 | 
			
		||||
								</span>
 | 
			
		||||
							</div>
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div class="flex-1 text-right flex gap-1">
 | 
			
		||||
						<div class="flex-1" />
 | 
			
		||||
						<!-- Lyrics button with tooltip only on hover -->
 | 
			
		||||
						<div @mouseenter="showLyricsTooltip = true" @mouseleave="showLyricsTooltip = false" class="relative">
 | 
			
		||||
							<button
 | 
			
		||||
								class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
 | 
			
		||||
								ref="lyricsButton" @click="preferences.presentLyrics = !preferences.presentLyrics">
 | 
			
		||||
								<div class="w-6 h-6 relative">
 | 
			
		||||
									<span class="text-white absolute top-0 left-0 z-10">
 | 
			
		||||
										<ChatBubbleQuoteFullIcon :size="6" v-if="preferences.presentLyrics" />
 | 
			
		||||
										<ChatBubbleQuoteIcon :size="6" v-else />
 | 
			
		||||
									</span>
 | 
			
		||||
									<span class="text-black/40 blur-md absolute top-0 left-0 z-0">
 | 
			
		||||
										<ChatBubbleQuoteFullIcon :size="6" v-if="preferences.presentLyrics" />
 | 
			
		||||
										<ChatBubbleQuoteIcon :size="6" v-else />
 | 
			
		||||
									</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</button>
 | 
			
		||||
							<!-- Show tooltip only on hover, with transition -->
 | 
			
		||||
							<transition name="lyrics-tooltip-fade">
 | 
			
		||||
								<div v-if="showLyricsTooltip && !getCurrentTrack().song.lyricUrl"
 | 
			
		||||
									class="absolute bottom-10 w-60 left-[-7rem] bg-black/60 backdrop-blur-3xl rounded-md p-2 text-xs flex flex-col text-left shadow-2xl border border-[#ffffff39]">
 | 
			
		||||
									<div class="font-semibold text-white">这首曲目不提供歌词文本</div>
 | 
			
		||||
									<div class="text-white/60">启用歌词时,将会在下一首有歌词的曲目中显示歌词文本。</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</transition>
 | 
			
		||||
						</div>
 | 
			
		||||
						<button
 | 
			
		||||
							class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all"
 | 
			
		||||
							@click="toggleMoreOptions">
 | 
			
		||||
							<div class="w-6 h-6 relative">
 | 
			
		||||
								<span class="text-black blur-sm absolute top-0 left-0 hover:scale-110 transition-all">
 | 
			
		||||
									<EllipsisHorizontalIcon :size="6" />
 | 
			
		||||
								</span>
 | 
			
		||||
								<span class="text-white absolute top-0 left-0 hover:scale-110 transition-all">
 | 
			
		||||
									<EllipsisHorizontalIcon :size="6" />
 | 
			
		||||
								</span>
 | 
			
		||||
 | 
			
		||||
								<dialog :open="showMoreOptions" @click.self="toggleMoreOptions" ref="moreOptionsDialog"
 | 
			
		||||
									class="bottom-8 left-[-13.3rem] w-60 rounded-md overflow-hidden bg-black/60 backdrop-blur-3xl shadow-2xl border border-[#ffffff39]">
 | 
			
		||||
									<ul class="my-2 flex flex-col gap-1">
 | 
			
		||||
										<li>
 | 
			
		||||
											<button
 | 
			
		||||
												class="flex px-2 py-1 hover:bg-white/10 w-full text-left disabled:opacity-70 cursor-not-allowed transition-colors duration-150"
 | 
			
		||||
												disabled>
 | 
			
		||||
												<MuscialNoteSparklingIcon :size="4" class="text-white mr-2" />
 | 
			
		||||
												<div class="flex-col">
 | 
			
		||||
													<div class="text-white text-sm">音频质量</div>
 | 
			
		||||
													<div class="text-white/60 text-xs">改不了,海猫没给这个选项!</div>
 | 
			
		||||
												</div>
 | 
			
		||||
											</button>
 | 
			
		||||
										</li>
 | 
			
		||||
 | 
			
		||||
										<li>
 | 
			
		||||
											<button
 | 
			
		||||
												class="flex px-2 py-1 hover:bg-white/10 w-full text-left disabled:opacity-70 cursor-not-allowed transition-colors duration-150"
 | 
			
		||||
												disabled>
 | 
			
		||||
												<CastEmptyIcon :size="4" class="text-white mr-2" />
 | 
			
		||||
												<div class="flex-col">
 | 
			
		||||
													<div class="text-white text-sm">Chromecast 投放</div>
 | 
			
		||||
													<div class="text-white/60 text-xs">下个版本!</div>
 | 
			
		||||
												</div>
 | 
			
		||||
											</button>
 | 
			
		||||
										</li>
 | 
			
		||||
									</ul>
 | 
			
		||||
								</dialog>
 | 
			
		||||
							</div>
 | 
			
		||||
						</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2" v-if="playQueueStore.playMode.shuffle">
 | 
			
		||||
					<PlayQueueItem v-for="(oriIndex, shuffledIndex) in playQueueStore.shuffleList"
 | 
			
		||||
						:queueItem="playQueueStore.list[oriIndex]" :isCurrent="playQueueStore.currentIndex === shuffledIndex"
 | 
			
		||||
						:key="playQueueStore.list[oriIndex].song.cid" :index="shuffledIndex" />
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2" v-else>
 | 
			
		||||
					<PlayQueueItem :queueItem="track" :isCurrent="playQueueStore.currentIndex === index"
 | 
			
		||||
						v-for="(track, index) in playQueueStore.list" :index="index" :key="track.song.cid" />
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<!-- Lyrics section - full screen height -->
 | 
			
		||||
			<div class="w-[40rem] h-screen" ref="lyricsSection" v-if="presentLyrics">
 | 
			
		||||
				<ScrollingLyrics :lrcSrc="getCurrentTrack().song.lyricUrl ?? undefined" class="h-full" ref="scrollingLyrics" />
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		</dialog>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div v-else class="flex items-center justify-center h-screen w-screen flex-col gap-4 text-center select-none">
 | 
			
		||||
		<div class="text-white text-2xl">现在没有播放任何东西喔</div>
 | 
			
		||||
		<div class="text-white text-lg">要来一块苹果派吗?</div>
 | 
			
		||||
		<RouterLink to="/">
 | 
			
		||||
			<button class="bg-white/10 hover:bg-white/20 text-white px-4 py-2 rounded-lg transition-all duration-200 text-lg">
 | 
			
		||||
				返回首页
 | 
			
		||||
			</button>
 | 
			
		||||
		</RouterLink>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<!-- Queue list dialog with enhanced animations -->
 | 
			
		||||
	<dialog :open="presentQueueListDialog" class="z-20 w-screen h-screen" @click="makePlayQueueListDismiss"
 | 
			
		||||
		ref="playQueueDialogContainer" style="background-color: transparent;">
 | 
			
		||||
		<div
 | 
			
		||||
			class="w-96 h-screen bg-neutral-900/80 shadow-[0_0_16px_0_rgba(0,0,0,0.5)] backdrop-blur-2xl pt-8 flex flex-col transform -translate-x-96"
 | 
			
		||||
			@click.stop ref="playQueueDialog">
 | 
			
		||||
			<div class="flex justify-between mx-8 mb-4">
 | 
			
		||||
				<div class="text-white font-medium text-2xl">播放队列</div>
 | 
			
		||||
				<button
 | 
			
		||||
					class="text-white w-9 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:bg-neutral-700/80 hover:scale-110"
 | 
			
		||||
					@click="makePlayQueueListDismiss">
 | 
			
		||||
					<XIcon :size="4" />
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="flex gap-2 mx-8 mb-4">
 | 
			
		||||
				<button
 | 
			
		||||
					class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
 | 
			
		||||
					:class="playQueueStore.playMode.shuffle ? 'bg-[#ffffffaa] text-neutral-700' : 'text-white bg-neutral-800/80'"
 | 
			
		||||
					@click="toggleShuffle">
 | 
			
		||||
					<ShuffleIcon :size="4" />
 | 
			
		||||
				</button>
 | 
			
		||||
				<button
 | 
			
		||||
					class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
 | 
			
		||||
					:class="playQueueStore.playMode.repeat === 'off' ? 'text-white bg-neutral-800/80' : 'bg-[#ffffffaa] text-neutral-700'"
 | 
			
		||||
					@click="toggleRepeat">
 | 
			
		||||
					<CycleTwoArrowsIcon :size="4" v-if="playQueueStore.playMode.repeat !== 'single'" />
 | 
			
		||||
					<CycleTwoArrowsWithNumOneIcon :size="4" v-else />
 | 
			
		||||
				</button>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<hr class="border-[#ffffff39]" />
 | 
			
		||||
 | 
			
		||||
			<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2" v-if="playQueueStore.playMode.shuffle">
 | 
			
		||||
				<PlayQueueItem v-for="(oriIndex, shuffledIndex) in playQueueStore.shuffleList"
 | 
			
		||||
					:queueItem="playQueueStore.list[oriIndex]" :isCurrent="playQueueStore.currentIndex === shuffledIndex"
 | 
			
		||||
					:key="playQueueStore.list[oriIndex].song.cid" :index="shuffledIndex" />
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="flex-auto h-0 overflow-y-auto px-4 flex flex-col gap-2" v-else>
 | 
			
		||||
				<PlayQueueItem :queueItem="track" :isCurrent="playQueueStore.currentIndex === index"
 | 
			
		||||
					v-for="(track, index) in playQueueStore.list" :index="index" :key="track.song.cid" />
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</dialog>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user