hejianhao
2025-04-21 c08d0ebace5e9f20eb442ad7cb1db05d61ecbd0d
src/components/MapPanel.vue
@@ -7,6 +7,7 @@
<script>
import AMapLoader from '@amap/amap-jsapi-loader'
import { mapState } from 'vuex'
import { getHouseMapDistribution } from './service'
export default {
  name: 'MapPanel',
@@ -14,12 +15,29 @@
    return {
      map: null,
      markers: [],
      infoWindow: null
      currentMakers: [],
      infoWindow: null,
      updateTimer: null,
      markerObjects: {} // Store marker objects by ID
    }
  },
  watch: {
    mapMarkerStatus(newVal) {
      console.log(newVal)
      if (newVal === 'all') {
        this.markers = this.currentMakers
        this.$nextTick(() => {
          this.map.clearMap()
          this.addMapMarkers()
        })
      } else {
        let arr = this.currentMakers.filter(item => item.info.statusText == newVal)
        this.markers = arr
        this.$nextTick(() => {
          this.map.clearMap()
          this.addMapMarkers()
        })
      }
    }
  },
  computed: {
@@ -28,99 +46,161 @@
    ]),
  },
  mounted() {
    this.$nextTick(() => {
      this.initMap()
      window.addEventListener('resize', () => {
        this.map && this.map.resize()
      })
    })
    this.fetchMapData()
    this.startUpdateTimer()
  },
  beforeDestroy() {
    window.removeEventListener('resize', () => {
      this.map && this.map.resize()
    })
    if (this.updateTimer) {
      clearInterval(this.updateTimer)
    }
  },
  methods: {
    startUpdateTimer() {
      this.updateTimer = setInterval(() => {
        this.fetchMapData()
      }, 3000)
    },
    fetchMapData() {
      getHouseMapDistribution().then(res => {
        if (res.code === 200) {
          const newData = res.data.map(item => {
            item.position = [item.longitude, item.latitude]
            item.info = {
              address: item.houseAddress,
              name: item.houseName,
              status: item.houseStatus,
              rentStatus: item.rentStatus,
              statusText: item.houseStatus == 1 ? '待出租' : item.houseStatus == 2 ? '已出租' : item.houseStatus == 3 ? '维修中' : '欠费',
              tenant: item.tenant,
              rent: item.rent,
              color: item.houseStatus == 1 ? '#618CE9' : item.houseStatus == 2 ? '#FDAE03' : item.houseStatus == 3 ? '#F64E4F' : '#0FBE6B'
            }
            return item
          })
          if (!this.map) {
            this.currentMakers = JSON.parse(JSON.stringify(newData))
            this.markers = newData
            this.$nextTick(() => {
              this.initMap()
              window.addEventListener('resize', () => {
                this.map && this.map.resize()
              })
            })
          } else {
            this.updateMarkers(newData)
          }
        }
      })
    },
    updateMarkers(newData) {
      // Update existing markers and add new ones
      newData.forEach(newMarker => {
        const markerId = `${newMarker.longitude}-${newMarker.latitude}`
        const existingMarker = this.markerObjects[markerId]
        if (existingMarker) {
          // Update existing marker content
          const content = this.generateMarkerContent(newMarker)
          existingMarker.setContent(content)
        } else {
          // Create new marker
          const content = this.generateMarkerContent(newMarker)
          const markerObj = new AMap.Marker({
            position: newMarker.position,
            content: content,
            anchor: 'center',
            offset: new AMap.Pixel(0, 0),
            zIndex: 100
          })
          markerObj.on('click', () => {
            this.showInfoWindow(markerObj, newMarker.info)
          })
          markerObj.setMap(this.map)
          this.markerObjects[markerId] = markerObj
        }
      })
      // Remove markers that no longer exist
      Object.keys(this.markerObjects).forEach(markerId => {
        const [longitude, latitude] = markerId.split('-')
        const markerExists = newData.some(marker =>
          marker.longitude === parseFloat(longitude) &&
          marker.latitude === parseFloat(latitude)
        )
        if (!markerExists) {
          this.markerObjects[markerId].setMap(null)
          delete this.markerObjects[markerId]
        }
      })
      this.currentMakers = JSON.parse(JSON.stringify(newData))
      this.markers = newData
    },
    generateMarkerContent(marker) {
      return `
        <div class="marker-container">
          <svg width="120" height="120" viewBox="0 0 120 120">
            <defs>
              <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
                <feMerge>
                  <feMergeNode in="coloredBlur"/>
                  <feMergeNode in="SourceGraphic"/>
                </feMerge>
              </filter>
            </defs>
            <circle class="ripple" cx="60" cy="60" r="12" fill="none" stroke="${marker.info.color}" stroke-width="3" style="opacity: 0.4">
              <animate attributeName="r" from="12" to="45" dur="3s" begin="0s" repeatCount="indefinite" />
              <animate attributeName="opacity" from="0.8" to="0" dur="3s" begin="0s" repeatCount="indefinite" />
            </circle>
            <circle class="ripple" cx="60" cy="60" r="12" fill="none" stroke="${marker.info.color}" stroke-width="3" style="opacity: 0.4">
              <animate attributeName="r" from="12" to="45" dur="3s" begin="1s" repeatCount="indefinite" />
              <animate attributeName="opacity" from="0.8" to="0" dur="3s" begin="1s" repeatCount="indefinite" />
            </circle>
            <circle class="ripple" cx="60" cy="60" r="12" fill="none" stroke="${marker.info.color}" stroke-width="3" style="opacity: 0.4">
              <animate attributeName="r" from="12" to="45" dur="3s" begin="2s" repeatCount="indefinite" />
              <animate attributeName="opacity" from="0.8" to="0" dur="3s" begin="2s" repeatCount="indefinite" />
            </circle>
            <circle class="marker" cx="60" cy="60" r="8" fill="${marker.info.color}" filter="url(#glow)">
              <animate attributeName="r" values="8;10;8" dur="2s" repeatCount="indefinite" />
              <animate attributeName="fill-opacity" values="1;0.8;1" dur="2s" repeatCount="indefinite" />
            </circle>
          </svg>
        </div>
      `
    },
    async initMap() {
      const map = await AMapLoader.load({
        key: '67968c82f27c7e2cb9f40c1a9aa3042b',
        key: '526e04b30ceba8f217c5def5a92392f9',
        version: '2.0',
        plugins: ['AMap.MarkerClusterer']
      })
      this.map = new map.Map('map-container', {
        zoom: 13,
        zoom: 14,
        center: [91.172119, 29.652941],
        mapStyle: 'amap://styles/normal'
      })
      this.map.on('click', () => {
        this.markers = this.currentMakers
        this.$nextTick(() => {
          this.map.clearMap()
          this.addMapMarkers()
        })
      })
      this.addMapMarkers()
    },
    addMapMarkers() {
      const markers = [
        {
          position: [91.172119, 29.652941],
          info: {
            address: '西花部汾罚藏医院',
            name: '名称名称名称',
            status: 'vacant',
            statusText: '待出租',
            tenant: '张三',
            currentRent: 1600,
            totalRent: 20000,
            quarterlyPaid: 500,
            quarterlyRent: 2000,
            color: '#faad14'
          }
        },
        {
          position: [91.182119, 29.662941],
          info: {
            address: '拉萨市城关区江苏路15号',
            name: '泊江大道B栋',
            status: 'rented',
            statusText: '已出租',
            tenant: '-',
            currentRent: 3000,
            totalRent: 15000,
            quarterlyPaid: 1000,
            quarterlyRent: 4500,
            color: '#4fd9ff'
          }
        },
        {
          position: [91.162119, 29.642941],
          info: {
            address: '拉萨市城关区北京中路8号',
            name: '新城大道C区',
            status: 'maintenance',
            statusText: '维修中',
            tenant: '-',
            currentRent: 0,
            totalRent: 16000,
            quarterlyPaid: 0,
            quarterlyRent: 5400,
            color: '#ff4d4f'
          }
        },
        {
          position: [91.192119, 29.672941],
          info: {
            address: '拉萨市城关区夺底路33号',
            name: '高江路D座',
            status: 'overdue',
            statusText: '欠费',
            tenant: '西藏联通',
            currentRent: 12000,
            totalRent: 20000,
            quarterlyPaid: 4000,
            quarterlyRent: 6600,
            color: '#39c5bb'
          }
        }
      ]
      markers.forEach(marker => {
      this.markers.forEach(marker => {
        const content = `
          <div class="marker-container">
            <svg width="120" height="120" viewBox="0 0 120 120">
@@ -170,20 +250,17 @@
    },
    showInfoWindow(marker, info) {
      const content = `
        <div class="map-info-card" style="color:#000;background-color: #fff !important;padding: 10px;border-radius: 12px;box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);">
          <div class="info-header">
            <div class="info-title">房屋详情</div>
          </div>
        <div class="map-info-card" style="min-width:300px !important;color:#fff;background-color: rgba(146, 146, 146, 0.7) !important;padding: 10px;border-radius: 12px;box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);border-radius:0px 35px 35px 0px;">
          <div class="info-body">
            <div class="info-item">房屋地址:${info.address}</div>
            <div class="info-item">房屋名称:${info.name}</div>
            <div class="info-item">房屋状态:<span class="status-tag" style="background: rgba(${info.color.replace(/[^\d,]/g, '')}, 0.1); color: ${info.color};">${info.statusText}</span></div>
            <div class="info-item">租户:${info.tenant}</div>
            <div class="info-item">
              租金状态:${info.currentRent}/${info.totalRent}
              租金状态:${info.rentStatus}
            </div>
            <div class="info-item">
              本季租金:${info.quarterlyPaid}/${info.quarterlyRent}
              本季租金:${info.rent}
            </div>
          </div>
        </div>
@@ -222,7 +299,7 @@
.info-window {
  padding: 10px;
  background: rgba(146, 146, 146, 0.5);
  background: rgba(146, 146, 146, 0.7);
  border-radius: 8px;
  z-index: 10003;
  min-width: 200px;
@@ -231,7 +308,7 @@
.info-title {
  font-size: 16px;
  font-weight: bold;
  color: #fff;
  color: #fff !important;
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid #eee;
@@ -269,7 +346,7 @@
}
.amap-info-content {
  background: rgba(146, 146, 146, 0.5);
  background: rgba(146, 146, 146, 0.7);
  padding: 0 !important;
}
@@ -303,7 +380,7 @@
.map-info-card {
  border: 1px solid rgba(79, 217, 255, 0.2);
  overflow: hidden;
  width: 300px;
  width: 300px !important;
  backdrop-filter: blur(12px);
  .info-header {