From a37a47bdabfe2eb05c0bd4c12bc1e49da0e238b0 Mon Sep 17 00:00:00 2001
From: ikmkj <1@qq,com>
Date: Sat, 26 Jul 2025 00:08:07 +0800
Subject: [PATCH] =?UTF-8?q?refactor(player):=20=E9=87=8D=E6=9E=84=E6=92=AD?=
=?UTF-8?q?=E6=94=BE=E5=99=A8=E7=BB=84=E4=BB=B6=E5=B9=B6=E4=BC=98=E5=8C=96?=
=?UTF-8?q?=E9=9F=B3=E4=B9=90=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 优化了音乐收藏和点赞状态的管理逻辑
- 重构了播放器组件中的多个函数,提高了代码可读性和维护性
- 添加了 MusicStatusDTO 类,用于封装音乐状态信息
- 优化了歌词显示和滚动逻辑
- 简化了部分代码结构,提高了执行效率
---
.../test/musichouduan/dto/MusicStatusDTO.java | 24 ++
music-qianduan/src/components/MusicPlayer.vue | 263 ++++--------------
music-qianduan/src/stores/player.js | 12 +
3 files changed, 96 insertions(+), 203 deletions(-)
create mode 100644 music-houduan/src/main/java/com/test/musichouduan/dto/MusicStatusDTO.java
diff --git a/music-houduan/src/main/java/com/test/musichouduan/dto/MusicStatusDTO.java b/music-houduan/src/main/java/com/test/musichouduan/dto/MusicStatusDTO.java
new file mode 100644
index 0000000..6baee4c
--- /dev/null
+++ b/music-houduan/src/main/java/com/test/musichouduan/dto/MusicStatusDTO.java
@@ -0,0 +1,24 @@
+package com.test.musichouduan.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+/**
+ * 音乐状态DTO,用于封装收藏和点赞状态
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MusicStatusDTO {
+
+ /**
+ * 是否已收藏
+ */
+ private boolean collected;
+
+ /**
+ * 是否已点赞
+ */
+ private boolean liked;
+}
diff --git a/music-qianduan/src/components/MusicPlayer.vue b/music-qianduan/src/components/MusicPlayer.vue
index 945db0e..8b09932 100644
--- a/music-qianduan/src/components/MusicPlayer.vue
+++ b/music-qianduan/src/components/MusicPlayer.vue
@@ -92,10 +92,10 @@
-
+
-
+
@@ -288,6 +288,10 @@ import { usePlayerStore } from '../stores/player'
import { useUserStore } from '../stores/user'
import { processMusicUrl, toggleCollectMusic, getMusicStatus } from '../api/music'
import { ElMessage } from 'element-plus'
+import {
+ VideoPlay, VideoPause, Back, Right, List, Close, Delete,
+ Mute, VideoCamera, Microphone, Star, StarFilled, ArrowDown
+} from '@element-plus/icons-vue'
const router = useRouter()
const userStore = useUserStore()
@@ -320,22 +324,18 @@ const loopMode = computed(() => playerStore.getLoop)
// 获取歌手名称
const getSingerNames = computed(() => {
if (!currentMusic.value) return ''
-
if (currentMusic.value.singerNames && currentMusic.value.singerNames.length > 0) {
return currentMusic.value.singerNames.join('、')
}
-
return currentMusic.value.singer || '未知歌手'
})
// 获取音乐的歌手名称
const getSingerNamesFromMusic = (music) => {
if (!music) return ''
-
if (music.singerNames && music.singerNames.length > 0) {
return music.singerNames.join('、')
}
-
return music.singer || '未知歌手'
}
@@ -353,70 +353,48 @@ const volumeIcon = computed(() => {
// 循环模式图标
const loopModeIcon = computed(() => {
switch (loopMode.value) {
- case 'single':
- return 'Refresh'
- case 'list':
- return 'Sort'
- default:
- return 'Connection'
+ case 'single': return 'Refresh'
+ case 'list': return 'Sort'
+ default: return 'Connection'
}
})
// 循环模式文本
const loopModeText = computed(() => {
switch (loopMode.value) {
- case 'single':
- return '单曲循环'
- case 'list':
- return '列表循环'
- default:
- return '顺序播放'
+ case 'single': return '单曲循环'
+ case 'list': return '列表循环'
+ default: return '顺序播放'
}
})
// 解析歌词
const parsedLyric = computed(() => {
- if (!currentMusic.value || !currentMusic.value.lyric) {
- return []
- }
-
+ if (!currentMusic.value || !currentMusic.value.lyric) return []
const lyric = currentMusic.value.lyric
const lines = lyric.split('\n')
const timeRegExp = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/g
-
const result = []
-
for (const line of lines) {
- // 使用正则表达式的g标志匹配所有时间标记
const timeMatches = [...line.matchAll(timeRegExp)]
if (timeMatches.length > 0) {
- // 取第一个时间标记作为歌词时间
- const match = timeMatches[0]
- const min = parseInt(match[1])
- const sec = parseInt(match[2])
- const ms = parseInt(match[3])
+ const match = timeMatches
+ const min = parseInt(match)
+ const sec = parseInt(match)
+ const ms = parseInt(match)
const time = min * 60 + sec + ms / 1000
-
- // 删除所有时间标记
let text = line.replace(timeRegExp, '').trim()
-
- // 只有当文本非空时才添加到结果中
if (text) {
result.push({ time, text })
}
}
}
-
- // 按时间排序
return result.sort((a, b) => a.time - b.time)
})
// 格式化时间
const formatTime = (seconds) => {
- if (isNaN(seconds) || seconds < 0) {
- return '00:00'
- }
-
+ if (isNaN(seconds) || seconds < 0) return '00:00'
const mins = Math.floor(seconds / 60)
const secs = Math.floor(seconds % 60)
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
@@ -425,10 +403,8 @@ const formatTime = (seconds) => {
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return ''
-
try {
- const date = new Date(dateStr)
- return date.toLocaleDateString()
+ return new Date(dateStr).toLocaleDateString()
} catch (e) {
return dateStr
}
@@ -443,31 +419,19 @@ const togglePlay = () => {
}
}
-// 上一首
-const prevSong = () => {
- playerStore.playPrev()
-}
+// 上一首/下一首
+const prevSong = () => playerStore.playPrev()
+const nextSong = () => playerStore.playNext()
-// 下一首
-const nextSong = () => {
- playerStore.playNext()
-}
-
-// 切换静音
+// 静音切换
const toggleMute = () => {
- if (volume.value > 0) {
- playerStore.setVolume(0)
- } else {
- playerStore.setVolume(80)
- }
+ playerStore.setVolume(volume.value > 0 ? 0 : 80)
}
-// 处理音量变化
-const handleVolumeChange = (val) => {
- playerStore.setVolume(val)
-}
+// 音量变化
+const handleVolumeChange = (val) => playerStore.setVolume(val)
-// 切换循环模式
+// 循环模式切换
const toggleLoopMode = () => {
const modes = ['none', 'list', 'single']
const currentIndex = modes.indexOf(loopMode.value)
@@ -475,23 +439,19 @@ const toggleLoopMode = () => {
playerStore.setLoop(modes[nextIndex])
}
-// 切换收藏状态
+// 收藏/取消收藏
const toggleCollect = async () => {
if (!userStore.isLoggedIn) {
ElMessage.warning('请先登录')
router.push('/login')
return
}
-
- if (!currentMusic.value || !currentMusic.value.id) {
- return
- }
+ if (!currentMusic.value || !currentMusic.value.id) return
try {
const res = await toggleCollectMusic(currentMusic.value.id)
if (res.code === 200) {
- // 直接使用后端返回的最新状态
- playerStore.updateCurrentMusicStatus({ collected: res.data });
+ playerStore.updateCurrentMusicStatus({ collected: res.data })
ElMessage.success(res.message || (res.data ? '收藏成功' : '已取消收藏'))
} else {
ElMessage.error(res.message || '操作失败')
@@ -502,157 +462,61 @@ const toggleCollect = async () => {
}
}
-// 切换播放列表显示
-const togglePlaylist = () => {
- playerStore.setShowPlaylist(!showPlaylist.value)
-}
+// 播放列表相关
+const togglePlaylist = () => playerStore.setShowPlaylist(!showPlaylist.value)
+const playFromPlaylist = (index) => playerStore.playFromPlaylist(index)
+const removeFromPlaylist = (index) => playerStore.removeFromPlaylist(index)
+const clearPlaylist = () => playerStore.clearPlaylist()
-// 从播放列表播放
-const playFromPlaylist = (index) => {
- playerStore.playFromPlaylist(index)
-}
-
-// 从播放列表移除
-const removeFromPlaylist = (index) => {
- playerStore.removeFromPlaylist(index)
-}
-
-// 清空播放列表
-const clearPlaylist = () => {
- playerStore.clearPlaylist()
-}
-
-// 切换展开状态
+// 展开/收起播放器
const toggleExpand = () => {
isExpanded.value = !isExpanded.value
-
- // 当展开播放器时,禁止外部页面滚动
- if (isExpanded.value) {
- document.body.style.overflow = 'hidden'
- } else {
- document.body.style.overflow = ''
- }
+ document.body.style.overflow = isExpanded.value ? 'hidden' : ''
}
-// 跳转到音乐详情页面
-const goToMusicDetail = () => {
- if (currentMusic.value && currentMusic.value.id) {
- router.push(`/music/${currentMusic.value.id}`)
- }
-}
-
-// 处理进度条变化
+// 进度条处理
+const isUserSeeking = ref(false)
const handleSliderChange = (val) => {
- // 暂停监听时间更新,避免冲突
isUserSeeking.value = true
playerStore.setCurrentTime(val)
- // 延迟恢复监听,确保播放器有时间响应
- setTimeout(() => {
- isUserSeeking.value = false
- }, 500) // 增加延迟时间,确保播放器有足够时间响应
+ setTimeout(() => { isUserSeeking.value = false }, 500)
}
+const handleSliderInput = () => { isUserSeeking.value = true }
+const formatSliderTooltip = (val) => formatTime(val)
-// 处理进度条输入(拖动过程中)
-const handleSliderInput = (val) => {
- isUserSeeking.value = true
-}
-
-// 格式化进度条提示文本
-const formatSliderTooltip = (val) => {
- return formatTime(val)
-}
-
-// 标记用户是否正在拖动进度条
-const isUserSeeking = ref(false)
-
-// 监听当前时间变化,更新滑块位置
+// 监听 Pinia store 的时间更新
watch(currentTime, (newVal) => {
- // 只有当用户不在拖动进度条时才更新滑块位置
if (!isUserSeeking.value) {
sliderTime.value = newVal
}
-
- // 更新当前歌词索引
+ // 更新歌词
if (parsedLyric.value.length > 0) {
- let foundMatch = false
- for (let i = 0; i < parsedLyric.value.length; i++) {
- // 如果当前时间小于歌词时间,则使用前一行歌词
- if (newVal < parsedLyric.value[i].time) {
- if (i > 0 && currentLyricIndex.value !== i - 1) {
- currentLyricIndex.value = i - 1
- if (isExpanded.value) {
- scrollToActiveLyric()
- }
- }
- foundMatch = true
- break
- }
- // 如果是最后一行歌词
- else if (i === parsedLyric.value.length - 1) {
- if (currentLyricIndex.value !== i) {
- currentLyricIndex.value = i
- if (isExpanded.value) {
- scrollToActiveLyric()
- }
- }
- foundMatch = true
- }
- }
-
- // 如果没有找到匹配的歌词,则重置到第一行
- if (!foundMatch && currentLyricIndex.value !== -1) {
- currentLyricIndex.value = -1
+ let newIndex = parsedLyric.value.findIndex(line => line.time > newVal) - 1
+ if (newIndex < 0) newIndex = parsedLyric.value.length - 1
+ if (currentLyricIndex.value !== newIndex) {
+ currentLyricIndex.value = newIndex
+ if (isExpanded.value) scrollToActiveLyric()
}
}
})
// 滚动到当前歌词
const scrollToActiveLyric = async () => {
+ await nextTick()
if (lyricScrollbar.value && currentLyricIndex.value >= 0) {
- await nextTick()
- try {
- // 使用ref获取当前歌词元素
- const element = lyricLines.value[currentLyricIndex.value]
- if (element) {
- // 尝试多种方法来确保滚动生效
-
- // 方法 1: 使用scrollIntoView
- element.scrollIntoView({ behavior: 'smooth', block: 'center' })
-
- // 方法 2: 使用el-scrollbar的方法
- if (lyricScrollbar.value.setScrollTop) {
- lyricScrollbar.value.setScrollTop(element.offsetTop - 150, 300)
- }
-
- // 方法 3: 直接设置scrollTop
- if (lyricScrollbar.value.wrap) {
- setTimeout(() => {
- lyricScrollbar.value.wrap.scrollTop = element.offsetTop - 150
- }, 10)
- }
- }
- } catch (error) {
- console.error('Failed to scroll to active lyric:', error)
+ const element = lyricLines.value[currentLyricIndex.value]
+ if (element) {
+ lyricScrollbar.value.setScrollTop(element.offsetTop - 150)
}
}
}
-// 监听当前音乐变化,处理音乐URL和状态
+// 监听当前播放歌曲的变化
watch(currentMusic, (newMusic) => {
if (newMusic && newMusic.id) {
- // 处理音乐URL
- const processedUrl = processMusicUrl(newMusic.url)
- if (processedUrl !== newMusic.url) {
- playerStore.updateMusicUrl(newMusic.id, processedUrl)
- }
-
- // 处理封面URL
- if (newMusic.cover) {
- const processedCover = processMusicUrl(newMusic.cover)
- if (processedCover !== newMusic.cover) {
- playerStore.updateMusicCover(newMusic.id, processedCover)
- }
- }
+ // 处理 URL
+ playerStore.updateMusicUrl(newMusic.id, processMusicUrl(newMusic.url))
+ playerStore.updateMusicCover(newMusic.id, processMusicUrl(newMusic.cover))
// 主动获取最新的收藏和点赞状态
if (userStore.isLoggedIn) {
@@ -663,22 +527,15 @@ watch(currentMusic, (newMusic) => {
liked: res.data.liked
});
}
- }).catch(err => {
- console.error("获取播放器音乐状态失败:", err);
- });
+ }).catch(err => console.error("获取播放器音乐状态失败:", err));
} else {
- // 未登录则重置状态
playerStore.updateCurrentMusicStatus({ collected: false, liked: false });
}
- // 重置歌词索引和元素引用
+ // 重置歌词
currentLyricIndex.value = -1
lyricLines.value = {}
-
- // 如果展开了播放器,切换到歌词标签页
- if (isExpanded.value) {
- expandedActiveTab.value = 'lyric'
- }
+ if (isExpanded.value) expandedActiveTab.value = 'lyric'
}
})
diff --git a/music-qianduan/src/stores/player.js b/music-qianduan/src/stores/player.js
index 2f687ea..0daa8c5 100644
--- a/music-qianduan/src/stores/player.js
+++ b/music-qianduan/src/stores/player.js
@@ -280,6 +280,18 @@ export const usePlayerStore = defineStore('player', {
if (index !== -1) {
this.playlist[index].cover = cover
}
+ },
+
+ // 更新当前音乐的状态(如收藏、点赞)
+ updateCurrentMusicStatus(status) {
+ if (this.currentMusic) {
+ this.currentMusic = { ...this.currentMusic, ...status };
+
+ const index = this.playlist.findIndex(item => item.id === this.currentMusic.id);
+ if (index !== -1) {
+ this.playlist[index] = { ...this.playlist[index], ...status };
+ }
+ }
}
}
})