From c08d0ebace5e9f20eb442ad7cb1db05d61ecbd0d Mon Sep 17 00:00:00 2001
From: hejianhao <15708179461@qq.com>
Date: 星期一, 21 四月 2025 14:56:28 +0800
Subject: [PATCH] 修改bug
---
src/components/MapPanel.vue | 542 ++++++++++++++++++++++++++++++++++++++---------------
1 files changed, 386 insertions(+), 156 deletions(-)
diff --git a/src/components/MapPanel.vue b/src/components/MapPanel.vue
index ba1e8dd..9e147e9 100644
--- a/src/components/MapPanel.vue
+++ b/src/components/MapPanel.vue
@@ -1,11 +1,13 @@
<template>
<div class="center-panel">
- <div class="map-container" ref="mapContainer"></div>
+ <div id="map-container" class="map-container"></div>
</div>
</template>
<script>
import AMapLoader from '@amap/amap-jsapi-loader'
+import { mapState } from 'vuex'
+import { getHouseMapDistribution } from './service'
export default {
name: 'MapPanel',
@@ -13,183 +15,291 @@
return {
map: null,
markers: [],
- infoWindow: null
+ currentMakers: [],
+ infoWindow: null,
+ updateTimer: null,
+ markerObjects: {} // Store marker objects by ID
}
+ },
+ watch: {
+ mapMarkerStatus(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: {
+ ...mapState([
+ 'mapMarkerStatus'
+ ]),
},
mounted() {
- this.$nextTick(() => {
- this.initMap()
+ this.fetchMapData()
+ this.startUpdateTimer()
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', () => {
+ this.map && this.map.resize()
})
+ if (this.updateTimer) {
+ clearInterval(this.updateTimer)
+ }
},
methods: {
- async initMap() {
- try {
- const AMap = await AMapLoader.load({
- key: '526e04b30ceba8f217c5def5a92392f9',
- version: '2.0',
- plugins: ['AMap.ToolBar', 'AMap.Scale']
- })
-
- this.map = new AMap.Map(this.$refs.mapContainer, {
- zoom: 16,
- center: [91.1172, 29.6487],
- viewMode: '3D',
- pitch: 35,
- mapStyle: 'amap://styles/normal',
- features: ['bg', 'road', 'building', 'point'],
- buildingAnimation: true
- })
-
- this.map.addControl(new AMap.ToolBar({
- position: 'RB'
- }))
- this.map.addControl(new AMap.Scale({
- position: 'RB'
- }))
-
- this.infoWindow = new AMap.InfoWindow({
- offset: new AMap.Pixel(0, -30),
- closeWhenClickMap: true,
- autoMove: true,
- anchor: 'bottom-center'
- })
-
- this.map.on('click', () => {
- if (this.infoWindow) {
- this.infoWindow.close()
- }
- })
-
- this.addMarkers()
- } catch (error) {
- console.error('地图加载失败:', error)
- }
+ startUpdateTimer() {
+ this.updateTimer = setInterval(() => {
+ this.fetchMapData()
+ }, 3000)
},
- addMarkers() {
- const houses = [
- {
- position: [91.1172, 29.6487],
- title: '新城大道房源',
- status: 'waiting',
- content: `
- <div class="info-window" data-status="waiting">
- <div class="info-title">房屋状态:待租出</div>
- <div class="info-content">
- <p>房屋编号:新城大道</p>
- <p>房屋状态:待租出</p>
- <p>租赁面积:1000/2000㎡</p>
- <p>本季租金:500/2000元/月</p>
- </div>
- </div>
- `
- },
- {
- position: [91.1272, 29.6587],
- title: '拉萨路房源',
- status: 'rented',
- content: `
- <div class="info-window" data-status="rented">
- <div class="info-title">房屋状态:已租出</div>
- <div class="info-content">
- <p>房屋编号:拉萨路</p>
- <p>房屋状态:已租出</p>
- <p>租赁面积:800/1500㎡</p>
- <p>本季租金:400/1800元/月</p>
- </div>
- </div>
- `
- }
- ]
-
- houses.forEach(house => {
- const circles = []
- const baseRadius = 15
- const numCircles = 3
-
- const centerCircle = new AMap.Circle({
- center: house.position,
- radius: baseRadius / 2,
- fillColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50',
- fillOpacity: 0.8,
- strokeWeight: 0,
- zIndex: numCircles + 2,
- bubble: true,
- cursor: 'pointer'
- })
-
- for (let i = 0; i < numCircles; i++) {
- circles.push(new AMap.Circle({
- center: house.position,
- radius: baseRadius * (i + 1),
- fillColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50',
- fillOpacity: 0.3 - (i * 0.05),
- strokeColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50',
- strokeWeight: 2,
- strokeOpacity: 0.5 - (i * 0.08),
- zIndex: numCircles - i,
- bubble: true,
- cursor: 'pointer'
- }))
- }
-
- const clickHandler = () => {
- if (this.infoWindow) {
- this.infoWindow.setContent(house.content)
- this.infoWindow.open(this.map, house.position)
- }
- }
-
- centerCircle.on('click', clickHandler)
- circles.forEach(circle => circle.on('click', clickHandler))
-
- this.map.add([centerCircle, ...circles])
- this.markers.push({ center: centerCircle, rings: circles })
-
- let phase = 0
- const animate = () => {
- phase += 0.15
- const scale = 1 + 0.6 * Math.sin(phase)
-
- circles.forEach((circle, index) => {
- const currentRadius = baseRadius * (index + 1)
- circle.setRadius(currentRadius * scale)
-
- const baseOpacity = 0.3 - (index * 0.05)
- const opacityScale = 1 + 0.5 * Math.sin(phase)
- circle.setOptions({
- fillOpacity: baseOpacity * opacityScale,
- strokeOpacity: (0.5 - (index * 0.08)) * opacityScale
- })
+ 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
})
- requestAnimationFrame(animate)
+ 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)
+ }
}
- animate()
})
- }
+ },
+ 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: '526e04b30ceba8f217c5def5a92392f9',
+ version: '2.0',
+ plugins: ['AMap.MarkerClusterer']
+ })
+
+ this.map = new map.Map('map-container', {
+ 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() {
+ this.markers.forEach(marker => {
+ const content = `
+ <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>
+ `
+
+ const markerObj = new AMap.Marker({
+ position: marker.position,
+ content: content,
+ anchor: 'center',
+ offset: new AMap.Pixel(0, 0),
+ zIndex: 100
+ })
+
+ markerObj.on('click', () => {
+ this.showInfoWindow(markerObj, marker.info)
+ })
+
+ markerObj.setMap(this.map)
+ })
+ },
+ showInfoWindow(marker, info) {
+ const content = `
+ <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.rentStatus}
+ </div>
+ <div class="info-item">
+ 本季租金:${info.rent}
+ </div>
+ </div>
+ </div>
+ `
+
+ const infoWindow = new AMap.InfoWindow({
+ content: content,
+ offset: new AMap.Pixel(0, -20),
+ closeWhenClickMap: true,
+ anchor: 'bottom-center',
+ isCustom: true
+ })
+
+ infoWindow.open(this.map, marker.getPosition())
+ },
}
}
</script>
-<style scoped>
+<style scoped lang="scss">
.center-panel {
flex: 1;
display: flex;
flex-direction: column;
- gap: 10px;
+ position: relative;
+ height: 100%;
+ min-height: 0;
}
.map-container {
- border-radius: 8px;
- overflow: hidden;
- background: rgba(255, 255, 255, 0.05);
width: 100%;
- height: 100vh;
+ height: 100%;
+ flex: 1;
+ position: relative;
}
.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;
@@ -198,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;
@@ -215,11 +325,19 @@
}
.info-window[data-status="waiting"] .info-title {
- color: #fff;
+ color: #2196F3;
}
.info-window[data-status="rented"] .info-title {
- color: #fff;
+ color: #FF9800;
+}
+
+.info-window[data-status="repairing"] .info-title {
+ color: #F44336;
+}
+
+.info-window[data-status="overdue"] .info-title {
+ color: #39C5BB;
}
.amap-info {
@@ -228,11 +346,123 @@
}
.amap-info-content {
- background: rgba(146, 146, 146, 0.5);
+ background: rgba(146, 146, 146, 0.7);
padding: 0 !important;
}
.amap-info-sharp {
display: none !important;
}
-</style>
\ No newline at end of file
+
+.marker-container {
+ width: 120px;
+ height: 120px;
+ transform: translate(-50%, -50%);
+ pointer-events: all;
+ filter: drop-shadow(0 4px 12px rgba(79, 217, 255, 0.4));
+
+}
+
+svg {
+ width: 100%;
+ height: 100%;
+}
+
+.ripple {
+ transform-origin: center;
+ stroke-width: 3;
+}
+
+.marker {
+ filter: drop-shadow(0 0 8px #4fd9ff);
+}
+
+.map-info-card {
+ border: 1px solid rgba(79, 217, 255, 0.2);
+ overflow: hidden;
+ width: 300px !important;
+ backdrop-filter: blur(12px);
+
+ .info-header {
+ padding: 12px 15px;
+ border-bottom: 1px solid rgba(79, 217, 255, 0.15);
+
+ .info-title {
+ color: #fff;
+ font-size: 15px;
+ font-weight: 500;
+ position: relative;
+ padding-left: 12px;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 3px;
+ height: 14px;
+ background: linear-gradient(to bottom, #4fd9ff, #568aea);
+ border-radius: 2px;
+ }
+ }
+ }
+
+ .info-body {
+ padding: 15px;
+ background: rgba(15, 19, 37, 0.95);
+
+ .info-item {
+ font-size: 14px;
+ color: rgba(255, 255, 255, 0.9);
+ margin-bottom: 12px;
+ line-height: 1.5;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ .status-tag {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 13px;
+ }
+
+ .rent-status {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 4px;
+ width: 100%;
+
+ .progress-bar {
+ flex: 1;
+ height: 4px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 2px;
+ overflow: hidden;
+ position: relative;
+
+ .progress {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 100%;
+ border-radius: 2px;
+ transition: width 0.3s ease;
+ }
+ }
+
+ .rent-text {
+ font-size: 13px;
+ color: rgba(255, 255, 255, 0.9);
+ white-space: nowrap;
+ min-width: 80px;
+ text-align: right;
+ }
+ }
+ }
+ }
+}
+</style>
\ No newline at end of file
--
Gitblit v1.7.1