music / src /stores /playqueue.js
ahutchen's picture
refactor(search): 重构搜索功能并添加搜索历史页面
a1eddda
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
/**
* 播放列表 Store - 单一职责:管理当前播放器的临时队列
* 对应网易云音乐的 "Now Playing" 概念
*/
export const usePlayQueueStore = defineStore('playqueue', () => {
// 状态
const queue = ref([])
const currentIndex = ref(-1)
const playMode = ref('list') // 'list', 'random', 'single'
// 常量
const STORAGE_KEY = 'vue-music-play-queue'
const MAX_QUEUE_SIZE = 2000
// 计算属性
const currentSong = computed(() => {
return currentIndex.value >= 0 && currentIndex.value < queue.value.length
? queue.value[currentIndex.value]
: null
})
const queueLength = computed(() => queue.value.length)
const isEmpty = computed(() => queue.value.length === 0)
const hasPrevious = computed(() => currentIndex.value > 0)
const hasNext = computed(() => currentIndex.value < queue.value.length - 1)
// 队列管理方法
const setQueue = (songs, startIndex = 0) => {
if (!Array.isArray(songs)) {
console.error('队列必须是数组')
return false
}
// 去重:移除重复的歌曲
const uniqueSongs = []
const seen = new Set()
for (const song of songs) {
if (song && song.id) {
const key = `${song.source}-${song.id}`
if (!seen.has(key)) {
seen.add(key)
uniqueSongs.push(song)
}
}
}
queue.value = uniqueSongs.slice(0, MAX_QUEUE_SIZE)
currentIndex.value = Math.max(0, Math.min(startIndex, queue.value.length - 1))
saveQueue()
return true
}
const addToQueue = (song, position = 'end') => {
if (!song || !song.id) {
return { success: false, message: '歌曲信息无效' }
}
// 防重复:检查歌曲是否已在队列中
const exists = queue.value.some(s => s.id === song.id && s.source === song.source)
if (exists) {
return { success: false, message: '歌曲已在播放列表中' }
}
if (queue.value.length >= MAX_QUEUE_SIZE) {
return { success: false, message: `播放列表最多只能添加 ${MAX_QUEUE_SIZE} 首歌曲` }
}
switch (position) {
case 'next':
// 插入到当前播放歌曲的下一个位置
const insertIndex = Math.min(currentIndex.value + 1, queue.value.length)
queue.value.splice(insertIndex, 0, { ...song })
break
case 'start':
queue.value.unshift({ ...song })
if (currentIndex.value >= 0) {
currentIndex.value++
}
break
case 'end':
default:
queue.value.push({ ...song })
break
}
saveQueue()
return { success: true, message: '已添加到播放列表' }
}
const removeFromQueue = (index) => {
if (index < 0 || index >= queue.value.length) {
return { success: false, message: '无效的索引' }
}
queue.value.splice(index, 1)
// 调整当前播放索引
if (currentIndex.value > index) {
currentIndex.value--
} else if (currentIndex.value === index) {
// 如果删除的是当前播放的歌曲
if (queue.value.length === 0) {
currentIndex.value = -1
} else {
currentIndex.value = Math.min(currentIndex.value, queue.value.length - 1)
}
}
saveQueue()
return { success: true, message: '已从播放列表移除' }
}
const clearQueue = () => {
queue.value = []
currentIndex.value = -1
saveQueue()
}
// 播放控制方法
const playAtIndex = (index) => {
if (index < 0 || index >= queue.value.length) {
return null
}
currentIndex.value = index
saveQueue()
return queue.value[index]
}
const playNext = () => {
if (queue.value.length === 0) return null
let nextIndex = currentIndex.value
switch (playMode.value) {
case 'random':
// 随机播放:生成与当前不同的随机索引
if (queue.value.length > 1) {
do {
nextIndex = Math.floor(Math.random() * queue.value.length)
} while (nextIndex === currentIndex.value)
}
break
case 'single':
// 单曲循环:保持当前索引
break
case 'list':
default:
// 顺序播放:下一首,到末尾则循环到开头
nextIndex = hasNext.value ? currentIndex.value + 1 : 0
break
}
return playAtIndex(nextIndex)
}
const playPrevious = () => {
if (queue.value.length === 0) return null
let prevIndex = currentIndex.value
switch (playMode.value) {
case 'random':
// 随机播放:生成与当前不同的随机索引
if (queue.value.length > 1) {
do {
prevIndex = Math.floor(Math.random() * queue.value.length)
} while (prevIndex === currentIndex.value)
}
break
case 'single':
// 单曲循环:保持当前索引
break
case 'list':
default:
// 顺序播放:上一首,到开头则循环到末尾
prevIndex = hasPrevious.value ? currentIndex.value - 1 : queue.value.length - 1
break
}
return playAtIndex(prevIndex)
}
const setPlayMode = (mode) => {
const validModes = ['list', 'random', 'single']
if (validModes.includes(mode)) {
playMode.value = mode
saveQueue()
return true
}
return false
}
const togglePlayMode = () => {
const modes = ['list', 'random', 'single']
const currentIndex = modes.indexOf(playMode.value)
const nextIndex = (currentIndex + 1) % modes.length
setPlayMode(modes[nextIndex])
return playMode.value
}
// 队列操作方法
const moveInQueue = (fromIndex, toIndex) => {
if (fromIndex < 0 || fromIndex >= queue.value.length ||
toIndex < 0 || toIndex >= queue.value.length) {
return false
}
const item = queue.value.splice(fromIndex, 1)[0]
queue.value.splice(toIndex, 0, item)
// 调整当前播放索引
if (currentIndex.value === fromIndex) {
currentIndex.value = toIndex
} else if (fromIndex < currentIndex.value && toIndex >= currentIndex.value) {
currentIndex.value--
} else if (fromIndex > currentIndex.value && toIndex <= currentIndex.value) {
currentIndex.value++
}
saveQueue()
return true
}
const shuffleQueue = () => {
if (queue.value.length <= 1) return
const currentSong = currentSong.value
// Fisher-Yates洗牌算法
for (let i = queue.value.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[queue.value[i], queue.value[j]] = [queue.value[j], queue.value[i]]
}
// 如果有当前播放的歌曲,找到它的新位置
if (currentSong) {
const newIndex = queue.value.findIndex(song =>
song.id === currentSong.id && song.source === currentSong.source
)
if (newIndex >= 0) {
currentIndex.value = newIndex
}
}
saveQueue()
}
// 批量操作
const addMultipleToQueue = (songs, position = 'end') => {
let successCount = 0
const results = []
for (const song of songs) {
const result = addToQueue(song, position)
results.push(result)
if (result.success) successCount++
}
return {
success: successCount > 0,
successCount,
totalCount: songs.length,
results
}
}
// 搜索功能
const searchInQueue = (keyword) => {
if (!keyword || !keyword.trim()) {
return queue.value.map((song, index) => ({ song, index }))
}
const lowerKeyword = keyword.toLowerCase().trim()
return queue.value
.map((song, index) => ({ song, index }))
.filter(({ song }) => {
// 安全的字符串比较函数
const safeIncludes = (text) => {
if (!text) return false
if (typeof text === 'string') {
return text.toLowerCase().includes(lowerKeyword)
}
if (Array.isArray(text)) {
// 如果是数组,检查每个元素
return text.some(item => {
if (typeof item === 'string') {
return item.toLowerCase().includes(lowerKeyword)
}
if (typeof item === 'object' && item?.name) {
return item.name.toLowerCase().includes(lowerKeyword)
}
return false
})
}
if (typeof text === 'object' && text?.name) {
return text.name.toLowerCase().includes(lowerKeyword)
}
return String(text).toLowerCase().includes(lowerKeyword)
}
return safeIncludes(song.name) ||
safeIncludes(song.artist) ||
safeIncludes(song.album)
})
}
// 存储管理
const saveQueue = () => {
try {
const data = {
queue: queue.value,
currentIndex: currentIndex.value,
playMode: playMode.value,
lastUpdated: Date.now(),
version: '1.0'
}
localStorage.setItem(STORAGE_KEY, JSON.stringify(data))
} catch (error) {
console.error('保存播放列表失败:', error)
}
}
const loadQueue = () => {
try {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved) {
const data = JSON.parse(saved)
// 检查数据完整性
if (Array.isArray(data.queue)) {
queue.value = data.queue
currentIndex.value = data.currentIndex >= 0 ? data.currentIndex : -1
playMode.value = data.playMode || 'list'
}
}
} catch (error) {
console.error('加载播放列表失败:', error)
queue.value = []
currentIndex.value = -1
playMode.value = 'list'
}
}
return {
// 状态
queue: computed(() => queue.value),
currentIndex: computed(() => currentIndex.value),
currentSong,
playMode: computed(() => playMode.value),
// 计算属性
queueLength,
isEmpty,
hasPrevious,
hasNext,
// 队列管理
setQueue,
addToQueue,
removeFromQueue,
clearQueue,
// 播放控制
playAtIndex,
playNext,
playPrevious,
setPlayMode,
togglePlayMode,
// 队列操作
moveInQueue,
shuffleQueue,
// 批量操作
addMultipleToQueue,
// 搜索功能
searchInQueue,
// 存储管理
saveQueue,
loadQueue
}
})