213 lines
4.8 KiB
Vue
213 lines
4.8 KiB
Vue
<template>
|
||
<div id="userLoginView">
|
||
<div class="login-container">
|
||
<div class="login-card">
|
||
<h2 class="login-title">欢迎登录 AI OJ</h2>
|
||
<p class="login-subtitle">AI Online Judge By MeowRain</p>
|
||
|
||
<a-form
|
||
:model="form"
|
||
:rules="rules"
|
||
@submit="handleSubmit"
|
||
layout="vertical"
|
||
class="login-form"
|
||
>
|
||
<a-form-item field="userAccount" label="账号" validate-trigger="blur">
|
||
<a-input
|
||
v-model="form.userAccount"
|
||
placeholder="请输入账号"
|
||
size="large"
|
||
allow-clear
|
||
>
|
||
<template #prefix>
|
||
<icon-user />
|
||
</template>
|
||
</a-input>
|
||
</a-form-item>
|
||
|
||
<a-form-item field="userPassword" label="密码" validate-trigger="blur">
|
||
<a-input-password
|
||
v-model="form.userPassword"
|
||
placeholder="请输入密码"
|
||
size="large"
|
||
allow-clear
|
||
>
|
||
<template #prefix>
|
||
<icon-lock />
|
||
</template>
|
||
</a-input-password>
|
||
</a-form-item>
|
||
|
||
<a-form-item>
|
||
<div class="form-actions">
|
||
<a-checkbox v-model="rememberMe">记住我</a-checkbox>
|
||
<a-link>忘记密码?</a-link>
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item>
|
||
<a-button
|
||
type="primary"
|
||
html-type="submit"
|
||
long
|
||
size="large"
|
||
:loading="loading"
|
||
>
|
||
登录
|
||
</a-button>
|
||
</a-form-item>
|
||
|
||
<a-form-item>
|
||
<div class="register-link">
|
||
还没有账号?
|
||
<a-link @click="goToRegister">立即注册</a-link>
|
||
</div>
|
||
</a-form-item>
|
||
</a-form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, ref } from "vue";
|
||
import { useRouter } from "vue-router";
|
||
import { Message } from "@arco-design/web-vue";
|
||
import { IconUser, IconLock } from "@arco-design/web-vue/es/icon";
|
||
import { login } from "@/api/auth/auth";
|
||
import { setTokens } from "@/utils/token";
|
||
import { useUserStore } from "@/store/user";
|
||
import ACCESS_ENUM from "@/access/accessEnum";
|
||
import { isApiSuccess } from "@/api/response";
|
||
|
||
const router = useRouter();
|
||
const userStore = useUserStore();
|
||
|
||
const form = reactive({
|
||
userAccount: "",
|
||
userPassword: "",
|
||
});
|
||
|
||
const rules = {
|
||
userAccount: [
|
||
{ required: true, message: "请输入账号" },
|
||
{ minLength: 4, message: "账号不能少于4位" },
|
||
],
|
||
userPassword: [
|
||
{ required: true, message: "请输入密码" },
|
||
{ minLength: 8, message: "密码不能少于8位" },
|
||
],
|
||
};
|
||
|
||
const loading = ref(false);
|
||
const rememberMe = ref(false);
|
||
|
||
const handleSubmit = async (data: any) => {
|
||
if (data.errors) {
|
||
return;
|
||
}
|
||
|
||
loading.value = true;
|
||
|
||
try {
|
||
const response = await login({
|
||
userAccount: form.userAccount,
|
||
userPassword: form.userPassword,
|
||
});
|
||
|
||
if (isApiSuccess(response)) {
|
||
Message.success("登录成功!");
|
||
|
||
// 存储 token
|
||
setTokens(response.data.accessToken, response.data.refreshToken);
|
||
|
||
// 更新用户状态
|
||
userStore.updateUserLoginStatus({
|
||
userName: response.data.userAccount,
|
||
userRole: ACCESS_ENUM.USER,
|
||
});
|
||
|
||
// 跳转到首页
|
||
router.push("/home");
|
||
} else {
|
||
Message.error(response.message || "登录失败");
|
||
}
|
||
} catch (error: any) {
|
||
console.error("登录失败:", error);
|
||
Message.error(error.response?.data?.message || "登录失败,请稍后重试");
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
const goToRegister = () => {
|
||
router.push("/user/register");
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
#userLoginView {
|
||
min-height: 100vh;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
}
|
||
|
||
.login-container {
|
||
width: 100%;
|
||
max-width: 420px;
|
||
}
|
||
|
||
.login-card {
|
||
background: white;
|
||
border-radius: 16px;
|
||
padding: 40px;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.login-title {
|
||
text-align: center;
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
color: #1d2129;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.login-subtitle {
|
||
text-align: center;
|
||
font-size: 14px;
|
||
color: #86909c;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.login-form {
|
||
:deep(.arco-form-item) {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
:deep(.arco-form-item-label-col) {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
:deep(.arco-form-item-label) {
|
||
font-weight: 500;
|
||
color: #1d2129;
|
||
}
|
||
}
|
||
|
||
.form-actions {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
|
||
.register-link {
|
||
text-align: center;
|
||
color: #86909c;
|
||
font-size: 14px;
|
||
}
|
||
</style>
|