跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

UniApp WebView 与 H5 双向通信实战指南

UniApp 开发中 WebView 是连接原生与 H5 的关键。实现双向通信需处理参数传递、消息监听及结果回传。核心方案为 H5 通过 uni.webView.postMessage 发送指令,UniApp 端利用 evalJS 执行全局函数回传数据。需注意条件编译区分 App 与 H5 逻辑,App 端获取 webview 实例需延时,Token 传递建议拼接 URL。调试时注意本地 IP 配置及权限声明,确保跨端交互稳定可靠。

极客零度发布于 2026/4/12更新于 2026/10/2599 浏览
UniApp WebView 与 H5 双向通信实战指南

UniApp WebView 与 H5 双向通信实战指南

在 UniApp 开发里,WebView 是连接原生应用和 H5 页面的桥梁。特别是需要复用现有 Web 页面或集成复杂交互时,这块能力必不可少。今天结合实际案例,聊聊怎么实现 UniApp(App/H5)和内嵌 H5 的双向通信,顺便封装几个常用的原生调用,比如扫码、打电话、蓝牙打印这些。

核心需求与技术背景

通常我们需要在 UniApp 里嵌入 H5,并搞定这几件事:

  1. H5 触发原生能力(扫码、拨号、蓝牙、上传)。
  2. 原生处理完把结果回传给 H5。
  3. 兼容 App 和 H5 端的逻辑差异。
  4. 给 H5 透传鉴权 Token 等参数。

整体思路

参数传递上,UniApp 打开 WebView 时直接拼接到 URL 里传 Token。 H5 到 UniApp 这边,用 uni.webView.postMessage 发指令,UniApp 监听 message 事件接收。 UniApp 到 H5 这边,通过 evalJS 执行 H5 的全局函数来回传结果。 至于端兼容,靠条件编译区分 App 和 H5 的处理逻辑。

代码实现详解

UniApp 端:WebView 容器

这里有个坑要注意,App 端获取 webview 实例不能马上取,得延时一下确保创建完成。

<template>
  <view style="position: relative;">
    <web-view :src="reportSrc" :webview-styles="webviewStyles" @message="message"></web-view>
  </view>
</template>
<script>
import { getToken } from '@/utils/auth';
import cameraTool from '../mixins/camera-tool.vue';

var wv; // WebView 实例

export default {
  mixins: [cameraTool],
  data() {
    return {
      webviewStyles: { width: '100%', height: '100%' },
      reportSrc: ''
    };
  },
  onLoad(option) {
    const eventChannel = this.getOpenerEventChannel();
    eventChannel.on('acceptDataFromOpenerPage', (data) => {
      this.reportSrc = this.addQueryParam(data.url, `token=${getToken()}`);
      uni.setNavigationBarTitle({ title: data.title });
    });

    #ifdef APP-PLUS
    this.initAppWebView();
    #endif
    #ifdef H5
    this.initH5Listener();
    #endif
  },
  methods: {
    initAppWebView() {
      uni.showLoading({ title: '加载中' });
      var currentWebview = this.$scope.$getAppWebview();
      setTimeout(() => {
        wv = currentWebview.children()[0];
        uni.hideLoading();
      }, 1000);
    },
    initH5Listener() {
      window.addEventListener('message', (e) => {
        console.log('接收 H5 消息', e.data);
      }, false);
    },
    addQueryParam(url, param) {
      return url.includes('?') ? `${url}&${param}` : `${url}?${param}`;
    },
    message(e) {
      const data = e.detail.data[0];
      const { type, info, options } = data;
      const actions = {
        'scanCode': () => this.handleScan(info),
        'phoneCall': () => this.handlePhoneCall(info),
        'bluetoothPrint': () => this.handleBluetoothPrint(info),
        'chooseImage': () => this.handleChooseImage(options),
        'chooseVideo': () => this.handleChooseVideo(options),
        'logout': () => this.handleLogout()
      };
      if (actions[type]) {
        actions[type]();
      } else {
        console.warn('未知操作类型:', type);
      }
    },
    handleScan() {
      uni.scanCode({
        success: (res) => {
          wv.evalJS(`window.scanCodeFromUniApp('${res.result}')`);
        }
      });
    },
    handlePhoneCall(phoneNumber) {
      uni.makePhoneCall({ phoneNumber: phoneNumber || '114' });
    },
    handleBluetoothPrint(printData) {
      uni.navigateTo({ url: `/pages/common/blueTooth/index?printData=${JSON.stringify(printData)}` });
    },
    async handleChooseImage(options) {
      try {
        const imageUrl = await this.uploadImage(options);
        wv.evalJS(`window.chooseImageFromUniApp(${JSON.stringify(imageUrl)})`);
      } catch (error) {
        console.error('图片上传失败', error);
      }
    },
    async handleChooseVideo(options) {
      try {
        const videoUrl = await this.uploadVideo(options);
        wv.evalJS(`window.chooseVideoFromUniApp(${JSON.stringify(videoUrl)})`);
      } catch (error) {
        console.error('视频上传失败', error);
      }
    },
    submit() {
      const data = { name: 'wft', timestamp: Date.now() };
      wv.evalJS(`window.msgFromUniApp(${JSON.stringify(data)})`);
    }
  }
};
</script>

H5 端:页面结构与通信

H5 侧需要引入桥接文件,并准备好接收回调的全局函数。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
  <div id="app"></div>
  <!-- 关键:引入 uni.webView 桥接文件 -->
  <script src="./uni.webview.js" type="text/javascript"></script>
  <script type="text/javascript">
    document.addEventListener("UniAppJSBridgeReady", function () {
      uni.webView.getEnv(function (res) {
        console.log("当前环境:", res);
      });
    });

    window.msgFromUniApp = function (val) {};
    window.scanCodeFromUniApp = function (val) {};
    window.chooseImageFromUniApp = function (val) {};
    window.chooseVideoFromUniApp = function (val) {};
  </script>
</body>
</html>

业务页面里,我们重写这些全局回调来更新 UI。

<template>
  <div class="hello">
    <h3>H5 内嵌页面</h3>
    <van-field v-model="scanCode" label="扫一扫" placeholder="扫码结果">
      <template #button>
        <van-button type="primary" size="small" @click="handleScan"> 扫一扫 </van-button>
      </template>
    </van-field>
    <van-field v-model="phone" label="拨打电话">
      <template #button>
        <van-button type="primary" size="small" @click="makePhoneCall"> 拨打 </van-button>
      </template>
    </van-field>
    <div class="section">
      <van-button type="primary" size="small" @click="handleBluetoothPrint"> 蓝牙打印 </van-button>
    </div>
    <div class="section">
      <div class="preview">
        <img v-for="item in fileList" :key="item.url" :src="item.url" />
      </div>
      <van-button type="primary" size="small" @click="handleUploadImage"> 上传图片 </van-button>
    </div>
    <div class="section">
      <div class="preview">
        <video v-for="item in videoList" :key="item.url" :src="item.url" controls />
      </div>
      <van-button type="primary" size="small" @click="handleUploadVideo"> 上传视频 </van-button>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      scanCode: '',
      phone: '13800138000',
      fileList: [],
      videoList: []
    };
  },
  mounted() {
    window.scanCodeFromUniApp = (val) => {
      this.scanCode = val;
    };
    window.chooseImageFromUniApp = (val) => {
      this.fileList = this.fileList.concat(val);
    };
    window.chooseVideoFromUniApp = (val) => {
      this.videoList = this.videoList.concat(val);
    };
    window.msgFromUniApp = (val) => {
      console.log('App 主动消息:', val);
    };
  },
  methods: {
    postMessage(type, payload = {}) {
      if (!window.uni || !window.uni.webView) {
        console.error('uni.webView 未就绪');
        return;
      }
      window.uni.webView.postMessage({ data: { type, ...payload } });
    },
    handleScan() {
      this.postMessage('scanCode');
    },
    makePhoneCall() {
      this.postMessage('phoneCall', { info: this.phone });
    },
    handleBluetoothPrint() {
      this.postMessage('bluetoothPrint', { info: [11, 22] });
    },
    handleUploadImage() {
      this.postMessage('chooseImage', {
        options: {
          maxNumber: 2,
          sourceTypeIndex: 3,
          sizeTypeIndex: 2,
          url: 'http://your-server/upload',
          bucketName: 'jingsheng'
        }
      });
    },
    handleUploadVideo() {
      this.postMessage('chooseVideo', {
        options: {
          maxDuration: 60,
          sourceTypeIndex: 3,
          url: 'http://your-server/upload',
          bucketName: 'jingsheng'
        }
      });
    }
  }
};
</script>

避坑指南

实际踩坑的时候,这几个点得留神:

  1. 本地调试:H5 用 Live Server 打开时,地址换成局域网 IP(如 192.168.x.x),别用 127.0.0.1,手机扫不到。
  2. 实例获取:App 端拿 webview 实例必须延时 1 秒左右,不然可能拿到空的。
  3. 数据序列化:evalJS 传复杂对象记得 JSON.stringify,不然容易语法报错。
  4. 端兼容:App 用 evalJS,H5 用 window.postMessage,条件编译别搞混了。
  5. 权限配置:manifest.json 里要把扫码、电话等权限配好,不然会闪退。
  6. 版本要求:App 端使用的 uni.webview.js 版本需匹配,建议查阅官方最新文档确认兼容性。

总结

UniApp 和 H5 双向通信的核心在于:H5 发 postMessage,UniApp 收消息后 evalJS 回传。 一定要做好条件编译,App 依赖 webview 实例,H5 依赖 window 对象。 细节决定成败,IP 替换、参数序列化、权限配置这些都得到位,跨端通信才能稳。

这套方案下来,既能复用 Web 成果,又能发挥原生优势,开发体验比较顺畅。

目录

  1. UniApp WebView 与 H5 双向通信实战指南
  2. 核心需求与技术背景
  3. 整体思路
  4. 代码实现详解
  5. UniApp 端:WebView 容器
  6. H5 端:页面结构与通信
  7. 避坑指南
  8. 总结

更多推荐文章

查看全部
  • π0 源码剖析:基于 PaLI-Gemma 的扩散策略与 C/S 架构部署
  • LLaMA-Factory 大模型微调实战指南
  • IPSG 静态绑定配置:IP 与 MAC 地址绑定
  • 程序员为何需要了解 GPT 并掌握 AI 大模型构建技术
  • 前端开发核心基础:HTML、CSS 与 JavaScript 实战入门
  • 环境模型在 DQN 中的应用:预测与规划机制解析
  • Apache SeaTunnel Web 部署及可视化数据集成实践
  • 大模型落地困境分析与解决思路
  • C++ 基础实战:从循环控制到算法入门
  • JVM 运行时数据区域详解:从内存结构到面试考点
  • Llama3 个人电脑本地部署与 WebUI 使用指南
  • Vue3 前端开发配置指南:VSCode settings.json 与 Prettier
  • 2026 年 3 月全球大模型全景:国产登顶、百万上下文与智能体爆发
  • Java 渲染性能优化:15 个提升界面流畅度的技巧
  • COT思维链技术:让AI学会拆解复杂问题
  • 人工智能与大语言模型学习指南
  • Java 面试题及答案汇总
  • 从零构建并训练基于 BERT 架构的生成式大模型
  • GPU/TPU/FPGA 异构算力成本健康度审计与优化实践
  • C 语言指针与数组的深层关系及实战
  • 相关免费在线工具

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

    • JavaScript 压缩与混淆

      Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online