一、下载arcgis依赖
# 安装官方发布最新版本 $ npm install @arcgis/core # 或安装指定版本 $ npm install @arcgis/core@4.24二、配置路径
*** resolve: { alias: { // 若依初始路径 //'@': resolve('src') // 设置路径 '~': path.resolve(__dirname, './'), // 设置别名 '@': path.resolve(__dirname, './src'), // arcgis资源路径 '@arcgis/core': path.resolve(__dirname, 'node_modules/@arcgis/core') } }, ***三、arcgis基础组件
components/ArcGisMap/index.vue:
<template> <div ref="mapContainer" class="arcgis-map" :style="{ width, height }"></div> </template> <script setup name="ArcGISMap"> import { ref, onMounted, onUnmounted, watch, defineProps, defineEmits, defineExpose } from 'vue' // 核心 ArcGIS 模块 import Map from '@arcgis/core/Map' import MapView from '@arcgis/core/views/MapView' import Graphic from '@arcgis/core/Graphic' import Point from '@arcgis/core/geometry/Point' import SimpleMarkerSymbol from '@arcgis/core/symbols/SimpleMarkerSymbol' import ScaleBar from '@arcgis/core/widgets/ScaleBar' import Popup from '@arcgis/core/widgets/Popup' // 加载天地图所需图层模块 import WebTileLayer from '@arcgis/core/layers/WebTileLayer' // 引入 ArcGIS 核心样式 import '@arcgis/core/assets/esri/themes/light/main.css' // 1. 定义组件属性 const props = defineProps({ // 地图中心点 [经度, 纬度](GCJ02坐标系) center: { type: Array, default: () => [125.311,43.8698] // 长春市测绘院 }, // 初始缩放级别 zoom: { type: Number, default: 12 }, // 地图宽高 width: { type: String, default: '100%' }, height: { type: String, default: '800px' }, // 天地图密钥(必填) tiandituKey: { type: String, required: true }, // 天地图类型:vec(矢量)、img(影像)、ter(地形) tiandituType: { type: String, default: 'vec', validator: (val) => ['vec', 'img', 'ter'].includes(val) } }) // 2. 定义事件 const emit = defineEmits(['mapLoaded', 'mapError']) // 3. 响应式变量 const mapContainer = ref(null) let mapInstance = null let mapViewInstance = null const isMapLoaded = ref(false) // 4. 天地图瓦片规则配置(核心:匹配天地图WMTS的tileInfo) // 5. 初始化天地图图层 const initTiandituLayer = () => { const subDomains = ["t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"]; // 天地图图层URL配置,tk后面是天地图密钥 const layerConfig = { vec: { base: "http://{subDomain}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={col}&TILEROW={row}&TILEMATRIX={level}&tk=7b79e37115f10c512f76cdecda*", anno: "http://{subDomain}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={col}&TILEROW={row}&TILEMATRIX={level}&tk=7b79e37115f10c512f76cdecda*" }, img: { base: "http://{subDomain}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={col}&TILEROW={row}&TILEMATRIX={level}&tk=7b79e37115f10c512f76cdecda*", anno: "http://{subDomain}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={col}&TILEROW={row}&TILEMATRIX={level}&tk=7b79e37115f10c512f76cdecda*" } } // 创建底图图层(核心:补充tileInfo和spatialReference) const baseLayer = new WebTileLayer({ urlTemplate: layerConfig[props.tiandituType]["base"], subDomains: subDomains, // tileInfo: tileInfo, // 匹配瓦片规则 // spatialReference: { wkid: 4490 }, // 匹配天地图坐标系 title: '天地图底图' }) // 创建注记图层 const annoLayer = new WebTileLayer({ urlTemplate: layerConfig[props.tiandituType]["anno"], subDomains: subDomains, // tileInfo: tileInfo, // 注记图层与底图瓦片规则一致 // spatialReference: { wkid: 4490 }, title: '天地图注记' }) return [baseLayer, annoLayer] } // 6. 初始化地图 const initMap = async () => { try { if (!props.tiandituKey) { throw new Error('天地图密钥不能为空') } // 创建天地图图层 const [baseLayer, annoLayer] = initTiandituLayer() // 创建地图实例 mapInstance = new Map({ layers: [baseLayer, annoLayer] }) // 创建地图视图(核心:spatialReference改为4490) mapViewInstance = new MapView({ container: mapContainer.value, map: mapInstance, center: props.center, zoom: props.zoom, // spatialReference: { wkid: 4490 }, // 匹配天地图坐标系 // rotationEnabled: false, popup: new Popup({ dockEnabled: true, dockOptions: { position: 'bottom-right', breakpoint: false } }) }) // 添加比例尺 const scaleBar = new ScaleBar({ view: mapViewInstance, unit: 'metric' }) mapViewInstance.ui.add(scaleBar, 'bottom-left') // 等待地图加载 await mapViewInstance.when() isMapLoaded.value = true emit('mapLoaded', { map: mapInstance, view: mapViewInstance }) console.log('✅ 天地图初始化完成(4490坐标系)') // 添加测试标记(匹配4490坐标系) addTestMarker() } catch (error) { isMapLoaded.value = false emit('mapError', error) console.error('❌ 天地图初始化失败:', error) } } // 7. 添加测试标记(适配4490坐标系) const addTestMarker = () => { if (!mapViewInstance) return const point = new Point({ longitude: props.center[0], latitude: props.center[1], spatialReference: { wkid: 4490 } // 标记点坐标系匹配地图 }) const markerSymbol = new SimpleMarkerSymbol({ color: [226, 119, 40], outline: { color: [255, 255, 255], width: 2 }, size: 14, style: 'circle' }) const markerGraphic = new Graphic({ geometry: point, symbol: markerSymbol, attributes: { name: '测试标记点', desc: 'Vue3 + 天地图(4490坐标系)测试', address: '北京市东城区' } }) mapViewInstance.graphics.add(markerGraphic) // 点击弹窗逻辑不变 mapViewInstance.on('click', async (event) => { const hitResult = await mapViewInstance.hitTest(event) if (hitResult.results.length) { const graphic = hitResult.results[0].graphic if (graphic.attributes) { mapViewInstance.popup.open({ location: event.mapPoint, title: graphic.attributes.name, content: ` <div style="padding: 8px 0;"> <p>描述:${graphic.attributes.desc}</p> <p>地址:${graphic.attributes.address}</p> <p>坐标:${event.mapPoint.longitude.toFixed(6)}, ${event.mapPoint.latitude.toFixed(6)}</p> </div> ` }) } } else { mapViewInstance.popup.close() } }) } // 8. 监听属性变化 watch([() => props.center, () => props.zoom], ([newCenter, newZoom]) => { if (mapViewInstance && isMapLoaded.value) { mapViewInstance.goTo({ center: newCenter, zoom: newZoom }, { animate: true, duration: 800 }) } }, { deep: true }) watch(() => props.tiandituType, async (newType) => { if (mapInstance && isMapLoaded.value) { mapInstance.layers.removeAll() const [baseLayer, annoLayer] = initTiandituLayer() mapInstance.layers.addMany([baseLayer, annoLayer]) console.log(`✅ 天地图切换为:${newType === 'vec' ? '矢量' : newType === 'img' ? '影像' : '地形'}`) } }) // 9. 生命周期 onMounted(() => { if (mapContainer.value) initMap() else console.error('地图容器DOM不存在') }) onUnmounted(() => { if (mapViewInstance) mapViewInstance.destroy() mapInstance = null mapViewInstance = null isMapLoaded.value = false }) // 10. 暴露方法 defineExpose({ mapViewInstance, isMapLoaded, getCurrentCenter: () => { if (isMapLoaded.value && mapViewInstance) { return { longitude: mapViewInstance.center.longitude, latitude: mapViewInstance.center.latitude } } return null } }) </script> <style scoped> .arcgis-map { border: 1px solid #e5e7eb; border-radius: 4px; box-sizing: border-box; z-index: 1000; position: relative; } :deep(.esri-popup) { --esri-popup-background-color: #fff; --esri-popup-border-color: #e5e7eb; --esri-popup-title-color: #303133; --esri-popup-content-color: #606266; } :deep(.esri-scale-bar) { background-color: rgba(255, 255, 255, 0.8); border-radius: 4px; } :deep(.esri-layer) { opacity: 1 !important; } </style>views/zmap/index.vue:
<template> <div class="app-container"> <!-- 页面头部 --> <div class="page-header"> <el-page-header content="天地图测试页" @back="goBack"></el-page-header> <el-divider></el-divider> <el-card shadow="hover" class="control-card"> <el-row :gutter="16" align="middle"> <!-- 天地图类型切换 --> <el-col :span="6"> <el-form-item label="天地图类型:"> <el-select v-model="tiandituType" @change="handleTiandituTypeChange"> <el-option label="矢量地图" value="vec"></el-option> <el-option label="影像地图" value="img"></el-option> </el-select> </el-form-item> </el-col> <!-- 其他控件不变 --> <el-col :span="7"> <el-form-item label="中心点(经,纬):"> <el-input v-model="centerInput" @change="handleCenterChange" clearable></el-input> </el-form-item> </el-col> <el-col :span="5"> <el-form-item label="缩放级别:"> <el-input-number v-model="zoomLevel" :min="1" :max="20" @change="handleZoomChange"></el-input-number> </el-form-item> </el-col> <el-col :span="6"> <el-button type="primary" @click="resetMap">重置地图</el-button> <el-button type="success" @click="getCurrentCenter">获取当前中心</el-button> </el-col> </el-row> </el-card> </div> <!-- 天地图组件(核心:传入你的天地图密钥) --> <ArcGISMap ref="mapRef" :center="mapCenter" :zoom="zoomLevel" :tianditu-key="'574e0a9ee38998d3ff466ae67*'" :tianditu-type="tiandituType" height="800px" @mapLoaded="handleMapLoaded" @mapError="handleMapError" ></ArcGISMap> </div> </template> <script setup name="MapTest"> import { ref } from 'vue' import { ElMessage } from 'element-plus' import { useRouter } from 'vue-router' import ArcGISMap from '@/components/ArcGISMap' const router = useRouter() // 核心配置:替换为你申请的天地图密钥!!! const tiandituKey = ref('你的天地图密钥') const tiandituType = ref('vec') // 默认矢量地图 const mapCenter = ref([125.311,43.8698]) const zoomLevel = ref(12) const centerInput = ref('125.311,43.8698') const mapRef = ref(null) // 切换天地图类型 const handleTiandituTypeChange = (val) => { ElMessage.success(`已切换为:${val === 'vec' ? '矢量地图' : val === 'img' ? '影像地图' : '地形地图'}`) } // 其他方法(goBack/handleCenterChange/getCurrentCenter 等)不变 const goBack = () => router.back() const handleCenterChange = () => { const [lng, lat] = centerInput.value.split(',').map(Number) if (isNaN(lng) || isNaN(lat) || lng < -180 || lng > 180 || lat < -90 || lat > 90) { ElMessage.error('请输入合法经纬度!') centerInput.value = `${mapCenter.value[0]},${mapCenter.value[1]}` return } mapCenter.value = [lng, lat] ElMessage.success(`中心点更新为:${lng.toFixed(6)}, ${lat.toFixed(6)}`) } const handleZoomChange = (val) => ElMessage.success(`缩放级别:${val}`) const resetMap = () => { mapCenter.value = [125.311,43.8698] zoomLevel.value = 12 tiandituType.value = 'vec' centerInput.value = '125.311,43.8698' ElMessage.success('地图已重置!') } const getCurrentCenter = () => { const center = mapRef.value?.getCurrentCenter() if (!center) { ElMessage.warning('地图未加载完成!') return } const centerStr = `${center.longitude.toFixed(6)},${center.latitude.toFixed(6)}` mapCenter.value = [center.longitude, center.latitude] centerInput.value = centerStr ElMessage.success(`当前中心点:${centerStr}`) } const handleMapLoaded = () => ElMessage.success('✅ 天地图加载成功!') const handleMapError = (err) => { console.error(err) ElMessage.error('❌ 天地图加载失败!') } </script> <style scoped> .app-container { padding: 20px; height: 100vh; box-sizing: border-box; background-color: #f9f9f9; } .page-header { margin-bottom: 20px; } .control-card { margin-bottom: 16px; } :deep(.el-form-item) { margin-bottom: 0; } </style>permission.js添加例外,不要登陆就可访问:
//修改前 const whiteList = ['/login', '/register'] //修改后 const whiteList = ['/login', '/register','/zmap']访问地址(根据自己的ip地址):http://localhost/zmap