Skip to content

🪝 Composables 组合式函数

🎧 useAudio

音频播放控制的核心组合式函数,封装了播放器的所有操作。

📁 文件位置src/composables/useAudio.ts

📤 返回值

📊 状态

属性类型说明
currentSongComputedRef<Song | null>当前播放歌曲
isPlayingComputedRef<boolean>是否正在播放
isLoadingComputedRef<boolean>是否加载中
playlistComputedRef<Song[]>播放列表
playHistoryComputedRef<Song[]>播放历史
playModeComputedRef<PlayMode>播放模式
volumeComputedRef<number>音量 (0-1)
isMutedComputedRef<boolean>是否静音
currentTimeComputedRef<number>当前播放时间(秒)
durationComputedRef<number>歌曲总时长(秒)
progressComputedRef<number>播放进度 (0-100)
hasNextComputedRef<boolean>是否有下一首
hasPreviousComputedRef<boolean>是否有上一首
formattedCurrentTimeComputedRef<string>格式化当前时间 mm:ss
formattedDurationComputedRef<string>格式化总时长 mm:ss
playModeTextComputedRef<string>播放模式文本
playModeIconComputedRef<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     // 罗马音
}

📤 返回值

属性/方法类型说明
lyricsOriginalRef<RawLyricLine[]>原始歌词数组
lyricsTransRef<RawLyricLine[]>翻译歌词数组
lyricsRomaRef<RawLyricLine[]>罗马音数组
showTransRef<boolean>是否显示翻译
showRomaRef<boolean>是否显示罗马音
mergedLinesComputedRef<LyricLine[]>合并后的歌词行
activeSingleLyricsComputedRef<LyricLine[]>活动歌词列表
activeTimelineComputedRef<number[]>时间轴数组
loadingRef<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)
}

📤 返回值

📊 状态

属性类型说明
frequencyDataRef<Uint8Array>频域数据(0-255)
timeDomainDataRef<Uint8Array>时域数据(0-255)
isInitializedRef<boolean>是否已初始化
isAnalysingRef<boolean>是否正在分析
bufferLengthComputedRef<number>数据缓冲区长度
averageVolumeComputedRef<number>平均音量 (0-100)
bassLevelComputedRef<number>低音强度 (0-100)
trebleLevelComputedRef<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             // 歌词缩放比例
}

📤 返回值

属性/方法类型说明
currentIndexRef<number>当前高亮的歌词索引
positionedRef<boolean>是否已定位
autoScrollRef<boolean>是否自动滚动
scaleRef<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层渐变色数组
}

📤 返回值

属性/方法类型说明
useCoverBgRef<boolean>是否使用封面背景
bgActiveRef<'A' | 'B'>当前激活的背景层
bgAGradientRef<string[]>背景A层渐变色
bgBGradientRef<string[]>背景B层渐变色
bgAStyleComputedRef<object>背景A层样式
bgBStyleComputedRef<object>背景B层样式
activeGradientComputedRef<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(响应式)
}

📤 返回值

属性/方法类型说明
commentCountRef<number>评论数量
isLoadingRef<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>

基于 PolyForm-Noncommercial-1.0.0 许可发布