mirror of
https://github.com/LiuYuYang01/ThriveX-Blog.git
synced 2026-05-06 13:51:57 +08:00
重构记录页面,优化数据获取逻辑,添加滚动加载功能和加载状态指示,提升用户体验。更新组件以支持初始数据加载和懒加载指示器,确保更流畅的内容展示。
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user