mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-07 00:36:41 +08:00
fix(resource): 候选资源全部被过滤时显示友好提示而非 404 空态
搜索流追踪过滤前候选资源数(candidate_items 或搜索阶段 total_items),最终结果为空且存在候选时显示"找到 N 个资源,但均不符合过滤规则"。Closes #646
This commit is contained in:
@@ -1416,6 +1416,7 @@ export default {
|
||||
searching: 'Searching, please wait...',
|
||||
noData: 'No Data',
|
||||
noResourceFound: 'No resources found',
|
||||
filteredNoResults: 'Found {count} resource(s), but none match the filter rules',
|
||||
searchStreamTimeout: 'The search connection timed out. Please try again later.',
|
||||
searchStreamDisconnected: 'The search connection was interrupted. Please try again later.',
|
||||
aiRecommend: 'AI Recommendation',
|
||||
|
||||
@@ -1406,6 +1406,7 @@ export default {
|
||||
searching: '正在搜索,请稍候...',
|
||||
noData: '没有数据',
|
||||
noResourceFound: '未搜索到任何资源',
|
||||
filteredNoResults: '找到 {count} 个资源,但均不符合过滤规则',
|
||||
searchStreamTimeout: '搜索连接长时间无响应,请稍后重试',
|
||||
searchStreamDisconnected: '搜索连接已中断,请稍后重试',
|
||||
aiRecommend: '智能推荐',
|
||||
|
||||
@@ -1404,6 +1404,7 @@ export default {
|
||||
searching: '正在搜索,請稍候...',
|
||||
noData: '沒有數據',
|
||||
noResourceFound: '未搜索到任何資源',
|
||||
filteredNoResults: '找到 {count} 個資源,但均不符合過濾規則',
|
||||
searchStreamTimeout: '搜索連線長時間無回應,請稍後重試',
|
||||
searchStreamDisconnected: '搜索連線已中斷,請稍後重試',
|
||||
aiRecommend: '智能推薦',
|
||||
|
||||
@@ -549,6 +549,54 @@ describe('resource page search flow', () => {
|
||||
expect(screen.queryByText('搜索服务暂不可用')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows a friendly message when all candidate resources are filtered out', async () => {
|
||||
await renderResource({
|
||||
path: '/resource',
|
||||
query: { keyword: '过滤空结果', result_type: 'torrent' },
|
||||
})
|
||||
const source = await latestEventSource()
|
||||
|
||||
// 搜索阶段返回候选资源,最终替换事件为空表示全部被过滤规则淘汰
|
||||
source.message({
|
||||
items: [createTorrent({ title: '候选资源' })],
|
||||
stage: 'searching',
|
||||
total_items: 19,
|
||||
type: 'append',
|
||||
})
|
||||
source.message({
|
||||
candidate_items: 19,
|
||||
items: [],
|
||||
stage: 'filtered',
|
||||
total_items: 0,
|
||||
type: 'replace',
|
||||
value: 100,
|
||||
})
|
||||
source.message({ type: 'done' })
|
||||
source.fail()
|
||||
|
||||
expect(await screen.findByTestId('no-data')).toHaveTextContent('找到 19 个资源,但均不符合过滤规则')
|
||||
expect(screen.queryByText('未搜索到任何资源')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows a friendly message when the done event reports filtered candidates for title search', async () => {
|
||||
await renderResource({
|
||||
path: '/resource',
|
||||
query: { keyword: '标题过滤空结果', result_type: 'torrent' },
|
||||
})
|
||||
const source = await latestEventSource()
|
||||
|
||||
// 标题搜索没有 replace 事件,候选数由 done 事件的 candidate_items 携带
|
||||
source.message({
|
||||
candidate_items: 5,
|
||||
items: [],
|
||||
total_items: 0,
|
||||
type: 'done',
|
||||
})
|
||||
source.fail()
|
||||
|
||||
expect(await screen.findByTestId('no-data')).toHaveTextContent('找到 5 个资源,但均不符合过滤规则')
|
||||
})
|
||||
|
||||
it('restores the default empty-state message when the fallback request succeeds without results', async () => {
|
||||
mocks.apiGet.mockResolvedValueOnce({ data: [], success: true })
|
||||
const rendered = await renderResource({
|
||||
|
||||
@@ -355,6 +355,9 @@ const displayResourceCount = computed(() =>
|
||||
// 搜索中只显示进度区域,避免结果抬头和进度条同时占用顶部空间。
|
||||
const showResultHeader = computed(() => isRefreshed.value && !progressActive.value)
|
||||
|
||||
// 记录过滤前的候选资源数,用于过滤后无结果时给出友好提示
|
||||
let streamCandidateCount = 0
|
||||
|
||||
let pendingStreamItems: Array<Context> = []
|
||||
let pendingSubtitleStreamItems: Array<SubtitleInfo> = []
|
||||
const streamReplaceBatchCollector = new SearchReplaceBatchCollector<Context>()
|
||||
@@ -597,6 +600,7 @@ function resetSearchResults() {
|
||||
// 新搜索开始时先回到未完成态,避免上一轮空态在 SSE 返回前抢先显示。
|
||||
isRefreshed.value = false
|
||||
errorDescription.value = t('resource.noResourceFound')
|
||||
streamCandidateCount = 0
|
||||
rawDataList.value = []
|
||||
rawSubtitleDataList.value = []
|
||||
originalDataList.value = []
|
||||
@@ -684,11 +688,29 @@ function appendSubtitleStreamResults(items: SubtitleInfo[]) {
|
||||
scheduleStreamFlush()
|
||||
}
|
||||
|
||||
// 更新候选资源数:优先使用后端显式给出的 candidate_items,否则取搜索阶段事件的 total_items
|
||||
function updateStreamCandidateCount(eventData: { candidate_items?: unknown; stage?: unknown; total_items?: unknown }) {
|
||||
if (typeof eventData.candidate_items === 'number') {
|
||||
streamCandidateCount = Math.max(streamCandidateCount, eventData.candidate_items)
|
||||
} else if (eventData.stage === 'searching' && typeof eventData.total_items === 'number') {
|
||||
streamCandidateCount = Math.max(streamCandidateCount, eventData.total_items)
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤后无结果但存在候选资源时,用友好提示替代默认的“未搜索到任何资源”
|
||||
function applyFilteredEmptyResultMessage(resultCount: number) {
|
||||
if (resultCount === 0 && streamCandidateCount > 0) {
|
||||
errorDescription.value = t('resource.filteredNoResults', { count: streamCandidateCount })
|
||||
}
|
||||
}
|
||||
|
||||
// 完整最终结果到达后原子替换资源列表。
|
||||
function applyFinalStreamResults(items: Context[]) {
|
||||
streamFinalResultApplied = true
|
||||
flushBufferedStreamState()
|
||||
setStreamResults(items)
|
||||
// 候选全部被过滤规则淘汰时给出友好提示
|
||||
applyFilteredEmptyResultMessage(items.length)
|
||||
}
|
||||
|
||||
// 应用最终字幕搜索结果
|
||||
@@ -734,6 +756,7 @@ function handleSearchStreamMessage(eventData: { [key: string]: any }) {
|
||||
updateSearchProgress(eventData, completedItems !== null)
|
||||
if (completedItems) applyFinalSubtitleStreamResults(completedItems)
|
||||
} else {
|
||||
updateStreamCandidateCount(eventData)
|
||||
const completedItems = streamReplaceBatchCollector.append(eventData)
|
||||
updateSearchProgress(eventData, completedItems !== null)
|
||||
if (completedItems) applyFinalStreamResults(completedItems)
|
||||
@@ -759,6 +782,7 @@ function handleSearchStreamMessage(eventData: { [key: string]: any }) {
|
||||
}
|
||||
|
||||
const items = Array.isArray(eventData.items) ? (eventData.items as Context[]) : []
|
||||
updateStreamCandidateCount(eventData)
|
||||
if (eventData.type === 'append') {
|
||||
updateSearchProgress(eventData)
|
||||
appendStreamResults(items)
|
||||
@@ -770,6 +794,10 @@ function handleSearchStreamMessage(eventData: { [key: string]: any }) {
|
||||
applyFinalStreamResults(items)
|
||||
} else {
|
||||
updateSearchProgress(eventData)
|
||||
// 标题搜索没有 replace 事件,最终结果为空时在此给出友好提示
|
||||
if (eventData.type === 'done' && !streamFinalResultApplied) {
|
||||
applyFilteredEmptyResultMessage(items.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user