bike/pages_adminSet/components/CustomMap.vue

523 lines
13 KiB
Vue
Raw Normal View History

2024-12-21 15:41:58 +08:00
<template>
<view class="map-container">
<u-notice-bar mode="horizontal" :list="list"></u-notice-bar>
<map id="map" class="map" :latitude="latitude" :longitude="longitude" :polygons="polygons" :markers="markers"
:show-location="true" @tap="handleMapTap" @markertap="handleMarkerTap" @markerdragend="handleMarkerDrag"
scale="14">
</map>
<!-- 定位按钮 -->
<cover-image class="location-btn" src="https://lxnapi.ccttiot.com/bike/img/static/uoxanRjBrBrtcYwRGXKa"
@tap="moveToLocation"></cover-image>
<!-- 提示文本 -->
<cover-view class="tip-text" v-if="isDrawing">
{{ editMode ? '点击锚点进行删除' : '点击地图添加锚点,拖动锚点可调整位置' }}
</cover-view>
<!-- 控制按钮组 -->
<cover-view class="controls">
<!-- 未开始绘制时显示开始按钮 -->
<cover-view class="control-btn" @tap="startDrawing" v-if="!isDrawing">开始绘制</cover-view>
<!-- 绘制过程中显示的按钮组 -->
<block v-else>
<cover-view class="control-btn-group">
<!-- 绘制锚点按钮 -->
<cover-view class="control-btn control-btn-small" :class="{ 'control-btn-active': !editMode }"
@tap="toggleEditMode('draw')">绘制锚点</cover-view>
<!-- 删除锚点按钮 -->
<cover-view class="control-btn control-btn-small" :class="{ 'control-btn-active': editMode }"
@tap="toggleEditMode('delete')">删除锚点</cover-view>
</cover-view>
<cover-view class="control-btn-group">
<!-- 完成按钮 -->
<cover-view class="control-btn" @tap="finishDrawing" :class="{ 'btn-disabled': points.length < 3 }">完成绘制
</cover-view>
<!-- 清除按钮 -->
<cover-view class="control-btn control-btn-danger" @tap="clearPolygon">清除</cover-view>
</cover-view>
</block>
</cover-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个锚点才能形成有效区域'
],
// noticeText: '提示:点击"开始绘制"按钮开始绘制区域点击地图添加锚点拖动锚点可调整位置点击锚点可删除。至少需要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,
// callout: {
// // content: `${index + 1}`,
// color: '#ffffff',
// fontSize: 14,
// borderRadius: 4,
// bgColor: '#007AFF',
// padding: 4,
// display: 'ALWAYS'
// },
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.points = []
// this.markers = []
// this.polygons = []
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) {
if (!this.isDrawing || this.editMode) return
const { latitude, longitude } = e.detail
// 检查新点是否与现有点过近
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
const markerId = e.detail.markerId
// 删除对应的点
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
})
// 格式化坐标点保留5位小数
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>