
《一步步了解iOS APP上架流程,让你的APP顺利进入App Store的大门》
随着Apple Store越来越成熟,以及越来越多的开发者和公司希望在该平台上投放自己的产品,iOS APP上架成为许多开发者和公司普遍关注的话题。但是,由于苹果App Store的审核政策日益严格,大多数开发者和公司都不太清楚iOS APP上架的具体流程。今天,我们将为您介绍iOS APP上架的具体流程,希望可以帮助您顺利的完成iOS APP的上架。
1、准备App Store账号和必要的资料;
2、登录App Store Connect并注册开发者账号;
3、填写应用信息,包括:应用名称、描述、标签、版本等;
4、上传产品报告文件;
5、上传应用截图和APP文件;
6、提交审核;
7、审核通过后,即可在App Store上架。
证书我们这边可以借助辅助工具appuploader
Appuploader可以辅助在Windows、linux或mac系统直接申请iOS证书p12,及上传ipa到App Store,最方便在Windows开发上架没有苹果Mac电脑的开发者!配合本教程使用,可以快速掌握如何真机测试及上架!
点击苹果证书按钮
点击新增
输入证书密码,名称
这个密码不是账号密码,而是一个保护证书的密码,是p12文件的密码,此密码设置后没有其他地方可以找到,忘记了只能删除证书重新制作,所以请务必记住密码。还有为了安全起见,密码不要太简单。 证书名称是你为了在证书列表里面便于区别的一个字符,自己好辨识就可以,尽量是是字母和数字之类
选择证书类型
带distribution的是发布类型,带development的是开发类型。
apple类型=ios+mac,所以开发时选择ios app development和apple development 类型都是可以的
选择bundle id
只有部分类型的证书需要选择bundle id,例如推送证书。因为大部分证书是不和app关联的。而是通过描述文件profile文件关联app。
使用appuploader同步服务
如果期望制作好证书后在其他电脑上同样可以下载到这个证书,或者和你同事同步此证书,则需要勾选使用appuploader服务同步。否则您需要手动管理p12文件在不同电脑之间的传输,并且一但创建下载后,无法在其他电脑下载,只能手动复制文件过去。一般情况下,推荐使用appuploader服务同步。
证书类型说明
IOS开发选择apple development或者ios app development 类型 ios 发布选择 apple distribution或者 ios distribution (app store and ad hoc) 开发推送证书选择 apple push notification service ssl (sandbox) 发布推送证书选择 apple push notification service ssl (sandbox & production)
其他证书不是很常用,可以自行百度各种证书说明
随着Apple Store越来越成熟,以及越来越多的开发者和公司希望在该平台上投放自己的产品,iOS APP上架成为许多开发者和公司普遍关注的话题。但是,由于苹果App Store的审核政策日益严格,大多数开发者和公司都不太清楚iOS APP上架的具体流程。今天,我们将为您介绍iOS APP上架的具体流程,希望可以帮助您顺利的完成iOS APP的上架。
1、准备App Store账号和必要的资料;
2、登录App Store Connect并注册开发者账号;
3、填写应用信息,包括:应用名称、描述、标签、版本等;
4、上传产品报告文件;
5、上传应用截图和APP文件;
6、提交审核;
7、审核通过后,即可在App Store上架。
证书我们这边可以借助辅助工具appuploader
Appuploader可以辅助在Windows、linux或mac系统直接申请iOS证书p12,及上传ipa到App Store,最方便在Windows开发上架没有苹果Mac电脑的开发者!配合本教程使用,可以快速掌握如何真机测试及上架!
点击苹果证书按钮
点击新增
输入证书密码,名称
这个密码不是账号密码,而是一个保护证书的密码,是p12文件的密码,此密码设置后没有其他地方可以找到,忘记了只能删除证书重新制作,所以请务必记住密码。还有为了安全起见,密码不要太简单。 证书名称是你为了在证书列表里面便于区别的一个字符,自己好辨识就可以,尽量是是字母和数字之类
选择证书类型
带distribution的是发布类型,带development的是开发类型。
apple类型=ios+mac,所以开发时选择ios app development和apple development 类型都是可以的
选择bundle id
只有部分类型的证书需要选择bundle id,例如推送证书。因为大部分证书是不和app关联的。而是通过描述文件profile文件关联app。
使用appuploader同步服务
如果期望制作好证书后在其他电脑上同样可以下载到这个证书,或者和你同事同步此证书,则需要勾选使用appuploader服务同步。否则您需要手动管理p12文件在不同电脑之间的传输,并且一但创建下载后,无法在其他电脑下载,只能手动复制文件过去。一般情况下,推荐使用appuploader服务同步。
证书类型说明
IOS开发选择apple development或者ios app development 类型 ios 发布选择 apple distribution或者 ios distribution (app store and ad hoc) 开发推送证书选择 apple push notification service ssl (sandbox) 发布推送证书选择 apple push notification service ssl (sandbox & production)
其他证书不是很常用,可以自行百度各种证书说明

uni-push 2.0 快速接入指南
uni-push 2.0 快速接入指南
简介
此文档用于帮助开发者快速接入集成 uni-push 2.0 ,若想详细了解可以查看 uni-push 2.0 业务文档 。
名词解释
名词 | 解释 |
---|---|
通知消息 | 指定通知标题和内容后,由个推 SDK 自动处理在系统通知栏中展示通知栏消息,同时响铃或震动提醒用户(响铃和震动受手机系统的设置状态影响)。 |
透传消息 | 即自定义消息,消息体格式客户可以自己定义,如纯文本、json 串等。透传消息个推只传递数据,不做任何处理,客户端接收到透传消息后需要自己去做后续动作处理,如通知栏展示、弹框等。 |
ClientId | 个推业务层中的对外用户标识,用于标识客户端身份,由第三方客户端获取并保存到第三方服务端,是个推 SDK 的唯一识别号,简称 CID、cid。 |
在线推送 | app 在前台打开运行时,通过个推渠道下发消息。 |
离线推送 | app在后台、锁屏、进程关闭时,通过厂商渠道下发消息。若未集成 android 多厂商、未配置 ios 推送证书,则该机型无法使用离线推送。 |
消息下发流程

一、客户端集成
1.1 开通 uni-push 2.0 推送服务
unipush 内部封装好了个推及主流厂商 SDK,开发者在使用前必须开通相关服务。
使用 HBuilder 账号登录 开发者中心 ,创建应用、填写应用信息。
1.2 开通离线厂商推送服务
若未完成开通离线厂商推送,只有在 app 在线时才能收到消息
所有的厂商参数都是在下图所示位置配置,配置完成后,必须进行云打包, app 端才会生效
-
Android 多厂商开通:个推与主流安卓厂商合作融合了厂商推送 SDK,在后台配置 “厂商推送设置” 、并云打包后,可以同时使用 “离线推送”,能提高在安卓厂商设备上的消息到达率。
- 目前华为、荣耀、魅族、oppo(测试环境)不要求上架应用商店;vivo 、小米、oppo(正式环境)必须上架应用商店后才可使用离线厂商推送。
-
iOS 推送证书生成:iOS 支持的推送通知功能,从苹果开发者官网导出证书并配置在后台的 “厂商推送设置” 后,可以同时使用 “离线推送”,能提高在 iOS 设备上的消息到达率。
- iOS 使用推送无需上架 Appstore
- iOS 使用推送无需上架 Appstore
-
鸿蒙Next 开通:纯血鸿蒙(HarmonyOS Next)系统的推送,配置鸿蒙Next 参数并进行云打包。
1.3 云打包
打开 HBuilderX (3.5.1及其以上版本),双击项目中的 “manifest.json” 文件,选择“App 模块配置”,向下找到“Push(消息推送)”,勾选后,点击 “uniPush” 下面配置。
注意:如果是 APP 集成推送,云打包请一定要勾选“离线推送”。如果不勾选“离线推送”,则打的包是小程序 SDK(小程序 SDK无法在 app 上稳定使用),且仅支持发在线透传消息,不会有通知栏展示。
云打包可以选择”正式包“和”自定义基座包“,开发测试建议使用 ”自定义基座包“。
1.4 集成验证
1.4.1 客户端获取 cid
假如我要给“张三”打电话,那就需要知道对方的电话标识,即电话号码是多少。 同理,要给某个客户端推送消息,也需要知道该设备的客户端推送标识。
启动 app 后成功获取 cid 则说明云打包 “在线推送” 成功,支持在线推送。
先跟着示例代码简单体验,详细的uni.getPushClientId API介绍 详情参考 代码示例:
// uni-app客户端获取push客户端标记,代码可以实现在App.vue中
uni.getPushClientId({
success: (res) => {
let push_clientid = res.cid
console.log('客户端推送标识cid:',push_clientid)
},
fail(err) {
console.log(err)
}
})
1.4.2 校验厂商离线推送是否集成成功
输入上方获取的 cid ,查询到对应的 Device Token 则说明云打包 “离线推送” 成功,同时支持离线推送。
如果未查询到 device token,则只能 “在线推送” 。若需要使用 “离线推送” 请重新检查 ”1.2 开通离线厂商推送服务“ 。
二、服务端推送消息
注意:使用 uni-push 2.0,服务端不支持用个推 api 推送,只能用 dcloud 提供的 服务端(云函数)推送 。
2.1 云函数创建
注意:扩展库依赖 3 张 opendb 表:opendb-tempdata
,opendb-device
,uni-id-device
。公测版 uniCloud,执行扩展库会自动创建。如果你使用的是 uniCloud 正式版需要自己在 uniCloud
的 web控制台 创建这3张表。
示例如下:
2.2 云函数执行
在云函数文件目录右键(或按快捷键ctrl + r)-> 上传并运行云函数
,此时你的客户端将收到推送消息(应用关闭时为通知栏消息,在线时代码监听到推送消息)。
云函数中调用uni-cloud-push扩展库的sendMessage方法,向客户端推送消息
// 简单的使用示例
'use strict';
const uniPush = uniCloud.getPushManager({appId:"__UNI__XXXXXX"}) //注意这里需要传入你的应用appId
exports.main = async (event, context) => {
return await uniPush.sendMessage({
"push_clientid": "xxx", //填写上一步在uni-app客户端获取到的客户端推送标识push_clientid
"force_notification":true, //填写true,客户端就会对在线消息自动创建“通知栏消息”。
"title": "通知栏显示的标题",
"content": "通知栏显示的内容",
"settings": {
//消息有效期设置,单位毫秒,-1表示不设离线。默认是 2 小时,取值范围:-1 ~ 3 * 24 * 3600 * 1000(3天)之间
"ttl":86400000
},
"payload": {
"text":"体验一下uni-push2.0"
},
"category": {
//HarmonyOS NEXT系统(纯血鸿蒙、非安卓鸿蒙)的消息分类,要给鸿蒙设备推送时才必传
"harmony":"MARKETING"
},
"options":{
"HW": {
// 值为int 类型。1 表示华为测试消息,华为每个应用每日可发送该测试消息500条。此 target_user_type 参数请勿发布至线上。
"/message/android/target_user_type":1
} ,
"HO": {
//值为int 类型。1 表示测试推送,不填默认为0。荣耀每个应用每日可发送该测试消息1000条。此测试参数请勿发布至线上。
"/android/targetUserType": 1
} ,
"VV": {
//值为int 类型。0 表示正式推送;1 表示测试推送,不填默认为0。此 pushMode 参数请勿发布至线上。
"/pushMode":1
} ,
"XM": {
//新小米消息分类下,私信公信id都必须要传,否则请求小米厂商接口会被拦截
"/extra.channel_id": "填写小米平台申请的渠道id"
}
}
})
};
注意:非OPPO、VIVO软件商店官方渠道下载的应用,厂商不提供公信消息服务。
vivo、oppo 对接离线推送时,可以暂时发送测试消息,额外需要分别在 vivo 开放平台 、 oppo开放平台 录入测试用户(regid 对应个推cid 绑定的 device token,可以从个推后台的“故障排查”中查询 cid 信息获取)。
在线消息无额度限制。离线推送各厂商的限额(包含 extra.channel_id 如何申请),详见: 厂商通道限额
HarmonyOS NEXT系统(纯血鸿蒙、非安卓鸿蒙)的消息分类,harmony取值参考 云端通知category取值
如果你希望当应用在线时,也通过“通知栏消息”来提醒用户;可以通过以下两种方式实现:
- 监听到消息内容后,根据业务需要自己判断是否要创建“通知栏消息”,需要就调用创建本地消息API uni.createPushMessage手动创建通知栏消息。
- 服务端执行推送时,传递参数
force_notification:true
,客户端就会自动创建“通知栏消息”(此时你监听不到消息内容),当用户点击通知栏消息后,APP才能监听到消息内容。
先跟着示例代码简单体验,详细的 uniPush.sendMessage API介绍 详情参考
检查确认当前 app 的通知栏权限开启后,则可以开始进行推送测试。
2.2.1 测试在线推送,打开 app 在前台时进行推送
2.2.2 测试离线推送,关闭 app 进程时进行推送
2.2.3 手机成功收到消息展示
恭喜您,如果您已经成功收到 在线 和 离线 的消息展示,那说明已经集成推送成功啦!
2.3 云函数 URL 化 (可选)
在 uniCloud 的云函数中,加载扩展库 uni-cloud-push
,直接调用相关 API,无需额外的服务端配置。
传统服务器开发者(例如:Java、php、python等)需要把这个 云函数URL化 后变成 http 接口,再由代码调用这个 http 接口。
云函数URL化 步骤简介----------->
1,上传步骤云函数代码
2,云函数URL化
3,api 工具测试请求(单推示例)
4,云函数代码示例
// 简单的使用示例
'use strict';
const uniPush = uniCloud.getPushManager({
appId: "你的__UNI__开头的AppId"
})
exports.main = async (event) => {
let obj = JSON.parse(event.body)
const res = await uniPush.sendMessage({
"push_clientid": obj.cids, // 设备id,支持多个以数组的形式指定多个设备,如["cid-1","cid-2"],数组长度不大于1000
"title": obj.title, // 标题
"content": obj.content, // 内容
"settings": obj.settings, // 消息有效期
"payload": obj.payload, // 数据
"category": obj.category, // HarmonyOS NEXT系统(纯血鸿蒙、非安卓鸿蒙)的消息分类,要给鸿蒙设备推送时才必传
"force_notification": true, //填写true,客户端就会对在线消息自动创建“通知栏消息”,不填写则需要客户端自己处理。
"request_id": obj.request_id ,//请求唯一标识号,10-32位之间;如果request_id重复,会导致消息丢失
"options":obj.options //消息分类,没申请可以不传这个参数
})
return res;
};
注意:如果推送时不传入 cid,则发送的是全推(给所有用户推)消息。全推每分钟不能超过5次,10分钟内不能推重复消息体 。 通过返回值中的 taskid 几个首字母可以判断是什么推送。RASS:单推、 RASL:批量推、 RASA:全推
5,postman请求示例
{
"request_id": "212320028909901111",
"cids": "此处填写自定义打包获取的clientid参数",
"title": "通知标题",
"content": "通知内容 ",
"settings": {
//消息有效期设置,单位毫秒,-1表示不设离线。默认是 2 小时,取值范围:-1 ~ 3 * 24 * 3600 * 1000(3天)之间
"ttl": 86400000
"options": {
"HW": {
// 值为int 类型。1 表示华为测试消息,华为每个应用每日可发送该测试消息500条。此 target_user_type 参数请勿发布至线上。
"/message/android/target_user_type": 1
},
"HO": {
//值为int 类型。1 表示测试推送,不填默认为0。荣耀每个应用每日可发送该测试消息1000条。此测试参数请勿发布至线上。
"/android/targetUserType": 1
},
"VV": {
//值为int 类型。0 表示正式推送;1 表示测试推送,不填默认为0。此 pushMode 参数请勿发布至线上。
"/pushMode": 1
},
"XM": {
//新小米消息分类下,私信公信id都必须要传,否则请求小米厂商接口会被拦截
"/extra.channel_id": "填写小米平台申请的渠道id"
}
},
"category": {
//HarmonyOS NEXT系统(纯血鸿蒙、非安卓鸿蒙)的消息分类,要给鸿蒙设备推送时才必传
"harmony":"MARKETING"
},
//payload是点击通知栏消息后,传给客户端click回调的自定义参数
"payload": {
"data1": 1,
"data2": 2
}
}
三、客户端接收消息
3.1 客户端监听推送消息
如果您需要对接收到的消息进行自定义处理,可以再阅读一下本文开头的 “消息下发流程图”。
客户端监听推送消息的代码,需要在收到推送消息之前被执行。所以应当写在应用一启动就会触发的 应用生命周期 onLaunch
中。
示例代码:
//文件路径:项目根目录/App.vue
export default {
onLaunch: function() {
console.log('App Launch')
uni.onPushMessage((res) => {
// 监听【在线、离线】通知栏消息的点击
if(res.type == 'click'){
// 如果需要跳转app内指定页面,则自己实现下方的跳转代码。
uni.navigateTo({
//页面路径示例值:/pages/pushinfo/pushinfo
url:'指定页面路径'
})
}
// 监听【在线】消息到达。若云函数设置了 "force_notification":true,则不会触发此 receive。
if(res.type == 'receive'){
console.log("接收到的消息内容",res.payload);
}
})
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
如果要在receive回调里面,自己创建通知栏消息展示,可以调用 createMessage
先跟着示例代码简单体验,详细的 uni.onPushMessage API介绍 详情参考
四、注意事项
4.1 合规上架
app 若需要上架应用商店,须配置 隐私弹窗 提示用户。
4.2 帮助中心
当您在集成推送服务遇到问题时:
先仔细阅读此接入指南及对应的集成文档,查看是否有遗漏。
阅读 uni-push2.0 常见问题 ,查看是否能解决。
uni-push 2.0 快速接入指南
简介
此文档用于帮助开发者快速接入集成 uni-push 2.0 ,若想详细了解可以查看 uni-push 2.0 业务文档 。
名词解释
名词 | 解释 |
---|---|
通知消息 | 指定通知标题和内容后,由个推 SDK 自动处理在系统通知栏中展示通知栏消息,同时响铃或震动提醒用户(响铃和震动受手机系统的设置状态影响)。 |
透传消息 | 即自定义消息,消息体格式客户可以自己定义,如纯文本、json 串等。透传消息个推只传递数据,不做任何处理,客户端接收到透传消息后需要自己去做后续动作处理,如通知栏展示、弹框等。 |
ClientId | 个推业务层中的对外用户标识,用于标识客户端身份,由第三方客户端获取并保存到第三方服务端,是个推 SDK 的唯一识别号,简称 CID、cid。 |
在线推送 | app 在前台打开运行时,通过个推渠道下发消息。 |
离线推送 | app在后台、锁屏、进程关闭时,通过厂商渠道下发消息。若未集成 android 多厂商、未配置 ios 推送证书,则该机型无法使用离线推送。 |
消息下发流程
一、客户端集成
1.1 开通 uni-push 2.0 推送服务
unipush 内部封装好了个推及主流厂商 SDK,开发者在使用前必须开通相关服务。
使用 HBuilder 账号登录 开发者中心 ,创建应用、填写应用信息。
1.2 开通离线厂商推送服务
若未完成开通离线厂商推送,只有在 app 在线时才能收到消息
所有的厂商参数都是在下图所示位置配置,配置完成后,必须进行云打包, app 端才会生效
-
Android 多厂商开通:个推与主流安卓厂商合作融合了厂商推送 SDK,在后台配置 “厂商推送设置” 、并云打包后,可以同时使用 “离线推送”,能提高在安卓厂商设备上的消息到达率。
- 目前华为、荣耀、魅族、oppo(测试环境)不要求上架应用商店;vivo 、小米、oppo(正式环境)必须上架应用商店后才可使用离线厂商推送。
-
iOS 推送证书生成:iOS 支持的推送通知功能,从苹果开发者官网导出证书并配置在后台的 “厂商推送设置” 后,可以同时使用 “离线推送”,能提高在 iOS 设备上的消息到达率。
- iOS 使用推送无需上架 Appstore
- iOS 使用推送无需上架 Appstore
-
鸿蒙Next 开通:纯血鸿蒙(HarmonyOS Next)系统的推送,配置鸿蒙Next 参数并进行云打包。
1.3 云打包
打开 HBuilderX (3.5.1及其以上版本),双击项目中的 “manifest.json” 文件,选择“App 模块配置”,向下找到“Push(消息推送)”,勾选后,点击 “uniPush” 下面配置。
注意:如果是 APP 集成推送,云打包请一定要勾选“离线推送”。如果不勾选“离线推送”,则打的包是小程序 SDK(小程序 SDK无法在 app 上稳定使用),且仅支持发在线透传消息,不会有通知栏展示。
云打包可以选择”正式包“和”自定义基座包“,开发测试建议使用 ”自定义基座包“。
1.4 集成验证
1.4.1 客户端获取 cid
假如我要给“张三”打电话,那就需要知道对方的电话标识,即电话号码是多少。 同理,要给某个客户端推送消息,也需要知道该设备的客户端推送标识。
启动 app 后成功获取 cid 则说明云打包 “在线推送” 成功,支持在线推送。
先跟着示例代码简单体验,详细的uni.getPushClientId API介绍 详情参考 代码示例:
// uni-app客户端获取push客户端标记,代码可以实现在App.vue中
uni.getPushClientId({
success: (res) => {
let push_clientid = res.cid
console.log('客户端推送标识cid:',push_clientid)
},
fail(err) {
console.log(err)
}
})
1.4.2 校验厂商离线推送是否集成成功
输入上方获取的 cid ,查询到对应的 Device Token 则说明云打包 “离线推送” 成功,同时支持离线推送。
如果未查询到 device token,则只能 “在线推送” 。若需要使用 “离线推送” 请重新检查 ”1.2 开通离线厂商推送服务“ 。
二、服务端推送消息
注意:使用 uni-push 2.0,服务端不支持用个推 api 推送,只能用 dcloud 提供的 服务端(云函数)推送 。
2.1 云函数创建
注意:扩展库依赖 3 张 opendb 表:opendb-tempdata
,opendb-device
,uni-id-device
。公测版 uniCloud,执行扩展库会自动创建。如果你使用的是 uniCloud 正式版需要自己在 uniCloud
的 web控制台 创建这3张表。
示例如下:
2.2 云函数执行
在云函数文件目录右键(或按快捷键ctrl + r)-> 上传并运行云函数
,此时你的客户端将收到推送消息(应用关闭时为通知栏消息,在线时代码监听到推送消息)。
云函数中调用uni-cloud-push扩展库的sendMessage方法,向客户端推送消息
// 简单的使用示例
'use strict';
const uniPush = uniCloud.getPushManager({appId:"__UNI__XXXXXX"}) //注意这里需要传入你的应用appId
exports.main = async (event, context) => {
return await uniPush.sendMessage({
"push_clientid": "xxx", //填写上一步在uni-app客户端获取到的客户端推送标识push_clientid
"force_notification":true, //填写true,客户端就会对在线消息自动创建“通知栏消息”。
"title": "通知栏显示的标题",
"content": "通知栏显示的内容",
"settings": {
//消息有效期设置,单位毫秒,-1表示不设离线。默认是 2 小时,取值范围:-1 ~ 3 * 24 * 3600 * 1000(3天)之间
"ttl":86400000
},
"payload": {
"text":"体验一下uni-push2.0"
},
"category": {
//HarmonyOS NEXT系统(纯血鸿蒙、非安卓鸿蒙)的消息分类,要给鸿蒙设备推送时才必传
"harmony":"MARKETING"
},
"options":{
"HW": {
// 值为int 类型。1 表示华为测试消息,华为每个应用每日可发送该测试消息500条。此 target_user_type 参数请勿发布至线上。
"/message/android/target_user_type":1
} ,
"HO": {
//值为int 类型。1 表示测试推送,不填默认为0。荣耀每个应用每日可发送该测试消息1000条。此测试参数请勿发布至线上。
"/android/targetUserType": 1
} ,
"VV": {
//值为int 类型。0 表示正式推送;1 表示测试推送,不填默认为0。此 pushMode 参数请勿发布至线上。
"/pushMode":1
} ,
"XM": {
//新小米消息分类下,私信公信id都必须要传,否则请求小米厂商接口会被拦截
"/extra.channel_id": "填写小米平台申请的渠道id"
}
}
})
};
注意:非OPPO、VIVO软件商店官方渠道下载的应用,厂商不提供公信消息服务。
vivo、oppo 对接离线推送时,可以暂时发送测试消息,额外需要分别在 vivo 开放平台 、 oppo开放平台 录入测试用户(regid 对应个推cid 绑定的 device token,可以从个推后台的“故障排查”中查询 cid 信息获取)。
在线消息无额度限制。离线推送各厂商的限额(包含 extra.channel_id 如何申请),详见: 厂商通道限额
HarmonyOS NEXT系统(纯血鸿蒙、非安卓鸿蒙)的消息分类,harmony取值参考 云端通知category取值
如果你希望当应用在线时,也通过“通知栏消息”来提醒用户;可以通过以下两种方式实现:
- 监听到消息内容后,根据业务需要自己判断是否要创建“通知栏消息”,需要就调用创建本地消息API uni.createPushMessage手动创建通知栏消息。
- 服务端执行推送时,传递参数
force_notification:true
,客户端就会自动创建“通知栏消息”(此时你监听不到消息内容),当用户点击通知栏消息后,APP才能监听到消息内容。
先跟着示例代码简单体验,详细的 uniPush.sendMessage API介绍 详情参考
检查确认当前 app 的通知栏权限开启后,则可以开始进行推送测试。
2.2.1 测试在线推送,打开 app 在前台时进行推送
2.2.2 测试离线推送,关闭 app 进程时进行推送
2.2.3 手机成功收到消息展示
恭喜您,如果您已经成功收到 在线 和 离线 的消息展示,那说明已经集成推送成功啦!
2.3 云函数 URL 化 (可选)
在 uniCloud 的云函数中,加载扩展库 uni-cloud-push
,直接调用相关 API,无需额外的服务端配置。
传统服务器开发者(例如:Java、php、python等)需要把这个 云函数URL化 后变成 http 接口,再由代码调用这个 http 接口。
云函数URL化 步骤简介----------->
1,上传步骤云函数代码
2,云函数URL化
3,api 工具测试请求(单推示例)
4,云函数代码示例
// 简单的使用示例
'use strict';
const uniPush = uniCloud.getPushManager({
appId: "你的__UNI__开头的AppId"
})
exports.main = async (event) => {
let obj = JSON.parse(event.body)
const res = await uniPush.sendMessage({
"push_clientid": obj.cids, // 设备id,支持多个以数组的形式指定多个设备,如["cid-1","cid-2"],数组长度不大于1000
"title": obj.title, // 标题
"content": obj.content, // 内容
"settings": obj.settings, // 消息有效期
"payload": obj.payload, // 数据
"category": obj.category, // HarmonyOS NEXT系统(纯血鸿蒙、非安卓鸿蒙)的消息分类,要给鸿蒙设备推送时才必传
"force_notification": true, //填写true,客户端就会对在线消息自动创建“通知栏消息”,不填写则需要客户端自己处理。
"request_id": obj.request_id ,//请求唯一标识号,10-32位之间;如果request_id重复,会导致消息丢失
"options":obj.options //消息分类,没申请可以不传这个参数
})
return res;
};
注意:如果推送时不传入 cid,则发送的是全推(给所有用户推)消息。全推每分钟不能超过5次,10分钟内不能推重复消息体 。 通过返回值中的 taskid 几个首字母可以判断是什么推送。RASS:单推、 RASL:批量推、 RASA:全推
5,postman请求示例
{
"request_id": "212320028909901111",
"cids": "此处填写自定义打包获取的clientid参数",
"title": "通知标题",
"content": "通知内容 ",
"settings": {
//消息有效期设置,单位毫秒,-1表示不设离线。默认是 2 小时,取值范围:-1 ~ 3 * 24 * 3600 * 1000(3天)之间
"ttl": 86400000
"options": {
"HW": {
// 值为int 类型。1 表示华为测试消息,华为每个应用每日可发送该测试消息500条。此 target_user_type 参数请勿发布至线上。
"/message/android/target_user_type": 1
},
"HO": {
//值为int 类型。1 表示测试推送,不填默认为0。荣耀每个应用每日可发送该测试消息1000条。此测试参数请勿发布至线上。
"/android/targetUserType": 1
},
"VV": {
//值为int 类型。0 表示正式推送;1 表示测试推送,不填默认为0。此 pushMode 参数请勿发布至线上。
"/pushMode": 1
},
"XM": {
//新小米消息分类下,私信公信id都必须要传,否则请求小米厂商接口会被拦截
"/extra.channel_id": "填写小米平台申请的渠道id"
}
},
"category": {
//HarmonyOS NEXT系统(纯血鸿蒙、非安卓鸿蒙)的消息分类,要给鸿蒙设备推送时才必传
"harmony":"MARKETING"
},
//payload是点击通知栏消息后,传给客户端click回调的自定义参数
"payload": {
"data1": 1,
"data2": 2
}
}
三、客户端接收消息
3.1 客户端监听推送消息
如果您需要对接收到的消息进行自定义处理,可以再阅读一下本文开头的 “消息下发流程图”。
客户端监听推送消息的代码,需要在收到推送消息之前被执行。所以应当写在应用一启动就会触发的 应用生命周期 onLaunch
中。
示例代码:
//文件路径:项目根目录/App.vue
export default {
onLaunch: function() {
console.log('App Launch')
uni.onPushMessage((res) => {
// 监听【在线、离线】通知栏消息的点击
if(res.type == 'click'){
// 如果需要跳转app内指定页面,则自己实现下方的跳转代码。
uni.navigateTo({
//页面路径示例值:/pages/pushinfo/pushinfo
url:'指定页面路径'
})
}
// 监听【在线】消息到达。若云函数设置了 "force_notification":true,则不会触发此 receive。
if(res.type == 'receive'){
console.log("接收到的消息内容",res.payload);
}
})
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
如果要在receive回调里面,自己创建通知栏消息展示,可以调用 createMessage
先跟着示例代码简单体验,详细的 uni.onPushMessage API介绍 详情参考
四、注意事项
4.1 合规上架
app 若需要上架应用商店,须配置 隐私弹窗 提示用户。
4.2 帮助中心
当您在集成推送服务遇到问题时:
先仔细阅读此接入指南及对应的集成文档,查看是否有遗漏。
阅读 uni-push2.0 常见问题 ,查看是否能解决。

wifi打印机打印(二维码,条形码等)
- 打印机,需要插usb(或者微信小程序设置wifi)
- 打印机的默认端口是9100(一般是,具体看购买的打印机)
- wifitool.js是用于连接打印机的
-
此文章是用的CPCL指令,可以根据自己的指令格式修改(var command = tsc.jpPrinter.createNew();)
vue<template> <view> <button @click="printSocket">WiFi打印</button> </view> </template>
js
<script> import tsc from "@/static/libs/tsc.js"; import wifiTool from "@/static/libs/WifiTool.js"; export default { data() { return { ip: "192.168.4.245", port: 9100, str: "yyy", }; }, mounted() { wifiTool.connectWifi("192.168.4.245", 9100); }, methods: { test(str, ip, port) { str = "test"; var Socket = plus.android.importClass("java.net.Socket"); var JavaByte = plus.android.importClass("java.lang.Byte"); var PrintWriter = plus.android.importClass("java.io.PrintWriter"); var BufferedWriter = plus.android.importClass("java.io.BufferedWriter"); var OutputStreamWriter = plus.android.importClass("java.io.OutputStreamWriter"); var BufferedReader = plus.android.importClass("java.io.BufferedReader"); var InputStreamReader = plus.android.importClass("java.io.InputStreamReader"); //测试改良 var StrictMode = plus.android.importClass("android.os.StrictMode"); var Build = plus.android.importClass("android.os.Build"); if (Build.VERSION.SDK_INT > 9) { var policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); } var socket = new Socket(ip, port); var outputStreamWriter = new OutputStreamWriter(socket.getOutputStream()); var bufferWriter = new BufferedWriter(outputStreamWriter); var out = new PrintWriter(bufferWriter, true); var command = tsc.jpPrinter.createNew(); command.init(0, 200, 200, 210, 1); command.setCls(); command.setQR(30, 40, 2, 6, "123"); // command.setMag(2, 2); // command.setText1(55, 30, 180, 60, "地址: 内蒙"); // command.setMag(2, 2); // command.setText1(55, 6, 180, 120, "重量: 15kg"); command.setPagePrint(); let data = command.getData(); out.println(data); socket.setSoTimeout(3000); socket.close(); }, printSocket() { var command = tsc.jpPrinter.createNew(); command.init(0, 200, 250, 210, 1); command.setCls(); command.setQR(30, 40, 2, 6, "123"); command.setMag(2, 2); command.setText1(55, 30, 180, 60, "地址: 内蒙"); command.setMag(2, 2); command.setText1(55, 6, 180, 120, "重量: 15kg"); command.setPostfeed(32);//打印后走纸 command.setPagePrint(); let data = command.getData(); wifiTool.sendData(data); }, connectWifi(ip, port) { if (plus.os.name == "Android") { // plus.nativeUI.showWaiting("正在打印中。。。"); var Socket = plus.android.importClass("java.net.Socket"); var socket; var outputStream; //解决高低版本兼容 var StrictMode = plus.android.importClass("android.os.StrictMode"); var Build = plus.android.importClass("android.os.Build"); if (Build.VERSION.SDK_INT > 9) { var policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); } try { socket = new Socket(ip, port); socket.setKeepAlive(true); outputStream = socket.getOutputStream(); plus.android.importClass(outputStream); // var bytes = plus.android.invoke(str, "getBytes", "gbk"); uni.showToast({ title: "wifi连接成功", }); // plus.nativeUI.closeWaiting(); } catch (e) { console.log("网络连接超时,请重新连接!"); //TODO handle the exception // plus.nativeUI.closeWaiting(); } } }, }, }; </script>
WifiTool.js
```javascript
/**
* 初始化参数
*/
//#ifdef APP-PLUS
var Socket = plus.android.importClass("java.net.Socket");
var socket;
var outputStream;
//解决高低版本兼容
var StrictMode = plus.android.importClass("android.os.StrictMode");
var Build = plus.android.importClass("android.os.Build");
//#endif
/**
* 构造对象
*/
var wifiTool = {
connectWifi(ip, port) {
if (Build.VERSION.SDK_INT > 9) {
var policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
try {
socket = new Socket(ip, port);
socket.setKeepAlive(true);
outputStream = socket.getOutputStream();
plus.android.importClass(outputStream);
// var bytes = plus.android.invoke(str, "getBytes", "gbk");
uni.showToast({
title: 'wifi连接成功',
});
// plus.nativeUI.closeWaiting();
} catch (e) {
console.log("网络连接超时,请重新连接!")
//TODO handle the exception
// plus.nativeUI.closeWaiting();
}
},
sendData(data) {
console.log(outputStream);
outputStream.write(data);
outputStream.flush();
// 关闭socket
// socket.shutdownOutput();
console.log(socket);
}
}
module.exports = wifiTool
tsc.js
/**
* tsc 命令打印工具类
* 2021.04.26 uni-app版本
* @auth boolTrue
*/
var encode = require("./encoding.js");
var jpPrinter = {
createNew: function () {
var jpPrinter = {};
var data = "";
var command = []
var rawCommand = ''
jpPrinter.name = "标签模式";
jpPrinter.init = function (x, y, z, h, q) {
data = "! " + x + " " + y + " " + z + " " + h + " " + q + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.addCommand = function (content) { //将指令转成数组装起
//#ifdef MP-WEIXIN
var code = new encode.TextEncoder(
'gb18030', {
NONSTANDARD_allowLegacyEncoding: true
}).encode(content)
for (var i = 0; i < code.length; ++i) {
command.push(code[i])
}
//#endif
//#ifdef APP-PLUS
let byteCommand = plus.android.invoke(content, 'getBytes', 'gb18030');
for (var i = 0; i < byteCommand.length; ++i) {
command.push(byteCommand[i])
}
//#endif
console.log('command--:', content)
rawCommand += content
}
function intToByte(i) {
// 此处关键 -- android是java平台 byte数值范围是 [-128, 127]
// 因为java平台的byte类型是有符号的 最高位表示符号,所以数值范围固定
// 而图片计算出来的是数值是 0 -255 属于int类型
// 所以把int 转换成byte类型
//#ifdef APP-PLUS
var b = i & 0xFF;
var c = 0;
if (b >= 128) {
c = b % 128;
c = -1 * (128 - c);
} else {
c = b;
}
return c
//#endif
// 而微信小程序不需要,因为小程序api接收的是 无符号8位
//#ifdef MP-WEIXIN
return i
//#endif
}
jpPrinter.setPostfeed = function (length) { //打印之后走纸距离指令
data = "POSTFEED " +length + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setSize = function (pageWidght, pageHeight) { //设置页面大小
data = "SIZE " + pageWidght.toString() + " mm" + "," + pageHeight.toString() + " mm" + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setSpeed = function (printSpeed) { //设置打印机速度
data = "SPEED " + printSpeed.toString() + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setDensity = function (printDensity) { //设置打印机浓度
data = "DENSITY " + printDensity.toString() + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setGap = function (printGap) { //传感器
data = "GAP " + printGap.toString() + " mm\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setCountry = function (country) { //选择国际字符集
/*
001:USA
002:French
003:Latin America
034:Spanish
039:Italian
044:United Kingdom
046:Swedish
047:Norwegian
049:German
*/
data = "COUNTRY " + country + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setCodepage = function (codepage) { //选择国际代码页
/*
8-bit codepage 字符集代表
437:United States
850:Multilingual
852:Slavic
860:Portuguese
863:Canadian/French
865:Nordic
Windows code page
1250:Central Europe
1252:Latin I
1253:Greek
1254:Turkish
以下代码页仅限于 12×24 dot 英数字体
WestEurope:WestEurope
Greek:Greek
Hebrew:Hebrew
EastEurope:EastEurope
Iran:Iran
IranII:IranII
Latvian:Latvian
Arabic:Arabic
Vietnam:Vietnam
Uygur:Uygur
Thai:Thai
1252:Latin I
1257:WPC1257
1251:WPC1251
866:Cyrillic
858:PC858
747:PC747
864:PC864
1001:PC100
*/
data = "CODEPAGE " + codepage + "\r\n";
jpPrinter.addCommand(data)
}
jpPrinter.setCls = function () { //清除打印机缓存
data = "CLS " + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setFeed = function (feed) { //将纸向前推出n
data = "FEED " + feed + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setBackFeed = function (backup) { //将纸向后回拉n
data = "BACKFEED " + backup + "\r\n";
jpPrinter.addCommand(data)
}
jpPrinter.setDirection = function (direction) { //设置打印方向,参考编程手册
data = "DIRECTION " + direction + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setReference = function (x, y) { //设置坐标原点,与打印方向有关
data = "REFERENCE " + x + "," + y + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setFrom = function () { //定位控制指令
data = "FORM \r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setFromfeed = function () { //根据Size进一张标签纸
data = "FORMFEED \r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setHome = function () { //根据Size找到下一张标签纸的位置
data = "HOME \r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setSound = function (level, interval) { //控制蜂鸣器
data = "SOUND " + level + "," + interval + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setLimitfeed = function (limit) { // 检测垂直间距
data = "LIMITFEED " + limit + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setBar = function (x, y, width, height) { //绘制线条
data = "BAR " + x + "," + y + "," + width + "," + height + "\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setBox = function (x_start, y_start, x_end, y_end, thickness) { //绘制方框
data = "BOX " + x_start + "," + y_start + "," + x_end + "," + y_end + "," + thickness + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setErase = function (x_start, y_start, x_width, y_height) { //清除指定区域的数据
data = "ERASE " + x_start + "," + y_start + "," + x_width + "," + y_height + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setReverse = function (x_start, y_start, x_width, y_height) { //将指定的区域反相打印
data = "REVERSE " + x_start + "," + y_start + "," + x_width + "," + y_height + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setText1 = function (font, s, x, y, str) { //打印文字
data = "TEXT " + font + " " + s + " " + x + " " + y + " " + str + "\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setText = function (x, y, font, x_, y_, str) { //打印文字
data = "TEXT " + x + "," + y + ",\"" + font + "\"," + 0 + "," + x_ + "," + y_ + "," + "\"" +
str + "\"\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setMag = function (w,h) { //打印二维码
data = "SETMAG " + w + " " + h + "\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setQR = function (x, y, m, u, content) { //打印二维码
data = "BARCODE QR " + x + " " + y + " M " + m + " U " + u + "\r\n MA," + content + "\r\nENDQR\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setPDF = function (type, x, y,xd,yd,c,s, content) { //打印条形
data = "BARCODE " + type + " " + x + " " + y + " XD " + xd + " YD " + yd + " C " + c + " S " + s + "\r\n"+ content+ "\r\nENDPDF\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setBarCode = function (type, w, r, h, x, y, content) { //打印条形码
data = "BARCODE " + type + " " + w + " " + r + " " + h + " " + x + " " + y + " " + content + "\r\n"
jpPrinter.addCommand(data)
};
// 固定灰度阈值(128以上的都看作白色)
jpPrinter.setBitmap = function (x, y, mode, res) { //添加图片,res为画布参数
var width = parseInt((res.width) / 8 * 8 / 8)
var height = res.height
var imgWidth = res.width
var time = 1;
var temp = res.data.length - width * 32;
var pointList = []
var resultData = []
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
//console.log(res.data)
console.log('---以上是原始数据---')
//for循环顺序不要错了,外层遍历高度,内层遍历宽度,因为横向每8个像素点组成一个字节
for (var y = 0; y < height; y++) {
for (var x = 0; x < imgWidth; x++) {
let r = res.data[(y * imgWidth + x) * 4];
let g = res.data[(y * imgWidth + x) * 4 + 1];
let b = res.data[(y * imgWidth + x) * 4 + 2];
let a = res.data[(y * imgWidth + x) * 4 + 3]
//console.log(`当前${y}行${x}列像素,rgba值:(${r},${g},${b},${a})`)
// 像素灰度值
let grayColor = r * 0.299 + g * 0.587 + b * 0.114
//灰度值大于128位
//1不打印, 0打印 (参考:佳博标签打印机编程手册tspl)
if (grayColor > 128) {
pointList.push(1)
} else {
pointList.push(0)
}
}
}
//console.log(pointList)
for (var i = 0; i < pointList.length; i += 8) {
var p = pointList[i] * 128 + pointList[i + 1] * 64 + pointList[i + 2] * 32 + pointList[i +
3] * 16 + pointList[i + 4] * 8 + pointList[i + 5] * 4 + pointList[i + 6] * 2 +
pointList[i + 7]
resultData.push(p)
}
console.log('最终数据:')
//console.log(resultData)
for (var i = 0; i < resultData.length; ++i) {
command.push(intToByte(resultData[i]))
}
}
jpPrinter.setBitmap2 = function (x, y, mode, res) { //添加图片,res为画布参数
var w = res.width
var width = parseInt((res.width + 7) / 8 * 8 / 8)
var height = res.height;
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
var r = []
var bits = new Uint8Array(height * width);
for (y = 0; y < height; y++) {
for (x = 0; x < w; x++) {
let r = res.data[(y * w + x) * 4];
let g = res.data[(y * w + x) * 4 + 1];
let b = res.data[(y * w + x) * 4 + 2];
let a = res.data[(y * w + x) * 4 + 3]
var color = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) <<
0);
if ((color & 0xFF) > 128) {
bits[parseInt(y * width + x / 8)] |= (0x80 >> (x % 8));
}
}
}
for (var i = 0; i < bits.length; i++) {
//command.push((~bits[i]) & 0xFF);
command.push(intToByte(bits[i]));
//r.push((~bits[i]) & 0xFF);
}
}
// 平均灰度阈值(先计算平均灰度,然后大于平均灰度的都算作白色)
jpPrinter.setBitmap3 = function (x, y, mode, res) { //添加图片,res为画布参数
var width = parseInt((res.width) / 8 * 8 / 8)
var height = res.height
var imgWidth = res.width
var time = 1;
var temp = res.data.length - width * 32;
var pointList = []
var resultData = []
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
//console.log(res.data)
console.log('---以上是原始数据---')
let sumRed = 0,
sumGreen = 0,
sumBlue = 0;
let total = height * imgWidth;
let pix = res.data;
for (var i = 0; i < pix.length; i += 4) {
sumRed += pix[i]
sumGreen += pix[i + 1]
sumBlue += pix[i + 2]
}
let avgRed = parseInt(sumRed / total);
let avgGreen = parseInt(sumGreen / total);
let avgBlue = parseInt(sumBlue / total);
let avgGrayColor = avgRed * 0.299 + avgGreen * 0.587 + avgBlue * 0.114
//for循环顺序不要错了,外层遍历高度,内层遍历宽度,因为横向每8个像素点组成一个字节
for (var y = 0; y < height; y++) {
for (var x = 0; x < imgWidth; x++) {
let r = res.data[(y * imgWidth + x) * 4];
let g = res.data[(y * imgWidth + x) * 4 + 1];
let b = res.data[(y * imgWidth + x) * 4 + 2];
let a = res.data[(y * imgWidth + x) * 4 + 3]
// 像素灰度值
let grayColor = r * 0.299 + g * 0.587 + b * 0.114
//灰度值大于128位
//1不打印, 0打印 (参考:佳博标签打印机编程手册tspl)
if (grayColor > avgGrayColor) {
pointList.push(1)
} else {
pointList.push(0)
}
}
}
//console.log(pointList)
for (var i = 0; i < pointList.length; i += 8) {
var p = pointList[i] * 128 + pointList[i + 1] * 64 + pointList[i + 2] * 32 + pointList[i +
3] * 16 + pointList[i + 4] * 8 + pointList[i + 5] * 4 + pointList[i + 6] * 2 +
pointList[i + 7]
resultData.push(p)
}
console.log('最终数据:')
//console.log(resultData)
for (var i = 0; i < resultData.length; ++i) {
command.push(intToByte(resultData[i]))
}
}
jpPrinter.RawCommand = function (data) {
jpPrinter.addCommand(data)
}
jpPrinter.setPagePrint = function () { //打印页面
// data = "PRINT 1,1\r\n"
data = "PRINT \r\n"
jpPrinter.addCommand(data)
};
//获取打印数据
jpPrinter.getData = function () {
return command;
};
jpPrinter.getRawData = function () {
return rawCommand;
};
jpPrinter.clearCommand = function () {
rawCommand = ''
};
return jpPrinter;
}
};
module.exports.jpPrinter = jpPrinter;
- 打印机,需要插usb(或者微信小程序设置wifi)
- 打印机的默认端口是9100(一般是,具体看购买的打印机)
- wifitool.js是用于连接打印机的
-
此文章是用的CPCL指令,可以根据自己的指令格式修改(var command = tsc.jpPrinter.createNew();)
vue<template> <view> <button @click="printSocket">WiFi打印</button> </view> </template>
js
<script> import tsc from "@/static/libs/tsc.js"; import wifiTool from "@/static/libs/WifiTool.js"; export default { data() { return { ip: "192.168.4.245", port: 9100, str: "yyy", }; }, mounted() { wifiTool.connectWifi("192.168.4.245", 9100); }, methods: { test(str, ip, port) { str = "test"; var Socket = plus.android.importClass("java.net.Socket"); var JavaByte = plus.android.importClass("java.lang.Byte"); var PrintWriter = plus.android.importClass("java.io.PrintWriter"); var BufferedWriter = plus.android.importClass("java.io.BufferedWriter"); var OutputStreamWriter = plus.android.importClass("java.io.OutputStreamWriter"); var BufferedReader = plus.android.importClass("java.io.BufferedReader"); var InputStreamReader = plus.android.importClass("java.io.InputStreamReader"); //测试改良 var StrictMode = plus.android.importClass("android.os.StrictMode"); var Build = plus.android.importClass("android.os.Build"); if (Build.VERSION.SDK_INT > 9) { var policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); } var socket = new Socket(ip, port); var outputStreamWriter = new OutputStreamWriter(socket.getOutputStream()); var bufferWriter = new BufferedWriter(outputStreamWriter); var out = new PrintWriter(bufferWriter, true); var command = tsc.jpPrinter.createNew(); command.init(0, 200, 200, 210, 1); command.setCls(); command.setQR(30, 40, 2, 6, "123"); // command.setMag(2, 2); // command.setText1(55, 30, 180, 60, "地址: 内蒙"); // command.setMag(2, 2); // command.setText1(55, 6, 180, 120, "重量: 15kg"); command.setPagePrint(); let data = command.getData(); out.println(data); socket.setSoTimeout(3000); socket.close(); }, printSocket() { var command = tsc.jpPrinter.createNew(); command.init(0, 200, 250, 210, 1); command.setCls(); command.setQR(30, 40, 2, 6, "123"); command.setMag(2, 2); command.setText1(55, 30, 180, 60, "地址: 内蒙"); command.setMag(2, 2); command.setText1(55, 6, 180, 120, "重量: 15kg"); command.setPostfeed(32);//打印后走纸 command.setPagePrint(); let data = command.getData(); wifiTool.sendData(data); }, connectWifi(ip, port) { if (plus.os.name == "Android") { // plus.nativeUI.showWaiting("正在打印中。。。"); var Socket = plus.android.importClass("java.net.Socket"); var socket; var outputStream; //解决高低版本兼容 var StrictMode = plus.android.importClass("android.os.StrictMode"); var Build = plus.android.importClass("android.os.Build"); if (Build.VERSION.SDK_INT > 9) { var policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); } try { socket = new Socket(ip, port); socket.setKeepAlive(true); outputStream = socket.getOutputStream(); plus.android.importClass(outputStream); // var bytes = plus.android.invoke(str, "getBytes", "gbk"); uni.showToast({ title: "wifi连接成功", }); // plus.nativeUI.closeWaiting(); } catch (e) { console.log("网络连接超时,请重新连接!"); //TODO handle the exception // plus.nativeUI.closeWaiting(); } } }, }, }; </script>
WifiTool.js
```javascript
/**
* 初始化参数
*/
//#ifdef APP-PLUS
var Socket = plus.android.importClass("java.net.Socket");
var socket;
var outputStream;
//解决高低版本兼容
var StrictMode = plus.android.importClass("android.os.StrictMode");
var Build = plus.android.importClass("android.os.Build");
//#endif
/**
* 构造对象
*/
var wifiTool = {
connectWifi(ip, port) {
if (Build.VERSION.SDK_INT > 9) {
var policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
try {
socket = new Socket(ip, port);
socket.setKeepAlive(true);
outputStream = socket.getOutputStream();
plus.android.importClass(outputStream);
// var bytes = plus.android.invoke(str, "getBytes", "gbk");
uni.showToast({
title: 'wifi连接成功',
});
// plus.nativeUI.closeWaiting();
} catch (e) {
console.log("网络连接超时,请重新连接!")
//TODO handle the exception
// plus.nativeUI.closeWaiting();
}
},
sendData(data) {
console.log(outputStream);
outputStream.write(data);
outputStream.flush();
// 关闭socket
// socket.shutdownOutput();
console.log(socket);
}
}
module.exports = wifiTool
tsc.js
/**
* tsc 命令打印工具类
* 2021.04.26 uni-app版本
* @auth boolTrue
*/
var encode = require("./encoding.js");
var jpPrinter = {
createNew: function () {
var jpPrinter = {};
var data = "";
var command = []
var rawCommand = ''
jpPrinter.name = "标签模式";
jpPrinter.init = function (x, y, z, h, q) {
data = "! " + x + " " + y + " " + z + " " + h + " " + q + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.addCommand = function (content) { //将指令转成数组装起
//#ifdef MP-WEIXIN
var code = new encode.TextEncoder(
'gb18030', {
NONSTANDARD_allowLegacyEncoding: true
}).encode(content)
for (var i = 0; i < code.length; ++i) {
command.push(code[i])
}
//#endif
//#ifdef APP-PLUS
let byteCommand = plus.android.invoke(content, 'getBytes', 'gb18030');
for (var i = 0; i < byteCommand.length; ++i) {
command.push(byteCommand[i])
}
//#endif
console.log('command--:', content)
rawCommand += content
}
function intToByte(i) {
// 此处关键 -- android是java平台 byte数值范围是 [-128, 127]
// 因为java平台的byte类型是有符号的 最高位表示符号,所以数值范围固定
// 而图片计算出来的是数值是 0 -255 属于int类型
// 所以把int 转换成byte类型
//#ifdef APP-PLUS
var b = i & 0xFF;
var c = 0;
if (b >= 128) {
c = b % 128;
c = -1 * (128 - c);
} else {
c = b;
}
return c
//#endif
// 而微信小程序不需要,因为小程序api接收的是 无符号8位
//#ifdef MP-WEIXIN
return i
//#endif
}
jpPrinter.setPostfeed = function (length) { //打印之后走纸距离指令
data = "POSTFEED " +length + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setSize = function (pageWidght, pageHeight) { //设置页面大小
data = "SIZE " + pageWidght.toString() + " mm" + "," + pageHeight.toString() + " mm" + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setSpeed = function (printSpeed) { //设置打印机速度
data = "SPEED " + printSpeed.toString() + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setDensity = function (printDensity) { //设置打印机浓度
data = "DENSITY " + printDensity.toString() + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setGap = function (printGap) { //传感器
data = "GAP " + printGap.toString() + " mm\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setCountry = function (country) { //选择国际字符集
/*
001:USA
002:French
003:Latin America
034:Spanish
039:Italian
044:United Kingdom
046:Swedish
047:Norwegian
049:German
*/
data = "COUNTRY " + country + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setCodepage = function (codepage) { //选择国际代码页
/*
8-bit codepage 字符集代表
437:United States
850:Multilingual
852:Slavic
860:Portuguese
863:Canadian/French
865:Nordic
Windows code page
1250:Central Europe
1252:Latin I
1253:Greek
1254:Turkish
以下代码页仅限于 12×24 dot 英数字体
WestEurope:WestEurope
Greek:Greek
Hebrew:Hebrew
EastEurope:EastEurope
Iran:Iran
IranII:IranII
Latvian:Latvian
Arabic:Arabic
Vietnam:Vietnam
Uygur:Uygur
Thai:Thai
1252:Latin I
1257:WPC1257
1251:WPC1251
866:Cyrillic
858:PC858
747:PC747
864:PC864
1001:PC100
*/
data = "CODEPAGE " + codepage + "\r\n";
jpPrinter.addCommand(data)
}
jpPrinter.setCls = function () { //清除打印机缓存
data = "CLS " + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setFeed = function (feed) { //将纸向前推出n
data = "FEED " + feed + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setBackFeed = function (backup) { //将纸向后回拉n
data = "BACKFEED " + backup + "\r\n";
jpPrinter.addCommand(data)
}
jpPrinter.setDirection = function (direction) { //设置打印方向,参考编程手册
data = "DIRECTION " + direction + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setReference = function (x, y) { //设置坐标原点,与打印方向有关
data = "REFERENCE " + x + "," + y + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setFrom = function () { //定位控制指令
data = "FORM \r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setFromfeed = function () { //根据Size进一张标签纸
data = "FORMFEED \r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setHome = function () { //根据Size找到下一张标签纸的位置
data = "HOME \r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setSound = function (level, interval) { //控制蜂鸣器
data = "SOUND " + level + "," + interval + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setLimitfeed = function (limit) { // 检测垂直间距
data = "LIMITFEED " + limit + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setBar = function (x, y, width, height) { //绘制线条
data = "BAR " + x + "," + y + "," + width + "," + height + "\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setBox = function (x_start, y_start, x_end, y_end, thickness) { //绘制方框
data = "BOX " + x_start + "," + y_start + "," + x_end + "," + y_end + "," + thickness + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setErase = function (x_start, y_start, x_width, y_height) { //清除指定区域的数据
data = "ERASE " + x_start + "," + y_start + "," + x_width + "," + y_height + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setReverse = function (x_start, y_start, x_width, y_height) { //将指定的区域反相打印
data = "REVERSE " + x_start + "," + y_start + "," + x_width + "," + y_height + "\r\n";
jpPrinter.addCommand(data)
};
jpPrinter.setText1 = function (font, s, x, y, str) { //打印文字
data = "TEXT " + font + " " + s + " " + x + " " + y + " " + str + "\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setText = function (x, y, font, x_, y_, str) { //打印文字
data = "TEXT " + x + "," + y + ",\"" + font + "\"," + 0 + "," + x_ + "," + y_ + "," + "\"" +
str + "\"\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setMag = function (w,h) { //打印二维码
data = "SETMAG " + w + " " + h + "\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setQR = function (x, y, m, u, content) { //打印二维码
data = "BARCODE QR " + x + " " + y + " M " + m + " U " + u + "\r\n MA," + content + "\r\nENDQR\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setPDF = function (type, x, y,xd,yd,c,s, content) { //打印条形
data = "BARCODE " + type + " " + x + " " + y + " XD " + xd + " YD " + yd + " C " + c + " S " + s + "\r\n"+ content+ "\r\nENDPDF\r\n"
jpPrinter.addCommand(data)
};
jpPrinter.setBarCode = function (type, w, r, h, x, y, content) { //打印条形码
data = "BARCODE " + type + " " + w + " " + r + " " + h + " " + x + " " + y + " " + content + "\r\n"
jpPrinter.addCommand(data)
};
// 固定灰度阈值(128以上的都看作白色)
jpPrinter.setBitmap = function (x, y, mode, res) { //添加图片,res为画布参数
var width = parseInt((res.width) / 8 * 8 / 8)
var height = res.height
var imgWidth = res.width
var time = 1;
var temp = res.data.length - width * 32;
var pointList = []
var resultData = []
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
//console.log(res.data)
console.log('---以上是原始数据---')
//for循环顺序不要错了,外层遍历高度,内层遍历宽度,因为横向每8个像素点组成一个字节
for (var y = 0; y < height; y++) {
for (var x = 0; x < imgWidth; x++) {
let r = res.data[(y * imgWidth + x) * 4];
let g = res.data[(y * imgWidth + x) * 4 + 1];
let b = res.data[(y * imgWidth + x) * 4 + 2];
let a = res.data[(y * imgWidth + x) * 4 + 3]
//console.log(`当前${y}行${x}列像素,rgba值:(${r},${g},${b},${a})`)
// 像素灰度值
let grayColor = r * 0.299 + g * 0.587 + b * 0.114
//灰度值大于128位
//1不打印, 0打印 (参考:佳博标签打印机编程手册tspl)
if (grayColor > 128) {
pointList.push(1)
} else {
pointList.push(0)
}
}
}
//console.log(pointList)
for (var i = 0; i < pointList.length; i += 8) {
var p = pointList[i] * 128 + pointList[i + 1] * 64 + pointList[i + 2] * 32 + pointList[i +
3] * 16 + pointList[i + 4] * 8 + pointList[i + 5] * 4 + pointList[i + 6] * 2 +
pointList[i + 7]
resultData.push(p)
}
console.log('最终数据:')
//console.log(resultData)
for (var i = 0; i < resultData.length; ++i) {
command.push(intToByte(resultData[i]))
}
}
jpPrinter.setBitmap2 = function (x, y, mode, res) { //添加图片,res为画布参数
var w = res.width
var width = parseInt((res.width + 7) / 8 * 8 / 8)
var height = res.height;
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
var r = []
var bits = new Uint8Array(height * width);
for (y = 0; y < height; y++) {
for (x = 0; x < w; x++) {
let r = res.data[(y * w + x) * 4];
let g = res.data[(y * w + x) * 4 + 1];
let b = res.data[(y * w + x) * 4 + 2];
let a = res.data[(y * w + x) * 4 + 3]
var color = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) <<
0);
if ((color & 0xFF) > 128) {
bits[parseInt(y * width + x / 8)] |= (0x80 >> (x % 8));
}
}
}
for (var i = 0; i < bits.length; i++) {
//command.push((~bits[i]) & 0xFF);
command.push(intToByte(bits[i]));
//r.push((~bits[i]) & 0xFF);
}
}
// 平均灰度阈值(先计算平均灰度,然后大于平均灰度的都算作白色)
jpPrinter.setBitmap3 = function (x, y, mode, res) { //添加图片,res为画布参数
var width = parseInt((res.width) / 8 * 8 / 8)
var height = res.height
var imgWidth = res.width
var time = 1;
var temp = res.data.length - width * 32;
var pointList = []
var resultData = []
console.log(width + "--" + height)
data = "BITMAP " + x + "," + y + "," + width + "," + height + "," + mode + ","
jpPrinter.addCommand(data)
//console.log(res.data)
console.log('---以上是原始数据---')
let sumRed = 0,
sumGreen = 0,
sumBlue = 0;
let total = height * imgWidth;
let pix = res.data;
for (var i = 0; i < pix.length; i += 4) {
sumRed += pix[i]
sumGreen += pix[i + 1]
sumBlue += pix[i + 2]
}
let avgRed = parseInt(sumRed / total);
let avgGreen = parseInt(sumGreen / total);
let avgBlue = parseInt(sumBlue / total);
let avgGrayColor = avgRed * 0.299 + avgGreen * 0.587 + avgBlue * 0.114
//for循环顺序不要错了,外层遍历高度,内层遍历宽度,因为横向每8个像素点组成一个字节
for (var y = 0; y < height; y++) {
for (var x = 0; x < imgWidth; x++) {
let r = res.data[(y * imgWidth + x) * 4];
let g = res.data[(y * imgWidth + x) * 4 + 1];
let b = res.data[(y * imgWidth + x) * 4 + 2];
let a = res.data[(y * imgWidth + x) * 4 + 3]
// 像素灰度值
let grayColor = r * 0.299 + g * 0.587 + b * 0.114
//灰度值大于128位
//1不打印, 0打印 (参考:佳博标签打印机编程手册tspl)
if (grayColor > avgGrayColor) {
pointList.push(1)
} else {
pointList.push(0)
}
}
}
//console.log(pointList)
for (var i = 0; i < pointList.length; i += 8) {
var p = pointList[i] * 128 + pointList[i + 1] * 64 + pointList[i + 2] * 32 + pointList[i +
3] * 16 + pointList[i + 4] * 8 + pointList[i + 5] * 4 + pointList[i + 6] * 2 +
pointList[i + 7]
resultData.push(p)
}
console.log('最终数据:')
//console.log(resultData)
for (var i = 0; i < resultData.length; ++i) {
command.push(intToByte(resultData[i]))
}
}
jpPrinter.RawCommand = function (data) {
jpPrinter.addCommand(data)
}
jpPrinter.setPagePrint = function () { //打印页面
// data = "PRINT 1,1\r\n"
data = "PRINT \r\n"
jpPrinter.addCommand(data)
};
//获取打印数据
jpPrinter.getData = function () {
return command;
};
jpPrinter.getRawData = function () {
return rawCommand;
};
jpPrinter.clearCommand = function () {
rawCommand = ''
};
return jpPrinter;
}
};
module.exports.jpPrinter = jpPrinter;
收起阅读 »

蓝牙连接便携式打印机(芝柯)
vue
<template>
<view>
<button @click="searchBle">搜索蓝牙</button>
<view style="margin-top: 30upx" :key="index" v-for="(item, index) in devices">
<button style="width: 400upx; color: #0081ff" @click="onConn(item)">
{{ item.name }}
</button>
</view>
<button style="margin-top: 100upx" @click="senBleLabel()">标签打印</button>
</view>
</template>
js
<script>
import tsc from "@/static/libs/tsc.js";
import bluetoothTool from "@/static/libs/BluetoothTool.js";
export default {
data() {
return {
devices: [],
currDev: null,
connId: "",
piaojuText: "",
tableDomId: "",
tableImgPath: "",
canvasWidth: 80,
canvasHeight: 60,
msg: "",
count: 0,
};
},
watch: {
msg() {
uni.showToast({
title: this.msg,
});
},
},
onReady() {
this.renderCanvas();
},
mounted() {
//#ifdef APP-PLUS
// 蓝牙
bluetoothTool.init({
listenBTStatusCallback: (state) => {
if (state == "STATE_ON") {
let lastBleAddress = uni.getStorageSync("lastBleAddress");
if (lastBleAddress) {
uni.showLoading({
title: "正在连接...",
});
console.log(lastBleAddress);
bluetoothTool.connDevice(lastBleAddress, (result) => {
uni.hideLoading();
uni.showToast({
title: result ? "连接成功!" : "连接失败...",
});
let _this = this;
let countSet = setInterval(() => {
if (_this.count == 5) {
clearInterval(countSet);
} else {
_this.count ;
_this.senBleLabel();
}
}, 2000);
});
}
}
},
discoveryDeviceCallback: this.onDevice,
discoveryFinishedCallback: function () {
that.msg = "搜索完成";
},
readDataCallback: function (dataByteArr) {
// 读取蓝牙返回的数据
/* if(that.receiveDataArr.length >= 200) {
that.receiveDataArr = [];
}
that.receiveDataArr.push.apply(that.receiveDataArr, dataByteArr); */
},
connExceptionCallback: function (e) {
console.log(e);
that.msg = "设备连接失败";
},
});
//#endif
},
methods: {
destroyed: function () {
console.log("destroyed----------");
if (this.connId != "") {
uni.closeBLEConnection({
deviceId: this.connId,
success(res) {
console.log(res);
},
});
}
},
searchBle() {
var that = this;
console.log("initBule");
// 使用openBluetoothAdapter 接口,免去主动申请权限的麻烦
uni.openBluetoothAdapter({
success(res) {
this.devices = [];
console.log("打开 蓝牙模块,开始搜索模式...");
console.log(res);
bluetoothTool.discoveryNewDevice();
//that.onDevice()
},
});
},
onDevice(newDevice) {
console.log("监听寻找到新设备的事件---------------");
console.log(newDevice);
if (newDevice.name && newDevice.name != "null") {
this.devices.push({
name: newDevice.name,
address: newDevice.address,
});
}
},
stopFindBule() {
console.log("停止搜寻附近的蓝牙外围设备---------------");
uni.stopBluetoothDevicesDiscovery({
success(res) {
console.log(res);
},
});
},
onConn(item) {
console.log("连接蓝牙---------------" + item.address);
if (this.selectDevices.includes(item.address)) {
that.msg = "该蓝牙已连接";
}
bluetoothTool.connDevice(item.address, (result) => {
if (result) {
if (this.selectDevices.length == 3) {
this.selectDevices.shift();
}
this.selectDevices.push(item);
uni.setStorageSync("selectDevices", this.selectDevices);
}
console.log("连接结果:", result);
});
},
senBleLabel() {
var command = tsc.jpPrinter.createNew();
command.init(0, 200, 200, 210, 1);
command.setCls();
command.setQR(30, 40, 2, 6, "123");
command.setMag(2, 2);
command.setText1(55, 30, 180, 60, "地址: 内蒙");
command.setMag(2, 2);
command.setText1(55, 6, 180, 120, "重量: 15kg");
command.setPagePrint();
let data = command.getData();
bluetoothTool.sendByteData(data);
},
},
};
</script>
vue
<template>
<view>
<button @click="searchBle">搜索蓝牙</button>
<view style="margin-top: 30upx" :key="index" v-for="(item, index) in devices">
<button style="width: 400upx; color: #0081ff" @click="onConn(item)">
{{ item.name }}
</button>
</view>
<button style="margin-top: 100upx" @click="senBleLabel()">标签打印</button>
</view>
</template>
js
<script>
import tsc from "@/static/libs/tsc.js";
import bluetoothTool from "@/static/libs/BluetoothTool.js";
export default {
data() {
return {
devices: [],
currDev: null,
connId: "",
piaojuText: "",
tableDomId: "",
tableImgPath: "",
canvasWidth: 80,
canvasHeight: 60,
msg: "",
count: 0,
};
},
watch: {
msg() {
uni.showToast({
title: this.msg,
});
},
},
onReady() {
this.renderCanvas();
},
mounted() {
//#ifdef APP-PLUS
// 蓝牙
bluetoothTool.init({
listenBTStatusCallback: (state) => {
if (state == "STATE_ON") {
let lastBleAddress = uni.getStorageSync("lastBleAddress");
if (lastBleAddress) {
uni.showLoading({
title: "正在连接...",
});
console.log(lastBleAddress);
bluetoothTool.connDevice(lastBleAddress, (result) => {
uni.hideLoading();
uni.showToast({
title: result ? "连接成功!" : "连接失败...",
});
let _this = this;
let countSet = setInterval(() => {
if (_this.count == 5) {
clearInterval(countSet);
} else {
_this.count ;
_this.senBleLabel();
}
}, 2000);
});
}
}
},
discoveryDeviceCallback: this.onDevice,
discoveryFinishedCallback: function () {
that.msg = "搜索完成";
},
readDataCallback: function (dataByteArr) {
// 读取蓝牙返回的数据
/* if(that.receiveDataArr.length >= 200) {
that.receiveDataArr = [];
}
that.receiveDataArr.push.apply(that.receiveDataArr, dataByteArr); */
},
connExceptionCallback: function (e) {
console.log(e);
that.msg = "设备连接失败";
},
});
//#endif
},
methods: {
destroyed: function () {
console.log("destroyed----------");
if (this.connId != "") {
uni.closeBLEConnection({
deviceId: this.connId,
success(res) {
console.log(res);
},
});
}
},
searchBle() {
var that = this;
console.log("initBule");
// 使用openBluetoothAdapter 接口,免去主动申请权限的麻烦
uni.openBluetoothAdapter({
success(res) {
this.devices = [];
console.log("打开 蓝牙模块,开始搜索模式...");
console.log(res);
bluetoothTool.discoveryNewDevice();
//that.onDevice()
},
});
},
onDevice(newDevice) {
console.log("监听寻找到新设备的事件---------------");
console.log(newDevice);
if (newDevice.name && newDevice.name != "null") {
this.devices.push({
name: newDevice.name,
address: newDevice.address,
});
}
},
stopFindBule() {
console.log("停止搜寻附近的蓝牙外围设备---------------");
uni.stopBluetoothDevicesDiscovery({
success(res) {
console.log(res);
},
});
},
onConn(item) {
console.log("连接蓝牙---------------" + item.address);
if (this.selectDevices.includes(item.address)) {
that.msg = "该蓝牙已连接";
}
bluetoothTool.connDevice(item.address, (result) => {
if (result) {
if (this.selectDevices.length == 3) {
this.selectDevices.shift();
}
this.selectDevices.push(item);
uni.setStorageSync("selectDevices", this.selectDevices);
}
console.log("连接结果:", result);
});
},
senBleLabel() {
var command = tsc.jpPrinter.createNew();
command.init(0, 200, 200, 210, 1);
command.setCls();
command.setQR(30, 40, 2, 6, "123");
command.setMag(2, 2);
command.setText1(55, 30, 180, 60, "地址: 内蒙");
command.setMag(2, 2);
command.setText1(55, 6, 180, 120, "重量: 15kg");
command.setPagePrint();
let data = command.getData();
bluetoothTool.sendByteData(data);
},
},
};
</script>
收起阅读 »

在uniapp中使用lodash
众所周知,小程序的全局对象跟browser的全局对象不同,所以lodash使用browser全局对象下的api会报错。这就需要在uniapp中根据小程序环境引入polyfill。要特殊处理的东西不是很多,就是把Array,DataView等等这些api挂在到小程序环境下的global中。
大部分都会选择直接在项目中实现这个polyfill,那么问题又来了,在项目中定义的ployfill引入执行始终会在npm包之后。因为npm包会被打包到vendor中,这个包因为包含了uniapp的运行时,所以被提前导入。这样的话引入lodash,lodash初始化执行获取全局对象global对应api的时候,ployfill还没有被导入执行,所以还是会报错。想要解决这个问题,最好的方法就是使用npm包的ployfill并提前于lodash导入。
我自己已经构建发布了一个npm包 lodash-miniprogram-polyfill 可以直接使用。想要自己实现也可以,不过都需要发布成一个npm包。
使用的时候也有说法,因为ployfill必须要早于lodash初始化执行前被执行,所以推荐在一个文件中先导入ployfill,同时导入导出lodash的api,项目中使用的时候就直接从这个文件中导入lodash就行了,有类型推断的话也能保留。推荐使用lodash-es,这样可以按需导入,减少打包的体积。
示例
-/src/utils/lodashFix.ts
// #ifdef MP-WEIXIN
import 'lodash-miniprogram-polyfill'
// #endif
export * from 'lodash-es'
-/src/pages/index/index.vue
<script setup lang="ts">
import { throttle } from '@/utils/lodashFix'
</script>
众所周知,小程序的全局对象跟browser的全局对象不同,所以lodash使用browser全局对象下的api会报错。这就需要在uniapp中根据小程序环境引入polyfill。要特殊处理的东西不是很多,就是把Array,DataView等等这些api挂在到小程序环境下的global中。
大部分都会选择直接在项目中实现这个polyfill,那么问题又来了,在项目中定义的ployfill引入执行始终会在npm包之后。因为npm包会被打包到vendor中,这个包因为包含了uniapp的运行时,所以被提前导入。这样的话引入lodash,lodash初始化执行获取全局对象global对应api的时候,ployfill还没有被导入执行,所以还是会报错。想要解决这个问题,最好的方法就是使用npm包的ployfill并提前于lodash导入。
我自己已经构建发布了一个npm包 lodash-miniprogram-polyfill 可以直接使用。想要自己实现也可以,不过都需要发布成一个npm包。
使用的时候也有说法,因为ployfill必须要早于lodash初始化执行前被执行,所以推荐在一个文件中先导入ployfill,同时导入导出lodash的api,项目中使用的时候就直接从这个文件中导入lodash就行了,有类型推断的话也能保留。推荐使用lodash-es,这样可以按需导入,减少打包的体积。
示例
-/src/utils/lodashFix.ts
// #ifdef MP-WEIXIN
import 'lodash-miniprogram-polyfill'
// #endif
export * from 'lodash-es'
-/src/pages/index/index.vue
<script setup lang="ts">
import { throttle } from '@/utils/lodashFix'
</script>
收起阅读 »

不修改组件代码,uni-data-picker选择中间节点的方法
模板中添加事件
<uni-data-picker :step-searh="true" self-field="_id" parent-field="parent_id" v-model="formData.d_ids" collection="opendb-department" field="_id as value, name as text,parent_id" @nodeclick="nodeclick" @popupclosed="popupclosed"></uni-data-picker>
data()函数的return部分添加个变量
return {
tempPdid:"",
方法中添加上面事件对应的方法
methods: {
nodeclick(e){
this.tempPdid = e.value
},
popupclosed(){
this.formData.d_ids = this.tempPdid // 这个例子是用户修改页,d_ids是uni-id-users表的部门字段
},
操作效果:点击任意节点,再点击选框之外,会自动关闭选框,选项即生效。
模板中添加事件
<uni-data-picker :step-searh="true" self-field="_id" parent-field="parent_id" v-model="formData.d_ids" collection="opendb-department" field="_id as value, name as text,parent_id" @nodeclick="nodeclick" @popupclosed="popupclosed"></uni-data-picker>
data()函数的return部分添加个变量
return {
tempPdid:"",
方法中添加上面事件对应的方法
methods: {
nodeclick(e){
this.tempPdid = e.value
},
popupclosed(){
this.formData.d_ids = this.tempPdid // 这个例子是用户修改页,d_ids是uni-id-users表的部门字段
},
操作效果:点击任意节点,再点击选框之外,会自动关闭选框,选项即生效。
收起阅读 »
[ios开发]-APP-上架流程
由于苹果的机制,在非越狱机器上安装必须通过官方的Appstore, 开发者开发好应用后上传Appstore,也需要通过审核等环节。 AppCan作为一个跨主流平台的一个开发平台,也对ipa包上传Appstore作了支持。 本文从三个流程来介绍如何实现AppCan在 线编译出ipa包,以及上传到苹果Appstore。
一、证书的导出
1.1、前期工作
首先你需要有一个苹果的开发者帐号,一个mac系统。 如果没有帐号可以在打开申请加入苹果的开发者 计划。支付99美元每年,怎么申请网上有详细的介绍,在此不多做介绍。
如果你已经有了一个IDP,打开并登录到苹果MemberCenter,见下图
登录以后可以看到下面这个界面,列出了你开发需要的一些工具,支持,itunes app管理等内容。
选择第二项:Ios provisioning Portal,进入,所有证书相关的都在这里进行。
1.2、申请appid
在下图的左边选择 App IDs,我们先创建一个AppId,对于要发布到Appstore上的程序, 都有一个唯一的AppId,下面会列出你当前所有的AppId
我们点击右上角的New App ID
其中有两项需要你自己填:
第一个Description,用来描述你的appid,这个随便填,没有什么限制;
第二项Bundle Identifier (App ID Suffix),这是你appid的后缀,这个需要仔细, 因为这个内容和你的程序直接相关,后面很多地方要用到,最好是 com.yourcompany.yourappname的格式,当然没有公司名的个人开发者, 第二项可以用你自己的英文名字或者拼音,如下图
时需要填写的iapp IDs就是你再此输入的第二项内容
填完后submit,如下图,可以看见我们已经生成的appid:ebook appid。想要支持推送服务和icould等也可以在这儿配置:
1.3、申请发布证书
1.3.1、先创建一个证书请求文件
这儿需要一个mac系统。以下内容以雪豹系统为例,其他版本差别不是很大。
首先打开应用程序-?实用工具-?钥匙串访问(KEY CHAIN),在证书助理中,选择"从证书颁发机构求证书",如下图
在下图所示的界面,你的电子邮件地址:填你申请idp的电子邮件地址,常用名称,默认就好,CA空, 选择存贮到磁盘,点击"继续":
选择保存的位置,比如选择桌面
下一步点击完成,你就可以看到你的桌面多了一个CertificateSigningRequest.certSigningRequest的证 书请求文件。
1.3.2、安装WWDR证书
继续登录到你的MEMBER CENTER,选择左边的certificates项,可以看到它右边有四个选项, 我们选择Distribution, 点击下面的click here to download now.
下载完成后,双击安装,安装成功后,可以在你的钥匙串里面的证书下面看到这个中级证书。
1.3.3、请求一个发布证书
OK,现在来请求一个真正的发布证书,还是在这个页面,点击request certificate
这个页面告诉你怎么生成发布证书,点击下面的"选取文件",选择你在第一步创建的证书请求文件, 然后点击"submit"
OK。现在你有一个证书可以下载了,如下图(不能下载请刷新页面)
1.3.4、安装和导出
点击"download"下载你生成的证书,下载完成后双击安装,如果有如下提示,选择login,OK
这时再查看你的钥匙串,应该有下面这一行Iphone Distribution的证书,注意,这个证书有一个小三角可以点击, 展开后有一个对应的密钥。如果你没有这个钥匙,那么请检查上面那一步做错了。
现在发布证书已经安装了,我们选择这个证书,右击,选择,导出"xxxxxxx",如下图
给你要导出的证书起个名字,选择一个存的位置,注意,保存成P12的信息交换文件
输入密码,如果mac系统有密码,后面还会要求你输入系统密码。
现在你就有了发布程序需要的p12文件。
时需要上传的distribution.p12就是你导出的发布证书; certificate password就是导出证书时填写的密码。
1.4、生成provisioning文件
在下图左边选择provisioning选项,同样的右边的子项中选择distribution,来生成一个发布的准备文件
选择new profile,在下图中,第一个method,选择appstore;
Profile name,这个随便填,下面的App ID,选择我们开始的时候创建的appid,这个必须一致。确认后提交。
等待几秒钟,provisioning就可以下载了,点击download,下载。我们得到了一个xxxxxx.mobileprovision
时需要上传的distribution.mobileprovision就是你生成的文件
现在,我们的证书的准备工作就做完 了,我们有了一个appid,一个p12格式的证书文件,一个provisioning文件。
二、Appcan.cn在线ipa包编译
根据流程一制作的证书及p12文件,开发者就能够对应于进行混编,从而生成出可上传Appstore的ipa包,其流程如下:
AppCan在线的打包方式(非IDE打包方式),用户生成应用时需要选择生成ios平台,勾选后弹出下图窗口,
需要填写上传Apple开发者在Apple获得的发布应用相关资质信息(详情请参考),并却确认提交。 务必填写正确否则会打包失败的。
上传相关资质信息后,【注意】提交打出的文件包是用来上传到苹果Appstore 用的 不能直接安装到手机测试
Q:打包成功后我如何上传到Appstore
A:下载.ipa文件到本地,更改文件后缀.ipa为.zip。(上传请参看)
Q:打包完成后我如何安装到越狱手机上测试?
A:1、下载.ipa文件到本地,更改文件后缀.ipa为.zip并解压缩文件包,
2、新建Payload文件夹,
3、把解压缩.zip包里面的文件夹拷到Payload文件夹里
4、压缩Payload文件夹为.zip文件包,改.zip后缀为.ipa
5、安装到手机
跳过后打出的安装包可以直接安装到越狱手机
三、Ipa包提交苹果Appstore
通过AppCan在线与开发者证书混编后,生成的ipa包(后缀.ipa改为.zip)即可上传至苹果Appstore,以下是操作流程
3.1、在itunes中创建程序
该部分内容继续以雪豹系统为例
打开(membercenter中也可以找到)选择"Manage Your Applications"
点击"Add New App"。
填写下面的表格。
默认语言,
appname,
SKU Number,这是自己程序的标识,点击后面的"?"有说明
Bundle id:这个可以选择,必须和你申请证书时候的appid保持一致。否则会上传失败。
填完后,点击"Continue"
这个页面设置程序的生效时间和价格,选择后,点击"Continue"
下图的页面需要填一些程序的信息,注意"Review Notes (optional)"这个选项是对苹 果review程序的说明,如果你的程序需要登录 才可以使用,要在这里提供用户名和密码,如果你的程序的一些特色很隐晦,可以在这里提供操作步骤, 这是让apple的review人员看的东西,不会在appstore里面显示。
下图的页面是一些分级的说明,根据需要选择
下面是metadata,这个可能很重要,注意那个keywords,设置的越多你的程序被搜到 的几率越大。Large app icon 这需要一张512x512的icon。screenshots是屏幕截图或者说明性的图片。
填完后done,这样就创建好了一个app,点击"view detail",然后选择 “Ready to Upload Binary”。这时发 现你的app显 示一个黄色的wait for upload,表示你可以上传你的二进制代码了.如下图
3.2、上传程序
上传程序请在你的mac系统下找到application uploader工具,找不到的可以在下图这儿下载。Xcode4.x将该工 具集成到了xcode里面。
在mac下安装了该工具后,运行,如果是第一次,可能需要你用你自己的idp帐号登录(以后会保存在钥匙串中), 登录后会自动检查你有没有等待上传状态的app,选择你创建好的app,如下图
点击"Next"按钮
点击"choose"按钮,选择你从Appcan在线编译出来的ipa包,修改.ipa后缀为.zip,请不要打开修改包里面的内容 (包括添加和删除资源图片等文件),否则会上传失败。
点击"Send"按钮,等待上传,上传完成后,打开itunesconnect,这时候你的程序状态变为"upload received", 程序进入苹果第一步审核。第一步审核几分钟到几小时。当通过第一步审核后,程序状态变为"wait for review", 此时程序进入等待人工审核的状态,大概4天到2周的时间都是这个状态,直到排队轮到你的程序时, 程序进入"in review"状态, 如果你的程序没有内容上的问题,符合苹果的审核标准,那么审核成功,你程序的状态变为绿色的" Ready for Sale", 如果审核失败,程序状态变为红色的" Rejected " 上传过程中每一步的失败苹果都会发邮件给你,或者你能在Resolution Center详细的失败信息。
当你的程序变为"ready for sale"状态时候,你就可以在appstore里面找到它了。至此,上传结束。 你就可以通过IOS设备在Appstore中找到你的应用了
转载:
https://www.163.com/dy/article/HQL3LE9L05561DIZ.html
https://www.163.com/dy/article/HQCTFEBT05561DIZ.html
苹果开发者账号申请教程
http://www.applicationloader.net/
下载Xcode
https://developer.apple.com/xcode/
developer官网
https://developer.apple.com/
豫ICP备19030742号
由于苹果的机制,在非越狱机器上安装必须通过官方的Appstore, 开发者开发好应用后上传Appstore,也需要通过审核等环节。 AppCan作为一个跨主流平台的一个开发平台,也对ipa包上传Appstore作了支持。 本文从三个流程来介绍如何实现AppCan在 线编译出ipa包,以及上传到苹果Appstore。
一、证书的导出
1.1、前期工作
首先你需要有一个苹果的开发者帐号,一个mac系统。 如果没有帐号可以在打开申请加入苹果的开发者 计划。支付99美元每年,怎么申请网上有详细的介绍,在此不多做介绍。
如果你已经有了一个IDP,打开并登录到苹果MemberCenter,见下图
登录以后可以看到下面这个界面,列出了你开发需要的一些工具,支持,itunes app管理等内容。
选择第二项:Ios provisioning Portal,进入,所有证书相关的都在这里进行。
1.2、申请appid
在下图的左边选择 App IDs,我们先创建一个AppId,对于要发布到Appstore上的程序, 都有一个唯一的AppId,下面会列出你当前所有的AppId
我们点击右上角的New App ID
其中有两项需要你自己填:
第一个Description,用来描述你的appid,这个随便填,没有什么限制;
第二项Bundle Identifier (App ID Suffix),这是你appid的后缀,这个需要仔细, 因为这个内容和你的程序直接相关,后面很多地方要用到,最好是 com.yourcompany.yourappname的格式,当然没有公司名的个人开发者, 第二项可以用你自己的英文名字或者拼音,如下图
时需要填写的iapp IDs就是你再此输入的第二项内容
填完后submit,如下图,可以看见我们已经生成的appid:ebook appid。想要支持推送服务和icould等也可以在这儿配置:
1.3、申请发布证书
1.3.1、先创建一个证书请求文件
这儿需要一个mac系统。以下内容以雪豹系统为例,其他版本差别不是很大。
首先打开应用程序-?实用工具-?钥匙串访问(KEY CHAIN),在证书助理中,选择"从证书颁发机构求证书",如下图
在下图所示的界面,你的电子邮件地址:填你申请idp的电子邮件地址,常用名称,默认就好,CA空, 选择存贮到磁盘,点击"继续":
选择保存的位置,比如选择桌面
下一步点击完成,你就可以看到你的桌面多了一个CertificateSigningRequest.certSigningRequest的证 书请求文件。
1.3.2、安装WWDR证书
继续登录到你的MEMBER CENTER,选择左边的certificates项,可以看到它右边有四个选项, 我们选择Distribution, 点击下面的click here to download now.
下载完成后,双击安装,安装成功后,可以在你的钥匙串里面的证书下面看到这个中级证书。
1.3.3、请求一个发布证书
OK,现在来请求一个真正的发布证书,还是在这个页面,点击request certificate
这个页面告诉你怎么生成发布证书,点击下面的"选取文件",选择你在第一步创建的证书请求文件, 然后点击"submit"
OK。现在你有一个证书可以下载了,如下图(不能下载请刷新页面)
1.3.4、安装和导出
点击"download"下载你生成的证书,下载完成后双击安装,如果有如下提示,选择login,OK
这时再查看你的钥匙串,应该有下面这一行Iphone Distribution的证书,注意,这个证书有一个小三角可以点击, 展开后有一个对应的密钥。如果你没有这个钥匙,那么请检查上面那一步做错了。
现在发布证书已经安装了,我们选择这个证书,右击,选择,导出"xxxxxxx",如下图
给你要导出的证书起个名字,选择一个存的位置,注意,保存成P12的信息交换文件
输入密码,如果mac系统有密码,后面还会要求你输入系统密码。
现在你就有了发布程序需要的p12文件。
时需要上传的distribution.p12就是你导出的发布证书; certificate password就是导出证书时填写的密码。
1.4、生成provisioning文件
在下图左边选择provisioning选项,同样的右边的子项中选择distribution,来生成一个发布的准备文件
选择new profile,在下图中,第一个method,选择appstore;
Profile name,这个随便填,下面的App ID,选择我们开始的时候创建的appid,这个必须一致。确认后提交。
等待几秒钟,provisioning就可以下载了,点击download,下载。我们得到了一个xxxxxx.mobileprovision
时需要上传的distribution.mobileprovision就是你生成的文件
现在,我们的证书的准备工作就做完 了,我们有了一个appid,一个p12格式的证书文件,一个provisioning文件。
二、Appcan.cn在线ipa包编译
根据流程一制作的证书及p12文件,开发者就能够对应于进行混编,从而生成出可上传Appstore的ipa包,其流程如下:
AppCan在线的打包方式(非IDE打包方式),用户生成应用时需要选择生成ios平台,勾选后弹出下图窗口,
需要填写上传Apple开发者在Apple获得的发布应用相关资质信息(详情请参考),并却确认提交。 务必填写正确否则会打包失败的。
上传相关资质信息后,【注意】提交打出的文件包是用来上传到苹果Appstore 用的 不能直接安装到手机测试
Q:打包成功后我如何上传到Appstore
A:下载.ipa文件到本地,更改文件后缀.ipa为.zip。(上传请参看)
Q:打包完成后我如何安装到越狱手机上测试?
A:1、下载.ipa文件到本地,更改文件后缀.ipa为.zip并解压缩文件包,
2、新建Payload文件夹,
3、把解压缩.zip包里面的文件夹拷到Payload文件夹里
4、压缩Payload文件夹为.zip文件包,改.zip后缀为.ipa
5、安装到手机
跳过后打出的安装包可以直接安装到越狱手机
三、Ipa包提交苹果Appstore
通过AppCan在线与开发者证书混编后,生成的ipa包(后缀.ipa改为.zip)即可上传至苹果Appstore,以下是操作流程
3.1、在itunes中创建程序
该部分内容继续以雪豹系统为例
打开(membercenter中也可以找到)选择"Manage Your Applications"
点击"Add New App"。
填写下面的表格。
默认语言,
appname,
SKU Number,这是自己程序的标识,点击后面的"?"有说明
Bundle id:这个可以选择,必须和你申请证书时候的appid保持一致。否则会上传失败。
填完后,点击"Continue"
这个页面设置程序的生效时间和价格,选择后,点击"Continue"
下图的页面需要填一些程序的信息,注意"Review Notes (optional)"这个选项是对苹 果review程序的说明,如果你的程序需要登录 才可以使用,要在这里提供用户名和密码,如果你的程序的一些特色很隐晦,可以在这里提供操作步骤, 这是让apple的review人员看的东西,不会在appstore里面显示。
下图的页面是一些分级的说明,根据需要选择
下面是metadata,这个可能很重要,注意那个keywords,设置的越多你的程序被搜到 的几率越大。Large app icon 这需要一张512x512的icon。screenshots是屏幕截图或者说明性的图片。
填完后done,这样就创建好了一个app,点击"view detail",然后选择 “Ready to Upload Binary”。这时发 现你的app显 示一个黄色的wait for upload,表示你可以上传你的二进制代码了.如下图
3.2、上传程序
上传程序请在你的mac系统下找到application uploader工具,找不到的可以在下图这儿下载。Xcode4.x将该工 具集成到了xcode里面。
在mac下安装了该工具后,运行,如果是第一次,可能需要你用你自己的idp帐号登录(以后会保存在钥匙串中), 登录后会自动检查你有没有等待上传状态的app,选择你创建好的app,如下图
点击"Next"按钮
点击"choose"按钮,选择你从Appcan在线编译出来的ipa包,修改.ipa后缀为.zip,请不要打开修改包里面的内容 (包括添加和删除资源图片等文件),否则会上传失败。
点击"Send"按钮,等待上传,上传完成后,打开itunesconnect,这时候你的程序状态变为"upload received", 程序进入苹果第一步审核。第一步审核几分钟到几小时。当通过第一步审核后,程序状态变为"wait for review", 此时程序进入等待人工审核的状态,大概4天到2周的时间都是这个状态,直到排队轮到你的程序时, 程序进入"in review"状态, 如果你的程序没有内容上的问题,符合苹果的审核标准,那么审核成功,你程序的状态变为绿色的" Ready for Sale", 如果审核失败,程序状态变为红色的" Rejected " 上传过程中每一步的失败苹果都会发邮件给你,或者你能在Resolution Center详细的失败信息。
当你的程序变为"ready for sale"状态时候,你就可以在appstore里面找到它了。至此,上传结束。 你就可以通过IOS设备在Appstore中找到你的应用了
转载:
https://www.163.com/dy/article/HQL3LE9L05561DIZ.html
https://www.163.com/dy/article/HQCTFEBT05561DIZ.html
苹果开发者账号申请教程
http://www.applicationloader.net/
下载Xcode
https://developer.apple.com/xcode/
developer官网
https://developer.apple.com/
豫ICP备19030742号

图解多渠道/多租户热更新
本篇适用于多产品使用同一套代码。区分不同渠道热更新。
1多租户更新介绍图
APP 多租户热更新是在之前APP热更新的基础上,添加了多租户的模式。
2. 打APP包
2.1渠道
Android
和iOS
在打包时配置渠道不一样。
1. Android
分两步
a. 在manifest.json
根目录下 添加渠道列表
b. 打包时选择本次打包所打的渠道名称
2. iOS
只有一步
在manifest.json
文件中app-plus
节点下添加channel
字段
iOS
打包之前在此位置修改对应渠道的channel
3. 热更新
3.1 打wgt
包
3.1.1 说明
在HBuilder X
中打wgt
包,不需要修改渠道。因为渠道channel
是在打包阶段直接打到基座中的。热更新不会影响到channel
的值
4. 修改uniCloud
下篇分享详细聊聊
本篇适用于多产品使用同一套代码。区分不同渠道热更新。
1多租户更新介绍图
APP 多租户热更新是在之前APP热更新的基础上,添加了多租户的模式。
2. 打APP包
2.1渠道
Android
和iOS
在打包时配置渠道不一样。
1. Android
分两步
a. 在manifest.json
根目录下 添加渠道列表
b. 打包时选择本次打包所打的渠道名称
2. iOS
只有一步
在manifest.json
文件中app-plus
节点下添加channel
字段
iOS
打包之前在此位置修改对应渠道的channel
3. 热更新
3.1 打wgt
包
3.1.1 说明
在HBuilder X
中打wgt
包,不需要修改渠道。因为渠道channel
是在打包阶段直接打到基座中的。热更新不会影响到channel
的值
4. 修改uniCloud
下篇分享详细聊聊
收起阅读 »
Hbuilder用自有证书打包 ios App上架AppStore流程
最近在用Hbuilder做跨平台开发,经过一番研究终于在苹果商店上架成功了一款产品!这款产品就很简单,直接用hbuilder打包好,然后上传到商店即可。这里参照ios app提交应用商店 这篇文章结合hbuilder,从应用打包,到提交到苹果商店的流程详细介绍一下,希望对有需要的哥哥姐姐们有帮助、
一、打包:
应用写好了之后就可以打包了,hbuilder云打包很简单,但是你想要发布到应用商店的话是需要自有证书的,
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
选择“使用苹果证书”
1。 前期工作
这里就需要用到 AppId,描述文件profile,以及私钥证书。下面详细讲解这三项的申请步骤
必须条件:苹果开发者账号,mac系统
苹果开发者账号:我是直接用公司额账号,所以省去了申请账号的步骤,访问苹果开发者中心https://developer.apple.com/account/overview.action注册或者登录账号,
编辑
添加图片注释,不超过 140 字(可选)
登录界面
1.1创建appId
登录进去之后,找到Identifiers
编辑
添加图片注释,不超过 140 字(可选)
点击appId
下一步点加号,对点加号
编辑
添加图片注释,不超过 140 字(可选)
然后会跳出一个界面,有两个表单是要自己填写的,分别是Name和Bundle Id
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
App Name
上面这段话翻译过来就是:App ID字符串包含两个部分,以句点(.)分隔 - 应用ID前缀(默认情况下定义为您的Team ID)和App ID后缀(定义为Bundle ID搜索字符串)。 App ID的每个部分都有不同的重要用途
这边Name就可以随便填,没有什么限制,最好是项目名称,这样方便自己辨识(不允许中文);
另外一个就是Bundle Id
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
Bundle Id
这是你appid的后缀,这个需要仔细。因为这个内容和你的程序直接相关,后面很多地方要用到,最好是com.yourcompany.yourappname的格式,我用的是项目中的Bundle ID(反正这样是最保险的)
还有几个App server,建议就选择一下推送Push Notifications,至于为什么我也不太清楚,选上肯定没错..
编辑
添加图片注释,不超过 140 字(可选)
App server
下面就是一路点击过去continue,registe,done,最后注册成功的id是这样的
编辑
添加图片注释,不超过 140 字(可选)
成功啦
1.2申请证书
这里呢需要一个mac系统的电脑,如果有苹果机那最好了,没有也没关系,可以用虚拟机安装一个呀。
这里抛一个虚拟机装mac系统的链接:从0到100安装,虚拟机装mac;
如果遇到报错,可以参照下面链接,找不到安装磁盘看这里:请选择要插入的磁盘 ;不可恢复错误报错看这里:不可恢复错误: (vcpu-0) ;锁定文件失败,打不开磁盘或快照所依赖的磁盘的解决方案:方案 ;
好了,现在我们有了苹果电脑,可以进行正式的申请工作了。
1.1.1 请求文件CertificateSigningRequest.certSigningRequest
在实用工具找到-钥匙串访问(KEY CHAIN),在证书助理中,选择"从证书颁发机构求证书",如下图:
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
在下图所示的界面,你的电子邮件地址:填你申请idp的电子邮件地址,常用名称,默认就好,CA空,选择存贮到磁盘,点击"继续":
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
选择保存的位置,比如选择桌面。下一步点击完成,你就可以看到你的桌面多了一个CertificateSigningRequest.certSigningRequest的证书请求文件。
注:CSR文件尽量每个证书都制作一次,将常用名称区分开来,因为该常用名称是证书中的密钥的名字。
1.1.2 制作描述文件Provisioning Profile
登陆到开发者中心,找到证书配置的版块,选择点击右上角的加号:
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
发布证书和开发者证书需要操作两次,分别创建,开发者证书用于真机调试,发布证书用于提交到AppStore。注意这两个文件的区分。
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
然后下一步,会提示上传CSR文件,也就是证书签名请求文件。前面申请的那个CertificateSigningRequest.certSigningRequest,
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
提交上去后就会生成一个cer证书,如图所示,有效期为一年。
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
下载下来是这样的,如图:
添加图片注释,不超过 140 字(可选)
做到这一步还不够,我们最终的目标是一个后缀名是.mobileprovision的证书。
所以继续
找到Provisioning Profiles,然后点加号添加
编辑
添加图片注释,不超过 140 字(可选)
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
按照需要选择证书类别,开发者证书或者提交应用商店的证书,然后continue就会出现让你选择AppId的界面。选择你刚创建的AppId
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
选好了点继续,然后会让你选一个certificates,这个大概就是开发者许可证书,就是那个后缀cer的,就选你刚创建的那个。
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
然后下一步填描述文件的名字,这个就因人而异没什么限制
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
填好名字就可以有一个描述文件了,下载即可,后缀名是.mobileprovision,下载下来就可以用。
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
1.1.3 私钥证书
这个是非常重要的证书,
这个呢,就是把之前下载的ios_distribution2.cer,或者(ios_development.cer),
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
然后双击添加文件到钥匙串
编辑
添加图片注释,不超过 140 字(可选)
点击添加,既可以在钥匙串中看到啦
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
下一步就是导出.p12后缀的证书,右键你要打包的文件,然后点导出
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
存储的时候回提示你设一个密码,在hbuilder打包的时候也会有一个私钥密码,就是在这里设置的,
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
好了该填的都填好了,导出的文件就会出现在你的文稿里边
编辑
添加图片注释,不超过 140 字(可选)
做到这一步,准备工作就做好了。下面就是利用hbuilder进行打包,选择使用自有证书,选择生成的对应文件,然后打包生成.ipa的文件。
二、上架
打包好了ipa文件就可以通过xcode上架了,我电脑装的是最新的mac系统,macOS sierra。为什么要强调这一点呢?因为最新的苹果系统要上架应用,就必须下载最新的xcode。要不然会报错的
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
2.1 在itunes中创建程序
登录ios开发者中心,找到并选择Itunes Connect(在account点进去登录后就能看到,或者在页面底部的Distribute下),选择我的app
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
点击左上角的+选择“新建App”,根据自己的app然后填写相应的信息即可,因为项目较多,就不一一讲解了,但是其中的注意事项会写到:
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
(1)选择语言的时候,简体中文是Simplified Chinese,不要再找Chinese了,找不到的~
(2)套装ID与SKU主要是app的唯一标识吧,我是用的项目中Bundle Identifier的内容,即com.company.projectname
2.1.1 图片上传尺寸要求:
(1)另外屏幕截图一定要按照尺寸哦,那边会有提示尺寸是多少,可以只上传一组5.5寸屏幕的,然后其他的都勾选用5.5寸显示即可。
(2)上传logo的尺寸也有要求,必须是1024*1024,而且不能有圆角
2.2 构建版本
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
这个构建版本就是上架一开始提到的,如果你是最新版本的系统就一定要下载最新的xcode
打开xcode,选择开发工具里边的Application Loader。
编辑
添加图片注释,不超过 140 字(可选)
打开之后,双击 交付应用,把打包好的.ipa文件上传上去
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
成功之后,过个30分钟左右吧,就会在构建版本那边出现一个加号,然后就可以把需要上架的软件添加上去。
另外,审核信息的填写要按照标准
编辑
添加图片注释,不超过 140 字(可选)
至于演示账号,我在提交的时候没有填写,不过还是建议填写一下,据说不填可能被拒。
都差不多了就可以保存,提交审核了,审核成功就可以在应用商店下载喽~
编辑
添加图片注释,不超过 140 字(可选)
The end
版本更新:
版本更新如果直接按照前面的步骤进行会报错
所以这边要做一些修改在mainfest.json里边修改版本号,这个按照自己需要填写
编辑
添加图片注释,不超过 140 字(可选)
这个好像不是那么重要,重要的是在iTunes Connect中要再加一个版本,然后用xcode提交,就可以在构建版本里选择了
编辑
添加图片注释,不超过 140 字(可选)
证书我们这边可以借助辅助工具appuploader
Appuploader可以辅助在Windows、linux或mac系统直接申请iOS证书p12,及上传ipa到App Store,最方便在Windows开发上架没有苹果Mac电脑的开发者!配合本教程使用,可以快速掌握如何真机测试及上架!
点击苹果证书按钮
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
点击新增
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
输入证书密码,名称
这个密码不是账号密码,而是一个保护证书的密码,是p12文件的密码,此密码设置后没有其他地方可以找到,忘记了只能删除证书重新制作,所以请务必记住密码。还有为了安全起见,密码不要太简单。 证书名称是你为了在证书列表里面便于区别的一个字符,自己好辨识就可以,尽量是是字母和数字之类
选择证书类型
带distribution的是发布类型,带development的是开发类型。
apple类型=ios+mac,所以开发时选择ios app development和apple development 类型都是可以的
选择bundle id
只有部分类型的证书需要选择bundle id,例如推送证书。因为大部分证书是不和app关联的。而是通过描述文件profile文件关联app。
使用appuploader同步服务
如果期望制作好证书后在其他电脑上同样可以下载到这个证书,或者和你同事同步此证书,则需要勾选使用appuploader服务同步。否则您需要手动管理p12文件在不同电脑之间的传输,并且一但创建下载后,无法在其他电脑下载,只能手动复制文件过去。一般情况下,推荐使用appuploader服务同步。
证书类型说明
IOS开发选择apple development或者ios app development 类型 ios 发布选择 apple distribution或者 ios distribution (app store and ad hoc) 开发推送证书选择 apple push notification service ssl (sandbox) 发布推送证书选择 apple push notification service ssl (sandbox & production)
其他证书不是很常用,可以自行百度各种证书说明
最近在用Hbuilder做跨平台开发,经过一番研究终于在苹果商店上架成功了一款产品!这款产品就很简单,直接用hbuilder打包好,然后上传到商店即可。这里参照ios app提交应用商店 这篇文章结合hbuilder,从应用打包,到提交到苹果商店的流程详细介绍一下,希望对有需要的哥哥姐姐们有帮助、
一、打包:
应用写好了之后就可以打包了,hbuilder云打包很简单,但是你想要发布到应用商店的话是需要自有证书的,
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
选择“使用苹果证书”
1。 前期工作
这里就需要用到 AppId,描述文件profile,以及私钥证书。下面详细讲解这三项的申请步骤
必须条件:苹果开发者账号,mac系统
苹果开发者账号:我是直接用公司额账号,所以省去了申请账号的步骤,访问苹果开发者中心https://developer.apple.com/account/overview.action注册或者登录账号,
编辑
添加图片注释,不超过 140 字(可选)
登录界面
1.1创建appId
登录进去之后,找到Identifiers
编辑
添加图片注释,不超过 140 字(可选)
点击appId
下一步点加号,对点加号
编辑
添加图片注释,不超过 140 字(可选)
然后会跳出一个界面,有两个表单是要自己填写的,分别是Name和Bundle Id
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
App Name
上面这段话翻译过来就是:App ID字符串包含两个部分,以句点(.)分隔 - 应用ID前缀(默认情况下定义为您的Team ID)和App ID后缀(定义为Bundle ID搜索字符串)。 App ID的每个部分都有不同的重要用途
这边Name就可以随便填,没有什么限制,最好是项目名称,这样方便自己辨识(不允许中文);
另外一个就是Bundle Id
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
Bundle Id
这是你appid的后缀,这个需要仔细。因为这个内容和你的程序直接相关,后面很多地方要用到,最好是com.yourcompany.yourappname的格式,我用的是项目中的Bundle ID(反正这样是最保险的)
还有几个App server,建议就选择一下推送Push Notifications,至于为什么我也不太清楚,选上肯定没错..
编辑
添加图片注释,不超过 140 字(可选)
App server
下面就是一路点击过去continue,registe,done,最后注册成功的id是这样的
编辑
添加图片注释,不超过 140 字(可选)
成功啦
1.2申请证书
这里呢需要一个mac系统的电脑,如果有苹果机那最好了,没有也没关系,可以用虚拟机安装一个呀。
这里抛一个虚拟机装mac系统的链接:从0到100安装,虚拟机装mac;
如果遇到报错,可以参照下面链接,找不到安装磁盘看这里:请选择要插入的磁盘 ;不可恢复错误报错看这里:不可恢复错误: (vcpu-0) ;锁定文件失败,打不开磁盘或快照所依赖的磁盘的解决方案:方案 ;
好了,现在我们有了苹果电脑,可以进行正式的申请工作了。
1.1.1 请求文件CertificateSigningRequest.certSigningRequest
在实用工具找到-钥匙串访问(KEY CHAIN),在证书助理中,选择"从证书颁发机构求证书",如下图:
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
在下图所示的界面,你的电子邮件地址:填你申请idp的电子邮件地址,常用名称,默认就好,CA空,选择存贮到磁盘,点击"继续":
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
选择保存的位置,比如选择桌面。下一步点击完成,你就可以看到你的桌面多了一个CertificateSigningRequest.certSigningRequest的证书请求文件。
注:CSR文件尽量每个证书都制作一次,将常用名称区分开来,因为该常用名称是证书中的密钥的名字。
1.1.2 制作描述文件Provisioning Profile
登陆到开发者中心,找到证书配置的版块,选择点击右上角的加号:
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
发布证书和开发者证书需要操作两次,分别创建,开发者证书用于真机调试,发布证书用于提交到AppStore。注意这两个文件的区分。
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
然后下一步,会提示上传CSR文件,也就是证书签名请求文件。前面申请的那个CertificateSigningRequest.certSigningRequest,
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
提交上去后就会生成一个cer证书,如图所示,有效期为一年。
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
下载下来是这样的,如图:
添加图片注释,不超过 140 字(可选)
做到这一步还不够,我们最终的目标是一个后缀名是.mobileprovision的证书。
所以继续
找到Provisioning Profiles,然后点加号添加
编辑
添加图片注释,不超过 140 字(可选)
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
按照需要选择证书类别,开发者证书或者提交应用商店的证书,然后continue就会出现让你选择AppId的界面。选择你刚创建的AppId
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
选好了点继续,然后会让你选一个certificates,这个大概就是开发者许可证书,就是那个后缀cer的,就选你刚创建的那个。
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
然后下一步填描述文件的名字,这个就因人而异没什么限制
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
填好名字就可以有一个描述文件了,下载即可,后缀名是.mobileprovision,下载下来就可以用。
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
1.1.3 私钥证书
这个是非常重要的证书,
这个呢,就是把之前下载的ios_distribution2.cer,或者(ios_development.cer),
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
然后双击添加文件到钥匙串
编辑
添加图片注释,不超过 140 字(可选)
点击添加,既可以在钥匙串中看到啦
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
下一步就是导出.p12后缀的证书,右键你要打包的文件,然后点导出
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
存储的时候回提示你设一个密码,在hbuilder打包的时候也会有一个私钥密码,就是在这里设置的,
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
好了该填的都填好了,导出的文件就会出现在你的文稿里边
编辑
添加图片注释,不超过 140 字(可选)
做到这一步,准备工作就做好了。下面就是利用hbuilder进行打包,选择使用自有证书,选择生成的对应文件,然后打包生成.ipa的文件。
二、上架
打包好了ipa文件就可以通过xcode上架了,我电脑装的是最新的mac系统,macOS sierra。为什么要强调这一点呢?因为最新的苹果系统要上架应用,就必须下载最新的xcode。要不然会报错的
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
2.1 在itunes中创建程序
登录ios开发者中心,找到并选择Itunes Connect(在account点进去登录后就能看到,或者在页面底部的Distribute下),选择我的app
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
点击左上角的+选择“新建App”,根据自己的app然后填写相应的信息即可,因为项目较多,就不一一讲解了,但是其中的注意事项会写到:
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
(1)选择语言的时候,简体中文是Simplified Chinese,不要再找Chinese了,找不到的~
(2)套装ID与SKU主要是app的唯一标识吧,我是用的项目中Bundle Identifier的内容,即com.company.projectname
2.1.1 图片上传尺寸要求:
(1)另外屏幕截图一定要按照尺寸哦,那边会有提示尺寸是多少,可以只上传一组5.5寸屏幕的,然后其他的都勾选用5.5寸显示即可。
(2)上传logo的尺寸也有要求,必须是1024*1024,而且不能有圆角
2.2 构建版本
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
这个构建版本就是上架一开始提到的,如果你是最新版本的系统就一定要下载最新的xcode
打开xcode,选择开发工具里边的Application Loader。
编辑
添加图片注释,不超过 140 字(可选)
打开之后,双击 交付应用,把打包好的.ipa文件上传上去
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
成功之后,过个30分钟左右吧,就会在构建版本那边出现一个加号,然后就可以把需要上架的软件添加上去。
另外,审核信息的填写要按照标准
编辑
添加图片注释,不超过 140 字(可选)
至于演示账号,我在提交的时候没有填写,不过还是建议填写一下,据说不填可能被拒。
都差不多了就可以保存,提交审核了,审核成功就可以在应用商店下载喽~
编辑
添加图片注释,不超过 140 字(可选)
The end
版本更新:
版本更新如果直接按照前面的步骤进行会报错
所以这边要做一些修改在mainfest.json里边修改版本号,这个按照自己需要填写
编辑
添加图片注释,不超过 140 字(可选)
这个好像不是那么重要,重要的是在iTunes Connect中要再加一个版本,然后用xcode提交,就可以在构建版本里选择了
编辑
添加图片注释,不超过 140 字(可选)
证书我们这边可以借助辅助工具appuploader
Appuploader可以辅助在Windows、linux或mac系统直接申请iOS证书p12,及上传ipa到App Store,最方便在Windows开发上架没有苹果Mac电脑的开发者!配合本教程使用,可以快速掌握如何真机测试及上架!
点击苹果证书按钮
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
点击新增
编辑
切换为居中
添加图片注释,不超过 140 字(可选)
输入证书密码,名称
这个密码不是账号密码,而是一个保护证书的密码,是p12文件的密码,此密码设置后没有其他地方可以找到,忘记了只能删除证书重新制作,所以请务必记住密码。还有为了安全起见,密码不要太简单。 证书名称是你为了在证书列表里面便于区别的一个字符,自己好辨识就可以,尽量是是字母和数字之类
选择证书类型
带distribution的是发布类型,带development的是开发类型。
apple类型=ios+mac,所以开发时选择ios app development和apple development 类型都是可以的
选择bundle id
只有部分类型的证书需要选择bundle id,例如推送证书。因为大部分证书是不和app关联的。而是通过描述文件profile文件关联app。
使用appuploader同步服务
如果期望制作好证书后在其他电脑上同样可以下载到这个证书,或者和你同事同步此证书,则需要勾选使用appuploader服务同步。否则您需要手动管理p12文件在不同电脑之间的传输,并且一但创建下载后,无法在其他电脑下载,只能手动复制文件过去。一般情况下,推荐使用appuploader服务同步。
证书类型说明
IOS开发选择apple development或者ios app development 类型 ios 发布选择 apple distribution或者 ios distribution (app store and ad hoc) 开发推送证书选择 apple push notification service ssl (sandbox) 发布推送证书选择 apple push notification service ssl (sandbox & production)
其他证书不是很常用,可以自行百度各种证书说明