Files
estate/src/components/Map/GD.vue
2026-08-31 18:00:11 +08:00

370 lines
10 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.
<script lang="ts" setup>
import AMapLoader from '@amap/amap-jsapi-loader';
import { getVillageByIdAPI } from '@/api/system/community';
import { onUnmounted, ref, watch, onMounted, nextTick } from 'vue';
import { LngLat } from '@/components/Map/type';
// 密钥配置
const autoloader = import.meta.env.VITE_GCJ02_KEY_GD;
const VITE_GCJ02_JS_CODE_GD = import.meta.env.VITE_GCJ02_JS_CODE_GD;
window._AMapSecurityConfig = { securityJsCode: VITE_GCJ02_JS_CODE_GD };
// 全局实例
const AMAP = ref<any>(null);
const map = ref<any>(null);
const mapReady = ref(false);
const mapLoading = ref(false);
const markers = ref<any[]>([]);
const mouseTool = ref<any>(null);
const AddMarkered = new Map<string, any>();
let drawnPolylines: any[] = [];
// Props & Emits
const props = defineProps({
disabled: {
type: Boolean,
default: false
},
clickOrDraw: {
type: Boolean,
default: true
},
smallScroll: {
type: Boolean,
default: false
}
});
const emits = defineEmits<{
clickPoint: [value: LngLat];
}>();
// 对外暴露方法
defineExpose({
addMarker_only,
clearLocation,
addMarker_more,
clearAll,
getAllDrawLines,
renderSavedPath,
clearMapMarker
});
const village = localStorage.getItem('villageid');
// ===================== 工具函数 =====================
/** 地图完整可用校验 */
function isMapAvailable() {
return mapReady.value && map.value && AMAP.value;
}
/** 统一适配视野,无动画 + 留白 + 最大缩放限制 */
function fitMapView(overlays: any[]) {
if (!isMapAvailable() || overlays.length === 0) return;
// 第二个参数true关闭动画,留白80px,最大缩放18级
map.value.setFitView(overlays, props.smallScroll);
}
// ===================== 渲染等待队列(响应式) =====================
type WaitMarkerItem = { angular: number[]; pointName: string };
type WaitMoreMarkerItem = { angular: number[]; pointName: string; pointId: string };
const waitQueue = ref<{
onlyPoints: WaitMarkerItem[];
point: WaitMoreMarkerItem[];
routes: string[];
clear: boolean;
}>({
onlyPoints: [],
point: [],
routes: [],
clear: false
});
// 地图就绪后批量执行队列任务
watch(mapReady, async (ready) => {
if (!ready || !isMapAvailable()) return;
await nextTick();
// 先执行清空
if (waitQueue.value.clear) {
map.value.clearMap();
AddMarkered.clear();
markers.value = [];
drawnPolylines = [];
waitQueue.value.clear = false;
}
// 批量多点标记
waitQueue.value.point.forEach((item) => addMarker_more(item.angular, item.pointName, item.pointId));
waitQueue.value.point = [];
// 单点标记
waitQueue.value.onlyPoints.forEach((item) => addMarker_only(item.angular, item.pointName));
waitQueue.value.onlyPoints = [];
// 路线回显
waitQueue.value.routes.forEach((pathStr) => renderSavedPath(pathStr));
waitQueue.value.routes = [];
});
// ===================== 单点标记 =====================
function addMarker_only(angular: number[], pointName: string) {
if (!isMapAvailable()) {
waitQueue.value.onlyPoints.push({ angular, pointName });
return;
}
clearLocation();
const marker = new AMAP.value.Marker({
position: angular,
map: map.value,
title: pointName || '巡检点',
content: `
<div style="display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:50%;background:rgba(26,117,255,0.3);">
<div style="width:20px;height:20px;border-radius:50%;line-height:20px;background:#1a75ff;color:#fff;font-size:20px;text-align:center;">1</div>
</div>`
});
markers.value = [marker];
fitMapView(markers.value);
return marker;
}
/** 清空单点标记 */
function clearLocation() {
if (!isMapAvailable() || markers.value.length === 0) return;
markers.value.forEach((item) => map.value.remove(item));
markers.value = [];
}
let lastFitTime = 0;
let fitTimer: ReturnType<typeof setTimeout> | null = null;
const FIT_THROTTLE_MS = 100; // 节流间隔(ms)
// === 新增函数 ===
function throttledFitView() {
const now = Date.now();
if (now - lastFitTime >= FIT_THROTTLE_MS) {
// 距离上次调用已超过阈值,立即执行
if (fitTimer) {
clearTimeout(fitTimer);
fitTimer = null;
}
fitMapView(Array.from(AddMarkered.values()));
lastFitTime = now;
} else if (!fitTimer) {
// 未超过阈值,延迟到阈值时再执行
fitTimer = setTimeout(() => {
fitMapView(Array.from(AddMarkered.values()));
lastFitTime = Date.now();
fitTimer = null;
}, FIT_THROTTLE_MS);
}
// 如果已经有定时器在等,不重复创建
}
// ===================== 多点标记(可增删) =====================
function addMarker_more(angular: number[], pointName: string, pointId: string) {
if (!isMapAvailable()) {
waitQueue.value.point.push({ angular, pointName, pointId });
return;
}
// 存在则删除
if (AddMarkered.has(pointId)) {
map.value.remove(AddMarkered.get(pointId));
AddMarkered.delete(pointId);
// 删除后重新适配视野
// fitMapView(Array.from(AddMarkered.values()));
throttledFitView();
return;
}
const index = AddMarkered.size + 1;
const marker = new AMAP.value.Marker({
position: angular,
map: map.value,
title: pointName || '巡检点',
content: `
<div style="display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:50%;background:rgba(26,117,255,0.3);">
<div style="width:20px;height:20px;border-radius:50%;line-height:20px;background:#1a75ff;color:#fff;font-size:20px;text-align:center;">${index}</div>
</div>`
});
AddMarkered.set(pointId, marker);
markers.value.push(marker);
// fitMapView(Array.from(AddMarkered.values()));
throttledFitView();
return marker;
}
// ===================== 绘制路线 =====================
function MapDrawLine() {
if (!isMapAvailable()) return;
mouseTool.value = new AMAP.value.MouseTool(map.value);
mouseTool.value.on('draw', (e: any) => {
if (!e.obj) return;
// 只保留一条线
if (drawnPolylines.length > 0) map.value.remove(drawnPolylines[0]);
drawnPolylines = [e.obj];
initDrawTool();
});
initDrawTool();
}
function initDrawTool() {
if (!mouseTool.value || !isMapAvailable()) return;
mouseTool.value.close(false);
mouseTool.value.polyline({
strokeColor: '#3366FF',
strokeOpacity: 1,
strokeWeight: 3,
strokeStyle: 'solid'
});
}
/** 获取路线坐标数组 */
function getAllDrawLines(): LngLat[] | [] {
if (!isMapAvailable() || drawnPolylines.length === 0) return [];
const path = drawnPolylines[0].getPath();
return path.map((p: any) => [p.lng, p.lat]);
}
/** 回显历史路线 */
function renderSavedPath(pathStr: string) {
if (!isMapAvailable()) {
waitQueue.value.routes.push(pathStr);
return;
}
try {
const path = JSON.parse(pathStr);
if (!Array.isArray(path) || path.length === 0) return;
// 清空旧线
drawnPolylines.forEach((line) => map.value.remove(line));
drawnPolylines = [];
const polyline = new AMAP.value.Polyline({
path,
strokeColor: '#3366FF',
strokeOpacity: 1,
strokeWeight: 3,
strokeStyle: 'solid',
map: map.value
});
drawnPolylines.push(polyline);
fitMapView([polyline]);
} catch (err) {
console.error('路线解析失败', err);
}
}
/** 清空绘图工具+路线 */
function clearAll() {
if (!isMapAvailable() || !mouseTool.value) return;
mouseTool.value.close(true);
drawnPolylines.forEach((line) => map.value.removeOverlay(line));
drawnPolylines = [];
initDrawTool();
}
/** 清空地图所有覆盖物(标记+路线) */
function clearMapMarker() {
if (isMapAvailable()) {
map.value.clearMap();
AddMarkered.clear();
markers.value = [];
drawnPolylines = [];
} else {
waitQueue.value.clear = true;
}
}
/** 地图点击拾取坐标 */
function handleMap() {
if (!isMapAvailable()) return;
map.value.on('click', (e: any) => {
const { lng, lat } = e.lnglat;
emits('clickPoint', [lng, lat]);
});
}
// ===================== 地图初始化 =====================
onMounted(async () => {
mapLoading.value = true;
try {
const AMap = await AMapLoader.load({
key: autoloader,
version: '2.0',
plugins: ['AMap.Scale', 'AMap.Marker', 'AMap.Geocoder', 'AMap.MouseTool']
});
AMAP.value = AMap;
// 创建地图,全局关闭动画,解决移动缓慢
map.value = new AMap.Map('GD-Map', {
viewMode: '3D',
zoom: 11,
center: [116.397428, 39.90923]
});
try {
// 获取村庄城市定位
const villageInfo = await getVillageByIdAPI(village);
const city = villageInfo.data.city.replaceAll('/', '');
// 城市地理编码定位
const geocoder = new AMap.Geocoder({ city });
geocoder.getLocation(city, (status: string, result: any) => {
if (status === 'complete' && result.info === 'OK') {
map.value.setCenter(result.geocodes[0].location.toArray());
}
});
} catch (e) {
console.error(e);
map.value.setCenter([116.397428, 39.90923]);
}
mapReady.value = true;
// 禁用状态不开启绘制/点击
if (!props.disabled) {
props.clickOrDraw ? handleMap() : MapDrawLine();
}
} catch (err) {
console.error('高德地图加载失败', err);
} finally {
mapLoading.value = false;
}
});
// ===================== 销毁清理 =====================
onUnmounted(() => {
// 销毁地图实例
map.value?.destroy();
// 清空所有缓存队列
waitQueue.value.onlyPoints = [];
waitQueue.value.point = [];
waitQueue.value.routes = [];
waitQueue.value.clear = false;
// 清空覆盖物缓存
markers.value = [];
drawnPolylines = [];
AddMarkered.clear();
mouseTool.value = null;
AMAP.value = null;
map.value = null;
mapReady.value = false;
if (fitTimer) {
clearTimeout(fitTimer);
fitTimer = null;
}
});
</script>
<template>
<div id="GD-Map" v-loading="mapLoading" class="map"></div>
</template>
<style lang="scss" scoped>
.map {
width: 100%;
min-height: 450px;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
margin-top: 10px;
}
</style>