<template> <div class="overview"> <div class="top"> <div class="block" v-for="(value,key,index) in data" :key="index" @click="btnClick(value)"> <image-block :data="value"/> </div> </div> <div ref="body" class="body"> <left ref="left" class="left"/> <div ref="right" class="middle"> <div class="title">{{active.name}}</div> <div class="angle">横滚角: {{roll}} <br/>俯仰角: {{pitch}} <br/>航向角: {{heading}} </div> <div class="fresh"> 实时姿态: <el-switch v-model="value"/> </div> <div ref="device"/> </div> <right ref="devright" class="right"/> </div> </div> </template> <script> import { getRobotList,getRobotDetail,getRobotTask } from '@/api/robot' import ImageBlock from "./components/imageBlock"; import Left from './left' import { Notification } from 'element-ui' import * as THREE from 'three' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { STLLoader } from 'three/examples/jsm/loaders/STLLoader' import OrbitControls from 'three-orbitcontrols' import dat from 'dat.gui' import Right from "./right"; var refresh = false export default { name: 'Device', components: {Right, Left,ImageBlock, THREE, dat,OrbitControls,GLTFLoader,STLLoader}, data() { return { active: {name:''}, clock: 10, // 定时时间 roll: '45.21°', pitch: '51.41°', heading: '93.00°', arr:[], value: false, socket: null, controls: null, scene: null, gltf: null, modelName:'', camera: null, renderer: null, timer: null, // 定时器 plane:null, paramsThree: { modelName: '', // .glb模型的文件名字 name: 'devGlb', // 模型加入场景的名字,此处统一是devGlb }, data:[ // {name:'飞翼滑翔机一号',gltf:''}, // {name:'飞翼滑翔机二号',gltf:''}, // {name:'飞翼滑翔机三号',gltf:''}, // {name:'飞翼滑翔机四号',gltf:''}, // {name:'飞翼滑翔机五号',gltf:''}, // {name:'飞翼滑翔机六号',gltf:''}, // {name:'飞翼滑翔机七号',gltf:''}, // {name:'飞翼滑翔机八号',gltf:''} ], jumpTo:[] } }, created() { this.$nextTick(() => { this.init() this.initWebSocket() }) }, beforeDestroy(){ this.stopTimer() this.destoryWebSocket() }, watch:{ 'camera.rotation.x':{ handler: function() { this.pitch = (this.camera.rotation.x/Math.PI*180+this.arr[1]).toFixed(2) + '°' this.roll = (this.camera.rotation.y/Math.PI*180+this.arr[0]).toFixed(2) + '°' this.heading = (this.camera.rotation.z/Math.PI*180+this.arr[2]).toFixed(2) + '°' } }, 'camera.rotation.y':{ handler: function() { this.pitch = (this.camera.rotation.x/Math.PI*180+this.arr[1]).toFixed(2) + '°' this.roll = (this.camera.rotation.y/Math.PI*180+this.arr[0]).toFixed(2) + '°' this.heading = (this.camera.rotation.z/Math.PI*180+this.arr[2]).toFixed(2) + '°' } }, 'camera.rotation.z':{ handler: function() { this.pitch = (this.camera.rotation.x/Math.PI*180+this.arr[1]).toFixed(2) + '°' this.roll = (this.camera.rotation.y/Math.PI*180+this.arr[0]).toFixed(2) + '°' this.heading = (this.camera.rotation.z/Math.PI*180+this.arr[2]).toFixed(2) + '°' } } }, methods: { initWebSocket() { let that = this if (typeof (WebSocket) === 'undefined') { Notification({ title: '提示', message: '当前浏览器无法接收实时报警信息,请使用谷歌浏览器或360浏览器极速模式!', type: 'warning', duration: 0 }) } else { const socketUrl = this.baseConfig.webSocketUrl + this.$store.getters.id console.log(socketUrl,'*********************') that.socket = new WebSocket(socketUrl) // 监听socket打开 that.socket.onopen = function() { console.log('浏览器WebSocket已打开') } // 监听socket消息接收 that.socket.onmessage = function(msg) { // 转换为json对象 const msgdata = JSON.parse(msg.data) console.log(msgdata) const data = msgdata.messageObject if (msgdata.type === 'alarm') { let message switch (data.alarmType) { case '1': message = data.robotId + '号' + data.alarmTypeName + ':' + data.alarmValue + '%' break case '2': message = data.robotId + '号' + data.alarmTypeName + ':' + data.alarmValue + '海里' break case '3': message = data.alarmTypeName + ':' + data.alarmValue + 'm/s' break case '4': message = data.robotId + '号' + data.alarmTypeName + ':' + data.alarmValue + '海里' break case '5': message = data.robotId + '号' + data.alarmTypeName + ':' + data.alarmValue + '海里' break case '6': message = data.alarmTypeName + ':' + data.alarmValue + 'kb/s' break } Notification({ title: '新报警来了', // 这里也可以把返回信息加入到message中显示 message: message, type: 'warning', }) } else if(msgdata.type === 'robotInfo'){ let data = msgdata.messageObject for(let i=0;i<that.data.length;i++){ let item = that.data[i] if(item.id === data.robotId && that.active.id !== data.robotId){ if(!that.jumpTo[i]){ that.btnClick(item) that.jumpTo[i] = true } break } } } } // 监听socket错误 that.socket.onerror = function() { Notification({ title: '服务器错误', message: '无法接收实时报警信息,请检查服务器后重新刷新页面', type: 'error', duration: 0 }) } // 监听socket关闭 that.socket.onclose = function() { console.log('WebSocket已关闭') } } }, destoryWebSocket(){ this.socket.close() }, openTimer(){ this.timer = setInterval(() => { if(this.value){ this.btnClick(this.active) } }, this.clock * 1000) }, // 停止定时器 stopTimer(){ if (this.timer){ clearInterval(this.timer) this.timer = null } }, btnClick(val){ this.active = val let that = this getRobotTask(val.id).then(res => { if (res.code === 200) { this.$refs.left.initTask(res.data) } }) getRobotDetail(val.id).then(res => { if (res.code === 200) { this.$refs.left.init(res.data) this.$refs.devright.init(res.data) this.roll = res.data.rollAngle + '°' this.pitch = res.data.pitchAngle + '°' this.heading = res.data.headingAngle + '°' this.arr = [res.data.rollAngle,res.data.pitchAngle,res.data.headingAngle] if(that.modelName!==val.gltf){ this.initModel(val.gltf,res.data.rollAngle,res.data.pitchAngle,res.data.headingAngle) }else{ console.log('changePos') this.changePos(val.gltf,res.data.rollAngle,res.data.pitchAngle,res.data.headingAngle) } } }) }, init () { this.initList() this.initMesh() }, changePos(name,roll,pitch,heading){ let that = this this.gltf = null if(this.scene&&this.paramsThree.modelName!==''){ // debugger let objM = this.scene.getObjectByName(this.paramsThree.modelName) objM.position.set( 0,0,0); objM.rotation.set( pitch*Math.PI/180, roll*Math.PI/180, heading*Math.PI/180 ); refresh = true } // if(name==='') return // const loader = new STLLoader(); // loader.load(`${process.env.BASE_URL}model/${name}.stl`, function ( geometry ) { // // const material = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x111111, shininess: 200 } ); // const mesh = new THREE.Mesh( geometry, material ); // mesh.name = name // mesh.position.set( 0,0,0); // mesh.rotation.set( pitch*Math.PI/180, roll*Math.PI/180, heading*Math.PI/180 ); // mesh.scale.set( 0.03, 0.03, 0.03 ); // // mesh.castShadow = true; // mesh.receiveShadow = true; // // that.scene.add( mesh ); // that.paramsThree.modelName = name // that.gltf = mesh // } ); }, initModel(name,roll,pitch,heading){ let that = this this.gltf = null if(this.scene&&this.paramsThree.modelName!==''){ let objM = this.scene.getObjectByName(this.paramsThree.modelName) if (objM) this.scene.remove(objM) refresh = true } if(name==='') return const loader = new STLLoader(); loader.load(`${process.env.BASE_URL}model/${name}.stl`, function ( geometry ) { const material = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x111111, shininess: 200 } ); const mesh = new THREE.Mesh( geometry, material ); mesh.name = name mesh.position.set( 0,0,0); mesh.rotation.set( pitch*Math.PI/180, roll*Math.PI/180, heading*Math.PI/180 ); mesh.scale.set( 0.03, 0.03, 0.03 ); mesh.castShadow = true; mesh.receiveShadow = true; that.scene.add( mesh ); that.paramsThree.modelName = name that.gltf = mesh that.modelName = name } ); }, addShadowedLight( x, y, z, color, intensity ) { const directionalLight = new THREE.DirectionalLight( color, intensity ); directionalLight.position.set( x, y, z ); this.scene.add( directionalLight ); directionalLight.castShadow = true; const d = 1; directionalLight.shadow.camera.left = - d; directionalLight.shadow.camera.right = d; directionalLight.shadow.camera.top = d; directionalLight.shadow.camera.bottom = - d; directionalLight.shadow.camera.near = 1; directionalLight.shadow.camera.far = 4; directionalLight.shadow.bias = - 0.002; }, initList(){ getRobotList().then(res => { if (res.code === 200) { this.data = res.data.map(item => { return {name:'飞翼滑翔机'+item.robotId+'号',id:item.robotId,gltf:item.modelType} }) this.active = this.data[0] this.jumpTo = this.data.map(item => {return false}) this.btnClick(this.active) this.openTimer() } }) }, initMesh () { this.scene = new THREE.Scene() // 场景 this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000) // 相机.视场,长宽比,近面,远面 this.camera.position.set( 0, 0, 30 ); this.camera.lookAt(this.scene.position) this.renderer = new THREE.WebGLRenderer({ antialias: true })// 渲染器 this.renderer.setClearColor(0x179AC6, 1); //设置背景颜色 this.renderer.setSize(this.$refs.right.clientWidth,this.$refs.right.clientHeight) this.renderer.shadowMapEnabled = true // 开启阴影 this.scene.add( new THREE.HemisphereLight( 0x443333, 0x111122 ) ); this.addShadowedLight( 1, 1, 1, 0xffffff, 1.35 ); this.addShadowedLight( 0.5, 1, - 1, 0xffffff, 1 ); this.$refs.device.append(this.renderer.domElement) this.renderer.render(this.scene, this.camera) this.controls = new OrbitControls(this.camera, this.renderer.domElement); //摄像机和容器div 注意事div this.controls.mouseButtons = { //左键平移 LEFT: THREE.MOUSE.PAN, //滚轮滑动 MIDDLE: THREE.MOUSE.DOLLY, //右键旋转 RIGHT: THREE.MOUSE.ROTATE } // 使动画循环使用时阻尼或自转 意思是否有惯性 this.controls.enableDamping = false; //是否可以缩放 this.controls.enableZoom = true; //是否自动旋转 this.controls.autoRotate = true; // //设置相机距离原点的最远距离 // this.controls.minDistance = 200; //设置相机距离原点的最远距离 this.controls.maxDistance = 600; //是否开启右键拖拽 this.controls.enablePan = true; this.renderScene() }, renderScene () { let {scene, camera, renderer} = this requestAnimationFrame(this.renderScene) if(refresh){ refresh = false camera.position.set( 0, 0, 30 ) camera.lookAt(scene.position) } renderer.render(scene, camera) } } } </script> <style rel="stylesheet/scss" lang="scss" scoped> .overview{ display: flex; flex-direction: column; padding-right: 30px; width: 100%; height: 100%; .title{ color: #ffffff; font-size: 20px; font-weight: bold; position: absolute; top: 30px; left: 360px; } .angle{ color: #2f3244; font-size: 17px; position: absolute; top: 80px; left: 360px; line-height: 25px; font-weight: bold; } .fresh{ color: white; font-size: 17px; position: absolute; top: 40px; right: 300px; line-height: 25px; font-weight: bold; } .top{ margin: 10px 30px; display: flex; flex-direction: row; .block{ width: 12.5%; height: 30px; margin-right: 10px; cursor: pointer; } } .body{ position:relative; width: 100%; flex: 1; display: flex; flex-direction: row; .left{ margin-left: 20px; min-width: 300px; width: 300px; display: flex; flex-direction: column; padding-bottom: 20px; } .right{ margin-left: 20px; margin-right: 10px; min-width: 200px; width: 200px; display: flex; flex-direction: column; padding-bottom: 20px; } .middle{ flex: 1; /*background-color: #E6A23C;*/ margin-left: 20px; margin-top: 5px; margin-bottom: 25px; } } .map{ width: 100%; flex: 1; background-color: white; } } </style>