lipenghui преди 2 месеца
родител
ревизия
cb98f8251b
променени са 4 файла, в които са добавени 620 реда и са изтрити 285 реда
  1. 1 0
      public/images/ding300.svg
  2. 0 2
      src/views/pms/device/monitor/TdDeviceInfo.vue
  3. 341 0
      src/views/pms/map/DeviceMonitorDrawer.vue
  4. 278 283
      src/views/pms/map/Map.vue

+ 1 - 0
public/images/ding300.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1749536872427" class="icon" viewBox="0 0 1129 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12543" xmlns:xlink="http://www.w3.org/1999/xlink" width="33.076171875" height="30"><path d="M828.027586 330.151724c-35.310345-82.97931-105.931034-146.537931-187.144827-171.255172-22.951724-7.062069-47.668966-10.593103-72.386207-10.593104-86.510345 0-171.255172 44.137931-224.22069 118.289655-60.027586 81.213793-82.97931 187.144828-33.544828 278.951725 40.606897 74.151724 204.8 286.013793 248.937932 346.041379 75.917241-105.931034 171.255172-201.268966 238.344827-314.262069 30.013793-51.2 42.372414-63.558621 49.434483-114.758621 5.296552-42.372414 0-86.510345-19.42069-132.413793zM564.965517 609.103448c-95.337931 0-173.02069-77.682759-173.020689-173.020689s77.682759-173.02069 173.020689-173.02069 173.02069 77.682759 173.02069 173.02069-77.682759 173.02069-173.02069 173.020689z" fill="#1296db" p-id="12544"></path><path d="M564.965517 436.082759m-82.97931 0a82.97931 82.97931 0 1 0 165.958621 0 82.97931 82.97931 0 1 0-165.958621 0Z" fill="#1296db" p-id="12545"></path></svg>

+ 0 - 2
src/views/pms/device/monitor/TdDeviceInfo.vue

@@ -227,8 +227,6 @@ onMounted(async () => {
   })
   await getChart(dateRange.value)
   await renderChart()
-
-
 })
 </script>
 <style scoped lang="scss">

+ 341 - 0
src/views/pms/map/DeviceMonitorDrawer.vue

@@ -0,0 +1,341 @@
+<template>
+  <el-drawer
+    title="设备运行监控"
+    :append-to-body="true"
+    :model-value="modelValue"
+    @update:model-value="$emit('update:modelValue', $event)"
+    :show-close="false"
+    :size="computedSize"
+    :before-close="handleClose"
+  >
+    <template v-if="true">
+      <ContentWrap v-loading="formLoading">
+        <ContentWrap>
+          <el-form style="height: 89px; margin-left: 20px">
+            <el-row style="display: flex; flex-direction: row">
+              <el-col :span="8">
+                <el-form-item prop="deviceCode">
+                  <template #label>
+                    <span class="custom-label">资产编码:</span>
+                  </template>
+                  <span class="custom-label">{{ formData.deviceCode }}</span>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item prop="deviceName">
+                  <template #label>
+                    <span class="custom-label">设备类别:</span>
+                  </template>
+                  <span class="custom-label">{{ formData.deviceName }}</span>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item prop="dept">
+                  <template #label>
+                    <span class="custom-label">所在部门:</span>
+                  </template>
+                  <span class="custom-label">{{ formData.dept }}</span>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item prop="ifInline">
+                  <template #label>
+                    <span class="custom-label">是否在线:</span>
+                  </template>
+                  <template #default>
+                    <dict-tag :type="DICT_TYPE.IOT_DEVICE_STATUS" :value="formData.ifInline" />
+                  </template>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item prop="lastInlineTime">
+                  <template #label>
+                    <span class="custom-label">最后数据时间:</span>
+                  </template>
+                  <span class="custom-label">{{ formData.lastInlineTime }}</span>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </ContentWrap>
+        <ContentWrap>
+          <el-row>
+            <el-col :span="24">
+              <TdDeviceLabel :tags="specs" @select="labelSelect" tag-width="32%" />
+            </el-col>
+          </el-row>
+        </ContentWrap>
+        <ContentWrap>
+          <div class="chart-container">
+            <!-- 图表容器 -->
+            <el-date-picker
+              v-model="dateRange"
+              type="datetimerange"
+              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+              start-placeholder="起始日期时间"
+              end-placeholder="结束日期时间"
+              format="YYYY-MM-DD HH:mm:ss"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              @change="handleDateChange"
+            />
+            <div v-loading="loading" style="height: 100%" ref="chartContainer"></div>
+          </div>
+        </ContentWrap>
+      </ContentWrap>
+    </template>
+  </el-drawer>
+</template>
+<script setup lang="ts">
+import {defineEmits, defineOptions, ref, watch} from 'vue'
+import { DICT_TYPE } from '@/utils/dict'
+import TdDeviceLabel from '@/views/pms/device/monitor/TdDeviceLabel.vue'
+import dayjs from "dayjs";
+import {IotStatApi} from "@/api/pms/stat";
+import * as echarts from "echarts";
+import {IotDeviceApi} from "@/api/pms/device";
+import {propTypes} from "@/utils/propTypes";
+
+const drawerVisible = ref<boolean>(false)
+const emit = defineEmits(['update:modelValue', 'add', 'delete'])
+
+defineOptions({
+  name: 'DeviceMonitorDrawer'
+})
+
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  createTime: [],
+  bomId: '',
+  name: '',
+  code: ''
+})
+
+const windowWidth = ref(window.innerWidth)
+// 动态计算百分比
+const computedSize = computed(() => {
+  return windowWidth.value > 1200 ? '60%' : '80%'
+})
+
+const formLoading = ref(false)
+const total = ref(0) // 列表的总页数
+const materials = ref([])
+
+const props = defineProps({
+  id: propTypes.number.def(undefined),
+  dept: undefined,
+  deviceCode: undefined,
+  deviceName: undefined,
+  ifLine: undefined,
+  lastLineTime: undefined
+})
+
+const formData = ref({
+  id: undefined,
+  deptName: '',
+  deviceCode: '',
+  deviceName: '',
+  ifInline: undefined,
+  lastInlineTime: ''
+})
+const specs = ref([])
+
+// 响应式数据
+const startTime = ref('')
+const endTime = ref('')
+const topicName = ref([])
+const loading = ref(false)
+const topic = ref('')
+
+const handleDateChange = async (val) => {
+  if (val && val.length === 2) {
+    await getChart(val)
+    await renderChart()
+
+  }
+}
+const defaultEnd = dayjs()
+const defaultStart = defaultEnd.subtract(1, 'day')
+const dateRange = ref([
+  defaultStart.format('YYYY-MM-DD HH:mm:ss'),
+  defaultEnd.format('YYYY-MM-DD HH:mm:ss')
+])
+const labelSelect =async (row) =>{
+  topic.value = row.identifier
+  topicName.value = row.modelName
+  await getChart(dateRange.value)
+  await renderChart()
+}
+
+const chartContainer = ref(null)
+let chartInstance = null
+
+// 时间格式化(HH:mm)
+const formatTime = timestamp => {
+  return new Date(timestamp)
+    .toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit',second:'2-digit' })
+    .slice(0, 5)
+}
+const result = ref([])
+const getChart = async (range) =>{
+  loading.value = true
+  debugger
+  await IotStatApi.getDeviceInfoChart(props.deviceCode, topic.value, range[0], range[1]).then(res=>{
+    result.value = res
+    loading.value = false
+  })
+}
+// 初始化图表
+const renderChart = async () => {
+  debugger
+  if (!chartContainer.value) return
+
+  // 销毁旧实例
+  if (chartInstance) chartInstance.dispose()
+
+  chartInstance = markRaw(echarts.init(chartContainer.value))
+  const option = {
+    title:{
+      text: topicName.value+'数据趋势',
+      left:'center',
+    },
+    tooltip: { trigger: 'axis', },
+    xAxis: {
+      type: 'category',
+      // data: result.map(d => formatTime(d.timestamp)),
+      data: result.value.map(item => Object.keys(item)[0]),
+      axisLabel: { rotate: 45 }, // X轴标签旋转防止重叠
+      inverse: true,
+    },
+    yAxis: { type: 'value' },
+    dataZoom: [{
+      type: 'slider',
+      xAxisIndex: 0,
+      start: 0,  // 初始显示范围开始位置
+      end: 100   // 初始显示范围结束位置:ml-citation{ref="7" data="citationList"}
+    }],
+    series: [{
+      // data: result.map(d => d.value),
+      data: result.value.map(item => {
+        const key = Object.keys(item)[0]; // 获取当前元素的key
+        return item[key][0][topic.value]; // 提取value数组中第一个对象的属性
+      }),
+      type: 'line',
+      smooth: true,
+      areaStyle: {} // 显示区域填充
+    }]
+  }
+
+  chartInstance.setOption(option)
+
+  // 窗口自适应
+  window.addEventListener('resize', () => chartInstance.resize())
+}
+watch(() => props.id, async (newVal) => {
+  if (newVal) {
+    await init()
+  }
+})
+const init =async () =>{
+  drawerVisible.value = true
+  formLoading.value = true
+
+  formData.value.deviceCode = props.deviceCode
+  formData.value.deviceName = props.deviceName
+  formData.value.lastInlineTime = props.lastLineTime
+  formData.value.ifInline = props.ifLine
+  formData.value.dept = props.dept
+  debugger
+  await IotDeviceApi.getIotDeviceTds(props.id).then(res => {
+    specs.value = res
+    specs.value = specs.value.sort((a, b) => {
+      return b.modelOrder - a.modelOrder
+    })
+    formLoading.value = false
+    topic.value = specs.value[0].identifier
+    topicName.value = specs.value[0].modelName
+  })
+  await getChart(dateRange.value)
+  debugger
+  await renderChart()
+}
+// 打开抽屉
+const openDrawer = async () => {
+  if (props.id) {
+    await init()
+  }
+}
+
+// 关闭抽屉
+const closeDrawer = () => {
+  drawerVisible.value = false
+}
+
+// 关闭抽屉
+const handleClose = () => {
+  emit('update:modelValue', false)
+  materials.value = []
+}
+
+defineExpose({ openDrawer, closeDrawer }) // 暴露方法给父组件
+</script>
+
+<style lang="scss" scoped>
+.container {
+  width: 100%;
+  margin: 20px auto;
+  padding: 24px;
+  //background: #f8f9fa;
+  border-radius: 12px;
+}
+.chart-container {
+  width: 100%;
+  height: 600px;
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0,0,0,0.1);
+}
+
+.date-controls {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 20px;
+}
+
+input[type="datetime-local"] {
+  padding: 8px 12px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  transition: border-color 0.2s;
+}
+
+input[type="datetime-local"]:focus {
+  border-color: #409eff;
+  outline: none;
+}
+
+.separator {
+  color: #606266;
+}
+
+.query-btn {
+  padding: 8px 20px;
+  background: #409eff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: opacity 0.2s;
+}
+
+.query-btn:hover {
+  opacity: 0.8;
+}
+
+.custom-label{
+  font-size: 17px;
+  font-weight: bold;
+}
+</style>

+ 278 - 283
src/views/pms/map/Map.vue

@@ -4,151 +4,144 @@
     <div class="map-controls">
       <button @click="zoomIn">放大</button>
       <button @click="zoomOut">缩小</button>
-      <button @click="toggleMapType">切换地图类型({{ mapType === 'BMAP_NORMAL_MAP' ? '地图' : '卫星' }})</button>
+      <button @click="toggleMapType"
+        >切换地图类型({{ mapType === 'BMAP_NORMAL_MAP' ? '地图' : '卫星' }})</button>
     </div>
   </div>
+  <DeviceMonitorDrawer :model-value="drawerVisible" @update:model-value="(val) => (drawerVisible = val)" :id="deviceId" :deviceName="deviceName" :lastLineTime="lastLineTime"
+                       :ifLine="ifLine" :dept="dept" :deviceCode="deviceCode"
+                       ref="showDrawer" />
 </template>
 
-<script lang="ts">
-import { defineComponent, onMounted, onBeforeUnmount, ref } from 'vue';
-import {DICT_TYPE, getDictLabel} from "@/utils/dict";
+<script setup lang="ts">
+import { onBeforeUnmount, onMounted, ref } from 'vue'
+import { DICT_TYPE, getDictLabel } from '@/utils/dict'
 import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
-import {aY} from "@fullcalendar/core/internal-common";
+import DeviceMonitorDrawer from '@/views/pms/map/DeviceMonitorDrawer.vue'
+import * as DeptApi from '@/api/system/dept'
 
 interface Cluster {
-  lng: number;
-  lat: number;
-  count: number;
-  devices?: Device[];
+  lng: number
+  lat: number
+  count: number
+  devices?: Device[]
+}
+defineOptions({ name: 'DeviceMap' })
+const showDrawer = ref()
+const drawerVisible = ref<boolean>(false)
+const mapContainer = ref<HTMLElement | null>(null)
+const map = ref<any>(null)
+const mapType = ref<'BMAP_NORMAL_MAP' | 'BMAP_SATELLITE_MAP'>('BMAP_NORMAL_MAP')
+const selectedDevice = ref<IotDeviceVO | null>(null)
+const hoverDevice = ref<IotDeviceVO | null>(null)
+const clusters = ref<Cluster[]>([])
+const deviceId = ref()
+const deviceName = ref('')
+const lastLineTime = ref('')
+const ifLine = ref()
+const dept = ref('')
+const deviceCode = ref('')
+
+// 设备数据示例
+const devices = ref<IotDeviceVO[]>()
+
+// 初始化地图
+const initMap = () => {
+  if (!mapContainer.value) return
+
+  const script = document.createElement('script')
+  script.src = `https://api.map.baidu.com/api?v=3.0&ak=c0crhdxQ5H7WcqbcazGr7mnHrLa4GmO0&type=webgl`
+  script.async = true
+  script.onload = () => {
+    if ((window as any).BMap) {
+      map.value = new (window as any).BMap.Map(mapContainer.value)
+      const point = new (window as any).BMap.Point(104.114129, 37.550339)
+      map.value.centerAndZoom(point, 6)
+
+      map.value.enableScrollWheelZoom(true)
+      map.value.setMapType((window as any)[mapType.value])
+
+      map.value.addControl(new (window as any).BMap.NavigationControl())
+      map.value.addControl(new (window as any).BMap.ScaleControl())
+
+      initDeviceMarkers()
+      map.value.addEventListener('zoomend', () => {
+        initDeviceMarkers()
+      })
+    } else {
+      console.error('百度地图API加载失败')
+    }
+  }
+  script.onerror = () => {
+    console.error('百度地图API加载失败')
+  }
+  document.head.appendChild(script)
 }
 
-export default defineComponent({
-  name: 'ChinaDeviceMap',
-  setup() {
-    const mapContainer = ref<HTMLElement | null>(null);
-    const map = ref<any>(null);
-    const mapType = ref<'BMAP_NORMAL_MAP' | 'BMAP_SATELLITE_MAP'>('BMAP_NORMAL_MAP');
-    const selectedDevice = ref<IotDeviceVO | null>(null);
-    const hoverDevice = ref<IotDeviceVO | null>(null);
-    const clusters = ref<Cluster[]>([]);
-
-    // 设备数据示例
-    const devices = ref<IotDeviceVO[]>();
-
-    // const devices = ref<Device[]>([
-    //   { id: 'D001', deviceName: '设备1', location: '北京', deviceStatus: 1, lng: 84.053192, lat: 41.319412 },
-      // { id: 'D002', deviceName: '设备2', location: '上海', deviceStatus: 2, lng: 121.474, lat: 31.230 },
-      // { id: 'D003', deviceName: '设备3', location: '广州', deviceStatus: 1, lng: 113.264, lat: 23.129 },
-      // { id: 'D004', deviceName: '设备4', location: '深圳', deviceStatus: 3, lng: 114.058, lat: 22.543 },
-      // { id: 'D005', deviceName: '设备5', location: '成都', deviceStatus: 1, lng: 104.065, lat: 30.659 },
-      // { id: 'D006', deviceName: '设备6', location: '武汉', deviceStatus: 2, lng: 114.305, lat: 30.593 },
-      // { id: 'D007', deviceName: '设备7', location: '西安', deviceStatus: 1, lng: 108.948, lat: 34.263 },
-      // { id: 'D008', deviceName: '设备8', location: '杭州', deviceStatus: 3, lng: 120.155, lat: 30.274 },
-      // { id: 'D009', deviceName: '设备9', location: '南京', deviceStatus: 1, lng: 118.797, lat: 32.060 },
-      // { id: 'D010', deviceName: '设备10', location: '重庆', deviceStatus: 2, lng: 106.505, lat: 29.533 },
-    // ]);
-    // const deviceStatusMap = {
-    //   1: '在线',
-    //   2: '离线',
-    //   3: '异常'
-    // };
-
-    // 初始化地图
-    const initMap = () => {
-      if (!mapContainer.value) return;
-
-      const script = document.createElement('script');
-      script.src = `https://api.map.baidu.com/api?v=3.0&ak=c0crhdxQ5H7WcqbcazGr7mnHrLa4GmO0&type=webgl`;
-      script.async = true;
-      script.onload = () => {
-        if ((window as any).BMap) {
-          map.value = new (window as any).BMap.Map(mapContainer.value);
-          const point = new (window as any).BMap.Point(104.114129, 37.550339);
-          map.value.centerAndZoom(point, 6);
-
-          map.value.enableScrollWheelZoom(true);
-          map.value.setMapType((window as any)[mapType.value]);
-
-          map.value.addControl(new (window as any).BMap.NavigationControl());
-          map.value.addControl(new (window as any).BMap.ScaleControl());
-
-          initDeviceMarkers();
-          map.value.addEventListener('zoomend', () => {
-            initDeviceMarkers();
-          });
-        } else {
-          console.error('百度地图API加载失败');
+const initDeviceMarkers = () => {
+  if (!map.value) return
+
+  map.value.clearOverlays()
+
+  const zoomLevel = map.value.getZoom()
+  if (zoomLevel > 8) {
+    // 高缩放级别下显示单个设备标记
+    devices.value.forEach((device) => {
+      const point = new (window as any).BMap.Point(device.lng, device.lat)
+      const marker = createDeviceMarker(device, point)
+      map.value.addOverlay(marker)
+    })
+  } else {
+    // 低缩放级别下进行聚合
+    clusters.value = clusterDevices(devices.value, map.value)
+    clusters.value.forEach((cluster) => {
+      if (cluster.count === 1) {
+        // 只有一个设备时显示设备图标
+        const device = cluster.devices?.[0]
+        if (device) {
+          const point = new (window as any).BMap.Point(device.lng, device.lat)
+          const marker = createDeviceMarker(device, point)
+          map.value.addOverlay(marker)
         }
-      };
-      script.onerror = () => {
-        console.error('百度地图API加载失败');
-      };
-      document.head.appendChild(script);
-    };
-
-    const initDeviceMarkers = () => {
-      if (!map.value) return;
-
-      map.value.clearOverlays();
-
-      const zoomLevel = map.value.getZoom();
-      if (zoomLevel > 8) {
-        // 高缩放级别下显示单个设备标记
-        devices.value.forEach(device => {
-          const point = new (window as any).BMap.Point(device.lng, device.lat);
-          const marker = createDeviceMarker(device, point);
-          map.value.addOverlay(marker);
-        });
-      } else {
-        // 低缩放级别下进行聚合
-        debugger;
-        clusters.value = clusterDevices(devices.value, map.value);
-        clusters.value.forEach(cluster => {
-          if (cluster.count === 1) {
-            // 只有一个设备时显示设备图标
-            const device = cluster.devices?.[0];
-            if (device) {
-              const point = new (window as any).BMap.Point(device.lng, device.lat);
-              const marker = createDeviceMarker(device, point);
-              map.value.addOverlay(marker);
-            }
-          } else if (cluster.count > 1) {
-            // 多个设备时显示聚合标签
-            const point = new (window as any).BMap.Point(cluster.lng, cluster.lat);
-            const label = createClusterLabel(cluster, point);
-            map.value.addOverlay(label);
-          }
-        });
+      } else if (cluster.count > 1) {
+        // 多个设备时显示聚合标签
+        const point = new (window as any).BMap.Point(cluster.lng, cluster.lat)
+        const label = createClusterLabel(cluster, point)
+        map.value.addOverlay(label)
+      }
+    })
+  }
+}
+
+const createDeviceMarker = (device: IotDeviceVO, point: any) => {
+  const marker = new (window as any).BMap.Marker(point, {
+    icon: new (window as any).BMap.Icon(
+      'https://iot.deepoil.cc/images/ding300.svg',
+      new (window as any).BMap.Size(28, 30),
+      {
+        anchor: new (window as any).BMap.Size(14, 25)
+        // imageOffset: new (window as any).BMap.Size(0, -5)
       }
-    };
-
-    const createDeviceMarker = (device: IotDeviceVO, point: any) => {
-      const marker = new (window as any).BMap.Marker(point, {
-        icon: new (window as any).BMap.Icon('https://iot.deepoil.cc/images/ding300.svg', new (window as any).BMap.Size(28, 30),
-          {
-            anchor: new (window as any).BMap.Size(14, 25),
-            // imageOffset: new (window as any).BMap.Size(0, -5)
-          }
-        )
-      });
-
-      // 添加点击事件
-      marker.addEventListener('click', () => {
-        showDeviceInfoWindow(device, point);
-      });
-
-      return marker;
-    };
-
-    const createClusterLabel = (cluster: Cluster, point: any) => {
-      const label = new (window as any).BMap.Label(cluster.count.toString(), {
-        position: point,
-        offset: new (window as any).BMap.Size(-10, -10)
-      });
-
-      // 创建一个 style 标签并添加到 head 中,定义呼吸动画
-      const style = document.createElement('style');
-      style.textContent = `
+    )
+  })
+
+  // 添加点击事件
+  marker.addEventListener('click', () => {
+    showDeviceInfoWindow(device, point)
+  })
+
+  return marker
+}
+
+const createClusterLabel = (cluster: Cluster, point: any) => {
+  const label = new (window as any).BMap.Label(cluster.count.toString(), {
+    position: point,
+    offset: new (window as any).BMap.Size(-10, -10)
+  })
+
+  // 创建一个 style 标签并添加到 head 中,定义呼吸动画
+  const style = document.createElement('style')
+  style.textContent = `
     @keyframes breathing {
       0% {
         border-color: rgba(255, 255, 255, 0.3);
@@ -160,171 +153,173 @@ export default defineComponent({
         border-color: rgba(255, 255, 255, 0.3);
       }
     }
-  `;
-      document.head.appendChild(style);
-
-      // 初始样式
-      label.setStyle({
-        color: '#fff',
-        backgroundColor: '#c38f65',
-        borderRadius: '50%',
-        width: '50px',
-        height: '50px',
-        textAlign: 'center',
-        lineHeight: '40px',
-        cursor: 'pointer',
-        fontSize: '18px',
-        fontWeight: 'bold',
-        boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
-        transition: 'all 0.3s ease',
-        border: '4px solid rgba(255, 255, 255, 0.5)', // 添加带有不透明度的边框
-        animation: 'breathing 2s infinite' // 添加呼吸动画
-      });
-
-      // 鼠标悬停样式
-      const hoverStyle = {
-        backgroundColor: '#2196df',
-        transform: 'scale(1.1)'
-      };
-
-      // 鼠标移出样式
-      const normalStyle = {
-        backgroundColor: '#c38f65',
-        transform: 'scale(1)'
-      };
-
-      // // 添加鼠标悬停事件
-      // label.addEventListener('mouseover', () => {
-      //   Object.assign(label.getStyle(), hoverStyle);
-      //   label.setStyle(label.getStyle());
-      // });
-      //
-      // // 添加鼠标移出事件
-      // label.addEventListener('mouseout', () => {
-      //   Object.assign(label.getStyle(), normalStyle);
-      //   label.setStyle(label.getStyle());
-      // });
-
-      // 添加点击事件
-      label.addEventListener('click', () => {
-        map.value?.setZoom(9);
-        map.value?.panTo(point);
-      });
-
-      return label;
-    };
-
-    const clusterDevices = (devices: IotDeviceVO[], map: any): Cluster[] => {
-      const clusters: Cluster[] = [];
-      debugger
-      const gridSize = getGridSize(map.getZoom());
-
-      const gridMap = new Map<string, Cluster>();
-
-      devices.forEach(device => {
-        const gridKey = `${Math.floor(device.lng / gridSize)}_${Math.floor(device.lat / gridSize)}`;
-
-        if (!gridMap.has(gridKey)) {
-          gridMap.set(gridKey, {
-            lng: Math.floor(device.lng / gridSize) * gridSize + gridSize / 2,
-            lat: Math.floor(device.lat / gridSize) * gridSize + gridSize / 2,
-            count: 0,
-            devices: []
-          });
-        }
+  `
+  document.head.appendChild(style)
+
+  // 初始样式
+  label.setStyle({
+    color: '#fff',
+    backgroundColor: '#c38f65',
+    borderRadius: '50%',
+    width: '50px',
+    height: '50px',
+    textAlign: 'center',
+    lineHeight: '40px',
+    cursor: 'pointer',
+    fontSize: '18px',
+    fontWeight: 'bold',
+    boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
+    transition: 'all 0.3s ease',
+    border: '4px solid rgba(255, 255, 255, 0.5)', // 添加带有不透明度的边框
+    animation: 'breathing 2s infinite' // 添加呼吸动画
+  })
+
+  // 鼠标悬停样式
+  const hoverStyle = {
+    backgroundColor: '#2196df',
+    transform: 'scale(1.1)'
+  }
+
+  // 鼠标移出样式
+  const normalStyle = {
+    backgroundColor: '#c38f65',
+    transform: 'scale(1)'
+  }
+
+  // 添加点击事件
+  label.addEventListener('click', () => {
+    map.value?.setZoom(9)
+    map.value?.panTo(point)
+  })
+
+  return label
+}
+
+const clusterDevices = (devices: IotDeviceVO[], map: any): Cluster[] => {
+  const clusters: Cluster[] = []
+  const gridSize = getGridSize(map.getZoom())
+
+  const gridMap = new Map<string, Cluster>()
+
+  devices.forEach((device) => {
+    const gridKey = `${Math.floor(device.lng / gridSize)}_${Math.floor(device.lat / gridSize)}`
 
-        const cluster = gridMap.get(gridKey)!;
-        cluster.count++;
-        if (!cluster.devices) cluster.devices = [];
-        cluster.devices.push(device);
-      });
-
-      return Array.from(gridMap.values());
-    };
-
-    const getGridSize = (zoom: number): number => {
-      debugger
-      if (zoom <= 5) return 2;
-      if (zoom <= 8) return 1;
-      if (zoom < 10) return 5;
-      return 0.1;
-    };
-    const deviceDetail = (device: IotDeviceVO, point: any) => {
-      alert("abc")
+    if (!gridMap.has(gridKey)) {
+      gridMap.set(gridKey, {
+        lng: Math.floor(device.lng / gridSize) * gridSize + gridSize / 2,
+        lat: Math.floor(device.lat / gridSize) * gridSize + gridSize / 2,
+        count: 0,
+        devices: []
+      })
     }
-    const showDeviceInfoWindow = (device: IotDeviceVO, point: any) => {
-      const content = `
+
+    const cluster = gridMap.get(gridKey)!
+    cluster.count++
+    if (!cluster.devices) cluster.devices = []
+    cluster.devices.push(device)
+  })
+
+  return Array.from(gridMap.values())
+}
+
+const getGridSize = (zoom: number): number => {
+  if (zoom <= 5) return 2
+  if (zoom <= 8) return 1
+  if (zoom < 10) return 5
+  return 0.1
+}
+
+const showDeviceInfoWindow = (device: IotDeviceVO, point: any) => {
+  DeptApi.getDept(device.deptId).then(res=>{
+    dept.value = res.name
+    const content = `
         <div style="margin-top: 5px">
           <p><strong>设备编码:</strong> ${device.deviceCode}</p>
           <p><strong>设备名称:</strong> ${device.deviceName}</p>
+          <p><strong>所在部门:</strong> ${res.name}</p>
           <p><strong>位置:</strong> ${device.location}</p>
           <p><strong>状态:</strong> ${getDictLabel(DICT_TYPE.PMS_DEVICE_STATUS, device.deviceStatus)}</p>
+          <p><strong>是否在线:</strong> ${getDictLabel(DICT_TYPE.IOT_DEVICE_STATUS, device.ifInline)}</p>
+          <p><strong>最后在线时间:</strong> ${device.lastInlineTime}</p>
         </div>
         <div>
-            <button :@click="deviceDetail" style=" background-color: #2196f3;
-  color: white;
-  border: none;
-  padding: 8px 16px;
-  border-radius: 4px;
-  margin-top: 15px;
-  cursor: pointer;">查看</button>
+            <button id="device-detail-btn" style=" background-color: #2196f3;
+              color: white;
+              border: none;
+              padding: 8px 16px;
+              border-radius: 4px;
+              margin-top: 15px;
+              cursor: pointer;">查看</button>
         </div>
-      `;
-      const infoWindow = new (window as any).BMap.InfoWindow(content, {
-        width: 300,
-        height: 180
-      });
-      map.value.openInfoWindow(infoWindow, point);
-    };
-
-    const zoomIn = () => {
-      if (map.value) {
-        map.value.zoomIn();
-      }
-    };
+      `
+    const infoWindow = new (window as any).BMap.InfoWindow(content, {
+      width: 350,
+      height: 220
+    })
+    map.value.openInfoWindow(infoWindow, point)
+
+    // 事件绑定(需延迟确保DOM加载)
+    setTimeout(function () {
+      document.getElementById('device-detail-btn').addEventListener('click', function () {
+        drawerVisible.value = true
+        deviceId.value = device.id;
+        deviceCode.value = device.deviceCode
+        deviceName.value = device.deviceName
+        ifLine.value = device.ifInline
+        lastLineTime.value = device.lastInlineTime
+        showDrawer.value.openDrawer()
 
-    const zoomOut = () => {
-      if (map.value) {
-        map.value.zoomOut();
-      }
-    };
+      })
+    }, 200)
+  })
 
-    const toggleMapType = () => {
-      if (!map.value) return;
+}
 
-      mapType.value = mapType.value === 'BMAP_NORMAL_MAP'
-        ? 'BMAP_SATELLITE_MAP'
-        : 'BMAP_NORMAL_MAP';
-      map.value.setMapType((window as any)[mapType.value]);
-    };
+const zoomIn = () => {
+  if (map.value) {
+    map.value.zoomIn()
+  }
+}
 
-    const getData = async () =>{
-      await IotDeviceApi.getMapDevice().then(res=>{
-        devices.value = res
-        initMap()
-      })
-    }
-    onMounted(async () => {
-      await getData();
-    });
+const zoomOut = () => {
+  if (map.value) {
+    map.value.zoomOut()
+  }
+}
 
-    onBeforeUnmount(() => {
-      if (map.value) {
-        map.value.destroy();
-      }
-    });
-
-    return {
-      mapContainer,
-      selectedDevice,
-      hoverDevice,
-      // deviceStatusMap,
-      zoomIn,
-      zoomOut,
-      toggleMapType
-    };
+const toggleMapType = () => {
+  if (!map.value) return
+
+  mapType.value = mapType.value === 'BMAP_NORMAL_MAP' ? 'BMAP_SATELLITE_MAP' : 'BMAP_NORMAL_MAP'
+  map.value.setMapType((window as any)[mapType.value])
+}
+
+const getData = async () => {
+  await IotDeviceApi.getMapDevice().then((res) => {
+    devices.value = res
+    initMap()
+  })
+}
+onMounted(async () => {
+  await getData()
+})
+
+onBeforeUnmount(() => {
+  if (map.value) {
+    map.value.destroy()
   }
-});
+})
+
+//     return {
+//       mapContainer,
+//       selectedDevice,
+//       hoverDevice,
+//       // deviceStatusMap,
+//       zoomIn,
+//       zoomOut,
+//       toggleMapType
+//     }
+// })
 </script>
 
 <style scoped>