Files
photography/admin/CLAUDE.md
xujiang 73197d8da8 feat: 完善模块化 CLAUDE.md 文档体系
- 新增 admin/CLAUDE.md - 管理后台开发指导文档
  - 修正技术栈为 React + TypeScript + shadcn/ui
  - 提供完整的管理后台架构设计
  - 包含照片管理、分类管理、日志管理等核心功能
  - 详细的开发环境配置和部署指南

- 新增 backend/CLAUDE.md - 后端开发指导文档
  - 基于 Golang + Gin + GORM 技术栈
  - 完整的 API 接口设计和数据库架构
  - 包含认证、权限、文件存储等核心功能
  - 详细的部署和监控配置

- 新增 ui/CLAUDE.md - UI 备份模块管理文档
  - 支持组件备份和 A/B 测试功能
  - 详细的同步策略和实验环境配置
  - 完整的版本管理和协作流程

- 更新 CLAUDE.md 根目录文档
  - 完善模块选择指南和协调机制
  - 新增模块间通信和依赖关系说明
  - 优化文档维护和使用建议
  - 建立完整的模块化开发规范

通过模块化设计最大限度减少 AI 幻觉,提高开发效率。
2025-07-09 14:23:15 +08:00

18 KiB

管理后台模块 - CLAUDE.md

此文件为 Claude Code 在管理后台模块工作时提供指导。

🎯 模块概览

管理后台是摄影作品集项目的管理界面,用于内容管理、用户管理和系统配置。

功能特性

  • 📸 照片管理:上传、编辑、删除照片
  • 🏷️ 分类管理:相册分类、标签管理
  • 👥 用户管理:用户权限、访问控制
  • 📊 数据统计:访问统计、照片数据
  • ⚙️ 系统配置:站点设置、主题配置

技术栈

  • 前端: React + TypeScript + Vite
  • 组件: shadcn/ui + Radix UI
  • 状态管理: Zustand / React Query
  • 路由: React Router
  • 认证: JWT Token
  • 构建: Vite + TypeScript

📁 目录结构

admin/
├── CLAUDE.md                      # 🔍 当前文件 - 管理后台指导
├── src/                           # 源代码目录
│   ├── components/               # 组件目录
│   │   ├── ui/                  # shadcn/ui 基础组件
│   │   ├── layout/              # 布局组件
│   │   ├── photo/               # 照片管理组件
│   │   ├── category/            # 分类管理组件
│   │   ├── logs/                # 日志管理组件
│   │   └── common/              # 通用组件
│   ├── pages/                   # 页面组件
│   │   ├── dashboard/           # 仪表板
│   │   ├── photos/              # 照片管理
│   │   ├── categories/          # 分类管理
│   │   ├── logs/                # 日志管理
│   │   └── settings/            # 系统设置
│   ├── hooks/                   # 自定义 Hooks
│   ├── services/                # API 服务
│   ├── store/                   # 状态管理
│   ├── utils/                   # 工具函数
│   └── types/                   # TypeScript 类型定义
├── public/                      # 静态资源
├── package.json                 # 项目配置
├── vite.config.ts              # Vite 配置
├── tsconfig.json               # TypeScript 配置
├── tailwind.config.js          # Tailwind 配置
└── README.md                   # 模块说明

🚀 开发环境配置

环境要求

  • Node.js: 18+
  • 包管理器: npm/yarn/pnpm
  • 编辑器: VS Code (推荐)
  • 后端依赖: Golang 后端 API 服务

初始化项目

# 进入管理后台目录
cd admin/

# 安装依赖
npm install

# 启动开发服务器
npm run dev

# 构建生产版本
npm run build

环境变量配置

# .env.development
VITE_APP_TITLE=摄影作品集管理后台
VITE_API_BASE_URL=http://localhost:8080/api
VITE_UPLOAD_URL=http://localhost:8080/api/upload

# .env.production
VITE_APP_TITLE=摄影作品集管理后台
VITE_API_BASE_URL=https://api.photography.iriver.top
VITE_UPLOAD_URL=https://api.photography.iriver.top/upload

🏗️ 项目架构

目录结构详解

🧩 组件架构

src/components/
├── ui/                        # shadcn/ui 基础组件
│   ├── button.tsx            # 按钮组件
│   ├── input.tsx             # 输入框组件
│   ├── table.tsx             # 表格组件
│   ├── modal.tsx             # 弹窗组件
│   ├── card.tsx              # 卡片组件
│   └── form.tsx              # 表单组件
├── layout/                   # 布局组件
│   ├── header.tsx            # 顶部导航
│   ├── sidebar.tsx           # 侧边栏
│   └── main-layout.tsx       # 主布局
├── photo/                    # 照片管理组件
│   ├── photo-list.tsx        # 照片列表
│   ├── photo-form.tsx        # 照片表单
│   ├── photo-upload.tsx      # 照片上传
│   └── photo-detail.tsx      # 照片详情
├── category/                 # 分类管理组件
│   ├── category-tree.tsx     # 分类树
│   ├── category-form.tsx     # 分类表单
│   └── category-stats.tsx    # 分类统计
├── logs/                     # 日志管理组件
│   ├── log-viewer.tsx        # 日志查看器
│   ├── log-filter.tsx        # 日志过滤器
│   └── log-detail.tsx        # 日志详情
└── common/                   # 通用组件
    ├── loading.tsx           # 加载组件
    ├── error-boundary.tsx    # 错误边界
    └── confirmation.tsx      # 确认对话框

📱 页面结构

src/pages/
├── dashboard/                # 仪表板
│   └── index.tsx            # 首页
├── photos/                  # 照片管理
│   ├── index.tsx            # 照片列表
│   ├── edit.tsx             # 照片编辑
│   └── upload.tsx           # 照片上传
├── categories/              # 分类管理
│   └── index.tsx            # 分类管理
├── tags/                    # 标签管理
│   └── index.tsx            # 标签管理
├── logs/                    # 日志管理
│   ├── index.tsx            # 日志列表
│   └── detail.tsx           # 日志详情
└── settings/                # 系统设置
    └── index.tsx            # 系统设置

🔄 状态管理

src/store/
├── auth.ts                   # 认证状态 (Zustand)
├── photo.ts                  # 照片状态
├── logs.ts                   # 日志状态
└── ui.ts                     # UI 状态

src/hooks/
├── useAuth.ts                # 认证 Hook
├── usePhotos.ts              # 照片数据 (React Query)
├── useCategories.ts          # 分类数据
├── useLogs.ts                # 日志数据
└── useUpload.ts              # 上传功能

🔌 API 集成

API 接口规范

// src/services/photo.ts
interface PhotoAPI {
  // 获取照片列表
  getPhotos(params: PhotoListParams): Promise<PhotoListResponse>
  
  // 上传照片
  uploadPhoto(file: File, metadata: PhotoMetadata): Promise<Photo>
  
  // 更新照片信息
  updatePhoto(id: string, data: PhotoUpdateData): Promise<Photo>
  
  // 删除照片
  deletePhoto(id: string): Promise<void>
  
  // 批量操作
  batchUpdatePhotos(ids: string[], data: Partial<Photo>): Promise<void>
}

// src/services/category.ts
interface CategoryAPI {
  getCategories(): Promise<Category[]>
  getCategoryTree(): Promise<CategoryTree[]>
  createCategory(data: CategoryCreateData): Promise<Category>
  updateCategory(id: string, data: CategoryUpdateData): Promise<Category>
  deleteCategory(id: string): Promise<void>
}

// src/services/logs.ts
interface LogsAPI {
  getLogs(params: LogListParams): Promise<LogListResponse>
  getLogDetail(traceId: string): Promise<LogDetail>
  getLogStats(): Promise<LogStats>
}

数据类型定义

// src/types/photo.ts
interface Photo {
  id: string
  title: string
  description: string
  originalFilename: string
  fileSize: number
  status: 'processing' | 'published' | 'draft' | 'archived'
  categories: Category[]
  tags: Tag[]
  formats: PhotoFormat[]
  camera?: string
  lens?: string
  iso?: number
  aperture?: string
  shutterSpeed?: string
  focalLength?: string
  takenAt?: string
  createdAt: string
  updatedAt: string
}

// src/types/category.ts
interface Category {
  id: string
  name: string
  slug: string
  description: string
  parentId?: string
  sortOrder: number
  isActive: boolean
  photoCount: number
  createdAt: string
  updatedAt: string
}

// src/types/logs.ts
interface LogEntry {
  id: string
  timestamp: string
  level: 'error' | 'warn' | 'info' | 'debug'
  message: string
  traceId: string
  userId?: string
  action?: string
  details?: Record<string, any>
}

🎨 UI 设计规范

设计原则

  • 一致性: 保持界面元素的一致性
  • 易用性: 简洁直观的操作流程
  • 响应式: 适配不同屏幕尺寸
  • 可访问性: 支持键盘导航和屏幕阅读器

主题配置

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: '#d4af37',
          50: '#fefce8',
          500: '#d4af37',
          900: '#713f12'
        },
        border: 'hsl(var(--border))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        muted: 'hsl(var(--muted))',
        'muted-foreground': 'hsl(var(--muted-foreground))'
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif']
      }
    }
  },
  plugins: [require('tailwindcss-animate')]
}

// src/lib/utils.ts
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

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

🔐 权限管理

角色权限设计

// src/types/permission.ts
interface Permission {
  id: string
  name: string
  code: string
  description: string
}

interface Role {
  id: string
  name: string
  permissions: Permission[]
}

interface User {
  id: string
  username: string
  email: string
  role: Role
  isActive: boolean
}

路由保护

// src/components/auth/ProtectedRoute.tsx
import { useAuth } from '@/hooks/useAuth'
import { Navigate, useLocation } from 'react-router-dom'

interface ProtectedRouteProps {
  children: React.ReactNode
  requiredRole?: string
}

export function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) {
  const { user, isLoading } = useAuth()
  const location = useLocation()
  
  if (isLoading) {
    return <div>Loading...</div>
  }
  
  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />
  }
  
  if (requiredRole && user.role !== requiredRole) {
    return <Navigate to="/403" replace />
  }
  
  return <>{children}</>
}

// src/hooks/useAuth.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface AuthState {
  user: User | null
  token: string | null
  login: (token: string, user: User) => void
  logout: () => void
  isLoggedIn: boolean
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      user: null,
      token: null,
      isLoggedIn: false,
      login: (token, user) => set({ token, user, isLoggedIn: true }),
      logout: () => set({ token: null, user: null, isLoggedIn: false })
    }),
    { name: 'auth-storage' }
  )
)

🚀 构建和部署

开发命令

# 安装依赖
npm install

# 启动开发服务器
npm run dev

# 类型检查
npm run type-check

# 代码检查
npm run lint

# 代码格式化
npm run format

# 构建生产版本
npm run build

# 预览构建结果
npm run preview

部署配置

// 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')
    }
  },
  base: '/admin/', // 部署路径
  build: {
    outDir: 'dist',
    assetsDir: 'static',
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
          'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-select'],
          'utils-vendor': ['clsx', 'tailwind-merge', 'date-fns']
        }
      }
    }
  }
})

🔧 开发指南

代码规范

  • 使用 TypeScript 进行类型约束
  • 遵循 React Hooks 最佳实践
  • 组件命名采用 PascalCase
  • 文件命名采用 kebab-case
  • 使用 ESLint + Prettier 进行代码格式化

组件开发

// src/components/photo/photo-list.tsx
import { useState } from 'react'
import { usePhotos } from '@/hooks/usePhotos'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'

interface PhotoListProps {
  onPhotoSelect?: (photo: Photo) => void
}

export function PhotoList({ onPhotoSelect }: PhotoListProps) {
  const [search, setSearch] = useState('')
  const [page, setPage] = useState(1)
  
  const { data: photosData, isLoading, error } = usePhotos({
    search,
    page,
    limit: 20
  })
  
  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error loading photos</div>
  
  return (
    <div className="space-y-4">
      {/* 搜索栏 */}
      <div className="flex gap-4">
        <Input
          placeholder="搜索照片..."
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          className="max-w-sm"
        />
        <Button>+ 上传照片</Button>
      </div>
      
      {/* 照片网格 */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        {photosData?.photos.map((photo) => (
          <Card 
            key={photo.id} 
            className="cursor-pointer hover:shadow-lg transition-shadow"
            onClick={() => onPhotoSelect?.(photo)}
          >
            <CardHeader className="p-0">
              <div className="aspect-square bg-gray-100 rounded-t-lg" />
            </CardHeader>
            <CardContent className="p-4">
              <CardTitle className="text-sm truncate">{photo.title}</CardTitle>
              <div className="flex gap-1 mt-2">
                <Badge variant={photo.status === 'published' ? 'default' : 'secondary'}>
                  {photo.status}
                </Badge>
              </div>
            </CardContent>
          </Card>
        ))}
      </div>
    </div>
  )
}

API 请求封装

// src/services/api.ts
import axios from 'axios'
import { useAuthStore } from '@/store/auth'
import { toast } from 'sonner'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

// 请求拦截器
api.interceptors.request.use(
  (config) => {
    const { token } = useAuthStore.getState()
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

// 响应拦截器
api.interceptors.response.use(
  (response) => response.data,
  (error) => {
    const message = error.response?.data?.message || '请求失败'
    toast.error(message)
    
    // 401 错误自动跳转登录
    if (error.response?.status === 401) {
      useAuthStore.getState().logout()
      window.location.href = '/login'
    }
    
    return Promise.reject(error)
  }
)

export default api

// src/hooks/usePhotos.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { photoService } from '@/services/photo'

export function usePhotos(params: PhotoListParams) {
  return useQuery({
    queryKey: ['photos', params],
    queryFn: () => photoService.getPhotos(params)
  })
}

export function useCreatePhoto() {
  const queryClient = useQueryClient()
  
  return useMutation({
    mutationFn: photoService.createPhoto,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['photos'] })
      toast.success('照片创建成功')
    }
  })
}

🔄 与其他模块的集成

与前端展示的关系

  • 管理后台修改的数据通过 API 影响前端展示
  • 照片和相册的增删改查直接影响用户访问体验
  • 主题设置和配置修改需要前端重新获取

与后端 API 的关系

  • 依赖后端提供的 REST API 接口
  • 需要后端提供认证和权限控制
  • 文件上传需要后端存储支持

与部署模块的关系

  • 构建产物需要部署到 Web 服务器
  • 需要配置反向代理到管理后台路径
  • 与前端项目共享域名和 SSL 证书

🐛 问题排查

常见问题

  1. 登录失败: 检查 API 地址和 Token 存储
  2. 图片上传失败: 检查文件大小和格式限制
  3. 权限错误: 检查用户角色和权限配置
  4. 页面空白: 检查路由配置和组件导入

调试技巧

# 查看开发环境变量
npm run dev --debug

# 查看构建详情
npm run build --debug

# 分析构建产物
npm run analyze

📈 性能优化

代码分割

// 路由懒加载
const PhotoList = () => import('@/views/photos/PhotoList.vue')
const AlbumList = () => import('@/views/albums/AlbumList.vue')

// 组件懒加载
const LazyComponent = defineAsyncComponent(() => import('./Component.vue'))

缓存策略

// src/lib/query-client.ts
import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5分钟缓存
      cacheTime: 10 * 60 * 1000, // 10分钟
      refetchOnWindowFocus: false,
      retry: (failureCount, error: any) => {
        if (error?.response?.status === 404) return false
        return failureCount < 3
      }
    }
  }
})

// 使用示例
export function usePhotos(params: PhotoListParams) {
  return useQuery({
    queryKey: ['photos', params],
    queryFn: () => photoService.getPhotos(params),
    staleTime: 5 * 60 * 1000 // 5分钟缓存
  })
}

🔮 未来规划

功能扩展

  • 📊 高级数据分析和报表
  • 🔄 批量操作和导入导出
  • 📱 移动端管理应用
  • 🎨 可视化主题编辑器
  • 🔌 插件系统支持

技术升级

  • 升级到 React 19 最新特性
  • 引入 React Server Components
  • 支持 PWA 离线访问
  • 集成 AI 辅助功能
  • 支持 Micro Frontend 架构

📚 参考资料


💡 开发提示: 在开始开发前,请确保已经阅读根目录的 CLAUDE.md 文件,了解项目整体架构和模块间的关系。开发过程中遇到问题,可以参考对应模块的 CLAUDE.md 文件寻找解决方案。