6/24 统一地图组件入口,规范地图方法,属性。 完成天地图和高德切换

This commit is contained in:
Zy
2026-06-24 16:19:19 +08:00
parent 33d35c8d70
commit 4e2ae414c4
14 changed files with 515 additions and 188 deletions

View File

@@ -2,6 +2,7 @@
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;
@@ -12,7 +13,7 @@ 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 mapLoading = ref(false);
const markers = ref<any[]>([]);
const mouseTool = ref<any>(null);
const AddMarkered = new Map<string, any>();
@@ -26,7 +27,7 @@ const props = defineProps({
},
clickOrDraw: {
type: Boolean,
default: false
default: true
},
smallScroll: {
type: Boolean,
@@ -34,7 +35,7 @@ const props = defineProps({
}
});
const emits = defineEmits<{
clickPoint: [value: number[]];
clickPoint: [value: LngLat];
}>();
// 对外暴露方法
@@ -191,7 +192,7 @@ function initDrawTool() {
}
/** 获取路线坐标数组 */
function getAllDrawLines(): number[][] {
function getAllDrawLines(): LngLat[] | [] {
if (!isMapAvailable() || drawnPolylines.length === 0) return [];
const path = drawnPolylines[0].getPath();
return path.map((p: any) => [p.lng, p.lat]);
@@ -257,7 +258,7 @@ function handleMap() {
// ===================== 地图初始化 =====================
onMounted(async () => {
maploading.value = true;
mapLoading.value = true;
try {
const AMap = await AMapLoader.load({
key: autoloader,
@@ -266,10 +267,6 @@ onMounted(async () => {
});
AMAP.value = AMap;
// 获取村庄城市定位
const villageInfo = await getVillageByIdAPI(village);
const city = villageInfo.data.city.replaceAll('/', '');
// 创建地图,全局关闭动画,解决移动缓慢
map.value = new AMap.Map('GD-Map', {
viewMode: '3D',
@@ -277,24 +274,33 @@ onMounted(async () => {
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());
}
});
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) {
console.log(props.clickOrDraw);
props.clickOrDraw ? handleMap() : MapDrawLine();
}
} catch (err) {
console.error('高德地图加载失败', err);
} finally {
maploading.value = false;
mapLoading.value = false;
}
});
@@ -319,7 +325,7 @@ onUnmounted(() => {
</script>
<template>
<div v-loading="maploading" class="map" id="GD-Map"></div>
<div v-loading="mapLoading" class="map" id="GD-Map"></div>
</template>
<style scoped lang="scss">

View File

@@ -1,9 +1,298 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue';
import { LngLat } from '@/components/Map/type';
// ========== 配置项,替换成你自己的天地图密钥 ==========
const TDT_TOKEN = import.meta.env.VITE_TIANDITU_KEY;
const TDT_CDN_URL = 'https://api.tianditu.gov.cn/api?v=4.0';
const mapLoading = ref(false);
let T: any = null;
let map = null;
const mapReady = ref(false);
// Props
const props = defineProps({
disabled: {
type: Boolean,
default: false
},
clickOrDraw: {
type: Boolean,
default: true
}
});
const emits = defineEmits<{
clickPoint: [lnglat: LngLat];
}>();
defineExpose({
addMarker_only,
clearLocation,
getAllDrawLines,
clearAll,
renderSavedPath,
clearMapMarker,
addMarker_more
});
const PointMap = new Map();
let handler = null; // 绘制工具
let lines = []; // 存储线
type WaitMarkerItem = { angular: LngLat; pointName: string };
type WaitMoreMarkerItem = { angular: LngLat; 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.clearMap();
PointMap.clear();
markers = [];
lines = [];
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 = [];
});
let markers = [];
// ==================== 动态加载天地图JS + 初始化地图 ====================
function loadTDT(): Promise<any> {
return new Promise((resolve) => {
if ((window as any).T) return resolve((window as any).T);
const script = document.createElement('script');
script.src = `${TDT_CDN_URL}&tk=${TDT_TOKEN}`;
script.onload = () => resolve((window as any).T);
document.body.appendChild(script);
});
}
/** 根据提供的 坐标点数组 设置地图视野,调整后的视野会保证包含提供的坐标点。 */
function fitMapView(overlays: any[] = []) {
if (!isMapAvailable()) return;
map.setViewport(overlays);
}
// 判断 地图是否加载完成
function isMapAvailable() {
return mapReady.value && map && T;
}
/** 点击事件 */
function bindMapClick() {
if (!isMapAvailable()) return;
map.addEventListener('click', (e) => {
const { lng, lat } = e.lnglat;
emits('clickPoint', [lng, lat]);
});
}
/** 添加标注点 */
function addMarker_only(LngLat: LngLat, pointName: string = '巡检点') {
if (!isMapAvailable()) {
waitQueue.value.onlyPoints.push({ angular: LngLat, pointName });
return;
}
// 清空地图覆盖物
clearLocation();
const marker = new T.Marker(new T.LngLat(LngLat[0], LngLat[1]), {
title: pointName
});
//向地图上添加标注
markers = [marker];
map.addOverLay(marker);
fitMapView(markers.map((item) => item.getLngLat()));
return marker;
}
/** 清空单点标记 */
function clearLocation() {
if (!isMapAvailable() || markers.length === 0) return;
map.clearOverLays();
markers = [];
}
/** 添加 路线中的巡检点 */
function addMarker_more(LngLat: LngLat, pointName: string, pointId: string) {
if (!isMapAvailable()) {
waitQueue.value.point.push({ angular: LngLat, pointName, pointId });
return;
}
if (PointMap.has(pointId)) {
map.removeOverLay(PointMap.get(pointId));
PointMap.delete(pointId);
return;
}
const marker = new T.Marker(new T.LngLat(LngLat[0], LngLat[1]), {
title: pointName
});
PointMap.set(pointId, marker);
map.addOverLay(marker);
}
// !===================================================================
/** 初始化 划线 */
function initDrawTool() {
if (handler) handler.close();
handler = new T.PolylineTool(map);
handler.open();
handler.off('draw');
handler.on('draw', lineDraw);
}
// 监听 绘画完成事件
function lineDraw(data) {
if (lines.length > 0) {
lines.forEach((item) => {
map.removeOverLay(item);
});
}
lines = [data.currentPolyline];
initDrawTool();
}
/** 清空绘制工具 + 所有路线 */
function clearAll() {
if (!isMapAvailable()) return;
// 关闭绘图工具
handler.close();
// 删除所有折线
lines.forEach((line) => map.removeOverLay(line));
lines = [];
// 重新初始化绘图,可再次绘制
initDrawTool();
}
/** 获取当前路线坐标数组,对外统一格式 [[lng,lat],...] */
function getAllDrawLines(): LngLat[] | [] {
if (lines.length === 0) return [];
const path = lines.at(0).getLngLats();
return path.map((p: { lng: number; lat: number }) => [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;
// 清空旧线
lines.forEach((line) => map.removeOverLay(line));
lines = [];
const paths = path.map((item) => {
return new T.LngLat(item[0], item[1]);
});
const polyline = new T.Polyline(paths);
lines.push(polyline);
map.addOverLay(polyline);
fitMapView(paths);
} catch (err) {
console.error('路线解析失败', err);
}
}
/** 清空地图所有覆盖物(标记+路线) */
function clearMapMarker() {
if (isMapAvailable()) {
map.clearOverLays();
markers = [];
lines = [];
} else {
waitQueue.value.clear = true;
}
}
onMounted(async () => {
mapLoading.value = true;
try {
T = await loadTDT();
map = new T.Map('TDT-Map', {
zoom: 11,
center: new T.LngLat(119.54, 35.42),
animateEnable: true
});
mapReady.value = true;
if (!props.disabled) {
props.clickOrDraw ? bindMapClick() : initDrawTool();
}
} finally {
mapLoading.value = false;
}
});
// ===================== 销毁清理 =====================
onUnmounted(() => {
map.clearOverLays();
map.clearLayers();
// 清空所有缓存队列
waitQueue.value.onlyPoints = [];
waitQueue.value.point = [];
waitQueue.value.routes = [];
waitQueue.value.clear = false;
// 清空覆盖物缓存
markers = [];
lines = [];
PointMap.clear();
if (handler) {
handler.close();
handler.off('draw');
handler = null;
}
mapReady.value = false;
// 销毁地图实例
map = null;
});
</script>
<template>
<div>
<!-- 天地图-->
</div>
<div v-loading="mapLoading" class="map" id="TDT-Map"></div>
</template>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.map {
width: 450px;
height: 450px;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
margin-top: 10px;
overflow: hidden;
position: relative;
}
/* 穿透天地图内部图层,强制限制在容器内 */
:deep(.tdt-map-container) {
width: 100% !important;
height: 100% !important;
overflow: hidden !important;
}
</style>

View File

@@ -1,14 +1,36 @@
<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 setup lang="ts">
import { ref, computed } from 'vue';
import type { IMapInstance, MapBaseProps, MapEmits } from './type';
import GDMap from './GD.vue';
import TdtMap from './TDT.vue';
const emits = defineEmits<MapEmits>();
const props = defineProps<MapBaseProps>();
const GDorTDT = import.meta.env.VITE_APP_MAP_TYPE !== 'GCJ02';
// 实例绑定
const GDRef = ref<InstanceType<typeof GDMap>>();
const tdtRef = ref<InstanceType<typeof TdtMap>>();
// 获取当前激活地图实例
const currentMap = computed(() => {
return GDorTDT ? GDRef.value : tdtRef.value;
});
// 统一代理所有地图方法,对外抹平差异
defineExpose<IMapInstance>({
addMarker_only: (...args: Parameters<IMapInstance['addMarker_only']>) => currentMap.value?.addMarker_only(...args),
clearLocation: () => currentMap.value?.clearLocation(),
addMarker_more: (...args: Parameters<IMapInstance['addMarker_more']>) => currentMap.value?.addMarker_more(...args),
getAllDrawLines: () => currentMap.value?.getAllDrawLines() ?? [],
renderSavedPath: (str: string) => currentMap.value?.renderSavedPath(str),
clearAll: () => currentMap.value?.clearAll(),
clearMapMarker: () => currentMap.value?.clearMapMarker()
});
</script>
<template>
<div>
<!-- 天地图 -->
<TDT v-if="!isGD"></TDT>
<!-- 高德地图-->
<GD v-else></GD>
</div>
<GDMap v-if="GDorTDT" v-bind="props" ref="GDRef" @click-point="(...args) => emits('clickPoint', ...args)" />
<TdtMap v-else ref="tdtRef" v-bind="props" @click-point="(...args) => emits('clickPoint', ...args)" />
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,30 @@
// src/types/map.ts
export type LngLat = [number, number];
// 统一对外暴露的地图方法类型
export interface IMapInstance {
// 单点标记
addMarker_only(lnglat: LngLat, pointName: string): void;
clearLocation(): void;
// 多点可删除标记
addMarker_more(lnglat: LngLat, pointName: string, pointId: string): void;
// 绘制路线
getAllDrawLines(): LngLat[] | [];
renderSavedPath(pathStr: string): void;
clearAll(): void;
// 全局清空覆盖物
clearMapMarker(): void;
}
// 统一Props
export interface MapBaseProps {
/** 禁用功能,只做展示 */
disabled?: boolean;
/** true 标注点 false 路线*/
clickOrDraw?: boolean;
}
// 统一事件
export type MapEmits = {
clickPoint: [lnglat: LngLat];
};