vue2 h5 畫高德地圖電子圍欄

使用前請先申請高德地圖key

JavaScript API | 騰訊位置服務

npm install lodash-es

效果圖

子組件代碼

<template><div class="fence-container"><div v-if="loading" class="map-loading"><div class="loader"></div><div class="loading-text">地圖加載中...</div></div><div id="map-container" style="width: 100%; height: 80vh"></div><div class="control-panel"><buttonv-if="!isCZ"@click="startCreate":disabled="isEditing || loading"class="create-btn">🗺? 新建圍欄</button><button @click="cancelCreate" v-show="isCreating" class="cancel-btn">? 取消創建</button><button v-if="isEditing" @click="finishEditing" class="edit-complete-btn">?? 完成編輯</button><div class="fence-list" v-if="fences.length"><h3>電子圍欄列表({{ fences.length }})</h3><div v-for="fence in fences" :key="fence.id" class="fence-item"><span class="fence-id">圍欄#{{ fence.id }}</span><div class="actions"><button@click="editFence(fence)":disabled="isEditing"class="edit-btn">?? 編輯</button><button@click="deleteFence(fence.id)":disabled="isEditing"class="delete-btn">🗑? 刪除</button></div></div></div></div></div>
</template><script>
import { debounce } from "lodash-es";export default {data() {return {isCZ: false, //是否存在map: null,fences: [],mapInstances: new Map(),mapInitPromise: null,mouseTool: null,editor: null,isCreating: false,isEditing: false,isClosing: false,loading: true,currentFence: null,editingFence: null,currentAdjustCallback: null,currentEndCallback: null,abortController: null,activeDrawHandler: null,debouncedUpdate: debounce(this.safeUpdateMapInstances, 300),};},props: {statrAddress: {type: Array,default: () => [],},},async mounted() {await this.initializeMap();let that = this;that.setCreated();//初始化圍欄window.addEventListener("beforeunload", this.cleanupResources);},beforeDestroy() {this.cleanupResources();window.removeEventListener("beforeunload", this.cleanupResources);},methods: {//父組件幫助子組件完成編輯edneditF() {if (this.isEditing) {this.finishEditing();}console.log(this.fences, "判斷是否還有地圖存在");if (this.fences.length == 0) {this.$emit("getMapArr", "");}},//回顯地圖,單個多個都 可以 ,遵循格式// {//           id: 1740361973441,//           path: [//             [116.380138, 39.920941],//             [116.373443, 39.891708],//             [116.41653, 39.887229],//             [116.420993, 39.917254],//             [116.380138, 39.920941],//           ],//           status: 'active',//         },setCreated() {if (this.fences.length == 0) return;this.isCZ = true;for (let index = 0; index < this.fences.length; index++) {//必循遵循回顯流程const polygon = new AMap.Polygon({path: this.fences[index].path,strokeColor: "#1791fc",fillColor: "#1791fc",strokeWeight: 4,fillOpacity: 0.4,extData: { id: this.fences[index].id }, // 添加擴展數據用于追蹤});polygon.setMap(this.map);if (polygon) this.mapInstances.set(this.fences[index].id, polygon);}},//編輯完成finishEditing() {//   this.map.remove(this.fences);// 執行編輯器關閉前的確認操作if (this.editor) {// 獲取最終路徑const finalPath = this.editor.getTarget().getPath();const index = this.fences.findIndex((f) => f.id === this.editingFence.id);console.log(this.fences, index, this.editingFence);// 更新數據this.$set(this.fences, {id: this.editingFence,path: [finalPath.lng, finalPath.lat],status: "active",});}// 強制關閉編輯器this.safeCloseEditor();//完成編輯后console.log(this.fences, "編輯后的數據");let data = JSON.stringify(this.fences);this.$emit("getMapArr", data);// 刷新地圖顯示this.$nextTick(() => {this.debouncedUpdate.flush();});},//創建 圍欄cancelCreate() {try {// 關閉所有繪圖工具this.mouseTool?.close(true);// 移除臨時繪圖事件監聽if (this.activeDrawHandler) {this.mouseTool?.off("draw", this.activeDrawHandler);}// 清理可能存在的半成品圍欄if (this.currentFence?.status === "creating") {const tempId = this.currentFence.id;this.safeRemovePolygon(tempId);}// 重置狀態this.isCreating = false;this.currentFence = null;this.activeDrawHandler = null;// 強制重繪有效圍欄this.$nextTick(() => {this.debouncedUpdate.flush();});} catch (error) {console.error("取消創建失敗:", error);}},// 初始化地圖async initializeMap() {let that = this;this.abortController = new AbortController();const { signal } = this.abortController;try {this.mapInitPromise = new Promise((resolve, reject) => {if (signal.aborted) return reject(new Error("用戶取消加載"));AMap.plugin(["AMap.MouseTool", "AMap.PolygonEditor"], () => {if (signal.aborted) {this.cleanupResources();return reject(new Error("加載已中止"));}this.map = new AMap.Map("map-container", {zoom: 12,center: that.statrAddress,viewMode: "2D",});this.map.on("complete", () => {this.loading = false;this.initMouseTool();});resolve(true);});});await this.mapInitPromise;} catch (error) {console.error("地圖初始化失敗:", error);this.loading = false;}},// 初始化鼠標工具initMouseTool() {this.mouseTool = new AMap.MouseTool(this.map);this.mouseTool.on("draw", (event) => {this.handleDrawEvent(event);});},// 資源清理cleanupResources() {// 終止異步操作this.abortController?.abort();this.debouncedUpdate.cancel();// 清理繪圖狀態if (this.activeDrawHandler) {this.mouseTool?.off("draw", this.activeDrawHandler);this.activeDrawHandler = null;}// 清理編輯器this.safeCloseEditor();// 清理鼠標工具if (this.mouseTool) {this.mouseTool.close(true);this.mouseTool = null;}// 清理地圖實例this.mapInstances.forEach((polygon) => {polygon?.setMap(null);polygon = null;});this.mapInstances.clear();// 銷毀地圖if (this.map) {try {this.map.destroy();} catch (e) {console.warn("地圖銷毀異常:", e);}this.map = null;}},// 安全創建多邊形createMapPolygon(fence) {if (!this.map || !fence?.path) return null;try {const polygon = new AMap.Polygon({path: fence.path,strokeColor: "#1791fc",fillColor: "#1791fc",strokeWeight: 4,fillOpacity: 0.4,extData: { id: fence.id }, // 添加擴展數據用于追蹤});polygon.setMap(this.map);return polygon;} catch (error) {console.error("創建多邊形失敗:", error);return null;}},// 安全更新地圖實例async safeUpdateMapInstances(newVal) {try {await this.mapInitPromise;if (!this.map || this.isClosing) return;const currentIds = newVal.map((f) => f.id);// 清理無效實例this.mapInstances.forEach((polygon, id) => {if (!currentIds.includes(id)) {this.safeRemovePolygon(id);}});// 批量更新newVal.forEach((fence) => {if (!this.mapInstances.has(fence.id)) {const polygon = this.createMapPolygon(fence);if (polygon) this.mapInstances.set(fence.id, polygon);} else {this.updatePolygonPath(fence);}});} catch (error) {console.warn("地圖更新中止:", error);}},// 安全更新路徑updatePolygonPath(fence) {const polygon = this.mapInstances.get(fence.id);if (!polygon) return;try {const currentPath = polygon.getPath().map((p) => [p.lng, p.lat]);if (JSON.stringify(currentPath) !== JSON.stringify(fence.path)) {polygon.setPath(fence.path);}} catch (error) {console.error("路徑更新失敗:", error);this.safeRemovePolygon(fence.id);}},// 安全移除多邊形safeRemovePolygon(fenceId) {const polygon = this.mapInstances.get(fenceId);if (!polygon) return;try {polygon.setMap(null);this.map.remove(polygon);this.mapInstances.delete(fenceId);} catch (error) {console.warn("多邊形移除失敗:", error);}},// 開始創建startCreate() {if (this.isEditing || this.loading) return;this.isCreating = true;this.currentFence = {id: Date.now(),path: [],status: "creating",};this.mouseTool.close(true);this.mouseTool.polygon({strokeColor: "#FF33FF",fillColor: "#1791fc00", // 半透明填充strokeWeight: 2,});},// 處理繪制事件handleDrawEvent(event) {if (!this.isCreating) return;const polygon = event.obj;const path = polygon.getPath();if (path.length < 3) {this.mouseTool.close(true);alert("至少需要3個頂點來創建圍欄");return;}// 自動閉合路徑const firstPoint = path[0];const lastPoint = path[path.length - 1];if (firstPoint.distance(lastPoint) > 1e-6) {path.push(firstPoint);}this.currentFence.path = path.map((p) => [p.lng, p.lat]);this.saveFence();this.mouseTool.close(true);},// 保存圍欄saveFence() {try {if (!this.validateFence(this.currentFence)) return;this.fences = [...this.fences,{...this.currentFence,status: "active",},];this.debouncedUpdate.flush();} catch (error) {console.error("保存圍欄失敗:", error);} finally {this.resetCreationState();}//完成創建圍欄let data = JSON.stringify(this.fences);this.$emit("getMapArr", data);this.isCZ = true;//   console.log(this.fences, '電子圍欄數組');},// 驗證圍欄validateFence(fence) {if (!fence?.path) return false;if (fence.path.length < 3) {alert("無效的圍欄路徑");return false;}// 檢查路徑閉合const first = fence.path[0];const last = fence.path[fence.path.length - 1];return first[0] === last[0] && first[1] === last[1];},// 重置創建狀態resetCreationState() {this.isCreating = false;this.currentFence = null;this.mouseTool.close(true);},// 編輯圍欄async editFence(fence) {if (this.isEditing || !this.mapInstances.has(fence.id)) return;await this.safeCloseEditor();this.isEditing = true;this.editingFence = fence;console.log(fence, "fence");// 創建編輯副本const original = this.mapInstances.get(fence.id);// this.safeRemovePolygon(fence.id);// const editPolygon = this.createMapPolygon({//   // ...fence,//   strokeColor: '#FF0000', // 編輯狀態紅色邊框// });console.log(this.map, "this.map", original);this.editor = new AMap.PolygonEditor(this.map, original);this.editor.open();// 事件處理this.currentAdjustCallback = ({ target }) => {const newPath = target.getPath().map((p) => [p.lng, p.lat]);if (JSON.stringify(newPath) === JSON.stringify(fence.path)) return;const index = this.fences.findIndex((f) => f.id === fence.id);if (index > -1) {this.$set(this.fences, index, {...fence,path: newPath,});}};this.currentEndCallback = () => {const finalPath = this.editor.getTarget().getPath().map((p) => [p.lng, p.lat]);this.$set(this.fences,this.fences.findIndex((f) => f.id === fence.id),{ ...fence, path: finalPath });this.safeCloseEditor();};this.editor.on("adjust", this.currentAdjustCallback);this.editor.on("end", this.currentEndCallback);},// 安全關閉編輯器async safeCloseEditor() {if (this.isClosing || !this.editor) return;this.isClosing = true;try {// 關閉編輯器前保存狀態const finalPath = this.editor.getTarget()?.getPath();if (finalPath) {const index = this.fences.findIndex((f) => f.id === this.editingFence?.id);if (index > -1) {this.fences[index].path = finalPath.map((p) => [p.lng, p.lat]);}}// 執行清理this.editor.off("adjust", this.currentAdjustCallback);this.editor.off("end", this.currentEndCallback);this.editor.close();} catch (error) {console.error("編輯器關閉異常:", error);} finally {this.isClosing = false;this.isEditing = false;this.editingFence = null;this.editor = null;}},// 刪除圍欄deleteFence(fenceId) {if (!confirm("確定要刪除這個電子圍欄嗎?")) return;const index = this.fences.findIndex((f) => f.id === fenceId);if (index === -1) return;// 三重清理this.safeRemovePolygon(fenceId);this.$delete(this.fences, index);this.debouncedUpdate.flush();this.isCZ = false;},},//編輯出現舊路徑更新watch: {fences: {deep: true,handler(newVal) {if (!this.isClosing) {this.debouncedUpdate(newVal);}},flush: "post",},},
};
</script><style scoped>
.fence-container {position: relative;height: 80vh;
}.map-loading {position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: rgba(255, 255, 255, 0.95);z-index: 1000;display: flex;flex-direction: column;align-items: center;justify-content: center;
}.loader {border: 4px solid #f3f3f3;border-top: 4px solid #3498db;border-radius: 50%;width: 40px;height: 40px;animation: spin 1s linear infinite;
}.loading-text {margin-top: 15px;color: #666;
}.control-panel {position: absolute;top: 20px;left: 20px;background: rgba(255, 255, 255, 0.95);padding: 15px;border-radius: 8px;box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);min-width: 260px;z-index: 999;
}button {margin: 5px;padding: 8px 12px;border: none;border-radius: 4px;cursor: pointer;transition: all 0.3s;
}.create-btn {background: #4caf50;color: white;
}.cancel-btn {background: #f44336;color: white;
}.edit-complete-btn {background: #2196f3;color: white;animation: pulse 1.5s infinite;
}.edit-btn {background: #ffc107;color: black;
}.delete-btn {background: #9e9e9e;color: white;
}button:disabled {opacity: 0.6;cursor: not-allowed;
}.fence-list {margin-top: 15px;max-height: 60vh;overflow-y: auto;
}.fence-item {display: flex;justify-content: space-between;align-items: center;padding: 10px;margin: 8px 0;background: #f8f9fa;border-radius: 4px;
}.fence-id {font-size: 14px;color: #333;
}.actions {display: flex;gap: 8px;
}@keyframes spin {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}
}@keyframes pulse {0% {transform: scale(1);}50% {transform: scale(1.05);}100% {transform: scale(1);}
}
</style>

父組件使用

<template><div id="app"><div v-if="flag"><GaoDeMap ref="Map" :statrAddress="statrAddress" v-if="flag" /></div><button @click="openMap">喚醒地圖</button></div>
</template><script>
import GaoDeMap from "./components/GaoDeMap.vue";export default {name: "App",components: {GaoDeMap,},data() {return {flag: false,MapRealm: [], //圍欄數據statrAddress: [], //當前位置};},mounted() {//創建script 標簽window.tmapPromise = new Promise((resolve) => {window.init = () => {resolve();};const script = document.createElement("script");script.src = `https://webapi.amap.com/maps?v=2.0&key=67c5ae46f24b49d70af672c993a3bbfc`;document.head.appendChild(script);}).then(() => {});},methods: {//打開電子圍欄openMap() {this.flag = true;this.statrAddress = [116.397428, 39.90923]; //自身當前位置//有圍欄信息的話初始化地圖if (this.MapRealm.length!=0) {this.$nextTick(() => {let arr = JSON.parse(this.MapRealm);this.$refs.Map.fences = arr;});return;}},//畫完電子圍欄后彈窗消失,拿到圍欄信息getMapArr(coordinates) {console.log("用戶繪制的區域坐標:", coordinates);// 接收參數提交到后端或進一步處理this.MapRealm = coordinates;},},
};
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/70878.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/70878.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/70878.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

unity學習54:圖片+精靈+遮罩mask,舊版文本 text 和新的TMP文本

目錄 1 圖片 image 1.1 如果直接導入image 1.2 圖片 image 和精靈 sprite 1.2.1 繼續修改上面的格式 texture type 是default 1.2.2 再次關聯到UI的 image 物體上就可以了 1.3 圖片和遮罩 mask 1.3.1 創建1個父物體和1個子物體&#xff0c;分別都是image 1.3.2 如果父…

Spring Data JPA vs MyBatis:ORM框架如何選擇?

在選擇ORM框架時&#xff0c;Spring Data JPA和MyBatis是兩個常見的選擇&#xff0c;它們各有優缺點&#xff0c;適用于不同的場景。以下是兩者的對比&#xff0c;幫助你做出選擇&#xff1a; 1. Spring Data JPA 優點&#xff1a; 開發效率高&#xff1a;通過簡單的接口定義和…

Selenium 與 Coze 集成

涵蓋兩者的基本概念、集成步驟、代碼示例以及相關注意事項。 基本概念 Selenium:是一個用于自動化瀏覽器操作的工具集,支持多種瀏覽器(如 Chrome、Firefox 等),能夠模擬用戶在瀏覽器中的各種操作,如點擊、輸入文本、選擇下拉框等,常用于 Web 應用的自動化測試。Coze:它…

在線騎行|基于SpringBoot的在線騎行網站設計與實現(源碼+數據庫+文檔)

在線騎行網站系統 目錄 基于SpringBoot的在線騎行設計與實現 一、前言 二、系統設計 三、系統功能設計 5.1用戶信息管理 5.2 路線攻略管理 5.3路線類型管理 5.4新聞賽事管理 四、數據庫設計 五、核心代碼 六、論文參考 七、最新計算機畢設選題推薦 八、源碼獲取…

[深度學習]基于C++和onnxruntime部署yolov12的onnx模型

基于C和ONNX Runtime部署YOLOv12的ONNX模型&#xff0c;可以遵循以下步驟&#xff1a; 準備環境&#xff1a;首先&#xff0c;確保已經下載后指定版本opencv和onnruntime的C庫。 模型轉換&#xff1a; 安裝好yolov12環境并將YOLOv12模型轉換為ONNX格式。這通常涉及使用深度學習…

Imagination DXTP GPU IP:加速游戲AI應用,全天候暢玩無阻

日前&#xff0c;Imagination 推出了最新產品——Imagination DXTP GPU IP&#xff0c;在智能手機和其他功耗受限設備上加速圖形和AI工作負載時&#xff0c;保證全天候的電池續航。它是我們最新D系列GPU的最終產品&#xff0c;集成了自2022年發布以來引入的一系列功能&#xff…

(python)Arrow庫使時間處理變得更簡單

前言 Arrow庫并不是簡單的二次開發,而是在datetime的基礎上進行了擴展和增強。它通過提供更簡潔的API、強大的時區支持、豐富的格式化和解析功能以及人性化的顯示,填補了datetime在某些功能上的空白。如果你需要更高效、更人性化的日期時間處理方式,Arrow庫是一個不錯的選擇…

pandas中的數據結構+數據查詢

pandas 數據結構 Series Series是一種類似于一維數組的對象&#xff0c;它由一組數據&#xff08;不同數據類型&#xff09;以及一組與之相關的數據標簽&#xff08;即索引&#xff09;組成。 列表創建 僅有數據列表即可產生最簡單的Series s1 pd.Series([1,a,5.2,7]) 左側…

使用前端 html css 和js 開發一個AI智能平臺官網模板-前端靜態頁面項目

最近 AI 人工智能這么火&#xff0c;那必須針對AI 做一個 AI方面的 官方靜態網站練手。讓自己的前端技術更上一層樓&#xff0c;哈哈。 隨著人工智能技術的不斷發展&#xff0c;越來越多的AI應用開始滲透到各行各業&#xff0c;為不同領域的用戶提供智能化解決方案。本網站致力…

React + TypeScript 數據模型驅動數據字典生成示例

React TypeScript 數據模型驅動數據字典生成示例 引言&#xff1a;數據字典的工程價值 在現代化全棧開發中&#xff0c;數據字典作為業務實體與數據存儲的映射橋梁&#xff0c;直接影響系統可維護性與團隊協作效率。傳統手動維護字典的方式存在同步成本高和版本管理混亂兩大痛…

MySQL八股整理

1. 如何定位慢查詢&#xff1f; 慢查詢一般發生在聯表查詢或者表中數據量較大時&#xff0c;當響應時間較長或者壓測時間超過2s時&#xff0c;就認為是慢查詢。定位慢查詢的話一般有兩種方法&#xff0c;一種是使用專門的分析工具去定位。另一種也是我們項目中之前使用過的方法…

ShardingSphere Proxy 配置

在使用 ShardingSphere Proxy 模式時&#xff0c;結合 主從復制架構 實現 讀寫分離&#xff0c;并按照 用戶ID哈希算法 確定庫、時間范圍 確定表的場景下&#xff0c;配置文件需要做一些調整以支持分片、讀寫分離以及主從復制。 以下是如何配置 ShardingSphere Proxy 模式的詳…

Redis集群機制及一個Redis架構演進實例

Replication&#xff08;主從復制&#xff09; Redis的replication機制允許slave從master那里通過網絡傳輸拷貝到完整的數據備份&#xff0c;從而達到主從機制。為了實現主從復制&#xff0c;我們準備三個redis服務&#xff0c;依次命名為master&#xff0c;slave1&#xff0c;…

Qt QScrollArea 總結

Qt QScrollArea 總結 1. 功能概述 滾動容器&#xff1a;用于顯示超出視口&#xff08;Viewport&#xff09;范圍的內容&#xff0c;自動提供滾動條。子部件管理&#xff1a;可包裹單個子部件&#xff08;通過 setWidget()&#xff09;&#xff0c;當子部件尺寸 > 視口時&a…

Windows系統編程項目(一)進程管理器

本項目將通過MFC實現一個進程管理器&#xff0c;如下圖詳細信息頁所示&#xff1a; 一.首先創建一個基于對話框的MFC項目&#xff0c;在靜態庫中使用MFC 二.在項目默認的對話框中添加一個列表 三.列表添加變量 四.初始化列表 1.設置列表風格和表頭 2.填充列表內容 我們需要在…

RAG-202502

目錄 RAG場景的坑知識等級金字塔 初級RAG存在的問題高級RAG索前優化檢索優化檢索后優化 優化經驗總結參考 RAG場景的坑 晦澀的專業術語 誤區&#xff1a;在專業領域中。許多文獻和資料中充滿了專業術語&#xff0c;這些術語對于非專業人士&#xff08;甚至是大模型&#xff0…

CDN與群聯云防護的技術差異在哪?

CDN&#xff08;內容分發網絡&#xff09;與群聯云防護是兩種常用于提升網站性能和安全的解決方案&#xff0c;但兩者的核心目標和技術實現存在顯著差異。本文將從防御機制、技術架構、適用場景和代碼實現等方面詳細對比兩者的區別&#xff0c;并提供可直接運行的代碼示例。 一…

STM32-智能小車項目

項目框圖 ST-link接線 實物圖&#xff1a; 正面&#xff1a; 反面&#xff1a; 相關內容 使用L9110S電機模塊 電機驅動模塊L9110S詳解 | 良許嵌入式 測速模塊 語音模塊SU-03T 網站&#xff1a;智能公元/AI產品零代碼平臺 一、讓小車動起來 新建文件夾智能小車項目 在里面…

【Linux】vim 設置

【Linux】vim 設置 零、起因 剛學Linux&#xff0c;有時候會重裝Linux系統&#xff0c;然后默認的vi不太好用&#xff0c;需要進行一些設置&#xff0c;本文簡述如何配置一個好用的vim。 壹、軟件安裝 sudo apt-get install vim貳、配置路徑 對所有用戶生效&#xff1a; …

【Python爬蟲(90)】以Python爬蟲為眼,洞察金融科技監管風云

【Python爬蟲】專欄簡介&#xff1a;本專欄是 Python 爬蟲領域的集大成之作&#xff0c;共 100 章節。從 Python 基礎語法、爬蟲入門知識講起&#xff0c;深入探討反爬蟲、多線程、分布式等進階技術。以大量實例為支撐&#xff0c;覆蓋網頁、圖片、音頻等各類數據爬取&#xff…