@ -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) |
|||
|
@ -0,0 +1,3 @@ |
|||
node_modules/ |
|||
/miniprogram/miniprogram_npm/ |
|||
/miniprogram/package-lock.json |
@ -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 |
@ -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; |
|||
} |
|||
}; |
@ -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==" |
|||
} |
|||
} |
|||
} |
@ -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" |
|||
} |
|||
} |
@ -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, |
|||
}, |
|||
}); |
@ -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需要授权使用您的位置信息" |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
} |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 655 B |
After Width: | Height: | Size: 582 B |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 4.0 KiB |
@ -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, |
|||
}, {}); |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,3 @@ |
|||
{ |
|||
"component": true |
|||
} |
@ -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,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, {}); |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"single-button": "/components/single-button/single-button" |
|||
} |
|||
} |
@ -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> |
@ -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; |
|||
} |
@ -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, {}); |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,7 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"btn-group": "/components/btn-group/btn-group", |
|||
"mp-loading": "/miniprogram_npm/weui-miniprogram/loading/loading" |
|||
} |
|||
} |
@ -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> |
@ -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; |
|||
} |
@ -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, {}); |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"btn-group": "/components/btn-group/btn-group" |
|||
} |
|||
} |
@ -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,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, |
|||
}; |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"ios-wifi-guide": "/components/ios-wifi-guide/ios-wifi-guide" |
|||
} |
|||
} |
@ -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> |
@ -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; |
|||
} |
@ -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'; |
@ -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 }); |
|||
|
|||
}))); |
@ -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); |
|||
} |
|||
}); |
|||
}); |
|||
}; |
@ -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; |
|||
}; |
@ -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' }); |
|||
} |
|||
}); |
@ -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, |
|||
}; |
@ -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" |
|||
} |
@ -0,0 +1,9 @@ |
|||
Page({ |
|||
data: { |
|||
errorTips: [ |
|||
'确认设备处于AirKiss配网模式', |
|||
'核对家庭WiFi密码是否正确', |
|||
'确认路由设备是否为2.4G WiFi频段', |
|||
], |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"wifi-conf": "../components/wifi-conf/wifi-conf" |
|||
}, |
|||
"navigationBarTitleText": "" |
|||
} |
@ -0,0 +1,5 @@ |
|||
<wifi-conf |
|||
title="AirKiss 配网" |
|||
pluginName="wifiConfAirKiss" |
|||
errorTips="{{errorTips}}" |
|||
/> |
@ -0,0 +1,10 @@ |
|||
Page({ |
|||
data: { |
|||
errorTips: [ |
|||
'确认设备处于BleCombo配网模式', |
|||
'核对家庭WiFi密码是否正确', |
|||
'确认路由设备是否为2.4G WiFi频段', |
|||
'确认设备三元组信息是否有效' |
|||
], |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"wifi-conf": "../components/wifi-conf/wifi-conf" |
|||
}, |
|||
"navigationBarTitleText": "" |
|||
} |
@ -0,0 +1,5 @@ |
|||
<wifi-conf |
|||
title="Ble-Combo 配网" |
|||
pluginName="wifiConfBleCombo" |
|||
errorTips="{{errorTips}}" |
|||
/> |
@ -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, |
|||
}; |
@ -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; |
|||
} |
@ -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: { |
|||
|
|||
} |
|||
}); |
@ -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 |
|||
}) |
|||
} |
|||
}); |
|||
} |
|||
}) |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"btn-group": "/components/btn-group/btn-group" |
|||
} |
|||
} |
@ -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> |
@ -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; |
|||
} |
@ -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; |
|||
} |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"btn-group": "/components/btn-group/btn-group" |
|||
} |
|||
} |
@ -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> |
@ -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); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"btn-group": "/components/btn-group/btn-group" |
|||
} |
|||
} |
@ -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> |
|||
|
|||
|
@ -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; |
|||
} |
@ -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; |
|||
} |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"btn-group": "/components/btn-group/btn-group" |
|||
} |
|||
} |
@ -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}}" |
|||
/> |
@ -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; |
|||
} |
@ -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 }, {}); |
|||
} |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,7 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"btn-group": "/components/btn-group/btn-group", |
|||
"wifi-form": "/components/wifi-form/wifi-form" |
|||
} |
|||
} |
@ -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}}" |
|||
/> |
@ -0,0 +1,10 @@ |
|||
@import '../../common.wxss'; |
|||
|
|||
:host { |
|||
width: 100%; |
|||
} |
|||
|
|||
.page-main { |
|||
margin-left: -50rpx; |
|||
margin-right: -50rpx; |
|||
} |
@ -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; |
|||
} |
|||
}, |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"btn-group": "/components/btn-group/btn-group" |
|||
} |
|||
} |
@ -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}}" |
|||
/> |
@ -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; |
|||
} |
@ -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); |
|||
}, |
|||
}, |
|||
}); |
@ -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" |
|||
} |
|||
} |
@ -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> |
@ -0,0 +1 @@ |
|||
@import '../../common.wxss'; |
@ -0,0 +1,6 @@ |
|||
module.exports.connectDeviceSteps = [ |
|||
'手机与设备连接成功', |
|||
'向设备发送信息成功', |
|||
'设备连接云端成功', |
|||
'初始化成功', |
|||
]; |
@ -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); |
|||
} |
|||
}, |
|||
}) |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"bluetooth-finder": "../components/bluetooth-finder/bluetooth-finder" |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
<!--pages/add-device/llsync.wxml--> |
|||
<bluetooth-finder |
|||
adapterType="LLSync" |
|||
bind:connected="onBluetoothConnected" |
|||
bind:nextStep="onNextStep" |
|||
/> |
@ -0,0 +1 @@ |
|||
/* pages/add-device/llsync.wxss */ |
@ -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, |
|||
}; |
@ -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(); |
|||
} |
|||
}, |
|||
}); |
|||
}; |
@ -0,0 +1,9 @@ |
|||
Page({ |
|||
data: { |
|||
errorTips: [ |
|||
'确认设备处于SimpleConfig配网模式', |
|||
'核对家庭WiFi密码是否正确', |
|||
'确认路由设备是否为2.4G WiFi频段', |
|||
], |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"wifi-conf": "../components/wifi-conf/wifi-conf" |
|||
}, |
|||
"navigationBarTitleText": "" |
|||
} |
@ -0,0 +1,5 @@ |
|||
<wifi-conf |
|||
title="SimpleConfig 配网" |
|||
pluginName="wifiConfSimpleConfig" |
|||
errorTips="{{errorTips}}" |
|||
/> |
@ -0,0 +1,9 @@ |
|||
Page({ |
|||
data: { |
|||
errorTips: [ |
|||
'确认设备处于SmartConfig配网模式', |
|||
'核对家庭WiFi密码是否正确', |
|||
'确认路由设备是否为2.4G WiFi频段', |
|||
], |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"wifi-conf": "../components/wifi-conf/wifi-conf" |
|||
}, |
|||
"navigationBarTitleText": "" |
|||
} |
@ -0,0 +1,5 @@ |
|||
<wifi-conf |
|||
title="SmartConfig 配网" |
|||
pluginName="wifiConfSmartConfig" |
|||
errorTips="{{errorTips}}" |
|||
/> |
@ -0,0 +1,10 @@ |
|||
Page({ |
|||
data: { |
|||
errorTips: [ |
|||
'确认设备处于SoftAP配网模式', |
|||
'核对是否成功连接到设备热点', |
|||
'核对家庭WiFi密码是否正确', |
|||
'确认路由设备是否为2.4G WiFi频段', |
|||
], |
|||
}, |
|||
}); |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"usingComponents": { |
|||
"wifi-conf": "../components/wifi-conf/wifi-conf" |
|||
}, |
|||
"navigationBarTitleText": "" |
|||
} |
@ -0,0 +1,6 @@ |
|||
<wifi-conf |
|||
title="SoftAP 配网" |
|||
pluginName="wifiConfSoftAp" |
|||
errorTips="{{errorTips}}" |
|||
needDeviceAp="{{true}}" |
|||
/> |