
uniapp创建企微在ios端wx.config 以及wx.agentConfig问题的完美解决
之前写过一篇解决的文章 但是 后续发现还是有那么一点问题 就是ios 在调用 agentConfig的API 的时候会发现 agentConfig 找不到 是 undefined。
https://developer.work.weixin.qq.com/community/article/detail?content_id=16288602254668494166
之前的文章,通过后学的摸索,终于完美解决了。
步骤一:创建项目
我们知道 官方然我们在项目中 需要建立一个 index.html 文件用于 引入企微的SDK。我发现及时在index.html中引入了,uniapp在打包的时候还是会占用wx这个类,说明uniapp内部还是一个异步的过程。因此我们需要动态引入,而不是 在index中引入。
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
})
window.wx = null;
</script>
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
<!-- 企业微信h5授权 config注入 -->
<!-- <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script> -->
<!-- 调用 wx.agentConfig需要引入 jwxwork sdk -->
<!-- <script type="text/javascript" src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script> -->
<!-- 用于调试所用 -->
<script type="text/javascript" src="https://cdn.bootcss.com/vConsole/3.3.0/vconsole.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/mobile-detect@1.4.5/mobile-detect.min.js"></script>
<script type="text/javascript">
delete window.wx;
</script>
<head>
因此 在index中我们只引入 vconsole 就行了 不要引入企微的SDK。后续我们通过动态引入
二:动态引入sdk 并注册
我们可以在工程中建立一个专门的 企微的工具库 ,我的工程是放在一起的 没有分开。你可以根据自己的逻辑来建立库 我的库为 ESUtils.js 并将 企微的方法全放在了 wxUntils中
我们动态 引入SDK,动态引入
createdScript: function(callback) {
// window.wx = null;
let userInfo = ESUtils.ESDB.getLoginInfo(); //自己存的相关缓存
const script1 = document.createElement("script");
script1.setAttribute("type", "text/javascript");
script1.setAttribute("referrerpolicy", "origin");
script1.setAttribute("src", "https://res.wx.qq.com/open/js/jweixin-1.2.0.js");
document.head.appendChild(script1);
script1.onload = function() {
const script2 = document.createElement("script");
script2.setAttribute("type", "text/javascript");
script2.setAttribute("referrerpolicy", "origin");
script2.setAttribute("src", "https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js");
document.head.appendChild(script2);
script2.onload = () => {
callback && callback(userInfo)
}
}
},
注册 config
initJssdk: function(callback) {
this.createdScript((userInfo) => {
ESUtils.network.request({
url: userInfo.server,
data: {
method: "Work_WXInitJSSDK", //初始化SDK
userId:userInfo.userId,
url:userInfo.url,
cropId:userInfo.cropId
},
success: res => {
if (res.data.response.code == 200) {
let info = res.data.body; //需要判断 ios 还是 安卓
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
wx.config({
beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题
debug: false, // 开启调试模式
appId: info.appId, // 必填,企业微信的corpID
timestamp: info.timestamp, // 必填,生成签名的时间戳
nonceStr: info.nonceStr, // 必填,生成签名的随机串
signature: info.signature, // 必填,签名,见 附录-JS-SDK使用权限签名算法
jsApiList: [
'chooseImage',
'scanQRCode',
'previewImage',
'getLocation',
'previewFile',
'openLocation',
'uploadImage',
'getLocalImgData',
'downloadImage',
'startRecord',
'stopRecord',
'onVoiceRecordEnd',
'playVoice',
'pauseVoice',
'stopVoice',
'onVoicePlayEnd',
'uploadVoice',
'downloadVoice',
'translateVoice'
]
});
wx.ready((res) => {
callback && callback(info);
})
wx.error((err) => {
});
}else{
jWeixin.config({
beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题
debug: false, // 开启调试模式
appId: info.appId, // 必填,企业微信的corpID
timestamp: info.timestamp, // 必填,生成签名的时间戳
nonceStr: info.nonceStr, // 必填,生成签名的随机串
signature: info.signature, // 必填,签名,见 附录-JS-SDK使用权限签名算法
jsApiList: [
'chooseImage',
'scanQRCode',
'previewImage',
'getLocation',
'previewFile',
'openLocation',
'uploadImage',
'getLocalImgData',
'downloadImage',
'startRecord',
'stopRecord',
'onVoiceRecordEnd',
'playVoice',
'pauseVoice',
'stopVoice',
'onVoicePlayEnd',
'uploadVoice',
'downloadVoice',
'translateVoice'
]
});
jWeixin.ready((res) => {
callback && callback(info);
})
jWeixin.error((err) => {
});
}
}
}
});
});
},
注册 agentConfig
//agentConfig必须是 config注册成功后才能 注册
initJssdkAgent: function(callback) {
ESUtils.wxUntils.initJssdk((info) => {
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
setTimeout(()=>{
wx.agentConfig({
corpid: info.appId, // 必填,企业微信的corpid,必须与当前登录的企业一致
agentid: info.angetId, // 必填,企业微信的应用id (e.g. 1000247)
timestamp: info.timestamp, // 必填,生成签名的时间戳
nonceStr: info.nonceStr, // 必填,生成签名的随机串
signature: info.signature1, // 必填,签名,见附录-JS-SDK使用权限签名算法
jsApiList: [
"selectEnterpriseContact",
"getContext",
"getCurExternalContact",
"openUserProfile",
"getCurExternalChat",
"selectExternalContact",
"selectPrivilegedContact",
"createChatWithMsg",
"sendChatMessage",
"openExistedChatWithMsg",
"shareToExternalContact",
"shareToExternalChat",
"setShareAttr",
"getShareInfo",
"shareAppMessage", //自定义转发发到会话
"shareWechatMessage", //自定义转发到微信
"openEnterpriseChat"
],
success: res => {
callback && callback(res)
},
fail: err => {
callback && callback(err)
}
});
}, 1000);
}else{
setTimeout(()=>{
jWeixin.agentConfig({
corpid: info.appId, // 必填,企业微信的corpid,必须与当前登录的企业一致
agentid: info.angetId, // 必填,企业微信的应用id (e.g. 1000247)
timestamp: info.timestamp, // 必填,生成签名的时间戳
nonceStr: info.nonceStr, // 必填,生成签名的随机串
signature: info.signature1, // 必填,签名,见附录-JS-SDK使用权限签名算法
jsApiList: [
"selectEnterpriseContact",
"getContext",
"getCurExternalContact",
"openUserProfile",
"getCurExternalChat",
"selectExternalContact",
"selectPrivilegedContact",
"createChatWithMsg",
"sendChatMessage",
"openExistedChatWithMsg",
"shareToExternalContact",
"shareToExternalChat",
"setShareAttr",
"getShareInfo",
"shareAppMessage", //自定义转发发到会话
"shareWechatMessage", //自定义转发到微信
"openEnterpriseChat"
],
success: res => {
callback && callback(res)
},
fail: err => {
callback && callback(err)
}
});
}, 1000);
}
});
},
现在我们就已经解决了 初始化及注册的问题了,然后我们将我们需要用到的 API 进行二次封装
//扫一扫的 API
scanCode: function(callback) {
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
wx.scanQRCode({
desc: 'scanQRCode desc',
needResult: 1, // 默认为0,扫描结果由企业微信处理,1则直接返回扫描结果,
scanType: ["qrCode", "barCode"], // 可以指定扫二维码还是条形码(一维码),默认二者都有
success: function(res) {
callback && callback(res);
}
});
}else{
jWeixin.scanQRCode({
desc: 'scanQRCode desc',
needResult: 1, // 默认为0,扫描结果由企业微信处理,1则直接返回扫描结果,
scanType: ["qrCode", "barCode"], // 可以指定扫二维码还是条形码(一维码),默认二者都有
success: function(res) {
callback && callback(res);
}
});
}
},
//获取进入H5页面的入口环境
getContext: function(callback) {
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
wx.invoke('getContext', {}, (res) => {
callback && callback(res)
});
}else{
jWeixin.invoke('getContext', {}, (res) => {
callback && callback(res)
});
}
},
// 选择外部联系人
selectEContact: function(callback) {
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
wx.invoke('selectExternalContact', {
"filterType": 0, //0表示展示全部外部联系人列表,1表示仅展示未曾选择过的外部联系人。默认值为0;除了0与1,其他值非法。在企业微信2.4.22及以后版本支持该参数
}, (res) => {
if (res.err_msg == "selectExternalContact:ok") {
callback && callback(res, 1);
} else {
callback && callback(res, 0);
}
});
}else{
jWeixin.invoke('selectExternalContact', {
"filterType": 0, //0表示展示全部外部联系人列表,1表示仅展示未曾选择过的外部联系人。默认值为0;除了0与1,其他值非法。在企业微信2.4.22及以后版本支持该参数
}, (res) => {
if (res.err_msg == "selectExternalContact:ok") {
callback && callback(res, 1);
} else {
callback && callback(res, 0);
}
});
}
},
上述为一部分的API封装
三:页面的使用
做完所有的准备工作后,我们就需要进行项目的建立了 首先我们需要在页面中进行引入。
index.vue。
这个有一个非常重要的信息,我最开始 也是 这样然后再页面引入了但是我通过console 发现 当第一次进入的时候 在ios中无法识别出 agentConfig,一次偶然的时机我通过刷新一次页面后发现就能抽取出来,但是 动态引入只能引入一次,因此我决定更具这个情况进行曲线救国策略。
在index.vue中
onload(option){
var url = document.location.href;
let value = ESUtils.fn.getQueryString("eventType");
//每次进入index的我们让他自动刷新一次
if(url.indexOf("refreshed=") == -1){
var time = new Date();
window.location.href=url+"&refreshed="+time.getTime();
}
this.$nextTick(()=>{
let cropId = ESUtils.ESDB.getLoginInfo().cropId;
let userId = ESUtils.ESDB.getLoginInfo().id;
let url = window.location.href;
ESUtils.wxUntils.initJssdkAgent(res=>{
//引入并注册SDK
WWOpenData.bind(document.querySelectorAll('ww-open-data'));
ESUtils.wxUntils.getContext(result=>{ //这是判断是否 从快捷栏进入的 从而跳转指定的页面 并将入口的参数传过去
var loginInfo = ESUtils.ESDB.getLoginInfo();
if(url.indexOf("refreshed=") != -1){
if(option.eventType && option.eventType != ""){
var push = ESPush[option.eventType];
var params = '?pushEventId=' + option.eventId + "&workWX=" + result.entry;
let page = "";
if(ESUtils.ESDB.getLoginInfo().isJava == 1){
page = push.page_java;
}else{
page = push.page;
}
push && uni.navigateTo({
url: page + params
});
}
}
});
})
});
}
通过这样 我们就可以发现 在ios 和安卓中我们都可以进行兼容了 ,不会出现 wx.config 或者 wx.agnetConfig 为undefined的问题了。注意 其他的界面最好不要再调用 initJssdkAgent()方法了
如果 其他的朋友还有更好的解决方法,可以多多交流
之前写过一篇解决的文章 但是 后续发现还是有那么一点问题 就是ios 在调用 agentConfig的API 的时候会发现 agentConfig 找不到 是 undefined。
https://developer.work.weixin.qq.com/community/article/detail?content_id=16288602254668494166
之前的文章,通过后学的摸索,终于完美解决了。
步骤一:创建项目
我们知道 官方然我们在项目中 需要建立一个 index.html 文件用于 引入企微的SDK。我发现及时在index.html中引入了,uniapp在打包的时候还是会占用wx这个类,说明uniapp内部还是一个异步的过程。因此我们需要动态引入,而不是 在index中引入。
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
})
window.wx = null;
</script>
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
<!-- 企业微信h5授权 config注入 -->
<!-- <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script> -->
<!-- 调用 wx.agentConfig需要引入 jwxwork sdk -->
<!-- <script type="text/javascript" src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script> -->
<!-- 用于调试所用 -->
<script type="text/javascript" src="https://cdn.bootcss.com/vConsole/3.3.0/vconsole.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/mobile-detect@1.4.5/mobile-detect.min.js"></script>
<script type="text/javascript">
delete window.wx;
</script>
<head>
因此 在index中我们只引入 vconsole 就行了 不要引入企微的SDK。后续我们通过动态引入
二:动态引入sdk 并注册
我们可以在工程中建立一个专门的 企微的工具库 ,我的工程是放在一起的 没有分开。你可以根据自己的逻辑来建立库 我的库为 ESUtils.js 并将 企微的方法全放在了 wxUntils中
我们动态 引入SDK,动态引入
createdScript: function(callback) {
// window.wx = null;
let userInfo = ESUtils.ESDB.getLoginInfo(); //自己存的相关缓存
const script1 = document.createElement("script");
script1.setAttribute("type", "text/javascript");
script1.setAttribute("referrerpolicy", "origin");
script1.setAttribute("src", "https://res.wx.qq.com/open/js/jweixin-1.2.0.js");
document.head.appendChild(script1);
script1.onload = function() {
const script2 = document.createElement("script");
script2.setAttribute("type", "text/javascript");
script2.setAttribute("referrerpolicy", "origin");
script2.setAttribute("src", "https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js");
document.head.appendChild(script2);
script2.onload = () => {
callback && callback(userInfo)
}
}
},
注册 config
initJssdk: function(callback) {
this.createdScript((userInfo) => {
ESUtils.network.request({
url: userInfo.server,
data: {
method: "Work_WXInitJSSDK", //初始化SDK
userId:userInfo.userId,
url:userInfo.url,
cropId:userInfo.cropId
},
success: res => {
if (res.data.response.code == 200) {
let info = res.data.body; //需要判断 ios 还是 安卓
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
wx.config({
beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题
debug: false, // 开启调试模式
appId: info.appId, // 必填,企业微信的corpID
timestamp: info.timestamp, // 必填,生成签名的时间戳
nonceStr: info.nonceStr, // 必填,生成签名的随机串
signature: info.signature, // 必填,签名,见 附录-JS-SDK使用权限签名算法
jsApiList: [
'chooseImage',
'scanQRCode',
'previewImage',
'getLocation',
'previewFile',
'openLocation',
'uploadImage',
'getLocalImgData',
'downloadImage',
'startRecord',
'stopRecord',
'onVoiceRecordEnd',
'playVoice',
'pauseVoice',
'stopVoice',
'onVoicePlayEnd',
'uploadVoice',
'downloadVoice',
'translateVoice'
]
});
wx.ready((res) => {
callback && callback(info);
})
wx.error((err) => {
});
}else{
jWeixin.config({
beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题
debug: false, // 开启调试模式
appId: info.appId, // 必填,企业微信的corpID
timestamp: info.timestamp, // 必填,生成签名的时间戳
nonceStr: info.nonceStr, // 必填,生成签名的随机串
signature: info.signature, // 必填,签名,见 附录-JS-SDK使用权限签名算法
jsApiList: [
'chooseImage',
'scanQRCode',
'previewImage',
'getLocation',
'previewFile',
'openLocation',
'uploadImage',
'getLocalImgData',
'downloadImage',
'startRecord',
'stopRecord',
'onVoiceRecordEnd',
'playVoice',
'pauseVoice',
'stopVoice',
'onVoicePlayEnd',
'uploadVoice',
'downloadVoice',
'translateVoice'
]
});
jWeixin.ready((res) => {
callback && callback(info);
})
jWeixin.error((err) => {
});
}
}
}
});
});
},
注册 agentConfig
//agentConfig必须是 config注册成功后才能 注册
initJssdkAgent: function(callback) {
ESUtils.wxUntils.initJssdk((info) => {
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
setTimeout(()=>{
wx.agentConfig({
corpid: info.appId, // 必填,企业微信的corpid,必须与当前登录的企业一致
agentid: info.angetId, // 必填,企业微信的应用id (e.g. 1000247)
timestamp: info.timestamp, // 必填,生成签名的时间戳
nonceStr: info.nonceStr, // 必填,生成签名的随机串
signature: info.signature1, // 必填,签名,见附录-JS-SDK使用权限签名算法
jsApiList: [
"selectEnterpriseContact",
"getContext",
"getCurExternalContact",
"openUserProfile",
"getCurExternalChat",
"selectExternalContact",
"selectPrivilegedContact",
"createChatWithMsg",
"sendChatMessage",
"openExistedChatWithMsg",
"shareToExternalContact",
"shareToExternalChat",
"setShareAttr",
"getShareInfo",
"shareAppMessage", //自定义转发发到会话
"shareWechatMessage", //自定义转发到微信
"openEnterpriseChat"
],
success: res => {
callback && callback(res)
},
fail: err => {
callback && callback(err)
}
});
}, 1000);
}else{
setTimeout(()=>{
jWeixin.agentConfig({
corpid: info.appId, // 必填,企业微信的corpid,必须与当前登录的企业一致
agentid: info.angetId, // 必填,企业微信的应用id (e.g. 1000247)
timestamp: info.timestamp, // 必填,生成签名的时间戳
nonceStr: info.nonceStr, // 必填,生成签名的随机串
signature: info.signature1, // 必填,签名,见附录-JS-SDK使用权限签名算法
jsApiList: [
"selectEnterpriseContact",
"getContext",
"getCurExternalContact",
"openUserProfile",
"getCurExternalChat",
"selectExternalContact",
"selectPrivilegedContact",
"createChatWithMsg",
"sendChatMessage",
"openExistedChatWithMsg",
"shareToExternalContact",
"shareToExternalChat",
"setShareAttr",
"getShareInfo",
"shareAppMessage", //自定义转发发到会话
"shareWechatMessage", //自定义转发到微信
"openEnterpriseChat"
],
success: res => {
callback && callback(res)
},
fail: err => {
callback && callback(err)
}
});
}, 1000);
}
});
},
现在我们就已经解决了 初始化及注册的问题了,然后我们将我们需要用到的 API 进行二次封装
//扫一扫的 API
scanCode: function(callback) {
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
wx.scanQRCode({
desc: 'scanQRCode desc',
needResult: 1, // 默认为0,扫描结果由企业微信处理,1则直接返回扫描结果,
scanType: ["qrCode", "barCode"], // 可以指定扫二维码还是条形码(一维码),默认二者都有
success: function(res) {
callback && callback(res);
}
});
}else{
jWeixin.scanQRCode({
desc: 'scanQRCode desc',
needResult: 1, // 默认为0,扫描结果由企业微信处理,1则直接返回扫描结果,
scanType: ["qrCode", "barCode"], // 可以指定扫二维码还是条形码(一维码),默认二者都有
success: function(res) {
callback && callback(res);
}
});
}
},
//获取进入H5页面的入口环境
getContext: function(callback) {
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
wx.invoke('getContext', {}, (res) => {
callback && callback(res)
});
}else{
jWeixin.invoke('getContext', {}, (res) => {
callback && callback(res)
});
}
},
// 选择外部联系人
selectEContact: function(callback) {
if (/(iPhone|iPad|iPod|iOS|macintosh|mac os x)/i.test(ESUtils.wxUntils.system)) {
wx.invoke('selectExternalContact', {
"filterType": 0, //0表示展示全部外部联系人列表,1表示仅展示未曾选择过的外部联系人。默认值为0;除了0与1,其他值非法。在企业微信2.4.22及以后版本支持该参数
}, (res) => {
if (res.err_msg == "selectExternalContact:ok") {
callback && callback(res, 1);
} else {
callback && callback(res, 0);
}
});
}else{
jWeixin.invoke('selectExternalContact', {
"filterType": 0, //0表示展示全部外部联系人列表,1表示仅展示未曾选择过的外部联系人。默认值为0;除了0与1,其他值非法。在企业微信2.4.22及以后版本支持该参数
}, (res) => {
if (res.err_msg == "selectExternalContact:ok") {
callback && callback(res, 1);
} else {
callback && callback(res, 0);
}
});
}
},
上述为一部分的API封装
三:页面的使用
做完所有的准备工作后,我们就需要进行项目的建立了 首先我们需要在页面中进行引入。
index.vue。
这个有一个非常重要的信息,我最开始 也是 这样然后再页面引入了但是我通过console 发现 当第一次进入的时候 在ios中无法识别出 agentConfig,一次偶然的时机我通过刷新一次页面后发现就能抽取出来,但是 动态引入只能引入一次,因此我决定更具这个情况进行曲线救国策略。
在index.vue中
onload(option){
var url = document.location.href;
let value = ESUtils.fn.getQueryString("eventType");
//每次进入index的我们让他自动刷新一次
if(url.indexOf("refreshed=") == -1){
var time = new Date();
window.location.href=url+"&refreshed="+time.getTime();
}
this.$nextTick(()=>{
let cropId = ESUtils.ESDB.getLoginInfo().cropId;
let userId = ESUtils.ESDB.getLoginInfo().id;
let url = window.location.href;
ESUtils.wxUntils.initJssdkAgent(res=>{
//引入并注册SDK
WWOpenData.bind(document.querySelectorAll('ww-open-data'));
ESUtils.wxUntils.getContext(result=>{ //这是判断是否 从快捷栏进入的 从而跳转指定的页面 并将入口的参数传过去
var loginInfo = ESUtils.ESDB.getLoginInfo();
if(url.indexOf("refreshed=") != -1){
if(option.eventType && option.eventType != ""){
var push = ESPush[option.eventType];
var params = '?pushEventId=' + option.eventId + "&workWX=" + result.entry;
let page = "";
if(ESUtils.ESDB.getLoginInfo().isJava == 1){
page = push.page_java;
}else{
page = push.page;
}
push && uni.navigateTo({
url: page + params
});
}
}
});
})
});
}
通过这样 我们就可以发现 在ios 和安卓中我们都可以进行兼容了 ,不会出现 wx.config 或者 wx.agnetConfig 为undefined的问题了。注意 其他的界面最好不要再调用 initJssdkAgent()方法了
如果 其他的朋友还有更好的解决方法,可以多多交流
收起阅读 »

mac环境,HBuilder X开发uni-app项目使用安卓模拟器
环境:
- mac:12.6
- cpu:intel
步骤:
- 下载并安装夜神模拟器,这是夜神模拟器官网,我安装的是 3.8.5.7。
- 按照提示安装并启动夜神模拟器,注意一定要授权。
- HBuilder X,设置-运行配置-Android模拟器端口,端口号填写62001。这是官方端口号说明](https://www.yeshen.com/faqs/H15tDZ6YW),也可以通过在终端使用
adb devices
来查看端口号。 - 运行,运行到安卓手机模拟器基座,这时应该就可以用了。
如果还是不行,检查你的/Applications/NemuPlayer.app/Contents/MacOS目录下是否有adb,hbuderX会默认找这个地址下的adb,adb目录可以在配置接口的地方配置。
说一下为什么不用mumu,我这边运行到mumu一直显示“同步手机端程序文件完成”,不能进入页面。
环境:
- mac:12.6
- cpu:intel
步骤:
- 下载并安装夜神模拟器,这是夜神模拟器官网,我安装的是 3.8.5.7。
- 按照提示安装并启动夜神模拟器,注意一定要授权。
- HBuilder X,设置-运行配置-Android模拟器端口,端口号填写62001。这是官方端口号说明](https://www.yeshen.com/faqs/H15tDZ6YW),也可以通过在终端使用
adb devices
来查看端口号。 - 运行,运行到安卓手机模拟器基座,这时应该就可以用了。
如果还是不行,检查你的/Applications/NemuPlayer.app/Contents/MacOS目录下是否有adb,hbuderX会默认找这个地址下的adb,adb目录可以在配置接口的地方配置。
说一下为什么不用mumu,我这边运行到mumu一直显示“同步手机端程序文件完成”,不能进入页面。
收起阅读 »
uniCloud中schema角色权限配置没有生效的原因
现象
按照文档在admin项目中创建了权限和角色,正确分配给了用户,也正确配置了schema,但是测试过程中发现总是报没有权限。
原因
在文档的角落里发现了一句话:“uni-id将用户的角色权限缓存在token内。”,我顿时内心一万匹马儿跑过。在客户端增加了一个刷新token,果然可以。
现象
按照文档在admin项目中创建了权限和角色,正确分配给了用户,也正确配置了schema,但是测试过程中发现总是报没有权限。
原因
在文档的角落里发现了一句话:“uni-id将用户的角色权限缓存在token内。”,我顿时内心一万匹马儿跑过。在客户端增加了一个刷新token,果然可以。

上架 Google Play 被拒,提示“包含用于从 Google Play 之外的未知来源下载或安装应用程序的代码”的解决方案
使用HBuilderX 3.8.7以上版本打包上架google play的应用是正常的不受影响。
根据目前统计到的信息,审核被拒一般是以下两种情况:
- 审核被拒的版本号为较老版本号,并不是本次升级对应的版本号。
- 打包使用的HBuilderX的为3.8.7以前的版本。
解决方案:
- 升级HBuilderX到最新版并重新打包上架。
- 将所有发布过版本的轨道都用最新的HBuilderX重新打包上架,具体步骤参考下图(截图来自于google play邮件,具体可以参考Google Play审核邮件完整内容)。
注意:发版时在预览并确认
页面将分阶段发布百分比
填100%,适用的国家/地区
选择发布到所有目标国家/地区
。确保所有用户都能升级到最新版。参考下图。
如果按照上述方式上架仍然被拒。可以选择申诉。
申诉方式:点击政策状态
->“设备和网络滥用”政策:违反“设备和网络滥用”政策
,点击最下方申诉按钮,等待申诉结果。
<!--目前接收到的结果分三类:
- 申诉通过。
- 申诉失败,但是过段时间刷新google play后台显示通过。
- 申诉失败,重新提交上架审核,上架成功。
申诉结果可能会失败,但是目前所有的应用后续的上架审核都成功了。-->
使用HBuilderX 3.8.7以上版本打包上架google play的应用是正常的不受影响。
根据目前统计到的信息,审核被拒一般是以下两种情况:
- 审核被拒的版本号为较老版本号,并不是本次升级对应的版本号。
- 打包使用的HBuilderX的为3.8.7以前的版本。
解决方案:
- 升级HBuilderX到最新版并重新打包上架。
- 将所有发布过版本的轨道都用最新的HBuilderX重新打包上架,具体步骤参考下图(截图来自于google play邮件,具体可以参考Google Play审核邮件完整内容)。
注意:发版时在预览并确认
页面将分阶段发布百分比
填100%,适用的国家/地区
选择发布到所有目标国家/地区
。确保所有用户都能升级到最新版。参考下图。
如果按照上述方式上架仍然被拒。可以选择申诉。
申诉方式:点击政策状态
->“设备和网络滥用”政策:违反“设备和网络滥用”政策
,点击最下方申诉按钮,等待申诉结果。
<!--目前接收到的结果分三类:
- 申诉通过。
- 申诉失败,但是过段时间刷新google play后台显示通过。
- 申诉失败,重新提交上架审核,上架成功。
申诉结果可能会失败,但是目前所有的应用后续的上架审核都成功了。-->
收起阅读 »
华为审核申请权限告知目的
华为app上架审核,申请权限的时候只有弹框,没有告知目的,被审核驳回了。。。
然后看官方,发现plus.android.requestPermissions只有点击才有回调啊,然后看到一个大佬代码设置了一个初始值,缝缝补补,整出来一个将就使用的完整代码。合不合适看最后压缩包视频就知道啦
//这是vuex单独一个模块的完整代码
const state = {
WRITE_EXTERNAL_STORAGE: false,
READ_EXTERNAL_STORAGE: false,
CALL_PHONE: false,
isIos: plus.os.name == "iOS",
mapping: {
'WRITE_EXTERNAL_STORAGE': {
title: "存储空间/照片权限说明",
content: "便于您使用该功能上传您的照片/图片/视频及用于更换头像、发布评论/分享、下载、与客服沟通等场景中读取和写入相册和文件内容。",
methods: 'SET_WRITE_EXTERNAL_STORAGE'
},
'READ_EXTERNAL_STORAGE': {
title: "存储空间/照片权限说明",
content: "便于您使用该功能上传您的照片/图片/视频及用于更换头像、发布评论/分享、下载、与客服沟通等场景中读取和写入相册和文件内容。",
methods: 'SET_READ_EXTERNAL_STORAGE'
},
'CALL_PHONE': {
title: "拨打/管理电话权限说明",
content: "便于您使用该功能联系商家或者商家与您联系等场景",
methods: 'SET_CALL_PHONE'
}
}
}
const mutations = {
SET_WRITE_EXTERNAL_STORAGE(state, val) {
state.WRITE_EXTERNAL_STORAGE = val
},
SET_CALL_PHONE(state, val) {
state.CALL_PHONE = val
},
SET_READ_EXTERNAL_STORAGE(state, val) {
state.READ_EXTERNAL_STORAGE = val
}
}
const actions = {
//权限获取
async requestPermissions({
state,
dispatch,
commit
}, permissionID) {
try {
if (!state[permissionID]) {
var viewObj = await dispatch('nativeObjView', permissionID);
viewObj.show();
}
console.log('android.permission.' + permissionID, '当前手机权限');
return new Promise(async (resolve, reject) => {
// Android权限查询
function requestAndroidPermission(permissionID_) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[
permissionID_
], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
function (resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent
.length; i++) {
var deniedPresentPermission = resultObj
.deniedPresent[
i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways
.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[
i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve(result);
},
function (error) {
console.log('申请权限错误:' + error.code + " = " + error
.message);
resolve({
code: error.code,
message: error.message
});
}
);
});
}
const result = await requestAndroidPermission(
'android.permission.' + permissionID
);
if (result === 1) {
//'已获得授权'
commit(state.mapping[permissionID].methods, true)
} else if (result === 0) {
//'未获得授权'
commit(state.mapping[permissionID].methods, false)
} else {
commit(state.mapping[permissionID].methods, true)
uni.showModal({
title: '提示',
content: '操作权限已被拒绝,请手动前往设置',
confirmText: "立即设置",
success: (res) => {
if (res.confirm) {
dispatch('gotoAppPermissionSetting')
}
}
})
}
if (viewObj) viewObj.close()
resolve(result);
});
} catch (error) {
console.log(error);
reject(error);
}
},
//提示框
nativeObjView({
state
}, permissionID) {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight;
const navigationBarHeight = systemInfo.platform === 'android' ? 48 :
44; // Set the navigation bar height based on the platform
const totalHeight = statusBarHeight + navigationBarHeight;
let view = new plus.nativeObj.View('per-modal', {
top: '0px',
left: '0px',
width: '100%',
backgroundColor: '#444',
//opacity: '.9'
})
view.drawRect({
color: '#fff',
radius: '5px'
}, {
top: totalHeight + 'px',
left: '5%',
width: '90%',
height: "100px",
})
view.drawText(state.mapping[permissionID].title, {
top: totalHeight + 5 + 'px',
left: "8%",
height: "30px"
}, {
align: "left",
color: "#000",
}, {
onClick: function (e) {
console.log(e);
}
})
view.drawText(state.mapping[permissionID].content, {
top: totalHeight + 35 + 'px',
height: "60px",
left: "8%",
width: "84%"
}, {
whiteSpace: 'normal',
size: "14px",
align: "left",
color: "#656563"
})
function show() {
view = plus.nativeObj.View.getViewById('per-modal');
view.show()
view = null //这里要为null,不然隐藏不了,不知道为啥
}
function close() {
view = plus.nativeObj.View.getViewById('per-modal');
view.close();
view = null
}
return {
show,
close
}
},
// 跳转到**应用**的权限页面
gotoAppPermissionSetting({
state
}) {
if (state.isIos) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
// console.log(plus.device.vendor);
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);
}
}
}
export default {
namespaced: true,
state,
mutations,
actions
};
我是定义vuex里面的一个模块上面完整代码。
然后在原来的基础上加上这个判断就好啦。
<script>
export default {
data() {
return {
}
},
methods: {
async save() {
let result = await this.$store.dispatch("app/requestPermissions",
'WRITE_EXTERNAL_STORAGE')
if (result !== 1) return
uni.saveImageToPhotosAlbum({
filePath: "https://shop.baihuitimes.com/uploads/def/20230913/3fcaeccc6290e1b8367c00ea0aa600e4.png",
success: function(res) {
uni.showToast({
title: '保存成功',
icon:"success"
});
},
fail: function(res) {
uni.showToast({
title: '保存失败',
icon:"fail"
});
},
})
}
}
}
</script>
最后说明,如果用户没有点击授权的操作,将会无法及时拿到权限回调,可以自己在show里面再改改。上架审核人员前期点点点影响不到的。
华为app上架审核,申请权限的时候只有弹框,没有告知目的,被审核驳回了。。。
然后看官方,发现plus.android.requestPermissions只有点击才有回调啊,然后看到一个大佬代码设置了一个初始值,缝缝补补,整出来一个将就使用的完整代码。合不合适看最后压缩包视频就知道啦
//这是vuex单独一个模块的完整代码
const state = {
WRITE_EXTERNAL_STORAGE: false,
READ_EXTERNAL_STORAGE: false,
CALL_PHONE: false,
isIos: plus.os.name == "iOS",
mapping: {
'WRITE_EXTERNAL_STORAGE': {
title: "存储空间/照片权限说明",
content: "便于您使用该功能上传您的照片/图片/视频及用于更换头像、发布评论/分享、下载、与客服沟通等场景中读取和写入相册和文件内容。",
methods: 'SET_WRITE_EXTERNAL_STORAGE'
},
'READ_EXTERNAL_STORAGE': {
title: "存储空间/照片权限说明",
content: "便于您使用该功能上传您的照片/图片/视频及用于更换头像、发布评论/分享、下载、与客服沟通等场景中读取和写入相册和文件内容。",
methods: 'SET_READ_EXTERNAL_STORAGE'
},
'CALL_PHONE': {
title: "拨打/管理电话权限说明",
content: "便于您使用该功能联系商家或者商家与您联系等场景",
methods: 'SET_CALL_PHONE'
}
}
}
const mutations = {
SET_WRITE_EXTERNAL_STORAGE(state, val) {
state.WRITE_EXTERNAL_STORAGE = val
},
SET_CALL_PHONE(state, val) {
state.CALL_PHONE = val
},
SET_READ_EXTERNAL_STORAGE(state, val) {
state.READ_EXTERNAL_STORAGE = val
}
}
const actions = {
//权限获取
async requestPermissions({
state,
dispatch,
commit
}, permissionID) {
try {
if (!state[permissionID]) {
var viewObj = await dispatch('nativeObjView', permissionID);
viewObj.show();
}
console.log('android.permission.' + permissionID, '当前手机权限');
return new Promise(async (resolve, reject) => {
// Android权限查询
function requestAndroidPermission(permissionID_) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[
permissionID_
], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
function (resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent
.length; i++) {
var deniedPresentPermission = resultObj
.deniedPresent[
i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways
.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[
i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve(result);
},
function (error) {
console.log('申请权限错误:' + error.code + " = " + error
.message);
resolve({
code: error.code,
message: error.message
});
}
);
});
}
const result = await requestAndroidPermission(
'android.permission.' + permissionID
);
if (result === 1) {
//'已获得授权'
commit(state.mapping[permissionID].methods, true)
} else if (result === 0) {
//'未获得授权'
commit(state.mapping[permissionID].methods, false)
} else {
commit(state.mapping[permissionID].methods, true)
uni.showModal({
title: '提示',
content: '操作权限已被拒绝,请手动前往设置',
confirmText: "立即设置",
success: (res) => {
if (res.confirm) {
dispatch('gotoAppPermissionSetting')
}
}
})
}
if (viewObj) viewObj.close()
resolve(result);
});
} catch (error) {
console.log(error);
reject(error);
}
},
//提示框
nativeObjView({
state
}, permissionID) {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight;
const navigationBarHeight = systemInfo.platform === 'android' ? 48 :
44; // Set the navigation bar height based on the platform
const totalHeight = statusBarHeight + navigationBarHeight;
let view = new plus.nativeObj.View('per-modal', {
top: '0px',
left: '0px',
width: '100%',
backgroundColor: '#444',
//opacity: '.9'
})
view.drawRect({
color: '#fff',
radius: '5px'
}, {
top: totalHeight + 'px',
left: '5%',
width: '90%',
height: "100px",
})
view.drawText(state.mapping[permissionID].title, {
top: totalHeight + 5 + 'px',
left: "8%",
height: "30px"
}, {
align: "left",
color: "#000",
}, {
onClick: function (e) {
console.log(e);
}
})
view.drawText(state.mapping[permissionID].content, {
top: totalHeight + 35 + 'px',
height: "60px",
left: "8%",
width: "84%"
}, {
whiteSpace: 'normal',
size: "14px",
align: "left",
color: "#656563"
})
function show() {
view = plus.nativeObj.View.getViewById('per-modal');
view.show()
view = null //这里要为null,不然隐藏不了,不知道为啥
}
function close() {
view = plus.nativeObj.View.getViewById('per-modal');
view.close();
view = null
}
return {
show,
close
}
},
// 跳转到**应用**的权限页面
gotoAppPermissionSetting({
state
}) {
if (state.isIos) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
// console.log(plus.device.vendor);
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);
}
}
}
export default {
namespaced: true,
state,
mutations,
actions
};
我是定义vuex里面的一个模块上面完整代码。
然后在原来的基础上加上这个判断就好啦。
<script>
export default {
data() {
return {
}
},
methods: {
async save() {
let result = await this.$store.dispatch("app/requestPermissions",
'WRITE_EXTERNAL_STORAGE')
if (result !== 1) return
uni.saveImageToPhotosAlbum({
filePath: "https://shop.baihuitimes.com/uploads/def/20230913/3fcaeccc6290e1b8367c00ea0aa600e4.png",
success: function(res) {
uni.showToast({
title: '保存成功',
icon:"success"
});
},
fail: function(res) {
uni.showToast({
title: '保存失败',
icon:"fail"
});
},
})
}
}
}
</script>
最后说明,如果用户没有点击授权的操作,将会无法及时拿到权限回调,可以自己在show里面再改改。上架审核人员前期点点点影响不到的。
收起阅读 »
调试模式提示Uncaught SyntaxError: Unexpected token ?解决经验分享
全局搜索??运算符,使用||替代??运算符,或更改为其他方式进行运算
全局搜索??运算符,使用||替代??运算符,或更改为其他方式进行运算

uniapp 蓝牙无法监听多个特征的问题解决办法
问题描述:
在开发app时,项目需要蓝牙同时订阅某个服务下的多个特征值的变化时,直接使用uni.notifyBLECharacteristicValueChange只能订阅一个特征值的变化。
具体实现:
1.将 this.onBLECharacteristicValueChange();放到onLoad()中注册,确保该方法只注册一次,如果该方法注册了多次,蓝牙发送的数据会重复上传。
- 在蓝牙的订阅函数notifyBLECharacteristicValueChange()中设置定时器来轮流订阅蓝牙的多个特征值,具体如下:
-
notifyBLECharacteristicValueChange() { let _this = this; let deviceId = _this.mDevice.deviceId;//这里是蓝牙设备id let serviceId = SERVICE_UUID;//这里是蓝牙的服务UUID let reads = readUUID3;//这里是需要订阅的特征值UUID的数组,[特征值1,特征值2,特征值n] let num = numberReadUUID;//这里是需要订阅的特征值的个数 // let characteristicId = reads[0]; let countConnect = 0;//连接次数统计 let cID=0;//要订阅的特征值的编号 _this.notifyCycleTimer = setInterval(()=>{ let characteristicId = reads[cID];//要订阅的特征值 countConnect = countConnect+1; if(countConnect>10){//连接超过10次没有连接成功就直接返回 uni.hideLoading(); clearInterval(_this.notifyCycleTimer);//注册超时,清理掉定时器 closeBLEConnection();//注册超时就断开蓝牙的连接 closeBluetoothAdapter();//注册超时就关闭蓝牙适配器 uni.showModal({ title: '提示', content: '蓝牙连接失败,请重试!', showCancel:false, complete:function(){ //返回上一界面 uni.navigateBack({ delta:1 }); } }); } uni.notifyBLECharacteristicValueChange({ state: true, // 启用 notify 功能 deviceId, serviceId, characteristicId, success(res) { console.log('notifyBLECharacteristicValueChange success:' + characteristicId); cID=cID+1;//订阅成功就订阅下一个特征 if(cID==num){ uni.hideLoading(); clearInterval(_this.notifyCycleTimer);//注册监听成功就清理掉定时器 //返回上一界面 uni.navigateBack({ delta:1 }); } }, fail(e) { console.log("订阅失败"); } }); },500);//每隔500ms执行一次订阅 },
问题描述:
在开发app时,项目需要蓝牙同时订阅某个服务下的多个特征值的变化时,直接使用uni.notifyBLECharacteristicValueChange只能订阅一个特征值的变化。
具体实现:
1.将 this.onBLECharacteristicValueChange();放到onLoad()中注册,确保该方法只注册一次,如果该方法注册了多次,蓝牙发送的数据会重复上传。
- 在蓝牙的订阅函数notifyBLECharacteristicValueChange()中设置定时器来轮流订阅蓝牙的多个特征值,具体如下:
-
notifyBLECharacteristicValueChange() { let _this = this; let deviceId = _this.mDevice.deviceId;//这里是蓝牙设备id let serviceId = SERVICE_UUID;//这里是蓝牙的服务UUID let reads = readUUID3;//这里是需要订阅的特征值UUID的数组,[特征值1,特征值2,特征值n] let num = numberReadUUID;//这里是需要订阅的特征值的个数 // let characteristicId = reads[0]; let countConnect = 0;//连接次数统计 let cID=0;//要订阅的特征值的编号 _this.notifyCycleTimer = setInterval(()=>{ let characteristicId = reads[cID];//要订阅的特征值 countConnect = countConnect+1; if(countConnect>10){//连接超过10次没有连接成功就直接返回 uni.hideLoading(); clearInterval(_this.notifyCycleTimer);//注册超时,清理掉定时器 closeBLEConnection();//注册超时就断开蓝牙的连接 closeBluetoothAdapter();//注册超时就关闭蓝牙适配器 uni.showModal({ title: '提示', content: '蓝牙连接失败,请重试!', showCancel:false, complete:function(){ //返回上一界面 uni.navigateBack({ delta:1 }); } }); } uni.notifyBLECharacteristicValueChange({ state: true, // 启用 notify 功能 deviceId, serviceId, characteristicId, success(res) { console.log('notifyBLECharacteristicValueChange success:' + characteristicId); cID=cID+1;//订阅成功就订阅下一个特征 if(cID==num){ uni.hideLoading(); clearInterval(_this.notifyCycleTimer);//注册监听成功就清理掉定时器 //返回上一界面 uni.navigateBack({ delta:1 }); } }, fail(e) { console.log("订阅失败"); } }); },500);//每隔500ms执行一次订阅 },

globalEvent不起作用;引发globalEvent不起作用的原因之一
Android原生插件
现象:插件的方法能正常运行,就是发送了mUniSDKInstance.fireGlobalEventCallback,uni这边的globalEvent监听不到消息,也不报错。
在onLoad中,接收页面传递参数时未判空,离线打包无任何错误提示,打包插件uni也不会有错误提示,即
//引发不起作用的原因
onLoad(options){
if (JSON.parse(options.print)) {
console.log("TO DO")
}
globalEvent.addEventListener('myEvent', (e) => {
console.log("myEvent:", JSON.stringify(e))
});
}
正确方式;
对options进行判空
onLoad(options){
if(optins!=null){
if (JSON.parse(options.print)) {
console.log("TO DO")
}
globalEvent.addEventListener('myEvent', (e) => {
console.log("myEvent:", JSON.stringify(e))
});
}
}
Android原生插件
现象:插件的方法能正常运行,就是发送了mUniSDKInstance.fireGlobalEventCallback,uni这边的globalEvent监听不到消息,也不报错。
在onLoad中,接收页面传递参数时未判空,离线打包无任何错误提示,打包插件uni也不会有错误提示,即
//引发不起作用的原因
onLoad(options){
if (JSON.parse(options.print)) {
console.log("TO DO")
}
globalEvent.addEventListener('myEvent', (e) => {
console.log("myEvent:", JSON.stringify(e))
});
}
正确方式;
对options进行判空
onLoad(options){
if(optins!=null){
if (JSON.parse(options.print)) {
console.log("TO DO")
}
globalEvent.addEventListener('myEvent', (e) => {
console.log("myEvent:", JSON.stringify(e))
});
}
}
收起阅读 »

Wot Design Uni 增加 Table 表格组件 ,赶快进来看看效果吧!
Table 表格
用于展示多条结构类似的数据, 可对数据进行排序等操作。
地址
先看交互效果
基础用法
通过data
设置表格数据。
<wd-table :data="dataList">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
const dataList = reactive([
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业'
}
])
固定列
通过fixed
设置表格列是否固定展示,默认false
。
::warning 提示
目前仅支持固定在左侧,且固定列组件的排列顺序要和实际想要展示的顺序相同。
::
<wd-table :data="dataList">
<wd-table-col prop="name" label="姓名" fixed></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
斑马纹
通过stripe
设置表格是否展示斑马纹,默认true
。
<wd-table :data="dataList" :stripe="false">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
边框
通过border
设置表格是否展示边框,默认true
。
<wd-table :data="dataList" :border="false">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
表格高度
通过height
设置表格高度,默认为80vh
。
<wd-table :data="dataList" height="328px">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
排序事件
当存在列参与排序时,点击会触发sort-method
排序事件。
<wd-table :data="dataList" @sort-method="handleSort">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所" :sortable="true"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
function handleSort(e) {
console.log('这里是排序事件')
}
自定义列模板
自定义列的显示内容,可组合其他组件使用。
通过 Scoped slot
可以获取到 row
的数据,用法参考 demo。
<wd-table :data="dataList" @sort-method="handleSort">
<wd-table-col prop="name" label="姓名" fixed="true" width="320rpx" :sortable="true"></wd-table-col>
<wd-table-col prop="grade" label="分数" width="220rpx" :sortable="true">
<template #value="{row}">
<view class="custom-class">
<text>{{ row.grade }}</text>
<text>同比{{ row.compare }}</text>
</view>
</template>
</wd-table-col>
<wd-table-col prop="hobby" label="一言以蔽之" :sortable="true"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
<wd-table-col prop="gender" label="性别"></wd-table-col>
<wd-table-col prop="graduation" label="学成时间"></wd-table-col>
</wd-table>
import { ref } from 'vue'
const dataList = ref<Record<string, any>[]>([
{
name: '张飞',
school: '武汉市阳逻杀猪学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 56,
compare: '10%',
hobby: '燕人张飞在此!'
},
{
name: '关羽',
school: '武汉市阳逻绿豆学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 66,
compare: '11%',
hobby: '颜良文丑,以吾观之,如土鸡瓦犬耳。'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 45,
compare: '1%',
hobby: '我得空明,如鱼得水也'
},
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 69,
compare: '14%',
hobby: '子龙,子龙,世无双'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 88,
compare: '21%',
hobby: '兴汉讨贼,克复中原'
},
{
name: '姜维',
school: '武汉市阳逻停水停电技术学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 87,
compare: '32%',
hobby: '我计不成,乃天命也!'
}
])
/**
* 排序
* @param e
*/
function handleSort(e) {
dataList.value = dataList.value.reverse()
}
.custom-class {
height: 80rpx;
width: 220rpx;
display: flex;
flex-direction: col;
align-items: center;
}
Attributes
参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
---|---|---|---|---|---|
data | 显示的数据 | Array | - | - | 0.0.39 |
border | 是否带有边框 | boolean | - | true | 0.0.39 |
stripe | 是否为斑马纹表 | boolean | - | true | 0.0.39 |
height | Table 的高度,默认为80vh |
string | - | 80vh |
0.0.39 |
rowHeight | 行高 | number / string |
- | 50 | 0.0.39 |
showHeader | 是否显示表头 | boolean | - | true | 0.0.39 |
ellipsis | 是否超出2行隐藏 | boolean | - | true | 0.0.39 |
TableColumn Attributes
参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
---|---|---|---|---|---|
prop | 字段名称,对应列内容的字段名 | string | - | - | 0.0.39 |
label | 显示的标题 | string | - | - | 0.0.39 |
width | 对应列的宽度,单位为px | number / string | - | 100 | 0.0.39 |
sortable | 是否开启列排序 | boolean | - | false | 0.0.39 |
fixed | 是否固定本列 | boolean | - | false | 0.0.39 |
align | 列的对齐方式 | AlignType | left, center, right | left | 0.0.39 |
地址
Table 表格
用于展示多条结构类似的数据, 可对数据进行排序等操作。
地址
先看交互效果
基础用法
通过data
设置表格数据。
<wd-table :data="dataList">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
const dataList = reactive([
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业'
}
])
固定列
通过fixed
设置表格列是否固定展示,默认false
。
::warning 提示
目前仅支持固定在左侧,且固定列组件的排列顺序要和实际想要展示的顺序相同。
::
<wd-table :data="dataList">
<wd-table-col prop="name" label="姓名" fixed></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
斑马纹
通过stripe
设置表格是否展示斑马纹,默认true
。
<wd-table :data="dataList" :stripe="false">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
边框
通过border
设置表格是否展示边框,默认true
。
<wd-table :data="dataList" :border="false">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
表格高度
通过height
设置表格高度,默认为80vh
。
<wd-table :data="dataList" height="328px">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
排序事件
当存在列参与排序时,点击会触发sort-method
排序事件。
<wd-table :data="dataList" @sort-method="handleSort">
<wd-table-col prop="name" label="姓名"></wd-table-col>
<wd-table-col prop="school" label="求学之所" :sortable="true"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
</wd-table>
function handleSort(e) {
console.log('这里是排序事件')
}
自定义列模板
自定义列的显示内容,可组合其他组件使用。
通过 Scoped slot
可以获取到 row
的数据,用法参考 demo。
<wd-table :data="dataList" @sort-method="handleSort">
<wd-table-col prop="name" label="姓名" fixed="true" width="320rpx" :sortable="true"></wd-table-col>
<wd-table-col prop="grade" label="分数" width="220rpx" :sortable="true">
<template #value="{row}">
<view class="custom-class">
<text>{{ row.grade }}</text>
<text>同比{{ row.compare }}</text>
</view>
</template>
</wd-table-col>
<wd-table-col prop="hobby" label="一言以蔽之" :sortable="true"></wd-table-col>
<wd-table-col prop="school" label="求学之所"></wd-table-col>
<wd-table-col prop="major" label="专业"></wd-table-col>
<wd-table-col prop="gender" label="性别"></wd-table-col>
<wd-table-col prop="graduation" label="学成时间"></wd-table-col>
</wd-table>
import { ref } from 'vue'
const dataList = ref<Record<string, any>[]>([
{
name: '张飞',
school: '武汉市阳逻杀猪学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 56,
compare: '10%',
hobby: '燕人张飞在此!'
},
{
name: '关羽',
school: '武汉市阳逻绿豆学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 66,
compare: '11%',
hobby: '颜良文丑,以吾观之,如土鸡瓦犬耳。'
},
{
name: '刘备',
school: '武汉市阳逻编织学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 45,
compare: '1%',
hobby: '我得空明,如鱼得水也'
},
{
name: '赵云',
school: '武汉市阳逻妇幼保健学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 69,
compare: '14%',
hobby: '子龙,子龙,世无双'
},
{
name: '孔明',
school: '武汉市阳逻卧龙学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 88,
compare: '21%',
hobby: '兴汉讨贼,克复中原'
},
{
name: '姜维',
school: '武汉市阳逻停水停电技术学院',
major: '计算机科学与技术专业',
gender: '男',
graduation: '2022年1月12日',
grade: 87,
compare: '32%',
hobby: '我计不成,乃天命也!'
}
])
/**
* 排序
* @param e
*/
function handleSort(e) {
dataList.value = dataList.value.reverse()
}
.custom-class {
height: 80rpx;
width: 220rpx;
display: flex;
flex-direction: col;
align-items: center;
}
Attributes
参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
---|---|---|---|---|---|
data | 显示的数据 | Array | - | - | 0.0.39 |
border | 是否带有边框 | boolean | - | true | 0.0.39 |
stripe | 是否为斑马纹表 | boolean | - | true | 0.0.39 |
height | Table 的高度,默认为80vh |
string | - | 80vh |
0.0.39 |
rowHeight | 行高 | number / string |
- | 50 | 0.0.39 |
showHeader | 是否显示表头 | boolean | - | true | 0.0.39 |
ellipsis | 是否超出2行隐藏 | boolean | - | true | 0.0.39 |
TableColumn Attributes
参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
---|---|---|---|---|---|
prop | 字段名称,对应列内容的字段名 | string | - | - | 0.0.39 |
label | 显示的标题 | string | - | - | 0.0.39 |
width | 对应列的宽度,单位为px | number / string | - | 100 | 0.0.39 |
sortable | 是否开启列排序 | boolean | - | false | 0.0.39 |
fixed | 是否固定本列 | boolean | - | false | 0.0.39 |
align | 列的对齐方式 | AlignType | left, center, right | left | 0.0.39 |
地址
收起阅读 »
uniapp对微信小程序异步加载分包组件的做法--vite插件方案
微信原生文档见 文档
社区有方案,是在style中写入
这里的方案是使用vite插件,在编译完成后,对目标插件进行代码注入 (仅适用vue3 + vite) , 目前运行在uniapp 微信小程序上
目录结构:
/project-config
componentPlaceholder
componentPlaceholder.js
vite.uni-component-placeholder.js
vite插件代码
/**
实现了 uni编译完成后 , 处理 componentPlaceholder
*/
var fs = require('fs');
var path = require('path');
class ProcessComponentPlaceholder{
constructor(){
this.destFolder = process.env.UNI_OUTPUT_DIR;
}
process(){
let dev_fold = process.env.UNI_INPUT_DIR;
let dir = dev_fold+"/project-config/componentPlaceHolder/"
let files = fs.readdirSync(dir, 'utf-8');
let fileMap = {}
files.some((f) => {
let p = dir + "/" + f;
let stat = fs.lstatSync(p);
if (!stat.isDirectory()) {
fileMap[f] = p;
}
})
this.processNodes(fileMap)
}
processNodes(fileMap){
console.log("处理异步组件引用componentPlaceHolder",fileMap)
for(let jsonFileName in fileMap){
let path = fileMap[jsonFileName];
if(!path.lastIndexOf(".js")<0)continue;
let obj = require(path);
// console.log("读componentPlaceHolder", obj)
this.processOneConfig(obj);
}
console.table("componentPlaceHolder处理完毕")
}
processOneConfig(config){
for(let f in config){ //某个配置文件
let weixinJSONFile = this.destFolder+f+".json";
fs.readFile(weixinJSONFile,'utf8',(err, data)=>{
if (err) {
return console.log('componentPlaceHolder文件读取失败,失败原因是:' + err)
}
let weixinJSON = JSON.parse(data);
// console.log("读componentPlaceHolder",weixinJSON);
//准备合并配置
let usingComponents = weixinJSON["usingComponents"]||{};
let componentPlaceholder = weixinJSON["componentPlaceholder"]||{};
let customConfig = config[f];
for(let tag in customConfig) {
let tagVal = customConfig[tag];
let path = tagVal.path;
let replace = tagVal.replace;
// console.log(weixinJSONFile+ " " +tag+" "+path )
if(!usingComponents[tag]){
usingComponents[tag]="../.."+path; //这里的双层目录有必要可能动态算相对层级,根据项目自身情况而定
}
if(!componentPlaceholder[tag]){
componentPlaceholder[tag] = replace;
}
}
weixinJSON.usingComponents = usingComponents;
weixinJSON.componentPlaceholder = componentPlaceholder;
fs.writeFileSync(weixinJSONFile, JSON.stringify(weixinJSON,null,4))
}) ;
}
}
}
export default (options)=> {
var name = 'vite-plugin-copy-uniapp_config';
return {
name: name,
enforce: 'post',
closeBundle:()=>{ //buildEnd之后运行
options.forEach(function(option) {
let processor = new ProcessComponentPlaceholder();
processor.process();
});
}
};
}
配置文件 componentPlaceholder.js 代码:
/**
配置:
{
"某个包的组件路径,不带.vue后缀":{
"组件名,一般为文件名不带.vue和路径":{
path:"引用某个包的组件路径,不带.vue后缀",
replace:"未加载完成时的替换组件,比如view或某个全局组件"
}
}
}
*/
module.exports = {
"/pages/index/index":{
"tabbar-me":{
path:"/package-my/pages/my/my",
replace:"view"
},
},
}
vite.config.js
import viteComponentPlaceHolder from "./project-config/vite.uni-component-placeholder.js"
plugins.push(viteComponentPlaceHolder([{}]))
export default defineConfig({
plugins
});
启动时会有日志
08:48:43.602 处理异步组件引用componentPlaceHolder {
08:48:43.609 'componentPlaceholder.js': '/project-config/componentPlaceHolder//componentPlaceholder.js'
08:48:43.610 }
微信原生文档见 文档
社区有方案,是在style中写入
这里的方案是使用vite插件,在编译完成后,对目标插件进行代码注入 (仅适用vue3 + vite) , 目前运行在uniapp 微信小程序上
目录结构:
/project-config
componentPlaceholder
componentPlaceholder.js
vite.uni-component-placeholder.js
vite插件代码
/**
实现了 uni编译完成后 , 处理 componentPlaceholder
*/
var fs = require('fs');
var path = require('path');
class ProcessComponentPlaceholder{
constructor(){
this.destFolder = process.env.UNI_OUTPUT_DIR;
}
process(){
let dev_fold = process.env.UNI_INPUT_DIR;
let dir = dev_fold+"/project-config/componentPlaceHolder/"
let files = fs.readdirSync(dir, 'utf-8');
let fileMap = {}
files.some((f) => {
let p = dir + "/" + f;
let stat = fs.lstatSync(p);
if (!stat.isDirectory()) {
fileMap[f] = p;
}
})
this.processNodes(fileMap)
}
processNodes(fileMap){
console.log("处理异步组件引用componentPlaceHolder",fileMap)
for(let jsonFileName in fileMap){
let path = fileMap[jsonFileName];
if(!path.lastIndexOf(".js")<0)continue;
let obj = require(path);
// console.log("读componentPlaceHolder", obj)
this.processOneConfig(obj);
}
console.table("componentPlaceHolder处理完毕")
}
processOneConfig(config){
for(let f in config){ //某个配置文件
let weixinJSONFile = this.destFolder+f+".json";
fs.readFile(weixinJSONFile,'utf8',(err, data)=>{
if (err) {
return console.log('componentPlaceHolder文件读取失败,失败原因是:' + err)
}
let weixinJSON = JSON.parse(data);
// console.log("读componentPlaceHolder",weixinJSON);
//准备合并配置
let usingComponents = weixinJSON["usingComponents"]||{};
let componentPlaceholder = weixinJSON["componentPlaceholder"]||{};
let customConfig = config[f];
for(let tag in customConfig) {
let tagVal = customConfig[tag];
let path = tagVal.path;
let replace = tagVal.replace;
// console.log(weixinJSONFile+ " " +tag+" "+path )
if(!usingComponents[tag]){
usingComponents[tag]="../.."+path; //这里的双层目录有必要可能动态算相对层级,根据项目自身情况而定
}
if(!componentPlaceholder[tag]){
componentPlaceholder[tag] = replace;
}
}
weixinJSON.usingComponents = usingComponents;
weixinJSON.componentPlaceholder = componentPlaceholder;
fs.writeFileSync(weixinJSONFile, JSON.stringify(weixinJSON,null,4))
}) ;
}
}
}
export default (options)=> {
var name = 'vite-plugin-copy-uniapp_config';
return {
name: name,
enforce: 'post',
closeBundle:()=>{ //buildEnd之后运行
options.forEach(function(option) {
let processor = new ProcessComponentPlaceholder();
processor.process();
});
}
};
}
配置文件 componentPlaceholder.js 代码:
/**
配置:
{
"某个包的组件路径,不带.vue后缀":{
"组件名,一般为文件名不带.vue和路径":{
path:"引用某个包的组件路径,不带.vue后缀",
replace:"未加载完成时的替换组件,比如view或某个全局组件"
}
}
}
*/
module.exports = {
"/pages/index/index":{
"tabbar-me":{
path:"/package-my/pages/my/my",
replace:"view"
},
},
}
vite.config.js
import viteComponentPlaceHolder from "./project-config/vite.uni-component-placeholder.js"
plugins.push(viteComponentPlaceHolder([{}]))
export default defineConfig({
plugins
});
启动时会有日志
08:48:43.602 处理异步组件引用componentPlaceHolder {
08:48:43.609 'componentPlaceholder.js': '/project-config/componentPlaceHolder//componentPlaceholder.js'
08:48:43.610 }