# 管理后台 React + Vite 开发规则 ## 🏗️ 项目架构 ### 目录结构 ``` admin/ ├── src/ │ ├── components/ # React 组件 │ │ ├── ui/ # shadcn/ui 基础组件 │ │ ├── DashboardLayout.tsx # 布局组件 │ │ ├── ProtectedRoute.tsx # 路由守卫 │ │ └── ErrorBoundary.tsx # 错误边界 │ ├── pages/ # 页面组件 │ │ ├── Dashboard.tsx # 仪表盘 │ │ ├── Photos.tsx # 照片管理 │ │ ├── Categories.tsx # 分类管理 │ │ ├── Users.tsx # 用户管理 │ │ └── LoginPage.tsx # 登录页 │ ├── services/ # API 服务 │ │ ├── api.ts # API 基础配置 │ │ ├── authService.ts # 认证服务 │ │ ├── photoService.ts # 照片服务 │ │ └── categoryService.ts # 分类服务 │ ├── stores/ # 状态管理 │ │ └── authStore.ts # 认证状态 │ ├── types/ # TypeScript 类型 │ │ └── index.ts # 类型定义 │ ├── utils/ # 工具函数 │ ├── lib/ # 库配置 │ │ └── utils.ts # shadcn utils │ ├── App.tsx # 应用根组件 │ └── main.tsx # 应用入口 ├── public/ # 静态资源 ├── index.html # HTML 模板 ├── vite.config.ts # Vite 配置 └── package.json # 依赖配置 ``` ## 🎯 开发规范 ### 应用入口配置 ```tsx // main.tsx import React from 'react' import ReactDOM from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import App from './App' import './index.css' const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5分钟 retry: 2, }, }, }) ReactDOM.createRoot(document.getElementById('root')!).render( ) ``` ### 应用根组件 ```tsx // App.tsx import { Routes, Route, Navigate } from 'react-router-dom' import { Toaster } from '@/components/ui/toaster' import { ErrorBoundary } from '@/components/ErrorBoundary' import { ProtectedRoute } from '@/components/ProtectedRoute' import { DashboardLayout } from '@/components/DashboardLayout' import { LoginPage } from '@/pages/LoginPage' import { Dashboard } from '@/pages/Dashboard' import { Photos } from '@/pages/Photos' import { PhotoUpload } from '@/pages/PhotoUpload' import { Categories } from '@/pages/Categories' import { Users } from '@/pages/Users' import { Settings } from '@/pages/Settings' function App() { return ( } /> } /> } /> } /> } /> } /> } /> } /> } /> ) } export default App ``` ## 🧩 核心组件 ### 布局组件 ```tsx // components/DashboardLayout.tsx import { useState } from 'react' import { Link, useLocation } from 'react-router-dom' import { LayoutDashboard, Image, FolderOpen, Users, Settings, LogOut, Menu } from 'lucide-react' import { Button } from '@/components/ui/button' import { useAuthStore } from '@/stores/authStore' const navigation = [ { name: '仪表盘', href: '/dashboard', icon: LayoutDashboard }, { name: '照片管理', href: '/photos', icon: Image }, { name: '分类管理', href: '/categories', icon: FolderOpen }, { name: '用户管理', href: '/users', icon: Users }, { name: '系统设置', href: '/settings', icon: Settings }, ] interface DashboardLayoutProps { children: React.ReactNode } export function DashboardLayout({ children }: DashboardLayoutProps) { const [sidebarOpen, setSidebarOpen] = useState(false) const location = useLocation() const { user, logout } = useAuthStore() return (
{/* 侧边栏 */}

摄影管理

{/* 主内容区 */}
{/* 顶部导航 */}

{navigation.find(item => item.href === location.pathname)?.name || '管理后台'}

欢迎, {user?.username}
{/* 页面内容 */}
{children}
) } ``` ### 路由守卫 ```tsx // components/ProtectedRoute.tsx import { Navigate } from 'react-router-dom' import { useAuthStore } from '@/stores/authStore' import { Loading } from '@/components/Loading' interface ProtectedRouteProps { children: React.ReactNode } export function ProtectedRoute({ children }: ProtectedRouteProps) { const { token, isLoading } = useAuthStore() if (isLoading) { return } if (!token) { return } return <>{children} } ``` ## 🎛️ 状态管理 (Zustand) ### 认证状态管理 ```typescript // stores/authStore.ts import { create } from 'zustand' import { persist } from 'zustand/middleware' import { authService } from '@/services/authService' interface User { id: string username: string email: string role: string } interface AuthState { user: User | null token: string | null isLoading: boolean // Actions login: (credentials: LoginCredentials) => Promise logout: () => void checkAuth: () => Promise } export const useAuthStore = create()( persist( (set, get) => ({ user: null, token: null, isLoading: false, login: async (credentials) => { set({ isLoading: true }) try { const response = await authService.login(credentials) set({ user: response.user, token: response.token, isLoading: false, }) } catch (error) { set({ isLoading: false }) throw error } }, logout: () => { set({ user: null, token: null }) // 清除持久化数据 localStorage.removeItem('auth-storage') }, checkAuth: async () => { const { token } = get() if (!token) return try { const user = await authService.getCurrentUser() set({ user }) } catch (error) { // Token 无效,清除状态 get().logout() } }, }), { name: 'auth-storage', partialize: (state) => ({ token: state.token, user: state.user }), } ) ) ``` ## 🌐 API 服务层 ### API 基础配置 ```typescript // services/api.ts import axios, { AxiosError } from 'axios' import { useAuthStore } from '@/stores/authStore' export const api = axios.create({ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8888/api/v1', timeout: 10000, }) // 请求拦截器 api.interceptors.request.use((config) => { const token = useAuthStore.getState().token if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器 api.interceptors.response.use( (response) => response, (error: AxiosError) => { if (error.response?.status === 401) { useAuthStore.getState().logout() window.location.href = '/login' } return Promise.reject(error) } ) // API 响应类型 export interface ApiResponse { code: number msg: string data: T } ``` ### 照片服务 ```typescript // services/photoService.ts import { api, ApiResponse } from './api' export interface Photo { id: string title: string description?: string filename: string thumbnail: string category_id?: string user_id: string created_at: string updated_at: string } export interface PhotoListResponse { photos: Photo[] total: number page: number limit: number } export const photoService = { // 获取照片列表 getPhotos: async (params: { page?: number limit?: number category_id?: string keyword?: string }): Promise => { const { data } = await api.get>('/photos', { params, }) return data.data }, // 上传照片 uploadPhoto: async (formData: FormData): Promise => { const { data } = await api.post>('/photos', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }) return data.data }, // 更新照片 updatePhoto: async (id: string, updates: Partial): Promise => { await api.put(`/photos/${id}`, updates) }, // 删除照片 deletePhoto: async (id: string): Promise => { await api.delete(`/photos/${id}`) }, // 获取照片详情 getPhoto: async (id: string): Promise => { const { data } = await api.get>(`/photos/${id}`) return data.data }, } ``` ## 📱 页面组件 ### 照片管理页面 ```tsx // pages/Photos.tsx import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Plus, Edit, Trash2 } from 'lucide-react' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { useToast } from '@/hooks/use-toast' import { photoService } from '@/services/photoService' export function Photos() { const [page, setPage] = useState(1) const { toast } = useToast() const queryClient = useQueryClient() // 获取照片列表 const { data, isLoading, error } = useQuery({ queryKey: ['photos', page], queryFn: () => photoService.getPhotos({ page, limit: 12 }), }) // 删除照片 const deleteMutation = useMutation({ mutationFn: photoService.deletePhoto, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['photos'] }) toast({ title: '成功', description: '照片已删除', }) }, onError: (error) => { toast({ title: '错误', description: '删除失败', variant: 'destructive', }) }, }) const handleDelete = (id: string) => { if (confirm('确认删除这张照片吗?')) { deleteMutation.mutate(id) } } if (isLoading) return
加载中...
if (error) return
加载失败
return (

照片管理

{data?.photos.map((photo) => (
{photo.title}

{photo.title}

{photo.description && (

{photo.description}

)}
{photo.category_id ? '已分类' : '未分类'} {new Date(photo.created_at).toLocaleDateString()}
))}
{/* 分页 */} {data && data.total > 12 && (
)}
) } ``` ## 🔧 开发工具配置 ### Vite 配置 ```typescript // vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8888', changeOrigin: true, }, }, }, build: { outDir: 'dist', sourcemap: true, }, }) ``` ### 环境变量 ```bash # .env.local VITE_API_URL=http://localhost:8888/api/v1 VITE_APP_TITLE=摄影作品管理后台 ``` 参考 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md) 了解管理后台开发进度。 description: globs: alwaysApply: false ---