重构记录页面,优化数据获取逻辑,添加滚动加载功能和加载状态指示,提升用户体验。更新组件以支持初始数据加载和懒加载指示器,确保更流畅的内容展示。

This commit is contained in:
宇阳
2026-01-10 21:17:43 +08:00
parent 3ac341f9fd
commit aa74fe1295

View File

@@ -1,26 +1,116 @@
'use client';
import { useState, useEffect, useCallback, useRef } from 'react';
import RecordCard from './components/RecordCard';
import { getRecordPagingAPI } from '@/api/record';
import { getAuthorDataAPI } from '@/api/user';
import { Record } from '@/types/app/record';
import { User } from '@/types/app/user';
import Pagination from '@/components/Pagination';
import Empty from '@/components/Empty';
import Show from '@/components/Show';
import Loading from '@/components/Loading';
import { getWebConfigDataAPI } from '@/api/config';
import { Theme } from '@/types/app/config';
interface Props {
searchParams: Promise<{ page: number }>;
}
export default () => {
const [records, setRecords] = useState<Record[]>([]);
const [user, setUser] = useState<User>({} as User);
const [theme, setTheme] = useState<Theme>({} as Theme);
const [totalPages, setTotalPages] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [initialLoading, setInitialLoading] = useState(true);
const currentPageRef = useRef(1);
export default async (props: Props) => {
const searchParams = await props.searchParams;
const page = searchParams.page || 1;
// 获取记录列表
const fetchRecordList = useCallback(async (page: number, append: boolean = false) => {
setLoading(true);
try {
const { data: recordData } = (await getRecordPagingAPI({ pagination: { page, size: 8 } })) || {
data: {} as Paginate<Record[]>,
};
const { data: user } = (await getAuthorDataAPI()) || { data: {} as User };
const { data: record } = (await getRecordPagingAPI({ pagination: { page, size: 8 } })) || { data: {} as Paginate<Record[]> };
const themeResponse = await getWebConfigDataAPI<{ value: Theme }>('theme');
const theme = themeResponse?.data?.value || ({} as Theme);
if (recordData.result && recordData.result.length > 0) {
if (append) {
setRecords((prev) => [...prev, ...recordData.result]);
} else {
setRecords(recordData.result);
}
setTotalPages(recordData.pages || 1);
setHasMore(page < (recordData.pages || 1));
currentPageRef.current = page;
} else {
setHasMore(false);
}
} catch (error) {
console.error('获取记录列表失败:', error);
setHasMore(false);
} finally {
setLoading(false);
setInitialLoading(false);
}
}, []);
// 初始加载:获取用户信息、主题配置和第一页记录
useEffect(() => {
const fetchInitialData = async () => {
try {
// 并行获取用户信息、主题配置和第一页记录
const [userResponse, themeResponse] = await Promise.all([getAuthorDataAPI(), getWebConfigDataAPI<{ value: Theme }>('theme')]);
if (userResponse?.data) {
setUser(userResponse.data);
}
if (themeResponse?.data?.value) {
setTheme(themeResponse.data.value);
}
// 获取第一页记录
setRecords([]);
setHasMore(true);
setInitialLoading(true);
currentPageRef.current = 1;
await fetchRecordList(1, false);
} catch (error) {
console.error('获取初始数据失败:', error);
setInitialLoading(false);
}
};
fetchInitialData();
}, [fetchRecordList]);
// 滚动监听
useEffect(() => {
const handleScroll = () => {
// 如果正在加载或没有更多数据,则不处理
if (loading || !hasMore) return;
// 检查是否滚动到底部距离底部100px时触发
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
if (scrollTop + windowHeight >= documentHeight - 100) {
const nextPage = currentPageRef.current + 1;
if (nextPage <= totalPages) {
fetchRecordList(nextPage, true);
}
}
};
// 使用防抖优化滚动事件
let timeoutId: NodeJS.Timeout;
const debouncedHandleScroll = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(handleScroll, 200);
};
window.addEventListener('scroll', debouncedHandleScroll);
return () => {
window.removeEventListener('scroll', debouncedHandleScroll);
clearTimeout(timeoutId);
};
}, [hasMore, loading, totalPages, fetchRecordList]);
return (
<>
@@ -35,25 +125,39 @@ export default async (props: Props) => {
<h4 className="text-xs text-gray-300">{theme?.record_info}</h4>
</div>
<div className="space-y-12">
{!!record?.result?.length &&
record?.result.map((item) => (
<RecordCard
key={item.id}
id={item.id as any}
content={item.content as any}
images={item.images as any}
createTime={item.createTime as any}
user={user as any}
/>
))}
{initialLoading ? (
<div className="flex justify-center items-center py-20">
<Loading />
</div>
) : (
<>
<div className="space-y-12">
{!!records?.length && records.map((item) => <RecordCard key={item.id} id={item.id as any} content={item.content as any} images={item.images as any} createTime={item.createTime as any} user={user as any} />)}
<Show is={!record?.result?.length}>
<Empty info="闪念为空~" />
</Show>
</div>
<Show is={!records?.length}>
<Empty info="内容为空~" />
</Show>
</div>
{record?.total && <Pagination total={record?.pages} page={page} className="flex justify-center mt-5" />}
{/* 懒加载指示器 */}
{loading && records.length > 0 && (
<div className="flex justify-center items-center py-8 mt-5 gap-2">
<div className="flex items-center gap-2 text-gray-500 dark:text-gray-400 text-sm">
<svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>...</span>
</div>
</div>
)}
{!hasMore && records.length > 0 && (
<div className="flex justify-center items-center py-8 mt-5">
<div className="text-gray-500 dark:text-gray-400 text-sm"></div>
</div>
)}
</>
)}
</div>
</div>
</>