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

相关推荐
码途进化论1 小时前
从Chrome跳转到IE浏览器的完整解决方案
前端·javascript
笙年1 小时前
Vue 基础配置新手总结
前端·javascript·vue.js
哆啦A梦15881 小时前
40 token
前端·vue.js·node.js
摇滚侠1 小时前
Vue 项目实战《尚医通》,获取挂号医生的信息展示,笔记43
前端·javascript·vue.js·笔记·html5
k09331 小时前
vue3中基于AntDesign的Form嵌套表单的校验
前端·javascript·vue.js
茶憶2 小时前
UniApp RenderJS中集成 Leaflet地图,突破APP跨端开发限制
javascript·vue.js·uni-app
没头脑和不高兴y2 小时前
Element-Plus-X:基于Vue 3的AI交互组件库
前端·javascript
ErMao2 小时前
Proxy 与 Reflect:最硬核、最实用的解释
前端·javascript
N***73852 小时前
前端路由权限动态更新,Vue与React实现
前端·vue.js·react.js
k09332 小时前
在组件外(.js文件)中使用pinia的方法2--在http.js中使用pinia
开发语言·javascript·http