> 技术文档 > UniApp 加载 Web 页面完整解决方案_uniwebview

UniApp 加载 Web 页面完整解决方案_uniwebview


背景与需求

在 UniApp 开发过程中,我们经常需要加载 H5 页面来展示复杂的业务内容,比如审批流程、表单填写、数据展示等。传统方案是使用原生插件来实现 WebView 功能,但这种方式存在以下问题:

  1. 依赖原生插件:需要维护 Android 和 iOS 两套原生代码
  2. 通信复杂:H5 页面与 UniApp 的数据交互实现困难
  3. 功能受限:文件上传、页面跳转等功能需要额外开发
  4. 维护成本高:版本更新时需要同步更新原生插件

本文将介绍一套完整的 UniApp WebView 解决方案,实现 H5 页面与 UniApp 的无缝集成。

核心功能需求

我们的目标是实现以下功能:

  • 基础加载:在 UniApp 中加载任意 H5 页面
  • 双向通信:H5 页面能调用 UniApp 功能,UniApp 能向 H5 页面传递数据
  • 页面跳转:H5 页面中的房源编号、客源编号能直接跳转到对应详情页
  • 文件上传:支持图片选择和文件选择功能
  • 业务集成:支持表单提交、数据回调等业务场景

解决方案演进

方案一:自定义注入 Bridge(失败)

思路:通过 evalJS 向 WebView 注入自定义的通信桥接对象。

// 尝试注入自定义 Bridgewebview.evalJS(` window.uniAppBridge = { jumpToPropertyDetail: function(code) { /* ... */ } };`);

问题

  • 在 Android 平台上注入不稳定
  • 时机难以控制,容易失败
  • 兼容性差

方案二:使用 UniApp 官方 SDK(成功)

思路:使用 UniApp 官方提供的 WebView 通信 SDK。

优势

  • 官方支持,稳定可靠
  • 标准化的通信方式
  • 完整的 API 支持

完整实现方案

1. 创建 WebView 页面组件

创建 pages/common/webview-page-simple.vue

   import CookieUtils from \'@/util/cookie.js\';export default { data() { return { webUrl: \'\' } }, onLoad(options) { if (options.url) { let url = decodeURIComponent(options.url); // 处理本地文件路径 if (url.startsWith(\'/\')) { // #ifdef H5 url = window.location.origin + url; // #endif // #ifdef APP-PLUS // 直接使用相对路径,UniApp 会自动处理 url = url; // #endif } // 添加参数 const cookie = CookieUtils.getCookie(); const separator = url.includes(\'?\') ? \'&\' : \'?\'; this.webUrl = `${url}${separator}cookie=${encodeURIComponent(cookie)}&platform=uniapp&device=${uni.getSystemInfoSync().platform}`; console.log(\'加载URL:\', this.webUrl); } // 设置标题 if (options.title) { uni.setNavigationBarTitle({ title: decodeURIComponent(options.title) }); } }, methods: { handleMessage(e) { console.log(\'收到消息:\', e.detail.data); const data = e.detail.data; const messages = Array.isArray(data) ? data : [data]; messages.forEach(message => { this.processMessage(message); }); }, processMessage(message) { switch (message.action || message.type) { case \'navigateTo\':  this.handleNavigate(message);  break; case \'jumpToPropertyDetail\':  this.jumpToPropertyDetail(message.data || message);  break; case \'jumpToInquiryDetail\':  this.jumpToInquiryDetail(message.data || message);  break; case \'chooseImage\':  this.handleChooseImage(message);  break; case \'chooseFile\':  this.handleChooseFile(message);  break; case \'submitApproval\':  this.handleSubmitApproval(message);  break; case \'back\':  uni.navigateBack();  break; default:  console.log(\'未知消息类型:\', message); } }, jumpToPropertyDetail(data) { const propertyCode = data.propertyCode || data.code || data.PropertyCode; if (propertyCode) { uni.navigateTo({  url: `/pages/house/detail-page?PropertyCode=${propertyCode}&isWeb=true` }); } }, jumpToInquiryDetail(data) { const inquiryCode = data.inquiryCode || data.code || data.InquiryCode; if (inquiryCode) { uni.navigateTo({  url: `/pages/passenger/passenger-detail?InquiryCode=${inquiryCode}&isWeb=true` }); } }, handleChooseImage(message) { uni.chooseImage({ count: message.count || 9, success: (res) => {  console.log(\'选择图片成功:\', res);  uni.showToast({ title: `已选择 ${res.tempFilePaths.length} 张图片`, icon: \'success\'  }); }, fail: (err) => {  console.error(\'选择图片失败:\', err);  uni.showToast({ title: \'选择图片失败\', icon: \'none\'  }); } }); }, handleChooseFile(message) { // #ifdef APP-PLUS uni.chooseFile({ count: message.count || 1, extension: message.extensions || [\'*\'], success: (res) => {  console.log(\'选择文件成功:\', res);  uni.showToast({ title: `已选择 ${res.tempFiles.length} 个文件`, icon: \'success\'  }); }, fail: (err) => {  console.error(\'选择文件失败:\', err);  uni.showToast({ title: \'选择文件失败\', icon: \'none\'  }); } }); // #endif // #ifdef H5 uni.showModal({ title: \'提示\', content: \'H5环境暂不支持文件选择,请使用图片选择功能\', showCancel: false }); // #endif }, handleSubmitApproval(message) { console.log(\'处理审批提交:\', message); const data = message.data || {}; uni.showToast({ title: \'审批已提交\', icon: \'success\' }); setTimeout(() => { uni.showModal({  title: \'提交完成\',  content: \'审批已成功提交,是否返回?\',  success: (res) => { if (res.confirm) { uni.navigateBack(); }  } }); }, 1500); } }}.webview-container { width: 100%; height: 100vh;}

2. 注册页面路由

pages.json 中添加:

{ \"path\": \"pages/common/webview-page-simple\", \"style\": { \"navigationBarTitleText\": \"加载中...\", \"navigationBarTextStyle\": \"black\" }}

3. 封装调用方法

util/native_plug_util.js 中添加:

/** * 跳转到 UniApp 的简化 web-view 页面(推荐使用) * @param {Object} url h5页面地址 * @param {Object} title 页面标题 * @param {Object} options 额外选项 */jumpToUniWebViewSimple(url, title, options = {}) { // 对 URL 进行编码,避免参数丢失 const encodedUrl = encodeURIComponent(url); let navigateUrl = `/pages/common/webview-page-simple?url=${encodedUrl}`; // 如果有标题,也进行编码 if (title) { const encodedTitle = encodeURIComponent(title); navigateUrl += `&title=${encodedTitle}`; } // 添加额外参数 if (options.useSDK !== false) { navigateUrl += \'&useSDK=true\'; } // 跳转到 UniApp 的简化 web-view 页面 uni.navigateTo({ url: navigateUrl, success: () => { console.log(\'成功跳转到 WebView 页面:\', url); }, fail: (err) => { console.error(\'跳转到 web-view 页面失败:\', err); uni.showToast({ title: \'页面跳转失败\', icon: \'none\' }); } });},

4. 创建业务 H5 页面模板

创建 static/html/business_template.html

   业务页面模板    /* 样式代码... */ body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 20px; background-color: #f5f5f5; } .container { max-width: 800px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .btn { display: inline-block; padding: 12px 24px; margin: 5px; background: #007aff; color: white; border: none; border-radius: 4px; cursor: pointer; } .property-link { color: #007aff; text-decoration: underline; cursor: pointer; font-weight: bold; }   
初始化中...

业务审批页面

房源编号:FY20240101 - 某某小区3室2厅

点击选择文件或图片

let isUniAppReady = false; // 更新状态指示器 function updateStatus(ready) { const statusEl = document.getElementById(\'status\'); if (ready) { statusEl.textContent = \'✓ 通信就绪\'; statusEl.style.background = \'#d4edda\'; statusEl.style.color = \'#155724\'; isUniAppReady = true; } else { statusEl.textContent = \'✗ 通信未就绪\'; statusEl.style.background = \'#f8d7da\'; statusEl.style.color = \'#721c24\'; } } // 监听 UniApp 环境准备就绪 document.addEventListener(\'UniAppJSBridgeReady\', function() { console.log(\'UniApp 通信已就绪\'); updateStatus(true); }); // 跳转到房源详情 function jumpToProperty(propertyCode) { if (!isUniAppReady) { alert(\'通信未就绪,请稍后再试\'); return; } console.log(\'跳转到房源详情:\', propertyCode); uni.postMessage({ data: { type: \'jumpToPropertyDetail\', data: { propertyCode: propertyCode } } }); } // 上传文件 function uploadFiles() { if (!isUniAppReady) { alert(\'通信未就绪,请稍后再试\'); return; } const choice = confirm(\'选择\"确定\"上传图片,选择\"取消\"上传文件\'); if (choice) { // 通过 UniApp 选择图片 uni.postMessage({ data: { action: \'chooseImage\', count: 9 } }); } else { // 选择文件 uni.postMessage({ data: { action: \'chooseFile\', count: 5, extensions: [\'.pdf\', \'.doc\', \'.docx\'] } }); } } // 提交审批 function submitApproval() { const title = document.getElementById(\'title\').value; const content = document.getElementById(\'content\').value; if (!title || !content) { alert(\'请填写完整的审批信息\'); return; } const approvalData = { title: title, content: content, timestamp: new Date().toISOString() }; console.log(\'提交审批数据:\', approvalData); if (isUniAppReady) { uni.postMessage({ data: { action: \'submitApproval\', data: approvalData } }); } else { alert(\'通信未就绪,请稍后再试\'); } } // 返回上一页 function goBack() { if (isUniAppReady && uni.navigateBack) { uni.navigateBack(); } else if (isUniAppReady) { uni.postMessage({ data: { type: \'back\' } }); } else { window.history.back(); } } // 页面加载完成 window.onload = function() { console.log(\'业务页面加载完成\'); // 检查环境 setTimeout(function() { if (window.uni) { updateStatus(true); } else { updateStatus(false); } }, 1000); };

使用方法

1. 在 UniApp 中调用

import nativePlugUtil from \'@/util/native_plug_util.js\';// 加载远程页面nativePlugUtil.jumpToUniWebViewSimple(\'https://your-domain.com/approval.html\', \'审批页面\');// 加载本地页面nativePlugUtil.jumpToUniWebViewSimple(\'/static/html/business_template.html\', \'业务页面\');

2. H5 页面与 UniApp 通信

页面跳转
// 跳转到房源详情uni.postMessage({ data: { type: \'jumpToPropertyDetail\', data: { propertyCode: \'FY123456\' } }});// 跳转到客源详情uni.postMessage({ data: { type: \'jumpToInquiryDetail\', data: { inquiryCode: \'KY123456\' } }});
文件操作
// 选择图片uni.postMessage({ data: { action: \'chooseImage\', count: 9 }});// 选择文件uni.postMessage({ data: { action: \'chooseFile\', extensions: [\'.pdf\', \'.doc\', \'.docx\'] }});
业务操作
// 提交数据uni.postMessage({ data: { action: \'submitApproval\', data: { title: \'审批标题\', content: \'审批内容\' } }});

核心技术要点

1. SDK 引入

关键:必须在 H5 页面中引入 UniApp 官方 SDK。

2. 环境检测

// 监听环境就绪document.addEventListener(\'UniAppJSBridgeReady\', function() { console.log(\'通信已就绪\'); // 此时可以安全使用 uni.postMessage});

3. 消息通信

H5 → UniApp

uni.postMessage({ data: { type: \'messageType\', data: { /* 数据 */ } }});

UniApp → H5

// 在 handleMessage 方法中处理handleMessage(e) { const message = e.detail.data; // 处理消息}

4. 错误处理

function safeCall(callback) { if (!isUniAppReady) { alert(\'通信未就绪,请稍后再试\'); return; } callback();}

最佳实践

1. 页面结构规范

   页面标题     // 必须:监听环境就绪 document.addEventListener(\'UniAppJSBridgeReady\', function() { console.log(\'通信已就绪\'); }); 

2. 状态管理

let isUniAppReady = false;function updateStatus(ready) { isUniAppReady = ready; // 更新 UI 状态指示器}function safeExecute(fn) { if (isUniAppReady) { fn(); } else { console.warn(\'UniApp 通信未就绪\'); }}

3. 错误处理

// 统一的错误处理function handleError(error, context) { console.error(`${context} 出错:`, error); if (isUniAppReady) { uni.postMessage({ data: { type: \'error\', error: error.message, context: context } }); } else { alert(`操作失败: ${error.message}`); }}

常见问题与解决方案

1. SDK 加载失败

问题:网络问题导致 SDK 无法加载
解决

 function loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement(\'script\'); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } const sdkSources = [ \'https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js\', \'https://unpkg.com/@dcloudio/uni-webview-js@0.0.3/index.js\' ]; async function loadUniSDK() { for (const src of sdkSources) { try { await loadScript(src); console.log(\'SDK 加载成功\'); break; } catch (e) { console.warn(\'SDK 加载失败,尝试下一个源\', src); } } } loadUniSDK();

2. 通信失败

问题:发送消息后没有响应
检查步骤

  1. 确认 SDK 已正确加载
  2. 确认监听了 UniAppJSBridgeReady 事件
  3. 确认消息格式正确
  4. 检查 UniApp 端的消息处理逻辑

3. 文件上传问题

问题:选择文件后没有反应
解决

// 确保在 APP 环境下使用// #ifdef APP-PLUSuni.chooseFile({ // 文件选择逻辑});// #endif// #ifdef H5// H5 环境下提供替代方案uni.showModal({ title: \'提示\', content: \'H5环境请使用图片上传功能\'});// #endif

4. 页面跳转问题

问题:点击链接无法跳转
解决

// 确保传递正确的参数格式uni.postMessage({ data: { type: \'jumpToPropertyDetail\', // 确保类型正确 data: { propertyCode: code // 确保字段名正确 } }});

总结

通过使用 UniApp 官方 WebView SDK,我们可以实现:

  1. 稳定的通信:基于官方 SDK,兼容性好
  2. 丰富的功能:支持文件上传、页面跳转、数据回调等
  3. 简单的维护:无需维护原生插件代码
  4. 良好的体验:接近原生应用的使用体验

这套解决方案已在实际项目中验证,能够满足大部分 H5 页面集成需求。我们成功地将复杂的原生 WebView 实现简化为纯 UniApp 代码,大大提升了开发效率和维护性。