> 技术文档 > UniApp 加载 Web 页面解决方案_uniapp evaljs

UniApp 加载 Web 页面解决方案_uniapp evaljs


背景与需求

在 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-PLUS uni.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 代码,大大提升了开发效率和维护性。