refactor(player): 重构播放器组件并优化音乐状态管理
- 优化了音乐收藏和点赞状态的管理逻辑 - 重构了播放器组件中的多个函数,提高了代码可读性和维护性 - 添加了 MusicStatusDTO 类,用于封装音乐状态信息 - 优化了歌词显示和滚动逻辑 - 简化了部分代码结构,提高了执行效率
This commit is contained in:
@@ -92,10 +92,10 @@
|
||||
</div>
|
||||
|
||||
<div class="collect-button" v-if="currentMusic" style="margin-right: 15px;">
|
||||
<el-tooltip :content="currentMusic.isCollected ? '取消收藏' : '收藏'" placement="top">
|
||||
<el-tooltip :content="currentMusic.collected ? '取消收藏' : '收藏'" placement="top">
|
||||
<el-button circle @click="toggleCollect">
|
||||
<el-icon>
|
||||
<component :is="currentMusic.isCollected ? 'StarFilled' : 'Star'" />
|
||||
<component :is="currentMusic.collected ? 'StarFilled' : 'Star'" />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
@@ -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'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user