Files
Rs_blog_front/docs/apidev_part1.md
2026-02-12 00:34:21 +08:00

7.9 KiB
Raw Blame History

太棒了。既然前端视觉风格(极简 + Inter + 锐利图片)已经确认,接下来我们进入 逻辑层

这是第二份文档:《Rust Backend API 集成指南 (Backend Integration Spec)》

这份文档将指导你如何将那个“冷峻”的界面与 Rust 后端进行优雅对接。我们不仅关注数据获取,更关注数据如何驱动极简交互(例如:如何用数据加载状态代替复杂的 Loading 动画)。


🔌 后端对接集成文档 (API Integration Spec)

后端服务: Rust Blog Backend Base URL: http://127.0.0.1:8765 协议: REST / JSON 认证: Bearer Token


1. 数据模型定义 (TypeScript Interfaces)

为了确保前端代码的健壮性,我们需要根据 OpenAPI 文档定义严格的 TypeScript 接口。建议存放在 src/types/api.ts

// 基础响应包装(根据 Rust 后端习惯,通常可能直接返回数据或包裹在 data 字段)
// 针对此 API 文档的直接映射:
export interface ApiResponse<T> {
  [key: string]: any; 
  // 注意:实际调试时请确认后端是否包裹了 { data: ... } 结构
}

// 1. 认证模型 (Auth)
export interface LoginRequest {
  email: string;
  password: string;
}

export interface AuthResponse {
  token: string; // 假设后端返回 JWT 字段
  user: {
    id: string;
    email: string;
  };
}

// 2. 文章模型 (Post)
export interface Post {
  id: string;          // 对应 Rust 的 UUID
  title: string;       // 对应 OpenAPI schema
  content: string;     // Markdown 原始内容
  published: boolean;
  created_at?: string; // ISO 8601 Date String
  cover_image?: string; // 文章封面图 URL (可选)
}

// 3. 分页参数 (Pagination)
export interface PageParams {
  page: number; // e.g. 1
  limit: number; // e.g. 10
}


2. API 客户端封装 (Service Layer)

为了保持组件的“极简”,不要在组件内部直接写 fetch。建议封装一个 apiClient

文件: src/lib/api.ts

const BASE_URL = 'http://127.0.0.1:8765/api';

// 极简的 Fetch 封装,自动处理 Token 和 错误
async function request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const token = localStorage.getItem('auth_token');
  
  const headers = {
    'Content-Type': 'application/json',
    ...(token && { 'Authorization': `Bearer ${token}` }),
    ...options.headers,
  };

  const response = await fetch(`${BASE_URL}${endpoint}`, {
    ...options,
    headers,
  });

  // 极简错误处理:直接抛出状态码,由 UI 层决定是变红框还是显示线条
  if (!response.ok) {
    throw { status: response.status, message: response.statusText };
  }

  // 处理 204 No Content
  if (response.status === 204) return {} as T;

  return response.json();
}

// 核心服务导出
export const api = {
  // Auth
  login: (data: LoginRequest) => request<AuthResponse>('/auth/login', {
    method: 'POST',
    body: JSON.stringify(data),
  }),
  
  // Posts
  getPosts: (params: PageParams) => {
    const query = new URLSearchParams({ 
      page: params.page.toString(), 
      limit: params.limit.toString() 
    });
    return request<Post[]>(`/posts?${query}`);
  },
  
  getPostById: (id: string) => request<Post>(`/posts/${id}`),
  
  createPost: (data: Partial<Post>) => request<Post>('/posts', {
    method: 'POST',
    body: JSON.stringify(data),
  }),
  
  // Upload (特殊处理 Multipart)
  uploadImage: async (file: File) => {
    const token = localStorage.getItem('auth_token');
    const formData = new FormData();
    formData.append('file', file);
    
    const response = await fetch(`${BASE_URL}/upload`, {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${token}` }, // 不设置 Content-Type让浏览器自动设置 boundary
      body: formData
    });
    
    if (!response.ok) throw new Error('Upload failed');
    return response.json(); // 返回 { url: "..." }
  }
};


3. 功能模块集成策略

3.1 首页文章列表 (Infinite Scroll / Pagination)

极简主义的数据加载不应有“旋转的菊花”

  • 加载状态 (Loading): 使用骨架屏 (Skeleton)

  • 在数据回来之前,渲染 3 个灰色的方块 (bg-neutral-100),位置与图片和标题完全重合。这能保持页面的“稳定感”,不会因为数据加载而发生布局跳动。

  • API: GET /api/posts?page=1&limit=5

  • 错误状态: 如果 API 挂了,不要弹窗。在列表顶部显示一行极小的灰色文字:"Could not retrieve latest transmission."

3.2 文章详情与 Markdown 渲染

技术博客的核心是代码阅读体验。

  • API: GET /api/posts/{id}
  • Markdown 解析: 推荐使用 react-markdown
  • 代码高亮: 使用 rehype-highlightprismjs
  • 样式定制:
  • 为了符合极简风格,必须重写 <pre><code> 的样式。
  • 代码块背景: #fafafa (极淡灰)。
  • 字体: JetBrains Mono
  • 边框: 无边框,直角。
// Markdown 组件配置示例
<ReactMarkdown
  rehypePlugins={[rehypeHighlight]}
  components={{
    // 覆盖默认图片样式,确保圆角为 0
    img: ({node, ...props}) => (
      <img {...props} className="w-full h-auto aspect-video object-cover my-8 border border-neutral-100" />
    ),
    // 覆盖代码块
    code: ({node, inline, className, children, ...props}) => {
      return !inline ? (
        <pre className="bg-neutral-50 p-6 overflow-x-auto text-sm my-8">
          <code className={className} {...props}>{children}</code>
        </pre>
      ) : (
        <code className="bg-neutral-100 px-1 py-0.5 text-sm font-mono text-red-600" {...props}>{children}</code>
      )
    }
  }}
>
  {post.content}
</ReactMarkdown>

3.3 管理员发布文章 (Writer Mode)

这是一个关键的交互点。我们需要一个所见即所得 (WYSIWYG) 但又极简的编辑器。

  • 布局: 左侧 Markdown 输入框 (Monospace),右侧实时预览。
  • 图片上传集成:
  1. 用户将截图直接拖入 Markdown 输入框。
  2. 触发 onDrop 事件 -> 调用 api.uploadImage(file)
  3. 上传中:光标处显示 ![Uploading...] (灰色)。
  4. 上传完成:替换为 ![Alt text](http://.../image.png)
  • API 调用:
  • 点击 "Publish" -> POST /api/posts -> 成功后跳转至详情页。
  • 验证错误 (400 Empty Title) -> 标题输入框下方的黑线变红 (border-red-600),不显示文字提示,或者仅显示极小的 "Required"。

4. 状态反馈映射表 (UI Feedback)

在极简设计中,我们利用微交互来反馈后端状态,而非模态框。

后端状态 (Status) 场景 (Context) 前端极简反馈 (Visual Feedback)
200 OK 登录成功 按钮文字从 "Enter" 变为 "Welcome",淡出并跳转。
200 OK 文章发布成功 页面右上角显示 "Saved." (纯文本3秒消失)。
401 Unauthorized Token 过期 自动静默跳转至 /login,无提示。
400 Bad Request 密码太短 密码框下边框变红,同时有一个极轻微的左右震动动画 (Shake)。
404 Not Found 文章 ID 错误 页面中心显示巨大的 "404",配文 "Signal Lost"。
500 Server Error 服务器崩溃 页面顶部出现 2px 高的红色静止线条。

5. 开发路线图 (Roadmap)

建议按以下顺序开发,以最快速度跑通 MVP

  1. Step 1: API Client: 搭建 api.ts,跑通 fetch 封装。
  2. Step 2: Read-Only: 完成首页列表 (GET /posts) 和详情页 (GET /posts/:id)。此时数据可以是 Mock 的。
  3. Step 3: Markdown: 调优 react-markdown 样式,确保代码块和图片符合极简规范。
  4. Step 4: Auth: 完成极简登录页 (POST /login)。
  5. Step 5: Editor: 完成发布功能和图片上传 (POST /upload + Drag & Drop)。

准备好开始编码了吗?建议从配置 api.tstailwind.config.js 开始。