6/23 更改高德地图组件,统一入口
This commit is contained in:
334
src/components/Map/GD.vue
Normal file
334
src/components/Map/GD.vue
Normal file
@@ -0,0 +1,334 @@
|
||||
<script setup lang="ts">
|
||||
import AMapLoader from '@amap/amap-jsapi-loader';
|
||||
import { getVillageByIdAPI } from '@/api/system/community';
|
||||
import { onUnmounted, ref, watch, onMounted, nextTick } from 'vue';
|
||||
|
||||
// 密钥配置
|
||||
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: false
|
||||
},
|
||||
smallScroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
const emits = defineEmits<{
|
||||
clickPoint: [value: number[]];
|
||||
}>();
|
||||
|
||||
// 对外暴露方法
|
||||
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级
|
||||
console.log(props.smallScroll);
|
||||
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 = [];
|
||||
}
|
||||
|
||||
// ===================== 多点标记(可增删) =====================
|
||||
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()));
|
||||
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()));
|
||||
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(): number[][] {
|
||||
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;
|
||||
|
||||
// 获取村庄城市定位
|
||||
const villageInfo = await getVillageByIdAPI(village);
|
||||
const city = villageInfo.data.city.replaceAll('/', '');
|
||||
|
||||
// 创建地图,全局关闭动画,解决移动缓慢
|
||||
map.value = new AMap.Map('GD-Map', {
|
||||
viewMode: '3D',
|
||||
zoom: 11,
|
||||
center: [116.397428, 39.90923]
|
||||
});
|
||||
|
||||
// 城市地理编码定位
|
||||
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());
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="maploading" class="map" id="GD-Map"></div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.map {
|
||||
width: 100%;
|
||||
min-height: 450px;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
9
src/components/Map/TDT.vue
Normal file
9
src/components/Map/TDT.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 天地图-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
14
src/components/Map/index.vue
Normal file
14
src/components/Map/index.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup name="MapIndex" lang="ts">
|
||||
import TDT from './TDT.vue';
|
||||
import GD from './GD.vue';
|
||||
const isGD = import.meta.env.VITE_APP_MAP_TYPE === 'GCJ02';
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<!-- 天地图 -->
|
||||
<TDT v-if="!isGD"></TDT>
|
||||
<!-- 高德地图-->
|
||||
<GD v-else></GD>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss"></style>
|
||||
Reference in New Issue
Block a user