LiLongLong 3 months ago
parent
commit
3ad4958410
  1. 4
      src/App.vue
  2. BIN
      src/assets/avatar.png
  3. 3
      src/pages/Login/Login.vue
  4. 248
      src/pages/Login/index.vue
  5. 2
      src/router/router.ts
  6. 10
      src/style.css

4
src/App.vue

@ -4,9 +4,7 @@ import { ErrorModal, StackInfoModal } from './components/dialogs';
</script>
<template>
<div>
<router-view></router-view>
</div>
<ErrorModal />
<StackInfoModal />
</template>
@ -14,6 +12,8 @@ import { ErrorModal, StackInfoModal } from './components/dialogs';
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
box-sizing: border-box;

BIN
src/assets/avatar.png

After

Width: 120  |  Height: 120  |  Size: 4.0 KiB

3
src/pages/Login/Login.vue

@ -45,8 +45,7 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { login, getUserList } from '@/services'
import { isBoardParamInited } from '@/services'
import { login, getUserList, isBoardParamInited } from '@/services'
import type { User } from '@/types/Index'
import { useRouter } from 'vue-router'
import { getServerInfo } from '@/utils/getServerInfo'

248
src/pages/Login/index.vue

@ -0,0 +1,248 @@
<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'
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 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>

2
src/router/router.ts

@ -78,7 +78,7 @@ const routes = [
},
{
path: '/login',
component: () => import('@/pages/Login/Login.vue'),
component: () => import('@/pages/Login/index.vue'),
},
//匹配任何找不到的路由,404兜底
{

10
src/style.css

@ -54,7 +54,7 @@ body {
margin: 0 auto;
display: flex;
place-items: center;
min-width: 320px;
min-width: 100vw;
min-height: 100vh;
}
@ -72,7 +72,8 @@ button {
}
#app {
max-width: 800px;
width: 100vw;
height: 100%;
margin: 0 auto;
text-align: center;
}
@ -97,3 +98,8 @@ button {
font-size: 0.8em;
}
}
* {
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
Loading…
Cancel
Save