uni.onBLECharacteristicValueChange 遇到的问题
问题:uni.onBLECharacteristicValueChange 无法监听到第二次 uni.writeBLECharacteristicValue 特征值写入。
ps:代码就不描述了,写一下流程
描述:初始化蓝牙->启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值->监听蓝牙特征值变化->writeBLECharacteristicValue 下发参数给设备
->onBLECharacteristicValueChange 正常监听到特征值变化,成功获取报文->此时获取到成功报文后我又执行了,启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值(此时的uuid,服务id,特征值id,都应需求原因不予第一次获取的一致)->未调用蓝牙特征值变化方法,因为第一次已经调用(后续试过再次调用结果也一致,失败)
->onBLECharacteristicValueChange 未监听到特征值变化,无报文。
解决:在onBLECharacteristicValueChange 中获取到第一次成功报文后,断开与低功耗蓝牙设备的连接uni.closeBLEConnection,再次初始化蓝牙->启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值->监听蓝牙特征值变化->writeBLECharacteristicValue 下发参数给设备 -> 又可以成功获取到 onBLECharacteristicValueChange 特征值变化,成功获取报文。
问题:uni.onBLECharacteristicValueChange 无法监听到第二次 uni.writeBLECharacteristicValue 特征值写入。
ps:代码就不描述了,写一下流程
描述:初始化蓝牙->启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值->监听蓝牙特征值变化->writeBLECharacteristicValue 下发参数给设备
->onBLECharacteristicValueChange 正常监听到特征值变化,成功获取报文->此时获取到成功报文后我又执行了,启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值(此时的uuid,服务id,特征值id,都应需求原因不予第一次获取的一致)->未调用蓝牙特征值变化方法,因为第一次已经调用(后续试过再次调用结果也一致,失败)
->onBLECharacteristicValueChange 未监听到特征值变化,无报文。
解决:在onBLECharacteristicValueChange 中获取到第一次成功报文后,断开与低功耗蓝牙设备的连接uni.closeBLEConnection,再次初始化蓝牙->启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值->监听蓝牙特征值变化->writeBLECharacteristicValue 下发参数给设备 -> 又可以成功获取到 onBLECharacteristicValueChange 特征值变化,成功获取报文。
Ai绘图系统搭建教程
应用名称:Aai绘图工具
支持Ai功能:文生图、图生图、艺术二维码、Ai消除去水印、局部重绘、老照片修复、高清放大、图片转漫画、图片扩展、Ai抠图
绘图费用
一般生图费用:0.09/张,包含高清修复的:0.15/张
如果用量较多可以拿到半价:0.045/张,包含高清修复的:0.075/张
如果用量更多,联系开发者可以拿到更低价
需要Api绘图接口的话可以咨询开发者,微信:wga1122
支持平台(支持uniCloud阿里云空间)
1:支持微信H5公众号版 PC电脑版。(需要已认证的微信公众号)
2:支持微信小程序。(上架需申请Ai绘图类目,可使用第三方合同申请Ai类目,使用第三个合同比较简单,开发者可指导协作完成)
3:支持支付宝小程序。(无需Ai类目,工具类目即可上架)
4:支持抖音小程序。(必须深度算法合成备案才能上架)
应用演示
1:H5客户端体验地址(手机版更优) [http://www.msshequ.cn/ai/](http://www.msshequ.cn/ai/#/)
2:微信小程序
3:支付宝小程序
源码下载
客户端(ai-image):https://ext.dcloud.net.cn/plugin?id=12217
ai-image可打包成 H5、微信小程序、支付宝小程序、抖音小程序 并发布
管理端(ai-image-admin):https://ext.dcloud.net.cn/plugin?id=12211
ai-image-admin可打包成H5发布,用于管理配置客户端,或者说是站长的控制台,站长自己用不要对外开放
开通uniCloud阿里云服务空间
开通地址:https://unicloud.dcloud.net.cn/
建议按量付费,请使用阿里云服务,其他服务可能不兼容
下载HBuilder开发工具:https://www.dcloud.io/hbuilderx.html
小程序域名白名单
请求域名:api.next.bspapp.com、sd-json.oss-cn-hongkong.aliyuncs.com
下载域名:sd-json.oss-cn-hongkong.aliyuncs.com
除了阿里云自带的请求域名外,请求和下载还要额外添加:sd-json.oss-cn-hongkong.aliyuncs.com,该域名是Ai绘图回调的文件地址,
Ai回调有些是JSON数据,有些是图片地址,所有需要把该域名添加到请求域名,和下载域名
同时你unicloud的上传域名,和下载域名也要添加,unicloud空间查看域名
安装教程
先安装ai-image-admin,导入HBuilder 后关联阿里云空间,HBuilder下载地址:https://www.dcloud.io/hbuilderx.html
初始化云数据库
如果弹窗窗口直接覆盖
等待初始化完成
上传所有云函数及公共模块
如果弹出窗口直接替换
等待上传完成
再安装ai-image,必须一起安装完在测试使用,步骤与上面都差不多
导入HBuilder后关联阿里云空间
初始化云数据库
如果弹出询问窗直接覆盖
等待初始化完成
上传所有云函数及公共模块
如果弹出询问窗直接替换
等待上传完成
发布管理端(ai-image-admin)得先发布管理端完成小程序配置才能发布小程序端
ai-image-admin发行 网站-PC,并关联你的uniCloud阿里云空间一般会自动上传
如果无法自动上传,需要手动上传,先等待打包完成
打开打包完成的目录地址,把web改成admin,方便上传,因为打包的H5运行路径(会改的话可以自己改路径)就是admin,所有要用admin
上传文件到云空间,这里上传的是unicloud,如果你用其他云空间托管也可以上传其他云空间,记得配置跨域
配置跨域
访问管理端,在默认域名后面添加目录/admin
管理端初始化账号:admin
管理端初始化密码:112233
添加一个小程序,获取mymp_id,这里以支付宝小程序为例
在ai-image客户端配置mymp_id,就可以运行或者打包ai-image项目了
不用全部配置,要发布支付宝小程序,就修改mp-alipay的参数,要上架哪个平台就修改对应平台的mymp_id就行
发布到支付宝小程序
注意:是ai-image项目。建议先运行测试没问题在打包发布,不会运行测试的直接打包发布应该也没问题(开发者已经测试好没问题了)
应用名称:Aai绘图工具
支持Ai功能:文生图、图生图、艺术二维码、Ai消除去水印、局部重绘、老照片修复、高清放大、图片转漫画、图片扩展、Ai抠图
绘图费用
一般生图费用:0.09/张,包含高清修复的:0.15/张
如果用量较多可以拿到半价:0.045/张,包含高清修复的:0.075/张
如果用量更多,联系开发者可以拿到更低价
需要Api绘图接口的话可以咨询开发者,微信:wga1122
支持平台(支持uniCloud阿里云空间)
1:支持微信H5公众号版 PC电脑版。(需要已认证的微信公众号)
2:支持微信小程序。(上架需申请Ai绘图类目,可使用第三方合同申请Ai类目,使用第三个合同比较简单,开发者可指导协作完成)
3:支持支付宝小程序。(无需Ai类目,工具类目即可上架)
4:支持抖音小程序。(必须深度算法合成备案才能上架)
应用演示
1:H5客户端体验地址(手机版更优) [http://www.msshequ.cn/ai/](http://www.msshequ.cn/ai/#/)
2:微信小程序
3:支付宝小程序
源码下载
客户端(ai-image):https://ext.dcloud.net.cn/plugin?id=12217
ai-image可打包成 H5、微信小程序、支付宝小程序、抖音小程序 并发布
管理端(ai-image-admin):https://ext.dcloud.net.cn/plugin?id=12211
ai-image-admin可打包成H5发布,用于管理配置客户端,或者说是站长的控制台,站长自己用不要对外开放
开通uniCloud阿里云服务空间
开通地址:https://unicloud.dcloud.net.cn/
建议按量付费,请使用阿里云服务,其他服务可能不兼容
下载HBuilder开发工具:https://www.dcloud.io/hbuilderx.html
小程序域名白名单
请求域名:api.next.bspapp.com、sd-json.oss-cn-hongkong.aliyuncs.com
下载域名:sd-json.oss-cn-hongkong.aliyuncs.com
除了阿里云自带的请求域名外,请求和下载还要额外添加:sd-json.oss-cn-hongkong.aliyuncs.com,该域名是Ai绘图回调的文件地址,
Ai回调有些是JSON数据,有些是图片地址,所有需要把该域名添加到请求域名,和下载域名
同时你unicloud的上传域名,和下载域名也要添加,unicloud空间查看域名
安装教程
先安装ai-image-admin,导入HBuilder 后关联阿里云空间,HBuilder下载地址:https://www.dcloud.io/hbuilderx.html
初始化云数据库
如果弹窗窗口直接覆盖
等待初始化完成
上传所有云函数及公共模块
如果弹出窗口直接替换
等待上传完成
再安装ai-image,必须一起安装完在测试使用,步骤与上面都差不多
导入HBuilder后关联阿里云空间
初始化云数据库
如果弹出询问窗直接覆盖
等待初始化完成
上传所有云函数及公共模块
如果弹出询问窗直接替换
等待上传完成
发布管理端(ai-image-admin)得先发布管理端完成小程序配置才能发布小程序端
ai-image-admin发行 网站-PC,并关联你的uniCloud阿里云空间一般会自动上传
如果无法自动上传,需要手动上传,先等待打包完成
打开打包完成的目录地址,把web改成admin,方便上传,因为打包的H5运行路径(会改的话可以自己改路径)就是admin,所有要用admin
上传文件到云空间,这里上传的是unicloud,如果你用其他云空间托管也可以上传其他云空间,记得配置跨域
配置跨域
访问管理端,在默认域名后面添加目录/admin
管理端初始化账号:admin
管理端初始化密码:112233
添加一个小程序,获取mymp_id,这里以支付宝小程序为例
在ai-image客户端配置mymp_id,就可以运行或者打包ai-image项目了
不用全部配置,要发布支付宝小程序,就修改mp-alipay的参数,要上架哪个平台就修改对应平台的mymp_id就行
发布到支付宝小程序
注意:是ai-image项目。建议先运行测试没问题在打包发布,不会运行测试的直接打包发布应该也没问题(开发者已经测试好没问题了)
uniapp App map地图组件+subnvues弹窗,路由跳转后再进页面时subnvues弹窗有缓存的问题!
hbuildX版本:4.24
uniapp:vue3(默认模板,不包含uniapp-x)
注:只是分享,之前是做pc端的,刚接触uniapp,不清楚这个问题在历代uniapp更新中是否被解决,但是我在开发中遇到了,所以就发出来了。
情景描述:在一个除顶部菜单的全屏地图中,添加自定义控件,因为map地图层级很高,故使用subnvues,进行控件展示,包含两个方形小按钮,一个是定位自己,一个是点击进行弹窗,在弹窗中在进行操作,然后就发现了退出当前路由,再进入时,弹窗会出现重复!
解决办法:反正是困扰了一下午,无意中发现,将地图代码注释之后,再执行退出进入的操作,就不会出现这种情况,经过漫长的时间琢磨,给地图组件加上v-if,一是在subnvues弹窗之前将地图隐掉,执行完弹窗操作后,再显示出来,就只是一瞬,有用,二是,在退出当前页面时提前将地图隐掉,再执行页面跳转,就一个异步执行代码;
//打开菜单弹窗
const OpenMenuPopu = ()=>{
MapIsShow.value = false;
ChoiceMenuExample.value = uni.getSubNVueById('MultipleChoiceMenu');
ChoiceMenuExample.value.show('slide-in-bottom',600);
uni.$emit('FatherCheckboxValue',FatherCheckboxValue.value)
MapIsShow.value = true;
}
//返回首页
const GoHome = () => {
MapIsShow.value = false;
MyAMap.value = null;
let time = setTimeout(()=>{
uni.redirectTo({
url: '/pages/Home/index'
});
clearTimeout(time);
},10)
uni.$emit('FatherCheckboxValue',[]);
uni.$off('SearchData')
uni.$off('FatherCheckboxValue')
uni.$off('ControlID')
uni.$off('CheckboxValue')
}
onBackPress(()=>{
GoHome();
return true;
})
hbuildX版本:4.24
uniapp:vue3(默认模板,不包含uniapp-x)
注:只是分享,之前是做pc端的,刚接触uniapp,不清楚这个问题在历代uniapp更新中是否被解决,但是我在开发中遇到了,所以就发出来了。
情景描述:在一个除顶部菜单的全屏地图中,添加自定义控件,因为map地图层级很高,故使用subnvues,进行控件展示,包含两个方形小按钮,一个是定位自己,一个是点击进行弹窗,在弹窗中在进行操作,然后就发现了退出当前路由,再进入时,弹窗会出现重复!
解决办法:反正是困扰了一下午,无意中发现,将地图代码注释之后,再执行退出进入的操作,就不会出现这种情况,经过漫长的时间琢磨,给地图组件加上v-if,一是在subnvues弹窗之前将地图隐掉,执行完弹窗操作后,再显示出来,就只是一瞬,有用,二是,在退出当前页面时提前将地图隐掉,再执行页面跳转,就一个异步执行代码;
//打开菜单弹窗
const OpenMenuPopu = ()=>{
MapIsShow.value = false;
ChoiceMenuExample.value = uni.getSubNVueById('MultipleChoiceMenu');
ChoiceMenuExample.value.show('slide-in-bottom',600);
uni.$emit('FatherCheckboxValue',FatherCheckboxValue.value)
MapIsShow.value = true;
}
//返回首页
const GoHome = () => {
MapIsShow.value = false;
MyAMap.value = null;
let time = setTimeout(()=>{
uni.redirectTo({
url: '/pages/Home/index'
});
clearTimeout(time);
},10)
uni.$emit('FatherCheckboxValue',[]);
uni.$off('SearchData')
uni.$off('FatherCheckboxValue')
uni.$off('ControlID')
uni.$off('CheckboxValue')
}
onBackPress(()=>{
GoHome();
return true;
})
收起阅读 »
关于 app跳转微信客服报错deeplink custmerservice no permission排查解决
1.准备工作,微信开放平企业认证微信开放平台app认证,企业微信企业认证共计600元
- 认证通过后在企业微信中>微信客服 绑定 微信开放平台认证的app
- 在manifest.json>app模块配置>share分享填入 开放平台认证的appid
- 重新打自定义包(标准基座会报错deeplink custmerservice no permission)
- 填写企业微信的id plus中的方法备注的是填写g_开头的不要管他
附上代码openWeixinService(e){ let sweixin = null plus.share.getServices(res=>{ sweixin = res.find(i => i.id === 'weixin') if(sweixin){ sweixin.openCustomerServiceChat({ corpid: '企业微信id', //获取链接https://work.weixin.qq.com/wework_admin/frame#/profile url: '客服链接', //获取链接https://work.weixin.qq.com/wework_admin/frame#/app/servicer },suc=>{ console.log("success",JSON.stringify(res)) },err=>{ console.log("error",JSON.stringify(err)) }) }else{ plus.nativeUI.alert('当前环境不支持微信操作!') } },function(){ uni.showToast({title: "获取服务失败,不支持该操作。"+JSON.stringify(e), icon: 'error'}) }) }
1.准备工作,微信开放平企业认证微信开放平台app认证,企业微信企业认证共计600元
- 认证通过后在企业微信中>微信客服 绑定 微信开放平台认证的app
- 在manifest.json>app模块配置>share分享填入 开放平台认证的appid
- 重新打自定义包(标准基座会报错deeplink custmerservice no permission)
- 填写企业微信的id plus中的方法备注的是填写g_开头的不要管他
附上代码openWeixinService(e){ let sweixin = null plus.share.getServices(res=>{ sweixin = res.find(i => i.id === 'weixin') if(sweixin){ sweixin.openCustomerServiceChat({ corpid: '企业微信id', //获取链接https://work.weixin.qq.com/wework_admin/frame#/profile url: '客服链接', //获取链接https://work.weixin.qq.com/wework_admin/frame#/app/servicer },suc=>{ console.log("success",JSON.stringify(res)) },err=>{ console.log("error",JSON.stringify(err)) }) }else{ plus.nativeUI.alert('当前环境不支持微信操作!') } },function(){ uni.showToast({title: "获取服务失败,不支持该操作。"+JSON.stringify(e), icon: 'error'}) }) }
解决 隐藏原生tabbar之后,手机屏幕下方会出现白色区域的问题
在源码视图中添加"safearea": {"bottom": { "offset": "none" }
在源码视图中添加"safearea": {"bottom": { "offset": "none" }
横屏启动样式错乱强制竖屏
当我们横屏启动应用时,如果应用需要保持竖屏使用的,应用会先以横屏启动后再恢复竖屏,这时会导致样式错乱,查了很多方法没有解决我的问题。
想过操作webview,但是没实现
最后解决方案
const isLandScape = Math.abs(plus.navigator.getOrientation()) === 90
plus.screen.lockOrientation("portrait-primary")
if(isLandScape) plus.runtime.restart()
其他配置和官方说的一样
当我们横屏启动应用时,如果应用需要保持竖屏使用的,应用会先以横屏启动后再恢复竖屏,这时会导致样式错乱,查了很多方法没有解决我的问题。
想过操作webview,但是没实现
最后解决方案
const isLandScape = Math.abs(plus.navigator.getOrientation()) === 90
plus.screen.lockOrientation("portrait-primary")
if(isLandScape) plus.runtime.restart()
其他配置和官方说的一样
uniapp app自动监听权限
自动监听 onLaunch 引入 listener方法即可
监听权限
// @ts-nocheck
// #ifdef APP-PLUS
import permissions from "./permissions";
import {closeNotify, drawNotify} from "@/utils/notify";
const permissionListener = uni.createRequestPermissionListener()
// #endif
/**
* 监听权限
*/
export function listener() {
/* 是否已经弹出设置框 */
let hasConfirm = false
// 监听申请系统权限
permissionListener.onRequest((data: string[]) => {
})
// 监听弹出系统权限授权框
permissionListener.onConfirm((data: string[]) => {
const permissionsObj = getPermissionsObj(data)
drawNotify(permissionsObj.title, permissionsObj.desc)
})
// 监听权限申请完成
permissionListener.onComplete((data: string[]) => {
closeNotify()
const parts = data[data.length - 1].split('.');
let permissionName = parts[parts.length - 1];
const Manifest = plus.android.importClass('android.Manifest')
const MainActivity = plus.android.runtimeMainActivity()
const permissionStatus = MainActivity.checkSelfPermission(Manifest.permission[permissionName])
// 彻底拒绝
if (permissionStatus === -1 && !hasConfirm) {
hasConfirm = true
const permissionsObj = getPermissionsObj(data)
plus.nativeUI.confirm(permissionsObj.desc, (e) => {
if (e.index === 0) uni.openAppAuthorizeSetting()
hasConfirm = false
}, {
title: permissionsObj.title,
"buttons": ["去设置", "取消"],
});
}
})
}
function getPermissionsObj(data: string[]): { title: string, desc: string } {
let title: string = ""
let desc: string = ""
data.forEach((permission) => {
permissions.forEach(item => {
if (item.permissions.indexOf(permission) !== -1) {
title = item.title
desc = item.desc
}
})
})
return {title, desc}
}
权限设置
export interface Permissions {
title: string,
desc: string,
permissions: string[]
}
const permissions: Permissions[] = [
{
title: "读取短信权限",
desc: "读取短信权限达到个性化目的",
permissions: [
"android.permission.SEND_SMS",
"android.permission.RECEIVE_SMS",
"android.permission.READ_SMS",
"android.permission.RECEIVE_WAP_PUSH",
"android.permission.RECEIVE_MMS",
]
},
{
title: "读取存储卡,包括相册等权限",
desc: "读取存储卡,包括相册等权限达到个性化目的",
permissions: [
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
]
},
{
title: "读取联系人权限",
desc: "读取联系人权限达到个性化目的",
permissions: [
"android.permission.READ_CONTACTS",
"android.permission.WRITE_CONTACTS",
"android.permission.GET_ACCOUNTS",
]
},
{
title: "读取手机权限",
desc: "读取手机权限达到个性化目的",
permissions: [
"android.permission.READ_PHONE_STATE",
"android.permission.CALL_PHONE",
"android.permission.READ_CALL_LOG",
"android.permission.WRITE_CALL_LOG",
"android.permission.ADD_VOICEMAIL",
"android.permission.USE_SIP",
"android.permission.PROCESS_OUTGOING_CALLS",
]
},
{
title: "读取日历权限",
desc: "读取日历权限达到个性化目的",
permissions: [
"android.permission.READ_CALENDAR",
"android.permission.WRITE_CALENDAR",
]
},
{
title: "读取相机权限",
desc: "读取相机权限达到个性化目的",
permissions: [
"android.permission.CAMERA",
]
},
{
title: "读取位置权限",
desc: "读取位置权限达到个性化目的",
permissions: [
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION",
]
},
{
title: "读取传感器权限",
desc: "读取传感器权限达到个性化目的",
permissions: [
"android.permission.BODY_SENSORS",
]
},
{
title: "读取麦克风权限",
desc: "读取麦克风权限达到个性化目的",
permissions: [
"android.permission.RECORD_AUDIO",
]
},
]
export default permissions
顶部信息
let view: any
let idKey: string = 'permissionNotify'
/**
* 绘制消息
* @description 可根据描述字数动态变更高度
* @param {string} title 标题
* @param {string} desc 描述
*/
export function drawNotify(title: string, desc: string) {
// 关闭之前的 notify
// @ts-ignore
let idView = plus.nativeObj.View.getViewById(idKey)
if (idView) idView.close()
const {windowWidth, statusBarHeight} = uni.getSystemInfoSync()
const xDistance: number = 16 // 距离屏幕两侧的距离
const tDistance: number = 16 // 距离顶部的距离
const radius: number = 16 // 盒子圆角
const xPadding: number = 16 // 盒子paddingX
const YPadding: number = 16 // 盒子paddingY
const space: number = 12 // 描述距离标题的距离
const titleSize: number = 16 // 标题文字大小
const descSize: number = 14 // 描述文字大小
const width: number = windowWidth - xDistance * 2
// @ts-ignore
const top: number = statusBarHeight + tDistance
const lineNum = Math.ceil(desc.length / Math.floor((width - YPadding * 2) / descSize)) * 1.2
const height = YPadding * 2 + titleSize + space + descSize * lineNum
// @ts-ignore
let _view = new plus.nativeObj.View(
idKey,
{
width: `${width}`,
top: `${top}`,
left: `${xDistance}`,
height: `${height}`,
},
[
{
tag: 'rect',
id: 'rect',
rectStyles: {
color: '#FFFFFF',
radius: `${radius}`,
},
},
{
tag: 'font',
id: 'title',
text: title,
textStyles: {size: `${titleSize}`, align: 'left', weight: 'bold'},
position: {top: `${YPadding}`, left: `${xPadding}`, height: 'wrap_content'},
},
{
tag: 'font',
id: 'desc',
text: desc,
textStyles: {size: `${descSize}`, align: 'left', color: '#666666', whiteSpace: 'normal'},
position: {
top: `${YPadding + titleSize + space}`,
left: `${xPadding}`,
height: 'wrap_content',
width: `${width - YPadding * 2}`,
},
},
]
)
_view.show()
view = _view
}
/**
* 关闭消息
*/
export function closeNotify() {
if (view) view.close()
}
自动监听 onLaunch 引入 listener方法即可
监听权限
// @ts-nocheck
// #ifdef APP-PLUS
import permissions from "./permissions";
import {closeNotify, drawNotify} from "@/utils/notify";
const permissionListener = uni.createRequestPermissionListener()
// #endif
/**
* 监听权限
*/
export function listener() {
/* 是否已经弹出设置框 */
let hasConfirm = false
// 监听申请系统权限
permissionListener.onRequest((data: string[]) => {
})
// 监听弹出系统权限授权框
permissionListener.onConfirm((data: string[]) => {
const permissionsObj = getPermissionsObj(data)
drawNotify(permissionsObj.title, permissionsObj.desc)
})
// 监听权限申请完成
permissionListener.onComplete((data: string[]) => {
closeNotify()
const parts = data[data.length - 1].split('.');
let permissionName = parts[parts.length - 1];
const Manifest = plus.android.importClass('android.Manifest')
const MainActivity = plus.android.runtimeMainActivity()
const permissionStatus = MainActivity.checkSelfPermission(Manifest.permission[permissionName])
// 彻底拒绝
if (permissionStatus === -1 && !hasConfirm) {
hasConfirm = true
const permissionsObj = getPermissionsObj(data)
plus.nativeUI.confirm(permissionsObj.desc, (e) => {
if (e.index === 0) uni.openAppAuthorizeSetting()
hasConfirm = false
}, {
title: permissionsObj.title,
"buttons": ["去设置", "取消"],
});
}
})
}
function getPermissionsObj(data: string[]): { title: string, desc: string } {
let title: string = ""
let desc: string = ""
data.forEach((permission) => {
permissions.forEach(item => {
if (item.permissions.indexOf(permission) !== -1) {
title = item.title
desc = item.desc
}
})
})
return {title, desc}
}
权限设置
export interface Permissions {
title: string,
desc: string,
permissions: string[]
}
const permissions: Permissions[] = [
{
title: "读取短信权限",
desc: "读取短信权限达到个性化目的",
permissions: [
"android.permission.SEND_SMS",
"android.permission.RECEIVE_SMS",
"android.permission.READ_SMS",
"android.permission.RECEIVE_WAP_PUSH",
"android.permission.RECEIVE_MMS",
]
},
{
title: "读取存储卡,包括相册等权限",
desc: "读取存储卡,包括相册等权限达到个性化目的",
permissions: [
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
]
},
{
title: "读取联系人权限",
desc: "读取联系人权限达到个性化目的",
permissions: [
"android.permission.READ_CONTACTS",
"android.permission.WRITE_CONTACTS",
"android.permission.GET_ACCOUNTS",
]
},
{
title: "读取手机权限",
desc: "读取手机权限达到个性化目的",
permissions: [
"android.permission.READ_PHONE_STATE",
"android.permission.CALL_PHONE",
"android.permission.READ_CALL_LOG",
"android.permission.WRITE_CALL_LOG",
"android.permission.ADD_VOICEMAIL",
"android.permission.USE_SIP",
"android.permission.PROCESS_OUTGOING_CALLS",
]
},
{
title: "读取日历权限",
desc: "读取日历权限达到个性化目的",
permissions: [
"android.permission.READ_CALENDAR",
"android.permission.WRITE_CALENDAR",
]
},
{
title: "读取相机权限",
desc: "读取相机权限达到个性化目的",
permissions: [
"android.permission.CAMERA",
]
},
{
title: "读取位置权限",
desc: "读取位置权限达到个性化目的",
permissions: [
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION",
]
},
{
title: "读取传感器权限",
desc: "读取传感器权限达到个性化目的",
permissions: [
"android.permission.BODY_SENSORS",
]
},
{
title: "读取麦克风权限",
desc: "读取麦克风权限达到个性化目的",
permissions: [
"android.permission.RECORD_AUDIO",
]
},
]
export default permissions
顶部信息
let view: any
let idKey: string = 'permissionNotify'
/**
* 绘制消息
* @description 可根据描述字数动态变更高度
* @param {string} title 标题
* @param {string} desc 描述
*/
export function drawNotify(title: string, desc: string) {
// 关闭之前的 notify
// @ts-ignore
let idView = plus.nativeObj.View.getViewById(idKey)
if (idView) idView.close()
const {windowWidth, statusBarHeight} = uni.getSystemInfoSync()
const xDistance: number = 16 // 距离屏幕两侧的距离
const tDistance: number = 16 // 距离顶部的距离
const radius: number = 16 // 盒子圆角
const xPadding: number = 16 // 盒子paddingX
const YPadding: number = 16 // 盒子paddingY
const space: number = 12 // 描述距离标题的距离
const titleSize: number = 16 // 标题文字大小
const descSize: number = 14 // 描述文字大小
const width: number = windowWidth - xDistance * 2
// @ts-ignore
const top: number = statusBarHeight + tDistance
const lineNum = Math.ceil(desc.length / Math.floor((width - YPadding * 2) / descSize)) * 1.2
const height = YPadding * 2 + titleSize + space + descSize * lineNum
// @ts-ignore
let _view = new plus.nativeObj.View(
idKey,
{
width: `${width}`,
top: `${top}`,
left: `${xDistance}`,
height: `${height}`,
},
[
{
tag: 'rect',
id: 'rect',
rectStyles: {
color: '#FFFFFF',
radius: `${radius}`,
},
},
{
tag: 'font',
id: 'title',
text: title,
textStyles: {size: `${titleSize}`, align: 'left', weight: 'bold'},
position: {top: `${YPadding}`, left: `${xPadding}`, height: 'wrap_content'},
},
{
tag: 'font',
id: 'desc',
text: desc,
textStyles: {size: `${descSize}`, align: 'left', color: '#666666', whiteSpace: 'normal'},
position: {
top: `${YPadding + titleSize + space}`,
left: `${xPadding}`,
height: 'wrap_content',
width: `${width - YPadding * 2}`,
},
},
]
)
_view.show()
view = _view
}
/**
* 关闭消息
*/
export function closeNotify() {
if (view) view.close()
}
收起阅读 »
uniapp 在抖音(字节)小程序在安卓手机上,使用自己封装的input组件或者easy-input组件时,多次进入修改页面,不定时为空的问题。
在修改页面使用input组件时,多次反复进入修改页面,input的值有时为空,是因为 抖音小程序的input组件的maxlength会触发bindinput事件,并且执行顺序是不确定的。
目前抖音官方好像还没有修复,可使用如下代码临时解决:
/**
* 输入时触发
* @param {Object} event
*/
onInput(event) {
let value = event.detail.value
// 解决抖音小程序 反复返回,输入框值为空的问题(maxlength在安卓手机上会触发bindinput事件)
// #ifdef MP-TOUTIAO
if(!value && this.modelValue){
return
}
// #endif
this.val = value;
// TODO 兼容 vue2
this.$emit('input', value);
// TODO 兼容 vue3
this.$emit('update:modelValue', value);
},
在修改页面使用input组件时,多次反复进入修改页面,input的值有时为空,是因为 抖音小程序的input组件的maxlength会触发bindinput事件,并且执行顺序是不确定的。
目前抖音官方好像还没有修复,可使用如下代码临时解决:
/**
* 输入时触发
* @param {Object} event
*/
onInput(event) {
let value = event.detail.value
// 解决抖音小程序 反复返回,输入框值为空的问题(maxlength在安卓手机上会触发bindinput事件)
// #ifdef MP-TOUTIAO
if(!value && this.modelValue){
return
}
// #endif
this.val = value;
// TODO 兼容 vue2
this.$emit('input', value);
// TODO 兼容 vue3
this.$emit('update:modelValue', value);
},
收起阅读 »
支付宝小程序map高级定制渲染踩坑
使用支付宝小程序的map callout气泡功能时出现了customCallout不显示 / xml文件不被编译等等一系列的问题,网上的信息比较杂乱,分享一下自己的解决过程
支付宝小程序中的customCallout在真机中只显示第一个,改用iconLayout实现功能;
使用map高级定制渲染时,xml文件不会被编译,按照支付宝官方文档,在manifest.json中添加:
"mp-alipay": {
"include": ["**/*.xml"]
}
添加后没有效果,之后发现可以在项目根目录添加mini.project.json文件,在此文件中添加配置:
{
"enableAppxNg": true,
"include": ["**/*.xml"]
}
marker:
// #ifdef MP-ALIPAY
iconLayout: {
params: {
count: 1
},
src: '/static/map-xml/map-icon.xml'
}
// #endif
map-icon.xml:
<box layout="horizontal">
<text
id="test1"
clickable="true"
text="测试1"
padding-left="8"
padding-right="8"
font-size="16"
border-radius="6"
background-color="#FF0000"
/>
<text
id="test2"
clickable="true"
text="测试2"
padding-left="8"
padding-right="8"
font-size="16"
border-radius="6"
background-color="#FF0000"
/>
</box>
编译成功后在支付宝开发者工具mini.project.json文件中的配置
xml文件需要放入static文件夹中,放在根目录时不会编译
真机运行后气泡正常显示
使用支付宝小程序的map callout气泡功能时出现了customCallout不显示 / xml文件不被编译等等一系列的问题,网上的信息比较杂乱,分享一下自己的解决过程
支付宝小程序中的customCallout在真机中只显示第一个,改用iconLayout实现功能;
使用map高级定制渲染时,xml文件不会被编译,按照支付宝官方文档,在manifest.json中添加:
"mp-alipay": {
"include": ["**/*.xml"]
}
添加后没有效果,之后发现可以在项目根目录添加mini.project.json文件,在此文件中添加配置:
{
"enableAppxNg": true,
"include": ["**/*.xml"]
}
marker:
// #ifdef MP-ALIPAY
iconLayout: {
params: {
count: 1
},
src: '/static/map-xml/map-icon.xml'
}
// #endif
map-icon.xml:
<box layout="horizontal">
<text
id="test1"
clickable="true"
text="测试1"
padding-left="8"
padding-right="8"
font-size="16"
border-radius="6"
background-color="#FF0000"
/>
<text
id="test2"
clickable="true"
text="测试2"
padding-left="8"
padding-right="8"
font-size="16"
border-radius="6"
background-color="#FF0000"
/>
</box>
编译成功后在支付宝开发者工具mini.project.json文件中的配置
xml文件需要放入static文件夹中,放在根目录时不会编译
真机运行后气泡正常显示
收起阅读 »
热更新的静默更新会导致数据错误的优化方案
官方的热更新升级方案中是支持静默更新的,但是静默更新如果不重启常常会有页面样式错乱、数据加载错误等问题,看组件源码我们可以发现下载完成后是立即执行了wgt安装的操作,此时不重启app就会导致以上问题,这里我的思路是想在app在后台时执行安装并重启,或者在下次打开时执行安装并重启。
方案一、应用置于后台运行时安装热更新包并重启。
优点:更新会比较及时,只要应用置于后台了就会执行更新;
缺点:用户返回app时会执行安装重启,此时有明显的重启操作(安装完之后,我的wgt包20M需等3秒左右会重启),重启后会回到首页,这里可以记录重启前的页面,重启后重新定位该页面。
方案二、应用完全退出后重启时安装热更新包并再次重启。
优点:用户不会感觉到很明显的重启操作,不影响用户正常操作;
缺点:第一次启动时会有较长的时间停留在启动页(wgt包20M会停留在启动页7秒左右)安装完的重启几乎无感,很快,当然我们可以制作一个nvue的前端页面来代替启动页,并在这个页面用文字提示用户正在加载资源什么的(类似游戏打开先检查一番资源),获取用户信任,否则长时间的停留在启动页容易让用户误以为是app卡死。
主要的代码部分
uni-upgrade-center-app/utils/check-update.ts
// 静默更新,只有wgt有
if (uniUpgradeCenterResult.is_silently) {
uni.downloadFile({
url,
success: res => {
if (res.statusCode == 200) {
uni.setStorageSync('WGTFilePath', res.tempFilePath)
// 下载好直接安装,下次启动生效
// plus.runtime.install(res.tempFilePath, {
// force: false
// });
}
}
});
return;
}
保存wgt包的文件路径
App.vue
方案一:
onHide: function() {
let WGTFilePath = uni.getStorageSync('WGTFilePath')
if (WGTFilePath)
plus.runtime.install(WGTFilePath, {
force: false
}, () => {
uni.setStorageSync('WGTFilePath', '')
plus.runtime.restart()
return
})
},
方案二:
onLaunch: function() {
let WGTFilePath = uni.getStorageSync('WGTFilePath')
if (WGTFilePath)
plus.runtime.install(WGTFilePath, {
force: false
}, () => {
uni.setStorageSync('WGTFilePath', '')
plus.runtime.restart()
return
})
},
onShow: function() {
let WGTFilePath = uni.getStorageSync('WGTFilePath')
if (!WGTFilePath)//手动关闭启动页
plus.navigator.closeSplashscreen()
},
需要在配置文件关闭自动关闭启动页,并在app.vue手动处理
"splashscreen" : {
"alwaysShowBeforeRender" : false,
"waiting" : false,
"autoclose" : false,
"delay" : 0
},
官方的热更新升级方案中是支持静默更新的,但是静默更新如果不重启常常会有页面样式错乱、数据加载错误等问题,看组件源码我们可以发现下载完成后是立即执行了wgt安装的操作,此时不重启app就会导致以上问题,这里我的思路是想在app在后台时执行安装并重启,或者在下次打开时执行安装并重启。
方案一、应用置于后台运行时安装热更新包并重启。
优点:更新会比较及时,只要应用置于后台了就会执行更新;
缺点:用户返回app时会执行安装重启,此时有明显的重启操作(安装完之后,我的wgt包20M需等3秒左右会重启),重启后会回到首页,这里可以记录重启前的页面,重启后重新定位该页面。
方案二、应用完全退出后重启时安装热更新包并再次重启。
优点:用户不会感觉到很明显的重启操作,不影响用户正常操作;
缺点:第一次启动时会有较长的时间停留在启动页(wgt包20M会停留在启动页7秒左右)安装完的重启几乎无感,很快,当然我们可以制作一个nvue的前端页面来代替启动页,并在这个页面用文字提示用户正在加载资源什么的(类似游戏打开先检查一番资源),获取用户信任,否则长时间的停留在启动页容易让用户误以为是app卡死。
主要的代码部分
uni-upgrade-center-app/utils/check-update.ts
// 静默更新,只有wgt有
if (uniUpgradeCenterResult.is_silently) {
uni.downloadFile({
url,
success: res => {
if (res.statusCode == 200) {
uni.setStorageSync('WGTFilePath', res.tempFilePath)
// 下载好直接安装,下次启动生效
// plus.runtime.install(res.tempFilePath, {
// force: false
// });
}
}
});
return;
}
保存wgt包的文件路径
App.vue
方案一:
onHide: function() {
let WGTFilePath = uni.getStorageSync('WGTFilePath')
if (WGTFilePath)
plus.runtime.install(WGTFilePath, {
force: false
}, () => {
uni.setStorageSync('WGTFilePath', '')
plus.runtime.restart()
return
})
},
方案二:
onLaunch: function() {
let WGTFilePath = uni.getStorageSync('WGTFilePath')
if (WGTFilePath)
plus.runtime.install(WGTFilePath, {
force: false
}, () => {
uni.setStorageSync('WGTFilePath', '')
plus.runtime.restart()
return
})
},
onShow: function() {
let WGTFilePath = uni.getStorageSync('WGTFilePath')
if (!WGTFilePath)//手动关闭启动页
plus.navigator.closeSplashscreen()
},
需要在配置文件关闭自动关闭启动页,并在app.vue手动处理
"splashscreen" : {
"alwaysShowBeforeRender" : false,
"waiting" : false,
"autoclose" : false,
"delay" : 0
},
收起阅读 »
自研 - Web简谱渲染引擎
概述
欢迎您来到音乐创作的新境界!我们为您带来一款革命性的简谱渲染引擎,它将简谱的解析与播放体验提升到全新的高度。无论您是音乐教育者、作曲家,还是开发者,这款引擎都能为您提供无与伦比的功能与灵活性。
核心功能
概述
欢迎您来到音乐创作的新境界!我们为您带来一款革命性的简谱渲染引擎,它将简谱的解析与播放体验提升到全新的高度。无论您是音乐教育者、作曲家,还是开发者,这款引擎都能为您提供无与伦比的功能与灵活性。