Compare commits

6 Commits

Author SHA1 Message Date
dc938fdb01 feat: 优化博客页样式、语义化
All checks were successful
Deploy to K3s / deploy (push) Successful in 2m55s
2025-12-20 23:18:06 +08:00
d7c84ea0ce feat: 优化博客内容标题 描述 2025-12-20 23:09:35 +08:00
4d30605872 feat: 获取博客API添加描述字段 2025-12-20 23:09:03 +08:00
fbc12f97db refactor: 优化控制台、博客、资源语义化 2025-12-20 23:08:30 +08:00
5d62fd89b9 feat: 获取文章详情,返回描述 2025-12-20 23:06:59 +08:00
60d8ad8e8a refactor: 优化首页语义化 2025-12-20 22:39:48 +08:00
7 changed files with 73 additions and 32 deletions

View File

@@ -61,6 +61,7 @@ export class BlogController {
return { return {
id: blog.id, id: blog.id,
title: blog.title, title: blog.title,
description: blog.description,
createdAt: blog.createdAt, createdAt: blog.createdAt,
content: blogContent, content: blogContent,
}; };

View File

@@ -14,6 +14,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { BlogComments } from "./components/BlogComments"; import { BlogComments } from "./components/BlogComments";
import Image from "next/image"; import Image from "next/image";
import { BlogAPI } from "@/lib/api/client"; import { BlogAPI } from "@/lib/api/client";
import { useEffect } from "react";
export default function Blog() { export default function Blog() {
const params = useParams(); const params = useParams();
@@ -34,6 +35,16 @@ export default function Blog() {
() => BlogAPI.getBlog(id, password || undefined), () => BlogAPI.getBlog(id, password || undefined),
) )
useEffect(() => {
if (data) {
document.title = `${data.title} - 特恩的日志`;
const metaDescription = document.querySelector('meta[name="description"]');
if (metaDescription) {
metaDescription.setAttribute("content", data.description);
}
}
}, [data]);
return ( return (
<div className="w-full overflow-x-hidden"> <div className="w-full overflow-x-hidden">
<div className="max-w-200 mx-auto px-5 overflow-x-hidden mb-10"> <div className="max-w-200 mx-auto px-5 overflow-x-hidden mb-10">
@@ -48,18 +59,20 @@ export default function Blog() {
</div> </div>
)} )}
{data && ( {data && (
<> <article className="w-full">
<h1 className="text-center text-3xl font-bold mt-10">{data.title}</h1> <header className="flex flex-col items-center">
<p className="text-sm text-zinc-500 text-center my-5">{new Date(data.createdAt).toLocaleString()}</p> <h1 className="text-center text-2xl sm:text-3xl font-bold mt-10 transition-all duration-500">{data.title}</h1>
<time className="text-sm text-zinc-500 text-center my-2 sm:my-5 mb-5 transition-all duration-500">{new Date(data.createdAt).toLocaleString()}</time>
</header>
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkGfm]} remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeHighlight]} rehypePlugins={[rehypeRaw, rehypeHighlight]}
components={{ components={{
h1: ({ ...props }) => <h1 className="text-3xl font-bold py-2" {...props} />, h1: ({ ...props }) => <h2 className="text-3xl font-bold py-2" {...props} />,
h2: ({ ...props }) => <h2 className="text-2xl font-bold py-1" {...props} />, h2: ({ ...props }) => <h3 className="text-2xl font-bold py-1" {...props} />,
h3: ({ ...props }) => <h3 className="text-xl font-bold py-0.5" {...props} />, h3: ({ ...props }) => <h4 className="text-xl font-bold py-0.5" {...props} />,
h4: ({ ...props }) => <h4 className="text-lg font-bold" {...props} />, h4: ({ ...props }) => <h5 className="text-lg font-bold" {...props} />,
h5: ({ ...props }) => <h5 className="text-md font-bold" {...props} />, h5: ({ ...props }) => <h6 className="text-md font-bold" {...props} />,
p: ({ ...props }) => <p className="py-1 text-zinc-700" {...props} />, p: ({ ...props }) => <p className="py-1 text-zinc-700" {...props} />,
img: ({ src }) => ( img: ({ src }) => (
<PhotoProvider className="w-full"> <PhotoProvider className="w-full">
@@ -78,7 +91,7 @@ export default function Blog() {
a: ({ ...props }) => <a className="hover:underline" {...props} />, a: ({ ...props }) => <a className="hover:underline" {...props} />,
}} }}
>{data.content}</ReactMarkdown> >{data.content}</ReactMarkdown>
</> </article>
)} )}
{data && ( {data && (

View File

@@ -24,6 +24,12 @@ const getBlogDetailUrl = (id: string): string => {
return `/blog/${encoded}`; return `/blog/${encoded}`;
}; };
export const metadata = {
title: '日志 - 特恩的日志',
description: '我随便发点,你也随便看看~',
};
export default async function Blog() { export default async function Blog() {
let errorMsg = ''; let errorMsg = '';
const blogs = await BlogAPI.list().catch(e => { const blogs = await BlogAPI.list().catch(e => {

View File

@@ -2,22 +2,36 @@ import Image from 'next/image';
export default function Home() { export default function Home() {
return ( return (
<div className="w-full flex-1 flex flex-col items-center justify-center"> <section className="w-full flex-1 flex flex-col items-center justify-center">
<figure className="flex flex-col items-center">
<Image <Image
src="/avatar.png" src="/avatar.png"
alt="TONE's avatar" alt="TONE 的个人头像"
width={180} width={180}
height={180} height={180}
className="rounded-full duration-400 size-35 md:size-45 select-none" className="rounded-full duration-400 size-35 md:size-45 select-none"
priority priority
quality={100} quality={100}
/> />
</figure>
<h1 className='text-4xl md:text-5xl font-bold mt-5 md:mt-8 gradient-title duration-400 select-none'>(TONE)</h1> <h1 className='text-4xl md:text-5xl font-bold mt-5 md:mt-8 gradient-title duration-400 select-none'>(TONE)</h1>
<h2 className='text-lg sm:text-xl md:text-2xl mt-3 font-medium text-zinc-400 duration-400 select-none'></h2> <p className='text-lg sm:text-xl md:text-2xl mt-3 font-medium text-zinc-400 duration-400 select-none'></p>
<div className='flex sm:flex-row flex-col gap-2 sm:gap-10 mt-5 md:mt-8 duration-400'> <nav className='flex sm:flex-row flex-col gap-2 sm:gap-10 mt-5 md:mt-8 duration-400' aria-label="社交媒体链接">
<a href='https://space.bilibili.com/474156211' target='_black' className='bg-[#488fe9] hover:bg-[#3972ba] text-center text-white w-45 sm:w-32 px-6 py-2 text-lg rounded-full cursor-pointer'></a> <a href='https://space.bilibili.com/474156211'
<a href='https://github.com/tonecn' className='bg-[#da843f] hover:bg-[#c87d3e] text-center text-white w-45 sm:w-32 px-6 py-2 text-lg rounded-full cursor-pointer'>GitHub</a> target='_black'
</div> rel="noopener noreferrer"
</div> className='bg-[#488fe9] hover:bg-[#3972ba] text-center text-white w-45 sm:w-32 px-6 py-2 text-lg rounded-full cursor-pointer'
>
</a>
<a href='https://github.com/tonecn'
target='_black'
rel="noopener noreferrer"
className='bg-[#da843f] hover:bg-[#c87d3e] text-center text-white w-45 sm:w-32 px-6 py-2 text-lg rounded-full cursor-pointer'
>
GitHub
</a>
</nav>
</section>
); );
} }

View File

@@ -7,6 +7,11 @@ import {
import { ResourceAPI } from "@/lib/api/server"; import { ResourceAPI } from "@/lib/api/server";
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
export const metadata = {
title: '资源 - 特恩的日志',
description: '一些实用工具和学习资源',
};
export default async function Resources() { export default async function Resources() {
let errorMsg = ''; let errorMsg = '';
const data = await ResourceAPI.list().catch(e => { errorMsg = `${e}`; return null; }); const data = await ResourceAPI.list().catch(e => { errorMsg = `${e}`; return null; });

View File

@@ -1,10 +1,10 @@
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { ThemeProvider } from "../components/theme-provider"; import { ThemeProvider } from "../components/theme-provider";
import { metadata } from "./config/metadata";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
import { UserAPI } from "@/lib/api/server"; import { UserAPI } from "@/lib/api/server";
import { ClientProvider } from "./ClientProvider"; import { ClientProvider } from "./ClientProvider";
import { Metadata } from "next";
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: "--font-geist-sans",
@@ -16,7 +16,10 @@ const geistMono = Geist_Mono({
subsets: ["latin"], subsets: ["latin"],
}); });
export { metadata }; export const metadata: Metadata = {
title: "控制台 - 特恩的日志",
description: "登录或注册以解锁更多妙妙小工具",
};
export default async function RootLayout({ export default async function RootLayout({
children, children,
@@ -26,7 +29,7 @@ export default async function RootLayout({
const user = await UserAPI.me().catch(() => null); const user = await UserAPI.me().catch(() => null);
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="zh-CN" suppressHydrationWarning>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen flex flex-col`} className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen flex flex-col`}
@@ -39,10 +42,8 @@ export default async function RootLayout({
enableSystem enableSystem
disableTransitionOnChange disableTransitionOnChange
> >
<main className="flex-1 flex flex-col bg-zinc-50">
{children} {children}
<Toaster /> <Toaster />
</main>
</ThemeProvider> </ThemeProvider>
</ClientProvider> </ClientProvider>
</body> </body>

View File

@@ -5,6 +5,7 @@ export async function getBlog(id: string, password?: string) {
return clientFetch<{ return clientFetch<{
id: string; id: string;
title: string; title: string;
description: string;
createdAt: string; createdAt: string;
content: string; content: string;
}>(`/api/blog/${id}` + (password ? `?p=${password}` : '')); }>(`/api/blog/${id}` + (password ? `?p=${password}` : ''));