从零搭建一个现代化后台管理系统:基于 React 19 + Vite + Ant Design Pro 的最佳实践

645 阅读11分钟

AntD Pro Lite

基于 Vite + React + TypeScript + Ant Design Pro Components + Tailwind CSS v4 + React Query 的轻量级后台管理系统。

项目地址:github.com/secret821/r…

前言

最近在工作中需要搭建一个新的后台管理系统,考虑到项目规模和团队技术栈,我选择了 React + Ant Design 作为基础。但 Ant Design Pro 虽然功能强大,对于中小型项目来说显得有些"过重",配置也比较复杂。

于是,我花了一些时间,基于最新的技术栈搭建了一个轻量级的后台管理系统模板——AntD Pro Lite。这个项目既保留了 Ant Design Pro 的核心优势,又去除了很多不必要的复杂度,更适合快速启动项目。

为什么需要这个项目?

在做技术选型的时候,我主要考虑了以下几点:

  1. 开发效率:需要一个开箱即用的后台模板,而不是从零开始
  2. 技术前瞻性:使用最新的 React 19、Vite 7 等现代工具
  3. 灵活性强:不绑定太多预设配置,方便后续扩展
  4. 代码质量:TypeScript、ESLint、Prettier 等工具链完整

基于这些考虑,我搭建了这个项目,它既保持了 Ant Design Pro 的优秀体验,又提供了更灵活的自定义空间。

技术栈选择

核心框架

  • React 19:最新的 React 版本,带来了更好的性能和开发体验
  • TypeScript:类型安全,提升代码质量和开发效率
  • Vite 7:极速的开发服务器和构建工具,开发体验非常流畅

UI 组件库

  • Ant Design 5:成熟的组件库,组件丰富且文档完善
  • @ant-design/pro-components:Pro 系列组件,特别是 ProLayout 和 ProTable,大大提升了开发效率
  • Tailwind CSS v4:原子化 CSS,与 Ant Design 配合使用,既能快速开发又能精细调整样式

状态管理与数据获取

  • Zustand:轻量级状态管理库,用于全局状态(如用户认证信息)
  • React Query (TanStack Query):强大的数据获取和缓存库,处理服务端状态的最佳选择
  • React Router v7:路由管理,配合路由守卫实现权限控制

其他工具

  • i18next:国际化支持,内置中英文切换
  • Axios:HTTP 客户端
  • clsx + tailwind-merge:类名合并工具,优雅地处理条件样式

核心特性

1. 完整的认证体系

项目实现了基于 Cookie + Zustand 的认证方案:

// 路由守卫,自动判断登录状态
const ProtectedRoute = ({ children }) => {
  const { auth } = useAuthStore()
  if (!auth.accessToken) {
    return <Navigate to="/login" replace />
  }
  return children
}

未登录用户访问受保护页面时会自动跳转到登录页,已登录用户访问登录页会自动跳转到首页,体验非常流畅。

2. 优雅的布局系统

使用 ProLayout 构建的混合布局,支持侧边栏收起/展开:

  • 顶部导航栏:显示系统标题和语言切换
  • 侧边栏:菜单导航,支持图标和文字
  • 底部用户信息:显示用户头像、邮箱和退出按钮
  • 响应式设计:自适应不同屏幕尺寸

3. 强大的表格组件

基于 ProTable 实现的数据表格,内置了:

  • 搜索功能
  • 排序功能
  • 分页功能
  • 工具栏操作(新增、导出等)

只需要配置列定义,就能快速搭建一个功能完整的表格页面。

4. 国际化支持

内置 i18next,支持中英文切换。所有文本都通过翻译键管理,方便后续扩展更多语言。语言选择器位于顶部导航栏,切换即时生效。

5. 现代化的样式方案

采用 Tailwind CSS v4 + Ant Design 的组合:

  • Tailwind 提供原子化工具类,快速开发
  • Ant Design 提供完整的组件样式
  • 两者结合,既能快速开发又能精细定制

项目还配置了 clsxtailwind-merge,优雅地处理条件类名合并。

项目结构

src/
├── main.tsx              # 应用入口
├── router.tsx            # 路由配置(含路由守卫)
├── styles/               # 全局样式
│   ├── index.css
│   └── tailwind-theme.css  # Tailwind 主题配置
├── lib/                  # 工具库
│   └── cookies.ts        # Cookie 操作
├── stores/               # 状态管理
│   └── auth-store.ts     # 认证状态(Zustand)
├── context/              # React Context
│   └── AuthProvider.tsx  # 认证上下文
├── hooks/                # 自定义 Hooks
│   └── useLanguage.ts    # 语言切换 Hook
├── i18n/                 # 国际化配置
│   ├── config.ts
│   └── locales/          # 语言文件
├── utils/                # 工具函数
│   └── cn.ts             # 类名合并工具
├── layout/               # 布局组件
│   └── ProShell.tsx      # 主布局
├── pages/                # 页面组件
│   ├── LoginPage.tsx
│   ├── Dashboard.tsx
│   └── StockOrderList.tsx
└── services/             # API 服务
    └── stock.ts

结构清晰,职责分明,易于维护和扩展。

快速开始

安装依赖

pnpm install

启动开发服务器

pnpm dev

访问 http://localhost:5173 即可看到项目运行。

构建生产版本

pnpm build

预览构建结果

pnpm preview

登录说明

默认登录页面使用模拟数据,你可以:

  1. 输入任意邮箱和密码
  2. 登录后会生成模拟 token 和用户信息
  3. Token 存储在 Cookie 中(key: thisisjustarandomstring

自定义配置

修改 API 基础 URL

编辑 src/services/stock.ts,修改 baseURL

const api = axios.create({
  baseURL: process.env.VITE_API_BASE_URL || '/api',
  timeout: 10000,
})

添加新页面

  1. src/pages/ 创建新页面组件
  2. src/router.tsx 添加路由
  3. src/layout/ProShell.tsxmenuData 中添加菜单项

修改主题

编辑 src/layout/ProShell.tsx,修改 ConfigProvidertheme 属性。

开发体验优化

代码格式化

项目配置了 Prettier,支持自动格式化:

# 格式化所有代码
pnpm format

# 检查代码格式
pnpm format:check

Prettier 配置了 Tailwind CSS 类名排序、导入排序等功能,确保代码风格一致。

TypeScript 支持

完整的 TypeScript 配置,提供良好的类型提示和错误检查。

ESLint

配置了 ESLint,确保代码质量。在构建时会自动检查。

从零搭建教程

如果你想从零开始搭建这个项目,可以按照以下步骤进行:

第一步:项目初始化

使用 Vite 创建 React + TypeScript 项目:

pnpm create vite antd-pro-lite --template react-ts
cd antd-pro-lite

第二步:安装依赖

# UI 组件库
pnpm add antd @ant-design/icons @ant-design/pro-components @ant-design/pro-layout

# 路由
pnpm add react-router-dom

# 状态管理
pnpm add zustand

# 数据获取
pnpm add @tanstack/react-query @tanstack/react-query-devtools axios

# 国际化
pnpm add i18next react-i18next

# 工具库
pnpm add clsx tailwind-merge dayjs

# 开发依赖
pnpm add -D tailwindcss@next @tailwindcss/vite tw-animate-css prettier prettier-plugin-tailwindcss prettier-plugin-classnames @trivago/prettier-plugin-sort-imports

第三步:配置 Vite

修改 vite.config.ts

import path from 'path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  base: process.env.NODE_ENV === 'production' ? '/react-vite-antdpro/' : '/',
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

配置 TypeScript 路径别名,在 tsconfig.json 中添加:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

第四步:配置 Tailwind CSS v4

创建 src/styles/tailwind-theme.css

@theme {
  /* 颜色 */
  --color-background: #ffffff;
  --color-foreground: #000000;
  
  /* 圆角 */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;
  --radius-2xl: 1.5rem;
  --radius-full: 9999px;
  
  /* 阴影 */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
  --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
  
  /* 间距 */
  --spacing-18: 4.5rem;
  --spacing-88: 22rem;
  --spacing-128: 32rem;
  
  /* 动画时长 */
  --duration-fast: 150ms;
  --duration-normal: 300ms;
  --duration-slow: 500ms;
  
  /* Z-index */
  --z-dropdown: 1000;
  --z-sticky: 1020;
  --z-fixed: 1030;
  --z-modal-backdrop: 1040;
  --z-modal: 1050;
  --z-popover: 1060;
  --z-tooltip: 1070;
}

创建 src/styles/index.css

@import 'tailwindcss';
@import 'tw-animate-css';
@import './tailwind-theme.css';

@layer base {
  * {
    scrollbar-width: thin;
  }
  html {
    @apply overflow-x-hidden;
  }
  body {
    @apply min-h-screen w-full bg-background text-foreground;
    margin: 0;
  }
}

第五步:创建工具函数

创建 src/lib/cookies.ts

const DEFAULT_MAX_AGE = 60 * 60 * 24 * 7 // 7 days

export function getCookie(name: string): string | undefined {
  if (typeof document === 'undefined') return undefined

  const value = `; ${document.cookie}`
  const parts = value.split(`; ${name}=`)
  if (parts.length === 2) {
    const cookieValue = parts.pop()?.split(';').shift()
    return cookieValue
  }
  return undefined
}

export function setCookie(
  name: string,
  value: string,
  maxAge: number = DEFAULT_MAX_AGE
): void {
  if (typeof document === 'undefined') return

  document.cookie = `${name}=${value}; path=/; max-age=${maxAge}`
}

export function removeCookie(name: string): void {
  if (typeof document === 'undefined') return

  document.cookie = `${name}=; path=/; max-age=0`
}

创建 src/utils/cn.ts

import { clsx, type ClassValue } from 'clsx'
import { extendTailwindMerge } from 'tailwind-merge'

const twMerge = extendTailwindMerge({
  extend: {
    theme: {
      text: ['display-xs', 'display-sm', 'display-md', 'display-lg', 'display-xl', 'display-2xl'],
    },
  },
})

export function cx(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

第六步:实现认证系统

创建 src/stores/auth-store.ts

import { create } from 'zustand'
import { getCookie, setCookie, removeCookie } from '@/lib/cookies'

const ACCESS_TOKEN = 'thisisjustarandomstring'

interface AuthUser {
  accountNo: string
  email: string
  role: string[]
  exp: number
}

interface AuthState {
  auth: {
    user: AuthUser | null
    setUser: (user: AuthUser | null) => void
    accessToken: string
    setAccessToken: (accessToken: string) => void
    resetAccessToken: () => void
    reset: () => void
  }
}

export const useAuthStore = create<AuthState>()((set) => {
  const cookieState = getCookie(ACCESS_TOKEN)
  const initToken = cookieState ? JSON.parse(cookieState) : ''
  return {
    auth: {
      user: null,
      setUser: (user) =>
        set((state) => ({ ...state, auth: { ...state.auth, user } })),
      accessToken: initToken,
      setAccessToken: (accessToken) =>
        set((state) => {
          setCookie(ACCESS_TOKEN, JSON.stringify(accessToken))
          return { ...state, auth: { ...state.auth, accessToken } }
        }),
      resetAccessToken: () =>
        set((state) => {
          removeCookie(ACCESS_TOKEN)
          return { ...state, auth: { ...state.auth, accessToken: '' } }
        }),
      reset: () =>
        set((state) => {
          removeCookie(ACCESS_TOKEN)
          return {
            ...state,
            auth: { ...state.auth, user: null, accessToken: '' },
          }
        }),
    },
  }
})

创建 src/context/AuthProvider.tsx

import { createContext, useContext } from 'react'
import type { ReactNode } from 'react'
import { useAuthStore } from '@/stores/auth-store'

interface AuthContextType {
  user: { accountNo: string; email: string; role: string[]; exp: number } | null
  login: (token: string, user: { accountNo: string; email: string; role: string[]; exp: number }) => void
  logout: () => void
  hasRole: (role: string) => boolean
}

const AuthContext = createContext<AuthContextType | undefined>(undefined)

export function AuthProvider({ children }: { children: ReactNode }) {
  const { auth } = useAuthStore()

  const login = (token: string, user: { accountNo: string; email: string; role: string[]; exp: number }) => {
    auth.setAccessToken(token)
    auth.setUser(user)
  }

  const logout = () => {
    auth.reset()
  }

  const hasRole = (role: string): boolean => {
    return auth.user?.role?.includes(role) ?? false
  }

  return (
    <AuthContext.Provider
      value={{
        user: auth.user,
        login,
        logout,
        hasRole,
      }}
    >
      {children}
    </AuthContext.Provider>
  )
}

export function useAuth() {
  const context = useContext(AuthContext)
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider')
  }
  return context
}

第七步:配置国际化

创建 src/i18n/config.ts

import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import zhCN from './locales/zh-CN.json'
import enUS from './locales/en-US.json'

const resources = {
  'zh-CN': {
    translation: zhCN,
  },
  'en-US': {
    translation: enUS,
  },
}

i18n
  .use(initReactI18next)
  .init({
    resources,
    lng: localStorage.getItem('language') || 'zh-CN',
    fallbackLng: 'zh-CN',
    interpolation: {
      escapeValue: false,
    },
  })

export default i18n

创建语言文件 src/i18n/locales/zh-CN.jsonsrc/i18n/locales/en-US.json(参考项目中的实际文件)。

创建 src/hooks/useLanguage.ts

import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'

export function useLanguage() {
  const { i18n } = useTranslation()
  const [language, setLanguage] = useState(i18n.language)

  useEffect(() => {
    setLanguage(i18n.language)
  }, [i18n.language])

  const changeLanguage = (lng: string) => {
    i18n.changeLanguage(lng)
    localStorage.setItem('language', lng)
    setLanguage(lng)
  }

  return {
    language,
    changeLanguage,
  }
}

第八步:配置路由系统

创建 src/router.tsx

import { createBrowserRouter, Navigate } from 'react-router-dom'
import type { ReactElement } from 'react'
import { ProShell } from './layout/ProShell'
import { LoginPage } from './pages/LoginPage'
import { Dashboard } from './pages/Dashboard'
import { StockOrderList } from './pages/StockOrderList'
import { useAuthStore } from './stores/auth-store'

function ProtectedRoute({ children }: { children: ReactElement }) {
  const { auth } = useAuthStore()
  if (!auth.accessToken) {
    return <Navigate to="/login" replace />
  }
  return children
}

function LoginRoute() {
  const { auth } = useAuthStore()
  if (auth.accessToken) {
    return <Navigate to="/" replace />
  }
  return <LoginPage />
}

function ProtectedLayout() {
  return (
    <ProtectedRoute>
      <ProShell />
    </ProtectedRoute>
  )
}

export const router = createBrowserRouter(
  [
    {
      path: '/login',
      element: <LoginRoute />,
    },
    {
      path: '/',
      element: <ProtectedLayout />,
      children: [
        { index: true, element: <Dashboard /> },
        { path: 'stock-orders', element: <StockOrderList /> },
      ],
    },
  ],
  {
    basename: import.meta.env.BASE_URL,
  }
)

第九步:创建布局组件

创建 src/layout/ProShell.tsx(完整代码请参考项目中的实际文件),主要包含:

  • ProLayout 配置
  • 菜单项配置
  • 语言切换功能
  • 用户信息显示
  • 退出登录功能

第十步:创建页面组件

创建登录页、Dashboard、表格页面等(完整代码请参考项目中的实际文件)。

第十一步:配置应用入口

修改 src/main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { AuthProvider } from './context/AuthProvider'
import { router } from './router'
import './i18n/config'
import 'antd/dist/reset.css'
import './styles/index.css'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      retry: 1,
    },
  },
})

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <AuthProvider>
        <RouterProvider router={router} />
      </AuthProvider>
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  </React.StrictMode>
)

第十二步:配置开发工具

创建 .prettierrc.js

const path = require('path')

module.exports = {
  tabWidth: 2,
  printWidth: 120,
  singleQuote: true,
  semi: true,
  plugins: ['prettier-plugin-tailwindcss', 'prettier-plugin-classnames', '@trivago/prettier-plugin-sort-imports'],
  tailwindFunctions: ['clsx'],
  tailwindStylesheet: path.resolve(__dirname, 'src/styles/tailwind-theme.css'),
  importOrder: [
    '^react$',
    '^react-dom$',
    '<THIRD_PARTY_MODULES>',
    '^@.+',
    '^\\.(?!/?$).*$',
    '^\\./.*\\.(css|scss|sass|less)$',
  ],
  importOrderSeparation: true,
  importOrderSortSpecifiers: true,
}

创建 .vscode/settings.json

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "prettier.requireConfig": true
}

第十三步:配置部署

创建 .github/workflows/deploy.yml(参考项目中的实际文件),配置 GitHub Actions 自动部署到 GitHub Pages。

实际使用建议

这个模板适合以下场景:

  1. 中小型后台管理系统:功能相对简单,不需要太复杂的权限系统
  2. 快速原型开发:需要快速搭建一个可用的后台界面
  3. 学习和参考:想了解现代 React 项目的搭建方式

如果是大型项目,建议根据实际需求进行扩展,比如:

  • 添加更细粒度的权限控制
  • 集成更多的业务组件
  • 配置更复杂的路由结构
  • 接入真实的认证服务

一些设计思考

为什么选择 Zustand 而不是 Redux?

对于后台管理系统来说,全局状态相对简单(主要是用户信息),Zustand 的 API 更简洁,学习成本更低,完全够用。

为什么使用 React Query?

服务端状态管理是后台系统的重要组成部分。React Query 提供了强大的缓存、重试、轮询等功能,使用它来处理 API 请求会让代码更简洁、性能更好。

Tailwind CSS 和 Ant Design 如何共存?

Ant Design 提供了完整的组件样式,Tailwind 主要用于:

  • 布局相关的工具类(flex、grid、spacing 等)
  • 自定义样式调整
  • 快速原型开发

两者配合使用,既有组件库的便利,又有原子化 CSS 的灵活。

部署

项目配置了 GitHub Actions,可以自动部署到 GitHub Pages。部署配置已包含在 .github/workflows/deploy.yml 中,只需要:

  1. 在 GitHub 仓库的 Settings → Pages 中,将 Source 设置为 "GitHub Actions"
  2. 推送代码到 main 分支
  3. Actions 会自动构建并部署

注意事项

  • 本项目使用 Ant Design 5.x(ProComponents 目前不支持 AntD 6.x)
  • 认证 token 存储在 Cookie 中,与 auth-store.ts 保持一致
  • API 服务使用模拟数据,实际项目中需要替换为真实接口调用
  • Tailwind CSS v4 使用 CSS 中的 @theme 指令进行配置,不需要传统的 tailwind.config.js 文件

总结

这个项目是我在实际开发中总结出的一套最佳实践,它:

  • ✅ 使用了最新的技术栈
  • ✅ 代码结构清晰,易于维护
  • ✅ 开箱即用,快速启动项目
  • ✅ 保留了足够的灵活性,方便扩展
  • ✅ 配置了完整的开发工具链

如果你也在寻找一个轻量级的后台管理系统模板,或者想学习现代 React 项目的搭建方式,不妨看看这个项目。欢迎 Star 和 Fork,也欢迎提出 Issue 和 PR。

License

MIT