123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- <template>
- <div class="map-layout">
- <div class="search">
- <el-input
- v-model="keyword"
- :placeholder="`请输入${searchName}`"
- class="input-with-select"
- @keydown.enter="searchHandler"
- >
- <template #prepend>
- <el-select v-model="searchType" style="width: 100px">
- <el-option
- :label="type.label"
- :value="type.value"
- v-for="type in searchTypes"
- />
- </el-select>
- </template>
- </el-input>
- <div
- class="latlng-result"
- v-if="searchType === 'latlng' && (mark || mark === null)"
- :class="{ success: mark }"
- >
- <template v-if="mark">
- <h3>经纬度定位成功</h3>
- <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">
- <icon name="close" size="30px" color="#000" />
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item
- v-for="(group, ndx) in tileGroups"
- @click="groupIndex = ndx"
- >
- {{ group.name }}
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts" setup>
- import { computed, ref, shallowRef, watch, watchEffect } from "vue";
- import {
- getCurrentLatlng,
- LatLng,
- latlngStrTransform,
- useLMap,
- useOnlyMarker,
- useSetLTileLayers,
- } from "./useLeaflet";
- import { BasemapInfo, SearchResultItem, SelectMapImageProps } from "../index";
- import html2canvas from "html2canvas";
- import {
- ElInput,
- ElSelect,
- ElOption,
- ElDropdown,
- ElDropdownMenu,
- ElDropdownItem,
- } from "element-plus";
- const props = defineProps<SelectMapImageProps>();
- const mapEle = shallowRef<HTMLDivElement>();
- const lMap = useLMap(mapEle);
- const setTileLayers = useSetLTileLayers(lMap);
- const groupIndex = ref(props.activeGroupIndex || 0);
- watchEffect(
- () => {
- if (groupIndex.value > props.tileGroups.length - 1) {
- groupIndex.value = 0;
- }
- },
- { flush: "sync" }
- );
- const tiles = computed(() => props.tileGroups[groupIndex.value].tiles);
- watchEffect(() => setTileLayers(tiles.value));
- const searchTypes = [
- { label: "经纬度", value: "latlng" },
- { label: "名称", value: "name" },
- ];
- const searchType = ref(searchTypes[0].value as "latlng" | "name");
- const searchName = computed(
- () => searchTypes.find((item) => item.value === searchType.value)!.label
- );
- const keyword = ref("");
- const mark = useOnlyMarker(lMap, ref<LatLng | null>());
- watch(searchType, () => {
- mark.value = undefined;
- keyword.value = "";
- });
- 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;
- }
- };
- 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) {
- throw "地图未初始化";
- }
- const bound = lMap.value.getBounds();
- const southWest = bound.getSouthWest(); // 西南角
- const northEast = bound.getNorthEast(); // 东北角
- const width = lMap.value.distance(
- [southWest.lng, northEast.lat],
- [northEast.lng, northEast.lat]
- );
- const height = lMap.value.distance(
- [northEast.lng, southWest.lat],
- [northEast.lng, northEast.lat]
- );
- let blob: Blob;
- try {
- const canvas = await html2canvas(mapEle.value!, {
- useCORS: true, // 允许跨域图片(瓦片需支持 CORS)
- allowTaint: true, // 允许污染 Canvas(慎用)
- scale: 2, // 提高分辨率
- logging: false, // 关闭日志
- });
- blob = await new Promise<Blob>((resolve, reject) => {
- canvas.toBlob((blob) => {
- blob ? resolve(blob) : reject("截图失败");
- });
- });
- } catch (e) {
- throw "截图失败";
- }
- return {
- blob,
- size: { width, height },
- };
- };
- defineExpose({ submit });
- </script>
- <style lang="scss" scoped>
- .map-layout {
- display: flex;
- }
- .search {
- margin-right: 24px;
- flex: 1;
- }
- .map {
- flex: 0 0 auto;
- height: 600px;
- width: 800px;
- position: relative;
- }
- .tiles-select {
- position: absolute;
- z-index: 9999;
- 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>
- <style lang="scss">
- .leaflet-container {
- background: #fff;
- }
- </style>
|