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 (
);
}
return (
分类管理
{/**/}
| ID |
名称 |
描述 |
操作 |
{categories && categories.length > 0 ? (
renderCategoryList(categories)
) : (
| 暂无分类数据 |
)}
{/* 创建分类模态框 */}
{showCreateModal && (
setShowCreateModal(false)}>
e.stopPropagation()}>
创建分类
setCategoryName(e.target.value)}
/>
)}
{/* 编辑分类模态框 */}
{showEditModal && (
编辑分类
)}
{/* 分类模板模态框 */}
{showTemplateModal && (
应用分类模板
应用模板将创建预设的分类结构,不会影响现有分类。
)}
);
};
export default CategoryManagement;