修复素材筛选加载遮罩卡住

This commit is contained in:
chenpei 2026-08-21 12:53:28 +08:00
parent 0f319f7b68
commit bdec270c84
3 changed files with 98 additions and 3 deletions

View File

@ -133,6 +133,7 @@ export default function MaterialLibraryPage() {
const fetchMaterialsRef = useRef<typeof fetchMaterials | null>(null) // 存储fetchMaterials函数引用
const materialsRequestIdRef = useRef(0)
const materialsAbortRef = useRef<AbortController | null>(null)
const replacingMaterialsRequestIdRef = useRef<number | null>(null)
const facetsRequestIdRef = useRef(0)
const facetsAbortRef = useRef<AbortController | null>(null)
@ -565,7 +566,12 @@ export default function MaterialLibraryPage() {
// 获取素材列表(支持分页和追加模式)
const fetchMaterials = useCallback(
async (page = 1, append = false) => {
// 筛选刷新正在替换第一页时,禁止无限滚动请求抢占同一个请求通道。
// 否则追加请求会取消筛选请求,却不会负责关闭筛选遮罩,导致页面永久转圈。
if (append && replacingMaterialsRequestIdRef.current !== null) return 0
const requestId = ++materialsRequestIdRef.current
if (!append) replacingMaterialsRequestIdRef.current = requestId
materialsAbortRef.current?.abort()
const controller = new AbortController()
materialsAbortRef.current = controller
@ -661,6 +667,7 @@ export default function MaterialLibraryPage() {
}
} finally {
if (!append && requestId === materialsRequestIdRef.current) {
replacingMaterialsRequestIdRef.current = null
setLoading(false)
setIsTransitionLoading(false)
}
@ -993,7 +1000,7 @@ export default function MaterialLibraryPage() {
// 分页+懒加载函数(完全参考flnxi_sucai实现)
const handleLoadMore = useCallback(async () => {
if (isLoadingMore) return
if (isLoadingMore || isTransitionLoading || replacingMaterialsRequestIdRef.current !== null) return
// 如果当前显示的数量小于已加载的素材数量,直接增加显示数量
if (displayedCount < materials.length) {
@ -1013,7 +1020,7 @@ export default function MaterialLibraryPage() {
setDisplayedCount((prev) => Math.min(prev + 24, materials.length + appended))
}
setIsLoadingMore(false)
}, [isLoadingMore, displayedCount, materials.length, hasMore, currentPage, fetchMaterials])
}, [isLoadingMore, isTransitionLoading, displayedCount, materials.length, hasMore, currentPage, fetchMaterials])
// IntersectionObserver 逻辑已移至 LoadMoreTrigger 组件
@ -1537,6 +1544,7 @@ export default function MaterialLibraryPage() {
<LoadMoreTrigger
hasMore={hasMore}
isLoading={isLoadingMore}
disabled={isTransitionLoading}
onLoadMore={handleLoadMore}
themeColor={themeColor}
displayedCount={displayedCount}

View File

@ -0,0 +1,84 @@
import { act, render } from '@testing-library/react'
import LoadMoreTrigger from '@/components/home/LoadMoreTrigger'
type ObserverCallback = IntersectionObserverCallback
class IntersectionObserverMock {
static instances: IntersectionObserverMock[] = []
callback: ObserverCallback
observe = jest.fn()
disconnect = jest.fn()
unobserve = jest.fn()
takeRecords = jest.fn(() => [])
root = null
rootMargin = ''
thresholds = [0]
constructor(callback: ObserverCallback) {
this.callback = callback
IntersectionObserverMock.instances.push(this)
}
trigger(isIntersecting: boolean) {
this.callback([{ isIntersecting } as IntersectionObserverEntry], this as unknown as IntersectionObserver)
}
}
describe('LoadMoreTrigger', () => {
beforeEach(() => {
IntersectionObserverMock.instances = []
window.IntersectionObserver = IntersectionObserverMock as unknown as typeof IntersectionObserver
})
test('筛选刷新期间不启动懒加载,刷新结束后恢复观察', () => {
const onLoadMore = jest.fn()
const { rerender } = render(
<LoadMoreTrigger
hasMore
isLoading={false}
disabled
onLoadMore={onLoadMore}
/>,
)
expect(IntersectionObserverMock.instances).toHaveLength(0)
rerender(
<LoadMoreTrigger
hasMore
isLoading={false}
disabled={false}
onLoadMore={onLoadMore}
/>,
)
expect(IntersectionObserverMock.instances).toHaveLength(1)
act(() => IntersectionObserverMock.instances[0].trigger(true))
expect(onLoadMore).toHaveBeenCalledTimes(1)
})
test('筛选刷新开始时断开已有的懒加载观察器', () => {
const onLoadMore = jest.fn()
const { rerender } = render(
<LoadMoreTrigger
hasMore
isLoading={false}
onLoadMore={onLoadMore}
/>,
)
const observer = IntersectionObserverMock.instances[0]
rerender(
<LoadMoreTrigger
hasMore
isLoading={false}
disabled
onLoadMore={onLoadMore}
/>,
)
expect(observer.disconnect).toHaveBeenCalledTimes(1)
})
})

View File

@ -5,6 +5,7 @@ import React, { useEffect, useRef, forwardRef } from 'react'
interface LoadMoreTriggerProps {
hasMore: boolean
isLoading: boolean
disabled?: boolean
onLoadMore: () => void
themeColor?: string
displayedCount?: number // 当前显示数量
@ -19,6 +20,7 @@ interface LoadMoreTriggerProps {
const LoadMoreTrigger = forwardRef<HTMLDivElement, LoadMoreTriggerProps>(function LoadMoreTrigger({
hasMore,
isLoading,
disabled = false,
onLoadMore,
themeColor = '#1f2937',
displayedCount,
@ -34,6 +36,7 @@ const LoadMoreTrigger = forwardRef<HTMLDivElement, LoadMoreTriggerProps>(functio
useEffect(() => {
const element = typeof loadMoreRef === 'function' ? null : loadMoreRef.current
if (!element) return
if (disabled) return
if (isLoading) return
if (!shouldShowLoadMore) return
@ -49,7 +52,7 @@ const LoadMoreTrigger = forwardRef<HTMLDivElement, LoadMoreTriggerProps>(functio
observer.observe(element)
return () => observer.disconnect()
}, [loadMoreRef, hasMore, isLoading, onLoadMore, shouldShowLoadMore])
}, [loadMoreRef, hasMore, isLoading, disabled, onLoadMore, shouldShowLoadMore])
// 显示加载更多
if (shouldShowLoadMore) {