🪝 Composables 组合式函数
🎧 useAudio
音频播放控制的核心组合式函数,封装了播放器的所有操作。
📁 文件位置:src/composables/useAudio.ts
📤 返回值
📊 状态
| 属性 | 类型 | 说明 |
|---|---|---|
currentSong | ComputedRef<Song | null> | 当前播放歌曲 |
isPlaying | ComputedRef<boolean> | 是否正在播放 |
isLoading | ComputedRef<boolean> | 是否加载中 |
playlist | ComputedRef<Song[]> | 播放列表 |
playHistory | ComputedRef<Song[]> | 播放历史 |
playMode | ComputedRef<PlayMode> | 播放模式 |
volume | ComputedRef<number> | 音量 (0-1) |
isMuted | ComputedRef<boolean> | 是否静音 |
currentTime | ComputedRef<number> | 当前播放时间(秒) |
duration | ComputedRef<number> | 歌曲总时长(秒) |
progress | ComputedRef<number> | 播放进度 (0-100) |
hasNext | ComputedRef<boolean> | 是否有下一首 |
hasPrevious | ComputedRef<boolean> | 是否有上一首 |
formattedCurrentTime | ComputedRef<string> | 格式化当前时间 mm:ss |
formattedDuration | ComputedRef<string> | 格式化总时长 mm:ss |
playModeText | ComputedRef<string> | 播放模式文本 |
playModeIcon | ComputedRef<string> | 播放模式图标类名 |
▶️ 播放控制
| 方法 | 参数 | 说明 |
|---|---|---|
play(song?, index?) | Song, number | 播放指定歌曲 |
pause() | - | 暂停播放 |
resume() | - | 继续播放 |
togglePlay() | - | 切换播放/暂停 |
next() | - | 下一首 |
previous() | - | 上一首 |
stop() | - | 停止播放 |
🔁 播放模式
| 方法 | 参数 | 说明 |
|---|---|---|
togglePlayMode() | - | 循环切换播放模式 |
setPlayMode(mode) | PlayMode | 设置指定播放模式 |
🔊 音量控制
| 方法 | 参数 | 说明 |
|---|---|---|
setVolume(vol) | number (0-1) | 设置音量 |
toggleMute() | - | 切换静音 |
⏱️ 进度控制
| 方法 | 参数 | 说明 |
|---|---|---|
setProgress(prog) | number (0-100) | 设置播放进度百分比 |
setCurrentTime(time) | number | 设置播放时间(秒) |
📋 播放列表管理
| 方法 | 参数 | 说明 |
|---|---|---|
addSong(song) | Song | 添加歌曲到列表 |
addSongs(songs) | Song[] | 批量添加歌曲 |
removeSong(id) | string | number | 移除歌曲 |
removeSongs(ids) | Array<string | number> | 批量移除 |
clearPlaylist() | - | 清空播放列表 |
setPlaylist(songs, startIndex?) | Song[], number | 设置播放列表 |
playByIndex(index) | number | 播放指定索引歌曲 |
moveSong(from, to) | number, number | 移动歌曲位置 |
queueNext(id) | string | number | 添加到下一首播放 |
🔧 其他
| 方法 | 说明 |
|---|---|
clearHistory() | 清空播放历史 |
clearError() | 清除错误信息 |
handleKeyboard(event) | 处理键盘事件 |
setupMediaSession() | 设置系统媒体控制 |
💡 使用示例
vue
<script setup lang="ts">
import { useAudio } from '@/composables/useAudio'
const {
currentSong,
isPlaying,
progress,
formattedCurrentTime,
formattedDuration,
togglePlay,
next,
previous,
setProgress,
} = useAudio()
</script>
<template>
<div class="player">
<div v-if="currentSong">
<img :src="currentSong.cover" />
<span>{{ currentSong.name }} - {{ currentSong.artist }}</span>
</div>
<div class="controls">
<button @click="previous">上一首</button>
<button @click="togglePlay">
{{ isPlaying ? '暂停' : '播放' }}
</button>
<button @click="next">下一首</button>
</div>
<div class="progress">
<span>{{ formattedCurrentTime }}</span>
<input
type="range"
:value="progress"
@input="setProgress($event.target.value)"
/>
<span>{{ formattedDuration }}</span>
</div>
</div>
</template>📝 useLyrics
歌词解析与同步的组合式函数。
📁 文件位置:src/composables/useLyrics.ts
📘 类型定义
ts
interface LyricLine {
time: number // 时间戳(秒)
ori: string // 原文歌词
tran?: string // 翻译歌词
roma?: string // 罗马音
}📤 返回值
| 属性/方法 | 类型 | 说明 |
|---|---|---|
lyricsOriginal | Ref<RawLyricLine[]> | 原始歌词数组 |
lyricsTrans | Ref<RawLyricLine[]> | 翻译歌词数组 |
lyricsRoma | Ref<RawLyricLine[]> | 罗马音数组 |
showTrans | Ref<boolean> | 是否显示翻译 |
showRoma | Ref<boolean> | 是否显示罗马音 |
mergedLines | ComputedRef<LyricLine[]> | 合并后的歌词行 |
activeSingleLyrics | ComputedRef<LyricLine[]> | 活动歌词列表 |
activeTimeline | ComputedRef<number[]> | 时间轴数组 |
loading | Ref<boolean> | 是否加载中 |
fetchLyrics(id, force?) | Function | 获取歌词 |
timeForIndex(index) | Function | 获取指定索引的时间 |
💡 使用示例
vue
<script setup lang="ts">
import { useLyrics } from '@/composables/useLyrics'
import { useAudio } from '@/composables/useAudio'
const { activeSingleLyrics, fetchLyrics, loading } = useLyrics()
const { currentSong, currentTime } = useAudio()
watch(() => currentSong.value?.id, (id) => {
if (id) fetchLyrics(id)
})
const currentLineIndex = computed(() => {
const time = currentTime.value
let idx = 0
for (let i = 0; i < activeSingleLyrics.value.length; i++) {
if (activeSingleLyrics.value[i].time <= time) idx = i
else break
}
return idx
})
</script>
<template>
<div class="lyrics">
<div v-if="loading">加载中...</div>
<div
v-for="(line, i) in activeSingleLyrics"
:key="i"
:class="{ active: i === currentLineIndex }"
>
<p class="ori">{{ line.ori }}</p>
<p v-if="line.tran" class="tran">{{ line.tran }}</p>
</div>
</div>
</template>📊 useAudioAnalyser
音频分析器组合式函数,基于 Web Audio API 实现实时音频频谱分析。采用全局单例模式,确保只创建一次音频上下文。
📁 文件位置:src/composables/useAudioAnalyser.ts
📘 类型定义
ts
type VisualizerType = 'bars' | 'wave' | 'circular'
interface AudioAnalyserOptions {
fftSize?: number // FFT 大小(默认 2048)
smoothingTimeConstant?: number // 平滑时间常数(默认 0.8)
minDecibels?: number // 最小分贝值(默认 -90)
maxDecibels?: number // 最大分贝值(默认 -10)
}📤 返回值
📊 状态
| 属性 | 类型 | 说明 |
|---|---|---|
frequencyData | Ref<Uint8Array> | 频域数据(0-255) |
timeDomainData | Ref<Uint8Array> | 时域数据(0-255) |
isInitialized | Ref<boolean> | 是否已初始化 |
isAnalysing | Ref<boolean> | 是否正在分析 |
bufferLength | ComputedRef<number> | 数据缓冲区长度 |
averageVolume | ComputedRef<number> | 平均音量 (0-100) |
bassLevel | ComputedRef<number> | 低音强度 (0-100) |
trebleLevel | ComputedRef<number> | 高音强度 (0-100) |
▶️ 方法
| 方法 | 参数 | 说明 |
|---|---|---|
init(audioElement) | HTMLAudioElement | 初始化音频分析器 |
start() | - | 开始分析 |
stop() | - | 停止分析(带平滑降落动画) |
resume() | - | 恢复 AudioContext |
destroy() | - | 销毁分析器(谨慎使用) |
💡 使用示例
vue
<script setup lang="ts">
import { useAudioAnalyser } from '@/composables/useAudioAnalyser'
import { useAudioStore } from '@/stores/modules/audio'
const audioStore = useAudioStore()
const {
frequencyData,
timeDomainData,
isInitialized,
isRunning,
init,
start,
stop
} = useAudioAnalyser({
fftSize: 2048,
smoothingTimeConstant: 0.8
})
// 初始化
onMounted(() => {
const audioElement = audioStore.audio.audio
if (audioElement) {
init(audioElement)
start()
}
})
// 清理
onUnmounted(() => {
stop()
})
</script>
<template>
<div v-if="isInitialized">
<canvas ref="canvasRef"></canvas>
</div>
</template>🎨 配合 AudioVisualizer 组件使用
vue
<script setup lang="ts">
import { useAudioAnalyser } from '@/composables/useAudioAnalyser'
import AudioVisualizer from '@/components/Ui/AudioVisualizer.vue'
const { frequencyData, timeDomainData, init, start } = useAudioAnalyser()
// 初始化...
</script>
<template>
<AudioVisualizer
:frequency-data="frequencyData"
:time-domain-data="timeDomainData"
type="bars"
:gradient-colors="['#3b82f6', '#8b5cf6', '#ec4899']"
/>
</template>⌨️ useGlobalKeyboard
全局键盘快捷键支持。
📁 文件位置:src/composables/useAudio.ts
📤 返回值
| 方法 | 说明 |
|---|---|
enableGlobalKeyboard() | 启用全局键盘监听 |
disableGlobalKeyboard() | 禁用全局键盘监听 |
💡 使用示例
vue
<script setup lang="ts">
import { useGlobalKeyboard } from '@/composables/useAudio'
const { enableGlobalKeyboard, disableGlobalKeyboard } = useGlobalKeyboard()
onMounted(() => {
enableGlobalKeyboard()
})
onUnmounted(() => {
disableGlobalKeyboard()
})
</script>🎹 支持的快捷键
| 按键 | 功能 |
|---|---|
Space | 播放/暂停 |
ArrowLeft | 快退 10 秒 |
ArrowRight | 快进 10 秒 |
Ctrl + ArrowLeft | 上一首 |
Ctrl + ArrowRight | 下一首 |
ArrowUp | 音量 +10% |
ArrowDown | 音量 -10% |
M | 静音切换 |
R | 切换播放模式 |
📜 useLyricsScroll
歌词滚动控制的组合式函数,基于 GSAP 实现平滑滚动动画。
📁 文件位置:src/composables/useLyricsScroll.ts
📘 类型定义
ts
interface LyricsScrollOptions {
lyricsRef: Ref<HTMLElement | null> // 歌词容器元素引用
timeline: Ref<number[]> // 时间轴数组
currentTime: Ref<number> // 当前播放时间
offset?: Ref<number> // 时间偏移量(秒)
}
interface LyricsScrollState {
currentIndex: number // 当前高亮的歌词索引
positioned: boolean // 歌词是否已定位到当前播放位置
autoScroll: boolean // 是否启用自动滚动
scale: number // 歌词缩放比例
}📤 返回值
| 属性/方法 | 类型 | 说明 |
|---|---|---|
currentIndex | Ref<number> | 当前高亮的歌词索引 |
positioned | Ref<boolean> | 是否已定位 |
autoScroll | Ref<boolean> | 是否自动滚动 |
scale | Ref<number> | 字体缩放比例 |
updateCurrentLyric(instant?) | Function | 更新当前歌词索引 |
scrollToCurrentLyric(instant?) | Function | 滚动到当前歌词位置 |
toggleAutoScroll() | Function | 切换自动滚动 |
resetLyrics() | Function | 重置歌词状态(切歌时调用) |
increaseScale(step?, max?) | Function | 增大字号(默认步长 0.05,最大 1.4) |
decreaseScale(step?, min?) | Function | 减小字号(默认步长 0.05,最小 0.8) |
💡 使用示例
vue
<script setup lang="ts">
import { useLyricsScroll } from '@/composables/useLyricsScroll'
import { useLyrics } from '@/composables/useLyrics'
import { useAudio } from '@/composables/useAudio'
const lyricsRef = ref<HTMLElement | null>(null)
const { activeTimeline } = useLyrics()
const { currentTime } = useAudio()
const {
currentIndex,
scale,
toggleAutoScroll,
increaseScale,
decreaseScale,
resetLyrics,
} = useLyricsScroll({
lyricsRef,
timeline: activeTimeline,
currentTime,
})
</script>🎨 useGradientBackground
渐变背景控制的组合式函数,基于 GSAP 实现双层背景平滑过渡和呼吸动画。
📁 文件位置:src/composables/useGradientBackground.ts
📘 类型定义
ts
interface GradientBackgroundOptions {
bgARef: Ref<HTMLElement | null> // 背景A层元素引用
bgBRef: Ref<HTMLElement | null> // 背景B层元素引用
isPlaying: Ref<boolean> // 是否正在播放
isOpen: Ref<boolean> // 抽屉是否打开
}
interface GradientBackgroundState {
useCoverBg: boolean // 是否使用封面背景
bgActive: 'A' | 'B' // 当前激活的背景层
bgAGradient: string[] // 背景A层渐变色数组
bgBGradient: string[] // 背景B层渐变色数组
}📤 返回值
| 属性/方法 | 类型 | 说明 |
|---|---|---|
useCoverBg | Ref<boolean> | 是否使用封面背景 |
bgActive | Ref<'A' | 'B'> | 当前激活的背景层 |
bgAGradient | Ref<string[]> | 背景A层渐变色 |
bgBGradient | Ref<string[]> | 背景B层渐变色 |
bgAStyle | ComputedRef<object> | 背景A层样式 |
bgBStyle | ComputedRef<object> | 背景B层样式 |
activeGradient | ComputedRef<string[]> | 当前激活背景的渐变色 |
startBackgroundBreathing() | Function | 开始背景呼吸动画 |
stopBackgroundBreathing() | Function | 停止背景呼吸动画 |
setBackgroundGradient(coverUrl?, delay?) | Function | 从封面提取颜色并设置背景 |
toggleCoverBg() | Function | 切换封面背景开关 |
💬 useCommentCount
歌曲评论数量获取的组合式函数,自动监听歌曲 ID 变化并加载评论数。
📁 文件位置:src/composables/useCommentCount.ts
📘 类型定义
ts
interface CommentCountOptions {
songId: Ref<number | string | undefined> // 歌曲ID(响应式)
}📤 返回值
| 属性/方法 | 类型 | 说明 |
|---|---|---|
commentCount | Ref<number> | 评论数量 |
isLoading | Ref<boolean> | 是否加载中 |
loadCommentCount(id?) | Function | 手动加载评论数量 |
💡 使用示例
vue
<script setup lang="ts">
import { useCommentCount } from '@/composables/useCommentCount'
import { useAudio } from '@/composables/useAudio'
const { currentSong } = useAudio()
const songId = computed(() => currentSong.value?.id)
const { commentCount, isLoading } = useCommentCount({ songId })
</script>
<template>
<span v-if="!isLoading">{{ commentCount }} 条评论</span>
</template>✨ useSharedElement
共享元素动画组合式函数,用于创建类似 iOS 的过渡动画效果,基于 GSAP。
📁 文件位置:src/composables/useSharedElement.ts
📤 返回值
| 方法 | 参数 | 说明 |
|---|---|---|
flyTo(source, target, imageUrl, options?) | 源元素、目标元素、图片URL | 抛物线飞行动画 |
expandTo(source, options?) | 源元素 | 卡片展开成全屏动画 |
collapseTo(expanded, target, options?) | 展开元素、目标元素 | 全屏收起回卡片动画 |
createRipple(event, container, color?) | 鼠标事件、容器元素 | 创建涟漪点击效果 |
createPulse(element, color?) | 元素 | 创建脉冲光环效果 |
create3DPress(element) | 元素 | 创建 3D 按压效果 |
staggerIn(elements, options?) | 元素列表 | 列表项错开入场动画 |
📘 配置选项
ts
interface SharedElementOptions {
duration?: number // 动画时长
ease?: string // 缓动函数
borderRadius?: { from: string; to: string } // 圆角过渡
scale?: { from: number; to: number } // 缩放过渡
onStart?: () => void // 动画开始回调
onComplete?: () => void // 动画完成回调
}💡 使用示例
vue
<script setup lang="ts">
import { useSharedElement } from '@/composables/useSharedElement'
const { flyTo, createRipple, staggerIn } = useSharedElement()
// 添加到播放列表的飞行动画
const handleAddToPlaylist = async (event: MouseEvent) => {
const sourceEl = event.currentTarget as HTMLElement
const targetEl = document.querySelector('.playlist-icon')
await flyTo(sourceEl, targetEl, coverUrl)
}
// 列表入场动画
onMounted(() => {
const items = document.querySelectorAll('.list-item')
staggerIn(items, { stagger: 0.05 })
})
</script>