Files
qhmes/jeecgboot-vue3/src/views/workshop/Workshop3D.vue
2026-04-03 09:56:14 +08:00

268 lines
6.8 KiB
Vue
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.
<template>
<div class="workshop-container">
<div ref="container" class="three-container"></div>
<!-- 信息面板 -->
<div v-if="selectedCube" class="info-panel">
<div class="info-header">
<h3>设备信息</h3>
<button @click="selectedCube = null" class="close-btn">×</button>
</div>
<div class="info-content">
<p><strong>设备名称:</strong> {{ selectedCube.name }}</p>
<p><strong>设备ID:</strong> {{ selectedCube.id }}</p>
<p><strong>状态:</strong> <span :style="{ color: selectedCube.statusColor }">{{ selectedCube.status }}</span></p>
<p><strong>位置:</strong> X: {{ selectedCube.position.x.toFixed(2) }}, Y: {{ selectedCube.position.y.toFixed(2) }}, Z: {{ selectedCube.position.z.toFixed(2) }}</p>
<p><strong>温度:</strong> {{ selectedCube.temperature }}°C</p>
<p><strong>运行时长:</strong> {{ selectedCube.runtime }}小时</p>
</div>
</div>
</div>
</template>
<script setup>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { onMounted, onBeforeUnmount, ref } from 'vue'
const container = ref(null)
const selectedCube = ref(null)
let scene, camera, renderer, controls, animationId
let cubes = []
let raycaster, mouse
// 设备数据
const cubeData = [
{ id: 'DEV-001', name: '加工中心A', status: '运行中', statusColor: '#52c41a', temperature: 45, runtime: 1234, color: 0x1890ff },
{ id: 'DEV-002', name: '加工中心B', status: '待机', statusColor: '#faad14', temperature: 28, runtime: 2456, color: 0x52c41a },
{ id: 'DEV-003', name: '装配机器人', status: '运行中', statusColor: '#52c41a', temperature: 38, runtime: 3678, color: 0xf5222d },
{ id: 'DEV-004', name: '质检设备', status: '故障', statusColor: '#f5222d', temperature: 65, runtime: 890, color: 0xfa8c16 },
{ id: 'DEV-005', name: '包装机', status: '运行中', statusColor: '#52c41a', temperature: 42, runtime: 5432, color: 0x722ed1 }
]
onMounted(() => {
initScene()
createCubes()
setupInteraction()
animate()
window.addEventListener('resize', onWindowResize)
})
onBeforeUnmount(() => {
cancelAnimationFrame(animationId)
window.removeEventListener('resize', onWindowResize)
window.removeEventListener('click', onMouseClick)
controls.dispose()
renderer.dispose()
scene.clear()
})
function initScene() {
// 场景
scene = new THREE.Scene()
scene.background = new THREE.Color(0xf0f2f5)
// 相机
camera = new THREE.PerspectiveCamera(
75,
container.value.clientWidth / container.value.clientHeight,
0.1,
1000
)
camera.position.set(8, 8, 8)
camera.lookAt(0, 0, 0)
// 渲染器
renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(container.value.clientWidth, container.value.clientHeight)
renderer.shadowMap.enabled = true
container.value.appendChild(renderer.domElement)
// 轨道控制器
controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.05
// 光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6)
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
directionalLight.position.set(10, 10, 5)
directionalLight.castShadow = true
scene.add(directionalLight)
// 地面
const groundGeometry = new THREE.PlaneGeometry(20, 20)
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0xcccccc })
const ground = new THREE.Mesh(groundGeometry, groundMaterial)
ground.rotation.x = -Math.PI / 2
ground.receiveShadow = true
scene.add(ground)
// 网格辅助线
const gridHelper = new THREE.GridHelper(20, 20, 0x888888, 0xdddddd)
scene.add(gridHelper)
}
function createCubes() {
const positions = [
{ x: -4, y: 1, z: -4 },
{ x: 4, y: 1, z: -4 },
{ x: 0, y: 1, z: 0 },
{ x: -4, y: 1, z: 4 },
{ x: 4, y: 1, z: 4 }
]
cubeData.forEach((data, index) => {
const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5)
const material = new THREE.MeshStandardMaterial({
color: data.color,
metalness: 0.3,
roughness: 0.4
})
const cube = new THREE.Mesh(geometry, material)
cube.position.set(positions[index].x, positions[index].y, positions[index].z)
cube.castShadow = true
cube.receiveShadow = true
// 存储设备数据
cube.userData = {
...data,
position: positions[index],
originalColor: data.color
}
scene.add(cube)
cubes.push(cube)
})
}
function setupInteraction() {
raycaster = new THREE.Raycaster()
mouse = new THREE.Vector2()
window.addEventListener('click', onMouseClick)
}
function onMouseClick(event) {
const rect = container.value.getBoundingClientRect()
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
raycaster.setFromCamera(mouse, camera)
const intersects = raycaster.intersectObjects(cubes)
// 重置所有立方体颜色
cubes.forEach(cube => {
cube.material.color.setHex(cube.userData.originalColor)
cube.material.emissive.setHex(0x000000)
})
if (intersects.length > 0) {
const clickedCube = intersects[0].object
// 高亮选中的立方体
clickedCube.material.emissive.setHex(0x555555)
// 显示信息面板
selectedCube.value = clickedCube.userData
} else {
selectedCube.value = null
}
}
function onWindowResize() {
camera.aspect = container.value.clientWidth / container.value.clientHeight
camera.updateProjectionMatrix()
renderer.setSize(container.value.clientWidth, container.value.clientHeight)
}
function animate() {
animationId = requestAnimationFrame(animate)
// 轻微旋转动画
cubes.forEach((cube, index) => {
cube.rotation.y += 0.005 * (index % 2 === 0 ? 1 : -1)
})
controls.update()
renderer.render(scene, camera)
}
</script>
<style scoped>
.workshop-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.three-container {
width: 100%;
height: 100%;
}
.info-panel {
position: absolute;
top: 20px;
right: 20px;
width: 320px;
background: rgba(255, 255, 255, 0.95);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.info-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: #1890ff;
color: white;
}
.info-header h3 {
margin: 0;
font-size: 18px;
font-weight: 500;
}
.close-btn {
background: none;
border: none;
color: white;
font-size: 28px;
cursor: pointer;
padding: 0;
width: 28px;
height: 28px;
line-height: 1;
transition: opacity 0.2s;
}
.close-btn:hover {
opacity: 0.8;
}
.info-content {
padding: 20px;
}
.info-content p {
margin: 12px 0;
font-size: 14px;
line-height: 1.6;
}
.info-content strong {
color: #333;
font-weight: 500;
}
</style>