fix: 登录、锁屏加验证码

zcsf
TangShanDD 2025-09-09 14:55:00 +08:00
parent 0f9f99063e
commit 96dd695a72
4 changed files with 209 additions and 30 deletions

View File

@ -76,3 +76,6 @@ export function updateSystemConfigs(params) {
export function testLinkApi(category) {
return request.get('/sms/v1/configs/test', { params: { category } })
}
export function getVertifyCode(params) {
return request.post('/sms/v1/users/code', wrapperParams(params))
}

View File

@ -5,23 +5,29 @@
<p class="account">{{ userData.name }}</p>
<a-form :model="loginForm" ref="loginFormRef" @keyup.enter.prevent="handleLogin" @submit.prevent>
<a-form-item name="password" :rules="[{ ...required, message: '请输入密码' }]">
<a-input-password v-model:value="loginForm.password" placeholder="请输入密码">
<template #addonAfter>
<a-button class="text-white login-btn" @click="handleLogin()" :loading="loading">
<template #icon>
<ArrowRightOutlined />
</template>
</a-button>
</template>
</a-input-password>
<a-input-password v-model:value="loginForm.password" placeholder="请输入密码"></a-input-password>
</a-form-item>
<a-form-item name="verify" :rules="[{ ...required, message: '请输入验证码' }]">
<a-input v-model:value="loginForm.verify" placeholder="请输入验证码" style="width: 65%; vertical-align: middle"></a-input>
<a-button style="height: 32px; width: 33%; margin-left: 2%" @click="getVertify" :loading="verifyLoading" :disabled="isCountingDown">
{{ isCountingDown ? `${countdown}s后重新获取` : '获取验证码' }}
</a-button>
</a-form-item>
</a-form>
<a-button class="switch-button" type="text" @click="switchUser()">
<template #icon>
<LeftOutlined />
</template>
切换账户
</a-button>
<div class="button-group">
<a-button class="switch-button" type="text" @click="switchUser()">
<template #icon>
<LeftOutlined />
</template>
切换账户
</a-button>
<a-button class="login-btn" type="primary" @click="handleLogin()" :loading="loading">
<template #icon>
<ArrowRightOutlined />
</template>
登录
</a-button>
</div>
</div>
<div class="date-time">
<div class="time">{{ currentTime.time }}</div>
@ -36,6 +42,7 @@ import { encrypt } from 'utils/crypto'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { login } from 'services'
import { getVertifyCode } from '@/services'
import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, toRefs } from 'vue'
import setLoginData from './tools'
import { required } from '@/validate'
@ -47,23 +54,52 @@ export default defineComponent({
const state = reactive({
loginForm: {
password: '',
verify: ''
},
loading: false,
verifyLoading: false,
countdown: 0,
isCountingDown: false
})
let timer: any = 0
let countdownTimer: any = null
const store = useStore()
const userData = computed(() => store.getters.userData || {})
//
const startCountdown = () => {
state.countdown = 60
state.isCountingDown = true
countdownTimer = setInterval(() => {
state.countdown--
if (state.countdown <= 0) {
clearInterval(countdownTimer)
state.isCountingDown = false
state.countdown = 0
}
}, 1000)
}
//
const resetCountdown = () => {
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
state.isCountingDown = false
state.countdown = 0
}
//
const currentTime = ref({
time: '',
date: '',
date: ''
})
function setTimer() {
const getTime = () => {
const time = dayjs()
currentTime.value = {
time: time.format('HH:mm:ss'),
date: `${time.format('MM月DD日')}, ${time.format('dddd')}`,
date: `${time.format('MM月DD日')}, ${time.format('dddd')}`
}
}
getTime()
@ -73,7 +109,7 @@ export default defineComponent({
}
let lockData = {
isLock: false,
path: '',
path: ''
}
onMounted(() => {
const lockDataItem = localStorage.getItem('lockData')
@ -83,7 +119,7 @@ export default defineComponent({
'lockData',
JSON.stringify({
...lockData,
isLock: true,
isLock: true
})
)
setTimer()
@ -94,6 +130,26 @@ export default defineComponent({
function switchUser() {
store.dispatch('permission/ResetRoutes')
}
//
const getVertify = () => {
if (state.isCountingDown) return //
if (!userData.value.account) {
return //
}
state.verifyLoading = true
getVertifyCode({ account: userData.value.account })
.then((res) => {
if (res.success) {
state.verifyLoading = false
startCountdown() //
}
})
.finally(() => {
state.verifyLoading = false
})
}
const loginFormRef = ref()
const router = useRouter()
async function handleLogin() {
@ -104,6 +160,7 @@ export default defineComponent({
account: userData.value.account,
password: encrypt(values.password),
isManager: true,
code: values.verify
})
if (res.success) {
setLoginData(res.data)
@ -113,7 +170,7 @@ export default defineComponent({
'lockData',
JSON.stringify({
...lockData,
isLock: false,
isLock: false
})
)
}
@ -130,8 +187,11 @@ export default defineComponent({
required,
switchUser,
handleLogin,
getVertify,
startCountdown,
resetCountdown
}
},
}
})
</script>
@ -149,7 +209,7 @@ export default defineComponent({
text-align: center;
top: 40%;
left: 50%;
width: 300px;
width: 350px;
margin: -150px 0 0 -150px;
.logo {
display: inline-block;
@ -165,15 +225,55 @@ export default defineComponent({
color: #fff;
margin: 15px 0 25px 0;
}
.button-group {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
.login-btn {
background: #6b7485;
background: #1890ff;
border: none;
width: 48px;
padding: 0 20px;
}
.login-btn:hover {
background: #40a9ff;
}
::v-deep(.ant-input-group-addon) {
border: none;
padding: 0;
}
::v-deep(.ant-input) {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #fff;
}
::v-deep(.ant-input-password) {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #fff;
}
::v-deep(.ant-input::placeholder) {
color: rgba(255, 255, 255, 0.6);
}
::v-deep(.ant-input-password .ant-input) {
background: transparent;
border: none;
color: #fff;
}
::v-deep(.ant-input-password .ant-input::placeholder) {
color: rgba(255, 255, 255, 0.6);
}
::v-deep(.ant-btn) {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #fff;
}
::v-deep(.ant-btn:disabled) {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 0.4);
}
}
.date-time {
position: absolute;

View File

@ -26,6 +26,17 @@
</a-input-password>
</a-form-item>
</a-tooltip>
<a-form-item class="login-form-item" name="verify" :rules="[{ ...required, message: '请输入验证码' }]">
<a-input placeholder="请输入验证码" autocomplete="off" v-model:value="loginForm.verify" style="width: 67%; vertical-align: middle">
<template #prefix>
<LockOutlined />
</template>
</a-input>
<div class="login-code" v-if="identifyCode"></div>
<a-button v-else style="height: 60px; width: 31%; margin-left: 2%" @click="getVertify" :loading="verifyLoading" :disabled="isCountingDown || !loginForm.account.trim()">
{{ isCountingDown ? `${countdown}s后重新获取` : '获取验证码' }}
</a-button>
</a-form-item>
<a-form-item class="login-form-item">
<div class="operate-region">
<span class="remember">
@ -51,6 +62,7 @@ import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'
import setLoginData from './tools'
import { required } from '@/validate'
import { getVertifyCode } from '@/services'
export default {
components: { UserOutlined, LockOutlined },
@ -59,14 +71,43 @@ export default {
remember: false,
loginForm: {
account: '',
password: '',
password: ''
},
loading: false,
capsTooltip: false,
verifyLoading: false,
countdown: 0,
isCountingDown: false
})
const store = useStore()
const configs = computed(() => store.getters.pageConfig)
const loginFormRef = ref(null)
let countdownTimer = null
//
const startCountdown = () => {
state.countdown = 60
state.isCountingDown = true
countdownTimer = setInterval(() => {
state.countdown--
if (state.countdown <= 0) {
clearInterval(countdownTimer)
state.isCountingDown = false
state.countdown = 0
}
}, 1000)
}
//
const resetCountdown = () => {
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
state.isCountingDown = false
state.countdown = 0
}
const init = () => {
const local = localStorage.getItem('cmcLoginData')
if (local) {
@ -79,12 +120,33 @@ export default {
init()
const router = useRouter()
const route = useRoute()
const getVertify = () => {
if (state.isCountingDown) return //
if (!state.loginForm.account.trim()) {
// 使 message
return //
}
state.verifyLoading = true
getVertifyCode({ account: state.loginForm.account })
.then((res) => {
if (res.success) {
state.verifyLoading = false
state.hasVertify = res.data
state.identifyCode = res.data
startCountdown() //
}
})
.finally(() => {
state.verifyLoading = false
})
}
function goLogin(data) {
setLoginData(data)
if (state.remember) {
const obj = {
account: state.loginForm.account,
password: encrypt(state.loginForm.password),
password: encrypt(state.loginForm.password)
}
localStorage.setItem('cmcLoginData', JSON.stringify(obj))
} else {
@ -100,16 +162,19 @@ export default {
try {
state.loading = true
const values = await loginFormRef.value.validate()
const { account, password } = values
const { account, password, verify } = values
const res = await login({
account,
password: encrypt(password),
isManager: true,
code: verify
})
if (res.success) {
goLogin(res.data)
}
} catch (error) {}
} catch (error) {
console.error('登录失败:', error)
}
state.loading = false
}
@ -132,8 +197,11 @@ export default {
loginFormRef,
handleLogin,
checkCapslock,
getVertify,
startCountdown,
resetCountdown
}
},
}
}
</script>
@ -225,6 +293,14 @@ export default {
}
}
}
//
.ant-btn:disabled {
background-color: #f5f5f5;
border-color: #d9d9d9;
color: #999;
cursor: not-allowed;
}
}
}
.copyright-info {

View File

@ -12,7 +12,7 @@ function resolve(dir: string) {
}
const httpType = 'https://'
const proxyUrl = '10.10.33.172:60006' // EFC 3.0 代理地址设置
const proxyUrl = '10.10.33.173:60006' // 中车代理地址
// https://vitejs.dev/config/
export default defineConfig({