HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

Nvue 首页快速启动模式

v3

v3编译模式(HBuilderX 2.5.0+),支持 nvue 首页的快速启动,视图层层不等逻辑层初始化完毕,即可同时进行渲染。

启用方式

在 manifest.json 可视化视图,app其他常用设置里,勾上fast启动模式。

或者在 manifest.json 源码视图里 app-plus 节点下配置 nvueLaunchMode 为 fast。

注意事项

启用快速启动模式后,app启动时,逻辑层和首页视图层是同时初始化的。(非fast模式时,是逻辑层先初始化,然后才开始加载视图层)
由于视图层渲染时,逻辑层不一定初始化完毕,此时首页的nvue页面内不可以依赖逻辑层的状态,具体为:

  • 纯nvue + fast,vuex可用
  • V3 + fast,vuex不可用
  • getApp() 使用存在限制,具体参考:getApp()
继续阅读 »

v3编译模式(HBuilderX 2.5.0+),支持 nvue 首页的快速启动,视图层层不等逻辑层初始化完毕,即可同时进行渲染。

启用方式

在 manifest.json 可视化视图,app其他常用设置里,勾上fast启动模式。

或者在 manifest.json 源码视图里 app-plus 节点下配置 nvueLaunchMode 为 fast。

注意事项

启用快速启动模式后,app启动时,逻辑层和首页视图层是同时初始化的。(非fast模式时,是逻辑层先初始化,然后才开始加载视图层)
由于视图层渲染时,逻辑层不一定初始化完毕,此时首页的nvue页面内不可以依赖逻辑层的状态,具体为:

  • 纯nvue + fast,vuex可用
  • V3 + fast,vuex不可用
  • getApp() 使用存在限制,具体参考:getApp()
收起阅读 »

开启 optional chaining 语法

我是使用 uni-cli 构建的,所以开启 optional-chaining 语法只需要安装了@babel/plugin-proposal-optional-chaining 包后在项目下的 babel.config.js 中配置

const plugins = ["@babel/plugin-proposal-optional-chaining"];

就是在 plugins 中加上@babel/plugin-proposal-optional-chaining 就好了,但是有些玄学问题,昨天搞了半天这个语法都不能用各种报错,今天啥也没改重新开机就能用了。。。。

也就是说这样配置是没问题的,还报错就重启试试?

原文地址

继续阅读 »

我是使用 uni-cli 构建的,所以开启 optional-chaining 语法只需要安装了@babel/plugin-proposal-optional-chaining 包后在项目下的 babel.config.js 中配置

const plugins = ["@babel/plugin-proposal-optional-chaining"];

就是在 plugins 中加上@babel/plugin-proposal-optional-chaining 就好了,但是有些玄学问题,昨天搞了半天这个语法都不能用各种报错,今天啥也没改重新开机就能用了。。。。

也就是说这样配置是没问题的,还报错就重启试试?

原文地址

收起阅读 »

社区被恶意攻击了吧?

社区被恶意攻击了吧,某些平台是真小人啊! 鄙视
截图留个证据!
2019-12-27

社区被恶意攻击了吧,某些平台是真小人啊! 鄙视
截图留个证据!
2019-12-27

APP读取本地文件夹内视频播放

缓存 视频 App

需要用到的几个方法:

// 从本地缓存中同步获取指定 key 对应的内容。  
uni.getStorageSync(KEY)  

// 下载文件资源到本地,客户端直接发起一个 HTTP GET 请求,返回文件的本地临时路径。  
uni.downloadFile(OBJECT)  

// 保存视频到本地  
uni.saveFile(OBJECT)  

// 将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。  
uni.setStorageSync(KEY,DATA)

首先定义一个变量:获取本地缓存数据,看本地视频是存在。
获取已下载视频目录中的视频列表,删除无关视频。

const videoData = uni.getStorageSync('videoData')  
// 获取本地视频列表  
uni.getSavedFileList({  
    success: function (res) {  
        let fileList = res.fileList  
        console.log(fileList);  
        let needArr = videoData.map(item => item.videoAddress)  
        fileList.forEach((item, index) => {  
            if(needArr.includes(item.filePath)){  
                item.text = "需要这个视频"   
            }else{  
                // 删除视频列表中无关视频  
                uni.removeSavedFile({  
                    filePath: item.filePath,  
                    complete: function (res) {  
                        console.log(res);  
                    }  
                });  
            }  
        });  
        console.log(fileList)  

    }  
});  

写个条件判断:如果存在直接将视频地址插入页面进行播放,如果不存在则进行下载视频-保存视频-缓存本地链接。

const _this = this;  
// 判断是否有缓存  
if(videoData){  
    console.log('我是视频-有缓存')  
    _this.videoSrc = videoData  
}else{  
    console.log('我是视频-没有有缓存')  
    _this.videoSrc = url // 我是在线链接  
    // 下载视频  
    _this.downFile(videoUrlYi,'videoData');  
}  

// 下载视频  
downFile(url,name){  
    const _this = this;  
    uni.downloadFile({  
        url: url,   
        success: (res) => {  
            if (res.statusCode === 200) {  
                console.log('下载成功');  
                console.log(res.tempFilePath)  
                //保存视频到本地  
                uni.saveFile({  
                    tempFilePath: res.tempFilePath,  
                    success: function (res) {  
                        console.log('保存成功');  
                        var savedFilePath = res.savedFilePath;  
                        console.log(savedFilePath)  
                        _this.videoSrc = savedFilePath  
                        console.log('当前连接'+_this.videoSrc)  
                        uni.setStorageSync(name, savedFilePath);  
                    }  
                });  
            }  
        }  
    });  
}  

小编推荐:程序员网址导航

作为一名码农,随着平时工作的需要,这里收集了国内外很多优秀网站,这其中包括在线工具、在线运行、免费接口、在线资源、在线学习、技术论坛、技术博客等等,满足一般程序员日常需求。

地址:https://code-elf.cn/

继续阅读 »

需要用到的几个方法:

// 从本地缓存中同步获取指定 key 对应的内容。  
uni.getStorageSync(KEY)  

// 下载文件资源到本地,客户端直接发起一个 HTTP GET 请求,返回文件的本地临时路径。  
uni.downloadFile(OBJECT)  

// 保存视频到本地  
uni.saveFile(OBJECT)  

// 将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。  
uni.setStorageSync(KEY,DATA)

首先定义一个变量:获取本地缓存数据,看本地视频是存在。
获取已下载视频目录中的视频列表,删除无关视频。

const videoData = uni.getStorageSync('videoData')  
// 获取本地视频列表  
uni.getSavedFileList({  
    success: function (res) {  
        let fileList = res.fileList  
        console.log(fileList);  
        let needArr = videoData.map(item => item.videoAddress)  
        fileList.forEach((item, index) => {  
            if(needArr.includes(item.filePath)){  
                item.text = "需要这个视频"   
            }else{  
                // 删除视频列表中无关视频  
                uni.removeSavedFile({  
                    filePath: item.filePath,  
                    complete: function (res) {  
                        console.log(res);  
                    }  
                });  
            }  
        });  
        console.log(fileList)  

    }  
});  

写个条件判断:如果存在直接将视频地址插入页面进行播放,如果不存在则进行下载视频-保存视频-缓存本地链接。

const _this = this;  
// 判断是否有缓存  
if(videoData){  
    console.log('我是视频-有缓存')  
    _this.videoSrc = videoData  
}else{  
    console.log('我是视频-没有有缓存')  
    _this.videoSrc = url // 我是在线链接  
    // 下载视频  
    _this.downFile(videoUrlYi,'videoData');  
}  

// 下载视频  
downFile(url,name){  
    const _this = this;  
    uni.downloadFile({  
        url: url,   
        success: (res) => {  
            if (res.statusCode === 200) {  
                console.log('下载成功');  
                console.log(res.tempFilePath)  
                //保存视频到本地  
                uni.saveFile({  
                    tempFilePath: res.tempFilePath,  
                    success: function (res) {  
                        console.log('保存成功');  
                        var savedFilePath = res.savedFilePath;  
                        console.log(savedFilePath)  
                        _this.videoSrc = savedFilePath  
                        console.log('当前连接'+_this.videoSrc)  
                        uni.setStorageSync(name, savedFilePath);  
                    }  
                });  
            }  
        }  
    });  
}  

小编推荐:程序员网址导航

作为一名码农,随着平时工作的需要,这里收集了国内外很多优秀网站,这其中包括在线工具、在线运行、免费接口、在线资源、在线学习、技术论坛、技术博客等等,满足一般程序员日常需求。

地址:https://code-elf.cn/

收起阅读 »

授权弹出误点或者测试点击拒绝授权后无法在弹出授权窗口auth deny终极解决方案

小程序 uniapp

先贴代码
视图层部分

<template>  
    <view>  
        <button @tap="startRecord">开始录音</button>  
        <button @tap="endRecord">停止录音</button>  
        <button @tap="playVoice">播放录音</button>  
        <button @tap="uploadvoice">上传文件</button>  
    </view>  
</template>

js部分

<script>  
    let recorderManager = uni.getRecorderManager();  
    let innerAudioContext = uni.createInnerAudioContext();  

    innerAudioContext.autoplay = true;  

    export default {  
        data: {  
            text: 'uni-app',  
            voicePath: ''  
        },  
        onLoad() {  

        },  
        methods: {  
            uploadvoice() {  
                var target = this  
                let haslogin = false  
                let accesstoken = ''  
                try {  
                    accesstoken = uni.getStorageSync('accesstoken');  
                    if (accesstoken) {  

                        haslogin = true  
                    } else {  
                        uni.showToast({  
                            title: '你还没有登录',  
                            icon: 'none'  
                        })  
                        return  
                    }  

                } catch (e) {  
                    // error  
                }  
                if (target.voicePath == '') {  
                    uni.showToast({  
                        title: '你还没录音',  
                        icon: 'none'  
                    })  
                    return  
                }  
                //上传音频文件  
                uni.uploadFile({  
                    url: target.$api.UploadvoiceFile, //接口地址  
                    filePath: target.voicePath, //临时音频文件  
                    name: 'appfile',  
                    formData: {  
                        'accesstoken': accesstoken,  
                        'viewfee': 5 //付费金额  
                    },  
                    success: (uploadFileRes) => {  
                        console.log(uploadFileRes.data);  
                    }  
                });  
            },  
            startRecord() {  
                //录音先判断是否有录音权限  
                uni.getSetting({  
                    success(res) {  
                        //获取设置成功  
                        console.log(res.authSetting)  
                        if (!res.authSetting['scope.record']) {  
                            //如果没开启录音麦克风权限提示打开  
                            uni.openSetting({  
                                success(res) {  
                                    //打开成功,提示获取录音权限  
                                    console.log(res.authSetting)  
                                    uni.authorize({  
                                        scope: 'scope.record',  
                                        success() {  
                                            //录音权限获取成功开始录音  
                                            console.log('开始录音2');  

                                            recorderManager.start({  
                                                format: 'mp3',  
                                                duration: 600000  
                                            });  
                                        },  
                                        complete() {  
                                            console.log('调用接口完成');  
                                        },  
                                        fail(res) {  
                                            uni.showToast({  
                                                title: '您拒绝了应用获取麦克风权限',  
                                                icon: 'none'  
                                            })  
                                            console.log(res);  
                                        }  
                                    })  
                                }  
                            });  
                        } else {  
                            recorderManager.start({  
                                format: 'mp3',  
                                duration: 600000  
                            });  
                        }  
                    }  
                })  
            },  
            endRecord() {  
                //默认官方把此onStop方法写onload里,但是如果被拒绝的状态后续录音成功也会不执行停止回调,onload只在加载状态执行一次  
                //保险起见,用户后续开启麦克风权限还能正常回调写停止方法里  
                let self = this;  
                recorderManager.onStop(function(res) {  
                    console.log('recorder stop' + JSON.stringify(res));  
                    self.voicePath = res.tempFilePath;  
                });  
                console.log('录音结束');  
                recorderManager.stop();  

            },  
            playVoice() {  
                console.log('播放录音');  

                if (this.voicePath) {  
                    innerAudioContext.src = this.voicePath;  
                    innerAudioContext.play();  
                }  
            }  
        }  
    }  
</script>

音频文件设置了mp3格式,默认10分钟
上面demo是根据官方录音功能改进的,主要是小程序端,startRecord()录音方法里写了如何解决不弹出授权问题,demo可以直接使用测试,包含上传音频文件方法

php接收音频文件
function uploavoice() {
$message = array ();
$accesstoken = $_POST ['accesstoken'];

    $user = $this->check_token ( $accesstoken );  
    if ($user) {  
        if (! empty ( $_FILES ['appfile'] )) {  
            // 获取扩展名  
            $pathinfo = pathinfo ( $_FILES ['appfile'] ['name'] );  

            $exename = strtolower ( $pathinfo ['extension'] );  
            if ($exename != 'mp3' ) {  
                $message ['code'] = 2001;  
                $message ['data'] = null;  
                $message ['message'] = "音频扩展不支持";  
                echo json_encode ( $message );  
                exit ();  
            }  
            $imageSavePath = 'data/weixinrecord/' . uniqid () . '.' . $exename;  
            if (move_uploaded_file ( $_FILES ['appfile'] ['tmp_name'], FCPATH . $imageSavePath )) {  
                $message ['code'] = 2000;  
                $message ['data'] = SITE_URL . $imageSavePath;  
                $message ['message'] = "音频上传成功";  
                echo json_encode ( $message );  
                exit ();  
            }  
        } else {  
            $message ['code'] = 2002;  
            $message ['data'] = null;  
            $message ['message'] = "请上传音频文件";  
            echo json_encode ( $message );  
            exit ();  
        }  
    } else {  
        $message ['code'] = 2088;  
        $message ['data'] = null;  
        $message ['message'] = "用户信息过期";  
        echo json_encode ( $message );  
        exit ();  
    }  
}  
继续阅读 »

先贴代码
视图层部分

<template>  
    <view>  
        <button @tap="startRecord">开始录音</button>  
        <button @tap="endRecord">停止录音</button>  
        <button @tap="playVoice">播放录音</button>  
        <button @tap="uploadvoice">上传文件</button>  
    </view>  
</template>

js部分

<script>  
    let recorderManager = uni.getRecorderManager();  
    let innerAudioContext = uni.createInnerAudioContext();  

    innerAudioContext.autoplay = true;  

    export default {  
        data: {  
            text: 'uni-app',  
            voicePath: ''  
        },  
        onLoad() {  

        },  
        methods: {  
            uploadvoice() {  
                var target = this  
                let haslogin = false  
                let accesstoken = ''  
                try {  
                    accesstoken = uni.getStorageSync('accesstoken');  
                    if (accesstoken) {  

                        haslogin = true  
                    } else {  
                        uni.showToast({  
                            title: '你还没有登录',  
                            icon: 'none'  
                        })  
                        return  
                    }  

                } catch (e) {  
                    // error  
                }  
                if (target.voicePath == '') {  
                    uni.showToast({  
                        title: '你还没录音',  
                        icon: 'none'  
                    })  
                    return  
                }  
                //上传音频文件  
                uni.uploadFile({  
                    url: target.$api.UploadvoiceFile, //接口地址  
                    filePath: target.voicePath, //临时音频文件  
                    name: 'appfile',  
                    formData: {  
                        'accesstoken': accesstoken,  
                        'viewfee': 5 //付费金额  
                    },  
                    success: (uploadFileRes) => {  
                        console.log(uploadFileRes.data);  
                    }  
                });  
            },  
            startRecord() {  
                //录音先判断是否有录音权限  
                uni.getSetting({  
                    success(res) {  
                        //获取设置成功  
                        console.log(res.authSetting)  
                        if (!res.authSetting['scope.record']) {  
                            //如果没开启录音麦克风权限提示打开  
                            uni.openSetting({  
                                success(res) {  
                                    //打开成功,提示获取录音权限  
                                    console.log(res.authSetting)  
                                    uni.authorize({  
                                        scope: 'scope.record',  
                                        success() {  
                                            //录音权限获取成功开始录音  
                                            console.log('开始录音2');  

                                            recorderManager.start({  
                                                format: 'mp3',  
                                                duration: 600000  
                                            });  
                                        },  
                                        complete() {  
                                            console.log('调用接口完成');  
                                        },  
                                        fail(res) {  
                                            uni.showToast({  
                                                title: '您拒绝了应用获取麦克风权限',  
                                                icon: 'none'  
                                            })  
                                            console.log(res);  
                                        }  
                                    })  
                                }  
                            });  
                        } else {  
                            recorderManager.start({  
                                format: 'mp3',  
                                duration: 600000  
                            });  
                        }  
                    }  
                })  
            },  
            endRecord() {  
                //默认官方把此onStop方法写onload里,但是如果被拒绝的状态后续录音成功也会不执行停止回调,onload只在加载状态执行一次  
                //保险起见,用户后续开启麦克风权限还能正常回调写停止方法里  
                let self = this;  
                recorderManager.onStop(function(res) {  
                    console.log('recorder stop' + JSON.stringify(res));  
                    self.voicePath = res.tempFilePath;  
                });  
                console.log('录音结束');  
                recorderManager.stop();  

            },  
            playVoice() {  
                console.log('播放录音');  

                if (this.voicePath) {  
                    innerAudioContext.src = this.voicePath;  
                    innerAudioContext.play();  
                }  
            }  
        }  
    }  
</script>

音频文件设置了mp3格式,默认10分钟
上面demo是根据官方录音功能改进的,主要是小程序端,startRecord()录音方法里写了如何解决不弹出授权问题,demo可以直接使用测试,包含上传音频文件方法

php接收音频文件
function uploavoice() {
$message = array ();
$accesstoken = $_POST ['accesstoken'];

    $user = $this->check_token ( $accesstoken );  
    if ($user) {  
        if (! empty ( $_FILES ['appfile'] )) {  
            // 获取扩展名  
            $pathinfo = pathinfo ( $_FILES ['appfile'] ['name'] );  

            $exename = strtolower ( $pathinfo ['extension'] );  
            if ($exename != 'mp3' ) {  
                $message ['code'] = 2001;  
                $message ['data'] = null;  
                $message ['message'] = "音频扩展不支持";  
                echo json_encode ( $message );  
                exit ();  
            }  
            $imageSavePath = 'data/weixinrecord/' . uniqid () . '.' . $exename;  
            if (move_uploaded_file ( $_FILES ['appfile'] ['tmp_name'], FCPATH . $imageSavePath )) {  
                $message ['code'] = 2000;  
                $message ['data'] = SITE_URL . $imageSavePath;  
                $message ['message'] = "音频上传成功";  
                echo json_encode ( $message );  
                exit ();  
            }  
        } else {  
            $message ['code'] = 2002;  
            $message ['data'] = null;  
            $message ['message'] = "请上传音频文件";  
            echo json_encode ( $message );  
            exit ();  
        }  
    } else {  
        $message ['code'] = 2088;  
        $message ['data'] = null;  
        $message ['message'] = "用户信息过期";  
        echo json_encode ( $message );  
        exit ();  
    }  
}  
收起阅读 »

无条件支持Dcloud,希望Dcloud越走越远

uniapp DCloud

在客户端产品开发没选型之前,北京一个客户建议用apicloud开发,我当时建议是uni-app,也算是眼缘吧,然后试着把老的vue项目重构成uni-app,并编译了百度小程序,头条小程序,微信小程序,体验流畅不卡,唯独app端还没做适配,估计改起来也不是很大工作量,uni-app开发文档写的还可以的,国产应该支持,本人就是做国产开源社区的,Dcloud是我唯一见过能快速编译到多平台且兼容性不错的开发工具,确实提升了开发速度。
有个小建议:
插件市场里的有些作者有点不负责,导致项目会开发可能会返工,他们有的开发完成没标注是否适合app端或者兼容到哪个平台小程序或者哪些机型有问题。
希望增加一个选择项,把已知问题让作者列出来,或者在插件入口给此插件单独开通一个话题讨论,这样利于知识沉淀,后来者也可以少踩坑,直接可以找到现成问题并解决。

继续阅读 »

在客户端产品开发没选型之前,北京一个客户建议用apicloud开发,我当时建议是uni-app,也算是眼缘吧,然后试着把老的vue项目重构成uni-app,并编译了百度小程序,头条小程序,微信小程序,体验流畅不卡,唯独app端还没做适配,估计改起来也不是很大工作量,uni-app开发文档写的还可以的,国产应该支持,本人就是做国产开源社区的,Dcloud是我唯一见过能快速编译到多平台且兼容性不错的开发工具,确实提升了开发速度。
有个小建议:
插件市场里的有些作者有点不负责,导致项目会开发可能会返工,他们有的开发完成没标注是否适合app端或者兼容到哪个平台小程序或者哪些机型有问题。
希望增加一个选择项,把已知问题让作者列出来,或者在插件入口给此插件单独开通一个话题讨论,这样利于知识沉淀,后来者也可以少踩坑,直接可以找到现成问题并解决。

收起阅读 »

hbuilderX编辑器貌似有点low

HBuilder

HBuilderX编辑器下载后,点击运行,出现卡死的界面。

我的是win10 64位机器。且发现官方给的windows下载版本是32位的。。

官方是不是有点太草率了。连一个编辑器安装都出现各种奇葩问题。。。

HBuilderX编辑器下载后,点击运行,出现卡死的界面。

我的是win10 64位机器。且发现官方给的windows下载版本是32位的。。

官方是不是有点太草率了。连一个编辑器安装都出现各种奇葩问题。。。

uni-app使用总结

uniapp

本文主要uni-app开发项目遇到的一些问题的总结,关于uni-app具体的介绍和使用请查看uni-app官网
笔者编译使用的HBuilderX版本为2.4.2.20191115。

样式

1.1 无法设置背景图片

直接使用图片,并且图片大于40kb,官网有说明,主要是针对web之外的平台存在限制。

1.2 根据官方文档设置窗口背景颜色无效(在pages.json设置窗口默认backgroundColor或者单独设置窗口style中的backgroundColor都没有效果)

在页面中设置

page {  
    background-color: #ccc;  
}

1.3 设置页面navigationBarShadow导航栏阴影无效

关于导航栏的一些问题只能自己编写自定义组件或者nvue组件来解决,具体可以阅读[官方文档](uni-app官网

1.4 使用flex布局,如果嵌套了scroll-view,flex布局会失效

<view class="content">  
        <view class="fixed-item"></view>  
        <view class="flex-item">  
            <scroll-view></scroll-view>  
        </view>  
 </view>
.content {  
    display: flex;  
    flex-direction: column;  
    align-items: center;  
    justify-content: center;  
    position: absolute;  
    top: 0;  
    bottom: 0;  
    width: 100%;  
}  

.fixed-item {  
    width: 100%;  
    height: 100px;  
    background-color: #007AFF;  
}  
.flex-item {  
    flex: 1;     
}

1.5 原生端给组件设置margin无效,margin不会出现合并的情况(使用flex时)

原因未知

vue语法

2.1 在原生端App.vue没有vue的生命周期,但是有页面的生命周期,但是web端两者都存在

原因未知

2.2 vuex在原生端是不支持使用命名空间的,但是在web端是支持的

原因未知

2.3 $attrs无效

2.4 不支持vue-router

使用uni.navigateTo和uni.switchTab代替router.push方法实现页面跳转,uni.swtichTab在跳转tab页面时使用,但是不能再tab页面使用uni.redirectTo,不然跳转的目标页面也会出现底部tab栏

2.5 app端不支持v-slot传值

原因未知

2.6 v-for方法遍历数字时,web端从1开始,但是原生端从0开始

原因未知

2.7 原生端ref无法获取uni原生组件,web端可行

原因未知

2.8 onShow第一次触发时,$refs的内容为空

在this.$nextTick(() => {})中使用$refs

组件与接口

3.1 组件不可控,无法通过event修改组件的显示值(input、switch组件)

Web端可以通过$refs设置(switch通过$refs获取然后设置switchChecked的值),但原生端无法通过$refs获取组件无法使用这个方法实现

3.2 uni-request无法设置cookie

将cookie数据放在header字段中

3.3 监听subNVue的显示和隐藏

SubNVue无法监听显示和隐藏,显示可以在调用show时在回调函数中触发。可以通过下列方式监听:

const qrcode = uni.getSubNVueById(‘qr_code’)   
qrcode.addEventListener(‘hide’, () => {   
    console.log(‘hideQrCode’)   
})   
qrcode.addEventListener('show', () => {   
    console.log(‘showQrCode’)   
}) 

3.4 disableScroll: true无法禁止页面整体滚动

设置

“App-plus”: {  
    “bounce”: “none”  
}

3.5 使用subNVues,点击遮罩将无法关闭popup

style样式中的background不能设置为除transparent的其他值

3.6 CanvasContext.draw在App中无法执行回调函数

在vue页面中可以使用,在nvue中无法执行回调函数

3.7 修改导航栏

var wv = this.$mp.page.$getAppWebview();  
        wv.setStyle({    
            titleNView: {    
                “buttons”: [  
                {  
                  “fontSrc”: “/static/uni.ttf”,  
                  “fontSize”: “14”,  
                  “color”: “#FFFFFF”,  
                  “text”: `\ue333 ${address.city}`,  
                  "background": "rgba(0,0,0,0)”,  
                  "float": “left”,  
                  “width”: “96px”  
                }  
              ]  
            }    
        });  

具体可以参考[官网](uni-app在App端动态修改原生导航栏 - DCloud问答

其他

  1. 在原生端不同平台也会存在差异。
  2. 针对很多比较奇怪的问题可以选择重新编译。
继续阅读 »

本文主要uni-app开发项目遇到的一些问题的总结,关于uni-app具体的介绍和使用请查看uni-app官网
笔者编译使用的HBuilderX版本为2.4.2.20191115。

样式

1.1 无法设置背景图片

直接使用图片,并且图片大于40kb,官网有说明,主要是针对web之外的平台存在限制。

1.2 根据官方文档设置窗口背景颜色无效(在pages.json设置窗口默认backgroundColor或者单独设置窗口style中的backgroundColor都没有效果)

在页面中设置

page {  
    background-color: #ccc;  
}

1.3 设置页面navigationBarShadow导航栏阴影无效

关于导航栏的一些问题只能自己编写自定义组件或者nvue组件来解决,具体可以阅读[官方文档](uni-app官网

1.4 使用flex布局,如果嵌套了scroll-view,flex布局会失效

<view class="content">  
        <view class="fixed-item"></view>  
        <view class="flex-item">  
            <scroll-view></scroll-view>  
        </view>  
 </view>
.content {  
    display: flex;  
    flex-direction: column;  
    align-items: center;  
    justify-content: center;  
    position: absolute;  
    top: 0;  
    bottom: 0;  
    width: 100%;  
}  

.fixed-item {  
    width: 100%;  
    height: 100px;  
    background-color: #007AFF;  
}  
.flex-item {  
    flex: 1;     
}

1.5 原生端给组件设置margin无效,margin不会出现合并的情况(使用flex时)

原因未知

vue语法

2.1 在原生端App.vue没有vue的生命周期,但是有页面的生命周期,但是web端两者都存在

原因未知

2.2 vuex在原生端是不支持使用命名空间的,但是在web端是支持的

原因未知

2.3 $attrs无效

2.4 不支持vue-router

使用uni.navigateTo和uni.switchTab代替router.push方法实现页面跳转,uni.swtichTab在跳转tab页面时使用,但是不能再tab页面使用uni.redirectTo,不然跳转的目标页面也会出现底部tab栏

2.5 app端不支持v-slot传值

原因未知

2.6 v-for方法遍历数字时,web端从1开始,但是原生端从0开始

原因未知

2.7 原生端ref无法获取uni原生组件,web端可行

原因未知

2.8 onShow第一次触发时,$refs的内容为空

在this.$nextTick(() => {})中使用$refs

组件与接口

3.1 组件不可控,无法通过event修改组件的显示值(input、switch组件)

Web端可以通过$refs设置(switch通过$refs获取然后设置switchChecked的值),但原生端无法通过$refs获取组件无法使用这个方法实现

3.2 uni-request无法设置cookie

将cookie数据放在header字段中

3.3 监听subNVue的显示和隐藏

SubNVue无法监听显示和隐藏,显示可以在调用show时在回调函数中触发。可以通过下列方式监听:

const qrcode = uni.getSubNVueById(‘qr_code’)   
qrcode.addEventListener(‘hide’, () => {   
    console.log(‘hideQrCode’)   
})   
qrcode.addEventListener('show', () => {   
    console.log(‘showQrCode’)   
}) 

3.4 disableScroll: true无法禁止页面整体滚动

设置

“App-plus”: {  
    “bounce”: “none”  
}

3.5 使用subNVues,点击遮罩将无法关闭popup

style样式中的background不能设置为除transparent的其他值

3.6 CanvasContext.draw在App中无法执行回调函数

在vue页面中可以使用,在nvue中无法执行回调函数

3.7 修改导航栏

var wv = this.$mp.page.$getAppWebview();  
        wv.setStyle({    
            titleNView: {    
                “buttons”: [  
                {  
                  “fontSrc”: “/static/uni.ttf”,  
                  “fontSize”: “14”,  
                  “color”: “#FFFFFF”,  
                  “text”: `\ue333 ${address.city}`,  
                  "background": "rgba(0,0,0,0)”,  
                  "float": “left”,  
                  “width”: “96px”  
                }  
              ]  
            }    
        });  

具体可以参考[官网](uni-app在App端动态修改原生导航栏 - DCloud问答

其他

  1. 在原生端不同平台也会存在差异。
  2. 针对很多比较奇怪的问题可以选择重新编译。
收起阅读 »

uni-app商业级应用实战!

uniapp uni_app

需要资料加企鹅:3340563142

需要资料加企鹅:3340563142

加载字体文件过大的问题,不是icon,而是fontFamily

App iconfont

目前在写app时遇到ttf文件加载太大了,放本地打包比app还大(差不多了),放服务器加载太慢,于是乎想要看看能不能把这个ttf字体包搞小一点。
首先是加载字体文件的loadFontFace,插件市场也有很好的字体引入插件。但是各种字体文件的通病就是ttf字体包很小的不支持汉字,支持全汉字等的文件比较大(动不动就支持拉丁,日,韩,朝等各种文字,还有各种生僻字,总共5-6万字的字体库)。
后来有人说,这些字体在html中可以这样加载,但是这样加载是不是相当于从字体包中挑出相应的文字重新生成一个文件,这样不能满足我的需求,因为我的文字有新闻类的文章,总不能每次都请求一个字体文件啊(或许这样也可以)。
然后就是类似于阿里icon的在线字体生成,我觉得是不是这样的也许能满足我的需求,直接把汉字常用字生成对应的字体文件,但是发现文字数量有限制,并不现实(某些文本确定的地方这样生成的文件更合适)。
最后,找到了这个将原有字体文件多余文字抽离,精简字体包的方法,精简ttf的方法,按照这个教程,我实现了3500常用字,7600常用字和8000常用字的字体包的生成。
为什么要发这个帖子,效果太显著了,有下面几点,1、我使用的思源宋体,原otf大概23M,精简版ttf只有3M左右(7600字),加载速度大大提升;2、最后的精简字体方法中有坑:首先,FontCreator工具收费,仅有win版,FontSubsetGUI工具仅支持ttf,对于otf支持度不够等;3、最后就是FontSubsetGUI的下载源收费,而且需要.net环境的依赖,各种字体文件大家随便去各种字体网站找一找都有下载的。
##############
刚又发现了,Nvue中引入的字体没有生效,weex引入,loadFontface引入都不行,css引入还是不行oo原来路径的问题啊。比如我的代码‘/static/font/siyuanSimSun.ttf’这个路径,还有就是引入的fontfamily不要用‘string’,倒是和Vue页面内的相反。
var domModule = weex.requireModule('dom');
domModule.addRule('fontFace', {
'fontFamily': "siyuanSimSun",
'src': "url('/static/font/siyuanSimSun.ttf')"
});
##############
刚测试苹果机,注意一下ttf的路径,原安卓的路径(/static/font/siyuanSimSun.ttf)比较随意,倒是iOS相对路径(./static/font/siyuanSimSun.ttf)一定要写准确,也可以转本地绝对路径'src': 'url("'+"file:/" + plus.io.convertLocalFileSystemURL("_www/static/font/siyuanSimSun.ttf")+'")'。

##############
又发现一个问题,之前的环境都是V3引擎,现在我发现关闭V3后Nvue里的字体引入方式失效了,我会重新找方案。

##############
最近把字体的引入搞差不多了,有两个点,V3没有问题,尽量不要在app里引Vue页面的字体,分别引入。Nvue比较好一点,但是不论Nue还是Vue的input,textarea,在安卓端都没办法把内容字体使用指定字体。

##############
本身随笔性质的,也没描述太清楚,既然官方大佬来了,我要把这个字体精简工具放出来,里边有好几个,具体是什么我也不懂,反正按上边的教程,很傻瓜的。

##############
突然发现忘记了一点,我在app.vue里放了一个全局字体
.siyuanSimSun{
font-family: siyuanSimSun; //这里不要带引号
}

继续阅读 »

目前在写app时遇到ttf文件加载太大了,放本地打包比app还大(差不多了),放服务器加载太慢,于是乎想要看看能不能把这个ttf字体包搞小一点。
首先是加载字体文件的loadFontFace,插件市场也有很好的字体引入插件。但是各种字体文件的通病就是ttf字体包很小的不支持汉字,支持全汉字等的文件比较大(动不动就支持拉丁,日,韩,朝等各种文字,还有各种生僻字,总共5-6万字的字体库)。
后来有人说,这些字体在html中可以这样加载,但是这样加载是不是相当于从字体包中挑出相应的文字重新生成一个文件,这样不能满足我的需求,因为我的文字有新闻类的文章,总不能每次都请求一个字体文件啊(或许这样也可以)。
然后就是类似于阿里icon的在线字体生成,我觉得是不是这样的也许能满足我的需求,直接把汉字常用字生成对应的字体文件,但是发现文字数量有限制,并不现实(某些文本确定的地方这样生成的文件更合适)。
最后,找到了这个将原有字体文件多余文字抽离,精简字体包的方法,精简ttf的方法,按照这个教程,我实现了3500常用字,7600常用字和8000常用字的字体包的生成。
为什么要发这个帖子,效果太显著了,有下面几点,1、我使用的思源宋体,原otf大概23M,精简版ttf只有3M左右(7600字),加载速度大大提升;2、最后的精简字体方法中有坑:首先,FontCreator工具收费,仅有win版,FontSubsetGUI工具仅支持ttf,对于otf支持度不够等;3、最后就是FontSubsetGUI的下载源收费,而且需要.net环境的依赖,各种字体文件大家随便去各种字体网站找一找都有下载的。
##############
刚又发现了,Nvue中引入的字体没有生效,weex引入,loadFontface引入都不行,css引入还是不行oo原来路径的问题啊。比如我的代码‘/static/font/siyuanSimSun.ttf’这个路径,还有就是引入的fontfamily不要用‘string’,倒是和Vue页面内的相反。
var domModule = weex.requireModule('dom');
domModule.addRule('fontFace', {
'fontFamily': "siyuanSimSun",
'src': "url('/static/font/siyuanSimSun.ttf')"
});
##############
刚测试苹果机,注意一下ttf的路径,原安卓的路径(/static/font/siyuanSimSun.ttf)比较随意,倒是iOS相对路径(./static/font/siyuanSimSun.ttf)一定要写准确,也可以转本地绝对路径'src': 'url("'+"file:/" + plus.io.convertLocalFileSystemURL("_www/static/font/siyuanSimSun.ttf")+'")'。

##############
又发现一个问题,之前的环境都是V3引擎,现在我发现关闭V3后Nvue里的字体引入方式失效了,我会重新找方案。

##############
最近把字体的引入搞差不多了,有两个点,V3没有问题,尽量不要在app里引Vue页面的字体,分别引入。Nvue比较好一点,但是不论Nue还是Vue的input,textarea,在安卓端都没办法把内容字体使用指定字体。

##############
本身随笔性质的,也没描述太清楚,既然官方大佬来了,我要把这个字体精简工具放出来,里边有好几个,具体是什么我也不懂,反正按上边的教程,很傻瓜的。

##############
突然发现忘记了一点,我在app.vue里放了一个全局字体
.siyuanSimSun{
font-family: siyuanSimSun; //这里不要带引号
}

收起阅读 »

地点搜索功能分享,用来弥补uniapp 使用uni.chooseLocation无法选择其他城市地点的不足

自己在使用uni.chooseLocation时发现只能搜索到当前城市的地点,无法搜索到其他城市的地点,这样导致实现地点搜索时限制很多,因此分享给出自己结合HTML5+实现的地点选择。

<template>  
    <view class="full-wrap">  
        <view id="map">  
        </view>  
        <view class="now-pos" @tap="searchPos(posCity)">  
            定位城市:{{posCity}}  
        </view>  
        <view class="choose-city flex-box">  
            <view class="city-item flex-box">  
                <text class="city flex-box" @tap="chooseCity">{{nowCity}}</text>  
                <image src="../../static/down.png" class="down-ico"></image>  
            </view>  
            <view class="search-box">  
                <input placeholder="搜索地点" type="text" name="input" @input="searchPos(nowCity)" style="width: 100% ;" v-model="searchText"></input>  
            </view>  
        </view>  
        <view class="search-result-wrap">  
            <view class="cu-item padding" :key="index" v-for="(item,index) in cityList" @click="chooseHandle(item)">  
                <view class="search-result-content">  
                    <text class="list-city-name">{{item.name}}</text>  
                    <text class="list-city-address text-gray">{{item.city+item.address}}</text>  
                </view>  
            </view>  
        </view>  
        <!-- 城市选择 该插件从插件市场下载 -->  
        <mpvue-picker :themeColor="themeColor" ref="mpvuePicker" :mode="mode" :deepLength="deepLength" :pickerValueDefault="pickerValueDefault"  
         @onConfirm="onConfirm" @="" :pickerValueArray="pickerValueArray"></mpvue-picker>  
    </view>  
</template>  

<script>  
    import cityData from '../../commonjs/city.data.js'  
    import mpvuePicker from '../../components/mpvue-picker/mpvuePicker.vue'  
    export default {  
        components: {  
            mpvuePicker  
        },  
        data() {  
            return {  
                lat: " 39.915",  
                lng: '116.404',  
                posCity: "",  
                nowCity: '',  
                searchText: "",  
                cityList: [],  
                pickerValueDefault: [0, 0],  
                themeColor: '#007AFF',  
                mode: 'multiLinkageSelector',  
                deepLength: 2,  
                pickerValueArray: cityData  
            }  
        },  
        mounted() {  
            // 默认以当前位置为中心  
            uni.getLocation({  
                geocode: 'true',  
                success: res => {  
                    this.posCity = res.address.city;  
                    this.nowCity = res.address.city;  
                    this.lat = res.latitude;  
                    this.lng = res.longitude;  
                    this.searchPos(res.address.city);  
                }  
            });  
        },  
        methods: {  
            searchPos(cityName) {  
                let map = this.$refs.map;  
                let searchObj = new plus.maps.Search(map);  
                searchObj.onPoiSearchComplete = (state, result)=> {  
                    if (state == 0) {  
                        if (result.currentNumber <= 0) {  
                            uni.showToast({  
                                title: '没有检索到结果',  
                                icon: 'none'  
                            });  
                        }  
                        this.cityList = [];  
                        for (let i = 0; i < result.currentNumber; i++) {  
                            let pos = result.getPosition(i);  
                            this.cityList.push(pos);  
                        }  
                    } else {  
                        uni.showToast({  
                            title: '检索失败',  
                            icon: 'none'  
                        });  
                    }  
                }  
                let pt = new plus.maps.Point(this.lng, this.lat);  
                // 默认搜索火车站  
                let nowPosCIty = this.searchText ? this.searchText : '火车站';  
                searchObj.poiSearchInCity(cityName, nowPosCIty);  
            },  
            chooseCity() {  
                this.$refs.mpvuePicker.show();  
            },  
            onCancel(e) {  
                console.log(e);  
            },  
            onConfirm(e) {  
                let pickerText = e.label.split('-');  
                this.searchPos(pickerText[1])  
            },  
            chooseHandle(e) {  
                console.log('选择地点的地点信息:');  
                console.log(e);  
            }  
        }  
    }  
</script>  

<style scoped>  
    #map1 {  
        height: 0;  
    }  
    .flex-box {  
        display: flex;  
    }  
.now-pos {  
    font-size: 30upx;  
    padding: 25upx;  
}  
    .choose-city {  
        background-color: #FFFFFF;  
        border-bottom: 0.5px solid #ddd;  
    }  

    .city-item {  
        width: 160upx;  
        color: grey;  
        padding: 20upx 15upx;  
        margin-left: 15upx;  
        border-right: 0.5px solid #ddd;  
        border-radius: 5upx;  
    }  

    .city {  
        font-size: 30upx;  
        margin-top: 8upx;  
    }  
.down-ico {  
    width: 30upx;  
    height: 30upx;  
    position: relative;  
    top:16upx;  
}  
    .search-result-wrap {  
        padding: 10upx;  
    }  

    .search-result-content {  
        display: flex;  
        flex-direction: column;  
        padding: 10upx;  
        border-bottom: 0.5px solid #ddd;  
    }  

    .search-box {  
        font-size: 30upx;  
        width: 550upx;  
        padding: 20upx 15upx;  
        background-color: #FFFFFF;  
    }  

    .list-city-name {  
        font-size: 32upx;  
        margin-bottom: 0;  
        padding: 0;  
    }  

    .list-city-address {  
        font-size: 24upx;  
        color: gray;  
        margin-top: 0;  
    }  
</style>  

其中使用了城市选择插件,结合自己的需要自行选择,我直接在插件市场找了一个使用了,如有侵权,请联系本人QQ:1414901782;
演示视频:见附件
项目地址:uniapp选择地点

继续阅读 »

自己在使用uni.chooseLocation时发现只能搜索到当前城市的地点,无法搜索到其他城市的地点,这样导致实现地点搜索时限制很多,因此分享给出自己结合HTML5+实现的地点选择。

<template>  
    <view class="full-wrap">  
        <view id="map">  
        </view>  
        <view class="now-pos" @tap="searchPos(posCity)">  
            定位城市:{{posCity}}  
        </view>  
        <view class="choose-city flex-box">  
            <view class="city-item flex-box">  
                <text class="city flex-box" @tap="chooseCity">{{nowCity}}</text>  
                <image src="../../static/down.png" class="down-ico"></image>  
            </view>  
            <view class="search-box">  
                <input placeholder="搜索地点" type="text" name="input" @input="searchPos(nowCity)" style="width: 100% ;" v-model="searchText"></input>  
            </view>  
        </view>  
        <view class="search-result-wrap">  
            <view class="cu-item padding" :key="index" v-for="(item,index) in cityList" @click="chooseHandle(item)">  
                <view class="search-result-content">  
                    <text class="list-city-name">{{item.name}}</text>  
                    <text class="list-city-address text-gray">{{item.city+item.address}}</text>  
                </view>  
            </view>  
        </view>  
        <!-- 城市选择 该插件从插件市场下载 -->  
        <mpvue-picker :themeColor="themeColor" ref="mpvuePicker" :mode="mode" :deepLength="deepLength" :pickerValueDefault="pickerValueDefault"  
         @onConfirm="onConfirm" @="" :pickerValueArray="pickerValueArray"></mpvue-picker>  
    </view>  
</template>  

<script>  
    import cityData from '../../commonjs/city.data.js'  
    import mpvuePicker from '../../components/mpvue-picker/mpvuePicker.vue'  
    export default {  
        components: {  
            mpvuePicker  
        },  
        data() {  
            return {  
                lat: " 39.915",  
                lng: '116.404',  
                posCity: "",  
                nowCity: '',  
                searchText: "",  
                cityList: [],  
                pickerValueDefault: [0, 0],  
                themeColor: '#007AFF',  
                mode: 'multiLinkageSelector',  
                deepLength: 2,  
                pickerValueArray: cityData  
            }  
        },  
        mounted() {  
            // 默认以当前位置为中心  
            uni.getLocation({  
                geocode: 'true',  
                success: res => {  
                    this.posCity = res.address.city;  
                    this.nowCity = res.address.city;  
                    this.lat = res.latitude;  
                    this.lng = res.longitude;  
                    this.searchPos(res.address.city);  
                }  
            });  
        },  
        methods: {  
            searchPos(cityName) {  
                let map = this.$refs.map;  
                let searchObj = new plus.maps.Search(map);  
                searchObj.onPoiSearchComplete = (state, result)=> {  
                    if (state == 0) {  
                        if (result.currentNumber <= 0) {  
                            uni.showToast({  
                                title: '没有检索到结果',  
                                icon: 'none'  
                            });  
                        }  
                        this.cityList = [];  
                        for (let i = 0; i < result.currentNumber; i++) {  
                            let pos = result.getPosition(i);  
                            this.cityList.push(pos);  
                        }  
                    } else {  
                        uni.showToast({  
                            title: '检索失败',  
                            icon: 'none'  
                        });  
                    }  
                }  
                let pt = new plus.maps.Point(this.lng, this.lat);  
                // 默认搜索火车站  
                let nowPosCIty = this.searchText ? this.searchText : '火车站';  
                searchObj.poiSearchInCity(cityName, nowPosCIty);  
            },  
            chooseCity() {  
                this.$refs.mpvuePicker.show();  
            },  
            onCancel(e) {  
                console.log(e);  
            },  
            onConfirm(e) {  
                let pickerText = e.label.split('-');  
                this.searchPos(pickerText[1])  
            },  
            chooseHandle(e) {  
                console.log('选择地点的地点信息:');  
                console.log(e);  
            }  
        }  
    }  
</script>  

<style scoped>  
    #map1 {  
        height: 0;  
    }  
    .flex-box {  
        display: flex;  
    }  
.now-pos {  
    font-size: 30upx;  
    padding: 25upx;  
}  
    .choose-city {  
        background-color: #FFFFFF;  
        border-bottom: 0.5px solid #ddd;  
    }  

    .city-item {  
        width: 160upx;  
        color: grey;  
        padding: 20upx 15upx;  
        margin-left: 15upx;  
        border-right: 0.5px solid #ddd;  
        border-radius: 5upx;  
    }  

    .city {  
        font-size: 30upx;  
        margin-top: 8upx;  
    }  
.down-ico {  
    width: 30upx;  
    height: 30upx;  
    position: relative;  
    top:16upx;  
}  
    .search-result-wrap {  
        padding: 10upx;  
    }  

    .search-result-content {  
        display: flex;  
        flex-direction: column;  
        padding: 10upx;  
        border-bottom: 0.5px solid #ddd;  
    }  

    .search-box {  
        font-size: 30upx;  
        width: 550upx;  
        padding: 20upx 15upx;  
        background-color: #FFFFFF;  
    }  

    .list-city-name {  
        font-size: 32upx;  
        margin-bottom: 0;  
        padding: 0;  
    }  

    .list-city-address {  
        font-size: 24upx;  
        color: gray;  
        margin-top: 0;  
    }  
</style>  

其中使用了城市选择插件,结合自己的需要自行选择,我直接在插件市场找了一个使用了,如有侵权,请联系本人QQ:1414901782;
演示视频:见附件
项目地址:uniapp选择地点

收起阅读 »