|
@@ -0,0 +1,318 @@
|
|
|
+<template>
|
|
|
+ <div class="body">
|
|
|
+ <el-card class="head_card"
|
|
|
+ shadow="always">
|
|
|
+ <el-row class="head_card_row">
|
|
|
+ <el-col :span="6"
|
|
|
+ class="head_card_item">
|
|
|
+ <span class="button_title">当前设备:</span>
|
|
|
+ <el-select v-model="value"
|
|
|
+ placeholder="请选择"
|
|
|
+ @change="selectDevice">
|
|
|
+ <el-option v-for="item in options"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value">
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="6"
|
|
|
+ class="head_card_item">
|
|
|
+ <span class="button_title">当前设备状态:</span>
|
|
|
+
|
|
|
+ <el-tag type="success"
|
|
|
+ v-if="device.statue === 1">设备在线</el-tag>
|
|
|
+
|
|
|
+ <el-tag type="danger"
|
|
|
+ v-else-if="device.statue === 0">设备离线</el-tag>
|
|
|
+ <el-tag v-else>无设备</el-tag>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="4"
|
|
|
+ class="head_card_item">
|
|
|
+ <span class="button_title">当前设备X坐标:</span>
|
|
|
+ <span style="font-size: 17px;font-weight: bold;">{{ device.x }}</span>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="4"
|
|
|
+ class="head_card_item">
|
|
|
+ <span class="button_title">当前设备Y坐标:</span>
|
|
|
+ <span style="font-size: 17px;font-weight: bold;">{{ device.y }}</span>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="4"
|
|
|
+ class="head_card_item">
|
|
|
+ <span class="button_title">当前设备Z坐标:</span>
|
|
|
+ <span style="font-size: 17px;font-weight: bold;">{{ device.z }}</span>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-card style="padding: 25px">
|
|
|
+ <div ref="map"
|
|
|
+ class="map">
|
|
|
+
|
|
|
+ <p style="position:absolute;left: -20px;top: -20px;font-size: 20px;">0</p>
|
|
|
+ <p style="position:absolute;left: 5px;top: -45px;font-size: 20px;">0</p>
|
|
|
+ <p style="position:absolute;left: -20px;bottom: -45px;font-size: 20px;">600</p>
|
|
|
+ <p style="position:absolute;right: -20px;top: -45px;font-size: 20px;">700</p>
|
|
|
+
|
|
|
+ <img v-for="(image, index) in images"
|
|
|
+ :key="index"
|
|
|
+ :src="image.src"
|
|
|
+ :alt="image.alt"
|
|
|
+ :style="{
|
|
|
+ left: `${image.x}px`,
|
|
|
+ top: `${image.y}px`,
|
|
|
+ height: image.height,
|
|
|
+ width: image.width,
|
|
|
+ transform: image.transform,
|
|
|
+ }"
|
|
|
+ class="positioned-image" />
|
|
|
+
|
|
|
+ <img v-for="(box, index) in boxs"
|
|
|
+ :src="box.src"
|
|
|
+ :alt="box.alt"
|
|
|
+ :style="{
|
|
|
+ left: `${box.x}px`,
|
|
|
+ top: `${box.y}px`,
|
|
|
+ height: box.height,
|
|
|
+ width: box.width,
|
|
|
+ transform: box.transform,
|
|
|
+ }"
|
|
|
+ :class="box.class" />
|
|
|
+
|
|
|
+ <svg :width="mapWidth"
|
|
|
+ :height="mapHeight"
|
|
|
+ class="positioned-svg">
|
|
|
+ <path :d="doorPath"
|
|
|
+ stroke="#BDBDBD"
|
|
|
+ stroke-width="2"
|
|
|
+ fill="none" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import api from "@/api/item/device";
|
|
|
+import { images, doors } from "@/enums/mapData";
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+
|
|
|
+ device: {},
|
|
|
+ options: [],
|
|
|
+ value: '',
|
|
|
+ images: images,
|
|
|
+ doors: doors,
|
|
|
+ boxs: [],
|
|
|
+ mapWidth: 0,
|
|
|
+ mapHeight: 0
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ window.addEventListener('resize', this.handleResize);
|
|
|
+ this.handleResize();
|
|
|
+ this.getOptions()
|
|
|
+ this.getCoordinateData()
|
|
|
+ setInterval(() => {
|
|
|
+ if (this.value != null && this.value != '') {
|
|
|
+ this.selectDevice(this.value); // 定时刷新当前设备数据
|
|
|
+ }
|
|
|
+ this.getCoordinateData(); // 定时刷新坐标数据
|
|
|
+
|
|
|
+ }, 1000)
|
|
|
+
|
|
|
+ },
|
|
|
+ beforeUnmount() {
|
|
|
+ window.removeEventListener('resize', this.handleResize);
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ // 设备门坐标路径
|
|
|
+ doorPath() {
|
|
|
+ const scaleX = this.mapWidth / 700;
|
|
|
+ const scaleY = this.mapHeight / 600;
|
|
|
+ let pathData = '';
|
|
|
+ this.doors.forEach((door) => {
|
|
|
+ const centerX = door.centerX * scaleX;
|
|
|
+ const centerY = door.centerY * scaleY;
|
|
|
+ const radius = door.radius * Math.min(scaleX, scaleY);
|
|
|
+ // 绘制四分之一圆(从 90 度到 180 度)
|
|
|
+ const doorPathSegment = `M ${centerX} ${centerY} A ${radius} ${radius} 0 0 1 ${centerX + radius} ${centerY} L ${centerX} ${centerY + radius} A ${radius} ${radius} 0 0 0 ${centerX - radius} ${centerY} Z `;
|
|
|
+ pathData += doorPathSegment;
|
|
|
+ });
|
|
|
+ return pathData;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 获取设备选项
|
|
|
+ getOptions() {
|
|
|
+ api.getDeviceOptions().then(res => {
|
|
|
+
|
|
|
+ this.options = res.data;
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择设备
|
|
|
+ selectDevice(id) {
|
|
|
+ api.getDeviceData(id).then(res => {
|
|
|
+ this.device = res.data;
|
|
|
+ this.deviceId = res.data.deviceid; // 获取选中的设备ID
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取坐标数据
|
|
|
+ getCoordinateData() {
|
|
|
+ api.getCoordinateData().then(res => {
|
|
|
+ const filterData = res.data.filter(item => item.statue === 1);
|
|
|
+ for (let i = 0; i < filterData.length; i++) {
|
|
|
+ const item = filterData[i];
|
|
|
+ const existingBoxIndex = this.boxs.findIndex(box => box.alt === item.deviceid);
|
|
|
+
|
|
|
+ if (existingBoxIndex !== -1) {
|
|
|
+ // 如果设备已经存在,更新其属性
|
|
|
+ const existingBox = this.boxs[existingBoxIndex];
|
|
|
+ existingBox.centerX = item.coordinate[0] * 100;
|
|
|
+ existingBox.centerY = item.coordinate[1] * 100;
|
|
|
+ const classes = ['positioned-image'];
|
|
|
+ if (item.deviceid === this.device.deviceid) {
|
|
|
+ classes.push('boxs');
|
|
|
+ }
|
|
|
+ existingBox.class = classes.join(' ');
|
|
|
+ } else {
|
|
|
+ // 如果设备不存在,添加新的 box
|
|
|
+ const classes = ['positioned-image'];
|
|
|
+ if (item.deviceid === this.device.deviceid) {
|
|
|
+ classes.push('boxs');
|
|
|
+ }
|
|
|
+ this.boxs.push({
|
|
|
+ src: require("@/assets/images/box.png"),
|
|
|
+ alt: item.deviceid,
|
|
|
+ centerX: item.coordinate[0] * 100,
|
|
|
+ centerY: item.coordinate[1] * 100,
|
|
|
+ width: "50px",
|
|
|
+ height: "50px",
|
|
|
+ transform: "rotate(0)",
|
|
|
+ class: classes.join(' ')
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 调用封装的方法
|
|
|
+ this.boxs = this.updatePositionAndSize(this.boxs);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 监听窗口大小变化
|
|
|
+ handleResize() {
|
|
|
+ this.images = this.updatePositionAndSize(this.images);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新图片的位置和大小
|
|
|
+ updatePositionAndSize(arr) {
|
|
|
+ const rect = this.$refs.map && this.$refs.map.getBoundingClientRect();
|
|
|
+ if (rect) {
|
|
|
+ this.mapWidth = rect.width;
|
|
|
+ this.mapHeight = rect.height;
|
|
|
+ const scaleX = rect.width / 700;
|
|
|
+ const scaleY = rect.height / 600;
|
|
|
+ return arr.map(item => {
|
|
|
+ const width = parseInt(item.width, 10);
|
|
|
+ const height = parseInt(item.height, 10);
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ x: item.centerX * scaleX - width / 2,
|
|
|
+ y: item.centerY * scaleY - height / 2
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return arr;
|
|
|
+ },
|
|
|
+ // getClick(event) {
|
|
|
+ // const rect = this.$refs.map.getBoundingClientRect();
|
|
|
+ // const scaleX = rect.width / 700;
|
|
|
+ // const scaleY = rect.height / 600;
|
|
|
+ // const clickX = (event.clientX - rect.left) / scaleX;
|
|
|
+ // const clickY = (event.clientY - rect.top) / scaleY;
|
|
|
+ // this.x = Math.round(clickX);
|
|
|
+ // this.y = Math.round(clickY);
|
|
|
+ // },
|
|
|
+
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.body {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.head_card {
|
|
|
+ margin-bottom: 5px;
|
|
|
+ height: 55px;
|
|
|
+ width: calc(80vw + 95px);
|
|
|
+
|
|
|
+ &_row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ /* 垂直居中所有列 */
|
|
|
+ }
|
|
|
+
|
|
|
+ &_item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ /* 垂直居中 */
|
|
|
+ justify-content: flex-start;
|
|
|
+ /* 水平居中,视需要而定 */
|
|
|
+ height: 100%;
|
|
|
+ /* 确保有足够的高度 */
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.map {
|
|
|
+ height: 75vh;
|
|
|
+ width: 80vw;
|
|
|
+ border: 1px solid #cccccc;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.positioned-image {
|
|
|
+ position: absolute;
|
|
|
+}
|
|
|
+
|
|
|
+.boxs {
|
|
|
+ opacity: .5;
|
|
|
+ animation: fadeInOut 1s infinite ease-in-out;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+@keyframes fadeInOut {
|
|
|
+
|
|
|
+ 0%,
|
|
|
+ 100% {
|
|
|
+ opacity: .4;
|
|
|
+ }
|
|
|
+
|
|
|
+ 25% {
|
|
|
+ opacity: .6;
|
|
|
+ }
|
|
|
+
|
|
|
+ 50% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.positioned-svg {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+}
|
|
|
+</style>
|