|
@@ -1,27 +1,36 @@
|
|
<template>
|
|
<template>
|
|
<div class="search-layout">
|
|
<div class="search-layout">
|
|
- <el-input v-model="keyword" placeholder="输入名称搜索" style="width: 350px" clearable>
|
|
|
|
|
|
+ <el-input
|
|
|
|
+ v-model="keyword"
|
|
|
|
+ placeholder="输入名称或经纬度(如113.281272,23.117661)搜索"
|
|
|
|
+ style="width: 400px"
|
|
|
|
+ @focus="showSearch = true"
|
|
|
|
+ clearable
|
|
|
|
+ >
|
|
<template #append>
|
|
<template #append>
|
|
<el-button :icon="Search" />
|
|
<el-button :icon="Search" />
|
|
</template>
|
|
</template>
|
|
</el-input>
|
|
</el-input>
|
|
<div class="rrr">
|
|
<div class="rrr">
|
|
- <div class="search-result" v-show="keyword && showSearch" ref="resultEl"></div>
|
|
|
|
- <div class="search-sh" v-show="keyword">
|
|
|
|
- <el-button style="width: 100%" @click="showSearch = !showSearch">
|
|
|
|
- {{ showSearch ? "收起" : "展开" }}搜索结果
|
|
|
|
- </el-button>
|
|
|
|
- </div>
|
|
|
|
|
|
+ <div
|
|
|
|
+ class="search-result"
|
|
|
|
+ v-show="isKeySearch && keyword && showSearch"
|
|
|
|
+ ref="resultEl"
|
|
|
|
+ ></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="def-select-map-layout">
|
|
<div class="def-select-map-layout">
|
|
<div class="def-select-map" ref="mapEl"></div>
|
|
<div class="def-select-map" ref="mapEl"></div>
|
|
|
|
+ <div
|
|
|
|
+ class="split-map"
|
|
|
|
+ :style="{ width: size.width + 'px', height: size.height + 'px' }"
|
|
|
|
+ ></div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
- <div class="def-map-info" v-if="info">
|
|
|
|
- <p><span>纬度</span>{{ info.lat }}</p>
|
|
|
|
- <p><span>经度</span>{{ info.lng }}</p>
|
|
|
|
- <p><span>缩放级别</span>{{ info.zoom }}</p>
|
|
|
|
|
|
+ <div class="def-map-info" v-if="mapInfo">
|
|
|
|
+ <p><span>纬度</span>{{ mapInfo.lat }}</p>
|
|
|
|
+ <p><span>经度</span>{{ mapInfo.lng }}</p>
|
|
|
|
+ <p><span>缩放级别</span>{{ mapInfo.zoom }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
@@ -29,35 +38,41 @@
|
|
import AMapLoader from "@amap/amap-jsapi-loader";
|
|
import AMapLoader from "@amap/amap-jsapi-loader";
|
|
import { ElInput, ElButton } from "element-plus";
|
|
import { ElInput, ElButton } from "element-plus";
|
|
import { Search } from "@element-plus/icons-vue";
|
|
import { Search } from "@element-plus/icons-vue";
|
|
-import { ref, watchEffect } from "vue";
|
|
|
|
-import { debounce } from "@/utils/shared";
|
|
|
|
|
|
+import { ref, watch } from "vue";
|
|
|
|
+import { analysisGPS, debounce } from "@/utils/shared";
|
|
|
|
+import { Size } from "@/utils/math";
|
|
|
|
+import { BasemapInfo, MarkInfo } from "..";
|
|
|
|
|
|
-export type MapImage = { blob: Blob | null; search: MapInfo | null; ratio: number };
|
|
|
|
-type MapInfo = { lat: number; lng: number; zoom: number; text: string };
|
|
|
|
|
|
+const props = withDefaults(
|
|
|
|
+ defineProps<{
|
|
|
|
+ info?: MarkInfo;
|
|
|
|
+ size?: Size;
|
|
|
|
+ pixelRatio?: number;
|
|
|
|
+ }>(),
|
|
|
|
+ { size: () => ({ width: 800, height: 600 }), pixelRatio: 1 }
|
|
|
|
+);
|
|
|
|
|
|
-const keyword = ref("");
|
|
|
|
-const showSearch = ref(true);
|
|
|
|
-const info = ref<MapInfo>();
|
|
|
|
-const searchInfo = ref<MapInfo>();
|
|
|
|
|
|
+let AMap: any;
|
|
|
|
+let map: any;
|
|
|
|
+let placeSearch: any;
|
|
|
|
|
|
-watchEffect(() => {
|
|
|
|
- if (keyword.value) {
|
|
|
|
- showSearch.value = true;
|
|
|
|
- }
|
|
|
|
-});
|
|
|
|
|
|
+let __showMarker: any;
|
|
|
|
+const onlyMarker = (pos?: { lat: number; lng: number; text: string }) => {
|
|
|
|
+ __showMarker && map.remove(__showMarker);
|
|
|
|
+ __showMarker = null;
|
|
|
|
+ if (!pos) return;
|
|
|
|
|
|
-const mapEl = ref<HTMLDivElement>();
|
|
|
|
-const resultEl = ref<HTMLDivElement>();
|
|
|
|
-const searchAMap = ref<any>();
|
|
|
|
|
|
+ __showMarker = new AMap.Marker({
|
|
|
|
+ position: [pos.lng, pos.lat],
|
|
|
|
+ title: pos.text,
|
|
|
|
+ });
|
|
|
|
+ map.add(__showMarker);
|
|
|
|
+ map.panTo(__showMarker.getPosition());
|
|
|
|
+};
|
|
|
|
|
|
-let AMap: any;
|
|
|
|
-let map: any;
|
|
|
|
-watchEffect(async (onCleanup) => {
|
|
|
|
- if (!mapEl.value || !resultEl.value) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+const initMap = async () => {
|
|
AMap = await AMapLoader.load({
|
|
AMap = await AMapLoader.load({
|
|
- plugins: ["AMap.PlaceSearch", "AMap.Event"],
|
|
|
|
|
|
+ plugins: ["AMap.PlaceSearch", "AMap.Event", "AMap.Scale"],
|
|
key: "e661b00bdf2c44cccf71ef6070ef41b8",
|
|
key: "e661b00bdf2c44cccf71ef6070ef41b8",
|
|
version: "2.0",
|
|
version: "2.0",
|
|
});
|
|
});
|
|
@@ -67,127 +82,170 @@ watchEffect(async (onCleanup) => {
|
|
preserveDrawingBuffer: true,
|
|
preserveDrawingBuffer: true,
|
|
},
|
|
},
|
|
resizeEnable: true,
|
|
resizeEnable: true,
|
|
|
|
+ zoom: props.info?.zoom || 17.22,
|
|
|
|
+ });
|
|
|
|
+ const scale = new AMap.Scale();
|
|
|
|
+ map.addControl(scale);
|
|
|
|
+
|
|
|
|
+ map.on("click", function (e: any) {
|
|
|
|
+ onlyMarker({
|
|
|
|
+ lat: e.lnglat.lat,
|
|
|
|
+ lng: e.lnglat.lng,
|
|
|
|
+ text: "点击位置",
|
|
|
|
+ });
|
|
});
|
|
});
|
|
- const placeSearch = new AMap.PlaceSearch({
|
|
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const initSearch = () => {
|
|
|
|
+ placeSearch = new AMap.PlaceSearch({
|
|
pageSize: 5,
|
|
pageSize: 5,
|
|
showCover: false,
|
|
showCover: false,
|
|
pageIndex: 1,
|
|
pageIndex: 1,
|
|
map: map,
|
|
map: map,
|
|
panel: resultEl.value,
|
|
panel: resultEl.value,
|
|
- autoFitView: true,
|
|
|
|
});
|
|
});
|
|
- const setSearch = (data: any) => {
|
|
|
|
- searchInfo.value = {
|
|
|
|
|
|
+ placeSearch.on("listElementClick", (e: any) => {
|
|
|
|
+ showSearch.value = false;
|
|
|
|
+ const data = e.data;
|
|
|
|
+ onlyMarker({
|
|
text: data.pname + data.cityname + data.adname + data.address,
|
|
text: data.pname + data.cityname + data.adname + data.address,
|
|
lat: data.location.lat,
|
|
lat: data.location.lat,
|
|
lng: data.location.lng,
|
|
lng: data.location.lng,
|
|
- zoom: 0,
|
|
|
|
- };
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- placeSearch.on("listElementClick", (e: any) => {
|
|
|
|
- setSearch(e.data);
|
|
|
|
|
|
+ });
|
|
showSearch.value = false;
|
|
showSearch.value = false;
|
|
});
|
|
});
|
|
- let clickMarker: any;
|
|
|
|
-
|
|
|
|
- map.on("click", function (e: any) {
|
|
|
|
- // 获取点击位置的经纬度坐标
|
|
|
|
- var latitude = e.lnglat.lat;
|
|
|
|
- var longitude = e.lnglat.lng;
|
|
|
|
-
|
|
|
|
- searchInfo.value = {
|
|
|
|
- text: "",
|
|
|
|
- lat: latitude,
|
|
|
|
- lng: longitude,
|
|
|
|
- zoom: 0,
|
|
|
|
- };
|
|
|
|
- clickMarker && map.remove(clickMarker);
|
|
|
|
- clickMarker = null;
|
|
|
|
- // 在地图上添加标记
|
|
|
|
- clickMarker = new AMap.Marker({
|
|
|
|
- position: [longitude, latitude],
|
|
|
|
- title: "点击位置",
|
|
|
|
- });
|
|
|
|
|
|
|
|
- map.add(clickMarker);
|
|
|
|
- });
|
|
|
|
- placeSearch.on("complete", function (result: any) {
|
|
|
|
|
|
+ placeSearch.on("complete", function () {
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
const markers = map.getAllOverlays("marker");
|
|
const markers = map.getAllOverlays("marker");
|
|
for (const marker of markers) {
|
|
for (const marker of markers) {
|
|
- marker.on("click", () => {
|
|
|
|
- clickMarker && map.remove(clickMarker);
|
|
|
|
- clickMarker = null;
|
|
|
|
- setSearch(marker._data);
|
|
|
|
- });
|
|
|
|
|
|
+ if (marker !== __showMarker) {
|
|
|
|
+ map.remove(marker);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}, 500);
|
|
}, 500);
|
|
});
|
|
});
|
|
|
|
+};
|
|
|
|
|
|
- const getMapInfo = (): MapInfo => {
|
|
|
|
- var zoom = map.getZoom(); //获取当前地图级别
|
|
|
|
- var center = map.getCenter();
|
|
|
|
- return {
|
|
|
|
- text: "",
|
|
|
|
- zoom,
|
|
|
|
- lat: center.lat,
|
|
|
|
- lng: center.lng,
|
|
|
|
- };
|
|
|
|
|
|
+const mapInfo = ref<MarkInfo>();
|
|
|
|
+const updateMapInfo = () => {
|
|
|
|
+ var zoom = map.getZoom(); //获取当前地图级别
|
|
|
|
+ var center = map.getCenter();
|
|
|
|
+ mapInfo.value = {
|
|
|
|
+ text: "",
|
|
|
|
+ zoom,
|
|
|
|
+ lat: center.lat,
|
|
|
|
+ lng: center.lng,
|
|
};
|
|
};
|
|
- //绑定地图移动与缩放事件
|
|
|
|
- map.on("moveend", () => {
|
|
|
|
- info.value = getMapInfo();
|
|
|
|
- });
|
|
|
|
- map.on("zoomend", () => {
|
|
|
|
- info.value = getMapInfo();
|
|
|
|
- });
|
|
|
|
- searchAMap.value = placeSearch;
|
|
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const isKeySearch = ref(false);
|
|
|
|
+const keyword = ref("");
|
|
|
|
+const showSearch = ref(true);
|
|
|
|
+const init = async () => {
|
|
|
|
+ await initMap();
|
|
|
|
+ await initSearch();
|
|
|
|
+
|
|
|
|
+ map.on("moveend", updateMapInfo);
|
|
|
|
+ map.on("zoomend", updateMapInfo);
|
|
|
|
|
|
- onCleanup(() => {
|
|
|
|
- searchAMap.value = null;
|
|
|
|
|
|
+ const search = debounce(() => placeSearch.search(keyword.value), 100);
|
|
|
|
+ const stopWatch = watch(
|
|
|
|
+ keyword,
|
|
|
|
+ () => {
|
|
|
|
+ const gps = analysisGPS(keyword.value);
|
|
|
|
+ if (gps) {
|
|
|
|
+ isKeySearch.value = false;
|
|
|
|
+ onlyMarker({ lat: gps.y, lng: gps.x, text: "直接输入经纬度" });
|
|
|
|
+ console.log(gps);
|
|
|
|
+ } else {
|
|
|
|
+ isKeySearch.value = true;
|
|
|
|
+ search();
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ { immediate: true }
|
|
|
|
+ );
|
|
|
|
+ if (props.info) {
|
|
|
|
+ onlyMarker(props.info);
|
|
|
|
+ }
|
|
|
|
+ return () => {
|
|
map.destroy();
|
|
map.destroy();
|
|
- });
|
|
|
|
-});
|
|
|
|
|
|
+ stopWatch();
|
|
|
|
+ map = null;
|
|
|
|
+ placeSearch = null;
|
|
|
|
+ };
|
|
|
|
+};
|
|
|
|
|
|
-const search = debounce((keyword: string) => {
|
|
|
|
- searchAMap.value.search(keyword);
|
|
|
|
-}, 1000);
|
|
|
|
-watchEffect(() => {
|
|
|
|
- searchAMap.value && search(keyword.value);
|
|
|
|
|
|
+const mapEl = ref<HTMLDivElement>();
|
|
|
|
+const resultEl = ref<HTMLDivElement>();
|
|
|
|
+watch([mapEl, resultEl], async (_a, _b, onCleanup) => {
|
|
|
|
+ if (!mapEl.value || !resultEl.value) return;
|
|
|
|
+ onCleanup(await init());
|
|
});
|
|
});
|
|
|
|
|
|
const getPixelAspectRatio = () => {
|
|
const getPixelAspectRatio = () => {
|
|
// 获取地图视口的经纬度范围
|
|
// 获取地图视口的经纬度范围
|
|
const bounds = map.getBounds();
|
|
const bounds = map.getBounds();
|
|
-
|
|
|
|
// 计算视口的宽度和高度(单位为米)
|
|
// 计算视口的宽度和高度(单位为米)
|
|
const southWest = bounds.getSouthWest(); // 西南角
|
|
const southWest = bounds.getSouthWest(); // 西南角
|
|
const northEast = bounds.getNorthEast(); // 东北角
|
|
const northEast = bounds.getNorthEast(); // 东北角
|
|
const width = AMap.GeometryUtil.distance(
|
|
const width = AMap.GeometryUtil.distance(
|
|
[southWest.lng, northEast.lat],
|
|
[southWest.lng, northEast.lat],
|
|
[northEast.lng, northEast.lat]
|
|
[northEast.lng, northEast.lat]
|
|
- ); // 经度变化
|
|
|
|
- // const height = AMap.GeometryUtil.distance(
|
|
|
|
- // [southWest.lng, southWest.lat],
|
|
|
|
- // [southWest.lng, northEast.lat]
|
|
|
|
- // ); // 纬度变化
|
|
|
|
-
|
|
|
|
- return width / mapEl.value!.offsetWidth;
|
|
|
|
- // console.log(width / height);
|
|
|
|
- // console.log(mapEl.value?.offsetWidth / mapEl.value?.offsetHeight);
|
|
|
|
|
|
+ );
|
|
|
|
+ console.log(width / (mapEl.value!.offsetWidth * props.pixelRatio));
|
|
|
|
+ return width / (mapEl.value!.offsetWidth * props.pixelRatio);
|
|
};
|
|
};
|
|
|
|
|
|
const submit = () => {
|
|
const submit = () => {
|
|
- return new Promise<MapImage>((resolve) => {
|
|
|
|
|
|
+ return new Promise<BasemapInfo>((resolve, reject) => {
|
|
if (mapEl.value) {
|
|
if (mapEl.value) {
|
|
- getPixelAspectRatio();
|
|
|
|
|
|
+ console.log("ha?");
|
|
|
|
+ let info = undefined;
|
|
|
|
+ if (__showMarker) {
|
|
|
|
+ const pos = __showMarker.getPosition();
|
|
|
|
+ info = {
|
|
|
|
+ lat: pos.lat,
|
|
|
|
+ lng: pos.lng,
|
|
|
|
+ zoom: map.zoom,
|
|
|
|
+ text: __showMarker._opts.title,
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
const canvas = mapEl.value.querySelector("canvas") as HTMLCanvasElement;
|
|
const canvas = mapEl.value.querySelector("canvas") as HTMLCanvasElement;
|
|
- canvas.toBlob((blob) =>
|
|
|
|
- resolve({ blob, search: searchInfo.value!, ratio: getPixelAspectRatio() })
|
|
|
|
|
|
+ const mapPixelRatio = canvas.width / canvas.offsetWidth;
|
|
|
|
+ const size = {
|
|
|
|
+ width: canvas.width / mapPixelRatio,
|
|
|
|
+ height: canvas.height / mapPixelRatio,
|
|
|
|
+ };
|
|
|
|
+ const box = {
|
|
|
|
+ x: (size.width - props.size.width) / 2,
|
|
|
|
+ y: (size.height - props.size.height) / 2,
|
|
|
|
+ width: props.size.width,
|
|
|
|
+ height: props.size.height,
|
|
|
|
+ };
|
|
|
|
+ const tempCanvas = document.createElement("canvas")!;
|
|
|
|
+ tempCanvas.width = box.width * props.pixelRatio;
|
|
|
|
+ tempCanvas.height = box.height * props.pixelRatio;
|
|
|
|
+ const tempCtx = tempCanvas.getContext("2d")!;
|
|
|
|
+ tempCtx.drawImage(
|
|
|
|
+ canvas,
|
|
|
|
+ box.x * mapPixelRatio,
|
|
|
|
+ box.y * mapPixelRatio,
|
|
|
|
+ box.width * mapPixelRatio,
|
|
|
|
+ box.height * mapPixelRatio,
|
|
|
|
+ 0,
|
|
|
|
+ 0,
|
|
|
|
+ tempCanvas.width,
|
|
|
|
+ tempCanvas.height
|
|
|
|
+ );
|
|
|
|
+ tempCanvas.toBlob(
|
|
|
|
+ (blob) => resolve({ blob: blob!, info, ratio: getPixelAspectRatio() }),
|
|
|
|
+ "jpeg",
|
|
|
|
+ 1
|
|
);
|
|
);
|
|
} else {
|
|
} else {
|
|
- resolve({ blob: null, search: null, ratio: 1 });
|
|
|
|
|
|
+ reject("地图未初始化");
|
|
}
|
|
}
|
|
});
|
|
});
|
|
};
|
|
};
|
|
@@ -222,10 +280,12 @@ defineExpose({ submit });
|
|
|
|
|
|
.def-map-info {
|
|
.def-map-info {
|
|
margin-top: 10px;
|
|
margin-top: 10px;
|
|
|
|
+
|
|
p {
|
|
p {
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
color: rgba(0, 0, 0, 0.85);
|
|
display: inline;
|
|
display: inline;
|
|
|
|
+
|
|
&:not(:last-child)::after {
|
|
&:not(:last-child)::after {
|
|
content: ",";
|
|
content: ",";
|
|
margin-right: 6px;
|
|
margin-right: 6px;
|
|
@@ -238,9 +298,9 @@ defineExpose({ submit });
|
|
}
|
|
}
|
|
|
|
|
|
.def-select-map-layout {
|
|
.def-select-map-layout {
|
|
- --scale: 1.5;
|
|
|
|
|
|
+ // --scale: 1.5;
|
|
width: 100%;
|
|
width: 100%;
|
|
- padding-top: calc((390 / 540) * 100%);
|
|
|
|
|
|
+ padding-top: calc((800 / 1326) * 100%);
|
|
position: relative;
|
|
position: relative;
|
|
z-index: 1;
|
|
z-index: 1;
|
|
}
|
|
}
|
|
@@ -252,4 +312,13 @@ defineExpose({ submit });
|
|
width: 100%;
|
|
width: 100%;
|
|
height: 100%;
|
|
height: 100%;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+.split-map {
|
|
|
|
+ position: absolute;
|
|
|
|
+ left: 50%;
|
|
|
|
+ top: 50%;
|
|
|
|
+ border: 1px dashed red;
|
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
|
+ pointer-events: none;
|
|
|
|
+}
|
|
</style>
|
|
</style>
|