Browse Source

init

master
zhizhi wu 4 years ago
commit
8bba4267bd
  1. 21
      README.md
  2. 3
      demo/.gitignore
  3. 69
      demo/README.md
  4. 120
      demo/cloudfunctions/login/index.js
  5. 932
      demo/cloudfunctions/login/package-lock.json
  6. 14
      demo/cloudfunctions/login/package.json
  7. 215
      demo/miniprogram/app.js
  8. 40
      demo/miniprogram/app.json
  9. 199
      demo/miniprogram/app.wxss
  10. 20
      demo/miniprogram/assets/common/check-blue.svg
  11. 1
      demo/miniprogram/assets/common/loading.svg
  12. 42
      demo/miniprogram/assets/common/more.svg
  13. 13
      demo/miniprogram/assets/common/plus-blue.svg
  14. 9
      demo/miniprogram/assets/common/plus-white.svg
  15. 1
      demo/miniprogram/assets/device/connecting-loading.svg
  16. 38
      demo/miniprogram/assets/user/avatar-default.svg
  17. 41
      demo/miniprogram/components/btn-group/btn-group.js
  18. 3
      demo/miniprogram/components/btn-group/btn-group.json
  19. 19
      demo/miniprogram/components/btn-group/btn-group.wxml
  20. 0
      demo/miniprogram/components/btn-group/btn-group.wxss
  21. 16
      demo/miniprogram/components/ios-wifi-guide/ios-wifi-guide.js
  22. 6
      demo/miniprogram/components/ios-wifi-guide/ios-wifi-guide.json
  23. 37
      demo/miniprogram/components/ios-wifi-guide/ios-wifi-guide.wxml
  24. 72
      demo/miniprogram/components/ios-wifi-guide/ios-wifi-guide.wxss
  25. 98
      demo/miniprogram/components/page-wrapper/page-wrapper.js
  26. 7
      demo/miniprogram/components/page-wrapper/page-wrapper.json
  27. 27
      demo/miniprogram/components/page-wrapper/page-wrapper.wxml
  28. 31
      demo/miniprogram/components/page-wrapper/page-wrapper.wxss
  29. 38
      demo/miniprogram/components/single-button/single-button.js
  30. 6
      demo/miniprogram/components/single-button/single-button.json
  31. 7
      demo/miniprogram/components/single-button/single-button.wxml
  32. 0
      demo/miniprogram/components/single-button/single-button.wxss
  33. 274
      demo/miniprogram/components/wifi-form/wifi-form.js
  34. 6
      demo/miniprogram/components/wifi-form/wifi-form.json
  35. 56
      demo/miniprogram/components/wifi-form/wifi-form.wxml
  36. 16
      demo/miniprogram/components/wifi-form/wifi-form.wxss
  37. 14
      demo/miniprogram/constants.js
  38. 637
      demo/miniprogram/libs/redux.js
  39. 43
      demo/miniprogram/libs/store-subscribe.js
  40. 93
      demo/miniprogram/libs/utillib.js
  41. 8
      demo/miniprogram/libs/wx-promisify.js
  42. 230
      demo/miniprogram/models.js
  43. 35
      demo/miniprogram/package.json
  44. 9
      demo/miniprogram/pages/add-device/air-kiss/air-kiss.js
  45. 6
      demo/miniprogram/pages/add-device/air-kiss/air-kiss.json
  46. 5
      demo/miniprogram/pages/add-device/air-kiss/air-kiss.wxml
  47. 0
      demo/miniprogram/pages/add-device/air-kiss/air-kiss.wxss
  48. 10
      demo/miniprogram/pages/add-device/ble-combo/ble-combo.js
  49. 6
      demo/miniprogram/pages/add-device/ble-combo/ble-combo.json
  50. 5
      demo/miniprogram/pages/add-device/ble-combo/ble-combo.wxml
  51. 0
      demo/miniprogram/pages/add-device/ble-combo/ble-combo.wxss
  52. 63
      demo/miniprogram/pages/add-device/common.js
  53. 22
      demo/miniprogram/pages/add-device/common.wxss
  54. 25
      demo/miniprogram/pages/add-device/components/bluetooth-finder/blueToothAdapter.js
  55. 106
      demo/miniprogram/pages/add-device/components/bluetooth-finder/bluetooth-finder.js
  56. 6
      demo/miniprogram/pages/add-device/components/bluetooth-finder/bluetooth-finder.json
  57. 30
      demo/miniprogram/pages/add-device/components/bluetooth-finder/bluetooth-finder.wxml
  58. 11
      demo/miniprogram/pages/add-device/components/bluetooth-finder/bluetooth-finder.wxss
  59. 27
      demo/miniprogram/pages/add-device/components/do-config/do-config.js
  60. 6
      demo/miniprogram/pages/add-device/components/do-config/do-config.json
  61. 10
      demo/miniprogram/pages/add-device/components/do-config/do-config.wxml
  62. 66
      demo/miniprogram/pages/add-device/components/do-config/do-config.wxss
  63. 44
      demo/miniprogram/pages/add-device/components/error-view/error-view.js
  64. 6
      demo/miniprogram/pages/add-device/components/error-view/error-view.json
  65. 34
      demo/miniprogram/pages/add-device/components/error-view/error-view.wxml
  66. 52
      demo/miniprogram/pages/add-device/components/error-view/error-view.wxss
  67. 16
      demo/miniprogram/pages/add-device/components/guide/guide.js
  68. 6
      demo/miniprogram/pages/add-device/components/guide/guide.json
  69. 17
      demo/miniprogram/pages/add-device/components/guide/guide.wxml
  70. 23
      demo/miniprogram/pages/add-device/components/guide/guide.wxss
  71. 57
      demo/miniprogram/pages/add-device/components/input-wifi-info/input-wifi-info.js
  72. 7
      demo/miniprogram/pages/add-device/components/input-wifi-info/input-wifi-info.json
  73. 9
      demo/miniprogram/pages/add-device/components/input-wifi-info/input-wifi-info.wxml
  74. 10
      demo/miniprogram/pages/add-device/components/input-wifi-info/input-wifi-info.wxss
  75. 16
      demo/miniprogram/pages/add-device/components/success-view/success-view.js
  76. 6
      demo/miniprogram/pages/add-device/components/success-view/success-view.json
  77. 9
      demo/miniprogram/pages/add-device/components/success-view/success-view.wxml
  78. 43
      demo/miniprogram/pages/add-device/components/success-view/success-view.wxss
  79. 248
      demo/miniprogram/pages/add-device/components/wifi-conf/wifi-conf.js
  80. 13
      demo/miniprogram/pages/add-device/components/wifi-conf/wifi-conf.json
  81. 46
      demo/miniprogram/pages/add-device/components/wifi-conf/wifi-conf.wxml
  82. 1
      demo/miniprogram/pages/add-device/components/wifi-conf/wifi-conf.wxss
  83. 6
      demo/miniprogram/pages/add-device/constants.js
  84. 47
      demo/miniprogram/pages/add-device/llsync/llsync.js
  85. 5
      demo/miniprogram/pages/add-device/llsync/llsync.json
  86. 6
      demo/miniprogram/pages/add-device/llsync/llsync.wxml
  87. 1
      demo/miniprogram/pages/add-device/llsync/llsync.wxss
  88. 45
      demo/miniprogram/pages/add-device/logger.js
  89. 91
      demo/miniprogram/pages/add-device/qrCode.js
  90. 9
      demo/miniprogram/pages/add-device/simple-config/simple-config.js
  91. 6
      demo/miniprogram/pages/add-device/simple-config/simple-config.json
  92. 5
      demo/miniprogram/pages/add-device/simple-config/simple-config.wxml
  93. 0
      demo/miniprogram/pages/add-device/simple-config/simple-config.wxss
  94. 9
      demo/miniprogram/pages/add-device/smart-config/smart-config.js
  95. 6
      demo/miniprogram/pages/add-device/smart-config/smart-config.json
  96. 5
      demo/miniprogram/pages/add-device/smart-config/smart-config.wxml
  97. 0
      demo/miniprogram/pages/add-device/smart-config/smart-config.wxss
  98. 10
      demo/miniprogram/pages/add-device/soft-ap/soft-ap.js
  99. 6
      demo/miniprogram/pages/add-device/soft-ap/soft-ap.json
  100. 6
      demo/miniprogram/pages/add-device/soft-ap/soft-ap.wxml

21
README.md

@ -0,0 +1,21 @@
# qcloud-iotexplorer-appdev-sdk-demo
腾讯云物联网开发平台小程序 SDK 的使用示例。
## 小程序 SDK
npm 链接:[qcloud-iotexplorer-appdev-sdk](https://www.npmjs.com/package/qcloud-iotexplorer-appdev-sdk)
安装 SDK:`npm i qcloud-iotexplorer-appdev-sdk`
SDK 文档:请参见 [自主品牌小程序开发指南](https://cloud.tencent.com/document/product/1081/47686) 以及 [小程序 SDK](https://cloud.tencent.com/document/product/1081/47687)。
## 小程序 SDK Demo
Demo 小程序代码位于 `demo` 目录。
使用方法:请参见 [自主品牌小程序快速入门](https://cloud.tencent.com/document/product/1081/47685)。
[Changelog](https://github.com/tencentyun/qcloud-iotexplorer-appdev-miniprogram-sdk-demo/blob/master/demo/README.md#changelog)

3
demo/.gitignore

@ -0,0 +1,3 @@
node_modules/
/miniprogram/miniprogram_npm/
/miniprogram/package-lock.json

69
demo/README.md

@ -0,0 +1,69 @@
腾讯云物联网开发平台小程序 SDK Demo
===
腾讯云物联网开发平台小程序 SDK 的使用示例。
> 关于物联网开发平台小程序 SDK 的更多信息,请参见 [自主品牌小程序开发指南](https://cloud.tencent.com/document/product/1081/47686) 以及 [小程序 SDK](https://cloud.tencent.com/document/product/1081/47687)。
>
> 本 Demo 的具体使用步骤请参见 [自主品牌小程序快速入门](https://cloud.tencent.com/document/product/1081/47685)。
## 使用步骤
1. 前往 [腾讯云物联网开发平台控制台](https://console.cloud.tencent.com/iotexplorer) > 应用开发,获取小程序 AppKey 与 AppSecret。
2. 前往 [微信公众平台](https://mp.weixin.qq.com/) 的小程序后台,配置小程序服务器域名。
- request 合法域名:`https://iot.cloud.tencent.com`
- socket 合法域名:`wss://iot.cloud.tencent.com`
3. 下载、导入 Demo 小程序项目到 [微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)。
4. 配置 AppKey 与 AppSecret。
- `miniprogram/app.js`
```js
const APP_KEY = 'YOUR_APP_KEY_HERE'; // 填写 AppKey
```
- `cloudfunctions/login/index.js`
```js
const APP_KEY = 'YOUR_APP_KEY_HERE'; // 填写 AppKey
const APP_SECRET = 'YOUR_APP_SECRET_HERE'; // 填写 AppSecret
```
5. 开通小程序云开发,创建并部署 `login` 云函数(位于 `cloudfunctions/login` 目录)。
6. 在 `miniprogram` 目录下安装小程序 npm 依赖。
```
cd miniprogram
npm install
```
7. 在微信开发者工具中,选择菜单栏的【工具】>【构建 npm】。
## 注意事项
1. 小程序登录物联网开发平台,需要获取小程序用户信息后,由后台服务器调用相关的应用端 API,请参见 [应用端 API 简介](https://cloud.tencent.com/document/product/1081/40773) 以及 [微信号注册登录](https://cloud.tencent.com/document/product/1081/40781) 应用端 API。
2. 本 Demo 使用小程序云开发部署登录接口。您也可以将登录接口部署到自己的后台服务器,并且修改 `demo/miniprogram/app.js` 中的 `getAccessToken` 函数,以使用自行部署的登录接口。
3. 小程序只能对已关联的产品下的设备进行绑定、控制等操作。要将小程序与产品关联,请前往 [腾讯云物联网开发平台控制台](https://console.cloud.tencent.com/iotexplorer) > 应用开发 > 关联产品。
## Changelog
### v1.1.1 (2021/5/11)
- 修复:`formatDate` 工具函数报错的问题
### v1.1.0 (2021/4/6)
- 调整:登录获取用户信息接口调整为 wx.getUserProfile
**注意**:由于 [小程序登录、用户信息相关接口调整](https://developers.weixin.qq.com/community/develop/doc/000cacfa20ce88df04cb468bc52801),存量应用建议参考 [这个 commit](https://github.com/tencentyun/qcloud-iotexplorer-appdev-miniprogram-sdk-demo/commit/5647f4e88c4476c1f1e784b751a86b3f51d7fb9a) 对登录流程的相关代码进行调整
- 修复:一些情况下设备列表变更时不触发 subscribeDevices 订阅设备信息的问题
- 优化:LoRa 设备、BLE 设备不展示为离线状态
## Demo 结构说明
### 页面
- 首页:pages/index
- 设备面板:pages/panel
- Wi-Fi 配网:pages/add-device 目录下各个页面(配网交互共用 wifi-conf 组件)
- 固件升级:pages/firmware-upgrade
### 组件
- 小程序用户授权、登录:components/page-wrapper
- 配网交互步骤: pages/add-device/components/wifi-conf
- iOS 系统获取 Wi-Fi 列表步骤引导: components/ios-wifi-guide

120
demo/cloudfunctions/login/index.js

@ -0,0 +1,120 @@
// 云函数入口文件
// 请填写 物联网开发平台 > 应用开发 中申请的小程序 AppKey 及 AppSecret
const APP_KEY = 'YOUR_APP_KEY_HERE';
const APP_SECRET = 'YOUR_APP_SECRET_HERE';
const cloud = require('wx-server-sdk');
const CryptoJS = require('crypto-js');
const axios = require('axios');
const shortid = require('shortid');
class AppDevSdk {
constructor({
AppKey,
AppSecret,
}) {
this.AppKey = AppKey;
this.AppSecret = AppSecret;
}
async requestAppApi(Action, reqData = {}, options = {}) {
const requestOpts = {
method: 'POST',
url: 'https://iot.cloud.tencent.com/api/exploreropen/appapi',
...options,
};
const finalReqData = { ...reqData };
if (!finalReqData.RequestId) {
finalReqData.RequestId = shortid();
}
requestOpts.data = this.assignSignature({
Action,
...finalReqData,
});
const { status, statusText, data: response = {} } = await axios(requestOpts);
if (status !== 200) {
return Promise.reject({ code: status, msg: statusText });
}
const { code, msg, data = {} } = response;
if (code) {
return Promise.reject({ code, msg });
}
if (data.Error) {
return Promise.reject({ code: data.Error.Code, msg: data.Error.Message });
}
return data;
}
assignSignature(data) {
const Timestamp = Math.floor(Date.now() / 1000);
const Nonce = Math.floor((10000 * Math.random())) + 1; // 随机正整数
const tempData = {
...data,
Timestamp,
Nonce,
AppKey: this.AppKey,
};
const keys = Object.keys(tempData).sort();
const arr = keys
.filter(key => tempData[key] !== undefined && !!String(tempData[key]))
.map(key => `${key}=${tempData[key]}`);
const paramString = arr.join('&');
const hash = CryptoJS.HmacSHA1(paramString, this.AppSecret);
const signature = CryptoJS.enc.Base64.stringify(hash);
return {
...tempData,
Signature: signature,
};
}
}
cloud.init();
const sdk = new AppDevSdk({
AppKey: APP_KEY,
AppSecret: APP_SECRET,
});
// 云函数入口函数
exports.main = async (event) => {
const { Avatar, NickName } = event;
// Demo 配置指引
if (APP_KEY === 'YOUR_APP_KEY_HERE' || APP_SECRET === 'YOUR_APP_SECRET_HERE') {
return {
code: 'CLOUDFUNC_INVALID_APP_KEY_SECRET',
msg: '请在 cloudfunctions/login/index.js 中填写 APP_KEY 与 APP_SECRET,并重新部署云函数',
};
}
try {
const response = await sdk.requestAppApi('AppGetTokenByWeiXin', {
WxOpenID: cloud.getWXContext().OPENID, // or cloud.getWXContext().UNIONID
NickName,
Avatar,
});
return { code: 0, msg: 'ok', data: response };
} catch (err) {
if (err instanceof Error) {
return {
code: 'InternalError',
msg: String(err),
};
}
return err;
}
};

932
demo/cloudfunctions/login/package-lock.json

@ -0,0 +1,932 @@
{
"name": "login",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@cloudbase/database": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@cloudbase/database/-/database-1.2.2.tgz",
"integrity": "sha512-14GPoD0vdVnfdN+4rHlMmpkxAekFklt4X2gi33iCuoZUDC62p5LWS7OuTjoronnZ4QPsZPCKm+WsjE8mVD+Hmw==",
"requires": {
"bson": "^4.0.3",
"lodash.clonedeep": "4.5.0",
"lodash.set": "4.3.2",
"lodash.unset": "4.5.2"
}
},
"@cloudbase/node-sdk": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/@cloudbase/node-sdk/-/node-sdk-2.4.7.tgz",
"integrity": "sha512-gMtp+25nAJzpXTxpZzN7PTtsTdv6m7SNRszMwPpWB3pwAYyefbuOkR505iv+kYugsX6MkbgKjcCQ/F5dpNMMYw==",
"requires": {
"@cloudbase/database": "1.2.2",
"@cloudbase/signature-nodejs": "1.0.0-beta.0",
"@types/retry": "^0.12.0",
"agentkeepalive": "^4.1.3",
"is-regex": "^1.0.4",
"jsonwebtoken": "^8.5.1",
"lodash.merge": "^4.6.1",
"request": "^2.87.0",
"request-promise": "^4.2.5",
"retry": "^0.12.0",
"ts-node": "^8.10.2",
"xml2js": "^0.4.19"
}
},
"@cloudbase/signature-nodejs": {
"version": "1.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@cloudbase/signature-nodejs/-/signature-nodejs-1.0.0-beta.0.tgz",
"integrity": "sha512-gpKqwsVk/D2PzvFamYNReymXSdvRSY90eZ1ARf+1wZ8oT6OpK9kr6nmevGykMxN1n17Gn92hBbWqAxU9o3+kAQ==",
"requires": {
"@types/clone": "^0.1.30",
"clone": "^2.1.2",
"is-stream": "^2.0.0",
"url": "^0.11.0"
}
},
"@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78="
},
"@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
},
"@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
},
"@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A="
},
"@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
"requires": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E="
},
"@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik="
},
"@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0="
},
"@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q="
},
"@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
},
"@types/clone": {
"version": "0.1.30",
"resolved": "https://registry.npmjs.org/@types/clone/-/clone-0.1.30.tgz",
"integrity": "sha1-5zZWSMG0ITalnH1QQGN7O1yDthQ="
},
"@types/long": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
"integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
},
"@types/node": {
"version": "10.17.56",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.56.tgz",
"integrity": "sha512-LuAa6t1t0Bfw4CuSR0UITsm1hP17YL+u82kfHGrHUWdhlBtH7sa7jGY5z7glGaIj/WDYDkRtgGd+KCjCzxBW1w=="
},
"@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
},
"agentkeepalive": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.4.tgz",
"integrity": "sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ==",
"requires": {
"debug": "^4.1.0",
"depd": "^1.1.2",
"humanize-ms": "^1.2.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"requires": {
"safer-buffer": "~2.1.0"
}
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
},
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"requires": {
"tweetnacl": "^0.14.3"
}
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"bson": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.2.3.tgz",
"integrity": "sha512-3ztgjpKp0itFxGqzrLMHWqyZH5oMOIRWsjeY61yNVzrDGB/KxtgD6djFlz9n3vx7lLr2r6bkHagBCgyk1ZjETA==",
"requires": {
"buffer": "^5.6.0"
}
},
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"crypto-js": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz",
"integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q=="
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"requires": {
"assert-plus": "^1.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
}
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA=="
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"requires": {
"assert-plus": "^1.0.0"
}
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"requires": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
}
},
"humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
"requires": {
"ms": "^2.0.0"
}
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"is-regex": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
"integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
"requires": {
"call-bind": "^1.0.2",
"has-symbols": "^1.0.1"
}
},
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"jsonwebtoken": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
"requires": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^5.6.0"
},
"dependencies": {
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.2.3",
"verror": "1.10.0"
}
},
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
},
"lodash.set": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
"integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM="
},
"lodash.unset": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.unset/-/lodash.unset-4.5.2.tgz",
"integrity": "sha1-Nw0dPoW3Kn4bDN8tJyEhMG8j5O0="
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
},
"mime-db": {
"version": "1.46.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
"integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ=="
},
"mime-types": {
"version": "2.1.29",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
"integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
"requires": {
"mime-db": "1.46.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"nanoid": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"protobufjs": {
"version": "6.8.8",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz",
"integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==",
"requires": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/long": "^4.0.0",
"@types/node": "^10.1.0",
"long": "^4.0.0"
}
},
"psl": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
"request": {
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
},
"request-promise": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
"integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==",
"requires": {
"bluebird": "^3.5.0",
"request-promise-core": "1.1.4",
"stealthy-require": "^1.1.1",
"tough-cookie": "^2.3.3"
}
},
"request-promise-core": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
"requires": {
"lodash": "^4.17.19"
}
},
"retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"shortid": {
"version": "2.2.16",
"resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz",
"integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==",
"requires": {
"nanoid": "^2.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-support": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"sshpk": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
}
},
"stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
},
"tcb-admin-node": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/tcb-admin-node/-/tcb-admin-node-1.23.0.tgz",
"integrity": "sha512-SAbjTqMsSi63SId1BJ4kWdyGJzhxh9Tjvy3YXxcsoaAC2PtASn4UIYsBsiNEUfcn58QEn2tdvCvvf69WLLjjrg==",
"requires": {
"@cloudbase/database": "0.9.15",
"@cloudbase/signature-nodejs": "^1.0.0-beta.0",
"is-regex": "^1.0.4",
"jsonwebtoken": "^8.5.1",
"lodash.merge": "^4.6.1",
"request": "^2.87.0",
"xml2js": "^0.4.19"
},
"dependencies": {
"@cloudbase/database": {
"version": "0.9.15",
"resolved": "https://registry.npmjs.org/@cloudbase/database/-/database-0.9.15.tgz",
"integrity": "sha512-63e7iIl+van41B39Tw4ScNe9TRCt+5GHjc7q6i8NzkWBLC3U3KlbWo79YHsUHUPI79POpQ8UMlMVo7HXIAO3dg==",
"requires": {
"bson": "^4.0.2",
"lodash.clonedeep": "4.5.0",
"lodash.set": "4.3.2",
"lodash.unset": "4.5.2"
}
}
}
},
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
},
"dependencies": {
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}
}
},
"ts-node": {
"version": "8.10.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz",
"integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==",
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"yn": "3.1.1"
}
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"requires": {
"punycode": "^2.1.0"
},
"dependencies": {
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}
}
},
"url": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
"requires": {
"punycode": "1.3.2",
"querystring": "0.2.0"
}
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"wx-server-sdk": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/wx-server-sdk/-/wx-server-sdk-2.4.0.tgz",
"integrity": "sha512-+d/OAUgt3LVlIwC/EEd9oHK2VltMqvoSa3Z797sgZ/hBm/Z+bhYBX3PfrRgn41fprzNk49jdbmw8Rkwa4JryIQ==",
"requires": {
"@cloudbase/node-sdk": "2.4.7",
"protobufjs": "6.8.8",
"tcb-admin-node": "^1.23.0",
"tslib": "^1.9.3"
}
},
"xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
}
},
"xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="
}
}
}

14
demo/cloudfunctions/login/package.json

@ -0,0 +1,14 @@
{
"name": "login",
"version": "1.0.0",
"description": "",
"main": "index.js",
"author": "",
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"crypto-js": "^3.1.9-1",
"shortid": "^2.2.15",
"wx-server-sdk": "^2.4.0"
}
}

215
demo/miniprogram/app.js

@ -0,0 +1,215 @@
// 请填写 物联网开发平台 > 应用开发 中申请的小程序 AppKey
const APP_KEY = 'mEulfDBBOFeOjGavy';
// 如果在开发过程中需要更换 AppKey,请按照以下步骤操作:
// 1. 修改 app.js 以及 cloudfunctions/login/index.js 代码中配置的 AppKey 和 AppSecret。
// 2. 在微信开发者工具的文件列表中,对 cloudfunctions/login 右键,选择【上传并部署:云端安装依赖】。
// 3. 在微信开发者工具的工具栏中,选择【清缓存】>【清除数据缓存】。
// 4. 在手机微信的小程序列表中,删除当前小程序。
// 5. 重新编译运行小程序。
const { AppDevSdk } = require('qcloud-iotexplorer-appdev-sdk');
const { EventTypes } = AppDevSdk.constants;
const SimpleConfigPlug = require('qcloud-iotexplorer-appdev-plugin-wificonf-simpleconfig').default;
const AirKissPlug = require('qcloud-iotexplorer-appdev-plugin-wificonf-airkiss').default;
const SmartConfigPlug = require('qcloud-iotexplorer-appdev-plugin-wificonf-smartconfig').default;
const SoftApPlug = require('qcloud-iotexplorer-appdev-plugin-wificonf-softap').default;
const BleComboPlug = require('qcloud-iotexplorer-appdev-plugin-wificonf-blecombo').default;
const promisify = require('./libs/wx-promisify');
const { subscribeStore } = require('./libs/store-subscribe');
const actions = require('./redux/actions');
const signinUrl = "https://test.tall.wiki/gateway/tall3/v3.0/fiot/signin";
App({
onLaunch() {
const systemInfo = wx.getSystemInfoSync();
const platform = (systemInfo.platform || '').toLowerCase();
this.globalData = {
...this.globalData,
isIpx: (systemInfo.screenHeight / systemInfo.screenWidth) > 1.86,
isAndroid: platform.indexOf('android') > -1,
isIOS: platform.indexOf('ios') > -1,
};
// 初始化云开发
// if (!wx.cloud) {
// console.error('小程序基础库版本过低,请使用 2.2.3 或以上版本的支持库以使用云开发能力');
// } else {
// wx.cloud.init({
// // env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx)会默认请求到哪个云环境的资源
// // 此处填入云开发环境 ID, 如不填则使用默认环境(第一个创建的环境)
// // env: '',
// });
// }
// 初始化 SDK
this.sdk = new AppDevSdk({
debug: true,
appKey: APP_KEY,
getAccessToken: this.getAccessToken,
wsConfig: {},
});
// 安装配网插件
SimpleConfigPlug.install(this.sdk);
AirKissPlug.install(this.sdk);
SmartConfigPlug.install(this.sdk);
SoftApPlug.install(this.sdk);
BleComboPlug.install(this.sdk);
// WebSocket 订阅设备信息
this.wsSubscribe();
},
// 订阅设备信息
wsSubscribe() {
subscribeStore([
{
selector: state => state.deviceList.concat(state.shareDeviceList),
onChange: (deviceList, oldDeviceList) => {
// 设备列表无变化时不重新订阅
if (oldDeviceList
&& oldDeviceList.length === deviceList.length
&& deviceList.every((dev, index) => dev === oldDeviceList[index])
) {
return;
}
// 当设备列表更新时,重新进行订阅
this.sdk.subscribeDevices(deviceList);
},
},
]);
// 接收设备属性变化推送
this.sdk.on(EventTypes.WsReport, ({ deviceId, deviceData }) => {
actions.updateDeviceDataByPush({ deviceId, deviceData });
});
// 接收设备在线状态变化推送
this.sdk.on(EventTypes.WsStatusChange, ({ deviceId, deviceStatus }) => {
actions.updateDeviceStatusByPush({ deviceId, deviceStatus });
});
},
// sdk.init() 会调用该函数获取物联网开发平台 AccessToken
async getAccessToken() {
// // 小程序配置指引
// if (APP_KEY === 'YOUR_APP_KEY_HERE') {
// throw { msg: '请在 miniprogram/app.js 文件中填写 APP_KEY', code: 'INVALID_APP_KEY' };
// }
// // 小程序用户信息
// // 在 page-wrapper 组件中请求获取,写入到 app.globalData.userInfo
// const { userInfo } = this.globalData;
// // 是否需要注册
// const needRegister = !this.getIsRegisteredSync() && !!userInfo;
// // 注册/登录参数,注册时需要传入用户昵称和头像,下次登录时可以不传入
// const loginParams = needRegister ? {
// Avatar: userInfo.avatarUrl,
// NickName: userInfo.nickName,
// } : {};
// try {
// // 云函数 (cloudfunctions/login/index.js) 中调用 微信号注册登录 应用端 API
// // 获取物联网开发平台的 AccessToken
// // 请参见 https://cloud.tencent.com/document/product/1081/40781
// const res = await wx.cloud.callFunction({
// // 云函数名称
// name: 'login',
// // 传给云函数的参数
// data: loginParams,
// });
// const { code, msg, data } = res.result;
// console.log(code, msg, data );
// // 异常处理
// if (code) {
// throw { code, msg };
// }
// // 取得 AccessToken
// const { Token, ExpireAt } = data.Data;
try{
// 小程序登录凭证 code(后台服务端凭 code 向微信后台换取 OpenID)
const code = await new Promise((resolve, reject) => {
wx.login({
success: (res) => resolve(res.code),
fail: reject
});
});
// 用户昵称和头像(通过 wx.getUserProfile 获取)
const userInfo = this.globalData.userInfo;
// 调用开发者自建的后台服务端获取物联网开发平台的 AccessToken
// 请根据实际情况调整实现
console.log("code:", code)
const resp = await new Promise((resolve, reject) => {
wx.request({
url: signinUrl,
method: 'POST',
data: {
code: code,
nickName: userInfo.nickName,
avatar: userInfo.avatarUrl
},
header: {
'content-type': 'application/json'
},
success(res) {
resolve(res.data);
},
fail(err) {
reject(err);
}
});
});
if (resp.code != 200) {
// 弹出错误信息
return;
}
// 标记为已注册状态,下次登录时不需要请求获取用户昵称和头像
await this.setIsRegistered();
// return { Token, ExpireAt };
console.log("登录结果:", resp)
return {
Token: resp.data.token,
ExpireAt: resp.data.expireAt
};
} catch (err) {
// 云函数部署指引
if (err.errMsg && err.errMsg.indexOf('找不到对应的FunctionName') > -1) {
throw { code: 'CLOUDFUNC_NOT_FOUND', msg: '请创建并部署 cloudfunctions/login 中的云函数到云开发' };
}
throw err;
}
},
getIsRegisteredSync() {
return !!wx.getStorageSync('iot-explorer-user-registered');
},
async setIsRegistered(isRegistered = true) {
if (!isRegistered) {
return promisify(wx.removeStorage)({
key: 'iot-explorer-user-registered',
});
}
return promisify(wx.setStorage)({
key: 'iot-explorer-user-registered',
data: isRegistered,
});
},
globalData: {
userInfo: null,
},
});

40
demo/miniprogram/app.json

@ -0,0 +1,40 @@
{
"pages": [
"pages/index/index",
"pages/panel/panel",
"pages/add-device/simple-config/simple-config",
"pages/add-device/air-kiss/air-kiss",
"pages/add-device/smart-config/smart-config",
"pages/add-device/soft-ap/soft-ap",
"pages/firmware-upgrade/firmware-upgrade",
"pages/share/share-list/share-list",
"pages/share/receive-share/receive-share",
"pages/add-device/llsync/llsync",
"pages/test/index"
],
"subPackages":[
{
"root": "pages/file-manage",
"pages": [
"file-manage/file-manage",
"error/error",
"add-file/add-file"
]
}
],
"window": {
"backgroundColor": "#FFF",
"backgroundTextStyle": "dark",
"navigationBarBackgroundColor": "#FFF",
"navigationBarTitleText": "物联网开发平台小程序 Demo",
"navigationBarTextStyle": "black"
},
"sitemapLocation": "sitemap.json",
"style": "v2",
"permission": {
"scope.userLocation": {
"desc": "查询附近WiFi需要授权使用您的位置信息"
}
}
}

199
demo/miniprogram/app.wxss

@ -0,0 +1,199 @@
@import '/miniprogram_npm/weui-miniprogram/weui-wxss/dist/style/weui.wxss';
view.need-hover,
button {
position: relative;
}
view.need-hover.hover:not(.disabled)::before,
button.button-hover:not(.disabled):not(disabled)::before {
content: ' ';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background-color: rgba(0, 0, 0, 0.1);
}
button {
position: relative;
display: block;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
-webkit-tap-highlight-color: transparent;
color: inherit;
background-color: inherit;
}
button::after {
content: '';
border: none;
}
button[disabled]:not([type]),
button.disabled {
color: inherit;
background-color: inherit;
opacity: 0.6;
}
/* 会覆盖掉原生button样式 */
button.pure-btn {
font-size: inherit;
text-align: inherit;
text-decoration: none;
line-height: inherit;
-webkit-tap-highlight-color: transparent;
overflow: hidden;
color: inherit;
background-color: inherit;
}
.btn {
position: relative;
font-size: 32rpx;
height: 90rpx;
line-height: 90rpx;
text-align: center;
background-color: #fff;
border-radius: 0;
border: 1rpx solid #DDDDDD;
color: #0052D9;
}
.btn.with-margin {
margin: 0 30rpx;
}
.btn.btn-danger {
border: 1rpx solid #DDDDDD;
color: #FF584C;
}
.btn.btn-danger[disabled]:not([type]),
.btn.btn-danger.disabled {
opacity: 0.6;
border-color: rgba(221, 221, 221, 0.6);
}
.btn.btn-primary {
border: 1rpx solid #0052d9;
background: #0052D9;
color: #fff;
}
.btn.btn-primary[disabled]:not([type]),
.btn.btn-primary.disabled {
opacity: 0.6;
border-color: rgba(0, 82, 217, 0.6);
}
.btn.btn-warning {
color: #fff;
background-color: #ff9c19;
border: 1rpx solid #ff9c19;
}
.btn.btn-warning[disabled]:not([type]),
.btn.btn-warning.disabled {
opacity: 0.6;
border-color: rgba(255, 156, 25, 0.6);
}
.btn-group {
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
padding: 0 30rpx;
}
.btn-group.no-padding {
padding: 0;
}
.btn-group .btn {
margin-bottom: 20rpx;
}
.btn-group .btn .btn-icon {
width: 32rpx;
height: 32rpx;
margin-right: 20rpx;
}
.btn-group .btn:last-of-type {
margin-bottom: 0;
}
.btn-group.flex .btn {
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
margin-bottom: 0;
}
.btn-group.fixed-bottom {
position: fixed;
-webkit-box-sizing: border-box;
box-sizing: border-box;
bottom: 40rpx;
left: 0;
right: 0;
padding-left: 50rpx;
padding-right: 50rpx;
}
.btn-group.flex {
padding: 0;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.btn-group.fixed-bottom.ipx {
bottom: 74rpx;
}
.btn-group.fixed-bottom.flex {
bottom: 0;
left: 0;
right: 0;
}
.btn-group.fixed-bottom.flex .btn {
border-bottom: none;
border-left: none;
border-right: none;
height: 100rpx;
line-height: 100rpx;
}
.btn-group.fixed-bottom.flex.ipx {
height: 134rpx;
}
.btn-group.fixed-bottom.flex.ipx .btn {
height: 134rpx;
}
page {
background: #FFF;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.append-arrow:after {
content: ' ';
position: absolute;
width: 15rpx;
height: 24rpx;
background-image: url("https://main.qcloudimg.com/raw/3ad2287943cd913942f87304c8652457.svg");
background-repeat: no-repeat;
background-size: 100%;
right: 0;
top: 50%;
margin-top: -12rpx;
}
button.btn:not([size='mini']) {
padding: 0;
width: 100%;
font-weight: initial;
}
.fixed-bottom-btn {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
}
.fixed-bottom-btn.ipx {
height: 134rpx;
}
.wx_loading_view {
height: 50px;
}

20
demo/miniprogram/assets/common/check-blue.svg

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>勾选</title>
<desc>Created with Sketch.</desc>
<g id="文件汇总" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="房间设置" transform="translate(-672.000000, -169.000000)" fill-rule="nonzero">
<g id="Group-47备份" transform="translate(0.000000, 148.000000)">
<g id="Group-39">
<g id="勾选" transform="translate(672.000000, 21.000000)">
<rect id="矩形" fill="#D8D8D8" opacity="0" x="0" y="0" width="48" height="48"></rect>
<g id="分组-9" transform="translate(10.000000, 14.000000)" fill="#0052D9">
<polygon id="路径" points="10.4263808 12.9279221 23.1543028 0.2 26.6898367 3.73553391 10.4263808 19.9989899 0.526885835 10.0994949 4.06241974 6.56396103"></polygon>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
demo/miniprogram/assets/common/loading.svg

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 100 100'><path fill='none' d='M0 0h100v100H0z'/><rect width='7' height='20' x='46.5' y='40' fill='#E9E9E9' rx='5' ry='5' transform='translate(0 -30)'/><rect width='7' height='20' x='46.5' y='40' fill='#989697' rx='5' ry='5' transform='rotate(30 105.98 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#9B999A' rx='5' ry='5' transform='rotate(60 75.98 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#A3A1A2' rx='5' ry='5' transform='rotate(90 65 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#ABA9AA' rx='5' ry='5' transform='rotate(120 58.66 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#B2B2B2' rx='5' ry='5' transform='rotate(150 54.02 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#BAB8B9' rx='5' ry='5' transform='rotate(180 50 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#C2C0C1' rx='5' ry='5' transform='rotate(-150 45.98 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#CBCBCB' rx='5' ry='5' transform='rotate(-120 41.34 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#D2D2D2' rx='5' ry='5' transform='rotate(-90 35 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#DADADA' rx='5' ry='5' transform='rotate(-60 24.02 65)'/><rect width='7' height='20' x='46.5' y='40' fill='#E2E2E2' rx='5' ry='5' transform='rotate(-30 -5.98 65)'/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

42
demo/miniprogram/assets/common/more.svg

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="48px" height="48px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#CFE4FF;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
.st4{fill:url(#SVGID_4_);}
.st5{fill:#CCEDFF;}
.st6{fill:url(#SVGID_5_);}
.st7{fill:url(#SVGID_6_);}
.st8{fill:url(#SVGID_7_);}
.st9{fill:url(#SVGID_8_);}
.st10{fill:url(#SVGID_9_);}
.st11{fill:url(#SVGID_10_);}
.st12{fill:url(#SVGID_11_);}
.st13{fill:url(#SVGID_12_);}
.st14{fill:url(#SVGID_13_);}
.st15{fill:url(#SVGID_14_);}
.st16{fill:url(#SVGID_15_);}
.st17{fill:url(#SVGID_16_);}
.st18{fill-rule:evenodd;clip-rule:evenodd;fill:#444444;}
.st19{fill:#444444;}
.st20{fill:none;stroke:#444444;stroke-width:4;stroke-miterlimit:10;}
.st21{fill:#29CC85;}
.st22{fill:#006EFF;}
.st23{fill:url(#SVGID_17_);}
.st24{fill:#FFFFFF;}
.st25{fill:url(#SVGID_18_);}
.st26{fill:url(#SVGID_19_);}
.st27{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
.st28{fill-rule:evenodd;clip-rule:evenodd;fill:none;stroke:#444444;stroke-width:4;stroke-miterlimit:10;}
</style>
<g>
<circle class="st19" cx="10" cy="24" r="4"/>
<circle class="st19" cx="24" cy="24" r="4"/>
<circle class="st19" cx="38" cy="24" r="4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

13
demo/miniprogram/assets/common/plus-blue.svg

@ -0,0 +1,13 @@
<svg width="37px" height="37px" viewBox="0 0 37 37" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="首页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="首页设备状态" transform="translate(-652.000000, -183.000000)" fill="#0052d9">
<g id="语音备份" transform="translate(620.000000, 151.000000)">
<g id="编组-2" transform="translate(32.000000, 32.000000)">
<rect id="矩形" x="17" y="0" width="4" height="37"></rect>
<rect id="矩形" x="0" y="17" width="37" height="4"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 655 B

9
demo/miniprogram/assets/common/plus-white.svg

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="42px" height="42px" viewBox="0 0 42 42" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59 (86127) - https://sketch.com -->
<title>添加设备加号</title>
<desc>Created with Sketch.</desc>
<g id="添加设备加号" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M23,0 L23,19 L42,19 L42,23 L23,23 L23,42 L19,42 L19,23 L0,23 L0,19 L19,19 L19,0 L23,0 Z" id="Combined-Shape" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 582 B

1
demo/miniprogram/assets/device/connecting-loading.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

38
demo/miniprogram/assets/user/avatar-default.svg

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81px" height="116px" viewBox="0 0 81 116" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>编组 2</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="0.984465318%" y1="50.9844653%" x2="99.0155347%" y2="50.9844653%" id="linearGradient-1">
<stop stop-color="#00A4FF" offset="0%"></stop>
<stop stop-color="#006EFF" offset="100%"></stop>
</linearGradient>
</defs>
<g id="我的-家庭房间管理" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="我的" transform="translate(-54.000000, -166.000000)" fill-rule="nonzero">
<g id="List/List_3首页信息" transform="translate(0.000000, 128.000000)">
<g id="Group-2">
<g id="编组" transform="translate(30.000000, 24.000000)">
<g id="icon-头像-默认头像-男">
<g id="编组-2">
<g transform="translate(24.000000, 14.000000)">
<path d="M81,116 C80.1351351,108.741667 62.6216216,103 41,103 C19.3783784,103 1.86486486,108.741667 1,116 L81,116 Z" id="Shape" fill="url(#linearGradient-1)"></path>
<g id="Group" transform="translate(2.000000, 31.000000)" fill="#222222">
<polygon id="Shape" points="0 1.1 3.25 31.9 6.5 31.9 6.5 0 0 0"></polygon>
<polygon id="Shape" points="71.5 1.1 71.5 33 74.75 33 78 2.2 78 1.1"></polygon>
</g>
<path d="M57,116 C55.3946729,107.018132 54.9290461,101.123924 55.6031196,98.3173753 C56.6142298,94.1075531 27.0916994,93.6858853 27.3968804,98.3173753 C27.6003344,101.405035 27.1347076,107.299244 26,116 L57,116 Z" id="Shape" fill="#FFDFDD"></path>
<path d="M12.9684544,53.1242732 C10.3747635,51.8139282 0,49.7392153 0,59.6759982 C0,69.5035856 5.61966358,71.9058848 7.56493174,73.8714022 C9.5101999,75.8369197 15.5621453,76.9288739 16.210568,74.9633564 C16.8589907,72.9978389 15.5621453,54.4346182 12.9684544,53.1242732 Z M67.2468396,53.1242732 C64.6531487,54.4346182 63.3563033,72.9978389 64.004726,74.9633564 C64.6531487,76.9288739 70.5970237,75.8369197 72.5422918,73.8714022 C74.48756,71.9058848 79.9991531,69.5035856 79.9991531,59.6759982 C80.1072236,49.7392153 69.8405305,51.8139282 67.2468396,53.1242732 Z" id="Shape" fill="#FFDFDD"></path>
<path d="M41.5833333,104 C41.15,104 40.5,104 40.5,104 C40.5,104 39.85,104 39.4166667,104 C19.9166667,104 8,82.4970268 8,44.7087136 L8,22 L73,22 L73,44.7087136 C73,82.4970268 61.0833333,104 41.5833333,104 Z" id="Shape" fill="#FFF2F1"></path>
<path d="M74.5833333,33.3771518 L74.5833333,43 C72.5232243,43 70.3825064,42.5193528 69.5981262,41.3088027 C68.4703748,39.5683208 66.0920424,35.6364199 64.8333333,32.3004695 C63.5195378,28.8185239 63.9395833,25.8403756 58.3333333,25.8403756 L44.25,25.8403756 L29.0833333,25.8403756 L23.6666667,25.8403756 C19.0760417,25.8403756 18.4804622,28.8185239 17.1666667,32.3004695 C15.9054718,35.643008 13.5242424,39.5798624 12.3891659,41.3191006 C11.6032402,42.5233472 9.47014691,43 7.41666667,43 L7.41666667,32.3004695 L2,32.3004695 L2,31.2237872 C2,24.0100156 5.25,17.5499218 10.3416667,13.2431925 C7.85,9.47480438 6.33333333,4.84507042 6.33333333,0 L47.5,0 C65.4833333,0 80,14.427543 80,32.3004695 L80,33.3771518 L74.5833333,33.3771518 Z" id="Shape" fill="#222222"></path>
<path d="M41,95 C34.4,95 29,89.6 29,83 L53,83 C53,89.6 47.6,95 41,95 Z" id="Shape" fill="#FFC6C1"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

41
demo/miniprogram/components/btn-group/btn-group.js

@ -0,0 +1,41 @@
Component({
options: {
addGlobalClass: true,
},
properties: {
buttons: {
type: Array,
value: [],
},
noPadding: {
type: Boolean,
value: false,
},
flex: {
type: Boolean,
value: false,
},
fixedBottom: {
type: Boolean,
value: false,
},
},
data: {
ipx: false,
},
attached() {
this.setData({ ipx: getApp().globalData.isIpx });
},
methods: {
onClickBtn(e) {
this.triggerEvent('click', {
index: e.currentTarget.dataset.index,
btn: e.currentTarget.dataset.btn,
}, {});
},
},
});

3
demo/miniprogram/components/btn-group/btn-group.json

@ -0,0 +1,3 @@
{
"component": true
}

19
demo/miniprogram/components/btn-group/btn-group.wxml

@ -0,0 +1,19 @@
<view
class="btn-group {{noPadding ? 'no-padding' : ''}} {{flex ? 'flex' : ''}} {{fixedBottom ? 'fixed-bottom' : ''}} {{ipx ? 'ipx' : ''}}"
>
<view
wx:for="{{buttons}}"
wx:key="index"
disabled="{{item.disabled}}"
class="btn need-hover {{item.type ? 'btn-' + item.type : ''}} {{item.disabled ? 'disabled' : ''}}"
hover-start-time="20"
hover-stay-time="70"
hover-class="hover"
bindtap="onClickBtn"
data-index="{{index}}"
data-btn="{{item}}"
>
<image wx:if="{{item.icon}}" src="{{item.icon}}" class="btn-icon"/>
<block wx:else>{{item.btnText}}</block>
</view>
</view>

0
demo/miniprogram/components/btn-group/btn-group.wxss

16
demo/miniprogram/components/ios-wifi-guide/ios-wifi-guide.js

@ -0,0 +1,16 @@
Component({
data: {
iosGuideStepConf: [
{ desc: '1. 点击左上角“设置”返回设置列表', img: 'https://main.qcloudimg.com/raw/08af0dee22ec41df31caa1d911b954b4.png' },
{ desc: '2. 从设置列表顶部进入“无线局域网”', img: 'https://main.qcloudimg.com/raw/ada3e79b2518ae3b5a237d1688a3ee91.png' },
{ desc: '3. 等待WiFi列表刷新', img: 'https://main.qcloudimg.com/raw/8a28c2267326c2ab91ee224d061d099f.png' },
{ desc: '4. 点击左上角“微信”返回小程序', img: 'https://main.qcloudimg.com/raw/35371a858d8f54dc011c757a489349b2.png' },
],
},
methods: {
onConfirm() {
this.triggerEvent('confirm', null, {});
},
},
});

6
demo/miniprogram/components/ios-wifi-guide/ios-wifi-guide.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"single-button": "/components/single-button/single-button"
}
}

37
demo/miniprogram/components/ios-wifi-guide/ios-wifi-guide.wxml

@ -0,0 +1,37 @@
<view class="ios-get-wifi-list-guide-container">
<view class="ios-get-wifi-list-guide">
<view class="guide-header">
<view class="title">
iOS WiFi刷新教程
</view>
<view class="subtitle">
完成以下4个步骤,就可以刷新WiFi了
</view>
</view>
<view class="guide-body">
<view class="step-list">
<view class="step-item" wx:for="{{iosGuideStepConf}}">
<view class="step-desc">
{{item.desc}}
</view>
<image
class="step-img"
src="{{item.img}}"
mode="widthFix"
/>
</view>
</view>
</view>
<view class="guide-footer">
<single-button
text="看懂了,去刷新"
type="primary"
bind:click="onConfirm"
no-padding="{{true}}"
/>
</view>
</view>
</view>

72
demo/miniprogram/components/ios-wifi-guide/ios-wifi-guide.wxss

@ -0,0 +1,72 @@
.ios-get-wifi-list-guide-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
background-color: #fff;
}
.ios-get-wifi-list-guide {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #fff;
padding: 30rpx 40rpx 40rpx;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.ios-get-wifi-list-guide .guide-footer {
width: 650rpx;
-webkit-align-self: center;
-ms-flex-item-align: center;
align-self: center;
}
.ios-get-wifi-list-guide .guide-body {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1;
overflow-y: scroll;
margin: 20rpx 0;
}
.ios-get-wifi-list-guide .title {
margin-bottom: 0;
font-size: 34rpx;
font-weight: 500;
}
.ios-get-wifi-list-guide .subtitle {
color: #888;
font-size: 28rpx;
}
.ios-get-wifi-list-guide .step-list .step-item {
position: relative;
min-height: 170rpx;
background: #F2F2F2;
}
.ios-get-wifi-list-guide .step-list .step-item:not(:last-of-type) {
margin-bottom: 20rpx;
}
.ios-get-wifi-list-guide .step-list .step-item .step-desc {
padding: 20rpx;
margin-right: 302rpx;
font-size: 32rpx;
color: #888888;
line-height: 42rpx;
}
.ios-get-wifi-list-guide .step-list .step-item .step-img {
position: absolute;
right: 20rpx;
bottom: 0;
width: 282rpx;
}

98
demo/miniprogram/components/page-wrapper/page-wrapper.js

@ -0,0 +1,98 @@
const app = getApp();
const { getErrorMsg } = require('../../libs/utillib');
const promisify = require('../../libs/wx-promisify');
Component({
options: {
addGlobalClass: true,
},
data: {
show: 'auth', // page(正常显示页面), loading, auth(授权页面)
},
attached() {
const { isLogin } = app.sdk;
if (isLogin) {
// 已登录
this.onLoginSuccess();
} else if (app.getIsRegisteredSync()) {
// 已注册但未登录,尝试登录
// 显示 loading dots
this.setData({
show: 'loading',
});
app.sdk.init()
.then(() => {
// 登录成功
this.onLoginSuccess();
})
.catch((err) => {
// 登录失败
console.error('sdk.init fail', err.msg, err);
// 显示授权页面
this.setData({
show: 'auth',
});
});
}
},
methods: {
async onLoginButtonTap() {
// 未注册用户,获取用户信息后注册登录
if (!wx.getUserProfile) {
wx.showModal({
title: '提示',
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。',
showCancel: false,
});
return;
}
wx.showLoading({
title: '登录中…',
mask: true, // 遮罩,避免重复点击
});
try {
// 请求获取用户信息
const result = await promisify(wx.getUserProfile)({
desc: '注册自主品牌小程序账号',
});
const { nickName, avatarUrl } = result.userInfo;
// 获取到的用户信息写入到 globalData,SDK 登录时使用
app.globalData.userInfo = { nickName, avatarUrl };
// SDK 初始化并登录
await app.sdk.init();
this.onLoginSuccess();
} catch (err) {
if (err.errMsg !== 'getUserProfile:fail auth deny') {
console.error('login fail', err);
wx.showModal({
title: '登录失败',
content: getErrorMsg(err),
showCancel: false,
});
} else {
console.log('getUserProfile: 用户拒绝授权');
}
} finally {
wx.hideLoading();
}
},
onLoginSuccess() {
this.setData({ show: 'page' });
this.triggerEvent('loginready', null, {});
},
},
});

7
demo/miniprogram/components/page-wrapper/page-wrapper.json

@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"btn-group": "/components/btn-group/btn-group",
"mp-loading": "/miniprogram_npm/weui-miniprogram/loading/loading"
}
}

27
demo/miniprogram/components/page-wrapper/page-wrapper.wxml

@ -0,0 +1,27 @@
<block>
<view class="container" wx:if="{{show === 'auth'}}">
<view class="page-title">
<view>腾讯云物联网开发平台</view>
<view>小程序 Demo</view>
</view>
<image
src="https://main.qcloudimg.com/raw/5c1b129f7e134621e6ae981c9b00243e/home-loading-bg.png"
class="login-auth-bg"
/>
<view class="login-auth-notice">
使用前需要您授权登录
</view>
<button
class="btn btn-primary login-auth-btn"
bindtap="onLoginButtonTap"
>
授权登录
</button>
</view>
<mp-loading type="dot-gray" wx:elif="{{show === 'loading'}}"></mp-loading>
<block wx:elif="{{show === 'page'}}">
<slot></slot>
</block>
</block>

31
demo/miniprogram/components/page-wrapper/page-wrapper.wxss

@ -0,0 +1,31 @@
.container {
padding: 60rpx 50rpx 0;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.page-title {
color: #000;
font-size: 54rpx;
font-weight: 500;
align-self: flex-start;
}
.login-auth-bg {
margin-top: 120rpx;
width: 639rpx;
height: 357rpx;
}
.login-auth-notice {
text-align: center;
margin-top: 142rpx;
font-size: 28rpx;
color: #444;
}
.login-auth-btn {
margin-top: 32rpx;
}

38
demo/miniprogram/components/single-button/single-button.js

@ -0,0 +1,38 @@
Component({
properties: {
text: {
type: String,
value: '',
},
type: {
type: String,
value: '',
},
disabled: {
type: Boolean,
value: false,
},
icon: {
type: String,
value: '',
},
noPadding: {
type: Boolean,
value: false,
},
flex: {
type: Boolean,
value: false,
},
fixedBottom: {
type: Boolean,
value: false,
},
},
methods: {
onClick() {
this.triggerEvent('click', null, {});
},
},
});

6
demo/miniprogram/components/single-button/single-button.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"btn-group": "/components/btn-group/btn-group"
}
}

7
demo/miniprogram/components/single-button/single-button.wxml

@ -0,0 +1,7 @@
<btn-group
buttons="{{[{ btnText: text, type: type, disabled: disabled, icon: icon }]}}"
bind:click="onClick"
noPadding="{{noPadding}}"
flex="{{flex}}"
fixedBottom="{{fixedBottom}}"
/>

0
demo/miniprogram/components/single-button/single-button.wxss

274
demo/miniprogram/components/wifi-form/wifi-form.js

@ -0,0 +1,274 @@
const app = getApp();
const promisify = require('../../libs/wx-promisify');
const { subscribeStore } = require('../../libs/store-subscribe');
const actions = require('../../redux/actions');
const { getErrorMsg } = require('../../libs/utillib');
const isLocationPermissionGranted = async () => {
const { authSetting } = await promisify(wx.getSetting)();
return Boolean(authSetting['scope.userLocation']);
};
const requestLocationPermission = async () => {
try {
await promisify(wx.authorize)({
scope: 'scope.userLocation',
});
} catch (error) {
// 用户拒绝权限申请会抛异常, 引导用户进入设置页面授权
wx.showModal({
title: '',
content: '获取WiFi列表需要您授权使用位置信息',
confirmText: '去授权',
success: ({ confirm }) => {
if (confirm) {
wx.openSetting();
}
},
});
}
};
Component({
options: {
addGlobalClass: true,
},
properties: {
defaultSsid: {
type: String,
value: '',
},
autoSelectCurrentWifi: {
type: Boolean,
value: false,
},
},
data: {
wifiSSIDList: [],
selectedWifiIndex: -1,
selectedWifiSSID: '',
wifiNeedPassword: true,
iosWifiGuideVisible: false,
},
attached() {
this.wifiList = [];
this.selectedWifiPassword = '';
// 注册获取 WiFi 列表的回调函数
this.getWifiListCallback = this.onGetWifiList.bind(this);
wx.onGetWifiList(this.getWifiListCallback);
this.unsubscribeAll = subscribeStore([
{
selector: state => state.wifiList,
onChange: wifiList => this.updateWifiList(wifiList),
},
]);
this.autoGetWifiList();
console.log("this.data.autoSelectCurrentWifi", this.data.autoSelectCurrentWifi)
console.log("this.data.defaultSsid", this.data.defaultSsid)
if (this.data.autoSelectCurrentWifi) {
promisify(wx.getConnectedWifi)()
.then(({ wifi }) => {
this.selectWifi(wifi);
})
.catch(() => {});
} else if (this.data.defaultSsid) {
this.selectWifi({ SSID: this.data.defaultSsid, secure: true });
}
},
detached() {
// wx.offGetWifiList 兼容处理(最低基础库版本 2.9.0)
if (wx.offGetWifiList) {
wx.offGetWifiList(this.getWifiListCallback);
} else {
// 只有最后一个 getWifiList 回调会被使用,通过替换为空操作来清除回调
wx.onGetWifiList(() => {});
}
this.unsubscribeAll && this.unsubscribeAll();
},
methods: {
async autoGetWifiList() {
if (app.globalData.isAndroid && await isLocationPermissionGranted()) {
this.getWifiList({ silent: true });
}
},
onGetWifiList({ wifiList }) {
wx.hideLoading();
if (this.iosRefreshWifiCallback) {
// 刷新完成的回调
this.iosRefreshWifiCallback.success();
}
if (!this.getWifiListSilent) {
wx.showToast({
title: '刷新WiFi成功',
});
}
// SSID 去重复、按信号强度降序排序
const uniqSSIDMap = {};
const filteredWifiList = wifiList.filter((wifi) => {
if (!wifi.SSID) return false;
if (!(wifi.SSID in uniqSSIDMap)) {
uniqSSIDMap[wifi.SSID] = true;
return true;
}
return false;
}).sort((a, b) => b.signalStrength - a.signalStrength);
actions.updateWifiList(filteredWifiList);
},
updateWifiList(wifiList) {
this.wifiList = wifiList;
// 同步Picker数据
if (this.selectedWifi) {
this.selectWifi(this.selectedWifi);
}
this.setData({
wifiSSIDList: this.wifiList.map(wifi => wifi.SSID),
});
},
async getWifiList({ silent } = { silent: false }) {
if (this.iosRefreshWifiCallback) {
// 取消上一次刷新WiFi列表的 loading
this.iosRefreshWifiCallback.abort();
}
this.getWifiListSilent = silent;
try {
if (!silent) {
wx.showLoading({
title: '获取WiFi列表…',
mask: true,
});
}
await promisify(wx.getWifiList)();
} catch (err) {
console.error('getWifiList fail', err);
if (!silent) {
wx.hideLoading();
wx.showModal({
title: '获取WiFi列表失败',
content: getErrorMsg(err),
confirmText: '我知道了',
showCancel: false,
});
}
return;
}
// 处理iOS下需要跳转到设置页面获取WiFi列表的情况
if (app.globalData.isIOS) {
let callback = null;
const iosRefreshPromise = new Promise((resolve, reject) => {
callback = {
success: resolve,
abort: resolve,
fail: () => {
reject();
wx.hideLoading();
},
};
this.iosRefreshWifiCallback = callback;
});
this.iosRefreshWifiCallback.promise = iosRefreshPromise;
// 小程序回到前台时仍未回调 onGetWifiList 则刷新失败
const failOnAppShow = () => {
callback.fail();
};
wx.onAppShow(failOnAppShow);
try {
await iosRefreshPromise;
} catch (err) {
wx.showModal({
title: '获取WiFi列表失败,请重试',
confirmText: '我知道了',
showCancel: false,
});
} finally {
if (this.iosRefreshWifiCallback === callback) {
this.iosRefreshWifiCallback = null;
}
wx.offAppShow(failOnAppShow);
}
}
},
async triggerWifiListRefresh() {
if (app.globalData.isIOS) {
// iOS 下展示刷新WiFi指引
this.showIOSWifiGuide();
} else if (await isLocationPermissionGranted()) {
this.getWifiList();
} else {
// Android 下需要用户授予位置信息权限才能取得WiFi列表
requestLocationPermission();
}
},
showIOSWifiGuide() {
this.setData({ iosWifiGuideVisible: true });
},
selectWifi(wifi) {
this.selectedWifi = wifi;
let selectedWifiIndex = -1;
this.wifiList.forEach((w, index) => {
if (w.SSID === this.selectedWifi.SSID) {
selectedWifiIndex = index;
this.selectedWifi = w;
}
});
this.setData({
selectedWifiIndex,
selectedWifiSSID: wifi.SSID,
wifiNeedPassword: this.selectedWifi.secure,
});
if (!wifi.secure) {
this.selectedWifiPassword = '';
}
},
onIOSWifiGuideConfirm() {
this.setData({ iosWifiGuideVisible: false });
this.getWifiList();
},
onWifiPickerSelect(e) {
this.selectWifi(this.wifiList[e.detail.value]);
},
onWifiPasswordChange(e) {
this.selectedWifiPassword = e.detail.value;
},
getSelectedWifiInfo() {
return {
...this.selectedWifi,
password: this.selectedWifiPassword,
forceNewApi: true,
};
},
},
});

6
demo/miniprogram/components/wifi-form/wifi-form.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"ios-wifi-guide": "/components/ios-wifi-guide/ios-wifi-guide"
}
}

56
demo/miniprogram/components/wifi-form/wifi-form.wxml

@ -0,0 +1,56 @@
<block>
<view class="wifi-form">
<view class="weui-cells">
<view class="weui-cell weui-cell_select">
<view class="weui-cell__hd weui-cell__hd_in-select-after">
<view class="weui-label">WiFi</view>
</view>
<view class="weui-cell__bd">
<view
class="weui-select weui-select_in-select-after {{selectedWifiSSID ? '' : 'form-placeholder'}}"
wx:if="{{wifiSSIDList.length === 0}}"
bindtap="triggerWifiListRefresh"
>{{selectedWifiSSID ? selectedWifiSSID : '点击获取WiFi列表'}}</view>
<picker
wx:else
bindchange="onWifiPickerSelect"
value="{{selectedWifiIndex}}"
range="{{wifiSSIDList}}"
>
<view
class="weui-select weui-select_in-select-after {{selectedWifiSSID ? '' : 'form-placeholder'}}"
>
{{selectedWifiSSID ? selectedWifiSSID : '点击选择WiFi'}}
</view>
</picker>
</view>
</view>
<view class="weui-cell weui-cell_input">
<view class="weui-cell__hd">
<view class="weui-label">密码</view>
</view>
<view class="weui-cell__bd">
<input
wx:if="{{wifiNeedPassword}}"
class="weui-input"
password="{{true}}"
placeholder="请输入WiFi密码"
bindinput="onWifiPasswordChange"
placeholder-class="form-placeholder"
/>
<input
wx:else
class="weui-input"
password="{{true}}"
disabled="{{true}}"
placeholder="当前WiFi热点无需输入密码"
placeholder-class="form-placeholder"
value=""
/>
</view>
</view>
</view>
</view>
<ios-wifi-guide wx:if="{{iosWifiGuideVisible}}" bind:confirm="onIOSWifiGuideConfirm" />
</block>

16
demo/miniprogram/components/wifi-form/wifi-form.wxss

@ -0,0 +1,16 @@
.wifi-form {
width: 750rpx;
}
.wifi-form .weui-cell {
padding-left: 50rpx;
padding-right: 50rpx;
}
.wifi-form .weui-select {
padding-left: 0;
}
.form-placeholder {
color: #D8D8D8;
}

14
demo/miniprogram/constants.js

@ -0,0 +1,14 @@
module.exports.UpgradeStatus = {
OFFLINE: 0,
PENDING: 1,
SEND_MSG_SUCC: 2,
DOWNLOADING: 3,
BURNING: 4,
FAIL: 5,
SUCCESS: 6,
ASYNC_PROCESSED: 7,
USER_CONFIRM: 8,
DOWNLOAD_DONE: 20,
};
module.exports.dangerColor = '#ff584c';

637
demo/miniprogram/libs/redux.js

@ -0,0 +1,637 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.Redux = {})));
}(this, (function (exports) { 'use strict';
function symbolObservablePonyfill(root) {
var result;
var Symbol = root.Symbol;
if (typeof Symbol === 'function') {
if (Symbol.observable) {
result = Symbol.observable;
} else {
result = Symbol('observable');
Symbol.observable = result;
}
} else {
result = '@@observable';
}
return result;
}
/* global window */
var root;
if (typeof self !== 'undefined') {
root = self;
} else if (typeof window !== 'undefined') {
root = window;
} else if (typeof global !== 'undefined') {
root = global;
} else if (typeof module !== 'undefined') {
root = module;
} else {
root = Function('return this')();
}
var result = symbolObservablePonyfill(root);
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
var ActionTypes = {
INIT: '@@redux/INIT' + Math.random().toString(36).substring(7).split('').join('.'),
REPLACE: '@@redux/REPLACE' + Math.random().toString(36).substring(7).split('').join('.')
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
function isPlainObject(obj) {
if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
/**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
*
* @param {Function} [enhancer] The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error('You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.');
}
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.');
}
if (isDispatching) {
throw new Error('You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.');
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
if (isDispatching) {
throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.');
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing what changed. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
}
if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
currentReducer = nextReducer;
dispatch({ type: ActionTypes.REPLACE });
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: function subscribe(observer) {
if ((typeof observer === 'undefined' ? 'undefined' : _typeof(observer)) !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.');
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return { unsubscribe: unsubscribe };
}
}, _ref[result] = function () {
return this;
}, _ref;
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT });
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[result] = observable, _ref2;
}
/**
* Prints a warning in the console if it exists.
*
* @param {String} message The warning message.
* @returns {void}
*/
function warning(message) {
/* eslint-disable no-console */
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message);
}
/* eslint-enable no-console */
try {
// This error was thrown as a convenience so that if you enable
// "break on all exceptions" in your console,
// it would pause the execution at this line.
throw new Error(message);
} catch (e) {} // eslint-disable-line no-empty
}
function getUndefinedStateErrorMessage(key, action) {
var actionType = action && action.type;
var actionDescription = actionType && 'action "' + String(actionType) + '"' || 'an action';
return 'Given ' + actionDescription + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state. ' + 'If you want this reducer to hold no value, you can return null instead of undefined.';
}
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
var reducerKeys = Object.keys(reducers);
var argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer';
if (reducerKeys.length === 0) {
return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';
}
if (!isPlainObject(inputState)) {
return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"');
}
var unexpectedKeys = Object.keys(inputState).filter(function (key) {
return !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key];
});
unexpectedKeys.forEach(function (key) {
unexpectedKeyCache[key] = true;
});
if (action && action.type === ActionTypes.REPLACE) return;
if (unexpectedKeys.length > 0) {
return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.');
}
}
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(function (key) {
var reducer = reducers[key];
var initialState = reducer(undefined, { type: ActionTypes.INIT });
if (typeof initialState === 'undefined') {
throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined. If you don\'t want to set a value for this reducer, ' + 'you can use null instead of undefined.');
}
var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
if (typeof reducer(undefined, { type: type }) === 'undefined') {
throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined, but can be null.');
}
});
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
{
if (typeof reducers[key] === 'undefined') {
warning('No reducer provided for key "' + key + '"');
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers);
var unexpectedKeyCache = void 0;
{
unexpectedKeyCache = {};
}
var shapeAssertionError = void 0;
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
return function combination() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var action = arguments[1];
if (shapeAssertionError) {
throw shapeAssertionError;
}
{
var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
if (warningMessage) {
warning(warningMessage);
}
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
var errorMessage = getUndefinedStateErrorMessage(_key, action);
throw new Error(errorMessage);
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state;
};
}
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments));
};
}
/**
* Turns an object whose values are action creators, into an object with the
* same keys, but with every function wrapped into a `dispatch` call so they
* may be invoked directly. This is just a convenience method, as you can call
* `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
*
* For convenience, you can also pass a single function as the first argument,
* and get a function in return.
*
* @param {Function|Object} actionCreators An object whose values are action
* creator functions. One handy way to obtain it is to use ES6 `import * as`
* syntax. You may also pass a single function.
*
* @param {Function} dispatch The `dispatch` function available on your Redux
* store.
*
* @returns {Function|Object} The object mimicking the original object, but with
* every action creator wrapped into the `dispatch` call. If you passed a
* function as `actionCreators`, the return value will also be a single
* function.
*/
function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
if ((typeof actionCreators === 'undefined' ? 'undefined' : _typeof(actionCreators)) !== 'object' || actionCreators === null) {
throw new Error('bindActionCreators expected an object or a function, instead received ' + (actionCreators === null ? 'null' : typeof actionCreators === 'undefined' ? 'undefined' : _typeof(actionCreators)) + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');
}
var keys = Object.keys(actionCreators);
var boundActionCreators = {};
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
}
/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
*
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
function applyMiddleware() {
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
var store = createStore.apply(undefined, args);
var _dispatch = function dispatch() {
throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
};
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(undefined, arguments);
}
};
var chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
_dispatch = compose.apply(undefined, chain)(store.dispatch);
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}
/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
function isCrushed() {}
if (typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') {
// warning("You are currently using minified code outside of NODE_ENV === 'production'. " + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
}
exports.createStore = createStore;
exports.combineReducers = combineReducers;
exports.bindActionCreators = bindActionCreators;
exports.applyMiddleware = applyMiddleware;
exports.compose = compose;
exports.__DO_NOT_USE__ActionTypes = ActionTypes;
Object.defineProperty(exports, '__esModule', { value: true });
})));

43
demo/miniprogram/libs/store-subscribe.js

@ -0,0 +1,43 @@
const store = require('../redux/index');
/**
* @typedef {object} StateChangeListener redux state 变化监听器
* @property {Function} selector 用于从 state 中取出要监听变化的值
* @property {Function} [onChange] 变化回调函数被监听的值变化时被调用第一个参数为变化后的值第二个参数为变化前的值
*/
/**
* 注册 redux state 变化监听器
* @param {StateChangeListener[]} definitions 监听器定义
* @return {Function} 如果需要注销变化监听器调用返回的函数即可
*/
module.exports.subscribeStore = (definitions) => {
const listeners = [];
const state = store.getState();
definitions.forEach(({
selector,
onChange = null,
}) => {
const initialValue = selector(state);
onChange(initialValue);
listeners.push({
selector,
onChange,
value: initialValue,
});
});
return store.subscribe(() => {
const state = store.getState();
listeners.forEach((listener) => {
const { selector, onChange, value: oldValue } = listener;
const newValue = selector(state);
if (oldValue !== newValue) { // 浅比较
// eslint-disable-next-line no-param-reassign
listener.value = newValue;
onChange(newValue, oldValue);
}
});
});
};

93
demo/miniprogram/libs/utillib.js

@ -0,0 +1,93 @@
const urlParse = require('url-parse');
const querystring = require('query-string');
module.exports.delay = timeout => new Promise(resolve => setTimeout(() => resolve(), timeout));
module.exports.fetchAllList = async (fetchFn) => {
const limit = 100;
let offset = 0;
let total = 100;
let list = [];
while (offset === 0 || list.length < total) {
const resp = await fetchFn({ offset, limit });
if (!resp.list.length) return list;
total = resp.total;
offset = offset + limit;
list = list.concat(resp.list);
}
return list;
};
module.exports.getErrorMsg = (err) => {
if (!err) return;
let message = '';
if (typeof err === 'string') return err;
message = err.msg || err.message || err.errMsg || err.Message || '';
if (message && err.reqId) {
message += `(${err.reqId})`;
}
return message;
};
const pad = (str, len) => {
let paddedString = String(str);
while (paddedString.length < len) {
paddedString = `0${paddedString}`;
}
return paddedString;
};
/**
* 格式化时间
*
* yyyy-mm-dd HH:MM:ss
*
* @param {*} [date]
* @return {String}
*/
module.exports.formatDate = (date) => {
let dateObj;
if (date instanceof Date) {
dateObj = date;
} else if (typeof date === 'number') {
dateObj = new Date(date);
} else {
dateObj = null;
}
if (!dateObj) return '-';
const d = dateObj.getDate();
const m = dateObj.getMonth() + 1;
const y = dateObj.getFullYear();
const H = dateObj.getHours();
const M = dateObj.getMinutes();
const s = dateObj.getSeconds();
const yyyy = y;
const mm = pad(m, 2);
const dd = pad(d, 2);
const HH = pad(H, 2);
const MM = pad(M, 2);
const ss = pad(s, 2);
return `${yyyy}-${mm}-${dd} ${HH}:${MM}:${ss}`;
};
module.exports.parseUrl = (url) => {
const uri = urlParse(url);
if (uri && uri.query) {
uri.query = querystring.parse(uri.query);
}
return uri;
};

8
demo/miniprogram/libs/wx-promisify.js

@ -0,0 +1,8 @@
module.exports = api => (object, ...params) => new Promise((resolve, reject) => {
if (api) {
api.call(wx, { ...object, success: resolve, fail: reject }, ...params);
} else {
console.error('调用不支持的 API');
reject({ errCode: 'WXAPI_NOT_SUPPORTED' });
}
});

230
demo/miniprogram/models.js

@ -0,0 +1,230 @@
const { fetchAllList } = require('./libs/utillib');
const requestApi = (action, data, opts) => getApp().sdk.requestApi(action, data, opts);
const getDeviceList = async () => requestApi('AppGetFamilyDeviceList', {
FamilyId: 'default',
});
const getDeviceStatuses = async ({ DeviceIds }) => requestApi('AppGetDeviceStatuses', {
DeviceIds,
});
const getDeviceData = async ({ DeviceId }) => requestApi('AppGetDeviceData', {
DeviceId,
});
const getDeviceDataMap = async (deviceIdList) => {
const deviceDataMap = {};
return Promise.all(deviceIdList.map(DeviceId => getDeviceData({ DeviceId })
.then(({ Data }) => ({ Data, DeviceId }))))
.then((deviceDataList) => {
deviceDataList.forEach(({ Data: DataJSON, DeviceId }) => {
let Data;
try {
Data = JSON.parse(DataJSON);
} catch (err) {
// Data JSON 解析失败时,置 Data 为空对象
Data = {};
}
deviceDataMap[DeviceId] = Data;
});
return deviceDataMap;
});
};
const getProduct = async ({ ProductId }) => {
const { Products } = await requestApi('AppGetProducts', {
ProductIds: [ProductId],
});
return Products[0];
};
const getProducts = async ({ ProductIds }) => requestApi('AppGetProducts', {
ProductIds,
});
const controlDeviceData = async (device, deviceData) => requestApi('AppControlDeviceData', {
ProductId: device.ProductId,
DeviceName: device.DeviceName,
Data: JSON.stringify(deviceData),
});
const deleteDeviceFromFamily = async ({
FamilyId,
DeviceId,
}) => requestApi('AppDeleteDeviceInFamily', {
FamilyId,
DeviceId,
});
const createBindDeviceToken = async () => {
const { Token } = await requestApi('AppCreateDeviceBindToken');
return Token;
};
// 设备分享
const getShareDeviceList = async ({
Offset = 0,
Limit = 10,
}) => {
const { ShareDevices, Total } = await requestApi('AppListUserShareDevices', {
Offset,
Limit,
});
return {
list: ShareDevices,
total: Total,
};
};
const getAllShareDeviceList = async () => {
const shareDeviceList = fetchAllList(({ offset, limit }) => getShareDeviceList({
Offset: offset,
Limit: limit,
}));
return shareDeviceList;
};
const removeUserShareDevice = async ({
DeviceId,
}) => requestApi('AppRemoveUserShareDevice', {
DeviceId,
});
// 固件升级
const checkDeviceFirmwareUpdate = async ({ ProductId, DeviceName }) => requestApi('AppCheckFirmwareUpdate', {
ProductId,
DeviceName,
});
const publishDeviceFirmwareUpdateMessage = async ({ ProductId, DeviceName }) => requestApi('AppPublishFirmwareUpdateMessage', {
ProductId,
DeviceName,
});
const describeDeviceFirmwareUpdateStatus = async ({ ProductId, DeviceName }) => requestApi('AppDescribeFirmwareUpdateStatus', {
ProductId,
DeviceName,
});
// 设备分享
const getShareDeviceToken = async ({
FamilyId,
DeviceId,
TokenContext,
}) => {
let tokenContext = '';
if (TokenContext) {
tokenContext = typeof TokenContext !== 'string' ? JSON.stringify(TokenContext) : TokenContext;
}
const { ShareDeviceToken } = await requestApi('AppCreateShareDeviceToken', {
FamilyId,
DeviceId,
TokenContext: tokenContext,
});
return ShareDeviceToken;
};
const getShareTokenInfo = async ({
ShareDeviceToken,
}) => {
const { ShareDeviceTokenInfo } = await requestApi('AppDescribeShareDeviceToken', {
ShareDeviceToken,
});
return ShareDeviceTokenInfo;
};
const bindShareDevice = async ({
ShareDeviceToken,
DeviceId,
}) => requestApi('AppBindUserShareDevice', {
ShareDeviceToken,
DeviceId,
});
const getDeviceShareUserList = async ({
DeviceId,
Offset,
Limit,
}) => {
const { Users, Total } = await requestApi('AppListShareDeviceUsers', {
DeviceId,
Offset,
Limit,
});
return { list: Users, total: Total };
};
const getAllDeviceShareUserList = async ({
DeviceId,
}) => fetchAllList(({ offset, limit }) => getDeviceShareUserList({
Offset: offset,
Limit: limit,
DeviceId,
}));
const removeShareDeviceUser = async ({
RemoveUserID,
DeviceId,
}) => requestApi('AppRemoveShareDeviceUser', {
RemoveUserID,
DeviceId,
});
const setUserDeviceConfig = async ({
DeviceId,
DeviceKey,
DeviceValue,
}) => {
const DeviceValueJSON = typeof DeviceValue !== 'string' ? JSON.stringify(DeviceValue) : DeviceValue;
return requestApi('AppSetUserDeviceConfig', {
DeviceId,
DeviceKey,
DeviceValue: DeviceValueJSON,
});
};
const secureAddDeviceInFamily = async ({
DeviceSignature,
FamilyId,
RoomId,
}) => requestApi('AppSecureAddDeviceInFamily', {
DeviceSignature,
FamilyId,
RoomId,
});
module.exports = {
requestApi,
getDeviceList,
getDeviceStatuses,
getDeviceData,
getDeviceDataMap,
getProducts,
getProduct,
controlDeviceData,
createBindDeviceToken,
getAllShareDeviceList,
deleteDeviceFromFamily,
removeUserShareDevice,
checkDeviceFirmwareUpdate,
publishDeviceFirmwareUpdateMessage,
describeDeviceFirmwareUpdateStatus,
getAllDeviceShareUserList,
getShareDeviceToken,
getShareTokenInfo,
bindShareDevice,
removeShareDeviceUser,
setUserDeviceConfig,
secureAddDeviceInFamily,
};

35
demo/miniprogram/package.json

@ -0,0 +1,35 @@
{
"name": "qcloud-iotexplorer-appdev-miniprogram-sdk-demo",
"version": "1.1.1",
"description": "腾讯云物联网开发平台应用开发小程序端 SDK Demo",
"main": "app.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/tencentyun/qcloud-iotexplorer-appdev-miniprogram-sdk-demo.git"
},
"dependencies": {
"moment": "^2.29.1",
"qcloud-iotexplorer-appdev-plugin-wificonf-airkiss": "^1.4.6",
"qcloud-iotexplorer-appdev-plugin-wificonf-blecombo": "^1.4.6",
"qcloud-iotexplorer-appdev-plugin-wificonf-core": "^1.4.6",
"qcloud-iotexplorer-appdev-plugin-wificonf-simpleconfig": "^1.4.6",
"qcloud-iotexplorer-appdev-plugin-wificonf-smartconfig": "^1.4.6",
"qcloud-iotexplorer-appdev-plugin-wificonf-softap": "^1.4.6",
"qcloud-iotexplorer-appdev-sdk": "^1.4.6",
"qcloud-iotexplorer-bluetooth-adapter": "^0.4.6",
"qcloud-iotexplorer-bluetooth-adapter-llsync": "^1.4.6",
"qcloud-iotexplorer-fileresource-sdk": "^1.1.9",
"query-string": "^6.13.1",
"spark-md5": "^3.0.1",
"url-parse": "^1.4.7",
"weui-miniprogram": "1.0.8"
},
"bugs": {
"url": "https://github.com/tencentyun/qcloud-iotexplorer-appdev-miniprogram-sdk-demo/issues"
},
"homepage": "https://github.com/tencentyun/qcloud-iotexplorer-appdev-miniprogram-sdk-demo#readme"
}

9
demo/miniprogram/pages/add-device/air-kiss/air-kiss.js

@ -0,0 +1,9 @@
Page({
data: {
errorTips: [
'确认设备处于AirKiss配网模式',
'核对家庭WiFi密码是否正确',
'确认路由设备是否为2.4G WiFi频段',
],
},
});

6
demo/miniprogram/pages/add-device/air-kiss/air-kiss.json

@ -0,0 +1,6 @@
{
"usingComponents": {
"wifi-conf": "../components/wifi-conf/wifi-conf"
},
"navigationBarTitleText": ""
}

5
demo/miniprogram/pages/add-device/air-kiss/air-kiss.wxml

@ -0,0 +1,5 @@
<wifi-conf
title="AirKiss 配网"
pluginName="wifiConfAirKiss"
errorTips="{{errorTips}}"
/>

0
demo/miniprogram/pages/add-device/air-kiss/air-kiss.wxss

10
demo/miniprogram/pages/add-device/ble-combo/ble-combo.js

@ -0,0 +1,10 @@
Page({
data: {
errorTips: [
'确认设备处于BleCombo配网模式',
'核对家庭WiFi密码是否正确',
'确认路由设备是否为2.4G WiFi频段',
'确认设备三元组信息是否有效'
],
},
});

6
demo/miniprogram/pages/add-device/ble-combo/ble-combo.json

@ -0,0 +1,6 @@
{
"usingComponents": {
"wifi-conf": "../components/wifi-conf/wifi-conf"
},
"navigationBarTitleText": ""
}

5
demo/miniprogram/pages/add-device/ble-combo/ble-combo.wxml

@ -0,0 +1,5 @@
<wifi-conf
title="Ble-Combo 配网"
pluginName="wifiConfBleCombo"
errorTips="{{errorTips}}"
/>

0
demo/miniprogram/pages/add-device/ble-combo/ble-combo.wxss

63
demo/miniprogram/pages/add-device/common.js

@ -0,0 +1,63 @@
const app = getApp();
const models = require('../../models');
const { getErrorMsg, delay } = require('../../libs/utillib');
const promisify = require('../../libs/wx-promisify');
const requestBindDeviceToken = async () => {
try {
return await models.createBindDeviceToken();
} catch (err) {
console.error('requestBindDeviceToken fail', err);
wx.showModal({
title: '获取配网Token失败',
content: getErrorMsg(err),
showCancel: false,
confirmText: '我知道了',
});
return null;
}
};
const connectWifi = async (wifi) => {
try {
if (!app.globalData.isAndroid) {
// Android 下小程序 connectWifi 会弹出一个“微信连WiFi”的提示框
wx.showLoading({
title: 'WiFi连接中',
mask: true,
});
}
await promisify(wx.connectWifi)(wifi);
const { wifi: connectedWifi } = await promisify(wx.getConnectedWifi)();
if (connectedWifi.SSID !== wifi.SSID) {
throw {
code: 'SSID_MISMATCH',
};
}
wx.showToast({
title: 'WiFi连接成功',
duration: 1500,
});
await delay(1500);
} catch (err) {
wx.showModal({
title: 'WiFi连接失败',
content: getErrorMsg(err),
confirmText: '我知道了',
showCancel: false,
});
console.error('connect wifi fail', err);
return Promise.reject(err);
} finally {
if (!app.globalData.isAndroid) {
wx.hideLoading();
}
}
};
module.exports = {
requestBindDeviceToken,
connectWifi,
};

22
demo/miniprogram/pages/add-device/common.wxss

@ -0,0 +1,22 @@
.container {
padding: 60rpx 50rpx 0;
}
.page-title {
color: #333 ;
font-size: 54rpx;
font-weight: 500;
align-self: flex-start;
}
.page-subtitle {
color: #333;
font-size: 40rpx;
}
.page-main {
margin-top: 16px;
display: flex;
flex-direction: column;
align-items: center;
}

25
demo/miniprogram/pages/add-device/components/bluetooth-finder/blueToothAdapter.js

@ -0,0 +1,25 @@
import { BlueToothAdapter } from 'qcloud-iotexplorer-bluetooth-adapter';
import { BleComboEspDeviceAdapter, BleComboLLSyncDeviceAdapter } from 'qcloud-iotexplorer-appdev-plugin-wificonf-blecombo';
import { LLSyncDeviceAdapter } from 'qcloud-iotexplorer-bluetooth-adapter-llsync';
const app = getApp();
LLSyncDeviceAdapter.injectOptions({
appDevSdk: app.sdk
})
export const serviceIdMap = {
BLE_COMBO_LLSYNC: BleComboLLSyncDeviceAdapter.serviceId,
BLE_COMBO_ESP: BleComboEspDeviceAdapter.serviceId,
LLSync: LLSyncDeviceAdapter.serviceId,
};
export const bluetoothAdapter = new BlueToothAdapter({
deviceAdapters: [
BleComboEspDeviceAdapter,
BleComboLLSyncDeviceAdapter,
LLSyncDeviceAdapter,
],
actions: {
}
});

106
demo/miniprogram/pages/add-device/components/bluetooth-finder/bluetooth-finder.js

@ -0,0 +1,106 @@
// pages/add-device/components/bluetooth-finder.js
import {serviceIdMap, bluetoothAdapter} from './blueToothAdapter';
Component({
/**
* 组件的属性列表
*/
properties: {
adapterType: String
},
/**
* 组件的初始数据
*/
data: {
devices: [],
errMsg: '查找设备出错',
isSearching: true,
nextStepDisabled: true,
},
/**
* 组件的方法列表
*/
methods: {
async connectDevice(e) {
console.log(e.target.dataset.device);
const {
device,
index,
} = e.target.dataset;
try {
this.setData({
[`devices[${index}]`]: {
...device,
loading: true
},
nextStepDisabled: false,
});
const deviceAdapter = await bluetoothAdapter.connectDevice(device);
// 获得 deviceAdapter 之后可以使用它来发送wifi信息和token
console.info('deviceAdapter:', deviceAdapter);
this.setData({
[`devices[${index}]`]: {
...device,
isConnected: true,
loading: false,
},
nextStepDisabled: false,
});
deviceAdapter.on('disconnect', () => {
this.setData({
[`devices[${index}]`]: {
...device,
isConnected: false,
loading: false,
},
});
});
this.triggerEvent('connected', deviceAdapter);
} catch (err) {
this.setData({
[`devices[${index}]`]: {
...device,
loading: false,
},
});
wx.showModal({ content: '连接蓝牙设备失败'});
console.error('连接设备失败', err);
}
},
onBottomButtonClick(e) {
console.log(e);
this.triggerEvent('nextStep', );
}
},
attached() {
console.log('start search', bluetoothAdapter, this.data.adapterType);
bluetoothAdapter.startSearch({
serviceIds: [serviceIdMap[this.data.adapterType]], // 这里需要根据不同的协议选择不同的serviceId
onError: (err) => {
this.setData({
isSearching: false,
})
console.error('查找设备出错', err);
wx.showModal({
content: '查找设备出错',
})
},
onSearch: (devices) => {
console.log('searched devices', devices);
if (devices.length) {
bluetoothAdapter.stopSearch();
this.setData({
isSearching: false
})
}
this.setData({
devices: devices
})
}
});
}
})

6
demo/miniprogram/pages/add-device/components/bluetooth-finder/bluetooth-finder.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"btn-group": "/components/btn-group/btn-group"
}
}

30
demo/miniprogram/pages/add-device/components/bluetooth-finder/bluetooth-finder.wxml

@ -0,0 +1,30 @@
<!--pages/add-device/components/bluetooth-finder.wxml-->
<view class="page-subtitle">设备蓝牙</view>
<view>
<view wx:if="{{isSearching}}"> 正在搜索蓝牙设备...</view>
<view wx:else>
<view>设备列表</view>
<view wx:for="{{devices}}" wx:key="{{index}}">
<view class="device-item">
{{item.name}}
<button
style="color:#0052D9"
size="mini"
loading="{{item.loading}}"
disabled="{{item.isConnected}}"
data-device="{{item}}"
data-index="{{index}}"
bindtap="connectDevice"
>
{{ item.isConnected ? '已连接' : '连接' }}
</button>
</view>
</view>
<btn-group
wx:if="{{!nextStepDisabled}}"
buttons="{{[{ btnText: '下一步', type: 'primary', id: 'complete'}]}}"
bind:click="onBottomButtonClick"
fixed-bottom="{{true}}"
/>
</view>
</view>

11
demo/miniprogram/pages/add-device/components/bluetooth-finder/bluetooth-finder.wxss

@ -0,0 +1,11 @@
/* pages/add-device/components/bluetooth-finder.wxss */
.device-item{
display: flex;
justify-content: space-between;
padding: 4rpx 10rpx;
}
.device-item button{
color: #0052D9;
background-color: none;
border: none;
}

27
demo/miniprogram/pages/add-device/components/do-config/do-config.js

@ -0,0 +1,27 @@
const showWifiConfTypeMenu = require('../../wifiConfTypeMenu');
const { connectDeviceSteps } = require('../../constants');
Component({
properties: {
curStep: {
type: Number,
},
},
data: {
connectDeviceSteps,
},
methods: {
onBottomButtonClick(e) {
switch (e.detail.btn.id) {
case 'select-type':
showWifiConfTypeMenu(true);
break;
case 'complete':
this.triggerEvent('complete', {}, {});
break;
}
},
},
});

6
demo/miniprogram/pages/add-device/components/do-config/do-config.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"btn-group": "/components/btn-group/btn-group"
}
}

10
demo/miniprogram/pages/add-device/components/do-config/do-config.wxml

@ -0,0 +1,10 @@
<view class="page-subtitle">开始配网</view>
<view class="page-main">
<view class="progress-list">
<view wx:for="{{connectDeviceSteps}}" wx:key="index" class="progress-item {{curStep > index ? 'done' : ''}}">
<image class="item-icon icon-check" src="/assets/common/check-blue.svg" wx:if="{{curStep > index}}" />
<image class="item-icon icon-loading" src="/assets/device/connecting-loading.svg" wx:else />
{{item}}
</view>
</view>
</view>

66
demo/miniprogram/pages/add-device/components/do-config/do-config.wxss

@ -0,0 +1,66 @@
@import '../../common.wxss';
:host {
width: 100%;
}
.page-subtitle {
color: #333;
font-size: 40rpx;
}
.progress-list {
margin-top: 100rpx;
display: inline-flex;
flex-direction: column;
}
.progress-list .progress-item {
position: relative;
font-size: 30rpx;
color: #333333;
margin-bottom: 20rpx;
}
.progress-list .progress-item.done {
color: #888888;
}
.progress-list .progress-item .item-icon {
position: absolute;
left: 0;
top: 50%;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
}
.progress-list .progress-item .icon-check {
left: -59rpx;
width: 48rpx;
height: 48rpx;
}
.progress-list .progress-item .icon-loading {
left: -62rpx;
width: 48rpx;
height: 48rpx;
-webkit-animation: rotate 1s linear infinite;
animation: rotate 1s linear infinite;
}
@-webkit-keyframes rotate {
from {
-webkit-transform: translateY(-50%) rotate(0);
transform: translateY(-50%) rotate(0);
}
to {
-webkit-transform: translateY(-50%) rotate(360deg);
transform: translateY(-50%) rotate(360deg);
}
}
@keyframes rotate {
from {
-webkit-transform: translateY(-50%) rotate(0);
transform: translateY(-50%) rotate(0);
}
to {
-webkit-transform: translateY(-50%) rotate(360deg);
transform: translateY(-50%) rotate(360deg);
}
}

44
demo/miniprogram/pages/add-device/components/error-view/error-view.js

@ -0,0 +1,44 @@
const showWifiConfTypeMenu = require('../../wifiConfTypeMenu');
Component({
properties: {
errorTips: {
type: Array,
},
logs: {
type: String,
},
},
data: {
showLog: false,
},
methods: {
onBottomButtonClick(e) {
switch (e.detail.btn.id) {
case 'restart': {
const pages = getCurrentPages();
wx.redirectTo({
url: `/${pages[pages.length - 1].route}`,
});
break;
}
case 'select-type':
showWifiConfTypeMenu(true);
break;
case 'show-log':
this.setData({ showLog: true });
break;
case 'hide-log':
this.setData({ showLog: false });
break;
case 'copy-log':
wx.setClipboardData({
data: this.data.logs,
});
break;
}
},
},
});

6
demo/miniprogram/pages/add-device/components/error-view/error-view.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"btn-group": "/components/btn-group/btn-group"
}
}

34
demo/miniprogram/pages/add-device/components/error-view/error-view.wxml

@ -0,0 +1,34 @@
<block wx:if="{{!showLog}}">
<view class="result-view">
<icon type="warn" size="108rpx" class="result-icon" />
<view class="result-title">配网失败</view>
<view class="result-subtitle">请检查以下信息</view>
<view class="result-info-container">
<view class="result-info-list">
<view
wx:for="{{errorTips}}"
wx:key="index"
>
{{index + 1}}. {{item}}
</view>
</view>
</view>
</view>
<btn-group
buttons="{{[{ btnText: '重试', type: 'primary', id: 'restart' }, { btnText: '切换配网方式', id: 'select-type' }, { btnText: '查看日志', id: 'show-log'}]}}"
bind:click="onBottomButtonClick"
fixed-bottom="{{true}}"
/>
</block>
<block wx:else>
<view class="log-container">{{logs}}</view>
<btn-group
buttons="{{[{ btnText: '复制日志内容', id: 'copy-log'}, { btnText: '返回', id: 'hide-log'}]}}"
bind:click="onBottomButtonClick"
fixed-bottom="{{true}}"
/>
</block>

52
demo/miniprogram/pages/add-device/components/error-view/error-view.wxss

@ -0,0 +1,52 @@
@import '../../common.wxss';
:host {
width: 100%;
}
.result-view {
text-align: center;
}
.result-view .result-icon {
margin-top: 210rpx;
height: 108rpx;
}
.result-view .result-title {
font-size: 34rpx;
color: #000000;
margin-top: 10Px;
line-height: 1.4;
}
.result-view .result-subtitle {
font-size: 28rpx;
color: #888;
margin-top: 8Px;
line-height: 1.4;
}
.result-view .result-info-container {
margin: 20px auto 0;
font-size: 28rpx;
color: #888;
line-height: 1.7;
max-width: 590rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.result-view .result-info-list {
text-align: left;
}
.log-container {
font-size: 12px;
font-family: monospace;
white-space: pre-wrap;
word-break: break-all;
height: 70vh;
overflow: scroll;
}

16
demo/miniprogram/pages/add-device/components/guide/guide.js

@ -0,0 +1,16 @@
const showWifiConfTypeMenu = require('../../wifiConfTypeMenu');
Component({
methods: {
onBottomButtonClick(e) {
switch (e.detail.btn.id) {
case 'select-type':
showWifiConfTypeMenu(true);
break;
case 'complete':
this.triggerEvent('complete', {}, {});
break;
}
},
},
});

6
demo/miniprogram/pages/add-device/components/guide/guide.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"btn-group": "/components/btn-group/btn-group"
}
}

17
demo/miniprogram/pages/add-device/components/guide/guide.wxml

@ -0,0 +1,17 @@
<view class="page-subtitle">配置设备</view>
<view class="page-main">
<image
src="https://main.qcloudimg.com/raw/31a4f30e9ab5af7e22ee5c96d795712b.jpg"
class="config-device-img"
/>
<view class="config-device-guide">
<view class="config-device-guide-line">1. 接通设备电源。</view>
<view class="config-device-guide-line">2. 长按复位键(开关)5 秒,指示灯快闪时,再次长按复位键 3 秒。</view>
<view class="config-device-guide-line">3. 指示灯进入慢闪状态即可。</view>
</view>
</view>
<btn-group
buttons="{{[{ btnText: '切换配网方式', id: 'select-type' }, { btnText: '下一步', type: 'primary', id: 'complete'}]}}"
bind:click="onBottomButtonClick"
fixed-bottom="{{true}}"
/>

23
demo/miniprogram/pages/add-device/components/guide/guide.wxss

@ -0,0 +1,23 @@
@import '../../common.wxss';
:host {
width: 100%;
}
.config-device-img {
width: 650rpx;
height: 374.5rpx;
border-radius: 8rpx;
}
.config-device-guide {
margin-top: 16px;
color: #333333;
font-size: 28rpx;
line-height: 1.5;
word-break: break-all;
}
.config-device-guide-line {
margin-bottom: 7px;
}

57
demo/miniprogram/pages/add-device/components/input-wifi-info/input-wifi-info.js

@ -0,0 +1,57 @@
const app = getApp();
const { connectWifi } = require('../../common');
Component({
properties: {
title: {
type: String,
},
autoConnect: {
type: Boolean,
value: true,
},
},
attached() {
this.wifiForm = this.selectComponent('#wifi-form');
},
methods: {
onBottomButtonClick(e) {
switch (e.detail.btn.id) {
case 'refresh-wifi-list':
this.wifiForm.triggerWifiListRefresh();
break;
case 'complete':
this.onClickComplete();
break;
}
},
onClickComplete() {
const targetWifi = this.wifiForm.getSelectedWifiInfo();
if (!targetWifi || !targetWifi.SSID) {
wx.showModal({
title: '请先选择WiFi',
confirmText: '我知道了',
showCancel: false,
});
return;
}
if (this.data.autoConnect) {
console.info("targetWifi:", targetWifi)
connectWifi(targetWifi)
.then(() => {
app.wifiConfLogger.error('connectWifiSuccess', targetWifi);
this.triggerEvent('complete', { wifi: targetWifi }, {});
})
.catch((err) => {
app.wifiConfLogger.error('connectWifiFail', err);
});
} else {
this.triggerEvent('complete', { wifi: targetWifi }, {});
}
},
},
});

7
demo/miniprogram/pages/add-device/components/input-wifi-info/input-wifi-info.json

@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"btn-group": "/components/btn-group/btn-group",
"wifi-form": "/components/wifi-form/wifi-form"
}
}

9
demo/miniprogram/pages/add-device/components/input-wifi-info/input-wifi-info.wxml

@ -0,0 +1,9 @@
<view class="page-subtitle">{{title}}</view>
<view class="page-main">
<wifi-form id="wifi-form" auto-select-current-wifi="{{true}}" />
</view>
<btn-group
buttons="{{[{ btnText: '刷新 WiFi 列表', id: 'refresh-wifi-list' }, { btnText: '下一步', type: 'primary', id: 'complete'}]}}"
bind:click="onBottomButtonClick"
fixed-bottom="{{true}}"
/>

10
demo/miniprogram/pages/add-device/components/input-wifi-info/input-wifi-info.wxss

@ -0,0 +1,10 @@
@import '../../common.wxss';
:host {
width: 100%;
}
.page-main {
margin-left: -50rpx;
margin-right: -50rpx;
}

16
demo/miniprogram/pages/add-device/components/success-view/success-view.js

@ -0,0 +1,16 @@
const showWifiConfTypeMenu = require('../../wifiConfTypeMenu');
Component({
methods: {
onBottomButtonClick(e) {
switch (e.detail.btn.id) {
case 'restart':
showWifiConfTypeMenu(true);
break;
case 'complete':
this.triggerEvent('complete', {}, {});
break;
}
},
},
});

6
demo/miniprogram/pages/add-device/components/success-view/success-view.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"btn-group": "/components/btn-group/btn-group"
}
}

9
demo/miniprogram/pages/add-device/components/success-view/success-view.wxml

@ -0,0 +1,9 @@
<view class="result-view">
<icon type="success" size="108rpx" class="result-icon" />
<view class="result-title">配网完成,设备添加成功</view>
</view>
<btn-group
buttons="{{[{ btnText: '继续添加其他设备', id: 'restart' }, { btnText: '完成', type: 'primary', id: 'complete'}]}}"
bind:click="onBottomButtonClick"
fixed-bottom="{{true}}"
/>

43
demo/miniprogram/pages/add-device/components/success-view/success-view.wxss

@ -0,0 +1,43 @@
@import '../../common.wxss';
:host {
width: 100%;
}
.result-view {
text-align: center;
}
.result-view .result-icon {
margin-top: 210rpx;
height: 108rpx;
}
.result-view .result-title {
font-size: 34rpx;
color: #000000;
margin-top: 10Px;
line-height: 1.4;
}
.result-view .result-subtitle {
font-size: 28rpx;
color: #888;
margin-top: 8Px;
line-height: 1.4;
}
.result-view .result-info-container {
margin: 20px auto 0;
font-size: 28rpx;
color: #888;
line-height: 1.7;
max-width: 590rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.result-view .result-info-list {
text-align: left;
}

248
demo/miniprogram/pages/add-device/components/wifi-conf/wifi-conf.js

@ -0,0 +1,248 @@
const app = getApp();
const actions = require('../../../../redux/actions');
const { requestBindDeviceToken } = require('../../common');
const { Logger } = require('../../logger');
const { constants: WifiConfConstants } = require('qcloud-iotexplorer-appdev-plugin-wificonf-core');
const { WifiConfStepCode } = WifiConfConstants;
const Steps = {
Guide: 0,
InputTargetWiFi: 1,
ConnectBlueTooth: 2,
InputDeviceWiFi: 2,
DoConfig: 3,
Success: -1,
Fail: -2,
};
let deviceAdapter = null;
Component({
properties: {
title: {
type: String,
},
pluginName: {
type: String,
},
errorTips: {
type: Array,
},
needDeviceAp: {
type: Boolean,
value: false,
},
},
data: {
step: Steps.Guide,
isReady: false,
curConnStep: 0,
},
attached() {
this.logger = new Logger();
app.wifiConfLogger = this.logger;
wx.startWifi();
console.log("wx.startWifi")
requestBindDeviceToken().then((token) => {
if (token) {
this.bindDeviceToken = token;
this.logger.info('bindDeviceToken', token);
this.setData({ isReady: true });
}
});
},
detached() {
app.wifiConfLogger = null;
wx.stopWifi();
actions.resetWifiList();
},
methods: {
onGuideComplete() {
console.log("onGuideComplete")
this.setData({ step: Steps.InputTargetWiFi });
},
onTargetWifiInputComplete(e) {
console.log("onTargetWifiInputComplete", e)
this.targetWifi = e.detail.wifi;
if (this.data.needDeviceAp) {
// SoftAP配网:需要先连接到设备热点
this.setData({ step: Steps.InputDeviceWiFi });
} else if (this.data.pluginName === 'wifiConfBleCombo') {
// Ble-Combo配网 需要先连接蓝牙
this.connectDevice();
} else {
// 其他配网方式:已连接到目标WiFi,开始配网
this.startConfig();
}
},
onDeviceWifiInputComplete() {
// SoftAP配网:已连接到设备热点,开始配网
this.startConfig();
// SoftAP配网:未连接到设备热点,开始配网
// this.startConfig({
// softAPInfo: {
// SSID: '----',
// password: '----'
// }
// });
},
onBack() {
wx.navigateBack();
},
onConfigProgress(data) {
console.log(this.data.pluginName, 'progress', data);
this.logger.info(`${this.data.pluginName}:progress`, data);
switch (data.code) {
case WifiConfStepCode.PROTOCOL_SUCCESS:
this.setData({ curConnStep: 1 });
break;
case WifiConfStepCode.CREATE_UDP_CONNECTION_SUCCESS:
this.setData({ curConnStep: 2 });
break;
case WifiConfStepCode.BUSINESS_QUERY_TOKEN_STATE_SUCCESS:
this.setData({ curConnStep: 3 });
break;
case WifiConfStepCode.WIFI_CONF_SUCCESS:
this.setData({ curConnStep: 4 });
break;
}
},
onBluetoothConnected(e) {
console.log('deviceAdapter:', e.detail);
deviceAdapter = e.detail;
},
onNextStep() {
this.startConfig();
},
// SoftAP 配网的 StepCode 与其他配网方式的不同
onSoftApConfigProgress(data) {
console.log(this.data.pluginName, 'progress', data);
this.logger.info(`${this.data.pluginName}:progress`, data);
switch (data.code) {
case WifiConfStepCode.CREATE_UDP_CONNECTION_SUCCESS:
this.setData({ curConnStep: 1 });
break;
case WifiConfStepCode.SOFTAP_SEND_TARGET_WIFIINFO_SUCCESS:
this.setData({ curConnStep: 2 });
break;
case WifiConfStepCode.SOFTAP_GET_DEVICE_SIGNATURE_SUCCESS:
case WifiConfStepCode.BUSINESS_QUERY_TOKEN_STATE_SUCCESS:
this.setData({ curConnStep: 3 });
break;
case WifiConfStepCode.WIFI_CONF_SUCCESS:
this.setData({ curConnStep: 4 });
break;
}
},
// ble-combo 事件处理函数
onBleComboProgress({ code, detail }) {
console.log(code, detail);
switch (code) {
case WifiConfStepCode.PROTOCOL_START:
this.setData({ curConnStep: 1 });
break;
case WifiConfStepCode.PROTOCOL_SUCCESS:
this.setData({ curConnStep: 2 });
break;
case WifiConfStepCode.BLE_SEND_TOKEN_SUCCESS: {
const { productId, deviceName } = detail.response;
console.log({ productId, deviceName });
this.setData({ curConnStep: 3 });
break;
}
case WifiConfStepCode.WIFI_CONF_SUCCESS:
this.setData({ curConnStep: 4 });
break;
}
},
onConfigError(error) {
console.error(this.data.pluginName, 'error', error);
this.logger.error(`${this.data.pluginName}:error`, error);
const { code, detail } = error;
let { msg } = error;
if (!msg && detail && detail.error && detail.error.uiMsg) {
msg = detail.error.uiMsg;
}
wx.showModal({
title: '配网失败',
content: `${msg} (${code})`,
showCancel: false,
confirmText: '我知道了',
success: () => {
this.setData({
step: Steps.Fail,
logs: this.logger.toString(),
});
},
});
},
onConfigComplete(data) {
console.log(this.data.pluginName, 'complete', data.productId, data.deviceName);
this.logger.error(`${this.data.pluginName}:complete`, data);
this.setData({ step: Steps.Success });
// 刷新设备列表
actions.getDevicesData();
},
connectDevice() {
// 切换到蓝牙连接页面
this.setData({
step: Steps.ConnectBlueTooth,
});
},
startConfig(options = {}) {
// 切换到开始配网页面
this.setData({
step: Steps.DoConfig,
});
const progresshandlerMap = {
wifiConfSoftAp: this.onSoftApConfigProgress.bind(this),
wifiConfBleCombo: this.onBleComboProgress.bind(this),
}
// 调用SDK开始配网
const params = {
deviceAdapter, // ble-combo进行配网时需要
wifiConfToken: this.bindDeviceToken,
targetWifiInfo: this.targetWifi,
wifiConfType: 'llsyncble', // ble or llsyncble
autoRetry: true, // 自动处理故障流程
familyId: 'default',
roomId: '0',
onProgress: progresshandlerMap[this.data.pluginName] || this.onConfigProgress.bind(this),
onError: this.onConfigError.bind(this),
onComplete: this.onConfigComplete.bind(this),
...options,
};
app.sdk.plugins[this.data.pluginName].start(params);
},
},
});

13
demo/miniprogram/pages/add-device/components/wifi-conf/wifi-conf.json

@ -0,0 +1,13 @@
{
"component": true,
"usingComponents": {
"page-wrapper": "/components/page-wrapper/page-wrapper",
"mp-loading": "/miniprogram_npm/weui-miniprogram/loading/loading",
"guide": "../guide/guide",
"input-wifi-info": "../input-wifi-info/input-wifi-info",
"bluetooth-finder": "../bluetooth-finder/bluetooth-finder",
"do-config": "../do-config/do-config",
"success-view": "../success-view/success-view",
"error-view": "../error-view/error-view"
}
}

46
demo/miniprogram/pages/add-device/components/wifi-conf/wifi-conf.wxml

@ -0,0 +1,46 @@
<page-wrapper>
<view class="container" wx:if="{{isReady}}">
<view class="page-title" wx:if="{{step !== -1 && step !== -2}}">
{{title}}
</view>
<block wx:if="{{step === 0}}">
<guide bind:complete="onGuideComplete" />
</block>
<block wx:elif="{{step === 1}}">
<input-wifi-info
title="{{needDeviceAp ? '选择目标WiFi' : '连接目标WiFi'}}"
bind:complete="onTargetWifiInputComplete"
autoConnect="{{!needDeviceAp}}"
/>
</block>
<block wx:elif="{{step === 2}}">
<bluetooth-finder
wx:if="{{pluginName === 'wifiConfBleCombo'}}"
adapterType="BLE_COMBO_LLSYNC"
bind:connected="onBluetoothConnected"
bind:nextStep="onNextStep"
/>
<input-wifi-info
wx:else
title="连接设备WiFi"
bind:complete="onDeviceWifiInputComplete"
/>
</block>
<block wx:elif="{{step === 3}}">
<do-config curStep="{{curConnStep}}" />
</block>
<block wx:elif="{{step === -1}}">
<success-view bind:complete="onBack" />
</block>
<block wx:elif="{{step === -2}}">
<error-view bind:complete="onBack" errorTips="{{errorTips}}" logs="{{logs}}"/>
</block>
</view>
<mp-loading type="dot-gray" wx:else></mp-loading>
</page-wrapper>

1
demo/miniprogram/pages/add-device/components/wifi-conf/wifi-conf.wxss

@ -0,0 +1 @@
@import '../../common.wxss';

6
demo/miniprogram/pages/add-device/constants.js

@ -0,0 +1,6 @@
module.exports.connectDeviceSteps = [
'手机与设备连接成功',
'向设备发送信息成功',
'设备连接云端成功',
'初始化成功',
];

47
demo/miniprogram/pages/add-device/llsync/llsync.js

@ -0,0 +1,47 @@
// pages/add-device/llsync.js
import wxPromisify from '../../../libs/wx-promisify';
import bluetoothAdapter from '../components/bluetooth-finder/blueToothAdapter';
let deviceAdapter = null;
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
},
onBluetoothConnected(e) {
console.log('deviceAdapter:', e.detail);
deviceAdapter = e.detail;
},
async onNextStep(data) {
console.log('开始绑定', data);
try {
wx.showLoading({
title:'正在绑定..'
})
const deviceId = await deviceAdapter.bindDevice({familyId: 'default'});
console.log(deviceId);
wx.showModal({
title: '绑定成功',
content: '请到首页查看设备',
success(res) {
if (res.confirm) {
wx.switchTab({
url: '/pages/index/index'
})
}
}
});
} catch (err) {
console.error('绑定失败', err);
}
},
})

5
demo/miniprogram/pages/add-device/llsync/llsync.json

@ -0,0 +1,5 @@
{
"usingComponents": {
"bluetooth-finder": "../components/bluetooth-finder/bluetooth-finder"
}
}

6
demo/miniprogram/pages/add-device/llsync/llsync.wxml

@ -0,0 +1,6 @@
<!--pages/add-device/llsync.wxml-->
<bluetooth-finder
adapterType="LLSync"
bind:connected="onBluetoothConnected"
bind:nextStep="onNextStep"
/>

1
demo/miniprogram/pages/add-device/llsync/llsync.wxss

@ -0,0 +1 @@
/* pages/add-device/llsync.wxss */

45
demo/miniprogram/pages/add-device/logger.js

@ -0,0 +1,45 @@
const app = getApp();
const dataToString = (data) => {
if (typeof data === 'string') {
return data;
}
if (typeof data === 'object' && data instanceof Error) {
return data.stack;
}
return JSON.stringify(data, null, 2);
};
const formatLog = (type, tag, data) => `[${(new Date()).toISOString()}] [${type}]\n[${tag}] ${dataToString(data)}`;
class Logger {
constructor() {
this.logs = [];
const { sdk } = app;
const systemInfo = wx.getSystemInfoSync();
this.info('Brand', systemInfo.brand);
this.info('Model', systemInfo.model);
this.info('System', systemInfo.system);
this.info('Platform', systemInfo.platform);
this.info('WXVer', systemInfo.version);
this.info('WXSdkVer', systemInfo.SDKVersion);
this.info('AppKey', sdk.loginManager.appKey);
this.info('UserID', (sdk.loginManager.userInfo || {}).UserID);
}
info(tag, data) {
this.logs.push(formatLog('INFO', tag, data));
}
error(tag, data) {
this.logs.push(formatLog('ERROR', tag, data));
}
toString() {
return this.logs.join('\n');
}
}
module.exports = {
Logger,
};

91
demo/miniprogram/pages/add-device/qrCode.js

@ -0,0 +1,91 @@
const { secureAddDeviceInFamily } = require('../../models');
const { getErrorMsg, parseUrl } = require('../../libs/utillib');
const addDevice = async ({
Signature,
}) => {
wx.showLoading({
title: '绑定设备中…',
mask: true,
});
try {
await secureAddDeviceInFamily({
DeviceSignature: Signature,
RoomId: '',
FamilyId: 'default',
});
wx.showModal({
title: '绑定设备成功',
confirmText: '确定',
showCancel: false,
success: () => {
wx.reLaunch({
url: '/pages/index/index',
});
},
});
} catch (err) {
console.error('addDevice2Family fail', err);
wx.showModal({
title: '绑定设备失败',
content: getErrorMsg(err),
confirmText: '我知道了',
showCancel: false,
});
} finally {
wx.hideLoading();
}
};
const onInvalidQrCode = () => {
wx.showModal({
title: '绑定设备失败',
content: '扫描的二维码不是有效的绑定设备二维码,请前往物联网开发平台获得绑定设备二维码',
showCancel: false,
confirmText: '我知道了',
});
};
module.exports = () => {
wx.scanCode({
scanType: 'qrCode',
success: (res) => {
const qrCodeContent = res.result;
try {
let signature = null;
if (qrCodeContent.startsWith('{')) {
// JSON: 设备配网二维码
const data = JSON.parse(qrCodeContent);
if (!data.Signature) {
throw { msg: '缺少必要的设备信息字段' };
}
signature = data.Signature;
} else if (qrCodeContent.startsWith('http')) {
// URL: 虚拟设备调试
const uri = parseUrl(qrCodeContent);
if (uri
&& uri.origin === 'https://iot.cloud.tencent.com'
&& uri.pathname === '/iotexplorer/device'
&& uri.query
&& uri.query.page === 'virtual'
&& uri.query.signature
) {
signature = uri.query.signature;
} else {
throw { msg: '未知URL' };
}
} else {
throw { msg: '无法识别的二维码内容' };
}
addDevice({
Signature: signature,
});
} catch (err) {
console.error('parse qrcode fail', err, qrCodeContent);
onInvalidQrCode();
}
},
});
};

9
demo/miniprogram/pages/add-device/simple-config/simple-config.js

@ -0,0 +1,9 @@
Page({
data: {
errorTips: [
'确认设备处于SimpleConfig配网模式',
'核对家庭WiFi密码是否正确',
'确认路由设备是否为2.4G WiFi频段',
],
},
});

6
demo/miniprogram/pages/add-device/simple-config/simple-config.json

@ -0,0 +1,6 @@
{
"usingComponents": {
"wifi-conf": "../components/wifi-conf/wifi-conf"
},
"navigationBarTitleText": ""
}

5
demo/miniprogram/pages/add-device/simple-config/simple-config.wxml

@ -0,0 +1,5 @@
<wifi-conf
title="SimpleConfig 配网"
pluginName="wifiConfSimpleConfig"
errorTips="{{errorTips}}"
/>

0
demo/miniprogram/pages/add-device/simple-config/simple-config.wxss

9
demo/miniprogram/pages/add-device/smart-config/smart-config.js

@ -0,0 +1,9 @@
Page({
data: {
errorTips: [
'确认设备处于SmartConfig配网模式',
'核对家庭WiFi密码是否正确',
'确认路由设备是否为2.4G WiFi频段',
],
},
});

6
demo/miniprogram/pages/add-device/smart-config/smart-config.json

@ -0,0 +1,6 @@
{
"usingComponents": {
"wifi-conf": "../components/wifi-conf/wifi-conf"
},
"navigationBarTitleText": ""
}

5
demo/miniprogram/pages/add-device/smart-config/smart-config.wxml

@ -0,0 +1,5 @@
<wifi-conf
title="SmartConfig 配网"
pluginName="wifiConfSmartConfig"
errorTips="{{errorTips}}"
/>

0
demo/miniprogram/pages/add-device/smart-config/smart-config.wxss

10
demo/miniprogram/pages/add-device/soft-ap/soft-ap.js

@ -0,0 +1,10 @@
Page({
data: {
errorTips: [
'确认设备处于SoftAP配网模式',
'核对是否成功连接到设备热点',
'核对家庭WiFi密码是否正确',
'确认路由设备是否为2.4G WiFi频段',
],
},
});

6
demo/miniprogram/pages/add-device/soft-ap/soft-ap.json

@ -0,0 +1,6 @@
{
"usingComponents": {
"wifi-conf": "../components/wifi-conf/wifi-conf"
},
"navigationBarTitleText": ""
}

6
demo/miniprogram/pages/add-device/soft-ap/soft-ap.wxml

@ -0,0 +1,6 @@
<wifi-conf
title="SoftAP 配网"
pluginName="wifiConfSoftAp"
errorTips="{{errorTips}}"
needDeviceAp="{{true}}"
/>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save