'use client'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box, Paper, Typography, Divider, CircularProgress, Tabs, Tab, List, ListItem, ListItemText, Collapse, IconButton, TextField, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Tooltip, Menu, MenuItem } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import TabPanel from './components/TabPanel'; import ReactMarkdown from 'react-markdown'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; import AddIcon from '@mui/icons-material/Add'; import EditIcon from '@mui/icons-material/Edit'; import DeleteIcon from '@mui/icons-material/Delete'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import axios from 'axios'; import { toast } from 'sonner'; import 'github-markdown-css/github-markdown-light.css'; /** * 领域分析组件 * @param {Object} props * @param {string} props.projectId - 项目ID * @param {Array} props.toc - 目录结构数组 * @param {Array} props.tags - 标签树数组 * @param {boolean} props.loading - 是否加载中 * @param {Function} props.onTagsUpdate - 标签更新回调 */ // 领域树节点组件 function TreeNode({ node, level = 0, onEdit, onDelete, onAddChild }) { const [open, setOpen] = useState(true); const theme = useTheme(); const hasChildren = node.child && node.child.length > 0; const [anchorEl, setAnchorEl] = useState(null); const menuOpen = Boolean(anchorEl); const { t } = useTranslation(); const handleClick = () => { if (hasChildren) { setOpen(!open); } }; const handleMenuOpen = event => { event.stopPropagation(); setAnchorEl(event.currentTarget); }; const handleMenuClose = event => { if (event) event.stopPropagation(); setAnchorEl(null); }; const handleEdit = event => { event.stopPropagation(); onEdit(node); handleMenuClose(); }; const handleDelete = event => { event.stopPropagation(); onDelete(node); handleMenuClose(); }; const handleAddChild = event => { event.stopPropagation(); onAddChild(node); handleMenuClose(); }; return ( <> {hasChildren && (open ? : )} e.stopPropagation()}> {t('textSplit.editTag')} {t('textSplit.deleteTag')} {level === 0 && ( {t('textSplit.addTag')} )} {hasChildren && ( {node.child.map((childNode, index) => ( ))} )} ); } // 领域树组件 function DomainTree({ tags, onEdit, onDelete, onAddChild }) { return ( {tags.map((node, index) => ( ))} ); } export default function DomainAnalysis({ projectId, toc = '', loading = false }) { const theme = useTheme(); const { t } = useTranslation(); const [activeTab, setActiveTab] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [currentNode, setCurrentNode] = useState(null); const [parentNode, setParentNode] = useState(''); const [dialogMode, setDialogMode] = useState('add'); const [labelValue, setLabelValue] = useState({}); const [saving, setSaving] = useState(false); const [tags, setTags] = useState([]); const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' }); const handleCloseSnackbar = () => { setSnackbar(prev => ({ ...prev, open: false })); }; useEffect(() => { getTags(); }, []); const getTags = async () => { const response = await axios.get(`/api/projects/${projectId}/tags`); setTags(response.data.tags); }; // 处理标签切换 const handleTabChange = (event, newValue) => { setActiveTab(newValue); }; // 打开添加标签对话框 const handleAddTag = () => { setDialogMode('add'); setCurrentNode(null); setParentNode(null); setLabelValue({}); setDialogOpen(true); }; // 打开编辑标签对话框 const handleEditTag = node => { setDialogMode('edit'); setCurrentNode({ id: node.id, label: node.label }); setLabelValue({ id: node.id, label: node.label }); setDialogOpen(true); }; // 打开添加子标签对话框 const handleAddChildTag = parentNode => { setDialogMode('addChild'); setParentNode(parentNode.label); setLabelValue({ parentId: parentNode.id }); setDialogOpen(true); }; // 打开删除标签对话框 const handleDeleteTag = node => { setCurrentNode(node); setDeleteDialogOpen(true); }; // 关闭对话框 const handleCloseDialog = () => { setDialogOpen(false); setDeleteDialogOpen(false); }; // 查找并更新节点 const findAndUpdateNode = (nodes, targetNode, newLabel) => { return nodes.map(node => { if (node === targetNode) { return { ...node, label: newLabel }; } if (node.child && node.child.length > 0) { return { ...node, child: findAndUpdateNode(node.child, targetNode, newLabel) }; } return node; }); }; // 查找并删除节点 const findAndDeleteNode = (nodes, targetNode) => { return nodes .filter(node => node !== targetNode) .map(node => { if (node.child && node.child.length > 0) { return { ...node, child: findAndDeleteNode(node.child, targetNode) }; } return node; }); }; // 查找并添加子节点 const findAndAddChildNode = (nodes, parentNode, childLabel) => { return nodes.map(node => { if (node === parentNode) { const childArray = node.child || []; return { ...node, child: [...childArray, { label: childLabel, child: [] }] }; } if (node.child && node.child.length > 0) { return { ...node, child: findAndAddChildNode(node.child, parentNode, childLabel) }; } return node; }); }; // 保存标签更改 const saveTagChanges = async updatedTags => { console.log('保存标签更改:', updatedTags); setSaving(true); try { const response = await fetch(`/api/projects/${projectId}/tags`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tags: updatedTags }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || t('domain.errors.saveFailed')); } getTags(); setSnackbar({ open: true, message: t('domain.messages.updateSuccess'), severity: 'success' }); } catch (error) { console.error('保存标签失败:', error); setSnackbar({ open: true, message: error.message || '保存标签失败', severity: 'error' }); } finally { setSaving(false); } }; // 提交表单 const handleSubmit = async () => { if (!labelValue.label.trim()) { setSnackbar({ open: true, message: '标签名称不能为空', severity: 'error' }); return; } await saveTagChanges(labelValue); handleCloseDialog(); }; const handleConfirmDelete = async () => { if (!currentNode) return; const res = await axios.delete(`/api/projects/${projectId}/tags?id=${currentNode.id}`); if (res.status === 200) { toast.success('删除成功'); getTags(); } setDeleteDialogOpen(false); }; if (loading) { return ( ); } if (toc.length === 0) { return ( {t('domain.noToc')} ); } return ( {t('domain.tabs.tree')} {tags && tags.length > 0 ? ( ) : ( {t('domain.noTags')} )} {t('domain.docStructure')}
(
{children}
) }} > {toc}
{/* 添加/编辑标签对话框 */} {dialogMode === 'add' ? t('domain.dialog.addTitle') : dialogMode === 'edit' ? t('domain.dialog.editTitle') : t('domain.dialog.addChildTitle')} {dialogMode === 'add' ? t('domain.dialog.inputRoot') : dialogMode === 'edit' ? t('domain.dialog.inputEdit') : t('domain.dialog.inputChild', { label: parentNode })} setLabelValue({ ...labelValue, label: e.target.value })} /> {/* 删除确认对话框 */} {t('common.confirmDelete')} {t('domain.dialog.deleteConfirm', { label: currentNode?.label })} {currentNode?.child && currentNode.child.length > 0 && t('domain.dialog.deleteWarning')}
); }