feat(global-header): 重构全局头部组件及样式优化

- 完全重写 GlobalHeader 组件结构,改用 div 语义及自定义样式替代原antd a-menu
- 新增 Logo 区域包含图标和标题,支持点击跳转首页功能
- 实现导航菜单动态渲染,根据路由权限过滤显示
- 用户区域支持未登录和已登录两种状态切换
- 未登录时展示登录、注册按钮,支持路由跳转
- 已登录时显示用户头像、用户名及下拉菜单,包含个人中心、设置和退出登录操作
- 引入 Arco Design 图标组件优化视觉表现
- 完善登出流程,清理本地Token并提示用户
- 优化响应式布局和交互体验,提升用户界面整体一致性与可用性
This commit is contained in:
2025-12-14 16:19:28 +08:00
parent a13dae85b3
commit 05669a6570
24 changed files with 17378 additions and 115 deletions

View File

@@ -1,41 +1,211 @@
<template>
<div id="userLoginView">
<h2 style="margin-bottom: 16px">用户登录</h2>
<a-form
style="max-width: 480px; margin: 0 auto"
label-align="left"
auto-label-width
:model="form"
@submit="handleSubmit"
>
<a-form-item field="userAccount" label="账号">
<a-input v-model="form.userAccount" placeholder="请输入账号" />
</a-form-item>
<a-form-item field="userPassword" tooltip="密码不少于 8 位" label="密码">
<a-input-password
v-model="form.userPassword"
placeholder="请输入密码"
/>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit" style="width: 120px">
登录
</a-button>
</a-form-item>
</a-form>
<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 } from "vue";
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";
import { setTokens } from "@/utils/token";
import { useUserStore } from "@/store/user";
import ACCESS_ENUM from "@/access/accessEnum";
const router = useRouter();
const userStore = useUserStore();
const form = reactive({
userAccount: "",
userPassword: "",
});
const handleSubmit = async () => {
console.log(form);
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 (response.success) {
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></style>
<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>