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

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

问题

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

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

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

javascript 复制代码
<!-- 引入官方 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

javascript 复制代码
 <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 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);
            }
        }
    }
    </script>
    <style scoped>
    .webview-container {
        width: 100%;
        height: 100vh;
    }
    </style>

2. 注册页面路由

pages.json 中添加:

js 复制代码
{
      "path": "pages/common/webview-page-simple",
      "style": {
        "navigationBarTitleText": "加载中...",
        "navigationBarTextStyle": "black"
      }
 }

3. 封装调用方法

util/native_plug_util.js 中添加:

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

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 中调用

js 复制代码
  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 通信

页面跳转

js 复制代码
    // 跳转到房源详情
    uni.postMessage({
        data: {
            type: 'jumpToPropertyDetail',
            data: { propertyCode: 'FY123456' }
        }
    });

    // 跳转到客源详情
    uni.postMessage({
        data: {
            type: 'jumpToInquiryDetail',
            data: { inquiryCode: 'KY123456' }
        }
    });

文件操作

js 复制代码
  // 选择图片
    uni.postMessage({
        data: {
            action: 'chooseImage',
            count: 9
        }
    });

    // 选择文件
    uni.postMessage({
        data: {
            action: 'chooseFile',
            extensions: ['.pdf', '.doc', '.docx']
        }
    });

业务操作

js 复制代码
  // 提交数据
    uni.postMessage({
        data: {
            action: 'submitApproval',
            data: {
                title: '审批标题',
                content: '审批内容'
            }
        }
    });

核心技术要点

1. SDK 引入

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

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

2. 环境检测

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

3. 消息通信

H5 → UniApp

js 复制代码
   uni.postMessage({
        data: {
            type: 'messageType',
            data: { /* 数据 */ }
        }
    });

UniApp → H5

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

4. 错误处理

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

最佳实践

1. 页面结构规范

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>
        <!-- 必须:引入 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. 状态管理

js 复制代码
    let isUniAppReady = false;

    function updateStatus(ready) {
        isUniAppReady = ready;
        // 更新 UI 状态指示器
    }

    function safeExecute(fn) {
        if (isUniAppReady) {
            fn();
        } else {
            console.warn('UniApp 通信未就绪');
        }
    }

3. 错误处理

js 复制代码
    // 统一的错误处理
    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 无法加载 解决

js 复制代码
 <!-- 使用多个 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. 文件上传问题

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

js 复制代码
    // 确保在 APP 环境下使用
    // #ifdef APP-PLUS
    uni.chooseFile({
        // 文件选择逻辑
    });
    // #endif

    // #ifdef H5
    // H5 环境下提供替代方案
    uni.showModal({
        title: '提示',
        content: 'H5环境请使用图片上传功能'
    });
    // #endif

4. 页面跳转问题

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

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

总结

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

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

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

相关推荐
遂心_10 分钟前
React初学者必备:用“状态管家”Reducer轻松管理复杂状态!
前端·javascript·react.js
用户33790448021717 分钟前
ECMA6 ---- Class篇 (重难点个人向)
javascript
李明卫杭州19 分钟前
前端实现多标签页通讯
前端·javascript
在钱塘江23 分钟前
《你不知道的JavaScript-上卷》第二部分-this和对象原型-笔记-6-行为委托
前端·javascript
Point24 分钟前
[ahooks] useControllableValue源码阅读
前端·javascript
HexCIer24 分钟前
cbT.js: 一个让模板继承变得优雅的 Node.js 模板引擎
javascript·node.js
独立开阀者_FwtCoder31 分钟前
踩坑无数后,我终于总结出这份最全的 Vue3 组件通信实战指南
前端·javascript·vue.js
20261 小时前
12. npm version方法总结
前端·javascript·vue.js
用户87612829073741 小时前
mapboxgl中对popup弹窗添加事件
前端·vue.js
帅夫帅夫1 小时前
JavaScript继承探秘:从原型链到ES6 Class
前端·javascript