|
@@ -1,16 +1,44 @@
|
|
|
<template>
|
|
|
<div class="app">
|
|
|
- <el-container class="container" @mousedown="startDrag" @mousemove="drag" @mouseup="endDrag" @mouseleave="endDrag" @contextmenu.prevent>
|
|
|
+ <el-container>
|
|
|
<BorderBox1 class="cs">
|
|
|
<div style="margin: 20px 0 10px 0;">在线设备:{{onlineData.online}}</div>
|
|
|
<div style="margin: 0 0 20px 0;">总设备:{{onlineData.total}}</div>
|
|
|
<el-progress type="dashboard" :percentage="percentage" :color="colors" />
|
|
|
</BorderBox1>
|
|
|
- <div/>
|
|
|
- <img ref="map" :src="imageSrc" alt="Map" @dragstart.prevent />
|
|
|
- <el-button v-for="(button, index) in buttons" :key="index" class="button" :style="button.style" @click="openRegionDrawer(button)">
|
|
|
- {{ button.text }}
|
|
|
- </el-button>
|
|
|
+ <div class="top-label">
|
|
|
+ <div style="height: 50px;line-height: 50px">
|
|
|
+ <img src="/src/static/jx.png" alt="Map" style="top: 0;left: -100px;width: 80px;height: 80px"/>
|
|
|
+ 军信路灯控制
|
|
|
+ </div>
|
|
|
+ <Decoration5 style="width:500px; height:120px;" />
|
|
|
+ </div>
|
|
|
+ <div class="fixed-sidebar">
|
|
|
+ <BorderBox8 :reverse="true" class="upper-part">
|
|
|
+ <el-button v-for="(button, index) in buttons" :key="index" style="margin: 20px 0 30px 20px;display: block" @click="openRegionDrawer(button)">
|
|
|
+ {{ button.text }}
|
|
|
+ </el-button>
|
|
|
+ <!-- 上部分内容 -->
|
|
|
+ </BorderBox8>
|
|
|
+ <BorderBox8 class="lower-part">
|
|
|
+ <!-- 下部分内容 -->
|
|
|
+ <div v-for="device in sunDevices" class="sun_info">
|
|
|
+ <div>
|
|
|
+ 名称:{{device.name}}
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ 电池电流:{{device.sun.batteryCurrent}}mA
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ 电池电压:{{device.sun.batteryVoltage}}V
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ 电池板电压:{{device.sun.batteryPlateVoltage}}V
|
|
|
+ </div>
|
|
|
+ <el-divider />
|
|
|
+ </div>
|
|
|
+ </BorderBox8>
|
|
|
+ </div>
|
|
|
</el-container>
|
|
|
<el-drawer
|
|
|
v-model="table"
|
|
@@ -165,21 +193,20 @@
|
|
|
|
|
|
<script setup>
|
|
|
import {ref, onMounted, reactive, computed} from 'vue';
|
|
|
-import {BorderBox1,BorderBox3 } from '@dataview/datav-vue3';
|
|
|
+import {BorderBox1,BorderBox2,BorderBox8,Decoration5 } from '@dataview/datav-vue3';
|
|
|
import {ElContainer, ElButton, ElMessage, ElMessageBox} from 'element-plus';
|
|
|
-import {deviceBatchSwitch, deviceLoopSwitch, deviceSwitch, getOnlineDevice, queryData, saveData} from "@/api/device.js";
|
|
|
+import {
|
|
|
+ deviceBatchSwitch,
|
|
|
+ deviceLoopSwitch,
|
|
|
+ deviceSwitch,
|
|
|
+ getOnlineDevice,
|
|
|
+ getSunDevices,
|
|
|
+ queryData,
|
|
|
+ saveData
|
|
|
+} from "@/api/device.js";
|
|
|
import DeviceReplayTimeSet from "@/comm/deviceReplayTimeSet.vue";
|
|
|
import DeviceLoopReplayTimeSet from "@/comm/deviceLoopReplayTimeSet.vue";
|
|
|
|
|
|
-const imageSrc = '/src/static/jx4.png'; // 替换为你的图片路径
|
|
|
-const map = ref(null);
|
|
|
-let isDragging = false;
|
|
|
-let dragStartX = 0;
|
|
|
-let dragStartY = 0;
|
|
|
-let currentMapLeft = 0;
|
|
|
-let currentMapTop = 0;
|
|
|
-const dragSpeed = 0.1; // 控制拖动速度,值越小速度越慢
|
|
|
-
|
|
|
const buttons = ref([
|
|
|
{ text: '生活区', initialLeft: 500, initialTop: 1800 },
|
|
|
{ text: '股份公司进场上坡', initialLeft: 500, initialTop: 1550 },
|
|
@@ -191,71 +218,23 @@ const buttons = ref([
|
|
|
{ text: '污水进场道路', initialLeft: 2200, initialTop: 300 }
|
|
|
]);
|
|
|
|
|
|
-function startDrag(event) {
|
|
|
- isDragging = true;
|
|
|
- dragStartX = event.clientX;
|
|
|
- dragStartY = event.clientY;
|
|
|
-}
|
|
|
-
|
|
|
-function drag(event) {
|
|
|
- if (!isDragging) return;
|
|
|
-
|
|
|
- const containerRect = map.value.parentElement.getBoundingClientRect();
|
|
|
- const mapRect = map.value.getBoundingClientRect();
|
|
|
-
|
|
|
- const deltaX = (event.clientX - dragStartX) * dragSpeed;
|
|
|
- const deltaY = (event.clientY - dragStartY) * dragSpeed;
|
|
|
-
|
|
|
- const newX = currentMapLeft + deltaX;
|
|
|
- const newY = currentMapTop + deltaY;
|
|
|
-
|
|
|
- // 计算边界限制
|
|
|
- const minX = -(mapRect.width - containerRect.width); // 左侧边缘不能完全移出容器
|
|
|
- const maxX = 0; // 右侧边缘可以超过容器
|
|
|
- const minY = -(mapRect.height - containerRect.height); // 顶部边缘不能完全移出容器
|
|
|
- const maxY = 0; // 底部边缘可以超过容器
|
|
|
-
|
|
|
- // 应用边界限制
|
|
|
- const finalX = Math.max(minX, Math.min(maxX, newX));
|
|
|
- const finalY = Math.max(minY, Math.min(maxY, newY));
|
|
|
-
|
|
|
- currentMapLeft = finalX;
|
|
|
- currentMapTop = finalY;
|
|
|
-
|
|
|
- map.value.style.left = `${finalX}px`;
|
|
|
- map.value.style.top = `${finalY}px`;
|
|
|
-
|
|
|
- // 更新按钮位置
|
|
|
- updateButtonPositions();
|
|
|
-}
|
|
|
-
|
|
|
-function endDrag() {
|
|
|
- isDragging = false;
|
|
|
-}
|
|
|
-
|
|
|
-function updateButtonPositions() {
|
|
|
- buttons.value.forEach((button) => {
|
|
|
- button.style = {
|
|
|
- left: `${button.initialLeft + currentMapLeft}px`,
|
|
|
- top: `${button.initialTop + currentMapTop}px`
|
|
|
- };
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
const regionData = ref()
|
|
|
const onlineData = ref({
|
|
|
online: 0,
|
|
|
total:0
|
|
|
})
|
|
|
+const sunDevices =ref()
|
|
|
const getData = async() => {
|
|
|
await queryData().then(res => {
|
|
|
- console.log(res)
|
|
|
regionData.value = res.data
|
|
|
})
|
|
|
await getOnlineDevice().then(res => {
|
|
|
- console.log(res.data)
|
|
|
onlineData.value = res.data
|
|
|
})
|
|
|
+ await getSunDevices().then(res => {
|
|
|
+ console.log(res.data)
|
|
|
+ sunDevices.value = res.data
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
const percentage = computed(() => {
|
|
@@ -281,6 +260,9 @@ const deviceData = ref()
|
|
|
const openRegionDrawer = (val) => {
|
|
|
table.value = true;
|
|
|
regionName.value = val.text;
|
|
|
+ if (!regionName.value) {
|
|
|
+ regionName.value = val
|
|
|
+ }
|
|
|
|
|
|
for (let i = 0; i < regionData.value.length; i++) {
|
|
|
if (regionData.value[i].name === regionName.value) {
|
|
@@ -298,10 +280,10 @@ const addDeviceData = ref({
|
|
|
sn: '',
|
|
|
name: '',
|
|
|
regionId: null,
|
|
|
- genre: "八回路控制",
|
|
|
+ genre: "四回路控制",
|
|
|
state: 1,
|
|
|
isSun: false,
|
|
|
- loopNumber : 8,
|
|
|
+ loopNumber : 4,
|
|
|
deviceLoops: []
|
|
|
})
|
|
|
|
|
@@ -492,33 +474,104 @@ setInterval(() => {
|
|
|
}, 300000);
|
|
|
|
|
|
onMounted(() => {
|
|
|
- // 初始化地图位置
|
|
|
- map.value.style.left = `${currentMapLeft}px`;
|
|
|
- map.value.style.top = `${currentMapTop}px`;
|
|
|
-
|
|
|
- // 初始化按钮位置
|
|
|
- updateButtonPositions();
|
|
|
getData()
|
|
|
});
|
|
|
+
|
|
|
+// 3d图
|
|
|
+
|
|
|
+import * as THREE from 'three';
|
|
|
+import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
|
|
|
+import {GLTFLoader} from "three/addons/loaders/GLTFLoader.js";
|
|
|
+// 创建场景
|
|
|
+const scene = new THREE.Scene();
|
|
|
+
|
|
|
+// 创建相机
|
|
|
+const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
|
|
|
+camera.position.set(0, 2000, 0);
|
|
|
+camera.lookAt(0,0,0)
|
|
|
+
|
|
|
+// 创建渲染器
|
|
|
+const renderer = new THREE.WebGLRenderer({
|
|
|
+ antialias: true,
|
|
|
+});
|
|
|
+renderer.setClearColor(0x060c18,1.0);
|
|
|
+renderer.setPixelRatio(window.devicePixelRatio)
|
|
|
+renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
+document.body.appendChild(renderer.domElement);
|
|
|
+
|
|
|
+const point = ['生活区','股份公司进场上坡','地磅房至污泥厂门口','三角花园至油库','地磅房至搅拌站','搅拌站至三岔路口','垃圾堆体道路','污水进场道路']
|
|
|
+
|
|
|
+//环境光
|
|
|
+const ambientLight = new THREE.AmbientLight(0xffffff, 2);
|
|
|
+scene.add(ambientLight);
|
|
|
+
|
|
|
+const loader = new GLTFLoader();
|
|
|
+
|
|
|
+loader.load( '/src/static/jx.glb', function ( gltf ) {
|
|
|
+ scene.add( gltf.scene );
|
|
|
+})
|
|
|
+
|
|
|
+window.onresize = function () {
|
|
|
+ renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
+ camera.aspect = window.innerWidth / window.innerHeight
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
+}
|
|
|
+
|
|
|
+// 设置相机控件轨道控制器OrbitControls
|
|
|
+const controls = new OrbitControls(camera, renderer.domElement);
|
|
|
+// 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
|
|
|
+controls.addEventListener('change', function () {
|
|
|
+ renderer.render(scene, camera); //执行渲染操作
|
|
|
+});//监听鼠标、键盘事件
|
|
|
+
|
|
|
+// 创建光线投射器
|
|
|
+const raycaster = new THREE.Raycaster();
|
|
|
+const mouse = new THREE.Vector2();
|
|
|
+
|
|
|
+// 渲染函数
|
|
|
+function animate() {
|
|
|
+ requestAnimationFrame(animate);
|
|
|
+ renderer.render(scene, camera);
|
|
|
+}
|
|
|
+
|
|
|
+// 鼠标点击事件处理
|
|
|
+function onDocumentMouseDown(event) {
|
|
|
+ event.preventDefault();
|
|
|
+
|
|
|
+ // 将鼠标位置归一化到 -1 到 1 的范围内
|
|
|
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
|
+ mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
|
+
|
|
|
+ // 更新光线投射器
|
|
|
+ raycaster.setFromCamera(mouse, camera);
|
|
|
+
|
|
|
+ // 计算所有与光线相交的对象
|
|
|
+ const intersects = raycaster.intersectObjects(scene.children);
|
|
|
+
|
|
|
+ if (intersects.length > 0) {
|
|
|
+ const intersectedObject = intersects[0].object;
|
|
|
+
|
|
|
+ // 根据点击的对象执行不同的操作
|
|
|
+ if (point.includes(intersectedObject.name)) {
|
|
|
+ openRegionDrawer(intersectedObject.name)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 监听鼠标点击事件
|
|
|
+document.addEventListener('mousedown', onDocumentMouseDown, false);
|
|
|
+
|
|
|
+// 开始渲染
|
|
|
+animate();
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.app {
|
|
|
position: relative;
|
|
|
width: 100vw;
|
|
|
- height: 100vh;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
-.container {
|
|
|
- position: relative;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background-color: #f4f4f4;
|
|
|
- border: 1px solid #ccc;
|
|
|
- cursor: move;
|
|
|
-}
|
|
|
-
|
|
|
img {
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
@@ -529,27 +582,108 @@ img {
|
|
|
-webkit-user-drag: none; /* 禁止图片拖动 */
|
|
|
}
|
|
|
|
|
|
-.button {
|
|
|
- position: absolute;
|
|
|
- padding: 5px 10px;
|
|
|
- background-color: rgba(0, 0, 0, 0.5);
|
|
|
- color: white;
|
|
|
- border-radius: 5px;
|
|
|
- cursor: pointer;
|
|
|
- user-select: none;
|
|
|
-}
|
|
|
.cs {
|
|
|
- z-index: 10;
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 5%;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
width: 200px;
|
|
|
- height: 230px;
|
|
|
+ height: 250px;
|
|
|
color: white;
|
|
|
text-align: center;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ background-color: rgba(0, 0, 0, 0);
|
|
|
+ z-index: 10; /* 确保它在最上层 */
|
|
|
}
|
|
|
-.cs1 {
|
|
|
+
|
|
|
+.fixed-sidebar {
|
|
|
z-index: 10;
|
|
|
- width: 200px;
|
|
|
- height: 100px;
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ right: 0;
|
|
|
+ width: 16.67%; /* 100% / 6 = 16.67% */
|
|
|
+ height: 100%;
|
|
|
+ //background-color: #f0f0f0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.upper-part {
|
|
|
+ flex: 6; /* 60% */
|
|
|
+ background-color: rgba(0, 0, 0, 0.1);
|
|
|
+ padding: 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column; /* 使按钮竖直排列 */
|
|
|
+ align-items: flex-start; /* 使按钮左对齐 */
|
|
|
+}
|
|
|
+
|
|
|
+.lower-part {
|
|
|
+ flex: 4; /* 40% */
|
|
|
+ background-color: rgba(0, 0, 0, 0.1);
|
|
|
+ padding: 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+/* 可选:添加一些样式来美化盒子 */
|
|
|
+.upper-part h2, .lower-part h2 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 18px;
|
|
|
+}
|
|
|
+
|
|
|
+.upper-part p, .lower-part p {
|
|
|
+ margin: 10px 0;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.top-label {
|
|
|
+ position: fixed;
|
|
|
+ top: 20px;
|
|
|
+ left: 50%;
|
|
|
+ font-size: 50px;
|
|
|
+ font-weight: 600;
|
|
|
color: white;
|
|
|
text-align: center;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ background-color: rgba(0, 0, 0, 0);
|
|
|
+ padding: 10px 20px;
|
|
|
+ z-index: 10; /* 确保它在最上层 */
|
|
|
+}
|
|
|
+.sun_info div{
|
|
|
+ color: white;
|
|
|
+ margin: 15px 0;
|
|
|
+}
|
|
|
+.upper-part button {
|
|
|
+ border: 2px solid white;
|
|
|
+ background: transparent;
|
|
|
+ text-transform: uppercase;
|
|
|
+ color: white;
|
|
|
+ outline: none;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+span {
|
|
|
+ z-index: 20;
|
|
|
+}
|
|
|
+
|
|
|
+.upper-part button:after {
|
|
|
+ content: '';
|
|
|
+ display: block;
|
|
|
+ position: absolute;
|
|
|
+ top: -36px;
|
|
|
+ left: -100px;
|
|
|
+ background: white;
|
|
|
+ width: 50px;
|
|
|
+ height: 125px;
|
|
|
+ opacity: 20%;
|
|
|
+ transform: rotate(-45deg);
|
|
|
+}
|
|
|
+
|
|
|
+.upper-part button:hover:after {
|
|
|
+ left: 120%;
|
|
|
+ transition: all 600ms cubic-bezier(0.3, 1, 0.2, 1);
|
|
|
+ -webkit-transition: all 600ms cubic-bezier(0.3, 1, 0.2, 1);
|
|
|
}
|
|
|
</style>
|