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.
298 lines
6.6 KiB
298 lines
6.6 KiB
<template>
|
|
<div class="login-container">
|
|
<aside class="sidebar">
|
|
<ul>
|
|
<li
|
|
v-for="user in userList"
|
|
:key="user.id"
|
|
:class="[
|
|
'user',
|
|
{ selected: user.account === selectedUser?.account },
|
|
]"
|
|
@click="selectUser(user)"
|
|
>
|
|
{{ user.account }}
|
|
</li>
|
|
</ul>
|
|
</aside>
|
|
<main class="main-content">
|
|
<div class="pin-container">
|
|
<p :class="['pin-prompt', { shake: isShaking }]">{{ loginStatus }}</p>
|
|
<div class="pin-indicators">
|
|
<span
|
|
v-for="n in maxPinLength"
|
|
:key="n"
|
|
:class="['pin-dot', { filled: n <= pin.length }]"
|
|
></span>
|
|
</div>
|
|
<div class="pin-keypad">
|
|
<div
|
|
v-for="n in 9"
|
|
:key="n"
|
|
class="key"
|
|
@click="inputPin(n.toString())"
|
|
>
|
|
{{ n }}
|
|
</div>
|
|
<div class="key" @click="clearPin">重输</div>
|
|
<div class="key" @click="inputPin('0')">0</div>
|
|
<div class="key" @click="submitPin">确定</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
import { login, getUserList } from '@/services'
|
|
import { isBoardParamInited } from '@/services'
|
|
import type { User } from '@/types/Index'
|
|
import { useRouter } from 'vue-router'
|
|
import { getServerInfo } from '@/utils/getServerInfo'
|
|
import { createWebSocket, DeviceContextStateMessage } from '@/websocket/socket'
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
const router = useRouter()
|
|
|
|
const stateUrl = getServerInfo('/api/v1/app/ws/state')
|
|
const wsState = createWebSocket(stateUrl.wsUrl)
|
|
|
|
//用户列表
|
|
const userList = ref<User[]>([])
|
|
//获取用户列表
|
|
const getUserListData = async () => {
|
|
const res = await getUserList()
|
|
userList.value = res.data
|
|
}
|
|
|
|
const handleDeviceContextState = (data: DeviceContextStateMessage['data']) => {
|
|
if (data.loginFlag) {
|
|
sessionStorage.setItem('token', JSON.stringify(data.loginUser))
|
|
router.push('/index')
|
|
}
|
|
}
|
|
onMounted(() => {
|
|
wsState.subscribe<DeviceContextStateMessage>(
|
|
'DeviceContext',
|
|
handleDeviceContextState,
|
|
)
|
|
wsState.connect()
|
|
|
|
setTimeout(() => {
|
|
getUserListData()
|
|
}, 500)
|
|
})
|
|
onUnmounted(() => {
|
|
wsState.unsubscribe<DeviceContextStateMessage>(
|
|
'DeviceContext',
|
|
handleDeviceContextState,
|
|
)
|
|
})
|
|
//选中的用户
|
|
const selectedUser = ref<User | null>(null)
|
|
// PIN 相关
|
|
const pin = ref('')
|
|
const maxPinLength = 4
|
|
const loginStatus = ref('请输入您的pin码')
|
|
const isShaking = ref(false) // 控制抖动效果的触发
|
|
// 切换角色时清空PIN
|
|
const selectUser = (user: User) => {
|
|
selectedUser.value = user
|
|
pin.value = ''
|
|
loginStatus.value = '请输入您的pin码'
|
|
}
|
|
|
|
// 输入PIN码
|
|
const inputPin = (key: string) => {
|
|
if (selectedUser.value === null) {
|
|
loginStatus.value = '请选择用户'
|
|
return
|
|
}
|
|
if (pin.value.length >= maxPinLength) {
|
|
return
|
|
}
|
|
pin.value += key
|
|
if (pin.value.length === maxPinLength) {
|
|
return
|
|
}
|
|
}
|
|
|
|
// 重置PIN码
|
|
const clearPin = () => {
|
|
pin.value = ''
|
|
loginStatus.value = '请输入您的pin码'
|
|
}
|
|
|
|
// 点击确认验证PIN码
|
|
// 模拟后端请求验证PIN
|
|
const submitPin = async () => {
|
|
if (selectedUser.value === null) {
|
|
loginStatus.value = '请选择用户'
|
|
return
|
|
}
|
|
if (pin.value.length !== maxPinLength) {
|
|
loginStatus.value = '请输入密码'
|
|
return
|
|
}
|
|
let resData = await isBoardParamInited()
|
|
if (!resData.data) {
|
|
//设备正在初始化
|
|
ElMessage.warning('设备正在初始化,请稍候...')
|
|
return
|
|
}
|
|
const params = {
|
|
id: selectedUser.value?.id,
|
|
password: pin.value,
|
|
}
|
|
const res = await login(params)
|
|
if (res.success) {
|
|
loginStatus.value = '登录成功'
|
|
sessionStorage.setItem('token', JSON.stringify(res.data))
|
|
await router.push('/index')
|
|
} else {
|
|
loginStatus.value = res.info
|
|
pin.value = ''
|
|
}
|
|
}
|
|
// 监听 loginStatus 变化并触发抖动效果
|
|
watch(loginStatus, () => {
|
|
isShaking.value = true
|
|
setTimeout(() => {
|
|
isShaking.value = false
|
|
}, 500) // 动画持续时间为0.5秒
|
|
})
|
|
</script>
|
|
<style scoped lang="less">
|
|
.login-container {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background: linear-gradient(to bottom, #a1d0f5, #bce0fe); // 垂直线性渐变
|
|
padding: 15px; // 减小padding
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.sidebar {
|
|
width: 220px;
|
|
height: 80vh;
|
|
background-color: #e1f0fb;
|
|
border-radius: 20px;
|
|
padding: 20px 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
|
margin-right: 20px;
|
|
overflow: auto;
|
|
|
|
ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.user {
|
|
padding: 12px 0;
|
|
font-size: 24px;
|
|
color: #666;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
border-radius: 10px;
|
|
margin: 10px 0;
|
|
|
|
&:hover {
|
|
background-color: #b3d9f9;
|
|
color: #fff;
|
|
}
|
|
}
|
|
|
|
.selected {
|
|
background-color: #53b6f3;
|
|
color: white;
|
|
font-weight: bold;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
}
|
|
}
|
|
|
|
.main-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
.pin-container {
|
|
background-color: #fff;
|
|
border-radius: 20px;
|
|
padding: 30px 40px;
|
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
|
text-align: center;
|
|
|
|
.pin-prompt {
|
|
font-size: 28px;
|
|
color: #333;
|
|
margin-bottom: 20px;
|
|
font-weight: bold;
|
|
|
|
&.shake {
|
|
animation: shake 0.5s ease;
|
|
color: #d9534f; // 抖动时变色
|
|
}
|
|
}
|
|
|
|
.pin-indicators {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin: 15px 0;
|
|
|
|
.pin-dot {
|
|
width: 25px;
|
|
height: 25px;
|
|
background-color: #d5e6f2;
|
|
border-radius: 50%;
|
|
margin: 0 10px;
|
|
transition: background-color 0.3s;
|
|
|
|
&.filled {
|
|
background-color: #53b6f3;
|
|
}
|
|
}
|
|
}
|
|
|
|
.pin-keypad {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 20px;
|
|
margin-top: 30px;
|
|
|
|
.key {
|
|
width: 90px;
|
|
height: 90px;
|
|
background-color: #f0f8ff;
|
|
border-radius: 15px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 32px;
|
|
color: #333;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
|
|
|
|
&:hover {
|
|
background-color: #53b6f3;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.login-status {
|
|
margin-top: 20px;
|
|
font-size: 22px;
|
|
color: #d9534f;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|