/** * 管理后台状态管理 Hook * 封装管理后台的所有状态和数据获取逻辑 */ import { useState, useEffect, useCallback, useRef } from 'react' import { useToast } from './useToast' // ========== 类型定义 ========== export interface User { id: string email: string username: string department: string role: string isActive: boolean canDownload: boolean managedDepartment?: string createdAt: string _count: { materials: number } } export interface Material { id: string previewUrl: string productModel: string categoryName: string sceneName: string materialType: string renderer: string authorName: string createdAt: string videoCoverUrl?: string videoPreviewClipUrl?: string videoPreview540Url?: string user: { username: string } } export interface Category { id: string name: string count: number } export interface Scene { id: string name: string count: number } export interface Platform { id: string name: string count: number } export interface ProductCategory { id: string name: string count: number } export interface Department { id: string name: string // 阶段三:部门层级结构字段 code?: string | null parentId?: string | null level?: number managerId?: string | null status?: string description?: string | null sortOrder?: number createdAt?: string updatedAt?: string // 关联数据(可选,通过includeHierarchy获取) parent?: Department | null children?: Department[] manager?: { id: string email: string username: string realName?: string | null } | null } export type AdminTab = 'materials' | 'users' | 'categories' | 'departments' | 'permissions' | 'superAdmins' // ========== Hook 实现 ========== export function useAdminState() { // 当前标签页 const [activeTab, setActiveTab] = useState('materials') // 数据状态 const [materials, setMaterials] = useState([]) const [users, setUsers] = useState([]) const [categories, setCategories] = useState([]) const [scenes, setScenes] = useState([]) const [platforms, setPlatforms] = useState([]) const [productCategories, setProductCategories] = useState([]) const [departments, setDepartments] = useState([]) const [loading, setLoading] = useState(true) // 编辑状态 const [editingCategory, setEditingCategory] = useState(null) const [editingScene, setEditingScene] = useState(null) const [editingPlatform, setEditingPlatform] = useState(null) const [editingProductCategory, setEditingProductCategory] = useState(null) const [editingUser, setEditingUser] = useState(null) // 添加弹窗状态 const [showAddCategoryModal, setShowAddCategoryModal] = useState(false) const [showAddSceneModal, setShowAddSceneModal] = useState(false) const [showAddPlatformModal, setShowAddPlatformModal] = useState(false) const [showAddProductCategoryModal, setShowAddProductCategoryModal] = useState(false) // 新建输入状态 const [newCategoryName, setNewCategoryName] = useState('') const [newSceneName, setNewSceneName] = useState('') const [newPlatformName, setNewPlatformName] = useState('') const [newProductCategoryName, setNewProductCategoryName] = useState('') const [newDepartmentName, setNewDepartmentName] = useState('') const [addingDepartment, setAddingDepartment] = useState(false) const [selectedDepartment, setSelectedDepartment] = useState('') // 搜索状态 const [userSearchTerm, setUserSearchTerm] = useState('') const [materialSearchTerm, setMaterialSearchTerm] = useState('') // 素材批量操作 const [selectedMaterials, setSelectedMaterials] = useState>(new Set()) const [showRecycleBin, setShowRecycleBin] = useState(false) // 折叠状态 const [collapsedDepartments, setCollapsedDepartments] = useState>(new Set()) const [collapsedAdmins, setCollapsedAdmins] = useState>(new Set()) // 素材分页 const [displayedMaterialsCount, setDisplayedMaterialsCount] = useState(24) const [currentMaterialsPage, setCurrentMaterialsPage] = useState(1) const [hasMoreMaterials, setHasMoreMaterials] = useState(true) const [isLoadingMoreMaterials, setIsLoadingMoreMaterials] = useState(false) const materialsPageSize = 20 const materialSearchDebounceRef = useRef(null) // 使用统一的Toast Hook const { toast, showToast } = useToast() // ========== 数据获取函数 ========== const fetchUsers = useCallback(async () => { try { const response = await fetch('/api/admin/users') if (response.ok) { const data = await response.json() setUsers(data) } } catch (error) { console.error('Error fetching users:', error) } }, []) const fetchCategories = useCallback(async () => { try { const response = await fetch('/api/categories') if (response.ok) { const data = await response.json() setCategories(data) } } catch (error) { console.error('Error fetching categories:', error) } }, []) const fetchScenes = useCallback(async () => { try { const response = await fetch('/api/scenes') if (response.ok) { const data = await response.json() setScenes(data) } } catch (error) { console.error('Error fetching scenes:', error) } }, []) const fetchPlatforms = useCallback(async () => { try { const response = await fetch('/api/platforms') if (response.ok) { const data = await response.json() setPlatforms(data) } } catch (error) { console.error('Error fetching platforms:', error) } }, []) const fetchProductCategories = useCallback(async () => { try { const response = await fetch('/api/product-categories') if (response.ok) { const data = await response.json() setProductCategories(data) } } catch (error) { console.error('Error fetching product categories:', error) } }, []) const fetchDepartments = useCallback(async () => { try { const response = await fetch('/api/departments') if (response.ok) { const data = await response.json() setDepartments(data) } } catch (error) { console.error('Error fetching departments:', error) } }, []) const fetchMaterials = useCallback(async (page = 1, append = false) => { try { if (!append) { setLoading(true) } const url = showRecycleBin ? `/api/materials?page=${page}&pageSize=${materialsPageSize}&deleted=true` : `/api/materials?page=${page}&pageSize=${materialsPageSize}` const response = await fetch(url) if (response.ok) { const data = await response.json() const materialsData = data.materials || data if (append) { setMaterials(prev => [...prev, ...materialsData]) } else { setMaterials(materialsData) } setHasMoreMaterials(materialsData.length === materialsPageSize) setCurrentMaterialsPage(page) setDisplayedMaterialsCount(append ? materials.length + materialsData.length : materialsData.length) } } catch (error) { console.error('Error fetching materials:', error) } finally { setLoading(false) setIsLoadingMoreMaterials(false) } }, [showRecycleBin, materialsPageSize, materials.length]) const searchMaterials = useCallback(async (searchTerm: string) => { try { const hasSearch = searchTerm.trim().length > 0 const url = showRecycleBin ? `/api/materials?page=1&pageSize=${hasSearch ? 10000 : materialsPageSize}&deleted=true${hasSearch ? `&search=${encodeURIComponent(searchTerm.trim())}` : ''}` : `/api/materials?page=1&pageSize=${hasSearch ? 10000 : materialsPageSize}${hasSearch ? `&search=${encodeURIComponent(searchTerm.trim())}` : ''}` const response = await fetch(url) if (response.ok) { const data = await response.json() const materialsData = data.materials || data setMaterials(materialsData) setDisplayedMaterialsCount(materialsData.length) setCurrentMaterialsPage(1) setHasMoreMaterials(!hasSearch && materialsData.length === materialsPageSize) } } catch (error) { console.error('Error searching materials:', error) } }, [showRecycleBin, materialsPageSize]) const loadMoreMaterials = useCallback(async () => { if (isLoadingMoreMaterials || !hasMoreMaterials || materialSearchTerm.trim()) return setIsLoadingMoreMaterials(true) await fetchMaterials(currentMaterialsPage + 1, true) }, [isLoadingMoreMaterials, hasMoreMaterials, materialSearchTerm, currentMaterialsPage, fetchMaterials]) // 初始化数据 const initializeData = useCallback(async () => { setLoading(true) await Promise.all([ fetchUsers(), fetchCategories(), fetchScenes(), fetchPlatforms(), fetchProductCategories(), fetchDepartments(), fetchMaterials(1, false), ]) setLoading(false) }, [fetchUsers, fetchCategories, fetchScenes, fetchPlatforms, fetchProductCategories, fetchDepartments, fetchMaterials]) // ========== 折叠操作 ========== const toggleDepartmentCollapse = useCallback((dept: string) => { setCollapsedDepartments(prev => { const newSet = new Set(prev) if (newSet.has(dept)) { newSet.delete(dept) } else { newSet.add(dept) } return newSet }) }, []) const toggleAdminCollapse = useCallback((adminId: string) => { setCollapsedAdmins(prev => { const newSet = new Set(prev) if (newSet.has(adminId)) { newSet.delete(adminId) } else { newSet.add(adminId) } return newSet }) }, []) // ========== 素材选择操作 ========== const toggleMaterialSelection = useCallback((id: string) => { setSelectedMaterials(prev => { const newSet = new Set(prev) if (newSet.has(id)) { newSet.delete(id) } else { newSet.add(id) } return newSet }) }, []) const selectAllMaterials = useCallback(() => { setSelectedMaterials(new Set(materials.map(m => m.id))) }, [materials]) const clearMaterialSelection = useCallback(() => { setSelectedMaterials(new Set()) }, []) return { // 标签页 activeTab, setActiveTab, // 数据 materials, setMaterials, users, setUsers, categories, setCategories, scenes, setScenes, platforms, setPlatforms, productCategories, setProductCategories, departments, setDepartments, loading, setLoading, // 编辑状态 editingCategory, setEditingCategory, editingScene, setEditingScene, editingPlatform, setEditingPlatform, editingProductCategory, setEditingProductCategory, editingUser, setEditingUser, // 添加弹窗 showAddCategoryModal, setShowAddCategoryModal, showAddSceneModal, setShowAddSceneModal, showAddPlatformModal, setShowAddPlatformModal, showAddProductCategoryModal, setShowAddProductCategoryModal, // 新建输入 newCategoryName, setNewCategoryName, newSceneName, setNewSceneName, newPlatformName, setNewPlatformName, newProductCategoryName, setNewProductCategoryName, newDepartmentName, setNewDepartmentName, addingDepartment, setAddingDepartment, selectedDepartment, setSelectedDepartment, // 搜索 userSearchTerm, setUserSearchTerm, materialSearchTerm, setMaterialSearchTerm, // 素材批量操作 selectedMaterials, setSelectedMaterials, showRecycleBin, setShowRecycleBin, toggleMaterialSelection, selectAllMaterials, clearMaterialSelection, // 折叠状态 collapsedDepartments, collapsedAdmins, toggleDepartmentCollapse, toggleAdminCollapse, // 素材分页 displayedMaterialsCount, currentMaterialsPage, hasMoreMaterials, isLoadingMoreMaterials, materialsPageSize, materialSearchDebounceRef, // Toast toast, showToast, // 数据获取函数 fetchUsers, fetchCategories, fetchScenes, fetchPlatforms, fetchProductCategories, fetchDepartments, fetchMaterials, searchMaterials, loadMoreMaterials, initializeData, } }