Files
raisfast/docs/web-guide.md
T
2026-04-13 11:40:37 +08:00

539 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端技术方案
## 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 | v4CSS-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 # 根 layoutGeist 字体 + 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 # 全局 ProvidersReact Query + Toaster
│ ├── lib/
│ │ ├── api.ts # API 封装(自动 token / refresh / i18n
│ │ └── utils.ts # shadcn cn() 工具函数
│ ├── stores/
│ │ └── auth.ts # zustand auth storelocalStorage 持久化)
│ └── 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<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 后端响应格式
```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 中包裹
<ThemeProvider attribute="class" defaultTheme="system">
```
### 8.3 字体
- 正文字体:Geist SansNext.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 <component-name>
```
### 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**:后端已对登录、注册、评论接口限流