stream_app/index.html
2025-04-18 14:03:36 +08:00

269 lines
10 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; }
</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>
</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>
</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 activeCamera = 'front';
let tooltip = document.createElement('div');
tooltip.className = 'tooltip';
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);
camera.position.set(15000, 5000, 15000);
controls.update();
const gridHelper = new THREE.GridHelper(20000, 20, 0x888888, 0x888888);
scene.add(gridHelper);
const axesHelper = new THREE.AxesHelper(5000);
scene.add(axesHelper);
animate();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
// 更新提示信息
if (document.querySelector('.tooltip')) {
document.body.removeChild(document.querySelector('.tooltip'));
}
document.body.appendChild(tooltip);
tooltip.style.opacity = 0;
}
async function calculate() {
const data = {
container: {
length: parseFloat(document.getElementById('conLen').value),
width: parseFloat(document.getElementById('conWid').value),
height: parseFloat(document.getElementById('conHei').value)
},
box: {
length: parseFloat(document.getElementById('boxLen').value),
width: parseFloat(document.getElementById('boxWid').value),
height: parseFloat(document.getElementById('boxHei').value)
}
};
const response = await fetch('/calculate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
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.density.toFixed(2)}%<br>
4. 空间利用率:${result.spaceUtilization.toFixed(2)}%<br>
5. 实际占用体积:${(result.usedVolume/1000000000).toFixed(2)}
`;
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: 0x333333,
wireframe: true,
transparent: true,
opacity: 0.3
});
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(10000, 10000, 10000);
scene.add(light);
}
function updateVisibility() {
// 移除现有箱子
boxMeshes.forEach(mesh => scene.remove(mesh));
boxMeshes = [];
const boxMat = new THREE.MeshLambertMaterial({
color: 0x00ff00,
transparent: true,
opacity: 0.6
});
// 根据选中层创建箱子
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 box = new THREE.Mesh(boxGeo, boxMat);
box.position.set(
pos.x + result.boxLength/2,
pos.y + result.boxHeight/2,
pos.z + result.boxWidth/2
);
box.rotation.set(
THREE.MathUtils.degToRad(pos.rotationX),
THREE.MathUtils.degToRad(pos.rotationY),
THREE.MathUtils.degToRad(pos.rotationZ)
);
box.userData.index = pos.boxNumber;
box.on('pointerover', showTooltip);
box.on('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;
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)}°
`;
tooltip.style.left = `${event.clientX + 10}px`;
tooltip.style.top = `${event.clientY - 30}px`;
tooltip.style.opacity = 1;
}
function hideTooltip() {
tooltip.style.opacity = 0;
}
window.addEventListener('keydown', (e) => {
switch(e.key) {
case '1':
camera.position.set(15000, 5000, 15000);
activeCamera = 'front';
break;
case '2':
camera.position.set(-15000, 5000, 15000);
activeCamera = 'back';
break;
case '3':
camera.position.set(0, 15000, 0);
activeCamera = 'top';
break;
}
document.getElementById('axisInfo').innerHTML = `视角:${activeCamera}`;
});
initThree();
</script>
</body>
</html>