/** * 分类管理组件 * 提取自 app/admin/page.tsx 的分类管理功能 * 包含:店铺分类、场景分类、平台分类、品类管理 */ 'use client' import { useState } from 'react' import type { Category, Scene, Platform, ProductCategory } from '@/hooks/useAdminState' import { parseFetchError, handleError } from '@/lib/error-handler' interface CategoryManagementProps { categories: Category[] setCategories: React.Dispatch> scenes: Scene[] setScenes: React.Dispatch> platforms: Platform[] setPlatforms: React.Dispatch> productCategories: ProductCategory[] setProductCategories: React.Dispatch> showToast: (message: string, type?: 'success' | 'error') => void } export default function CategoryManagement({ categories, setCategories, scenes, setScenes, platforms, setPlatforms, productCategories, setProductCategories, showToast, }: CategoryManagementProps) { // 编辑状态 const [editingCategory, setEditingCategory] = useState(null) const [editingScene, setEditingScene] = useState(null) const [editingPlatform, setEditingPlatform] = useState(null) const [editingProductCategory, setEditingProductCategory] = 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 handleAddCategory = async () => { if (!newCategoryName.trim()) { showToast('分类名称不能为空', 'error') return } try { const response = await fetch('/api/categories', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newCategoryName.trim() }), }) if (response.ok) { const data = await response.json() setCategories(prev => [...prev, { id: data.id, name: newCategoryName.trim(), count: 0 }]) setShowAddCategoryModal(false) setNewCategoryName('') showToast('添加成功') } else { const parsedError = await parseFetchError(response) showToast(parsedError.message, 'error') } } catch (error) { console.error('Error adding category:', error) const { message } = handleError(error) showToast(message, 'error') } } const handleUpdateCategory = async (id: string, newName: string) => { if (!newName.trim()) { showToast('分类名称不能为空', 'error') return } try { const response = await fetch('/api/categories', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, name: newName.trim() }), }) if (response.ok) { setCategories(prev => prev.map(cat => cat.id === id ? { ...cat, name: newName.trim() } : cat) ) setEditingCategory(null) showToast('修改成功') } else { const parsedError = await parseFetchError(response) showToast(parsedError.message, 'error') } } catch (error) { console.error('Error updating category:', error) const { message } = handleError(error) showToast(message, 'error') } } const handleDeleteCategory = async (id: string) => { if (!confirm('确定要删除这个使用店铺分类吗?')) return try { const response = await fetch(`/api/categories?id=${id}`, { method: 'DELETE' }) if (response.ok) { setCategories(prev => prev.filter(cat => cat.id !== id)) showToast('删除成功') } else { const parsedError = await parseFetchError(response) showToast(parsedError.message, 'error') } } catch (error) { console.error('Error deleting category:', error) const { message } = handleError(error) showToast(message, 'error') } } // ========== 场景分类操作 ========== const handleAddScene = async () => { if (!newSceneName.trim()) { showToast('场景名称不能为空', 'error') return } try { const response = await fetch('/api/scenes', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newSceneName.trim() }), }) if (response.ok) { const data = await response.json() setScenes(prev => [...prev, { id: data.id, name: newSceneName.trim(), count: 0 }]) setShowAddSceneModal(false) setNewSceneName('') showToast('添加成功') } else { const data = await response.json() showToast(data.error || '添加失败', 'error') } } catch (error) { console.error('Error adding scene:', error) showToast('添加失败', 'error') } } const handleUpdateScene = async (id: string, newName: string) => { if (!newName.trim()) { showToast('场景名称不能为空', 'error') return } try { const response = await fetch('/api/scenes', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, name: newName.trim() }), }) if (response.ok) { setScenes(prev => prev.map(scene => scene.id === id ? { ...scene, name: newName.trim() } : scene) ) setEditingScene(null) showToast('修改成功') } else { const data = await response.json() showToast(data.error || '修改失败', 'error') } } catch (error) { console.error('Error updating scene:', error) showToast('修改失败', 'error') } } const handleDeleteScene = async (id: string) => { if (!confirm('确定要删除这个场景类别吗?')) return try { const response = await fetch(`/api/scenes?id=${id}`, { method: 'DELETE' }) if (response.ok) { setScenes(prev => prev.filter(scene => scene.id !== id)) showToast('删除成功') } else { const data = await response.json() showToast(data.error || '删除失败', 'error') } } catch (error) { console.error('Error deleting scene:', error) showToast('删除失败', 'error') } } // ========== 平台分类操作 ========== const handleAddPlatform = async () => { if (!newPlatformName.trim()) { showToast('平台名称不能为空', 'error') return } try { const response = await fetch('/api/platforms', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newPlatformName.trim() }), }) if (response.ok) { const data = await response.json() setPlatforms(prev => [...prev, { id: data.id, name: newPlatformName.trim(), count: 0 }]) setShowAddPlatformModal(false) setNewPlatformName('') showToast('添加成功') } else { const data = await response.json() showToast(data.error || '添加失败', 'error') } } catch (error) { console.error('Error adding platform:', error) showToast('添加失败', 'error') } } const handleUpdatePlatform = async (id: string, newName: string) => { if (!newName.trim()) { showToast('平台名称不能为空', 'error') return } try { const response = await fetch('/api/platforms', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, name: newName.trim() }), }) if (response.ok) { setPlatforms(prev => prev.map(platform => platform.id === id ? { ...platform, name: newName.trim() } : platform) ) setEditingPlatform(null) showToast('修改成功') } else { const data = await response.json() showToast(data.error || '修改失败', 'error') } } catch (error) { console.error('Error updating platform:', error) showToast('修改失败', 'error') } } const handleDeletePlatform = async (id: string) => { if (!confirm('确定要删除这个平台分类吗?')) return try { const response = await fetch(`/api/platforms?id=${id}`, { method: 'DELETE' }) if (response.ok) { setPlatforms(prev => prev.filter(platform => platform.id !== id)) showToast('删除成功') } else { const data = await response.json() showToast(data.error || '删除失败', 'error') } } catch (error) { console.error('Error deleting platform:', error) showToast('删除失败', 'error') } } // ========== 品类操作 ========== const handleAddProductCategory = async () => { if (!newProductCategoryName.trim()) { showToast('请输入品类名称', 'error') return } try { const response = await fetch('/api/product-categories', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newProductCategoryName.trim() }), }) if (response.ok) { const data = await response.json() setProductCategories(prev => [...prev, { id: data.id, name: newProductCategoryName.trim(), count: 0 }]) setShowAddProductCategoryModal(false) setNewProductCategoryName('') showToast('添加成功') } else { const data = await response.json() showToast(data.error || '添加失败', 'error') } } catch (error) { console.error('Error adding product category:', error) showToast('添加失败', 'error') } } const handleUpdateProductCategory = async (id: string, newName: string) => { if (!newName.trim()) { showToast('品类名称不能为空', 'error') return } try { const response = await fetch('/api/product-categories', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, name: newName.trim() }), }) if (response.ok) { setProductCategories(prev => prev.map(cat => cat.id === id ? { ...cat, name: newName.trim() } : cat) ) setEditingProductCategory(null) showToast('修改成功') } else { const data = await response.json() showToast(data.error || '修改失败', 'error') } } catch (error) { console.error('Error updating product category:', error) showToast('修改失败', 'error') } } const handleDeleteProductCategory = async (id: string) => { if (!confirm('确定要删除这个品类吗?')) return try { const response = await fetch(`/api/product-categories?id=${id}`, { method: 'DELETE' }) if (response.ok) { setProductCategories(prev => prev.filter(cat => cat.id !== id)) showToast('删除成功') } else { const data = await response.json() showToast(data.error || '删除失败', 'error') } } catch (error) { console.error('Error deleting product category:', error) showToast('删除失败', 'error') } } return (
{/* 使用店铺分类 */} setShowAddCategoryModal(true)} onEdit={setEditingCategory} onUpdate={handleUpdateCategory} onDelete={handleDeleteCategory} onCancelEdit={() => setEditingCategory(null)} /> {/* 场景类别 */} setShowAddSceneModal(true)} onEdit={setEditingScene} onUpdate={handleUpdateScene} onDelete={handleDeleteScene} onCancelEdit={() => setEditingScene(null)} /> {/* 平台分类 */} setShowAddPlatformModal(true)} onEdit={setEditingPlatform} onUpdate={handleUpdatePlatform} onDelete={handleDeletePlatform} onCancelEdit={() => setEditingPlatform(null)} /> {/* 品类管理 */} setShowAddProductCategoryModal(true)} onEdit={setEditingProductCategory} onUpdate={handleUpdateProductCategory} onDelete={handleDeleteProductCategory} onCancelEdit={() => setEditingProductCategory(null)} /> {/* 添加弹窗 */} { setShowAddCategoryModal(false) setNewCategoryName('') }} /> { setShowAddSceneModal(false) setNewSceneName('') }} /> { setShowAddPlatformModal(false) setNewPlatformName('') }} /> { setShowAddProductCategoryModal(false) setNewProductCategoryName('') }} />
) } // 分类区块组件 interface CategoryItem { id: string name: string count: number } interface CategorySectionProps { title: string description: string addButtonText: string items: CategoryItem[] editingItem: CategoryItem | null onAdd: () => void onEdit: (item: CategoryItem) => void onUpdate: (id: string, newName: string) => void onDelete: (id: string) => void onCancelEdit: () => void } function CategorySection({ title, description, addButtonText, items, editingItem, onAdd, onEdit, onUpdate, onDelete, onCancelEdit, }: CategorySectionProps) { const [editValue, setEditValue] = useState('') const handleStartEdit = (item: CategoryItem) => { onEdit(item) setEditValue(item.name) } const handleConfirmEdit = (id: string) => { onUpdate(id, editValue) setEditValue('') } const handleCancelEdit = () => { onCancelEdit() setEditValue('') } return (

{title}

{description}

{items.map((item) => (
{editingItem?.id === item.id ? (
setEditValue(e.target.value)} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-gray-400 outline-none dark:bg-gray-700 dark:text-white" autoFocus />
) : (

{item.name}

{item.count} 个素材

)}
))}
{items.length === 0 && (
暂无数据
)}
) } // 添加弹窗组件 interface AddModalProps { show: boolean title: string placeholder: string value: string onChange: (value: string) => void onConfirm: () => void onCancel: () => void } function AddModal({ show, title, placeholder, value, onChange, onConfirm, onCancel, }: AddModalProps) { if (!show) return null return (

{title}

onChange(e.target.value)} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-gray-400 outline-none dark:bg-gray-700 dark:text-white mb-4" autoFocus onKeyDown={(e) => { if (e.key === 'Enter') onConfirm() if (e.key === 'Escape') onCancel() }} />
) }