A8000
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

373 lines
8.1 KiB

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { getUserList } from '@/services/Index/user-manage'
import type { User } from '@/types/Index'
import { createWebSocket, DeviceContextStateMessage } from '@/websocket/socket.ts'
import { getServerInfo } from '@/utils/getServerInfo.ts'
import router from '@/router/router.ts'
import { isBoardParamInited, login } from '@/services'
import circleUrl from '@/assets/avatar.png'
import { eMessage } from '@/pages/Index/utils'
const stateUrl = getServerInfo('/api/v1/app/ws/state')
const wsState = createWebSocket(stateUrl.wsUrl)
const handleDeviceContextState = (data: DeviceContextStateMessage['data']) => {
if (data.loginFlag) {
sessionStorage.setItem('token', JSON.stringify(data.loginUser))
router.push('/index')
}
}
//用户列表
const userList = ref<User[]>([])
//获取用户列表
const getUserListData = async () => {
const res = await getUserList()
userList.value = res.data
}
onMounted(() => {
wsState.subscribe<DeviceContextStateMessage>(
'DeviceContext',
handleDeviceContextState,
)
wsState.connect()
getUserListData()
})
onUnmounted(() => {
wsState.unsubscribe<DeviceContextStateMessage>(
'DeviceContext',
handleDeviceContextState,
)
})
const activeUser = ref<User>()
const password = ref('')
const inputPin = (pin: string) => {
if (password.value.length >= 4) {
return
}
if (password.value.length < 4) {
password.value += pin
}
if (password.value.length === 4) {
// 输入完成,执行登录操作
submitPin()
}
}
const passwordError = ref(false)
const submitPin = async () => {
let resData = await isBoardParamInited()
if (!resData.data) {
//设备正在初始化
eMessage.error('设备正在初始化,请稍候重试')
return
}
const params = {
id: activeUser.value?.id,
password: password.value,
}
const res = await login(params)
if (res.success) {
sessionStorage.setItem('token', JSON.stringify(res.data))
await router.push('/index')
} else {
passwordError.value = true // 触发抖动动画
// 动画结束后重置状态(2次抖动共0.6秒)
setTimeout(() => {
passwordError.value = false
}, 600)
password.value = ''
}
}
const showPassword = ref(false)
const next = () => {
if (activeUser.value?.id) {
showPassword.value = true
}
}
const back = () => {
showPassword.value = false;
password.value = ''
}
const version = __APP_VERSION__
</script>
<template>
<div class="login-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 }">
<el-avatar :size="60" :src="circleUrl" />
<span class="name">{{user.account}}</span>
<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" 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, 'password-error': passwordError}"></div>
</div>
<div class="pin-keypad">
<div
v-for="n in 9"
:key="n"
class="key"
@click="inputPin(n.toString())"
>
{{ n }}
</div>
<div></div>
<div class="key" @click="inputPin('0')">0</div>
</div>
<div class="next-box next-box-active" @click="back">
<el-icon><Back /></el-icon>
</div>
</div>
</transition>
<div class="version">版本号v {{version}}</div>
</div>
</template>
<style lang="less" scoped>
@keyframes iconAnim {
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%;
font-size: 50px;
font-weight: bold;
}
.login-box {
perspective: 1000px;
width: 100%;
height: 100%;
background: #A0CEF2;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.user-box {
width: 50%;
height: 50%;
background: #fff;
border-radius: 30px;
padding: 40px 30px;
display: flex;
flex-direction: column;
align-items: center;
.user-list {
width: 100%;
flex: 1;
overflow: auto;
.user-info {
display: flex;
align-items: center;
padding: 20px;
border-radius: 20px;
.name {
margin-left: 30px;
font-size: 25px;
font-weight: bold;
}
.el-icon {
animation: iconAnim 0.3s ease forwards;
font-size: 30px;
margin-left: auto;
}
}
.user-info-active {
background: #3E8ED1;
color: #fff;
}
}
}
.password-box {
width: 50%;
height: 50%;
background: #fff;
border-radius: 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 {
font-size: 30px;
font-weight: bold;
}
.password-list {
display: flex;
justify-content: center;
padding: 20px 0;
.password-item {
width: 30px;
height: 30px;
border-radius: 50%;
background: #CDCFD4;
margin: 0 10px;
}
.password-item-fill {
background: #3E8ED1;
}
}
.pin-keypad {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-top: 30px;
.key {
width: 100px;
height: 80px;
background-color: #fff;
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
color: #333;
cursor: pointer;
transition: all 0.3s;
border: 1px solid #E7EDF1;
box-shadow: 0 3px 8px #E7EDF1;
&:active {
transform: scale(0.95);
}
}
}
.error-box {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
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;
}
}
.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;
bottom: 10px;
color: rgba(0,0,0,0.25);
}
</style>