6/24 统一地图组件入口,规范地图方法,属性。 完成天地图和高德切换
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
30
src/components/Map/type.ts
Normal file
30
src/components/Map/type.ts
Normal 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];
|
||||
};
|
||||
Reference in New Issue
Block a user