2***@qq.com
2***@qq.com
  • 发布:2026-02-02 19:58
  • 更新:2026-02-02 20:24
  • 阅读:19

【报Bug】nvue页面使用v-if控制webview显示隐藏导致evalJS方法调用失败

分类:uni-app

产品分类: uniapp/App

PC开发环境操作系统: Windows

PC开发环境操作系统版本号: Windows 11 专业工作站版24H2

手机系统: Android

手机系统版本号: Android 15

手机厂商: 小米

手机机型: redmi note 12 turbo

页面类型: nvue

vue版本: vue3

打包方式: 云端

项目创建方式: CLI

CLI版本号: 3.0.0-4080720251210001

示例代码:

不能正常的代码

<template>  
    <view class="container" :style="{height:pageHeight+'px'}">  
        <view v-if="userStore.userInfo.is_examine===0 || userStore.userInfo.status ===0" class="contentEx"  
            :style="{height:pageHeight+'px'}">  
            <image src="@/static/images/login.png" class="loginImage"></image>  
            <view class="loginText">  
                <text v-if="userStore.userInfo.status ===0" class="statusText">  
                    你的账号已被禁用,无法看课</text>  
                <text v-else class="statusText">注册成功,等待群管审核</text>  
            </view>  
        </view>  
        <view v-else-if="msg" class="contentError" :style="{height:pageHeight+'px'}">  
            <image src="@/static/images/login.png" class="loginImage"></image>  
            <view class="loginText">  
                <text class="statusText">{{msg}}</text>  
            </view>  
        </view>  
        <view v-else class="content" :style="{height:pageHeight+'px'}">  
            <block v-if="courseInfo">  
                <!-- 1横屏 2竖屏 -->  
                <block v-if="courseInfo.video_type===1">  
                    <video ref="videoContent" class="videoHorizontal" :src="courseInfo.video_file"  
                        :poster="courseInfo.video_image"></video>  
                    <view class="videoTab">  
                        <view :class="['tab-item',tabAvtive===index?'tab-item-active':'']"  
                            v-for="(item,index) in tabList" :key="index" @click="changeTabItem(index)">  
                            <text  
                                :class="['tab-item-text',tabAvtive===index?'tab-item-text-active':'']">{{item.name}}</text>  
                        </view>  
                    </view>  
                    <swiper :current="tabAvtive" class="swiperContainer" :style="{height:swiperHeight+'px'}"  
                        @change="changeSwiper">  
                        <swiper-item v-for="(item,index) in tabList" :key="index" class="swiperContainer"  
                            :style="{height:swiperHeight+'px'}">  
                            <block v-if="item.value===1">  
                                <answer :questions="courseInfo.question" @submit="onSubmit"></answer>  
                            </block>  
                            <block v-else>  
                                <web-view ref="webview" :style="{height: swiperHeight + 'px',width: '690rpx'}"  
                                    src="/hybrid/html/index.html" @onPostMessage="handerMessage"  
                                    @message="handerMessage"></web-view>  
                                <view v-if="!isWebviewInit" :style="{height: swiperHeight + 'px',width: '690rpx'}"  
                                    class="webLoading">  
                                    <view class="loading-spinner"></view>  
                                    <view class="loading-text">加载中...</view>  
                                </view>  
                            </block>  
                        </swiper-item>  
                    </swiper>  
                </block>  
                <block v-else></block>  
            </block>  
        </view>  
    </view>  
</template>  

<script>  
    import {  
        useUserStore  
    } from '@/store/userStore'  
    import {  
        useToast  
    } from 'wot-design-uni'  
    import {  
        getCourseDetail,  
        getAliIMParams  
    } from "@/api/course.js"  

    export default {  
        data() {  
            return {  
                pageHeight: 0,  
                swiperHeight: 0,  
                train_lesson_id: "",  
                rpxTopx: 0.5,  
                msg: "",  
                isWebviewInit: false,  
                courseInfo: null,  
                tabList: [{  
                    name: "答题",  
                    value: 1  
                }, {  
                    name: "聊天",  
                    value: 2  
                }],  
                tabAvtive: 0,  
                redMoney: 0,  
                initialTime: 0,  
                playEnd: false  
            }  
        },  
        computed: {  
            userStore() {  
                return useUserStore()  
            },  
            toast() {  
                return useToast()  
            }  
        },  
        onLoad(options) {  
            this.train_lesson_id = options.id  
            let system = uni.getSystemInfoSync()  
            this.pageHeight = system.windowHeight  
            this.rpxTopx = system.windowWidth / system.windowHeight  
            this.swiperHeight = this.pageHeight - 630 * this.rpxTopx  
        },  
        mounted() {  
            if (this.userStore.userInfo.is_examine == 0) {  
                this.updataUserInfo();  
                return;  
            }  
            this.getParamCourseInfo();  
        },  
        methods: {  
            changeTabItem(index) {  
                this.tabAvtive = index  
            },  
            changeSwiper(e) {  
                this.tabAvtive = e.detail.current  
            },  
            handerMessage(e) {  
                let obj = e.detail.data.length > 0 ? e.detail.data[0] : null  
                console.log("收到网页的消息", obj)  
                if (obj) {  
                    switch (obj.type) {  
                        case "init":  
                            this.isWebviewInit = true  
                            this.getImData()  
                            break;  
                        default:  
                            break;  
                    }  
                }  
            },  
            updataUserInfo(flag = true) {  
                this.userStore.refreshUserInfo((userInfo) => {  
                    if (userInfo.is_examine != 0 && flag) {  
                        this.getParamCourseInfo()  
                    }  
                })  
            },  
            getParamCourseInfo() {  
                getCourseDetail({  
                    train_lesson_id: this.train_lesson_id  
                }).then((res) => {  
                    if (res.code == 1) {  
                        res.data.question.sort((a, b) => {  
                            return a.question_id - b.question_id;  
                        });  
                        res.data.question.forEach((q) => {  
                            q.question_option_json = JSON.parse(q.question_option_json);  
                        });  
                        this.courseInfo = res.data;  
                        this.redMoney = res.data.red_money  
                        if (!this.playEnd) {  
                            this.initialTime = parseInt(this.courseInfo.progress);  
                        }  
                        console.log(this.courseInfo)  
                    } else {  
                        this.msg = res.msg  
                        if (res.msg.includes('审核')) {  
                            this.updataUserInfo(false);  
                        }  
                    }  
                }).catch((error) => {  
                    console.error(error);  
                    console.log("获取课程信息错误", error.errMsg || error.msg);  
                })  
            },  
            getImData() {  
                getAliIMParams({  
                    feature: "im_join_room",  
                    room_id: this.courseInfo.train_lesson_id,  
                }).then((res) => {  
                    if (res.code == 1) {  
                        this.sendWebviewMessage({  
                            type: "chatInit",  
                            ...res.data,  
                            userInfo: this.userStore.userInfo,  
                            room_id: String(this.courseInfo.train_lesson_id)  
                        })  
                    } else {  
                        this.toast.error(res.msg)  
                    }  
                }).catch((err) => {  
                    console.log("获取IM数据错误", err);  
                });  
            },  
            sendWebviewMessage(data) {  
                console.log(this.$refs.webview)  
                this.$refs.webview.evalJS(`msgFromUniapp('${JSON.stringify(data)}')`)  
            },  
            onSubmit(e) {  

            }  
        }  
    }  
</script>

能正常的代码

<template>  
    <view class="container" :style="{height:pageHeight+'px'}">  
        <web-view ref="webview" :style="{height: swiperHeight + 'px',width: '690rpx'}"  
                                    src="/hybrid/html/index.html" @onPostMessage="handerMessage"  
                                    @message="handerMessage"></web-view>  
        <view v-if="userStore.userInfo.is_examine===0 || userStore.userInfo.status ===0" class="contentEx"  
            :style="{height:pageHeight+'px'}">  
            <image src="@/static/images/login.png" class="loginImage"></image>  
            <view class="loginText">  
                <text v-if="userStore.userInfo.status ===0" class="statusText">  
                    你的账号已被禁用,无法看课</text>  
                <text v-else class="statusText">注册成功,等待群管审核</text>  
            </view>  
        </view>  
        <view v-else-if="msg" class="contentError" :style="{height:pageHeight+'px'}">  
            <image src="@/static/images/login.png" class="loginImage"></image>  
            <view class="loginText">  
                <text class="statusText">{{msg}}</text>  
            </view>  
        </view>  
        <view v-else class="content" :style="{height:pageHeight+'px'}">  
            <block v-if="courseInfo">  
                <!-- 1横屏 2竖屏 -->  
                <block v-if="courseInfo.video_type===1">  
                    <video ref="videoContent" class="videoHorizontal" :src="courseInfo.video_file"  
                        :poster="courseInfo.video_image"></video>  
                    <view class="videoTab">  
                        <view :class="['tab-item',tabAvtive===index?'tab-item-active':'']"  
                            v-for="(item,index) in tabList" :key="index" @click="changeTabItem(index)">  
                            <text  
                                :class="['tab-item-text',tabAvtive===index?'tab-item-text-active':'']">{{item.name}}</text>  
                        </view>  
                    </view>  
                    <swiper :current="tabAvtive" class="swiperContainer" :style="{height:swiperHeight+'px'}"  
                        @change="changeSwiper">  
                        <swiper-item v-for="(item,index) in tabList" :key="index" class="swiperContainer"  
                            :style="{height:swiperHeight+'px'}">  
                            <block v-if="item.value===1">  
                                <answer :questions="courseInfo.question" @submit="onSubmit"></answer>  
                            </block>  
                            <block v-else>  
                                <!-- <web-view ref="webview" :style="{height: swiperHeight + 'px',width: '690rpx'}"  
                                    src="/hybrid/html/index.html" @onPostMessage="handerMessage"  
                                    @message="handerMessage"></web-view> -->  
                                <view v-if="!isWebviewInit" :style="{height: swiperHeight + 'px',width: '690rpx'}"  
                                    class="webLoading">  
                                    <view class="loading-spinner"></view>  
                                    <view class="loading-text">加载中...</view>  
                                </view>  
                            </block>  
                        </swiper-item>  
                    </swiper>  
                </block>  
                <block v-else></block>  
            </block>  
        </view>  
    </view>  
</template>

就把webview拿出来了就能正常了

操作步骤:

使用v-if后

预期结果:

网页能收到数据

实际结果:

网页收不到数据

bug描述:

nvue页面webview和本地html通信报错,使用的ref引用的方式获取的webview实例,报错evalJS方法不存在。问题大概是出在这里我页面有v-if 当请求拿到详情了才会渲染webview,然后webview给app发送消息,app收到后才会去调用evalJS方法,这样就会报错。但是我把webview移出v-if就能正常调用evalJS方法不报错

2026-02-02 19:58 负责人:无 分享
已邀请:
Ask小助手

Ask小助手

欢迎到专业群( uni-app 官方技术交流群 1 ) 咨询,群中有相关专业的管理员和群友。

您好,您遇到的问题是 nvue 页面中使用 v-if 控制 <web-view> 渲染时,后续调用 evalJS 方法失败,这是 nvue 中 web-view 与条件渲染组合使用的已知限制,并非 bug。


✅ 问题原因

在 nvue 页面中:

  • v-iffalse 时,<web-view> 组件会被 销毁
  • v-if 变为 true,组件会 重新创建,但此时 DOM 尚未挂载完成,立即通过 this.$refs.webview.evalJS(...) 调用会失败,因为 this.$refs.webviewundefined 或尚未初始化。

✅ 官方建议与解决方案

✅ 方案一:使用 v-show 替代 v-if

nvue 页面中 不支持 v-show,因此此方案仅适用于 vue 页面
在 nvue 中请使用以下方式:

✅ 方案二:使用 nextTick 延迟调用

v-if 变为 true 后,使用 this.$nextTick() 等待组件挂载完成再调用 evalJS

this.courseInfo = res.data; // v-if 条件变为 true  
this.$nextTick(() => {  
  this.$refs.webview.evalJS(`msgFromUniapp('${JSON.stringify(data)}')`);  
});

✅ 方案三:由 web-view 主动通知“我已加载完成”

这是最稳妥的方式,避免外部主动调用 evalJS 的时机不确定问题:

  1. index.html 中监听 DOMContentLoaded 后向 uni-app 发送消息:

    document.addEventListener('DOMContentLoaded', () => {  
     uni.postMessage({ type: 'init' });  
    });  
  2. 在 uni-app 中监听 onPostMessage,收到 init 后再调用 evalJS

    handerMessage(e) {  
     const msg = e.detail.data?.[0];  
     if (msg?.type === 'init') {  
       this.getImData(); // 此时 webview 已就绪  
     }  
    }  

✅ 推荐做法(适用于您当前项目)

由于您使用的是 nvue 页面,建议:

  • 保留 v-if 控制渲染
  • 在收到 init 消息后再调用 evalJS
  • 避免在 mountedcourseInfo 赋值后立即调用 evalJS

✅ 小结

方式 是否推荐 说明
v-if + 立即调用 evalJS 会导致 evalJS 方法不存在
v-if + nextTick 需确保组件已挂载
v-if + 由 webview 通知 ✅✅ 最稳妥,避免时机问题

如需进一步参考,请查看官方文档:

内容为 AI 生成,仅供参考
2***@qq.com

2***@qq.com (作者)

不行 我就是这样做的,只有收到消息才会去调用

handerMessage(e) {    
                let obj = e.detail.data.length > 0 ? e.detail.data[0] : null    
                console.log("收到网页的消息", obj)    
                if (obj) {    
                    switch (obj.type) {    
                        case "init":    
                            this.isWebviewInit = true    
                            this.getImData()    
                            break;    
                        default:    
                            break;    
                    }    
                }    
            },  

还是不行

要回复问题请先登录注册