mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-23 16:02:25 +00:00
17 KiB
17 KiB
前端技术方案
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.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<T>(path) |
GET 请求 | application/json |
api.post<T>(path, body) |
POST JSON | application/json |
api.put<T>(path, body) |
PUT JSON | application/json |
api.delete<T>(path) |
DELETE | application/json |
api.upload<T>(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 后端响应格式
// 成功
{ "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 配置:
/* 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 实现:
import { ThemeProvider } from "next-themes";
// 在 Providers 中包裹
<ThemeProvider attribute="class" defaultTheme="system">
8.3 字体
- 正文字体:Geist Sans(Next.js 内置)
- 代码字体:Geist Mono
9. 开发流程
9.1 环境准备
cd web
pnpm install # 安装依赖
cp .env.local .env # 确认 API 地址
9.2 启动开发服务器
# 终端 1:启动后端
cd /path/to/hello-axum
cargo run
# 终端 2:启动前端
cd web
pnpm dev # 默认 http://localhost:3001
9.3 添加 shadcn 组件
pnpm dlx shadcn@latest add <component-name>
9.4 构建
pnpm build # 生产构建,输出到 .next/
pnpm start # 启动生产服务器
9.5 代码规范
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 配置示例:
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 中添加:
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:后端已对登录、注册、评论接口限流