forked from gzt/A8000
6 changed files with 253 additions and 7 deletions
-
4src/App.vue
-
BINsrc/assets/avatar.png
-
3src/pages/Login/Login.vue
-
241src/pages/Login/index.vue
-
2src/router/router.ts
-
10src/style.css
After Width: 120 | Height: 120 | Size: 4.0 KiB |
@ -0,0 +1,241 @@ |
|||
<script setup lang="ts"> |
|||
import { ref, onMounted } 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' |
|||
|
|||
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() |
|||
}) |
|||
|
|||
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 errorMsg = ref('') |
|||
|
|||
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 = '设备正在初始化,请稍候重试' |
|||
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 { |
|||
errorMsg.value = res.info |
|||
// password.value = '' |
|||
} |
|||
} |
|||
</script> |
|||
<template> |
|||
<div class="login-box"> |
|||
<p class="login-title">登录</p> |
|||
<div class="user-box"> |
|||
<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> |
|||
<div class="password-box"> |
|||
<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> |
|||
<div class="pin-keypad"> |
|||
<div |
|||
v-for="n in 9" |
|||
:key="n" |
|||
class="key" |
|||
@click="inputPin(n.toString())" |
|||
> |
|||
{{ n }} |
|||
</div> |
|||
<div class="key" @click="password = ''">重输</div> |
|||
<div class="key" @click="inputPin('0')">0</div> |
|||
<div class="key" @click="submitPin">确定</div> |
|||
</div> |
|||
<div class="error-box">{{errorMsg}}</div> |
|||
</div> |
|||
</div> |
|||
|
|||
</template> |
|||
<style lang="less" scoped> |
|||
@keyframes iconAnim { |
|||
0% { transform: scale(0.8); opacity: 0; } |
|||
100% { transform: scale(1); opacity: 1; } |
|||
} |
|||
.login-title { |
|||
position: absolute; |
|||
top: 10%; |
|||
font-size: 50px; |
|||
font-weight: bold; |
|||
} |
|||
.login-box { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: #A0CEF2; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
position: relative; |
|||
.user-box { |
|||
width: 35%; |
|||
height: 40%; |
|||
margin-right: 20px; |
|||
background: #fff; |
|||
border-radius: 30px; |
|||
padding: 60px 30px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
.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 { |
|||
margin-left: 20px; |
|||
width: 35%; |
|||
height: 40%; |
|||
background: #fff; |
|||
border-radius: 30px; |
|||
padding: 60px 30px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
} |
|||
.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; |
|||
} |
|||
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue