diff --git a/src/assets/icons/starfilled.vue b/src/assets/icons/starfilled.vue
new file mode 100644
index 0000000..b405ec0
--- /dev/null
+++ b/src/assets/icons/starfilled.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/Playroom.vue b/src/pages/Playroom.vue
index debf6ba..5759094 100644
--- a/src/pages/Playroom.vue
+++ b/src/pages/Playroom.vue
@@ -7,6 +7,7 @@ import { onMounted, onUnmounted, nextTick } from 'vue'
import { useTemplateRef } from 'vue'
import { ref, watch } from 'vue'
import { usePreferences } from '../stores/usePreferences'
+import { useFavourites } from '../stores/useFavourites'
import ScrollingLyrics from '../components/ScrollingLyrics.vue'
@@ -18,6 +19,7 @@ import LoadingIndicator from '../assets/icons/loadingindicator.vue'
import ChatBubbleQuoteIcon from '../assets/icons/chatbubblequote.vue'
import ChatBubbleQuoteFullIcon from '../assets/icons/chatbubblequotefull.vue'
import StarEmptyIcon from '../assets/icons/starempty.vue'
+import StarFilledIcon from '../assets/icons/starfilled.vue'
import MusicListIcon from '../assets/icons/musiclist.vue'
import EllipsisHorizontalIcon from '../assets/icons/ellipsishorizontal.vue'
import XIcon from '../assets/icons/x.vue'
@@ -28,6 +30,8 @@ import SpeakerIcon from '../assets/icons/speaker.vue'
const playQueueStore = usePlayQueueStore()
const preferences = usePreferences()
+const favourites = useFavourites()
+
gsap.registerPlugin(Draggable)
const progressBarThumb = useTemplateRef('progressBarThumb')
@@ -353,10 +357,12 @@ watch(() => playQueueStore.currentIndex, () => {
diff --git a/src/stores/useFavourites.ts b/src/stores/useFavourites.ts
new file mode 100644
index 0000000..ef54145
--- /dev/null
+++ b/src/stores/useFavourites.ts
@@ -0,0 +1,260 @@
+import { defineStore } from "pinia"
+import { ref, watch, computed } from "vue"
+
+// 声明全局类型
+declare global {
+ interface Window {
+ browser?: any
+ }
+}
+
+export const useFavourites = defineStore('favourites', () => {
+ const favourites = ref([])
+
+ const isLoaded = ref(false)
+ const storageType = ref<'chrome' | 'localStorage' | 'memory'>('chrome')
+
+ // 默认收藏列表
+ const defaultFavourites: QueueItem[] = []
+
+ // 检测可用的 API
+ const detectAvailableAPIs = () => {
+ // 检查原生 chrome API
+ try {
+ if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.sync) {
+ storageType.value = 'chrome'
+ return 'chrome'
+ }
+ } catch (error) {
+ // Silent fail
+ }
+
+ // 检查 window.chrome
+ try {
+ if (window.chrome && window.chrome.storage && window.chrome.storage.sync) {
+ storageType.value = 'chrome'
+ return 'chrome'
+ }
+ } catch (error) {
+ // Silent fail
+ }
+
+ // 检查 localStorage
+ try {
+ if (typeof localStorage !== 'undefined') {
+ localStorage.setItem('msr_test', 'test')
+ localStorage.removeItem('msr_test')
+ storageType.value = 'localStorage'
+ return 'localStorage'
+ }
+ } catch (error) {
+ // Silent fail
+ }
+
+ // 都不可用,使用内存存储
+ storageType.value = 'memory'
+ return 'memory'
+ }
+
+ // 通用的获取存储值函数
+ const getStoredValue = async (key: string, defaultValue: any) => {
+ const type = detectAvailableAPIs()
+
+ try {
+ switch (type) {
+ case 'chrome':
+ return await new Promise((resolve) => {
+ const api = chrome?.storage?.sync || window.chrome?.storage?.sync
+ if (api) {
+ api.get({ [key]: defaultValue }, (result) => {
+ if (chrome.runtime.lastError) {
+ resolve(defaultValue)
+ } else {
+ resolve(result[key])
+ }
+ })
+ } else {
+ resolve(defaultValue)
+ }
+ })
+
+ case 'localStorage':
+ const stored = localStorage.getItem(`msr_${key}`)
+ const value = stored ? JSON.parse(stored) : defaultValue
+ return value
+
+ case 'memory':
+ default:
+ return defaultValue
+ }
+ } catch (error) {
+ return defaultValue
+ }
+ }
+
+ // 通用的设置存储值函数
+ const setStoredValue = async (key: string, value: any) => {
+ const type = storageType.value
+
+ try {
+ switch (type) {
+ case 'chrome':
+ return await new Promise((resolve, reject) => {
+ const api = chrome?.storage?.sync || window.chrome?.storage?.sync
+ if (api) {
+ api.set({ [key]: value }, () => {
+ if (chrome.runtime.lastError) {
+ reject(new Error(chrome.runtime.lastError.message))
+ } else {
+ resolve()
+ }
+ })
+ } else {
+ reject(new Error('Chrome storage API 不可用'))
+ }
+ })
+
+ case 'localStorage':
+ localStorage.setItem(`msr_${key}`, JSON.stringify(value))
+ break
+
+ case 'memory':
+ // 内存存储(不持久化)
+ break
+ }
+ } catch (error) {
+ throw error
+ }
+ }
+
+ // 获取收藏列表
+ const getFavourites = async () => {
+ const result = await getStoredValue('favourites', defaultFavourites)
+ // 确保返回的是数组
+ return Array.isArray(result) ? result : defaultFavourites
+ }
+
+ // 保存收藏列表
+ const saveFavourites = async () => {
+ // 确保保存的是数组
+ await setStoredValue('favourites', [...favourites.value])
+ }
+
+ // 检查歌曲是否已收藏
+ const isFavourite = (songCid: string): boolean => {
+ return favourites.value.some(item => item.song.cid === songCid)
+ }
+
+ // 添加到收藏
+ const addToFavourites = async (queueItem: QueueItem) => {
+ if (!isFavourite(queueItem.song.cid)) {
+ favourites.value.push(queueItem)
+ if (isLoaded.value) {
+ try {
+ await saveFavourites()
+ } catch (error) {
+ // 保存失败时回滚
+ favourites.value.pop()
+ throw error
+ }
+ }
+ }
+ }
+
+ // 从收藏中移除
+ const removeFromFavourites = async (songCid: string) => {
+ const index = favourites.value.findIndex(item => item.song.cid === songCid)
+ if (index !== -1) {
+ const removedItem = favourites.value.splice(index, 1)[0]
+ if (isLoaded.value) {
+ try {
+ await saveFavourites()
+ } catch (error) {
+ // 保存失败时回滚
+ favourites.value.splice(index, 0, removedItem)
+ throw error
+ }
+ }
+ }
+ }
+
+ // 切换收藏状态
+ const toggleFavourite = async (queueItem: QueueItem) => {
+ if (isFavourite(queueItem.song.cid)) {
+ await removeFromFavourites(queueItem.song.cid)
+ } else {
+ await addToFavourites(queueItem)
+ }
+ }
+
+ // 清空收藏列表
+ const clearFavourites = async () => {
+ const backup = [...favourites.value]
+ favourites.value = []
+ if (isLoaded.value) {
+ try {
+ await saveFavourites()
+ } catch (error) {
+ // 保存失败时回滚
+ favourites.value = backup
+ throw error
+ }
+ }
+ }
+
+ // 获取收藏数量
+ const favouritesCount = computed(() => favourites.value.length)
+
+ // 异步初始化函数
+ const initializeFavourites = async () => {
+ try {
+ const savedFavourites = await getFavourites()
+ // 确保设置的是有效数组
+ favourites.value = Array.isArray(savedFavourites) ? savedFavourites : []
+ isLoaded.value = true
+ } catch (error) {
+ favourites.value = []
+ isLoaded.value = true
+ }
+ }
+
+ // 监听变化并保存(防抖处理)
+ let saveTimeout: NodeJS.Timeout | null = null
+ watch(favourites, async () => {
+ if (isLoaded.value) {
+ // 清除之前的定时器
+ if (saveTimeout) {
+ clearTimeout(saveTimeout)
+ }
+ // 设置新的定时器,防抖保存
+ saveTimeout = setTimeout(async () => {
+ try {
+ await saveFavourites()
+ } catch (error) {
+ // Silent fail
+ }
+ }, 300)
+ }
+ }, { deep: true })
+
+ // 立即初始化
+ initializeFavourites()
+
+ return {
+ favourites,
+ isLoaded,
+ storageType,
+ favouritesCount,
+ initializeFavourites,
+ getFavourites,
+ saveFavourites,
+ isFavourite,
+ addToFavourites,
+ removeFromFavourites,
+ toggleFavourite,
+ clearFavourites,
+ getStoredValue,
+ setStoredValue
+ }
+})
+