Files
webgame-frontend/src/views/auth/Login.vue
2026-06-18 11:01:11 +08:00

243 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>