|
@@ -0,0 +1,255 @@
|
|
|
+<template>
|
|
|
+ <div class="search-layout">
|
|
|
+ <el-input v-model="keyword" placeholder="输入名称搜索" style="width: 350px" clearable>
|
|
|
+ <template #append>
|
|
|
+ <el-button :icon="Search" />
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ <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>
|
|
|
+ </div>
|
|
|
+ <div class="def-select-map-layout">
|
|
|
+ <div class="def-select-map" ref="mapEl"></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>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import AMapLoader from "@amap/amap-jsapi-loader";
|
|
|
+import { ElInput, ElButton } from "element-plus";
|
|
|
+import { Search } from "@element-plus/icons-vue";
|
|
|
+import { ref, watchEffect } from "vue";
|
|
|
+import { debounce } from "@/utils/shared";
|
|
|
+
|
|
|
+export type MapImage = { blob: Blob | null; search: MapInfo | null; ratio: number };
|
|
|
+type MapInfo = { lat: number; lng: number; zoom: number; text: string };
|
|
|
+
|
|
|
+const keyword = ref("");
|
|
|
+const showSearch = ref(true);
|
|
|
+const info = ref<MapInfo>();
|
|
|
+const searchInfo = ref<MapInfo>();
|
|
|
+
|
|
|
+watchEffect(() => {
|
|
|
+ if (keyword.value) {
|
|
|
+ showSearch.value = true;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const mapEl = ref<HTMLDivElement>();
|
|
|
+const resultEl = ref<HTMLDivElement>();
|
|
|
+const searchAMap = ref<any>();
|
|
|
+
|
|
|
+let AMap: any;
|
|
|
+let map: any;
|
|
|
+watchEffect(async (onCleanup) => {
|
|
|
+ if (!mapEl.value || !resultEl.value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ AMap = await AMapLoader.load({
|
|
|
+ plugins: ["AMap.PlaceSearch", "AMap.Event"],
|
|
|
+ key: "e661b00bdf2c44cccf71ef6070ef41b8",
|
|
|
+ version: "2.0",
|
|
|
+ });
|
|
|
+
|
|
|
+ map = new AMap.Map(mapEl.value, {
|
|
|
+ WebGLParams: {
|
|
|
+ preserveDrawingBuffer: true,
|
|
|
+ },
|
|
|
+ resizeEnable: true,
|
|
|
+ });
|
|
|
+ const placeSearch = new AMap.PlaceSearch({
|
|
|
+ pageSize: 5,
|
|
|
+ showCover: false,
|
|
|
+ pageIndex: 1,
|
|
|
+ map: map,
|
|
|
+ panel: resultEl.value,
|
|
|
+ autoFitView: true,
|
|
|
+ });
|
|
|
+ const setSearch = (data: any) => {
|
|
|
+ searchInfo.value = {
|
|
|
+ text: data.pname + data.cityname + data.adname + data.address,
|
|
|
+ lat: data.location.lat,
|
|
|
+ lng: data.location.lng,
|
|
|
+ zoom: 0,
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ placeSearch.on("listElementClick", (e: any) => {
|
|
|
+ setSearch(e.data);
|
|
|
+ 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) {
|
|
|
+ setTimeout(() => {
|
|
|
+ const markers = map.getAllOverlays("marker");
|
|
|
+ for (const marker of markers) {
|
|
|
+ marker.on("click", () => {
|
|
|
+ clickMarker && map.remove(clickMarker);
|
|
|
+ clickMarker = null;
|
|
|
+ setSearch(marker._data);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, 500);
|
|
|
+ });
|
|
|
+
|
|
|
+ const getMapInfo = (): MapInfo => {
|
|
|
+ var zoom = map.getZoom(); //获取当前地图级别
|
|
|
+ var center = map.getCenter();
|
|
|
+ return {
|
|
|
+ text: "",
|
|
|
+ zoom,
|
|
|
+ lat: center.lat,
|
|
|
+ lng: center.lng,
|
|
|
+ };
|
|
|
+ };
|
|
|
+ //绑定地图移动与缩放事件
|
|
|
+ map.on("moveend", () => {
|
|
|
+ info.value = getMapInfo();
|
|
|
+ });
|
|
|
+ map.on("zoomend", () => {
|
|
|
+ info.value = getMapInfo();
|
|
|
+ });
|
|
|
+ searchAMap.value = placeSearch;
|
|
|
+
|
|
|
+ onCleanup(() => {
|
|
|
+ searchAMap.value = null;
|
|
|
+ map.destroy();
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+const search = debounce((keyword: string) => {
|
|
|
+ searchAMap.value.search(keyword);
|
|
|
+}, 1000);
|
|
|
+watchEffect(() => {
|
|
|
+ searchAMap.value && search(keyword.value);
|
|
|
+});
|
|
|
+
|
|
|
+const getPixelAspectRatio = () => {
|
|
|
+ // 获取地图视口的经纬度范围
|
|
|
+ const bounds = map.getBounds();
|
|
|
+
|
|
|
+ // 计算视口的宽度和高度(单位为米)
|
|
|
+ const southWest = bounds.getSouthWest(); // 西南角
|
|
|
+ const northEast = bounds.getNorthEast(); // 东北角
|
|
|
+ const width = AMap.GeometryUtil.distance(
|
|
|
+ [southWest.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);
|
|
|
+};
|
|
|
+
|
|
|
+const submit = () => {
|
|
|
+ return new Promise<MapImage>((resolve) => {
|
|
|
+ if (mapEl.value) {
|
|
|
+ getPixelAspectRatio();
|
|
|
+ const canvas = mapEl.value.querySelector("canvas") as HTMLCanvasElement;
|
|
|
+ canvas.toBlob((blob) =>
|
|
|
+ resolve({ blob, search: searchInfo.value!, ratio: getPixelAspectRatio() })
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ resolve({ blob: null, search: null, ratio: 1 });
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+defineExpose({ submit });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.search-layout {
|
|
|
+ display: inline-block;
|
|
|
+ position: relative;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ z-index: 2;
|
|
|
+}
|
|
|
+
|
|
|
+.rrr {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.search-sh,
|
|
|
+.search-result {
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &.show {
|
|
|
+ max-height: 450px;
|
|
|
+ overflow-y: auto;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.def-map-info {
|
|
|
+ margin-top: 10px;
|
|
|
+ p {
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.85);
|
|
|
+ display: inline;
|
|
|
+ &:not(:last-child)::after {
|
|
|
+ content: ",";
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ span::after {
|
|
|
+ content: ":";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.def-select-map-layout {
|
|
|
+ --scale: 1.5;
|
|
|
+ width: 100%;
|
|
|
+ padding-top: calc((390 / 540) * 100%);
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.def-select-map {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+</style>
|