HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

热更新的静默更新会导致数据错误的优化方案

升级中心 wgt升级 热更新

官方的热更新升级方案中是支持静默更新的,但是静默更新如果不重启常常会有页面样式错乱、数据加载错误等问题,看组件源码我们可以发现下载完成后是立即执行了wgt安装的操作,此时不重启app就会导致以上问题,这里我的思路是想在app在后台时执行安装并重启,或者在下次打开时执行安装并重启。

方案一、应用置于后台运行时安装热更新包并重启。
优点:更新会比较及时,只要应用置于后台了就会执行更新;
缺点:用户返回app时会执行安装重启,此时有明显的重启操作(安装完之后,我的wgt包20M需等3秒左右会重启),重启后会回到首页,这里可以记录重启前的页面,重启后重新定位该页面。

方案二、应用完全退出后重启时安装热更新包并再次重启。
优点:用户不会感觉到很明显的重启操作,不影响用户正常操作;
缺点:第一次启动时会有较长的时间停留在启动页(wgt包20M会停留在启动页7秒左右)安装完的重启几乎无感,很快,当然我们可以制作一个nvue的前端页面来代替启动页,并在这个页面用文字提示用户正在加载资源什么的(类似游戏打开先检查一番资源),获取用户信任,否则长时间的停留在启动页容易让用户误以为是app卡死。

主要的代码部分

uni-upgrade-center-app/utils/check-update.ts

// 静默更新,只有wgt有  
                    if (uniUpgradeCenterResult.is_silently) {  
                        uni.downloadFile({  
                            url,  
                            success: res => {  
                                if (res.statusCode == 200) {  
                                    uni.setStorageSync('WGTFilePath', res.tempFilePath)  
                                    // 下载好直接安装,下次启动生效  
                                    // plus.runtime.install(res.tempFilePath, {  
                                    //  force: false  
                                    // });  
                                }  
                            }  
                        });  
                        return;  
                    }  
保存wgt包的文件路径

App.vue
方案一:

onHide: function() {  
            let WGTFilePath = uni.getStorageSync('WGTFilePath')  
            if (WGTFilePath)  
                plus.runtime.install(WGTFilePath, {  
                    force: false  
                }, () => {  
                    uni.setStorageSync('WGTFilePath', '')  
                    plus.runtime.restart()  
                    return  
                })  
        },  

方案二:

onLaunch: function() {  
            let WGTFilePath = uni.getStorageSync('WGTFilePath')  
            if (WGTFilePath)  
                plus.runtime.install(WGTFilePath, {  
                    force: false  
                }, () => {  
                    uni.setStorageSync('WGTFilePath', '')  
                    plus.runtime.restart()  
                    return  
                })  
        },  

onShow: function() {  
            let WGTFilePath = uni.getStorageSync('WGTFilePath')  
            if (!WGTFilePath)//手动关闭启动页  
                plus.navigator.closeSplashscreen()  
},  

需要在配置文件关闭自动关闭启动页,并在app.vue手动处理  
  "splashscreen" : {  
            "alwaysShowBeforeRender" : false,  
            "waiting" : false,  
            "autoclose" : false,  
            "delay" : 0  
        },  
继续阅读 »

官方的热更新升级方案中是支持静默更新的,但是静默更新如果不重启常常会有页面样式错乱、数据加载错误等问题,看组件源码我们可以发现下载完成后是立即执行了wgt安装的操作,此时不重启app就会导致以上问题,这里我的思路是想在app在后台时执行安装并重启,或者在下次打开时执行安装并重启。

方案一、应用置于后台运行时安装热更新包并重启。
优点:更新会比较及时,只要应用置于后台了就会执行更新;
缺点:用户返回app时会执行安装重启,此时有明显的重启操作(安装完之后,我的wgt包20M需等3秒左右会重启),重启后会回到首页,这里可以记录重启前的页面,重启后重新定位该页面。

方案二、应用完全退出后重启时安装热更新包并再次重启。
优点:用户不会感觉到很明显的重启操作,不影响用户正常操作;
缺点:第一次启动时会有较长的时间停留在启动页(wgt包20M会停留在启动页7秒左右)安装完的重启几乎无感,很快,当然我们可以制作一个nvue的前端页面来代替启动页,并在这个页面用文字提示用户正在加载资源什么的(类似游戏打开先检查一番资源),获取用户信任,否则长时间的停留在启动页容易让用户误以为是app卡死。

主要的代码部分

uni-upgrade-center-app/utils/check-update.ts

// 静默更新,只有wgt有  
                    if (uniUpgradeCenterResult.is_silently) {  
                        uni.downloadFile({  
                            url,  
                            success: res => {  
                                if (res.statusCode == 200) {  
                                    uni.setStorageSync('WGTFilePath', res.tempFilePath)  
                                    // 下载好直接安装,下次启动生效  
                                    // plus.runtime.install(res.tempFilePath, {  
                                    //  force: false  
                                    // });  
                                }  
                            }  
                        });  
                        return;  
                    }  
保存wgt包的文件路径

App.vue
方案一:

onHide: function() {  
            let WGTFilePath = uni.getStorageSync('WGTFilePath')  
            if (WGTFilePath)  
                plus.runtime.install(WGTFilePath, {  
                    force: false  
                }, () => {  
                    uni.setStorageSync('WGTFilePath', '')  
                    plus.runtime.restart()  
                    return  
                })  
        },  

方案二:

onLaunch: function() {  
            let WGTFilePath = uni.getStorageSync('WGTFilePath')  
            if (WGTFilePath)  
                plus.runtime.install(WGTFilePath, {  
                    force: false  
                }, () => {  
                    uni.setStorageSync('WGTFilePath', '')  
                    plus.runtime.restart()  
                    return  
                })  
        },  

onShow: function() {  
            let WGTFilePath = uni.getStorageSync('WGTFilePath')  
            if (!WGTFilePath)//手动关闭启动页  
                plus.navigator.closeSplashscreen()  
},  

需要在配置文件关闭自动关闭启动页,并在app.vue手动处理  
  "splashscreen" : {  
            "alwaysShowBeforeRender" : false,  
            "waiting" : false,  
            "autoclose" : false,  
            "delay" : 0  
        },  
收起阅读 »

Web简谱渲染引擎

跳转到我的项目

https://blog.csdn.net/Date_Boy/article/details/142333049

https://blog.csdn.net/Date_Boy/article/details/142333049

继续阅读 »

跳转到我的项目

https://blog.csdn.net/Date_Boy/article/details/142333049

https://blog.csdn.net/Date_Boy/article/details/142333049

收起阅读 »

个人全职接单!时间充裕,诚心做事,合作靠谱,全职做项目

uniapp 外包接单 外包

全栈经验,这方面的软件开发的比较多,有相关案例,时间充裕,诚心合作,个人全职工作。不接二开和游戏,其他都可做。uniapp 做过很多,十分熟练,有需要请联系我 V:zwwz123123

全栈经验,这方面的软件开发的比较多,有相关案例,时间充裕,诚心合作,个人全职工作。不接二开和游戏,其他都可做。uniapp 做过很多,十分熟练,有需要请联系我 V:zwwz123123

H5莫名出现取消<uni-actionsheet>节点

已知bug

APP.vue中style加入全局隐藏
uni-actionsheet {
display: none !important;
}

APP.vue中style加入全局隐藏
uni-actionsheet {
display: none !important;
}

mqtt-vue2,mqtt-vue app,nvue 示例

https://ext.dcloud.net.cn/plugin?id=20250 地址 打开uniapp的插件市场 下载
https://ext.dcloud.net.cn/plugin?id=20250 地址 打开uniapp的插件市场 下载

uniCloud 短信服务1001报错问题

微信小程序 uni_app 短信

阿里云短信函数设置了定时触发,会有1001的报错,报错信息是smsKey不能为空,官方文档也标注了更新至最新后不需要填写smsKey,解决办法是在HBuilder X里,选中短信发送函数,右击后选中管理公共模块或扩展库依赖,然后勾选上uni-cloud-sms并确定即可

继续阅读 »

阿里云短信函数设置了定时触发,会有1001的报错,报错信息是smsKey不能为空,官方文档也标注了更新至最新后不需要填写smsKey,解决办法是在HBuilder X里,选中短信发送函数,右击后选中管理公共模块或扩展库依赖,然后勾选上uni-cloud-sms并确定即可

收起阅读 »

大家好啊,新手一枚,请多关照哈

大家好啊,新手一枚,请多关照哈。。。。。。。。。。。。。。。

大家好啊,新手一枚,请多关照哈。。。。。。。。。。。。。。。

安卓.9图启动图变形苹果storyboard 启动图片代做原生插件bug解决uniapp flutter开发

35岁被那个了,开发经验丰富,

原生Android iOS,uniapp flutter,vue,小程序都可以干,

同学朋友都是这行的,可接整体项目,可接项目咨询,项目设计,项目风险咨询等,

主要是性价比高,专业还便宜,

都在一线干的资深码农,如果你是老板请联系我,不一定就能省大钱办大事,还有惊喜,

如果你是一个技术需要解决问题请联系我,总之多问一嘴不收费,可能就有意外收获!QQ 1004898113

继续阅读 »

35岁被那个了,开发经验丰富,

原生Android iOS,uniapp flutter,vue,小程序都可以干,

同学朋友都是这行的,可接整体项目,可接项目咨询,项目设计,项目风险咨询等,

主要是性价比高,专业还便宜,

都在一线干的资深码农,如果你是老板请联系我,不一定就能省大钱办大事,还有惊喜,

如果你是一个技术需要解决问题请联系我,总之多问一嘴不收费,可能就有意外收获!QQ 1004898113

收起阅读 »

在uniapp插件市场上一直找不到合适的瀑布流组件,于是自己动手写了一个,并在项目中实践出来的瀑布流解决方案

自定义组件

一、我对于瀑布流布局组件的要求

  1. 要有骨架屏,能提前将基本结构展示给用户;
  2. 不要挨个渲染,否则如果有图片的话加载将会很慢;
  3. 可以实现多列瀑布流;
  4. 绝对不能出现错位渲染,很多瀑布流组件都出现这种情况,特别在页面跳转或Tabs切换下。
  5. 渲染过程中不能有卡顿影响用户体验。
  6. 应用于小程序、H5、App等

二、地址

uniapp插件地址:
https://ext.dcloud.net.cn/plugin?id=20167

组件文档地址:
https://github.com/Raito-Liao/raito-waterfall/blob/master/uni_modules/raito-waterfall/readme.md

三、实现讲解

1、计算渲染项的位置
原理都差不多,在获得渲染项的宽高后,将渲染项定位在最小高度的那一列的下面。

// raito-waterfall部分代码  
export default {  
    methods: {  
        generateRenderList() {  
            const renderList = []  
            const {  
                columnCount,  
                columnWidth,  
                skeletonHeight,  
                gutter,  
                lrPading,  
                keyName,  
                cache  
            } = this  
            const columnHeights = new Array(columnCount).fill(0);  

            if (!keyName) {  
                throw new Error('keyName is required!')  
            }  

            this.data.forEach((item, index) => {  
                const itemKey = item[keyName]  

                if (!cache[itemKey]) {  
                    cache[itemKey] = {  
                        top: 0,  
                        left: 0,  
                        width: columnWidth, // item宽度  
                        height: skeletonHeight // 骨架屏高度  
                    }  
                }  

                if (index < columnCount) {  
                    cache[itemKey].top = 0  
                    cache[itemKey].left = lrPading   index * (columnWidth   gutter)  
                    columnHeights[index]  = cache[itemKey].height  
                } else {  
                    const minHeight = Math.min(...columnHeights)  
                    const minIndex = columnHeights.indexOf(minHeight);  
                    cache[itemKey].top = minHeight   gutter  
                    cache[itemKey].left = lrPading   minIndex * (columnWidth   gutter)  
                    columnHeights[minIndex]  = cache[itemKey].height   gutter  
                }  

                renderList.push({  
                    ...item,  
                    _layoutRect: cache[itemKey],  
                    _renderKey: itemKey  
                })  
            })  

            const maxHeight = Math.max(...columnHeights)  

            return {  
                renderList,  
                maxHeight  
            }  
        },  
        startRender() {  
            const {  
                maxHeight,  
                renderList  
            } = this.generateRenderList()  

            if (!renderList.length) {  
                return  
            }  
            this.renderList = renderList  
            this.waterfallBoxHeight = maxHeight  
        },  
    }  
}

2、如何避免错位渲染?
这种情况发生于页面跳转或Tabs切换,导致获取不到元素的boundingClientRect,也就获取不到元素的高度,于是会导致计算错误。

function getRect(selector, context) {  
    return new Promise((resolve, reject) => {  
        const query = uni.createSelectorQuery().in(context);  
        query  
            .select(selector)  
            .boundingClientRect(rect => {  
                rect ? resolve(rect) : reject('Not Found');  
            })  
            .exec();  
    });  
}

可以这样解决:当createSelectorQuery获取不到boundingClientRect时,使用createIntersectionObserver来监听元素,这样同样可以获取得到boundingClientRect。当重新回到页面或Tabs切换回来时,intersectionObserver会被触发。

// raito-waterfall-item部分代码  
export default {  
    mounted() {  
        this.getExtraBoxWH().finally(() => {  
            /* 可能由于页面跳转、元素隐藏导致获取不到宽度和高度,于是通过监听元素来重新获取高度 */  
            if (!this.contentRect.extraBoxWidth || !this.contentRect.extraBoxHeight) {  
                this.startObserver();  
            }  
        });  
    },  
    beforeDestroy() {  
        if (this.intersectionObserver) {  
            this.intersectionObserver.disconnect();  
            this.intersectionObserver = null;  
        }  
    },  
    methods: {  
        startObserver() {  
            this.intersectionObserver = uni.createIntersectionObserver(this, {  
                thresholds: [0, 1],  
                initialRatio: 0,  
                observeAll: false  
            });  
            this.intersectionObserver.relativeToViewport();  
            this.intersectionObserver.observe('.content-box__extra', res => {  
                const { width, height } = res.boundingClientRect;  
                this.contentRect.extraBoxWidth = width;  
                this.contentRect.extraBoxHeight = height;  
                this.intersectionObserver.disconnect();  
                this.intersectionObserver = null;  
            });  
        },  
        // 获取extraBox的宽高  
        getExtraBoxWH() {  
            return getRect('.content-box__extra', this).then(rect => {  
                if (rect) {  
                    this.contentRect.extraBoxWidth = rect.width;  
                    this.contentRect.extraBoxHeight = rect.height;  
                }  
            });  
        },  
    }  
}

3、防止数据频繁变化和渲染项高度变化导致卡顿,需要对其节流

import { isArraysEqual, throttle } from '../../util.js'  
export default {  
    created() {  
        this.throttleRender = throttle(this.startRender.bind(this), 100) // 防止频繁调用  
        this.handleDataChange = throttle(this.handleDataChange.bind(this), 100) // 防止频繁调用  
        this.$watch('data', this.handleDataChange, {  
            deep: true,  
            immediate: true  
        })  
    },  
    methods: {  
        onHeightChange(item, height) {  
            const itemKey = item._renderKey  
            this.cache[itemKey].height = height   
            this.throttleRender()  
        },  
        handleDataChange(newData, oldData) {  
            if (isArraysEqual(newData, oldData)) {  
                return  
            }  
            this.startRender()  
        },  
    }  
}

五、扫码预览

H5扫码体验(国内可能会慢一些)

继续阅读 »

一、我对于瀑布流布局组件的要求

  1. 要有骨架屏,能提前将基本结构展示给用户;
  2. 不要挨个渲染,否则如果有图片的话加载将会很慢;
  3. 可以实现多列瀑布流;
  4. 绝对不能出现错位渲染,很多瀑布流组件都出现这种情况,特别在页面跳转或Tabs切换下。
  5. 渲染过程中不能有卡顿影响用户体验。
  6. 应用于小程序、H5、App等

二、地址

uniapp插件地址:
https://ext.dcloud.net.cn/plugin?id=20167

组件文档地址:
https://github.com/Raito-Liao/raito-waterfall/blob/master/uni_modules/raito-waterfall/readme.md

三、实现讲解

1、计算渲染项的位置
原理都差不多,在获得渲染项的宽高后,将渲染项定位在最小高度的那一列的下面。

// raito-waterfall部分代码  
export default {  
    methods: {  
        generateRenderList() {  
            const renderList = []  
            const {  
                columnCount,  
                columnWidth,  
                skeletonHeight,  
                gutter,  
                lrPading,  
                keyName,  
                cache  
            } = this  
            const columnHeights = new Array(columnCount).fill(0);  

            if (!keyName) {  
                throw new Error('keyName is required!')  
            }  

            this.data.forEach((item, index) => {  
                const itemKey = item[keyName]  

                if (!cache[itemKey]) {  
                    cache[itemKey] = {  
                        top: 0,  
                        left: 0,  
                        width: columnWidth, // item宽度  
                        height: skeletonHeight // 骨架屏高度  
                    }  
                }  

                if (index < columnCount) {  
                    cache[itemKey].top = 0  
                    cache[itemKey].left = lrPading   index * (columnWidth   gutter)  
                    columnHeights[index]  = cache[itemKey].height  
                } else {  
                    const minHeight = Math.min(...columnHeights)  
                    const minIndex = columnHeights.indexOf(minHeight);  
                    cache[itemKey].top = minHeight   gutter  
                    cache[itemKey].left = lrPading   minIndex * (columnWidth   gutter)  
                    columnHeights[minIndex]  = cache[itemKey].height   gutter  
                }  

                renderList.push({  
                    ...item,  
                    _layoutRect: cache[itemKey],  
                    _renderKey: itemKey  
                })  
            })  

            const maxHeight = Math.max(...columnHeights)  

            return {  
                renderList,  
                maxHeight  
            }  
        },  
        startRender() {  
            const {  
                maxHeight,  
                renderList  
            } = this.generateRenderList()  

            if (!renderList.length) {  
                return  
            }  
            this.renderList = renderList  
            this.waterfallBoxHeight = maxHeight  
        },  
    }  
}

2、如何避免错位渲染?
这种情况发生于页面跳转或Tabs切换,导致获取不到元素的boundingClientRect,也就获取不到元素的高度,于是会导致计算错误。

function getRect(selector, context) {  
    return new Promise((resolve, reject) => {  
        const query = uni.createSelectorQuery().in(context);  
        query  
            .select(selector)  
            .boundingClientRect(rect => {  
                rect ? resolve(rect) : reject('Not Found');  
            })  
            .exec();  
    });  
}

可以这样解决:当createSelectorQuery获取不到boundingClientRect时,使用createIntersectionObserver来监听元素,这样同样可以获取得到boundingClientRect。当重新回到页面或Tabs切换回来时,intersectionObserver会被触发。

// raito-waterfall-item部分代码  
export default {  
    mounted() {  
        this.getExtraBoxWH().finally(() => {  
            /* 可能由于页面跳转、元素隐藏导致获取不到宽度和高度,于是通过监听元素来重新获取高度 */  
            if (!this.contentRect.extraBoxWidth || !this.contentRect.extraBoxHeight) {  
                this.startObserver();  
            }  
        });  
    },  
    beforeDestroy() {  
        if (this.intersectionObserver) {  
            this.intersectionObserver.disconnect();  
            this.intersectionObserver = null;  
        }  
    },  
    methods: {  
        startObserver() {  
            this.intersectionObserver = uni.createIntersectionObserver(this, {  
                thresholds: [0, 1],  
                initialRatio: 0,  
                observeAll: false  
            });  
            this.intersectionObserver.relativeToViewport();  
            this.intersectionObserver.observe('.content-box__extra', res => {  
                const { width, height } = res.boundingClientRect;  
                this.contentRect.extraBoxWidth = width;  
                this.contentRect.extraBoxHeight = height;  
                this.intersectionObserver.disconnect();  
                this.intersectionObserver = null;  
            });  
        },  
        // 获取extraBox的宽高  
        getExtraBoxWH() {  
            return getRect('.content-box__extra', this).then(rect => {  
                if (rect) {  
                    this.contentRect.extraBoxWidth = rect.width;  
                    this.contentRect.extraBoxHeight = rect.height;  
                }  
            });  
        },  
    }  
}

3、防止数据频繁变化和渲染项高度变化导致卡顿,需要对其节流

import { isArraysEqual, throttle } from '../../util.js'  
export default {  
    created() {  
        this.throttleRender = throttle(this.startRender.bind(this), 100) // 防止频繁调用  
        this.handleDataChange = throttle(this.handleDataChange.bind(this), 100) // 防止频繁调用  
        this.$watch('data', this.handleDataChange, {  
            deep: true,  
            immediate: true  
        })  
    },  
    methods: {  
        onHeightChange(item, height) {  
            const itemKey = item._renderKey  
            this.cache[itemKey].height = height   
            this.throttleRender()  
        },  
        handleDataChange(newData, oldData) {  
            if (isArraysEqual(newData, oldData)) {  
                return  
            }  
            this.startRender()  
        },  
    }  
}

五、扫码预览

H5扫码体验(国内可能会慢一些)

收起阅读 »