HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

uniapp 在App端上传多张图片, 后端Java利用Spring的MultipartFile接收文件

提示: 由于在dcloud论坛发布的文章贴代码不便浏览,贴图片有些浏览器图片不能加载,所以本文章发布在简书了, 详情点击这里: uniapp在app端上传多图片,后端java接收

继续阅读 »

提示: 由于在dcloud论坛发布的文章贴代码不便浏览,贴图片有些浏览器图片不能加载,所以本文章发布在简书了, 详情点击这里: uniapp在app端上传多图片,后端java接收

收起阅读 »

Android动态权限申请

Android权限 Native.JS

更新:插件市场已经提供了封装更完善版本:https://ext.dcloud.net.cn/plugin?id=594

从HBuilderX1.9.4及以上版本开始,Android平台默认targetSdkVersion从21(Android5.0)调整为23(Android6.0)。

Android动态权限申请机制

Android6.0(API23)及以后,系统对权限的管理更加严格,放弃了以往manifest中注册所需权限,用户只要安装APP,便获取了所有注册权限的权限管理机制,而是改为除了需manifest中注册,部分危险权限另需在用户使用某项特殊功能时,向用户动态申请的机制。

当用户手机系统为Android6.0及以上,APP的targetSdkVersion>=23时,新的动态权限申请机制将会被触发,其它所有情况(1.系统版本>=6.0,targetSdkVersion<23;2.系统版本<6.0,targetSdkVersion>=23;3.系统版本<6.0,targetSdkVersion<23)都不会触发动态权限申请机制,因此,如果你不想在APP中动态申请权限,可以将targetSdkVersion设置为小于23。如不然,你就需要在使用某些涉及危险权限的功能(如读取通讯录)时通过系统弹窗的形式向用户动态申请该权限。动态申请权限下,如果用户在权限申请弹窗中拒绝了该申请,则用户将不能使用需要该权限的功能,再次申请该权限时依然会弹窗向用户申请;若用户在权限申请弹窗中勾选了“不再提示”并拒绝,那么再次申请该权限的时候将不会弹出系统弹窗向用户申请权限,此时需要APP引导用户打开设置,在设置中给与APP所需权限。
注意:云端打包targetSdkVersion默认值为26

5+APP中动态权限申请机制的实现

5+APP各独立模块中已经集成了功能所需权限的动态申请机制,开发者无需另做处理。但是如果需要使用某些尚未集成的特殊功能,如通过native.js调用原生方法获取手机扫描到的wifi列表,由于android可以通过访问wifi获取位置信息,因此需要在使用原生方法前先动态申请该功能所需的ACCESS_FINE_LOCATION权限。正因为有这样的需求,DCloud在native.js中为Android提供了动态申请权限的功能。

开发者通过调用plus.android.requestPermissions申请权限。参数permissions为所需权限数组;resultCallback为申请结果回调,将会返回已获取的权限、拒绝本次申请的权限、永久拒绝申请的权限3种结果的权限列表,开发者可以读取各权限申请结果并做相应处理;errorCallback为权限参数格式错误时调用,返回错误信息。

代码举例

依然以获取wifi列表为例,使用该功能前需要开发者先申请所需权限ACCESS_FINE_LOCATION:

function requestPermission() {  
    plus.android.requestPermissions(  
    ["android.permission.ACCESS_FINE_LOCATION"],  
    function(resultObj){  
        for (var i = 0; i < resultObj.granted.length; i++) {  
            var grantedPermission = resultObj.granted[i];  
            console.log('已获取的权限:'+ grantedPermission);  
        }  
        for (var i = 0; i < resultObj.deniedPresent.length; i++) {  
            var deniedPresentPermission = resultObj.deniedPresent[i];  
            console.log('拒绝本次申请的权限:'+ deniedPresentPermission );  
        }  
        for (var i = 0; i < resultObj.deniedAlways.length; i++) {  
            var deniedAlwaysPermission = resultObj.deniedAlways[i];  
            console.log('永久拒绝申请的权限:'+ deniedAlwaysPermission);  
        }  
        // 若所需权限被永久拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限  
        if (resultObj.deniedAlways.length > 0) {  
            var Intent = plus.android.importClass("android.content.Intent");  
            var Settings = plus.android.importClass("android.provider.Settings");  
            var Uri = plus.android.importClass("android.net.Uri");  
            var mainActivity = plus.android.runtimeMainActivity();  
            var intent = new Intent();  
            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);  
            var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);  
            intent.setData(uri);  
            mainActivity.startActivity(intent);  
        }  
    },  
    function(error){  
        console.log('申请权限错误:'+ error.code+ " = "+ error.message);  
    });  
}

引导用户打开所需权限的方法分析

当需要引导用户打开特定权限时,最理想的情况是打开一个只有该权限开关的页面让用户开启权限,但是Android会将应用申请的所有权限集中在一个页面,因此从Android系统提供的功能的角度讲,最好是能引导用户进入应用的权限管理页面,在这个页面中让用户根据提示打开相应权限。然而,国内厂商早在Android未提供动态权限申请功能时就对Android应用的权限申请进行了改造和封装,这就使开发者无法通过统一的入口进入应用权限管理页面,而需要通过各个厂商自己的入口进入,如

// 华为  
Intent intent = new Intent(packageName);  
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");  
intent.setComponent(comp);  
mContext.startActivity(intent);  
// 魅族  
Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");  
intent.addCategory(Intent.CATEGORY_DEFAULT);  
intent.putExtra("packageName", packageName);  
mContext.startActivity(intent);

这无疑加大了开发难度。而且各厂商更可能随着版本的升级更改权限管理入口,这就加大了开发的不确定性。因此,我们推荐的最理想的引导用户打开权限的入口是应用设置页面,然后用户通过点击“权限管理”自主进入权限管理页面进行相关权限的设置。
5+APP中利用native.js打开应用设置页面的方法见上方代码举例。

附Android危险权限列表

  • SMS(短信)
    android.permission.SEND_SMS
    android.permission.RECEIVE_SMS
    android.permission.READ_SMS
    android.permission.RECEIVE_WAP_PUSH
    android.permission.RECEIVE_MMS

  • STORAGE(存储卡,包括相册等)
    android.permission.READ_EXTERNAL_STORAGE
    android.permission.WRITE_EXTERNAL_STORAGE

  • CONTACTS(联系人)
    android.permission.READ_CONTACTS
    android.permission.WRITE_CONTACTS
    android.permission.GET_ACCOUNTS

  • PHONE(手机)
    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

  • CALENDAR(日历)
    android.permission.READ_CALENDAR
    android.permission.WRITE_CALENDAR

  • CAMERA(相机)
    android.permission.CAMERA

  • LOCATION(位置)
    android.permission.ACCESS_FINE_LOCATION
    android.permission.ACCESS_COARSE_LOCATION

  • SENSORS(传感器)
    android.permission.BODY_SENSORS

  • MICROPHONE(麦克风)
    android.permission.RECORD_AUDIO

Android官方权限概述(需翻墙)
Android官方权限列表(需翻墙)

相关问题

【报Bug】新版本hbuilder不支持安卓WIFI的扫描

iOS权限检查见:https://ask.dcloud.net.cn/article/35915

继续阅读 »

更新:插件市场已经提供了封装更完善版本:https://ext.dcloud.net.cn/plugin?id=594

从HBuilderX1.9.4及以上版本开始,Android平台默认targetSdkVersion从21(Android5.0)调整为23(Android6.0)。

Android动态权限申请机制

Android6.0(API23)及以后,系统对权限的管理更加严格,放弃了以往manifest中注册所需权限,用户只要安装APP,便获取了所有注册权限的权限管理机制,而是改为除了需manifest中注册,部分危险权限另需在用户使用某项特殊功能时,向用户动态申请的机制。

当用户手机系统为Android6.0及以上,APP的targetSdkVersion>=23时,新的动态权限申请机制将会被触发,其它所有情况(1.系统版本>=6.0,targetSdkVersion<23;2.系统版本<6.0,targetSdkVersion>=23;3.系统版本<6.0,targetSdkVersion<23)都不会触发动态权限申请机制,因此,如果你不想在APP中动态申请权限,可以将targetSdkVersion设置为小于23。如不然,你就需要在使用某些涉及危险权限的功能(如读取通讯录)时通过系统弹窗的形式向用户动态申请该权限。动态申请权限下,如果用户在权限申请弹窗中拒绝了该申请,则用户将不能使用需要该权限的功能,再次申请该权限时依然会弹窗向用户申请;若用户在权限申请弹窗中勾选了“不再提示”并拒绝,那么再次申请该权限的时候将不会弹出系统弹窗向用户申请权限,此时需要APP引导用户打开设置,在设置中给与APP所需权限。
注意:云端打包targetSdkVersion默认值为26

5+APP中动态权限申请机制的实现

5+APP各独立模块中已经集成了功能所需权限的动态申请机制,开发者无需另做处理。但是如果需要使用某些尚未集成的特殊功能,如通过native.js调用原生方法获取手机扫描到的wifi列表,由于android可以通过访问wifi获取位置信息,因此需要在使用原生方法前先动态申请该功能所需的ACCESS_FINE_LOCATION权限。正因为有这样的需求,DCloud在native.js中为Android提供了动态申请权限的功能。

开发者通过调用plus.android.requestPermissions申请权限。参数permissions为所需权限数组;resultCallback为申请结果回调,将会返回已获取的权限、拒绝本次申请的权限、永久拒绝申请的权限3种结果的权限列表,开发者可以读取各权限申请结果并做相应处理;errorCallback为权限参数格式错误时调用,返回错误信息。

代码举例

依然以获取wifi列表为例,使用该功能前需要开发者先申请所需权限ACCESS_FINE_LOCATION:

function requestPermission() {  
    plus.android.requestPermissions(  
    ["android.permission.ACCESS_FINE_LOCATION"],  
    function(resultObj){  
        for (var i = 0; i < resultObj.granted.length; i++) {  
            var grantedPermission = resultObj.granted[i];  
            console.log('已获取的权限:'+ grantedPermission);  
        }  
        for (var i = 0; i < resultObj.deniedPresent.length; i++) {  
            var deniedPresentPermission = resultObj.deniedPresent[i];  
            console.log('拒绝本次申请的权限:'+ deniedPresentPermission );  
        }  
        for (var i = 0; i < resultObj.deniedAlways.length; i++) {  
            var deniedAlwaysPermission = resultObj.deniedAlways[i];  
            console.log('永久拒绝申请的权限:'+ deniedAlwaysPermission);  
        }  
        // 若所需权限被永久拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限  
        if (resultObj.deniedAlways.length > 0) {  
            var Intent = plus.android.importClass("android.content.Intent");  
            var Settings = plus.android.importClass("android.provider.Settings");  
            var Uri = plus.android.importClass("android.net.Uri");  
            var mainActivity = plus.android.runtimeMainActivity();  
            var intent = new Intent();  
            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);  
            var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);  
            intent.setData(uri);  
            mainActivity.startActivity(intent);  
        }  
    },  
    function(error){  
        console.log('申请权限错误:'+ error.code+ " = "+ error.message);  
    });  
}

引导用户打开所需权限的方法分析

当需要引导用户打开特定权限时,最理想的情况是打开一个只有该权限开关的页面让用户开启权限,但是Android会将应用申请的所有权限集中在一个页面,因此从Android系统提供的功能的角度讲,最好是能引导用户进入应用的权限管理页面,在这个页面中让用户根据提示打开相应权限。然而,国内厂商早在Android未提供动态权限申请功能时就对Android应用的权限申请进行了改造和封装,这就使开发者无法通过统一的入口进入应用权限管理页面,而需要通过各个厂商自己的入口进入,如

// 华为  
Intent intent = new Intent(packageName);  
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");  
intent.setComponent(comp);  
mContext.startActivity(intent);  
// 魅族  
Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");  
intent.addCategory(Intent.CATEGORY_DEFAULT);  
intent.putExtra("packageName", packageName);  
mContext.startActivity(intent);

这无疑加大了开发难度。而且各厂商更可能随着版本的升级更改权限管理入口,这就加大了开发的不确定性。因此,我们推荐的最理想的引导用户打开权限的入口是应用设置页面,然后用户通过点击“权限管理”自主进入权限管理页面进行相关权限的设置。
5+APP中利用native.js打开应用设置页面的方法见上方代码举例。

附Android危险权限列表

  • SMS(短信)
    android.permission.SEND_SMS
    android.permission.RECEIVE_SMS
    android.permission.READ_SMS
    android.permission.RECEIVE_WAP_PUSH
    android.permission.RECEIVE_MMS

  • STORAGE(存储卡,包括相册等)
    android.permission.READ_EXTERNAL_STORAGE
    android.permission.WRITE_EXTERNAL_STORAGE

  • CONTACTS(联系人)
    android.permission.READ_CONTACTS
    android.permission.WRITE_CONTACTS
    android.permission.GET_ACCOUNTS

  • PHONE(手机)
    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

  • CALENDAR(日历)
    android.permission.READ_CALENDAR
    android.permission.WRITE_CALENDAR

  • CAMERA(相机)
    android.permission.CAMERA

  • LOCATION(位置)
    android.permission.ACCESS_FINE_LOCATION
    android.permission.ACCESS_COARSE_LOCATION

  • SENSORS(传感器)
    android.permission.BODY_SENSORS

  • MICROPHONE(麦克风)
    android.permission.RECORD_AUDIO

Android官方权限概述(需翻墙)
Android官方权限列表(需翻墙)

相关问题

【报Bug】新版本hbuilder不支持安卓WIFI的扫描

iOS权限检查见:https://ask.dcloud.net.cn/article/35915

收起阅读 »

应用云端打包国际化处理

国际化

云端打包配置国际化

从HBuilderX1.9.5开始,云端打包支持配置部分国际化功能,应用会根据当前系统设置的语言自动选择配置的国际化字符,如果不配置则使用应用默认配置的信息。

国际化支持的语言

应用内置支持以下语言:

  • 英文
  • 中文(简体)

国际化支持的内容

目前云端打包支持的国际化内容包括:

  • 应用名称
    在桌面显示的应用名称,配置国际化后,切换系统语言则会显示对应国际化配置的应用名称。
  • iOS平台隐私访问描述信息
    应用第一次使用涉及到用户隐私的功能是弹出授权确认框上显示的信息,提交App store审核时此信息必须准确描述获取此权限的原因。
    切换系统语言时弹出的授权确认框会显示对应国际化配置的描述信息,不配置国际化则总是使用HBuilderX可视化界面配置的描述信息。(注意:系统授权弹窗国际化只受系统语言设置影响,app 内设置语言不受影响)

manifest.json国际化配置

HBuilderX暂时还不支持可视化界面配置,需在“源码视图”中手动添加配置。
打开项目的manifest.json文件,切换到“源码视图”。
uni-app项目在"app-plus"->"locales"节点,5+ APP项目在"plus"->"locales"节点配置以下信息:

"locales": {  
        "en": {   // 英文  
            "name": "HBuilder",  // 应用名称  
            "android": {  
                "strings": {  //Android平台自定义字符串  
                    "CustomKey": "CustomValue",  
                    //...  
                }  
            },  
            "ios": {  
                "privacyDescription": { //iOS平台隐私访问描述信息  
                    "NSPhotoLibraryUsageDescription": "access to the user’s photo library(read)",  
                    //...  
                },  
                "infoPlist": {  //iOS平台自定义InfoPlist.strings  
                    "CustomKey": "CustomValue",  
                    //...  
                }  
            }  
        }  
        "zh": {   // 中文(简体)  
        }  
}

其中locales下的键名(key)可以取值:

  • 语言代码,通常为两个或三个字母,参考ISO 639规范,示例如下
语言名称 语言代码
中文 zh
英文 en
日语 ja
韩语 ko
法语 fr
西班牙语 es
  • 语言代码-地区代码,地区代码为两个字母,参考ISO 3166-2规范,示例如下
地区名称 地区代码
中国 CN
中国台湾 TW
中国香港 HK
美国 US
英国 GB
日本 JP
韩国 KR
法国 FR
西班牙 ES

<a id="strings"/>

android -> strings

Android平台配置自定义strings.xml文件,HBuilderX2.5.0+版本支持。
用于配置strings.xml国际化文件,可在此节点下配置使用uni原生插件使用的自定义国际化键值对。

                "strings": {  
                    "CustomKey1": "CustomValue1",  
                    "CustomKey2": "CustomValue2",  
                    "CustomKey3": "CustomValue3"  
                }

也可以配置内部业务逻辑使用的国际化字符串

应用启动时引导用户允许权限的提示语

字符串键名 字符串键值
dcloud_permission_write_external_storage_message 引导用户开启“读写手机存储”权限提示语
dcloud_permission_read_phone_state_message 引导用户开启“获取设备信息”权限提示语

配置应用启动时申请权限详细配置参考:https://ask.dcloud.net.cn/article/36549

html页面中input(type=file)打开的选择页面国际化字符串

字符串键名 字符串键值
dcloud_choose_an_action 选择页面标题,默认中文为“选择操作”

图片选择plus.gallery.pick国际化字符串(多图)

字符串键名 字符串键值
dcloud_gallery_library_name 媒体选择器
dcloud_gallery_all_dir_name 所有图片和视频
dcloud_gallery_all_video 所有视频
dcloud_gallery_all_image 所有图片
dcloud_gallery_select_title 选择图片和视频
dcloud_gallery_select_video_title 选择视频
dcloud_gallery_select_image_title 选择图片
dcloud_gallery_video_dir_name 所有视频
dcloud_gallery_msg_amount_limit 已达到选择数量上限
dcloud_gallery_msg_size_limit 请压缩和剪切后上传,文件最大只支持
dcloud_gallery_select_null 请选择文件
dcloud_gallery_done 完成
dcloud_gallery_count_string
dcloud_gallery_preview 预览
dcloud_gallery_select 选择
dcloud_gallery_video 视频
dcloud_gallery_cant_play_video 没有可以播放的程序
dcloud_gallery_read_external_storage 需要打开读取存储权限

<a id="privacyDescription"/>

ios -> privacyDescription

iOS平台配置隐私权限描述国际化。
建议将manifest.json页面切换到“模块权限配置”项,在“iOS隐私信息访问的许可描述”栏下配置应用需要使用到的隐私描述信息:


输入完成后切换到代码视图,uni-app项目在"app-plus"->"distribute"->"ios"->"privacyDescription"节点,5+ APP项目在"plus"->"distribute"->"apple"->"privacyDescription"节点下可看到输入的内容:

将"privacyDescription"节点下的内容拷贝到"locales"节点下要配置语言下的"ios"->"privacyDescription"节点下,并将值翻译为对应语言的描述。

完整可配置的隐私项可参考苹果官网https://developer.apple.com/documentation/bundleresources/information_property_list中以“NS”开头、“Description”结尾的项。

<a id="infoPlist"/>

ios -> infoPlist

iOS平台配置自定义InfoPlist.Strings文件,HBuilderX2.3.4+版本支持。
用于配置InfoPlist.Strings国际化文件, 可在此节点下配置使用uni原生插件使用的自定义国际化键值对。

                "infoPlist": {  
                    "CustomKey1": "CustomValue1",  
                    "CustomKey2": "CustomValue2",  
                    "CustomKey3": "CustomValue3"  
                }

应用内国际化处理

这里不描述应用如何动态切换语言(相关方法请参考其它标准H5方案),仅考虑如果获取当前系统的语言环境,业务代码根据语言环境进行国际化处理。

5+ APP项目

所有js都运行在系统Webview环境中,可以直接使用H5标准API获取当前系统设置的语言:

var lan = navigator.language||navigator.browserLanguage;  
console.log(lan);

详细规范参考:https://developer.mozilla.org/zh-CN/docs/Web/API/NavigatorLanguage/languages

uni-app项目

可以调用5+ API(plus.os.language)获取当前系统设置的语言:

var lan = plus.os.language;  
console.log(lan);

通常可以在App.vue页面的onLaunch中获取。
5+ API规范参考:http://www.html5plus.org/doc/zh_cn/device.html#plus.os.language

5+ API的国际化支持说明

大多数5+ API都不会涉及到国际化问题,除非有部分界面相关的API,5+ Runtime内部已经支持国际化,即根据当前系统设置的语言会自动处理国际化。
可能存在部分界面未完成国际化处理,我们会根据反馈情况确定优先级进行支持。

uni-app应用开发国际化说明参考https://ask.dcloud.net.cn/article/35872

继续阅读 »

云端打包配置国际化

从HBuilderX1.9.5开始,云端打包支持配置部分国际化功能,应用会根据当前系统设置的语言自动选择配置的国际化字符,如果不配置则使用应用默认配置的信息。

国际化支持的语言

应用内置支持以下语言:

  • 英文
  • 中文(简体)

国际化支持的内容

目前云端打包支持的国际化内容包括:

  • 应用名称
    在桌面显示的应用名称,配置国际化后,切换系统语言则会显示对应国际化配置的应用名称。
  • iOS平台隐私访问描述信息
    应用第一次使用涉及到用户隐私的功能是弹出授权确认框上显示的信息,提交App store审核时此信息必须准确描述获取此权限的原因。
    切换系统语言时弹出的授权确认框会显示对应国际化配置的描述信息,不配置国际化则总是使用HBuilderX可视化界面配置的描述信息。(注意:系统授权弹窗国际化只受系统语言设置影响,app 内设置语言不受影响)

manifest.json国际化配置

HBuilderX暂时还不支持可视化界面配置,需在“源码视图”中手动添加配置。
打开项目的manifest.json文件,切换到“源码视图”。
uni-app项目在"app-plus"->"locales"节点,5+ APP项目在"plus"->"locales"节点配置以下信息:

"locales": {  
        "en": {   // 英文  
            "name": "HBuilder",  // 应用名称  
            "android": {  
                "strings": {  //Android平台自定义字符串  
                    "CustomKey": "CustomValue",  
                    //...  
                }  
            },  
            "ios": {  
                "privacyDescription": { //iOS平台隐私访问描述信息  
                    "NSPhotoLibraryUsageDescription": "access to the user’s photo library(read)",  
                    //...  
                },  
                "infoPlist": {  //iOS平台自定义InfoPlist.strings  
                    "CustomKey": "CustomValue",  
                    //...  
                }  
            }  
        }  
        "zh": {   // 中文(简体)  
        }  
}

其中locales下的键名(key)可以取值:

  • 语言代码,通常为两个或三个字母,参考ISO 639规范,示例如下
语言名称 语言代码
中文 zh
英文 en
日语 ja
韩语 ko
法语 fr
西班牙语 es
  • 语言代码-地区代码,地区代码为两个字母,参考ISO 3166-2规范,示例如下
地区名称 地区代码
中国 CN
中国台湾 TW
中国香港 HK
美国 US
英国 GB
日本 JP
韩国 KR
法国 FR
西班牙 ES

<a id="strings"/>

android -> strings

Android平台配置自定义strings.xml文件,HBuilderX2.5.0+版本支持。
用于配置strings.xml国际化文件,可在此节点下配置使用uni原生插件使用的自定义国际化键值对。

                "strings": {  
                    "CustomKey1": "CustomValue1",  
                    "CustomKey2": "CustomValue2",  
                    "CustomKey3": "CustomValue3"  
                }

也可以配置内部业务逻辑使用的国际化字符串

应用启动时引导用户允许权限的提示语

字符串键名 字符串键值
dcloud_permission_write_external_storage_message 引导用户开启“读写手机存储”权限提示语
dcloud_permission_read_phone_state_message 引导用户开启“获取设备信息”权限提示语

配置应用启动时申请权限详细配置参考:https://ask.dcloud.net.cn/article/36549

html页面中input(type=file)打开的选择页面国际化字符串

字符串键名 字符串键值
dcloud_choose_an_action 选择页面标题,默认中文为“选择操作”

图片选择plus.gallery.pick国际化字符串(多图)

字符串键名 字符串键值
dcloud_gallery_library_name 媒体选择器
dcloud_gallery_all_dir_name 所有图片和视频
dcloud_gallery_all_video 所有视频
dcloud_gallery_all_image 所有图片
dcloud_gallery_select_title 选择图片和视频
dcloud_gallery_select_video_title 选择视频
dcloud_gallery_select_image_title 选择图片
dcloud_gallery_video_dir_name 所有视频
dcloud_gallery_msg_amount_limit 已达到选择数量上限
dcloud_gallery_msg_size_limit 请压缩和剪切后上传,文件最大只支持
dcloud_gallery_select_null 请选择文件
dcloud_gallery_done 完成
dcloud_gallery_count_string
dcloud_gallery_preview 预览
dcloud_gallery_select 选择
dcloud_gallery_video 视频
dcloud_gallery_cant_play_video 没有可以播放的程序
dcloud_gallery_read_external_storage 需要打开读取存储权限

<a id="privacyDescription"/>

ios -> privacyDescription

iOS平台配置隐私权限描述国际化。
建议将manifest.json页面切换到“模块权限配置”项,在“iOS隐私信息访问的许可描述”栏下配置应用需要使用到的隐私描述信息:


输入完成后切换到代码视图,uni-app项目在"app-plus"->"distribute"->"ios"->"privacyDescription"节点,5+ APP项目在"plus"->"distribute"->"apple"->"privacyDescription"节点下可看到输入的内容:

将"privacyDescription"节点下的内容拷贝到"locales"节点下要配置语言下的"ios"->"privacyDescription"节点下,并将值翻译为对应语言的描述。

完整可配置的隐私项可参考苹果官网https://developer.apple.com/documentation/bundleresources/information_property_list中以“NS”开头、“Description”结尾的项。

<a id="infoPlist"/>

ios -> infoPlist

iOS平台配置自定义InfoPlist.Strings文件,HBuilderX2.3.4+版本支持。
用于配置InfoPlist.Strings国际化文件, 可在此节点下配置使用uni原生插件使用的自定义国际化键值对。

                "infoPlist": {  
                    "CustomKey1": "CustomValue1",  
                    "CustomKey2": "CustomValue2",  
                    "CustomKey3": "CustomValue3"  
                }

应用内国际化处理

这里不描述应用如何动态切换语言(相关方法请参考其它标准H5方案),仅考虑如果获取当前系统的语言环境,业务代码根据语言环境进行国际化处理。

5+ APP项目

所有js都运行在系统Webview环境中,可以直接使用H5标准API获取当前系统设置的语言:

var lan = navigator.language||navigator.browserLanguage;  
console.log(lan);

详细规范参考:https://developer.mozilla.org/zh-CN/docs/Web/API/NavigatorLanguage/languages

uni-app项目

可以调用5+ API(plus.os.language)获取当前系统设置的语言:

var lan = plus.os.language;  
console.log(lan);

通常可以在App.vue页面的onLaunch中获取。
5+ API规范参考:http://www.html5plus.org/doc/zh_cn/device.html#plus.os.language

5+ API的国际化支持说明

大多数5+ API都不会涉及到国际化问题,除非有部分界面相关的API,5+ Runtime内部已经支持国际化,即根据当前系统设置的语言会自动处理国际化。
可能存在部分界面未完成国际化处理,我们会根据反馈情况确定优先级进行支持。

uni-app应用开发国际化说明参考https://ask.dcloud.net.cn/article/35872

收起阅读 »

h5模拟导航栏滑动渐变

查看视频

由于原生的导航栏自定义有限,使用plus.nativeObj.View自己来画又太费劲了,所以使用h5来实现一个,分享给大家。

  1. 修改pages.json,去年原生导航栏

    {  
                    "path": "nav-bar2/nav-bar2",  
                    "style": {  
                        "navigationBarTitleText": "NavBar 导航栏",  
                        "app-plus": {  
                            "titleNView": false  
                        }  
                    }  
                }

    2、页面

    
    <template>  
    <view style="display: flex; flex: 1;justify-content: center; background: #00BFFF;">  
        <view  style="position: fixed; width: 100%; background-color: #000000; " :style="{height: searchBgHeight + 'px', opacity:searchBgOpcity}" ></view>  
        <view class="header" :style="{'margin-top':statusBarHeight}"   
        style="background: #fff; position: fixed; width: 90%; display: flex;flex: 1;justify-content: center;box-shadow: none;border: 1px solid transparent;border-radius: 10upx;">  
            <uni-nav-bar style="box-shadow:none;"  color="#333333"   @click-left="showCity" @click-right="scan">  
                <block slot="left">  
                    <view class="city">  
                        <view>北京北京</view>  
                        <uni-icon type="arrowdown" color="#333333" size="22"></uni-icon>  
                    </view>  
                </block>  
                <view class="input-view">  
                    <input confirm-type="search" @confirm="confirm" class="input" type="text" placeholder="输入搜索关键词" />  
                    <uni-icon type="search" size="22" color="#666666"></uni-icon>  
                </view>  
            </uni-nav-bar>  
        </view>  
        <view class="content">  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
        </view>  
    
    </view>  
    </template>  

<script>
import uniStatusBar from '@/components/uni-status-bar/uni-status-bar.vue'
import uniNavBar from '@/components/uni-nav-bar2/uni-nav-bar2.vue'
import uniIcon from '@/components/uni-icon/uni-icon.vue'

var statusBarHeight = uni.getSystemInfoSync().statusBarHeight + 'px';  
export default {  
    components: {  
        uniNavBar,  
        uniIcon  
    },  
    data() {  

        return {  
            statusBarHeight: statusBarHeight,  
            searchBgHeight: '',  
            searchBgOpcity: '',  
            city: '北京北京'  
        }  
    },  
    created() {    
        let _t = this;    
        setTimeout(() => { //获取状态栏高度,setTimeout后才能调用uni.    
            uni.getSystemInfo({    
                success: function(res) {    
                    _t.searchBgHeight = res.statusBarHeight + 50  
                    _t.searchBgOpcity = 0.0  
                    console.log(_t.statusBarHeight);    
                }    
            });    
        }, 1);    
    } ,  
    methods: {  
        back() {  
            uni.navigateBack({  
                delta: 1  
            })  
        },  
        showMenu() {  
            uni.showToast({  
                title: '菜单'  
            })  
        },  
        clickLeft() {  
            uni.showToast({  
                title: '左侧按钮'  
            })  
        },  
        search() {  
            uni.showToast({  
                title: '搜索'  
            })  
        },  
        showCity() {  
            uni.showToast({  
                title: '选择城市'  
            })  
        },  
        scan() {  
            uni.showToast({  
                title: '扫码'  
            })  
        },  
        confirm() {  
            uni.showToast({  
                title: '搜索'  
            })  
        }  
    },  
    onPullDownRefresh() {  
        console.log('onPullDownRefresh')  
        setTimeout(function() {  
            uni.stopPullDownRefresh()  
            console.log('stopPullDownRefresh')  
        }, 1000)  
    },  
    onLoad() {  
        const pages = getCurrentPages();  
        const page = pages[pages.length - 1];  

        var that = this;  
        page.onPageScroll = function(data) {  

            let opcity = data.scrollTop  / 100;  
            if (opcity > 1) {  
                opcity = 1.0  
            }  
            that.searchBgOpcity = opcity;  
            console.log("page......searchBgOpcity==" + that.searchBgOpcity);  
        }  
    }  
}  

</script>

<style>
page {
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #fff
}

view {  
    font-size: 28upx;  
    line-height: inherit  
}  
..uni-navbar--shadow {  
    box-shadow: none;  
}  

.example {  
    padding: 0 30upx 30upx  
}  

.example-title {  
    font-size: 32upx;  
    line-height: 32upx;  
    color: #777;  
    margin: 40upx 25upx;  
    position: relative  
}  

.example .example-title {  
    margin: 40upx 0  
}  

.example-body {  
    padding: 0 40upx  
}  

.uni-common-mt {  
    color: #7a7e83;  
    font-size: 28upx;  
    padding: 30upx;  
}  

.title {  
    font-size: 15px;  
    line-height: 20px;  
    color: #333333;  
    padding: 15px;  
}  

.city {  
    display: flex;  
    flex-direction: row;  
    align-items: center;  
    justify-content: center;  
    width: 100%;  
    margin-left: 8px;  
    white-space: nowrap;  
}  

.input-view {  
    width: 92%;  
    display: flex;  
    height: 30px;  
    border-radius: 15px;  
    padding: 0 4%;  
    flex-wrap: nowrap;  
    margin: 3px 0;  
    line-height: 30px;  
}  

.input-view .uni-icon {  
    line-height: 30px !important;  
}  

.input-view .input {  
    height: 30px;  
    line-height: 30px;  
    width: 94%;  
    padding: 0 3%;  
}  

</style>


3、修改到Hello-uni-app中的一个uni-nav-bar.vue,修改名称叫uni-nav-bar2.vue  
<template>  
    <view class="uni-navbar">  
        <view class="uni-navbar__content" :class="{'uni-navbar--fixed': !!fixed}" :style="{'background-color':backgroundColor}" style="width: 100%;">  

            <view class="uni-navbar__header" :style="{color:color}"  >  
                <view class="uni-navbar__header-btns" @tap="onClickLeft">  
                    <view v-if="leftIcon.length">  
                        <uni-icon :type="leftIcon" :color="color" size="24"></uni-icon>  
                    </view>  
                    <view v-if="leftText.length" class="uni-navbar-btn-text" :class="{'uni-navbar-btn-icon-left':!leftIcon.length}">{{leftText}}</view>  
                    <slot name="left"></slot>  
                </view>  
                <view class="uni-navbar__header-container">  
                    <view v-if="title.length" class="uni-navbar__header-container-inner">{{title}}</view>  
                    <!-- 标题插槽 -->  
                    <slot></slot>  
                </view>  
                <!-- <view class="uni-navbar__header-btns" @tap="onClickRight">  
                    <view v-if="rightIcon.length">  
                        <uni-icon :type="rightIcon" :color="color" size="24"></uni-icon>  
                    </view>  
                     优先显示图标   
                    <view v-if="rightText.length&&!rightIcon.length" class="uni-navbar-btn-text">{{rightText}}</view>  
                    <slot name="right"></slot>  
                </view> -->  
            </view>  
        </view>  
        <view class="uni-navbar__placeholder" v-if="fixed">  
            <uni-status-bar v-if="statusBar"></uni-status-bar>  
            <view class="uni-navbar__placeholder-view"></view>  
        </view>  
    </view>  
</template>  

<script>  
    import uniStatusBar from '../uni-status-bar/uni-status-bar.vue'  
    import uniIcon from '../uni-icon/uni-icon.vue'  

    export default {  
        name: 'uni-nav-bar',  
        components: {  
            uniStatusBar,  
            uniIcon  
        },  
        props: {  
            title: {  
                type: String,  
                default: ''  
            },  
            leftText: {  
                type: String,  
                default: ''  
            },  
            rightText: {  
                type: String,  
                default: ''  
            },  
            leftIcon: {  
                type: String,  
                default: ''  
            },  
            rightIcon: {  
                type: String,  
                default: ''  
            },  
            fixed: {  
                type: [Boolean, String],  
                default: false  
            },  
            color: {  
                type: String,  
                default: '#000000'  
            },  
            backgroundColor: {  
                type: String,  
                default: 'transparent'  
            },  
            statusBar: {  
                type: [Boolean, String],  
                default: false  
            },  
            shadow: {  
                type: [String, Boolean],  
                default: true  
            },  
            border: {  
                type: [String, Boolean],  
                default: true  
            }  
        },  
        methods: {  
            onClickLeft() {  
                this.$emit('click-left')  
            },  
            onClickRight() {  
                this.$emit('click-right')  
            }  
        }  
    }  
</script>  

<style>  
    @charset "UTF-8";  

    .uni-navbar__content {  
        display: block;  
        position: relative;  
        width: 100%;  
        /* background-color: #fff; */  
        overflow: hidden  
    }  

    .uni-navbar__content view {  
        line-height: 44px  
    }  

    .uni-navbar__header {  
        display: flex;  
        flex-direction: row;  
        width: 100%;  
        height: 38px;  
        line-height: 38px;  
        font-size: 16px  
    }  

    .uni-navbar__header-btns {  
        display: inline-flex;  
        flex-wrap: nowrap;  
        flex-shrink: 0;  
        width: 120upx;  
        padding: 0 12upx  
    }  

    .uni-navbar__header-btns:first-child {  
        padding-left: 0  
    }  

    .uni-navbar__header-btns:last-child {  
        width: 60upx  
    }  

    .uni-navbar__header-container {  
        width: 100%;  
        margin: 0 10upx  
    }  

    .uni-navbar__header-container-inner {  
        font-size: 30upx;  
        text-align: center;  
        padding-right: 60upx  
    }  

    .uni-navbar__placeholder-view {  
        height: 38px  
    }  

    .uni-navbar--fixed {  
        position: fixed;  
        z-index: 998  
    }  

    .uni-navbar--shadow {  
        box-shadow: 0 1px 6px #ccc  
    }  

    .uni-navbar--border:after {  
        position: absolute;  
        z-index: 3;  
        bottom: 0;  
        left: 0;  
        right: 0;  
        height: 1px;  
        content: '';  
        -webkit-transform: scaleY(.5);  
        transform: scaleY(.5);  
        background-color: #c8c7cc  
    }  
</style>
继续阅读 »

查看视频

由于原生的导航栏自定义有限,使用plus.nativeObj.View自己来画又太费劲了,所以使用h5来实现一个,分享给大家。

  1. 修改pages.json,去年原生导航栏

    {  
                    "path": "nav-bar2/nav-bar2",  
                    "style": {  
                        "navigationBarTitleText": "NavBar 导航栏",  
                        "app-plus": {  
                            "titleNView": false  
                        }  
                    }  
                }

    2、页面

    
    <template>  
    <view style="display: flex; flex: 1;justify-content: center; background: #00BFFF;">  
        <view  style="position: fixed; width: 100%; background-color: #000000; " :style="{height: searchBgHeight + 'px', opacity:searchBgOpcity}" ></view>  
        <view class="header" :style="{'margin-top':statusBarHeight}"   
        style="background: #fff; position: fixed; width: 90%; display: flex;flex: 1;justify-content: center;box-shadow: none;border: 1px solid transparent;border-radius: 10upx;">  
            <uni-nav-bar style="box-shadow:none;"  color="#333333"   @click-left="showCity" @click-right="scan">  
                <block slot="left">  
                    <view class="city">  
                        <view>北京北京</view>  
                        <uni-icon type="arrowdown" color="#333333" size="22"></uni-icon>  
                    </view>  
                </block>  
                <view class="input-view">  
                    <input confirm-type="search" @confirm="confirm" class="input" type="text" placeholder="输入搜索关键词" />  
                    <uni-icon type="search" size="22" color="#666666"></uni-icon>  
                </view>  
            </uni-nav-bar>  
        </view>  
        <view class="content">  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
            <view style="margin: 20upx;">1111</view>  
        </view>  
    
    </view>  
    </template>  

<script>
import uniStatusBar from '@/components/uni-status-bar/uni-status-bar.vue'
import uniNavBar from '@/components/uni-nav-bar2/uni-nav-bar2.vue'
import uniIcon from '@/components/uni-icon/uni-icon.vue'

var statusBarHeight = uni.getSystemInfoSync().statusBarHeight + 'px';  
export default {  
    components: {  
        uniNavBar,  
        uniIcon  
    },  
    data() {  

        return {  
            statusBarHeight: statusBarHeight,  
            searchBgHeight: '',  
            searchBgOpcity: '',  
            city: '北京北京'  
        }  
    },  
    created() {    
        let _t = this;    
        setTimeout(() => { //获取状态栏高度,setTimeout后才能调用uni.    
            uni.getSystemInfo({    
                success: function(res) {    
                    _t.searchBgHeight = res.statusBarHeight + 50  
                    _t.searchBgOpcity = 0.0  
                    console.log(_t.statusBarHeight);    
                }    
            });    
        }, 1);    
    } ,  
    methods: {  
        back() {  
            uni.navigateBack({  
                delta: 1  
            })  
        },  
        showMenu() {  
            uni.showToast({  
                title: '菜单'  
            })  
        },  
        clickLeft() {  
            uni.showToast({  
                title: '左侧按钮'  
            })  
        },  
        search() {  
            uni.showToast({  
                title: '搜索'  
            })  
        },  
        showCity() {  
            uni.showToast({  
                title: '选择城市'  
            })  
        },  
        scan() {  
            uni.showToast({  
                title: '扫码'  
            })  
        },  
        confirm() {  
            uni.showToast({  
                title: '搜索'  
            })  
        }  
    },  
    onPullDownRefresh() {  
        console.log('onPullDownRefresh')  
        setTimeout(function() {  
            uni.stopPullDownRefresh()  
            console.log('stopPullDownRefresh')  
        }, 1000)  
    },  
    onLoad() {  
        const pages = getCurrentPages();  
        const page = pages[pages.length - 1];  

        var that = this;  
        page.onPageScroll = function(data) {  

            let opcity = data.scrollTop  / 100;  
            if (opcity > 1) {  
                opcity = 1.0  
            }  
            that.searchBgOpcity = opcity;  
            console.log("page......searchBgOpcity==" + that.searchBgOpcity);  
        }  
    }  
}  

</script>

<style>
page {
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #fff
}

view {  
    font-size: 28upx;  
    line-height: inherit  
}  
..uni-navbar--shadow {  
    box-shadow: none;  
}  

.example {  
    padding: 0 30upx 30upx  
}  

.example-title {  
    font-size: 32upx;  
    line-height: 32upx;  
    color: #777;  
    margin: 40upx 25upx;  
    position: relative  
}  

.example .example-title {  
    margin: 40upx 0  
}  

.example-body {  
    padding: 0 40upx  
}  

.uni-common-mt {  
    color: #7a7e83;  
    font-size: 28upx;  
    padding: 30upx;  
}  

.title {  
    font-size: 15px;  
    line-height: 20px;  
    color: #333333;  
    padding: 15px;  
}  

.city {  
    display: flex;  
    flex-direction: row;  
    align-items: center;  
    justify-content: center;  
    width: 100%;  
    margin-left: 8px;  
    white-space: nowrap;  
}  

.input-view {  
    width: 92%;  
    display: flex;  
    height: 30px;  
    border-radius: 15px;  
    padding: 0 4%;  
    flex-wrap: nowrap;  
    margin: 3px 0;  
    line-height: 30px;  
}  

.input-view .uni-icon {  
    line-height: 30px !important;  
}  

.input-view .input {  
    height: 30px;  
    line-height: 30px;  
    width: 94%;  
    padding: 0 3%;  
}  

</style>


3、修改到Hello-uni-app中的一个uni-nav-bar.vue,修改名称叫uni-nav-bar2.vue  
<template>  
    <view class="uni-navbar">  
        <view class="uni-navbar__content" :class="{'uni-navbar--fixed': !!fixed}" :style="{'background-color':backgroundColor}" style="width: 100%;">  

            <view class="uni-navbar__header" :style="{color:color}"  >  
                <view class="uni-navbar__header-btns" @tap="onClickLeft">  
                    <view v-if="leftIcon.length">  
                        <uni-icon :type="leftIcon" :color="color" size="24"></uni-icon>  
                    </view>  
                    <view v-if="leftText.length" class="uni-navbar-btn-text" :class="{'uni-navbar-btn-icon-left':!leftIcon.length}">{{leftText}}</view>  
                    <slot name="left"></slot>  
                </view>  
                <view class="uni-navbar__header-container">  
                    <view v-if="title.length" class="uni-navbar__header-container-inner">{{title}}</view>  
                    <!-- 标题插槽 -->  
                    <slot></slot>  
                </view>  
                <!-- <view class="uni-navbar__header-btns" @tap="onClickRight">  
                    <view v-if="rightIcon.length">  
                        <uni-icon :type="rightIcon" :color="color" size="24"></uni-icon>  
                    </view>  
                     优先显示图标   
                    <view v-if="rightText.length&&!rightIcon.length" class="uni-navbar-btn-text">{{rightText}}</view>  
                    <slot name="right"></slot>  
                </view> -->  
            </view>  
        </view>  
        <view class="uni-navbar__placeholder" v-if="fixed">  
            <uni-status-bar v-if="statusBar"></uni-status-bar>  
            <view class="uni-navbar__placeholder-view"></view>  
        </view>  
    </view>  
</template>  

<script>  
    import uniStatusBar from '../uni-status-bar/uni-status-bar.vue'  
    import uniIcon from '../uni-icon/uni-icon.vue'  

    export default {  
        name: 'uni-nav-bar',  
        components: {  
            uniStatusBar,  
            uniIcon  
        },  
        props: {  
            title: {  
                type: String,  
                default: ''  
            },  
            leftText: {  
                type: String,  
                default: ''  
            },  
            rightText: {  
                type: String,  
                default: ''  
            },  
            leftIcon: {  
                type: String,  
                default: ''  
            },  
            rightIcon: {  
                type: String,  
                default: ''  
            },  
            fixed: {  
                type: [Boolean, String],  
                default: false  
            },  
            color: {  
                type: String,  
                default: '#000000'  
            },  
            backgroundColor: {  
                type: String,  
                default: 'transparent'  
            },  
            statusBar: {  
                type: [Boolean, String],  
                default: false  
            },  
            shadow: {  
                type: [String, Boolean],  
                default: true  
            },  
            border: {  
                type: [String, Boolean],  
                default: true  
            }  
        },  
        methods: {  
            onClickLeft() {  
                this.$emit('click-left')  
            },  
            onClickRight() {  
                this.$emit('click-right')  
            }  
        }  
    }  
</script>  

<style>  
    @charset "UTF-8";  

    .uni-navbar__content {  
        display: block;  
        position: relative;  
        width: 100%;  
        /* background-color: #fff; */  
        overflow: hidden  
    }  

    .uni-navbar__content view {  
        line-height: 44px  
    }  

    .uni-navbar__header {  
        display: flex;  
        flex-direction: row;  
        width: 100%;  
        height: 38px;  
        line-height: 38px;  
        font-size: 16px  
    }  

    .uni-navbar__header-btns {  
        display: inline-flex;  
        flex-wrap: nowrap;  
        flex-shrink: 0;  
        width: 120upx;  
        padding: 0 12upx  
    }  

    .uni-navbar__header-btns:first-child {  
        padding-left: 0  
    }  

    .uni-navbar__header-btns:last-child {  
        width: 60upx  
    }  

    .uni-navbar__header-container {  
        width: 100%;  
        margin: 0 10upx  
    }  

    .uni-navbar__header-container-inner {  
        font-size: 30upx;  
        text-align: center;  
        padding-right: 60upx  
    }  

    .uni-navbar__placeholder-view {  
        height: 38px  
    }  

    .uni-navbar--fixed {  
        position: fixed;  
        z-index: 998  
    }  

    .uni-navbar--shadow {  
        box-shadow: 0 1px 6px #ccc  
    }  

    .uni-navbar--border:after {  
        position: absolute;  
        z-index: 3;  
        bottom: 0;  
        left: 0;  
        right: 0;  
        height: 1px;  
        content: '';  
        -webkit-transform: scaleY(.5);  
        transform: scaleY(.5);  
        background-color: #c8c7cc  
    }  
</style>
收起阅读 »

关于小米手机中 plus.ui.Toast 存在被自动添加 HBuilder 字样的解决方法

toast

前几天就遇到了这个问题,没有时间去处理这个小问题,所以就放过一边等待最后去解决,今天有闲下来了就一并解决这些小问题

遇到的小问题:
遇到的小问题

解决以后:
解决以后

解决方法:

var Toast = plus.android.importClass("android.widget.Toast");  
var mToast = Toast.makeText(plus.android.runtimeMainActivity(), null, Toast.LENGTH_SHORT);  
mToast.setText('Hello World!');  
mToast.show();
继续阅读 »

前几天就遇到了这个问题,没有时间去处理这个小问题,所以就放过一边等待最后去解决,今天有闲下来了就一并解决这些小问题

遇到的小问题:
遇到的小问题

解决以后:
解决以后

解决方法:

var Toast = plus.android.importClass("android.widget.Toast");  
var mToast = Toast.makeText(plus.android.runtimeMainActivity(), null, Toast.LENGTH_SHORT);  
mToast.setText('Hello World!');  
mToast.show();
收起阅读 »

解决上拉加载

当总页数小于等于pageSize时,首次加载需要禁用上拉加载,一般使用mui('#pullrefreshContainer').pullRefresh().disablePullupToRefresh();但是该方法时好时坏,经常有解决办法是使用定时器延时执行该方法,实际上仍不能解决mui存在的bug;以下是我的解决办法,供大家参考:
1、定义全局变量pullupToRefresh = true;//能否上拉加载
2、在ajax的回调函数里判断,如果总页数是否小于等于pageSize,则pullupToRefresh = false,否则pullupToRefresh = true;
3、在上拉加载方法里首先判断pullupToRefresh的值,当pullupToRefresh = true时才执行下拉刷新

继续阅读 »

当总页数小于等于pageSize时,首次加载需要禁用上拉加载,一般使用mui('#pullrefreshContainer').pullRefresh().disablePullupToRefresh();但是该方法时好时坏,经常有解决办法是使用定时器延时执行该方法,实际上仍不能解决mui存在的bug;以下是我的解决办法,供大家参考:
1、定义全局变量pullupToRefresh = true;//能否上拉加载
2、在ajax的回调函数里判断,如果总页数是否小于等于pageSize,则pullupToRefresh = false,否则pullupToRefresh = true;
3、在上拉加载方法里首先判断pullupToRefresh的值,当pullupToRefresh = true时才执行下拉刷新

收起阅读 »

接兼职\外包\咨询类

个人插件页LinkedIn个人网站

uni框架1年经验(熟悉vue生态,熟读源码),nodejs经验2年(express+mongodb+redis)
熟悉js原生开发与安卓原生插件开发

可接外包:

  • 网站项目(静态/动态)
  • uniapp项目
  • 服务器应用(数据库的Restful api设计或websocket服务器设计)
  • 普通组件
  • 第三方原生lib转化为uniapp的原生插件
  • 应用中接入ai功能
  • BUG消除

可全部typescript开发,可以个人或公司名义合作,可签合同

如有需求请联系报价
联系方式:qq527501080 email :snowolfjay@qq.com

继续阅读 »

个人插件页LinkedIn个人网站

uni框架1年经验(熟悉vue生态,熟读源码),nodejs经验2年(express+mongodb+redis)
熟悉js原生开发与安卓原生插件开发

可接外包:

  • 网站项目(静态/动态)
  • uniapp项目
  • 服务器应用(数据库的Restful api设计或websocket服务器设计)
  • 普通组件
  • 第三方原生lib转化为uniapp的原生插件
  • 应用中接入ai功能
  • BUG消除

可全部typescript开发,可以个人或公司名义合作,可签合同

如有需求请联系报价
联系方式:qq527501080 email :snowolfjay@qq.com

收起阅读 »

uni-app调用Android文件系统

最近用 uni-app做多媒体上传功能, 文件选择,视频,图片官方分别有uni.chooseVideo和uni.chooseImage接口,很方便,但是涉及到其他文件类型选择比如音频,就没有现成的接口。 在网上搜索了一圈,终于找到了通过Native.js调用原生文件系统的方法 。 原博客地址:https://www.cnblogs.com/lizhao123/p/9951581.html
我结合了作者的方法,简单的封装为pickFile.js,调用起来很方便。用法很简单,把该文件放到你的项目JS目录下,通过 import {pickFile} from '@/js/common/pickFile.js' 方式引入,然后

pickFile.PickFile(function(audioSrc){  
     console.log(audioSrc)  
    _this.audioSrc = 'file://' + audioSrc;  
    // _this.audioSrc = 'https://img-cdn-qiniu.dcloud.net.cn/uniapp/audio/music.mp3'  
    // 这里就会得到 你选择的文件路径。拿到路径后,你可以用uni.uploadFile  进行上传。  
}, 'audio/*');  
//这里第一个参数是 回调函数,第二个是你要选择的文件类 "image/*","audio/*","video/*;image/*"

以下是我修改后的pickFile.js 源码。

var pickFile = {  
    //调用原生文件系统管理器并选取文件获取文件地址  
    PickFile:function(callback, acceptType) { //acceptType为你要查的文件类型"image/*","audio/*","video/*;image/*"  // intent.setType("image/*");//intent.setType("audio/*"); //选择音频//intent.setType("video/*;image/*"); //选择视频 (mp4 3gp 是android支持的视频格式)  
        var CODE_REQUEST = 1000;  
        var main = plus.android.runtimeMainActivity();  
        if (plus.os.name == 'Android') {  
            var Intent = plus.android.importClass('android.content.Intent');  
            var intent = new Intent(Intent.ACTION_GET_CONTENT);  
            intent.addCategory(Intent.CATEGORY_OPENABLE);  
            if (acceptType) {  
                intent.setType(acceptType);  
            } else {  
                intent.setType("*/*");  
            }  
            let _this = pickFile;  
            main.onActivityResult = function(requestCode, resultCode, data) {  
                if (requestCode == CODE_REQUEST) {  
                    var uri = data.getData();  
                    plus.android.importClass(uri);  
                    var Build = plus.android.importClass('android.os.Build');  
                    var isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;  

                    var DocumentsContract = plus.android.importClass('android.provider.DocumentsContract');  
                    // DocumentProvider  
                    if (isKitKat && DocumentsContract.isDocumentUri(main, uri)) {  
                        console.log("版本大于 4.4 ");  
                        // ExternalStorageProvider  
                        if ("com.android.externalstorage.documents" == uri.getAuthority()) {  
                            var docId = DocumentsContract.getDocumentId(uri);  
                            var split = docId.split(":");  
                            var type = split[0];  

                            if ("primary" == type) {  
                                var Environment = plus.android.importClass('android.os.Environment');  
                                callback(Environment.getExternalStorageDirectory() + "/" + split[1]);  
                            } else {  
                                var System = plus.android.importClass('java.lang.System');  
                                var sdPath = System.getenv("SECONDARY_STORAGE");  
                                if (sdPath) {  
                                    callback(sdPath + "/" + split[1]);  
                                }  
                            }  
                        }  
                        // DownloadsProvider  
                        else if ("com.android.providers.downloads.documents" == uri.getAuthority()) {  
                            var id = DocumentsContract.getDocumentId(uri);  
                            var ContentUris = plus.android.importClass('android.content.ContentUris');  
                            var contentUri = ContentUris.withAppendedId(  
                                //    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));  
                                Uri.parse("content://downloads/public_downloads"), id);  
                            callback(_this.getDataColumn(main, contentUri, null, null));  
                        }  
                        // MediaProvider  
                        else if ("com.android.providers.media.documents" == uri.getAuthority()) {  
                            var docId = DocumentsContract.getDocumentId(uri);  
                            var split = docId.split(":");  
                            var type = split[0];  

                            var MediaStore = plus.android.importClass('android.provider.MediaStore');  
                            if ("image" == type) {  
                                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;  
                            } else if ("video" == type) {  
                                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;  
                            } else if ("audio" == type) {  
                                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;  
                            }  

                            var selection = "_id=?";  
                            var selectionArgs = new Array();  
                            selectionArgs[0] = split[1];  

                            callback(_this.getDataColumn(main, contentUri, selection, selectionArgs));  
                        }  
                    }  
                    // MediaStore (and general)  
                    else if ("content" == uri.getScheme()) {  
                        callback(_this.getDataColumn(main, uri, null, null));  
                    }  
                    // File  
                    else if ("file" == uri.getScheme()) {  
                        callback(uri.getPath());  
                    }  
                }  
            }  
            main.startActivityForResult(intent, CODE_REQUEST);  
        }  
    },  

    getDataColumn:function(main, uri, selection, selectionArgs) {  
        plus.android.importClass(main.getContentResolver());  
        let cursor = main.getContentResolver().query(uri, ['_data'], selection, selectionArgs,  
            null);  
        plus.android.importClass(cursor);  
        if (cursor != null && cursor.moveToFirst()) {  
            var column_index = cursor.getColumnIndexOrThrow('_data');  
            var result = cursor.getString(column_index)  
            cursor.close();  
            return result;  
        }  
        return null;  
    }  
}  

module.exports = {  
    pickFile: pickFile  
}  
继续阅读 »

最近用 uni-app做多媒体上传功能, 文件选择,视频,图片官方分别有uni.chooseVideo和uni.chooseImage接口,很方便,但是涉及到其他文件类型选择比如音频,就没有现成的接口。 在网上搜索了一圈,终于找到了通过Native.js调用原生文件系统的方法 。 原博客地址:https://www.cnblogs.com/lizhao123/p/9951581.html
我结合了作者的方法,简单的封装为pickFile.js,调用起来很方便。用法很简单,把该文件放到你的项目JS目录下,通过 import {pickFile} from '@/js/common/pickFile.js' 方式引入,然后

pickFile.PickFile(function(audioSrc){  
     console.log(audioSrc)  
    _this.audioSrc = 'file://' + audioSrc;  
    // _this.audioSrc = 'https://img-cdn-qiniu.dcloud.net.cn/uniapp/audio/music.mp3'  
    // 这里就会得到 你选择的文件路径。拿到路径后,你可以用uni.uploadFile  进行上传。  
}, 'audio/*');  
//这里第一个参数是 回调函数,第二个是你要选择的文件类 "image/*","audio/*","video/*;image/*"

以下是我修改后的pickFile.js 源码。

var pickFile = {  
    //调用原生文件系统管理器并选取文件获取文件地址  
    PickFile:function(callback, acceptType) { //acceptType为你要查的文件类型"image/*","audio/*","video/*;image/*"  // intent.setType("image/*");//intent.setType("audio/*"); //选择音频//intent.setType("video/*;image/*"); //选择视频 (mp4 3gp 是android支持的视频格式)  
        var CODE_REQUEST = 1000;  
        var main = plus.android.runtimeMainActivity();  
        if (plus.os.name == 'Android') {  
            var Intent = plus.android.importClass('android.content.Intent');  
            var intent = new Intent(Intent.ACTION_GET_CONTENT);  
            intent.addCategory(Intent.CATEGORY_OPENABLE);  
            if (acceptType) {  
                intent.setType(acceptType);  
            } else {  
                intent.setType("*/*");  
            }  
            let _this = pickFile;  
            main.onActivityResult = function(requestCode, resultCode, data) {  
                if (requestCode == CODE_REQUEST) {  
                    var uri = data.getData();  
                    plus.android.importClass(uri);  
                    var Build = plus.android.importClass('android.os.Build');  
                    var isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;  

                    var DocumentsContract = plus.android.importClass('android.provider.DocumentsContract');  
                    // DocumentProvider  
                    if (isKitKat && DocumentsContract.isDocumentUri(main, uri)) {  
                        console.log("版本大于 4.4 ");  
                        // ExternalStorageProvider  
                        if ("com.android.externalstorage.documents" == uri.getAuthority()) {  
                            var docId = DocumentsContract.getDocumentId(uri);  
                            var split = docId.split(":");  
                            var type = split[0];  

                            if ("primary" == type) {  
                                var Environment = plus.android.importClass('android.os.Environment');  
                                callback(Environment.getExternalStorageDirectory() + "/" + split[1]);  
                            } else {  
                                var System = plus.android.importClass('java.lang.System');  
                                var sdPath = System.getenv("SECONDARY_STORAGE");  
                                if (sdPath) {  
                                    callback(sdPath + "/" + split[1]);  
                                }  
                            }  
                        }  
                        // DownloadsProvider  
                        else if ("com.android.providers.downloads.documents" == uri.getAuthority()) {  
                            var id = DocumentsContract.getDocumentId(uri);  
                            var ContentUris = plus.android.importClass('android.content.ContentUris');  
                            var contentUri = ContentUris.withAppendedId(  
                                //    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));  
                                Uri.parse("content://downloads/public_downloads"), id);  
                            callback(_this.getDataColumn(main, contentUri, null, null));  
                        }  
                        // MediaProvider  
                        else if ("com.android.providers.media.documents" == uri.getAuthority()) {  
                            var docId = DocumentsContract.getDocumentId(uri);  
                            var split = docId.split(":");  
                            var type = split[0];  

                            var MediaStore = plus.android.importClass('android.provider.MediaStore');  
                            if ("image" == type) {  
                                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;  
                            } else if ("video" == type) {  
                                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;  
                            } else if ("audio" == type) {  
                                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;  
                            }  

                            var selection = "_id=?";  
                            var selectionArgs = new Array();  
                            selectionArgs[0] = split[1];  

                            callback(_this.getDataColumn(main, contentUri, selection, selectionArgs));  
                        }  
                    }  
                    // MediaStore (and general)  
                    else if ("content" == uri.getScheme()) {  
                        callback(_this.getDataColumn(main, uri, null, null));  
                    }  
                    // File  
                    else if ("file" == uri.getScheme()) {  
                        callback(uri.getPath());  
                    }  
                }  
            }  
            main.startActivityForResult(intent, CODE_REQUEST);  
        }  
    },  

    getDataColumn:function(main, uri, selection, selectionArgs) {  
        plus.android.importClass(main.getContentResolver());  
        let cursor = main.getContentResolver().query(uri, ['_data'], selection, selectionArgs,  
            null);  
        plus.android.importClass(cursor);  
        if (cursor != null && cursor.moveToFirst()) {  
            var column_index = cursor.getColumnIndexOrThrow('_data');  
            var result = cursor.getString(column_index)  
            cursor.close();  
            return result;  
        }  
        return null;  
    }  
}  

module.exports = {  
    pickFile: pickFile  
}  
收起阅读 »

uniapp 在H5APP内长按图片保存到系统相册

原文链接:https://www.hebaocun.com/blog/97.html

var bitmap = new plus.nativeObj.Bitmap();
// 加载海报图片
bitmap.loadBase64Data(_this.poster, function() {
//保存到系统相册
bitmap.save("doc/poster"+_this.coupon.item_id+".jpg", {
overwrite: true, //是否覆盖已有图片, true 是
quality: 100 //图片质量,1-100 默认50, 100质量最高
}, function(e) {
//重点就是这里, 需要将bitmap保存的临时路径再使用saveImageToPhotosAlbum进行保存
uni.saveImageToPhotosAlbum({
filePath: e.target,
success: function () {
uni.showToast({
title: "图片保存成功",
icon: "none"
});
}
});
}, function(e) {
uni.showToast({
title: "图片保存失败",
icon: "none"
});
});
}, function(e) {
uni.showToast({
title: "图片保存失败",
icon: "none"
});
});

继续阅读 »

原文链接:https://www.hebaocun.com/blog/97.html

var bitmap = new plus.nativeObj.Bitmap();
// 加载海报图片
bitmap.loadBase64Data(_this.poster, function() {
//保存到系统相册
bitmap.save("doc/poster"+_this.coupon.item_id+".jpg", {
overwrite: true, //是否覆盖已有图片, true 是
quality: 100 //图片质量,1-100 默认50, 100质量最高
}, function(e) {
//重点就是这里, 需要将bitmap保存的临时路径再使用saveImageToPhotosAlbum进行保存
uni.saveImageToPhotosAlbum({
filePath: e.target,
success: function () {
uni.showToast({
title: "图片保存成功",
icon: "none"
});
}
});
}, function(e) {
uni.showToast({
title: "图片保存失败",
icon: "none"
});
});
}, function(e) {
uni.showToast({
title: "图片保存失败",
icon: "none"
});
});

收起阅读 »

对uniapp的吐槽

uniapp

举例:
一、原生样式的问题
就拿radio来说吧 内置的样式改不了 最后我用view模拟圆框 在里面套了个对勾来实现的

还有其他各种原生组件内置样式 有些能改 有些不能改 能改也麻烦 为什么不跟前端页面似的 让用户自己选择引不引用uniapp的样式?

二、原生DOM的问题
平时最烦的就是小程序 不允许用DOM有多不方便 知道吗(直接操作DOM的速度并不慢 说直接操作DOM慢的都是菜)
现在学小程序 不允许用DOM 还不如以前开发速度快
现在的时间都耽误在各种修改样式上

三、各种意外BUG
比如我封装了一个组件 给它一个方法当做props属性 然后在组件里调用这个方法 在H5端一切正常 在APP就不行 报错

吐完槽,说一下优点,就是文档比以前好了,这是唯一的优点。

希望贵司能相信用户,把控制权都放开,比如原生样式这个,能让用户自由选择用不用。
DOM就不指望了,唉 愁

继续阅读 »

举例:
一、原生样式的问题
就拿radio来说吧 内置的样式改不了 最后我用view模拟圆框 在里面套了个对勾来实现的

还有其他各种原生组件内置样式 有些能改 有些不能改 能改也麻烦 为什么不跟前端页面似的 让用户自己选择引不引用uniapp的样式?

二、原生DOM的问题
平时最烦的就是小程序 不允许用DOM有多不方便 知道吗(直接操作DOM的速度并不慢 说直接操作DOM慢的都是菜)
现在学小程序 不允许用DOM 还不如以前开发速度快
现在的时间都耽误在各种修改样式上

三、各种意外BUG
比如我封装了一个组件 给它一个方法当做props属性 然后在组件里调用这个方法 在H5端一切正常 在APP就不行 报错

吐完槽,说一下优点,就是文档比以前好了,这是唯一的优点。

希望贵司能相信用户,把控制权都放开,比如原生样式这个,能让用户自由选择用不用。
DOM就不指望了,唉 愁

收起阅读 »

关于微信登陆获取code的方式。抄代码就可以了。

//全局申请一个:var auths=null;

// #ifdef APP-PLUS

plus.oauth.getServices(function(servies){  
    auths = servies;  
    var s = auths[0];  
    if( !s.authResult ){  

        s.authorize(function(e){  
            console.log('获取code!' + JSON.stringify(e));  
            uni.showModal({ title: '用户信息',content:  e.code,});    

        }, function(e){  
            console.log('登陆认证失败!');  
            uni.showModal({ title: '认证失败1',content: JSON.stringify(e),});     
        },  {scope:'snsapi_userinfo',state:'123'});  

    }else{  
        console.log('已经登陆认证');  

    }  

}, function(e){console.log("获取服务列表失败:"+JSON.stringify(e));})  

// #endif

如果会返回错误的话。建议以下:

第一,appid一定要对。是开放平台那里取,如果您自己是后台管理员就没有关系。如果您是从客户那里或是别的管理员,那就要非常注意了。我为了这个问题坑了几天。死活不成功,但是我自己亲自向微信公众平台申请了一个应用,一次成功。说明给的appid根本就是不正确的。

第二,签名一定要正确。应用在手机上安装后,用微信的工具去获取一般这步可能不会错。

第三,包名。一定不能出错。基本上这三步Ok。就能顺利进行授权。

继续阅读 »

//全局申请一个:var auths=null;

// #ifdef APP-PLUS

plus.oauth.getServices(function(servies){  
    auths = servies;  
    var s = auths[0];  
    if( !s.authResult ){  

        s.authorize(function(e){  
            console.log('获取code!' + JSON.stringify(e));  
            uni.showModal({ title: '用户信息',content:  e.code,});    

        }, function(e){  
            console.log('登陆认证失败!');  
            uni.showModal({ title: '认证失败1',content: JSON.stringify(e),});     
        },  {scope:'snsapi_userinfo',state:'123'});  

    }else{  
        console.log('已经登陆认证');  

    }  

}, function(e){console.log("获取服务列表失败:"+JSON.stringify(e));})  

// #endif

如果会返回错误的话。建议以下:

第一,appid一定要对。是开放平台那里取,如果您自己是后台管理员就没有关系。如果您是从客户那里或是别的管理员,那就要非常注意了。我为了这个问题坑了几天。死活不成功,但是我自己亲自向微信公众平台申请了一个应用,一次成功。说明给的appid根本就是不正确的。

第二,签名一定要正确。应用在手机上安装后,用微信的工具去获取一般这步可能不会错。

第三,包名。一定不能出错。基本上这三步Ok。就能顺利进行授权。

收起阅读 »

uni-app自定义组件模式开发注意事项

新框架 自定义组件模式

uni-app 自 1.8版本开始,新增支持自定义组件模式,该编译模式组件性能更高,支持更多的Vue语法。

请开发者尽快升级老版项目为自定义组件模式,老版的模板编译模式将不再维护。

Tips: uni-app 不同编译模式差异,参考:https://ask.dcloud.net.cn/article/35843

开发者启用自定义组件模式后,在进行自定义组件开发(页面开发不影响)时,需注意如下约束

  • id 为保留属性名,不能作为 props 传递,微信小程序自定义组件限制
  • props 中不能定义 data 为属性名,百度小程序限制
  • props 不支持 date 类型数据传递,微信小程序自定义组件限制
  • uni.createSelectorQuery()uni.createIntersectionObserver() 的调整
// 错误  
uni.createSelectorQuery();  
uni.createIntersectionObserver();  

// 正确  
uni.createSelectorQuery().in(this);                                                        
uni.createIntersectionObserver(this, options);
  • uni.createCanvasContext() 的调整
// 错误  
uni.createCanvasContext();  

// 正确  
uni.createCanvasContext('#canvas',this);
  • Array.length 改为计算属性
// 错误  
<view v-if="{Array.length >= 0}"></view>  

// 正确  
<view v-if="{count}" ></view>  
computed: {  
    count() {  
        return (Array.length >= 0)  
    }  
}
  • uParse 富文本解析组件的调整
    优化了 uParse 组件 的性能,如老项目有使用,请直接替换最新组件,使用方式不变:https://ext.dcloud.net.cn/plugin?id=183
    其实插件市场有更多三方增强的uparser组件可用,可以搜一下。

  • ECharts 图表组件的调整
    替换最新的 ucharts 组件, 源码地址:https://ext.dcloud.net.cn/plugin?id=271
    使用方式参考 :http://doc.ucharts.cn/1074673
    其实插件市场有更多优秀的三方增强的图表组件可用,可以搜一下。

  • css 调整
    在编译成微信小程序原生组件的时候,会在组件外增加一层父节点。有可能样式会受到影响,比较典型的就是 flex 布局,请在微信端完成一遍测试。
    在字节跳动小程序组件内引入字体会失效,解决方式:在 App.vue 内的 style 节点引入字体。

  • 组件内 css 选择器使用
    在微信小程序真机运行时,组件内如果使用了 tagName, ID, attribute 三种选择器时,会导致控制台出现告警信息。 所以我们在编写组件时,需要尽量避免使用上述三种选择器。

  • 组件生命周期
    编译成原生组件的时候,组件的生命周期请严格参考 组件生命周期 中的说明。
    需要注意的是,组件不支持 onLoad 等页面的生命周期。

  • 仅支持解构插槽 Prop(支持设置默认值,但不提供重命名)

<!—不支持—>  
<template v-slot:default="slotProps">  
    {{ slotProps.user.firstName }}  
</template>  
<!—支持—>  
<template v-slot:default="{user}">  
    {{ user.firstName }}  
</template>
  • 组件中引用图片等静态资源时,一定要使用绝对路径,即 /static/logo.png 这样。其实,无论是页面还是组件,引用静态资源时统一采用绝对路径的方式是最优方案。

App额外注意事项

  • App使用自定义组件模式,Android会增加6M左右的包体积。原因是App端增加了一个独立的v8以减少js的阻塞。iOS不变化,因为iOS的jscore是iOS自带的。
  • 离线打包的项目需要在原生工程里引入 :离线sdk包里的liblibWeex.a库 和 weex-main-jsfm.js 和 weex-polyfill.js文件。
  • 在自定义组件编译模式下,HBuilderX 2.4.0+ 支持 crypto.getRandomValues 方法,一些区块链应用的框架会用到此api
  • 在自定义组件编译模式下,uni.request 和 websocket 暂时不支持传输 ArrayBuffer 类型的数据,且 websocket 只能创建一个连接,有需求的用户可改用老的模板编译模式。
继续阅读 »

uni-app 自 1.8版本开始,新增支持自定义组件模式,该编译模式组件性能更高,支持更多的Vue语法。

请开发者尽快升级老版项目为自定义组件模式,老版的模板编译模式将不再维护。

Tips: uni-app 不同编译模式差异,参考:https://ask.dcloud.net.cn/article/35843

开发者启用自定义组件模式后,在进行自定义组件开发(页面开发不影响)时,需注意如下约束

  • id 为保留属性名,不能作为 props 传递,微信小程序自定义组件限制
  • props 中不能定义 data 为属性名,百度小程序限制
  • props 不支持 date 类型数据传递,微信小程序自定义组件限制
  • uni.createSelectorQuery()uni.createIntersectionObserver() 的调整
// 错误  
uni.createSelectorQuery();  
uni.createIntersectionObserver();  

// 正确  
uni.createSelectorQuery().in(this);                                                        
uni.createIntersectionObserver(this, options);
  • uni.createCanvasContext() 的调整
// 错误  
uni.createCanvasContext();  

// 正确  
uni.createCanvasContext('#canvas',this);
  • Array.length 改为计算属性
// 错误  
<view v-if="{Array.length >= 0}"></view>  

// 正确  
<view v-if="{count}" ></view>  
computed: {  
    count() {  
        return (Array.length >= 0)  
    }  
}
  • uParse 富文本解析组件的调整
    优化了 uParse 组件 的性能,如老项目有使用,请直接替换最新组件,使用方式不变:https://ext.dcloud.net.cn/plugin?id=183
    其实插件市场有更多三方增强的uparser组件可用,可以搜一下。

  • ECharts 图表组件的调整
    替换最新的 ucharts 组件, 源码地址:https://ext.dcloud.net.cn/plugin?id=271
    使用方式参考 :http://doc.ucharts.cn/1074673
    其实插件市场有更多优秀的三方增强的图表组件可用,可以搜一下。

  • css 调整
    在编译成微信小程序原生组件的时候,会在组件外增加一层父节点。有可能样式会受到影响,比较典型的就是 flex 布局,请在微信端完成一遍测试。
    在字节跳动小程序组件内引入字体会失效,解决方式:在 App.vue 内的 style 节点引入字体。

  • 组件内 css 选择器使用
    在微信小程序真机运行时,组件内如果使用了 tagName, ID, attribute 三种选择器时,会导致控制台出现告警信息。 所以我们在编写组件时,需要尽量避免使用上述三种选择器。

  • 组件生命周期
    编译成原生组件的时候,组件的生命周期请严格参考 组件生命周期 中的说明。
    需要注意的是,组件不支持 onLoad 等页面的生命周期。

  • 仅支持解构插槽 Prop(支持设置默认值,但不提供重命名)

<!—不支持—>  
<template v-slot:default="slotProps">  
    {{ slotProps.user.firstName }}  
</template>  
<!—支持—>  
<template v-slot:default="{user}">  
    {{ user.firstName }}  
</template>
  • 组件中引用图片等静态资源时,一定要使用绝对路径,即 /static/logo.png 这样。其实,无论是页面还是组件,引用静态资源时统一采用绝对路径的方式是最优方案。

App额外注意事项

  • App使用自定义组件模式,Android会增加6M左右的包体积。原因是App端增加了一个独立的v8以减少js的阻塞。iOS不变化,因为iOS的jscore是iOS自带的。
  • 离线打包的项目需要在原生工程里引入 :离线sdk包里的liblibWeex.a库 和 weex-main-jsfm.js 和 weex-polyfill.js文件。
  • 在自定义组件编译模式下,HBuilderX 2.4.0+ 支持 crypto.getRandomValues 方法,一些区块链应用的框架会用到此api
  • 在自定义组件编译模式下,uni.request 和 websocket 暂时不支持传输 ArrayBuffer 类型的数据,且 websocket 只能创建一个连接,有需求的用户可改用老的模板编译模式。
收起阅读 »