mirror of
https://github.com/LiuYuYang01/ThriveX-Blog.git
synced 2026-09-03 06:24:24 +08:00
refactor(pages, components): 移除冗余组件并优化页面结构
- 删除不再使用的 HomeContent 组件,简化主页逻辑。 - 更新文章和分类页面,使用 Suspense 组件优化加载状态,提升用户体验。 - 修改 API 调用逻辑,确保在记录文章访问量时处理异常情况。 - 更新分页组件,支持从父组件传递 basePath,增强灵活性。
This commit is contained in:
@@ -23,7 +23,6 @@ const nextConfig = {
|
||||
reactCompiler: true,
|
||||
// 启用 Turbopack 文件系统缓存,加快开发时候的构建速度
|
||||
experimental: {
|
||||
dynamicIO: true,
|
||||
turbopackFileSystemCacheForDev: true,
|
||||
},
|
||||
// 配置图片来源
|
||||
|
||||
38
src/app/(home)/components/HomeArticles/index.tsx
Normal file
38
src/app/(home)/components/HomeArticles/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import ArticleLayout from '@/components/ArticleLayout';
|
||||
import { getArticlePagingCacheAPI } from '@/lib/article';
|
||||
import { getThemeConfigCacheAPI, getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
import { getSwiperListCacheAPI } from '@/lib/swiper';
|
||||
|
||||
interface Props {
|
||||
searchParams: Promise<{ page?: number }>;
|
||||
}
|
||||
|
||||
export default async ({ searchParams }: Props) => {
|
||||
const { page: pageParam } = await searchParams;
|
||||
const page = Number(pageParam) || 1;
|
||||
|
||||
const [theme, covers, { data: swiper }] = await Promise.all([
|
||||
getThemeConfigCacheAPI(),
|
||||
getThemeCoversCacheAPI(),
|
||||
getSwiperListCacheAPI(),
|
||||
]);
|
||||
|
||||
swiper.result = swiper.result?.sort((a, b) => (a.order || 0) - (b.order || 0)) ?? [];
|
||||
|
||||
const { data } = await getArticlePagingCacheAPI({
|
||||
pageNum: page,
|
||||
pageSize: theme.is_article_layout === 'waterfall' ? 28 : 8,
|
||||
});
|
||||
data.result = data?.result?.filter((item) => item.config.status !== 'no_home') ?? [];
|
||||
|
||||
return (
|
||||
<ArticleLayout
|
||||
page={page}
|
||||
basePath="/"
|
||||
theme={theme}
|
||||
covers={covers}
|
||||
swiper={swiper}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
21
src/app/(home)/components/HomeShell/index.tsx
Normal file
21
src/app/(home)/components/HomeShell/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import Slide from '@/components/Slide';
|
||||
import Typed from '@/components/Typed';
|
||||
import Starry from '@/components/Starry';
|
||||
import { getThemeConfigCacheAPI, getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
|
||||
export default async () => {
|
||||
const [theme, covers] = await Promise.all([
|
||||
getThemeConfigCacheAPI(),
|
||||
getThemeCoversCacheAPI(),
|
||||
]);
|
||||
|
||||
return (
|
||||
<Slide src={theme?.swiper_image} covers={covers}>
|
||||
<Starry />
|
||||
<Typed
|
||||
swiperText={theme?.swiper_text}
|
||||
className="absolute top-[45%] sm:top-[40%] left-[50%] transform -translate-x-1/2 w-[80%] text-center text-white xs:text-xl sm:text-[30px] leading-7 sm:leading-[40px] md:leading-[50px] custom_text_shadow"
|
||||
/>
|
||||
</Slide>
|
||||
);
|
||||
};
|
||||
14
src/app/(home)/components/HomeSidebar/index.tsx
Normal file
14
src/app/(home)/components/HomeSidebar/index.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Sidebar from '@/components/Sidebar';
|
||||
import { getThemeConfigCacheAPI } from '@/lib/theme';
|
||||
|
||||
export default async () => {
|
||||
const theme = await getThemeConfigCacheAPI();
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
sidebar={theme?.right_sidebar ?? []}
|
||||
social={theme?.social ?? []}
|
||||
recoArticleIds={theme?.reco_article ?? []}
|
||||
/>
|
||||
);
|
||||
};
|
||||
25
src/app/(home)/page.tsx
Normal file
25
src/app/(home)/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import Container from '@/components/Container';
|
||||
import ArticlesFallback from '@/components/ArticlesFallback';
|
||||
import HomeShell from './components/HomeShell';
|
||||
import HomeArticles from './components/HomeArticles';
|
||||
import HomeSidebar from './components/HomeSidebar';
|
||||
|
||||
interface Props {
|
||||
searchParams: Promise<{ page?: number }>;
|
||||
}
|
||||
|
||||
export default (props: Props) => (
|
||||
<>
|
||||
<HomeShell />
|
||||
<Container>
|
||||
<Suspense fallback={<ArticlesFallback />}>
|
||||
<HomeArticles searchParams={props.searchParams} />
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<HomeSidebar />
|
||||
</Suspense>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
@@ -104,8 +104,12 @@ export default async (props: Props) => {
|
||||
}
|
||||
|
||||
// 记录文章访问量
|
||||
after(() => {
|
||||
void recordViewAPI(id);
|
||||
after(async () => {
|
||||
try {
|
||||
await recordViewAPI(id);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') return;
|
||||
}
|
||||
});
|
||||
|
||||
const headings = extractArticleHeadings(data?.content);
|
||||
|
||||
30
src/app/cate/[id]/components/CateArticles/index.tsx
Normal file
30
src/app/cate/[id]/components/CateArticles/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import Classics from '@/components/ArticleLayout/Classics';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import { getCateArticleListCacheAPI } from '@/lib/cate';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
searchParams: Promise<{ page?: number; name?: string }>;
|
||||
}
|
||||
|
||||
export default async ({ id, searchParams }: Props) => {
|
||||
const { page: pageParam, name } = await searchParams;
|
||||
const page = Number(pageParam) || 1;
|
||||
|
||||
const { data } = await getCateArticleListCacheAPI(id, { pageNum: page, pageSize: 8 });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Classics data={data} />
|
||||
{data?.total > 0 && (
|
||||
<Pagination
|
||||
total={data.pages}
|
||||
page={page}
|
||||
basePath={`/cate/${id}`}
|
||||
path={name ? `?name=${name}` : undefined}
|
||||
className="flex justify-center mt-5"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
30
src/app/cate/[id]/components/CateHeroSection/index.tsx
Normal file
30
src/app/cate/[id]/components/CateHeroSection/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { getCateArticleListCacheAPI, getCateListCacheAPI } from '@/lib/cate';
|
||||
import CateHero from '../../../components/CateHero';
|
||||
import CateHeroContent from '../../../components/CateHeroContent';
|
||||
import { CATE_HERO_IMAGE, findCateById } from '../../../utils';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: number }>;
|
||||
searchParams: Promise<{ name?: string }>;
|
||||
}
|
||||
|
||||
export default async ({ params, searchParams }: Props) => {
|
||||
const [{ id }, { name }] = await Promise.all([params, searchParams]);
|
||||
|
||||
const [{ data }, { data: cateListData }] = await Promise.all([
|
||||
getCateArticleListCacheAPI(id, { pageNum: 1, pageSize: 8 }),
|
||||
getCateListCacheAPI(),
|
||||
]);
|
||||
|
||||
const cateInfo = findCateById(cateListData?.result ?? [], id);
|
||||
|
||||
return (
|
||||
<CateHero image={CATE_HERO_IMAGE}>
|
||||
<CateHeroContent
|
||||
name={cateInfo?.name ?? name ?? '分类'}
|
||||
icon={cateInfo?.icon}
|
||||
articleCount={data?.total ?? 0}
|
||||
/>
|
||||
</CateHero>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Suspense } from 'react';
|
||||
import { Metadata } from 'next';
|
||||
import { getCateArticleListCacheAPI, getCateListCacheAPI } from '@/lib/cate';
|
||||
import Classics from '@/components/ArticleLayout/Classics';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import CateHero from '../components/CateHero';
|
||||
import CateHeroContent from '../components/CateHeroContent';
|
||||
import { CATE_HERO_IMAGE, findCateById } from '../utils';
|
||||
|
||||
import ArticlesFallback from '@/components/ArticlesFallback';
|
||||
import { getCateListCacheAPI } from '@/lib/cate';
|
||||
import CateHeroSection from './components/CateHeroSection';
|
||||
import CateArticles from './components/CateArticles';
|
||||
import { findCateById } from '../utils';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: number }>;
|
||||
searchParams: Promise<{ page: number; name: string }>;
|
||||
searchParams: Promise<{ page?: number; name?: string }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: Props): Promise<Metadata> {
|
||||
@@ -24,38 +25,18 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export default async (props: Props) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const id = params.id;
|
||||
const page = +(searchParams.page ?? 1);
|
||||
const name = searchParams.name;
|
||||
|
||||
const [{ data }, { data: cateListData }] = await Promise.all([
|
||||
getCateArticleListCacheAPI(id, { pageNum: page, pageSize: 8 }),
|
||||
getCateListCacheAPI(),
|
||||
]);
|
||||
|
||||
const cateInfo = findCateById(cateListData?.result ?? [], id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="min-h-screen bg-background text-foreground dark:bg-black-a">
|
||||
<CateHero image={CATE_HERO_IMAGE}>
|
||||
<CateHeroContent
|
||||
name={cateInfo?.name ?? name}
|
||||
icon={cateInfo?.icon}
|
||||
articleCount={data?.total ?? 0}
|
||||
/>
|
||||
</CateHero>
|
||||
<div className="min-h-screen bg-background text-foreground dark:bg-black-a">
|
||||
<CateHeroSection params={props.params} searchParams={props.searchParams} />
|
||||
|
||||
<main className="relative mx-auto max-w-[900px] px-3 pb-10 sm:mt-8 sm:px-6 sm:pb-12 lg:px-8">
|
||||
<Classics data={data} />
|
||||
|
||||
{data?.total > 0 && (
|
||||
<Pagination total={data?.pages} page={page} path={`?name=${name}`} className="flex justify-center mt-5" />
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
<main className="relative mx-auto max-w-[900px] px-3 pb-10 sm:mt-8 sm:px-6 sm:pb-12 lg:px-8">
|
||||
<Suspense fallback={<ArticlesFallback count={3} />}>
|
||||
<CateArticles id={id} searchParams={props.searchParams} />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import Slide from '@/components/Slide';
|
||||
import Typed from '@/components/Typed';
|
||||
import Starry from '@/components/Starry';
|
||||
import Container from '@/components/Container';
|
||||
import ArticleLayout from '@/components/ArticleLayout';
|
||||
import Sidebar from '@/components/Sidebar';
|
||||
|
||||
import { getArticlePagingCacheAPI } from '@/lib/article';
|
||||
import { getThemeConfigCacheAPI, getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
import { getSwiperListCacheAPI } from '@/lib/swiper';
|
||||
|
||||
export default async ({ page }: { page: number }) => {
|
||||
const [theme, covers, { data: swiper }] = await Promise.all([
|
||||
getThemeConfigCacheAPI(),
|
||||
getThemeCoversCacheAPI(),
|
||||
getSwiperListCacheAPI(),
|
||||
]);
|
||||
|
||||
swiper.result = swiper.result?.sort((a, b) => (a.order || 0) - (b.order || 0)) ?? [];
|
||||
|
||||
const { data } = await getArticlePagingCacheAPI({
|
||||
pageNum: page || 1,
|
||||
pageSize: theme.is_article_layout === 'waterfall' ? 28 : 8,
|
||||
});
|
||||
data.result = data?.result?.filter((item) => item.config.status !== 'no_home') ?? [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Slide src={theme?.swiper_image} covers={covers}>
|
||||
<Starry />
|
||||
<Typed
|
||||
swiperText={theme?.swiper_text}
|
||||
className="absolute top-[45%] sm:top-[40%] left-[50%] transform -translate-x-1/2 w-[80%] text-center text-white xs:text-xl sm:text-[30px] leading-7 sm:leading-[40px] md:leading-[50px] custom_text_shadow"
|
||||
/>
|
||||
</Slide>
|
||||
|
||||
<Container>
|
||||
<ArticleLayout page={page} theme={theme} covers={covers} swiper={swiper} data={data} />
|
||||
<Sidebar sidebar={theme?.right_sidebar ?? []} social={theme?.social ?? []} recoArticleIds={theme?.reco_article ?? []} />
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import HomeContent from './components/HomeContent';
|
||||
|
||||
interface Props {
|
||||
searchParams: Promise<{ page?: number }>;
|
||||
}
|
||||
|
||||
export default async (props: Props) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const page = Number(searchParams.page) || 1;
|
||||
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="min-h-screen animate-pulse bg-neutral-100 dark:bg-neutral-900" aria-hidden />
|
||||
}
|
||||
>
|
||||
<HomeContent page={page} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
30
src/app/tag/[id]/components/TagArticles/index.tsx
Normal file
30
src/app/tag/[id]/components/TagArticles/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import Classics from '@/components/ArticleLayout/Classics';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import { getTagArticleListCacheAPI } from '@/lib/tag';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
searchParams: Promise<{ page?: number; name?: string }>;
|
||||
}
|
||||
|
||||
export default async ({ id, searchParams }: Props) => {
|
||||
const { page: pageParam, name } = await searchParams;
|
||||
const page = Number(pageParam) || 1;
|
||||
|
||||
const { data } = await getTagArticleListCacheAPI(id, { pageNum: page, pageSize: 8 });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Classics data={data} />
|
||||
{data?.total > 0 && (
|
||||
<Pagination
|
||||
total={data.pages}
|
||||
page={page}
|
||||
basePath={`/tag/${id}`}
|
||||
path={name ? `?name=${name}` : undefined}
|
||||
className="flex justify-center mt-5"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
29
src/app/tag/[id]/components/TagHero/index.tsx
Normal file
29
src/app/tag/[id]/components/TagHero/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import Starry from '@/components/Starry';
|
||||
import Slide from '@/components/Slide';
|
||||
import { getTagArticleListCacheAPI } from '@/lib/tag';
|
||||
import { getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
searchParams: Promise<{ name?: string }>;
|
||||
}
|
||||
|
||||
export default async ({ id, searchParams }: Props) => {
|
||||
const name = (await searchParams).name ?? '标签';
|
||||
|
||||
const [{ data }, covers] = await Promise.all([
|
||||
getTagArticleListCacheAPI(id, { pageNum: 1, pageSize: 8 }),
|
||||
getThemeCoversCacheAPI(),
|
||||
]);
|
||||
|
||||
return (
|
||||
<Slide isRipple={false} covers={covers}>
|
||||
<Starry />
|
||||
<div className="absolute top-[40%] left-[50%] transform -translate-x-1/2 w-[80%] text-center text-white text-[20px] xs:text-[25px] sm:text-[30px] custom_text_shadow">
|
||||
<span>
|
||||
该标签:{name} ~ 共计{data?.total}篇文章
|
||||
</span>
|
||||
</div>
|
||||
</Slide>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Suspense } from 'react';
|
||||
import { Metadata } from 'next';
|
||||
import Starry from '@/components/Starry';
|
||||
import Slide from '@/components/Slide';
|
||||
import Classics from '@/components/ArticleLayout/Classics';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import { getTagArticleListCacheAPI } from '@/lib/tag';
|
||||
import { getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
|
||||
import ArticlesFallback from '@/components/ArticlesFallback';
|
||||
import TagHero from './components/TagHero';
|
||||
import TagArticles from './components/TagArticles';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: number }>;
|
||||
searchParams: Promise<{ page: number; name: string }>;
|
||||
searchParams: Promise<{ page?: number; name?: string }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: Props): Promise<Metadata> {
|
||||
@@ -22,37 +21,17 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export default async (props: Props) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const id = params.id;
|
||||
const page = searchParams.page ?? 1;
|
||||
const name = searchParams.name;
|
||||
|
||||
const [{ data }, covers] = await Promise.all([
|
||||
getTagArticleListCacheAPI(id, { pageNum: page, pageSize: 8 }),
|
||||
getThemeCoversCacheAPI(),
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Slide isRipple={false} covers={covers}>
|
||||
{/* 星空背景组件 */}
|
||||
<Starry />
|
||||
<TagHero id={id} searchParams={props.searchParams} />
|
||||
|
||||
{/* 标签信息 */}
|
||||
<div className="absolute top-[40%] left-[50%] transform -translate-x-1/2 w-[80%] text-center text-white text-[20px] xs:text-[25px] sm:text-[30px] custom_text_shadow">
|
||||
<span>
|
||||
该标签:{name} ~ 共计{data?.total}篇文章
|
||||
</span>
|
||||
</div>
|
||||
</Slide>
|
||||
|
||||
<div className="md:w-full lg:w-[900px] lg:mx-auto px-4 lg:p-0 my-5">
|
||||
<Classics data={data} />
|
||||
|
||||
{data?.total > 0 && <Pagination total={data?.pages} page={page} path={`?name=${name}`} className="flex justify-center mt-5" />}
|
||||
</div>
|
||||
<div className="md:w-full lg:w-[900px] lg:mx-auto px-4 lg:p-0 my-5">
|
||||
<Suspense fallback={<ArticlesFallback count={3} />}>
|
||||
<TagArticles id={id} searchParams={props.searchParams} />
|
||||
</Suspense>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -11,13 +11,14 @@ import { Article } from '@/types/app/article';
|
||||
|
||||
interface Props {
|
||||
page: number;
|
||||
basePath: string;
|
||||
theme: Theme;
|
||||
covers: string[];
|
||||
swiper: { result?: SwiperItem[] };
|
||||
data: Paginate<Article[]>;
|
||||
}
|
||||
|
||||
export default ({ page, theme, covers, swiper, data }: Props) => {
|
||||
export default ({ page, basePath, theme, covers, swiper, data }: Props) => {
|
||||
const sidebar = theme?.right_sidebar ?? [];
|
||||
|
||||
return (
|
||||
@@ -29,7 +30,9 @@ export default ({ page, theme, covers, swiper, data }: Props) => {
|
||||
{theme.is_article_layout === 'card' && <Card data={data} covers={covers} />}
|
||||
{theme.is_article_layout === 'waterfall' && <Waterfall data={data} covers={covers} />}
|
||||
|
||||
{!!data.total && <Pagination total={data?.pages} page={page} className="flex justify-center mt-5" />}
|
||||
{!!data.total && (
|
||||
<Pagination total={data.pages} page={page} basePath={basePath} className="flex justify-center mt-5" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
12
src/components/ArticlesFallback/index.tsx
Normal file
12
src/components/ArticlesFallback/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export default ({ count = 4 }: { count?: number }) => {
|
||||
return (
|
||||
<div className="space-y-2" aria-hidden>
|
||||
{Array.from({ length: count }, (_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="panel h-[190px] animate-pulse bg-neutral-200/60 md:h-60 lg:h-52 xl:h-60 dark:bg-neutral-800/60"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Pagination from '@/ThriveUI/Pagination';
|
||||
|
||||
interface Props {
|
||||
total: number;
|
||||
page: number;
|
||||
basePath: string;
|
||||
path?: string;
|
||||
className?: string;
|
||||
}
|
||||
@@ -16,16 +14,12 @@ function parseQuery(path?: string): Record<string, string> {
|
||||
return Object.fromEntries(new URLSearchParams(qs));
|
||||
}
|
||||
|
||||
export default ({ total, page, path, className }: Props) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<Pagination
|
||||
current={+page}
|
||||
totalPages={total}
|
||||
basePath={pathname}
|
||||
query={parseQuery(path)}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default ({ total, page, basePath, path, className }: Props) => (
|
||||
<Pagination
|
||||
current={+page}
|
||||
totalPages={total}
|
||||
basePath={basePath}
|
||||
query={parseQuery(path)}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
|
||||
// 监听路由变化
|
||||
const RouteChangeHandler: React.FC = () => {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const query = searchParams.toString();
|
||||
|
||||
// 每次切换页面滚动到顶部
|
||||
// 每次切换页面或分页参数变化时滚动到顶部
|
||||
useEffect(() => {
|
||||
// 尊重开源,禁止删除此版权信息!!!
|
||||
console.log(`%c 博客系统 %c ThriveX `, 'background: #35495e; padding: 4px; border-radius: 3px 0 0 3px; color: #fff', 'background: #539dfd; padding: 4px; border-radius: 0 3px 3px 0; color: #fff');
|
||||
@@ -17,7 +19,7 @@ const RouteChangeHandler: React.FC = () => {
|
||||
console.log('🌟 觉得好用的话记得点个 Star 哦 🙏');
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
}, [pathname, query]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,9 @@ export const Request = async <T>(method: string, api: string, data?: any) => {
|
||||
|
||||
return res?.json() as Promise<ResponseData<T>>;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
throw error;
|
||||
}
|
||||
console.log('捕获到异常:', error);
|
||||
return { code: 500, message: 'Request failed', data: {} as T };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user