+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('setting.cache.refresh') }}
+
+
+ {{ t('setting.cache.clearAll') }}
+
+
+
+
+
+
+
+
+ {{ t('setting.cache.loadingMore') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.title || t('setting.cache.unrecognized') }}
+
+
+
+ {{ getMediaTypeLabel(item.media_type) }}
+
+ {{ item.year }}
+ {{ recognitionIdLabel }} #{{ getRecognitionId(item) }}
+
+
{{ item.key }}
+
+
+
+
+
+
+
+
+
+
+
{{ t('setting.cache.noRecognitionCache', { source: recognitionSourceName }) }}
{{ t('setting.cache.noRecognitionCacheHint') }}
-
+
+
+
+
+
+
+
+
+
+
+ {{ item.key }}
+
+
+
+
+ {{ item.title || t('setting.cache.unrecognized') }}
+ {{ item.year }}
+
+ {{ getMediaTypeLabel(item.media_type) }}
+
+
+
+
+
+ #{{ getRecognitionId(item) }}
+ -
+
+
+
+
+ {{ getRecognitionStatusLabel(item) }}
+
+
+
+
+
+
+ {{ t('common.delete') }}
+
+
+
+
+
+
+ {{ t('setting.cache.noRecognitionCache', { source: recognitionSourceName }) }}
+ {{ t('setting.cache.noRecognitionCacheHint') }}
+
+
+
+
@@ -494,6 +531,25 @@ watch([searchFilter, statusFilter], () => {
overflow-y: auto;
}
+.recognition-cache-categories {
+ display: flex;
+ flex: 0 0 auto;
+ justify-content: center;
+}
+
+.recognition-cache-categories__switcher {
+ overflow: hidden;
+ border: var(--app-surface-border);
+ border-radius: var(--app-control-radius);
+ backdrop-filter: var(--app-grouped-list-backdrop-filter);
+ background: var(--app-grouped-list-background);
+ box-shadow: var(--app-surface-shadow);
+}
+
+.recognition-cache-categories__switcher :deep(.v-btn) {
+ min-inline-size: 150px;
+}
+
.cache-panel-toolbar {
display: flex;
align-items: center;
@@ -664,6 +720,19 @@ watch([searchFilter, statusFilter], () => {
padding-inline: 16px;
}
+ .recognition-cache-categories {
+ inline-size: 100%;
+ }
+
+ .recognition-cache-categories__switcher {
+ inline-size: 100%;
+ }
+
+ .recognition-cache-categories__switcher :deep(.v-btn) {
+ flex: 1 1 0;
+ min-inline-size: 0;
+ }
+
.cache-panel-toolbar {
align-items: flex-start;
}
diff --git a/src/components/cache/__tests__/MusicRecognitionCachePanel.spec.ts b/src/components/cache/__tests__/MusicRecognitionCachePanel.spec.ts
new file mode 100644
index 00000000..c911acaf
--- /dev/null
+++ b/src/components/cache/__tests__/MusicRecognitionCachePanel.spec.ts
@@ -0,0 +1,126 @@
+import MusicRecognitionCachePanel from '@/components/cache/MusicRecognitionCachePanel.vue'
+import { screen, waitFor } from '@testing-library/vue'
+import userEvent from '@testing-library/user-event'
+import { renderWithProviders } from '@tests/support/render'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+const mocks = vi.hoisted(() => ({
+ apiGet: vi.fn(),
+ apiDelete: vi.fn(),
+ toastError: vi.fn(),
+ toastSuccess: vi.fn(),
+}))
+
+vi.mock('@/api', () => ({
+ default: {
+ get: mocks.apiGet,
+ delete: mocks.apiDelete,
+ },
+}))
+
+vi.mock('vue-toastification', () => ({
+ useToast: () => ({
+ error: mocks.toastError,
+ success: mocks.toastSuccess,
+ }),
+}))
+
+const cacheKey = '[音乐]晴天-周杰伦-叶惠美-2003'
+
+function mockMusicCacheData() {
+ mocks.apiGet.mockResolvedValue({
+ data: {
+ count: 2,
+ recognized: 1,
+ unrecognized: 1,
+ data: [
+ {
+ key: cacheKey,
+ media_id: 'rec-1',
+ title: '晴天',
+ artists: ['周杰伦'],
+ album: '叶惠美',
+ year: 2003,
+ music_type: 'recording',
+ cover_url: '',
+ },
+ {
+ key: '[音乐]未知曲目--None-None',
+ media_id: '',
+ title: '未知曲目',
+ artists: [],
+ album: '',
+ year: '',
+ music_type: 'recording',
+ cover_url: '',
+ },
+ ],
+ },
+ })
+}
+
+async function renderMusicRecognitionCachePanel() {
+ return renderWithProviders(MusicRecognitionCachePanel, {
+ initialState: {
+ globalSettings: {
+ data: {
+ GLOBAL_IMAGE_CACHE: false,
+ RECOGNIZE_SOURCE: 'musicbrainz',
+ TMDB_IMAGE_DOMAIN: 'image.tmdb.org',
+ },
+ },
+ },
+ })
+}
+
+describe('MusicRecognitionCachePanel', () => {
+ beforeEach(() => {
+ mocks.apiGet.mockReset()
+ mocks.apiDelete.mockReset()
+ mocks.toastError.mockReset()
+ mocks.toastSuccess.mockReset()
+ })
+
+ it('loads music recognition cache from the music endpoint', async () => {
+ mockMusicCacheData()
+
+ await renderMusicRecognitionCachePanel()
+
+ await waitFor(() => expect(mocks.apiGet).toHaveBeenCalledWith('music/cache'))
+ expect(await screen.findByText('晴天')).toBeInTheDocument()
+ expect(screen.getByText('周杰伦')).toBeInTheDocument()
+ expect(screen.getByText('未知曲目')).toBeInTheDocument()
+ expect(screen.getByText('MusicBrainz ID')).toBeInTheDocument()
+ })
+
+ it('shows empty hint when music cache is empty', async () => {
+ mocks.apiGet.mockResolvedValue({
+ data: {
+ count: 0,
+ recognized: 0,
+ unrecognized: 0,
+ data: [],
+ },
+ })
+
+ await renderMusicRecognitionCachePanel()
+
+ await waitFor(() => expect(mocks.apiGet).toHaveBeenCalledWith('music/cache'))
+ expect(await screen.findByText('暂无 MusicBrainz 识别缓存')).toBeInTheDocument()
+ })
+
+ it('deletes a music cache item through the encoded endpoint', async () => {
+ mockMusicCacheData()
+ mocks.apiDelete.mockResolvedValue({ success: true, message: '音乐识别缓存删除成功' })
+
+ await renderMusicRecognitionCachePanel()
+ await screen.findByText('晴天')
+
+ const user = userEvent.setup()
+ const deleteButtons = screen.getAllByRole('button', { name: '删除' })
+ await user.click(deleteButtons[0])
+
+ await waitFor(() => expect(mocks.apiDelete).toHaveBeenCalledWith(`music/cache/${encodeURIComponent(cacheKey)}`))
+ expect(mocks.toastSuccess).toHaveBeenCalled()
+ })
+})
diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts
index 396de964..1b43e3d3 100644
--- a/src/locales/en-US.ts
+++ b/src/locales/en-US.ts
@@ -2703,6 +2703,11 @@ export default {
cacheType: 'Cache Type',
torrentCache: 'Resource Cache',
recognitionCache: 'Recognition Cache',
+ recognitionCategoryLabel: 'Recognition Cache Category',
+ recognitionCategory: {
+ media: 'Movie/TV',
+ music: 'Music',
+ },
totalCount: 'Total Count',
siteCount: 'Site Count',
recognized: 'Recognized',
@@ -2736,6 +2741,7 @@ export default {
poster: 'Poster',
cacheKey: 'Recognition Request',
tmdbId: 'TMDB ID',
+ musicbrainzId: 'MusicBrainz ID',
doubanId: 'Douban ID',
torrentTitle: 'Title',
site: 'Site',
@@ -2769,8 +2775,14 @@ export default {
mediaType: {
movie: 'Movie',
tv: 'TV Show',
+ music: 'Music',
unknown: 'Unknown',
},
+ musicType: {
+ recording: 'Single',
+ album: 'Album',
+ artist: 'Artist',
+ },
clearConfirm: 'Are you sure you want to clear all cache?',
recognitionClearConfirm: 'Clear all {source} recognition cache?',
tmdbClearConfirm: 'Clear all TheMovieDb recognition cache?',
diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
index f6e4484f..1d5bf55f 100644
--- a/src/locales/zh-CN.ts
+++ b/src/locales/zh-CN.ts
@@ -2652,6 +2652,11 @@ export default {
cacheType: '缓存类型',
torrentCache: '资源缓存',
recognitionCache: '识别缓存',
+ recognitionCategoryLabel: '识别缓存分类',
+ recognitionCategory: {
+ media: '电影/电视剧',
+ music: '音乐',
+ },
totalCount: '总条数',
siteCount: '站点数',
recognized: '已识别',
@@ -2685,6 +2690,7 @@ export default {
poster: '海报',
cacheKey: '识别请求',
tmdbId: 'TMDB ID',
+ musicbrainzId: 'MusicBrainz ID',
doubanId: '豆瓣 ID',
torrentTitle: '标题',
site: '站点',
@@ -2718,8 +2724,14 @@ export default {
mediaType: {
movie: '电影',
tv: '电视剧',
+ music: '音乐',
unknown: '未知',
},
+ musicType: {
+ recording: '单曲',
+ album: '专辑',
+ artist: '艺术家',
+ },
clearConfirm: '确认清空所有缓存吗?',
recognitionClearConfirm: '确认清空全部 {source} 识别缓存吗?',
tmdbClearConfirm: '确认清空全部 TheMovieDb 识别缓存吗?',
diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts
index f5a9bccd..60de6437 100644
--- a/src/locales/zh-TW.ts
+++ b/src/locales/zh-TW.ts
@@ -2651,6 +2651,11 @@ export default {
cacheType: '緩存類型',
torrentCache: '資源緩存',
recognitionCache: '識別緩存',
+ recognitionCategoryLabel: '識別緩存分類',
+ recognitionCategory: {
+ media: '電影/電視劇',
+ music: '音樂',
+ },
totalCount: '總條數',
siteCount: '站點數',
recognized: '已識別',
@@ -2684,6 +2689,7 @@ export default {
poster: '海報',
cacheKey: '識別請求',
tmdbId: 'TMDB ID',
+ musicbrainzId: 'MusicBrainz ID',
doubanId: '豆瓣 ID',
torrentTitle: '標題',
site: '站點',
@@ -2717,8 +2723,14 @@ export default {
mediaType: {
movie: '電影',
tv: '電視劇',
+ music: '音樂',
unknown: '未知',
},
+ musicType: {
+ recording: '單曲',
+ album: '專輯',
+ artist: '藝術家',
+ },
clearConfirm: '確認清空所有緩存嗎?',
recognitionClearConfirm: '確認清空全部 {source} 識別緩存嗎?',
tmdbClearConfirm: '確認清空全部 TheMovieDb 識別緩存嗎?',