UniApp 集成 OpenLayers 加载天地图 (Web-view双向通信方案)
1. 核心思路概述
由于 UniApp 的 小程序、App 端(Android/iOS)原生环境不存在 DOM 树,无法直接运行依赖 DOM 操作的 OpenLayers 库。本方案采用 web-view 组件作为容器,加载独立的 H5 页面来渲染地图,并通过uni.webview 桥接协议实现 H5 与 Native App 之间的双向通信(消息传递、方法调用)。
即:所有与地图相关的业务全写在h5页面,如有控权等需求就可通过双向通信拿到权限标识后,在h5页面单独处理。
注:以下代码在app端测试成功,其余端未测试
2. 前置准备

需下载官方桥接库 uni.webview.1.5.6.js 并置于项目静态资源目录(如 /static/dist_uni.webview.1.5.6.js),以便 H5 页面调用原生能力。
# 下载地址https://gitcode.com/dcloud/uni-app/blob/dev/dist/uni.webview.1.5.6.js
3. H5 地图页面实现 (map-view.html)

该文件为纯 H5 环境,负责地图渲染及与 App 端的交互逻辑。!!!一定记得修改天地图key,否者是白屏!!! 没有的上官网免费创建一个
<!DOCTYPE html><htmllang="zh-CN"><head><metacharset="UTF-8"><!-- 视口配置:禁止用户缩放,确保地图容器尺寸与设备屏幕严格匹配,防止地图控件错位 --><metaname="viewport"content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>天地图</title><!-- 引入 OpenLayers 样式表 (CDN) --><linkrel="stylesheet"href="https://cdn.jsdelivr.net/npm/ol@v10.2.1/ol.css"><style>/* 全局样式重置:移除默认边距,确保占满全屏 */body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }/* 地图容器:绝对定位覆盖全屏,作为 OpenLayers 的挂载点 */#map { width: 100%; height: 100%; position: absolute; left: 0; top: 0; }/* 业务容器:相对定位,用于承载地图和浮层按钮 */.view-map-container {width: 100%;height: 100%;position: relative;}/* 模拟业务浮层:绝对定位于顶部,演示 H5 内部 UI 与地图的层级关系 */.map-btn {position: absolute;top: 0;left: 50%;transform: translateX(-50%);/* 实际开发中需注意 z-index 以避免遮挡地图操作 */}</style></head><body><divclass="view-map-container"><!-- OpenLayers 地图挂载节点 --><divid="map"></div><!-- 交互测试区域:触发 H5 向 App 发送消息 --><buttonclass="map-btn"onclick="handleClick()">发送消息给app</button></div><!-- 1. 引入 UniApp Webview 桥接库 (本地路径,需提前下载) --><scriptsrc="./dist_uni.webview.1.5.6.js"></script><!-- 2. 引入 OpenLayers 核心库 (CDN) --><scriptsrc="https://cdn.jsdelivr.net/npm/ol@v10.2.1/dist/ol.js"></script><script>/*** 【通信方向:App -> H5】* 定义全局回调方法 getToken,供 App 端通过 evalJS 调用。* 用于接收 App 传递的业务数据(如 Token、坐标等)。*/window.getToken = function(token) {console.log("📩 收到 uniapp 传来的 token:" + token);// 此处可执行后续业务逻辑,如利用 token 请求受保护的地图服务}/*** 【通信方向:H5 -> App】* 处理按钮点击事件,向 App 端发送消息。* 包含兼容性检查:在非 UniApp 环境(如普通浏览器)下不执行发送,防止报错。*/function handleClick() {if (window.uni && window.uni.postMessage) {// 发送结构化数据:action 标识动作类型,params 携带具体参数window.uni.postMessage({data: {action: 'scanCode', // 通知 App 执行扫码或其他原生能力params: { type: 'qr' }}});} else {console.log('当前不在 UniApp 环境中,无法调用原生');// Fallback 逻辑:开发调试时可在此处模拟返回结果}}// 【配置项】天地图 API Key (TK)const TK = '????????????';localStorage.setItem("tdt-key", TK);// 检查 OpenLayers 库是否加载成功,防止异步加载导致的初始化失败if (!window.ol) {console.error('OpenLayers 未加载');} else {initMap();}/*** 地图初始化核心函数*/function initMap() {const ol = window.ol;const imgUrl = `https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TK}`;const labelUrl = `https://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TK}`;const imgSource = new ol.source.XYZ({url: imgUrl,wrapX: false,maxZoom: 18,minZoom: 1});// 2. 创建注记数据源const labelSource = new ol.source.XYZ({url: labelUrl,wrapX: false,maxZoom: 18,minZoom: 1});// 3. 创建图层对象const imgLayer = new ol.layer.Tile({ source: imgSource });const labelLayer = new ol.layer.Tile({ source: labelSource });// 4. 初始化地图实例const map = new ol.Map({target: 'map', // 绑定 DOM 节点 IDlayers: [imgLayer, labelLayer], // 叠加影像与注记图层view: new ol.View({// 坐标转换:将经纬度 [116.4074, 39.9042] 转换为 EPSG:3857 (Web Mercator)center: ol.proj.fromLonLat([116.4074, 39.9042]),zoom: 12,minZoom: 1,maxZoom: 18}),});console.log('✅ 天地图加载成功 (XYZ 模式)');/*** 【生命周期监听】* 监听 UniApp JSBridge 就绪事件,确保桥接通道已建立后再进行通信。*/document.addEventListener('UniAppJSBridgeReady', function() {// 获取当前运行环境信息 (App/H5/小程序等)uni.webView.getEnv(function(res) {console.log('当前环境:' + JSON.stringify(res));});// 主动发送初始化完成消息给 App 端uni.postMessage({data: {action: 'message' // 告知 App 地图已就绪}});});}</script></body></html>
4. UniApp 宿主页面实现
该文件为 .vue 组件,负责嵌入 H5 页面,接收 H5 消息及反向调用 H5 方法。
<template><viewclass="page-index-container"><web-view:src="webViewUrl"@message="handleMessage"></web-view></view></template><scriptsetup>import { ref, getCurrentInstance, reactive } from 'vue';import { onLoad, onReady } from '@dcloudio/uni-app';let wv = null; // 存储原生的 Webview 实例对象,用于高级控制const instance = getCurrentInstance();const webViewUrl = ref('/static/map/map-view.html');/*** 页面渲染完成后执行*/onReady(function() {// 仅在 App 端执行以下原生操作if (!instance) {console.error('无法获取组件实例');return;}const proxy = instance.proxy;if (!proxy || !proxy.$scope) {console.error('无法找到 $scope,请确认是否在 App 端运行');return;}const currentWebview = proxy.$scope.$getAppWebview();/*** APP -> H5* 调用 H5 中定义的 window.getToken 方法,传入模拟 Token* 延时执行:等待 web-view 组件完成内部渲染,确保 children 数组已生成*/setTimeout(() => {if (currentWebview && currentWebview.children()) {wv = currentWebview.children()[0];const token='我是app端token'wv.evalJS(`getToken(${token})`);}}, 1000);});/*** H5 -> APP* 消息接收处理器* 接收来自 H5 的 postMessage 数据*/const handleMessage = (e) => {const msg = e.detail?.data;console.log('📩 收到 H5 消息:', msg);// 业务逻辑:根据 msg.action 判断 H5 意图// 例如:if (msg.action === 'scanCode') { uni.scanCode(...) }// TODO: 验证消息合法性并执行相应原生操作};</script><style></style>
5. 关键技术细节说明
H5 -> App: H5 页面必须引入 uni.webview.x.x.x.js,并在 UniAppJSBridgeReady 事件触发后,使用 uni.postMessage 发送数据。App 端通过 <web-view @message="..."> 接收。
App -> H5: App 端需获取原生 webview 实例(通过 $getAppWebview().children()[0]),调用 wv.evalJS('functionName(args)') 直接执行 H5 全局作用域下的函数。
夜雨聆风