修复素材筛选加载遮罩卡住
This commit is contained in:
parent
0f319f7b68
commit
bdec270c84
@ -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}
|
||||
|
||||
84
components/home/LoadMoreTrigger.test.tsx
Normal file
84
components/home/LoadMoreTrigger.test.tsx
Normal 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)
|
||||
})
|
||||
})
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user