mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 优化音乐播放器组件,增强用户体验和功能
- 在ManageLayout.vue中新增音乐播放器控制功能,包括播放、暂停、上一首、下一首等按钮 - 改进音乐播放器高度计算逻辑,确保播放器在可见时正确显示 - 添加音量控制滑块,允许用户调整音量 - 增强队列管理功能,支持清空等待队列 - 更新样式以提升播放器的视觉效果和响应式设计
This commit is contained in:
@@ -23,7 +23,7 @@ import {
|
||||
VideoAdd20Filled,
|
||||
Mail24Filled,
|
||||
} from '@vicons/fluent'
|
||||
import { AnalyticsSharp, BrowsersOutline, Chatbox, Moon, MusicalNote, Sunny, Eye } from '@vicons/ionicons5'
|
||||
import { AnalyticsSharp, BrowsersOutline, Chatbox, Moon, MusicalNote, Sunny, Eye, PlayForward, PlayBack, Play, Pause, VolumeHigh, ChevronUp, ChevronDown, TrashBin } from '@vicons/ionicons5'
|
||||
import { useElementSize, useStorage } from '@vueuse/core'
|
||||
import {
|
||||
NAlert,
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
NTooltip,
|
||||
useMessage,
|
||||
NCard,
|
||||
NSlider,
|
||||
} from 'naive-ui'
|
||||
import { computed, h, onMounted, ref, watch } from 'vue'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
@@ -68,20 +69,57 @@ const themeType = useStorage('Settings.Theme', ThemeType.Auto)
|
||||
// 侧边栏和布局相关
|
||||
const sider = ref()
|
||||
const { width } = useElementSize(sider)
|
||||
const musicPlayerCardRef = ref(null)
|
||||
const { height: musicPlayerCardHeight } = useElementSize(musicPlayerCardRef)
|
||||
|
||||
// 页面类型计算
|
||||
const type = computed(() => route.meta.danmaku ? 'danmaku' : '')
|
||||
|
||||
// 音乐请求服务相关
|
||||
const musicRquestStore = useMusicRequestProvider()
|
||||
const aplayerHeight = computed(() =>
|
||||
musicRquestStore.originMusics.length === 0 ? '0' : '80'
|
||||
|
||||
// 优化音乐播放器高度计算逻辑
|
||||
const aplayerHeight = computed(() => {
|
||||
if (!isPlayerVisible.value) {
|
||||
return '0'
|
||||
}
|
||||
// Add 16px for NCard's top/bottom margin.
|
||||
return `${musicPlayerCardHeight.value + 16}`
|
||||
})
|
||||
|
||||
// 播放器是否可见
|
||||
const isPlayerVisible = computed(
|
||||
() => musicRquestStore.originMusics.length > 0 || musicRquestStore.waitingMusics.length > 0
|
||||
)
|
||||
|
||||
// 音乐播放器相关状态
|
||||
const isPlayerMinimized = useStorage('Settings.MusicPlayer.Minimized', false)
|
||||
const playerVolume = computed({
|
||||
get: () => musicRquestStore.settings.volume,
|
||||
set: (value) => musicRquestStore.settings.volume = value
|
||||
})
|
||||
|
||||
const aplayer = ref()
|
||||
watch(aplayer, () => {
|
||||
musicRquestStore.aplayerRef = aplayer.value
|
||||
})
|
||||
|
||||
// 当前播放信息
|
||||
const currentPlayingInfo = computed(() => {
|
||||
if (musicRquestStore.currentOriginMusic && musicRquestStore.isPlayingOrderMusic) {
|
||||
return {
|
||||
type: 'request',
|
||||
info: `正在播放 ${musicRquestStore.currentOriginMusic.from.name} 点的歌`
|
||||
}
|
||||
} else if (musicRquestStore.currentMusic && musicRquestStore.currentMusic.title) {
|
||||
return {
|
||||
type: 'normal',
|
||||
info: '正在播放背景音乐'
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
// 邮箱验证相关
|
||||
const canResendEmail = ref(false)
|
||||
const isBiliVerified = computed(() => accountInfo.value?.isBiliVerified)
|
||||
@@ -373,6 +411,45 @@ function onNextMusic() {
|
||||
musicRquestStore.nextMusic()
|
||||
}
|
||||
|
||||
// 音乐播放器控制功能
|
||||
function togglePlay() {
|
||||
if (aplayer.value) {
|
||||
const audio = aplayer.value.audio
|
||||
if (audio.paused) {
|
||||
aplayer.value.play()
|
||||
} else {
|
||||
aplayer.value.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onPreviousMusic() {
|
||||
if (aplayer.value) {
|
||||
// 如果当前播放时间大于3秒,则重新开始播放当前歌曲
|
||||
if (aplayer.value.audio.currentTime > 3) {
|
||||
aplayer.value.audio.currentTime = 0
|
||||
} else {
|
||||
// 否则播放上一首
|
||||
const currentIndex = musicRquestStore.aplayerMusics.findIndex(
|
||||
music => music.id === musicRquestStore.currentMusic.id
|
||||
)
|
||||
if (currentIndex > 0) {
|
||||
musicRquestStore.currentMusic = musicRquestStore.aplayerMusics[currentIndex - 1]
|
||||
aplayer.value.thenPlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearWaitingQueue() {
|
||||
musicRquestStore.waitingMusics.splice(0)
|
||||
message.success('已清空等待队列')
|
||||
}
|
||||
|
||||
function togglePlayerMinimize() {
|
||||
isPlayerMinimized.value = !isPlayerMinimized.value
|
||||
}
|
||||
|
||||
// 跳转到认证页面
|
||||
function gotoAuthPage() {
|
||||
if (!accountInfo.value?.biliUserAuthInfo) {
|
||||
@@ -676,40 +753,346 @@ onMounted(() => {
|
||||
</NScrollbar>
|
||||
|
||||
<!-- 音乐播放器区域 -->
|
||||
<NLayoutFooter :style="`height: ${aplayerHeight}px;overflow: auto`">
|
||||
<div style="display: flex; align-items: center; margin: 0 10px">
|
||||
<NLayoutFooter
|
||||
v-if="isPlayerVisible"
|
||||
:style="`height: ${aplayerHeight}px; overflow: hidden; transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);`"
|
||||
class="music-player-footer"
|
||||
>
|
||||
<NCard
|
||||
ref="musicPlayerCardRef"
|
||||
:bordered="false"
|
||||
embedded
|
||||
:content-style="isPlayerMinimized ? 'padding: 0' : undefined"
|
||||
size="small"
|
||||
class="music-player-card"
|
||||
:style="`
|
||||
margin: 8px;
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(10px);
|
||||
`"
|
||||
>
|
||||
<!-- 播放器头部控制栏 -->
|
||||
<template #header>
|
||||
<NFlex
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style="padding: 0;"
|
||||
>
|
||||
<NFlex
|
||||
align="center"
|
||||
size="small"
|
||||
>
|
||||
<NIcon
|
||||
:component="MusicalNote"
|
||||
size="16"
|
||||
:style="`color: ${isDarkMode ? '#a8dadc' : '#457b9d'}`"
|
||||
/>
|
||||
<NText
|
||||
:depth="2"
|
||||
style="font-size: 13px; font-weight: 500;"
|
||||
>
|
||||
音乐播放器
|
||||
</NText>
|
||||
<NTag
|
||||
v-if="currentPlayingInfo && !isPlayerMinimized"
|
||||
:type="currentPlayingInfo.type === 'request' ? 'success' : 'info'"
|
||||
size="small"
|
||||
round
|
||||
:bordered="false"
|
||||
style="font-size: 11px; padding: 2px 8px;"
|
||||
>
|
||||
{{ currentPlayingInfo.info }}
|
||||
</NTag>
|
||||
|
||||
<template v-if="isPlayerMinimized">
|
||||
<NText
|
||||
v-if="musicRquestStore.currentMusic.title"
|
||||
style="font-size: 13px; max-width: 250px; margin-left: 12px"
|
||||
:ellipsis="{ tooltip: true }"
|
||||
>
|
||||
{{ musicRquestStore.currentMusic.title }} - {{ musicRquestStore.currentMusic.artist }}
|
||||
</NText>
|
||||
<NText
|
||||
v-else
|
||||
depth="3"
|
||||
style="font-size: 13px; margin-left: 12px"
|
||||
>
|
||||
暂无播放
|
||||
</NText>
|
||||
</template>
|
||||
</NFlex>
|
||||
|
||||
<NFlex
|
||||
align="center"
|
||||
size="small"
|
||||
>
|
||||
<template v-if="isPlayerMinimized">
|
||||
<NTag
|
||||
v-if="musicRquestStore.waitingMusics.length > 0"
|
||||
type="warning"
|
||||
size="small"
|
||||
round
|
||||
:bordered="false"
|
||||
>
|
||||
{{ musicRquestStore.waitingMusics.length }}
|
||||
</NTag>
|
||||
|
||||
<NButton
|
||||
circle
|
||||
size="tiny"
|
||||
tertiary
|
||||
:disabled="musicRquestStore.aplayerMusics.length === 0"
|
||||
@click.stop="togglePlay"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon
|
||||
:component="aplayer?.audio?.paused !== false ? Play : Pause"
|
||||
size="14"
|
||||
/>
|
||||
</template>
|
||||
</NButton>
|
||||
|
||||
<NButton
|
||||
circle
|
||||
size="tiny"
|
||||
tertiary
|
||||
:disabled="musicRquestStore.waitingMusics.length === 0 && musicRquestStore.aplayerMusics.length <= 1"
|
||||
@click.stop="onNextMusic"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon
|
||||
:component="PlayForward"
|
||||
size="14"
|
||||
/>
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
:type="isPlayerMinimized ? 'primary' : 'default'"
|
||||
tertiary
|
||||
size="small"
|
||||
circle
|
||||
@click="togglePlayerMinimize"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="isPlayerMinimized ? ChevronUp : ChevronDown" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
{{ isPlayerMinimized ? '展开播放器' : '收起播放器' }}
|
||||
</NTooltip>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
<!-- 主播放器内容 -->
|
||||
<div v-show="!isPlayerMinimized">
|
||||
<NFlex
|
||||
align="center"
|
||||
:wrap="false"
|
||||
style="gap: 12px;"
|
||||
>
|
||||
<!-- APlayer组件 -->
|
||||
<div style="flex: 1; min-width: 280px;">
|
||||
<APlayer
|
||||
v-if="musicRquestStore.aplayerMusics.length > 0"
|
||||
ref="aplayer"
|
||||
v-model:music="musicRquestStore.currentMusic"
|
||||
v-model:volume="musicRquestStore.settings.volume"
|
||||
v-model:volume="playerVolume"
|
||||
v-model:shuffle="musicRquestStore.settings.shuffle"
|
||||
v-model:repeat="musicRquestStore.settings.repeat"
|
||||
:list="musicRquestStore.aplayerMusics"
|
||||
:list-max-height="'200'"
|
||||
mutex
|
||||
list-folded
|
||||
style="flex: 1; min-width: 400px"
|
||||
style="border-radius: 8px;"
|
||||
@ended="musicRquestStore.onMusicEnd"
|
||||
@play="musicRquestStore.onMusicPlay"
|
||||
/>
|
||||
<NSpace vertical>
|
||||
<NTag
|
||||
:bordered="false"
|
||||
type="info"
|
||||
</div>
|
||||
|
||||
<!-- 右侧控制面板 -->
|
||||
<div class="music-control-panel">
|
||||
<!-- 播放控制按钮 -->
|
||||
<NFlex
|
||||
vertical
|
||||
size="small"
|
||||
align="center"
|
||||
style="min-width: 100px;"
|
||||
>
|
||||
队列: {{ musicRquestStore.waitingMusics.length }}
|
||||
</NTag>
|
||||
<NButton
|
||||
<NText
|
||||
depth="3"
|
||||
style="font-size: 12px; margin-bottom: 4px;"
|
||||
>
|
||||
播放控制
|
||||
</NText>
|
||||
<NFlex
|
||||
size="small"
|
||||
type="info"
|
||||
justify="center"
|
||||
>
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
circle
|
||||
secondary
|
||||
size="small"
|
||||
:disabled="musicRquestStore.aplayerMusics.length === 0"
|
||||
@click="onPreviousMusic"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="PlayBack" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
上一首 / 重播
|
||||
</NTooltip>
|
||||
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
circle
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="musicRquestStore.aplayerMusics.length === 0"
|
||||
@click="togglePlay"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="aplayer?.audio?.paused !== false ? Play : Pause" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
{{ aplayer?.audio?.paused !== false ? '播放' : '暂停' }}
|
||||
</NTooltip>
|
||||
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
circle
|
||||
secondary
|
||||
size="small"
|
||||
:disabled="musicRquestStore.waitingMusics.length === 0 && musicRquestStore.aplayerMusics.length <= 1"
|
||||
@click="onNextMusic"
|
||||
>
|
||||
下一首
|
||||
<template #icon>
|
||||
<NIcon :component="PlayForward" />
|
||||
</template>
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
下一首
|
||||
</NTooltip>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
|
||||
<!-- 队列信息和管理 -->
|
||||
<NFlex
|
||||
vertical
|
||||
size="small"
|
||||
align="center"
|
||||
style="min-width: 100px;"
|
||||
>
|
||||
<NText
|
||||
depth="3"
|
||||
style="font-size: 12px; margin-bottom: 4px;"
|
||||
>
|
||||
队列管理
|
||||
</NText>
|
||||
<NFlex
|
||||
vertical
|
||||
size="small"
|
||||
align="center"
|
||||
>
|
||||
<NTag
|
||||
:bordered="false"
|
||||
:type="musicRquestStore.waitingMusics.length > 0 ? 'warning' : 'info'"
|
||||
size="small"
|
||||
round
|
||||
style="min-width: 80px; text-align: center;"
|
||||
>
|
||||
等待: {{ musicRquestStore.waitingMusics.length }}
|
||||
</NTag>
|
||||
|
||||
<NTag
|
||||
:bordered="false"
|
||||
type="success"
|
||||
size="small"
|
||||
round
|
||||
style="min-width: 80px; text-align: center;"
|
||||
>
|
||||
歌单: {{ musicRquestStore.originMusics.length }}
|
||||
</NTag>
|
||||
|
||||
<NTooltip v-if="musicRquestStore.waitingMusics.length > 0">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
size="tiny"
|
||||
type="error"
|
||||
secondary
|
||||
@click="clearWaitingQueue"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon
|
||||
:component="TrashBin"
|
||||
size="12"
|
||||
/>
|
||||
</template>
|
||||
清空队列
|
||||
</NButton>
|
||||
</template>
|
||||
清空所有等待中的点歌
|
||||
</NTooltip>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
|
||||
<!-- 音量控制 -->
|
||||
<NFlex
|
||||
vertical
|
||||
size="small"
|
||||
align="center"
|
||||
style="min-width: 100px;"
|
||||
>
|
||||
<NFlex
|
||||
align="center"
|
||||
size="small"
|
||||
>
|
||||
<NIcon
|
||||
:component="VolumeHigh"
|
||||
size="14"
|
||||
:depth="3"
|
||||
/>
|
||||
<NText
|
||||
depth="3"
|
||||
style="font-size: 12px;"
|
||||
>
|
||||
音量
|
||||
</NText>
|
||||
</NFlex>
|
||||
<NSlider
|
||||
v-model:value="playerVolume"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
style="width: 80px;"
|
||||
:tooltip="false"
|
||||
size="small"
|
||||
/>
|
||||
<NText
|
||||
depth="3"
|
||||
style="font-size: 11px;"
|
||||
>
|
||||
{{ Math.round(playerVolume * 100) }}%
|
||||
</NText>
|
||||
</NFlex>
|
||||
</div>
|
||||
</NFlex>
|
||||
</div>
|
||||
|
||||
<!-- 最小化状态显示 -->
|
||||
<template v-if="isPlayerMinimized">
|
||||
<!-- Content is moved to the header for minimized state -->
|
||||
</template>
|
||||
</NCard>
|
||||
</NLayoutFooter>
|
||||
</NLayout>
|
||||
</NLayout>
|
||||
@@ -863,10 +1246,92 @@ onMounted(() => {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
/* 音乐播放器样式 */
|
||||
.music-player-footer {
|
||||
background: var(--body-color);
|
||||
}
|
||||
|
||||
.music-player-card {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.music-player-card:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 12px 40px rgba(0,0,0,0.15) !important;
|
||||
}
|
||||
|
||||
.music-control-panel {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.music-control-panel {
|
||||
min-width: auto;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.music-control-panel > div {
|
||||
min-width: auto !important;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.login-card, .loading-card {
|
||||
width: 95%;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.music-player-card {
|
||||
margin: 4px !important;
|
||||
}
|
||||
|
||||
.music-control-panel {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.music-control-panel > div {
|
||||
width: 100% !important;
|
||||
min-width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 播放器按钮悬停效果 */
|
||||
.music-player-card .n-button {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.music-player-card .n-button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 音量滑块样式 */
|
||||
.music-player-card .n-slider {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.music-player-card .n-slider:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* 标签动画 */
|
||||
.music-player-card .n-tag {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.music-player-card .n-tag:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user