cadeditfronttest/app.js
2025-09-12 18:44:30 +08:00

298 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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));