|
|
@ -7,7 +7,8 @@ import { getServerInfo } from '@/utils/getServerInfo.ts' |
|
|
|
import router from '@/router/router.ts' |
|
|
|
import { isBoardParamInited, login } from '@/services' |
|
|
|
import circleUrl from '@/assets/avatar.png' |
|
|
|
import { VERSION } from '@/constant' |
|
|
|
import { eMessage } from '@/pages/Index/utils' |
|
|
|
|
|
|
|
const stateUrl = getServerInfo('/api/v1/app/ws/state') |
|
|
|
const wsState = createWebSocket(stateUrl.wsUrl) |
|
|
|
|
|
|
@ -59,21 +60,14 @@ const inputPin = (pin: string) => { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const errorMsg = ref('') |
|
|
|
|
|
|
|
const passwordError = ref(false) |
|
|
|
|
|
|
|
const submitPin = async () => { |
|
|
|
if (!activeUser.value?.id) { |
|
|
|
errorMsg.value = '请选择用户' |
|
|
|
return |
|
|
|
} |
|
|
|
if (password.value.length !== 4) { |
|
|
|
errorMsg.value = '请输入密码' |
|
|
|
return |
|
|
|
} |
|
|
|
let resData = await isBoardParamInited() |
|
|
|
if (!resData.data) { |
|
|
|
//设备正在初始化 |
|
|
|
errorMsg.value = '设备正在初始化,请稍候重试' |
|
|
|
eMessage.error('设备正在初始化,请稍候重试') |
|
|
|
return |
|
|
|
} |
|
|
|
const params = { |
|
|
@ -85,15 +79,29 @@ const submitPin = async () => { |
|
|
|
sessionStorage.setItem('token', JSON.stringify(res.data)) |
|
|
|
await router.push('/index') |
|
|
|
} else { |
|
|
|
errorMsg.value = res.info |
|
|
|
// password.value = '' |
|
|
|
passwordError.value = true // 触发抖动动画 |
|
|
|
// 动画结束后重置状态(2次抖动共0.6秒) |
|
|
|
setTimeout(() => { |
|
|
|
passwordError.value = false |
|
|
|
}, 600) |
|
|
|
password.value = '' |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const showPassword = ref(false) |
|
|
|
const next = () => { |
|
|
|
showPassword.value = true |
|
|
|
} |
|
|
|
const back = () => { |
|
|
|
showPassword.value = false |
|
|
|
} |
|
|
|
|
|
|
|
const version = __APP_VERSION__ |
|
|
|
</script> |
|
|
|
<template> |
|
|
|
<div class="login-box"> |
|
|
|
<p class="login-title">登录</p> |
|
|
|
<div class="user-box"> |
|
|
|
<transition name="flip-card" mode="out-in"> |
|
|
|
<div class="user-box" v-if="!showPassword"> |
|
|
|
<p class="title">选择用户</p> |
|
|
|
<div class="user-list"> |
|
|
|
<div class="user-info" v-for="user in userList" :key="user.id" @click="activeUser = user" :class="{ 'user-info-active': activeUser?.id === user.id }"> |
|
|
@ -102,11 +110,19 @@ const submitPin = async () => { |
|
|
|
<el-icon v-show="activeUser?.id === user.id" color="#fff"><Select /></el-icon> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="next-box" :class="{'next-box-active': activeUser?.id}" @click="next"> |
|
|
|
<el-icon><Right /></el-icon> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="password-box"> |
|
|
|
|
|
|
|
<div class="password-box" v-else> |
|
|
|
<div class="user-info user-info-active"> |
|
|
|
<el-avatar :size="40" :src="circleUrl" /> |
|
|
|
<span class="name">{{activeUser!.account}}</span> |
|
|
|
</div> |
|
|
|
<p class="title">请输入4位PIN码</p> |
|
|
|
<div class="password-list"> |
|
|
|
<div v-for="i in 4" :key="i" class="password-item" :class="{'password-item-fill': i <= password.length}"></div> |
|
|
|
<div v-for="i in 4" :key="i" class="password-item" :class="{'password-item-fill': i <= password.length, 'password-error': passwordError}"></div> |
|
|
|
</div> |
|
|
|
<div class="pin-keypad"> |
|
|
|
<div |
|
|
@ -117,13 +133,15 @@ const submitPin = async () => { |
|
|
|
> |
|
|
|
{{ n }} |
|
|
|
</div> |
|
|
|
<div class="key" @click="password = '';errorMsg = ''">重输</div> |
|
|
|
<div></div> |
|
|
|
<div class="key" @click="inputPin('0')">0</div> |
|
|
|
<div class="key" @click="submitPin">确定</div> |
|
|
|
</div> |
|
|
|
<div class="error-box">{{errorMsg}}</div> |
|
|
|
<div class="next-box next-box-active" @click="back"> |
|
|
|
<el-icon><Back /></el-icon> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="version">版本号:{{`V${VERSION}`}}</div> |
|
|
|
</transition> |
|
|
|
<div class="version">版本号:v {{version}}</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
</template> |
|
|
@ -132,6 +150,25 @@ const submitPin = async () => { |
|
|
|
0% { transform: scale(0.8); opacity: 0; } |
|
|
|
100% { transform: scale(1); opacity: 1; } |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes shake { |
|
|
|
0%, 100% { |
|
|
|
transform: translateX(0); |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
25% { |
|
|
|
transform: translateX(-10px); |
|
|
|
opacity: 0.7; |
|
|
|
} |
|
|
|
50% { |
|
|
|
transform: translateX(10px); |
|
|
|
opacity: 0.7; |
|
|
|
} |
|
|
|
75% { |
|
|
|
transform: translateX(-7px); |
|
|
|
opacity: 0.7; |
|
|
|
} |
|
|
|
} |
|
|
|
.login-title { |
|
|
|
position: absolute; |
|
|
|
top: 10%; |
|
|
@ -139,6 +176,7 @@ const submitPin = async () => { |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
.login-box { |
|
|
|
perspective: 1000px; |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
background: #A0CEF2; |
|
|
@ -147,14 +185,14 @@ const submitPin = async () => { |
|
|
|
justify-content: center; |
|
|
|
position: relative; |
|
|
|
.user-box { |
|
|
|
width: 35%; |
|
|
|
height: 40%; |
|
|
|
margin-right: 20px; |
|
|
|
width: 50%; |
|
|
|
height: 50%; |
|
|
|
background: #fff; |
|
|
|
border-radius: 30px; |
|
|
|
padding: 60px 30px; |
|
|
|
padding: 40px 30px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: center; |
|
|
|
.user-list { |
|
|
|
width: 100%; |
|
|
|
flex: 1; |
|
|
@ -183,14 +221,30 @@ const submitPin = async () => { |
|
|
|
} |
|
|
|
} |
|
|
|
.password-box { |
|
|
|
margin-left: 20px; |
|
|
|
width: 35%; |
|
|
|
height: 40%; |
|
|
|
width: 50%; |
|
|
|
height: 50%; |
|
|
|
background: #fff; |
|
|
|
border-radius: 30px; |
|
|
|
padding: 60px 30px; |
|
|
|
padding: 40px 30px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: center; |
|
|
|
justify-content: space-between; |
|
|
|
.user-info { |
|
|
|
width: 50%; |
|
|
|
padding: 10px 0; |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
border-radius: 20px; |
|
|
|
background: #3E8ED1; |
|
|
|
color: #fff; |
|
|
|
.name { |
|
|
|
margin-left: 30px; |
|
|
|
font-size: 25px; |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
.title { |
|
|
@ -246,10 +300,70 @@ const submitPin = async () => { |
|
|
|
font-size: 20px; |
|
|
|
color: #ff0000; |
|
|
|
} |
|
|
|
.password-error { |
|
|
|
animation: shake 0.3s ease-in-out 2; |
|
|
|
} |
|
|
|
.next-box { |
|
|
|
width: 80px; |
|
|
|
height: 80px; |
|
|
|
border-radius: 50%; |
|
|
|
background: #ddd; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
margin: 20px auto; |
|
|
|
.el-icon { |
|
|
|
font-size: 30px; |
|
|
|
color: #bbb |
|
|
|
} |
|
|
|
} |
|
|
|
.next-box-active { |
|
|
|
background: #3E8ED1; |
|
|
|
.el-icon { |
|
|
|
color: #fff; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.version{ |
|
|
|
margin-top: 95vh; |
|
|
|
.flip-card-leave-active, |
|
|
|
.flip-card-enter-active { |
|
|
|
transition: all 0.5s ease; |
|
|
|
backface-visibility: hidden; /* 隐藏背面 */ |
|
|
|
} |
|
|
|
|
|
|
|
.flip-card-enter-from, |
|
|
|
.flip-card-leave-to { |
|
|
|
transform: rotateY(90deg); |
|
|
|
opacity: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.flip-card-enter-active .user-box, |
|
|
|
.flip-card-leave-active .user-box, |
|
|
|
.flip-card-enter-active .password-box, |
|
|
|
.flip-card-leave-active .password-box { |
|
|
|
position: absolute; |
|
|
|
backface-visibility: hidden; |
|
|
|
transition: transform 0.5s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.flip-card-enter .user-box { |
|
|
|
transform: rotateY(-90deg); |
|
|
|
} |
|
|
|
|
|
|
|
.flip-card-leave-to .user-box { |
|
|
|
transform: rotateY(90deg); |
|
|
|
} |
|
|
|
|
|
|
|
.flip-card-enter .password-box { |
|
|
|
transform: rotateY(90deg); |
|
|
|
} |
|
|
|
|
|
|
|
.flip-card-leave-to .password-box { |
|
|
|
transform: rotateY(-90deg); |
|
|
|
} |
|
|
|
.version { |
|
|
|
position: absolute; |
|
|
|
color: #f4f4f4; |
|
|
|
bottom: 10px; |
|
|
|
color: rgba(0,0,0,0.25); |
|
|
|
} |
|
|
|
</style> |