|
@@ -0,0 +1,491 @@
|
|
|
+<template>
|
|
|
+ <!--地图-->
|
|
|
+ <div
|
|
|
+ class="M-map"
|
|
|
+ :style="isShowMap ? 'display:inline-block' : 'display:none'"
|
|
|
+ >
|
|
|
+ <div class="but-box">
|
|
|
+ <a-button type="primary" size="small" round @click="isShowMap = false"
|
|
|
+ >切换列表</a-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <baidu-map
|
|
|
+ class="map"
|
|
|
+ ref="map"
|
|
|
+ :center="latlng"
|
|
|
+ :zoom="zoom"
|
|
|
+ :scroll-wheel-zoom="true"
|
|
|
+ @ready="mapReady"
|
|
|
+ @click="mapClick"
|
|
|
+ :mapStyle="mapConfig"
|
|
|
+ >
|
|
|
+ <bm-marker
|
|
|
+ v-for="item in list"
|
|
|
+ :position="{ lat: item.latitude, lng: item.longitude }"
|
|
|
+ @click="devicedClick($event, item)"
|
|
|
+ :icon="{ url: MarkerIcon, size: { width: 25, height: 25 } }"
|
|
|
+ >
|
|
|
+ <bm-label
|
|
|
+ :content="item.devicename"
|
|
|
+ :labelStyle="{ border: 'none', color: '#000' }"
|
|
|
+ :offset="{ width: -35, height: 16 }"
|
|
|
+ />
|
|
|
+ </bm-marker>
|
|
|
+ </baidu-map>
|
|
|
+
|
|
|
+ <!--设备信息面板-->
|
|
|
+ <div
|
|
|
+ class="info-panel"
|
|
|
+ :style="infoPanel.baseInfo ? 'display:inline-block' : 'display:none'"
|
|
|
+ >
|
|
|
+ <div class="info-header">
|
|
|
+ <div class="left">
|
|
|
+ <span>设备信息</span>
|
|
|
+ </div>
|
|
|
+ <div class="right">
|
|
|
+ <customBtn
|
|
|
+ :btn="true"
|
|
|
+ :btn-options="[{ label: '控制面板' }]"
|
|
|
+ @clickBtn="deviceClick(infoPanel.baseInfo)"
|
|
|
+ ></customBtn>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="map-deviced-info">
|
|
|
+ <p>
|
|
|
+ 设备名称:<span>{{ infoPanel.baseInfo.devicename }}</span>
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ 设备编码:<span>{{ infoPanel.baseInfo.serialnumber }}</span>
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ 状态:<a-tag type="info">{{ infoPanel.baseInfo.status }}</a-tag>
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ 区域:<span>{{ infoPanel.baseInfo.areaname }}</span>
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ 设备地点:<span>{{ infoPanel.baseInfo.address }}</span>
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ 固件版本:<span>{{ infoPanel.baseInfo.version }}</span>
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ 上次在线时间:<span>{{ infoPanel.baseInfo.lastconnecttime }}</span>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <p>监测指数</p>
|
|
|
+ <div class="data-info">
|
|
|
+ <p v-for="item in infoPanel.dataInfo">
|
|
|
+ {{ item.paramname }}:<span>{{ item.lastvalue }}{{ item.unit }}</span>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ style="margin: 10rem 20rem 10rem 20rem"
|
|
|
+ :style="!isShowMap ? 'display:inline-block' : 'display:none'"
|
|
|
+ >
|
|
|
+ <a-button type="primary" size="small" round @click="isShowMap = true"
|
|
|
+ >切换地图</a-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="device-wrapper"
|
|
|
+ :style="!isShowMap ? 'display:inline-block' : 'display:none'"
|
|
|
+ v-load-directive="deviceLoad"
|
|
|
+ >
|
|
|
+ <div class="device-list" v-if="deviceList.length">
|
|
|
+ <ContentBox
|
|
|
+ v-for="(item, index) in deviceList"
|
|
|
+ :key="index"
|
|
|
+ bgColor="rgb(255,255,255,0.25)"
|
|
|
+ color="#16FFF6"
|
|
|
+ @click="deviceClick(item)"
|
|
|
+ >
|
|
|
+ <div style="height: 30rem"></div>
|
|
|
+ <div class="siteinfo">
|
|
|
+ <div class="siteinfo-wrapper">
|
|
|
+ <EnvironmentOutlined />
|
|
|
+ <span>{{ item.sitename || "暂无站点" }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="device-title"
|
|
|
+ :style="[
|
|
|
+ { '--bg': item.eventstatus ? 'red' : 'rgba(255, 164, 6)' },
|
|
|
+ { '--icon': item.status == '离线' ? '#cccccc' : '#27AD00' },
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <i class="used"></i>
|
|
|
+ <a-tooltip>
|
|
|
+ <template #title>
|
|
|
+ <span>{{ item.devicename }}</span>
|
|
|
+ </template>
|
|
|
+ <span>{{ item.devicename }}</span>
|
|
|
+ </a-tooltip>
|
|
|
+ </div>
|
|
|
+ <img style="width: 100%" :src="item.attinfos[0].url" alt="" />
|
|
|
+ </ContentBox>
|
|
|
+ </div>
|
|
|
+ <a-empty :image="simpleImage" v-else>
|
|
|
+ <template #description>
|
|
|
+ <span style="color: #ffffff">暂无数据</span>
|
|
|
+ </template>
|
|
|
+ </a-empty>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, defineProps, onMounted, inject } from "vue";
|
|
|
+import Api from "@/api/api";
|
|
|
+import { useRouter } from "vue-router";
|
|
|
+import ContentBox from "./Box.vue";
|
|
|
+import { EnvironmentOutlined } from "@ant-design/icons-vue";
|
|
|
+import { Empty } from "ant-design-vue";
|
|
|
+import mapConfig from "../../IoTdashboard/modules/mapConfig.js";
|
|
|
+import customBtn from "../../controlPanel/modules/customBtn.vue";
|
|
|
+import MarkerIcon from "../../../../assets/map/marker1.svg";
|
|
|
+const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
|
|
+const router = useRouter();
|
|
|
+let isShowMap = ref(true);
|
|
|
+
|
|
|
+/* 地图 */
|
|
|
+let infoPanel = ref({
|
|
|
+ baseInfo: "",
|
|
|
+ dataInfo: "",
|
|
|
+ }),
|
|
|
+ latlng = ref({
|
|
|
+ lat: 39.9,
|
|
|
+ lng: 116.39,
|
|
|
+ }),
|
|
|
+ zoom = ref(3),
|
|
|
+ list = ref([]);
|
|
|
+
|
|
|
+let mapReady = ({ Bmap, map }) => {
|
|
|
+ let lngLatArr = list.value.map((item) => {
|
|
|
+ return item.longitude + "," + item.latitude;
|
|
|
+ });
|
|
|
+ map.value = map;
|
|
|
+ autoRange(map, lngLatArr);
|
|
|
+};
|
|
|
+
|
|
|
+let mapClick = (e) => {
|
|
|
+ if (infoPanel.value.baseInfo)
|
|
|
+ infoPanel.value = { baseInfo: "", dataInfo: "" };
|
|
|
+};
|
|
|
+
|
|
|
+let devicedClick = async (map, data) => {
|
|
|
+ console.log(map, data, "触发");
|
|
|
+ let res = await Api.requested({
|
|
|
+ id: 20230711165702,
|
|
|
+ content: {
|
|
|
+ w_deviceid: data.w_deviceid,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ infoPanel.value = {
|
|
|
+ baseInfo: data,
|
|
|
+ dataInfo: res.data,
|
|
|
+ };
|
|
|
+ console.log("infoPanel", infoPanel);
|
|
|
+};
|
|
|
+
|
|
|
+let autoRange = (map, points_arr) => {
|
|
|
+ if (points_arr.length === 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 先计算中心点坐标
|
|
|
+ * 1. 找出所有点中最大最小经纬度
|
|
|
+ * 2. 计算中心点坐标
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * bssw 为左下角
|
|
|
+ * bsne 为右上角
|
|
|
+ *
|
|
|
+ * 目标点的纬度需要大于左下角点的纬度并且小于右上角的纬度
|
|
|
+ * 目标点的经度需要大于左下角点的经度并且小于右上角的经度
|
|
|
+ *
|
|
|
+ */
|
|
|
+ let max_lng = points_arr[0].split(",")[0],
|
|
|
+ min_lat = points_arr[0].split(",")[1],
|
|
|
+ max_lat = points_arr[0].split(",")[1],
|
|
|
+ min_lng = points_arr[0].split(",")[0];
|
|
|
+ for (let i = 0; i < points_arr.length - 1; i++) {
|
|
|
+ let lng_lat = points_arr[i + 1].split(",");
|
|
|
+ // max = max < arr[i+1] ? arr[i+1] : max
|
|
|
+ max_lng = max_lng < lng_lat[0] ? lng_lat[0] : max_lng;
|
|
|
+ min_lng = min_lng > lng_lat[0] ? lng_lat[0] : min_lng;
|
|
|
+ max_lat = max_lat < lng_lat[1] ? lng_lat[1] : max_lat;
|
|
|
+ min_lat = min_lat > lng_lat[1] ? lng_lat[1] : min_lat;
|
|
|
+ }
|
|
|
+ // console.log((Number(max_lng) + Number(min_lng)) / 2,(Number(max_lat)+Number(min_lat))/2);
|
|
|
+ let n_lng = (Number(max_lng) + Number(min_lng)) / 2;
|
|
|
+ let n_lat = (Number(max_lat) + Number(min_lat)) / 2;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算层级
|
|
|
+ * 百度地图比例尺与级别关系
|
|
|
+ */
|
|
|
+ let bs = map.getBounds(); //获取可视区域
|
|
|
+ let bssw = bs.getSouthWest(); //可视区域左下角
|
|
|
+ let bsne = bs.getNorthEast(); //可视区域右上角
|
|
|
+ // 百度地图比例尺与级别关系,参考https://blog.csdn.net/tjj3027/article/details/81015138
|
|
|
+ const map_rule = [
|
|
|
+ 500000, 250000, 100000, 50000, 25000, 10000, 5000, 2500, 1250, 1000, 500,
|
|
|
+ 250, 100, 50, 25, 10, 5, 2.5, 1,
|
|
|
+ ];
|
|
|
+
|
|
|
+ let zoomA = [4, 4];
|
|
|
+ for (let j = 0; j < 2; j++) {
|
|
|
+ let viewSize, searchSize;
|
|
|
+ if (j === 0) {
|
|
|
+ viewSize = bsne.lng - bssw.lng; // 当前可视宽度右上角经度-左下角经度
|
|
|
+ searchSize = (max_lng - min_lng) * 1.1; // 搜索结果的最大宽度 经度最大值-经度最小值
|
|
|
+ } else {
|
|
|
+ viewSize = bsne.lat - bssw.lat; // 当前可视高度右上角纬度-左下角纬度
|
|
|
+ searchSize = (max_lat - min_lat) * 1.1; // 搜索结果的最大宽度 纬度最大值-纬度最小值
|
|
|
+ }
|
|
|
+
|
|
|
+ let minDiff = 0;
|
|
|
+ for (let i = 0; i < map_rule.length; i++) {
|
|
|
+ let diff =
|
|
|
+ (viewSize * map_rule[i]) / map_rule[zoom.value - 1] - searchSize;
|
|
|
+ if (diff > 0) {
|
|
|
+ if (minDiff == 0 || diff < minDiff) {
|
|
|
+ zoomA[j] = i + 1;
|
|
|
+ minDiff = diff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let zoom1 = zoomA[0] > zoomA[1] ? zoomA[1] : zoomA[0];
|
|
|
+ // 设定地图最大缩放层级为18,最小为4
|
|
|
+ zoom1 = zoom1 >= 18 ? 18 : zoom1;
|
|
|
+ zoom1 = zoom1 <= 4 ? 4 : zoom1;
|
|
|
+ /*
|
|
|
+ * 图移动到新的中点,调整好层级
|
|
|
+ * 这里使用 map.centerAndZoom(new BMap.Point(n_lng, n_lat),zoom)
|
|
|
+ * 屏幕可能会过度晃动,暂时知道原因,所以用了下面的方案
|
|
|
+ **/
|
|
|
+ zoom.value = zoom1;
|
|
|
+ function setCenter() {
|
|
|
+ map.panTo(new BMap.Point(n_lng, n_lat), { noAnimation: false }); // 设置中心点坐标
|
|
|
+ console.log("触发");
|
|
|
+ map.removeEventListener("tilesloaded", setCenter);
|
|
|
+ }
|
|
|
+ //地图加载完毕(地图稍微有改动就会触发) 当地图所有图块完成加载时触发此事件
|
|
|
+ map.addEventListener("tilesloaded", setCenter);
|
|
|
+};
|
|
|
+
|
|
|
+/* 列表 */
|
|
|
+let DeviceTotalPage = ref(0);
|
|
|
+let deviceList = ref([]);
|
|
|
+let deviceParam = ref({
|
|
|
+ id: 20230914133302,
|
|
|
+ content: {
|
|
|
+ pageNumber: 1,
|
|
|
+ pageSize: 15,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+let deviceLoad = () => {
|
|
|
+ if (deviceParam.value.content.pageNumber == DeviceTotalPage.value) return;
|
|
|
+ deviceParam.value.content.pageNumber += 1;
|
|
|
+ console.log(deviceParam.value);
|
|
|
+ getDeviceData();
|
|
|
+};
|
|
|
+
|
|
|
+let getDeviceData = async () => {
|
|
|
+ let res = await Api.requested(deviceParam.value);
|
|
|
+ console.log("获取列表", res);
|
|
|
+ deviceList.value =
|
|
|
+ deviceParam.value.content.pageNumber == 1
|
|
|
+ ? res.data
|
|
|
+ : deviceList.value.concat(res.data);
|
|
|
+
|
|
|
+ list.value = res.data.filter((item) => item.latitude != "");
|
|
|
+ console.log("list.value", list.value);
|
|
|
+ if (!list.value.length) {
|
|
|
+ latlng.value = {
|
|
|
+ lat: 39.9,
|
|
|
+ lng: 116.39,
|
|
|
+ };
|
|
|
+ zoom.value = 6;
|
|
|
+ }
|
|
|
+ DeviceTotalPage.value = res.pageTotal;
|
|
|
+};
|
|
|
+
|
|
|
+let deviceClick = (data) => {
|
|
|
+ console.log("跳转", data);
|
|
|
+ router.push({
|
|
|
+ path: "/" + (data.dashboardpath || "baseDevice"),
|
|
|
+ query: {
|
|
|
+ id: data.w_deviceid,
|
|
|
+ },
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ getDeviceData();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scope>
|
|
|
+* {
|
|
|
+ box-sizing: border-box;
|
|
|
+ --color1: #16fff6;
|
|
|
+ --color2: rgba(255, 164, 6);
|
|
|
+}
|
|
|
+/* 地图 */
|
|
|
+.M-map {
|
|
|
+ width: 100%;
|
|
|
+ height: calc(100vh - 290rem);
|
|
|
+ position: relative;
|
|
|
+ padding: 12rem;
|
|
|
+}
|
|
|
+.M-map .map {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 8rem;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.M-map .but-box {
|
|
|
+ position: absolute;
|
|
|
+ top: 25rem;
|
|
|
+ left: 25rem;
|
|
|
+ z-index: 9999;
|
|
|
+}
|
|
|
+
|
|
|
+.info-panel {
|
|
|
+ width: 250rem;
|
|
|
+ background: rgb(90, 100, 119, 0.8) !important;
|
|
|
+ position: absolute;
|
|
|
+ top: 48%;
|
|
|
+ left: 4%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ z-index: 9999999999999999999;
|
|
|
+ padding: 10rem;
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 14rem;
|
|
|
+}
|
|
|
+.info-panel .info-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.info-panel .map-deviced-info {
|
|
|
+ background: rgba(255, 255, 255, 0.2);
|
|
|
+ border-radius: 5rem;
|
|
|
+ padding: 10rem;
|
|
|
+ margin: 10rem 0;
|
|
|
+}
|
|
|
+.info-panel .data-info {
|
|
|
+ background: rgba(255, 255, 255, 0.2);
|
|
|
+ border-radius: 5rem;
|
|
|
+ padding: 10rem;
|
|
|
+ margin: 10rem 0;
|
|
|
+}
|
|
|
+/* 列表 */
|
|
|
+.device-wrapper {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ overflow-y: scroll;
|
|
|
+ height: calc(100% - 180rem);
|
|
|
+ scrollbar-width: none; /* firefox */
|
|
|
+ -ms-overflow-style: none; /* IE 10+ */
|
|
|
+ margin-top: 10rem;
|
|
|
+}
|
|
|
+.device-wrapper .device-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ padding-bottom: 50rem;
|
|
|
+}
|
|
|
+
|
|
|
+.device-wrapper .device-list .content_box {
|
|
|
+ flex: 1 !important;
|
|
|
+ max-width: 212rem;
|
|
|
+ margin: 0 18rem;
|
|
|
+}
|
|
|
+
|
|
|
+.device-wrapper .device-list .used {
|
|
|
+ display: inline-block;
|
|
|
+ width: 10rem;
|
|
|
+ height: 10rem;
|
|
|
+ border-radius: 10rem;
|
|
|
+ background: var(--icon);
|
|
|
+ margin-right: 10rem;
|
|
|
+}
|
|
|
+.device-wrapper .device-list .device-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 44rem;
|
|
|
+ font-size: 14rem;
|
|
|
+}
|
|
|
+.device-wrapper .device-list .device-title,
|
|
|
+img,
|
|
|
+.status {
|
|
|
+ margin-bottom: 10rem;
|
|
|
+ font-size: 14rem;
|
|
|
+}
|
|
|
+.device-wrapper .device-list .device-title span {
|
|
|
+ display: inline-block;
|
|
|
+ width: 140rem;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ overflow: hidden;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+.device-wrapper .device-list .content_box {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ align-content: center;
|
|
|
+ margin-bottom: 15rem;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 0rem 25rem;
|
|
|
+ width: auto !important;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+.device-wrapper .device-list .content_box:hover {
|
|
|
+ --color: red !important;
|
|
|
+}
|
|
|
+.device-wrapper .device-list .content_box:hover img {
|
|
|
+ transition: all 0.2s;
|
|
|
+ transform: scale(1.2);
|
|
|
+}
|
|
|
+.device-wrapper .device-list .i {
|
|
|
+ border-radius: 20rem;
|
|
|
+ background: red;
|
|
|
+}
|
|
|
+.device-wrapper .device-list img {
|
|
|
+ width: 100%;
|
|
|
+ max-height: 70rem;
|
|
|
+}
|
|
|
+.device-wrapper .device-list .status {
|
|
|
+ color: var(--color2);
|
|
|
+}
|
|
|
+
|
|
|
+.siteinfo {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.siteinfo .siteinfo-wrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ align-self: flex-start;
|
|
|
+ width: 100%;
|
|
|
+ background: #40a9ff;
|
|
|
+}
|
|
|
+.siteinfo span {
|
|
|
+ margin-left: 10px;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ overflow: hidden;
|
|
|
+ font-size: 14rem;
|
|
|
+}
|
|
|
+</style>
|