# 前端技术方案 ## 1. 概述 博客系统的前端,基于 Next.js 16 (App Router) + Tailwind CSS v4 + shadcn/ui 构建。与 Rust 后端 (hello-axum) 前后端分离部署,开发时前端 dev server 直接请求后端 API。 项目位于 `web/` 目录。 --- ## 2. 技术栈 | 类别 | 技术 | 版本 | 说明 | |------|------|------|------| | 框架 | Next.js (App Router) | 16.2 | RSC + Client Components | | 语言 | TypeScript | 5.x | 严格模式 | | 样式 | Tailwind CSS | 4.2 | v4,CSS-based 配置(无 `tailwind.config.ts`) | | UI 组件库 | shadcn/ui (base-nova) | 4.2 | 按需复制组件到项目,可定制 | | 基础 UI | @base-ui/react | 1.3 | shadcn v4 底层无样式原语 | | 状态管理 | zustand | 5.0 | 轻量,存 auth 状态,persist 到 localStorage | | 数据请求 | @tanstack/react-query | 5.97 | 缓存、自动刷新、loading/error 管理 | | 表单 | react-hook-form + zod | 7.72 / 4.3 | 表单验证,和后端 validator 对齐 | | 图标 | lucide-react | 1.8 | shadcn 默认图标库 | | Toast | sonner | 2.0 | shadcn 推荐的 toast 方案 | | i18n | next-intl | 4.9 | 国际化(复用后端 locale 逻辑) | | 包管理器 | pnpm | 10.x | workspace 支持 | --- ## 3. 项目结构 ``` web/ ├── src/ │ ├── app/ # Next.js App Router 页面 │ │ ├── layout.tsx # 根 layout(Geist 字体 + Providers) │ │ ├── page.tsx # 首页 │ │ ├── globals.css # Tailwind v4 + shadcn CSS 变量 │ │ ├── (public)/ # 公开页面组(Header + Footer 布局) │ │ │ ├── layout.tsx │ │ │ ├── posts/ │ │ │ │ ├── page.tsx # 文章列表(分页、搜索、筛选) │ │ │ │ └── [slug]/ │ │ │ │ └── page.tsx # 文章详情(Markdown 渲染 + 评论) │ │ │ └── categories/ │ │ │ └── [id]/ │ │ │ └── page.tsx # 按分类浏览 │ │ ├── (admin)/ # 后台页面组(Sidebar 布局) │ │ │ ├── layout.tsx # AdminLayout:侧边栏 + 顶部导航 │ │ │ ├── dashboard/ │ │ │ │ └── page.tsx # 仪表盘(统计卡片 + 最近评论) │ │ │ ├── posts/ │ │ │ │ ├── page.tsx # 文章管理 DataTable │ │ │ │ ├── new/ │ │ │ │ │ └── page.tsx # 新建文章(Markdown 编辑器) │ │ │ │ └── [id]/edit/ │ │ │ │ └── page.tsx # 编辑文章 │ │ │ ├── categories/ │ │ │ │ └── page.tsx # 分类管理 │ │ │ ├── tags/ │ │ │ │ └── page.tsx # 标签管理 │ │ │ ├── comments/ │ │ │ │ └── page.tsx # 评论管理(审核 + 状态切换) │ │ │ ├── media/ │ │ │ │ └── page.tsx # 媒体管理(网格 + 拖拽上传) │ │ │ └── users/ │ │ │ └── page.tsx # 用户管理(仅 Admin) │ │ ├── auth/ │ │ │ ├── login/ │ │ │ │ └── page.tsx # 登录页 │ │ │ └── register/ │ │ │ └── page.tsx # 注册页 │ │ └── profile/ │ │ └── page.tsx # 个人资料设置 │ ├── components/ │ │ ├── ui/ # shadcn 组件(自动生成) │ │ ├── blog/ # 博客专用组件 │ │ │ ├── post-card.tsx # 文章卡片(列表展示) │ │ │ ├── post-content.tsx # 文章内容(HTML 渲染 + 代码高亮) │ │ │ ├── post-toc.tsx # 文章目录(TOC) │ │ │ ├── comment-section.tsx # 评论区(树形嵌套) │ │ │ ├── comment-form.tsx # 评论表单(游客/登录) │ │ │ ├── search-bar.tsx # 搜索栏 │ │ │ └── tag-badge.tsx # 标签徽章 │ │ ├── admin/ # 后台专用组件 │ │ │ ├── post-form.tsx # 文章表单(Markdown 编辑器 + 标签多选) │ │ │ ├── media-grid.tsx # 媒体网格(上传 + 管理器) │ │ │ ├── comment-table.tsx # 评论 DataTable │ │ │ └── stats-card.tsx # 统计卡片 │ │ ├── common/ # 通用组件 │ │ │ ├── header.tsx # 顶部导航栏 │ │ │ ├── footer.tsx # 底部 │ │ │ ├── pagination.tsx # 分页组件 │ │ │ └── user-menu.tsx # 用户下拉菜单 │ │ └── providers.tsx # 全局 Providers(React Query + Toaster) │ ├── lib/ │ │ ├── api.ts # API 封装(自动 token / refresh / i18n) │ │ └── utils.ts # shadcn cn() 工具函数 │ ├── stores/ │ │ └── auth.ts # zustand auth store(localStorage 持久化) │ └── hooks/ │ └── use-mobile.ts # shadcn 移动端检测 hook ├── public/ # 静态资源 ├── .env.local # 环境变量 ├── next.config.ts # Next.js 配置 ├── postcss.config.mjs # PostCSS(@tailwindcss/postcss) ├── components.json # shadcn 配置 ├── tsconfig.json # TypeScript 配置(@/* 路径别名) └── package.json ``` ### 3.1 路由组说明 | 路由组 | 布局 | 认证要求 | 页面 | |--------|------|----------|------| | `(public)` | Header + Footer | 无 | 首页、文章列表、文章详情、分类 | | `(admin)` | Sidebar + TopNav | 需登录(Author/Admin) | 仪表盘、文章管理、评论管理、媒体管理、用户管理 | | `auth` | 居中卡片布局 | 无 | 登录、注册 | | `profile` | Header + Footer | 需登录 | 个人资料 | --- ## 4. API 集成 ### 4.1 后端 API 基地址 ```env # .env.local NEXT_PUBLIC_API_URL=http://localhost:3000/api/v1 ``` 生产环境改为实际后端地址。 ### 4.2 API 封装设计 (`src/lib/api.ts`) ``` 请求流程: fetch → 自动加 Authorization → 自动加 Accept-Language → 401? → 尝试 refresh token → 成功? 重试原请求 → 失败? logout → 解析 { code, message, data } → code≠0? throw ApiError → 返回 data ``` 封装提供以下方法: | 方法 | 用途 | Content-Type | |------|------|--------------| | `api.get(path)` | GET 请求 | application/json | | `api.post(path, body)` | POST JSON | application/json | | `api.put(path, body)` | PUT JSON | application/json | | `api.delete(path)` | DELETE | application/json | | `api.upload(path, file)` | POST 文件 | multipart/form-data | ### 4.3 Token 刷新机制 - Access Token 过期(后端默认 15 分钟) - API 层自动拦截 401,调用 `/auth/refresh` 获取新 token - 刷新成功:更新 zustand store,重试原请求 - 刷新失败:清除 store,跳转登录页 - Refresh Token 过期(后端默认 7 天):需重新登录 ### 4.4 后端 API 路由表 | 方法 | 路径 | 认证 | 说明 | |------|------|------|------| | GET | `/health` | 无 | 健康检查 | | POST | `/auth/register` | 无 | 注册 | | POST | `/auth/login` | 无 | 登录 | | POST | `/auth/refresh` | 无 | 刷新 Token | | POST | `/auth/logout` | 是 | 登出 | | GET | `/users/me` | 是 | 当前用户信息 | | PUT | `/users/me` | 是 | 更新用户资料 | | PUT | `/users/me/password` | 是 | 修改密码 | | GET | `/users/{id}` | 无 | 用户公开信息 | | GET | `/users` | Admin | 用户列表 | | GET | `/categories` | 无 | 分类列表 | | POST | `/categories` | Author | 创建分类 | | PUT | `/categories/{id}` | Author | 更新分类 | | DELETE | `/categories/{id}` | Author | 删除分类 | | GET | `/tags` | 无 | 标签列表 | | POST | `/tags` | Author | 创建标签 | | DELETE | `/tags/{id}` | Author | 删除标签 | | GET | `/posts` | 无 | 文章列表(支持 ?page=&q=&category_id=&tag_id=) | | POST | `/posts` | Author | 创建文章 | | GET | `/posts/{slug}` | 无 | 文章详情 | | PUT | `/posts/{slug}` | 是* | 更新文章(*作者或 Admin) | | DELETE | `/posts/{slug}` | 是* | 删除文章(*作者或 Admin) | | GET | `/posts/{slug}/comments` | 无 | 评论列表 | | POST | `/posts/{slug}/comments` | 无 | 游客评论 | | POST | `/posts/{slug}/comments/authed` | 是 | 登录用户评论 | | DELETE | `/comments/{id}` | 是* | 删除评论(*作者或 Admin) | | PUT | `/comments/{id}/status` | Admin | 审核评论 | | POST | `/media/upload` | 是 | 上传文件 | | GET | `/media` | 是 | 媒体列表 | | DELETE | `/media/{id}` | 是* | 删除媒体(*所有者或 Admin) | | GET | `/feed.xml` | 无 | RSS 订阅 | ### 4.5 后端响应格式 ```json // 成功 { "code": 0, "message": "success", "data": { ... } } // 分页 { "code": 0, "message": "success", "data": { "items": [...], "total": 42, "page": 1, "page_size": 20 } } // 错误 { "code": 40100, "message": "unauthorized", "data": null } ``` 错误码范围: | 范围 | HTTP 状态 | |------|-----------| | 40000 | 400 Bad Request | | 40100 | 401 Unauthorized | | 40300 | 403 Forbidden | | 40400 | 404 Not Found | | 40900 | 409 Conflict | | 42900 | 429 Too Many Requests | | 50000 | 500 Internal Server Error | ### 4.6 国际化 (i18n) 对接 后端已支持通过 `Accept-Language` 头或 `?lang=` 参数切换语言(en / zh-CN)。 API 层自动附加 `Accept-Language: navigator.language`,前端无需额外处理。 如需前端 UI 也做国际化,使用 `next-intl`,翻译文件放在 `src/i18n/` 目录。 --- ## 5. 状态管理 ### 5.1 Auth Store (`src/stores/auth.ts`) 使用 zustand + persist 中间件,自动持久化到 `localStorage("auth-storage")`。 ``` AuthState { user: User | null // 用户信息 accessToken: string | null // JWT access token refreshToken: string | null // JWT refresh token login(user, access, refresh) → 设置全部 logout() → 清除全部 isLoggedIn() → accessToken !== null isAdmin() → user.role === "admin" isAuthor() → role in ["admin", "author"] } ``` ### 5.2 服务端数据 (React Query) 所有 API 数据通过 `@tanstack/react-query` 管理: - 缓存:默认 `staleTime: 60s` - 自动重试:1 次 - 使用 `useQuery` 读取、`useMutation` 写入 - Query Key 约定:`["posts", { page, q, category_id }]`、`["post", slug]`、`["comments", slug]` --- ## 6. 页面设计 ### 6.1 公开页面 #### 首页 (`/`) - Hero 区域:博客标题 + 简介 - 最新文章列表(6 篇) - 快速链接:浏览全部、RSS #### 文章列表 (`/posts`) - 搜索栏 + 分类筛选 + 标签筛选 - 文章卡片网格(标题、摘要、标签、日期、浏览量) - 分页组件 #### 文章详情 (`/posts/[slug]`) - 文章标题、元信息(作者、日期、分类、标签) - HTML 内容(后端已渲染,前端直接展示) - 代码高亮(Shiki,客户端增强) - 目录导航(TOC,从 h2/h3 标题提取) - 评论区:树形嵌套展示 + 评论表单 #### 登录 (`/auth/login`) - 邮箱 + 密码表单 - zod 校验(邮箱格式、密码长度) - 登录成功后 redirect 到之前的页面 #### 注册 (`/auth/register`) - 邮箱 + 用户名 + 密码表单 - zod 校验(邮箱格式、用户名 2-50 字符、密码 ≥ 8 字符) ### 6.2 后台页面 #### 仪表盘 (`/admin/dashboard`) - 统计卡片:文章数、评论数、媒体数 - 最近待审核评论列表 - 最近文章列表 #### 文章管理 (`/admin/posts`) - DataTable:标题、状态(draft/published)、分类、标签、日期 - 操作:编辑、删除 - 状态筛选 - 新建按钮 → `/admin/posts/new` #### 文章编辑 (`/admin/posts/[id]/edit`) - Markdown 编辑器(Milkdown 或 Novel) - 标题、分类选择、标签多选 - 封面图上传 - 摘要编辑(自动从内容提取) - 状态切换:草稿 / 发布 #### 评论管理 (`/admin/comments`) - DataTable:评论内容、文章、作者、状态、日期 - 状态筛选:pending / approved / spam - 快速操作:通过、标记垃圾、删除 #### 媒体管理 (`/admin/media`) - 网格视图 - 拖拽上传 - 点击复制 URL - 删除 #### 分类管理 (`/admin/categories`) - 简单列表 + 新建/编辑/删除 #### 标签管理 (`/admin/tags`) - 简单列表 + 新建/删除 #### 用户管理 (`/admin/users`) - DataTable:用户名、邮箱、角色、注册时间 - 仅 Admin 可访问 --- ## 7. 组件库 ### 7.1 已安装的 shadcn 组件 | 组件 | 用途 | |------|------| | button | 按钮 | | card | 卡片容器 | | input | 输入框 | | label | 表单标签 | | textarea | 多行文本 | | select | 下拉选择 | | badge | 徽章标签 | | avatar | 用户头像 | | dropdown-menu | 下拉菜单 | | dialog | 模态框 | | sheet | 侧滑面板 | | separator | 分隔线 | | skeleton | 加载骨架屏 | | sonner | Toast 通知 | | tabs | 标签页 | | table | 表格 | | command | 命令面板 | | sidebar | 侧边栏 | | tooltip | 提示气泡 | ### 7.2 待按需添加 | 组件 | 用途 | 安装命令 | |------|------|----------| | form | react-hook-form 集成 | `pnpm dlx shadcn@latest add form` | | pagination | 分页 | `pnpm dlx shadcn@latest add pagination` | | calendar | 日期选择 | `pnpm dlx shadcn@latest add calendar` | | popover | 弹出层 | `pnpm dlx shadcn@latest add popover` | | switch | 开关 | `pnpm dlx shadcn@latest add switch` | | alert-dialog | 确认对话框 | `pnpm dlx shadcn@latest add alert-dialog` | | breadcrumb | 面包屑 | `pnpm dlx shadcn@latest add breadcrumb` | --- ## 8. 样式方案 ### 8.1 Tailwind CSS v4 v4 不再使用 `tailwind.config.ts`,改为 CSS-based 配置: ```css /* src/app/globals.css */ @import "tailwindcss"; @import "tw-animate-css"; @import "shadcn/tailwind.css"; /* CSS 变量定义主题色(oklch 色彩空间) */ :root { --primary: oklch(0.205 0 0); ... } .dark { --primary: oklch(0.922 0 0); ... } ``` ### 8.2 主题切换 已内置 light/dark 双主题 CSS 变量。可集成 `next-themes` 实现: ```tsx import { ThemeProvider } from "next-themes"; // 在 Providers 中包裹 ``` ### 8.3 字体 - 正文字体:Geist Sans(Next.js 内置) - 代码字体:Geist Mono --- ## 9. 开发流程 ### 9.1 环境准备 ```bash cd web pnpm install # 安装依赖 cp .env.local .env # 确认 API 地址 ``` ### 9.2 启动开发服务器 ```bash # 终端 1:启动后端 cd /path/to/hello-axum cargo run # 终端 2:启动前端 cd web pnpm dev # 默认 http://localhost:3001 ``` ### 9.3 添加 shadcn 组件 ```bash pnpm dlx shadcn@latest add ``` ### 9.4 构建 ```bash pnpm build # 生产构建,输出到 .next/ pnpm start # 启动生产服务器 ``` ### 9.5 代码规范 ```bash pnpm lint # ESLint 检查 ``` --- ## 10. 部署方案 ### 10.1 推荐:前后端分离部署 ``` 用户 → Nginx ├── / → Next.js (Node.js) 或静态文件 ├── /api/v1/* → hello-axum (Rust) ├── /uploads/* → hello-axum 静态文件 └── /feed.xml → hello-axum RSS ``` Nginx 配置示例: ```nginx server { listen 80; server_name blog.example.com; # 前端 location / { proxy_pass http://127.0.0.1:3001; } # 后端 API location /api/v1/ { proxy_pass http://127.0.0.1:3000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # RSS location /feed.xml { proxy_pass http://127.0.0.1:3000; } # 上传文件 location /uploads/ { proxy_pass http://127.0.0.1:3000; expires 30d; } } ``` ### 10.2 备选:前端静态导出 如果博客不需要 SSR,可以 `next export` 输出纯静态文件,用 Nginx 直接托管(无需 Node.js 进程)。 需要在 `next.config.ts` 中添加: ```ts const nextConfig: NextConfig = { output: "export", }; ``` ### 10.3 环境变量 | 变量 | 开发默认值 | 生产值 | |------|-----------|--------| | `NEXT_PUBLIC_API_URL` | `http://localhost:3000/api/v1` | `https://blog.example.com/api/v1` | --- ## 11. 安全注意事项 - **Token 存储**:Access Token 和 Refresh Token 存在 localStorage(仅限 HTTPS 环境使用) - **XSS 防护**:React 自动转义,后端已用 ammonia 清洗 HTML - **CSRF**:REST API 使用 Bearer Token 认证,无 Cookie,不受 CSRF 攻击 - **CORS**:后端已配置 `CORS_ORIGINS` 白名单,生产环境设置为前端域名 - **上传**:后端已限制文件类型(magic bytes 校验)和大小(5MB) - **Rate Limiting**:后端已对登录、注册、评论接口限流