当前位置: 首页 > news >正文

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

背景与需求

在 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 注入自定义的通信桥接对象。

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

问题

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

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

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

<!-- 引入官方 SDK -->
<script src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>

优势

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

完整实现方案

1. 创建 WebView 页面组件

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

<template><view class="webview-container"><web-view :src="webUrl" @message="handleMessage"></web-view></view>
</template>
<script>
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 H5url = 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-PLUSuni.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 H5uni.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);}}
}
</script>
<style scoped>
.webview-container {width: 100%;height: 100vh;
}
</style>

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

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>业务页面模板</title><!-- 引入 UniApp WebView SDK --><script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script><style>/* 样式代码... */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;}</style>
</head>
<body><!-- 状态指示器 --><div id="status" class="status-indicator">初始化中...</div><div class="container"><div class="header"><h1>业务审批页面</h1></div><div class="content"><!-- 房源/客源信息展示 --><div class="form-group"><label>关联房源:</label><div><span class="property-link" onclick="jumpToProperty('FY20240101')">房源编号:FY20240101 - 某某小区3室2厅</span></div></div><!-- 表单字段 --><div class="form-group"><label for="title">审批标题:</label><input type="text" id="title" placeholder="请输入审批标题"></div><div class="form-group"><label for="content">审批内容:</label><textarea id="content" placeholder="请输入审批内容..."></textarea></div><!-- 文件上传区域 --><div class="form-group"><label>附件上传:</label><div class="file-upload" onclick="uploadFiles()"><p>点击选择文件或图片</p></div></div></div><!-- 操作按钮 --><div class="footer"><button class="btn" onclick="submitApproval()">提交审批</button><button class="btn" onclick="goBack()">返回</button></div></div><script>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);};</script>
</body>
</html>

使用方法

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。

<script src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>

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. 页面结构规范

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>页面标题</title><!-- 必须:引入 SDK --><script src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
</head>
<body><!-- 页面内容 --><script>// 必须:监听环境就绪document.addEventListener('UniAppJSBridgeReady', function() {console.log('通信已就绪');});</script>
</body>
</html>

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 无法加载
解决

<!-- 使用多个 CDN 源 -->
<script>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();
</script>

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

http://www.dtcms.com/a/264450.html

相关文章:

  • UniApp(vue3+vite)如何原生引入TailwindCSS(4)
  • YOLOv11深度解析:Ultralytics新一代目标检测王者的创新与实践(附网络结构图+训练/推理/导出全流程代码详解)
  • 【Erdas实验教程】024:遥感图像辐射增强(亮度反转Brightness Inversion)
  • Python数据解析与图片下载工具:从JSON到本地文件的自动化流程
  • springboot使用redisTemplate的方法,详细说明
  • 以智能楼宇自动化控制系统为基石,构筑绿色建筑节能增效新标杆
  • cmake笔记
  • 【分明集合】特征函数、关系与运算
  • 【格与代数系统】格与哈斯图
  • 笨方法学python-习题12
  • Sql注入中万能密码order by联合查询利用
  • 应急响应类题练习——玄机第四章 windows实战-emlog
  • Foundation 5 安装使用教程
  • SQL SELECT 语句
  • 在线租房平台源码+springboot+vue3(前后端分离)
  • 应急响应类题练习——玄机第五章 Windows 实战-evtx 文件分析
  • 6.Docker部署ES+kibana
  • Vite 7.0 与 Vue 3.5:前端开发的性能革命与功能升级
  • 【环境配置】Neo4j Community Windows 安装教程
  • HDMI 2.1 FRL协议的流控机制:切片传输(Slicing)和GAP插入
  • LL面试题11
  • 10授权
  • Vue 3 中的 `h` 函数详解
  • Rust征服字节跳动:高并发服务器实战
  • 飞算智造JavaAI:智能编程革命——AI重构Java开发新范式
  • Windows10/11 轻度优化 纯净版,12个版本!
  • 深度学习常见的激活函数
  • 【Python基础】11 Python深度学习生态系统全景解析:从基础框架到专业应用的技术深度剖析(超长版,附多个代码及结果)
  • 【深度学习1】ModernBert学习
  • RNN和LSTM