|
|
@@ -1,7 +1,6 @@
|
|
|
<template>
|
|
|
<div class="map-page with-bottom-nav">
|
|
|
<div ref="mapContainer" class="map-container" />
|
|
|
-
|
|
|
<!-- 定位按钮 -->
|
|
|
<div class="map-controls">
|
|
|
<van-button
|
|
|
@@ -15,7 +14,6 @@
|
|
|
定位
|
|
|
</van-button>
|
|
|
</div>
|
|
|
-
|
|
|
<!-- 许愿树信息弹出层 -->
|
|
|
<van-popup
|
|
|
v-model:show="showPopup"
|
|
|
@@ -71,7 +69,6 @@
|
|
|
</van-popup>
|
|
|
</div>
|
|
|
</template>
|
|
|
-
|
|
|
<script setup lang="ts">
|
|
|
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
|
|
|
import { useRouter } from "vue-router";
|
|
|
@@ -80,43 +77,35 @@ import { useLocationStore } from "@/stores/location";
|
|
|
import { getUserLocation } from "@/utils/geo";
|
|
|
import { getTreeGradient, getTreeEmoji } from "@/utils/theme";
|
|
|
import { fetchNearbyTrees } from "@/api/tree";
|
|
|
+import { gcj02ToWgs84 } from "@/utils/mapTool";
|
|
|
import markerImg from "@/assets/location-marker.png";
|
|
|
import L from "leaflet";
|
|
|
import "leaflet/dist/leaflet.css";
|
|
|
-
|
|
|
const router = useRouter();
|
|
|
const locationStore = useLocationStore();
|
|
|
-
|
|
|
const mapContainer = ref<HTMLDivElement>();
|
|
|
const showPopup = ref(false);
|
|
|
const selectedTree = ref<any>(null);
|
|
|
const locating = ref(false);
|
|
|
-
|
|
|
const popupGradient = computed(() =>
|
|
|
getTreeGradient(selectedTree.value?.id || 1),
|
|
|
);
|
|
|
const popupEmoji = computed(() => getTreeEmoji(selectedTree.value?.id || 1));
|
|
|
-
|
|
|
let map: any = null;
|
|
|
let userMarker: any = null;
|
|
|
let treeMarkers: L.Marker[] = [];
|
|
|
-
|
|
|
function fmtDist(m: number) {
|
|
|
return m >= 1000 ? (m / 1000).toFixed(1) + "km" : m + "m";
|
|
|
}
|
|
|
-
|
|
|
function goTreeDetail() {
|
|
|
showPopup.value = false;
|
|
|
router.push(`/tree/${selectedTree.value.id}`);
|
|
|
}
|
|
|
-
|
|
|
function goMakeWish() {
|
|
|
showPopup.value = false;
|
|
|
router.push({ path: "/make-wish", query: { treeId: selectedTree.value.id } });
|
|
|
}
|
|
|
-
|
|
|
// 高德瓦片(GCJ-02 坐标系)
|
|
|
-
|
|
|
async function initMap() {
|
|
|
try {
|
|
|
// 尝试获取当前位置作为地图中心
|
|
|
@@ -129,7 +118,6 @@ async function initMap() {
|
|
|
} catch {
|
|
|
// 定位失败使用默认坐标
|
|
|
}
|
|
|
-
|
|
|
// 天地图参数
|
|
|
const tileOptions = {
|
|
|
zoomControl: true,
|
|
|
@@ -159,32 +147,26 @@ async function initMap() {
|
|
|
zoomControl: false,
|
|
|
attributionControl: false,
|
|
|
});
|
|
|
-
|
|
|
// L.tileLayer(GAODE_TILE, {
|
|
|
// subdomains: ["1", "2", "3", "4"],
|
|
|
// maxZoom: 18,
|
|
|
// }).addTo(map);
|
|
|
L.layerGroup().addTo(map);
|
|
|
-
|
|
|
L.control.scale({ metric: true, imperial: false }).addTo(map);
|
|
|
L.control.zoom({ position: "topright" }).addTo(map);
|
|
|
-
|
|
|
locateUser();
|
|
|
} catch (err) {
|
|
|
showToast("地图加载失败");
|
|
|
console.error(err);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
async function locateUser() {
|
|
|
locating.value = true;
|
|
|
try {
|
|
|
const { lng, lat } = await getUserLocation();
|
|
|
locationStore.setLocation(lng, lat);
|
|
|
-
|
|
|
if (map) {
|
|
|
map.setView([lat, lng], 18);
|
|
|
-
|
|
|
if (userMarker) map.removeLayer(userMarker);
|
|
|
const icon = L.icon({
|
|
|
iconUrl: markerImg,
|
|
|
@@ -193,7 +175,6 @@ async function locateUser() {
|
|
|
});
|
|
|
userMarker = L.marker([lat, lng], { icon, zIndexOffset: 200 }).addTo(map);
|
|
|
}
|
|
|
-
|
|
|
await loadTrees();
|
|
|
} catch {
|
|
|
await loadTrees();
|
|
|
@@ -201,25 +182,20 @@ async function locateUser() {
|
|
|
locating.value = false;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
async function loadTrees() {
|
|
|
if (!locationStore.lng) return;
|
|
|
-
|
|
|
const trees = await fetchNearbyTrees(
|
|
|
locationStore.lng,
|
|
|
locationStore.lat!,
|
|
|
50000,
|
|
|
100,
|
|
|
);
|
|
|
-
|
|
|
treeMarkers.forEach((m) => map.removeLayer(m));
|
|
|
treeMarkers = [];
|
|
|
-
|
|
|
trees.forEach((tree: any) => {
|
|
|
const isIn = tree.isInRange;
|
|
|
const color = isIn ? "#07c160" : "#ff976a";
|
|
|
const label = `${tree.name} ${fmtDist(tree.distance)}`;
|
|
|
-
|
|
|
const icon = L.divIcon({
|
|
|
className: "",
|
|
|
html: `
|
|
|
@@ -253,20 +229,16 @@ async function loadTrees() {
|
|
|
iconSize: [0, 0] as any,
|
|
|
iconAnchor: [0, 0],
|
|
|
});
|
|
|
-
|
|
|
- const marker = L.marker([tree.latitude, tree.longitude], { icon }).addTo(
|
|
|
- map,
|
|
|
- );
|
|
|
-
|
|
|
+ // DB 存储的是 GCJ-02 坐标,天地图 img_w 使用 WGS-84,需转换
|
|
|
+ const wgs = gcj02ToWgs84({ lng: tree.longitude, lat: tree.latitude });
|
|
|
+ const marker = L.marker([wgs.lat, wgs.lng], { icon }).addTo(map);
|
|
|
marker.on("click", () => {
|
|
|
selectedTree.value = tree;
|
|
|
showPopup.value = true;
|
|
|
});
|
|
|
-
|
|
|
treeMarkers.push(marker);
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
watch(
|
|
|
() => [locationStore.lng, locationStore.lat] as const,
|
|
|
([lng, lat]) => {
|
|
|
@@ -275,19 +247,16 @@ watch(
|
|
|
}
|
|
|
},
|
|
|
);
|
|
|
-
|
|
|
onMounted(async () => {
|
|
|
await nextTick();
|
|
|
initMap();
|
|
|
locationStore.startWatch(5000);
|
|
|
});
|
|
|
-
|
|
|
onUnmounted(() => {
|
|
|
locationStore.stopWatch();
|
|
|
if (map) map.remove();
|
|
|
});
|
|
|
</script>
|
|
|
-
|
|
|
<style scoped>
|
|
|
.map-page {
|
|
|
position: relative;
|