import React, { useState, useEffect } from 'react'; import axios from '../../services/axios'; import { adminAPI } from '../../services/api'; import { toast } from 'react-toastify'; import '../../styles/admin/CategoryManagement.css'; const CategoryManagement = () => { const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showTemplateModal, setShowTemplateModal] = useState(false); const [currentCategory, setCurrentCategory] = useState(null); const [categoryName, setCategoryName] = useState(''); const [categoryDescription, setCategoryDescription] = useState(''); const [parentCategory, setParentCategory] = useState(''); // 用于创建和编辑时的父分类选择 const [templates, setTemplates] = useState([]); const [selectedTemplate, setSelectedTemplate] = useState(''); const [expandedCategories, setExpandedCategories] = useState({}); // 用于跟踪哪些分类被展开 // 切换分类展开/折叠状态 const toggleCategory = (categoryId) => { setExpandedCategories(prev => { const newState = { ...prev }; // 如果当前是折叠状态,则展开;如果是展开状态,则折叠 newState[categoryId] = !prev[categoryId]; return newState; }); }; // 辅助函数:渲染分类列表(递归) const renderCategoryList = (categoryList, isRoot = true) => { if (!categoryList || !Array.isArray(categoryList)) { console.error('renderCategoryList: categoryList is not an array', categoryList); return null; } // 如果不是根级列表,并且父分类没有被展开,则不渲染 if (!isRoot && categoryList[0]?.parent_id && !expandedCategories[categoryList[0]?.parent_id]) { return null; } return categoryList.map(category => { const hasChildren = category.children && Array.isArray(category.children) && category.children.length > 0; const isExpanded = expandedCategories[category.id]; return ( 1 ? 'child-category' : 'parent-category'}`}> {category.id}
{/* 缩进显示 */}
{/* 展开/折叠图标 */} toggleCategory(category.id)} style={{ cursor: 'pointer' }} > {hasChildren ? ( isExpanded ? ( ) : ( ) ) : ( )} {/* 分类名称 */} {category.name} {/* 父级标识 - 移除显示父级ID,通过缩进和层次结构已经清晰表达 */}
{category.description} {/* 子分类,只有当父分类被展开时才渲染 */} {hasChildren && isExpanded && renderCategoryList(category.children, false)}
); }); }; // 获取分类列表 const fetchCategories = async () => { try { setLoading(true); // 添加hierarchical=true参数,获取层次化的分类数据 const res = await adminAPI.getCategories({hierarchical: true}); // 检查返回的数据结构 console.log('API返回的分类数据:', res.data); // 确保categories是一个数组 let categoriesData = []; if (res.data && res.data.categories) { // 如果API返回的是 {categories: [...]} 格式 categoriesData = res.data.categories || []; } else if (Array.isArray(res.data)) { // 如果API直接返回数组 categoriesData = res.data; } else { console.error('API返回的分类数据格式不正确:', res.data); categoriesData = []; } // 递归处理分类数据,确保每个分类都有必要的字段 const processCategories = (categories) => { return categories.map(cat => ({ ...cat, id: cat.id || cat._id || cat.category_id || '', name: cat.name || cat.category_name || '未命名分类', level: cat.level || 1, parent_id: cat.parent_id || null, children: cat.children ? processCategories(cat.children) : [] })); }; const formattedCategories = processCategories(categoriesData); // 初始化展开状态 - 默认只展开顶级分类 const initialExpandedState = {}; formattedCategories.forEach(cat => { // 只有顶级分类(parent_id为null或undefined的分类)默认展开 if (!cat.parent_id) { initialExpandedState[cat.id] = true; } }); setExpandedCategories(initialExpandedState); setCategories(formattedCategories); } catch (err) { toast.error('获取分类列表失败'); console.error(err); setCategories([]); } finally { setLoading(false); } }; // 获取分类模板列表 const fetchTemplates = async () => { try { const res = await axios.get('/api/admin/category-templates'); // 确保templates是一个数组 let templatesData = []; if (res.data && res.data.templates) { // 如果API返回的是 {templates: [...]} 格式 templatesData = res.data.templates || []; } else if (Array.isArray(res.data)) { // 如果API直接返回数组 templatesData = res.data; } else { console.error('API返回的模板数据格式不正确:', res.data); templatesData = []; } // 确保每个模板对象都有id和name属性 const formattedTemplates = templatesData.map(template => { return { ...template, id: template.id || template._id || template.template_id || '', name: template.name || template.template_name || '未命名模板' }; }); setTemplates(formattedTemplates); } catch (err) { toast.error('获取分类模板列表失败'); console.error(err); setTemplates([]); } }; // 应用分类模板 const applyTemplate = async () => { if (!selectedTemplate) { toast.error('请选择一个模板'); return; } try { await axios.post(`/api/admin/category-templates/${selectedTemplate}/apply`); toast.success('分类模板应用成功'); setShowTemplateModal(false); setSelectedTemplate(''); fetchCategories(); } catch (err) { toast.error('应用分类模板失败'); console.error(err); } }; useEffect(() => { fetchCategories(); fetchTemplates(); }, []); // 检查是否存在重复的分类名称(在同一父级下) const checkDuplicateCategoryName = (name, parentId, excludeId = null) => { // 扁平化分类列表,便于检查 const flattenCategories = (cats) => { let result = []; cats.forEach(cat => { result.push(cat); if (cat.children && cat.children.length > 0) { result = [...result, ...flattenCategories(cat.children)]; } }); return result; }; const allCategories = flattenCategories(categories); // 检查是否存在同名分类(在相同父级下,排除自身) return allCategories.some(cat => cat.name.toLowerCase() === name.toLowerCase() && cat.parent_id === parentId && cat.id !== excludeId ); }; // 创建分类 const createCategory = async () => { try { // 检查分类名称是否为空 if (!categoryName.trim()) { toast.error('分类名称不能为空'); return; } // 检查是否存在同名分类(在相同父级下) const isCategoryNameExists = checkDuplicateCategoryName(categoryName.trim(), parentCategory); if (isCategoryNameExists) { toast.error('该父级下已存在同名分类,请使用其他名称'); return; } await adminAPI.createCategory({ name: categoryName, description: categoryDescription, parent_id: parentCategory || null // 如果没有选择父分类,则为null }); toast.success('分类创建成功'); setShowCreateModal(false); setCategoryName(''); setCategoryDescription(''); setParentCategory(''); fetchCategories(); } catch (err) { toast.error('创建分类失败'); console.error(err); } }; // 更新分类 const updateCategory = async () => { if (!currentCategory) return; try { // 检查分类名称是否为空 if (!categoryName.trim()) { toast.error('分类名称不能为空'); return; } // 检查是否存在同名分类(在相同父级下,排除自身) const isCategoryNameExists = checkDuplicateCategoryName( categoryName.trim(), currentCategory.parent_id, currentCategory.id ); if (isCategoryNameExists) { toast.error('该父级下已存在同名分类,请使用其他名称'); return; } await adminAPI.updateCategory(currentCategory.id, { name: categoryName, description: categoryDescription // parent_id 不允许直接修改,因为会影响路径,由后端处理 }); toast.success('分类更新成功'); setShowEditModal(false); fetchCategories(); } catch (err) { toast.error('更新分类失败'); console.error(err); } }; // 删除分类 const deleteCategory = async (categoryId) => { if (window.confirm('确定要删除这个分类吗?')) { try { await adminAPI.deleteCategory(categoryId); toast.success('分类已删除'); fetchCategories(); } catch (err) { toast.error('删除分类失败'); console.error(err); } } }; // 打开创建分类模态框 const openCreateModal = () => { setCategoryName(''); setCategoryDescription(''); setParentCategory(''); setShowCreateModal(true); }; // 打开模板模态框 const openTemplateModal = () => { setSelectedTemplate(''); setShowTemplateModal(true); }; // 打开编辑分类模态框 const openEditModal = (category) => { setCurrentCategory(category); setCategoryName(category.name); setCategoryDescription(category.description); setParentCategory(category.parent_id || ''); // 设置当前父分类 setShowEditModal(true); }; // 辅助函数:生成父分类选项(扁平化列表) const getParentOptions = (cats, level = 0) => { let options = []; cats.forEach(cat => { options.push({ id: cat.id, name: '— '.repeat(level) + cat.name }); if (cat.children && cat.children.length > 0) { options = options.concat(getParentOptions(cat.children, level + 1)); } }); return options; }; if (loading) { return (
Loading...
); } return (

分类管理

{/**/}
{categories && categories.length > 0 ? ( renderCategoryList(categories) ) : ( )}
ID 名称 描述 操作
暂无分类数据
{/* 创建分类模态框 */} {showCreateModal && (
setShowCreateModal(false)}>
e.stopPropagation()}>
创建分类
setCategoryName(e.target.value)} />
)} {/* 编辑分类模态框 */} {showEditModal && (
编辑分类
setCategoryName(e.target.value)} />
{/* 父分类不允许直接修改,因为会影响路径,由后端处理 */} {currentCategory && currentCategory.parent_id && (
cat.id === currentCategory.parent_id)?.name || '无'} disabled />
)}
)} {/* 分类模板模态框 */} {showTemplateModal && (
应用分类模板
应用模板将创建预设的分类结构,不会影响现有分类。
)}
); }; export default CategoryManagement;