forked from carrydela/Rs_blog_front
first commit
This commit is contained in:
450
docs/Rust Blog Backend API.openapi.json
Normal file
450
docs/Rust Blog Backend API.openapi.json
Normal file
@@ -0,0 +1,450 @@
|
||||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "Rust Blog Backend API",
|
||||
"description": "API collection for testing the Rust Blog Backend",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"tags": [],
|
||||
"paths": {
|
||||
"/api/auth/register": {
|
||||
"post": {
|
||||
"summary": "Register - Short Password",
|
||||
"deprecated": false,
|
||||
"description": "Test validation: password too short (min 8 chars)",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "application/json",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"password"
|
||||
]
|
||||
},
|
||||
"example": {
|
||||
"email": "test2@example.com",
|
||||
"password": "short"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/auth/login": {
|
||||
"post": {
|
||||
"summary": "Login - Wrong Password",
|
||||
"deprecated": false,
|
||||
"description": "Test: wrong password returns 401",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "application/json",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email",
|
||||
"password"
|
||||
]
|
||||
},
|
||||
"example": {
|
||||
"email": "test@example.com",
|
||||
"password": "wrongpassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/posts": {
|
||||
"post": {
|
||||
"summary": "Create Post - Empty Title",
|
||||
"deprecated": false,
|
||||
"description": "Test validation: empty title returns 400",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "application/json",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "Bearer {{token}}",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"published": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"content",
|
||||
"published"
|
||||
]
|
||||
},
|
||||
"example": {
|
||||
"title": "",
|
||||
"content": "Content without title",
|
||||
"published": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
},
|
||||
"get": {
|
||||
"summary": "List Posts (Page 2)",
|
||||
"deprecated": false,
|
||||
"description": "List posts - second page with 5 items per page.",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "2",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "5",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/posts/post_id": {
|
||||
"get": {
|
||||
"summary": "Get Post by ID",
|
||||
"deprecated": false,
|
||||
"description": "Get a single post by ID. No authentication required.",
|
||||
"tags": [],
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
},
|
||||
"put": {
|
||||
"summary": "Update Post (Publish)",
|
||||
"deprecated": false,
|
||||
"description": "Publish a draft post. Only updates the published field.",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "application/json",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "Bearer {{token}}",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"published": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"published"
|
||||
]
|
||||
},
|
||||
"example": {
|
||||
"published": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete Post",
|
||||
"deprecated": false,
|
||||
"description": "Soft delete a post. Requires authentication and ownership.",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "Bearer {{token}}",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/upload": {
|
||||
"post": {
|
||||
"summary": "Upload Image",
|
||||
"deprecated": false,
|
||||
"description": "Upload an image file. Requires authentication. Allowed types: jpg, jpeg, png, webp. Max size: 5MB.",
|
||||
"tags": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"example": "Bearer {{token}}",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "Select a JPG, JPEG, PNG, or WebP image (max 5MB)",
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/posts/non-existent-id": {
|
||||
"get": {
|
||||
"summary": "Get Post - Not Found",
|
||||
"deprecated": false,
|
||||
"description": "Test: getting non-existent post returns 404",
|
||||
"tags": [],
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {},
|
||||
"responses": {},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://127.0.0.1:8765",
|
||||
"description": "测试环境"
|
||||
}
|
||||
],
|
||||
"security": []
|
||||
}
|
||||
243
docs/apidev.md
Normal file
243
docs/apidev.md
Normal file
@@ -0,0 +1,243 @@
|
||||
太棒了。既然前端视觉风格(极简 + 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`。
|
||||
|
||||
```typescript
|
||||
// 基础响应包装(根据 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`
|
||||
|
||||
```typescript
|
||||
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-highlight` 或 `prismjs`。
|
||||
* **样式定制**:
|
||||
* 为了符合极简风格,必须重写 `<pre>` 和 `<code>` 的样式。
|
||||
* **代码块背景**: `#fafafa` (极淡灰)。
|
||||
* **字体**: `JetBrains Mono`。
|
||||
* **边框**: 无边框,直角。
|
||||
|
||||
|
||||
|
||||
```typescript
|
||||
// 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. 上传完成:替换为 ``。
|
||||
|
||||
|
||||
* **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.ts` 和 `tailwind.config.js` 开始。
|
||||
217
docs/design.md
Normal file
217
docs/design.md
Normal file
@@ -0,0 +1,217 @@
|
||||
这是为您定制的 **《极简主义博客前端设计规范 v1.0 (The Minimalist Tech Spec)》**。
|
||||
|
||||
这份文档不仅是开发指南,更是视觉准则。它将指导您如何使用 Tailwind CSS 构建一个冷峻、理性且充满科技感的 AI 博客。
|
||||
|
||||
---
|
||||
|
||||
# 📐 前端设计规范 (Frontend Design System)
|
||||
|
||||
**项目名称**: Minimalist AI Blog
|
||||
**核心理念**: 内容即界面 (Content is Interface)
|
||||
**视觉风格**: 瑞士国际主义 (Swiss Style) + 科技极简 (Tech Utilitarianism)
|
||||
|
||||
---
|
||||
|
||||
## 1. 基础系统 (Foundations)
|
||||
|
||||
### 1.1 色彩体系 (Color Palette)
|
||||
|
||||
我们摒弃所有非功能性的色彩。界面只存在黑、白、灰。唯一的色彩将来自**文章配图**和**代码高亮**。
|
||||
|
||||
| 语义名称 | Tailwind 类名 | Hex 值 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| **Canvas** | `bg-white` | `#ffffff` | 页面背景,卡片背景 |
|
||||
| **Ink** | `text-black` | `#000000` | 标题,主要按钮,强强调 |
|
||||
| **Graphite** | `text-neutral-800` | `#262626` | 正文阅读色 (纯黑阅读易疲劳) |
|
||||
| **Ash** | `text-neutral-500` | `#737373` | 辅助信息,日期,元数据 |
|
||||
| **Vapor** | `bg-neutral-50` | `#fafafa` | 极淡背景 (代码块,引用) |
|
||||
| **Line** | `border-neutral-200` | `#e5e5e5` | 结构分割线 (极少使用) |
|
||||
| **Error** | `text-red-600` | `#dc2626` | 错误状态 (仅用于表单反馈) |
|
||||
|
||||
### 1.2 排版系统 (Typography)
|
||||
|
||||
全站强制使用 **Inter**。通过字重 (Weight) 和字间距 (Tracking) 的极端对比来建立层级,而非依赖颜色。
|
||||
|
||||
* **字体栈**: `font-sans` -> `Inter, system-ui, sans-serif`
|
||||
* **字重策略**:
|
||||
* **标题**: `font-bold` (700) 或 `font-medium` (500)
|
||||
* **正文**: `font-normal` (400)
|
||||
* **元数据**: `font-medium` (500) + 全大写
|
||||
|
||||
|
||||
|
||||
| 样式名称 | 大小 (Size) | 行高 (Leading) | 字间距 (Tracking) | Tailwind 组合 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Display** | 72px (6xl) | 1.1 | -0.04em | `text-6xl font-bold tracking-tighter leading-tight` |
|
||||
| **Heading 1** | 48px (5xl) | 1.2 | -0.03em | `text-5xl font-bold tracking-tight leading-tight` |
|
||||
| **Heading 2** | 30px (3xl) | 1.3 | -0.02em | `text-3xl font-semibold tracking-tight` |
|
||||
| **Body** | 18px (lg) | 1.8 | -0.01em | `text-lg font-normal leading-relaxed text-neutral-800` |
|
||||
| **Caption** | 12px (xs) | 1.5 | 0.05em | `text-xs font-medium tracking-wide uppercase text-neutral-500` |
|
||||
|
||||
### 1.3 网格与间距 (Grid & Spacing)
|
||||
|
||||
* **容器**: 最大宽度 `max-w-screen-xl` (1280px)。
|
||||
* **网格**: 12 列网格,列间距 `gap-8` (32px)。
|
||||
* **呼吸感**: 垂直方向的留白必须奢侈。
|
||||
* **Section Spacing**: `py-24` (96px) 或 `py-32` (128px)。
|
||||
* **Component Spacing**: `mb-12` (48px)。
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 2. 组件规范 (Components)
|
||||
|
||||
### 2.1 图片处理 (Imagery)
|
||||
|
||||
图片是极简页面中唯一的“重”元素。
|
||||
|
||||
* **形状**: **严格直角** (No Border Radius)。
|
||||
* **比例**: 统一使用 `aspect-video` (16:9) 或 `aspect-[3/2]`。
|
||||
* **边框**: 无边框。图片直接与背景接触。
|
||||
* **蒙版**: 默认原图。Hover 时可叠加 5% 的黑色遮罩 (`bg-black/5`) 以增加质感。
|
||||
|
||||
```html
|
||||
<figure class="relative w-full aspect-video overflow-hidden bg-neutral-100">
|
||||
<img src="..." class="object-cover w-full h-full" alt="Tech" />
|
||||
</figure>
|
||||
|
||||
```
|
||||
|
||||
### 2.2 按钮与交互 (Buttons & Interactions)
|
||||
|
||||
拒绝实体色块按钮。使用 **“幽灵按钮” (Ghost Button)** 或 **“文本链接”**。
|
||||
|
||||
* **Primary Button**: 黑框,白底,黑字。Hover 变黑底白字。
|
||||
* `border border-black px-6 py-3 text-sm font-medium hover:bg-black hover:text-white transition-colors duration-300`
|
||||
|
||||
|
||||
* **Text Link**: 纯文字,底部有一条 1px 的黑线。Hover 时线消失或变粗。
|
||||
* `border-b border-black pb-0.5 hover:border-transparent transition-colors`
|
||||
|
||||
|
||||
* **Input Field**: 无背景,仅保留底部边框。
|
||||
* `w-full border-b border-neutral-200 py-3 bg-transparent focus:border-black focus:outline-none transition-colors`
|
||||
|
||||
|
||||
|
||||
### 2.3 代码块 (Code Blocks)
|
||||
|
||||
作为技术博客的核心,代码块必须干净。
|
||||
|
||||
* **Theme**: 推荐 GitHub Light 或类似的高对比度浅色主题。
|
||||
* **容器**: 浅灰色背景 `#fafafa`,无边框,直角。
|
||||
* **字体**: JetBrains Mono 或 Fira Code。
|
||||
|
||||
---
|
||||
|
||||
## 3. Tailwind 配置文件 (Configuration)
|
||||
|
||||
请将以下代码直接覆盖您的 `tailwind.config.js`。
|
||||
|
||||
```javascript
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const defaultTheme = require('tailwindcss/defaultTheme')
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
// 1. 字体系统:强制 Inter
|
||||
fontFamily: {
|
||||
sans: ['Inter', ...defaultTheme.fontFamily.sans],
|
||||
mono: ['JetBrains Mono', 'Fira Code', ...defaultTheme.fontFamily.mono],
|
||||
},
|
||||
// 2. 扩展间距:为了极致的留白
|
||||
spacing: {
|
||||
'18': '4.5rem', // 72px
|
||||
'24': '6rem', // 96px
|
||||
'32': '8rem', // 128px
|
||||
},
|
||||
// 3. 极简色板
|
||||
colors: {
|
||||
neutral: {
|
||||
50: '#fafafa',
|
||||
100: '#f5f5f5',
|
||||
200: '#e5e5e5', // 边框
|
||||
300: '#d4d4d4',
|
||||
400: '#a3a3a3',
|
||||
500: '#737373', // 辅助文字
|
||||
600: '#525252',
|
||||
700: '#404040',
|
||||
800: '#262626', // 正文
|
||||
900: '#171717',
|
||||
950: '#0a0a0a', // 接近纯黑
|
||||
}
|
||||
},
|
||||
// 4. 排版微调
|
||||
letterSpacing: {
|
||||
tighter: '-0.04em',
|
||||
tight: '-0.02em',
|
||||
normal: '-0.01em',
|
||||
wide: '0.02em',
|
||||
widest: '0.08em', // 用于全大写标签
|
||||
},
|
||||
lineHeight: {
|
||||
relaxed: '1.8', // 黄金阅读行高
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'), // 必须安装: npm install -D @tailwindcss/typography
|
||||
],
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 典型页面布局示例 (Layout Mockup)
|
||||
|
||||
### 首页文章列表 (The "Grid" List)
|
||||
|
||||
这是一个结合了 **Inter 字体** 和 **锐利图片** 的列表项设计。
|
||||
|
||||
```html
|
||||
<article class="group grid grid-cols-12 gap-6 md:gap-12 mb-24 cursor-pointer">
|
||||
|
||||
<div class="col-span-12 md:col-span-5 relative">
|
||||
<div class="aspect-[3/2] w-full overflow-hidden bg-neutral-100">
|
||||
<img
|
||||
src="/api/placeholder/800/600"
|
||||
alt="AI Neural Network"
|
||||
class="h-full w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105 group-hover:opacity-90"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-7 flex flex-col justify-center">
|
||||
|
||||
<div class="flex items-center space-x-3 mb-6">
|
||||
<span class="text-xs font-bold tracking-widest text-black uppercase border border-black px-2 py-1">
|
||||
Algorithm
|
||||
</span>
|
||||
<span class="text-xs font-medium tracking-wide text-neutral-400 uppercase">
|
||||
Oct 24, 2026
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 class="text-3xl md:text-5xl font-bold tracking-tighter text-black mb-6 leading-[1.1] group-hover:underline decoration-1 underline-offset-8">
|
||||
Optimizing Transformer Attention Mechanisms
|
||||
</h2>
|
||||
|
||||
<p class="text-lg text-neutral-600 leading-relaxed mb-8 md:w-10/12 line-clamp-3">
|
||||
Deep dive into the architecture of FlashAttention-2. We explore how memory hierarchy awareness can significantly speed up training for large language models.
|
||||
</p>
|
||||
|
||||
<div class="flex items-center text-sm font-semibold text-black group-hover:translate-x-2 transition-transform duration-300">
|
||||
Read Analysis <span class="ml-2">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
```
|
||||
|
||||
**下一步**:如果您确认这份风格文档(尤其是对字体 Inter、图片直角处理、黑白灰配色)满意,我将为您生成第二部分:**前后端对接集成文档**。
|
||||
Reference in New Issue
Block a user