const { useState, useEffect } = React; // 模拟的初始数据 const initialData = { drawings: { 'drawing-1': { id: 'drawing-1', name: '总装图', drawingNumber: 'Abcd', url: 'assets/images/drawing_main.svg' }, 'drawing-2': { id: 'drawing-2', name: '组件A', drawingNumber: 'Bcde', url: 'assets/images/drawing_part_A.svg' }, 'drawing-3': { id: 'drawing-3', name: '组件B', drawingNumber: 'Cdef', url: null }, // 无图纸 'drawing-4': { id: 'drawing-4', name: '零件C', drawingNumber: 'Defg', url: null }, }, items: { '__ROOT__': { id: '__ROOT__', name: '总装图', quantity: 1, weight: 0, drawingId: 'drawing-1', parentId: null, children: ['1', '2'] }, '1': { id: '1', name: '产品', quantity: 1, weight: 0, drawingId: 'drawing-1', parentId: '__ROOT__', children: ['1-1', '1-2'] }, '1-1': { id: '1-1', name: '部件A', quantity: 1, weight: 10, drawingId: 'drawing-2', parentId: '1', children: ['1-1-1'] }, '1-2': { id: '1-2', name: '部件B', quantity: 2, weight: 5, drawingId: 'drawing-3', parentId: '1', children: [] }, '1-1-1': { id: '1-1-1', name: '零件C', quantity: 3, weight: 2, drawingId: 'drawing-4', parentId: '1-1', children: [] }, '2': { id: '2', name: '螺丝', quantity: 10, weight: 0.1, drawingId: null, parentId: '__ROOT__', children: [] }, }, // 存储表格在图纸上的位置 tablePositions: { 'drawing-1': { x: 50, y: 50 }, 'drawing-2': { x: 30, y: 40 }, 'drawing-3': { x: 60, y: 70 }, } }; function App() { const [drawings, setDrawings] = useState(initialData.drawings); const [items, setItems] = useState(initialData.items); const [tablePositions, setTablePositions] = useState(initialData.tablePositions); const [selectedItemId, setSelectedItemId] = useState('__ROOT__'); // 自动计算重量的副作用钩子 useEffect(() => { const calculateWeight = (itemId) => { const item = items[itemId]; if (!item) return 0; if (item.children.length === 0) { return item.quantity * (item.weight || 0); } const childrenWeight = item.children.reduce((sum, childId) => { return sum + calculateWeight(childId); }, 0); // 更新当前项的重量(如果是父项) if (items[itemId].weight !== childrenWeight) { setItems(prevItems => ({ ...prevItems, [itemId]: { ...prevItems[itemId], weight: childrenWeight } })); } return childrenWeight; }; // 从根节点开始计算 const rootItems = Object.keys(items).filter(id => items[id].parentId === null); rootItems.forEach(calculateWeight); }, [items]); // 依赖于 items 状态 const handleItemRename = (oldId, newId) => { if (oldId === '__ROOT__') { alert('错误:根节点“总装图”的ID不可修改。'); return; } if (!newId || oldId === newId) return; if (items[newId]) { alert(`错误:件号 "${newId}" 已存在,请使用唯一的件号。`); return; } setItems(prevItems => { const newItems = { ...prevItems }; const itemToMove = { ...newItems[oldId] }; const oldParentId = itemToMove.parentId; // 1. 从旧的父节点移除 if (oldParentId && newItems[oldParentId]) { newItems[oldParentId].children = newItems[oldParentId].children.filter(id => id !== oldId); } // 2. 递归更新所有子孙 const descendantsToUpdate = {}; const updateDescendantsRecursively = (currentOldId, currentNewId) => { const item = newItems[currentOldId]; if (!item || !item.children) return; item.children.forEach(childOldId => { // e.g., child '1-1-1' of '1-1' becomes child '3-1' of '3' const newChildId = childOldId.replace(currentOldId, currentNewId); const childItem = { ...newItems[childOldId] }; childItem.id = newChildId; childItem.parentId = currentNewId; descendantsToUpdate[childOldId] = { newId: newChildId, newItem: childItem }; updateDescendantsRecursively(childOldId, newChildId); }); }; updateDescendantsRecursively(oldId, newId); // 3. 应用子孙更新 Object.values(descendantsToUpdate).forEach(({ newId, newItem }) => { newItem.children = newItem.children.map(cid => descendantsToUpdate[cid] ? descendantsToUpdate[cid].newId : cid); newItems[newId] = newItem; }); Object.keys(descendantsToUpdate).forEach(oldDescendantId => delete newItems[oldDescendantId]); // 4. 更新当前项本身 itemToMove.id = newId; itemToMove.parentId = newId.includes('-') ? newId.substring(0, newId.lastIndexOf('-')) : '__ROOT__'; itemToMove.children = itemToMove.children.map(cid => descendantsToUpdate[cid] ? descendantsToUpdate[cid].newId : cid); delete newItems[oldId]; newItems[newId] = itemToMove; // 5. 添加到新的父节点 const newParentId = itemToMove.parentId; if (newParentId) { if (!newItems[newParentId]) { // 如果新父项不存在,则创建它 (e.g. renaming 1-1 to 3-1-1, and 3-1 does not exist) newItems[newParentId] = { id: newParentId, name: `新建部件 ${newParentId}`, quantity: 1, weight: 0, drawingId: null, parentId: newParentId.includes('-') ? newParentId.substring(0, newParentId.lastIndexOf('-')) : '__ROOT__', children: [] }; } if (!newItems[newParentId].children.includes(newId)) { newItems[newParentId].children.push(newId); } } return newItems; }); setSelectedItemId(newId); }; const handleCycleDrawing = (direction) => { const drawingsInUse = [...new Set(Object.values(items).map(i => i.drawingId).filter(Boolean))]; if (drawingsInUse.length <= 1) return; const currentDrawingId = items[selectedItemId]?.drawingId; let currentIndex = drawingsInUse.indexOf(currentDrawingId); if (currentIndex === -1) currentIndex = 0; let nextIndex = currentIndex + direction; if (nextIndex >= drawingsInUse.length) nextIndex = 0; if (nextIndex < 0) nextIndex = drawingsInUse.length - 1; const nextDrawingId = drawingsInUse[nextIndex]; // 找到第一个使用该图纸的 item 并选中它 const itemToSelect = Object.values(items).find(i => i.drawingId === nextDrawingId); if (itemToSelect) { setSelectedItemId(itemToSelect.id); } }; const handleItemUpdate = (newItemData) => { const { id } = newItemData; // 检查父节点是否存在 const parentId = id.includes('-') ? id.substring(0, id.lastIndexOf('-')) : null; setItems(prevItems => { const newItems = { ...prevItems }; // 如果父节点不存在,则创建 if (parentId && !newItems[parentId]) { newItems[parentId] = { id: parentId, name: `新建部件 ${parentId}`, quantity: 1, weight: 0, drawingId: null, // 父项通常关联更高级别的图纸 parentId: parentId.includes('-') ? parentId.substring(0, parentId.lastIndexOf('-')) : null, children: [] }; } // 更新或创建当前项 newItems[id] = { ...newItems[id], ...newItemData }; // 更新父节点的 children 列表 if (parentId && newItems[parentId] && !newItems[parentId].children.includes(id)) { newItems[parentId] = { ...newItems[parentId], children: [...newItems[parentId].children, id] }; } return newItems; }); // 选中新创建的项 setSelectedItemId(id); }; const bindDrawingToItem = (itemId, drawingId) => { setItems(prev => ({ ...prev, [itemId]: { ...prev[itemId], drawingId } })); }; const handleSave = () => { const emptyItems = Object.values(items).filter(item => { return !item.id || (item.weight === 0 && item.children.length === 0); }); if (emptyItems.length > 0) { const itemIds = emptyItems.map(item => item.id || '[空ID]').join(', '); alert(`校验失败!\n以下件号存在问题(ID为空或重量为0):\n${itemIds}\n\n请处理后再保存。`); return; } alert('校验通过,保存成功!\n(原型功能,数据未实际持久化)'); }; const handleExport = () => { // 模拟DXF文件内容 let dxfContent = "0\nSECTION\n2\nENTITIES\n"; Object.values(items).forEach(item => { dxfContent += "0\nTEXT\n"; dxfContent += `10\n${Math.random() * 100}\n`; // X coordinate dxfContent += `20\n${Math.random() * 100}\n`; // Y coordinate dxfContent += `40\n8.0\n`; // Text height dxfContent += `1\nID: ${item.id}, Name: ${item.name}, Qty: ${item.quantity}, W: ${item.weight}\n`; }); dxfContent += "0\nENDSEC\n0\nEOF\n"; // 创建并触发下载 const blob = new Blob([dxfContent], { type: 'application/dxf' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `export_${new Date().toISOString()}.dxf`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert('DXF文件已开始下载。'); }; const selectedItem = items[selectedItemId]; const currentDrawing = drawings[selectedItem?.drawingId]; return React.createElement('div', { className: 'flex h-screen bg-gray-100' }, React.createElement('aside', { className: 'w-[var(--sidebar-width)] bg-white p-4 border-r border-gray-200' }, React.createElement(StructureTree, { items, selectedItemId, setSelectedItemId }) ), React.createElement('main', { className: 'flex-1 flex flex-col' }, React.createElement(Header, { drawings, items, onSave: handleSave, onExport: handleExport }), React.createElement('div', { className: 'flex-1 p-4 grid grid-cols-3 gap-4' }, React.createElement('div', { className: 'col-span-2' }, React.createElement(DrawingViewer, { drawing: currentDrawing, item: selectedItem, items, tablePositions, setTablePositions, drawings, onItemUpdate: handleItemUpdate, // This is now for creating children, which is not used by the UI but kept for future. onItemRename: handleItemRename, onBindDrawing: bindDrawingToItem, onCycleDrawing: handleCycleDrawing }) ), React.createElement('div', { className: 'col-span-1' }, React.createElement(DataEditor, { item: selectedItem, items, setItems }) ) ) ) ); } const container = document.getElementById('root'); const root = ReactDOM.createRoot(container); root.render(React.createElement(App));