/* === Data Dictionary Management Page === */ /* global useToast, Icons, ConfirmModal */ function PageOperationDict() { const toast = useToast(); const [categories, setCategories] = React.useState([]); const [selectedCategory, setSelectedCategory] = React.useState(null); const [items, setItems] = React.useState([]); const [categoryModalVisible, setCategoryModalVisible] = React.useState(false); const [itemModalVisible, setItemModalVisible] = React.useState(false); const [editingCategory, setEditingCategory] = React.useState(null); const [editingItem, setEditingItem] = React.useState(null); const [loading, setLoading] = React.useState(false); const [expandedNodes, setExpandedNodes] = React.useState(new Set()); const [draggingNode, setDraggingNode] = React.useState(null); const [dropTarget, setDropTarget] = React.useState(null); const [confirmModalVisible, setConfirmModalVisible] = React.useState(false); const [confirmConfig, setConfirmConfig] = React.useState({ title: '', message: '', onConfirm: () => {} }); const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false); const fetchCategories = async function() { try { const data = await window.apiFetch('/api/dict/category/tree'); if (Array.isArray(data)) { setCategories(data); // 默认展开根节点 const newExpanded = new Set(); function collectNodeIds(nodes) { nodes.forEach(node => { if (node.parent_id === 0) { newExpanded.add(node.id); } if (node.children && node.children.length > 0) { collectNodeIds(node.children); } }); } collectNodeIds(data); setExpandedNodes(newExpanded); } else { console.error('API返回数据格式错误:', data); setCategories([]); } } catch (error) { console.error('获取分类失败:', error); setCategories([]); } }; React.useEffect(function() { fetchCategories(); }, []); const fetchItems = async function(categoryCode) { if (!categoryCode) return; try { setLoading(true); const data = await window.apiFetch('/api/dict/item/list?category_code=' + categoryCode); if (Array.isArray(data)) { setItems(data); } else { console.error('API返回数据格式错误:', data); setItems([]); } } catch (error) { console.error('获取字典项失败:', error); setItems([]); } finally { setLoading(false); } }; React.useEffect(function() { if (selectedCategory) { fetchItems(selectedCategory.code); } }, [selectedCategory]); const toggleExpand = function(nodeId) { const newExpanded = new Set(expandedNodes); if (newExpanded.has(nodeId)) { newExpanded.delete(nodeId); } else { newExpanded.add(nodeId); } setExpandedNodes(newExpanded); }; const handleAddCategory = function() { setEditingCategory({ parent_id: selectedCategory ? selectedCategory.id : 0, code: '', name: '', importance: 'normal', sort: 1, status: 1, remark: '' }); setCategoryModalVisible(true); }; const handleEditCategory = function(category) { setEditingCategory(Object.assign({}, category)); setCategoryModalVisible(true); }; const handleSaveCategory = async function() { if (!editingCategory.code || !editingCategory.name) { toast.push('请填写编码和名称', 'warn'); return; } try { if (editingCategory.id) { await window.apiFetch('/api/dict/category/' + editingCategory.id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editingCategory) }); toast.push('分类更新成功', 'success'); } else { await window.apiFetch('/api/dict/category/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editingCategory) }); toast.push('分类创建成功', 'success'); } setCategoryModalVisible(false); setEditingCategory(null); fetchCategories(); } catch (error) { console.error('保存分类失败:', error); toast.push('保存失败: ' + (error.detail || error.message || '未知错误'), 'error'); } }; const handleDeleteCategory = async function(category) { setConfirmConfig({ title: '确认删除', message: '确定要删除分类"' + category.name + '"吗?', onConfirm: async () => { try { await window.apiFetch('/api/dict/category/' + category.id, { method: 'DELETE' }); if (selectedCategory && selectedCategory.id === category.id) { setSelectedCategory(null); } toast.push('分类删除成功', 'success'); fetchCategories(); } catch (error) { console.error('删除分类失败:', error); toast.push('删除失败: ' + (error.detail || error.message || '未知错误'), 'error'); } } }); setConfirmModalVisible(true); }; const handleAddItem = function() { setEditingItem({ category_code: selectedCategory.code, label: '', value: '', parent_value: '', ref_category_code: '', sort: 1, status: 1, extra: '', remark: '' }); setItemModalVisible(true); }; const handleEditItem = function(item) { setEditingItem(Object.assign({}, item)); setItemModalVisible(true); }; const handleSaveItem = async function() { if (!editingItem.label || !editingItem.value) { toast.push('请填写显示名称和编码', 'warn'); return; } try { var saveData = Object.assign({}, editingItem); if (saveData.extra && typeof saveData.extra === 'string') { try { saveData.extra = JSON.parse(saveData.extra); } catch (e) { saveData.extra = null; } } if (!saveData.extra || Object.keys(saveData.extra).length === 0) { saveData.extra = null; } if (!saveData.parent_value) saveData.parent_value = null; if (!saveData.ref_category_code) saveData.ref_category_code = null; if (editingItem.id) { await window.apiFetch('/api/dict/item/' + editingItem.id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(saveData) }); toast.push('字典项更新成功', 'success'); } else { await window.apiFetch('/api/dict/item/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(saveData) }); toast.push('字典项创建成功', 'success'); } setItemModalVisible(false); setEditingItem(null); fetchItems(selectedCategory.code); } catch (error) { console.error('保存字典项失败:', error); toast.push('保存失败: ' + (error.detail || error.message || '未知错误'), 'error'); } }; const handleDeleteItem = async function(item) { setConfirmConfig({ title: '确认删除', message: '确定要删除字典项"' + item.label + '"吗?', onConfirm: async () => { try { await window.apiFetch('/api/dict/item/' + item.id, { method: 'DELETE' }); toast.push('字典项删除成功', 'success'); fetchItems(selectedCategory.code); } catch (error) { console.error('删除字典项失败:', error); toast.push('删除失败: ' + (error.detail || error.message || '未知错误'), 'error'); } } }); setConfirmModalVisible(true); }; const handleDragStart = function(e, node) { if (node.id === 'root') return; setDraggingNode(node); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', JSON.stringify(node)); }; const handleDragOver = function(e, node) { if (!draggingNode || draggingNode.id === node.id) { e.preventDefault(); return; } e.preventDefault(); setDropTarget(node.id); }; const handleDragLeave = function() { setDropTarget(null); }; const handleDrop = async function(e, targetNode) { e.preventDefault(); if (!draggingNode || draggingNode.id === targetNode.id) { setDropTarget(null); setDraggingNode(null); return; } try { await window.apiFetch('/api/dict/category/' + draggingNode.id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...draggingNode, parent_id: targetNode.id }) }); toast.push('分类移动成功', 'success'); fetchCategories(); } catch (error) { console.error('移动分类失败:', error); toast.push('移动失败: ' + (error.detail || error.message || '未知错误'), 'error'); } setDropTarget(null); setDraggingNode(null); }; const handleDragEnd = function() { setDraggingNode(null); setDropTarget(null); }; const renderTree = function(nodes, level) { if (!Array.isArray(nodes)) return []; level = level || 0; var elements = []; // 根目录节点 if (level === 0) { var isRootActive = selectedCategory === null; var isRootDropTarget = dropTarget === 'root'; elements.push(
setSelectedCategory(null)} onDragOver={(e) => { e.preventDefault(); if (draggingNode) { setDropTarget('root'); } }} onDragLeave={() => { if (dropTarget === 'root') { setDropTarget(null); } }} onDrop={async (e) => { e.preventDefault(); if (!draggingNode) return; try { await window.apiFetch('/api/dict/category/' + draggingNode.id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...draggingNode, parent_id: 0 }) }); toast.push('分类移动成功', 'success'); fetchCategories(); } catch (error) { console.error('移动分类失败:', error); toast.push('移动失败: ' + (error.detail || error.message || '未知错误'), 'error'); } setDropTarget(null); setDraggingNode(null); }} > {Icons.home} 根目录
); } // 普通分类节点 nodes.forEach(function(node) { var isActive = selectedCategory && selectedCategory.id === node.id; var hasChildren = node.children && node.children.length > 0; var isExpanded = expandedNodes.has(node.id); var isDragging = draggingNode && draggingNode.id === node.id; var isDropTarget = dropTarget === node.id; elements.push(
setSelectedCategory(node)} onDragStart={(e) => handleDragStart(e, node)} onDragOver={(e) => handleDragOver(e, node)} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, node)} onDragEnd={handleDragEnd} > { e.stopPropagation(); if (hasChildren) toggleExpand(node.id); }} style={{ visibility: hasChildren ? 'visible' : 'hidden', cursor: hasChildren ? 'pointer' : 'default' }} > {isExpanded ? '▾' : '▸'} {hasChildren ? ( isExpanded ? Icons.folderOpen : Icons.folder ) : Icons.file} {node.name} ({node.code})
{hasChildren && isExpanded && (
{renderTree(node.children, level + 1)}
)}
); }); return elements; }; const [sidebarWidth, setSidebarWidth] = React.useState(320); const [isDragging, setIsDragging] = React.useState(false); const handleMouseDown = function(e) { setIsDragging(true); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); document.body.classList.add('dragging-no-select'); }; const handleMouseMove = function(e) { if (!isDragging) return; const container = document.querySelector('.dict-layout'); if (!container) return; const containerRect = container.getBoundingClientRect(); const newWidth = e.clientX - containerRect.left; const clampedWidth = Math.max(200, Math.min(400, newWidth)); setSidebarWidth(clampedWidth); }; const handleMouseUp = function() { setIsDragging(false); document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); document.body.classList.remove('dragging-no-select'); }; return (
{sidebarCollapsed ? (
) : ( <>

字典分类

{categories.length === 0 ? (
暂无分类
) : ( renderTree(categories) )}
)}

{selectedCategory ? selectedCategory.name : '请选择分类'}

{selectedCategory && ( <> | {selectedCategory.code}|重要等级:{selectedCategory.importance === "system" ? "系统级" : selectedCategory.importance === "application" ? "应用级" : selectedCategory.importance === "business" ? "业务级" : selectedCategory.importance === "temp" ? "临时级" : "普通级"} )}

{selectedCategory ? '请在下方管理该分类的字典项' : '请在左侧选择一个字典分类进行管理'}

{selectedCategory && ( <> )}
{!selectedCategory ? (
🏠

根目录

点击上方"新增"按钮可以添加一级分类

) : loading ? (
加载中...
) : ( {items.length === 0 ? ( ) : ( items.map(item => ( )) )}
显示名称 编码 父级编码 引用分类 排序 状态 备注 操作
暂无字典项
{item.label} {item.value} {item.parent_value || '—'} {item.ref_category_code || '—'} {item.sort} {item.status === 1 ? '启用' : '禁用'} {item.remark || '—'}
)}
{categoryModalVisible && editingCategory && (
setCategoryModalVisible(false)} >
e.stopPropagation()} >

{editingCategory.id ? '编辑分类' : '新增分类'}

分类信息
setEditingCategory(Object.assign({}, editingCategory, { code: e.target.value }))} />
setEditingCategory(Object.assign({}, editingCategory, { name: e.target.value }))} />
setEditingCategory(Object.assign({}, editingCategory, { sort: parseInt(e.target.value) || 1 }))} />
备注信息