diff --git a/next.config.mjs b/next.config.mjs index 34af4f9..15e6330 100755 --- a/next.config.mjs +++ b/next.config.mjs @@ -23,7 +23,6 @@ const nextConfig = { reactCompiler: true, // 启用 Turbopack 文件系统缓存,加快开发时候的构建速度 experimental: { - dynamicIO: true, turbopackFileSystemCacheForDev: true, }, // 配置图片来源 diff --git a/src/app/(home)/components/HomeArticles/index.tsx b/src/app/(home)/components/HomeArticles/index.tsx new file mode 100644 index 0000000..1c63030 --- /dev/null +++ b/src/app/(home)/components/HomeArticles/index.tsx @@ -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 ( + + ); +}; diff --git a/src/app/(home)/components/HomeShell/index.tsx b/src/app/(home)/components/HomeShell/index.tsx new file mode 100644 index 0000000..88a30b1 --- /dev/null +++ b/src/app/(home)/components/HomeShell/index.tsx @@ -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 ( + + + + + ); +}; diff --git a/src/app/(home)/components/HomeSidebar/index.tsx b/src/app/(home)/components/HomeSidebar/index.tsx new file mode 100644 index 0000000..57a4ab3 --- /dev/null +++ b/src/app/(home)/components/HomeSidebar/index.tsx @@ -0,0 +1,14 @@ +import Sidebar from '@/components/Sidebar'; +import { getThemeConfigCacheAPI } from '@/lib/theme'; + +export default async () => { + const theme = await getThemeConfigCacheAPI(); + + return ( + + ); +}; diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx new file mode 100644 index 0000000..7e3bf11 --- /dev/null +++ b/src/app/(home)/page.tsx @@ -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) => ( + <> + + + }> + + + + + + + +); diff --git a/src/app/article/[id]/page.tsx b/src/app/article/[id]/page.tsx index ed8f009..ea42439 100755 --- a/src/app/article/[id]/page.tsx +++ b/src/app/article/[id]/page.tsx @@ -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); diff --git a/src/app/cate/[id]/components/CateArticles/index.tsx b/src/app/cate/[id]/components/CateArticles/index.tsx new file mode 100644 index 0000000..44b56c1 --- /dev/null +++ b/src/app/cate/[id]/components/CateArticles/index.tsx @@ -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 ( + <> + + {data?.total > 0 && ( + + )} + + ); +}; diff --git a/src/app/cate/[id]/components/CateHeroSection/index.tsx b/src/app/cate/[id]/components/CateHeroSection/index.tsx new file mode 100644 index 0000000..7fb77a9 --- /dev/null +++ b/src/app/cate/[id]/components/CateHeroSection/index.tsx @@ -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 ( + + + + ); +}; diff --git a/src/app/cate/[id]/page.tsx b/src/app/cate/[id]/page.tsx index d384b34..5292e29 100755 --- a/src/app/cate/[id]/page.tsx +++ b/src/app/cate/[id]/page.tsx @@ -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 { @@ -24,38 +25,18 @@ export async function generateMetadata(props: Props): Promise { } 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 ( - <> -
- - - +
+ -
- - - {data?.total > 0 && ( - - )} -
-
- +
+ }> + + +
+
); }; diff --git a/src/app/components/HomeContent/index.tsx b/src/app/components/HomeContent/index.tsx deleted file mode 100644 index 8c0542c..0000000 --- a/src/app/components/HomeContent/index.tsx +++ /dev/null @@ -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 ( - <> - - - - - - - - - - - ); -}; diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100755 index 87d0e13..0000000 --- a/src/app/page.tsx +++ /dev/null @@ -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 ( - - } - > - - - ); -}; diff --git a/src/app/tag/[id]/components/TagArticles/index.tsx b/src/app/tag/[id]/components/TagArticles/index.tsx new file mode 100644 index 0000000..73e8e0c --- /dev/null +++ b/src/app/tag/[id]/components/TagArticles/index.tsx @@ -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 ( + <> + + {data?.total > 0 && ( + + )} + + ); +}; diff --git a/src/app/tag/[id]/components/TagHero/index.tsx b/src/app/tag/[id]/components/TagHero/index.tsx new file mode 100644 index 0000000..d24ed62 --- /dev/null +++ b/src/app/tag/[id]/components/TagHero/index.tsx @@ -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 ( + + +
+ + 该标签:{name} ~ 共计{data?.total}篇文章 + +
+
+ ); +}; diff --git a/src/app/tag/[id]/page.tsx b/src/app/tag/[id]/page.tsx index b05e38e..21c93c2 100755 --- a/src/app/tag/[id]/page.tsx +++ b/src/app/tag/[id]/page.tsx @@ -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 { @@ -22,37 +21,17 @@ export async function generateMetadata(props: Props): Promise { } 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 ( <> -
- - {/* 星空背景组件 */} - + - {/* 标签信息 */} -
- - 该标签:{name} ~ 共计{data?.total}篇文章 - -
-
- -
- - - {data?.total > 0 && } -
+
+ }> + +
); diff --git a/src/components/ArticleLayout/index.tsx b/src/components/ArticleLayout/index.tsx index f50f613..50c10e6 100755 --- a/src/components/ArticleLayout/index.tsx +++ b/src/components/ArticleLayout/index.tsx @@ -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; } -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' && } {theme.is_article_layout === 'waterfall' && } - {!!data.total && } + {!!data.total && ( + + )}
); }; diff --git a/src/components/ArticlesFallback/index.tsx b/src/components/ArticlesFallback/index.tsx new file mode 100644 index 0000000..2c19720 --- /dev/null +++ b/src/components/ArticlesFallback/index.tsx @@ -0,0 +1,12 @@ +export default ({ count = 4 }: { count?: number }) => { + return ( +
+ {Array.from({ length: count }, (_, i) => ( +
+ ))} +
+ ); +}; diff --git a/src/components/Pagination/index.tsx b/src/components/Pagination/index.tsx index c9ea93e..25c8d02 100755 --- a/src/components/Pagination/index.tsx +++ b/src/components/Pagination/index.tsx @@ -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 { return Object.fromEntries(new URLSearchParams(qs)); } -export default ({ total, page, path, className }: Props) => { - const pathname = usePathname(); - - return ( - - ); -}; +export default ({ total, page, basePath, path, className }: Props) => ( + +); diff --git a/src/components/RouteChangeHandler/index.tsx b/src/components/RouteChangeHandler/index.tsx index bffe269..2764645 100755 --- a/src/components/RouteChangeHandler/index.tsx +++ b/src/components/RouteChangeHandler/index.tsx @@ -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; }; diff --git a/src/utils/request.ts b/src/utils/request.ts index 4c8b550..7fa446e 100755 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -16,6 +16,9 @@ export const Request = async (method: string, api: string, data?: any) => { return res?.json() as Promise>; } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + throw error; + } console.log('捕获到异常:', error); return { code: 500, message: 'Request failed', data: {} as T }; }