第一次提交
This commit is contained in:
267
jeecgboot-vue3/src/views/workshop/Workshop3D.vue
Normal file
267
jeecgboot-vue3/src/views/workshop/Workshop3D.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user