stream_app/index.html

305 lines
13 KiB
HTML
Raw Normal View History

2025-04-18 13:20:01 +08:00
<!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; }
2025-04-18 14:03:36 +08:00
.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; }
2025-04-21 14:12:26 +08:00
#weightLimit { margin-top: 10px; }
.layer-checkboxes { margin-top: 20px; }
2025-04-18 13:20:01 +08:00
</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>
2025-04-21 14:12:26 +08:00
承重上限kg<input type="number" id="weightLimit" value="2000"><br>
2025-04-18 13:20:01 +08:00
</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>
2025-04-21 14:12:26 +08:00
重量kg<input type="number" id="boxWeight" value="10"><br>
2025-04-18 13:20:01 +08:00
</div>
<button onclick="calculate()">开始计算</button>
<div id="result"></div>
2025-04-18 14:03:36 +08:00
<div id="instructions" style="margin-top:20px; max-width:300px; line-height:1.5;"></div>
<div class="layer-checkboxes"></div>
2025-04-18 13:20:01 +08:00
</div>
<div id="axisInfo" class="axis">视角:前视图</div>
<div id="container"></div>
<script>
let scene, camera, renderer, controls;
let containerMesh, boxMeshes = [];
let activeCamera = 'front';
2025-04-18 14:03:36 +08:00
let tooltip = document.createElement('div');
tooltip.className = 'tooltip';
let result = {};
2025-04-18 13:20:01 +08:00
function initThree() {
scene = new THREE.Scene();
2025-04-21 14:12:26 +08:00
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000);
2025-04-18 13:20:01 +08:00
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);
2025-04-21 14:12:26 +08:00
controls.enableZoom = true;
controls.enablePan = true;
controls.enableRotate = true;
// 初始化相机位置
2025-04-18 13:20:01 +08:00
camera.position.set(15000, 5000, 15000);
2025-04-21 14:12:26 +08:00
controls.target.set(0, 0, 0);
2025-04-18 13:20:01 +08:00
controls.update();
2025-04-21 14:12:26 +08:00
// 添加网格辅助线
const gridHelper = new THREE.GridHelper(20000, 20, 0xffffff, 0x444444);
gridHelper.material.opacity = 0.5;
gridHelper.material.transparent = true;
2025-04-18 13:20:01 +08:00
scene.add(gridHelper);
2025-04-21 14:12:26 +08:00
// 添加坐标轴辅助线
2025-04-18 13:20:01 +08:00
const axesHelper = new THREE.AxesHelper(5000);
2025-04-21 14:12:26 +08:00
axesHelper.material.color.set(0xff0000); // 红色坐标轴
2025-04-18 13:20:01 +08:00
scene.add(axesHelper);
animate();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
2025-04-18 14:03:36 +08:00
// 更新提示信息
if (document.querySelector('.tooltip')) {
document.body.removeChild(document.querySelector('.tooltip'));
}
document.body.appendChild(tooltip);
tooltip.style.opacity = 0;
2025-04-18 13:20:01 +08:00
}
async function calculate() {
const data = {
container: {
length: parseFloat(document.getElementById('conLen').value),
width: parseFloat(document.getElementById('conWid').value),
2025-04-21 14:12:26 +08:00
height: parseFloat(document.getElementById('conHei').value),
weightLimit: parseFloat(document.getElementById('weightLimit').value)
2025-04-18 13:20:01 +08:00
},
box: {
length: parseFloat(document.getElementById('boxLen').value),
width: parseFloat(document.getElementById('boxWid').value),
2025-04-21 14:12:26 +08:00
height: parseFloat(document.getElementById('boxHei').value),
weight: parseFloat(document.getElementById('boxWeight').value)
2025-04-18 13:20:01 +08:00
}
};
2025-04-21 14:12:26 +08:00
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 = '计算失败,请检查输入参数。';
}
2025-04-18 14:03:36 +08:00
}
function updateVisibility() {
boxMeshes.forEach(mesh => scene.remove(mesh));
boxMeshes = [];
2025-04-21 14:12:26 +08:00
// 纸箱材质:鲜艳的红色,不透明
const boxMat = new THREE.MeshLambertMaterial({
color: 0xff0000, // 红色
transparent: false
2025-04-18 13:20:01 +08:00
});
2025-04-21 14:12:26 +08:00
2025-04-18 14:03:36 +08:00
const checkedLayers = Array.from(document.querySelectorAll('.layer-checkboxes input:checked'))
.map(cb => parseInt(cb.id.split('-')[1]));
2025-04-21 14:12:26 +08:00
2025-04-18 14:03:36 +08:00
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);
2025-04-21 14:12:26 +08:00
// 设置箱子中心位置
2025-04-18 14:03:36 +08:00
box.position.set(
2025-04-21 14:12:26 +08:00
pos.x + result.boxLength / 2,
pos.y + result.boxHeight / 2,
pos.z + result.boxWidth / 2
2025-04-18 14:03:36 +08:00
);
2025-04-21 14:12:26 +08:00
// 应用旋转角度
2025-04-18 14:03:36 +08:00
box.rotation.set(
THREE.MathUtils.degToRad(pos.rotationX),
THREE.MathUtils.degToRad(pos.rotationY),
THREE.MathUtils.degToRad(pos.rotationZ)
);
2025-04-21 14:12:26 +08:00
2025-04-18 14:03:36 +08:00
box.userData.index = pos.boxNumber;
2025-04-21 14:12:26 +08:00
box.addEventListener('pointerover', showTooltip);
box.addEventListener('pointerout', hideTooltip);
2025-04-18 14:03:36 +08:00
scene.add(box);
boxMeshes.push(box);
});
2025-04-18 13:20:01 +08:00
});
2025-04-18 14:03:36 +08:00
}
2025-04-18 13:20:01 +08:00
2025-04-18 14:03:36 +08:00
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>
2025-04-21 14:12:26 +08:00
旋转X=${(rotation.x * 180 / Math.PI).toFixed(0)}°,
Y=${(rotation.y * 180 / Math.PI).toFixed(0)}°,
Z=${(rotation.z * 180 / Math.PI).toFixed(0)}°
2025-04-18 14:03:36 +08:00
`;
tooltip.style.left = `${event.clientX + 10}px`;
tooltip.style.top = `${event.clientY - 30}px`;
tooltip.style.opacity = 1;
}
function hideTooltip() {
tooltip.style.opacity = 0;
2025-04-18 13:20:01 +08:00
}
window.addEventListener('keydown', (e) => {
2025-04-21 14:12:26 +08:00
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);
2025-04-18 13:20:01 +08:00
activeCamera = 'front';
break;
2025-04-21 14:12:26 +08:00
case '2': // 后视图
camera.position.set(-data.container.length * 2, data.container.height / 2, data.container.width / 2);
2025-04-18 13:20:01 +08:00
activeCamera = 'back';
break;
2025-04-21 14:12:26 +08:00
case '3': // 顶视图
camera.position.set(data.container.length / 2, data.container.height * 2, data.container.width / 2);
2025-04-18 13:20:01 +08:00
activeCamera = 'top';
break;
}
2025-04-21 14:12:26 +08:00
controls.update();
2025-04-18 13:20:01 +08:00
document.getElementById('axisInfo').innerHTML = `视角:${activeCamera}`;
});
2025-04-21 14:12:26 +08:00
// 确保在页面加载完成后初始化Three.js
window.onload = function() {
initThree();
};
2025-04-18 13:20:01 +08:00
</script>
</body>
</html>