phase-6-ui-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePhase 6: UI Implementation + API Integration
第六阶段:UI实现 + API集成
Actual UI implementation and API integration
实际UI实现与API集成
Purpose
目标
Implement actual screens using design system components and integrate with APIs.
使用设计系统组件实现实际页面,并与API集成。
What to Do in This Phase
本阶段工作内容
- Page Implementation: Develop each screen
- State Management: Handle client state
- API Integration: Call backend APIs
- Error Handling: Handle loading and error states
- 页面实现:开发每个页面
- 状态管理:处理客户端状态
- API集成:调用后端API
- 错误处理:处理加载和错误状态
Deliverables
交付物
src/
├── pages/ # Page components
│ ├── index.tsx
│ ├── login.tsx
│ └── ...
├── features/ # Feature-specific components
│ ├── auth/
│ ├── product/
│ └── ...
└── hooks/ # API call hooks
├── useAuth.ts
└── useProducts.ts
docs/03-analysis/
└── ui-qa.md # QA resultssrc/
├── pages/ # 页面组件
│ ├── index.tsx
│ ├── login.tsx
│ └── ...
├── features/ # 功能专属组件
│ ├── auth/
│ ├── product/
│ └── ...
└── hooks/ # API调用钩子
├── useAuth.ts
└── useProducts.ts
docs/03-analysis/
└── ui-qa.md # QA结果PDCA Application
PDCA循环应用
- Plan: Define screens/features to implement
- Design: Component structure, state management design
- Do: UI implementation + API integration
- Check: Zero Script QA
- Act: Fix bugs and proceed to Phase 7
- 计划:定义需要实现的页面/功能
- 设计:组件结构、状态管理设计
- 执行:UI实现 + API集成
- 检查:零脚本QA
- 处理:修复问题并进入第七阶段
Level-wise Application
分级别应用
| Level | Application Method |
|---|---|
| Starter | Static UI only (no API integration) |
| Dynamic | Full integration |
| Enterprise | Full integration + optimization |
| 级别 | 应用方式 |
|---|---|
| 入门级 | 仅静态UI(无API集成) |
| 动态级 | 完整集成 |
| 企业级 | 完整集成 + 优化 |
API Client Architecture
API客户端架构
Why is a Centralized API Client Needed?
为什么需要集中式API客户端?
| Problem (Scattered API Calls) | Solution (Centralized Client) |
|---|---|
| Duplicate error handling logic | Common error handler |
| Distributed auth token handling | Automatic token injection |
| Inconsistent response formats | Standardized response types |
| Multiple changes when endpoint changes | Single point of management |
| Difficult testing/mocking | Easy mock replacement |
| 问题(分散式API调用) | 解决方案(集中式客户端) |
|---|---|
| 重复的错误处理逻辑 | 通用错误处理器 |
| 分散的认证令牌处理 | 自动令牌注入 |
| 不一致的响应格式 | 标准化响应类型 |
| 端点变更时需多处修改 | 单点管理 |
| 测试/模拟困难 | 轻松替换模拟数据 |
3-Layer API Client Structure
三层API客户端结构
┌─────────────────────────────────────────────────────────┐
│ UI Components │
│ (pages, features, hooks) │
├─────────────────────────────────────────────────────────┤
│ Service Layer │
│ (Domain-specific API call functions) │
│ authService, productService, orderService, ... │
├─────────────────────────────────────────────────────────┤
│ API Client Layer │
│ (Common settings, interceptors, error handling) │
│ apiClient (axios/fetch wrapper) │
└─────────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────────┐
│ UI组件 │
│ (pages, features, hooks) │
├─────────────────────────────────────────────────────────┤
│ 服务层 │
│ (领域专属的API调用函数) │
│ authService, productService, orderService, ... │
├─────────────────────────────────────────────────────────┤
│ API客户端层 │
│ (通用配置、拦截器、错误处理) │
│ apiClient (axios/fetch 封装) │
└─────────────────────────────────────────────────────────┘Folder Structure
目录结构
src/
├── lib/
│ └── api/
│ ├── client.ts # API client (axios/fetch wrapper)
│ ├── interceptors.ts # Request/response interceptors
│ └── error-handler.ts # Error handling logic
├── services/
│ ├── auth.service.ts # Auth-related APIs
│ ├── product.service.ts # Product-related APIs
│ └── order.service.ts # Order-related APIs
├── types/
│ ├── api.types.ts # Common API types
│ ├── auth.types.ts # Auth domain types
│ └── product.types.ts # Product domain types
└── hooks/
├── useAuth.ts # Hooks using Service
└── useProducts.tssrc/
├── lib/
│ └── api/
│ ├── client.ts # API客户端(axios/fetch封装)
│ ├── interceptors.ts # 请求/响应拦截器
│ └── error-handler.ts # 错误处理逻辑
├── services/
│ ├── auth.service.ts # 认证相关API
│ ├── product.service.ts # 产品相关API
│ └── order.service.ts # 订单相关API
├── types/
│ ├── api.types.ts # 通用API类型
│ ├── auth.types.ts # 认证领域类型
│ └── product.types.ts # 产品领域类型
└── hooks/
├── useAuth.ts # 使用服务的钩子
└── useProducts.tsAPI Client Implementation
API客户端实现
1. Basic API Client (lib/api/client.ts)
1. 基础API客户端(lib/api/client.ts)
typescript
// lib/api/client.ts
import { ApiError, ApiResponse } from '@/types/api.types';
const BASE_URL = process.env.NEXT_PUBLIC_API_URL || '/api';
interface RequestConfig extends RequestInit {
params?: Record<string, string>;
}
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
private async request<T>(
endpoint: string,
config: RequestConfig = {}
): Promise<ApiResponse<T>> {
const { params, ...init } = config;
// URL parameter handling
const url = new URL(`${this.baseUrl}${endpoint}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
// Default header settings
const headers = new Headers(init.headers);
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
// Automatic auth token injection
const token = this.getAuthToken();
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
try {
const response = await fetch(url.toString(), {
...init,
headers,
});
return this.handleResponse<T>(response);
} catch (error) {
throw this.handleNetworkError(error);
}
}
private async handleResponse<T>(response: Response): Promise<ApiResponse<T>> {
const data = await response.json();
if (!response.ok) {
throw new ApiError(
data.error?.code || 'UNKNOWN_ERROR',
data.error?.message || 'An error occurred',
response.status,
data.error?.details
);
}
return data as ApiResponse<T>;
}
private handleNetworkError(error: unknown): ApiError {
if (error instanceof TypeError && error.message === 'Failed to fetch') {
return new ApiError('NETWORK_ERROR', 'Please check your network connection.', 0);
}
return new ApiError('UNKNOWN_ERROR', 'An unknown error occurred.', 0);
}
private getAuthToken(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem('auth_token');
}
// HTTP method wrappers
get<T>(endpoint: string, params?: Record<string, string>) {
return this.request<T>(endpoint, { method: 'GET', params });
}
post<T>(endpoint: string, body?: unknown) {
return this.request<T>(endpoint, {
method: 'POST',
body: JSON.stringify(body),
});
}
put<T>(endpoint: string, body?: unknown) {
return this.request<T>(endpoint, {
method: 'PUT',
body: JSON.stringify(body),
});
}
patch<T>(endpoint: string, body?: unknown) {
return this.request<T>(endpoint, {
method: 'PATCH',
body: JSON.stringify(body),
});
}
delete<T>(endpoint: string) {
return this.request<T>(endpoint, { method: 'DELETE' });
}
}
export const apiClient = new ApiClient(BASE_URL);typescript
// lib/api/client.ts
import { ApiError, ApiResponse } from '@/types/api.types';
const BASE_URL = process.env.NEXT_PUBLIC_API_URL || '/api';
interface RequestConfig extends RequestInit {
params?: Record<string, string>;
}
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
private async request<T>(
endpoint: string,
config: RequestConfig = {}
): Promise<ApiResponse<T>> {
const { params, ...init } = config;
// URL参数处理
const url = new URL(`${this.baseUrl}${endpoint}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
// 默认头部设置
const headers = new Headers(init.headers);
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
// 自动认证令牌注入
const token = this.getAuthToken();
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
try {
const response = await fetch(url.toString(), {
...init,
headers,
});
return this.handleResponse<T>(response);
} catch (error) {
throw this.handleNetworkError(error);
}
}
private async handleResponse<T>(response: Response): Promise<ApiResponse<T>> {
const data = await response.json();
if (!response.ok) {
throw new ApiError(
data.error?.code || 'UNKNOWN_ERROR',
data.error?.message || '发生错误',
response.status,
data.error?.details
);
}
return data as ApiResponse<T>;
}
private handleNetworkError(error: unknown): ApiError {
if (error instanceof TypeError && error.message === 'Failed to fetch') {
return new ApiError('NETWORK_ERROR', '请检查您的网络连接。', 0);
}
return new ApiError('UNKNOWN_ERROR', '发生未知错误。', 0);
}
private getAuthToken(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem('auth_token');
}
// HTTP方法封装
get<T>(endpoint: string, params?: Record<string, string>) {
return this.request<T>(endpoint, { method: 'GET', params });
}
post<T>(endpoint: string, body?: unknown) {
return this.request<T>(endpoint, {
method: 'POST',
body: JSON.stringify(body),
});
}
put<T>(endpoint: string, body?: unknown) {
return this.request<T>(endpoint, {
method: 'PUT',
body: JSON.stringify(body),
});
}
patch<T>(endpoint: string, body?: unknown) {
return this.request<T>(endpoint, {
method: 'PATCH',
body: JSON.stringify(body),
});
}
delete<T>(endpoint: string) {
return this.request<T>(endpoint, { method: 'DELETE' });
}
}
export const apiClient = new ApiClient(BASE_URL);2. Common Type Definitions (types/api.types.ts)
2. 通用类型定义(types/api.types.ts)
typescript
// types/api.types.ts
// ===== Standard API Response Format (matches Phase 4) =====
/** Success response */
export interface ApiResponse<T> {
data: T;
meta?: {
timestamp: string;
requestId?: string;
};
}
/** Paginated response */
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
/** Error response */
export interface ApiErrorResponse {
error: {
code: string;
message: string;
details?: Array<{
field: string;
message: string;
}>;
};
}
// ===== Error Class =====
export class ApiError extends Error {
constructor(
public code: string,
message: string,
public status: number,
public details?: Array<{ field: string; message: string }>
) {
super(message);
this.name = 'ApiError';
}
/** Check if validation error */
isValidationError(): boolean {
return this.code === 'VALIDATION_ERROR' && !!this.details;
}
/** Check if auth error */
isAuthError(): boolean {
return this.status === 401 || this.code === 'UNAUTHORIZED';
}
/** Check if forbidden error */
isForbiddenError(): boolean {
return this.status === 403 || this.code === 'FORBIDDEN';
}
/** Check if not found error */
isNotFoundError(): boolean {
return this.status === 404 || this.code === 'NOT_FOUND';
}
}
// ===== Common Error Codes =====
export const ERROR_CODES = {
// Client errors
VALIDATION_ERROR: 'VALIDATION_ERROR',
UNAUTHORIZED: 'UNAUTHORIZED',
FORBIDDEN: 'FORBIDDEN',
NOT_FOUND: 'NOT_FOUND',
CONFLICT: 'CONFLICT',
// Server errors
INTERNAL_ERROR: 'INTERNAL_ERROR',
SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
// Network errors
NETWORK_ERROR: 'NETWORK_ERROR',
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
} as const;
export type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];typescript
// types/api.types.ts
// ===== 标准API响应格式(匹配第四阶段)=====
/** 成功响应 */
export interface ApiResponse<T> {
data: T;
meta?: {
timestamp: string;
requestId?: string;
};
}
/** 分页响应 */
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
/** 错误响应 */
export interface ApiErrorResponse {
error: {
code: string;
message: string;
details?: Array<{
field: string;
message: string;
}>;
};
}
// ===== 错误类 =====
export class ApiError extends Error {
constructor(
public code: string,
message: string,
public status: number,
public details?: Array<{ field: string; message: string }>
) {
super(message);
this.name = 'ApiError';
}
/** 检查是否为验证错误 */
isValidationError(): boolean {
return this.code === 'VALIDATION_ERROR' && !!this.details;
}
/** 检查是否为认证错误 */
isAuthError(): boolean {
return this.status === 401 || this.code === 'UNAUTHORIZED';
}
/** 检查是否为权限错误 */
isForbiddenError(): boolean {
return this.status === 403 || this.code === 'FORBIDDEN';
}
/** 检查是否为资源不存在错误 */
isNotFoundError(): boolean {
return this.status === 404 || this.code === 'NOT_FOUND';
}
}
// ===== 通用错误码 =====
export const ERROR_CODES = {
// 客户端错误
VALIDATION_ERROR: 'VALIDATION_ERROR',
UNAUTHORIZED: 'UNAUTHORIZED',
FORBIDDEN: 'FORBIDDEN',
NOT_FOUND: 'NOT_FOUND',
CONFLICT: 'CONFLICT',
// 服务端错误
INTERNAL_ERROR: 'INTERNAL_ERROR',
SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
// 网络错误
NETWORK_ERROR: 'NETWORK_ERROR',
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
} as const;
export type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];Service Layer Pattern
服务层模式
Domain-specific Service Separation
领域专属服务拆分
typescript
// services/auth.service.ts
import { apiClient } from '@/lib/api/client';
import { User, LoginRequest, LoginResponse, SignupRequest } from '@/types/auth.types';
export const authService = {
/** Login */
login(credentials: LoginRequest) {
return apiClient.post<LoginResponse>('/auth/login', credentials);
},
/** Signup */
signup(data: SignupRequest) {
return apiClient.post<User>('/auth/signup', data);
},
/** Logout */
logout() {
return apiClient.post<void>('/auth/logout');
},
/** Get current user info */
getMe() {
return apiClient.get<User>('/auth/me');
},
/** Refresh token */
refreshToken() {
return apiClient.post<LoginResponse>('/auth/refresh');
},
};
// services/product.service.ts
import { apiClient } from '@/lib/api/client';
import { Product, ProductFilter, CreateProductRequest } from '@/types/product.types';
import { PaginatedResponse } from '@/types/api.types';
export const productService = {
/** Get product list */
getList(filter?: ProductFilter) {
const params = filter ? {
page: String(filter.page || 1),
limit: String(filter.limit || 20),
...(filter.category && { category: filter.category }),
...(filter.search && { search: filter.search }),
} : undefined;
return apiClient.get<PaginatedResponse<Product>>('/products', params);
},
/** Get product details */
getById(id: string) {
return apiClient.get<Product>(`/products/${id}`);
},
/** Create product */
create(data: CreateProductRequest) {
return apiClient.post<Product>('/products', data);
},
/** Update product */
update(id: string, data: Partial<CreateProductRequest>) {
return apiClient.patch<Product>(`/products/${id}`, data);
},
/** Delete product */
delete(id: string) {
return apiClient.delete<void>(`/products/${id}`);
},
};typescript
// services/auth.service.ts
import { apiClient } from '@/lib/api/client';
import { User, LoginRequest, LoginResponse, SignupRequest } from '@/types/auth.types';
export const authService = {
/** 登录 */
login(credentials: LoginRequest) {
return apiClient.post<LoginResponse>('/auth/login', credentials);
},
/** 注册 */
signup(data: SignupRequest) {
return apiClient.post<User>('/auth/signup', data);
},
/** 登出 */
logout() {
return apiClient.post<void>('/auth/logout');
},
/** 获取当前用户信息 */
getMe() {
return apiClient.get<User>('/auth/me');
},
/** 刷新令牌 */
refreshToken() {
return apiClient.post<LoginResponse>('/auth/refresh');
},
};
// services/product.service.ts
import { apiClient } from '@/lib/api/client';
import { Product, ProductFilter, CreateProductRequest } from '@/types/product.types';
import { PaginatedResponse } from '@/types/api.types';
export const productService = {
/** 获取产品列表 */
getList(filter?: ProductFilter) {
const params = filter ? {
page: String(filter.page || 1),
limit: String(filter.limit || 20),
...(filter.category && { category: filter.category }),
...(filter.search && { search: filter.search }),
} : undefined;
return apiClient.get<PaginatedResponse<Product>>('/products', params);
},
/** 获取产品详情 */
getById(id: string) {
return apiClient.get<Product>(`/products/${id}`);
},
/** 创建产品 */
create(data: CreateProductRequest) {
return apiClient.post<Product>('/products', data);
},
/** 更新产品 */
update(id: string, data: Partial<CreateProductRequest>) {
return apiClient.patch<Product>(`/products/${id}`, data);
},
/** 删除产品 */
delete(id: string) {
return apiClient.delete<void>(`/products/${id}`);
},
};Error Handling Pattern
错误处理模式
Global Error Handler
全局错误处理器
typescript
// lib/api/error-handler.ts
import { ApiError, ERROR_CODES } from '@/types/api.types';
import { toast } from 'sonner'; // or another toast library
interface ErrorHandlerOptions {
showToast?: boolean;
redirectOnAuth?: boolean;
customMessages?: Record<string, string>;
}
export function handleApiError(
error: unknown,
options: ErrorHandlerOptions = {}
): void {
const { showToast = true, redirectOnAuth = true, customMessages = {} } = options;
if (!(error instanceof ApiError)) {
console.error('Unexpected error:', error);
if (showToast) {
toast.error('An unknown error occurred.');
}
return;
}
// Use custom message if available
const message = customMessages[error.code] || error.message;
// Handle by error type
switch (error.code) {
case ERROR_CODES.UNAUTHORIZED:
if (redirectOnAuth && typeof window !== 'undefined') {
localStorage.removeItem('auth_token');
window.location.href = '/login';
}
break;
case ERROR_CODES.FORBIDDEN:
if (showToast) toast.error('You do not have permission.');
break;
case ERROR_CODES.NOT_FOUND:
if (showToast) toast.error('The requested resource was not found.');
break;
case ERROR_CODES.VALIDATION_ERROR:
// Validation errors are handled by form
break;
case ERROR_CODES.NETWORK_ERROR:
if (showToast) toast.error('Please check your network connection.');
break;
default:
if (showToast) toast.error(message);
}
// Error logging (development environment)
if (process.env.NODE_ENV === 'development') {
console.error(`[API Error] ${error.code}:`, {
message: error.message,
status: error.status,
details: error.details,
});
}
}typescript
// lib/api/error-handler.ts
import { ApiError, ERROR_CODES } from '@/types/api.types';
import { toast } from 'sonner'; // 或其他提示库
interface ErrorHandlerOptions {
showToast?: boolean;
redirectOnAuth?: boolean;
customMessages?: Record<string, string>;
}
export function handleApiError(
error: unknown,
options: ErrorHandlerOptions = {}
): void {
const { showToast = true, redirectOnAuth = true, customMessages = {} } = options;
if (!(error instanceof ApiError)) {
console.error('意外错误:', error);
if (showToast) {
toast.error('发生未知错误。');
}
return;
}
// 如果有自定义消息则使用
const message = customMessages[error.code] || error.message;
// 按错误类型处理
switch (error.code) {
case ERROR_CODES.UNAUTHORIZED:
if (redirectOnAuth && typeof window !== 'undefined') {
localStorage.removeItem('auth_token');
window.location.href = '/login';
}
break;
case ERROR_CODES.FORBIDDEN:
if (showToast) toast.error('您没有权限。');
break;
case ERROR_CODES.NOT_FOUND:
if (showToast) toast.error('请求的资源不存在。');
break;
case ERROR_CODES.VALIDATION_ERROR:
// 验证错误由表单处理
break;
case ERROR_CODES.NETWORK_ERROR:
if (showToast) toast.error('请检查您的网络连接。');
break;
default:
if (showToast) toast.error(message);
}
// 错误日志(开发环境)
if (process.env.NODE_ENV === 'development') {
console.error(`[API错误] ${error.code}:`, {
message: error.message,
status: error.status,
details: error.details,
});
}
}Error Handling in Hooks
钩子中的错误处理
typescript
// hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { productService } from '@/services/product.service';
import { handleApiError } from '@/lib/api/error-handler';
import { ProductFilter } from '@/types/product.types';
export function useProducts(filter?: ProductFilter) {
return useQuery({
queryKey: ['products', filter],
queryFn: () => productService.getList(filter),
// Auto error handling
throwOnError: false,
meta: {
errorHandler: (error: unknown) => handleApiError(error),
},
});
}
export function useCreateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: productService.create,
onSuccess: () => {
// Invalidate cache
queryClient.invalidateQueries({ queryKey: ['products'] });
},
onError: (error) => {
handleApiError(error, {
customMessages: {
CONFLICT: 'Product name already exists.',
},
});
},
});
}typescript
// hooks/useProducts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { productService } from '@/services/product.service';
import { handleApiError } from '@/lib/api/error-handler';
import { ProductFilter } from '@/types/product.types';
export function useProducts(filter?: ProductFilter) {
return useQuery({
queryKey: ['products', filter],
queryFn: () => productService.getList(filter),
// 自动错误处理
throwOnError: false,
meta: {
errorHandler: (error: unknown) => handleApiError(error),
},
});
}
export function useCreateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: productService.create,
onSuccess: () => {
// 使缓存失效
queryClient.invalidateQueries({ queryKey: ['products'] });
},
onError: (error) => {
handleApiError(error, {
customMessages: {
CONFLICT: '产品名称已存在。',
},
});
},
});
}Client-Server Type Sharing
客户端-服务端类型共享
Methods for Type Consistency
类型一致性方法
Method 1: Shared Package (Monorepo)
├── packages/
│ └── shared-types/ # Common types
│ ├── api.types.ts
│ ├── auth.types.ts
│ └── product.types.ts
├── apps/
│ ├── web/ # Frontend
│ └── api/ # Backend
Method 2: Auto-generate Types from API Spec
├── openapi.yaml # OpenAPI spec
└── scripts/
└── generate-types.ts # Type auto-generation script
Method 3: tRPC / GraphQL CodeGen
└── Auto-infer types from schema方法1:共享包(单体仓库)
├── packages/
│ └── shared-types/ # 通用类型
│ ├── api.types.ts
│ ├── auth.types.ts
│ └── product.types.ts
├── apps/
│ ├── web/ # 前端
│ └── api/ # 后端
方法2:从API规范自动生成类型
├── openapi.yaml # OpenAPI规范
└── scripts/
└── generate-types.ts # 类型自动生成脚本
方法3:tRPC / GraphQL代码生成
└ 从Schema自动推断类型Type Definition Example
类型定义示例
typescript
// types/auth.types.ts (client-server shared)
export interface User {
id: string;
email: string;
name: string;
role: 'user' | 'admin';
createdAt: string;
updatedAt: string;
}
export interface LoginRequest {
email: string;
password: string;
}
export interface LoginResponse {
user: User;
token: string;
expiresAt: string;
}
export interface SignupRequest {
email: string;
password: string;
name: string;
termsAgreed: boolean;
}typescript
// types/auth.types.ts(客户端-服务端共享)
export interface User {
id: string;
email: string;
name: string;
role: 'user' | 'admin';
createdAt: string;
updatedAt: string;
}
export interface LoginRequest {
email: string;
password: string;
}
export interface LoginResponse {
user: User;
token: string;
expiresAt: string;
}
export interface SignupRequest {
email: string;
password: string;
name: string;
termsAgreed: boolean;
}API Integration Patterns
API集成模式
Basic Pattern (fetch)
基础模式(fetch)
typescript
async function getProducts() {
const response = await fetch('/api/products');
if (!response.ok) throw new Error('Failed to fetch');
return response.json();
}typescript
async function getProducts() {
const response = await fetch('/api/products');
if (!response.ok) throw new Error('获取失败');
return response.json();
}React Query Pattern
React Query模式
typescript
function useProducts() {
return useQuery({
queryKey: ['products'],
queryFn: getProducts,
});
}typescript
function useProducts() {
return useQuery({
queryKey: ['products'],
queryFn: getProducts,
});
}SWR Pattern
SWR模式
typescript
function useProducts() {
return useSWR('/api/products', fetcher);
}typescript
function useProducts() {
return useSWR('/api/products', fetcher);
}State Management Guide
状态管理指南
Server state (API data) → React Query / SWR
Client state (UI state) → useState / useReducer
Global state (auth, etc.) → Context / Zustand
Form state → React Hook Form服务端状态(API数据)→ React Query / SWR
客户端状态(UI状态)→ useState / useReducer
全局状态(认证等)→ Context / Zustand
表单状态 → React Hook FormZero Script QA Application
零脚本QA应用
Validate UI behavior with logs:
[UI] Login button clicked
[STATE] isLoading: true
[API] POST /api/auth/login
[RESPONSE] { token: "...", user: {...} }
[STATE] isLoading: false, isLoggedIn: true
[NAVIGATE] → /dashboard
[RESULT] ✅ Login successful通过日志验证UI行为:
[UI] 登录按钮被点击
[STATE] isLoading: true
[API] POST /api/auth/login
[RESPONSE] { token: "...", user: {...} }
[STATE] isLoading: false, isLoggedIn: true
[NAVIGATE] → /dashboard
[RESULT] ✅ 登录成功API Integration Checklist
API集成检查清单
Architecture
架构
-
Build API client layer
- Centralized API client (lib/api/client.ts)
- Automatic auth token injection
- Common header settings
-
Service Layer separation
- Domain-specific service files (auth, product, order, etc.)
- Each service uses only apiClient
-
Type consistency
- Common API type definitions (ApiResponse, ApiError)
- Domain-specific type definitions (Request, Response)
- Decide server-client type sharing method
-
构建API客户端层
- 集中式API客户端(lib/api/client.ts)
- 自动认证令牌注入
- 通用头部设置
-
服务层拆分
- 领域专属服务文件(auth、product、order等)
- 每个服务仅使用apiClient
-
类型一致性
- 通用API类型定义(ApiResponse、ApiError)
- 领域专属类型定义(请求、响应)
- 确定客户端-服务端类型共享方式
Error Handling
错误处理
-
Error code standardization
- Error codes matching Phase 4 API spec
- User messages defined per error code
-
Global error handler
- Redirect on auth error
- Network error handling
- Toast notifications
-
Form validation error handling
- Field-specific error message display
- Integration with server validation errors
-
错误码标准化
- 错误码匹配第四阶段API规范
- 每个错误码定义用户可见消息
-
全局错误处理器
- 认证错误时重定向
- 网络错误处理
- 提示通知
-
表单验证错误处理
- 显示字段专属错误消息
- 与服务端验证错误集成
Coding Conventions
编码规范
-
API call rules
- No direct fetch in components
- Must follow hooks → services → apiClient order
- Prevent duplicate calls for same data (caching)
-
Naming rules
- Services:
{domain}.service.ts - Hooks:
use{Domain}{Action}.ts - Types:
{domain}.types.ts
- Services:
-
API调用规则
- 组件中不直接使用fetch
- 必须遵循 钩子 → 服务 → apiClient 的顺序
- 避免相同数据的重复调用(缓存)
-
命名规则
- 服务:
{domain}.service.ts - 钩子:
use{Domain}{Action}.ts - 类型:
{domain}.types.ts
- 服务:
Template
模板
See
templates/pipeline/phase-6-ui.template.md查看
templates/pipeline/phase-6-ui.template.mdNext Phase
下一阶段
Phase 7: SEO/Security → Features are complete, now optimize and strengthen security
第七阶段:SEO/安全 → 功能已完成,现在进行优化并加强安全