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 } })