phase-6-ui-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Phase 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

本阶段工作内容

  1. Page Implementation: Develop each screen
  2. State Management: Handle client state
  3. API Integration: Call backend APIs
  4. Error Handling: Handle loading and error states
  1. 页面实现:开发每个页面
  2. 状态管理:处理客户端状态
  3. API集成:调用后端API
  4. 错误处理:处理加载和错误状态

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 results
src/
├── 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

分级别应用

LevelApplication Method
StarterStatic UI only (no API integration)
DynamicFull integration
EnterpriseFull 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 logicCommon error handler
Distributed auth token handlingAutomatic token injection
Inconsistent response formatsStandardized response types
Multiple changes when endpoint changesSingle point of management
Difficult testing/mockingEasy 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.ts

src/
├── 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.ts

API 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 Form

Zero 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

  • 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.md

Next Phase

下一阶段

Phase 7: SEO/Security → Features are complete, now optimize and strengthen security
第七阶段:SEO/安全 → 功能已完成,现在进行优化并加强安全