test
This commit is contained in:
parent
d596db7e29
commit
441a7d1bd7
Binary file not shown.
Binary file not shown.
125
index.html
125
index.html
@ -18,6 +18,16 @@
|
|||||||
}
|
}
|
||||||
#result { margin-top: 20px; font-weight: bold; }
|
#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; }
|
.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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -37,6 +47,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<button onclick="calculate()">开始计算</button>
|
<button onclick="calculate()">开始计算</button>
|
||||||
<div id="result"></div>
|
<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>
|
||||||
|
|
||||||
<div id="axisInfo" class="axis">视角:前视图</div>
|
<div id="axisInfo" class="axis">视角:前视图</div>
|
||||||
@ -46,6 +58,9 @@
|
|||||||
let scene, camera, renderer, controls;
|
let scene, camera, renderer, controls;
|
||||||
let containerMesh, boxMeshes = [];
|
let containerMesh, boxMeshes = [];
|
||||||
let activeCamera = 'front';
|
let activeCamera = 'front';
|
||||||
|
let tooltip = document.createElement('div');
|
||||||
|
tooltip.className = 'tooltip';
|
||||||
|
let result = {};
|
||||||
|
|
||||||
function initThree() {
|
function initThree() {
|
||||||
scene = new THREE.Scene();
|
scene = new THREE.Scene();
|
||||||
@ -71,6 +86,13 @@
|
|||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
controls.update();
|
controls.update();
|
||||||
renderer.render(scene, camera);
|
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() {
|
async function calculate() {
|
||||||
@ -93,13 +115,26 @@
|
|||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
result = await response.json();
|
||||||
document.getElementById('result').innerHTML = `最优装箱数:${result.count}`;
|
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)} m³
|
||||||
|
`;
|
||||||
|
document.getElementById('instructions').innerHTML = instructions;
|
||||||
|
|
||||||
|
// 清理现有场景
|
||||||
scene.remove(containerMesh);
|
scene.remove(containerMesh);
|
||||||
boxMeshes.forEach(mesh => scene.remove(mesh));
|
boxMeshes.forEach(mesh => scene.remove(mesh));
|
||||||
boxMeshes = [];
|
boxMeshes = [];
|
||||||
|
|
||||||
|
// 创建集装箱
|
||||||
const containerGeo = new THREE.BoxGeometry(
|
const containerGeo = new THREE.BoxGeometry(
|
||||||
data.container.length,
|
data.container.length,
|
||||||
data.container.height,
|
data.container.height,
|
||||||
@ -115,31 +150,99 @@
|
|||||||
containerMesh.position.set(data.container.length/2, data.container.height/2, data.container.width/2);
|
containerMesh.position.set(data.container.length/2, data.container.height/2, data.container.width/2);
|
||||||
scene.add(containerMesh);
|
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({
|
const boxMat = new THREE.MeshLambertMaterial({
|
||||||
color: 0x00ff00,
|
color: 0x00ff00,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
});
|
});
|
||||||
|
|
||||||
result.layout.forEach(pos => {
|
// 根据选中层创建箱子
|
||||||
|
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(
|
const boxGeo = new THREE.BoxGeometry(
|
||||||
data.box.length,
|
result.boxLength,
|
||||||
data.box.height,
|
result.boxHeight,
|
||||||
data.box.width
|
result.boxWidth
|
||||||
);
|
);
|
||||||
const box = new THREE.Mesh(boxGeo, boxMat);
|
const box = new THREE.Mesh(boxGeo, boxMat);
|
||||||
box.position.set(
|
box.position.set(
|
||||||
pos.x + data.box.length/2,
|
pos.x + result.boxLength/2,
|
||||||
pos.y + data.box.height/2,
|
pos.y + result.boxHeight/2,
|
||||||
pos.z + data.box.width/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);
|
scene.add(box);
|
||||||
boxMeshes.push(box);
|
boxMeshes.push(box);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const light = new THREE.DirectionalLight(0xffffff, 1);
|
function showTooltip(event) {
|
||||||
light.position.set(10000, 10000, 10000);
|
const box = event.target;
|
||||||
scene.add(light);
|
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) => {
|
window.addEventListener('keydown', (e) => {
|
||||||
|
|||||||
46
main.go
46
main.go
@ -22,6 +22,18 @@ type Placement struct {
|
|||||||
X, Y, Z float64 `json:"x"`
|
X, Y, Z float64 `json:"x"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Layout []Placement `json:"layout"`
|
||||||
|
BoxLength float64 `json:"boxLength"`
|
||||||
|
BoxWidth float64 `json:"boxWidth"`
|
||||||
|
BoxHeight float64 `json:"boxHeight"`
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
Density float64 `json:"density"`
|
||||||
|
SpaceUtilization float64 `json:"spaceUtilization"`
|
||||||
|
UsedVolume float64 `json:"usedVolume"`
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.ServeFile(w, r, "index.html")
|
http.ServeFile(w, r, "index.html")
|
||||||
@ -34,15 +46,19 @@ func main() {
|
|||||||
}
|
}
|
||||||
json.NewDecoder(r.Body).Decode(&data)
|
json.NewDecoder(r.Body).Decode(&data)
|
||||||
|
|
||||||
layout := optimizePacking(data.Container, data.Box)
|
layout, bestRotation, strategy, density, spaceUtilization, usedVolume := optimizePacking(data.Container, data.Box)
|
||||||
count := len(layout)
|
count := len(layout)
|
||||||
|
|
||||||
response := struct {
|
response := Response{
|
||||||
Count int `json:"count"`
|
|
||||||
Layout []Placement `json:"layout"`
|
|
||||||
}{
|
|
||||||
Count: count,
|
Count: count,
|
||||||
Layout: layout,
|
Layout: layout,
|
||||||
|
BoxLength: bestRotation.Length,
|
||||||
|
BoxWidth: bestRotation.Width,
|
||||||
|
BoxHeight: bestRotation.Height,
|
||||||
|
Strategy: strategy,
|
||||||
|
Density: density,
|
||||||
|
SpaceUtilization: spaceUtilization,
|
||||||
|
UsedVolume: usedVolume,
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
@ -51,11 +67,17 @@ func main() {
|
|||||||
http.ListenAndServe(":8080", nil)
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func optimizePacking(con Container, box Box) []Placement {
|
func optimizePacking(con Container, box Box) ([]Placement, Box, string, float64, float64, float64) {
|
||||||
var bestLayout []Placement
|
var bestLayout []Placement
|
||||||
|
var bestRotation Box
|
||||||
|
var bestStrategy string
|
||||||
maxCount := 0
|
maxCount := 0
|
||||||
|
var bestDensity float64
|
||||||
|
var bestSpaceUtilization float64
|
||||||
|
var bestUsedVolume float64
|
||||||
|
|
||||||
rotations := generateRotations(box)
|
rotations := generateRotations(box)
|
||||||
|
conVolume := con.Length * con.Width * con.Height
|
||||||
|
|
||||||
for _, r := range rotations {
|
for _, r := range rotations {
|
||||||
for _, strategy := range []string{"XY", "XZ", "YX", "YZ", "ZX", "ZY"} {
|
for _, strategy := range []string{"XY", "XZ", "YX", "YZ", "ZX", "ZY"} {
|
||||||
@ -90,13 +112,19 @@ func optimizePacking(con Container, box Box) []Placement {
|
|||||||
total := int(xCount * yCount * zCount)
|
total := int(xCount * yCount * zCount)
|
||||||
if total > maxCount && total > 0 {
|
if total > maxCount && total > 0 {
|
||||||
maxCount = total
|
maxCount = total
|
||||||
layout := generateLayout(r, xCount, yCount, zCount, strategy)
|
bestRotation = r
|
||||||
bestLayout = layout
|
bestStrategy = strategy
|
||||||
|
bestLayout = generateLayout(r, xCount, yCount, zCount, strategy)
|
||||||
|
|
||||||
|
boxVolume := r.Length * r.Width * r.Height
|
||||||
|
bestUsedVolume = float64(total) * boxVolume
|
||||||
|
bestDensity = (bestUsedVolume / conVolume) * 100
|
||||||
|
bestSpaceUtilization = bestDensity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestLayout
|
return bestLayout, bestRotation, bestStrategy, bestDensity, bestSpaceUtilization, bestUsedVolume
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRotations(box Box) []Box {
|
func generateRotations(box Box) []Box {
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>集装箱装箱计算器</title>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
||||||
<style>
|
|
||||||
#container {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
#controls {
|
|
||||||
width: 300px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
.input-group {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
background: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
#visualization {
|
|
||||||
width: 800px;
|
|
||||||
height: 600px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="container">
|
|
||||||
<div id="controls">
|
|
||||||
<h2>集装箱参数</h2>
|
|
||||||
<div class="input-group">
|
|
||||||
<label>长度: <input type="number" id="contLength" step="0.1"></label>
|
|
||||||
<label>宽度: <input type="number" id="contWidth" step="0.1"></label>
|
|
||||||
<label>高度: <input type="number" id="contHeight" step="0.1"></label>
|
|
||||||
<label>承重上限: <input type="number" id="contWeight" step="0.1"></label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>纸箱参数</h2>
|
|
||||||
<div class="input-group">
|
|
||||||
<label>长度: <input type="number" id="boxLength" step="0.1"></label>
|
|
||||||
<label>宽度: <input type="number" id="boxWidth" step="0.1"></label>
|
|
||||||
<label>高度: <input type="number" id="boxHeight" step="0.1"></label>
|
|
||||||
<label>重量: <input type="number" id="boxWeight" step="0.1"></label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button onclick="calculate()">开始计算</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="visualization"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
107
static/main.js
107
static/main.js
@ -1,107 +0,0 @@
|
|||||||
let scene, camera, renderer, controls;
|
|
||||||
|
|
||||||
function initThree() {
|
|
||||||
// 初始化场景
|
|
||||||
scene = new THREE.Scene();
|
|
||||||
camera = new THREE.PerspectiveCamera(75,
|
|
||||||
window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
||||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
||||||
renderer.setSize(800, 600);
|
|
||||||
document.getElementById('visualization').appendChild(renderer.domElement);
|
|
||||||
|
|
||||||
// 添加光源
|
|
||||||
const light = new THREE.AmbientLight(0xffffff, 0.8);
|
|
||||||
scene.add(light);
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
|
|
||||||
directionalLight.position.set(0, 1, 1);
|
|
||||||
scene.add(directionalLight);
|
|
||||||
|
|
||||||
// 设置相机位置
|
|
||||||
camera.position.set(10, 10, 10);
|
|
||||||
camera.lookAt(0, 0, 0);
|
|
||||||
|
|
||||||
// 添加轨道控制器
|
|
||||||
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearScene() {
|
|
||||||
while(scene.children.length > 0){
|
|
||||||
scene.remove(scene.children[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderContainer(container) {
|
|
||||||
// 绘制集装箱线框
|
|
||||||
const geometry = new THREE.BoxGeometry(
|
|
||||||
container.Length,
|
|
||||||
container.Width,
|
|
||||||
container.Height
|
|
||||||
);
|
|
||||||
const edges = new THREE.EdgesGeometry(geometry);
|
|
||||||
const line = new THREE.LineSegments(
|
|
||||||
edges,
|
|
||||||
new THREE.LineBasicMaterial({ color: 0x000000 })
|
|
||||||
);
|
|
||||||
scene.add(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderBoxes(placements) {
|
|
||||||
placements.forEach(pos => {
|
|
||||||
const geometry = new THREE.BoxGeometry(pos.Length, pos.Width, pos.Height);
|
|
||||||
const material = new THREE.MeshPhongMaterial({
|
|
||||||
color: 0x00ff00,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.7
|
|
||||||
});
|
|
||||||
const cube = new THREE.Mesh(geometry, material);
|
|
||||||
cube.position.set(
|
|
||||||
pos.X + pos.Length/2,
|
|
||||||
pos.Y + pos.Width/2,
|
|
||||||
pos.Z + pos.Height/2
|
|
||||||
);
|
|
||||||
scene.add(cube);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calculate() {
|
|
||||||
// 获取输入值
|
|
||||||
const container = {
|
|
||||||
Length: parseFloat(document.getElementById('contLength').value),
|
|
||||||
Width: parseFloat(document.getElementById('contWidth').value),
|
|
||||||
Height: parseFloat(document.getElementById('contHeight').value),
|
|
||||||
MaxWeight: parseFloat(document.getElementById('contWeight').value)
|
|
||||||
};
|
|
||||||
|
|
||||||
const box = {
|
|
||||||
Length: parseFloat(document.getElementById('boxLength').value),
|
|
||||||
Width: parseFloat(document.getElementById('boxWidth').value),
|
|
||||||
Height: parseFloat(document.getElementById('boxHeight').value),
|
|
||||||
Weight: parseFloat(document.getElementById('boxWeight').value)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 发送请求
|
|
||||||
const response = await fetch('/api/calculate', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ container, box })
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
// 更新可视化
|
|
||||||
clearScene();
|
|
||||||
renderContainer(container);
|
|
||||||
renderBoxes(result.placements);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
initThree();
|
|
||||||
animate();
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
controls.update();
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
</h1>
|
|
||||||
<p>Sorry, the page you are looking for does not exist.</p>
|
|
||||||
<a href="/">Go back to the homepage</a>
|
|
||||||
</div>
|
|
||||||
@ -1,196 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>装货方案计算</title>
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="bg-gray-100">
|
|
||||||
<div class="container mx-auto p-4">
|
|
||||||
<h1 class="text-3xl font-bold text-center text-blue-600 mb-8">装货方案计算</h1>
|
|
||||||
<div class="bg-white p-6 rounded-md shadow-md">
|
|
||||||
<h2 class="text-xl font-bold mb-4">输入纸箱信息</h2>
|
|
||||||
<div id="cartons" class="mb-4">
|
|
||||||
<div class="flex mb-2">
|
|
||||||
<input type="number" placeholder="长" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="宽" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="高" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="重量" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<button onclick="addCartonInput()" class="bg-blue-500 text-white p-2 rounded-md hover:bg-blue-600">添加纸箱</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-xl font-bold mb-4">输入集装箱信息</h2>
|
|
||||||
<div id="containers" class="mb-4">
|
|
||||||
<div class="flex mb-2">
|
|
||||||
<input type="number" placeholder="长" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="宽" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="高" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="最大载重" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<button onclick="addContainerInput()" class="bg-blue-500 text-white p-2 rounded-md hover:bg-blue-600">添加集装箱</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button onclick="calculate()" class="bg-green-500 text-white p-2 rounded-md hover:bg-green-600">计算最优方案</button>
|
|
||||||
</div>
|
|
||||||
<div id="result" class="mt-8 bg-white p-6 rounded-md shadow-md"></div>
|
|
||||||
<div class="mt-8 bg-white p-6 rounded-md shadow-md">
|
|
||||||
<h3 class="text-xl font-bold mb-4">3D摆放视图</h3>
|
|
||||||
<div id="3d-view"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function addCartonInput() {
|
|
||||||
const cartonsDiv = document.getElementById('cartons');
|
|
||||||
const newCarton = document.createElement('div');
|
|
||||||
newCarton.classList.add('flex', 'mb-2');
|
|
||||||
newCarton.innerHTML = `
|
|
||||||
<input type="number" placeholder="长" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="宽" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="高" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="重量" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
`;
|
|
||||||
cartonsDiv.appendChild(newCarton);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addContainerInput() {
|
|
||||||
const containersDiv = document.getElementById('containers');
|
|
||||||
const newContainer = document.createElement('div');
|
|
||||||
newContainer.classList.add('flex', 'mb-2');
|
|
||||||
newContainer.innerHTML = `
|
|
||||||
<input type="number" placeholder="长" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="宽" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="高" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
<input type="number" placeholder="最大载重" class="border border-gray-300 p-2 mr-2 w-1/4 rounded-md">
|
|
||||||
`;
|
|
||||||
containersDiv.appendChild(newContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculate() {
|
|
||||||
const cartonsInputs = document.querySelectorAll('#cartons input');
|
|
||||||
const containersInputs = document.querySelectorAll('#containers input');
|
|
||||||
|
|
||||||
let cartons = [];
|
|
||||||
for (let i = 0; i < cartonsInputs.length; i += 4) {
|
|
||||||
const length = parseFloat(cartonsInputs[i].value);
|
|
||||||
const width = parseFloat(cartonsInputs[i + 1].value);
|
|
||||||
const height = parseFloat(cartonsInputs[i + 2].value);
|
|
||||||
const weight = parseFloat(cartonsInputs[i + 3].value);
|
|
||||||
if (!isNaN(length) && !isNaN(width) && !isNaN(height) && !isNaN(weight)) {
|
|
||||||
cartons.push({ length, width, height, weight });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let containers = [];
|
|
||||||
for (let i = 0; i < containersInputs.length; i += 4) {
|
|
||||||
const length = parseFloat(containersInputs[i].value);
|
|
||||||
const width = parseFloat(containersInputs[i + 1].value);
|
|
||||||
const height = parseFloat(containersInputs[i + 2].value);
|
|
||||||
const maxWeight = parseFloat(containersInputs[i + 3].value);
|
|
||||||
if (!isNaN(length) && !isNaN(width) && !isNaN(height) && !isNaN(maxWeight)) {
|
|
||||||
containers.push({ length, width, height, maxWeight });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/calculate', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ cartons, containers })
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
const resultDiv = document.getElementById('result');
|
|
||||||
resultDiv.innerHTML = '';
|
|
||||||
data.forEach((container, index) => {
|
|
||||||
const containerDiv = document.createElement('div');
|
|
||||||
containerDiv.classList.add('border', 'border-gray-300', 'p-4', 'mb-4', 'rounded-md');
|
|
||||||
containerDiv.innerHTML = `
|
|
||||||
<h3 class="text-lg font-bold">集装箱 ${index + 1}</h3>
|
|
||||||
<p>当前重量: ${container.currentWeight}</p>
|
|
||||||
<h4 class="text-md font-bold">装入的纸箱:</h4>
|
|
||||||
<ul>
|
|
||||||
${container.cartons.map(carton => `<li>长: ${carton.length}, 宽: ${carton.width}, 高: ${carton.height}, 重量: ${carton.weight}</li>`).join('')}
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
resultDiv.appendChild(containerDiv);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
||||||
<script>
|
|
||||||
let scene, camera, renderer;
|
|
||||||
let containerGroup;
|
|
||||||
|
|
||||||
function initThree() {
|
|
||||||
scene = new THREE.Scene();
|
|
||||||
camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
|
|
||||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
||||||
renderer.setSize(800, 500);
|
|
||||||
document.getElementById('3d-view').appendChild(renderer.domElement);
|
|
||||||
|
|
||||||
// 灯光设置
|
|
||||||
const light = new THREE.DirectionalLight(0xffffff, 1);
|
|
||||||
light.position.set(10, 10, 10);
|
|
||||||
scene.add(light);
|
|
||||||
scene.add(new THREE.AmbientLight(0x404040));
|
|
||||||
|
|
||||||
camera.position.z = 15;
|
|
||||||
camera.position.y = 10;
|
|
||||||
camera.lookAt(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderContainer(container) {
|
|
||||||
if(containerGroup) scene.remove(containerGroup);
|
|
||||||
containerGroup = new THREE.Group();
|
|
||||||
|
|
||||||
// 绘制集装箱
|
|
||||||
const geometry = new THREE.BoxGeometry(
|
|
||||||
container.length,
|
|
||||||
container.height,
|
|
||||||
container.width
|
|
||||||
);
|
|
||||||
const edges = new THREE.EdgesGeometry(geometry);
|
|
||||||
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x000000 }));
|
|
||||||
containerGroup.add(line);
|
|
||||||
|
|
||||||
// 绘制纸箱
|
|
||||||
container.cartons.forEach(c => {
|
|
||||||
const box = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(c.length, c.height, c.width),
|
|
||||||
new THREE.MeshPhongMaterial({ color: 0x2194f3 })
|
|
||||||
);
|
|
||||||
box.position.set(c.position[0], c.position[1], c.position[2]);
|
|
||||||
containerGroup.add(box);
|
|
||||||
});
|
|
||||||
|
|
||||||
scene.add(containerGroup);
|
|
||||||
stopAnimation();
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
let animationId;
|
|
||||||
function animate() {
|
|
||||||
animationId = requestAnimationFrame(animate);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
containerGroup.rotation.y += 0.005;
|
|
||||||
}
|
|
||||||
function stopAnimation() {
|
|
||||||
if(animationId) cancelAnimationFrame(animationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化3D场景
|
|
||||||
window.addEventListener('load', initThree);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@ -1 +0,0 @@
|
|||||||
login
|
|
||||||
Loading…
x
Reference in New Issue
Block a user