《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>
关键点说明:
getLocation的type参数用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:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="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>
<div id="mapContainer">
<p style="padding: 20px; text-align: center; color: #999;">地图加载中...</p>
</div>
<div class="info">
<p>定位数据:</p>
<p id="locationDisplay">等待获取...<p>
</div>
<button class="btn" onclick="requestLocation()">请求原生定位数据</button>
<button class="btn" style="background: #999; margin-top: 10px;" onclick="goBack()">返回上一页</button>
<script>
// 存储当前位置数据
let currentLocation = null
// 由原生端调用,设置初始定位数据
window.setLocation = function(data) {
currentLocation = data
updateDisplay(data)
console.log('setLocation from native:', data)
}
// 由原生端调用,接收实时定位数据
window.receiveLocation = function(data) {
currentLocation = data
updateDisplay(data)
console.log('receiveLocation from native:', data)
}
// 更新页面显示
function updateDisplay(data) {
if (data) {
document.getElementById('locationDisplay').innerHTML =
`纬度: ${data.latitude}<br>经度: ${data.longitude}<br>时间戳: ${new Date(data.timestamp).toLocaleString()}`
}
}
// H5主动请求定位数据(通过JSBridge)
function requestLocation() {
// 使用uni-app WebView的JSBridge协议
if (window.uni && typeof window.uni.postMessage === 'function') {
window.uni.postMessage({
action: 'getLocation',
data: {}
})
} else {
// 兜底:如果原生定好了直接显示
if (currentLocation) {
updateDisplay(currentLocation)
} else {
alert('定位数据尚未获取到')
}
}
}
// 返回原生页面
function goBack() {
if (window.uni && typeof window.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。
// 正确做法
function handleMessage(e) {
const dataList = e.detail.data
if (Array.isArray(dataList)) {
dataList.forEach(msg => {
if (msg.action === 'xxx') { ... }
})
}
}
坑3:定位权限弹窗只触发一次
现象:第一次运行会弹权限框,同意后关闭App再打开,getLocation仍然成功。但如果系统设置里关闭了定位,重新打开App不会再次弹框。
原因:鸿蒙的权限管理机制中,用户拒绝后不会自动重弹,需要主动引导用户去系统设置开启。
解决方案:在定位失败时判断错误码,如果是权限拒绝,通过uni.openAppAuthorizeSetting打开系统设置页。
async function getCurrentLocation() {
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会销毁。如果需要保持状态,可以考虑用pages的keep-alive或者将定位数据存入全局状态(pinia/vuex),下次进入时自动恢复。
更多推荐



所有评论(0)