本节将介绍如何使用 Tailwind CSS 开发一个功能完整的企业级后台管理系统,包括布局设计、组件开发、主题定制等方面。
系统布局
布局框架
// components/layout/AdminLayout.tsximport { useState } from 'react';interface AdminLayoutProps {children: React.ReactNode;}const AdminLayout: React.FC<AdminLayoutProps> = ({ children }) => {const [sidebarOpen, setSidebarOpen] = useState(false);return (<div className="min-h-screen bg-gray-100">{/* 顶部导航 */}<nav className="bg-white shadow-sm"><div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"><div className="flex justify-between h-16"><div className="flex">{/* 移动端菜单按钮 */}<buttonclassName="md:hidden p-2"onClick={() => setSidebarOpen(!sidebarOpen)}><svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /></svg></button>{/* Logo */}<div className="flex-shrink-0 flex items-center"><imgclassName="h-8 w-auto"src="/logo.svg"alt="Logo"/></div></div>{/* 用户菜单 */}<div className="flex items-center"><div className="ml-3 relative"><div className="flex items-center space-x-4"><button className="bg-gray-800 flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"><imgclassName="h-8 w-8 rounded-full"src="/avatar.jpg"alt="User avatar"/></button></div></div></div></div></div></nav>{/* 侧边栏 */}<div className={`fixed inset-y-0 left-0 w-64 bg-white shadow-lg transform transition-transform duration-300 ease-in-out${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}md:translate-x-0 md:static`}><div className="h-full flex flex-col"><nav className="flex-1 py-4 overflow-y-auto"><div className="px-2 space-y-1">{/* 导航菜单项 */}<a href="/dashboard" className="group flex items-center px-2 py-2 text-sm font-medium text-gray-600 rounded-md hover:bg-gray-50 hover:text-gray-900"><svg className="mr-3 h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /></svg>仪表盘</a></div></nav></div></div>{/* 主要内容区域 */}<main className="flex-1 py-6"><div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">{children}</div></main></div>);};
组件系统
数据表格组件
// components/DataTable/index.tsxinterface Column<T> {title: string;key: keyof T;render?: (value: any, record: T) => React.ReactNode;}interface DataTableProps<T> {columns: Column<T>[];dataSource: T[];loading?: boolean;pagination?: {current: number;pageSize: number;total: number;onChange: (page: number, pageSize: number) => void;};}function DataTable<T extends { id: string | number }>({columns,dataSource,loading,pagination}: DataTableProps<T>) {return (<div className="overflow-x-auto"><table className="min-w-full divide-y divide-gray-200"><thead className="bg-gray-50"><tr>{columns.map((column) => (<thkey={String(column.key)}className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{column.title}</th>))}</tr></thead><tbody className="bg-white divide-y divide-gray-200">{loading ? (<tr><td colSpan={columns.length} className="px-6 py-4 text-center"><div className="flex justify-center"><div className="animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900"></div></div></td></tr>) : (dataSource.map((record) => (<tr key={record.id}>{columns.map((column) => (<tdkey={String(column.key)}className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{column.render? column.render(record[column.key], record): record[column.key]}</td>))}</tr>)))}</tbody></table>{pagination && (<div className="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6"><div className="flex-1 flex justify-between items-center"><div><p className="text-sm text-gray-700">显示 {(pagination.current - 1) * pagination.pageSize + 1} 到{' '}{Math.min(pagination.current * pagination.pageSize, pagination.total)} 条,共 {pagination.total} 条</p></div><div className="flex space-x-2"><buttononClick={() => pagination.onChange(pagination.current - 1, pagination.pageSize)}disabled={pagination.current === 1}className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50">上一页</button><buttononClick={() => pagination.onChange(pagination.current + 1, pagination.pageSize)}disabled={pagination.current * pagination.pageSize >= pagination.total}className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50">下一页</button></div></div></div>)}</div>);}
表单组件
// components/Form/index.tsxinterface FormItemProps {label: string;required?: boolean;error?: string;children: React.ReactNode;}const FormItem: React.FC<FormItemProps> = ({label,required,error,children}) => {return (<div className="mb-4"><label className="block text-sm font-medium text-gray-700">{required && <span className="text-red-500 mr-1">*</span>}{label}</label><div className="mt-1">{children}</div>{error && (<p className="mt-1 text-sm text-red-600">{error}</p>)}</div>);};interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {error?: boolean;}const Input: React.FC<InputProps> = ({ error, ...props }) => {return (<input{...props}className={`block w-full rounded-md shadow-smsm:text-sm${error? 'border-red-300 text-red-900 placeholder-red-300 focus:ring-red-500 focus:border-red-500': 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'}`}/>);};
业务功能实现
用户管理页面
// pages/UserManagement.tsximport { useState, useEffect } from 'react';import DataTable from '../components/DataTable';import { Form, FormItem, Input } from '../components/Form';interface User {id: number;name: string;email: string;role: string;status: 'active' | 'inactive';}const UserManagement = () => {const [users, setUsers] = useState<User[]>([]);const [loading, setLoading] = useState(false);const [pagination, setPagination] = useState({current: 1,pageSize: 10,total: 0});const columns = [{title: '用户名',key: 'name',},{title: '邮箱',key: 'email',},{title: '角色',key: 'role',},{title: '状态',key: 'status',render: (value: string) => (<span className={`px-2 py-1 text-xs rounded-full${value === 'active'? 'bg-green-100 text-green-800': 'bg-red-100 text-red-800'}`}>{value === 'active' ? '激活' : '禁用'}</span>)},{title: '操作',key: 'action',render: (_: any, record: User) => (<div className="flex space-x-2"><buttononClick={() => handleEdit(record)}className="text-blue-600 hover:text-blue-800">编辑</button><buttononClick={() => handleDelete(record.id)}className="text-red-600 hover:text-red-800">删除</button></div>)}];const handleEdit = (user: User) => {// 实现编辑逻辑};const handleDelete = (id: number) => {// 实现删除逻辑};return (<div className="space-y-6"><div className="bg-white shadow rounded-lg"><div className="px-4 py-5 sm:p-6"><h3 className="text-lg leading-6 font-medium text-gray-900">用户管理</h3><div className="mt-4"><DataTablecolumns={columns}dataSource={users}loading={loading}pagination={pagination}/></div></div></div></div>);};
主题定制
主题配置
// tailwind.config.jsconst colors = require('tailwindcss/colors');module.exports = {theme: {extend: {colors: {primary: colors.blue,success: colors.green,warning: colors.yellow,danger: colors.red,// 自定义企业主题色brand: {light: '#60A5FA',DEFAULT: '#3B82F6',dark: '#2563EB',},},spacing: {'18': '4.5rem','72': '18rem','84': '21rem','96': '24rem',},},},variants: {extend: {backgroundColor: ['active', 'disabled'],textColor: ['active', 'disabled'],opacity: ['disabled'],},},};
暗色主题支持
// hooks/useTheme.tsimport { useState, useEffect } from 'react';type Theme = 'light' | 'dark';export const useTheme = () => {const [theme, setTheme] = useState<Theme>('light');useEffect(() => {const savedTheme = localStorage.getItem('theme') as Theme;if (savedTheme) {setTheme(savedTheme);} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {setTheme('dark');}}, []);const toggleTheme = () => {const newTheme = theme === 'light' ? 'dark' : 'light';setTheme(newTheme);localStorage.setItem('theme', newTheme);document.documentElement.classList.toggle('dark');};return { theme, toggleTheme };};
性能优化
路由懒加载
// routes/index.tsximport { lazy, Suspense } from 'react';const UserManagement = lazy(() => import('../pages/UserManagement'));const RoleManagement = lazy(() => import('../pages/RoleManagement'));const Dashboard = lazy(() => import('../pages/Dashboard'));const Routes = () => {return (<Suspense fallback={<div>Loading...</div>}><Switch><Route path="/dashboard" component={Dashboard} /><Route path="/users" component={UserManagement} /><Route path="/roles" component={RoleManagement} /></Switch></Suspense>);};
状态管理优化
// store/slices/userSlice.tsimport { createSlice, createAsyncThunk } from '@reduxjs/toolkit';export const fetchUsers = createAsyncThunk('users/fetchUsers',async (params: { page: number; pageSize: number }) => {const response = await fetch(`/api/users?page=${params.page}&pageSize=${params.pageSize}`);return response.json();});const userSlice = createSlice({name: 'users',initialState: {list: [],loading: false,error: null,pagination: {current: 1,pageSize: 10,total: 0}},reducers: {},extraReducers: (builder) => {builder.addCase(fetchUsers.pending, (state) => {state.loading = true;}).addCase(fetchUsers.fulfilled, (state, action) => {state.loading = false;state.list = action.payload.data;state.pagination = action.payload.pagination;}).addCase(fetchUsers.rejected, (state, action) => {state.loading = false;state.error = action.error.message;});}});
扩展功能
权限控制
// components/AuthWrapper.tsxinterface AuthWrapperProps {permission: string;children: React.ReactNode;}const AuthWrapper: React.FC<AuthWrapperProps> = ({ permission, children }) => {const { permissions } = useAuth();if (!permissions.includes(permission)) {return null;}return <>{children}</>;};// 使用示例<AuthWrapper permission="user.edit"><button onClick={handleEdit}>编辑用户</button></AuthWrapper>
操作日志
// hooks/useLogger.tsexport const useLogger = () => {const logOperation = async (params: {module: string;action: string;details: any;}) => {try {await fetch('/api/logs', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({...params,timestamp: new Date().toISOString(),userId: getCurrentUserId()})});} catch (error) {console.error('Failed to log operation:', error);}};return { logOperation };};// 使用示例const { logOperation } = useLogger();const handleUserUpdate = async (userData) => {try {await updateUser(userData);await logOperation({module: 'User',action: 'Update',details: userData});} catch (error) {// 错误处理}};
数据导出
// utils/exportData.tsimport { saveAs } from 'file-saver';import * as XLSX from 'xlsx';export const exportToExcel = (data: any[], filename: string) => {const worksheet = XLSX.utils.json_to_sheet(data);const workbook = XLSX.utils.book_new();XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');const excelBuffer = XLSX.write(workbook, {bookType: 'xlsx',type: 'array'});const dataBlob = new Blob([excelBuffer], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});saveAs(dataBlob, `${filename}.xlsx`);};// 使用示例const handleExport = () => {const formattedData = users.map(user => ({用户名: user.name,邮箱: user.email,角色: user.role,状态: user.status === 'active' ? '激活' : '禁用'}));exportToExcel(formattedData, '用户列表');};
最佳实践
布局规范
- 采用响应式设计
- 保持导航结构清晰
- 合理使用空间层级
组件设计
- 组件职责单一
- 保持可复用性
- 统一的样式规范
性能优化
- 按需加载组件
- 优化数据请求
- 合理使用缓存
开发建议
- 遵循代码规范
- 完善错误处理
- 做好单元测试
