stream_app/index.html
2025-04-23 11:13:26 +08:00

308 lines
13 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;
white-space: nowrap;
}
.layer-checkbox { margin-bottom: 10px; }
#weightLimit { margin-top: 10px; }
.layer-checkboxes { margin-top: 20px; }
.box-tooltip {
font-size: 14px;
line-height: 1.2;
}
</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 boxMatFill = new THREE.MeshLambertMaterial({ color: 0xFFAAAA });
const boxFill = new THREE.Mesh(boxGeo, boxMatFill);
// 创建激光线框
const lineGeo = new THREE.EdgesGeometry(boxGeo);
const lineMat = new THREE.LineBasicMaterial({
color: 0xffff00,
linewidth: 2
});
const line = new THREE.LineSegments(lineGeo, lineMat);
boxFill.add(line);
boxFill.position.set(
pos.x + result.boxLength / 2,
pos.y + result.boxHeight / 2,
pos.z + result.boxWidth / 2
);
boxFill.rotation.set(
pos.rotationX * Math.PI / 180,
pos.rotationY * Math.PI / 180,
pos.rotationZ * Math.PI / 180
);
boxFill.userData.index = pos.boxNumber;
boxFill.addEventListener('pointerover', showTooltip);
boxFill.addEventListener('pointerout', hideTooltip);
scene.add(boxFill);
boxMeshes.push(boxFill);
});
});
}
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>