gongxiangp2/hooks/useAdminState.ts

454 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 管理后台状态管理 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<AdminTab>('materials')
// 数据状态
const [materials, setMaterials] = useState<Material[]>([])
const [users, setUsers] = useState<User[]>([])
const [categories, setCategories] = useState<Category[]>([])
const [scenes, setScenes] = useState<Scene[]>([])
const [platforms, setPlatforms] = useState<Platform[]>([])
const [productCategories, setProductCategories] = useState<ProductCategory[]>([])
const [departments, setDepartments] = useState<Department[]>([])
const [loading, setLoading] = useState(true)
// 编辑状态
const [editingCategory, setEditingCategory] = useState<Category | null>(null)
const [editingScene, setEditingScene] = useState<Scene | null>(null)
const [editingPlatform, setEditingPlatform] = useState<Platform | null>(null)
const [editingProductCategory, setEditingProductCategory] = useState<ProductCategory | null>(null)
const [editingUser, setEditingUser] = useState<User | null>(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<Set<string>>(new Set())
const [showRecycleBin, setShowRecycleBin] = useState(false)
// 折叠状态
const [collapsedDepartments, setCollapsedDepartments] = useState<Set<string>>(new Set())
const [collapsedAdmins, setCollapsedAdmins] = useState<Set<string>>(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<NodeJS.Timeout | null>(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,
}
}