明明是原生 App,为什么你硬要塞个 H5?——鸿蒙 Web 组件混合开发实战

明明是原生 App,为什么你硬要塞个 H5?——鸿蒙 Web 组件混合开发实战
大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~

本文目录:

前言

说句实在话,只写原生的人,迟早要面对 H5;只写 H5 的人,也迟早会被拉去接原生。不管你情不情愿,这年头“Hybrid 应用”已经是常态——活动页是前端同学的一套 Vue/React,主流程又是原生 ArkUI;老板一句“这个网页能不能直接嵌进去?”你就懂了:跑不掉了。

好消息是:鸿蒙给我们准备好了 Web 组件,用来做内嵌网页、Hybrid 混合开发。一套视图里,你既可以用 ArkTS 写原生 UI,又能在一个区域里嵌一整块 H5,甚至还能 ArkTS ↔ JS 互相发消息,做登录、分享、设备能力调用这一整套联动。

但是啊,真要落地的时候,问题就出来了:

  • Web 组件到底有哪些 API?能不能控制前进、后退、注入 JS?
  • JS 到底怎么跟 ArkTS 说话?Native 又怎么把数据塞给网页?
  • 本地 html / 资源加载老报错:404、跨域、白屏、缓存问题一大堆?
  • 做 Hybrid 项目到底怎么设计结构,才能不变成“奇怪的大杂烩”?

今天就来好好聊聊这件事:
鸿蒙 Web 组件:嵌入网页与混合开发的完整实践。

一、Web 组件 API:先把“浏览器这块砖”掌握住

鸿蒙里用 Web 做嵌入网页,核心是 ArkUI 的 <Web> 组件 + WebController 控制对象

1.1 最小可用 Web 组件示例

先来个最小 demo,有个感觉:

import webview from'@ohos.web.webview';@Entry@Component struct WebDemoPage {private controller: webview.WebviewController =newwebview.WebviewController();build(){Column(){// 顶部简单导航栏Row(){Button('Back').onClick(()=>{if(this.controller?.canGoBack()){this.controller.goBack();}})Button('Reload').margin({ left:8}).onClick(()=>this.controller.reload())}.margin({ top:8, left:12})// Web 区域Web({ src:'https://www.example.com', controller:this.controller }).javaScriptAccess(true)// 允许 JS.zoomAccess(true)// 允许缩放.domStorageAccess(true)// 允许 DOM Storage.width('100%').height('100%')}.width('100%').height('100%')}}

几个关键点:

  • Web({ src, controller })
    • src 可以是远程 URL,也可以是本地 html
    • controller 用来做“浏览器导航控制”
  • .javaScriptAccess(true):不开这个,很多 H5 功能直接废掉
  • .domStorageAccess(true):涉及 localStorage / sessionStorage 的页面要开
  • .zoomAccess(true):是否允许手势缩放(视业务看要不要)

可以把它理解成:ArkUI 里嵌了一个小浏览器窗口,你再用控制器来对它下命令。


1.2 WebviewController 能干啥?

一般会这么写:

private controller: webview.WebviewController =newwebview.WebviewController();

常用 API 大概这些(只列最常用的,日常开发够用):

  • loadUrl(url: string):动态加载网页
  • reload():刷新当前页
  • canGoBack() / goBack():后退
  • canGoForward() / goForward():前进
  • stop():停止加载
  • zoomIn() / zoomOut():缩放
  • runJavaScript(script: string, callback?):执行 JS

比如点击按钮动态切换 H5 页面:

Button('打开活动页').onClick(()=>{this.controller.loadUrl('https://m.xxx.com/promo/2025-11-11');})

或者:

Button('调用 H5 函数').onClick(()=>{this.controller.runJavaScript('window.fromNative && window.fromNative("hello")');})

至此,你就有了一个“听得懂你指挥”的内嵌网页。


二、JS ↔ ArkTS 双向交互:Hybrid 的灵魂

只嵌页面不交互,其实只能算“简单内嵌”;
真正的 Hybrid,一定离不开:

  • ArkTS 调 JS:比如通知网页登录结果、传 token、传用户信息
  • JS 调 ArkTS:比如让原生弹出分享、打开扫码、调原生支付

2.1 ArkTS 调 JS:用 runJavaScript 注入

刚刚提过的 runJavaScript,是最直接、最常用的方式:

ArkTS 侧:

sendUserInfoToWeb(user:{ id:string; nickname:string}){const json =JSON.stringify(user);const script =`window.onNativeUserInfo && window.onNativeUserInfo(${json})`;this.controller.runJavaScript(script);}

H5 页面中:

// 在 H5 js 里 window.onNativeUserInfo=function(user){ console.log('收到来自原生的用户信息:', user)// 比如更新页面 UI document.getElementById('nickname').innerText = user.nickname }

这种方式非常直接:ArkTS 拼出一段 JS 字符串,让 Webview 去执行
适合“简单调用 + 传少量参数”。


2.2 JS 调 ArkTS:建立“消息通道”

从网页回调到 ArkTS,通常会有两种思路:

  1. URL 拦截
    • JS 修改 location,例如 myapp://doSomething?xxx=yyy
    • ArkTS 侧通过 Web 的 URL 拦截回调解析
  2. 消息机制(推荐)
    • 通过 webview 的消息机制派发数据

下面以一种常见的“约定 URL 协议 + 拦截”方式举例(思路清晰易懂):

H5 页面这样调用:

functioncallNativeShare(data){const encoded =encodeURIComponent(JSON.stringify(data)) window.location.href =`myapp://share?data=${encoded}`}

ArkTS 侧监听 URL 变化(具体 API 名称依版本有所差异,这里用伪代码表达下思路):

Web({ src:this.url, controller:this.controller }).onUrlLoad((event)=>{const url = event.url;if(url.startsWith('myapp://')){this.handleJsBridge(url);// 阻止 Webview 继续加载这个“假页面” event.preventDefault && event.preventDefault();}})

handleJsBridge 解析协议:

handleJsBridge(url:string){// myapp://share?data=xxxconst[schemaPart, queryPart]= url.split('?');if(schemaPart ==='myapp://share'){const searchParams =newURLSearchParams(queryPart);const data = searchParams.get('data');if(data){const payload =JSON.parse(decodeURIComponent(data));this.doShare(payload);}}}

doShare 就是原生侧的分享逻辑:跳系统分享、调 SDK 都可以。

这种写法优点:

  • 前端同学很好理解,只要改 URL 就行
  • ArkTS 这边只要解析 URL 即可
  • 不容易乱七八糟

当然,如果后续用到官方提供的 MessagePort、WebMessage 之类更正式的通道也可以,思路是一样的:双方约定协议 + 明确函数名 + 数据格式 JSON 化


2.3 一份常用交互约定建议

为了不搞成一团糊,建议团队约定个简单的“JSBridge 协议”,比如:

  • JS → Native:myapp://action?data=xxx
    • action:动作名称,比如 login, share, openScanner
    • data:JSON 字符串,需 encodeURIComponent
  • Native → JS:统一通过 window.onNativeMessage && window.onNativeMessage({ type, payload })

这样你的通信就会统一清晰得多:

ArkTS:

sendToWeb(type:string, payload:any){const json =JSON.stringify({ type, payload });const script =`window.onNativeMessage && window.onNativeMessage(${json})`;this.controller.runJavaScript(script);}

H5:

window.onNativeMessage=function(msg){switch(msg.type){case'loginSuccess':// ...breakcase'refreshToken':// ...break}}

三、资源加载问题:本地 html、图片、缓存那些坑

实际项目里,很少所有内容都在远端,有很多 Hybrid 场景会用到:

  • 本地打包 html 页面(比如某个内置帮助页、离线页面)
  • H5 里引用的静态资源(图片、css、js)
  • 缓存 / 刷新 / 404 / 白屏 等问题

3.1 本地 html 加载

假设你在工程 resources/rawfile 下放了一个 help/index.html

项目结构大概这样:

entry ├── src └── resources └── rawfile └── help └── index.html 

ArkTS 里可以这样加载(不同版本写法略有区别,示意):

import webview from'@ohos.web.webview';import resourceManager from'@ohos.resourceManager';@Entry@Component struct LocalHtmlPage {private controller: webview.WebviewController =newwebview.WebviewController();aboutToAppear(){// 有些版本支持直接写特殊协议,如 'file://…' 或 'rawfile://help/index.html'this.controller.loadUrl('rawfile://help/index.html');}build(){Web({ src:'', controller:this.controller }).javaScriptAccess(true).width('100%').height('100%')}}

如果你遇到“本地 html 里的 js/css 404”,别慌:

  • 检查资源路径是否相对正确(./js/main.js vs /js/main.js
  • 确保 html 所在路径和资源目录结构对应
  • 有些场景下需要使用 loadData 来加载 html 字符串与 baseUrl

3.2 远程资源加载与跨域

常见问题:

  1. H5 引用了第三方接口,报跨域
  2. 一些图片 / 脚本走 http 而页面是 https,会有“混合内容”警告
  3. Cookie / Storage 导致登录态混乱

一般 Hybrid 项目里,这些应该让前端统一配置:

  • 尽量全站 https
  • 域名统一(或 CORS 设置完整)
  • 不在 H5 里随便写死业务域名,而是通过配置或注入方式

ArkTS 这边需要注意的更多是:

  • 不乱把 Web 当浏览器那样打开任何地址
  • 对加载失败提供 fallback 页面或错误提示

3.3 缓存与刷新

很多 Hybrid 活动页会遇到一个经典问题:

“我们后台刚改完 H5,为什么用户还看到旧版本?”

因为缓存。

几种常见手段:

  • H5 侧给静态资源加版本号:main.js?v=20251114
  • 活动页面 URL 自身也带上版本:/promo/2025?v=2
  • ArkTS 这边在必要时调用 controller.reload() 强刷

更激进一点:

  • 为调试提供一个隐藏长按入口:长按某个区域 3 秒 → 出现“清缓存 / 刷新 H5 配置”的选项

四、Hybrid 应用开发案例:一套“原生 + H5 活动中心”的组合拳

说了这么多,咱们用一个稍微贴近实战的案例,把整个流程串一下:

场景:
你在做一个电商类 App,首页、商品详情、购物车等都是 ArkUI 原生写的。
运营同学三天两头丢一个“营销活动 H5 地址”给你,要你接入:需要在 App 里打开要能拿到用户登录态H5 里点击“立即分享”要调原生分享有时要调起原生支付

这是非常典型的 Hybrid 活动中心 场景。


4.1 页面结构设计

可以有一个原生页面:

  • 标题栏:原生
  • 网页区域:Web 组件
  • 底部可以视情况扩展一些原生按钮(比如“关闭”、“回到首页”)
@Entry@Component struct HybridActivityPage {private controller: webview.WebviewController =newwebview.WebviewController();private url:string='';aboutToAppear(){// 假设路由带进来一个活动地址const params = router.getParams()as Record<string,string>;this.url = params?.url ??'';}build(){Column(){// 原生头部Row(){Button('< 返回').onClick(()=>{if(this.controller.canGoBack()){this.controller.goBack();}else{ router.back();}})Text('活动页面').fontSize(18).margin({ left:12})}.height(48).padding({ left:12, right:12}).backgroundColor(0xFFFFFF)// Web 区域Web({ src:this.url, controller:this.controller }).onPageEnd((res)=>{console.info('页面加载完成',JSON.stringify(res));}).width('100%').height('100%')}.width('100%').height('100%')}}

4.2 登录态注入:ArkTS → JS

用户登录在原生侧完成,H5 又想知道当前用户是谁,这个场景非常普遍。

进入活动页后,可以这样做:

ArkTS:

aboutToAppear(){// 省略获取 url 逻辑// 页面加载完后再注入用户信息this.controller.on('pageEnd',()=>{const user ={ id:'u123', token:'xxxx', nick:'Mark'};const json =JSON.stringify(user);const script =`window.onNativeLogin && window.onNativeLogin(${json});`;this.controller.runJavaScript(script);});}

H5:

window.onNativeLogin=function(user){// 存本地,后续接口请求带上 localStorage.setItem('token', user.token)renderUserName(user.nick)}

这样就完成了最基础的 Hybrid 登录态打通。


4.3 JS 调原生分享

H5 里很多“分享活动给好友”按钮,其实都希望调用 App 原生的分享面板,而不是简单用 H5 的那套。

H5 侧统一这样调用:

functiontriggerShare(){const payload ={title:'双十一神券大放送',desc:'全场满 199 减 100',url: window.location.href,icon:'https://xxx.com/promo.png'}const encoded =encodeURIComponent(JSON.stringify(payload)) window.location.href =`myapp://share?data=${encoded}`}

ArkTS 侧拦截:

Web({ src:this.url, controller:this.controller }).onUrlLoad((event)=>{const url = event.url;if(url.startsWith('myapp://')){this.handleBridge(url); event.preventDefault && event.preventDefault();}})handleBridge(url:string){const[schemaPart, queryPart]= url.split('?');const params =newURLSearchParams(queryPart);const data = params.get('data');const payload = data ?JSON.parse(decodeURIComponent(data)):null;if(schemaPart ==='myapp://share'&& payload){this.doShare(payload);}}doShare(payload:{ title:string; desc:string; url:string; icon?:string}){// 这里调系统分享 / 三方 SDK / 自家分享面板都可console.info('执行原生分享:',JSON.stringify(payload));}

这样,H5 → Native 的动作就非常顺了。


4.4 Hybrid 工程结构建议

为了不让项目乱成一锅粥,结构上建议这样:

entry/src/main/ets ├── ability │ └── MainAbility.ets ├── pages │ ├── HomePage.ets │ ├── ProductDetailPage.ets │ └── HybridActivityPage.ets // 专门承载 H5 ├── hybrid │ ├── bridge │ │ └── JsBridgeHandler.ets // 统一处理 URL 协议、消息分发 │ └── config │ └── HybridConfig.ets // 白名单域名、特殊路由策略等 ├── services │ └── api └── ... resources └── rawfile └── hybrid └── offline.html // 断网时展示的本地 H5 

几条原则:

  • ArkTS 与 JS 交互逻辑集中管理hybrid/bridge
  • 域名白名单 / 特殊跳转策略单独配置 → 防止随便加载未知地址
  • 离线兜底页(offline html)单独放 rawfile
  • Web 组件最好封装成一个复用组件,例如 HybridWebView,不要在每个页面重复写乱七八糟的逻辑

最后一点小感慨

混合开发这东西,说简单也简单,说复杂也复杂。简单是因为技术上无非就那几个点:
嵌 Web、控导航、搞交互、管资源。
复杂是因为你得协调:
前端、客户端、服务端、运营的各种诉求,既要“随时能改 H5”,又要“体验接近原生”。

鸿蒙给了我们一个不错的工具:Web 组件 + WebviewController + 通信能力,剩下的就看你怎么把这些拼成一个有秩序、好维护的 Hybrid 体系。

如果你能把今天这几块思路吃透:

  • Web 组件 API:怎么嵌、怎么控、能做哪些事
  • JS ↔ ArkTS 通信:怎么设计协议、怎么落地实现
  • 本地 / 远程资源加载要注意什么、怎么防坑
  • Hybrid 案例:登录态、分享、活动中心怎么串起来

那你在鸿蒙这边搞 Hybrid,基本就算是“入门 + 实战”一条龙了。

如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~

Read more

GLM-4-9B-Chat-1M部署教程:vLLM多模型路由+Chainlit前端动态切换演示

GLM-4-9B-Chat-1M部署教程:vLLM多模型路由+Chainlit前端动态切换演示 1. 为什么需要部署GLM-4-9B-Chat-1M这样的大模型 你有没有遇到过这样的场景:要翻译一份长达50页的技术文档,中间还夹杂着大量专业术语和图表说明;或者需要从一份百页合同里精准定位某一条款的法律效力描述;又或者想让AI帮你分析整本产品需求文档,找出所有潜在的逻辑矛盾点?传统大模型在处理这类任务时往往力不从心——要么直接报错“上下文超限”,要么关键信息在长文本中“消失”得无影无踪。 GLM-4-9B-Chat-1M就是为解决这个问题而生的。它不是普通的大语言模型,而是真正能“吞下整本书”的长文本专家。支持100万token上下文长度(约200万中文字符),相当于一次性读完三本《三体》全集还能准确回答细节问题。更难得的是,它不只是“能装”,还“装得明白”——在LongBench-Chat等权威长文本评测中表现优异,证明它不仅能记住海量信息,更能理解、推理和精准提取。 但光有强大能力还不够。实际使用中,我们常面临两个现实难题:一是单个模型服务难以兼顾不同任务需求(比如有时要快

【FastapiAdmin V2.0.0】一套现代、开源、全栈融合的中后台快速开发平台,后端采用Fastapi + SQLAlchemy,前端采用基于 Vue3 + Typescript

【FastapiAdmin V2.0.0】一套现代、开源、全栈融合的中后台快速开发平台,后端采用Fastapi + SQLAlchemy,前端采用基于 Vue3 + Typescript

FastapiAdmin v2.0.0 一套现代、开源、全栈融合的中后台快速开发平台,给个⭐️支持一下 📘 项目介绍(作者:@1014TaoTao) FastapiAdmin 是一套 完全开源、高度模块化、技术先进的现代化快速开发平台,旨在帮助开发者高效搭建高质量的企业级中后台系统。该项目采用 前后端分离架构,融合 Python 后端框架 FastAPI 和前端主流框架 Vue3 实现多端统一开发,提供了一站式开箱即用的开发体验。 代码地址: github:https://github.com/1014TaoTao/FastapiAdmin giee:https://gitee.com/tao__tao/FastapiAdmin gitcode: https://gitcode.com/qq_36002987/fastapi_vue3_

Qwen3-VL-WEBUI建筑图纸生成:从草图到CAD转换实战

Qwen3-VL-WEBUI建筑图纸生成:从草图到CAD转换实战 1. 引言:AI驱动建筑设计的范式变革 1.1 业务场景描述 在建筑设计领域,设计师常常需要将手绘草图快速转化为标准CAD图纸。传统流程依赖人工识图与AutoCAD手动重绘,耗时长、成本高、易出错。尤其在方案初期频繁迭代阶段,这一瓶颈尤为突出。 随着多模态大模型的发展,视觉-语言模型(VLM) 正在成为打通“人→图→机”闭环的关键技术。阿里云最新发布的 Qwen3-VL-WEBUI 提供了一套开箱即用的解决方案,能够实现从手绘草图到结构化图纸代码的端到端生成,极大提升设计自动化水平。 1.2 痛点分析 当前主流做法存在三大痛点: - 识别精度低:传统OCR和图像识别难以理解建筑符号语义 - 结构化输出缺失:无法直接生成可编辑的CAD或Draw.io格式 - 交互效率差:缺乏自然语言指令控制能力,修改困难 而 Qwen3-VL-WEBUI 凭借其强大的视觉编码能力和空间感知机制,为解决上述问题提供了全新路径。 1.3

前端如何实现 [记住密码] 功能

前端如何实现 [记住密码] 功能

文章目录 * 一、核心实现原理:不是记住,而是“提示填充” * 二、技术实现方案详解 * 方案一:依赖浏览器原生行为(最常用) * 方案二:前端持久化存储(需谨慎考虑) * 三、安全考量与实践准则 * 四、最佳实践总结 我们在访问网站的时候,发现很多的登录页面都是有记住密码的功能的。 如gitee码云的登录页面: 一、核心实现原理:不是记住,而是“提示填充” 首先要澄清一个常见的误解:前端的“记住密码”功能通常并不直接存储你的密码明文。它的核心原理是:请求浏览器将账号密码保存到其密码管理器中,并在下次检测到对应登录表单时,自动或提示用户填充。 下图清晰地展示了这一核心流程: 服务器浏览器密码管理器登录表单用户服务器浏览器密码管理器登录表单用户首次登录与保存后续自动填充1. 输入账号密码,勾选“记住我”2. 提交表单,发送登录请求3. 返回登录成功响应4. 触发浏览器提示:“是否保存密码?”5. 用户点击“保存”6. 将账号、