1. 完成锁密码相关功能

2. 完成锁电子钥匙相关功能
This commit is contained in:
范鹏 2024-08-28 16:55:11 +08:00
parent 2fc7ad7ed7
commit f9669c8d2c
21 changed files with 1488 additions and 22 deletions

View File

@ -10,7 +10,9 @@
//
updateIsLogin(isLogin) {
useUserStore().updateLoginStatus(isLogin)
}
},
//
appid: ''
},
computed: {
...mapState(useBluetoothStore, ['bluetoothStatus']),

39
api/key.js Normal file
View File

@ -0,0 +1,39 @@
import request from '../utils/request'
// key 电子钥匙模块
// 获取电子钥匙列表
export function getKeyListRequest(data) {
return request({
url: '/key/listUser',
method: 'POST',
data
})
}
// 重置电子钥匙
export function resetKeyRequest(data) {
return request({
url: '/key/reset',
method: 'POST',
data
})
}
// 创建电子钥匙
export function createKeyRequest(data) {
return request({
url: '/v2/key/send',
method: 'POST',
data
})
}
// 删除电子钥匙
export function deleteKeyRequest(data) {
return request({
url: '/key/delete',
method: 'POST',
data
})
}

39
api/keyboardPwd.js Normal file
View File

@ -0,0 +1,39 @@
import request from '../utils/request'
// keyboardPwd 锁密码模块
// 获取密码列表
export function getPsaawordListRequest(data) {
return request({
url: '/keyboardPwd/listSendRecords',
method: 'POST',
data
})
}
// 重置密码
export function resetPsaawordListRequest(data) {
return request({
url: '/keyboardPwd/reset',
method: 'POST',
data
})
}
// 创建密码
export function createPsaawordRequest(data) {
return request({
url: '/keyboardPwd/get',
method: 'POST',
data
})
}
// 删除密码
export function deletePsaawordRequest(data) {
return request({
url: '/keyboardPwd/delete',
method: 'POST',
data
})
}

View File

@ -0,0 +1,75 @@
<template>
<view>
<view @click="changeShow" class="name">
<view class="name-text">{{ title }}</view>
<view class="picker">
{{ timeFormat(time, 'yyyy-mm-dd h:M') }}
</view>
</view>
<up-datetime-picker itemHeight="60" :minDate="minDate" :title="placeholder" :show="show" v-model="time"
mode="datetime" @confirm="confirm" :closeOnClickOverlay="true"
@close="close"></up-datetime-picker>
</view>
</template>
<script>
import { timeFormat } from 'uview-plus'
export default {
name: 'LockDatetimePicker',
props: {
title: String,
value: Number,
minDate: Number,
placeholder: String
},
data() {
return {
time: 0,
show: false
}
},
created() {
this.time = this.value
},
methods: {
timeFormat,
changeShow() {
this.show = !this.show
},
close() {
this.show = false
},
confirm(e) {
this.show = false
this.$emit('changeTime', e.value)
}
}
}
</script>
<style scoped lang="scss">
.name {
height: 100rpx;
width: 750rpx;
display: flex;
align-items: center;
background-color: #ffffff;
font-weight: bold;
font-size: 32rpx;
.name-text {
width: 168rpx;
margin-left: 32rpx;
line-height: 100rpx;
}
.picker {
margin-right: 32rpx;
text-align: right;
width: 518rpx;
height: 100rpx;
line-height: 100rpx;
}
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<view>
<view class="name">
<view class="name-text">{{ title }}</view>
<input :value="value" class="name-input" :placeholder="placeholder" placeholder-class="placeholder-class"
maxlength="16" @input="changeInput"></input>
</view>
</view>
</template>
<script>
export default {
name: 'LockInput',
props:{
title: String,
placeholder: String,
value: String
},
methods: {
changeInput(e) {
this.$emit('changeInput', e.detail.value)
}
},
}
</script>
<style scoped lang="scss">
.name {
height: 100rpx;
width: 750rpx;
display: flex;
align-items: center;
background-color: #ffffff;
.name-text {
width: 168rpx;
margin-left: 32rpx;
font-weight: bold;
font-size: 32rpx;
line-height: 100rpx;
}
.name-input {
margin-right: 32rpx;
text-align: right;
width: 518rpx;
height: 100rpx;
font-size: 32rpx;
line-height: 100rpx;
border: none;
outline: none;
}
}
.placeholder-class {
text-align: right;
font-size: 32rpx;
line-height: 100rpx;
}
</style>

View File

@ -112,14 +112,30 @@
{
"path": "pages/passwordList/passwordList",
"style": {
"disableScroll": true,
"navigationBarTitleText": "密码"
}
},
{
"path": "pages/createPassword/createPassword",
"style": {
"disableScroll": true,
"navigationBarTitleText": "获取密码"
}
},
{
"path": "pages/passwordDetail/passwordDetail",
"style": {
"disableScroll": true,
"navigationBarTitleText": "密码详情"
}
},
{
"path": "pages/keyDetail/keyDetail",
"style": {
"disableScroll": true,
"navigationBarTitleText": "钥匙详情"
}
}
],
"globalStyle": {

View File

@ -1,17 +1,236 @@
<template>
<view>
<view class="tabs">
<up-tabs :list="tabs" lineWidth="40rpx" lineHeight="5rpx" :current="currnetIndex" lineColor="#63b8af"
@click="clickTab" :inactiveStyle="{color:'#a3a3a3', fontSize: '32rpx', fontWeight: 'bold'}"
:activeStyle="{color:'#63b8af', fontSize: '32rpx', fontWeight: 'bold'}">
</up-tabs>
</view>
<swiper :style="{height: deviceInfo.screenHeight - deviceInfo.safeArea.top - 44 + 'px'}" v-if="deviceInfo"
:list="tabs" :autoplay="false"
:circular="true" :current="currnetIndex" @change="changeSwiper">
<swiper-item>
<LockInput :value="permanentAccount" title="接收者账号" placeholder="请输入手机号或邮箱"
@changeInput="changePermanentAccountInput"></LockInput>
<LockInput :value="permanentName" title="钥匙名称" placeholder="请输入钥匙名称"
@changeInput="changePermanentNmaeInput"></LockInput>
<view class="text">接收者可使用此小程序开关锁</view>
<view class="button" @click="createKey('permanent')">发送钥匙</view>
</swiper-item>
<swiper-item :style="{height: deviceInfo.windowHeight - 44 + 'px'}">
<LockInput :value="temporaryAccount" title="接收者账号" placeholder="请输入手机号或邮箱"
@changeInput="changeTemporaryAccountInput"></LockInput>
<LockInput :value="temporaryName" title="钥匙名称" placeholder="请输入钥匙名称"
@changeInput="changeTemporaryNameInput"></LockInput>
<view style="margin-top: 20rpx">
<LockDatetimePicker title="生效时间" :value="temporaryValidTime" :minDate="minDate"
placeholder="请选择失效时间" @changeTime="changeTemporaryValidTime"></LockDatetimePicker>
<LockDatetimePicker title="失效时间" :value="temporaryInvalidTime" :minDate="minDate"
placeholder="请选择失效时间" @changeTime="changeTemporaryInvalidTime"></LockDatetimePicker>
</view>
<view class="text">接收者在有效期内可以不限次数使用</view>
<view class="button" @click="createKey('temporary')">发送钥匙</view>
</swiper-item>
</swiper>
</view>
</template>
<script>
import { mapActions, mapState } from 'pinia'
import { useBasicStore } from '@/stores/basic'
import LockInput from '@/components/LockInput/LockInput.vue'
import LockDatetimePicker from '@/components/LockDatetimePicker/LockDatetimePicker.vue'
import { createPsaawordRequest } from '@/api/keyboardPwd'
import { useBluetoothStore } from '@/stores/bluetooth'
import { useLockStore } from '@/stores/lock'
import { test } from 'uview-plus'
import { createKeyRequest } from '@/api/key'
export default {
data () {
return {}
return {
tabs: [{
name: '永久'
}, {
name: '限时'
}],
permanentName: '',
permanentAccount: '',
temporaryName: '',
temporaryAccount: '',
temporaryValidTime: Number(new Date()),
temporaryInvalidTime: Number(new Date()),
minDate: Number(new Date()),
currnetIndex: 0,
deviceInfo: null,
pending: false
}
},
components: {
LockInput,
LockDatetimePicker
},
computed: {
...mapState(useBluetoothStore, ['currentLockInfo']),
...mapState(useLockStore, ['keySearch']),
},
async onLoad () {
this.deviceInfo = await this.getDeviceInfo()
this.temporaryInvalidTime = this.setTime()
},
methods: {
...mapActions(useBasicStore, ['getDeviceInfo']),
...mapActions(useLockStore, ['getKeyList', 'updateKeySearch']),
setTime () {
const now = new Date()
now.setMinutes(0, 0, 0)
now.setDate(now.getDate() + 3)
return now.getTime()
},
async createKey (type, createUser = false) {
if ((type === 'temporary' && !(test.email(this.temporaryAccount) || test.mobile(this.temporaryAccount))) ||
(type === 'permanent' && !(test.email(this.permanentAccount) || test.mobile(this.permanentAccount)))) {
uni.showToast({
title: '请输入格式正确的手机号或邮箱',
icon: 'none'
})
return
}
if ((type === 'temporary' && this.temporaryName === '') || (type === 'permanent' && this.permanentName === '')) {
uni.showToast({
title: '名称不能为空',
icon: 'none'
})
return
}
if(type === 'temporary' && this.temporaryValidTime >= this.temporaryInvalidTime) {
uni.showToast({
title: '失效时间必须大于生效时间',
icon: 'none'
})
return
}
if (this.pending) {
return
}
this.pending = true
let params = {
faceAuthentication: '2',
isRemoteUnlock: '2',
lockId: this.currentLockInfo.lockId,
keyRight: '0',
remarks: '',
countryCode: '86',
createUser: '0'
}
if(createUser) {
params.createUser = '1'
params.usernameType = test.mobile(this.temporaryAccount) ? '1' : '2'
}
if (type === 'temporary') {
params.keyNameForAdmin = this.temporaryName
params.endDate = this.temporaryInvalidTime.toString()
params.keyType = '2'
params.receiverUsername = this.temporaryAccount
params.startDate = this.temporaryValidTime.toString()
} else {
params.keyNameForAdmin = this.permanentName
params.startDate = new Date().getTime().toString()
params.endDate = '0'
params.keyType = '1'
params.receiverUsername = this.permanentAccount
}
const { code, message } = await createKeyRequest(params)
if (code === 0) {
this.updateKeySearch({
...this.keySearch,
pageNo: 1
})
this.getKeyList(this.keySearch)
uni.navigateBack({
complete: () => {
uni.showToast({
title: '钥匙已发送',
icon: 'none'
})
}
})
} else if(code === 425) {
this.pending = false
await this.createKey(type, true)
} else {
uni.showToast({
title: message,
icon: 'none'
})
}
this.pending = false
},
changePermanentAccountInput (e) {
this.permanentAccount = e
},
changePermanentNmaeInput (e) {
this.permanentName = e
},
changeTemporaryNameInput (e) {
this.temporaryName = e
},
changeTemporaryAccountInput (e) {
this.temporaryAccount = e
},
changeTemporaryValidTime (e) {
this.temporaryValidTime = e
},
changeTemporaryInvalidTime (e) {
this.temporaryInvalidTime = e
},
clickTab (data) {
this.currnetIndex = data.index
},
changeSwiper (e) {
this.currnetIndex = e.detail.current
}
}
}
</script>
<style lang="scss">
page {
background-color: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.tabs {
display: flex;
justify-content: center;
}
.text {
margin-top: 40rpx;
margin-bottom: 50rpx;
color: #262626;
font-size: 26rpx;
padding: 0 32rpx;
}
.button {
border-radius: 64rpx;
width: 686rpx;
margin-left: 32rpx;
height: 100rpx;
line-height: 100rpx;
text-align: center;
background-color: #63b8af;
color: #fff;
font-size: 32rpx;
font-weight: bold;
}
</style>

View File

@ -1,17 +1,207 @@
<template>
<view>
<view class="tabs">
<up-tabs :list="tabs" lineWidth="40rpx" lineHeight="5rpx" :current="currnetIndex" lineColor="#63b8af"
@click="clickTab" :inactiveStyle="{color:'#a3a3a3', fontSize: '32rpx', fontWeight: 'bold'}"
:activeStyle="{color:'#63b8af', fontSize: '32rpx', fontWeight: 'bold'}">
</up-tabs>
</view>
<swiper :style="{height: deviceInfo.screenHeight - deviceInfo.safeArea.top - 44 + 'px'}" v-if="deviceInfo"
:list="tabs" :autoplay="false"
:circular="true" :current="currnetIndex" @change="changeSwiper">
<swiper-item>
<LockInput :value="permanentName" title="名称" placeholder="给密码命名"
@changeInput="changePermanentInput"></LockInput>
<view class="text">{{ text }}</view>
<view class="button" @click="createPassword('permanent')">获取密码</view>
</swiper-item>
<swiper-item :style="{height: deviceInfo.windowHeight - 44 + 'px'}">
<LockInput :value="temporaryName" title="名称" placeholder="给密码命名"
@changeInput="changeTemporaryInput"></LockInput>
<view style="margin-top: 20rpx">
<LockDatetimePicker title="失效时间" :value="temporaryTime" :minDate="minDate"
placeholder="请选择失效时间" @changeTime="changeTemporaryTime"></LockDatetimePicker>
</view>
<view class="text">{{ text }}</view>
<view class="button" @click="createPassword('temporary')">获取密码</view>
</swiper-item>
</swiper>
</view>
</template>
<script>
import { mapActions, mapState } from 'pinia'
import { useBasicStore } from '@/stores/basic'
import LockInput from '@/components/LockInput/LockInput.vue'
import LockDatetimePicker from '@/components/LockDatetimePicker/LockDatetimePicker.vue'
import { createPsaawordRequest } from '@/api/keyboardPwd'
import { useBluetoothStore } from '@/stores/bluetooth'
import { useLockStore } from '@/stores/lock'
export default {
data () {
return {}
return {
tabs: [{
name: '永久'
}, {
name: '限时'
}],
permanentName: '',
temporaryName: '',
temporaryTime: Number(new Date()),
minDate: Number(new Date()),
currnetIndex: 0,
deviceInfo: null,
pending: false,
text: '密码生成后请在当日2359前使用一次进行激活否则过点后未激活则失效。密码激活后有效期内不限次数使用。'
}
},
components: {
LockInput,
LockDatetimePicker
},
computed: {
...mapState(useBluetoothStore, ['currentLockInfo']),
},
async onLoad() {
this.deviceInfo = await this.getDeviceInfo()
this.temporaryTime = this.setTime()
},
methods: {
...mapActions(useBasicStore, ['getDeviceInfo']),
...mapActions(useLockStore, ['getPasswordList']),
setTime() {
const now = new Date()
now.setMinutes(0, 0, 0)
now.setDate(now.getDate() + 1)
return now.getTime()
},
async createPassword(type) {
if((type === 'temporary' && this.temporaryName === '') || (type === 'permanent' && this.permanentName === '')) {
uni.showToast({
title: '名称不能为空',
icon: 'none'
})
return
}
if(this.pending) {
return
}
this.pending = true
let params = {
lockId: this.currentLockInfo.lockId,
isCoerced: 2,
pwdRight: 0
}
if(type === 'temporary') {
params.keyboardPwdName = this.temporaryName
params.keyboardPwdType = 3
params.startDate = new Date().getTime()
params.endDate = this.temporaryTime
params.hoursStart = 0
params.hoursEnd = 0
} else {
params.startDate = 0
params.endDate = 0
params.keyboardPwdName = this.permanentName
params.keyboardPwdType = 2
params.hoursStart = 0
params.hoursEnd = 0
}
const { code, data, message } = await createPsaawordRequest(params)
if(code === 0) {
const search = {
lockStatus: this.currentLockInfo.lockStatus,
lockId: this.currentLockInfo.lockId,
pageNo: 1,
pageSize: 50
}
this.getPasswordList(search)
uni.showModal({
title: '密码生成成功',
content: `密码:${data.keyboardPwd}`,
cancelText: '复制',
success: (res) => {
if(res.confirm) {
uni.navigateBack()
} else {
uni.setClipboardData({
data: data.keyboardPwd,
success: () => {
uni.navigateBack({
complete: () => {
uni.showToast({
title: '复制成功',
icon: 'none'
})
}
})
}
})
}
}
})
} else {
uni.showToast({
title: message,
icon: 'none'
})
}
this.pending = false
},
changePermanentInput(e) {
this.permanentName = e
},
changeTemporaryInput(e) {
this.temporaryName = e
},
changeTemporaryTime(e) {
this.temporaryTime = e
},
clickTab(data) {
this.currnetIndex = data.index
},
changeSwiper(e) {
this.currnetIndex = e.detail.current
}
}
}
</script>
<style lang="scss">
page {
background-color: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.tabs {
display: flex;
justify-content: center;
}
.text {
margin-top: 40rpx;
margin-bottom: 50rpx;
color: #262626;
font-size: 26rpx;
padding: 0 32rpx;
}
.button {
border-radius: 64rpx;
width: 686rpx;
margin-left: 32rpx;
height: 100rpx;
line-height: 100rpx;
text-align: center;
background-color: #63b8af;
color: #fff;
font-size: 32rpx;
font-weight: bold;
}
</style>

View File

@ -91,6 +91,8 @@
uni.showLoading({
title: '加载中'
})
const accountInfo = wx.getAccountInfoSync()
getApp().globalData.appid = accountInfo.miniProgram.appId
this.deviceInfo = await this.getDeviceInfo()
const token = uni.getStorageSync('token')

View File

@ -0,0 +1,70 @@
<template>
<view>
<view class="item">
<view class="item-title">名称</view>
<view class="item-content">{{ currentKeyInfo.nickname }}</view>
</view>
<view class="item" style="margin-top: 2rpx">
<view class="item-title">有效期</view>
<view v-if="currentKeyInfo.keyType === 1">永久</view>
<view v-else>
<view class="item-content">{{ timeFormat(currentKeyInfo.startDate, 'yyyy-mm-dd h:M') }}</view>
<view class="item-content">{{ timeFormat(currentKeyInfo.endDate, 'yyyy-mm-dd h:M') }}</view>
</view>
</view>
<view class="item" style="margin-top: 20rpx">
<view class="item-title">接收者</view>
<view class="item-content">{{ currentKeyInfo.username }}</view>
</view>
<view class="item" style="margin-top: 2rpx">
<view class="item-title">发送人</view>
<view class="item-content">{{ currentKeyInfo.senderUsername }}</view>
</view>
<view class="item" style="margin-top: 2rpx">
<view class="item-title">发送时间</view>
<view class="item-content">{{ timeFormat(currentKeyInfo.sendDate, 'yyyy-mm-dd h:M') }}</view>
</view>
</view>
</template>
<script>
import { mapState } from 'pinia'
import { useLockStore } from '@/stores/lock'
import { timeFormat } from 'uview-plus'
export default {
data () {
return {}
},
computed: {
...mapState(useLockStore, ['currentKeyInfo']),
},
methods: {
timeFormat
},
}
</script>
<style lang="scss">
page {
background-color: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.item {
padding: 24rpx 32rpx;
background-color: #FFFFFF;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 32rpx;
font-weight: 500;
}
.tips {
padding: 24rpx 32rpx;
font-size: 24rpx;
color: #999999;
}
</style>

View File

@ -1,17 +1,297 @@
<template>
<view>
<scroll-view v-if="deviceInfo" scroll-y="true" :style="{height: deviceInfo.screenHeight - deviceInfo.safeArea.top + 'px'}" lower-threshold="100"
@refresherrefresh="refresherList" :refresher-enabled="true" @scrolltolower="nextPage"
:refresher-triggered="refresherTriggered">
<view class="search">
<up-search shape="square" :searchIconSize="48" :inputStyle="{ fontSize: '32rpx' }" :height="80" placeholder="搜索"
:clearabled="false" @change="changeSearch"
v-model="keySearch.searchStr" bgColor="#ffffff" :showAction="false" maxlength="20"></up-search>
</view>
<view style="padding: 32rpx 0 calc(env(safe-area-inset-bottom) + 250rpx) 0">
<view v-if="keyList.length === 0 && requestFinished">
<image class="empty-list" src="/static/images/background_empty_list.png" mode="aspectFill"></image>
<view class="empty-list-text">暂无数据</view>
</view>
<view v-else>
<up-swipe-action>
<up-swipe-action-item ref="swipeItem" :options="options" v-for="(key, index) in keyList"
:key="key.keyboardPwdId" :threshold="50" @click="deleteKey(key)">
<view class="key" @click="toKeyDetail(key)">
<image class="key-left" :src="key.headUrl" mode="aspectFill"></image>
<view class="key-right">
<view class="key-right-top">{{ key.keyName }}</view>
<view class="key-right-bottom">{{ key.timeText }}</view>
</view>
</view>
<view class="line"></view>
</up-swipe-action-item>
</up-swipe-action>
</view>
</view>
</scroll-view>
<view class="button">
<view class="button-reset" @click="resetKey">重置钥匙</view>
<view class="button-create" @click="toCreateKey">发送钥匙</view>
</view>
</view>
</template>
<script>
import { useBasicStore } from '@/stores/basic'
import { mapActions, mapState } from 'pinia'
import { useBluetoothStore } from '@/stores/bluetooth'
import { useLockStore } from '@/stores/lock'
import { useUserStore } from '@/stores/user'
import { deletePsaawordRequest, resetPsaawordListRequest } from '@/api/keyboardPwd'
import { deleteKeyRequest, resetKeyRequest } from '@/api/key'
export default {
data () {
return {}
}
return {
deviceInfo: null,
refresherTriggered: false,
requestFinished: false,
options: [{
text: '删除',
style: {
backgroundColor: '#f56c6c'
}
}]
}
},
computed: {
...mapState(useUserStore, ['userInfo']),
...mapState(useBluetoothStore, ['currentLockInfo', 'keyId']),
...mapState(useLockStore, ['keyTotal', 'keyList', 'keySearch']),
},
async onLoad() {
uni.showLoading({
title: '加载中'
})
this.deviceInfo = await this.getDeviceInfo()
this.updateKeySearch({
...this.keySearch,
lockId: this.currentLockInfo.lockId
})
const { code, meesage } = await this.getKeyList(this.keySearch)
this.requestFinished = true
uni.hideLoading()
},
methods: {
...mapActions(useBasicStore, ['routeJump', 'getDeviceInfo']),
...mapActions(useLockStore, ['getKeyList', 'updateCurrentKeyInfo', 'updateKeySearch']),
toKeyDetail(key) {
this.updateCurrentKeyInfo(key)
this.routeJump({
name: 'keyDetail'
})
},
async deleteKey(data) {
const key = data
const that = this
let index = this.keyList.findIndex(item => item.keyId === key.keyId)
that.$refs.swipeItem[index].closeHandler()
uni.showModal({
title: '提示',
content: '确定要删除该钥匙',
async success(res) {
if(res.confirm) {
uni.showLoading({
title: '删除中',
mask: true
})
const { code: requestCode, message } = await deleteKeyRequest({
keyId: key.keyId
})
if(requestCode === 0) {
uni.hideLoading()
uni.showToast({
title: '删除成功',
icon: 'none'
})
that.updateKeySearch({
...that.keySearch,
pageNo: 1
})
await that.getKeyList(that.keySearch)
} else {
uni.hideLoading()
uni.showToast({
title: message,
icon: 'none'
})
}
}
}
})
},
async resetKey() {
const that = this
uni.showModal({
title: '提示',
content: '确定要重置钥匙,该锁的所有钥匙都将被删除',
async success(res) {
if(res.confirm) {
const { code: requestCode, message } = await resetKeyRequest({
lockId: that.currentLockInfo.lockId,
})
console.log('重置钥匙返回', requestCode, message)
if(requestCode === 0) {
uni.showToast({
title: '重置钥匙成功',
icon: 'none'
})
that.updateKeySearch({
...that.keySearch,
pageNo: 1
})
await that.getKeyList(that.keySearch)
} else {
uni.showToast({
title: message,
icon: 'none'
})
}
}
}
})
},
toCreateKey() {
this.routeJump({
name: 'createKey'
})
},
async refresherList() {
this.refresherTriggered = true
this.updateKeySearch({
...this.keySearch,
pageNo: 1
})
const { code, meesage } = await this.getKeyList(this.keySearch)
if(code === 0) {
uni.showToast({
title: '刷新成功',
icon: 'none'
})
}
this.refresherTriggered = false
},
async nextPage() {
if(this.keyTotal <= this.keySearch.pageNo * this.keySearch.pageSize) {
return
}
const pageNo = this.keySearch.pageNo + 1
const params = {
...this.keySearch,
pageNo
}
const { code, meesage } = await this.getKeyList(params)
if(code === 0) {
this.updateKeySearch({
...this.keySearch,
pageNo
})
}
},
async changeSearch(data) {
this.keySearch.searchStr = data
const { code, meesage } = await this.getKeyList(this.keySearch)
},
},
}
</script>
<style lang="scss">
page {
background-color: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.search {
margin-top: 32rpx;
width: 686rpx !important;
margin-left: 32rpx;
}
.button {
display: flex;
align-items: center;
position: fixed;
bottom: calc(env(safe-area-inset-bottom) + 48rpx);
font-weight: bold;
.button-reset {
margin-left: 50rpx;
width: 300rpx;
height: 88rpx;
background-color: #df282d;
color: white;
text-align: center;
line-height: 88rpx;
border-radius: 44rpx;
}
.button-create {
margin-left: 50rpx;
width: 300rpx;
height: 88rpx;
background-color: #63b8af;
color: white;
text-align: center;
line-height: 88rpx;
border-radius: 44rpx;
}
}
.key {
display: flex;
align-items: center;
background-color: #FFFFFF;
height: 120rpx;
width: 750rpx;
.key-left {
margin-left: 32rpx;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.key-right {
margin-left: 32rpx;
margin-right: 32rpx;
.key-right-top {
font-size: 32rpx;
font-weight: bold;
padding-bottom: 6rpx;
}
.key-right-bottom {
font-size: 26rpx;
color: #999999;
}
}
}
.line {
width: 100%;
height: 2rpx;
background: #EBEBEB;
}
.empty-list {
width: 150rpx;
height: 150rpx;
margin: 300rpx auto 20rpx 50%;
transform: translateX(-50%);
}
.empty-list-text {
text-align: center;
font-size: 32rpx;
color: #999999;
}
</style>

View File

@ -32,7 +32,7 @@
<view>功能</view>
</view>
<view class="menu-main">
<view v-if="currentLockInfo.lockFeature.bluetoothRemoteControl || currentLockInfo.userType === 110301"
<view v-if="currentLockInfo.userType === 110301"
class="menu-main-view" @click="routeJump({ name: 'keyList' })">
<image class="menu-main-image" src="/static/images/tabbar_key_select.png"></image>
<view>电子钥匙</view>

View File

@ -0,0 +1,71 @@
<template>
<view>
<view class="item">
<view class="item-title">密码</view>
<view class="item-content">{{ currentPasswordInfo.keyboardPwd }}</view>
</view>
<view class="item" style="margin-top: 2rpx">
<view class="item-title">名称</view>
<view class="item-content">{{ currentPasswordInfo.keyboardPwdName }}</view>
</view>
<view class="item" style="margin-top: 2rpx">
<view class="item-title">有效期</view>
<view v-if="currentPasswordInfo.keyboardPwdType === 2">永久</view>
<view v-else>
<view class="item-content">{{ timeFormat(currentPasswordInfo.startDate, 'yyyy-mm-dd h:M') }}</view>
<view class="item-content">{{ timeFormat(currentPasswordInfo.endDate, 'yyyy-mm-dd h:M') }}</view>
</view>
</view>
<view class="item" style="margin-top: 20rpx">
<view class="item-title">发送人</view>
<view class="item-content">{{ currentPasswordInfo.senderUsername }}</view>
</view>
<view class="item" style="margin-top: 2rpx">
<view class="item-title">发送时间</view>
<view class="item-content">{{ timeFormat(currentPasswordInfo.sendDate, 'yyyy-mm-dd h:M') }}</view>
</view>
<view class="tips">密码生成后请在当日2359前使用一次进行激活否则过0点后未激活则失效</view>
</view>
</template>
<script>
import { mapState } from 'pinia'
import { useLockStore } from '@/stores/lock'
import { timeFormat } from 'uview-plus'
export default {
data () {
return {}
},
computed: {
...mapState(useLockStore, ['currentPasswordInfo']),
},
methods: {
timeFormat
},
}
</script>
<style lang="scss">
page {
background-color: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.item {
padding: 24rpx 32rpx;
background-color: #FFFFFF;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 32rpx;
font-weight: 500;
}
.tips {
padding: 24rpx 32rpx;
font-size: 24rpx;
color: #999999;
}
</style>

View File

@ -1,17 +1,320 @@
<template>
<view>
<scroll-view v-if="deviceInfo" scroll-y="true" :style="{height: deviceInfo.screenHeight - deviceInfo.safeArea.top + 'px'}" lower-threshold="100"
@refresherrefresh="refresherList" :refresher-enabled="true" @scrolltolower="nextPage"
:refresher-triggered="refresherTriggered">
<view class="search">
<up-search shape="square" :searchIconSize="48" :inputStyle="{ fontSize: '32rpx' }" :height="80" placeholder="搜索"
:clearabled="false" @change="changeSearch"
v-model="search.searchStr" bgColor="#ffffff" :showAction="false" maxlength="20"></up-search>
</view>
<view style="padding: 32rpx 0 calc(env(safe-area-inset-bottom) + 250rpx) 0">
<view v-if="passwordList.length === 0 && requestFinished">
<image class="empty-list" src="/static/images/background_empty_list.png" mode="aspectFill"></image>
<view class="empty-list-text">暂无数据</view>
</view>
<view v-else>
<up-swipe-action>
<up-swipe-action-item ref="swipeItem" :options="options" v-for="(password, index) in passwordList"
:key="password.keyboardPwdId" :threshold="50" @click="deletePassword(password)">
<view class="password" @click="toPasswordDetail(password)">
<image class="password-left" src="/static/images/icon_lock_transparent.png" mode="aspectFill"></image>
<view class="password-right">
<view class="password-right-top">{{ password.keyboardPwdName }}</view>
<view class="password-right-bottom">{{ password.timeText }}</view>
</view>
</view>
<view class="line"></view>
</up-swipe-action-item>
</up-swipe-action>
</view>
</view>
</scroll-view>
<view class="button">
<view class="button-reset" @click="resetPassword">重置密码</view>
<view class="button-create" @click="toCreatePassword">获取密码</view>
</view>
</view>
</template>
<script>
import { useBasicStore } from '@/stores/basic'
import { mapActions, mapState } from 'pinia'
import { useBluetoothStore } from '@/stores/bluetooth'
import { useLockStore } from '@/stores/lock'
import { useUserStore } from '@/stores/user'
import { deletePsaawordRequest, resetPsaawordListRequest } from '@/api/keyboardPwd'
export default {
data () {
return {}
}
return {
search: {
pageNo: 1,
pageSize: 50,
searchStr: ''
},
deviceInfo: null,
refresherTriggered: false,
requestFinished: false,
options: [{
text: '删除',
style: {
backgroundColor: '#f56c6c'
}
}]
}
},
computed: {
...mapState(useUserStore, ['userInfo']),
...mapState(useBluetoothStore, ['currentLockInfo', 'keyId']),
...mapState(useLockStore, ['passwordTotal', 'passwordList']),
},
async onLoad() {
uni.showLoading({
title: '加载中'
})
this.deviceInfo = await this.getDeviceInfo()
this.search.lockStatus = this.currentLockInfo.lockStatus
this.search.lockId = this.currentLockInfo.lockId
const { code, meesage } = await this.getPasswordList(this.search)
this.requestFinished = true
uni.hideLoading()
},
methods: {
...mapActions(useBasicStore, ['routeJump', 'getDeviceInfo']),
...mapActions(useLockStore, ['getPasswordList', 'updateCurrentPasswordInfo']),
...mapActions(useBluetoothStore, ['resetLockPassword', 'setLockPassword']),
toPasswordDetail(password) {
this.updateCurrentPasswordInfo(password)
this.routeJump({
name: 'passwordDetail'
})
},
async deletePassword(data) {
const password = data
const that = this
let index = this.passwordList.findIndex(item => item.keyboardPwdId === password.keyboardPwdId)
that.$refs.swipeItem[index].closeHandler()
uni.showModal({
title: '提示',
content: '确定要删除该密码',
async success(res) {
if(res.confirm) {
uni.showLoading({
title: '删除中',
mask: true
})
const timestamp = parseInt(new Date().getTime() / 1000)
const { code } = await that.setLockPassword({
keyId: that.keyId.toString(),
uid: that.userInfo.uid.toString(),
pwdNo: password.pwdUserNo,
operate: 3,
isAdmin: password.pwdRight,
pwd: password.keyboardPwd,
userCountLimit: 0xFFFF,
startTime: timestamp,
endTime: timestamp
})
if(code === 0) {
const { code: requestCode, message } = await deletePsaawordRequest({
lockId: that.currentLockInfo.lockId,
keyboardPwdId: password.keyboardPwdId,
deleteType: 1
})
if(requestCode === 0) {
uni.hideLoading()
uni.showToast({
title: '删除成功',
icon: 'none'
})
that.search.pageNo = 1
await that.getPasswordList(that.search)
} else {
uni.hideLoading()
uni.showToast({
title: message,
icon: 'none'
})
}
} else {
uni.hideLoading()
uni.showToast({
title: '删除失败,请保持在锁附近',
icon: 'none'
})
}
}
}
})
},
async resetPassword() {
const that = this
uni.showModal({
title: '提示',
content: '确定要重置密码,该锁的所有密码都将被删除',
async success(res) {
if(res.confirm) {
const { code } = await that.resetLockPassword({
uid: that.userInfo.uid.toString(),
keyId: that.currentLockInfo.keyId.toString()
})
if(code === 0) {
const { code: requestCode, message } = await resetPsaawordListRequest({
lockId: that.currentLockInfo.lockId,
passwordKey: that.currentLockInfo.bluetooth.passwordKey
})
console.log('重置密码返回', requestCode, message)
if(requestCode === 0) {
uni.showToast({
title: '重置密码成功',
icon: 'none'
})
that.search.pageNo = 1
that.getPasswordList(that.search)
} else {
uni.showToast({
title: message,
icon: 'none'
})
}
} else {
uni.showToast({
title: '重置密码失败,请保持在锁附近',
icon: 'none'
})
}
}
}
})
},
toCreatePassword() {
this.routeJump({
name: 'createPassword'
})
},
async refresherList() {
this.refresherTriggered = true
this.search.pageNo = 1
const { code, meesage } = await this.getPasswordList(this.search)
if(code === 0) {
uni.showToast({
title: '刷新成功',
icon: 'none'
})
}
this.refresherTriggered = false
},
async nextPage() {
if(this.passwordTotal <= this.search.pageNo * this.search.pageSize) {
return
}
const pageNo = this.search.pageNo + 1
const params = {
...this.search,
pageNo
}
const { code, meesage } = await this.getPasswordList(params)
if(code === 0) {
this.search.pageNo = pageNo
}
},
async changeSearch(data) {
this.search.searchStr = data
const { code, meesage } = await this.getPasswordList(this.search)
},
},
}
</script>
<style lang="scss">
page {
background-color: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.search {
margin-top: 32rpx;
width: 686rpx !important;
margin-left: 32rpx;
}
.button {
display: flex;
align-items: center;
position: fixed;
bottom: calc(env(safe-area-inset-bottom) + 48rpx);
font-weight: bold;
.button-reset {
margin-left: 50rpx;
width: 300rpx;
height: 88rpx;
background-color: #df282d;
color: white;
text-align: center;
line-height: 88rpx;
border-radius: 44rpx;
}
.button-create {
margin-left: 50rpx;
width: 300rpx;
height: 88rpx;
background-color: #63b8af;
color: white;
text-align: center;
line-height: 88rpx;
border-radius: 44rpx;
}
}
.password {
display: flex;
align-items: center;
background-color: #FFFFFF;
height: 120rpx;
width: 750rpx;
.password-left {
margin-left: 32rpx;
width: 80rpx;
height: 80rpx;
}
.password-right {
margin-right: 32rpx;
.password-right-top {
font-size: 32rpx;
font-weight: bold;
padding-bottom: 6rpx;
}
.password-right-bottom {
font-size: 26rpx;
color: #999999;
}
}
}
.line {
width: 100%;
height: 2rpx;
background: #EBEBEB;
}
.empty-list {
width: 150rpx;
height: 150rpx;
margin: 300rpx auto 20rpx 50%;
transform: translateX(-50%);
}
.empty-list-text {
text-align: center;
font-size: 32rpx;
color: #999999;
}
</style>

View File

@ -86,7 +86,8 @@ export default {
})
return
}
const timestamp = parseInt(new Date().getTime() / 1000)
const date = new Date()
const timestamp = parseInt(date.getTime() / 1000) - date.getTimezoneOffset() * 60
const { code } = await this.getLockStatus({
name: this.currentLockInfo.name,
uid: this.userInfo.uid,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -96,6 +96,16 @@ const pages = [
name: 'createPassword',
path: '/pages/createPassword/createPassword',
tabBar: false
},
{
name: 'passwordDetail',
path: '/pages/passwordDetail/passwordDetail',
tabBar: false
},
{
name: 'keyDetail',
path: '/pages/keyDetail/keyDetail',
tabBar: false
}
]

View File

@ -978,11 +978,11 @@ export const useBluetoothStore = defineStore('ble', {
conentArray[i + 44] = uid.charCodeAt(i)
}
conentArray.set(this.currentLockInfo.token, 64)
conentArray.set(this.currentLockInfo.token || new Uint8Array([0, 0, 0, 0]), 64)
conentArray[68] = 16
const md5Array = this.md5Encrypte(keyId + uid, this.currentLockInfo.token, this.currentLockInfo.signKey)
const md5Array = this.md5Encrypte(keyId + uid, this.currentLockInfo.token || new Uint8Array([0, 0, 0, 0]), this.currentLockInfo.signKey)
conentArray.set(md5Array, 69)
@ -1035,14 +1035,14 @@ export const useBluetoothStore = defineStore('ble', {
conentArray[88] = userCountLimit / 256
conentArray[89] = userCountLimit % 256
conentArray.set(this.currentLockInfo.token, 90)
conentArray.set(this.currentLockInfo.token || new Uint8Array([0, 0, 0, 0]), 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)
const md5Array = this.md5Encrypte(keyId + uid, this.currentLockInfo.token || new Uint8Array([0, 0, 0, 0]), this.currentLockInfo.signKey)
conentArray.set(md5Array, 103)

View File

@ -3,6 +3,9 @@
*/
import { defineStore } from 'pinia'
import { getLockListRequest } from '@/api/lock'
import { getPsaawordListRequest } from '@/api/keyboardPwd'
import { timeFormat } from 'uview-plus'
import { getKeyListRequest } from '@/api/key'
export const useLockStore = defineStore('lock', {
state() {
@ -10,10 +13,41 @@ export const useLockStore = defineStore('lock', {
// 锁列表
lockList: [],
// 锁总数
lockTotal: 0
lockTotal: 0,
// 密码列表
passwordList: [],
// 密码总数
passwordTotal: 0,
// 当前密码详情
currentPasswordInfo: {},
// 电子钥匙总数
keyTotal: 0,
// 电子钥匙列表
keyList: [],
// 当前电子钥匙详情
currentKeyInfo: {},
// 电子钥匙列表搜索数据
keySearch: {
pageNo: 1,
pageSize: 50,
searchStr: '',
endDate: '0',
startDate: '0',
keyStatus: [110401,110402],
keyRight: 0
},
}
},
actions: {
updateKeySearch(search) {
this.keySearch = search
},
updateCurrentKeyInfo(info) {
this.currentKeyInfo = info
},
updateCurrentPasswordInfo(info) {
this.currentPasswordInfo = info
},
getRole(userType) {
if(userType === 110301) {
return '超级管理员'
@ -49,6 +83,56 @@ export const useLockStore = defineStore('lock', {
})
return { code, message }
}
},
async getPasswordList(params) {
const { code, data, message } = await getPsaawordListRequest(params)
if(code === 0) {
this.passwordTotal = data.total
for(let i = 0; i < data.list.length; i++) {
if(data.list[i].keyboardPwdType === 2) {
data.list[i].timeText = `${timeFormat(new Date(data.list[i].created_at), 'yyyy-mm-dd h:M')} 永久`
} else if(data.list[i].keyboardPwdType === 3) {
data.list[i].timeText = `${data.list[i].validTimeStr} 限时`
}
}
if(params.pageNo === 1) {
this.passwordList = data.list
} else {
this.passwordList = this.passwordList.concat(data.list)
}
return { code }
} else {
uni.showToast({
title: message,
icon: 'none'
})
return { code, message }
}
},
async getKeyList(params) {
const { code, data, message } = await getKeyListRequest(params)
if(code === 0) {
this.keyTotal = data.total
for(let i = 0; i < data.list.length; i++) {
if(data.list[i].keyType === 2) {
data.list[i].timeText = `${timeFormat(new Date(data.list[i].startDate), 'yyyy-mm-dd h:M')} - ${timeFormat(new Date(data.list[i].endDate), 'yyyy-mm-dd h:M')} 限时`
} else {
data.list[i].timeText = `${timeFormat(new Date(data.list[i].startDate), 'yyyy-mm-dd h:M')} 永久`
}
}
if(params.pageNo === 1) {
this.keyList = data.list
} else {
this.keyList = this.keyList.concat(data.list)
}
return { code }
} else {
uni.showToast({
title: message,
icon: 'none'
})
return { code, message }
}
}
}
})

View File

@ -77,3 +77,7 @@ $uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;
.u-picker__view {
height: 500rpx !important;
}

View File

@ -14,6 +14,7 @@ const request = (config) => {
return new Promise((resolve) => {
const token = config?.token ? config.token : uni.getStorageSync('token')
const headerDefault = {
appid: getApp().globalData.appid,
version: baseConfig.version,
authorization: `Bearer ${token}`
}