# 🔌 后端对接集成文档 Part 2 (API Integration Spec - Extended) **延续文档**: `apidev_part1.md` **覆盖功能**: 注册、文章更新/删除、认证管理、草稿流程 --- ## 1. 补充数据模型 (Extended TypeScript Interfaces) 在 `src/types/api.ts` 中追加以下接口: ```typescript // 1. 注册模型 (Registration) export interface RegisterRequest { email: string; password: string; // 最小 8 字符 } export interface RegisterResponse { message: string; user: { id: string; email: string; }; } // 2. 文章更新模型 (Post Update) export interface UpdatePostRequest { title?: string; content?: string; published?: boolean; } // 3. 删除响应 (Delete Response) export interface DeleteResponse { message: string; deleted_at?: string; // ISO 8601 - 软删除时间戳 } // 4. 分页响应包装 (Paginated Response) export interface PaginatedResponse { data: T[]; pagination: { page: number; limit: number; total: number; total_pages: number; }; } // 5. API 错误模型 (API Error) export interface ApiError { status: number; message: string; field?: string; // 验证错误时标识具体字段 } ``` --- ## 2. API 客户端扩展 (Extended Service Layer) 在 `src/lib/api.ts` 中追加以下方法: ```typescript export const api = { // === 原有方法保持不变 === // Auth - 新增注册 register: (data: RegisterRequest) => request('/auth/register', { method: 'POST', body: JSON.stringify(data), }), // Auth - 登出 (清理本地 Token) logout: () => { localStorage.removeItem('auth_token'); localStorage.removeItem('user_info'); // 无需调用后端,JWT 无状态 }, // Posts - 更新文章 updatePost: (id: string, data: UpdatePostRequest) => request(`/posts/${id}`, { method: 'PUT', body: JSON.stringify(data), }), // Posts - 删除文章 (软删除) deletePost: (id: string) => request(`/posts/${id}`, { method: 'DELETE', }), // Posts - 发布/取消发布 (便捷方法) publishPost: (id: string) => request(`/posts/${id}`, { method: 'PUT', body: JSON.stringify({ published: true }), }), unpublishPost: (id: string) => request(`/posts/${id}`, { method: 'PUT', body: JSON.stringify({ published: false }), }), }; ``` --- ## 3. 功能模块集成策略 (续) ### 3.4 用户注册 (Registration Flow) **极简注册表单应与登录共享视觉语言。** * **API**: `POST /api/auth/register` * **必填字段**: `email`, `password` * **验证规则**: * Email: 有效邮箱格式 * Password: 最少 8 字符 **UI 交互映射:** | 用户行为 | 前端反馈 | |---------|---------| | 输入密码 < 8 字符 | 密码框下边框变灰 `border-neutral-300`,显示字符计数 `5/8` | | 提交时密码过短 | 边框变红 + 微震动,显示 "Min 8 characters" | | Email 已存在 (409) | Email 框下方显示 "Already registered" | | 注册成功 | 按钮文字变为 "Created",1.5s 后跳转登录页 | **组件实现建议:** ```typescript // src/components/RegisterForm.tsx const [charCount, setCharCount] = useState(0); setCharCount(e.target.value.length)} className={cn( "border-b-2 transition-colors", charCount < 8 && charCount > 0 ? "border-neutral-300" : "border-black" )} /> {charCount > 0 && charCount < 8 && ( {charCount}/8 )} ``` --- ### 3.5 文章编辑与更新 (Edit Post Flow) **编辑器应复用发布页面,但预填充现有数据。** * **API**: `PUT /api/posts/{id}` * **可更新字段**: `title`, `content`, `published` * **权限**: 需要 Bearer Token + 文章所有权 **页面路由建议:** ``` /posts/[id]/edit -> 编辑页面 ``` **数据加载流程:** ```typescript // src/app/posts/[id]/edit/page.tsx useEffect(() => { const loadPost = async () => { try { const post = await api.getPostById(id); setTitle(post.title); setContent(post.content); setPublished(post.published); } catch (err) { if (err.status === 404) router.push('/404'); if (err.status === 401) router.push('/login'); } }; loadPost(); }, [id]); ``` **保存策略:** | 策略 | 实现方式 | |-----|---------| | 手动保存 | 点击 "Save" 按钮触发 `PUT` | | 自动保存 (可选) | `debounce` 300ms 后自动调用 `updatePost`,右上角显示 "Saving..." → "Saved" | **极简自动保存指示器:** ```typescript // 不要用 spinner,用文字状态 const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved'>('idle'); {saveStatus === 'saving' && Saving...} {saveStatus === 'saved' && Saved} ``` --- ### 3.6 文章删除 (Delete Post Flow) **删除是破坏性操作,但极简设计不使用模态确认框。** * **API**: `DELETE /api/posts/{id}` * **行为**: 软删除 (数据库标记 `deleted_at`) * **权限**: 需要 Bearer Token + 文章所有权 **极简确认模式 (Inline Confirmation):** ```typescript // 第一次点击:文字变化 // 第二次点击:执行删除 const [confirmDelete, setConfirmDelete] = useState(false); ``` **删除后行为:** | 场景 | 反馈 | |-----|-----| | 在列表页删除 | 该行淡出 (`opacity-0 transition-opacity`),然后移除 | | 在详情页删除 | 显示 "Deleted",2秒后跳转首页 | --- ### 3.7 草稿与发布状态管理 (Draft/Published Toggle) **文章有两种状态:草稿 (draft) 和已发布 (published)。** * **草稿**: `published: false` — 仅作者可见 * **已发布**: `published: true` — 公开可见 **状态切换 UI:** ```typescript // 极简 Toggle:不用开关组件,用文字按钮 ``` **列表页草稿标识:** ```typescript // 草稿文章标题后显示灰色 (Draft) 标记

{post.title} {!post.published && ( (Draft) )}

``` --- ## 4. 认证状态管理 (Auth State Management) ### 4.1 Token 存储策略 ```typescript // src/lib/auth.ts export const auth = { // 存储 Token setToken: (token: string) => { localStorage.setItem('auth_token', token); }, // 获取 Token getToken: () => localStorage.getItem('auth_token'), // 检查是否已登录 isAuthenticated: () => !!localStorage.getItem('auth_token'), // 清除认证信息 clear: () => { localStorage.removeItem('auth_token'); localStorage.removeItem('user_info'); }, // 存储用户信息 setUser: (user: { id: string; email: string }) => { localStorage.setItem('user_info', JSON.stringify(user)); }, // 获取用户信息 getUser: () => { const stored = localStorage.getItem('user_info'); return stored ? JSON.parse(stored) : null; }, }; ``` ### 4.2 全局认证守卫 (Auth Guard) ```typescript // src/components/AuthGuard.tsx 'use client'; import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { auth } from '@/lib/auth'; export function AuthGuard({ children }: { children: React.ReactNode }) { const router = useRouter(); useEffect(() => { if (!auth.isAuthenticated()) { router.replace('/login'); } }, [router]); if (!auth.isAuthenticated()) { return null; // 或返回骨架屏 } return <>{children}; } // 使用方式:包裹需要认证的页面 // src/app/posts/new/page.tsx export default function NewPostPage() { return ( ); } ``` ### 4.3 401 全局拦截 更新 `src/lib/api.ts` 中的 `request` 函数: ```typescript async function request(endpoint: string, options: RequestInit = {}): Promise { // ... 原有代码 ... if (!response.ok) { // 401 时自动清除 Token 并跳转 if (response.status === 401) { auth.clear(); // 使用 window.location 而非 router,确保完全刷新状态 window.location.href = '/login'; } throw { status: response.status, message: response.statusText }; } // ... 原有代码 ... } ``` --- ## 5. 扩展状态反馈映射表 (Extended UI Feedback) | 后端状态 | 场景 | 前端极简反馈 | |---------|-----|-------------| | **201 Created** | 注册成功 | 按钮文字变为 "Created",跳转登录页 | | **409 Conflict** | Email 已存在 | Email 框下方显示 "Already registered" | | **400 Bad Request** | 密码过短 | 显示字符计数 `5/8`,边框变红 | | **200 OK** | 文章更新成功 | 右上角显示 "Saved" (2秒消失) | | **200 OK** | 文章删除成功 | 当前行/页面淡出,跳转 | | **403 Forbidden** | 无权编辑他人文章 | 显示 "Not your post",禁用编辑按钮 | | **422 Unprocessable** | 文章内容不合法 | 对应字段边框变红 | --- ## 6. 页面路由规划 (Route Structure) ``` / # 首页 - 文章列表 /login # 登录 /register # 注册 (新增) /posts/[id] # 文章详情 /posts/[id]/edit # 编辑文章 (新增,需认证) /posts/new # 发布新文章 (需认证) /drafts # 我的草稿列表 (新增,需认证) /404 # 404 页面 ``` --- ## 7. 开发路线图 (续) 接续 Part 1 的开发顺序: 6. **Step 6: Register Page** — 完成注册页面,复用登录页样式。 7. **Step 7: Edit Flow** — 完成文章编辑功能,实现 `PUT /posts/:id`。 8. **Step 8: Delete Flow** — 完成删除功能,实现 Inline Confirmation 模式。 9. **Step 9: Draft Management** — 完成草稿列表页,支持发布/取消发布切换。 10. **Step 10: Auth Guard** — 添加全局认证守卫,保护需要登录的路由。 11. **Step 11: Polish** — 统一错误处理、Loading 状态、过渡动画。 --- ## 8. 完整 API 客户端 (Final api.ts) 汇总 Part 1 + Part 2 的完整 API 客户端: ```typescript // src/lib/api.ts import { auth } from './auth'; import type { LoginRequest, RegisterRequest, AuthResponse, RegisterResponse, Post, UpdatePostRequest, DeleteResponse, PageParams, } from '@/types/api'; const BASE_URL = 'http://127.0.0.1:8765/api'; async function request(endpoint: string, options: RequestInit = {}): Promise { const token = auth.getToken(); const headers: HeadersInit = { 'Content-Type': 'application/json', ...(token && { 'Authorization': `Bearer ${token}` }), ...options.headers, }; const response = await fetch(`${BASE_URL}${endpoint}`, { ...options, headers, }); if (!response.ok) { if (response.status === 401) { auth.clear(); window.location.href = '/login'; } const errorBody = await response.json().catch(() => ({})); throw { status: response.status, message: response.statusText, ...errorBody }; } if (response.status === 204) return {} as T; return response.json(); } export const api = { // ===== Auth ===== login: (data: LoginRequest) => request('/auth/login', { method: 'POST', body: JSON.stringify(data), }), register: (data: RegisterRequest) => request('/auth/register', { method: 'POST', body: JSON.stringify(data), }), logout: () => auth.clear(), // ===== Posts - Read ===== getPosts: (params: PageParams) => { const query = new URLSearchParams({ page: params.page.toString(), limit: params.limit.toString() }); return request(`/posts?${query}`); }, getPostById: (id: string) => request(`/posts/${id}`), // ===== Posts - Write ===== createPost: (data: Partial) => request('/posts', { method: 'POST', body: JSON.stringify(data), }), updatePost: (id: string, data: UpdatePostRequest) => request(`/posts/${id}`, { method: 'PUT', body: JSON.stringify(data), }), deletePost: (id: string) => request(`/posts/${id}`, { method: 'DELETE', }), // ===== Posts - Convenience ===== publishPost: (id: string) => request(`/posts/${id}`, { method: 'PUT', body: JSON.stringify({ published: true }), }), unpublishPost: (id: string) => request(`/posts/${id}`, { method: 'PUT', body: JSON.stringify({ published: false }), }), // ===== Upload ===== uploadImage: async (file: File) => { const token = auth.getToken(); const formData = new FormData(); formData.append('file', file); const response = await fetch(`${BASE_URL}/upload`, { method: 'POST', headers: { ...(token && { 'Authorization': `Bearer ${token}` }) }, body: formData }); if (!response.ok) { if (response.status === 401) { auth.clear(); window.location.href = '/login'; } throw new Error('Upload failed'); } return response.json(); } }; ``` --- 现在 Part 1 + Part 2 完整覆盖了你的 OpenAPI 规范中的所有端点。