diff --git a/App.vue b/App.vue index c67ca08..15ddf32 100644 --- a/App.vue +++ b/App.vue @@ -1,16 +1,41 @@ - - diff --git a/pages/home/home.vue b/pages/home/home.vue new file mode 100644 index 0000000..d96a461 --- /dev/null +++ b/pages/home/home.vue @@ -0,0 +1,317 @@ + + + + + + + diff --git a/pages/index/index.vue b/pages/index/index.vue index 320385f..c4ed272 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -1,68 +1,205 @@ - diff --git a/pages/lockDetail/lockDetail.vue b/pages/lockDetail/lockDetail.vue new file mode 100644 index 0000000..608c2be --- /dev/null +++ b/pages/lockDetail/lockDetail.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/pages/mine/mine.vue b/pages/mine/mine.vue new file mode 100644 index 0000000..05c5914 --- /dev/null +++ b/pages/mine/mine.vue @@ -0,0 +1,167 @@ + + + + + + + + diff --git a/pages/safeQuestion/safeQuestion.vue b/pages/safeQuestion/safeQuestion.vue new file mode 100644 index 0000000..7b59ca3 --- /dev/null +++ b/pages/safeQuestion/safeQuestion.vue @@ -0,0 +1,137 @@ + + + + + + + diff --git a/pages/searchDevice/searchDevice.vue b/pages/searchDevice/searchDevice.vue new file mode 100644 index 0000000..608c2be --- /dev/null +++ b/pages/searchDevice/searchDevice.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/pages/updateEmail/updateEmail.vue b/pages/updateEmail/updateEmail.vue new file mode 100644 index 0000000..cc45226 --- /dev/null +++ b/pages/updateEmail/updateEmail.vue @@ -0,0 +1,209 @@ + + + + + + + diff --git a/pages/updateName/updateName.vue b/pages/updateName/updateName.vue new file mode 100644 index 0000000..97f7538 --- /dev/null +++ b/pages/updateName/updateName.vue @@ -0,0 +1,110 @@ + + + + + + + diff --git a/pages/updatePassword/updatePassword.vue b/pages/updatePassword/updatePassword.vue new file mode 100644 index 0000000..11462b3 --- /dev/null +++ b/pages/updatePassword/updatePassword.vue @@ -0,0 +1,143 @@ + + + + + + + diff --git a/pages/updateSafeQuestion/updateSafeQuestion.vue b/pages/updateSafeQuestion/updateSafeQuestion.vue new file mode 100644 index 0000000..53d323f --- /dev/null +++ b/pages/updateSafeQuestion/updateSafeQuestion.vue @@ -0,0 +1,252 @@ + + + + + + + diff --git a/pages/userInfo/userInfo.vue b/pages/userInfo/userInfo.vue new file mode 100644 index 0000000..6ada060 --- /dev/null +++ b/pages/userInfo/userInfo.vue @@ -0,0 +1,251 @@ + + + + + + + + diff --git a/pages/verifyEmail/verifyEmail.vue b/pages/verifyEmail/verifyEmail.vue new file mode 100644 index 0000000..d041c40 --- /dev/null +++ b/pages/verifyEmail/verifyEmail.vue @@ -0,0 +1,182 @@ + + + + + + + diff --git a/pages/webview/webview.vue b/pages/webview/webview.vue new file mode 100644 index 0000000..29ca69c --- /dev/null +++ b/pages/webview/webview.vue @@ -0,0 +1,40 @@ + + + diff --git a/static/images/background_mine.png b/static/images/background_mine.png new file mode 100644 index 0000000..41ee0d6 Binary files /dev/null and b/static/images/background_mine.png differ diff --git a/static/images/icon_add.png b/static/images/icon_add.png new file mode 100644 index 0000000..d0f9a4d Binary files /dev/null and b/static/images/icon_add.png differ diff --git a/static/images/icon_arrow.png b/static/images/icon_arrow.png new file mode 100644 index 0000000..a0a91ec Binary files /dev/null and b/static/images/icon_arrow.png differ diff --git a/static/images/icon_lock.png b/static/images/icon_lock.png new file mode 100644 index 0000000..7997525 Binary files /dev/null and b/static/images/icon_lock.png differ diff --git a/static/images/icon_power.png b/static/images/icon_power.png new file mode 100755 index 0000000..ceeeb8d Binary files /dev/null and b/static/images/icon_power.png differ diff --git a/static/images/tabbar_key_no_select.png b/static/images/tabbar_key_no_select.png new file mode 100644 index 0000000..ea6aa06 Binary files /dev/null and b/static/images/tabbar_key_no_select.png differ diff --git a/static/images/tabbar_key_select.png b/static/images/tabbar_key_select.png new file mode 100644 index 0000000..2adb1a3 Binary files /dev/null and b/static/images/tabbar_key_select.png differ diff --git a/static/images/tabbar_mine_no_select.png b/static/images/tabbar_mine_no_select.png new file mode 100644 index 0000000..5cd3e24 Binary files /dev/null and b/static/images/tabbar_mine_no_select.png differ diff --git a/static/images/tabbar_mine_select.png b/static/images/tabbar_mine_select.png new file mode 100644 index 0000000..4864337 Binary files /dev/null and b/static/images/tabbar_mine_select.png differ diff --git a/static/logo.png b/static/logo.png deleted file mode 100644 index b5771e2..0000000 Binary files a/static/logo.png and /dev/null differ diff --git a/stores/basic.js b/stores/basic.js new file mode 100644 index 0000000..43e57e0 --- /dev/null +++ b/stores/basic.js @@ -0,0 +1,124 @@ +import { defineStore } from 'pinia' + +// 页面配置 +const pages = [ + { + name: 'home', + path: '/pages/home/home', + tabBar: true + }, + { + name: 'mine', + path: '/pages/mine/mine', + tabBar: true + }, + { + name: 'userInfo', + path: '/pages/userInfo/userInfo', + tabBar: false + }, + { + name: 'updateName', + path: '/pages/updateName/updateName', + tabBar: false + }, + { + name: 'updatePassword', + path: '/pages/updatePassword/updatePassword', + tabBar: false + }, + { + name: 'updateEmail', + path: '/pages/updateEmail/updateEmail', + tabBar: false + }, + { + name: 'verifyEmail', + path: '/pages/verifyEmail/verifyEmail', + tabBar: false + }, + { + name: 'safeQuestion', + path: '/pages/safeQuestion/safeQuestion', + tabBar: false + }, + { + name: 'updateSafeQuestion', + path: '/pages/updateSafeQuestion/updateSafeQuestion', + tabBar: false + }, + { + name: 'webview', + path: '/pages/webview/webview', + tabBar: false + }, + { + name: 'lockDetail', + path: '/pages/lockDetail/lockDetail', + tabBar: false + }, + { + name: 'searchDevice', + path: '/pages/searchDevice/searchDevice', + tabBar: false + } +] + + +export const useBasicStore = defineStore('basic', { + state() { + return { + // 设备信息 + deviceInfo: null, + // 胶囊按钮的位置信息 + buttonInfo: null + } + }, + actions: { + // 路由跳转 + /* data 入参 name string页面名称 type string跳转方式 params object传递参数 delta number返回页面数 + * 具体入参查看文档 https://www.uviewui.com/js/route.html */ + routeJump(data) { + const page = pages.find((page) => { + return page.name === data.name + }) + if (page) { + uni.$u.route({ + url: page.path, + ...data + }) + } + }, + // 获取设备信息 + getDeviceInfo() { + const that = this + return new Promise((resolve) => { + if (that.deviceInfo?.model) { + resolve(that.deviceInfo) + return + } + uni.getSystemInfo({ + success: function (res) { + that.deviceInfo = res + resolve(that.deviceInfo) + }, + fail: function () { + resolve({}) + } + }) + }) + }, + // 获取胶囊信息 + getButtonInfo() { + return new Promise((resolve, reject) => { + if (this.buttonInfo?.top) { + resolve(this.buttonInfo) + return + } + this.buttonInfo = uni.getMenuButtonBoundingClientRect() + resolve(this.buttonInfo) + return + }) + }, + } +}) diff --git a/stores/bluetooth.js b/stores/bluetooth.js new file mode 100644 index 0000000..541e2a7 --- /dev/null +++ b/stores/bluetooth.js @@ -0,0 +1,1024 @@ +/** + * @description 蓝牙数据持久化 + */ +import { defineStore } from 'pinia' +import crc from 'crc' +import { sm4 } from 'sm-crypto' +import { md5 } from 'js-md5' + +// 定时器 +let timer +// 特性值回调 +let characteristicValueCallback = null + +// 命令ID +const cmdIds = { + // 获取公钥 + getPublicKey: 0x3090, + // 获取私钥 + getCommKey: 0x3091, + // 获取锁状态 + getLockStatus: 0x3040, + // 新增用户 + addUser: 0x3001, + // 开门 + openDoor: 0x3005, + // 重置设备 + resetDevice: 0x3004, + // 清理用户 + cleanUser: 0x300C, + // 扩展命令 + expandCmd: 0x3030 +} + +// 子命令ID +const subCmdIds = { + // 设置开锁密码 + setLockPassword: 3, + // 重置开锁密码 + resetLockPassword: 19 +} + +export const useBluetoothStore = defineStore('ble', { + state() { + return { + /* + * 蓝牙状态 + * -1 未知(初始状态) + * 0 正常 + * 1 系统蓝牙未打开 + * 2 小程序蓝牙功能被禁用 + * 3 系统蓝牙未打开且小程序蓝牙功能被禁用 + */ + bluetoothStatus: -1, + // 设备列表 + deviceList: [], + // 当前锁信息 + currentLockInfo: {}, + // 消息序号 + messageCount: 1, + // 是否初始化蓝牙 + isInitBluetooth: false + } + }, + actions: { + // 初始化并监听 + async initAndListenBluetooth() { + // 初始化蓝牙 + const initResult = await this.initBluetooth() + if (initResult) { + // 更新蓝牙初始化状态 + this.updateInitBluetooth(true) + // 监听蓝牙开关状态 + this.onBluetoothState() + // 监听蓝牙连接状态 + this.onBluetoothConnectStatus() + // 监听设备特征值变化 + this.onBluetoothCharacteristicValueChange() + + return true + } else { + return false + } + }, + // 更新是否初始化蓝牙字段 + updateInitBluetooth(value) { + this.isInitBluetooth = value + }, + // 初始化蓝牙模块 + initBluetooth() { + const that = this + // 初始化蓝牙模块 + return new Promise(resolve => { + uni.openBluetoothAdapter({ + success() { + that.bluetoothStatus = 0 + console.log('蓝牙初始化成功') + resolve(true) + }, + fail(err) { + console.log('蓝牙初始化失败', err) + // 系统蓝牙未打开 + if(err.errCode === 10001) { + if(that.bluetoothStatus === 2) { + that.bluetoothStatus = 3 + } else { + that.bluetoothStatus = 1 + } + } + // 小程序蓝牙功能被禁用 + if(err.errno === 103) { + if(that.bluetoothStatus === 1) { + that.bluetoothStatus = 3 + } else { + that.bluetoothStatus = 2 + } + } + // 蓝牙已经初始化 + if(err.errMsg === 'openBluetoothAdapter:fail already opened') { + resolve(true) + return + } + resolve(false) + } + }) + }) + }, + // 监听蓝牙状态变化 + onBluetoothState() { + const that = this + uni.onBluetoothAdapterStateChange((res) => { + console.log('蓝牙状态改变', res) + if(that.bluetoothStatus === 3 && res.available) { + that.bluetoothStatus = 2 + } else if (that.bluetoothStatus === 2 && !res.available) { + that.bluetoothStatus = 3 + } else if (that.bluetoothStatus === 1 && res.available) { + that.bluetoothStatus = 0 + } else if (that.bluetoothStatus === 0 && !res.available){ + that.bluetoothStatus = 1 + } else if (that.bluetoothStatus === -1) { + if(res.available) { + that.bluetoothStatus = 0 + } else { + that.bluetoothStatus = 1 + } + } + }) + }, + // 监听蓝牙设备连接状态 + onBluetoothConnectStatus() { + const that = this + uni.onBLEConnectionStateChange(function (res) { + if(res.deviceId === that.currentLockInfo.deviceId) { + console.log('设备连接状态改变', res) + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + connected: res.connected + }) + } + }) + }, + // 解析特征值 + parsingCharacteristicValue(binaryData) { + const that = this + // 0x20 明文 0x22 SM4(事先约定密钥) 0x23 SM4(设备指定密钥) + if(binaryData[7] === 0x20) { + if(binaryData[12] * 256 + binaryData[13] === cmdIds.getPublicKey) { + if(binaryData[14] === 0) { + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + publicKey: binaryData.slice(15, 31) + }) + } + characteristicValueCallback({ + code: binaryData[14] + }) + } + } else if(binaryData[7] === 0x22) { + // 截取入参 + const cebBinaryData = binaryData.slice(12, binaryData.length - 2) + // 解密 + const key = new Uint8Array(16) + for (let i = 0; i < that.currentLockInfo.name.length; i++) { + key[i] = that.currentLockInfo.name.charCodeAt(i) + } + const decrypted = sm4.decrypt(cebBinaryData, key, { mode: 'ecb', output: 'array' }) + console.log('ecb解密后的数据', decrypted) + + if(decrypted[0] * 256 + decrypted[1] === cmdIds.getCommKey) { + if(decrypted[2] === 0) { + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + commKey: decrypted.slice(3, 19), + signKey: decrypted.slice(19, 35) + }) + console.log('commKey', Array.from(that.currentLockInfo.commKey)) + console.log('signKey', Array.from(that.currentLockInfo.signKey)) + } + characteristicValueCallback({ + code: decrypted[2] + }) + } + } else { + const cebBinaryData = binaryData.slice(12, binaryData.length - 2) + const decrypted = sm4.decrypt(cebBinaryData, that.currentLockInfo.commKey, { mode: 'ecb', output: 'array' }) + console.log('ecb解密后的数据', decrypted) + + const cmdId = decrypted[0] * 256 + decrypted[1] + + switch (cmdId) { + case cmdIds.getLockStatus: + if (decrypted[2] === 0) { + const lockConfig = { + vendor: decrypted.slice(3, 23), + product: decrypted[23], + model: decrypted.slice(24, 44), + fwVersion: decrypted.slice(44, 64), + hwVersion: decrypted.slice(64, 84), + serialNum0: decrypted.slice(84, 100), + serialNum1: decrypted.slice(100, 116), + btDeviceName: decrypted.slice(116, 132), + battRemCap: decrypted[132], + battRemCapStandby: decrypted[133], + restoreCounter: decrypted.slice(134, 136), + restoreDate: decrypted.slice(136, 140), + icPartNo: decrypted.slice(140, 150), + inDate: decrypted.slice(150, 154), + mac: decrypted.slice(154, 174), + featurevalueLength: decrypted[174], + featureValue: decrypted.slice(175, 175 + decrypted[174]), + featureEnValLength: decrypted[175 + decrypted[174]], + featureEnVal: decrypted.slice(176 + decrypted[174], 176 + decrypted[174] + decrypted[175 + decrypted[174]]), + } + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + lockConfig + }) + console.log('获取锁状态成功', that.currentLockInfo.lockConfig) + } + characteristicValueCallback({ + code: decrypted[2] + }) + break + case cmdIds.addUser: + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + token: decrypted.slice(42,46) + }) + characteristicValueCallback({ + code: decrypted[46] + }) + break + case cmdIds.expandCmd: + const subCmdId = decrypted[3] + switch (subCmdId) { + case subCmdIds.resetLockPassword: + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + token: decrypted.slice(5,9) + }) + characteristicValueCallback({ + code: decrypted[2] + }) + if(decrypted[2] === 0) { + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + encrpyKey: decrypted.slice(9, 17) + }) + } + break + case subCmdIds.setLockPassword: + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + token: decrypted.slice(5,9) + }) + characteristicValueCallback({ + code: decrypted[2], + data: { + status: decrypted[11] + } + }) + break + } + break + default: + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + token: decrypted.slice(2,6) + }) + characteristicValueCallback({ + code: decrypted[6] + }) + break + } + } + }, + // 监听蓝牙设备特征值改变 + onBluetoothCharacteristicValueChange() { + const that = this + // 完整数据 + let completeArray + // 完整内容数据长度 + let length + console.log('开始监听特征值改变') + uni.onBLECharacteristicValueChange(function (res) { + if(res.deviceId === that.currentLockInfo.deviceId) { + let binaryData = new Uint8Array(res.value) + if(binaryData[0] === 0xEF && binaryData[1] === 0x01 && binaryData[2] === 0xEE && binaryData[3] === 0x02) { + length = binaryData[8] * 256 + binaryData[9] + if(length + 14 > binaryData.length) { + completeArray = binaryData + } else { + that.parsingCharacteristicValue(binaryData) + } + } else { + if(completeArray) { + const combinedArray = new Uint8Array(completeArray.length + binaryData.length) + combinedArray.set(completeArray, 0); + combinedArray.set(binaryData, completeArray.length) + completeArray = combinedArray + if(length + 14 === completeArray.length) { + that.parsingCharacteristicValue(completeArray) + completeArray = null + } + } + } + } + }) + }, + // 开始搜索蓝牙设备 + getBluetoothDevices() { + const that = this + if(this.bluetoothStatus !== 0) { + console.log('搜索未执行', this.bluetoothStatus) + this.getBluetoothStatus() + return + } + uni.startBluetoothDevicesDiscovery({ + success: function (res) { + setTimeout(() => { + that.searchBluetoothDevices() + }, 300) + }, + fail: async function (res) { + console.log('开始搜索失败', res) + if(res.errCode === 10000) { + // 重新初始化蓝牙适配器 + await that.initBluetooth() + that.getBluetoothDevices() + } + } + }) + }, + // 定时查询蓝牙设备 + searchBluetoothDevices() { + const that = this + if(this.bluetoothStatus !== 0) { + console.log('搜索未执行', this.bluetoothStatus) + this.getBluetoothStatus() + return + } + timer = setInterval(() => { + uni.getBluetoothDevices({ + success(res) { + const deviceList = res.devices + that.deviceList = [] + for(let i = 0; i < deviceList.length; i++) { + if(deviceList[i]?.advertisServiceUUIDs) { + const uuid = deviceList[i]?.advertisServiceUUIDs[0] + if(uuid && uuid.slice(2,8)==='758824' && uuid.slice(30,32)==='00') { + that.deviceList.push(deviceList[i]) + } + } + } + console.log('设备列表', that.deviceList) + }, + fail: async function (res) { + console.log('获取设备列表失败', res) + if(res.errCode === 10000) { + // 重新初始化蓝牙适配器 + await that.initBluetooth() + } + } + }) + }, 1000) + }, + // 停止搜索蓝牙设备 + stopGetBluetoothDevices() { + clearInterval(timer) + uni.stopBluetoothDevicesDiscovery() + }, + // 蓝牙状态提示 + getBluetoothStatus() { + if(this.bluetoothStatus === 1) { + uni.showModal({ + title: '提示', + content: '蓝牙尚未打开,请先打开蓝牙', + showCancel: false, + confirmText: '确定', + }) + } else if(this.bluetoothStatus === 2 || this.bluetoothStatus === 3) { + uni.showModal({ + title: '提示', + content: '小程序蓝牙功能被禁用,请打开小程序蓝牙权限', + showCancel: false, + confirmText: '去设置', + success(res) { + if(res.confirm) { + uni.openSetting() + } + } + }) + } + }, + // 检查小程序设置 + checkSetting() { + const that = this + return new Promise((resolve) => { + uni.getSetting({ + async success(res) { + const bluetooth = res.authSetting['scope.bluetooth'] + if(that.bluetoothStatus === -1) { + if(bluetooth !== false) { + that.bluetoothStatus = 0 + } else { + that.bluetoothStatus = 2 + } + } else if(that.bluetoothStatus === 0 && bluetooth === false) { + that.bluetoothStatus = 2 + } else if(that.bluetoothStatus === 1 && bluetooth === false) { + that.bluetoothStatus = 3 + } else if(that.bluetoothStatus === 2 && bluetooth !== false) { + that.bluetoothStatus = 0 + await that.initBluetooth() + } else if(that.bluetoothStatus === 3 && bluetooth !== false) { + that.bluetoothStatus = 1 + await that.initBluetooth() + that.onBluetoothState() + } + console.log('蓝牙权限', bluetooth, that.bluetoothStatus) + resolve(bluetooth) + }, + fail() { + resolve(false) + } + }) + }) + }, + // 连接蓝牙设备+获取设备服务+获取设备特征值 + connectBluetoothDevice() { + const that = this + return new Promise((resolve) => { + if(that.bluetoothStatus !== 0) { + console.log('连接未执行', that.bluetoothStatus) + that.getBluetoothStatus() + resolve(false) + return + } + uni.createBLEConnection({ + deviceId: that.currentLockInfo.deviceId, + success(res) { + console.log('连接成功', res) + // 获取设备服务 + uni.getBLEDeviceServices({ + deviceId: that.currentLockInfo.deviceId, + success (res) { + let serviceId + for(let i = 0; i < res.services.length; i++) { + if(res.services[i].isPrimary) { + serviceId = res.services[i].uuid + } + } + // 获取设备对应服务的特征值 + uni.getBLEDeviceCharacteristics({ + deviceId: that.currentLockInfo.deviceId, + serviceId: serviceId, + success (res) { + let notifyCharacteristicId + let writeCharacteristicId + for(let i = 0; i < res.characteristics.length; i++) { + const characteristic = res.characteristics[i] + if(characteristic.properties.notify) { + notifyCharacteristicId = characteristic.uuid + } + if(characteristic.properties.write) { + writeCharacteristicId = characteristic.uuid + } + } + that.updateCurrentLockInfo({ + ...that.currentLockInfo, + serviceId, + notifyCharacteristicId, + writeCharacteristicId + }) + that.notifyBluetoothCharacteristicValueChange() + resolve(true) + }, + fail(res) { + if(res.errCode === 10006) { + uni.showToast({ + title: '连接失败,请靠近设备并保持设备处于唤醒状态', + icon: 'none' + }) + } + console.log('获取设备特征值失败', res) + resolve(false) + } + }) + }, + fail(res) { + if(res.errCode === 10006) { + uni.showToast({ + title: '连接失败,请靠近设备并保持设备处于唤醒状态', + icon: 'none' + }) + } + console.log('获取设备服务失败', res) + resolve(false) + } + }) + }, + fail(res) { + uni.showToast({ + title: '连接失败,请靠近设备并保持设备处于唤醒状态', + icon: 'none' + }) + console.log('连接失败', res) + resolve(false) + } + }) + }) + }, + // 更新当前锁信息 + updateCurrentLockInfo(lockInfo) { + console.log('更新当前锁信息', lockInfo) + this.currentLockInfo = lockInfo + }, + // 订阅设备特征值改变 + notifyBluetoothCharacteristicValueChange() { + const that = this + uni.notifyBLECharacteristicValueChange({ + state: true, + deviceId: that.currentLockInfo.deviceId, + serviceId: that.currentLockInfo.serviceId, + characteristicId: that.currentLockInfo.notifyCharacteristicId, + success(res) { + console.log('订阅成功', res) + }, + fail(res) { + console.log('订阅失败', res) + } + }) + }, + // 断开蓝牙连接 + closeBluetoothConnection() { + const that = this + uni.closeBLEConnection({ + deviceId: that.currentLockInfo.deviceId, + success(res) { + console.log('断开连接成功', res) + }, + fail(res) { + console.log('断开连接失败', res) + } + }) + }, + // 写入特征值 + async writeBLECharacteristicValue(binaryData) { + const that = this + + // 确认蓝牙状态正常 + if(this.bluetoothStatus !== 0) { + console.log('写入未执行', this.bluetoothStatus) + this.getBluetoothStatus() + return + } + + // 确认设备连接正常 + if(!that.currentLockInfo.connected) { + const result = await that.connectBluetoothDevice() + if(!result) return + } + + console.log('设备ID:', that.currentLockInfo.deviceId) + console.log('设备名称:', that.currentLockInfo.name) + console.log('设备主服务:', that.currentLockInfo.serviceId) + console.log('设备写入特征值:', that.currentLockInfo.writeCharacteristicId) + console.log('设备写入数据:', Array.from(binaryData)) + + // 次数 + const count = Math.ceil(binaryData.length / 20) + for(let i = 0; i < count; i++) { + const writeData = binaryData.slice(i * 20, i === (count - 1) ? binaryData.length : (i + 1) * 20) + + uni.writeBLECharacteristicValue({ + deviceId: that.currentLockInfo.deviceId, + serviceId: that.currentLockInfo.serviceId, + characteristicId: that.currentLockInfo.writeCharacteristicId, + value: writeData.buffer, + fail(res) { + console.log('写入失败', res) + } + }) + } + }, + /* + * 生成包头 + * encryptionType 加密类型 0:明文,1:AES128,2:SM4(事先约定密钥),3:SM4(设备指定密钥) + * originalLength 原始数据长度 + * */ + createPackageHeader(encryptionType, originalLength) { + // 头部数据 + let headArray = new Uint8Array(12) + + // 固定包头 + headArray[0] = 0xEF + headArray[1] = 0x01 + headArray[2] = 0xEE + headArray[3] = 0x02 + + // 包类型 发送 + headArray[4] = 0x01 + + // 包序号 + headArray[5] = this.messageCount / 256 + headArray[6] = this.messageCount % 256 + this.messageCount++ + + // 包标识 + if(encryptionType === 0) { + headArray[7] = 0x20 + } else if(encryptionType === 2) { + headArray[7] = 0x22 + } else { + headArray[7] = 0x23 + } + + // 数据长度 + if(encryptionType === 0) { + headArray[8] = originalLength / 256 + headArray[9] = originalLength % 256 + } else { + const length = Math.ceil(originalLength / 16) * 16 + headArray[8] = length / 256 + headArray[9] = length % 256 + } + headArray[10] = originalLength / 256 + headArray[11] = originalLength % 256 + + return headArray + }, + // 生成包尾 头部数据+内容数据 + createPackageEnd(headArray,conentArray) { + // 拼接头部和内容 + let mergerArray = new Uint8Array(headArray.length + conentArray.length) + mergerArray.set(headArray) + mergerArray.set(conentArray, headArray.length) + + // crc加密 + const crcResult = crc.crc16kermit(mergerArray) + + // 拼接crc + let newArray = new Uint8Array(mergerArray.length + 2) + newArray.set(mergerArray) + newArray.set([crcResult / 256, crcResult % 256], mergerArray.length) + + return newArray + }, + // 获取公钥 + async getPublicKey(name) { + const headArray = this.createPackageHeader(0, 42) + const conentArray = new Uint8Array(42) + + conentArray[0] = cmdIds.getPublicKey / 256 + conentArray[1] = cmdIds.getPublicKey % 256 + + for(let i = 0; i < name.length; i++) { + conentArray[i + 2] = name.charCodeAt(i) + } + + const packageArray = this.createPackageEnd(headArray, conentArray) + + await this.writeBLECharacteristicValue(packageArray) + return this.getWriteResult() + }, + // 获取私钥 + async getCommKey(name, keyId, authUid, nowTime) { + const length = 2 + 40 + 40 + 20 + 4 + 1 + 16 + const headArray = this.createPackageHeader(2, length) + const conentArray = new Uint8Array(length) + + conentArray[0] = cmdIds.getCommKey / 256 + conentArray[1] = cmdIds.getCommKey % 256 + + for (let i = 0; i < name.length; i++) { + conentArray[i + 2] = name.charCodeAt(i) + } + + for(let i = 0; i < keyId.length; i++) { + conentArray[i + 42] = keyId.charCodeAt(i) + } + + for(let i = 0; i < authUid.length; i++) { + conentArray[i + 82] = authUid.charCodeAt(i) + } + + conentArray.set(this.timestampToArray(nowTime), 102) + + conentArray[106] = 16 + + const md5Array = this.md5Encrypte(authUid + keyId, conentArray.slice(102, 106), this.currentLockInfo.publicKey) + + conentArray.set(md5Array, 107) + + const cebArray = sm4.encrypt(conentArray, conentArray.slice(2, 18), { mode: 'ecb', output: 'array' }) + + const packageArray = this.createPackageEnd(headArray, cebArray) + + await this.writeBLECharacteristicValue(packageArray) + return this.getWriteResult() + }, + // md5加密 + md5Encrypte(text, token, key) { + const length = text.length + 4 + 16 + const md5Array = new Uint8Array(length) + + for (let i = 0; i < text.length; i++) { + md5Array[i] = text.charCodeAt(i) + } + + md5Array.set(token , text.length) + md5Array.set(key, text.length + 4) + + const md5Text= md5(md5Array) + return new Uint8Array(md5Text.match(/.{1,2}/g).map(byte => parseInt(byte, 16))) + }, + // 获取锁状态 + async getLockStatus(data) { + const { name, uid, nowTime, localTime } = data + const length = 2 + 40 + 20 + 4 + 4 + const headArray = this.createPackageHeader(3, length) + + const conentArray = new Uint8Array(length) + conentArray[0] = cmdIds.getLockStatus / 256 + conentArray[1] = cmdIds.getLockStatus % 256 + for (let i = 0; i < name.length; i++) { + conentArray[i + 2] = name.charCodeAt(i) + } + for (let i = 0; i < uid.length; i++) { + conentArray[i + 42] = uid.charCodeAt(i) + } + conentArray.set(this.timestampToArray(nowTime), 62) + conentArray.set(this.timestampToArray(localTime), 66) + + const cebArray = sm4.encrypt(conentArray, this.currentLockInfo.commKey, { mode: 'ecb', output: 'array' }) + + const packageArray = this.createPackageEnd(headArray, cebArray) + + await this.writeBLECharacteristicValue(packageArray) + + return this.getWriteResult(this.getLockStatus, data) + }, + // 时间戳转二进制 + timestampToArray(timestamp) { + const array = new Uint8Array(4) + array[0] = (timestamp & 0xff000000) >> 24 + array[1] = (timestamp & 0xff0000) >> 16 + array[2] = (timestamp & 0xff00) >> 8 + array[3] = (timestamp & 0xff) + return array + }, + // 添加用户 + async addLockUser(data) { + const { name, authUid, uid, keyId, openMode, keyType, startDate, expireDate, useCountLimit, isRound, weekRound, + startHour, startMin, endHour, endMin, role, password, publicKey, commKey } = data + const length = 2 + 40 + 20 + 40 + 20 + 1 + 1 + 4 + 4 + 2 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 1 + 16 + const headArray = this.createPackageHeader(3, length) + const conentArray = new Uint8Array(length) + + conentArray[0] = cmdIds.addUser / 256 + conentArray[1] = cmdIds.addUser % 256 + + for(let i = 0; i < name.length; i++) { + conentArray[i + 2] = name.charCodeAt(i) + } + + for(let i = 0; i < authUid.length; i++) { + conentArray[i + 42] = authUid.charCodeAt(i) + } + + for(let i = 0; i < keyId.length; i++) { + conentArray[i + 62] = keyId.charCodeAt(i) + } + + for(let i = 0; i < uid.length; i++) { + conentArray[i + 102] = uid.charCodeAt(i) + } + + conentArray[122] = openMode + conentArray[123] = keyType + + conentArray.set(this.timestampToArray(startDate), 124) + conentArray.set(this.timestampToArray(expireDate), 128) + + conentArray[132] = useCountLimit / 256 + conentArray[133] = useCountLimit % 256 + + conentArray[134] = isRound + conentArray[135] = weekRound + conentArray[136] = startHour + conentArray[137] = startMin + conentArray[138] = endHour + conentArray[139] = endMin + conentArray[140] = role + + for(let i = 0; i < password.length; i++) { + conentArray[i + 141] = password.charCodeAt(i) + } + + conentArray.set(this.currentLockInfo.token || this.timestampToArray(startDate), 161) + + conentArray[165] = 16 + + const md5Array = this.md5Encrypte(authUid + keyId, this.currentLockInfo.token || this.timestampToArray(startDate), this.currentLockInfo.publicKey) + + conentArray.set(md5Array, 166) + + const cebArray = sm4.encrypt(conentArray, commKey, { mode: 'ecb', output: 'array' }) + + const packageArray = this.createPackageEnd(headArray, cebArray) + await this.writeBLECharacteristicValue(packageArray) + return this.getWriteResult(this.addLockUser, data) + }, + // 获取写入结果 + getWriteResult(request, params) { + return new Promise(resolve => { + const timer = setTimeout(() => { + resolve({ code: -1 }) + }, 10000) + characteristicValueCallback = async (data) => { + // code 6 token过期,重新获取 + if(data.code === 6) { + resolve(await request(params)) + } else { + clearTimeout(timer) + resolve(data) + } + } + }) + }, + // 开门 + async openDoor(data) { + const { name, uid, openMode, openTime, onlineToken } = data + const length = 2 + 40 + 20 + 1 + 4 + 4 + 1 + 16 + 16 + const headArray = this.createPackageHeader(3, length) + + const conentArray = new Uint8Array(length) + conentArray[0] = cmdIds.openDoor / 256 + conentArray[1] = cmdIds.openDoor % 256 + + for(let i = 0; i < name.length; i++) { + conentArray[i + 2] = name.charCodeAt(i) + } + + for (let i = 0; i < uid.length; i++) { + conentArray[i + 42] = uid.charCodeAt(i) + } + + conentArray[62] = openMode + + conentArray.set(this.timestampToArray(openTime), 63) + + conentArray.set(this.currentLockInfo.token, 67) + + conentArray[71] = 16 + + const md5Array = this.md5Encrypte(name + uid, this.currentLockInfo.token, this.currentLockInfo.signKey) + + conentArray.set(md5Array, 72) + + for (let i = 0; i < onlineToken.length; i++) { + conentArray[i + 88] = onlineToken.charCodeAt(i) + } + + const cebArray = sm4.encrypt(conentArray, this.currentLockInfo.commKey, { mode: 'ecb', output: 'array' }) + + const packageArray = this.createPackageEnd(headArray, cebArray) + + await this.writeBLECharacteristicValue(packageArray) + + return this.getWriteResult(this.openDoor, data) + }, + // 恢复出厂设置 + async resetDevice(data) { + const { name, authUid, keyId } = data + const length = 2 + 40 + 20 + 4 + 1 + 16 + const headArray = this.createPackageHeader(3, length) + const conentArray = new Uint8Array(length) + + conentArray[0] = cmdIds.resetDevice / 256 + conentArray[1] = cmdIds.resetDevice % 256 + + for(let i = 0; i < name.length; i++) { + conentArray[i + 2] = name.charCodeAt(i) + } + + for(let i = 0; i < authUid.length; i++) { + conentArray[i + 42] = authUid.charCodeAt(i) + } + conentArray.set(this.currentLockInfo.token, 62) + conentArray[66] = 16 + + const md5Array = this.md5Encrypte(name, this.currentLockInfo.token, this.currentLockInfo.publicKey) + conentArray.set(md5Array, 67) + + const cebArray = sm4.encrypt(conentArray, this.currentLockInfo.commKey, { mode: 'ecb', output: 'array' }) + + const packageArray = this.createPackageEnd(headArray, cebArray) + + await this.writeBLECharacteristicValue(packageArray) + + return this.getWriteResult(this.resetDevice, data) + }, + // 重置开锁密码 + async resetLockPassword(data) { + const { keyId, uid } = data + const length = 2 + 1 + 1 + 40 + 20 + 4 + 1 + 16 + const headArray = this.createPackageHeader(3, length) + const conentArray = new Uint8Array(length) + + conentArray[0] = cmdIds.expandCmd / 256 + conentArray[1] = cmdIds.expandCmd % 256 + + // 子命令 + conentArray[2] = subCmdIds.resetLockPassword + + conentArray[3] = length - 4 + + for (let i = 0; i < keyId.length; i++) { + conentArray[i + 4] = keyId.charCodeAt(i) + } + + for (let i = 0; i < uid.length; i++) { + conentArray[i + 44] = uid.charCodeAt(i) + } + + conentArray.set(this.currentLockInfo.token, 64) + + conentArray[68] = 16 + + const md5Array = this.md5Encrypte(keyId + uid, this.currentLockInfo.token, this.currentLockInfo.signKey) + + conentArray.set(md5Array, 69) + + const cebArray = sm4.encrypt(conentArray, this.currentLockInfo.commKey, { mode: 'ecb', output: 'array' }) + + const packageArray = this.createPackageEnd(headArray, cebArray) + + await this.writeBLECharacteristicValue(packageArray) + + return this.getWriteResult(this.resetLockPassword, data) + }, + // 清理用户 + async cleanUser(data) { + const { name, authUid, keyId, uid, userNoLength, userNoList } = data + // const length = 2 + 40 + 20 + 40 + 20 + 2 + userNoLength * 4 + 1 + 16 + }, + // 设置密码 + async setLockPassword(data) { + const { keyId, uid, pwdNo, operate, isAdmin, pwd, userCountLimit, startTime, endTime} = data + const length = 2 + 1 + 1 + 40 + 20 + 2 + 1 + 1 + 20 + 2 + 4 + 4 + 4 + 1 + 16 + const headArray = this.createPackageHeader(3, length) + const conentArray = new Uint8Array(length) + + conentArray[0] = cmdIds.expandCmd / 256 + conentArray[1] = cmdIds.expandCmd % 256 + + // 子命令 + conentArray[2] = subCmdIds.setLockPassword + + conentArray[3] = length - 3 + + for (let i = 0; i < keyId.length; i++) { + conentArray[i + 4] = keyId.charCodeAt(i) + } + + for (let i = 0; i < uid.length; i++) { + conentArray[i + 44] = uid.charCodeAt(i) + } + + conentArray[64] = pwdNo / 256 + conentArray[65] = pwdNo % 256 + + conentArray[66] = operate + conentArray[67] = isAdmin + + for (let i = 0; i < pwd.length; i++) { + conentArray[i + 68] = pwd.charCodeAt(i) + } + + conentArray[88] = userCountLimit / 256 + conentArray[89] = userCountLimit % 256 + + conentArray.set(this.currentLockInfo.token, 90) + + conentArray.set(this.timestampToArray(startTime), 94) + conentArray.set(this.timestampToArray(endTime), 98) + + conentArray[102] = 16 + + const md5Array = this.md5Encrypte(keyId + uid, this.currentLockInfo.token, this.currentLockInfo.signKey) + + conentArray.set(md5Array, 103) + + const cebArray = sm4.encrypt(conentArray, this.currentLockInfo.commKey, { mode: 'ecb', output: 'array' }) + + const packageArray = this.createPackageEnd(headArray, cebArray) + + await this.writeBLECharacteristicValue(packageArray) + + return this.getWriteResult(this.setLockPassword, data) + } + } +}) diff --git a/stores/lock.js b/stores/lock.js new file mode 100644 index 0000000..3a67f19 --- /dev/null +++ b/stores/lock.js @@ -0,0 +1,54 @@ +/** + * @description 锁信息数据持久化 + */ +import { defineStore } from 'pinia' +import { getLockListRequest } from '@/api/lock' + +export const useLockStore = defineStore('lock', { + state() { + return { + // 锁列表 + lockList: [], + // 锁总数 + lockTotal: 0 + } + }, + actions: { + getRole(userType) { + if(userType === 110301) { + return '超级管理员' + } else { + return '普通用户' + } + }, + getTimeLimit(keyType) { + if(keyType === 1) { + return '永久' + } else if(keyType === 2) { + return '限时' + } else if(keyType === 3) { + return '单次' + } else { + return '循环' + } + }, + async getLockList(params) { + const { code, data, message } = await getLockListRequest(params) + if(code === 0) { + this.lockTotal = data.total + if(params.pageNo === 1) { + this.lockList = data.groupList + } else { + this.lockList = this.lockList.concat(data.groupList) + } + return { code } + } else { + uni.showToast({ + title: message, + icon: 'none' + }) + return { code, message } + } + } + } +}) diff --git a/stores/user.js b/stores/user.js index a431d0f..5aa4988 100644 --- a/stores/user.js +++ b/stores/user.js @@ -2,16 +2,37 @@ * @description 用户信息数据持久化 */ import { defineStore } from 'pinia' +import { getUserInfoRequest } from '@/api/user' +import { useLockStore } from '@/stores/lock' export const useUserStore = defineStore('user', { state() { return { - userInfo: {} + // 用户信息 + userInfo: {}, + // 登录状态 + isLogin: false } }, actions: { updateUserInfo(data) { this.userInfo = data + }, + updateLoginStatus(status) { + this.isLogin = status + }, + async login() { + uni.setStorageSync('token', '3021|MZv7iEf0NwjCPSGx4QWs37zOjeVN3GrSJ2v7D56L7db1fcc5') + const { code, data } = await getUserInfoRequest() + await useLockStore().getLockList({ + pageNo: 1, + pageSize: 50 + }) + if(code === 0) { + this.updateUserInfo(data) + } + this.isLogin = true + return this.isLogin } } }) diff --git a/uni.scss b/uni.scss index c712a1a..642511a 100644 --- a/uni.scss +++ b/uni.scss @@ -32,7 +32,7 @@ $uni-text-color-disable:#c0c0c0; /* 背景颜色 */ $uni-bg-color:#ffffff; -$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-grey:#f3f3f3; $uni-bg-color-hover:#f1f1f1;//点击状态颜色 $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 diff --git a/utils/request.js b/utils/request.js index 6248774..f7567ae 100644 --- a/utils/request.js +++ b/utils/request.js @@ -12,9 +12,10 @@ import baseConfig from '@/config/env' const request = (config) => { return new Promise((resolve) => { + const token = config?.token ? config.token : uni.getStorageSync('token') const headerDefault = { version: baseConfig.version, - token: config?.token ? config.token : uni.getStorageSync('token') + authorization: `Bearer ${token}` } const URL = config.baseUrl ? config.baseUrl + config.url : baseConfig.baseUrl + config.url const method = config.method || 'POST' @@ -30,8 +31,29 @@ const request = (config) => { async success(res) { const { statusCode, data } = res if (statusCode === 200) { - // 根据情况添加处理代码 - resolve(data) + const code = data.errorCode + const message = data.errorMsg + if(code === 403) { + uni.removeStorageSync('token') + getApp().globalData.updateIsLogin(false) + resolve({ code: 403, data, message: '登录已过期' }) + uni.showModal({ + title: '提示', + content: '登录已过期,请重新登录', + showCancel: false, + success() { + uni.reLaunch({ + url: '/pages/home/home' + }) + } + }) + } else { + resolve({ + code, + data: data.data, + message + }) + } } else { resolve({ code: -1, data, message: '网络不太好哦,请稍后再试' }) } @@ -49,10 +71,9 @@ const request = (config) => { console.log(URL.substring(baseConfig.baseUrl.length + 1), { url: URL.substring(baseConfig.baseUrl.length + 1), req: config?.data || {}, - code: res?.data?.code, + code: res?.data?.errorCode, res: res?.data?.data, - requestId: res?.header ? res?.header['X-Ca-Request-Id'] : '', - token: header?.token || '', + token: header?.authorization || '', duration: new Date().getTime() - timestamp }) }