当前位置: 首页 > news >正文

《uni-app开发Harmony Next平台的App》第九篇:实战项目——打造一个集地图、定位和WebView通讯的鸿蒙App

uni-app开发Harmony Next平台的App——第九篇:实战项目——打造一个集地图、定位和WebView通讯的鸿蒙App

地图组件显示位置、内嵌H5页面通过JSBridge获取原生定位数据——这个场景在HarmonyOS NEXT开发里越来越常见。很多混合App都需要在地图上展示业务数据,同时内嵌一个H5运营页面,两者之间还要做数据交互。

这个功能本身不复杂,但真正麻烦的是三件事:地图组件的key配置、WebView和原生层的双向通讯、以及定位权限的生命周期管理。官方文档虽然提到了这些API,但没有解释实际使用中的限制和配合方式。

这篇直接用代码把这三个能力串起来,完成一个完整的示例:首页显示地图并获取用户位置,点击按钮后跳转到详情页,详情页内嵌H5页面并通过JSBridge把定位数据传给H5。

环境说明

DevEco Studio 版本:DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上 HBuilderX 版本:4.27 及以上 目标设备:HarmonyOS NEXT 真机 / 模拟器

先搞清楚这几件事

为什么需要WebView通讯

很多团队在迁移现有H5业务到Harmony App时,会遇到一个问题:H5页面想用原生的地图或定位能力,但H5自带的geolocation在WebView里不稳定。

方案对比:

方案优缺点
H5直接请求浏览器定位在WebView中精度差,部分设备不支持
原生定位后通过URL参数传值只适用于启动时传参,动态交互无法实现
原生定位后通过JSBridge传值实时通讯,支持双向调用,最稳定

实际项目里推荐用JSBridge方案。uni-app内置的WebView组件提供了evalJS方法和onMessage事件,刚好能做这个事。

项目功能拆解

  • 首页:使用map组件显示地图,调用getLocation获取当前经纬度
  • 跳转页:使用WebView组件加载本地H5页面
  • 通讯流程:点击WebView中的按钮→JSBridge触发postMessage→原生接收后通过evalJS传回定位数据

项目结构

pages/ ├── index/ # 首页:地图 + 定位 │ └── index.vue ├── webview/ # WebView通讯页 │ └── webview.vue hybrid/ └── html/ └── map-demo.html # WebView加载的H5页面

核心实现

第一步:配置腾讯地图key

在HarmonyOS NEXT上使用map组件,需要先配置腾讯地图key。这个很容易被忽略,直接贴配置代码:

编辑manifest.json,以源码方式打开,在app-plus节点下添加:

{"app-plus":{"distribute":{"sdkConfigs":{"maps":{"qqmap":{"key":"你的腾讯地图key"}}}}}}

注意:HBuilderX 4.31及以上版本可以在可视化界面中配置,不需要手动改json。

申请key时,域名白名单留空,因为当前页面协议不是http。后续版本调整后会支持配置。

第二步:首页——地图组件与定位

pages/index/index.vue

<template> <view class="content"> <map id="myMap" style="width: 100%; height: 500px;" :latitude="latitude" :longitude="longitude" :markers="markers" @markertap="handleMarkerTap" ></map> <view class="info"> <text>当前位置:</text> <text>{{ latitude }}, {{ longitude }}</text> </view> <button type="primary" @click="getCurrentLocation">刷新定位</button> <button type="default" @click="goWebView">打开内嵌H5页面</button> </view> </template> <script setup> import { ref } from 'vue' const latitude = ref(39.908860) const longitude = ref(116.397390) const markers = ref([]) // 获取定位 async function getCurrentLocation() { try { const res = await uni.getLocation({ type: 'gcj02', isHighAccuracy: true, highAccuracyExpireTime: 3000 }) latitude.value = res.latitude longitude.value = res.longitude // 更新标记点 markers.value = [{ id: 1, latitude: res.latitude, longitude: res.longitude, title: '当前位置', iconPath: '/static/location.png', width: 30, height: 30 }] } catch (err) { uni.showToast({ title: '定位失败', icon: 'none' }) console.error('getLocation err:', err) } } // 点击标记点事件 function handleMarkerTap(e) { uni.showToast({ title: `经纬度: ${e.detail.latitude}, ${e.detail.longitude}` }) } // 跳转到WebView页面,把定位数据作为参数传递 function goWebView() { const data = encodeURIComponent(JSON.stringify({ latitude: latitude.value, longitude: longitude.value, timestamp: Date.now() })) uni.navigateTo({ url: `/pages/webview/webview?location=${data}` }) } // 页面加载时自动定位 getCurrentLocation() </script>

关键点说明:

  • getLocationtype参数用gcj02,这是国内地图通用坐标系
  • isHighAccuracy设为true可以提升定位精度,但会增加耗电
  • highAccuracyExpireTime控制高精度模式超时时间,这里设了3秒
  • markers数组更新后地图会自动重绘,不需要手动调用

第三步:WebView页面——原生与H5通讯

pages/webview/webview.vue

<template> <view class="content"> <web-view ref="webviewRef" src="/hybrid/html/map-demo.html" @message="handleMessage" @loaded="handleLoaded" ></web-view> </view> </template> <script setup> import { ref, onLoad } from 'vue' const webviewRef = ref(null) let locationData = null // 接收首页传递的定位数据 onLoad((option) => { if (option.location) { try { locationData = JSON.parse(decodeURIComponent(option.location)) } catch (e) { console.error('参数解析失败', e) } } }) // WebView加载完成后,把定位数据传给H5 function handleLoaded() { if (locationData && webviewRef.value) { const jsCode = `window.setLocation(${JSON.stringify(locationData)})` webviewRef.value.evalJS(jsCode) } } // 接收H5传来的消息 function handleMessage(e) { const data = e.detail.data // data是数组,因为H5可能发送多条 if (Array.isArray(data)) { data.forEach(msg => { console.log('收到H5消息:', msg) if (msg.action === 'getLocation') { // H5请求定位数据,将数据传回 sendToWebView(locationData) } else if (msg.action === 'navigateBack') { uni.navigateBack() } }) } } // 通过evalJS向H5发送数据 function sendToWebView(data) { if (webviewRef.value) { const jsCode = `window.receiveLocation(${JSON.stringify(data)})` webviewRef.value.evalJS(jsCode) } } </script>

这套通讯机制的核心是:

  • @message监听H5通过uni.postMessage发送的消息
  • evalJS执行H5端定义的全局函数,把数据传回去
  • 页面加载时通过URL参数传递初始定位数据,减少延迟

第四步:H5页面中的JSBridge实现

hybrid/html/map-demo.html

<!DOCTYPEhtml><html><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>地图位置展示</title><style>*{margin:0;padding:0;box-sizing:border-box;}body{font-family:-apple-system,sans-serif;padding:20px;}.info{padding:20px;background:#f5f5f5;border-radius:8px;margin-bottom:20px;}.btn{padding:12px 20px;background:#07c160;color:#fff;border:none;border-radius:6px;font-size:16px;width:100%;}.btn:active{opacity:0.8;}#locationDisplay{font-size:14px;color:#666;margin-top:10px;}#mapContainer{width:100%;height:300px;background:#eee;border-radius:8px;margin-bottom:20px;}</style></head><body><h3>内嵌H5页面</h3><divid="mapContainer"><pstyle="padding:20px;text-align:center;color:#999;">地图加载中...</p></div><divclass="info"><p>定位数据:</p><pid="locationDisplay">等待获取...<p></div><buttonclass="btn"onclick="requestLocation()">请求原生定位数据</button><buttonclass="btn"style="background:#999;margin-top:10px;"onclick="goBack()">返回上一页</button><script>// 存储当前位置数据letcurrentLocation=null// 由原生端调用,设置初始定位数据window.setLocation=function(data){currentLocation=dataupdateDisplay(data)console.log('setLocation from native:',data)}// 由原生端调用,接收实时定位数据window.receiveLocation=function(data){currentLocation=dataupdateDisplay(data)console.log('receiveLocation from native:',data)}// 更新页面显示functionupdateDisplay(data){if(data){document.getElementById('locationDisplay').innerHTML=`纬度:${data.latitude}<br>经度:${data.longitude}<br>时间戳:${newDate(data.timestamp).toLocaleString()}`}}// H5主动请求定位数据(通过JSBridge)functionrequestLocation(){// 使用uni-app WebView的JSBridge协议if(window.uni&&typeofwindow.uni.postMessage==='function'){window.uni.postMessage({action:'getLocation',data:{}})}else{// 兜底:如果原生定好了直接显示if(currentLocation){updateDisplay(currentLocation)}else{alert('定位数据尚未获取到')}}}// 返回原生页面functiongoBack(){if(window.uni&&typeofwindow.uni.postMessage==='function'){window.uni.postMessage({action:'navigateBack',data:{}})}}// 页面加载时检查是否有缓存定位数据if(currentLocation){updateDisplay(currentLocation)}</script></body></html>

这里有一个隐藏细节:window.uni.postMessage是uni-app WebView注入的JSBridge对象,需要通过它发送消息到原生层。直接使用uni.postMessage就可以,不需要额外引入SDK。

踩坑记录

坑1:WebView的src路径问题

现象src="/hybrid/html/map-demo.html"在HarmonyOS真机上无法加载,页面空白。

原因:HarmonyOS NEXT平台的WebView不支持以/开头的绝对路径。需要换成相对路径或使用@/

解决方案

<web-view src="/hybrid/html/map-demo.html" <!-- 错误写法 --> src="../../hybrid/html/map-demo.html" <!-- 正确写法:相对路径 --> ></web-view>

坑2:postMessage接收数据格式

现象handleMessage方法中e.detail.data返回的是数组,但很多人误以为直接是对象。

原因:uni-app WebView的@message事件规定,H5发送的数据会封装为数组,每条消息一个元素。

解决方案:判断是数组后再遍历处理,不能直接使用e.detail.data.xxx

// 正确做法functionhandleMessage(e){constdataList=e.detail.dataif(Array.isArray(dataList)){dataList.forEach(msg=>{if(msg.action==='xxx'){...}})}}

坑3:定位权限弹窗只触发一次

现象:第一次运行会弹权限框,同意后关闭App再打开,getLocation仍然成功。但如果系统设置里关闭了定位,重新打开App不会再次弹框。

原因:鸿蒙的权限管理机制中,用户拒绝后不会自动重弹,需要主动引导用户去系统设置开启。

解决方案:在定位失败时判断错误码,如果是权限拒绝,通过uni.openAppAuthorizeSetting打开系统设置页。

asyncfunctiongetCurrentLocation(){try{// 定位逻辑...}catch(err){if(err.errCode===12){uni.showModal({title:'定位权限未开启',content:'请在设置中开启定位权限后重试',success:(res)=>{if(res.confirm){uni.openAppAuthorizeSetting()}}})}}}

最佳实践

1. 不要在onLoad里直接调用evalJS

原因:WebView组件可能有加载延迟,onLoad触发时页面可能还没渲染完毕。建议等@loaded事件触发后再执行JS。

2. 统一JSBridge消息格式

所有原生和H5之间的消息建议统一为{ action: string, data: any }格式,方便扩展和调试。

3. 定位数据传到WebView时做脱敏

正式项目里定位数据不应该直接暴露给H5,建议只传{ lat, lng }不传时间戳等信息。如果H5需要高频获取位置,可以在原生端做节流,避免频繁调用定位API。

FAQ

Q:为什么真机定位正常,模拟器获取不到位置?

A:HarmonyOS模拟器的定位功能是模拟的,默认返回北京天安门坐标(39.908860, 116.397390)。如果需要模拟其他位置,可以在模拟器设置中手动调整。

Q:WebView中postMessage发送的消息,原生收不到怎么办?

A:先确认H5页面中window.uni.postMessage是否可用。在WebView的@loaded事件触发后,可以通过evalJS执行一段脚本检测typeof window.uni是否存在。如果不存在,检查web-view组件的src路径和是否在子域名环境下。

Q:页面返回后,WebView的状态丢失了?

A:当前实现中,返回后WebView会销毁。如果需要保持状态,可以考虑用pageskeep-alive或者将定位数据存入全局状态(pinia/vuex),下次进入时自动恢复。

http://www.jsqmd.com/news/985822/

相关文章:

  • 使用k8s安装Sonarqube
  • Codex级产品!ToDesk AI 实测,用 Prompt 接管你的工作流
  • 2026年河北制造业企业如何被AI推荐:GEO优化与短视频获客完全实战指南 - 年度推荐企业名录
  • 超声波液位差计多少钱?2026年主流品牌价格体系与选型价值深度解析 - 仪表品牌排行榜
  • 专业的义乌做墨西哥货代推荐
  • 【无人机】基于matlab多架悬挂缆绳无人机协同有效载荷提升【含Matlab源码 15606期】
  • 阿坝藏族羌族自治州2026年5月最新黄金回收白银回收铂金回收权威排行榜TOP5:纯金+金条+银条+钯金门店地址联系方式推荐 - 千叶啊
  • 邯郸市2026年黄金回收白银回收铂金回收放心选真心推荐靠谱门店排行+联系电话整理 - 干豆腐啊
  • 鞍山市2026年5月最新黄金回收白银回收铂金回收权威排行榜TOP5:纯金+金条+银条+钯金门店地址联系方式推荐 - 千叶啊
  • Oracle与HP红蓝聚首之后:数据库一体机赛道的风云变幻
  • 2026重庆黄金回收门店综合榜单,闲置黄金置现避坑全攻略 - 奢侈品回收测评
  • 【毕业设计】基于springboot+微信小程序的智能停车场管理系统小程序基于微信小程序的智能停车场管理系统(源码+文档+远程调试,全bao定制等)
  • 陇南市2026年本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 千叶啊
  • Linux环境下Apache Web服务器部署与配置指南
  • 网络经纪人助手口碑实测:合规性与服务能力全维度评测 - 奔跑123
  • 【2026版】史上最新最全面的大模型面经,面试顺利通关
  • 2026年值得信赖的新加坡留学机构:五家优选深度解析 - 科技焦点
  • 2026年6月铝圆片厂家怎么选?5家靠谱生产企业横向测评对比 - 外贸老黄
  • 小红书矩阵运营,正在悄悄改变内容行业
  • 冥想第一千九百零五天(1905)
  • 消消乐Java代码一部分——方块移动窗口
  • 2026 汕头厨卫屋面地下室漏水瓷砖空鼓测评:吉修匠 99.8 分五星榜首 - 吉修匠
  • APP盲盒源码V6MAX:品牌自营平台搭建方案 - 壹软科技
  • 靠谱车衣工厂怎么挑?五大源头厂家实力拆解
  • 谷歌 GEO vs 传统 SEO!出海营销从业者必看的流量转型指南
  • 【课程设计/毕业设计】基于springboot+微信小程序的演唱会售票系统小程序【附源码、数据库、万字文档】
  • laravel的Blade 的源码解读的庖丁解牛牛
  • Ceph分布式存储核心知识点与实验总结
  • 115、FFT在飞控中的应用:振动分析
  • TSN恶劣环境鲁棒性测试全攻略:从实验室到工业现场的确定性验证