forked from gzt/A8000
1 changed files with 0 additions and 297 deletions
@ -1,297 +0,0 @@ |
|||
<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, 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> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue