forked from gzt/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
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>
|