<template> <div ref="mapContainer" class="map-container"></div> <!-- 自定义右键菜单 --> <div v-if="showContextMenu" :style="contextMenuStyle" class="context-menu"> <div @click="openAddLocationForm">添加地点</div> </div> <!-- 添加地点表单 --> <div v-if="showAddLocationForm" class="add-location-modal"> <div class="modal-content"> <h3>添加地点</h3> <form @submit.prevent="handleSubmit"> <div class="form-row"> <label>经度：</label> <input type="text" :value="formData.lng.toFixed(6)" disabled /> </div> <div class="form-row"> <label>纬度：</label> <input type="text" :value="formData.lat.toFixed(6)" disabled /> </div> <div class="form-row"> <label>说明：</label> <input type="text" v-model="formData.desc" /> </div> <div class="form-actions"> <button type="submit" class="submit-btn">提交</button> </div> </form> </div> </div> </template> <script setup> import{onMounted,onBeforeUnmount,nextTick,ref}from "vue"; import AMapLoader from "@amap/amap-jsapi-loader"; const mapContainer = ref(null); let mapInstance = null; const showContextMenu = ref(false); const contextMenuStyle = ref({}); const showAddLocationForm = ref(false); const formData = ref({lng: 0,lat: 0,desc: ""}); // 高德配置（必须包含 ContextMenu 插件） const amapKey = import.meta.env.VITE_AMAP_KEY; const MAP_CONFIG ={key:amapKey,version: "2.0",plugins: ["AMap.Scale","AMap.ToolBar","AMap.ContextMenu"],}; function openAddLocationForm(){showContextMenu.value = false;showAddLocationForm.value = true}function handleSubmit(){console.log("提交数据:",formData.value);showAddLocationForm.value = false}// 全局右键拦截（仅在地图容器内阻止） const handleGlobalContextMenu = (e) =>{const container = mapContainer.value;if (container && container.contains(e.target)){e.preventDefault()}}; onMounted(async () => {await nextTick(); try {await AMapLoader.load(MAP_CONFIG); const container = mapContainer.value; if (!container) return; mapInstance = new AMap.Map(container,{zoom: 12,center: [114.294706,39.762283],disableDefaultContextMenu: true,}); mapInstance.addControl(new AMap.Scale()); mapInstance.addControl(new AMap.ToolBar()); // 监听高德右键事件 mapInstance.on("rightclick",(e) => {formData.value.lng = e.lnglat.getLng(); formData.value.lat = e.lnglat.getLat(); formData.value.desc = ""; // ✅ 关键：使用 originalEvent 获取真实鼠标位置 const x = e.originalEvent.clientX; const y = e.originalEvent.clientY; // 菜单位置（防止超出视口） const menuWidth = 120; const menuHeight = 40; const left = Math.min(x,window.innerWidth - menuWidth); const top = Math.min(y,window.innerHeight - menuHeight); contextMenuStyle.value = {position: "fixed",left: left + "px",top: top + "px",zIndex: 1000,}; showAddLocationForm.value = false; showContextMenu.value = true;}); // 点击地图关闭菜单 mapInstance.on("click",() => {showContextMenu.value = false;}); // 绑定全局右键拦截 window.addEventListener("contextmenu",handleGlobalContextMenu);} catch (error) {console.error("地图加载失败:",error);}}); onBeforeUnmount(() => {if (mapInstance) mapInstance.destroy(); window.removeEventListener("contextmenu",handleGlobalContextMenu);}); </script> <style> *{margin:0;padding:0;box-sizing:border-box}.map-container{width:100vw;height:100vh;position:relative}</style> <style scoped> .context-menu{background:#fff;border:1px solid #ddd;border-radius:4px;box-shadow:0 2px 10px #0003;min-width:120px;padding:4px 0;position:fixed;z-index:1000}.context-menu>div{padding:8px 16px;cursor:pointer;font-size:14px}.context-menu>div:hover{background:#f0f9ff}.add-location-modal{position:fixed;right:20px;bottom:20px;z-index:2000}.modal-content{background:#fff;border-radius:8px;box-shadow:0 4px 20px #00000040;padding:20px;width:300px}.form-row{display:flex;margin-bottom:12px;align-items:center}.form-row label{width:60px;font-weight:700}.form-row input{flex:1;padding:6px 8px;border:1px solid #ccc;border-radius:4px}.form-actions{text-align:right}.submit-btn{background:#1890ff;color:#fff;border:none;border-radius:4px;padding:6px 16px;cursor:pointer}.submit-btn:hover{background:#40a9ff}</style> *{margin:0;padding:0;box-sizing:border-box}html,body,#app{width:100%;height:100%;overflow:hidden}.map-container{width:100vw;height:100vh}.context-menu[data-v-d9d3bd17]{background:#fff;border:1px solid #ddd;border-radius:4px;box-shadow:0 2px 10px #0003;min-width:120px;padding:4px 0;position:fixed;z-index:1000}.context-menu>div[data-v-d9d3bd17]{padding:8px 16px;cursor:pointer;font-size:14px}.context-menu>div[data-v-d9d3bd17]:hover{background:#f0f9ff}.add-location-modal[data-v-d9d3bd17]{position:fixed;right:20px;bottom:20px;z-index:2000}.modal-content[data-v-d9d3bd17]{background:#fff;border-radius:8px;box-shadow:0 4px 20px #00000040;padding:20px;width:300px}.form-row[data-v-d9d3bd17]{display:flex;margin-bottom:12px;align-items:center}.form-row label[data-v-d9d3bd17]{width:60px;font-weight:700}.form-row input[data-v-d9d3bd17]{flex:1;padding:6px 8px;border:1px solid #ccc;border-radius:4px}.form-actions[data-v-d9d3bd17]{text-align:right}.submit-btn[data-v-d9d3bd17]{background:#1890ff;color:#fff;border:none;border-radius:4px;padding:6px 16px;cursor:pointer}.submit-btn[data-v-d9d3bd17]:hover{background:#40a9ff}.item[data-v-40a5c0e5]{margin-top:2rem;display:flex;position:relative}.details[data-v-40a5c0e5]{flex:1;margin-left:1rem}i[data-v-40a5c0e5]{display:flex;place-items:center;place-content:center;width:32px;height:32px;color:var(--color-text)}h3[data-v-40a5c0e5]{font-size:1.2rem;font-weight:500;margin-bottom:.4rem;color:var(--color-heading)}@media (min-width: 1024px){.item[data-v-40a5c0e5]{margin-top:0;padding:.4rem 0 1rem calc(var(--section-gap) / 2)}i[data-v-40a5c0e5]{top:calc(50% - 25px);left:-26px;position:absolute;border:1px solid var(--color-border);background:var(--color-background);border-radius:8px;width:50px;height:50px}.item[data-v-40a5c0e5]:before{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;bottom:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-40a5c0e5]:after{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;top:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-40a5c0e5]:first-of-type:before{display:none}.item[data-v-40a5c0e5]:last-of-type:after{display:none}}
