AntD Pro Lite
基于 Vite + React + TypeScript + Ant Design Pro Components + Tailwind CSS v4 + React Query 的轻量级后台管理系统。
前言
最近在工作中需要搭建一个新的后台管理系统,考虑到项目规模和团队技术栈,我选择了 React + Ant Design 作为基础。但 Ant Design Pro 虽然功能强大,对于中小型项目来说显得有些"过重",配置也比较复杂。
于是,我花了一些时间,基于最新的技术栈搭建了一个轻量级的后台管理系统模板——AntD Pro Lite。这个项目既保留了 Ant Design Pro 的核心优势,又去除了很多不必要的复杂度,更适合快速启动项目。
为什么需要这个项目?
在做技术选型的时候,我主要考虑了以下几点:
- 开发效率:需要一个开箱即用的后台模板,而不是从零开始
- 技术前瞻性:使用最新的 React 19、Vite 7 等现代工具
- 灵活性强:不绑定太多预设配置,方便后续扩展
- 代码质量: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 提供完整的组件样式
- 两者结合,既能快速开发又能精细定制
项目还配置了 clsx 和 tailwind-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
登录说明
默认登录页面使用模拟数据,你可以:
- 输入任意邮箱和密码
- 登录后会生成模拟 token 和用户信息
- 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,
})
添加新页面
- 在
src/pages/创建新页面组件 - 在
src/router.tsx添加路由 - 在
src/layout/ProShell.tsx的menuData中添加菜单项
修改主题
编辑 src/layout/ProShell.tsx,修改 ConfigProvider 的 theme 属性。
开发体验优化
代码格式化
项目配置了 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.json 和 src/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。
实际使用建议
这个模板适合以下场景:
- 中小型后台管理系统:功能相对简单,不需要太复杂的权限系统
- 快速原型开发:需要快速搭建一个可用的后台界面
- 学习和参考:想了解现代 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 中,只需要:
- 在 GitHub 仓库的 Settings → Pages 中,将 Source 设置为 "GitHub Actions"
- 推送代码到 main 分支
- 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