
关于前端弹窗无法覆盖原生导航栏及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社区

HBuilderX android安卓手机 wifi无线调试
HBuilderX的安装包中有自带的adb 不需要自己重新下载
我的HBuilderX是最新的稳定版 目录是:
HBuilderX\plugins\launcher\tools\adbs
手机和电脑连接同一局域网(连同一个 WiFi)
运行cmd 切换到HBuilderX\plugins\launcher\tools\adbs
手机数据线连上电脑(打开允许usb调试),输入命令:adb tcpip <端口号>,<端口号> 可任意取,只要没被占用,如 6666
adb tcpip 6666 成功提示:restarting in TCP mode port :6666
查看手机的ip,输入命令 adb connect 192.168.2.102:666 此处192.168.2.102 替换为自己的ip
成功提示:connected to 192.168.2.102:6666
adb devices 可查看监听的设备
HBuilderX的安装包中有自带的adb 不需要自己重新下载
我的HBuilderX是最新的稳定版 目录是:
HBuilderX\plugins\launcher\tools\adbs
手机和电脑连接同一局域网(连同一个 WiFi)
运行cmd 切换到HBuilderX\plugins\launcher\tools\adbs
手机数据线连上电脑(打开允许usb调试),输入命令:adb tcpip <端口号>,<端口号> 可任意取,只要没被占用,如 6666
adb tcpip 6666 成功提示:restarting in TCP mode port :6666
查看手机的ip,输入命令 adb connect 192.168.2.102:666 此处192.168.2.102 替换为自己的ip
成功提示:connected to 192.168.2.102:6666
adb devices 可查看监听的设备
收起阅读 »
组件卸载之后取消未完成的请求
近期使用uni-app开发的项目中出现一个问题,在发起请求后如果用户直接点击的后退按钮,那么之前页面的请求还会继续加载,并且成功的时候依然会执行success的逻辑。
比如先从A页面跳转到B页面,然后在B页面查询数据,正常情况是查询成功后跳转到一个新的C页面,如果在请求成功之前用户不想去做后面的操作了,直接点击了浏览器的回退按钮,那么回退到A页面之后B页面的请求依然会继续执行,并且请求成功以后success的逻辑也会执行,会导致从A页面直接跳转的C页面。
最好的解决办法可能是①在离开一个页面后可以直接清空该页面组件的相关逻辑,并且停止页面内的请求?我找了一下没有看到这样的配置或者文档,所以不确定是否能够实现,并且如果有的页面逻辑是②:B页面同时发起两个请求task1,task2,在task1请求成功时会跳转页面,而task2的请求只负责传递数据,不做页面交互,那么在task1请求成功后页面跳转,B页面组件卸载直接停止task2请求的话可能也不合适。所以目前比较适合且可以实现的方式可能是在某些情况下手动停止请求。
关于uni.request停止请求的方法可以查看uni.request
直接使用uni.request的abort方法可以满足上方需求,但是写起来会比较麻烦,每一次请求都需要去保存对应的requestTask,在需要的时候再调用requestTask的abort方法。所以为了简化多请求的取消操作/写法,所以封装了一个方法。
我们的项目之前已经封装了uni.request方法,提供了自己的一些默认参数、请求头、返回数据处理以及错误提示等逻辑,这次在已有的基础上多加了取消请求的相关逻辑。这里我的思路是在发起请求时将当前requestTask作为一个对象的值存起来,对应的键可以是随机数或者guid格式的字符串,在请求成功或者失败后从对象中删除该键;当需要取消请求时把对象内的所有requestTask全部调用一次abort方法,以此取消请求。考虑到②的情况,所以暴露出去的request方法可以提供一个参数用于标识该请求是否可以被取消。而如果是希望实现①的情况的话,也可以监听路由变化,或者使用路由守卫,在页面变更时调用统一的取消请求方法。
import Vue from 'vue';
import { guid } from './utils.js';
let canCancelRequestObj = {};
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌过期)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
//校验不通过,则
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
}
/**
- Requests a URL, returning a promise.
- @param {object} [options] The options we want to pass to "request"
-
@return {object} An object containing either "data" or "err"
*/
export default function request(options,canCancel=false) {
// const defaultOptions = {
// credentials: 'include',
// };
let requestId = guid();
console.log('canCancelRequestObj:',canCancelRequestObj);
if(!options.url){
console.log('无请求url')
return
}
const newOptions = {
// ...defaultOptions,
...options,
success(res) {
delete canCancelRequestObj[requestId];
// console.log('response:'+JSON.stringify(res));
if(res.statusCode == '401' || (res.statusCode != '200'&&!res.data)){
uni.hideLoading();
let strResult='';
//清空登陆信息,跳转到登陆页
console.log('登陆信息失效')
if(res.data){
strResult = res.data;
}else{
strResult = res.errMsg;
}
let jsondata = {
result: strResult
};
Vue.prototype && Vue.prototype.$bridge && Vue.prototype.$bridge.callhandler&&Vue.prototype.$bridge.callhandler('jsCallNative', {
action: 'goLogin',
data: JSON.stringify(jsondata)
}, (data) => {}); return } options.success&&options.success(res.data)
},
fail(error) {
if(error&&error.errMsg&&error.errMsg.includes('fail abort')){
console.log(error)
//主动取消的请求
return
}else{
options.fail&&options.fail(error)
}
}
};
let Authorization = window.sessionStorage.getItem('ACCESS_TOKEN');
newOptions.header = {
Authorization:Authorization,
from:'PSM_ANDROID',
timestamp:new Date().getTime(),
};
switch (uni.getSystemInfoSync().platform) {
case 'android':
newOptions.header.from = 'PSM_ANDROID';
break;
case 'ios':
newOptions.header.from = 'PSM_APPLE';
break;
default:
newOptions.header.from = 'PSM_APPLE';break;
}
if (newOptions.method === 'POST' || newOptions.method === 'PUT' || newOptions.method === 'DELETE') {
newOptions.header = {
// Accept: 'application/json',
'content-type': 'application/json;',
...newOptions.header,
};
}
console.log(JSON.stringify(newOptions));
let requestTask = uni.request(newOptions);
if(canCancel){
canCancelRequestObj[requestId] = requestTask;
}
return requestTask;
}
export function cancelRequest (){
console.log(canCancelRequestObj);
if(!canCancelRequestObj||typeof canCancelRequestObj != 'object'){
return
}
Object.keys(canCancelRequestObj).forEach(item=>canCancelRequestObj[item].abort());
}
近期使用uni-app开发的项目中出现一个问题,在发起请求后如果用户直接点击的后退按钮,那么之前页面的请求还会继续加载,并且成功的时候依然会执行success的逻辑。
比如先从A页面跳转到B页面,然后在B页面查询数据,正常情况是查询成功后跳转到一个新的C页面,如果在请求成功之前用户不想去做后面的操作了,直接点击了浏览器的回退按钮,那么回退到A页面之后B页面的请求依然会继续执行,并且请求成功以后success的逻辑也会执行,会导致从A页面直接跳转的C页面。
最好的解决办法可能是①在离开一个页面后可以直接清空该页面组件的相关逻辑,并且停止页面内的请求?我找了一下没有看到这样的配置或者文档,所以不确定是否能够实现,并且如果有的页面逻辑是②:B页面同时发起两个请求task1,task2,在task1请求成功时会跳转页面,而task2的请求只负责传递数据,不做页面交互,那么在task1请求成功后页面跳转,B页面组件卸载直接停止task2请求的话可能也不合适。所以目前比较适合且可以实现的方式可能是在某些情况下手动停止请求。
关于uni.request停止请求的方法可以查看uni.request
直接使用uni.request的abort方法可以满足上方需求,但是写起来会比较麻烦,每一次请求都需要去保存对应的requestTask,在需要的时候再调用requestTask的abort方法。所以为了简化多请求的取消操作/写法,所以封装了一个方法。
我们的项目之前已经封装了uni.request方法,提供了自己的一些默认参数、请求头、返回数据处理以及错误提示等逻辑,这次在已有的基础上多加了取消请求的相关逻辑。这里我的思路是在发起请求时将当前requestTask作为一个对象的值存起来,对应的键可以是随机数或者guid格式的字符串,在请求成功或者失败后从对象中删除该键;当需要取消请求时把对象内的所有requestTask全部调用一次abort方法,以此取消请求。考虑到②的情况,所以暴露出去的request方法可以提供一个参数用于标识该请求是否可以被取消。而如果是希望实现①的情况的话,也可以监听路由变化,或者使用路由守卫,在页面变更时调用统一的取消请求方法。
import Vue from 'vue';
import { guid } from './utils.js';
let canCancelRequestObj = {};
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌过期)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
//校验不通过,则
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
}
/**
- Requests a URL, returning a promise.
- @param {object} [options] The options we want to pass to "request"
-
@return {object} An object containing either "data" or "err"
*/
export default function request(options,canCancel=false) {
// const defaultOptions = {
// credentials: 'include',
// };
let requestId = guid();
console.log('canCancelRequestObj:',canCancelRequestObj);
if(!options.url){
console.log('无请求url')
return
}
const newOptions = {
// ...defaultOptions,
...options,
success(res) {
delete canCancelRequestObj[requestId];
// console.log('response:'+JSON.stringify(res));
if(res.statusCode == '401' || (res.statusCode != '200'&&!res.data)){
uni.hideLoading();
let strResult='';
//清空登陆信息,跳转到登陆页
console.log('登陆信息失效')
if(res.data){
strResult = res.data;
}else{
strResult = res.errMsg;
}
let jsondata = {
result: strResult
};
Vue.prototype && Vue.prototype.$bridge && Vue.prototype.$bridge.callhandler&&Vue.prototype.$bridge.callhandler('jsCallNative', {
action: 'goLogin',
data: JSON.stringify(jsondata)
}, (data) => {}); return } options.success&&options.success(res.data)
},
fail(error) {
if(error&&error.errMsg&&error.errMsg.includes('fail abort')){
console.log(error)
//主动取消的请求
return
}else{
options.fail&&options.fail(error)
}
}
};
let Authorization = window.sessionStorage.getItem('ACCESS_TOKEN');
newOptions.header = {
Authorization:Authorization,
from:'PSM_ANDROID',
timestamp:new Date().getTime(),
};
switch (uni.getSystemInfoSync().platform) {
case 'android':
newOptions.header.from = 'PSM_ANDROID';
break;
case 'ios':
newOptions.header.from = 'PSM_APPLE';
break;
default:
newOptions.header.from = 'PSM_APPLE';break;
}
if (newOptions.method === 'POST' || newOptions.method === 'PUT' || newOptions.method === 'DELETE') {
newOptions.header = {
// Accept: 'application/json',
'content-type': 'application/json;',
...newOptions.header,
};
}
console.log(JSON.stringify(newOptions));
let requestTask = uni.request(newOptions);
if(canCancel){
canCancelRequestObj[requestId] = requestTask;
}
return requestTask;
}
export function cancelRequest (){
console.log(canCancelRequestObj);
if(!canCancelRequestObj||typeof canCancelRequestObj != 'object'){
return
}
Object.keys(canCancelRequestObj).forEach(item=>canCancelRequestObj[item].abort());
}

基于uniCloud/静态托管实现苹果IEP证书类型的ipa下载安装服务
uniCloud 是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编程的云开发平台。
uniCloud
的 web控制台地址:https://unicloud.dcloud.net.cn
Apple Developer Enterprise Program
可让大型组织开发内部专属的 app 并向其员工部署。此计划仅适用于需要使用安全的内部系统或经由移动设备管理解决方案,向员工私密地直接分发的特例情况。详情:https://developer.apple.com/cn/programs/enterprise/
原 IEP
(iOS Developer Enterprise Program),已统一为 Apple Developer Enterprise Program
步骤如下
第1步
开通uniCloud,详情 https://unicloud.dcloud.net.cn/
第2步
在 uniCloud web控制台左侧栏 "前端网页托管" 上传5个文件,目录结构如下
根目录 >
| app_name_v3.0.0.ipa
| manifest.plist
| index.html
| icon_58.png
| icon_512.png
文件目录介绍
app_name_v3.0.0.ipa 是使用`IEP`证书的应用安装包
manifest.plist 是应用的描述文件
index.html 是用户安装应用页面
icon_58.png 是应用显示的图标,分辨率 58x58,名字可自定义
icon_512.png 是应用的最大图标,分辨率 512x512,名字可自定义
manifest.plist
配置
- 新建文件 manifest.plist 文件并粘贴下面
新闻模板
的 manifest.plist 配置,.plist 是扩展名 - 替换 manifest.plist 文件内容为你的应用信息
- 在 manifest.plist 搜索应用名称
新闻模板
- 在 manifest.plist 搜索应用下载地址
https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa
- 在 manifest.plist 搜索icon_58.png图标
https://static-xxxx.bspapp.com/download/icon_58.png
- 在 manifest.plist 搜索icon_512.png图标
https://static-xxxx.bspapp.com/download/icon_512.png
- 在 manifest.plist 搜索应用名称
注意:
https://static-xxxx.bspapp.com/
是前端网页托管的默认域名,腾讯和阿里的不同,可绑定自己的域名manifest.plist
可配置不同设备的.ipa,例如 iPad, iPhone,示例中共用了一个.ipa- 安装包是有有效期的,过期后需要更新证书重新打包
新闻模板
的 manifest.plist
配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/icon_58.png</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/icon_512.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>io.dcloud.new.template</string>
<key>bundle-version</key>
<string>2.4.3</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>新闻模板</string>
</dict>
<key>thinned-assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone6,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone6,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone7,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone8,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone8,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPod7,1</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array/>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone8,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,3</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPhone11,6</string>
<string>iPhone11,2</string>
<string>iPhone11,4</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPad5,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,9</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,12</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,11</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,2</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPad8,1</string>
<string>iPad8,8</string>
<string>iPad8,7</string>
<string>iPad8,6</string>
<string>iPad8,5</string>
<string>iPad8,4</string>
<string>iPad8,3</string>
<string>iPad8,2</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad3,6</string>
<string>iPad3,4</string>
<string>iPad3,2</string>
<string>iPad3,5</string>
<string>iPad3,3</string>
<string>iPad3,1</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone11,8</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPhone11,8</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone10,2</string>
<string>iPhone7,1</string>
<string>iPhone10,6</string>
<string>iPhone10,5</string>
<string>iPhone8,2</string>
<string>iPhone9,4</string>
<string>iPhone10,3</string>
<string>iPhone9,2</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone5,2</string>
<string>iPod5,1</string>
<string>iPhone5,3</string>
<string>iPhone4,1</string>
<string>iPhone5,1</string>
<string>iPhone5,4</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad7,4</string>
<string>iPad4,5</string>
<string>iPad7,5</string>
<string>iPad4,6</string>
<string>iPad6,3</string>
<string>iPad5,1</string>
<string>iPad6,11</string>
<string>iPad7,6</string>
<string>iPad5,2</string>
<string>iPad6,4</string>
<string>iPad4,7</string>
<string>iPad5,3</string>
<string>iPad4,1</string>
<string>iPad4,8</string>
<string>iPad6,12</string>
<string>iPad7,1</string>
<string>iPad5,4</string>
<string>iPad4,9</string>
<string>iPad4,2</string>
<string>iPad7,2</string>
<string>iPad4,3</string>
<string>iPad6,7</string>
<string>iPad7,3</string>
<string>iPad4,4</string>
<string>iPad6,8</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone10,1</string>
<string>iPhone8,1</string>
<string>iPhone9,3</string>
<string>iPhone6,2</string>
<string>iPhone9,1</string>
<string>iPhone7,2</string>
<string>iPod7,1</string>
<string>iPhone10,4</string>
<string>iPhone8,4</string>
<string>iPhone6,1</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad2,3</string>
<string>iPad2,2</string>
<string>iPad2,1</string>
<string>iPad2,7</string>
<string>iPad2,6</string>
<string>iPad2,5</string>
<string>iPad2,4</string>
</array>
</dict>
</array>
</dict>
</array>
</dict>
</plist>
第3步
index.html 内容
需要替换 a
标签的 href
属性,用户点击 a 标签后将弹出下载确认框
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello news</title>
<meta name="author" content="dcloud">
<meta name="description" content="hello news">
<meta name="keywords" content="hello news,dcloud">
<meta name="viewport" content="width=device-width,user-scalable=no">
</head>
<body>
<!-- https://static-xxx.bspapp.com/download/manifest.plist 替换为你的前端网页托管中的 manifest.plist 文件目录 -->
<a href="itms-services://?action=download-manifest&url=https://static-xxx.bspapp.com/download/manifest.plist">下载 iOS hello news</a>
</body>
</html>
第4步
使用手机浏览器访问你的前端网页托管地址,示例: https://static-xxx.bspapp.com/
uniCloud 是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编程的云开发平台。
uniCloud
的 web控制台地址:https://unicloud.dcloud.net.cn
Apple Developer Enterprise Program
可让大型组织开发内部专属的 app 并向其员工部署。此计划仅适用于需要使用安全的内部系统或经由移动设备管理解决方案,向员工私密地直接分发的特例情况。详情:https://developer.apple.com/cn/programs/enterprise/
原 IEP
(iOS Developer Enterprise Program),已统一为 Apple Developer Enterprise Program
步骤如下
第1步
开通uniCloud,详情 https://unicloud.dcloud.net.cn/
第2步
在 uniCloud web控制台左侧栏 "前端网页托管" 上传5个文件,目录结构如下
根目录 >
| app_name_v3.0.0.ipa
| manifest.plist
| index.html
| icon_58.png
| icon_512.png
文件目录介绍
app_name_v3.0.0.ipa 是使用`IEP`证书的应用安装包
manifest.plist 是应用的描述文件
index.html 是用户安装应用页面
icon_58.png 是应用显示的图标,分辨率 58x58,名字可自定义
icon_512.png 是应用的最大图标,分辨率 512x512,名字可自定义
manifest.plist
配置
- 新建文件 manifest.plist 文件并粘贴下面
新闻模板
的 manifest.plist 配置,.plist 是扩展名 - 替换 manifest.plist 文件内容为你的应用信息
- 在 manifest.plist 搜索应用名称
新闻模板
- 在 manifest.plist 搜索应用下载地址
https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa
- 在 manifest.plist 搜索icon_58.png图标
https://static-xxxx.bspapp.com/download/icon_58.png
- 在 manifest.plist 搜索icon_512.png图标
https://static-xxxx.bspapp.com/download/icon_512.png
- 在 manifest.plist 搜索应用名称
注意:
https://static-xxxx.bspapp.com/
是前端网页托管的默认域名,腾讯和阿里的不同,可绑定自己的域名manifest.plist
可配置不同设备的.ipa,例如 iPad, iPhone,示例中共用了一个.ipa- 安装包是有有效期的,过期后需要更新证书重新打包
新闻模板
的 manifest.plist
配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/icon_58.png</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/icon_512.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>io.dcloud.new.template</string>
<key>bundle-version</key>
<string>2.4.3</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>新闻模板</string>
</dict>
<key>thinned-assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone6,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone6,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone7,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone8,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone8,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPod7,1</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array/>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone8,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone11,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone9,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPhone10,3</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPhone11,6</string>
<string>iPhone11,2</string>
<string>iPhone11,4</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPad5,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,9</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,6</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,3</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,7</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,12</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad5,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,11</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,5</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,1</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad7,4</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad8,2</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad6,8</string>
<key>os-version</key>
<string>12</string>
</dict>
<dict>
<key>device</key>
<string>iPad4,2</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPad8,1</string>
<string>iPad8,8</string>
<string>iPad8,7</string>
<string>iPad8,6</string>
<string>iPad8,5</string>
<string>iPad8,4</string>
<string>iPad8,3</string>
<string>iPad8,2</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad3,6</string>
<string>iPad3,4</string>
<string>iPad3,2</string>
<string>iPad3,5</string>
<string>iPad3,3</string>
<string>iPad3,1</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array>
<dict>
<key>device</key>
<string>iPhone11,8</string>
<key>os-version</key>
<string>12</string>
</dict>
</array>
<key>variantIds</key>
<array>
<string>iPhone11,8</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone10,2</string>
<string>iPhone7,1</string>
<string>iPhone10,6</string>
<string>iPhone10,5</string>
<string>iPhone8,2</string>
<string>iPhone9,4</string>
<string>iPhone10,3</string>
<string>iPhone9,2</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone5,2</string>
<string>iPod5,1</string>
<string>iPhone5,3</string>
<string>iPhone4,1</string>
<string>iPhone5,1</string>
<string>iPhone5,4</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad7,4</string>
<string>iPad4,5</string>
<string>iPad7,5</string>
<string>iPad4,6</string>
<string>iPad6,3</string>
<string>iPad5,1</string>
<string>iPad6,11</string>
<string>iPad7,6</string>
<string>iPad5,2</string>
<string>iPad6,4</string>
<string>iPad4,7</string>
<string>iPad5,3</string>
<string>iPad4,1</string>
<string>iPad4,8</string>
<string>iPad6,12</string>
<string>iPad7,1</string>
<string>iPad5,4</string>
<string>iPad4,9</string>
<string>iPad4,2</string>
<string>iPad7,2</string>
<string>iPad4,3</string>
<string>iPad6,7</string>
<string>iPad7,3</string>
<string>iPad4,4</string>
<string>iPad6,8</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPhone10,1</string>
<string>iPhone8,1</string>
<string>iPhone9,3</string>
<string>iPhone6,2</string>
<string>iPhone9,1</string>
<string>iPhone7,2</string>
<string>iPod7,1</string>
<string>iPhone10,4</string>
<string>iPhone8,4</string>
<string>iPhone6,1</string>
</array>
</dict>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://static-xxxx.bspapp.com/download/hello_news_v3.0.0.ipa</string>
<key>variantDescriptors</key>
<array/>
<key>variantIds</key>
<array>
<string>iPad2,3</string>
<string>iPad2,2</string>
<string>iPad2,1</string>
<string>iPad2,7</string>
<string>iPad2,6</string>
<string>iPad2,5</string>
<string>iPad2,4</string>
</array>
</dict>
</array>
</dict>
</array>
</dict>
</plist>
第3步
index.html 内容
需要替换 a
标签的 href
属性,用户点击 a 标签后将弹出下载确认框
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello news</title>
<meta name="author" content="dcloud">
<meta name="description" content="hello news">
<meta name="keywords" content="hello news,dcloud">
<meta name="viewport" content="width=device-width,user-scalable=no">
</head>
<body>
<!-- https://static-xxx.bspapp.com/download/manifest.plist 替换为你的前端网页托管中的 manifest.plist 文件目录 -->
<a href="itms-services://?action=download-manifest&url=https://static-xxx.bspapp.com/download/manifest.plist">下载 iOS hello news</a>
</body>
</html>
第4步
使用手机浏览器访问你的前端网页托管地址,示例: https://static-xxx.bspapp.com/