mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
refactor: 重构音乐和歌词缓存逻辑 可配置缓存目录
This commit is contained in:
@@ -29,6 +29,7 @@ export default {
|
|||||||
retry: 'Retry',
|
retry: 'Retry',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
loadFailed: 'Load Failed',
|
loadFailed: 'Load Failed',
|
||||||
|
noData: 'No data',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
copySuccess: 'Copied to clipboard',
|
copySuccess: 'Copied to clipboard',
|
||||||
copyFailed: 'Copy failed',
|
copyFailed: 'Copy failed',
|
||||||
@@ -36,9 +37,7 @@ export default {
|
|||||||
required: 'This field is required',
|
required: 'This field is required',
|
||||||
invalidInput: 'Invalid input',
|
invalidInput: 'Invalid input',
|
||||||
selectRequired: 'Please select an option',
|
selectRequired: 'Please select an option',
|
||||||
numberRange: 'Please enter a number between {min} and {max}',
|
numberRange: 'Please enter a number between {min} and {max}'
|
||||||
ipAddress: 'Please enter a valid IP address',
|
|
||||||
portNumber: 'Please enter a valid port number (1-65535)'
|
|
||||||
},
|
},
|
||||||
viewMore: 'View More',
|
viewMore: 'View More',
|
||||||
noMore: 'No more',
|
noMore: 'No more',
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export default {
|
|||||||
progress: {
|
progress: {
|
||||||
total: 'Total Progress: {progress}%'
|
total: 'Total Progress: {progress}%'
|
||||||
},
|
},
|
||||||
items: 'items',
|
|
||||||
status: {
|
status: {
|
||||||
downloading: 'Downloading',
|
downloading: 'Downloading',
|
||||||
completed: 'Completed',
|
completed: 'Completed',
|
||||||
@@ -42,12 +41,12 @@ export default {
|
|||||||
'Are you sure you want to clear all download records? This will not delete the actual music files, but will clear all records.',
|
'Are you sure you want to clear all download records? This will not delete the actual music files, but will clear all records.',
|
||||||
confirm: 'Clear',
|
confirm: 'Clear',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
success: 'Download records cleared'
|
success: 'Download records cleared',
|
||||||
|
failed: 'Failed to clear download records'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
downloadComplete: '{filename} download completed',
|
downloadComplete: '{filename} download completed',
|
||||||
downloadFailed: '{filename} download failed: {error}',
|
downloadFailed: '{filename} download failed: {error}'
|
||||||
alreadyDownloading: '{filename} is already downloading'
|
|
||||||
},
|
},
|
||||||
loading: 'Loading...',
|
loading: 'Loading...',
|
||||||
playStarted: 'Play started: {name}',
|
playStarted: 'Play started: {name}',
|
||||||
|
|||||||
@@ -2,12 +2,8 @@ export default {
|
|||||||
title: 'Favorites',
|
title: 'Favorites',
|
||||||
count: 'Total {count}',
|
count: 'Total {count}',
|
||||||
batchDownload: 'Batch Download',
|
batchDownload: 'Batch Download',
|
||||||
selectAll: 'All',
|
|
||||||
download: 'Download ({count})',
|
download: 'Download ({count})',
|
||||||
cancel: 'Cancel',
|
|
||||||
emptyTip: 'No favorite songs yet',
|
emptyTip: 'No favorite songs yet',
|
||||||
viewMore: 'View More',
|
|
||||||
noMore: 'No more',
|
|
||||||
downloadSuccess: 'Download completed',
|
downloadSuccess: 'Download completed',
|
||||||
downloadFailed: 'Download failed',
|
downloadFailed: 'Download failed',
|
||||||
downloading: 'Downloading, please wait...',
|
downloading: 'Downloading, please wait...',
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export default {
|
|||||||
merging: 'Merging records...',
|
merging: 'Merging records...',
|
||||||
noDescription: 'No description',
|
noDescription: 'No description',
|
||||||
noData: 'No records',
|
noData: 'No records',
|
||||||
newKey: 'New translation',
|
|
||||||
heatmap: {
|
heatmap: {
|
||||||
title: 'Play Heatmap',
|
title: 'Play Heatmap',
|
||||||
loading: 'Loading data...',
|
loading: 'Loading data...',
|
||||||
|
|||||||
@@ -131,10 +131,7 @@ export default {
|
|||||||
timerEnded: 'Sleep timer ended',
|
timerEnded: 'Sleep timer ended',
|
||||||
playbackStopped: 'Music playback stopped',
|
playbackStopped: 'Music playback stopped',
|
||||||
minutesRemaining: '{minutes} min remaining',
|
minutesRemaining: '{minutes} min remaining',
|
||||||
songsRemaining: '{count} songs remaining',
|
songsRemaining: '{count} songs remaining'
|
||||||
activeTime: 'Timer Active',
|
|
||||||
activeSongs: 'Counting Songs',
|
|
||||||
activeEnd: 'End After List'
|
|
||||||
},
|
},
|
||||||
playList: {
|
playList: {
|
||||||
clearAll: 'Clear Playlist',
|
clearAll: 'Clear Playlist',
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export default {
|
|||||||
subscribe: 'Subscribe',
|
subscribe: 'Subscribe',
|
||||||
subscribed: 'Subscribed',
|
subscribed: 'Subscribed',
|
||||||
unsubscribe: 'Unsubscribe',
|
unsubscribe: 'Unsubscribe',
|
||||||
|
unsubscribed: 'Unsubscribed',
|
||||||
|
subscribeSuccess: 'Subscribed successfully',
|
||||||
|
unsubscribeFailed: 'Failed to unsubscribe',
|
||||||
|
subscribeFailed: 'Failed to subscribe',
|
||||||
radioDetail: 'Radio Detail',
|
radioDetail: 'Radio Detail',
|
||||||
programList: 'Episodes',
|
programList: 'Episodes',
|
||||||
playProgram: 'Play',
|
playProgram: 'Play',
|
||||||
|
|||||||
@@ -196,6 +196,36 @@ export default {
|
|||||||
system: {
|
system: {
|
||||||
cache: 'Cache Management',
|
cache: 'Cache Management',
|
||||||
cacheDesc: 'Clear cache',
|
cacheDesc: 'Clear cache',
|
||||||
|
diskCache: 'Disk Cache',
|
||||||
|
diskCacheDesc: 'Cache played music and lyrics on local disk to speed up repeated playback',
|
||||||
|
cacheDirectory: 'Cache Directory',
|
||||||
|
cacheDirectoryDesc: 'Custom directory for music and lyric cache files',
|
||||||
|
selectDirectory: 'Select Directory',
|
||||||
|
openDirectory: 'Open Directory',
|
||||||
|
cacheMaxSize: 'Cache Size Limit',
|
||||||
|
cacheMaxSizeDesc: 'Older cache items are cleaned automatically when limit is reached',
|
||||||
|
cleanupPolicy: 'Cleanup Policy',
|
||||||
|
cleanupPolicyDesc: 'Auto cleanup rule when cache reaches the size limit',
|
||||||
|
cleanupPolicyOptions: {
|
||||||
|
lru: 'Least Recently Used',
|
||||||
|
fifo: 'First In, First Out'
|
||||||
|
},
|
||||||
|
cacheStatus: 'Cache Status',
|
||||||
|
cacheStatusDesc: 'Used {used} / Limit {limit}',
|
||||||
|
cacheStatusDetail: 'Music {musicCount}, Lyrics {lyricCount}',
|
||||||
|
manageDiskCache: 'Manual Disk Cache Cleanup',
|
||||||
|
manageDiskCacheDesc: 'Clean cache by category',
|
||||||
|
clearMusicCache: 'Clear Music Cache',
|
||||||
|
clearLyricCache: 'Clear Lyric Cache',
|
||||||
|
clearAllCache: 'Clear All Cache',
|
||||||
|
switchDirectoryMigrateTitle: 'Existing Cache Detected',
|
||||||
|
switchDirectoryMigrateContent: 'Do you want to migrate old cache files to the new directory?',
|
||||||
|
switchDirectoryMigrateConfirm: 'Migrate',
|
||||||
|
switchDirectoryDestroyTitle: 'Destroy Old Cache',
|
||||||
|
switchDirectoryDestroyContent:
|
||||||
|
'If you do not migrate, do you want to destroy old cache files in the previous directory?',
|
||||||
|
switchDirectoryDestroyConfirm: 'Destroy',
|
||||||
|
switchDirectoryKeepOld: 'Keep Old Cache',
|
||||||
cacheClearTitle: 'Select cache types to clear:',
|
cacheClearTitle: 'Select cache types to clear:',
|
||||||
cacheTypes: {
|
cacheTypes: {
|
||||||
history: {
|
history: {
|
||||||
@@ -230,7 +260,14 @@ export default {
|
|||||||
restart: 'Restart',
|
restart: 'Restart',
|
||||||
restartDesc: 'Restart application',
|
restartDesc: 'Restart application',
|
||||||
messages: {
|
messages: {
|
||||||
clearSuccess: 'Cache cleared successfully, some settings will take effect after restart'
|
clearSuccess: 'Cache cleared successfully, some settings will take effect after restart',
|
||||||
|
diskCacheClearSuccess: 'Disk cache cleaned',
|
||||||
|
diskCacheClearFailed: 'Failed to clean disk cache',
|
||||||
|
diskCacheStatsLoadFailed: 'Failed to load cache status',
|
||||||
|
switchDirectorySuccess: 'Cache directory switched, old cache is kept',
|
||||||
|
switchDirectoryFailed: 'Failed to switch cache directory',
|
||||||
|
switchDirectoryMigrated: 'Cache directory switched, migrated {count} cache files',
|
||||||
|
switchDirectoryDestroyed: 'Cache directory switched, destroyed {count} old cache files'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default {
|
|||||||
retry: '再試行',
|
retry: '再試行',
|
||||||
reset: 'リセット',
|
reset: 'リセット',
|
||||||
loadFailed: '読み込みに失敗しました',
|
loadFailed: '読み込みに失敗しました',
|
||||||
|
noData: 'データがありません',
|
||||||
back: '戻る',
|
back: '戻る',
|
||||||
copySuccess: 'クリップボードにコピーしました',
|
copySuccess: 'クリップボードにコピーしました',
|
||||||
copyFailed: 'コピーに失敗しました',
|
copyFailed: 'コピーに失敗しました',
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ export default {
|
|||||||
'すべてのダウンロード記録をクリアしますか?この操作はダウンロード済みの音楽ファイルを削除しませんが、すべての記録をクリアします。',
|
'すべてのダウンロード記録をクリアしますか?この操作はダウンロード済みの音楽ファイルを削除しませんが、すべての記録をクリアします。',
|
||||||
confirm: 'クリア確認',
|
confirm: 'クリア確認',
|
||||||
cancel: 'キャンセル',
|
cancel: 'キャンセル',
|
||||||
success: 'ダウンロード記録をクリアしました'
|
success: 'ダウンロード記録をクリアしました',
|
||||||
|
failed: 'ダウンロード記録のクリアに失敗しました'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
downloadComplete: '{filename}のダウンロードが完了しました',
|
downloadComplete: '{filename}のダウンロードが完了しました',
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export default {
|
|||||||
subscribe: '購読',
|
subscribe: '購読',
|
||||||
subscribed: '購読中',
|
subscribed: '購読中',
|
||||||
unsubscribe: '購読解除',
|
unsubscribe: '購読解除',
|
||||||
|
unsubscribed: '購読を解除しました',
|
||||||
|
subscribeSuccess: '購読しました',
|
||||||
|
unsubscribeFailed: '購読解除に失敗しました',
|
||||||
|
subscribeFailed: '購読に失敗しました',
|
||||||
radioDetail: 'ラジオ詳細',
|
radioDetail: 'ラジオ詳細',
|
||||||
programList: 'エピソード一覧',
|
programList: 'エピソード一覧',
|
||||||
playProgram: '再生',
|
playProgram: '再生',
|
||||||
|
|||||||
@@ -195,6 +195,35 @@ export default {
|
|||||||
system: {
|
system: {
|
||||||
cache: 'キャッシュ管理',
|
cache: 'キャッシュ管理',
|
||||||
cacheDesc: 'キャッシュをクリア',
|
cacheDesc: 'キャッシュをクリア',
|
||||||
|
diskCache: 'ディスクキャッシュ',
|
||||||
|
diskCacheDesc: '再生した音楽と歌詞をローカルディスクへ保存し、再生速度を向上します',
|
||||||
|
cacheDirectory: 'キャッシュディレクトリ',
|
||||||
|
cacheDirectoryDesc: '音楽・歌詞キャッシュの保存先を指定',
|
||||||
|
selectDirectory: 'ディレクトリ選択',
|
||||||
|
openDirectory: 'ディレクトリを開く',
|
||||||
|
cacheMaxSize: 'キャッシュ上限',
|
||||||
|
cacheMaxSizeDesc: '上限に達すると古いキャッシュを自動削除します',
|
||||||
|
cleanupPolicy: 'クリーンアップポリシー',
|
||||||
|
cleanupPolicyDesc: 'キャッシュ上限到達時の自動削除ルール',
|
||||||
|
cleanupPolicyOptions: {
|
||||||
|
lru: '最近未使用優先',
|
||||||
|
fifo: '先入れ先出し'
|
||||||
|
},
|
||||||
|
cacheStatus: 'キャッシュ状態',
|
||||||
|
cacheStatusDesc: '使用量 {used} / 上限 {limit}',
|
||||||
|
cacheStatusDetail: '音楽 {musicCount} 曲、歌詞 {lyricCount} 曲',
|
||||||
|
manageDiskCache: '手動キャッシュクリア',
|
||||||
|
manageDiskCacheDesc: '種類ごとにキャッシュを削除',
|
||||||
|
clearMusicCache: '音楽キャッシュを削除',
|
||||||
|
clearLyricCache: '歌詞キャッシュを削除',
|
||||||
|
clearAllCache: 'すべて削除',
|
||||||
|
switchDirectoryMigrateTitle: '既存キャッシュを検出',
|
||||||
|
switchDirectoryMigrateContent: '旧ディレクトリのキャッシュを新ディレクトリへ移行しますか?',
|
||||||
|
switchDirectoryMigrateConfirm: '移行する',
|
||||||
|
switchDirectoryDestroyTitle: '旧キャッシュを削除',
|
||||||
|
switchDirectoryDestroyContent: '移行しない場合、旧ディレクトリのキャッシュを削除しますか?',
|
||||||
|
switchDirectoryDestroyConfirm: '削除する',
|
||||||
|
switchDirectoryKeepOld: '旧キャッシュを保持',
|
||||||
cacheClearTitle: 'クリアするキャッシュタイプを選択してください:',
|
cacheClearTitle: 'クリアするキャッシュタイプを選択してください:',
|
||||||
cacheTypes: {
|
cacheTypes: {
|
||||||
history: {
|
history: {
|
||||||
@@ -229,7 +258,15 @@ export default {
|
|||||||
restart: '再起動',
|
restart: '再起動',
|
||||||
restartDesc: 'アプリを再起動',
|
restartDesc: 'アプリを再起動',
|
||||||
messages: {
|
messages: {
|
||||||
clearSuccess: 'クリア成功。一部の設定は再起動後に有効になります'
|
clearSuccess: 'クリア成功。一部の設定は再起動後に有効になります',
|
||||||
|
diskCacheClearSuccess: 'ディスクキャッシュを削除しました',
|
||||||
|
diskCacheClearFailed: 'ディスクキャッシュの削除に失敗しました',
|
||||||
|
diskCacheStatsLoadFailed: 'キャッシュ状態の取得に失敗しました',
|
||||||
|
switchDirectorySuccess: 'キャッシュディレクトリを切り替えました(旧キャッシュは保持)',
|
||||||
|
switchDirectoryFailed: 'キャッシュディレクトリの切り替えに失敗しました',
|
||||||
|
switchDirectoryMigrated: 'キャッシュディレクトリを切り替え、{count} 件を移行しました',
|
||||||
|
switchDirectoryDestroyed:
|
||||||
|
'キャッシュディレクトリを切り替え、旧キャッシュ {count} 件を削除しました'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default {
|
|||||||
retry: '다시 시도',
|
retry: '다시 시도',
|
||||||
reset: '재설정',
|
reset: '재설정',
|
||||||
loadFailed: '로드 실패',
|
loadFailed: '로드 실패',
|
||||||
|
noData: '데이터 없음',
|
||||||
back: '뒤로',
|
back: '뒤로',
|
||||||
copySuccess: '클립보드에 복사됨',
|
copySuccess: '클립보드에 복사됨',
|
||||||
copyFailed: '복사 실패',
|
copyFailed: '복사 실패',
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ export default {
|
|||||||
'모든 다운로드 기록을 지우시겠습니까? 이 작업은 다운로드된 음악 파일을 삭제하지 않지만 모든 기록을 지웁니다.',
|
'모든 다운로드 기록을 지우시겠습니까? 이 작업은 다운로드된 음악 파일을 삭제하지 않지만 모든 기록을 지웁니다.',
|
||||||
confirm: '지우기 확인',
|
confirm: '지우기 확인',
|
||||||
cancel: '취소',
|
cancel: '취소',
|
||||||
success: '다운로드 기록이 지워졌습니다'
|
success: '다운로드 기록이 지워졌습니다',
|
||||||
|
failed: '다운로드 기록 삭제에 실패했습니다'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
downloadComplete: '{filename} 다운로드 완료',
|
downloadComplete: '{filename} 다운로드 완료',
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export default {
|
|||||||
subscribe: '구독',
|
subscribe: '구독',
|
||||||
subscribed: '구독 중',
|
subscribed: '구독 중',
|
||||||
unsubscribe: '구독 취소',
|
unsubscribe: '구독 취소',
|
||||||
|
unsubscribed: '구독이 취소되었습니다',
|
||||||
|
subscribeSuccess: '구독되었습니다',
|
||||||
|
unsubscribeFailed: '구독 취소에 실패했습니다',
|
||||||
|
subscribeFailed: '구독에 실패했습니다',
|
||||||
radioDetail: '라디오 상세',
|
radioDetail: '라디오 상세',
|
||||||
programList: '에피소드 목록',
|
programList: '에피소드 목록',
|
||||||
playProgram: '재생',
|
playProgram: '재생',
|
||||||
|
|||||||
@@ -196,6 +196,36 @@ export default {
|
|||||||
system: {
|
system: {
|
||||||
cache: '캐시 관리',
|
cache: '캐시 관리',
|
||||||
cacheDesc: '캐시 지우기',
|
cacheDesc: '캐시 지우기',
|
||||||
|
diskCache: '디스크 캐시',
|
||||||
|
diskCacheDesc: '재생한 음악과 가사를 로컬 디스크에 캐시하여 재생 속도를 높입니다',
|
||||||
|
cacheDirectory: '캐시 디렉터리',
|
||||||
|
cacheDirectoryDesc: '음악 및 가사 캐시 저장 경로를 사용자 지정',
|
||||||
|
selectDirectory: '디렉터리 선택',
|
||||||
|
openDirectory: '디렉터리 열기',
|
||||||
|
cacheMaxSize: '캐시 용량 제한',
|
||||||
|
cacheMaxSizeDesc: '용량 제한 도달 시 오래된 캐시를 자동 정리합니다',
|
||||||
|
cleanupPolicy: '정리 정책',
|
||||||
|
cleanupPolicyDesc: '캐시 용량 제한 도달 시 적용할 자동 정리 규칙',
|
||||||
|
cleanupPolicyOptions: {
|
||||||
|
lru: '최근 사용 안 함 우선',
|
||||||
|
fifo: '선입선출'
|
||||||
|
},
|
||||||
|
cacheStatus: '캐시 상태',
|
||||||
|
cacheStatusDesc: '사용량 {used} / 제한 {limit}',
|
||||||
|
cacheStatusDetail: '음악 {musicCount}곡, 가사 {lyricCount}곡',
|
||||||
|
manageDiskCache: '수동 디스크 캐시 정리',
|
||||||
|
manageDiskCacheDesc: '캐시 유형별로 정리',
|
||||||
|
clearMusicCache: '음악 캐시 정리',
|
||||||
|
clearLyricCache: '가사 캐시 정리',
|
||||||
|
clearAllCache: '전체 캐시 정리',
|
||||||
|
switchDirectoryMigrateTitle: '기존 캐시가 감지되었습니다',
|
||||||
|
switchDirectoryMigrateContent: '기존 캐시를 새 디렉터리로 마이그레이션할까요?',
|
||||||
|
switchDirectoryMigrateConfirm: '마이그레이션',
|
||||||
|
switchDirectoryDestroyTitle: '기존 캐시 삭제',
|
||||||
|
switchDirectoryDestroyContent:
|
||||||
|
'마이그레이션하지 않을 경우, 이전 디렉터리의 캐시 파일을 삭제할까요?',
|
||||||
|
switchDirectoryDestroyConfirm: '삭제',
|
||||||
|
switchDirectoryKeepOld: '기존 캐시 유지',
|
||||||
cacheClearTitle: '지울 캐시 유형을 선택하세요:',
|
cacheClearTitle: '지울 캐시 유형을 선택하세요:',
|
||||||
cacheTypes: {
|
cacheTypes: {
|
||||||
history: {
|
history: {
|
||||||
@@ -230,7 +260,14 @@ export default {
|
|||||||
restart: '재시작',
|
restart: '재시작',
|
||||||
restartDesc: '앱 재시작',
|
restartDesc: '앱 재시작',
|
||||||
messages: {
|
messages: {
|
||||||
clearSuccess: '지우기 성공, 일부 설정은 재시작 후 적용됩니다'
|
clearSuccess: '지우기 성공, 일부 설정은 재시작 후 적용됩니다',
|
||||||
|
diskCacheClearSuccess: '디스크 캐시를 정리했습니다',
|
||||||
|
diskCacheClearFailed: '디스크 캐시 정리에 실패했습니다',
|
||||||
|
diskCacheStatsLoadFailed: '캐시 상태를 불러오지 못했습니다',
|
||||||
|
switchDirectorySuccess: '캐시 디렉터리가 변경되었습니다. 기존 캐시는 유지됩니다',
|
||||||
|
switchDirectoryFailed: '캐시 디렉터리 변경에 실패했습니다',
|
||||||
|
switchDirectoryMigrated: '캐시 디렉터리를 변경하고 {count}개 파일을 마이그레이션했습니다',
|
||||||
|
switchDirectoryDestroyed: '캐시 디렉터리를 변경하고 기존 캐시 {count}개 파일을 삭제했습니다'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default {
|
|||||||
retry: '重试',
|
retry: '重试',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
loadFailed: '加载失败',
|
loadFailed: '加载失败',
|
||||||
|
noData: '暂无数据',
|
||||||
back: '返回',
|
back: '返回',
|
||||||
copySuccess: '已复制到剪贴板',
|
copySuccess: '已复制到剪贴板',
|
||||||
copyFailed: '复制失败',
|
copyFailed: '复制失败',
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export default {
|
|||||||
message: '确定要清空所有下载记录吗?此操作不会删除已下载的音乐文件,但将清空所有记录。',
|
message: '确定要清空所有下载记录吗?此操作不会删除已下载的音乐文件,但将清空所有记录。',
|
||||||
confirm: '确定清空',
|
confirm: '确定清空',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
success: '下载记录已清空'
|
success: '下载记录已清空',
|
||||||
|
failed: '清空下载记录失败'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
downloadComplete: '{filename} 下载完成',
|
downloadComplete: '{filename} 下载完成',
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export default {
|
|||||||
subscribe: '订阅',
|
subscribe: '订阅',
|
||||||
subscribed: '已订阅',
|
subscribed: '已订阅',
|
||||||
unsubscribe: '取消订阅',
|
unsubscribe: '取消订阅',
|
||||||
|
unsubscribed: '已取消订阅',
|
||||||
|
subscribeSuccess: '订阅成功',
|
||||||
|
unsubscribeFailed: '取消订阅失败',
|
||||||
|
subscribeFailed: '订阅失败',
|
||||||
radioDetail: '电台详情',
|
radioDetail: '电台详情',
|
||||||
programList: '节目列表',
|
programList: '节目列表',
|
||||||
playProgram: '播放节目',
|
playProgram: '播放节目',
|
||||||
|
|||||||
@@ -193,6 +193,35 @@ export default {
|
|||||||
system: {
|
system: {
|
||||||
cache: '缓存管理',
|
cache: '缓存管理',
|
||||||
cacheDesc: '清除缓存',
|
cacheDesc: '清除缓存',
|
||||||
|
diskCache: '磁盘缓存',
|
||||||
|
diskCacheDesc: '将播放过的音乐与歌词缓存到本地磁盘,提升二次播放速度',
|
||||||
|
cacheDirectory: '缓存目录',
|
||||||
|
cacheDirectoryDesc: '自定义音乐与歌词缓存保存目录',
|
||||||
|
selectDirectory: '选择目录',
|
||||||
|
openDirectory: '打开目录',
|
||||||
|
cacheMaxSize: '缓存上限',
|
||||||
|
cacheMaxSizeDesc: '达到上限后将自动清理最旧缓存',
|
||||||
|
cleanupPolicy: '清理策略',
|
||||||
|
cleanupPolicyDesc: '达到缓存上限时的自动清理规则',
|
||||||
|
cleanupPolicyOptions: {
|
||||||
|
lru: '最近最少使用',
|
||||||
|
fifo: '先进先出'
|
||||||
|
},
|
||||||
|
cacheStatus: '缓存状态',
|
||||||
|
cacheStatusDesc: '已用 {used} / 上限 {limit}',
|
||||||
|
cacheStatusDetail: '音乐 {musicCount} 首,歌词 {lyricCount} 首',
|
||||||
|
manageDiskCache: '手动清理磁盘缓存',
|
||||||
|
manageDiskCacheDesc: '按缓存类型进行清理',
|
||||||
|
clearMusicCache: '清理音乐缓存',
|
||||||
|
clearLyricCache: '清理歌词缓存',
|
||||||
|
clearAllCache: '清理全部缓存',
|
||||||
|
switchDirectoryMigrateTitle: '检测到已有缓存',
|
||||||
|
switchDirectoryMigrateContent: '是否将旧目录缓存迁移到新目录?',
|
||||||
|
switchDirectoryMigrateConfirm: '迁移',
|
||||||
|
switchDirectoryDestroyTitle: '是否销毁旧缓存',
|
||||||
|
switchDirectoryDestroyContent: '不迁移时,是否销毁旧目录缓存文件?',
|
||||||
|
switchDirectoryDestroyConfirm: '销毁',
|
||||||
|
switchDirectoryKeepOld: '保留旧缓存',
|
||||||
cacheClearTitle: '请选择要清除的缓存类型:',
|
cacheClearTitle: '请选择要清除的缓存类型:',
|
||||||
cacheTypes: {
|
cacheTypes: {
|
||||||
history: {
|
history: {
|
||||||
@@ -227,7 +256,14 @@ export default {
|
|||||||
restart: '重启',
|
restart: '重启',
|
||||||
restartDesc: '重启应用',
|
restartDesc: '重启应用',
|
||||||
messages: {
|
messages: {
|
||||||
clearSuccess: '清除成功,部分设置在重启后生效'
|
clearSuccess: '清除成功,部分设置在重启后生效',
|
||||||
|
diskCacheClearSuccess: '磁盘缓存已清理',
|
||||||
|
diskCacheClearFailed: '清理磁盘缓存失败',
|
||||||
|
diskCacheStatsLoadFailed: '读取缓存状态失败',
|
||||||
|
switchDirectorySuccess: '缓存目录已切换,旧缓存已保留',
|
||||||
|
switchDirectoryFailed: '缓存目录切换失败',
|
||||||
|
switchDirectoryMigrated: '缓存目录已切换,已迁移 {count} 个缓存文件',
|
||||||
|
switchDirectoryDestroyed: '缓存目录已切换,已销毁 {count} 个旧缓存文件'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default {
|
|||||||
retry: '重試',
|
retry: '重試',
|
||||||
reset: '重設',
|
reset: '重設',
|
||||||
loadFailed: '載入失敗',
|
loadFailed: '載入失敗',
|
||||||
|
noData: '暫無資料',
|
||||||
back: '返回',
|
back: '返回',
|
||||||
copySuccess: '已複製到剪貼簿',
|
copySuccess: '已複製到剪貼簿',
|
||||||
copyFailed: '複製失敗',
|
copyFailed: '複製失敗',
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export default {
|
|||||||
message: '確定要清空所有下載記錄嗎?此操作不會刪除已下載的音樂檔案,但將清空所有記錄。',
|
message: '確定要清空所有下載記錄嗎?此操作不會刪除已下載的音樂檔案,但將清空所有記錄。',
|
||||||
confirm: '確定清空',
|
confirm: '確定清空',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
success: '下載記錄已清空'
|
success: '下載記錄已清空',
|
||||||
|
failed: '清空下載記錄失敗'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
downloadComplete: '{filename} 下載完成',
|
downloadComplete: '{filename} 下載完成',
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export default {
|
|||||||
subscribe: '訂閱',
|
subscribe: '訂閱',
|
||||||
subscribed: '已訂閱',
|
subscribed: '已訂閱',
|
||||||
unsubscribe: '取消訂閱',
|
unsubscribe: '取消訂閱',
|
||||||
|
unsubscribed: '已取消訂閱',
|
||||||
|
subscribeSuccess: '訂閱成功',
|
||||||
|
unsubscribeFailed: '取消訂閱失敗',
|
||||||
|
subscribeFailed: '訂閱失敗',
|
||||||
radioDetail: '電台詳情',
|
radioDetail: '電台詳情',
|
||||||
programList: '節目列表',
|
programList: '節目列表',
|
||||||
playProgram: '播放節目',
|
playProgram: '播放節目',
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export default {
|
|||||||
tokenSet: '已設定',
|
tokenSet: '已設定',
|
||||||
tokenNotSet: '未設定',
|
tokenNotSet: '未設定',
|
||||||
setToken: '設定Cookie',
|
setToken: '設定Cookie',
|
||||||
setCookie: '設定Cookie',
|
|
||||||
modifyToken: '修改Cookie',
|
modifyToken: '修改Cookie',
|
||||||
clearToken: '清除Cookie',
|
clearToken: '清除Cookie',
|
||||||
font: '字體設定',
|
font: '字體設定',
|
||||||
@@ -190,6 +189,35 @@ export default {
|
|||||||
system: {
|
system: {
|
||||||
cache: '快取管理',
|
cache: '快取管理',
|
||||||
cacheDesc: '清除快取',
|
cacheDesc: '清除快取',
|
||||||
|
diskCache: '磁碟快取',
|
||||||
|
diskCacheDesc: '將播放過的音樂與歌詞快取到本機磁碟,加速二次播放',
|
||||||
|
cacheDirectory: '快取目錄',
|
||||||
|
cacheDirectoryDesc: '自訂音樂與歌詞快取儲存位置',
|
||||||
|
selectDirectory: '選擇目錄',
|
||||||
|
openDirectory: '開啟目錄',
|
||||||
|
cacheMaxSize: '快取上限',
|
||||||
|
cacheMaxSizeDesc: '達到上限時會自動清理較舊快取',
|
||||||
|
cleanupPolicy: '清理策略',
|
||||||
|
cleanupPolicyDesc: '快取達到上限時的自動清理規則',
|
||||||
|
cleanupPolicyOptions: {
|
||||||
|
lru: '最近最少使用',
|
||||||
|
fifo: '先進先出'
|
||||||
|
},
|
||||||
|
cacheStatus: '快取狀態',
|
||||||
|
cacheStatusDesc: '已用 {used} / 上限 {limit}',
|
||||||
|
cacheStatusDetail: '音樂 {musicCount} 首,歌詞 {lyricCount} 首',
|
||||||
|
manageDiskCache: '手動清理磁碟快取',
|
||||||
|
manageDiskCacheDesc: '依快取類型進行清理',
|
||||||
|
clearMusicCache: '清理音樂快取',
|
||||||
|
clearLyricCache: '清理歌詞快取',
|
||||||
|
clearAllCache: '清理全部快取',
|
||||||
|
switchDirectoryMigrateTitle: '偵測到既有快取',
|
||||||
|
switchDirectoryMigrateContent: '是否將舊目錄快取搬移到新目錄?',
|
||||||
|
switchDirectoryMigrateConfirm: '搬移',
|
||||||
|
switchDirectoryDestroyTitle: '是否刪除舊快取',
|
||||||
|
switchDirectoryDestroyContent: '不搬移時,是否刪除舊目錄的快取檔案?',
|
||||||
|
switchDirectoryDestroyConfirm: '刪除',
|
||||||
|
switchDirectoryKeepOld: '保留舊快取',
|
||||||
cacheClearTitle: '請選擇要清除的快取類型:',
|
cacheClearTitle: '請選擇要清除的快取類型:',
|
||||||
cacheTypes: {
|
cacheTypes: {
|
||||||
history: {
|
history: {
|
||||||
@@ -224,7 +252,14 @@ export default {
|
|||||||
restart: '重新啟動',
|
restart: '重新啟動',
|
||||||
restartDesc: '重新啟動應用程式',
|
restartDesc: '重新啟動應用程式',
|
||||||
messages: {
|
messages: {
|
||||||
clearSuccess: '清除成功,部分設定在重啟後生效'
|
clearSuccess: '清除成功,部分設定在重啟後生效',
|
||||||
|
diskCacheClearSuccess: '磁碟快取已清理',
|
||||||
|
diskCacheClearFailed: '清理磁碟快取失敗',
|
||||||
|
diskCacheStatsLoadFailed: '讀取快取狀態失敗',
|
||||||
|
switchDirectorySuccess: '快取目錄已切換,舊快取已保留',
|
||||||
|
switchDirectoryFailed: '快取目錄切換失敗',
|
||||||
|
switchDirectoryMigrated: '快取目錄已切換,已搬移 {count} 個快取檔案',
|
||||||
|
switchDirectoryDestroyed: '快取目錄已切換,已刪除 {count} 個舊快取檔案'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
|
|||||||
+1044
-42
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
import { app, ipcMain } from 'electron';
|
import { app, ipcMain } from 'electron';
|
||||||
import Store from 'electron-store';
|
import Store from 'electron-store';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
import { createDefaultShortcuts, type ShortcutsConfig } from '../../shared/shortcuts';
|
import { createDefaultShortcuts, type ShortcutsConfig } from '../../shared/shortcuts';
|
||||||
import set from '../set.json';
|
import set from '../set.json';
|
||||||
@@ -26,6 +27,11 @@ type SetConfig = {
|
|||||||
language: string;
|
language: string;
|
||||||
showTopAction: boolean;
|
showTopAction: boolean;
|
||||||
enableGpuAcceleration: boolean;
|
enableGpuAcceleration: boolean;
|
||||||
|
downloadPath: string;
|
||||||
|
enableDiskCache: boolean;
|
||||||
|
diskCacheDir: string;
|
||||||
|
diskCacheMaxSizeMB: number;
|
||||||
|
diskCacheCleanupPolicy: 'lru' | 'fifo';
|
||||||
};
|
};
|
||||||
interface StoreType {
|
interface StoreType {
|
||||||
set: SetConfig;
|
set: SetConfig;
|
||||||
@@ -47,6 +53,17 @@ export function initializeConfig() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.get('set.downloadPath') || store.set('set.downloadPath', app.getPath('downloads'));
|
store.get('set.downloadPath') || store.set('set.downloadPath', app.getPath('downloads'));
|
||||||
|
store.get('set.diskCacheDir') ||
|
||||||
|
store.set('set.diskCacheDir', path.join(app.getPath('userData'), 'cache'));
|
||||||
|
if (store.get('set.diskCacheMaxSizeMB') === undefined) {
|
||||||
|
store.set('set.diskCacheMaxSizeMB', 4096);
|
||||||
|
}
|
||||||
|
if (!store.get('set.diskCacheCleanupPolicy')) {
|
||||||
|
store.set('set.diskCacheCleanupPolicy', 'lru');
|
||||||
|
}
|
||||||
|
if (store.get('set.enableDiskCache') === undefined) {
|
||||||
|
store.set('set.enableDiskCache', true);
|
||||||
|
}
|
||||||
|
|
||||||
// 定义ipcRenderer监听事件
|
// 定义ipcRenderer监听事件
|
||||||
ipcMain.on('set-store-value', (_, key, value) => {
|
ipcMain.on('set-store-value', (_, key, value) => {
|
||||||
|
|||||||
+5
-1
@@ -34,5 +34,9 @@
|
|||||||
"customApiPluginName": "",
|
"customApiPluginName": "",
|
||||||
"lxMusicScripts": [],
|
"lxMusicScripts": [],
|
||||||
"activeLxMusicApiId": null,
|
"activeLxMusicApiId": null,
|
||||||
"enableGpuAcceleration": true
|
"enableGpuAcceleration": true,
|
||||||
|
"enableDiskCache": true,
|
||||||
|
"diskCacheDir": "",
|
||||||
|
"diskCacheMaxSizeMB": 4096,
|
||||||
|
"diskCacheCleanupPolicy": "lru"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const getDescription = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (props.item.size !== undefined) {
|
if (props.item.size !== undefined) {
|
||||||
parts.push(t('user.album.songCount', { count: props.item.size }));
|
parts.push(t('common.songCount', { count: props.item.size }));
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join(' · ') || t('history.noDescription');
|
return parts.join(' · ') || t('history.noDescription');
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<span
|
<span
|
||||||
class="px-3 py-1 text-xs font-medium text-white bg-gradient-to-r from-green-500 to-emerald-600 rounded-full"
|
class="px-3 py-1 text-xs font-medium text-white bg-gradient-to-r from-green-500 to-emerald-600 rounded-full"
|
||||||
>
|
>
|
||||||
{{ t('comp.update.newVersion') }}
|
{{ t('comp.update.title') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white truncate">
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white truncate">
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
@click="handleLater"
|
@click="handleLater"
|
||||||
class="flex-1 py-4 px-4 rounded-2xl text-base font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 active:scale-[0.98] transition-all duration-200"
|
class="flex-1 py-4 px-4 rounded-2xl text-base font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 active:scale-[0.98] transition-all duration-200"
|
||||||
>
|
>
|
||||||
{{ t('comp.update.later') }}
|
{{ t('comp.update.noThanks') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="handleUpdate"
|
@click="handleUpdate"
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
>
|
>
|
||||||
<span class="flex items-center justify-center gap-2">
|
<span class="flex items-center justify-center gap-2">
|
||||||
<i class="ri-download-2-line text-lg"></i>
|
<i class="ri-download-2-line text-lg"></i>
|
||||||
{{ t('comp.update.updateNow') }}
|
{{ t('comp.update.nowUpdate') }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -213,9 +213,9 @@ let timerInterval: number | null = null;
|
|||||||
const hasTimerActive = computed(() => playerStore.hasSleepTimerActive);
|
const hasTimerActive = computed(() => playerStore.hasSleepTimerActive);
|
||||||
|
|
||||||
const timerStatusText = computed(() => {
|
const timerStatusText = computed(() => {
|
||||||
if (sleepTimer.value.type === 'time') return t('player.sleepTimer.activeTime');
|
if (sleepTimer.value.type === 'time') return t('player.sleepTimer.timeMode');
|
||||||
if (sleepTimer.value.type === 'songs') return t('player.sleepTimer.activeSongs');
|
if (sleepTimer.value.type === 'songs') return t('player.sleepTimer.songsMode');
|
||||||
if (sleepTimer.value.type === 'end') return t('player.sleepTimer.activeEnd');
|
if (sleepTimer.value.type === 'end') return t('player.sleepTimer.afterPlaylist');
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ const toggleSource = (sourceKey: string) => {
|
|||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
// 至少保留一个音源
|
// 至少保留一个音源
|
||||||
if (selectedSources.value.length <= 1) {
|
if (selectedSources.value.length <= 1) {
|
||||||
message.warning(t('settings.playback.musicSourcesMinWarning'));
|
message.warning(t('settings.playback.musicSourcesWarning'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectedSources.value.splice(index, 1);
|
selectedSources.value.splice(index, 1);
|
||||||
|
|||||||
@@ -6,12 +6,57 @@ import { getMusicLrc, getMusicUrl, getParsingMusicUrl } from '@/api/music';
|
|||||||
import { playbackRequestManager } from '@/services/playbackRequestManager';
|
import { playbackRequestManager } from '@/services/playbackRequestManager';
|
||||||
import { SongSourceConfigManager } from '@/services/SongSourceConfigManager';
|
import { SongSourceConfigManager } from '@/services/SongSourceConfigManager';
|
||||||
import type { ILyric, ILyricText, IWordData, SongResult } from '@/types/music';
|
import type { ILyric, ILyricText, IWordData, SongResult } from '@/types/music';
|
||||||
import { getImgUrl } from '@/utils';
|
import { getImgUrl, isElectron } from '@/utils';
|
||||||
import { getImageLinearBackground } from '@/utils/linearColor';
|
import { getImageLinearBackground } from '@/utils/linearColor';
|
||||||
import { parseLyrics as parseYrcLyrics } from '@/utils/yrcParser';
|
import { parseLyrics as parseYrcLyrics } from '@/utils/yrcParser';
|
||||||
|
|
||||||
const { message } = createDiscreteApi(['message']);
|
const { message } = createDiscreteApi(['message']);
|
||||||
|
|
||||||
|
type DiskCacheResolveResult = {
|
||||||
|
url?: string;
|
||||||
|
cached?: boolean;
|
||||||
|
queued?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSongArtistText = (songData: SongResult): string => {
|
||||||
|
if (songData?.ar?.length) {
|
||||||
|
return songData.ar.map((artist) => artist.name).join(' / ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (songData?.song?.artists?.length) {
|
||||||
|
return songData.song.artists.map((artist) => artist.name).join(' / ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveCachedPlaybackUrl = async (
|
||||||
|
url: string | null | undefined,
|
||||||
|
songData: SongResult
|
||||||
|
): Promise<string | null | undefined> => {
|
||||||
|
if (!url || !isElectron || !/^https?:\/\//i.test(url)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = (await window.electron.ipcRenderer.invoke('resolve-cached-music-url', {
|
||||||
|
songId: Number(songData.id),
|
||||||
|
source: songData.source,
|
||||||
|
url,
|
||||||
|
title: songData.name,
|
||||||
|
artist: getSongArtistText(songData)
|
||||||
|
})) as DiskCacheResolveResult;
|
||||||
|
|
||||||
|
if (result?.url) {
|
||||||
|
return result.url;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('解析缓存播放地址失败,回退到在线地址:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取歌曲播放URL(独立函数)
|
* 获取歌曲播放URL(独立函数)
|
||||||
*/
|
*/
|
||||||
@@ -35,7 +80,8 @@ export const getSongUrl = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (songData.playMusicUrl) {
|
if (songData.playMusicUrl) {
|
||||||
return songData.playMusicUrl;
|
if (isDownloaded) return songData.playMusicUrl;
|
||||||
|
return await resolveCachedPlaybackUrl(songData.playMusicUrl, songData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 自定义API最优先 ====================
|
// ==================== 自定义API最优先 ====================
|
||||||
@@ -70,7 +116,7 @@ export const getSongUrl = async (
|
|||||||
) {
|
) {
|
||||||
console.log('自定义API解析成功!');
|
console.log('自定义API解析成功!');
|
||||||
if (isDownloaded) return customResult.data.data as any;
|
if (isDownloaded) return customResult.data.data as any;
|
||||||
return customResult.data.data.url;
|
return await resolveCachedPlaybackUrl(customResult.data.data.url, songData);
|
||||||
} else {
|
} else {
|
||||||
console.log('自定义API解析失败,将使用默认降级流程...');
|
console.log('自定义API解析失败,将使用默认降级流程...');
|
||||||
message.warning(i18n.global.t('player.reparse.customApiFailed'));
|
message.warning(i18n.global.t('player.reparse.customApiFailed'));
|
||||||
@@ -98,7 +144,7 @@ export const getSongUrl = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res && res.data && res.data.data && res.data.data.url) {
|
if (res && res.data && res.data.data && res.data.data.url) {
|
||||||
return res.data.data.url;
|
return await resolveCachedPlaybackUrl(res.data.data.url, songData);
|
||||||
}
|
}
|
||||||
console.warn('自定义音源解析失败,使用默认音源');
|
console.warn('自定义音源解析失败,使用默认音源');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -133,12 +179,13 @@ export const getSongUrl = async (
|
|||||||
throw new Error('Request cancelled');
|
throw new Error('Request cancelled');
|
||||||
}
|
}
|
||||||
if (isDownloaded) return res?.data?.data as any;
|
if (isDownloaded) return res?.data?.data as any;
|
||||||
return res?.data?.data?.url || null;
|
const parsedUrl = res?.data?.data?.url || null;
|
||||||
|
return await resolveCachedPlaybackUrl(parsedUrl, songData);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('官方API解析成功!');
|
console.log('官方API解析成功!');
|
||||||
if (isDownloaded) return songDetail as any;
|
if (isDownloaded) return songDetail as any;
|
||||||
return songDetail.url;
|
return await resolveCachedPlaybackUrl(songDetail.url, songData);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('官方API返回数据结构异常,进入内置备用解析...');
|
console.log('官方API返回数据结构异常,进入内置备用解析...');
|
||||||
@@ -149,7 +196,8 @@ export const getSongUrl = async (
|
|||||||
throw new Error('Request cancelled');
|
throw new Error('Request cancelled');
|
||||||
}
|
}
|
||||||
if (isDownloaded) return res?.data?.data as any;
|
if (isDownloaded) return res?.data?.data as any;
|
||||||
return res?.data?.data?.url || null;
|
const parsedUrl = res?.data?.data?.url || null;
|
||||||
|
return await resolveCachedPlaybackUrl(parsedUrl, songData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as Error).message === 'Request cancelled') {
|
if ((error as Error).message === 'Request cancelled') {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -157,7 +205,8 @@ export const getSongUrl = async (
|
|||||||
console.error('官方API请求失败,进入内置备用解析流程:', error);
|
console.error('官方API请求失败,进入内置备用解析流程:', error);
|
||||||
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
|
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
|
||||||
if (isDownloaded) return res?.data?.data as any;
|
if (isDownloaded) return res?.data?.data as any;
|
||||||
return res?.data?.data?.url || null;
|
const parsedUrl = res?.data?.data?.url || null;
|
||||||
|
return await resolveCachedPlaybackUrl(parsedUrl, songData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -218,7 +267,28 @@ const parseLyrics = (lyricsString: string): { lyrics: ILyricText[]; times: numbe
|
|||||||
export const loadLrc = async (id: string | number): Promise<ILyric> => {
|
export const loadLrc = async (id: string | number): Promise<ILyric> => {
|
||||||
try {
|
try {
|
||||||
const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
|
const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
|
||||||
|
let lyricData: any;
|
||||||
|
|
||||||
|
if (isElectron) {
|
||||||
|
try {
|
||||||
|
lyricData = await window.electron.ipcRenderer.invoke('get-cached-lyric', numericId);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('读取磁盘歌词缓存失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lyricData) {
|
||||||
const { data } = await getMusicLrc(numericId);
|
const { data } = await getMusicLrc(numericId);
|
||||||
|
lyricData = data;
|
||||||
|
|
||||||
|
if (isElectron && lyricData) {
|
||||||
|
void window.electron.ipcRenderer
|
||||||
|
.invoke('cache-lyric', numericId, lyricData)
|
||||||
|
.catch((error) => console.warn('写入磁盘歌词缓存失败:', error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = lyricData ?? {};
|
||||||
const { lyrics, times } = parseLyrics(data?.yrc?.lyric || data?.lrc?.lyric);
|
const { lyrics, times } = parseLyrics(data?.yrc?.lyric || data?.lrc?.lyric);
|
||||||
|
|
||||||
// 检查是否有逐字歌词
|
// 检查是否有逐字歌词
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
@click="playAll"
|
@click="playAll"
|
||||||
>
|
>
|
||||||
<i class="iconfont icon-playfill text-sm" />
|
<i class="iconfont icon-playfill text-sm" />
|
||||||
<span>{{ t('musicList.playAll') }}</span>
|
<span>{{ t('comp.musicList.playAll') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
<!-- 无结果 -->
|
<!-- 无结果 -->
|
||||||
<div v-else-if="!loading" class="empty-state">
|
<div v-else-if="!loading" class="empty-state">
|
||||||
<i class="ri-search-line"></i>
|
<i class="ri-search-line"></i>
|
||||||
<span>{{ t('search.noResult') }}</span>
|
<span>{{ t('comp.musicList.noSearchResults') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ const categoryList = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const currentCategoryName = computed(() => {
|
const currentCategoryName = computed(() => {
|
||||||
if (currentCategoryId.value === -1) return t('podcast.recommend');
|
if (currentCategoryId.value === -1) return t('podcast.recommended');
|
||||||
return categories.value.find((c) => c.id === currentCategoryId.value)?.name || '';
|
return categories.value.find((c) => c.id === currentCategoryId.value)?.name || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -207,7 +207,7 @@
|
|||||||
class="flex flex-col items-center justify-center py-20 text-neutral-400"
|
class="flex flex-col items-center justify-center py-20 text-neutral-400"
|
||||||
>
|
>
|
||||||
<i class="ri-search-line text-6xl mb-4 opacity-20"></i>
|
<i class="ri-search-line text-6xl mb-4 opacity-20"></i>
|
||||||
<p>{{ t('search.noResults') }}</p>
|
<p>{{ t('comp.musicList.noSearchResults') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading More / Footer -->
|
<!-- Loading More / Footer -->
|
||||||
|
|||||||
@@ -473,6 +473,119 @@
|
|||||||
<!-- 系统管理 -->
|
<!-- 系统管理 -->
|
||||||
<div v-show="currentSection === 'system'" class="animate-fade-in">
|
<div v-show="currentSection === 'system'" class="animate-fade-in">
|
||||||
<setting-section v-if="isElectron" :title="t('settings.sections.system')">
|
<setting-section v-if="isElectron" :title="t('settings.sections.system')">
|
||||||
|
<!-- 磁盘缓存开关 -->
|
||||||
|
<setting-item
|
||||||
|
:title="t('settings.system.diskCache')"
|
||||||
|
:description="t('settings.system.diskCacheDesc')"
|
||||||
|
>
|
||||||
|
<n-switch v-model:value="setData.enableDiskCache">
|
||||||
|
<template #checked>{{ t('common.on') }}</template>
|
||||||
|
<template #unchecked>{{ t('common.off') }}</template>
|
||||||
|
</n-switch>
|
||||||
|
</setting-item>
|
||||||
|
|
||||||
|
<!-- 缓存目录 -->
|
||||||
|
<setting-item
|
||||||
|
:title="t('settings.system.cacheDirectory')"
|
||||||
|
:description="
|
||||||
|
setData.diskCacheDir ||
|
||||||
|
diskCacheStats.directory ||
|
||||||
|
t('settings.system.cacheDirectoryDesc')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #action>
|
||||||
|
<div class="flex items-center gap-2 max-md:flex-wrap">
|
||||||
|
<n-button size="small" @click="selectCacheDirectory">
|
||||||
|
{{ t('settings.system.selectDirectory') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="openCacheDirectory">
|
||||||
|
{{ t('settings.system.openDirectory') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</setting-item>
|
||||||
|
|
||||||
|
<!-- 缓存上限 -->
|
||||||
|
<setting-item
|
||||||
|
:title="t('settings.system.cacheMaxSize')"
|
||||||
|
:description="t('settings.system.cacheMaxSizeDesc')"
|
||||||
|
>
|
||||||
|
<template #action>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="setData.diskCacheMaxSizeMB"
|
||||||
|
:min="256"
|
||||||
|
:max="102400"
|
||||||
|
:step="256"
|
||||||
|
class="max-md:w-32"
|
||||||
|
/>
|
||||||
|
<span class="text-xs text-neutral-500">MB</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</setting-item>
|
||||||
|
|
||||||
|
<!-- 清理策略 -->
|
||||||
|
<setting-item
|
||||||
|
:title="t('settings.system.cleanupPolicy')"
|
||||||
|
:description="t('settings.system.cleanupPolicyDesc')"
|
||||||
|
>
|
||||||
|
<n-select
|
||||||
|
v-model:value="setData.diskCacheCleanupPolicy"
|
||||||
|
:options="cleanupPolicyOptions"
|
||||||
|
class="w-40"
|
||||||
|
/>
|
||||||
|
</setting-item>
|
||||||
|
|
||||||
|
<!-- 缓存状态 -->
|
||||||
|
<setting-item
|
||||||
|
:title="t('settings.system.cacheStatus')"
|
||||||
|
:description="
|
||||||
|
t('settings.system.cacheStatusDesc', {
|
||||||
|
used: formatBytes(diskCacheStats.totalSizeBytes),
|
||||||
|
limit: `${setData.diskCacheMaxSizeMB || diskCacheStats.maxSizeMB || 0} MB`
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #action>
|
||||||
|
<div class="flex items-center gap-3 max-md:flex-wrap">
|
||||||
|
<div class="w-40 max-md:w-32">
|
||||||
|
<n-progress type="line" :percentage="diskCacheUsagePercent" />
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-neutral-500">
|
||||||
|
{{
|
||||||
|
t('settings.system.cacheStatusDetail', {
|
||||||
|
musicCount: diskCacheStats.musicFiles,
|
||||||
|
lyricCount: diskCacheStats.lyricFiles
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<n-button size="small" @click="refreshDiskCacheStats()">{{
|
||||||
|
t('common.refresh')
|
||||||
|
}}</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</setting-item>
|
||||||
|
|
||||||
|
<!-- 手动清理磁盘缓存 -->
|
||||||
|
<setting-item
|
||||||
|
:title="t('settings.system.manageDiskCache')"
|
||||||
|
:description="t('settings.system.manageDiskCacheDesc')"
|
||||||
|
>
|
||||||
|
<template #action>
|
||||||
|
<div class="flex items-center gap-2 max-md:flex-wrap">
|
||||||
|
<n-button size="small" @click="clearDiskCacheByScope('music')">
|
||||||
|
{{ t('settings.system.clearMusicCache') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" @click="clearDiskCacheByScope('lyrics')">
|
||||||
|
{{ t('settings.system.clearLyricCache') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button type="error" size="small" @click="clearDiskCacheByScope('all')">
|
||||||
|
{{ t('settings.system.clearAllCache') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</setting-item>
|
||||||
|
|
||||||
<!-- 清除缓存 -->
|
<!-- 清除缓存 -->
|
||||||
<setting-item
|
<setting-item
|
||||||
:title="t('settings.system.cache')"
|
:title="t('settings.system.cache')"
|
||||||
@@ -567,7 +680,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useDialog, useMessage } from 'naive-ui';
|
||||||
import { computed, h, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, h, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
@@ -611,11 +724,40 @@ const fontPreviews = [
|
|||||||
{ key: 'korean' }
|
{ key: 'korean' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
type DiskCacheScope = 'all' | 'music' | 'lyrics';
|
||||||
|
type DiskCacheCleanupPolicy = 'lru' | 'fifo';
|
||||||
|
type CacheSwitchAction = 'migrate' | 'destroy' | 'keep';
|
||||||
|
|
||||||
|
type DiskCacheConfig = {
|
||||||
|
enabled: boolean;
|
||||||
|
directory: string;
|
||||||
|
maxSizeMB: number;
|
||||||
|
cleanupPolicy: DiskCacheCleanupPolicy;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DiskCacheStats = DiskCacheConfig & {
|
||||||
|
totalSizeBytes: number;
|
||||||
|
musicSizeBytes: number;
|
||||||
|
lyricSizeBytes: number;
|
||||||
|
totalFiles: number;
|
||||||
|
musicFiles: number;
|
||||||
|
lyricFiles: number;
|
||||||
|
usage: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SwitchCacheDirectoryResult = {
|
||||||
|
success: boolean;
|
||||||
|
config: DiskCacheConfig;
|
||||||
|
migratedFiles: number;
|
||||||
|
destroyedFiles: number;
|
||||||
|
};
|
||||||
|
|
||||||
// ==================== 平台和Store ====================
|
// ==================== 平台和Store ====================
|
||||||
const platform = window.electron ? window.electron.ipcRenderer.sendSync('get-platform') : 'web';
|
const platform = window.electron ? window.electron.ipcRenderer.sendSync('get-platform') : 'web';
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
const dialog = useDialog();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -764,6 +906,268 @@ const openDownloadPath = () => {
|
|||||||
openDirectory(setData.value.downloadPath, message);
|
openDirectory(setData.value.downloadPath, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==================== 磁盘缓存设置 ====================
|
||||||
|
const diskCacheStats = ref<DiskCacheStats>({
|
||||||
|
enabled: true,
|
||||||
|
directory: '',
|
||||||
|
maxSizeMB: 4096,
|
||||||
|
cleanupPolicy: 'lru',
|
||||||
|
totalSizeBytes: 0,
|
||||||
|
musicSizeBytes: 0,
|
||||||
|
lyricSizeBytes: 0,
|
||||||
|
totalFiles: 0,
|
||||||
|
musicFiles: 0,
|
||||||
|
lyricFiles: 0,
|
||||||
|
usage: 0
|
||||||
|
});
|
||||||
|
const applyingDiskCacheConfig = ref(false);
|
||||||
|
const switchingCacheDirectory = ref(false);
|
||||||
|
|
||||||
|
const cleanupPolicyOptions = computed(() => [
|
||||||
|
{ label: t('settings.system.cleanupPolicyOptions.lru'), value: 'lru' },
|
||||||
|
{ label: t('settings.system.cleanupPolicyOptions.fifo'), value: 'fifo' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const diskCacheUsagePercent = computed(() =>
|
||||||
|
Math.min(100, Math.max(0, Math.round((diskCacheStats.value.usage || 0) * 100)))
|
||||||
|
);
|
||||||
|
|
||||||
|
const formatBytes = (bytes: number) => {
|
||||||
|
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
let value = bytes;
|
||||||
|
let unitIndex = 0;
|
||||||
|
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
value /= 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unitIndex]}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readDiskCacheConfigFromUI = (): DiskCacheConfig => {
|
||||||
|
const cleanupPolicy: DiskCacheCleanupPolicy =
|
||||||
|
setData.value.diskCacheCleanupPolicy === 'fifo' ? 'fifo' : 'lru';
|
||||||
|
const maxSizeMB = Math.max(256, Math.floor(Number(setData.value.diskCacheMaxSizeMB || 4096)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: setData.value.enableDiskCache !== false,
|
||||||
|
directory: String(setData.value.diskCacheDir || ''),
|
||||||
|
maxSizeMB,
|
||||||
|
cleanupPolicy
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshDiskCacheStats = async (silent: boolean = true) => {
|
||||||
|
if (!window.electron) return;
|
||||||
|
try {
|
||||||
|
const stats = (await window.electron.ipcRenderer.invoke(
|
||||||
|
'get-disk-cache-stats'
|
||||||
|
)) as DiskCacheStats;
|
||||||
|
if (stats) {
|
||||||
|
diskCacheStats.value = stats;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取磁盘缓存统计失败:', error);
|
||||||
|
if (!silent) {
|
||||||
|
message.error(t('settings.system.messages.diskCacheStatsLoadFailed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDiskCacheConfig = async () => {
|
||||||
|
if (!window.electron) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const config = (await window.electron.ipcRenderer.invoke(
|
||||||
|
'get-disk-cache-config'
|
||||||
|
)) as DiskCacheConfig;
|
||||||
|
if (config) {
|
||||||
|
setData.value = {
|
||||||
|
...setData.value,
|
||||||
|
enableDiskCache: config.enabled,
|
||||||
|
diskCacheDir: config.directory,
|
||||||
|
diskCacheMaxSizeMB: config.maxSizeMB,
|
||||||
|
diskCacheCleanupPolicy: config.cleanupPolicy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取磁盘缓存配置失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyDiskCacheConfig = async () => {
|
||||||
|
if (!window.electron || applyingDiskCacheConfig.value) return;
|
||||||
|
|
||||||
|
applyingDiskCacheConfig.value = true;
|
||||||
|
try {
|
||||||
|
const config = readDiskCacheConfigFromUI();
|
||||||
|
const updated = (await window.electron.ipcRenderer.invoke(
|
||||||
|
'set-disk-cache-config',
|
||||||
|
config
|
||||||
|
)) as DiskCacheConfig;
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
setData.value = {
|
||||||
|
...setData.value,
|
||||||
|
enableDiskCache: updated.enabled,
|
||||||
|
diskCacheDir: updated.directory,
|
||||||
|
diskCacheMaxSizeMB: updated.maxSizeMB,
|
||||||
|
diskCacheCleanupPolicy: updated.cleanupPolicy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
await refreshDiskCacheStats();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新磁盘缓存配置失败:', error);
|
||||||
|
} finally {
|
||||||
|
applyingDiskCacheConfig.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyDiskCacheConfigDebounced = useDebounceFn(() => {
|
||||||
|
void applyDiskCacheConfig();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
setData.value.enableDiskCache,
|
||||||
|
setData.value.diskCacheDir,
|
||||||
|
setData.value.diskCacheMaxSizeMB,
|
||||||
|
setData.value.diskCacheCleanupPolicy
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
if (!window.electron || applyingDiskCacheConfig.value || switchingCacheDirectory.value) return;
|
||||||
|
applyDiskCacheConfigDebounced();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const askCacheSwitchMigrate = (): Promise<boolean> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let resolved = false;
|
||||||
|
const finish = (value: boolean) => {
|
||||||
|
if (resolved) return;
|
||||||
|
resolved = true;
|
||||||
|
resolve(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.warning({
|
||||||
|
title: t('settings.system.switchDirectoryMigrateTitle'),
|
||||||
|
content: t('settings.system.switchDirectoryMigrateContent'),
|
||||||
|
positiveText: t('settings.system.switchDirectoryMigrateConfirm'),
|
||||||
|
negativeText: t('settings.system.switchDirectoryKeepOld'),
|
||||||
|
onPositiveClick: () => finish(true),
|
||||||
|
onNegativeClick: () => finish(false),
|
||||||
|
onClose: () => finish(false)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const askCacheSwitchDestroy = (): Promise<boolean> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let resolved = false;
|
||||||
|
const finish = (value: boolean) => {
|
||||||
|
if (resolved) return;
|
||||||
|
resolved = true;
|
||||||
|
resolve(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.warning({
|
||||||
|
title: t('settings.system.switchDirectoryDestroyTitle'),
|
||||||
|
content: t('settings.system.switchDirectoryDestroyContent'),
|
||||||
|
positiveText: t('settings.system.switchDirectoryDestroyConfirm'),
|
||||||
|
negativeText: t('settings.system.switchDirectoryKeepOld'),
|
||||||
|
onPositiveClick: () => finish(true),
|
||||||
|
onNegativeClick: () => finish(false),
|
||||||
|
onClose: () => finish(false)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectCacheDirectory = async () => {
|
||||||
|
if (!window.electron) return;
|
||||||
|
|
||||||
|
const selectedPath = await selectDirectory(message);
|
||||||
|
if (!selectedPath) return;
|
||||||
|
|
||||||
|
const currentDirectory = setData.value.diskCacheDir || diskCacheStats.value.directory;
|
||||||
|
if (currentDirectory && selectedPath === currentDirectory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let action: CacheSwitchAction = 'keep';
|
||||||
|
if (currentDirectory && diskCacheStats.value.totalFiles > 0) {
|
||||||
|
const shouldMigrate = await askCacheSwitchMigrate();
|
||||||
|
if (shouldMigrate) {
|
||||||
|
action = 'migrate';
|
||||||
|
} else {
|
||||||
|
const shouldDestroy = await askCacheSwitchDestroy();
|
||||||
|
action = shouldDestroy ? 'destroy' : 'keep';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switchingCacheDirectory.value = true;
|
||||||
|
try {
|
||||||
|
const result = (await window.electron.ipcRenderer.invoke('switch-disk-cache-directory', {
|
||||||
|
directory: selectedPath,
|
||||||
|
action
|
||||||
|
})) as SwitchCacheDirectoryResult;
|
||||||
|
|
||||||
|
if (!result?.success) {
|
||||||
|
message.error(t('settings.system.messages.switchDirectoryFailed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setData.value = {
|
||||||
|
...setData.value,
|
||||||
|
enableDiskCache: result.config.enabled,
|
||||||
|
diskCacheDir: result.config.directory,
|
||||||
|
diskCacheMaxSizeMB: result.config.maxSizeMB,
|
||||||
|
diskCacheCleanupPolicy: result.config.cleanupPolicy
|
||||||
|
};
|
||||||
|
await refreshDiskCacheStats();
|
||||||
|
|
||||||
|
if (action === 'migrate') {
|
||||||
|
message.success(
|
||||||
|
t('settings.system.messages.switchDirectoryMigrated', { count: result.migratedFiles })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === 'destroy') {
|
||||||
|
message.success(
|
||||||
|
t('settings.system.messages.switchDirectoryDestroyed', { count: result.destroyedFiles })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.success(t('settings.system.messages.switchDirectorySuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('切换缓存目录失败:', error);
|
||||||
|
message.error(t('settings.system.messages.switchDirectoryFailed'));
|
||||||
|
} finally {
|
||||||
|
switchingCacheDirectory.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCacheDirectory = () => {
|
||||||
|
const targetPath = setData.value.diskCacheDir || diskCacheStats.value.directory;
|
||||||
|
openDirectory(targetPath, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearDiskCacheByScope = async (scope: DiskCacheScope) => {
|
||||||
|
if (!window.electron) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await window.electron.ipcRenderer.invoke('clear-disk-cache', scope);
|
||||||
|
if (success) {
|
||||||
|
await refreshDiskCacheStats();
|
||||||
|
message.success(t('settings.system.messages.diskCacheClearSuccess'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(t('settings.system.messages.diskCacheClearFailed'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('手动清理磁盘缓存失败:', error);
|
||||||
|
message.error(t('settings.system.messages.diskCacheClearFailed'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ==================== 代理设置 ====================
|
// ==================== 代理设置 ====================
|
||||||
const showProxyModal = ref(false);
|
const showProxyModal = ref(false);
|
||||||
const proxyForm = ref({ protocol: 'http', host: '127.0.0.1', port: 7890 });
|
const proxyForm = ref({ protocol: 'http', host: '127.0.0.1', port: 7890 });
|
||||||
@@ -883,6 +1287,7 @@ const clearCache = async (selectedCacheTypes: string[]) => {
|
|||||||
case 'resources':
|
case 'resources':
|
||||||
if (window.electron) {
|
if (window.electron) {
|
||||||
window.electron.ipcRenderer.send('clear-audio-cache');
|
window.electron.ipcRenderer.send('clear-audio-cache');
|
||||||
|
await window.electron.ipcRenderer.invoke('clear-disk-cache', 'music');
|
||||||
}
|
}
|
||||||
localStorage.removeItem('lyricCache');
|
localStorage.removeItem('lyricCache');
|
||||||
localStorage.removeItem('musicUrlCache');
|
localStorage.removeItem('musicUrlCache');
|
||||||
@@ -897,11 +1302,15 @@ const clearCache = async (selectedCacheTypes: string[]) => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'lyrics':
|
case 'lyrics':
|
||||||
window.api.invoke('clear-lyrics-cache');
|
if (window.electron) {
|
||||||
|
await window.electron.ipcRenderer.invoke('clear-disk-cache', 'lyrics');
|
||||||
|
}
|
||||||
|
await window.api.invoke('clear-lyrics-cache');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await Promise.all(clearTasks);
|
await Promise.all(clearTasks);
|
||||||
|
await refreshDiskCacheStats();
|
||||||
message.success(t('settings.system.messages.clearSuccess'));
|
message.success(t('settings.system.messages.clearSuccess'));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -997,6 +1406,18 @@ onMounted(async () => {
|
|||||||
if (setData.value.enableRealIP === undefined) {
|
if (setData.value.enableRealIP === undefined) {
|
||||||
setData.value = { ...setData.value, enableRealIP: false };
|
setData.value = { ...setData.value, enableRealIP: false };
|
||||||
}
|
}
|
||||||
|
if (setData.value.enableDiskCache === undefined) {
|
||||||
|
setData.value = { ...setData.value, enableDiskCache: true };
|
||||||
|
}
|
||||||
|
if (!setData.value.diskCacheMaxSizeMB) {
|
||||||
|
setData.value = { ...setData.value, diskCacheMaxSizeMB: 4096 };
|
||||||
|
}
|
||||||
|
if (!['lru', 'fifo'].includes(setData.value.diskCacheCleanupPolicy)) {
|
||||||
|
setData.value = { ...setData.value, diskCacheCleanupPolicy: 'lru' };
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadDiskCacheConfig();
|
||||||
|
await refreshDiskCacheStats();
|
||||||
|
|
||||||
if (window.electron) {
|
if (window.electron) {
|
||||||
window.electron.ipcRenderer.on('gpu-acceleration-updated', (_, enabled: boolean) => {
|
window.electron.ipcRenderer.on('gpu-acceleration-updated', (_, enabled: boolean) => {
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ const loadFollowerList = async () => {
|
|||||||
hasMoreFollowers.value = newFollowers.length >= followerLimit.value;
|
hasMoreFollowers.value = newFollowers.length >= followerLimit.value;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载粉丝列表失败:', error);
|
console.error('加载粉丝列表失败:', error);
|
||||||
message.error(t('user.follower.loadFailed'));
|
message.error(t('common.loadFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
followerListLoading.value = false;
|
followerListLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ const loadFollowList = async () => {
|
|||||||
hasMoreFollows.value = newFollows.length >= followLimit.value;
|
hasMoreFollows.value = newFollows.length >= followLimit.value;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载关注列表失败:', error);
|
console.error('加载关注列表失败:', error);
|
||||||
message.error(t('user.follow.loadFailed'));
|
message.error(t('common.loadFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
followListLoading.value = false;
|
followListLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user