bike-ali/pages_adminSet/components/EditMap.vue

394 lines
9.8 KiB
Vue
Raw Permalink 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="tipsList"></u-notice-bar>
<map
id="editMap"
class="map"
:latitude="latitude"
:longitude="longitude"
:polygon="polygons"
:markers="markers"
:show-location="true"
@tap="handleMapTap"
@markertap="handleMarkerTap"
@markerdragend="handleMarkerDrag"
scale="19"
></map>
<!-- 定位按钮 -->
<cover-image
class="location-btn"
src="https://lxnapi.ccttiot.com/bike/img/static/uoxanRjBrBrtcYwRGXKa"
@tap="moveToLocation"
></cover-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"
:class="{'control-btn-active': !editMode}"
@tap="toggleEditMode('draw')"
>
绘制锚点
</view>
<view
class="control-btn"
: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: 'EditMap',
props: {
boundaryStr: {
type: String,
default: ''
},
longitude: {
type: Number,
default: 0
},
latitude: {
type: Number,
default: 0
}
},
data() {
return {
// latitude: parseFloat(this.latitude),
// longitude: parseFloat(this.longitude),
points: [],
polygons: [],
markers: [],
isDrawing: false,
editMode: false,
mapContext: null,
tipsList: [
'提示:点击"开始绘制"按钮开始绘制区域',
'点击地图添加锚点',
'点击锚点可删除',
'至少需要3个锚点才能形成有效区域'
]
}
},
mounted() {
this.mapContext = uni.createMapContext('editMap', this)
this.initBoundary()
},
watch: {
boundaryStr: {
handler() {
this.initBoundary()
},
immediate: true
}
},
methods: {
// 初始化边界数据
initBoundary() {
if (!this.boundaryStr) return
try {
const points = JSON.parse(this.boundaryStr)
if (Array.isArray(points) && points.length > 0) {
this.points = points.map(point => ({
longitude: Number(point[0]),
latitude: Number(point[1])
})).filter(point =>
!isNaN(point.latitude) && !isNaN(point.longitude)
)
// 设置地图中心点
this.updateMarkers()
this.updatePolygonWithNearestNeighbor()
}
} catch (error) {
console.error('解析边界数据失败:', error)
}
},
// 更新标记点
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
})
} catch (error) {
uni.showToast({
title: '定位失败,请检查定位权限',
icon: 'none'
})
}
},
// 开始绘制
startDrawing() {
this.isDrawing = true
this.editMode = false
if (this.points.length === 0) {
uni.showToast({
title: '请点击地图添加锚点',
icon: 'none'
})
}
},
// 切换编辑模式
toggleEditMode(mode) {
this.editMode = mode === 'delete'
uni.showToast({
title: this.editMode ? '请点击锚点进行删除' : '请点击地图添加锚点',
icon: 'none'
})
},
// 处理地图点击
handleMapTap(e) {
if (!this.isDrawing || this.editMode) return
const { latitude, longitude } = e
this.points.push({ latitude, longitude })
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 < 3) {
this.polygons = []
return
}
// 计算中心点
const centerLat = this.points.reduce((sum, p) => sum + p.latitude, 0) / this.points.length
const 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
}
// 计算中心点
const centerLat = this.points.reduce((sum, p) => sum + p.latitude, 0) / this.points.length
const 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
})
// 返回排序后的坐标点数组
const coordinates = sortedPoints.map(point => [
point.longitude,
point.latitude
])
this.$emit('on-complete', JSON.stringify(coordinates))
// 清除绘制状态但保留图形
this.isDrawing = false
this.editMode = false
},
// 清除绘制
clearPolygon() {
this.points = []
this.markers = []
this.polygons = []
this.isDrawing = false
this.editMode = false
uni.showToast({
title: '已清除绘制',
icon: 'none'
})
}
}
}
</script>
<style lang="scss">
.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;
}
.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-group {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
}
.control-btn {
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;
}
.control-btn-active {
background-color: #007AFF;
color: #ffffff;
}
.control-btn-danger {
background-color: #ff4444;
color: #ffffff;
}
.btn-disabled {
opacity: 0.5;
background-color: #cccccc;
pointer-events: none;
}
</style>