stream_app/index.html
2025-04-22 18:59:06 +08:00

303 lines
12 KiB
HTML
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.

<!DOCTYPE html>
<html>
<head>
<title>集装箱装箱优化系统</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<style>
body { margin: 0; }
#container { width: 100vw; height: 100vh; }
#inputPanel {
position: absolute;
top: 20px;
left: 20px;
background: rgba(255,255,255,0.8);
padding: 20px;
border-radius: 8px;
font-family: Arial;
}
#result { margin-top: 20px; font-weight: bold; }
.axis { position: absolute; bottom: 20px; left: 20px; background: rgba(255,255,255,0.8); padding: 10px; border-radius: 4px; }
.tooltip {
position: absolute;
background: rgba(0,0,0,0.7);
color: white;
padding: 8px;
border-radius: 4px;
opacity: 0;
transition: opacity 0.3s;
}
.layer-checkbox { margin-bottom: 10px; }
#weightLimit { margin-top: 10px; }
.layer-checkboxes { margin-top: 20px; }
</style>
</head>
<body>
<div id="inputPanel">
<h3>参数输入</h3>
<div>
集装箱尺寸mm<br>
长:<input type="number" id="conLen" value="12014"><br>
宽:<input type="number" id="conWid" value="2337"><br>
高:<input type="number" id="conHei" value="2388"><br>
承重上限kg<input type="number" id="weightLimit" value="2000"><br>
</div>
<div>
纸箱尺寸mm<br>
长:<input type="number" id="boxLen" value="685"><br>
宽:<input type="number" id="boxWid" value="548"><br>
高:<input type="number" id="boxHei" value="489"><br>
重量kg<input type="number" id="boxWeight" value="10"><br>
</div>
<button onclick="calculate()">开始计算</button>
<div id="result"></div>
<div id="instructions" style="margin-top:20px; max-width:300px; line-height:1.5;"></div>
<div class="layer-checkboxes"></div>
</div>
<div id="axisInfo" class="axis">视角:前视图</div>
<div id="container"></div>
<script>
let scene, camera, renderer, controls;
let containerMesh, boxMeshes = [];
let result = {};
function initThree() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableZoom = true;
controls.enablePan = true;
controls.enableRotate = true;
// 初始化相机位置
camera.position.set(15000, 5000, 15000);
controls.target.set(0, 0, 0);
// 添加网格辅助线
const gridHelper = new THREE.GridHelper(20000, 20, 0xffffff, 0x444444);
gridHelper.material.opacity = 0.5;
scene.add(gridHelper);
// 添加坐标轴
const axesHelper = new THREE.AxesHelper(5000);
scene.add(axesHelper);
// 自适应窗口
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
async function calculate() {
const data = {
container: {
length: parseFloat(document.getElementById('conLen').value),
width: parseFloat(document.getElementById('conWid').value),
height: parseFloat(document.getElementById('conHei').value),
weightLimit: parseFloat(document.getElementById('weightLimit').value)
},
box: {
length: parseFloat(document.getElementById('boxLen').value),
width: parseFloat(document.getElementById('boxWid').value),
height: parseFloat(document.getElementById('boxHei').value),
weight: parseFloat(document.getElementById('boxWeight').value)
}
};
try {
const response = await fetch('/calculate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
result = await response.json();
document.getElementById('result').innerHTML = `最优装箱数:${result.count}`;
// 显示说明
const instructions = `
装箱方案说明:<br>
1. 纸箱尺寸:长${result.boxLength}mm ×${result.boxWidth}mm ×${result.boxHeight}mm<br>
2. 排列策略:${result.strategy}<br>
3. 空间利用率:${result.spaceUtilization.toFixed(2)}%<br>
4. 总重量:${result.totalWeight.toFixed(2)} kg承重上限${data.container.weightLimit} kg<br>
${result.totalWeight > data.container.weightLimit ? "⚠️ 超重!" : ""}
`;
document.getElementById('instructions').innerHTML = instructions;
// 清理场景
scene.remove(containerMesh);
boxMeshes.forEach(mesh => scene.remove(mesh));
boxMeshes = [];
// 创建集装箱
const containerGeo = new THREE.BoxGeometry(
data.container.length,
data.container.height,
data.container.width
);
const containerMat = new THREE.MeshBasicMaterial({
color: 0xAAAAAA,
wireframe: true
});
containerMesh = new THREE.Mesh(containerGeo, containerMat);
containerMesh.position.set(
data.container.length / 2,
data.container.height / 2,
data.container.width / 2
);
scene.add(containerMesh);
// 创建分层控制
const layerCheckboxes = document.querySelector('.layer-checkboxes');
layerCheckboxes.innerHTML = '';
result.layers.forEach((layer, index) => {
const div = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `layer-${index}`;
checkbox.checked = true;
checkbox.addEventListener('change', updateVisibility);
const label = document.createElement('label');
label.htmlFor = `layer-${index}`;
label.textContent = `${index + 1}层 (${layer.count}箱)`;
div.appendChild(checkbox);
div.appendChild(label);
layerCheckboxes.appendChild(div);
});
// 显示所有层
updateVisibility();
// 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1).normalize();
scene.add(light);
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
} catch (error) {
console.error('Error:', error);
document.getElementById('result').innerHTML = '计算失败,请检查输入参数。';
}
}
function updateVisibility() {
boxMeshes.forEach(mesh => scene.remove(mesh));
boxMeshes = [];
const checkedLayers = Array.from(document.querySelectorAll('.layer-checkboxes input:checked'))
.map(cb => parseInt(cb.id.split('-')[1]));
checkedLayers.forEach(layerIndex => {
const layer = result.layers[layerIndex];
layer.layout.forEach(pos => {
const boxGeo = new THREE.BoxGeometry(
result.boxLength,
result.boxHeight,
result.boxWidth
);
const boxMat = new THREE.MeshLambertMaterial({ color: 0xff0000 });
const box = new THREE.Mesh(boxGeo, boxMat);
// 设置中心坐标Three.js坐标系
box.position.set(
pos.x + result.boxLength / 2,
pos.y + result.boxHeight / 2,
pos.z + result.boxWidth / 2
);
// 应用旋转角度(弧度转角度)
box.rotation.set(
pos.rotationX * Math.PI / 180,
pos.rotationY * Math.PI / 180,
pos.rotationZ * Math.PI / 180
);
box.userData.index = pos.boxNumber;
box.addEventListener('pointerover', showTooltip);
box.addEventListener('pointerout', hideTooltip);
scene.add(box);
boxMeshes.push(box);
});
});
}
function showTooltip(event) {
const box = event.target;
const position = box.position;
const rotation = box.rotation;
const dimensions = box.geometry.parameters;
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.innerHTML = `
箱号:${box.userData.index}<br>
位置:(${position.x.toFixed(0)}, ${position.y.toFixed(0)}, ${position.z.toFixed(0)})<br>
尺寸:${dimensions.width}×${dimensions.height}×${dimensions.depth}<br>
旋转X=${(rotation.x * 180 / Math.PI).toFixed(0)}°,
Y=${(rotation.y * 180 / Math.PI).toFixed(0)}°,
Z=${(rotation.z * 180 / Math.PI).toFixed(0)}°
`;
document.body.appendChild(tooltip);
tooltip.style.left = `${event.clientX + 10}px`;
tooltip.style.top = `${event.clientY - 30}px`;
tooltip.style.opacity = 1;
}
function hideTooltip() {
const tooltips = document.querySelectorAll('.tooltip');
tooltips.forEach(t => t.remove());
}
window.addEventListener('keydown', (e) => {
const data = {
container: {
length: parseFloat(document.getElementById('conLen').value),
width: parseFloat(document.getElementById('conWid').value),
height: parseFloat(document.getElementById('conHei').value),
}
};
switch (e.key) {
case '1': // 前视图
camera.position.set(
data.container.length * 2,
data.container.height / 2,
data.container.width / 2
);
break;
case '2': // 顶视图
camera.position.set(
data.container.length / 2,
data.container.height * 2,
data.container.width / 2
);
break;
case '3': // 右视图
camera.position.set(
data.container.length / 2,
data.container.height / 2,
data.container.width * 2
);
break;
}
controls.update();
});
window.onload = function() {
initThree();
};
</script>
</body>
</html>