243 lines
6.4 KiB
Vue
243 lines
6.4 KiB
Vue
<template>
|
||
<div class="login-wrapper">
|
||
<div class="login-wrapper__header">
|
||
<h2 class="login-wrapper__title">欢迎回来 👋🏻</h2>
|
||
<p>
|
||
<span style="color: var(--text-tertiary)">登录账号以进入对战</span>
|
||
</p>
|
||
</div>
|
||
<el-form
|
||
ref="formRef"
|
||
:model="loginModel"
|
||
:rules="rules"
|
||
hide-required-asterisk
|
||
class="login-wrapper__form"
|
||
>
|
||
<el-form-item prop="username">
|
||
<el-input
|
||
size="large"
|
||
v-model="loginModel.username"
|
||
placeholder="请输入账号"
|
||
:maxlength="20"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item prop="password">
|
||
<el-input
|
||
v-model="loginModel.password"
|
||
size="large"
|
||
type="password"
|
||
show-password
|
||
placeholder="密码"
|
||
:maxlength="20"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<div class="login-wrapper__actions">
|
||
<el-checkbox v-model="rememberAccount">记住账号</el-checkbox>
|
||
<a href="javascript:void(0)" style="color: var(--text-link)">忘记密码?</a>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button :loading="loading" style="width: 100%" type="primary" @click="userLoginFun"
|
||
>登录
|
||
</el-button>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<div class="login-wrapper__quick-btns">
|
||
<el-button class="login-wrapper__quick-btn" @click="router.push('/auth/phone-login')">
|
||
手机登录
|
||
</el-button>
|
||
<el-button class="login-wrapper__quick-btn" @click="router.push('/auth/qrcode-login')">
|
||
扫码登录
|
||
</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<div class="login-wrapper__register-link">
|
||
<span>没有账号?</span>
|
||
<router-link to="/auth/register" style="color: var(--text-link)">创建账号</router-link>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, ref, useTemplateRef, onMounted } from 'vue'
|
||
import type { FormRules, FormInstance } from 'element-plus'
|
||
import { ElMessage } from 'element-plus'
|
||
import { useAccountStore } from '@/stores'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
|
||
/** 记住账号的 localStorage key */
|
||
const REMEMBER_KEY = 'login_remembered_username'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const accountStore = useAccountStore()
|
||
const formRef = useTemplateRef<FormInstance>('formRef')
|
||
const loginModel = reactive({
|
||
username: '',
|
||
password: ''
|
||
})
|
||
const loading = ref(false)
|
||
/** 是否记住账号 */
|
||
const rememberAccount = ref(false)
|
||
|
||
/** 页面初始化时恢复已记住的账号 */
|
||
onMounted(() => {
|
||
const savedUsername = localStorage.getItem(REMEMBER_KEY)
|
||
if (savedUsername) {
|
||
loginModel.username = savedUsername
|
||
rememberAccount.value = true
|
||
}
|
||
})
|
||
|
||
/** 账号验证规则 */
|
||
const validateUsername = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
|
||
if (!value) {
|
||
callback(new Error('请输入登陆账号'))
|
||
} else if (value.length < 2) {
|
||
callback(new Error('账号长度不能小于2位'))
|
||
} else if (value.length > 20) {
|
||
callback(new Error('账号长度不能大于20位'))
|
||
} else if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) {
|
||
callback(new Error('账号以字母开头,只能包含字母和数字'))
|
||
} else {
|
||
callback()
|
||
}
|
||
}
|
||
|
||
/** 密码验证规则 */
|
||
const validatePassword = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
|
||
if (!value) {
|
||
callback(new Error('请输入密码'))
|
||
} else if (value.length < 8) {
|
||
callback(new Error('密码长度不能小于8位'))
|
||
} else if (value.length > 20) {
|
||
callback(new Error('密码长度不能大于20位'))
|
||
} else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value)) {
|
||
callback(new Error('密码必须包含大小写字母和数字'))
|
||
} else {
|
||
callback()
|
||
}
|
||
}
|
||
|
||
const rules: FormRules = {
|
||
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
|
||
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
|
||
}
|
||
|
||
/**
|
||
* 获取友好的错误提示信息
|
||
* @param error - 捕获的错误对象
|
||
* @returns 用户可读的错误文本
|
||
*/
|
||
function getErrorMessage(error: unknown): string {
|
||
if (error instanceof Error) {
|
||
// 网络错误
|
||
if (error.message?.includes('Network Error') || error.message?.includes('网络')) {
|
||
return '网络连接失败,请检查网络后重试'
|
||
}
|
||
return error.message
|
||
}
|
||
if (typeof error === 'object' && error !== null) {
|
||
const err = error as Record<string, unknown>
|
||
if (typeof err.message === 'string' && err.message) {
|
||
return err.message
|
||
}
|
||
}
|
||
return '登录失败,请稍后重试'
|
||
}
|
||
|
||
/** 用户登录 */
|
||
const userLoginFun = () => {
|
||
formRef.value?.validate((valid: boolean) => {
|
||
if (!valid) return
|
||
|
||
loading.value = true
|
||
accountStore
|
||
.login({
|
||
username: loginModel.username,
|
||
password: loginModel.password
|
||
})
|
||
.then(() => {
|
||
// 记住账号:将用户名存入 localStorage
|
||
if (rememberAccount.value) {
|
||
localStorage.setItem(REMEMBER_KEY, loginModel.username)
|
||
} else {
|
||
localStorage.removeItem(REMEMBER_KEY)
|
||
}
|
||
|
||
ElMessage.success('登录成功')
|
||
// 跳转到目标页面(有 redirect 参数则优先跳回原页面,否则去首页)
|
||
const redirectPath = (route.query.redirect as string) || '/'
|
||
router.push(redirectPath)
|
||
})
|
||
.catch((error: unknown) => {
|
||
ElMessage.error(getErrorMessage(error))
|
||
})
|
||
.finally(() => {
|
||
loading.value = false
|
||
})
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.login-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.login-wrapper__header {
|
||
width: 100%;
|
||
}
|
||
|
||
.login-wrapper__title {
|
||
font-size: 30px;
|
||
line-height: 36px;
|
||
margin-bottom: 12px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
@media (min-width: 1024px) {
|
||
.login-wrapper__title {
|
||
font-size: 36px;
|
||
}
|
||
}
|
||
|
||
.login-wrapper__form {
|
||
width: 100%;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.login-wrapper__actions {
|
||
display: flex;
|
||
flex: 1;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.login-wrapper__quick-btns {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 1;
|
||
gap: 16px;
|
||
}
|
||
|
||
.login-wrapper__quick-btn {
|
||
flex: 1;
|
||
}
|
||
|
||
.login-wrapper__register-link {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 1;
|
||
}
|
||
</style>
|