From db8d8c429d22845063ef8067198229c7d5603639 Mon Sep 17 00:00:00 2001 From: tone Date: Thu, 25 Dec 2025 15:00:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E5=8D=9A=E5=AE=A2?= =?UTF-8?q?=E9=A1=B5=E8=8E=B7=E5=8F=96=E6=95=B0=E6=8D=AE=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=B8=BA=E6=9C=8D=E5=8A=A1=E7=AB=AF=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blog/[id]/BlogContent.tsx | 41 ++++++ .../(with-header-footer)/blog/[id]/page.tsx | 137 +++++++++--------- .../frontend/lib/api/endpoints/blog.server.ts | 10 ++ 3 files changed, 121 insertions(+), 67 deletions(-) create mode 100644 apps/frontend/app/(with-header-footer)/blog/[id]/BlogContent.tsx diff --git a/apps/frontend/app/(with-header-footer)/blog/[id]/BlogContent.tsx b/apps/frontend/app/(with-header-footer)/blog/[id]/BlogContent.tsx new file mode 100644 index 0000000..b0442e3 --- /dev/null +++ b/apps/frontend/app/(with-header-footer)/blog/[id]/BlogContent.tsx @@ -0,0 +1,41 @@ +'use client'; +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import rehypeHighlight from 'rehype-highlight' +import 'highlight.js/styles/github.css' +import { PhotoProvider, PhotoView } from 'react-photo-view'; +import 'react-photo-view/dist/react-photo-view.css'; +import rehypeRaw from 'rehype-raw' +import Image from "next/image"; + +export function BlogContent({ content }: { content?: string }) { + return ( +

, + h2: ({ ...props }) =>

, + h3: ({ ...props }) =>

, + h4: ({ ...props }) =>

, + h5: ({ ...props }) =>
, + p: ({ ...props }) =>

, + img: ({ src }) => ( + + +

+ 加载失败 +
+ + + ), + th: ({ ...props }) => , + td: ({ ...props }) => , + table: ({ ...props }) =>
, + pre: ({ ...props }) =>
,
+                blockquote: ({ ...props }) => 
, + a: ({ ...props }) => , + }} + >{content} + ) +} \ No newline at end of file diff --git a/apps/frontend/app/(with-header-footer)/blog/[id]/page.tsx b/apps/frontend/app/(with-header-footer)/blog/[id]/page.tsx index d08979f..f4cac2a 100644 --- a/apps/frontend/app/(with-header-footer)/blog/[id]/page.tsx +++ b/apps/frontend/app/(with-header-footer)/blog/[id]/page.tsx @@ -1,24 +1,31 @@ -'use client'; - import { base62 } from "@/lib/utils"; -import { useParams, useSearchParams } from "next/navigation"; -import useSWR from "swr"; -import ReactMarkdown from 'react-markdown' -import remarkGfm from 'remark-gfm' -import rehypeHighlight from 'rehype-highlight' -import 'highlight.js/styles/github.css' -import { PhotoProvider, PhotoView } from 'react-photo-view'; -import 'react-photo-view/dist/react-photo-view.css'; -import rehypeRaw from 'rehype-raw' -import { Skeleton } from "@/components/ui/skeleton"; +import { BlogContent } from "./BlogContent"; +import { BlogAPI } from "@/lib/api/server"; +import { handleAPIError } from "@/lib/api/common"; import { BlogComments } from "./components/BlogComments"; -import Image from "next/image"; -import { BlogAPI } from "@/lib/api/client"; -import { useEffect } from "react"; -export default function Blog() { - const params = useParams(); - const searchParams = useSearchParams(); +interface PageRouteProps { + params: Promise<{ id: string }> + searchParams: Promise<{ + [key: string]: string | string[] | undefined; + } | undefined> +} + +export async function parseBlogParams({ params: paramsPromise, searchParams: searchParamsPromise }: PageRouteProps) { + const params = await paramsPromise ?? {}; + const searchParams = await searchParamsPromise ?? {}; + + if (Array.isArray(searchParams.p)) { + return { + errorMsg: '密码错误或文章不存在' + } + } + + if (typeof params.id !== 'string' || params.id.trim() === '') { + return { + errorMsg: '文章不存在或无权限访问' + } + } const hex = Array.from(base62.decode(params.id as string)).map(b => b.toString(16).padStart(2, '0')).join(''); const id = [ @@ -29,68 +36,64 @@ export default function Blog() { hex.slice(20, 32) ].join('-'); - const password = searchParams.get('p'); - const { data, error, isLoading } = useSWR( - `/api/blog/${id}`, - () => BlogAPI.getBlog(id, password || undefined), - ) + return { + id, + p: searchParams.p, + } +} - useEffect(() => { - if (data) { - document.title = `${data.title} - 特恩的日志`; - const metaDescription = document.querySelector('meta[name="description"]'); - if (metaDescription) { - metaDescription.setAttribute("content", data.description); +export async function getBlog(paramsResult: ReturnType) { + const { errorMsg, id, p } = await paramsResult; + if (errorMsg) { + return { + errorMsg, + } + } else { + try { + const data = await BlogAPI.getBlog(`${id}`, p); + return { + data, + } + } catch (error) { + return { + errorMsg: handleAPIError(error, ({ message }) => message) } } - }, [data]); + } +} + +export async function generateMetadata({ params, searchParams }: PageRouteProps) { + const { errorMsg, data } = await getBlog(parseBlogParams({ params, searchParams })); + if (data) { + return { + title: `${data.title} - 特恩的日志`, + description: `${data.description}` + } + } else { + return { + title: `${errorMsg || '错误'} - 特恩的日志`, + description: `出错啦` + } + } +} + +export default async function Page({ params, searchParams }: PageRouteProps) { + let { errorMsg, id, p } = await parseBlogParams({ params, searchParams }); + + const data = errorMsg ? null + : await BlogAPI.getBlog(`${id}`, p).catch(e => handleAPIError(e, ({ message }) => { errorMsg = message; return null })); return (
- {error &&
{error.message}
} - {isLoading && ( -
- - - - - -
- )} + {errorMsg &&
{errorMsg}
} {data && (

{data.title}

-

, - h2: ({ ...props }) =>

, - h3: ({ ...props }) =>

, - h4: ({ ...props }) =>

, - h5: ({ ...props }) =>
, - p: ({ ...props }) =>

, - img: ({ src }) => ( - - -

- -
- - - ), - th: ({ ...props }) =>
, - td: ({ ...props }) => , - table: ({ ...props }) =>