stream_app/index.html
2025-04-21 14:12:26 +08:00

305 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;
}
.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 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);
controls.enableZoom = true;
controls.enablePan = true;
controls.enableRotate = true;
// 初始化相机位置
camera.position.set(15000, 5000, 15000);
controls.target.set(0, 0, 0);
controls.update();
// 添加网格辅助线
const gridHelper = new THREE.GridHelper(20000, 20, 0xffffff, 0x444444);
gridHelper.material.opacity = 0.5;
gridHelper.material.transparent = true;
scene.add(gridHelper);
// 添加坐标轴辅助线
const axesHelper = new THREE.AxesHelper(5000);
axesHelper.material.color.set(0xff0000); // 红色坐标轴
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),
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();
console.log(result); // 添加日志
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,
opacity: 0.5
});
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 directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(10000, 10000, 10000);
scene.add(directionalLight);
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 boxMat = new THREE.MeshLambertMaterial({
color: 0xff0000, // 红色
transparent: false
});
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.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;
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) => {
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);
activeCamera = 'front';
break;
case '2': // 后视图
camera.position.set(-data.container.length * 2, data.container.height / 2, data.container.width / 2);
activeCamera = 'back';
break;
case '3': // 顶视图
camera.position.set(data.container.length / 2, data.container.height * 2, data.container.width / 2);
activeCamera = 'top';
break;
}
controls.update();
document.getElementById('axisInfo').innerHTML = `视角:${activeCamera}`;
});
// 确保在页面加载完成后初始化Three.js
window.onload = function() {
initThree();
};
</script>
</body>
</html>