Files
seclusion/docs/web/DESIGN.md
charilezhou 2a9e46a3f2 docs: 更新项目文档
- 更新 CLAUDE.md 中 HTTP 客户端说明(apiFetch -> http)
- 更新 docs/web/DESIGN.md 技术栈说明
- 添加任务工作流文档

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 14:07:35 +08:00

30 KiB
Raw Blame History

Seclusion Web 前端设计文档

本文档定义了 Seclusion Web 前端的整体架构、技术选型、目录结构和开发规范。

一、技术栈

领域 技术选型 版本 说明
框架 Next.js 16.x App Router, React Server Components
UI 框架 React 19.x 最新版本
UI 组件库 shadcn/ui latest 基于 Radix UI + Tailwind CSS
状态管理 Zustand ^4.x 轻量级、类型安全
服务端状态 TanStack Query ^5.x 数据获取、缓存、同步
样式方案 Tailwind CSS ^3.x 工具优先 CSS 框架
表单处理 React Hook Form ^7.x 高性能表单库
表单验证 Zod ^3.x TypeScript 优先的 Schema 验证
图标 Lucide Icons latest shadcn/ui 默认搭配
HTTP 客户端 Axios ^1.x 封装为 http 模块

二、目录结构

apps/web/src/
├── app/                          # Next.js App Router
│   ├── (auth)/                   # 认证相关页面组
│   │   ├── login/
│   │   │   └── page.tsx
│   │   ├── register/
│   │   │   └── page.tsx
│   │   ├── forgot-password/
│   │   │   └── page.tsx
│   │   └── layout.tsx            # 认证页面布局(居中卡片样式)
│   │
│   ├── (dashboard)/              # 后台管理页面组
│   │   ├── dashboard/
│   │   │   └── page.tsx          # 仪表盘首页
│   │   ├── users/
│   │   │   ├── page.tsx          # 用户列表
│   │   │   └── [id]/
│   │   │       └── page.tsx      # 用户详情/编辑
│   │   ├── settings/
│   │   │   └── page.tsx          # 系统设置
│   │   ├── profile/
│   │   │   └── page.tsx          # 个人中心
│   │   └── layout.tsx            # 后台布局(侧边栏 + 顶栏)
│   │
│   ├── api/                      # API Routes (如需要)
│   ├── layout.tsx                # 根布局
│   ├── page.tsx                  # 首页/落地页
│   ├── globals.css               # 全局样式
│   ├── providers.tsx             # 全局 Providers 组合
│   └── not-found.tsx             # 404 页面
│
├── components/
│   ├── ui/                       # shadcn/ui 组件CLI 自动生成)
│   │   ├── button.tsx
│   │   ├── input.tsx
│   │   ├── card.tsx
│   │   ├── dialog.tsx
│   │   ├── form.tsx
│   │   ├── table.tsx
│   │   ├── toast.tsx
│   │   └── ...
│   │
│   ├── layout/                   # 布局组件
│   │   ├── Header.tsx            # 顶部导航栏
│   │   ├── Sidebar.tsx           # 侧边栏
│   │   ├── Footer.tsx            # 页脚
│   │   ├── MobileNav.tsx         # 移动端导航
│   │   ├── UserNav.tsx           # 用户下拉菜单
│   │   └── ThemeToggle.tsx       # 主题切换
│   │
│   ├── forms/                    # 表单组件
│   │   ├── LoginForm.tsx         # 登录表单
│   │   ├── RegisterForm.tsx      # 注册表单
│   │   ├── UserForm.tsx          # 用户编辑表单
│   │   └── SettingsForm.tsx      # 设置表单
│   │
│   └── shared/                   # 通用业务组件
│       ├── Captcha.tsx           # 验证码组件
│       ├── DataTable.tsx         # 通用数据表格
│       ├── DataTablePagination.tsx # 表格分页
│       ├── DataTableToolbar.tsx  # 表格工具栏
│       ├── ConfirmDialog.tsx     # 确认弹窗
│       ├── LoadingSpinner.tsx    # 加载动画
│       ├── EmptyState.tsx        # 空状态
│       ├── ErrorBoundary.tsx     # 错误边界
│       └── PageHeader.tsx        # 页面标题头
│
├── hooks/                        # 自定义 Hooks
│   ├── useAuth.ts                # 认证相关 Hook
│   ├── useUsers.ts               # 用户 CRUD Hooks
│   ├── useCaptcha.ts             # 验证码 Hook
│   ├── useMediaQuery.ts          # 响应式 Hook
│   └── useMounted.ts             # 挂载状态 Hook
│
├── stores/                       # Zustand Store
│   ├── index.ts                  # 统一导出
│   ├── authStore.ts              # 认证状态
│   ├── uiStore.ts                # UI 状态
│   └── types.ts                  # Store 类型定义
│
├── services/                     # API 服务层
│   ├── auth.service.ts           # 认证 API
│   ├── user.service.ts           # 用户 API
│   └── captcha.service.ts        # 验证码 API
│
├── lib/                          # 工具库
│   ├── http.ts                   # Axios HTTP 客户端封装
│   ├── crypto.ts                 # 加密模块 (已有)
│   ├── auth.ts                   # 认证相关工具函数
│   ├── query-client.ts           # TanStack Query 配置
│   ├── validations.ts            # Zod Schema 定义
│   └── utils.ts                  # 通用工具函数
│
├── types/                        # 类型定义
│   └── index.ts                  # 类型导出 (已有)
│
└── config/                       # 配置文件
    ├── site.ts                   # 站点配置
    ├── nav.ts                    # 导航菜单配置
    └── constants.ts              # 常量定义

三、状态管理

3.1 状态分类

状态类型 管理方式 示例
服务端状态 TanStack Query 用户列表、用户详情
客户端全局状态 Zustand 认证信息、主题设置
组件本地状态 useState 表单输入、弹窗开关
URL 状态 Next.js Router 分页参数、筛选条件

3.2 Auth Store

// stores/authStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import type { AuthUser } from '@seclusion/shared';

interface AuthState {
  // State
  token: string | null;
  refreshToken: string | null;
  user: AuthUser | null;

  // Computed
  isAuthenticated: boolean;

  // Actions
  setAuth: (token: string, refreshToken: string, user: AuthUser) => void;
  setUser: (user: AuthUser) => void;
  logout: () => void;
  clearAuth: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      token: null,
      refreshToken: null,
      user: null,

      get isAuthenticated() {
        return !!get().token && !!get().user;
      },

      setAuth: (token, refreshToken, user) =>
        set({ token, refreshToken, user }),

      setUser: (user) =>
        set({ user }),

      logout: () =>
        set({ token: null, refreshToken: null, user: null }),

      clearAuth: () =>
        set({ token: null, refreshToken: null, user: null }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        token: state.token,
        refreshToken: state.refreshToken,
        user: state.user,
      }),
    }
  )
);

3.3 UI Store

// stores/uiStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

type Theme = 'light' | 'dark' | 'system';

interface UIState {
  // State
  theme: Theme;
  sidebarOpen: boolean;
  sidebarCollapsed: boolean;

  // Actions
  setTheme: (theme: Theme) => void;
  toggleSidebar: () => void;
  setSidebarOpen: (open: boolean) => void;
  setSidebarCollapsed: (collapsed: boolean) => void;
}

export const useUIStore = create<UIState>()(
  persist(
    (set) => ({
      theme: 'system',
      sidebarOpen: true,
      sidebarCollapsed: false,

      setTheme: (theme) => set({ theme }),
      toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
      setSidebarOpen: (open) => set({ sidebarOpen: open }),
      setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
    }),
    {
      name: 'ui-storage',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        theme: state.theme,
        sidebarCollapsed: state.sidebarCollapsed,
      }),
    }
  )
);

四、数据请求层

4.1 架构分层

┌─────────────┐
│  Component  │  React 组件
└──────┬──────┘
       │ 调用
┌──────▼──────┐
│    Hook     │  useUsers, useAuth (TanStack Query)
└──────┬──────┘
       │ 调用
┌──────▼──────┐
│   Service   │  userService, authService
└──────┬──────┘
       │ 调用
┌──────▼──────┐
│    http     │  Axios HTTP 客户端 + 加密处理
└─────────────┘

4.2 Service 层示例

// services/user.service.ts
import { http } from '@/lib/http';
import type {
  UserResponse,
  PaginatedResponse,
  UpdateUserDto
} from '@seclusion/shared';
import { API_ENDPOINTS } from '@/config/constants';

export interface GetUsersParams {
  page?: number;
  pageSize?: number;
  search?: string;
}

export const userService = {
  // 获取用户列表
  getUsers: (params: GetUsersParams = {}): Promise<PaginatedResponse<UserResponse>> => {
    return http.get<PaginatedResponse<UserResponse>>(API_ENDPOINTS.USERS, { params });
  },

  // 获取单个用户
  getUser: (id: string): Promise<UserResponse> => {
    return http.get<UserResponse>(`${API_ENDPOINTS.USERS}/${id}`);
  },

  // 更新用户
  updateUser: (id: string, data: UpdateUserDto): Promise<UserResponse> => {
    return http.patch<UserResponse>(`${API_ENDPOINTS.USERS}/${id}`, data);
  },

  // 删除用户
  deleteUser: (id: string): Promise<void> => {
    return http.delete<void>(`${API_ENDPOINTS.USERS}/${id}`);
  },

  // 恢复用户
  restoreUser: (id: string): Promise<UserResponse> => {
    return http.patch<UserResponse>(`${API_ENDPOINTS.USERS}/${id}/restore`);
  },
};

4.3 Hook 层示例

// hooks/useUsers.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userService, type GetUsersParams } from '@/services/user.service';
import { useIsAuthenticated } from '@/stores';

// Query Keys
export const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  list: (params: GetUsersParams) => [...userKeys.lists(), params] as const,
  details: () => [...userKeys.all, 'detail'] as const,
  detail: (id: string) => [...userKeys.details(), id] as const,
};

// 获取用户列表
export function useUsers(params: GetUsersParams = {}) {
  const isAuthenticated = useIsAuthenticated();

  return useQuery({
    queryKey: userKeys.list(params),
    queryFn: () => userService.getUsers(params),
    enabled: isAuthenticated,
  });
}

// 获取单个用户
export function useUser(id: string) {
  const isAuthenticated = useIsAuthenticated();

  return useQuery({
    queryKey: userKeys.detail(id),
    queryFn: () => userService.getUser(id),
    enabled: isAuthenticated && !!id,
  });
}

// 更新用户
export function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) =>
      userService.updateUser(id, data),
    onSuccess: (_, { id }) => {
      queryClient.invalidateQueries({ queryKey: userKeys.lists() });
      queryClient.invalidateQueries({ queryKey: userKeys.detail(id) });
    },
  });
}

// 删除用户
export function useDeleteUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (id: string) => userService.deleteUser(id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: userKeys.lists() });
    },
  });
}

五、路由设计

5.1 路由表

路由 页面 布局 权限 说明
/ 首页 Root 公开 落地页/欢迎页
/login 登录 Auth 公开 登录表单
/register 注册 Auth 公开 注册表单
/forgot-password 忘记密码 Auth 公开 密码重置
/dashboard 仪表盘 Dashboard 需认证 数据概览
/users 用户列表 Dashboard 需认证 用户管理
/users/[id] 用户详情 Dashboard 需认证 查看/编辑用户
/settings 系统设置 Dashboard 需认证 系统配置
/profile 个人中心 Dashboard 需认证 个人信息

5.2 路由守卫

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

// 需要认证的路<E79A84><E8B7AF><EFBFBD>
const protectedRoutes = ['/dashboard', '/users', '/settings', '/profile'];
// 认证页面(已登录用户应该跳转)
const authRoutes = ['/login', '/register', '/forgot-password'];

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value;
  const { pathname } = request.nextUrl;

  // 检查是否访问受保护路由
  const isProtectedRoute = protectedRoutes.some(route =>
    pathname.startsWith(route)
  );

  // 检查是否访问认证页面
  const isAuthRoute = authRoutes.some(route =>
    pathname.startsWith(route)
  );

  // 未登录访问受保护路由 -> 跳转登录
  if (isProtectedRoute && !token) {
    const loginUrl = new URL('/login', request.url);
    loginUrl.searchParams.set('redirect', pathname);
    return NextResponse.redirect(loginUrl);
  }

  // 已登录访问认证页面 -> 跳转仪表盘
  if (isAuthRoute && token) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

六、布局系统

6.1 Auth 布局

用于登录、注册、忘记密码等认证页面。

┌─────────────────────────────────────────────────┐
│                                                 │
│              ┌─────────────────┐                │
│              │     Logo        │                │
│              │                 │                │
│              │  ┌───────────┐  │                │
│              │  │           │  │                │
│              │  │   表单    │  │                │
│              │  │           │  │                │
│              │  └───────────┘  │                │
│              │                 │                │
│              │   其他链接      │                │
│              └─────────────────┘                │
│                                                 │
└─────────────────────────────────────────────────┘

6.2 Dashboard 布局

用于后台管理页面。

┌─────────────────────────────────────────────────┐
│  Logo  │    Header (面包屑 / 搜索 / 通知 / 头像) │
├────────┼────────────────────────────────────────┤
│        │                                        │
│  S     │  ┌────────────────────────────────┐    │
│  i     │  │  Page Header                   │    │
│  d     │  ├────────────────────────────────┤    │
│  e     │  │                                │    │
│  b     │  │                                │    │
│  a     │  │       Page Content             │    │
│  r     │  │                                │    │
│        │  │                                │    │
│  导航   │  │                                │    │
│  菜单   │  └────────────────────────────────┘    │
│        │                                        │
└────────┴────────────────────────────────────────┘

6.3 响应式设计

断点 宽度 侧边栏 说明
sm < 640px 隐藏,抽屉式 移动端
md 640-1024px 折叠图标模式 平板
lg > 1024px 完整展开 桌面

七、表单处理

7.1 Zod Schema 定义

// lib/validations.ts
import { z } from 'zod';

// 登录表单
export const loginSchema = z.object({
  email: z
    .string()
    .min(1, '请输入邮箱')
    .email('邮箱格式不正确'),
  password: z
    .string()
    .min(1, '请输入密码')
    .min(6, '密码至少 6 位'),
  captchaId: z.string().min(1, '验证码 ID 不能为空'),
  captchaCode: z
    .string()
    .min(1, '请输入验证码')
    .length(4, '验证码为 4 位'),
});

export type LoginFormValues = z.infer<typeof loginSchema>;

// 注册表单
export const registerSchema = z.object({
  email: z
    .string()
    .min(1, '请输入邮箱')
    .email('邮箱格式不正确'),
  password: z
    .string()
    .min(1, '请输入密码')
    .min(6, '密码至少 6 位')
    .max(32, '密码最多 32 位'),
  confirmPassword: z.string().min(1, '请确认密码'),
  name: z
    .string()
    .min(1, '请输入用户名')
    .min(2, '用户名至少 2 位')
    .max(20, '用户名最多 20 位'),
  captchaId: z.string().min(1, '验证码 ID 不能为空'),
  captchaCode: z
    .string()
    .min(1, '请输入验证码')
    .length(4, '验证码为 4 位'),
}).refine((data) => data.password === data.confirmPassword, {
  message: '两次密码输入不一致',
  path: ['confirmPassword'],
});

export type RegisterFormValues = z.infer<typeof registerSchema>;

// 用户更新表单
export const updateUserSchema = z.object({
  name: z
    .string()
    .min(2, '用户名至少 2 位')
    .max(20, '用户名最多 20 位')
    .optional(),
  email: z
    .string()
    .email('邮箱格式不正确')
    .optional(),
});

export type UpdateUserFormValues = z.infer<typeof updateUserSchema>;

7.2 表单组件示例

// components/forms/LoginForm.tsx
'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { loginSchema, type LoginFormValues } from '@/lib/validations';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form';
import { Captcha } from '@/components/shared/Captcha';
import { useLogin } from '@/hooks/useAuth';
import { CaptchaScene } from '@seclusion/shared';

export function LoginForm() {
  const { mutate: login, isPending } = useLogin();

  const form = useForm<LoginFormValues>({
    resolver: zodResolver(loginSchema),
    defaultValues: {
      email: '',
      password: '',
      captchaId: '',
      captchaCode: '',
    },
  });

  const onSubmit = (values: LoginFormValues) => {
    login(values);
  };

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>邮箱</FormLabel>
              <FormControl>
                <Input placeholder="请输入邮箱" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>密码</FormLabel>
              <FormControl>
                <Input type="password" placeholder="请输入密码" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <Captcha
          scene={CaptchaScene.LOGIN}
          value={form.watch('captchaCode')}
          onChange={(id, code) => {
            form.setValue('captchaId', id);
            form.setValue('captchaCode', code);
          }}
        />

        <Button type="submit" className="w-full" disabled={isPending}>
          {isPending ? '登录中...' : '登录'}
        </Button>
      </form>
    </Form>
  );
}

八、组件规范

8.1 shadcn/ui 组件清单

基础组件

  • button - 按钮
  • input - 输入框
  • label - 标签
  • textarea - 多行输入
  • checkbox - 复选框
  • radio-group - 单选组
  • select - 下拉选择
  • switch - 开关

数据展示

  • card - 卡片
  • table - 表格
  • badge - 徽章
  • avatar - 头像
  • skeleton - 骨架屏

反馈

  • toast / sonner - 消息提示
  • dialog - 对话框
  • alert-dialog - 确认对话框
  • tooltip - 工具提示
  • popover - 气泡卡片

导航

  • tabs - 标签页
  • dropdown-menu - 下拉菜单
  • navigation-menu - 导航菜单
  • breadcrumb - 面包屑
  • pagination - 分页

布局

  • separator - 分隔线
  • scroll-area - 滚动区域
  • sheet - 抽屉(移动端侧边栏)
  • collapsible - 折叠面板

8.2 组件命名规范

类型 命名规则 示例
UI 组件 kebab-case 文件名 button.tsx, data-table.tsx
业务组件 PascalCase 文件名 LoginForm.tsx, UserTable.tsx
布局组件 PascalCase 文件名 Header.tsx, Sidebar.tsx
Hook camelCase useAuth.ts, useUsers.ts
Service kebab-case + .service user.service.ts
Store camelCase + Store authStore.ts

8.3 组件结构模板

// components/shared/ExampleComponent.tsx
'use client';

import { useState } from 'react';
import { cn } from '@/lib/utils';

// 类型定义
interface ExampleComponentProps {
  title: string;
  description?: string;
  className?: string;
  onAction?: () => void;
}

// 组件实现
export function ExampleComponent({
  title,
  description,
  className,
  onAction,
}: ExampleComponentProps) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className={cn('rounded-lg border p-4', className)}>
      <h3 className="font-semibold">{title}</h3>
      {description && (
        <p className="text-sm text-muted-foreground">{description}</p>
      )}
    </div>
  );
}

九、主题系统

9.1 颜色变量

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 212.7 26.8% 83.9%;
  }
}

9.2 主题切换实现

// components/layout/ThemeToggle.tsx
'use client';

import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';

export function ThemeToggle() {
  const { setTheme } = useTheme();

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="ghost" size="icon">
          <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
          <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
          <span className="sr-only">切换主题</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => setTheme('light')}>
          浅色
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme('dark')}>
          深色
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme('system')}>
          系统
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

十、实施计划

Phase 1: 基础设施 (1-2 天)

  • 安装配置 shadcn/ui
    npx shadcn@latest init
    
  • 安装 Zustand
    pnpm add zustand
    
  • 安装 TanStack Query
    pnpm add @tanstack/react-query
    
  • 安装 React Hook Form + Zod
    pnpm add react-hook-form @hookform/resolvers zod
    
  • 安装 next-themes (主题支持)
    pnpm add next-themes
    
  • 创建目录结构
  • 配置全局 Providers

Phase 2: 布局与基础组件 (2-3 天)

  • 安装必要的 shadcn/ui 组件
  • 实现 Auth 布局
  • 实现 Dashboard 布局
    • Header 组件
    • Sidebar 组件
    • UserNav 组件
  • 实现 ThemeToggle 组件
  • 配置响应式断点

Phase 3: 状态管理 (1 天)

  • 实现 authStore
  • 实现 uiStore
  • 配置 TanStack Query Client
  • 实现 API Services 层

Phase 4: 认证模块 (2 天)

  • 实现 Zod 验证 Schema
  • 实现登录页面
  • 实现注册页面
  • 实现路由中间件
  • 重构 Captcha 组件

Phase 5: 业务模块 (3-5 天)

  • 实现仪表盘页面
  • 实现用户列表页面
    • DataTable 组件
    • 搜索筛选
    • 分页
  • 实现用户详情/编辑页面
  • 实现设置页面
  • 实现个人中心页面

Phase 6: 优化与完善 (1-2 天)

  • 错误处理与边界
  • 加载状态与骨架屏
  • 性能优化
  • 响应式适配测试

十一、开发规范

11.1 文件命名

类型 命名方式 示例
页面文件 page.tsx app/users/page.tsx
布局文件 layout.tsx app/(dashboard)/layout.tsx
组件 PascalCase UserTable.tsx
Hook camelCase + use useAuth.ts
Service kebab-case.service user.service.ts
Store camelCase + Store authStore.ts
工具函数 camelCase formatDate.ts
类型 PascalCase UserResponse
常量 UPPER_SNAKE_CASE API_BASE_URL

11.2 导入顺序

// 1. React/Next 相关
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';

// 2. 第三方库
import { useQuery } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';

// 3. 共享包
import type { UserResponse } from '@seclusion/shared';

// 4. 内部 UI 组件
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';

// 5. 内部业务组件
import { UserTable } from '@/components/shared/UserTable';

// 6. Hooks
import { useUsers } from '@/hooks/useUsers';

// 7. Stores
import { useAuthStore } from '@/stores/authStore';

// 8. Services
import { userService } from '@/services/user.service';

// 9. 工具函数 & 配置
import { cn } from '@/lib/utils';

// 10. 类型
import type { PageProps } from '@/types';

// 11. 样式 (如果有)
import './styles.css';

11.3 组件编写原则

  1. 单一职责: 每个组件只做一件事
  2. 组合优于继承: 使用组合模式构建复杂组件
  3. Props 向下Events 向上: 数据流向清晰
  4. 优先使用服务端组件: 只在需要交互时使用 'use client'
  5. 类型安全: 所有 Props 都要定义 TypeScript 类型

11.4 状态管理原则

  1. URL 优先: 可共享的状态放 URL (分页、筛选)
  2. 服务端状态用 Query: 从 API 获取的数据用 TanStack Query
  3. 客户端全局状态用 Zustand: 认证、主题等跨组件状态
  4. 本地状态用 useState: 组件内部状态

附录

A. 常用命令

# 添加 shadcn/ui 组件
npx shadcn@latest add button

# 添加多个组件
npx shadcn@latest add button input card table

# 开发
pnpm dev

# 构建
pnpm build

# 类型检查
pnpm typecheck

# 代码检查
pnpm lint

B. 相关文档