|
@@ -2,10 +2,10 @@
|
|
|
<div class="map-layout">
|
|
|
<div class="search">
|
|
|
<el-input
|
|
|
- :model-value="keyword[searchType]"
|
|
|
- @update:model-value="(val) => (keyword[searchType] = val)"
|
|
|
+ v-model="keyword"
|
|
|
:placeholder="`请输入${searchName}`"
|
|
|
class="input-with-select"
|
|
|
+ @keydown.enter="searchHandler"
|
|
|
>
|
|
|
<template #prepend>
|
|
|
<el-select v-model="searchType" style="width: 100px">
|
|
@@ -18,13 +18,37 @@
|
|
|
</template>
|
|
|
</el-input>
|
|
|
|
|
|
- <div class="result" v-if="mark || mark === null">
|
|
|
+ <div
|
|
|
+ class="latlng-result"
|
|
|
+ v-if="searchType === 'latlng' && (mark || mark === null)"
|
|
|
+ :class="{ success: mark }"
|
|
|
+ >
|
|
|
<template v-if="mark">
|
|
|
<h3>经纬度定位成功</h3>
|
|
|
- <p>精度</p>
|
|
|
+ <p>经度:{{ mark.lat }}</p>
|
|
|
+ <p>纬度:{{ mark.lng }}</p>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <h3>经纬度定位失败</h3>
|
|
|
+ <p>请输入正确的经纬度格式</p>
|
|
|
+ <p>纬度,经度 (例如23.11766,113.28122)</p>
|
|
|
+ <p>纬度范围:-90到90</p>
|
|
|
+ <p>经度范围:-180到180</p>
|
|
|
</template>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div class="name-result" v-if="searchType === 'name' && options.length">
|
|
|
+ <div
|
|
|
+ class="address-item operate"
|
|
|
+ v-for="option in options"
|
|
|
+ :class="{ active: activeOption === option }"
|
|
|
+ @click="clickOption(option)"
|
|
|
+ >
|
|
|
+ {{ option.address }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="map" ref="mapEle">
|
|
|
<div class="tiles-select">
|
|
|
<el-dropdown placement="bottom-end">
|
|
@@ -47,8 +71,15 @@
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
import { computed, ref, shallowRef, watch, watchEffect } from "vue";
|
|
|
-import { isValidLatLng, useLMap, useSetLTileLayers } from "./useLeaflet";
|
|
|
-import { BasemapInfo, SelectMapImageProps } from "../index";
|
|
|
+import {
|
|
|
+ getCurrentLatlng,
|
|
|
+ LatLng,
|
|
|
+ latlngStrTransform,
|
|
|
+ useLMap,
|
|
|
+ useOnlyMarker,
|
|
|
+ useSetLTileLayers,
|
|
|
+} from "./useLeaflet";
|
|
|
+import { BasemapInfo, SearchResultItem, SelectMapImageProps } from "../index";
|
|
|
import html2canvas from "html2canvas";
|
|
|
import {
|
|
|
ElInput,
|
|
@@ -64,18 +95,7 @@ const props = defineProps<SelectMapImageProps>();
|
|
|
const mapEle = shallowRef<HTMLDivElement>();
|
|
|
const lMap = useLMap(mapEle);
|
|
|
const setTileLayers = useSetLTileLayers(lMap);
|
|
|
-const groupIndex = ref(0);
|
|
|
-const tiles = computed(() => props.tileGroups[groupIndex.value].tiles);
|
|
|
-
|
|
|
-watchEffect(
|
|
|
- () => {
|
|
|
- if (props.activeGroupIndex) {
|
|
|
- groupIndex.value = props.activeGroupIndex;
|
|
|
- }
|
|
|
- },
|
|
|
- { flush: "sync" }
|
|
|
-);
|
|
|
-
|
|
|
+const groupIndex = ref(props.activeGroupIndex || 0);
|
|
|
watchEffect(
|
|
|
() => {
|
|
|
if (groupIndex.value > props.tileGroups.length - 1) {
|
|
@@ -84,10 +104,8 @@ watchEffect(
|
|
|
},
|
|
|
{ flush: "sync" }
|
|
|
);
|
|
|
-
|
|
|
-watchEffect(() => {
|
|
|
- setTileLayers(tiles.value);
|
|
|
-});
|
|
|
+const tiles = computed(() => props.tileGroups[groupIndex.value].tiles);
|
|
|
+watchEffect(() => setTileLayers(tiles.value));
|
|
|
|
|
|
const searchTypes = [
|
|
|
{ label: "经纬度", value: "latlng" },
|
|
@@ -97,24 +115,43 @@ const searchType = ref(searchTypes[0].value as "latlng" | "name");
|
|
|
const searchName = computed(
|
|
|
() => searchTypes.find((item) => item.value === searchType.value)!.label
|
|
|
);
|
|
|
-const keyword = ref({
|
|
|
- latlng: "",
|
|
|
- name: "",
|
|
|
+const keyword = ref("");
|
|
|
+const mark = useOnlyMarker(lMap, ref<LatLng | null>());
|
|
|
+watch(searchType, () => {
|
|
|
+ mark.value = undefined;
|
|
|
+ keyword.value = "";
|
|
|
});
|
|
|
-const mark = ref<{ lat: number; lng: number } | null>();
|
|
|
|
|
|
-const search = (type: "latlng" | "name", keyword: string) => {
|
|
|
- mark.value = undefined;
|
|
|
- if (type === "latlng") {
|
|
|
- const [lng, lat] = keyword.split(",").map((s) => Number(s.trim()));
|
|
|
- if (!isValidLatLng(lat, lng)) {
|
|
|
- mark.value = null;
|
|
|
- } else {
|
|
|
- mark.value = { lat, lng };
|
|
|
- }
|
|
|
+const options = ref<SearchResultItem[]>([]);
|
|
|
+const activeOption = ref<SearchResultItem>();
|
|
|
+const clickOption = (item: SearchResultItem) => {
|
|
|
+ activeOption.value = item;
|
|
|
+ mark.value = item.latlng;
|
|
|
+};
|
|
|
+let token = 0;
|
|
|
+const searchHandler = async () => {
|
|
|
+ const currentToken = ++token;
|
|
|
+ if (searchType.value === "latlng") {
|
|
|
+ mark.value = latlngStrTransform(keyword.value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ options.value = [];
|
|
|
+ activeOption.value = undefined;
|
|
|
+
|
|
|
+ if (!keyword.value) return;
|
|
|
+
|
|
|
+ const result = await props.search(keyword.value);
|
|
|
+ if (currentToken === token) {
|
|
|
+ options.value = result;
|
|
|
}
|
|
|
};
|
|
|
-watch([searchType, keyword], ([type, keyword]) => search(type, keyword[type]));
|
|
|
+
|
|
|
+getCurrentLatlng().then((latlng) => {
|
|
|
+ if (!keyword.value && mark.value === undefined && searchType.value === "latlng") {
|
|
|
+ keyword.value = `${latlng.lat},${latlng.lng}`;
|
|
|
+ searchHandler();
|
|
|
+ }
|
|
|
+});
|
|
|
|
|
|
const submit = async (): Promise<BasemapInfo> => {
|
|
|
if (!lMap.value) {
|
|
@@ -180,8 +217,59 @@ defineExpose({ submit });
|
|
|
.tiles-select {
|
|
|
position: absolute;
|
|
|
z-index: 9999;
|
|
|
- right: 20px;
|
|
|
- top: 20px;
|
|
|
+ right: 0px;
|
|
|
+ top: 0px;
|
|
|
+}
|
|
|
+
|
|
|
+.latlng-result {
|
|
|
+ margin-top: 16px;
|
|
|
+ border-radius: 2px;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 16px;
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #a7a7a7;
|
|
|
+ line-height: 22px;
|
|
|
+ min-width: 144px;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+
|
|
|
+ h3 {
|
|
|
+ color: #f56c6c;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 14px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.success h3 {
|
|
|
+ color: #67c23a;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.name-result {
|
|
|
+ margin-top: 10px;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+ border-radius: 2px;
|
|
|
+
|
|
|
+ .address-item {
|
|
|
+ padding: 10px 6px;
|
|
|
+ margin: 0 10px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background: var(--el-color-primary-light-9);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:not(:last-child) {
|
|
|
+ padding-bottom: 6px;
|
|
|
+ border-bottom: 1px solid #d9d9d9;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|
|
|
|