
开源VUE盲盒小程序源码多端二开搭建(app+h5+pc+小程序)带数码商城和交友盲盒系统
本文将围绕盲盒小程序源码的设计、实现及运维过程,详细介绍从项目构思到最终上线的全流程,旨在为读者提供一个全面的参考和指导。
源码设计:n.ymzan.top
一、项目背景与需求分析
1.1 项目背景
盲盒小程序旨在为用户提供一个线上购买和开启盲盒的平台,用户可以通过小程序轻松购买盲盒,并在开启后获得随机的商品。这种新颖的消费模式不仅满足了用户的猎奇心理,还通过社交分享等功能增强了用户之间的互动和粘性。
1.2 需求分析
在项目启动前,我们进行了详细的需求分析,主要包括以下几个方面:
用户注册与登录:用户可以通过邮箱或手机号进行注册,支持第三方社交账号登录。
盲盒购买:用户可以在小程序中浏览盲盒列表,选择心仪的盲盒进行购买,支持多种支付方式。
盲盒开启:用户购买盲盒后,可以立即开启,系统根据预设的概率算法决定用户获得的商品。
商品展示:展示盲盒内的商品信息,包括图片、描述、价格等。
订单管理:用户可以查看自己的购买记录和订单状态,支持订单跟踪和售后服务。
社交分享:用户可以将自己的开箱体验分享到社交平台,增加用户之间的互动和传播。
二、技术选型与架构设计
2.1 技术选型
基于项目的需求,我们选择了以下技术栈进行开发:
前端:uniapp。uniapp 是一个使用 Vue.js 开发所有前端应用的框架,支持编译到 iOS、Android、H5、以及各种小程序等多个平台,极大地提高了开发效率。
后端:PHP TP6 框架。TP6 是一个现代化的 PHP 后端开发框架,拥有简洁的语法、丰富的功能组件和高效的性能,适合快速开发企业级应用。
数据库:MySQL。MySQL 是一个流行的关系型数据库管理系统,支持大型数据库和事务处理,能够满足本项目的数据存储需求。
2.2 架构设计
项目的整体架构采用前后端分离的模式,前端负责页面展示和用户交互,后端负责业务逻辑处理和数据库操作。具体架构如下:
前端:使用 uniapp 开发,包括页面设计、用户交互、API 接口调用等。
后端:使用 PHP TP6 框架,包括用户注册登录、盲盒购买逻辑、数据库操作等。
数据库:使用 MySQL 存储用户信息、盲盒信息、商品信息、订单信息等数据。
三、开发实现
3.1 前端开发
3.1.1 用户注册与登录
在 pages 文件夹下创建 register 和 login 页面,分别用于用户注册和登录。使用 uniapp 提供的表单组件和 API 接口调用功能,实现用户信息的提交和验证。
vue
// register.vue
async submitForm() {
const formData = {
username: this.username,
password: this.password,
};
try {
const res = await uni.request({
url: '/api/user/register',
method: 'POST',
data: formData,
});
if (res.data.code === 0) {
uni.showToast({
title: '注册成功',
icon: 'success',
});
this.$router.push('/pages/login');
} else {
uni.showToast({
title: '注册失败',
icon: 'none',
});
}
} catch (err) {
console.error(err);
}
}
3.1.2 盲盒购买与开启
在 pages 文件夹下创建 blindbox 页面,用于展示盲盒列表和购买操作。用户点击购买按钮后,调用后端接口完成购买操作,并实时更新页面状态。
vue
// blindbox.vue
async buyBlindBox(id) {
try {
const res = await uni.request({
url: /api/blindbox/buy/${id}
,
method: 'POST',
});
if (res.data.code === 0) {
uni.showToast({
title: '购买成功',
icon: 'success',
});
this.getBlindBoxList(); // 重新加载盲盒列表
} else {
uni.showToast
({
title: '购买失败',
icon: 'none',
});
}
// 开启盲盒
if (res.data.opened) {
this.openBlindBox(res.data.blindboxId);
}
} catch (err) {
console.error(err);
uni.showToast({
title: '网络错误',
icon: 'none',
});
}
}
async openBlindBox(blindboxId) {
try {
const res = await uni.request({
url: /api/blindbox/open/${blindboxId},
method: 'GET',
});
if (res.data.code === 0) {
uni.showModal({
title: '恭喜你!',
content: 你获得了${res.data.product.name}!,
showCancel: false,
success: () => {
// 可以添加分享到朋友圈或社交平台的逻辑
this.shareToFriends(res.data.product);
}
});
} else {
uni.showToast({
title: '开启失败',
icon: 'none',
});
}
} catch (err) {
console.error(err);
uni.showToast({
title: '网络错误',
icon: 'none',
});
}
}
shareToFriends(product) {
uni.share({
provider: 'weixin', // 指定分享到微信
title: 我获得了${product.name}!,
path: '/pages/blindbox/detail?productId=' + product.id, // 分享的页面路径
success: () => {
uni.showToast({
title: '分享成功',
icon: 'success',
});
},
fail: () => {
uni.showToast({
title: '分享失败',
icon: 'none',
});
}
});
}
### 3.2 后端开发
#### 3.2.1 用户注册与登录
在 PHP TP6 框架中,创建 User
模型和 UserController
控制器来处理用户注册和登录的逻辑。
```php
// UserController.php
public function register(Request $request)
{
$data = $request->post();
$user = new User();
$user->username = $data['username'];
$user->password = password_hash($data['password'], PASSWORD_DEFAULT);
if ($user->save()) {
return json(['code' => 0, 'msg' => '注册成功']);
} else {
return json(['code' => 1, 'msg' => '注册失败']);
}
}
public function login(Request $request)
{
$data = $request->post();
$user = User::where('username', $data['username'])->find();
if ($user && password_verify($data['password'], $user->password)) {
// 生成Token等操作
return json(['code' => 0, 'msg' => '登录成功', 'token' => 'your_token_here']);
} else {
return json(['code' => 1, 'msg' => '用户名或密码错误']);
}
}
3.2.2 盲盒购买与开启
在 BlindBox 模型和 BlindBoxController 控制器中处理盲盒的购买和开启逻辑。
php
// BlindBoxController.php
public function buy($id, Request $request)
{
// 验证用户身份、库存等
// ...
$blindbox = BlindBox::find($id);
if (!$blindbox || $blindbox->stock <= 0) {
return json(['code' => 1, 'msg' => '盲盒不存在或库存不足']);
}
// 减库存、创建订单等操作
$blindbox->stock--;
$blindbox->save();
// 假设这里直接开启盲盒
$product = $this->openBlindBox($blindbox);
return json([
'code' => 0,
'msg' => '购买成功',
'opened' => true,
'blindboxId' => $blindbox->id,
'product' => $product,)
本文将围绕盲盒小程序源码的设计、实现及运维过程,详细介绍从项目构思到最终上线的全流程,旨在为读者提供一个全面的参考和指导。
源码设计:n.ymzan.top
一、项目背景与需求分析
1.1 项目背景
盲盒小程序旨在为用户提供一个线上购买和开启盲盒的平台,用户可以通过小程序轻松购买盲盒,并在开启后获得随机的商品。这种新颖的消费模式不仅满足了用户的猎奇心理,还通过社交分享等功能增强了用户之间的互动和粘性。
1.2 需求分析
在项目启动前,我们进行了详细的需求分析,主要包括以下几个方面:
用户注册与登录:用户可以通过邮箱或手机号进行注册,支持第三方社交账号登录。
盲盒购买:用户可以在小程序中浏览盲盒列表,选择心仪的盲盒进行购买,支持多种支付方式。
盲盒开启:用户购买盲盒后,可以立即开启,系统根据预设的概率算法决定用户获得的商品。
商品展示:展示盲盒内的商品信息,包括图片、描述、价格等。
订单管理:用户可以查看自己的购买记录和订单状态,支持订单跟踪和售后服务。
社交分享:用户可以将自己的开箱体验分享到社交平台,增加用户之间的互动和传播。
二、技术选型与架构设计
2.1 技术选型
基于项目的需求,我们选择了以下技术栈进行开发:
前端:uniapp。uniapp 是一个使用 Vue.js 开发所有前端应用的框架,支持编译到 iOS、Android、H5、以及各种小程序等多个平台,极大地提高了开发效率。
后端:PHP TP6 框架。TP6 是一个现代化的 PHP 后端开发框架,拥有简洁的语法、丰富的功能组件和高效的性能,适合快速开发企业级应用。
数据库:MySQL。MySQL 是一个流行的关系型数据库管理系统,支持大型数据库和事务处理,能够满足本项目的数据存储需求。
2.2 架构设计
项目的整体架构采用前后端分离的模式,前端负责页面展示和用户交互,后端负责业务逻辑处理和数据库操作。具体架构如下:
前端:使用 uniapp 开发,包括页面设计、用户交互、API 接口调用等。
后端:使用 PHP TP6 框架,包括用户注册登录、盲盒购买逻辑、数据库操作等。
数据库:使用 MySQL 存储用户信息、盲盒信息、商品信息、订单信息等数据。
三、开发实现
3.1 前端开发
3.1.1 用户注册与登录
在 pages 文件夹下创建 register 和 login 页面,分别用于用户注册和登录。使用 uniapp 提供的表单组件和 API 接口调用功能,实现用户信息的提交和验证。
vue
// register.vue
async submitForm() {
const formData = {
username: this.username,
password: this.password,
};
try {
const res = await uni.request({
url: '/api/user/register',
method: 'POST',
data: formData,
});
if (res.data.code === 0) {
uni.showToast({
title: '注册成功',
icon: 'success',
});
this.$router.push('/pages/login');
} else {
uni.showToast({
title: '注册失败',
icon: 'none',
});
}
} catch (err) {
console.error(err);
}
}
3.1.2 盲盒购买与开启
在 pages 文件夹下创建 blindbox 页面,用于展示盲盒列表和购买操作。用户点击购买按钮后,调用后端接口完成购买操作,并实时更新页面状态。
vue
// blindbox.vue
async buyBlindBox(id) {
try {
const res = await uni.request({
url: /api/blindbox/buy/${id}
,
method: 'POST',
});
if (res.data.code === 0) {
uni.showToast({
title: '购买成功',
icon: 'success',
});
this.getBlindBoxList(); // 重新加载盲盒列表
} else {
uni.showToast
({
title: '购买失败',
icon: 'none',
});
}
// 开启盲盒
if (res.data.opened) {
this.openBlindBox(res.data.blindboxId);
}
} catch (err) {
console.error(err);
uni.showToast({
title: '网络错误',
icon: 'none',
});
}
}
async openBlindBox(blindboxId) {
try {
const res = await uni.request({
url: /api/blindbox/open/${blindboxId},
method: 'GET',
});
if (res.data.code === 0) {
uni.showModal({
title: '恭喜你!',
content: 你获得了${res.data.product.name}!,
showCancel: false,
success: () => {
// 可以添加分享到朋友圈或社交平台的逻辑
this.shareToFriends(res.data.product);
}
});
} else {
uni.showToast({
title: '开启失败',
icon: 'none',
});
}
} catch (err) {
console.error(err);
uni.showToast({
title: '网络错误',
icon: 'none',
});
}
}
shareToFriends(product) {
uni.share({
provider: 'weixin', // 指定分享到微信
title: 我获得了${product.name}!,
path: '/pages/blindbox/detail?productId=' + product.id, // 分享的页面路径
success: () => {
uni.showToast({
title: '分享成功',
icon: 'success',
});
},
fail: () => {
uni.showToast({
title: '分享失败',
icon: 'none',
});
}
});
}
### 3.2 后端开发
#### 3.2.1 用户注册与登录
在 PHP TP6 框架中,创建 User
模型和 UserController
控制器来处理用户注册和登录的逻辑。
```php
// UserController.php
public function register(Request $request)
{
$data = $request->post();
$user = new User();
$user->username = $data['username'];
$user->password = password_hash($data['password'], PASSWORD_DEFAULT);
if ($user->save()) {
return json(['code' => 0, 'msg' => '注册成功']);
} else {
return json(['code' => 1, 'msg' => '注册失败']);
}
}
public function login(Request $request)
{
$data = $request->post();
$user = User::where('username', $data['username'])->find();
if ($user && password_verify($data['password'], $user->password)) {
// 生成Token等操作
return json(['code' => 0, 'msg' => '登录成功', 'token' => 'your_token_here']);
} else {
return json(['code' => 1, 'msg' => '用户名或密码错误']);
}
}
3.2.2 盲盒购买与开启
在 BlindBox 模型和 BlindBoxController 控制器中处理盲盒的购买和开启逻辑。
php
// BlindBoxController.php
public function buy($id, Request $request)
{
// 验证用户身份、库存等
// ...
$blindbox = BlindBox::find($id);
if (!$blindbox || $blindbox->stock <= 0) {
return json(['code' => 1, 'msg' => '盲盒不存在或库存不足']);
}
// 减库存、创建订单等操作
$blindbox->stock--;
$blindbox->save();
// 假设这里直接开启盲盒
$product = $this->openBlindBox($blindbox);
return json([
'code' => 0,
'msg' => '购买成功',
'opened' => true,
'blindboxId' => $blindbox->id,
'product' => $product,)

基于微信/抖音开发的开源游戏小程序源码搭建
欢迎来到我们的代码共享网站上激动人心的 HTML5 游戏世界!此类别致力于为您提供一系列迷人的前端代码片段,直接为您的网络浏览器带来交互式游戏体验。无论您是经验丰富的开发人员还是刚刚开始编码之旅,我们的 HTML5 游戏部分都提供实时预览、分步教程和可下载链接的令人愉快的组合,以帮助您了解和创建自己的游戏项目。
源码及演示:y.wxlbyx.icu
安装游戏小程序源码步骤
1. 下载源码:首先,您需要从开发者提供的来源下载游戏小程序的源代码。通常,这些源代码会以压缩文件的形式提供,您可以将其保存到您的计算机上的一个文件夹中。
2. 解压源码:解压下载的源码压缩文件。在解压缩之后,您应该能够看到一个包含游戏小程序的文件夹。
3. 配置开发环境:在安装源码之前,您需要先配置好开发环境。对于小程序开发,您需要安装微信开发者工具。您可以从微信官方网站上下载并安装该工具。
4. 导入项目:打开微信开发者工具,并选择“导入项目”选项。然后,选择源码文件夹,并填写项目相关的信息,如项目名称、AppID等。
5. 编译和运行:导入项目后,您可以进行编译和运行。点击工具栏中的“编译”按钮可以将源码编译成小程序,并在右侧的预览窗口中显示运行结果。
6. 调试和修改:如果在编译和运行过程中出现错误或问题,您可以使用微信开发者工具提供的调试工具来查找和修复问题。可以查看开发者工具文档以了解更多关于调试和修改源码的信息。
通过以上步骤,您就能够成功安装和运行游戏小程序的源码。
结语
借助 HTML5、CSS 和 JavaScript 的强大力量,我们精选的游戏展示了前端 Web 开发的创造潜力。每个代码片段都带有实时预览,让您可以直接在我们的网站上玩和体验游戏。当您沉浸在动感十足的冒险中、解决令人费解的谜题或享受经典街机游戏的怀旧时刻时,感受肾上腺素的激增。
但这还不是全部!我们的 HTML5 游戏部分更进一步,为每个游戏提供详细的教程。我们相信培育学习社区,这些分步指南将引导您完成代码实现,解释所使用的逻辑和技术。无论您是想了解平台游戏中的碰撞检测如何工作,还是想了解如何创建引人入胜的动画,我们的教程都能满足您的需求。
更好的是所有前端代码都可以轻松下载。您可以访问每个游戏的源代码,进行试验,并修改它以满足您的喜好。这是一个绝佳的机会,可以学习现有项目、定制它们,甚至将它们用作您自己的游戏开发之旅的起点。想象一下,你拥有创造自己迷你游戏的魔力。你可以决定角色如何移动,他们的样子,以及当你赢或输时会发生什么。
欢迎来到我们的代码共享网站上激动人心的 HTML5 游戏世界!此类别致力于为您提供一系列迷人的前端代码片段,直接为您的网络浏览器带来交互式游戏体验。无论您是经验丰富的开发人员还是刚刚开始编码之旅,我们的 HTML5 游戏部分都提供实时预览、分步教程和可下载链接的令人愉快的组合,以帮助您了解和创建自己的游戏项目。
源码及演示:y.wxlbyx.icu
安装游戏小程序源码步骤
1. 下载源码:首先,您需要从开发者提供的来源下载游戏小程序的源代码。通常,这些源代码会以压缩文件的形式提供,您可以将其保存到您的计算机上的一个文件夹中。
2. 解压源码:解压下载的源码压缩文件。在解压缩之后,您应该能够看到一个包含游戏小程序的文件夹。
3. 配置开发环境:在安装源码之前,您需要先配置好开发环境。对于小程序开发,您需要安装微信开发者工具。您可以从微信官方网站上下载并安装该工具。
4. 导入项目:打开微信开发者工具,并选择“导入项目”选项。然后,选择源码文件夹,并填写项目相关的信息,如项目名称、AppID等。
5. 编译和运行:导入项目后,您可以进行编译和运行。点击工具栏中的“编译”按钮可以将源码编译成小程序,并在右侧的预览窗口中显示运行结果。
6. 调试和修改:如果在编译和运行过程中出现错误或问题,您可以使用微信开发者工具提供的调试工具来查找和修复问题。可以查看开发者工具文档以了解更多关于调试和修改源码的信息。
通过以上步骤,您就能够成功安装和运行游戏小程序的源码。
结语
借助 HTML5、CSS 和 JavaScript 的强大力量,我们精选的游戏展示了前端 Web 开发的创造潜力。每个代码片段都带有实时预览,让您可以直接在我们的网站上玩和体验游戏。当您沉浸在动感十足的冒险中、解决令人费解的谜题或享受经典街机游戏的怀旧时刻时,感受肾上腺素的激增。
但这还不是全部!我们的 HTML5 游戏部分更进一步,为每个游戏提供详细的教程。我们相信培育学习社区,这些分步指南将引导您完成代码实现,解释所使用的逻辑和技术。无论您是想了解平台游戏中的碰撞检测如何工作,还是想了解如何创建引人入胜的动画,我们的教程都能满足您的需求。
更好的是所有前端代码都可以轻松下载。您可以访问每个游戏的源代码,进行试验,并修改它以满足您的喜好。这是一个绝佳的机会,可以学习现有项目、定制它们,甚至将它们用作您自己的游戏开发之旅的起点。想象一下,你拥有创造自己迷你游戏的魔力。你可以决定角色如何移动,他们的样子,以及当你赢或输时会发生什么。

支付的云开发是否封装了requestVirtualPayment,微信的虚拟支付?
支付的云开发是否封装了requestVirtualPayment,微信的虚拟支付?
如果没有什么时候加上啊,急需
支付的云开发是否封装了requestVirtualPayment,微信的虚拟支付?
如果没有什么时候加上啊,急需

音频组件uni.createInnerAudioContext(),android播放声音,来回切换10+次就没有声音了
连续点播放10+以上就没有声音了,原来是创建的实例没有销毁的问题
var innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = true;
innerAudioContext.src = '..//souds/waitpay.mp3';
innerAudioContext.onPlay(() => {
console.log('开始播放');
});
innerAudioContext.onError((res) => {
console.log('播放出错');
console.log(res.errMsg);
console.log(res.errCode);
innerAudioContext.destroy();
});
innerAudioContext.onPause((res) => {
console.log('播放完成');
innerAudioContext.destroy();//这句话
innerAudioContext.src = '';
});
连续点播放10+以上就没有声音了,原来是创建的实例没有销毁的问题
var innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = true;
innerAudioContext.src = '..//souds/waitpay.mp3';
innerAudioContext.onPlay(() => {
console.log('开始播放');
});
innerAudioContext.onError((res) => {
console.log('播放出错');
console.log(res.errMsg);
console.log(res.errCode);
innerAudioContext.destroy();
});
innerAudioContext.onPause((res) => {
console.log('播放完成');
innerAudioContext.destroy();//这句话
innerAudioContext.src = '';
});

微信小程序隐私协议最新弹框解决方案
公众号文章:
https://mp.weixin.qq.com/s?__biz=MzIyNjE5ODA5NA==&mid=2247487487&idx=1&sn=36269ceb84a1ffb0a91f10a711b274cb&chksm=e87558dedf02d1c867f0a9fad1344d7bd7dc5542849543caf824051d56b89763b9a3e242efca#rd
代码片段:
https://developers.weixin.qq.com/s/VGANOSmb7VKl
公众号文章:
https://mp.weixin.qq.com/s?__biz=MzIyNjE5ODA5NA==&mid=2247487487&idx=1&sn=36269ceb84a1ffb0a91f10a711b274cb&chksm=e87558dedf02d1c867f0a9fad1344d7bd7dc5542849543caf824051d56b89763b9a3e242efca#rd
代码片段:
https://developers.weixin.qq.com/s/VGANOSmb7VKl

#creatcavascontext这个是小程序的废弃接口
creatcavascontext这个是小程序的废弃接口
https://uniapp.dcloud.net.cn/api/canvas/createCanvasContext.html#createcanvascontext
https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.createCanvasContext.html
creatcavascontext这个是小程序的废弃接口
https://uniapp.dcloud.net.cn/api/canvas/createCanvasContext.html#createcanvascontext
https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.createCanvasContext.html

实用小程序源码下载开发(附2万套uniapp小程序游戏源代码)
从技术上讲,微信小程序游戏框架在小程序框架中加入了程序游戏库API。因此,小程序游戏只能在小程序环境下运行,既不是原生程序游戏也不是HTML5程序游戏。也就是说,小程序游戏与HTML5程序游戏紧密相关,面向HTML5程序游戏开发者——很大程度上采用了WebGL、JavaScript等HTML5技术,将HTML5程序游戏转为微信小程序游戏的工作量降到最低。
源码:casgams.top/gm
玩家主要通过以下渠道进入小程序游戏:
●好友或微信群邀请
●扫描程序游戏二维码
●最近玩过的程序游戏显示在小程序历史记录中,或者拉下聊天收件箱后
●发现 > 小程序,然后搜索小程序游戏
●发现 > 程序游戏 > 我的小程序游戏,然后搜索“小程序游戏”
搜索“小程序游戏”(小程序游戏)将显示小程序游戏的简短列表。单击“显示完整列表”会将您带到一个隐藏的小程序游戏菜单,该菜单按最热门的程序游戏和朋友正在玩的程序游戏分类。
简而言之,微信小程序游戏:
1、在微信环境下运行
2、使用 HTML5 和相关网络技术
3、提供类似于原生程序游戏的程序游戏体验
●微信小程序游戏相对于其他类型的程序游戏有很多优势。两个最大的优势是*稳定性和可管理性。
●与原生程序游戏相比,微信的应用程序作为一个独立的平台,将用户留在微信生态系统中。
结论
微信小程序游戏结合了强大的获取渠道、社交程序游戏和分享以及庞大的用户群——同时无需下载任何东西即可享受近乎原生的应用体验。
所有这些特性都显示了微信小程序游戏的光明前景。现在就轮到你们这些程序游戏开发者利用这个机会,打造最适合微信用户的程序游戏了!
微信小程序游戏开发基础
上文提到,小程序游戏的开发涉及到HTML5,熟悉HTML5的开发者上手很快——短时间内将HTML5程序游戏变成微信小程序游戏。
具体来说,微信小程序游戏的开发技术可以分为三个部分:
相比HTML5程序游戏,小程序游戏玩家不会被弹窗广告打扰,体验更佳。
微信小程序游戏环境的一大优势是兼容HTML5程序游戏生态。也就是说,HTML5程序游戏可以更方便的转化为微信小程序游戏,不管你用什么程序游戏引擎开发的。这使得微信小程序游戏能够利用现有的大型 HTML5 生态系统的力量。
除了技术优势外,微信小程序游戏还利用了这样一个事实,即它是中国最受欢迎的社交平台,能够让应用病毒式传播。小程序游戏设计的一个关键部分是利用微信的社交特性来获取新用户。
微信小程序游戏主要通过好友推荐或分享链接被发现。这使得小程序游戏与以往的网页程序游戏通过广告或传统渠道获取用户到应用商店或下载链接有很大不同。
第1部分。底层技术
一、编程语言——微信小程序游戏只支持JavaScript(Web的主要编程语言),但也支持可以编译成JavaScript的语言,如TypeScript、CoffeeScript。
其次是微信小程序游戏框架支持的JavaScript API(应用程序接口):Canvas 2D和WebGL 1.0。这些 API 中的任何一个都可用于绘制图形、创建动画或实时渲染。但是您不想同时使用一种以上的技术。另请注意,只有 WebGL 支持 3D 渲染。
第2部分。中间件:程序游戏引擎
直接使用 Canvas 2D 或 WebGL 制作小程序游戏有一个陡峭的学习曲线。由于您可能不想花费超过一年的时间来开发一款程序游戏,因此使用 HTML5 程序游戏引擎是一个非常明智的选择。程序游戏引擎提供的高级功能可以大大降低开发者的入门门槛,缩短开发时间。
国内三大程序游戏引擎厂商Cocos Creator、Egret、Laya已经支持微信小程序游戏。目前国外流行的 HTML5 程序游戏引擎如Phaser.js、Three.js还没有直接支持,但还是可以适配使用(比如这个Phaser port for Mini Games)。
三、微信SDK(软件开发包)
微信小程序游戏为开发者提供了丰富的微信登录、分享、排行榜等社交功能的使用工具。
了解微信小程序游戏底层结构
小程序游戏既不是原生程序游戏,也不等同于 HTML5 程序游戏。但是,它的开发环境与这两种类型的程序游戏有关。小程序游戏使用HTML5相同的渲染接口。
小程序游戏实际上运行在微信App的原生环境中。程序游戏的JavaScript代码不是在浏览器环境中执行的,而是在移动设备的JS VM层上的一个独立的JavaScript引擎。在 Android 平台上,它使用Google 的 V8 引擎,而在 iOS 上,它使用苹果的 JavaScript Core 引擎。
当然,JS(JavaScript)引擎只负责JS逻辑的编译和运行,并没有渲染接口。
小程序游戏开发者如何将他们的程序游戏连接到渲染界面,以及微信框架中提供的许多其他功能?
引入了 JS-Native 脚本绑定。该技术可以桥接原生语言接口(iOS / Android 库)到脚本接口(JavaScript 库),并将 API 调用从脚本层转发到原生层以使用原生平台功能。
微信JavaScript SDK已经在使用绑定技术,让微信公众号和小程序通过JavaScript API实现相册、传感器等原生设备功能。
微信小程序游戏还使用绑定技术将原生平台(iOS / Android)的服务:渲染、用户数据、网络、音频和视频连接到 JavaScript 环境。小程序游戏层模块就是这样访问上图中的原生函数的。
通过支持 JavaScript 环境,微信提供了一个框架,可以将 HTML5 程序游戏转换为微信小程序游戏。但一些 API 兼容性问题可能是由于缺乏真正的浏览器环境和 DOM 而引起的。
为了降低 HTML5 程序游戏转小程序游戏的成本,微信团队还提供了一个“适配”脚本来支持浏览器 API。支持 HTML5 程序游戏所需的大多数浏览器功能 - 从而提高兼容性。
Adapter 脚本提供了 HTML5 程序游戏所依赖的大多数浏览器界面。
上图展示了微信小程序游戏中开发者可用的所有接口
●渲染界面
●浏览器适配器接口
●微信服务的微信API
请注意,Browser Adapter 接口不再由官方维护,因此任何附加功能都由开发人员决定。而且它的大部分依赖DOM的功能在小程序游戏环境下是不起作用的。
使用程序游戏引擎
由于开发栈的复杂性,一种选择是使用程序游戏引擎开发小程序游戏。程序游戏引擎不仅将常用的程序游戏功能封装在一个高阶接口上,还试图解决H5浏览器程序游戏与微信小程序游戏环境不兼容的问题。
开发人员可能需要使用不同层次的库——这取决于程序游戏的复杂性。使用程序游戏引擎时,它会为开发人员提供高级功能,同时调用这些相同的库。然后,开发人员需要学习引擎,并处理引擎未提供所有所需功能的情况。
以下是程序游戏引擎提供的好处摘要:
框架特点:
●对常见程序游戏开发特性的高度封装
●资源加载
●事件处理
●媒体和广播
●屏幕显示与控制
●用户输入
●附加接口,例如用于 TileMap 的 DOM 解析器
编辑:
●优化程序员、设计师和经理之间的协作。
●一个好的程序游戏编辑器可以显着缩短开发周期。
一般的:
●优秀的程序游戏引擎可以提供高设备兼容性和稳定的性能;
●跨平台程序游戏引擎可以让开发者同时发布HTML5程序游戏、微信小程序游戏和原生程序游戏。
高效的程序游戏编辑器可以降低开发成本和维护成本。对于程序游戏开发商而言,这些因素可能是盈利的关键。
4.开始搭建调试小程序游戏!
Step 1. 获取微信开发者工具
微信小程序开发者工具提供了编写、调试和运行小程序游戏的框架。在微信开发者网站这里下载。
首次运行开发者工具时,系统会要求您使用微信帐号登录。然后您将看到用于创建您的第一个项目的表单。
点击:“体验”右侧的“小程序游戏”(尝试:小程序游戏)
并为源代码选择一个位置项目目录,然后将您的项目命名为项目名称。
●页面顶部是工具栏。它可以配置、编译、预览和部署程序游戏。
●左边是模拟器。程序游戏随着代码的变化而运行和更新。
●右上角是代码编辑器。
●编辑器的左侧是文件菜单。它列出了项目文件。
●右下方是调试器。它的功能类似于Chrome开发者工具!
Step 2. 微信小程序游戏配置及文件导入
在微信小程序游戏工程中,添加配置文件project.config.json和game.json。
●project.config.json定义您的程序游戏 AppID、名称、版本、IDE 设置和运行时配置。
●game.json提供应用程序和代码的内部配置,例如设备方向和网络超时。这个类似于微信小程序中的app.json文件!
由于小程序游戏不支持 HTML 文件,入口在game.js中。您可以通过使用 require 函数将程序游戏引擎和程序游戏脚本引入game.js文件来启动程序游戏。注意: require函数的使用遵循Node.js的规范!
步骤 3. 编译、测试和提交
微信开发工具会监控脚本和配置的变化,实时更新程序游戏。您也可以点击页面顶部的编译按钮手动重新编译。当您需要在智能手机上预览程序游戏和测试时,您可以点击预览按钮生成二维码并扫描以玩程序游戏!
生成二维码的过程其实就是压缩一个小包上传到微信CDN,所以需要一定的时间。
如果微信小程序游戏遵循与小程序相同的分发规则,那么微信可能会在小程序游戏提交后对其进行审核——然后才能公开发布。也会有关于如何像小程序一样发现或分享小程序游戏的规则。
从技术上讲,微信小程序游戏框架在小程序框架中加入了程序游戏库API。因此,小程序游戏只能在小程序环境下运行,既不是原生程序游戏也不是HTML5程序游戏。也就是说,小程序游戏与HTML5程序游戏紧密相关,面向HTML5程序游戏开发者——很大程度上采用了WebGL、JavaScript等HTML5技术,将HTML5程序游戏转为微信小程序游戏的工作量降到最低。
源码:casgams.top/gm
玩家主要通过以下渠道进入小程序游戏:
●好友或微信群邀请
●扫描程序游戏二维码
●最近玩过的程序游戏显示在小程序历史记录中,或者拉下聊天收件箱后
●发现 > 小程序,然后搜索小程序游戏
●发现 > 程序游戏 > 我的小程序游戏,然后搜索“小程序游戏”
搜索“小程序游戏”(小程序游戏)将显示小程序游戏的简短列表。单击“显示完整列表”会将您带到一个隐藏的小程序游戏菜单,该菜单按最热门的程序游戏和朋友正在玩的程序游戏分类。
简而言之,微信小程序游戏:
1、在微信环境下运行
2、使用 HTML5 和相关网络技术
3、提供类似于原生程序游戏的程序游戏体验
●微信小程序游戏相对于其他类型的程序游戏有很多优势。两个最大的优势是*稳定性和可管理性。
●与原生程序游戏相比,微信的应用程序作为一个独立的平台,将用户留在微信生态系统中。
结论
微信小程序游戏结合了强大的获取渠道、社交程序游戏和分享以及庞大的用户群——同时无需下载任何东西即可享受近乎原生的应用体验。
所有这些特性都显示了微信小程序游戏的光明前景。现在就轮到你们这些程序游戏开发者利用这个机会,打造最适合微信用户的程序游戏了!
微信小程序游戏开发基础
上文提到,小程序游戏的开发涉及到HTML5,熟悉HTML5的开发者上手很快——短时间内将HTML5程序游戏变成微信小程序游戏。
具体来说,微信小程序游戏的开发技术可以分为三个部分:
相比HTML5程序游戏,小程序游戏玩家不会被弹窗广告打扰,体验更佳。
微信小程序游戏环境的一大优势是兼容HTML5程序游戏生态。也就是说,HTML5程序游戏可以更方便的转化为微信小程序游戏,不管你用什么程序游戏引擎开发的。这使得微信小程序游戏能够利用现有的大型 HTML5 生态系统的力量。
除了技术优势外,微信小程序游戏还利用了这样一个事实,即它是中国最受欢迎的社交平台,能够让应用病毒式传播。小程序游戏设计的一个关键部分是利用微信的社交特性来获取新用户。
微信小程序游戏主要通过好友推荐或分享链接被发现。这使得小程序游戏与以往的网页程序游戏通过广告或传统渠道获取用户到应用商店或下载链接有很大不同。
第1部分。底层技术
一、编程语言——微信小程序游戏只支持JavaScript(Web的主要编程语言),但也支持可以编译成JavaScript的语言,如TypeScript、CoffeeScript。
其次是微信小程序游戏框架支持的JavaScript API(应用程序接口):Canvas 2D和WebGL 1.0。这些 API 中的任何一个都可用于绘制图形、创建动画或实时渲染。但是您不想同时使用一种以上的技术。另请注意,只有 WebGL 支持 3D 渲染。
第2部分。中间件:程序游戏引擎
直接使用 Canvas 2D 或 WebGL 制作小程序游戏有一个陡峭的学习曲线。由于您可能不想花费超过一年的时间来开发一款程序游戏,因此使用 HTML5 程序游戏引擎是一个非常明智的选择。程序游戏引擎提供的高级功能可以大大降低开发者的入门门槛,缩短开发时间。
国内三大程序游戏引擎厂商Cocos Creator、Egret、Laya已经支持微信小程序游戏。目前国外流行的 HTML5 程序游戏引擎如Phaser.js、Three.js还没有直接支持,但还是可以适配使用(比如这个Phaser port for Mini Games)。
三、微信SDK(软件开发包)
微信小程序游戏为开发者提供了丰富的微信登录、分享、排行榜等社交功能的使用工具。
了解微信小程序游戏底层结构
小程序游戏既不是原生程序游戏,也不等同于 HTML5 程序游戏。但是,它的开发环境与这两种类型的程序游戏有关。小程序游戏使用HTML5相同的渲染接口。
小程序游戏实际上运行在微信App的原生环境中。程序游戏的JavaScript代码不是在浏览器环境中执行的,而是在移动设备的JS VM层上的一个独立的JavaScript引擎。在 Android 平台上,它使用Google 的 V8 引擎,而在 iOS 上,它使用苹果的 JavaScript Core 引擎。
当然,JS(JavaScript)引擎只负责JS逻辑的编译和运行,并没有渲染接口。
小程序游戏开发者如何将他们的程序游戏连接到渲染界面,以及微信框架中提供的许多其他功能?
引入了 JS-Native 脚本绑定。该技术可以桥接原生语言接口(iOS / Android 库)到脚本接口(JavaScript 库),并将 API 调用从脚本层转发到原生层以使用原生平台功能。
微信JavaScript SDK已经在使用绑定技术,让微信公众号和小程序通过JavaScript API实现相册、传感器等原生设备功能。
微信小程序游戏还使用绑定技术将原生平台(iOS / Android)的服务:渲染、用户数据、网络、音频和视频连接到 JavaScript 环境。小程序游戏层模块就是这样访问上图中的原生函数的。
通过支持 JavaScript 环境,微信提供了一个框架,可以将 HTML5 程序游戏转换为微信小程序游戏。但一些 API 兼容性问题可能是由于缺乏真正的浏览器环境和 DOM 而引起的。
为了降低 HTML5 程序游戏转小程序游戏的成本,微信团队还提供了一个“适配”脚本来支持浏览器 API。支持 HTML5 程序游戏所需的大多数浏览器功能 - 从而提高兼容性。
Adapter 脚本提供了 HTML5 程序游戏所依赖的大多数浏览器界面。
上图展示了微信小程序游戏中开发者可用的所有接口
●渲染界面
●浏览器适配器接口
●微信服务的微信API
请注意,Browser Adapter 接口不再由官方维护,因此任何附加功能都由开发人员决定。而且它的大部分依赖DOM的功能在小程序游戏环境下是不起作用的。
使用程序游戏引擎
由于开发栈的复杂性,一种选择是使用程序游戏引擎开发小程序游戏。程序游戏引擎不仅将常用的程序游戏功能封装在一个高阶接口上,还试图解决H5浏览器程序游戏与微信小程序游戏环境不兼容的问题。
开发人员可能需要使用不同层次的库——这取决于程序游戏的复杂性。使用程序游戏引擎时,它会为开发人员提供高级功能,同时调用这些相同的库。然后,开发人员需要学习引擎,并处理引擎未提供所有所需功能的情况。
以下是程序游戏引擎提供的好处摘要:
框架特点:
●对常见程序游戏开发特性的高度封装
●资源加载
●事件处理
●媒体和广播
●屏幕显示与控制
●用户输入
●附加接口,例如用于 TileMap 的 DOM 解析器
编辑:
●优化程序员、设计师和经理之间的协作。
●一个好的程序游戏编辑器可以显着缩短开发周期。
一般的:
●优秀的程序游戏引擎可以提供高设备兼容性和稳定的性能;
●跨平台程序游戏引擎可以让开发者同时发布HTML5程序游戏、微信小程序游戏和原生程序游戏。
高效的程序游戏编辑器可以降低开发成本和维护成本。对于程序游戏开发商而言,这些因素可能是盈利的关键。
4.开始搭建调试小程序游戏!
Step 1. 获取微信开发者工具
微信小程序开发者工具提供了编写、调试和运行小程序游戏的框架。在微信开发者网站这里下载。
首次运行开发者工具时,系统会要求您使用微信帐号登录。然后您将看到用于创建您的第一个项目的表单。
点击:“体验”右侧的“小程序游戏”(尝试:小程序游戏)
并为源代码选择一个位置项目目录,然后将您的项目命名为项目名称。
●页面顶部是工具栏。它可以配置、编译、预览和部署程序游戏。
●左边是模拟器。程序游戏随着代码的变化而运行和更新。
●右上角是代码编辑器。
●编辑器的左侧是文件菜单。它列出了项目文件。
●右下方是调试器。它的功能类似于Chrome开发者工具!
Step 2. 微信小程序游戏配置及文件导入
在微信小程序游戏工程中,添加配置文件project.config.json和game.json。
●project.config.json定义您的程序游戏 AppID、名称、版本、IDE 设置和运行时配置。
●game.json提供应用程序和代码的内部配置,例如设备方向和网络超时。这个类似于微信小程序中的app.json文件!
由于小程序游戏不支持 HTML 文件,入口在game.js中。您可以通过使用 require 函数将程序游戏引擎和程序游戏脚本引入game.js文件来启动程序游戏。注意: require函数的使用遵循Node.js的规范!
步骤 3. 编译、测试和提交
微信开发工具会监控脚本和配置的变化,实时更新程序游戏。您也可以点击页面顶部的编译按钮手动重新编译。当您需要在智能手机上预览程序游戏和测试时,您可以点击预览按钮生成二维码并扫描以玩程序游戏!
生成二维码的过程其实就是压缩一个小包上传到微信CDN,所以需要一定的时间。
如果微信小程序游戏遵循与小程序相同的分发规则,那么微信可能会在小程序游戏提交后对其进行审核——然后才能公开发布。也会有关于如何像小程序一样发现或分享小程序游戏的规则。

个推支持小程序消息推送,助力开发者实现用户高触达、高转化
随着小程序技术和应用场景的不断完善,越来越多的开发者搭建了小程序平台,为用户带来更“轻量”的服务。在小程序用户迅猛增长的同时,开发者对于小程序用户精细化触达的需求也愈加强烈。近日,个推消息推送上线了小程序推送功能,帮助开发者高效地连接小程序用户群体,提升用户活跃、留存和转化。

个推小程序推送提供功能完善、集成简单、使用方便的SDK解决方案,满足小程序开发者多渠道、多互动场景下的用户连接需求。通过个推小程序推送的在线+离线组合推送策略,开发者既可以在小程序在线时下发消息,实现消息毫秒级送达,也可以通过“离线模板消息”的形式对用户进行触达,最大化提升运营效果。在提高用户触达“量”的同时,个推小程序推送也支持开发者结合实际业务场景自助创建用户标签,实现精细化人群洞察、圈选、触达,提升用户触达的“质”。
此外,个推小程序推送还提供“模板管理”能力,帮助开发者高效管理小程序模板消息,优化消息转化内容与路径,让用户一站式完成内容的获取和消费,形成转化闭环。
除了小程序外,个推消息推送还高效整合了APP、Web、H5、短信等多推送渠道,并提供消息多渠道并发、分发、补发策略以及多维度的数据统计报表,帮助开发者实现多渠道灵活组合推送,提升整体运营能力。
作为国内移动推送领域的早期进入者,个推十余年来持续专注推送技术的创新与实践,帮助开发者挖掘更广阔的增长空间。比如,个推在与华为、小米、OPPO、vivo、魅族等厂商合作的基础上,今年又与荣耀达成合作,进一步加强个推消息推送的厂商推送能力,提升消息到达率。同时,为了帮助开发者高效开拓海外市场,个推推出了海外消息推送解决方案,高效整合海外消息通道,促进APP海外业务快速增长。
在新技术、新玩法、新场景不断涌现的当下,个推还将持续深耕行业应用实践、优化产品技术能力,帮助更多开发者实现与用户多平台、场景化的互动沟通,共同推动移动互联网领域创新发展。
随着小程序技术和应用场景的不断完善,越来越多的开发者搭建了小程序平台,为用户带来更“轻量”的服务。在小程序用户迅猛增长的同时,开发者对于小程序用户精细化触达的需求也愈加强烈。近日,个推消息推送上线了小程序推送功能,帮助开发者高效地连接小程序用户群体,提升用户活跃、留存和转化。
个推小程序推送提供功能完善、集成简单、使用方便的SDK解决方案,满足小程序开发者多渠道、多互动场景下的用户连接需求。通过个推小程序推送的在线+离线组合推送策略,开发者既可以在小程序在线时下发消息,实现消息毫秒级送达,也可以通过“离线模板消息”的形式对用户进行触达,最大化提升运营效果。在提高用户触达“量”的同时,个推小程序推送也支持开发者结合实际业务场景自助创建用户标签,实现精细化人群洞察、圈选、触达,提升用户触达的“质”。
此外,个推小程序推送还提供“模板管理”能力,帮助开发者高效管理小程序模板消息,优化消息转化内容与路径,让用户一站式完成内容的获取和消费,形成转化闭环。
除了小程序外,个推消息推送还高效整合了APP、Web、H5、短信等多推送渠道,并提供消息多渠道并发、分发、补发策略以及多维度的数据统计报表,帮助开发者实现多渠道灵活组合推送,提升整体运营能力。
作为国内移动推送领域的早期进入者,个推十余年来持续专注推送技术的创新与实践,帮助开发者挖掘更广阔的增长空间。比如,个推在与华为、小米、OPPO、vivo、魅族等厂商合作的基础上,今年又与荣耀达成合作,进一步加强个推消息推送的厂商推送能力,提升消息到达率。同时,为了帮助开发者高效开拓海外市场,个推推出了海外消息推送解决方案,高效整合海外消息通道,促进APP海外业务快速增长。
在新技术、新玩法、新场景不断涌现的当下,个推还将持续深耕行业应用实践、优化产品技术能力,帮助更多开发者实现与用户多平台、场景化的互动沟通,共同推动移动互联网领域创新发展。
收起阅读 »
如何优雅的使用小程序蓝牙通信 ……
问自己三个问题
1、蓝牙授权拿到了吗?
2、蓝牙模块启用了吗?
3、流程是从获取蓝牙模块状态发起的吗? (如果不是,想办法在前面流程中关闭蓝牙模块,并确保业务流程从启动蓝牙模块开始 … 后面所有流程都跑一遍,IOS是的)
const target = {
deviceId: null,
serviceId: null,
characteristicId: null,
notifyId: null,
}
// 获取蓝牙模块授权 ,这里存在已授权和已拒绝、未授权等情况,自己封装下 helperHandle.wxPreAuthorize
const initKitBluetooth = (force = false) => {
return new Promise((resolve, reject) => {
helperHandle
.wxPreAuthorize({
scope: 'scope.bluetooth',
scopeNamed: '蓝牙',
force,
})
.then(() => {
// 启动蓝牙
uni.openBluetoothAdapter({
success: async () => {
await store.dispatch('app/setBluetoothAdapterState', 2)
resolve()
},
fail: async (res) => {
let message = res.errMsg || ''
if (res.errCode === 10001) {
await store.dispatch('app/setBluetoothAdapterState', 1)
message = '请打开手机蓝牙'
} else {
await store.dispatch('app/setBluetoothAdapterState', 0)
}
reject(message)
},
})
})
.catch((reason) => {
reject(reason)
})
})
}
const linkBLEWithWrite = ({ deviceId, value = 'GET', onListen = null } = {}) => {
return new Promise((resolve, reject) => {
console.log('准备开始连接设备:' + deviceId)
createBLEConnection(deviceId)
.then(() => {
console.log('设备已连接')
target.deviceId = deviceId
// 设备已连接 # 获取服务
return getBLEDeviceServices(deviceId)
})
.then((services) => {
console.log('已获取到设备服务', services)
// 根据服务 # 获取特征值
const serviceId = services[0].uuid
target.serviceId = serviceId
return getBLEDeviceCharacteristics({
deviceId,
serviceId,
})
})
.then(async (characteristics) => {
console.log('已获取到特征值', characteristics)
// 根据特征值进行通信
// 订阅消息的特征值UUID
const notifyId = characteristics[0].uuid
target.notifyId = notifyId
// 发送指令的特征值UUID
const characteristicId = characteristics[1].uuid
target.characteristicId = characteristicId
// 判断是否订阅消息通知
if (onListen) {
console.log('开始侦听设备广播', target)
await notifyBLECharacteristicValueChange(target)
console.log('侦听已启动')
// 订阅回调
onBLECharacteristicValueChange(onListen)
}
return Promise.resolve()
})
.then(async () => {
console.log('开始向设备写入信息:' + value)
// 根据情景 # 向设备写入数据
await writeBLECharacteristicValue({
value,
deviceId: target.deviceId,
serviceId: target.serviceId,
characteristicId: target.characteristicId,
})
})
.then(() => {
console.log('已写入')
resolve()
})
.catch((reason) => {
reject(reason)
})
})
}
const clearBLEServices = (deviceId) => {
return new Promise((resolve) => {
closeBLEConnection(deviceId)
.then(() => {
console.log('连接已关闭')
return closeBluetoothAdapter()
})
.then(async () => {
await store.dispatch('app/setBluetoothAdapterState', 0)
console.log('蓝牙已关闭')
resolve()
})
})
}
问自己三个问题
1、蓝牙授权拿到了吗?
2、蓝牙模块启用了吗?
3、流程是从获取蓝牙模块状态发起的吗? (如果不是,想办法在前面流程中关闭蓝牙模块,并确保业务流程从启动蓝牙模块开始 … 后面所有流程都跑一遍,IOS是的)
const target = {
deviceId: null,
serviceId: null,
characteristicId: null,
notifyId: null,
}
// 获取蓝牙模块授权 ,这里存在已授权和已拒绝、未授权等情况,自己封装下 helperHandle.wxPreAuthorize
const initKitBluetooth = (force = false) => {
return new Promise((resolve, reject) => {
helperHandle
.wxPreAuthorize({
scope: 'scope.bluetooth',
scopeNamed: '蓝牙',
force,
})
.then(() => {
// 启动蓝牙
uni.openBluetoothAdapter({
success: async () => {
await store.dispatch('app/setBluetoothAdapterState', 2)
resolve()
},
fail: async (res) => {
let message = res.errMsg || ''
if (res.errCode === 10001) {
await store.dispatch('app/setBluetoothAdapterState', 1)
message = '请打开手机蓝牙'
} else {
await store.dispatch('app/setBluetoothAdapterState', 0)
}
reject(message)
},
})
})
.catch((reason) => {
reject(reason)
})
})
}
const linkBLEWithWrite = ({ deviceId, value = 'GET', onListen = null } = {}) => {
return new Promise((resolve, reject) => {
console.log('准备开始连接设备:' + deviceId)
createBLEConnection(deviceId)
.then(() => {
console.log('设备已连接')
target.deviceId = deviceId
// 设备已连接 # 获取服务
return getBLEDeviceServices(deviceId)
})
.then((services) => {
console.log('已获取到设备服务', services)
// 根据服务 # 获取特征值
const serviceId = services[0].uuid
target.serviceId = serviceId
return getBLEDeviceCharacteristics({
deviceId,
serviceId,
})
})
.then(async (characteristics) => {
console.log('已获取到特征值', characteristics)
// 根据特征值进行通信
// 订阅消息的特征值UUID
const notifyId = characteristics[0].uuid
target.notifyId = notifyId
// 发送指令的特征值UUID
const characteristicId = characteristics[1].uuid
target.characteristicId = characteristicId
// 判断是否订阅消息通知
if (onListen) {
console.log('开始侦听设备广播', target)
await notifyBLECharacteristicValueChange(target)
console.log('侦听已启动')
// 订阅回调
onBLECharacteristicValueChange(onListen)
}
return Promise.resolve()
})
.then(async () => {
console.log('开始向设备写入信息:' + value)
// 根据情景 # 向设备写入数据
await writeBLECharacteristicValue({
value,
deviceId: target.deviceId,
serviceId: target.serviceId,
characteristicId: target.characteristicId,
})
})
.then(() => {
console.log('已写入')
resolve()
})
.catch((reason) => {
reject(reason)
})
})
}
const clearBLEServices = (deviceId) => {
return new Promise((resolve) => {
closeBLEConnection(deviceId)
.then(() => {
console.log('连接已关闭')
return closeBluetoothAdapter()
})
.then(async () => {
await store.dispatch('app/setBluetoothAdapterState', 0)
console.log('蓝牙已关闭')
resolve()
})
})
}
收起阅读 »

uni小程序, 嵌入原生app, 文件下载到指定目录的实现方式
uni小程序, 嵌入原生app, 文件下载到指定目录的实现方式
- 实现该功能的时候, 两个问题需要解决, 1.uni.openDocument,只能打开几个常规后缀的文件 2. 下载文件下到了沙盒里面. _download开头的内部路径, 用户下次找不到了
为解决这两个问题, 我也是做了很多尝试, 现在将最终实现方式记录分享, 希望对你有些帮助
- 使用 plus.runtime.openFile 打开文件, 没有文件格式限制, 遇到打不开的文件时, 会有异常回调可以使用, 提示用户安装相关软件即可.
- 利用plus.downloader.createDownload 下载文件文档上说明只能保存到_dowloads, _doc,等几个固定开头的沙盒文件夹里, 所以只能扩展原生Moudle, 将文件移动到Download文件夹下
一下是代码部分,仅供参考
- 原生部分代码, 自定义Module,复制文件,一定要提前获取到用户的读写数据的权限!!!!!!!!!!!!!!!!!!!!!!!!!!!, 否则会提示权限问题, 文件无法复制
public class UniUesOaHeModule extends UniModule {
/**
* 输出日志
*/
@UniJSMethod(uiThread = false)
public void uniLog(String s) {
XLog.debug(s);
}
/**
* 沙盒文件移入媒体库
*/
@UniJSMethod(uiThread = false)
public void scanIntoMedia(String filePath, UniJSCallback callback) {
if (StringUtil.isEmpty(filePath)) {
if (callback != null) {
JSONObject data = new JSONObject();
data.put("code", "error");
data.put("message", "文件路径不能为空");
callback.invokeAndKeepAlive(data);
}
}
if (mUniSDKInstance != null) {
String[] split = filePath.split("/");
String fileName = split[split.length -1];
String targetPath = "/storage/emulated/0/Download/" + fileName;
FileUtil fileUtil = FileUtil.INSTANCE;
XLog.debug("开始转义目录");
XLog.debug("由:[ " + filePath + " ] 转移至 : [ "+ targetPath + " ]");
//tod
fileUtil.copyFileWithFileChannel(new File(filePath), new File(targetPath));
//对于小米来说, 拷贝到Downloads文件夹下, 系统就会自动扫描到了, 不知道其他机型怎么样
//图片等常规文件,能出现在 `最近`里, dwg在Download里能找到, 但是不会在 `最近` 里显示,可能是系统原因
Context context = mUniSDKInstance.getContext();
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.parse(targetPath);
XLog.debug("将要扫描的地址是: " + contentUri.getPath());
mediaScanIntent.setData(contentUri);
context.sendBroadcast(mediaScanIntent);
XLog.debug("发送扫描指令");
if (callback != null) {
JSONObject data = new JSONObject();
data.put("code", "success");
data.put("message", "添加到Download目录成功");
data.put("filePathAfterMove", "file:/"+ targetPath);
callback.invokeAndKeepAlive(data);
}
}else{
XLog.debug("实例不存在");
if (callback != null) {
JSONObject data = new JSONObject();
data.put("code", "error");
data.put("message", "实例不存在");
callback.invokeAndKeepAlive(data);
}
}
/**
* 下面的不管用, 可能是只能扫描固定的几个媒体库路径, 上面的直接复制到Downloads下, 不用扫描都能发现
*/
//
// if (StringUtil.isEmpty(filePath)) {
// if (callback != null) {
// JSONObject data = new JSONObject();
// data.put("code", "error");
// data.put("message", "文件路径不能为空");
// callback.invokeAndKeepAlive(data);
// }
// }
// if (mUniSDKInstance != null) {
// Context context = mUniSDKInstance.getContext();
// try {
// MediaScannerConnection.scanFile(context, new String[]{filePath}, null,
// new MediaScannerConnection.OnScanCompletedListener() {
// public void onScanCompleted(String path, Uri uri) {
// XLog.debug("扫描的路径是: " + path + ":");
// XLog.debug("返回的uri: " + uri);
// if (callback != null) {
// JSONObject data = new JSONObject();
// data.put("code", "success");
// data.put("message", "媒体库扫描完成!!!!");
// callback.invokeAndKeepAlive(data);
// }
// }
// });
// } catch (Exception e) {
// e.printStackTrace();
// }
// } else {
// if (callback != null) {
// JSONObject data = new JSONObject();
// data.put("code", "error");
// data.put("message", "移入媒体库失败, 找不到mUniSDKInstance实例");
// callback.invokeAndKeepAlive(data);
// }
// }
}
}
- FileUtil, 只贴这一个复制文件的函数
/**
* 复制文件
*/
fun copyFileWithFileChannel(fileSource: File, fileDest:File) {
var fi: FileInputStream? = null
var fo: FileOutputStream? = null
var `in`: FileChannel? = null
var out: FileChannel? = null
try {
fi = FileInputStream(fileSource)
fo = FileOutputStream(fileDest)
`in` = fi.channel//得到对应的文件通道
out = fo.channel//得到对应的文件通道
`in`!!.transferTo(0, `in`.size(), out)//连接两个通道,并且从in通道读取,然后写入out通道
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
fi!!.close()
`in`!!.close()
fo!!.close()
out!!.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
别忘记注册组件
UniSDKEngine.registerModule("UniUesOaHeModule", UniUesOaHeModule::class.java)
uni小程序代码, download2是最终使用的方法, 其他的是测试时使用的
<template>
<!-- 这是图纸展示页面 -->
<page-head title="无匹配数据" v-if="designDrawingArray.length == 0"></page-head>
<view v-else>
<!-- {{designDrawingArray}} -->
<view v-for="(item, index) in designDrawingArray" :key="item.id">
<!-- {{ item.name }} - {{ item.remark }} -->
<uni-card :title="item.name" :sub-title="item.remark">
<view v-if="item.attachmentList.length > 0">
<uni-list>
<uni-list-item v-for="(attachmentItem, attachmentIndex) in item.attachmentList"
:key="attachmentItem.id" :title="attachmentItem.attachName"
@click="download2(attachmentItem.attachUrl,attachmentItem.attachSize)" showArrow link>
</uni-list-item>
</uni-list>
</view>
<view v-else>
暂无图纸
</view>
</uni-card>
</view>
</view>
</template>
<script>
import config from "@/common/config.js"
var uniUesOaHeModule = uni.requireNativePlugin("UniUesOaHeModule")
export default {
data() {
return {
designDrawingArray: []
}
},
onLoad(e) {
var contractType = e.contractType
var contractId = e.contractId
this.queryHeDesignDrawingWithAttachmentList(contractType, contractId)
},
methods: {
queryHeDesignDrawingWithAttachmentList(contractType, contractId) {
this.$http.post({
url: '/engineering/queryHeDesignDrawingWithAttachmentList.action',
data: {
contractType: contractType,
contractId: contractId
},
success: (res) => {
console.log(res)
if (res.status == 200) {
this.designDrawingArray = res.data
}
}
})
},
// 该方法不再使用, openDocument只能打开常用格式的文件, 不能打开dwg
downLoadAttachment(url) {
var downloadUrl = config.serverUrl + url
console.log("下载地址---" + downloadUrl)
uni.showModal({
title: '提示',
content: '确定下载该图纸?',
success: function(res) {
if (res.confirm) {
uni.showLoading({
title: '下载中'
})
var self = this
uni.downloadFile({
url: downloadUrl,
success: (res) => {
//保存到本地
console.log("下载成功,临时路径是---" + res.tempFilePath)
uni.saveFile({
tempFilePath: res.tempFilePath, //文件的临时路径
success: function(res) {
const savedFilePath = res.savedFilePath;
uni.showToast({
title: '将临时文件保存完成,保存的地址为:' +
savedFilePath,
icon: 'none'
});
uni.hideLoading();
uni.showModal({
title: '提示',
content: '打开下载文件?',
success: function(res) {
// 打开文件
if (res.confirm) {
uni.showToast({
title: '点击了确定打开文件',
icon: 'none'
});
uni.openDocument({
filePath: savedFilePath,
showMenu: true,
success: function(
res
) {
console
.log(
'打开文档成功'
);
uni.showToast({
title: '打开文档成功',
icon: 'none'
});
},
fail: function(
res
) {
console
.log(
'打开文档失败'
);
uni.showToast({
title: '打开文档失败',
icon: 'none'
});
}
});
}
}
})
},
fail: function(err) {}
});
},
fail: (err) => {
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
})
}
}
});
},
download2(url,fileSize) {
var logger = this.$log
var downloadUrl = config.serverUrl + url
logger(uniUesOaHeModule, "下载地址---" + downloadUrl)
//测试方便,使用390576, 正式使用下面50M
// if(fileSize > 1000 * 1000 * 50){
if(fileSize > 390576){
logger(uniUesOaHeModule, "文件大于50M,使用浏览器下载: "+ fileSize)
plus.runtime.openURL(downloadUrl)
}else{
logger(uniUesOaHeModule, "文件小于50M: "+ fileSize)
var pathArray = url.split('/')
var fileName = pathArray[pathArray.length - 1]
uni.showLoading({
title: '下载中'
})
let dtask = plus.downloader.createDownload(downloadUrl, {
//利用保存路径,实现下载文件的重命名,文档上说明只能保存到_dowloads, _doc,等几个固定开头的沙盒文件夹里,
// filename:localFile
}, function(d, status) {
uni.hideLoading();
if (status == 200) {
//下载成功,d.filename是文件在保存在本地的相对路径,使用下面的API可转为平台绝对路径
logger(uniUesOaHeModule, "下载完成了,现在要去打开文件,文件地址也就是d.fileName: " + d.filename)
let fileSaveUrl = plus.io.convertLocalFileSystemURL(d.filename);
logger(uniUesOaHeModule, "将d.fileName 沙盒文件路径转化为android平台的路径: " + fileSaveUrl)
//这样下载打开文件, 除非用户自己操作或移动到自己知道的文件夹下, 下次他就找不到了, 因此调用原生Module,将这个文件放到/Download下,下次能看到
uniUesOaHeModule.scanIntoMedia(fileSaveUrl,
(ret) => {
console.log(JSON.stringify(ret))
if(ret.code == 'success'){
//文件转移到Download目录成功了
plus.runtime.openFile(ret.filePathAfterMove,{},function(error){
logger(uniUesOaHeModule,"打开文件有问题: "+ JSON.stringify(error))
uni.showToast({
title: '没有能打开该文件的软件,请安装',
icon: 'none',
duration: 3000
});
});
}
})
} else {
uni.showToast({
title: '下载失败',
icon: 'none',
duration: 3000
});
plus.downloader.clear(); //清除下载任务
}
})
dtask.addEventListener("statechanged", function(task, status) {
logger(uniUesOaHeModule, "task:" + JSON.stringify(task) + "===========status" + status)
switch (task.state) {
case 2:
break;
case 3:
let prg = parseInt((parseFloat(task.downloadedSize) / parseFloat(task.totalSize)) *100);
// logger(uniUesOaHeModule, prg + "%")
break;
case 4:
break;
}
}, false);
dtask.start();
}
},
test(url, fileSize){
var logger = this.$log
var downloadUrl = config.serverUrl + url
logger(uniUesOaHeModule, "下载地址---" + downloadUrl)
// uni.showToast({
// title: '测试打开外部文件',
// icon: 'none'
// });
// plus.runtime.openFile("file://storage/emulated/0/Download/e96cca6878b14911940c4ad9db8e1ff7_a(9).jpg",{},function(error){
// // logger(uniUesOaHeModule,"打开文件出错: "+ JSON.stringify(error))
// console.log(JSON.stringify(error))
// uni.showToast({
// title: '1111111111',
// icon: 'none'
// });
// });
//大于50M, 建议使用浏览器下载
if(fileSize > 1000 * 1000 * 50){
logger(uniUesOaHeModule, "文件大于50M: "+ fileSize)
}else{
logger(uniUesOaHeModule, "文件小于50M: "+ fileSize)
}
plus.runtime.openURL(downloadUrl)
}
}
}
</script>
<style>
</style>
uni小程序, 嵌入原生app, 文件下载到指定目录的实现方式
- 实现该功能的时候, 两个问题需要解决, 1.uni.openDocument,只能打开几个常规后缀的文件 2. 下载文件下到了沙盒里面. _download开头的内部路径, 用户下次找不到了
为解决这两个问题, 我也是做了很多尝试, 现在将最终实现方式记录分享, 希望对你有些帮助
- 使用 plus.runtime.openFile 打开文件, 没有文件格式限制, 遇到打不开的文件时, 会有异常回调可以使用, 提示用户安装相关软件即可.
- 利用plus.downloader.createDownload 下载文件文档上说明只能保存到_dowloads, _doc,等几个固定开头的沙盒文件夹里, 所以只能扩展原生Moudle, 将文件移动到Download文件夹下
一下是代码部分,仅供参考
- 原生部分代码, 自定义Module,复制文件,一定要提前获取到用户的读写数据的权限!!!!!!!!!!!!!!!!!!!!!!!!!!!, 否则会提示权限问题, 文件无法复制
public class UniUesOaHeModule extends UniModule {
/**
* 输出日志
*/
@UniJSMethod(uiThread = false)
public void uniLog(String s) {
XLog.debug(s);
}
/**
* 沙盒文件移入媒体库
*/
@UniJSMethod(uiThread = false)
public void scanIntoMedia(String filePath, UniJSCallback callback) {
if (StringUtil.isEmpty(filePath)) {
if (callback != null) {
JSONObject data = new JSONObject();
data.put("code", "error");
data.put("message", "文件路径不能为空");
callback.invokeAndKeepAlive(data);
}
}
if (mUniSDKInstance != null) {
String[] split = filePath.split("/");
String fileName = split[split.length -1];
String targetPath = "/storage/emulated/0/Download/" + fileName;
FileUtil fileUtil = FileUtil.INSTANCE;
XLog.debug("开始转义目录");
XLog.debug("由:[ " + filePath + " ] 转移至 : [ "+ targetPath + " ]");
//tod
fileUtil.copyFileWithFileChannel(new File(filePath), new File(targetPath));
//对于小米来说, 拷贝到Downloads文件夹下, 系统就会自动扫描到了, 不知道其他机型怎么样
//图片等常规文件,能出现在 `最近`里, dwg在Download里能找到, 但是不会在 `最近` 里显示,可能是系统原因
Context context = mUniSDKInstance.getContext();
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.parse(targetPath);
XLog.debug("将要扫描的地址是: " + contentUri.getPath());
mediaScanIntent.setData(contentUri);
context.sendBroadcast(mediaScanIntent);
XLog.debug("发送扫描指令");
if (callback != null) {
JSONObject data = new JSONObject();
data.put("code", "success");
data.put("message", "添加到Download目录成功");
data.put("filePathAfterMove", "file:/"+ targetPath);
callback.invokeAndKeepAlive(data);
}
}else{
XLog.debug("实例不存在");
if (callback != null) {
JSONObject data = new JSONObject();
data.put("code", "error");
data.put("message", "实例不存在");
callback.invokeAndKeepAlive(data);
}
}
/**
* 下面的不管用, 可能是只能扫描固定的几个媒体库路径, 上面的直接复制到Downloads下, 不用扫描都能发现
*/
//
// if (StringUtil.isEmpty(filePath)) {
// if (callback != null) {
// JSONObject data = new JSONObject();
// data.put("code", "error");
// data.put("message", "文件路径不能为空");
// callback.invokeAndKeepAlive(data);
// }
// }
// if (mUniSDKInstance != null) {
// Context context = mUniSDKInstance.getContext();
// try {
// MediaScannerConnection.scanFile(context, new String[]{filePath}, null,
// new MediaScannerConnection.OnScanCompletedListener() {
// public void onScanCompleted(String path, Uri uri) {
// XLog.debug("扫描的路径是: " + path + ":");
// XLog.debug("返回的uri: " + uri);
// if (callback != null) {
// JSONObject data = new JSONObject();
// data.put("code", "success");
// data.put("message", "媒体库扫描完成!!!!");
// callback.invokeAndKeepAlive(data);
// }
// }
// });
// } catch (Exception e) {
// e.printStackTrace();
// }
// } else {
// if (callback != null) {
// JSONObject data = new JSONObject();
// data.put("code", "error");
// data.put("message", "移入媒体库失败, 找不到mUniSDKInstance实例");
// callback.invokeAndKeepAlive(data);
// }
// }
}
}
- FileUtil, 只贴这一个复制文件的函数
/**
* 复制文件
*/
fun copyFileWithFileChannel(fileSource: File, fileDest:File) {
var fi: FileInputStream? = null
var fo: FileOutputStream? = null
var `in`: FileChannel? = null
var out: FileChannel? = null
try {
fi = FileInputStream(fileSource)
fo = FileOutputStream(fileDest)
`in` = fi.channel//得到对应的文件通道
out = fo.channel//得到对应的文件通道
`in`!!.transferTo(0, `in`.size(), out)//连接两个通道,并且从in通道读取,然后写入out通道
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
fi!!.close()
`in`!!.close()
fo!!.close()
out!!.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
别忘记注册组件
UniSDKEngine.registerModule("UniUesOaHeModule", UniUesOaHeModule::class.java)
uni小程序代码, download2是最终使用的方法, 其他的是测试时使用的
<template>
<!-- 这是图纸展示页面 -->
<page-head title="无匹配数据" v-if="designDrawingArray.length == 0"></page-head>
<view v-else>
<!-- {{designDrawingArray}} -->
<view v-for="(item, index) in designDrawingArray" :key="item.id">
<!-- {{ item.name }} - {{ item.remark }} -->
<uni-card :title="item.name" :sub-title="item.remark">
<view v-if="item.attachmentList.length > 0">
<uni-list>
<uni-list-item v-for="(attachmentItem, attachmentIndex) in item.attachmentList"
:key="attachmentItem.id" :title="attachmentItem.attachName"
@click="download2(attachmentItem.attachUrl,attachmentItem.attachSize)" showArrow link>
</uni-list-item>
</uni-list>
</view>
<view v-else>
暂无图纸
</view>
</uni-card>
</view>
</view>
</template>
<script>
import config from "@/common/config.js"
var uniUesOaHeModule = uni.requireNativePlugin("UniUesOaHeModule")
export default {
data() {
return {
designDrawingArray: []
}
},
onLoad(e) {
var contractType = e.contractType
var contractId = e.contractId
this.queryHeDesignDrawingWithAttachmentList(contractType, contractId)
},
methods: {
queryHeDesignDrawingWithAttachmentList(contractType, contractId) {
this.$http.post({
url: '/engineering/queryHeDesignDrawingWithAttachmentList.action',
data: {
contractType: contractType,
contractId: contractId
},
success: (res) => {
console.log(res)
if (res.status == 200) {
this.designDrawingArray = res.data
}
}
})
},
// 该方法不再使用, openDocument只能打开常用格式的文件, 不能打开dwg
downLoadAttachment(url) {
var downloadUrl = config.serverUrl + url
console.log("下载地址---" + downloadUrl)
uni.showModal({
title: '提示',
content: '确定下载该图纸?',
success: function(res) {
if (res.confirm) {
uni.showLoading({
title: '下载中'
})
var self = this
uni.downloadFile({
url: downloadUrl,
success: (res) => {
//保存到本地
console.log("下载成功,临时路径是---" + res.tempFilePath)
uni.saveFile({
tempFilePath: res.tempFilePath, //文件的临时路径
success: function(res) {
const savedFilePath = res.savedFilePath;
uni.showToast({
title: '将临时文件保存完成,保存的地址为:' +
savedFilePath,
icon: 'none'
});
uni.hideLoading();
uni.showModal({
title: '提示',
content: '打开下载文件?',
success: function(res) {
// 打开文件
if (res.confirm) {
uni.showToast({
title: '点击了确定打开文件',
icon: 'none'
});
uni.openDocument({
filePath: savedFilePath,
showMenu: true,
success: function(
res
) {
console
.log(
'打开文档成功'
);
uni.showToast({
title: '打开文档成功',
icon: 'none'
});
},
fail: function(
res
) {
console
.log(
'打开文档失败'
);
uni.showToast({
title: '打开文档失败',
icon: 'none'
});
}
});
}
}
})
},
fail: function(err) {}
});
},
fail: (err) => {
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
})
}
}
});
},
download2(url,fileSize) {
var logger = this.$log
var downloadUrl = config.serverUrl + url
logger(uniUesOaHeModule, "下载地址---" + downloadUrl)
//测试方便,使用390576, 正式使用下面50M
// if(fileSize > 1000 * 1000 * 50){
if(fileSize > 390576){
logger(uniUesOaHeModule, "文件大于50M,使用浏览器下载: "+ fileSize)
plus.runtime.openURL(downloadUrl)
}else{
logger(uniUesOaHeModule, "文件小于50M: "+ fileSize)
var pathArray = url.split('/')
var fileName = pathArray[pathArray.length - 1]
uni.showLoading({
title: '下载中'
})
let dtask = plus.downloader.createDownload(downloadUrl, {
//利用保存路径,实现下载文件的重命名,文档上说明只能保存到_dowloads, _doc,等几个固定开头的沙盒文件夹里,
// filename:localFile
}, function(d, status) {
uni.hideLoading();
if (status == 200) {
//下载成功,d.filename是文件在保存在本地的相对路径,使用下面的API可转为平台绝对路径
logger(uniUesOaHeModule, "下载完成了,现在要去打开文件,文件地址也就是d.fileName: " + d.filename)
let fileSaveUrl = plus.io.convertLocalFileSystemURL(d.filename);
logger(uniUesOaHeModule, "将d.fileName 沙盒文件路径转化为android平台的路径: " + fileSaveUrl)
//这样下载打开文件, 除非用户自己操作或移动到自己知道的文件夹下, 下次他就找不到了, 因此调用原生Module,将这个文件放到/Download下,下次能看到
uniUesOaHeModule.scanIntoMedia(fileSaveUrl,
(ret) => {
console.log(JSON.stringify(ret))
if(ret.code == 'success'){
//文件转移到Download目录成功了
plus.runtime.openFile(ret.filePathAfterMove,{},function(error){
logger(uniUesOaHeModule,"打开文件有问题: "+ JSON.stringify(error))
uni.showToast({
title: '没有能打开该文件的软件,请安装',
icon: 'none',
duration: 3000
});
});
}
})
} else {
uni.showToast({
title: '下载失败',
icon: 'none',
duration: 3000
});
plus.downloader.clear(); //清除下载任务
}
})
dtask.addEventListener("statechanged", function(task, status) {
logger(uniUesOaHeModule, "task:" + JSON.stringify(task) + "===========status" + status)
switch (task.state) {
case 2:
break;
case 3:
let prg = parseInt((parseFloat(task.downloadedSize) / parseFloat(task.totalSize)) *100);
// logger(uniUesOaHeModule, prg + "%")
break;
case 4:
break;
}
}, false);
dtask.start();
}
},
test(url, fileSize){
var logger = this.$log
var downloadUrl = config.serverUrl + url
logger(uniUesOaHeModule, "下载地址---" + downloadUrl)
// uni.showToast({
// title: '测试打开外部文件',
// icon: 'none'
// });
// plus.runtime.openFile("file://storage/emulated/0/Download/e96cca6878b14911940c4ad9db8e1ff7_a(9).jpg",{},function(error){
// // logger(uniUesOaHeModule,"打开文件出错: "+ JSON.stringify(error))
// console.log(JSON.stringify(error))
// uni.showToast({
// title: '1111111111',
// icon: 'none'
// });
// });
//大于50M, 建议使用浏览器下载
if(fileSize > 1000 * 1000 * 50){
logger(uniUesOaHeModule, "文件大于50M: "+ fileSize)
}else{
logger(uniUesOaHeModule, "文件小于50M: "+ fileSize)
}
plus.runtime.openURL(downloadUrl)
}
}
}
</script>
<style>
</style>
收起阅读 »

个推TechDay直播预告 | 8月24日晚19:30,实时数仓搭建保姆级教程开课
当下,企业的实时计算需求越来越高频,很多企业和组织选择建设实时数据仓库,以敏捷支撑实时报表分析、智能算法推荐、系统风险预警等多元业务场景需求。
相比离线数仓,实时数仓有哪些特性?如何进行实时数仓的技术选型?
个推TechDay“治数训练营”系列直播课第二期来了!
8月24日(下周三)晚上19:30-20:30,个推资深数据研发工程师为您解读实时数仓架构演进,分享实时数仓技术选型要点,并结合实战案例详细剖析实时数仓的搭建秘诀。
更有超多惊喜福利等你拿!
当下,企业的实时计算需求越来越高频,很多企业和组织选择建设实时数据仓库,以敏捷支撑实时报表分析、智能算法推荐、系统风险预警等多元业务场景需求。
相比离线数仓,实时数仓有哪些特性?如何进行实时数仓的技术选型?
个推TechDay“治数训练营”系列直播课第二期来了!
8月24日(下周三)晚上19:30-20:30,个推资深数据研发工程师为您解读实时数仓架构演进,分享实时数仓技术选型要点,并结合实战案例详细剖析实时数仓的搭建秘诀。
更有超多惊喜福利等你拿!
收起阅读 »
插件开发 | 插件需求 | 个人接单 | 多年uni原生插件开发| iOS+Android 。插件定制,欢迎骚扰
插件开发 | 插件需求 | 个人接单 | 多年uni原生插件开发| iOS+Android | 插件定制 | uniapp | 欢迎骚扰
uni原生插件 ! 个人接单!多年uni原生插件开发,原生sdk接入,iOS+Android。uni插件 5+插件,
小程序,uni前端,也可以~
阿里
阿里认证 | 阿里oss上传 | 阿里播放器
支付宝资金预授权 | 一键登录 | 阿里云短视频
阿里云风险控制 |
百度
百度播放器 | 文字识别 | 百度统计
离线人脸识别
腾讯
人脸识别 | 超级播放器 | IM单聊+离线推送
短视频编辑 | 短视频制作
支付类
中金支付 | 全民支付 | nihaooPay
latiPay | funsionPau | adyen drop-in
聚富通支付 |
国外
stripe pay | google map 谷歌地图 | twitter登录 | facebook登录/分享
微软授权登录 | FB AppEvents | FCM 谷歌推送 | firebase授权登录
google 登录
android平台
迅雷下载无限速 | 屏蔽短信 | 屏蔽通知 | usb 串口 | 呼叫转移
android分片上传(byte) | 华视身份证 | 获取通话记录 | 广告机 关机 重启 设置系统时间
DLNA | 悬浮窗截屏 | 获取通知 | hainaIOT
isProxy(是否使用代理) | 录制PCM | soundtouch(变声) | pcm 降噪
无障碍微信朋友圈多图分享 | 矩阵识别 | RDPOrigin | rfid
sicpay支付 | nihaoPay支付 | ios 裁剪 | 字节美颜组件
linphone sip | 快手分享sdk | 快手登录sdk | 极光分享 | 极光短信
极光认证 | 极光统计 | 汉枫配网 (SmartLink ) | 华为畅联(CaasKit)
助听器 耳返 | otg 单反相机 | TuyaSDK | Paddle文字识别
个推统计 | 自动通话录音 | 来电自动接听 | mqtt双向认证
vpn | openvpn | 禁止 app 联网 | 获取单反相机照片 Nikon
ios 平台
ios 后台播放 | ios后台持续录音 | ios后台定位 | 录制PCM | NFC读写
高精度图片扫码 | 矩阵识别 | nihaoPay支付 | ios healthkit (健康)
NEHotspot(设置wifi) | ios 裁剪 | 阿里云短视频制作 | 快手分享sdk
快手登录sdk | 汉枫配网 (SmartLink ) | 助听器 耳返 | 个推统计
mqtt双向认证 | vpn | openvpn |
工具类
svga播放器 | ios Wifi ssid Wifi mac地址 | 高德导航组件 | 高德导航component
快递单号识别 读取 | 手机号码识别 | 经典蓝牙2.0 nRF DFU SDK | android dfu
ios dfu | 蓝牙空中升级 | ios 后台录音
虹软人脸识别 | 蓝牙耳机录音 | 摄像头预览组件 | 录制PCM | 两端录屏
拦截截图 | 迅雷下载(不限速) | 微信分享第三方来源 | yuqueAD(广告)
mobAD(广告) | 矩阵识别 | 助听器 耳返 | 友盟统计
个推统计 (个数统计) | 自动通话录音 | 来电自动接听 | 控制其他app联网
音视频类
萤石云直播与回放 | 环信视频通话 | 美摄拍摄 | 环信音视频通话 | 音视频合成 | 腾讯推拉流
金山云推拉流 | 美狐美颜 | 字节美颜 | 相芯美颜 | 相芯背景分割
等等等等。。。。。
QQ 2253764427
QQ群 : 2253764427( uniapp原生插件开发 )
欢迎骚扰~
插件开发 | 插件需求 | 个人接单 | 多年uni原生插件开发| iOS+Android | 插件定制 | uniapp | 欢迎骚扰
uni原生插件 ! 个人接单!多年uni原生插件开发,原生sdk接入,iOS+Android。uni插件 5+插件,
小程序,uni前端,也可以~
阿里
阿里认证 | 阿里oss上传 | 阿里播放器
支付宝资金预授权 | 一键登录 | 阿里云短视频
阿里云风险控制 |
百度
百度播放器 | 文字识别 | 百度统计
离线人脸识别
腾讯
人脸识别 | 超级播放器 | IM单聊+离线推送
短视频编辑 | 短视频制作
支付类
中金支付 | 全民支付 | nihaooPay
latiPay | funsionPau | adyen drop-in
聚富通支付 |
国外
stripe pay | google map 谷歌地图 | twitter登录 | facebook登录/分享
微软授权登录 | FB AppEvents | FCM 谷歌推送 | firebase授权登录
google 登录
android平台
迅雷下载无限速 | 屏蔽短信 | 屏蔽通知 | usb 串口 | 呼叫转移
android分片上传(byte) | 华视身份证 | 获取通话记录 | 广告机 关机 重启 设置系统时间
DLNA | 悬浮窗截屏 | 获取通知 | hainaIOT
isProxy(是否使用代理) | 录制PCM | soundtouch(变声) | pcm 降噪
无障碍微信朋友圈多图分享 | 矩阵识别 | RDPOrigin | rfid
sicpay支付 | nihaoPay支付 | ios 裁剪 | 字节美颜组件
linphone sip | 快手分享sdk | 快手登录sdk | 极光分享 | 极光短信
极光认证 | 极光统计 | 汉枫配网 (SmartLink ) | 华为畅联(CaasKit)
助听器 耳返 | otg 单反相机 | TuyaSDK | Paddle文字识别
个推统计 | 自动通话录音 | 来电自动接听 | mqtt双向认证
vpn | openvpn | 禁止 app 联网 | 获取单反相机照片 Nikon
ios 平台
ios 后台播放 | ios后台持续录音 | ios后台定位 | 录制PCM | NFC读写
高精度图片扫码 | 矩阵识别 | nihaoPay支付 | ios healthkit (健康)
NEHotspot(设置wifi) | ios 裁剪 | 阿里云短视频制作 | 快手分享sdk
快手登录sdk | 汉枫配网 (SmartLink ) | 助听器 耳返 | 个推统计
mqtt双向认证 | vpn | openvpn |
工具类
svga播放器 | ios Wifi ssid Wifi mac地址 | 高德导航组件 | 高德导航component
快递单号识别 读取 | 手机号码识别 | 经典蓝牙2.0 nRF DFU SDK | android dfu
ios dfu | 蓝牙空中升级 | ios 后台录音
虹软人脸识别 | 蓝牙耳机录音 | 摄像头预览组件 | 录制PCM | 两端录屏
拦截截图 | 迅雷下载(不限速) | 微信分享第三方来源 | yuqueAD(广告)
mobAD(广告) | 矩阵识别 | 助听器 耳返 | 友盟统计
个推统计 (个数统计) | 自动通话录音 | 来电自动接听 | 控制其他app联网
音视频类
萤石云直播与回放 | 环信视频通话 | 美摄拍摄 | 环信音视频通话 | 音视频合成 | 腾讯推拉流
金山云推拉流 | 美狐美颜 | 字节美颜 | 相芯美颜 | 相芯背景分割
等等等等。。。。。
QQ 2253764427
QQ群 : 2253764427( uniapp原生插件开发 )
欢迎骚扰~
收起阅读 »