
个推解读Android 12首个开发者预览版
引言
近日,Google发布了新系统 Android 12 的首个开发者预览版。根据谷歌官方消息,最终版本的Android 12预计于今年下半年正式上线。此次发布预览版的目的主要是帮助开发者提前了解Android 12的新变化,为后续进行应用适配提前做准备。

作为陪伴开发者多年的重要伙伴,个推一直密切关注和跟进行业发展趋势。在Android12首个开发者预览版发布后,个推快速对新系统的有关更新展开了调研。本文对Android 12预览版的部分新功能、新特性进行了解读,希望能帮助广大开发者对新系统有个快速了解。
Android 12行为变更:面向所有应用
用户体验升级
沉浸式手势导航改进
从Android 10 开始,Android 系统就已支持手势导航,致力于给用户带来沉浸式的全新体验。在之前版本的系统下,用户启动安卓的沉浸模式,默认需要的手势操作是:从屏幕侧方滑入,退出沉浸模式,然后再返回上一个界面。最新发布的Android 12首个开发者预览版则对手势导航模式进行了简化:用户不再需要滑动两次,只需一次滑动手势,即可退出全屏沉浸模式并返回上个界面,更加高效和便利。
隐私/安全保护增强
MAC 地址权限限制
为进一步保护用户隐私和数据安全,Android 11引入了“单次授权”“权限自动重置”“分区存储”等功能。Android 12则更进一步限制了所有非系统应用程序对设备MAC地址的访问,无论目标API级别如何。
相关API返回空值或占位符值,具体取决于应用程序的目标SDK版本:
➀ target = 12 ,返回 null
➁ target <12 ,返回 02:00:00:00:00:00
未被信任的触摸事件将被阻止
为了保护系统安全和更好的用户体验,Android 12将会阻止某些窗口的触摸。
例如:
➀ 申请了 SYSTEM_ALERT_WINDOW 权限的 windows
➁ Toast messages
应用不能关闭系统对话框
ACTION_CLOSE_SYSTEM_DIALOGS intent 在 Android 12 被废弃。
➀ target = 12, 将抛出 SecurityException 异常
➁ target < 12 ,该 intent 不会执行,会在 logcat 打印警告日志:
E ActivityTaskManager Permission Denial:
android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from
com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS,
dropping broadcast.
Android 12行为变更:针对Target = 12的应用
自定义通知栏
之前,开发者能自定义整个通知栏区域的布局和样式,这就导致了不同设备的兼容适配问题以及用户的浏览不适应问题。
Android 12更改了完全自定义通知的外观。对于 target = 12 的应用,在通知栏的消息展示均使用统一的模板。上面应用名字显示和折叠按钮都是相同的、固定的,下面折叠和展开状态呈现的区域是可自定义的:
折叠和展开的样式:
折叠状态
展开状态
若APP中存在自定义Notification.Style,亦或是使用了Notification.Builder中 setCustomContentView(RemoteViews), setCustomBigContentView(RemoteViews)和setCustomHeadsUpContentView(RemoteViews)方法,可能会受此影响。
隐私/安全
WebView 中的SameSite cookie行为
Android的WebView组件基于Chromium来提高安全性和隐私性,去年,Chromium对第三方Cookie的处理方式进行了更改,并已面向众多Chrome用户推出。从Android 12开始,这些更改将应用于WebView。
SameSitecookie的属性控制它是否可以与任何请求一起发送,还是只能与相同站点的请求一起发送。Android 12中的WebView基本版本(版本89.0.4385.0)改进了第三方Cookie的默认处理,将有助于防止意外的跨站点共享。
ADB backup 限制
Android 12 限制了 adb backup 命令行的默认行为 (该命令行是用来备份恢复数据的),对应用程序数据adb backup有依赖的开发者可以在清单文件中设置 android:debuggable 为 true。
组件需要添加 exported 配置
target=12时,使用的activity 、service或者广播有用到 intent filters ,则需声明 android:exported 属性。不配置的话,在安卓 12 设备上将不能安装,logcat 也会打印错误日志:
Targeting S+ (version 10000 and above) requires that an explicit value for
android:exported be defined when intent filters are present
Pending intents 必须声明意图
使用 PendingIntent 需要声明 PendingIntent.FLAG_MUTABLE 或者 PendingIntent.FLAG_IMMUTABLE flag,否则系统会抛出异常 IllegalArgumentException。
性能
前台服务启动限制
以 Android 12 为目标的应用程序,无法在后台运行时启动前台服务,应用程序在后台运行时,可考虑使用 WorkManager 执行任务。
ForegroundService通知延迟
前台服务启动后必须调用startForeground() 来显示前台通知,如果应用在5s内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。
在Android 12中,限制时间由5s改为了10s。这样一来,对于部分APP来讲,将会有更充分的处理时间。
通知跳转
services 或者 broadcast receivers 中创建的通知将不能调用 startActivity() !!!
logcat 会打印:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \this should be avoided for performance reasons.
总结
以上,是个推对Android 12首个开发者预览版本中几个重要更新点的解读。
除了以上内容外,Android 12 预览版还在视频、音频和图片处理方面做了很多有趣的更新。比如,通过手机的振动马达增加对触觉耦合音频效果的支持,可以帮助游戏类APP提升玩家体验;引入了兼容媒体转码功能,可以让那些不支持 HEVC的应用,也能将文件高效转码为 AVC 格式;同时还引入了对 AV1 图像文件格式(AVIF)的支持,使得开发者可以同样的文件大小,收获比 JPEG 图像更高的图像质量……感兴趣的开发者可以进入Android 12官网进一步详细了解。
https://developer.android.google.cn/about/versions/12
后续,个推还将在持续打磨开发者服务和SDK产品的同时,密切跟进移动开发领域的相关动态,为开发者升级产品功能、迭代服务体验提供有效建议。
也欢迎更多的开发者和我们一起交流和探索Android及移动开发新技术,共同建设更好的安卓开发生态。
*本文图片来源于Android官网
引言
近日,Google发布了新系统 Android 12 的首个开发者预览版。根据谷歌官方消息,最终版本的Android 12预计于今年下半年正式上线。此次发布预览版的目的主要是帮助开发者提前了解Android 12的新变化,为后续进行应用适配提前做准备。
作为陪伴开发者多年的重要伙伴,个推一直密切关注和跟进行业发展趋势。在Android12首个开发者预览版发布后,个推快速对新系统的有关更新展开了调研。本文对Android 12预览版的部分新功能、新特性进行了解读,希望能帮助广大开发者对新系统有个快速了解。
Android 12行为变更:面向所有应用
用户体验升级
沉浸式手势导航改进
从Android 10 开始,Android 系统就已支持手势导航,致力于给用户带来沉浸式的全新体验。在之前版本的系统下,用户启动安卓的沉浸模式,默认需要的手势操作是:从屏幕侧方滑入,退出沉浸模式,然后再返回上一个界面。最新发布的Android 12首个开发者预览版则对手势导航模式进行了简化:用户不再需要滑动两次,只需一次滑动手势,即可退出全屏沉浸模式并返回上个界面,更加高效和便利。
隐私/安全保护增强
MAC 地址权限限制
为进一步保护用户隐私和数据安全,Android 11引入了“单次授权”“权限自动重置”“分区存储”等功能。Android 12则更进一步限制了所有非系统应用程序对设备MAC地址的访问,无论目标API级别如何。
相关API返回空值或占位符值,具体取决于应用程序的目标SDK版本:
➀ target = 12 ,返回 null
➁ target <12 ,返回 02:00:00:00:00:00
未被信任的触摸事件将被阻止
为了保护系统安全和更好的用户体验,Android 12将会阻止某些窗口的触摸。
例如:
➀ 申请了 SYSTEM_ALERT_WINDOW 权限的 windows
➁ Toast messages
应用不能关闭系统对话框
ACTION_CLOSE_SYSTEM_DIALOGS intent 在 Android 12 被废弃。
➀ target = 12, 将抛出 SecurityException 异常
➁ target < 12 ,该 intent 不会执行,会在 logcat 打印警告日志:
E ActivityTaskManager Permission Denial:
android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from
com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS,
dropping broadcast.
Android 12行为变更:针对Target = 12的应用
自定义通知栏
之前,开发者能自定义整个通知栏区域的布局和样式,这就导致了不同设备的兼容适配问题以及用户的浏览不适应问题。
Android 12更改了完全自定义通知的外观。对于 target = 12 的应用,在通知栏的消息展示均使用统一的模板。上面应用名字显示和折叠按钮都是相同的、固定的,下面折叠和展开状态呈现的区域是可自定义的:
折叠和展开的样式:
折叠状态
展开状态
若APP中存在自定义Notification.Style,亦或是使用了Notification.Builder中 setCustomContentView(RemoteViews), setCustomBigContentView(RemoteViews)和setCustomHeadsUpContentView(RemoteViews)方法,可能会受此影响。
隐私/安全
WebView 中的SameSite cookie行为
Android的WebView组件基于Chromium来提高安全性和隐私性,去年,Chromium对第三方Cookie的处理方式进行了更改,并已面向众多Chrome用户推出。从Android 12开始,这些更改将应用于WebView。
SameSitecookie的属性控制它是否可以与任何请求一起发送,还是只能与相同站点的请求一起发送。Android 12中的WebView基本版本(版本89.0.4385.0)改进了第三方Cookie的默认处理,将有助于防止意外的跨站点共享。
ADB backup 限制
Android 12 限制了 adb backup 命令行的默认行为 (该命令行是用来备份恢复数据的),对应用程序数据adb backup有依赖的开发者可以在清单文件中设置 android:debuggable 为 true。
组件需要添加 exported 配置
target=12时,使用的activity 、service或者广播有用到 intent filters ,则需声明 android:exported 属性。不配置的话,在安卓 12 设备上将不能安装,logcat 也会打印错误日志:
Targeting S+ (version 10000 and above) requires that an explicit value for
android:exported be defined when intent filters are present
Pending intents 必须声明意图
使用 PendingIntent 需要声明 PendingIntent.FLAG_MUTABLE 或者 PendingIntent.FLAG_IMMUTABLE flag,否则系统会抛出异常 IllegalArgumentException。
性能
前台服务启动限制
以 Android 12 为目标的应用程序,无法在后台运行时启动前台服务,应用程序在后台运行时,可考虑使用 WorkManager 执行任务。
ForegroundService通知延迟
前台服务启动后必须调用startForeground() 来显示前台通知,如果应用在5s内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。
在Android 12中,限制时间由5s改为了10s。这样一来,对于部分APP来讲,将会有更充分的处理时间。
通知跳转
services 或者 broadcast receivers 中创建的通知将不能调用 startActivity() !!!
logcat 会打印:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \this should be avoided for performance reasons.
总结
以上,是个推对Android 12首个开发者预览版本中几个重要更新点的解读。
除了以上内容外,Android 12 预览版还在视频、音频和图片处理方面做了很多有趣的更新。比如,通过手机的振动马达增加对触觉耦合音频效果的支持,可以帮助游戏类APP提升玩家体验;引入了兼容媒体转码功能,可以让那些不支持 HEVC的应用,也能将文件高效转码为 AVC 格式;同时还引入了对 AV1 图像文件格式(AVIF)的支持,使得开发者可以同样的文件大小,收获比 JPEG 图像更高的图像质量……感兴趣的开发者可以进入Android 12官网进一步详细了解。
https://developer.android.google.cn/about/versions/12
后续,个推还将在持续打磨开发者服务和SDK产品的同时,密切跟进移动开发领域的相关动态,为开发者升级产品功能、迭代服务体验提供有效建议。
也欢迎更多的开发者和我们一起交流和探索Android及移动开发新技术,共同建设更好的安卓开发生态。
*本文图片来源于Android官网
收起阅读 »
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
因为升级v3 之前,web上是通过 .uni-checkbox-input 来修改的复选框样式,app是通过 .wx-checkbox-input 来修改的复选框样式
升级之后.wx-checkbox-input就失效了,统一使用.uni-checkbox-input了
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
因为升级v3 之前,web上是通过 .uni-checkbox-input 来修改的复选框样式,app是通过 .wx-checkbox-input 来修改的复选框样式
升级之后.wx-checkbox-input就失效了,统一使用.uni-checkbox-input了
收起阅读 »
mui ajax ios wkwebview请求的问题
之前项目jquery+mui写的,脏乱差,现在进行改版,采用vue+mui,去掉沉重的html拼接,开始上手顺的一塌糊涂,后面打包发布真机,android的一切照旧,ios的ajax访问不到数据.心凉了一截,后面是所有代码放在mui.plusready里面,但是html里面却又不能实现vue的渲染,愁啊;调试都不方便;
后面又是百度又是啥的终于琢磨出一个办法,也不知道对大家有没有用,我这里在下面贴出来;
如下代码:
//业务页面js
mui.init();
mui.plusReady(function() {
my_packer_info.initPackageList(); //要写,否者会出现第一次打开页面没数据,因为当时plus还没被初始化,这里只要调一下vue里面的初始化数据的方法即可
});
var my_packer_info = new Vue({
el: "#my_packer_info",
data:{},
methods: {
//初始化包裹列表
initPackageList: function() {
//;
var req_data = {
PlatForm: this.PlatForm,
Shop: this.ClientInfo.ClientInfo.Shop,
Page: 1,
PageCount: 100,
}
let that = this;
//自己在mui.ajax外面封装了一层的方法,同时也要改一下mui.js里面的ajax模块的xhr的创建
AxiosPost('GetBillCode', req_data, (res) => {
if (res.State) {
....
} else {
mui.toast("系統繁忙,請稍後重試:" + res.MsgText);
}
}, (err) => {
mui.toast("系統繁忙,請稍後重試:" + err);
});
}
},
created: function() {
this.initPackageList();
}
});
//mui.js里面 $.ajax模块的修改,不是很完善,大家可以自行加点判断,我这里是从ajax下面有WKWebView提醒哪里直接拷贝过来用的
xhr: function(protocol) {
if(location.protocol === 'file:' && $.os.ios && window.webkit && window.webkit.messageHandlers && !(xhr instanceof plus.net.XMLHttpRequest)){
return plus.net.XMLHttpRequest;
}
else{
return new window.XMLHttpRequest();
}
}
之前项目jquery+mui写的,脏乱差,现在进行改版,采用vue+mui,去掉沉重的html拼接,开始上手顺的一塌糊涂,后面打包发布真机,android的一切照旧,ios的ajax访问不到数据.心凉了一截,后面是所有代码放在mui.plusready里面,但是html里面却又不能实现vue的渲染,愁啊;调试都不方便;
后面又是百度又是啥的终于琢磨出一个办法,也不知道对大家有没有用,我这里在下面贴出来;
如下代码:
//业务页面js
mui.init();
mui.plusReady(function() {
my_packer_info.initPackageList(); //要写,否者会出现第一次打开页面没数据,因为当时plus还没被初始化,这里只要调一下vue里面的初始化数据的方法即可
});
var my_packer_info = new Vue({
el: "#my_packer_info",
data:{},
methods: {
//初始化包裹列表
initPackageList: function() {
//;
var req_data = {
PlatForm: this.PlatForm,
Shop: this.ClientInfo.ClientInfo.Shop,
Page: 1,
PageCount: 100,
}
let that = this;
//自己在mui.ajax外面封装了一层的方法,同时也要改一下mui.js里面的ajax模块的xhr的创建
AxiosPost('GetBillCode', req_data, (res) => {
if (res.State) {
....
} else {
mui.toast("系統繁忙,請稍後重試:" + res.MsgText);
}
}, (err) => {
mui.toast("系統繁忙,請稍後重試:" + err);
});
}
},
created: function() {
this.initPackageList();
}
});
//mui.js里面 $.ajax模块的修改,不是很完善,大家可以自行加点判断,我这里是从ajax下面有WKWebView提醒哪里直接拷贝过来用的
xhr: function(protocol) {
if(location.protocol === 'file:' && $.os.ios && window.webkit && window.webkit.messageHandlers && !(xhr instanceof plus.net.XMLHttpRequest)){
return plus.net.XMLHttpRequest;
}
else{
return new window.XMLHttpRequest();
}
}
收起阅读 »

关于前端弹窗无法覆盖原生导航栏及tabbar的一个解决方案
直接简单的先说一下我的需求, 首页加载的时候有弹窗,可能会多个,但是需求不能一次性全部弹出需要队列弹出,就是关闭一个之后再弹出下一个。这边使用到了vuex来存储全局弹窗队列 最下方附带有效果视频
这边是再App.vue中监听一下vuex > state中的弹窗队列
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length >= 1 && oldVal.length == 0) { //该条件存在则证明是第一个弹窗,需要先跳转页面
this.myRouter({ //这边是跳转一个新页面
type: "navigateTo",
url: "/pages/public/alert/alert",
animationType:"fade-in",
animationDuration:300
})
}
}
}
///pages/public/alert/alert跳转的新页面中的page.json一定要这样设置,这样这个页面就把下面的页面给覆盖住包括tabbar与到导航栏,并且这个页面是透明的你可以再页面的style中自己给背景透明色
"style": {
"navigationStyle": "custom",
"app-plus": {
"animationType": "fade-in",
"animationDuration": "200",
"bounce": "none",
"backgroundColor": "rgba(0,0,0,0)" // 背景透明
}
}
在这个新页面中监听(也是这个队列)
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length == 0) { //如果是最后一个弹窗的情况下就返回也就是关闭这个弹窗容器
uni.navigateBack({})
}
}
},
这个就是你要显示的弹窗因为考虑到后期弹窗多的话我就做成了组件形式
说明:ALERT_LIST[0]就是拿这个队列中的第一项 name:就是你在往队列中添加新弹窗的时候定义的要与下面代码中的v-if后面对应的值一致这样在队列往前走的时候他就能显示对应的弹窗
<!-- 隐私的弹出 -->
<protocol-alert @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'protoco'"></protocol-alert>
<!-- //版本更新的弹出 -->
<versionup-date @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'version'" :versionInfo="ALERT_LIST[0].data"></versionup-date>
<!-- //淘宝客猜你想找的弹出 -->
<copy-search @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'copysearch'"></copy-search>
注:在弹窗中如果有按钮出发了关闭弹窗要将队列中的这一项删除掉 (代码只是提供了一个大概的思路具体写法看你自己)
直接简单的先说一下我的需求, 首页加载的时候有弹窗,可能会多个,但是需求不能一次性全部弹出需要队列弹出,就是关闭一个之后再弹出下一个。这边使用到了vuex来存储全局弹窗队列 最下方附带有效果视频
这边是再App.vue中监听一下vuex > state中的弹窗队列
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length >= 1 && oldVal.length == 0) { //该条件存在则证明是第一个弹窗,需要先跳转页面
this.myRouter({ //这边是跳转一个新页面
type: "navigateTo",
url: "/pages/public/alert/alert",
animationType:"fade-in",
animationDuration:300
})
}
}
}
///pages/public/alert/alert跳转的新页面中的page.json一定要这样设置,这样这个页面就把下面的页面给覆盖住包括tabbar与到导航栏,并且这个页面是透明的你可以再页面的style中自己给背景透明色
"style": {
"navigationStyle": "custom",
"app-plus": {
"animationType": "fade-in",
"animationDuration": "200",
"bounce": "none",
"backgroundColor": "rgba(0,0,0,0)" // 背景透明
}
}
在这个新页面中监听(也是这个队列)
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length == 0) { //如果是最后一个弹窗的情况下就返回也就是关闭这个弹窗容器
uni.navigateBack({})
}
}
},
这个就是你要显示的弹窗因为考虑到后期弹窗多的话我就做成了组件形式
说明:ALERT_LIST[0]就是拿这个队列中的第一项 name:就是你在往队列中添加新弹窗的时候定义的要与下面代码中的v-if后面对应的值一致这样在队列往前走的时候他就能显示对应的弹窗
<!-- 隐私的弹出 -->
<protocol-alert @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'protoco'"></protocol-alert>
<!-- //版本更新的弹出 -->
<versionup-date @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'version'" :versionInfo="ALERT_LIST[0].data"></versionup-date>
<!-- //淘宝客猜你想找的弹出 -->
<copy-search @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'copysearch'"></copy-search>
注:在弹窗中如果有按钮出发了关闭弹窗要将队列中的这一项删除掉 (代码只是提供了一个大概的思路具体写法看你自己)
收起阅读 »
UniPush从0到1
UniPush是DCloud联合个推公司推出的集成型统一推送服务,内建了苹果、华为、小米、OPPO、VIVO、魅族、谷歌FCM等手机厂商的系统级推送和个推等第三方推送。
推送的准备
- 1.CID 设备标识
// 最简单的推送把cid给到后端,后端通过cid来推送
const { clientid = '' } = plus.push.getClientInfo() || {} // 获取cid
- 2.alias 别名 给cid绑定一个别名(在个推服务器上)
// 别名推送后端不需要保存cid 前端打上别名后 后端通过别名进行推送 比如 用户ID 推送的时候只需要给相应的ID使用别名推送
ios
const GtSdk:any = plus.ios.importClass('GeTuiSdk')
// 为该设备绑定别名
GtSdk.bindAliasandSequenceNum(alias, alias)
Android
const PushManager:any = plus.android.importClass('com.igexin.sdk.PushManager')
const context:any = plus.android.runtimeMainActivity().getContext()
const Instance:any = PushManager.getInstance()
// 为该设备绑定别名
Instance.bindAlias(context, alias)
- 3.tag 把用户(cid)进行分组 (也是在个推服务器上)
// 后端通过标签进行推送 比如我们需要推送所有男性用户 首先要对用户打上标签(男) 后端只需要给个推服务器发送推送指令男 有效减少服务器压力
// uniPush 暂不支持前端打标签 (可由后端来打标签)
# 推送的接受
一般是可以写到App.vue文件里面
//消息的监听
plus.push.addEventListener(
'receive',
function (msg: any = {}) {
const isAndroid = plus.os.name === 'Android'
console.log(msg, !isAndroid)
if (!isAndroid) {
// 如果是IOS
const payload = msg.payload
// 【APP离线】收到消息,但没有提醒(发生在一次收到多个离线消息时,只有一个有提醒,但其他的没有提醒)
// 【APP在线】收到消息,不会触发系统消息,需要创建本地消息,但不能重复创建。必须加msg.type验证去除死循环
if (msg.aps == null && msg.type === 'receive') {
const messageTitle = payload.messageTitle || ''
const messageContent = payload.messageContent || ''
// 创建本地消息,发送的本地消息也会被receive方法接收到,但没有type属性,且aps是null
plus.push.createMessage(messageContent, JSON.stringify(payload), {
title: messageTitle
})
}
} else {
let payload:any = {}
console.log(typeof msg.payload === 'object', typeof msg.payload)
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
console.log(payload)
const messageTitle = payload.messageTitle || ''
const messageContent = payload.messageContent || ''
plus.push.createMessage(messageContent, JSON.stringify(payload), {
title: messageTitle
})
}
console.log(msg)
},
false
)
// 消息的点击事件
plus.push.addEventListener(
'click',
function (msg: any = {}) {
const isAndroid = plus.os.name === 'Android'
if (!isAndroid) {
// 如果是IOS
let payload
if (msg.type === 'click') {
// APP离线点击包含click属性,这时payload是JSON对象
payload = msg.payload
} else {
// APP在线,收到消息不会包含type属性,这时的payload是JSON字符串,需要转为JSON对象
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
}
// 点击后业务逻辑的处理
this.messageClick(payload)
} else {
// 如果是Android,收到playload均是是JSON字符串,需要转为JSON对象
let payload:any = {}
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
// 点击后业务逻辑的处理
this.messageClick(payload)
}
},
false
)
如果想离线推送需APP上架市场 申请厂商推送 把相应参数填写到Dcloud后台
UniPush是DCloud联合个推公司推出的集成型统一推送服务,内建了苹果、华为、小米、OPPO、VIVO、魅族、谷歌FCM等手机厂商的系统级推送和个推等第三方推送。
推送的准备
- 1.CID 设备标识
// 最简单的推送把cid给到后端,后端通过cid来推送
const { clientid = '' } = plus.push.getClientInfo() || {} // 获取cid
- 2.alias 别名 给cid绑定一个别名(在个推服务器上)
// 别名推送后端不需要保存cid 前端打上别名后 后端通过别名进行推送 比如 用户ID 推送的时候只需要给相应的ID使用别名推送
ios
const GtSdk:any = plus.ios.importClass('GeTuiSdk')
// 为该设备绑定别名
GtSdk.bindAliasandSequenceNum(alias, alias)
Android
const PushManager:any = plus.android.importClass('com.igexin.sdk.PushManager')
const context:any = plus.android.runtimeMainActivity().getContext()
const Instance:any = PushManager.getInstance()
// 为该设备绑定别名
Instance.bindAlias(context, alias)
- 3.tag 把用户(cid)进行分组 (也是在个推服务器上)
// 后端通过标签进行推送 比如我们需要推送所有男性用户 首先要对用户打上标签(男) 后端只需要给个推服务器发送推送指令男 有效减少服务器压力
// uniPush 暂不支持前端打标签 (可由后端来打标签)
# 推送的接受
一般是可以写到App.vue文件里面
//消息的监听
plus.push.addEventListener(
'receive',
function (msg: any = {}) {
const isAndroid = plus.os.name === 'Android'
console.log(msg, !isAndroid)
if (!isAndroid) {
// 如果是IOS
const payload = msg.payload
// 【APP离线】收到消息,但没有提醒(发生在一次收到多个离线消息时,只有一个有提醒,但其他的没有提醒)
// 【APP在线】收到消息,不会触发系统消息,需要创建本地消息,但不能重复创建。必须加msg.type验证去除死循环
if (msg.aps == null && msg.type === 'receive') {
const messageTitle = payload.messageTitle || ''
const messageContent = payload.messageContent || ''
// 创建本地消息,发送的本地消息也会被receive方法接收到,但没有type属性,且aps是null
plus.push.createMessage(messageContent, JSON.stringify(payload), {
title: messageTitle
})
}
} else {
let payload:any = {}
console.log(typeof msg.payload === 'object', typeof msg.payload)
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
console.log(payload)
const messageTitle = payload.messageTitle || ''
const messageContent = payload.messageContent || ''
plus.push.createMessage(messageContent, JSON.stringify(payload), {
title: messageTitle
})
}
console.log(msg)
},
false
)
// 消息的点击事件
plus.push.addEventListener(
'click',
function (msg: any = {}) {
const isAndroid = plus.os.name === 'Android'
if (!isAndroid) {
// 如果是IOS
let payload
if (msg.type === 'click') {
// APP离线点击包含click属性,这时payload是JSON对象
payload = msg.payload
} else {
// APP在线,收到消息不会包含type属性,这时的payload是JSON字符串,需要转为JSON对象
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
}
// 点击后业务逻辑的处理
this.messageClick(payload)
} else {
// 如果是Android,收到playload均是是JSON字符串,需要转为JSON对象
let payload:any = {}
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
// 点击后业务逻辑的处理
this.messageClick(payload)
}
},
false
)
如果想离线推送需APP上架市场 申请厂商推送 把相应参数填写到Dcloud后台
收起阅读 »

【分享】小程序原生项目转成uni-app中的vue项目
本片文章将从以下三个方面进行讲解:
- 背景
- 使用转换
- 注意事项
一、背景
之前一段时间,调研过跨平台开发一系列框架,最终筛选出uni-app这款可以适用多端的开源框架。一套代码可以同时生成ios,Android,H5,微信小程序,支付宝小程序,百度小程序等。拓展能力很强,封装了H5+,支持nvue,也支持原生Android,ios开发。可以将原有的移动应用和H5应用改成uni-app应用。基于此,恰好可以将我们之前早期的小程序项目,转化成基于vue.js的uni-app组件
这将带来很多优势:
- 更优的组件渲染更新性能
- 更强的拓展性、和可维护性
- 更快的打包速度、和更便捷的打包方式
- 一套代码多端适用
- ...
二、使用转换
使用官方推荐的HBuilderX, github地址:https://github.com/zhangdaren/miniprogram-to-uniapp。该插件十分强大,通过简单的脚本命令,直接将我本地的小程序原生项目,clone生成了一份vue.js的项目。
诸如: 小程序setData的兼容、小程序生命周期的兼容,小程序各种api的封装,几乎涵盖了所有的小程序原生api。
但是尽管如此,仍是有很多需要注意的点,在实际开发中需要我们注意的。
事先声明,以下需要关注的点,是本人在迁移自己的小程序项目中遇到的切实存在 的问题,如果有更好的解决方案,欢迎讨论。
三、注意事项【踩坑】
- 对于wx.setData的处理:
HBuilderX转换过来之后,会在app.js中加入setData的重写,但这个是通过vue中this.$set和this.$get来实现的,也就是说尽管小程序中的setData没有被转化成this.xxx =...的方式(vue的语法),但仍然可以生效。可是如果setData处于原来小程序组件的生命周期中如attached,这种生命周期会被转换为beforeMount 这时setData就会有报错,解决办法:(1)修改成this.xx = ...的方式 (2) 删除生命周期,因为vue中不支持attache等生命周期函数, 如果非要在组件挂载或者更新的时候将prop或其他变量存储到data中,可以尝试通过watch函数。 - 对于wx.selectComponent
经过反复测试,uni-app不会转化这个api,但是转成vue组件后,这个api将不在生效,需要替换成ref获取节点。
解决方案:这点可以使用ref替换,如果转换前微信组件的生命周期中,可以直接this.$refs.xxx.api()。如果转换前是在微信lifetimes中定义的生命周期,uni-app会转换成vue的生命周期,这时用ref不能直接调用组件的api需要加一层 .$vm, 既this.$refs.xxx.$vm.api() - 由于小程序的语法不强制要求wxml必须最外层是单节点。组件中<page></page>节点下如果存在多节点,转换后会多一层dom节点, 当然如果在wxml中有最外层的container的话就忽略这个问题。
- 原生小程序:
<page> 1 2 3 </page>
- 转换后
<page> <view> 1 2 3 </view> </page>
这在组件中会非常致命,会直接引起之前的样式如滚动条超出,高度的一些100%设置,会失效。导致样式错乱,要格外小心。
- 原生小程序:
- 事件绑定
(1)catchtap="true" 在小程序支持这种写法,可以理解为绑定一个空函数,转换之后变成@tap.stop="true" 这个在uni-app中会报错 需要改成 @tap.stop="emptyFunc".
(2)很多catch方法会被自动转换,如catchtap ----> @tap.stop 但是有些方法如catchtouchmove,这个方法不会转为@touchmove.stop,需要手动修改 。 - 转换过程部分函数名会被修改,如果小程序中定义了xxx,以""开头得函数名都会被修改名字,在调用的时候就找不到对应的函数了,以及vue内置得hide、show等方法名字会被修改成hideFun:
- data中引用类型的状态,以“_”开头的状态均无法通过this访问,都是undefined,需要手动更改状态变量的名称。
- 通过ref获取子组件的data,需要去掉data,如this.$ref.modal.data.name 需要换成 this.$ref.modal.name
- 有时我们想要父组件访问子组件的属性和方法,会将子组件的this作用域回调给父组件,这种方式在转化为uni-app后不会直接报错,但是无法获取到,进行下一步操作会报错。解决方案:在父组件直接使用ref。
- 一些写死的属性数据转换过来可能会变成 字符串,如showTitle="{{true}}"转换之后变成 showTitle="true" 这个时候如果子组件还当作boolean类型处理,就会报错。
- 奇怪的input:如果需要动态绑定input的value,并针对用户输入做一些必要的过滤,如不允许输入数字。 需要在用户输入的时候更改对应的value值,同时在input事件中要把经过js处理的value值return,这样才可以成功过滤input的value,否则不管如果用正则拦截,input输入框上都会输入啥显示啥。
- . vue组件中data中的数据this.xxx去获取,小程序转过来极少的时候是this.data.xxx需要手动更改一下,这个问题具体什么时候出现还不太明确,但是需要注意一下。
四、小程序组件
pages.json文件,这块和小程序不同,不需要再页面级json中配置
{
"pages": [
{
"path": "slide-view/slide-view",
"style": {
"navigationBarTitleText": "slide-view",
"usingComponents": {
"slide-view": "/wxcomponents/miniprogram-slide-view/index"
}
}
}
]
}
.vue:
<slide-view></slide-view>可直接使用
需要注意:
• 小程序组件需要放在项目特殊文件夹 wxcomponents(或 mycomponents、swancomponents)。HBuilderX 建立的工程 wxcomponents 文件夹在 项目根目录下。vue-cli 建立的工程 wxcomponents 文件夹在 src 目录下。可以在 vue.config.js 中自定义其他目录
• 小程序组件的性能,不如vue组件。使用小程序组件,需要自己手动setData,很难自动管理差量数据更新。而使用vue组件会自动diff更新差量数据。所以如无明显必要,建议使用vue组件而不是小程序组件。比如某些小程序ui组件,完全可以用更高性能的uni ui替代。
结论
ok,以上就是本人在项目转换迁移过程中遇到的问题。总体感受还是不错的,该说不说的,HBuilderX还是帮我们做了更大部分的事情,但仍需要我们针对各自的业务需求,做一些写法上的调整,做一切事物之前,需要头脑中有一个概念,“小程序中的代码在转换过程会被修改,小程序的实现逻辑,不具有普适性”。
本片文章将从以下三个方面进行讲解:
- 背景
- 使用转换
- 注意事项
一、背景
之前一段时间,调研过跨平台开发一系列框架,最终筛选出uni-app这款可以适用多端的开源框架。一套代码可以同时生成ios,Android,H5,微信小程序,支付宝小程序,百度小程序等。拓展能力很强,封装了H5+,支持nvue,也支持原生Android,ios开发。可以将原有的移动应用和H5应用改成uni-app应用。基于此,恰好可以将我们之前早期的小程序项目,转化成基于vue.js的uni-app组件
这将带来很多优势:
- 更优的组件渲染更新性能
- 更强的拓展性、和可维护性
- 更快的打包速度、和更便捷的打包方式
- 一套代码多端适用
- ...
二、使用转换
使用官方推荐的HBuilderX, github地址:https://github.com/zhangdaren/miniprogram-to-uniapp。该插件十分强大,通过简单的脚本命令,直接将我本地的小程序原生项目,clone生成了一份vue.js的项目。
诸如: 小程序setData的兼容、小程序生命周期的兼容,小程序各种api的封装,几乎涵盖了所有的小程序原生api。
但是尽管如此,仍是有很多需要注意的点,在实际开发中需要我们注意的。
事先声明,以下需要关注的点,是本人在迁移自己的小程序项目中遇到的切实存在 的问题,如果有更好的解决方案,欢迎讨论。
三、注意事项【踩坑】
- 对于wx.setData的处理:
HBuilderX转换过来之后,会在app.js中加入setData的重写,但这个是通过vue中this.$set和this.$get来实现的,也就是说尽管小程序中的setData没有被转化成this.xxx =...的方式(vue的语法),但仍然可以生效。可是如果setData处于原来小程序组件的生命周期中如attached,这种生命周期会被转换为beforeMount 这时setData就会有报错,解决办法:(1)修改成this.xx = ...的方式 (2) 删除生命周期,因为vue中不支持attache等生命周期函数, 如果非要在组件挂载或者更新的时候将prop或其他变量存储到data中,可以尝试通过watch函数。 - 对于wx.selectComponent
经过反复测试,uni-app不会转化这个api,但是转成vue组件后,这个api将不在生效,需要替换成ref获取节点。
解决方案:这点可以使用ref替换,如果转换前微信组件的生命周期中,可以直接this.$refs.xxx.api()。如果转换前是在微信lifetimes中定义的生命周期,uni-app会转换成vue的生命周期,这时用ref不能直接调用组件的api需要加一层 .$vm, 既this.$refs.xxx.$vm.api() - 由于小程序的语法不强制要求wxml必须最外层是单节点。组件中<page></page>节点下如果存在多节点,转换后会多一层dom节点, 当然如果在wxml中有最外层的container的话就忽略这个问题。
- 原生小程序:
<page> 1 2 3 </page>
- 转换后
<page> <view> 1 2 3 </view> </page>
这在组件中会非常致命,会直接引起之前的样式如滚动条超出,高度的一些100%设置,会失效。导致样式错乱,要格外小心。
- 原生小程序:
- 事件绑定
(1)catchtap="true" 在小程序支持这种写法,可以理解为绑定一个空函数,转换之后变成@tap.stop="true" 这个在uni-app中会报错 需要改成 @tap.stop="emptyFunc".
(2)很多catch方法会被自动转换,如catchtap ----> @tap.stop 但是有些方法如catchtouchmove,这个方法不会转为@touchmove.stop,需要手动修改 。 - 转换过程部分函数名会被修改,如果小程序中定义了xxx,以""开头得函数名都会被修改名字,在调用的时候就找不到对应的函数了,以及vue内置得hide、show等方法名字会被修改成hideFun:
- data中引用类型的状态,以“_”开头的状态均无法通过this访问,都是undefined,需要手动更改状态变量的名称。
- 通过ref获取子组件的data,需要去掉data,如this.$ref.modal.data.name 需要换成 this.$ref.modal.name
- 有时我们想要父组件访问子组件的属性和方法,会将子组件的this作用域回调给父组件,这种方式在转化为uni-app后不会直接报错,但是无法获取到,进行下一步操作会报错。解决方案:在父组件直接使用ref。
- 一些写死的属性数据转换过来可能会变成 字符串,如showTitle="{{true}}"转换之后变成 showTitle="true" 这个时候如果子组件还当作boolean类型处理,就会报错。
- 奇怪的input:如果需要动态绑定input的value,并针对用户输入做一些必要的过滤,如不允许输入数字。 需要在用户输入的时候更改对应的value值,同时在input事件中要把经过js处理的value值return,这样才可以成功过滤input的value,否则不管如果用正则拦截,input输入框上都会输入啥显示啥。
- . vue组件中data中的数据this.xxx去获取,小程序转过来极少的时候是this.data.xxx需要手动更改一下,这个问题具体什么时候出现还不太明确,但是需要注意一下。
四、小程序组件
pages.json文件,这块和小程序不同,不需要再页面级json中配置
{
"pages": [
{
"path": "slide-view/slide-view",
"style": {
"navigationBarTitleText": "slide-view",
"usingComponents": {
"slide-view": "/wxcomponents/miniprogram-slide-view/index"
}
}
}
]
}
.vue:
<slide-view></slide-view>可直接使用
需要注意:
• 小程序组件需要放在项目特殊文件夹 wxcomponents(或 mycomponents、swancomponents)。HBuilderX 建立的工程 wxcomponents 文件夹在 项目根目录下。vue-cli 建立的工程 wxcomponents 文件夹在 src 目录下。可以在 vue.config.js 中自定义其他目录
• 小程序组件的性能,不如vue组件。使用小程序组件,需要自己手动setData,很难自动管理差量数据更新。而使用vue组件会自动diff更新差量数据。所以如无明显必要,建议使用vue组件而不是小程序组件。比如某些小程序ui组件,完全可以用更高性能的uni ui替代。
结论
ok,以上就是本人在项目转换迁移过程中遇到的问题。总体感受还是不错的,该说不说的,HBuilderX还是帮我们做了更大部分的事情,但仍需要我们针对各自的业务需求,做一些写法上的调整,做一切事物之前,需要头脑中有一个概念,“小程序中的代码在转换过程会被修改,小程序的实现逻辑,不具有普适性”。
收起阅读 »
uni-app UDP发送与接收
var DatagramSocket = plus.android.importClass("java.net.DatagramSocket");
var DatagramPacket = plus.android.importClass("java.net.DatagramPacket");
var InetAddress = plus.android.importClass("java.net.InetAddress");
var String = plus.android.importClass("java.lang.String");
var udp = new DatagramSocket();
//UDP发送
var sendBuffer = {
"test": 123
}
var SendBuff = JSON.stringify(sendBuffer)
var data = new String(SendBuff).getBytes(); //发送中文需要指定编码
console.log((new String(data)).toString())
var packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.10.100.254"), 9091);
udp.send(packet);
//UDP接收
let charArr = new Array(100).fill(0) //100为接收数据长度,按需修改
var receivePacket = new DatagramPacket(charArr, charArr.length);
udp.setSoTimeout(5000); //新增,一定要设置超时,否则在没有收到返回数据的时候容易卡死
udp.receive(receivePacket);
var receiveData = (new String(receivePacket.getData())).toString().substring(0,receivePacket.getLength()); //receivePacket.getLength()可获取到接收数据长度,根据数据长度剪切出接收到的真实数据
console.log(receiveData)
udp.close();
var DatagramSocket = plus.android.importClass("java.net.DatagramSocket");
var DatagramPacket = plus.android.importClass("java.net.DatagramPacket");
var InetAddress = plus.android.importClass("java.net.InetAddress");
var String = plus.android.importClass("java.lang.String");
var udp = new DatagramSocket();
//UDP发送
var sendBuffer = {
"test": 123
}
var SendBuff = JSON.stringify(sendBuffer)
var data = new String(SendBuff).getBytes(); //发送中文需要指定编码
console.log((new String(data)).toString())
var packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.10.100.254"), 9091);
udp.send(packet);
//UDP接收
let charArr = new Array(100).fill(0) //100为接收数据长度,按需修改
var receivePacket = new DatagramPacket(charArr, charArr.length);
udp.setSoTimeout(5000); //新增,一定要设置超时,否则在没有收到返回数据的时候容易卡死
udp.receive(receivePacket);
var receiveData = (new String(receivePacket.getData())).toString().substring(0,receivePacket.getLength()); //receivePacket.getLength()可获取到接收数据长度,根据数据长度剪切出接收到的真实数据
console.log(receiveData)
udp.close();
收起阅读 »

uni-app 获取当前连接的WiFi名
getWifiName: function() {
var wifiManager, wifiInfo;
var Context = plus.android.importClass("android.content.Context");
var WifiManager = plus.android.importClass("android.net.wifi.WifiManager");
var WifiInfo = plus.android.importClass("android.net.wifi.WifiInfo");
wifiManager = plus.android.runtimeMainActivity().getSystemService(Context.WIFI_SERVICE);
wifiInfo = wifiManager.getConnectionInfo();
var ssid = wifiInfo.getSSID() || '';
if (ssid.length == 0) {
return null;
}
//一些手机上获取SSID是有值的,但是实际IP为空,真实为未连接
var i = parseInt(wifiInfo.getIpAddress());
var ipStr = (i & 0xFF) + "." +
((i >> 8) & 0xFF) + "." +
((i >> 16) & 0xFF) + "." +
(i >> 24 & 0xFF);
if (ipStr == "0.0.0.0") {
console.log("NULL");
return null;
}
if (ssid != "<unknown ssid>" && ssid.toUpperCase() != "0X") {
console.log(ssid.replace(/\"/g, ""));
return ssid.replace(/\"/g, "");
}
console.log("NULL");
return null;
},
getWifiName: function() {
var wifiManager, wifiInfo;
var Context = plus.android.importClass("android.content.Context");
var WifiManager = plus.android.importClass("android.net.wifi.WifiManager");
var WifiInfo = plus.android.importClass("android.net.wifi.WifiInfo");
wifiManager = plus.android.runtimeMainActivity().getSystemService(Context.WIFI_SERVICE);
wifiInfo = wifiManager.getConnectionInfo();
var ssid = wifiInfo.getSSID() || '';
if (ssid.length == 0) {
return null;
}
//一些手机上获取SSID是有值的,但是实际IP为空,真实为未连接
var i = parseInt(wifiInfo.getIpAddress());
var ipStr = (i & 0xFF) + "." +
((i >> 8) & 0xFF) + "." +
((i >> 16) & 0xFF) + "." +
(i >> 24 & 0xFF);
if (ipStr == "0.0.0.0") {
console.log("NULL");
return null;
}
if (ssid != "<unknown ssid>" && ssid.toUpperCase() != "0X") {
console.log(ssid.replace(/\"/g, ""));
return ssid.replace(/\"/g, "");
}
console.log("NULL");
return null;
},
收起阅读 »

HbuilderX 从命令行启动指定文件夹
3.29 更新
-
今天看HB更新了 多了个
cli
命令;原本以为是path 结果不是 -
我是这样用的 :
alias cli="/Applications/HBuilderX.app/Contents/MacOS/cli" -
用法就变成这样:
额 感觉还是差点意思(小声)
命令行 指定HbuilderX 启动指定目录(历史内容)
- 在VsCode 中 可以用code 加目录 快捷启动 编辑器
- 我看了下 如果在 HbuilderX中 可以通过设置别名 达到这个效果 以Mac (zsh)为例
#首先打开 .zshrc
#设置别名
alias hbd='hbd(){open $1 -a /Applications/HBuilderX.app}; hbd'
source 后 就可以如下操作了
cd 某个目录
git pull
hbd .
windows10 下
#首先打开 $PROFILE
function hbd([string]$_path){
{hbuilder.exe的路径} $_path
}
- 嗯 符合我的想法
3.29 更新
-
今天看HB更新了 多了个
cli
命令;原本以为是path 结果不是 -
我是这样用的 :
alias cli="/Applications/HBuilderX.app/Contents/MacOS/cli" -
用法就变成这样:
额 感觉还是差点意思(小声)
命令行 指定HbuilderX 启动指定目录(历史内容)
- 在VsCode 中 可以用code 加目录 快捷启动 编辑器
- 我看了下 如果在 HbuilderX中 可以通过设置别名 达到这个效果 以Mac (zsh)为例
#首先打开 .zshrc
#设置别名
alias hbd='hbd(){open $1 -a /Applications/HBuilderX.app}; hbd'
source 后 就可以如下操作了
cd 某个目录
git pull
hbd .
windows10 下
#首先打开 $PROFILE
function hbd([string]$_path){
{hbuilder.exe的路径} $_path
}
- 嗯 符合我的想法

vue3.x+electron聊天程序|Electron跨平台聊天室
前言
随着Electron快速更新迭代及Vue3生态圈愈来愈完善,二者结合开发桌面端程序必然再一次受到开发者热捧。
上一次有给大家分享一个vue3开发移动端短视频实例,今天带来的是最新开发的一款electron+vue3桌面版搭建TIM/QQ聊天应用程序实战项目ElectronQchat。
vue3-electron-qchat 支持同时新开多个窗口、换肤等功能。
框架技术
- 编码+技术:vscode | vue3.0+vuex4+vue-router@4
- 跨端框架:electron11.2.3
- UI组件库:ant-design-vue (蚂蚁桌面端vue3组件库)
- 弹窗组件:v3layer(vue3自定义弹窗组件)
- 滚动条组件:v3scroll(vue3自定义滚动条组件)
- 打包工具:vue-cli-plugin-electron-builder
- 按需引入:babel-plugin-import^1.13.3
项目结构
electron主进程入口
vue3+electron搭建的项目,根目录下有一个background.js,即主进程入口配置。
/**
* 主进程入口配置
*/
import { app, BrowserWindow, globalShortcut } from 'electron'
// import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import Windows from './module/windows'
const isDevelopment = process.env.NODE_ENV !== 'production'
async function createWindow() {
let window = new Windows()
window.listen()
window.createWin({isMainWin: true})
window.createTray()
}
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
app.on('ready', async () => {
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
electron创建多窗体|父子modal窗口模式
项目支持同时新开多个窗口及modal窗口模式。只需简单调用公共函数createWin即可快速生成一个新窗口。
// 换肤窗口
const handleSkinWin = () => {
createWin({
title: '换肤',
route: '/skin',
width: 720,
height: 475,
resize: false,
})
}
// 朋友圈窗口
const handleFZoneWin = () => {
createWin({
title: '朋友圈',
route: '/fzone',
width: 550,
height: 700,
resize: false,
})
}
支持如下参数配置:
export const winConfig = {
id: null, // 窗口唯一id
background: '#fff', // 背景色
route: '', // 路由地址url
title: '', // 标题
data: null, // 传入数据参数
width: '', // 窗口宽度
height: '', // 窗口高度
minWidth: '', // 窗口最小宽度
minHeight: '', // 窗口最小高度
x: '', // 窗口相对于屏幕左侧坐标
y: '', // 窗口相对于屏幕顶端坐标
resize: true, // 是否支持缩放
maximize: false, // 最大化窗口
isMultiWin: false, // 是否支持多开窗口(为true则会支持创建多个窗口)
isMainWin: false, // 是否主窗口(为true则会替代之前主窗口)
parent: '', // 父窗口(传入父窗口id)
modal: false, // 模态窗口(需设置parent和modal选项)
alwaysOnTop: false, // 是否置顶窗口
}
由于篇幅原因,大家如果对实现过程感兴趣,可以去看看下面这篇文章。
electron无边框窗口|自定义顶部菜单
项目整体采用无边框模式,开启frame:false即可。顶部导航栏及右上角按钮就需要自定义实现了。
设置-webkit-app-region:drag即可自定义拖拽区域,不过拖拽区域鼠标右键会有系统菜单。
可通过如下方法,在创建窗口的时候快速给屏蔽掉。
win.hookWindowMessage(278, () => {
win.setEnabled(false)
setTimeout(() => {
win.setEnabled(true)
}, 100)
return true
})
出于篇幅缘故,大家如果对具体如何实现导航栏菜单感兴趣,可以去看看这篇分享。
electron模仿QQ托盘图标/闪烁
electron提供了Tray函数来创建系统托盘图标,大家可以通过定时器来实现闪烁效果。
需提前准备好两张大小一致的图标文件,一个透明背景就行。然后通过定时器来轮流显示图标。
// 创建系统托盘图标
let tray = null
let flashTimer = null
let trayIco1 = path.join(__dirname, '../static/tray.ico')
let trayIco2 = path.join(__dirname, '../static/tray-empty.ico')
createTray() {
const trayMenu = Menu.buildFromTemplate([
{
label: '我在线上', icon: path.join(__dirname, '../static/icon-online.png'),
click: () => {...}
},
{
label: '忙碌', icon: path.join(__dirname, '../static/icon-busy.png'),
click: () => {...}
},
{
label: '隐身', icon: path.join(__dirname, '../static/icon-invisible.png'),
click: () => {...}
},
{
label: '离开', icon: path.join(__dirname, '../static/icon-offline.png'),
click: () => {...}
},
{type: 'separator'},
{
label: '关闭所有声音', click: () => {...},
},
{
label: '关闭头像闪动', click: () => {
this.flashTray(false)
}
},
{type: 'separator'},
{
label: '打开主窗口', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(!win) return
if(win.isMinimized()) win.restore()
win.show()
}
} catch (error) {
console.log(error)
}
}
},
{
label: '退出', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(win) win.webContents.send('win-logout')
}
app.quit()
} catch (error) {
console.log(error)
}
}
},
])
this.tray = new Tray(this.trayIco1)
this.tray.setContextMenu(trayMenu)
this.tray.setToolTip(app.name)
this.tray.on('double-click', () => {
// ...
})
}
// 托盘图标闪烁
flashTray(flash) {
let hasIco = false
if(flash) {
if(this.flashTimer) return
this.flashTimer = setInterval(() => {
this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
hasIco = !hasIco
}, 500)
}else {
if(this.flashTimer) {
clearInterval(this.flashTimer)
this.flashTimer = null
}
this.tray.setImage(this.trayIco1)
}
}
// 销毁托盘图标
destoryTray() {
this.flashTray(false)
this.tray.destroy()
this.tray = null
}
electron快速打包配置
vue3和electron创建的项目,目录下会有一个vue.config.js配置文件。里面可以进行一些vue3配置及electron-builder打包配置。
/**
* @Desc vue3项目配置文件
* @Create andy by 2021-02 Q:282310962 wx:xy190310
*/
const path = require('path')
module.exports = {
// 基本路径
// publicPath: '/',
// 输出文件目录
// outputDir: 'dist',
// assetsDir: '',
// 环境配置
devServer: {
// host: 'localhost',
// port: 8080,
// 是否开启https
https: false,
// 编译完是否打开网页
open: false,
// 代理配置
// proxy: {
// '^/api': {
// target: '<url>',
// ws: true,
// changeOrigin: true
// },
// '^/foo': {
// target: '<other_url>'
// }
// }
},
// webpack配置
chainWebpack: config => {
// 配置路径别名
config.resolve.alias
.set('@', path.join(__dirname, 'src'))
.set('@assets', path.join(__dirname, 'src/assets'))
.set('@components', path.join(__dirname, 'src/components'))
.set('@module', path.join(__dirname, 'src/module'))
.set('@plugins', path.join(__dirname, 'src/plugins'))
.set('@layouts', path.join(__dirname, 'src/layouts'))
.set('@views', path.join(__dirname, 'src/views'))
},
// 插件配置
pluginOptions: {
electronBuilder: {
// 配置后可以在渲染进程使用ipcRenderer
nodeIntegration: true,
// 项目打包参数配置
builderOptions: {
"productName": "electron-qchat", //项目名称 打包生成exe的前缀名
"appId": "com.example.electronqchat", //包名
"compression": "maximum", //store|normal|maximum 打包压缩情况(store速度较快)
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}", //打包后安装包名称
// "directories": {
// "output": "build", //输出文件夹(默认dist_electron)
// },
"asar": false, //asar打包
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
],
"nsis": {
"oneClick": false, //一键安装
"allowToChangeInstallationDirectory": true, //允许修改安装目录
"perMachine": true, //是否开启安装时权限设置(此电脑或当前用户)
"artifactName": "${productName}-${version}-${platform}-${arch}-setup.${ext}", //打包后安装包名称
"deleteAppDataOnUninstall": true, //卸载时删除数据
"createDesktopShortcut": true, //创建桌面图标
"createStartMenuShortcut": true, //创建开始菜单图标
"shortcutName": "ElectronQChat", //桌面快捷键图标名称
},
"win": {
"icon": "./static/shortcut.ico", //图标路径
}
}
}
}
}
注意事项
1、项目路径最好不能含有中文,否则打包会失败!
2、最好不要使用getCurrentInstance来操作router或store,否则打包会报错!
3、在渲染进程中使用ipcRenderer或remote模块,记得在创建窗体的时候配置nodeIntegration: true和enableRemoteModule: true
否则会出现如下错误提示:Uncaught TypeError: fs.existsSync is not a function
4、如果打包后图标或外部dll无效,记得在打包的时候配置静态资源转移extraResources
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
]
ok,以上就是vue3+electron开发客户端聊天软件的一些分享,希望对大家有些帮助!
链接:https://juejin.cn/post/6933871014937886727/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
前言
随着Electron快速更新迭代及Vue3生态圈愈来愈完善,二者结合开发桌面端程序必然再一次受到开发者热捧。
上一次有给大家分享一个vue3开发移动端短视频实例,今天带来的是最新开发的一款electron+vue3桌面版搭建TIM/QQ聊天应用程序实战项目ElectronQchat。
vue3-electron-qchat 支持同时新开多个窗口、换肤等功能。
框架技术
- 编码+技术:vscode | vue3.0+vuex4+vue-router@4
- 跨端框架:electron11.2.3
- UI组件库:ant-design-vue (蚂蚁桌面端vue3组件库)
- 弹窗组件:v3layer(vue3自定义弹窗组件)
- 滚动条组件:v3scroll(vue3自定义滚动条组件)
- 打包工具:vue-cli-plugin-electron-builder
- 按需引入:babel-plugin-import^1.13.3
项目结构
electron主进程入口
vue3+electron搭建的项目,根目录下有一个background.js,即主进程入口配置。
/**
* 主进程入口配置
*/
import { app, BrowserWindow, globalShortcut } from 'electron'
// import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import Windows from './module/windows'
const isDevelopment = process.env.NODE_ENV !== 'production'
async function createWindow() {
let window = new Windows()
window.listen()
window.createWin({isMainWin: true})
window.createTray()
}
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
app.on('ready', async () => {
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
electron创建多窗体|父子modal窗口模式
项目支持同时新开多个窗口及modal窗口模式。只需简单调用公共函数createWin即可快速生成一个新窗口。
// 换肤窗口
const handleSkinWin = () => {
createWin({
title: '换肤',
route: '/skin',
width: 720,
height: 475,
resize: false,
})
}
// 朋友圈窗口
const handleFZoneWin = () => {
createWin({
title: '朋友圈',
route: '/fzone',
width: 550,
height: 700,
resize: false,
})
}
支持如下参数配置:
export const winConfig = {
id: null, // 窗口唯一id
background: '#fff', // 背景色
route: '', // 路由地址url
title: '', // 标题
data: null, // 传入数据参数
width: '', // 窗口宽度
height: '', // 窗口高度
minWidth: '', // 窗口最小宽度
minHeight: '', // 窗口最小高度
x: '', // 窗口相对于屏幕左侧坐标
y: '', // 窗口相对于屏幕顶端坐标
resize: true, // 是否支持缩放
maximize: false, // 最大化窗口
isMultiWin: false, // 是否支持多开窗口(为true则会支持创建多个窗口)
isMainWin: false, // 是否主窗口(为true则会替代之前主窗口)
parent: '', // 父窗口(传入父窗口id)
modal: false, // 模态窗口(需设置parent和modal选项)
alwaysOnTop: false, // 是否置顶窗口
}
由于篇幅原因,大家如果对实现过程感兴趣,可以去看看下面这篇文章。
electron无边框窗口|自定义顶部菜单
项目整体采用无边框模式,开启frame:false即可。顶部导航栏及右上角按钮就需要自定义实现了。
设置-webkit-app-region:drag即可自定义拖拽区域,不过拖拽区域鼠标右键会有系统菜单。
可通过如下方法,在创建窗口的时候快速给屏蔽掉。
win.hookWindowMessage(278, () => {
win.setEnabled(false)
setTimeout(() => {
win.setEnabled(true)
}, 100)
return true
})
出于篇幅缘故,大家如果对具体如何实现导航栏菜单感兴趣,可以去看看这篇分享。
electron模仿QQ托盘图标/闪烁
electron提供了Tray函数来创建系统托盘图标,大家可以通过定时器来实现闪烁效果。
需提前准备好两张大小一致的图标文件,一个透明背景就行。然后通过定时器来轮流显示图标。
// 创建系统托盘图标
let tray = null
let flashTimer = null
let trayIco1 = path.join(__dirname, '../static/tray.ico')
let trayIco2 = path.join(__dirname, '../static/tray-empty.ico')
createTray() {
const trayMenu = Menu.buildFromTemplate([
{
label: '我在线上', icon: path.join(__dirname, '../static/icon-online.png'),
click: () => {...}
},
{
label: '忙碌', icon: path.join(__dirname, '../static/icon-busy.png'),
click: () => {...}
},
{
label: '隐身', icon: path.join(__dirname, '../static/icon-invisible.png'),
click: () => {...}
},
{
label: '离开', icon: path.join(__dirname, '../static/icon-offline.png'),
click: () => {...}
},
{type: 'separator'},
{
label: '关闭所有声音', click: () => {...},
},
{
label: '关闭头像闪动', click: () => {
this.flashTray(false)
}
},
{type: 'separator'},
{
label: '打开主窗口', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(!win) return
if(win.isMinimized()) win.restore()
win.show()
}
} catch (error) {
console.log(error)
}
}
},
{
label: '退出', click: () => {
try {
for(let i in this.winLs) {
let win = this.getWin(i)
if(win) win.webContents.send('win-logout')
}
app.quit()
} catch (error) {
console.log(error)
}
}
},
])
this.tray = new Tray(this.trayIco1)
this.tray.setContextMenu(trayMenu)
this.tray.setToolTip(app.name)
this.tray.on('double-click', () => {
// ...
})
}
// 托盘图标闪烁
flashTray(flash) {
let hasIco = false
if(flash) {
if(this.flashTimer) return
this.flashTimer = setInterval(() => {
this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
hasIco = !hasIco
}, 500)
}else {
if(this.flashTimer) {
clearInterval(this.flashTimer)
this.flashTimer = null
}
this.tray.setImage(this.trayIco1)
}
}
// 销毁托盘图标
destoryTray() {
this.flashTray(false)
this.tray.destroy()
this.tray = null
}
electron快速打包配置
vue3和electron创建的项目,目录下会有一个vue.config.js配置文件。里面可以进行一些vue3配置及electron-builder打包配置。
/**
* @Desc vue3项目配置文件
* @Create andy by 2021-02 Q:282310962 wx:xy190310
*/
const path = require('path')
module.exports = {
// 基本路径
// publicPath: '/',
// 输出文件目录
// outputDir: 'dist',
// assetsDir: '',
// 环境配置
devServer: {
// host: 'localhost',
// port: 8080,
// 是否开启https
https: false,
// 编译完是否打开网页
open: false,
// 代理配置
// proxy: {
// '^/api': {
// target: '<url>',
// ws: true,
// changeOrigin: true
// },
// '^/foo': {
// target: '<other_url>'
// }
// }
},
// webpack配置
chainWebpack: config => {
// 配置路径别名
config.resolve.alias
.set('@', path.join(__dirname, 'src'))
.set('@assets', path.join(__dirname, 'src/assets'))
.set('@components', path.join(__dirname, 'src/components'))
.set('@module', path.join(__dirname, 'src/module'))
.set('@plugins', path.join(__dirname, 'src/plugins'))
.set('@layouts', path.join(__dirname, 'src/layouts'))
.set('@views', path.join(__dirname, 'src/views'))
},
// 插件配置
pluginOptions: {
electronBuilder: {
// 配置后可以在渲染进程使用ipcRenderer
nodeIntegration: true,
// 项目打包参数配置
builderOptions: {
"productName": "electron-qchat", //项目名称 打包生成exe的前缀名
"appId": "com.example.electronqchat", //包名
"compression": "maximum", //store|normal|maximum 打包压缩情况(store速度较快)
"artifactName": "${productName}-${version}-${platform}-${arch}.${ext}", //打包后安装包名称
// "directories": {
// "output": "build", //输出文件夹(默认dist_electron)
// },
"asar": false, //asar打包
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
],
"nsis": {
"oneClick": false, //一键安装
"allowToChangeInstallationDirectory": true, //允许修改安装目录
"perMachine": true, //是否开启安装时权限设置(此电脑或当前用户)
"artifactName": "${productName}-${version}-${platform}-${arch}-setup.${ext}", //打包后安装包名称
"deleteAppDataOnUninstall": true, //卸载时删除数据
"createDesktopShortcut": true, //创建桌面图标
"createStartMenuShortcut": true, //创建开始菜单图标
"shortcutName": "ElectronQChat", //桌面快捷键图标名称
},
"win": {
"icon": "./static/shortcut.ico", //图标路径
}
}
}
}
}
注意事项
1、项目路径最好不能含有中文,否则打包会失败!
2、最好不要使用getCurrentInstance来操作router或store,否则打包会报错!
3、在渲染进程中使用ipcRenderer或remote模块,记得在创建窗体的时候配置nodeIntegration: true和enableRemoteModule: true
否则会出现如下错误提示:Uncaught TypeError: fs.existsSync is not a function
4、如果打包后图标或外部dll无效,记得在打包的时候配置静态资源转移extraResources
// 拷贝静态资源目录到指定位置
"extraResources": [
{
"from": "./static",
"to": "static"
},
]
ok,以上就是vue3+electron开发客户端聊天软件的一些分享,希望对大家有些帮助!
链接:https://juejin.cn/post/6933871014937886727/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

招聘UNI-APP开发(全职)
1、三年以上 uniapp/vue前端研发经验,参与过比较复杂的前端交互场景;
2、精通uniapp/vue,对Web标准和标签语义化有深入理解;
3、有良好的代码习惯,要求结构清晰,命名规范逻辑性强,代码冗余低,有用户需求分析能力。
4、具备良好的团队协作精神,能利用自身技术能力提升团队整体研发效率,提高团队影响力;
公司运营自有B2B平台项目,不是外包公司。项目有发展前景,欢迎联系
1、三年以上 uniapp/vue前端研发经验,参与过比较复杂的前端交互场景;
2、精通uniapp/vue,对Web标准和标签语义化有深入理解;
3、有良好的代码习惯,要求结构清晰,命名规范逻辑性强,代码冗余低,有用户需求分析能力。
4、具备良好的团队协作精神,能利用自身技术能力提升团队整体研发效率,提高团队影响力;
公司运营自有B2B平台项目,不是外包公司。项目有发展前景,欢迎联系
收起阅读 »
uni组件之——webview与APP之间的通讯
首先,让我们来谈谈应用的业务场景。大伙的业务场景是什么样我不知道,我来谈谈我踩坑时的业务场景。
老板:我们有个移动端的网页项目,但是为了唬客户,我们需要把这个网页打包成ios端和安卓端的app,页面中有一个需要调用摄像头的拍照签到功能。
我内心活动:ok,uni + webview没跑了,但是webview是不能直接调用手机的摄像头的,他的逻辑大概是,点击网页的签到按钮触发一个事件,事件给app发送一个我要使用手机摄像头的需求,然后再uni中收到了信号,去调起手机的摄像头。
所以,搞明白了业务场景,不管三七二十一,先上案例,看看最简单的demo咋实现。
html页面:
首先模拟一个网页,我们就用一个静态的html网页就好。webview要访问本地静态页面的话,静态页面是需要放在static目录底下的,在这我们命名为index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>网页实时向APP发送消息</title>
<!-- uni 的 SDK,必须引用。 -->
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<!-- 微信 JS-SDK 如果不需要兼容小程序,则无需引用此 JS 文件。 -->
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
</head>
<body>
<script type="text/javascript">
// 在引用依赖的文件后,需要在 HTML 中监听 UniAppJSBridgeReady 事件触发后,才能安全调用 uni 的 API。
document.addEventListener('UniAppJSBridgeReady', function() {
uni.getEnv(function(res) {
console.log('当前环境:' + JSON.stringify(res));
});
//向APP发送消息
uni.postMessage({
data: {
action: 'message'
}
});
//接收APP发送过来的消息
handleMessage(evt) {
console.log('接收到的消息:' + JSON.stringify(evt.detail.data));
}
});
</script>
</body>
</html>
uni页面:
这里为index.nvue页面(注意这个nvue,跟vue页面是有差别的,后面就是踩坑点之一)
<template>
<view class="webview-box">
<web-view ref="webview" class="webview" src="../../static/index.html" @onPostMessage="handleMessage"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
wv: null
}
},
onLoad() {
const self = this;
// #ifdef APP-PLUS
let currentWebview = getCurrentPages()[0];
setTimeout(function() {
self.wv = currentWebview.$getAppWebview();
//调用下面函数
self.handlePostMessage("消息");
}, 1000);
// #endif
},
mouted(){
},
methods: {
// 接收h5页面发来的键值判断需要执行的操作
handleMessage(evt) {
console.log("postMessage: ", evt)
},
// 获取到对应webview(关键)通过evalJs(注意大小写,如果不知道evalJ是什么,可自行百度) 执行网页的函数,可对其进行传参,完成与网页的通讯
handlePostMessage(res) {
this.$refs.webview.evalJs(`handleMessage(${res})`);
}
}
}
</script>
<style>
.webview-box {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
.webview {
flex: 1;
}
</style>
好了,是不是觉得很简单,事实上也很简单,但是鄙人非出脑瘫,踩了两个巨坑。两个都是在uni的页面的,如果大伙也踩到的话,那恭喜了,看到了我。
第一,我用的是nvue页面,nvue获取webview窗口的方式是与普通vue获取webview的方式不一样的,这一步非常关键,如果获取不正确就算你以下写的东西是正确的也没有效果,具体是什么原理我也没深究,以后懂了再更好了,先实现业务。具体获取方法看案例,自己console一下看看要获取的是哪个webview,因为如果大伙的项目是有自定义导航栏获取其他nuve组件的话也算是一个webview。
第二个坑就是,一定要用在uni的webview组件上使用@onPostMessage
这个方法进行消息的监听,我当时也不知道犯了什么病,我用的是message,老半天调不出。message只有在特定的情况下才会接收到网页的消息,比如你执行了返回或着其他的特定场景操作的时候,具体可以看官网给出的。踩坑记录就到这啦,希望能帮助到大伙。
- 参考资料:[1] dcloud社区
首先,让我们来谈谈应用的业务场景。大伙的业务场景是什么样我不知道,我来谈谈我踩坑时的业务场景。
老板:我们有个移动端的网页项目,但是为了唬客户,我们需要把这个网页打包成ios端和安卓端的app,页面中有一个需要调用摄像头的拍照签到功能。
我内心活动:ok,uni + webview没跑了,但是webview是不能直接调用手机的摄像头的,他的逻辑大概是,点击网页的签到按钮触发一个事件,事件给app发送一个我要使用手机摄像头的需求,然后再uni中收到了信号,去调起手机的摄像头。
所以,搞明白了业务场景,不管三七二十一,先上案例,看看最简单的demo咋实现。
html页面:
首先模拟一个网页,我们就用一个静态的html网页就好。webview要访问本地静态页面的话,静态页面是需要放在static目录底下的,在这我们命名为index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>网页实时向APP发送消息</title>
<!-- uni 的 SDK,必须引用。 -->
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<!-- 微信 JS-SDK 如果不需要兼容小程序,则无需引用此 JS 文件。 -->
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
</head>
<body>
<script type="text/javascript">
// 在引用依赖的文件后,需要在 HTML 中监听 UniAppJSBridgeReady 事件触发后,才能安全调用 uni 的 API。
document.addEventListener('UniAppJSBridgeReady', function() {
uni.getEnv(function(res) {
console.log('当前环境:' + JSON.stringify(res));
});
//向APP发送消息
uni.postMessage({
data: {
action: 'message'
}
});
//接收APP发送过来的消息
handleMessage(evt) {
console.log('接收到的消息:' + JSON.stringify(evt.detail.data));
}
});
</script>
</body>
</html>
uni页面:
这里为index.nvue页面(注意这个nvue,跟vue页面是有差别的,后面就是踩坑点之一)
<template>
<view class="webview-box">
<web-view ref="webview" class="webview" src="../../static/index.html" @onPostMessage="handleMessage"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
wv: null
}
},
onLoad() {
const self = this;
// #ifdef APP-PLUS
let currentWebview = getCurrentPages()[0];
setTimeout(function() {
self.wv = currentWebview.$getAppWebview();
//调用下面函数
self.handlePostMessage("消息");
}, 1000);
// #endif
},
mouted(){
},
methods: {
// 接收h5页面发来的键值判断需要执行的操作
handleMessage(evt) {
console.log("postMessage: ", evt)
},
// 获取到对应webview(关键)通过evalJs(注意大小写,如果不知道evalJ是什么,可自行百度) 执行网页的函数,可对其进行传参,完成与网页的通讯
handlePostMessage(res) {
this.$refs.webview.evalJs(`handleMessage(${res})`);
}
}
}
</script>
<style>
.webview-box {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
.webview {
flex: 1;
}
</style>
好了,是不是觉得很简单,事实上也很简单,但是鄙人非出脑瘫,踩了两个巨坑。两个都是在uni的页面的,如果大伙也踩到的话,那恭喜了,看到了我。
第一,我用的是nvue页面,nvue获取webview窗口的方式是与普通vue获取webview的方式不一样的,这一步非常关键,如果获取不正确就算你以下写的东西是正确的也没有效果,具体是什么原理我也没深究,以后懂了再更好了,先实现业务。具体获取方法看案例,自己console一下看看要获取的是哪个webview,因为如果大伙的项目是有自定义导航栏获取其他nuve组件的话也算是一个webview。
第二个坑就是,一定要用在uni的webview组件上使用@onPostMessage
这个方法进行消息的监听,我当时也不知道犯了什么病,我用的是message,老半天调不出。message只有在特定的情况下才会接收到网页的消息,比如你执行了返回或着其他的特定场景操作的时候,具体可以看官网给出的。踩坑记录就到这啦,希望能帮助到大伙。
- 参考资料:[1] dcloud社区