1. 完成锁详情页UI及开关锁功能
@ -19,3 +19,12 @@ export function bindLockAdmin(data) {
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取手机联网token
|
||||
export function getLockNetTokenRequest(data) {
|
||||
return request({
|
||||
url: '/lock/getLockNetToken',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
95
components/SwitchLoading/SwitchLoading.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="spinner-box" :style="{width: size + 'rpx', height: size + 'rpx'}">
|
||||
<view v-if="show" class="circle-border" :style="{width: size * 0.75 + 'rpx', height: size * 0.75 + 'rpx'}">
|
||||
</view>
|
||||
<view v-else class="circle-border-stop" :style="{width: size * 0.75 + 'rpx', height: size * 0.75 + 'rpx'}"></view>
|
||||
<view class="circle-core" :style="{width: size * 0.75 - 2 + 'rpx', height: size * 0.75 - 2 + 'rpx'}">
|
||||
<image src="/static/images/icon_lock_transparent.png" mode="aspectFill" :style="{width: size * 0.35 + 'rpx',
|
||||
height: size * 0.35 + 'rpx'}"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SwitchLoading",
|
||||
props:{
|
||||
size: Number,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open () {
|
||||
this.show = true
|
||||
},
|
||||
close () {
|
||||
this.show = false
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
.spinner-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.circle-border {
|
||||
padding: 3upx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
background: rgb(99, 184, 175);
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(99, 184, 175, 0.1) 33%,
|
||||
rgba(99, 184, 175, 1) 100%
|
||||
);
|
||||
animation: spin 0.8s linear 0s infinite;
|
||||
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.circle-border-stop {
|
||||
padding: 3upx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
background: rgb(99, 184, 175);
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.circle-core {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@ -64,7 +64,6 @@
|
||||
import { useBluetoothStore } from '@/stores/bluetooth'
|
||||
import { useBasicStore } from '@/stores/basic'
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import { getServerDatetime } from '@/api/check'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@ -106,7 +105,8 @@
|
||||
timeFormat,
|
||||
...mapActions(useUserStore, ['updateUserInfo', 'updateLoginStatus', 'login']),
|
||||
...mapActions(useLockStore, ['getLockList', 'getRole', 'getTimeLimit']),
|
||||
...mapActions(useBluetoothStore, ['getBluetoothStatus', 'initAndListenBluetooth', 'updateCurrentLockInfo', 'checkSetting']),
|
||||
...mapActions(useBluetoothStore, ['getBluetoothStatus', 'initAndListenBluetooth', 'updateCurrentLockInfo',
|
||||
'checkSetting', 'updateKeyId']),
|
||||
...mapActions(useBasicStore, ['routeJump', 'getDeviceInfo']),
|
||||
async nextPage() {
|
||||
if(this.lockList.length < this.lockTotal) {
|
||||
@ -172,7 +172,16 @@
|
||||
result = await this.initAndListenBluetooth()
|
||||
}
|
||||
if(result) {
|
||||
this.updateCurrentLockInfo(lock)
|
||||
const data = {
|
||||
...lock,
|
||||
name: lock.bluetooth.bluetoothDeviceName,
|
||||
deviceId: lock.bluetooth.bluetoothDeviceId,
|
||||
commKey: lock.bluetooth.privateKey,
|
||||
signKey: lock.bluetooth.signKey,
|
||||
publicKey: lock.bluetooth.publicKey,
|
||||
}
|
||||
this.updateKeyId(lock.keyId)
|
||||
this.updateCurrentLockInfo(data)
|
||||
this.routeJump({
|
||||
name: 'lockDetail'
|
||||
})
|
||||
|
||||
@ -1,17 +1,327 @@
|
||||
<template>
|
||||
<view>
|
||||
|
||||
<view class="top">
|
||||
<image class="top-background" src="/static/images/background_main.jpg" mode="aspectFill"></image>
|
||||
<view style="width: 100%;height: 50rpx">
|
||||
<view class="power" @click="powerTip">
|
||||
<image class="power-icon" src="/static/images/icon_power.png" mode="aspectFill"></image>
|
||||
<view class="power-text">{{ currentLockInfo.electricQuantity }}%</view>
|
||||
<image class="power-tips" src="/static/images/icon_tips.png" mode="aspectFill"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="switch" @click="openDoorOperate('open')" @longpress="openDoorOperate('close')">
|
||||
<SwitchLoading :size="220" ref="loading"></SwitchLoading>
|
||||
</view>
|
||||
<view class="switch-text">点击开锁,长按闭锁</view>
|
||||
<view class="bottom">
|
||||
<view class="bottom-side">
|
||||
<image class="bottom-icon" src="/static/images/icon_role.png" mode="aspectFill"></image>
|
||||
<view>{{ getRole(currentLockInfo.userType) }}</view>
|
||||
</view>
|
||||
<view class="bottom-side">
|
||||
<image class="bottom-icon" :src=" currentLockInfo.lockSetting.appUnlockOnline ?
|
||||
'/static/images/icon_cloud_active.png' : '/static/images/icon_cloud.png' "
|
||||
mode="aspectFill" style="width: 40rpx;height: 40rpx;"></image>
|
||||
<view :style="{color: currentLockInfo.lockSetting.appUnlockOnline ? '#63b8af' : '#a3a3a3'}">手机需联网</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="menu">
|
||||
<view class="menu-title">
|
||||
<image class="menu-image" src="/static/images/icon_menu.png"></image>
|
||||
<view>功能</view>
|
||||
</view>
|
||||
<view class="menu-main">
|
||||
<view v-if="currentLockInfo.lockFeature.bluetoothRemoteControl || currentLockInfo.userType === 110301"
|
||||
class="menu-main-view">
|
||||
<image class="menu-main-image" src="/static/images/tabbar_key_select.png"></image>
|
||||
<view>电子钥匙</view>
|
||||
</view>
|
||||
<view v-if="currentLockInfo.lockFeature.password || currentLockInfo.userType === 110301" class="menu-main-view">
|
||||
<image class="menu-main-image" src="/static/images/icon_lock_transparent.png"></image>
|
||||
<view>密码</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting">
|
||||
<image class="setting-image" src="/static/images/icon_setting.png"></image>
|
||||
<view class="setting-text">设置</view>
|
||||
<image class="setting-arrow" mode="aspectFill" src="/static/images/icon_arrow.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useBluetoothStore } from '@/stores/bluetooth'
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import SwitchLoading from '@/components/SwitchLoading/SwitchLoading.vue'
|
||||
import { useLockStore } from '@/stores/lock'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { getLockNetTokenRequest } from '@/api/lock'
|
||||
import { timeFormat } from 'uview-plus'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {}
|
||||
}
|
||||
return {
|
||||
time: 0,
|
||||
onlineToken: '0',
|
||||
pending: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useBluetoothStore, ['currentLockInfo']),
|
||||
...mapState(useUserStore, ['userInfo'])
|
||||
},
|
||||
components: {
|
||||
SwitchLoading
|
||||
},
|
||||
onLoad() {
|
||||
uni.setNavigationBarTitle({
|
||||
title: this.currentLockInfo.lockAlias
|
||||
})
|
||||
this.getServeTime()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useLockStore, ['getRole']),
|
||||
...mapActions(useBluetoothStore, ['openDoor', 'updateServerTimestamp']),
|
||||
powerTip() {
|
||||
const that = this
|
||||
uni.showModal({
|
||||
title: '锁电量更新时间',
|
||||
content: `${timeFormat(that.currentLockInfo.electricQuantityDate, 'yyyy-mm-dd h:M')}`,
|
||||
showCancel: false
|
||||
})
|
||||
},
|
||||
async getNetToken(){
|
||||
const { code, data } = await getLockNetTokenRequest({
|
||||
lockId: this.currentLockInfo.keyId
|
||||
})
|
||||
if(code === 0) {
|
||||
this.onlineToken = data.token
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
async getServeTime() {
|
||||
const { code, data } = await this.updateServerTimestamp()
|
||||
if(code === 0) {
|
||||
this.time = parseInt((data.date - new Date().getTime()) / 1000)
|
||||
}
|
||||
},
|
||||
async openDoorOperate(type) {
|
||||
if(this.pending) {
|
||||
return
|
||||
}
|
||||
uni.vibrateLong()
|
||||
this.pending = true
|
||||
this.$refs.loading.open()
|
||||
if(this.currentLockInfo.lockSetting.appUnlockOnline) {
|
||||
const result = await this.getNetToken()
|
||||
if(!result) {
|
||||
this.$refs.loading.close()
|
||||
this.pending = false
|
||||
uni.showToast({
|
||||
title: '联网开锁失败',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
let openMode
|
||||
if(type === 'close') {
|
||||
openMode = this.currentLockInfo.lockSetting.appUnlockOnline ? 33 : 32
|
||||
} else {
|
||||
openMode = this.currentLockInfo.lockSetting.appUnlockOnline ? 1 : 0
|
||||
}
|
||||
const { code } = await this.openDoor({
|
||||
name: this.currentLockInfo.name,
|
||||
uid: this.userInfo.uid.toString(),
|
||||
openMode: openMode,
|
||||
openTime: parseInt(new Date().getTime() / 1000) + this.time,
|
||||
onlineToken: this.onlineToken
|
||||
})
|
||||
if(code === 0) {
|
||||
uni.showToast({
|
||||
title: '开锁成功',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '开锁失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
this.$refs.loading.close()
|
||||
this.pending = false
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.top {
|
||||
margin-top: 32rpx;
|
||||
margin-left: 32rpx;
|
||||
width: 686rpx;
|
||||
height: 464rpx;
|
||||
border-radius: 32rpx;
|
||||
position: relative;
|
||||
|
||||
.top-background {
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
width: 686rpx;
|
||||
height: 464rpx;
|
||||
border-radius: 32rpx;
|
||||
}
|
||||
|
||||
.switch {
|
||||
margin-top: 20rpx;
|
||||
margin-left: 218rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 8rpx 36rpx 0 rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.power {
|
||||
float: right;
|
||||
padding-top: 18rpx;
|
||||
//width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 50rpx;
|
||||
justify-content: flex-end;
|
||||
|
||||
.power-icon {
|
||||
width: 50rpx;
|
||||
margin-right: 10rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
.power-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-right: 10rpx;
|
||||
line-height: 50rpx;
|
||||
}
|
||||
|
||||
.power-tips {
|
||||
margin-right: 32rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.switch-text {
|
||||
margin-top: 10rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
width: 686rpx;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
height: 48rpx;
|
||||
line-height: 48rpx;
|
||||
font-size: 32rpx;
|
||||
color: #63b8af;
|
||||
border-radius: 0 0 32rpx 32rpx;
|
||||
justify-content: space-around;
|
||||
|
||||
.bottom-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bottom-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
margin-top: 32rpx;
|
||||
margin-left: 32rpx;
|
||||
padding-bottom: 32rpx;
|
||||
width: 686rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 8rpx 36rpx 0 rgba(0,0,0,0.12);
|
||||
font-size: 40rpx;
|
||||
|
||||
.menu-title {
|
||||
padding: 24rpx 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-image {
|
||||
margin-right: 40rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.menu-main {
|
||||
padding-top: 32rpx;
|
||||
padding-bottom: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 32rpx;
|
||||
flex-wrap: wrap;
|
||||
text-align: center;
|
||||
margin-left: 43rpx;
|
||||
|
||||
.menu-main-view {
|
||||
width: 150rpx;
|
||||
|
||||
.menu-main-image {
|
||||
filter: sepia(100%) saturate(10000%) hue-rotate(180deg) brightness(0.1);
|
||||
margin-bottom: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting {
|
||||
padding: 24rpx 0;
|
||||
margin-top: 32rpx;
|
||||
margin-left: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 686rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 8rpx 36rpx 0 rgba(0,0,0,0.12);
|
||||
font-size: 40rpx;
|
||||
|
||||
.setting-text {
|
||||
margin-left: 32rpx;
|
||||
}
|
||||
|
||||
.setting-arrow {
|
||||
margin-right: 32rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.setting-image {
|
||||
margin-left: 32rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
static/images/background_main.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
static/images/icon_cloud.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
static/images/icon_cloud_active.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
static/images/icon_lock_transparent.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
static/images/icon_menu.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
static/images/icon_role.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
static/images/icon_setting.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
static/images/icon_tips.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
@ -67,6 +67,10 @@ export const useBluetoothStore = defineStore('ble', {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// 更新keyId
|
||||
updateKeyId(keyId) {
|
||||
this.keyId = keyId
|
||||
},
|
||||
// 二进制转字符串
|
||||
uint8ArrayToString(uint8Array) {
|
||||
let str = ''
|
||||
@ -900,11 +904,12 @@ export const useBluetoothStore = defineStore('ble', {
|
||||
|
||||
conentArray.set(this.timestampToArray(openTime), 63)
|
||||
|
||||
conentArray.set(this.currentLockInfo.token, 67)
|
||||
conentArray.set(this.currentLockInfo.token || this.timestampToArray(openTime), 67)
|
||||
|
||||
conentArray[71] = 16
|
||||
|
||||
const md5Array = this.md5Encrypte(name + uid, this.currentLockInfo.token, this.currentLockInfo.signKey)
|
||||
|
||||
const md5Array = this.md5Encrypte(name + uid, this.currentLockInfo.token || this.timestampToArray(openTime), this.currentLockInfo.signKey)
|
||||
|
||||
conentArray.set(md5Array, 72)
|
||||
|
||||
@ -915,7 +920,6 @@ export const useBluetoothStore = defineStore('ble', {
|
||||
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)
|
||||
|
||||
@ -22,7 +22,7 @@ export const useUserStore = defineStore('user', {
|
||||
this.isLogin = status
|
||||
},
|
||||
async login() {
|
||||
uni.setStorageSync('token', '3023|pZ1aoVlMKJBTBTGWlsZPpbLvxc8txcHbrJx2ljrf49c7efe0')
|
||||
uni.setStorageSync('token', '3028|6WkZCHj5yzLlXW3z3ylc1TDhtKYvF3jHB6e4eTWr22681e3e')
|
||||
const { code, data } = await getUserInfoRequest()
|
||||
await useLockStore().getLockList({
|
||||
pageNo: 1,
|
||||
|
||||