HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

uniadmin上有个uni-id-co的用户函数代码错误或者逻辑异常

uni-cloud

这个报错,个人复现出现的问题是,注册账号用的本地云,实际跑的时候用的云端,就会出错,兄弟们少走弯路,一路一律使用云端!这个破问题,我翻了接近一个多小时的文档。我把注册好的管理员账号删除了,然后用云端重新注册,再用云端跑就可以了,部署好野没问题了。这个项目过后,我也放弃uniapp了,从最开始apicloud那会儿,跑来使用uniapp来说,它确实是一款非常好的产品,免除了很多繁琐的工作,但问题也很明显,文档写的一塌糊涂,更新也不及时,内容也是过了几百年也不去补正,导致很多文档指向的链接根本就没有。写的有部分示例代码也让人看了之后产生分歧,还得挨个写测试。问答社区活跃度也堪忧,好多问题除了工作人员根本没人来解答,uniapp人手不足这个可以理解,但要做好一款产品只是靠大家理解,那可能远远不够。也希望以后的日子uniapp能更进一步。

继续阅读 »

这个报错,个人复现出现的问题是,注册账号用的本地云,实际跑的时候用的云端,就会出错,兄弟们少走弯路,一路一律使用云端!这个破问题,我翻了接近一个多小时的文档。我把注册好的管理员账号删除了,然后用云端重新注册,再用云端跑就可以了,部署好野没问题了。这个项目过后,我也放弃uniapp了,从最开始apicloud那会儿,跑来使用uniapp来说,它确实是一款非常好的产品,免除了很多繁琐的工作,但问题也很明显,文档写的一塌糊涂,更新也不及时,内容也是过了几百年也不去补正,导致很多文档指向的链接根本就没有。写的有部分示例代码也让人看了之后产生分歧,还得挨个写测试。问答社区活跃度也堪忧,好多问题除了工作人员根本没人来解答,uniapp人手不足这个可以理解,但要做好一款产品只是靠大家理解,那可能远远不够。也希望以后的日子uniapp能更进一步。

收起阅读 »

uniapp app自动监听权限

权限开启 系统功能权限 权限判断 权限配置

自动监听 onLaunch 引入 listener方法即可

监听权限

// @ts-nocheck  

// #ifdef APP-PLUS  
import permissions from "./permissions";  
import {closeNotify, drawNotify} from "@/utils/notify";  
const permissionListener = uni.createRequestPermissionListener()  
// #endif  

/**  
 * 监听权限  
 */  
export function listener() {  
    /* 是否已经弹出设置框 */  
    let hasConfirm = false  

    // 监听申请系统权限  
    permissionListener.onRequest((data: string[]) => {  
    })  
    // 监听弹出系统权限授权框  
    permissionListener.onConfirm((data: string[]) => {  
        const permissionsObj = getPermissionsObj(data)  
        drawNotify(permissionsObj.title, permissionsObj.desc)  
    })  
    // 监听权限申请完成  
    permissionListener.onComplete((data: string[]) => {  
        closeNotify()  

        const parts = data[data.length - 1].split('.');  
        let permissionName = parts[parts.length - 1];  

        const Manifest = plus.android.importClass('android.Manifest')  
        const MainActivity = plus.android.runtimeMainActivity()  
        const permissionStatus = MainActivity.checkSelfPermission(Manifest.permission[permissionName])  

        // 彻底拒绝  
        if (permissionStatus === -1 && !hasConfirm) {  
            hasConfirm = true  
            const permissionsObj = getPermissionsObj(data)  

            plus.nativeUI.confirm(permissionsObj.desc, (e) => {  
                if (e.index === 0) uni.openAppAuthorizeSetting()  
                hasConfirm = false  
            }, {  
                title: permissionsObj.title,  
                "buttons": ["去设置", "取消"],  
            });  
        }  
    })  
}  

function getPermissionsObj(data: string[]): { title: string, desc: string } {  
    let title: string = ""  
    let desc: string = ""  
    data.forEach((permission) => {  
        permissions.forEach(item => {  
            if (item.permissions.indexOf(permission) !== -1) {  
                title = item.title  
                desc = item.desc  
            }  
        })  
    })  

    return {title, desc}  
}  

权限设置

export interface Permissions {  
    title: string,  
    desc: string,  
    permissions: string[]  
}  

const permissions: Permissions[] = [  
    {  
        title: "读取短信权限",  
        desc: "读取短信权限达到个性化目的",  
        permissions: [  
            "android.permission.SEND_SMS",  
            "android.permission.RECEIVE_SMS",  
            "android.permission.READ_SMS",  
            "android.permission.RECEIVE_WAP_PUSH",  
            "android.permission.RECEIVE_MMS",  
        ]  
    },  
    {  
        title: "读取存储卡,包括相册等权限",  
        desc: "读取存储卡,包括相册等权限达到个性化目的",  
        permissions: [  
            "android.permission.READ_EXTERNAL_STORAGE",  
            "android.permission.WRITE_EXTERNAL_STORAGE",  
        ]  
    },  
    {  
        title: "读取联系人权限",  
        desc: "读取联系人权限达到个性化目的",  
        permissions: [  
            "android.permission.READ_CONTACTS",  
            "android.permission.WRITE_CONTACTS",  
            "android.permission.GET_ACCOUNTS",  
        ]  
    },  
    {  
        title: "读取手机权限",  
        desc: "读取手机权限达到个性化目的",  
        permissions: [  
            "android.permission.READ_PHONE_STATE",  
            "android.permission.CALL_PHONE",  
            "android.permission.READ_CALL_LOG",  
            "android.permission.WRITE_CALL_LOG",  
            "android.permission.ADD_VOICEMAIL",  
            "android.permission.USE_SIP",  
            "android.permission.PROCESS_OUTGOING_CALLS",  
        ]  
    },  
    {  
        title: "读取日历权限",  
        desc: "读取日历权限达到个性化目的",  
        permissions: [  
            "android.permission.READ_CALENDAR",  
            "android.permission.WRITE_CALENDAR",  
        ]  
    },  
    {  
        title: "读取相机权限",  
        desc: "读取相机权限达到个性化目的",  
        permissions: [  
            "android.permission.CAMERA",  
        ]  
    },  
    {  
        title: "读取位置权限",  
        desc: "读取位置权限达到个性化目的",  
        permissions: [  
            "android.permission.ACCESS_FINE_LOCATION",  
            "android.permission.ACCESS_COARSE_LOCATION",  
        ]  
    },  
    {  
        title: "读取传感器权限",  
        desc: "读取传感器权限达到个性化目的",  
        permissions: [  
            "android.permission.BODY_SENSORS",  
        ]  
    },  
    {  
        title: "读取麦克风权限",  
        desc: "读取麦克风权限达到个性化目的",  
        permissions: [  
            "android.permission.RECORD_AUDIO",  
        ]  
    },  
]  

export default permissions  

顶部信息

let view: any  
let idKey: string = 'permissionNotify'  

/**  
 * 绘制消息  
 * @description 可根据描述字数动态变更高度  
 * @param {string} title 标题  
 * @param {string} desc 描述  
 */  
export function drawNotify(title: string, desc: string) {  
    // 关闭之前的 notify  
    // @ts-ignore  
    let idView = plus.nativeObj.View.getViewById(idKey)  
    if (idView) idView.close()  

    const {windowWidth, statusBarHeight} = uni.getSystemInfoSync()  

    const xDistance: number = 16 // 距离屏幕两侧的距离  
    const tDistance: number = 16 // 距离顶部的距离  
    const radius: number = 16 // 盒子圆角  

    const xPadding: number = 16 // 盒子paddingX  
    const YPadding: number = 16 // 盒子paddingY  
    const space: number = 12 // 描述距离标题的距离  

    const titleSize: number = 16 // 标题文字大小  
    const descSize: number = 14 // 描述文字大小  

    const width: number = windowWidth - xDistance * 2  
    // @ts-ignore  
    const top: number = statusBarHeight + tDistance  

    const lineNum = Math.ceil(desc.length / Math.floor((width - YPadding * 2) / descSize)) * 1.2  

    const height = YPadding * 2 + titleSize + space + descSize * lineNum  

    // @ts-ignore  
    let _view = new plus.nativeObj.View(  
        idKey,  
        {  
            width: `${width}`,  
            top: `${top}`,  
            left: `${xDistance}`,  
            height: `${height}`,  
        },  
        [  
            {  
                tag: 'rect',  
                id: 'rect',  
                rectStyles: {  
                    color: '#FFFFFF',  
                    radius: `${radius}`,  
                },  
            },  
            {  
                tag: 'font',  
                id: 'title',  
                text: title,  
                textStyles: {size: `${titleSize}`, align: 'left', weight: 'bold'},  
                position: {top: `${YPadding}`, left: `${xPadding}`, height: 'wrap_content'},  
            },  
            {  
                tag: 'font',  
                id: 'desc',  
                text: desc,  
                textStyles: {size: `${descSize}`, align: 'left', color: '#666666', whiteSpace: 'normal'},  
                position: {  
                    top: `${YPadding + titleSize + space}`,  
                    left: `${xPadding}`,  
                    height: 'wrap_content',  
                    width: `${width - YPadding * 2}`,  
                },  
            },  
        ]  
    )  

    _view.show()  

    view = _view  
}  

/**  
 * 关闭消息  
 */  
export function closeNotify() {  
    if (view) view.close()  
}  
继续阅读 »

自动监听 onLaunch 引入 listener方法即可

监听权限

// @ts-nocheck  

// #ifdef APP-PLUS  
import permissions from "./permissions";  
import {closeNotify, drawNotify} from "@/utils/notify";  
const permissionListener = uni.createRequestPermissionListener()  
// #endif  

/**  
 * 监听权限  
 */  
export function listener() {  
    /* 是否已经弹出设置框 */  
    let hasConfirm = false  

    // 监听申请系统权限  
    permissionListener.onRequest((data: string[]) => {  
    })  
    // 监听弹出系统权限授权框  
    permissionListener.onConfirm((data: string[]) => {  
        const permissionsObj = getPermissionsObj(data)  
        drawNotify(permissionsObj.title, permissionsObj.desc)  
    })  
    // 监听权限申请完成  
    permissionListener.onComplete((data: string[]) => {  
        closeNotify()  

        const parts = data[data.length - 1].split('.');  
        let permissionName = parts[parts.length - 1];  

        const Manifest = plus.android.importClass('android.Manifest')  
        const MainActivity = plus.android.runtimeMainActivity()  
        const permissionStatus = MainActivity.checkSelfPermission(Manifest.permission[permissionName])  

        // 彻底拒绝  
        if (permissionStatus === -1 && !hasConfirm) {  
            hasConfirm = true  
            const permissionsObj = getPermissionsObj(data)  

            plus.nativeUI.confirm(permissionsObj.desc, (e) => {  
                if (e.index === 0) uni.openAppAuthorizeSetting()  
                hasConfirm = false  
            }, {  
                title: permissionsObj.title,  
                "buttons": ["去设置", "取消"],  
            });  
        }  
    })  
}  

function getPermissionsObj(data: string[]): { title: string, desc: string } {  
    let title: string = ""  
    let desc: string = ""  
    data.forEach((permission) => {  
        permissions.forEach(item => {  
            if (item.permissions.indexOf(permission) !== -1) {  
                title = item.title  
                desc = item.desc  
            }  
        })  
    })  

    return {title, desc}  
}  

权限设置

export interface Permissions {  
    title: string,  
    desc: string,  
    permissions: string[]  
}  

const permissions: Permissions[] = [  
    {  
        title: "读取短信权限",  
        desc: "读取短信权限达到个性化目的",  
        permissions: [  
            "android.permission.SEND_SMS",  
            "android.permission.RECEIVE_SMS",  
            "android.permission.READ_SMS",  
            "android.permission.RECEIVE_WAP_PUSH",  
            "android.permission.RECEIVE_MMS",  
        ]  
    },  
    {  
        title: "读取存储卡,包括相册等权限",  
        desc: "读取存储卡,包括相册等权限达到个性化目的",  
        permissions: [  
            "android.permission.READ_EXTERNAL_STORAGE",  
            "android.permission.WRITE_EXTERNAL_STORAGE",  
        ]  
    },  
    {  
        title: "读取联系人权限",  
        desc: "读取联系人权限达到个性化目的",  
        permissions: [  
            "android.permission.READ_CONTACTS",  
            "android.permission.WRITE_CONTACTS",  
            "android.permission.GET_ACCOUNTS",  
        ]  
    },  
    {  
        title: "读取手机权限",  
        desc: "读取手机权限达到个性化目的",  
        permissions: [  
            "android.permission.READ_PHONE_STATE",  
            "android.permission.CALL_PHONE",  
            "android.permission.READ_CALL_LOG",  
            "android.permission.WRITE_CALL_LOG",  
            "android.permission.ADD_VOICEMAIL",  
            "android.permission.USE_SIP",  
            "android.permission.PROCESS_OUTGOING_CALLS",  
        ]  
    },  
    {  
        title: "读取日历权限",  
        desc: "读取日历权限达到个性化目的",  
        permissions: [  
            "android.permission.READ_CALENDAR",  
            "android.permission.WRITE_CALENDAR",  
        ]  
    },  
    {  
        title: "读取相机权限",  
        desc: "读取相机权限达到个性化目的",  
        permissions: [  
            "android.permission.CAMERA",  
        ]  
    },  
    {  
        title: "读取位置权限",  
        desc: "读取位置权限达到个性化目的",  
        permissions: [  
            "android.permission.ACCESS_FINE_LOCATION",  
            "android.permission.ACCESS_COARSE_LOCATION",  
        ]  
    },  
    {  
        title: "读取传感器权限",  
        desc: "读取传感器权限达到个性化目的",  
        permissions: [  
            "android.permission.BODY_SENSORS",  
        ]  
    },  
    {  
        title: "读取麦克风权限",  
        desc: "读取麦克风权限达到个性化目的",  
        permissions: [  
            "android.permission.RECORD_AUDIO",  
        ]  
    },  
]  

export default permissions  

顶部信息

let view: any  
let idKey: string = 'permissionNotify'  

/**  
 * 绘制消息  
 * @description 可根据描述字数动态变更高度  
 * @param {string} title 标题  
 * @param {string} desc 描述  
 */  
export function drawNotify(title: string, desc: string) {  
    // 关闭之前的 notify  
    // @ts-ignore  
    let idView = plus.nativeObj.View.getViewById(idKey)  
    if (idView) idView.close()  

    const {windowWidth, statusBarHeight} = uni.getSystemInfoSync()  

    const xDistance: number = 16 // 距离屏幕两侧的距离  
    const tDistance: number = 16 // 距离顶部的距离  
    const radius: number = 16 // 盒子圆角  

    const xPadding: number = 16 // 盒子paddingX  
    const YPadding: number = 16 // 盒子paddingY  
    const space: number = 12 // 描述距离标题的距离  

    const titleSize: number = 16 // 标题文字大小  
    const descSize: number = 14 // 描述文字大小  

    const width: number = windowWidth - xDistance * 2  
    // @ts-ignore  
    const top: number = statusBarHeight + tDistance  

    const lineNum = Math.ceil(desc.length / Math.floor((width - YPadding * 2) / descSize)) * 1.2  

    const height = YPadding * 2 + titleSize + space + descSize * lineNum  

    // @ts-ignore  
    let _view = new plus.nativeObj.View(  
        idKey,  
        {  
            width: `${width}`,  
            top: `${top}`,  
            left: `${xDistance}`,  
            height: `${height}`,  
        },  
        [  
            {  
                tag: 'rect',  
                id: 'rect',  
                rectStyles: {  
                    color: '#FFFFFF',  
                    radius: `${radius}`,  
                },  
            },  
            {  
                tag: 'font',  
                id: 'title',  
                text: title,  
                textStyles: {size: `${titleSize}`, align: 'left', weight: 'bold'},  
                position: {top: `${YPadding}`, left: `${xPadding}`, height: 'wrap_content'},  
            },  
            {  
                tag: 'font',  
                id: 'desc',  
                text: desc,  
                textStyles: {size: `${descSize}`, align: 'left', color: '#666666', whiteSpace: 'normal'},  
                position: {  
                    top: `${YPadding + titleSize + space}`,  
                    left: `${xPadding}`,  
                    height: 'wrap_content',  
                    width: `${width - YPadding * 2}`,  
                },  
            },  
        ]  
    )  

    _view.show()  

    view = _view  
}  

/**  
 * 关闭消息  
 */  
export function closeNotify() {  
    if (view) view.close()  
}  
收起阅读 »

uniapp 在抖音(字节)小程序在安卓手机上,使用自己封装的input组件或者easy-input组件时,多次进入修改页面,不定时为空的问题。

input 抖音小程序

在修改页面使用input组件时,多次反复进入修改页面,input的值有时为空,是因为 抖音小程序的input组件的maxlength会触发bindinput事件,并且执行顺序是不确定的。
目前抖音官方好像还没有修复,可使用如下代码临时解决:

            /**  
             * 输入时触发  
             * @param {Object} event  
             */  
            onInput(event) {  
                let value = event.detail.value  

                // 解决抖音小程序 反复返回,输入框值为空的问题(maxlength在安卓手机上会触发bindinput事件)  
                // #ifdef MP-TOUTIAO  
                if(!value && this.modelValue){  
                    return  
                }  
                // #endif  

                this.val = value;  
                // TODO 兼容 vue2  
                this.$emit('input', value);  
                // TODO 兼容 vue3  
                this.$emit('update:modelValue', value);  
            },
继续阅读 »

在修改页面使用input组件时,多次反复进入修改页面,input的值有时为空,是因为 抖音小程序的input组件的maxlength会触发bindinput事件,并且执行顺序是不确定的。
目前抖音官方好像还没有修复,可使用如下代码临时解决:

            /**  
             * 输入时触发  
             * @param {Object} event  
             */  
            onInput(event) {  
                let value = event.detail.value  

                // 解决抖音小程序 反复返回,输入框值为空的问题(maxlength在安卓手机上会触发bindinput事件)  
                // #ifdef MP-TOUTIAO  
                if(!value && this.modelValue){  
                    return  
                }  
                // #endif  

                this.val = value;  
                // TODO 兼容 vue2  
                this.$emit('input', value);  
                // TODO 兼容 vue3  
                this.$emit('update:modelValue', value);  
            },
收起阅读 »

支付宝小程序map高级定制渲染踩坑

地图 xml map 支付宝小程序

使用支付宝小程序的map callout气泡功能时出现了customCallout不显示 / xml文件不被编译等等一系列的问题,网上的信息比较杂乱,分享一下自己的解决过程

支付宝小程序中的customCallout在真机中只显示第一个,改用iconLayout实现功能;

使用map高级定制渲染时,xml文件不会被编译,按照支付宝官方文档,在manifest.json中添加:

"mp-alipay": {  
  "include": ["**/*.xml"]  
}

添加后没有效果,之后发现可以在项目根目录添加mini.project.json文件,在此文件中添加配置:

{  
  "enableAppxNg": true,  
  "include": ["**/*.xml"]  
}

marker:

// #ifdef MP-ALIPAY  
iconLayout: {  
  params: {  
    count: 1  
  },  
  src: '/static/map-xml/map-icon.xml'  
}  
// #endif

map-icon.xml:

<box layout="horizontal">  
  <text  
    id="test1"  
    clickable="true"  
    text="测试1"  
    padding-left="8"  
    padding-right="8"  
    font-size="16"  
    border-radius="6"  
    background-color="#FF0000"  
  />  
  <text  
    id="test2"  
    clickable="true"  
    text="测试2"  
    padding-left="8"  
    padding-right="8"  
    font-size="16"  
    border-radius="6"  
    background-color="#FF0000"  
  />  
</box>

编译成功后在支付宝开发者工具mini.project.json文件中的配置


xml文件需要放入static文件夹中,放在根目录时不会编译

真机运行后气泡正常显示

继续阅读 »

使用支付宝小程序的map callout气泡功能时出现了customCallout不显示 / xml文件不被编译等等一系列的问题,网上的信息比较杂乱,分享一下自己的解决过程

支付宝小程序中的customCallout在真机中只显示第一个,改用iconLayout实现功能;

使用map高级定制渲染时,xml文件不会被编译,按照支付宝官方文档,在manifest.json中添加:

"mp-alipay": {  
  "include": ["**/*.xml"]  
}

添加后没有效果,之后发现可以在项目根目录添加mini.project.json文件,在此文件中添加配置:

{  
  "enableAppxNg": true,  
  "include": ["**/*.xml"]  
}

marker:

// #ifdef MP-ALIPAY  
iconLayout: {  
  params: {  
    count: 1  
  },  
  src: '/static/map-xml/map-icon.xml'  
}  
// #endif

map-icon.xml:

<box layout="horizontal">  
  <text  
    id="test1"  
    clickable="true"  
    text="测试1"  
    padding-left="8"  
    padding-right="8"  
    font-size="16"  
    border-radius="6"  
    background-color="#FF0000"  
  />  
  <text  
    id="test2"  
    clickable="true"  
    text="测试2"  
    padding-left="8"  
    padding-right="8"  
    font-size="16"  
    border-radius="6"  
    background-color="#FF0000"  
  />  
</box>

编译成功后在支付宝开发者工具mini.project.json文件中的配置


xml文件需要放入static文件夹中,放在根目录时不会编译

真机运行后气泡正常显示

收起阅读 »

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

升级中心 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简谱渲染引擎

概述

欢迎您来到音乐创作的新境界!我们为您带来一款革命性的简谱渲染引擎,它将简谱的解析与播放体验提升到全新的高度。无论您是音乐教育者、作曲家,还是开发者,这款引擎都能为您提供无与伦比的功能与灵活性。

核心功能

概述

欢迎您来到音乐创作的新境界!我们为您带来一款革命性的简谱渲染引擎,它将简谱的解析与播放体验提升到全新的高度。无论您是音乐教育者、作曲家,还是开发者,这款引擎都能为您提供无与伦比的功能与灵活性。

核心功能

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

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并确定即可

收起阅读 »