bike-ali/pages_adminSet/components/CustomMap.vue

473 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="map-container">
<u-notice-bar mode="horizontal" :list="list"></u-notice-bar>
<map id="map" class="map" :latitude="latitude" :longitude="longitude" :polygon="polygons" :markers="markers"
:show-location="true" @tap="handleMapTap" @markertap="handleMarkerTap" @markerdragend="handleMarkerDrag"
scale="14">
</map>
<!-- 定位按钮 -->
<image class="location-btn" src="https://lxnapi.ccttiot.com/bike/img/static/uoxanRjBrBrtcYwRGXKa"
@tap="moveToLocation"></image>
<!-- 提示文本 -->
<view class="tip-text" v-if="isDrawing">
{{ editMode ? '点击锚点进行删除' : '点击地图添加锚点拖动锚点可调整位置' }}
</view>
<!-- 控制按钮组 -->
<view class="controls">
<view class="control-btn" @tap="startDrawing" v-if="!isDrawing">开始绘制</view>
<block v-else>
<view class="control-btn-group">
<view class="control-btn control-btn-small"
:class="{ 'control-btn-active': !editMode }"
@tap="toggleEditMode('draw')">绘制锚点</view>
<view class="control-btn control-btn-small"
:class="{ 'control-btn-active': editMode }"
@tap="toggleEditMode('delete')">删除锚点</view>
</view>
<view class="control-btn-group">
<view class="control-btn"
@tap="finishDrawing"
:class="{ 'btn-disabled': points.length < 3 }">完成绘制</view>
<view class="control-btn control-btn-danger" @tap="clearPolygon">清除</view>
</view>
</block>
</view>
</view>
</template>
<script>
export default {
name: 'CustomMap',
props: {
areaInfo: {
type: Object,
default: () => ({})
}
},
data() {
return {
latitude: 39.909,
longitude: 116.397,
points: [],
polygons: [],
markers: [],
isDrawing: false,
editMode: false,
mapContext: null,
initialCenter: {
latitude: 39.909,
longitude: 116.397
},
list: [
'提示:点击"开始绘制"按钮开始绘制区域',
'点击地图添加锚点',
'点击锚点可删除',
'至少需要3个锚点才能形成有效区域'
],
}
},
mounted() {
this.mapContext = uni.createMapContext('map', this)
},
watch: {
areaInfo: {
handler(newVal) {
if (newVal.boundary && newVal.latitude && newVal.longitude) {
this.initBoundary(newVal)
}
},
immediate: true
}
},
methods: {
initBoundary(info) {
this.initialCenter = {
latitude: info.latitude,
longitude: info.longitude
}
this.latitude = info.latitude
this.longitude = info.longitude
const points = info.boundaryStr.split('],[').map(pointStr => {
const cleanStr = pointStr.replace(/^\[|\]$/g, '')
const [longitude, latitude] = cleanStr.split(',')
return {
longitude: Number(longitude),
latitude: Number(latitude)
}
})
console.log('解析后的点:', points)
this.points = points.filter(point =>
!isNaN(point.latitude) && !isNaN(point.longitude)
)
this.updateMarkers()
this.updatePolygonWithNearestNeighbor()
},
updateMarkers() {
this.markers = this.points.map((point, index) => ({
id: index,
latitude: point.latitude,
longitude: point.longitude,
width: 20,
height: 20,
draggable: true
}))
},
async moveToLocation() {
try {
const [err, res] = await uni.getLocation({
type: 'gcj02'
});
if (err) {
throw new Error('获取位置失败');
}
this.latitude = res.latitude;
this.longitude = res.longitude;
await new Promise(resolve => setTimeout(resolve, 100));
this.mapContext.moveToLocation({
latitude: res.latitude,
longitude: res.longitude,
success: () => {
setTimeout(() => {
this.mapContext.getScale({
success: () => { }
});
}, 500);
}
});
} catch (error) {
uni.showToast({
title: '定位失败,请检查定位权限',
icon: 'none'
});
}
},
toggleEditMode(mode) {
this.editMode = mode === 'delete'
uni.showToast({
title: this.editMode ? '请点击锚点进行删除' : '请点击地图添加锚点',
icon: 'none'
})
},
startDrawing() {
const currentCenter = {
latitude: this.latitude,
longitude: this.longitude
}
this.isDrawing = true
this.editMode = false
this.$nextTick(() => {
this.latitude = currentCenter.latitude
this.longitude = currentCenter.longitude
})
uni.showToast({
title: '开始绘制区域',
icon: 'none'
})
},
isPointTooClose(point1, point2, threshold = 0.0001) {
const distance = Math.sqrt(
Math.pow(point1.latitude - point2.latitude, 2) +
Math.pow(point1.longitude - point2.longitude, 2)
)
return distance < threshold
},
cleanNearbyPoints() {
if (this.points.length < 2) return
let cleanedPoints = []
let pointsToKeep = new Set()
for (let i = 0; i < this.points.length; i++) {
let shouldKeep = true
for (let j = 0; j < i; j++) {
if (pointsToKeep.has(j) && this.isPointTooClose(this.points[i], this.points[j])) {
shouldKeep = false
break
}
}
if (shouldKeep) {
pointsToKeep.add(i)
}
}
pointsToKeep.forEach(index => {
cleanedPoints.push(this.points[index])
})
this.points = cleanedPoints
this.updateMarkers()
},
handleMapTap(e) {
console.log('handleMapTap', e);
if (!this.isDrawing || this.editMode) return
const { latitude, longitude } = e
const newPoint = { latitude, longitude }
for (let point of this.points) {
if (this.isPointTooClose(newPoint, point)) {
uni.showToast({
title: '锚点位置过近',
icon: 'none'
})
return
}
}
this.points.push(newPoint)
this.updateMarkers()
this.updatePolygonWithNearestNeighbor()
},
handleMarkerTap(e) {
if (!this.editMode) return
console.log('handleMarkerTap', e);
const markerId = e.detail.markerId
if (markerId === undefined || markerId === null) {
uni.showToast({
title: '无法删除锚点',
icon: 'none'
})
return
}
this.points = this.points.filter((_, index) => index !== markerId)
this.updateMarkers()
this.updatePolygonWithNearestNeighbor()
},
handleMarkerDrag(e) {
const { markerId, latitude, longitude } = e.detail
this.points[markerId] = { latitude, longitude }
this.updateMarkers()
this.updatePolygonWithNearestNeighbor()
},
updatePolygonWithNearestNeighbor() {
if (this.points.length < 2) {
this.polygons = []
return
}
this.cleanNearbyPoints()
if (this.points.length < 3) {
this.polygons = [{
points: [...this.points],
strokeWidth: 2,
strokeColor: '#FF0000',
fillColor: '#FF000050',
zIndex: 1
}]
return
}
let centerLat = this.points.reduce((sum, p) => sum + p.latitude, 0) / this.points.length
let centerLng = this.points.reduce((sum, p) => sum + p.longitude, 0) / this.points.length
let sortedPoints = [...this.points].sort((a, b) => {
let angleA = Math.atan2(a.latitude - centerLat, a.longitude - centerLng)
let angleB = Math.atan2(b.latitude - centerLat, b.longitude - centerLng)
return angleA - angleB
})
sortedPoints.push(sortedPoints[0])
this.polygons = [{
points: sortedPoints,
strokeWidth: 2,
strokeColor: '#FF0000',
fillColor: '#FF000050',
zIndex: 1
}]
},
finishDrawing() {
if (this.points.length < 3) {
uni.showToast({
title: '至少需要3个锚点才能形成区域',
icon: 'none'
})
return
}
this.cleanNearbyPoints()
const centerLat = this.points.reduce((sum, p) => sum + Number(p.latitude), 0) / this.points.length
const centerLng = this.points.reduce((sum, p) => sum + Number(p.longitude), 0) / this.points.length
let sortedPoints = [...this.points].sort((a, b) => {
let angleA = Math.atan2(a.latitude - centerLat, a.longitude - centerLng)
let angleB = Math.atan2(b.latitude - centerLat, b.longitude - centerLng)
return angleA - angleB
})
const formattedPoints = sortedPoints.map(point => ({
latitude: Number(Number(point.latitude).toFixed(5)),
longitude: Number(Number(point.longitude).toFixed(5))
}))
this.isDrawing = false
this.editMode = false
this.polygons = [{
points: formattedPoints,
strokeWidth: 2,
strokeColor: '#FF0000',
fillColor: '#FF000050',
zIndex: 1
}]
this.$emit('on-polygon-complete', {
points: formattedPoints,
polygon: this.polygons[0]
})
},
clearPolygon() {
const currentCenter = {
latitude: this.latitude,
longitude: this.longitude
}
this.points = []
this.markers = []
this.polygons = []
this.isDrawing = false
this.editMode = false
this.$nextTick(() => {
this.latitude = currentCenter.latitude
this.longitude = currentCenter.longitude
})
uni.showToast({
title: '已清除绘制',
icon: 'none'
})
}
}
}
</script>
<style>
.map-container {
width: 100%;
height: 100vh;
position: relative;
}
.map {
width: 100%;
height: 100%;
}
.location-btn {
position: fixed;
right: 30rpx;
bottom: 300rpx;
width: 80rpx;
height: 80rpx;
z-index: 100;
}
.tip-text {
position: fixed;
top: 40rpx;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: #ffffff;
padding: 16rpx 30rpx;
border-radius: 30rpx;
font-size: 28rpx;
z-index: 100;
}
.control-btn-group {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
}
.control-btn-small {
min-width: 120rpx;
height: 60rpx;
line-height: 60rpx;
font-size: 24rpx;
padding: 0 20rpx;
}
.controls {
position: fixed;
bottom: 60rpx;
left: 0;
right: 0;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 30rpx;
z-index: 100;
}
.control-btn {
pointer-events: auto;
min-width: 200rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #ffffff;
border-radius: 10rpx;
font-size: 28rpx;
text-align: center;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
margin: 0 10rpx;
padding: 0 40rpx;
}
.control-btn-danger {
background-color: #ff4444;
color: #ffffff;
}
.control-btn-active {
background-color: #007AFF;
color: #ffffff;
}
.btn-disabled {
opacity: 0.5;
background-color: #cccccc;
pointer-events: none;
}
</style>