2025-08-29 03:09:49 +08:00

792 lines
18 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-page">
<!-- 背景图片 -->
<div class="background-image">
<img src="/images/loginImage/backImage.png" alt="登录背景" />
</div>
<!-- 左上角logo -->
<div class="top-logo">
<img src="/images/loginImage/logo.png" alt="云岭智教" />
</div>
<!-- 右侧登录区域 -->
<div class="login-area">
<!-- 学员端/教师端切换 -->
<div class="user-type-tabs">
<n-button
:type="activeTab === 'student' ? 'primary' : 'default'"
text
@click="activeTab = 'student'"
class="type-tab"
:class="{ active: activeTab === 'student' }"
>
学员端
</n-button>
<n-button
:type="activeTab === 'teacher' ? 'primary' : 'default'"
text
@click="activeTab = 'teacher'"
class="type-tab"
:class="{ active: activeTab === 'teacher' }"
>
教师端
</n-button>
</div>
<!-- 登录表单 -->
<div class="login-form">
<div class="form-header">
<h2>账号密码登录</h2>
</div>
<n-form
ref="formRef"
:model="formData"
:rules="rules"
size="large"
@submit.prevent="handleSubmit"
>
<n-form-item path="studentId">
<template #label>
<div class="student-label-container">
<span class="label-text">学号</span>
<div class="input-hint-right">
没有账号<n-button text type="primary" size="small">立即注册</n-button>
</div>
</div>
</template>
<n-input
v-model:value="formData.studentId"
placeholder="2014195268"
class="form-input"
/>
</n-form-item>
<n-form-item path="password" label="密码">
<n-input
v-model:value="formData.password"
placeholder="请输入密码"
type="password"
show-password-on="click"
class="form-input"
/>
</n-form-item>
<div class="form-options">
<n-checkbox v-model:checked="rememberMe" size="small">
下次自动登录
</n-checkbox>
<n-button text type="primary" size="small">
忘记密码
</n-button>
</div>
<n-form-item>
<n-button
type="primary"
size="large"
block
:loading="userStore.isLoading"
attr-type="submit"
class="login-btn"
>
登录
</n-button>
</n-form-item>
</n-form>
<div class="form-footer">
<p class="agreement-text">
登录即同意我们的用户协议
<n-button text type="primary" size="small">服务协议和隐私政策</n-button>
</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useMessage, type FormInst, type FormRules } from 'naive-ui'
import { useUserStore } from '@/stores/user'
import { AuthApi } from '@/api'
const router = useRouter()
const message = useMessage()
const userStore = useUserStore()
const formRef = ref<FormInst | null>(null)
const rememberMe = ref(false)
const activeTab = ref('student') // 当前选中的标签页
// 表单数据
const formData = reactive({
studentId: '',
password: ''
})
// 表单验证规则
const rules: FormRules = {
studentId: [
{
required: true,
message: '请输入学号',
trigger: ['input', 'blur']
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: ['input', 'blur']
},
{
min: 3,
message: '密码长度不能少于3位',
trigger: ['input', 'blur']
}
]
}
// 处理表单提交
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
// 显示加载状态
userStore.isLoading = true
// 调用登录API - 使用学号登录
const response = await AuthApi.login({
username: formData.studentId, // 使用username字段传递学号
password: formData.password
})
if (response.code === 200) {
const { user, token, refreshToken } = response.data
// 保存token到store和本地存储
userStore.token = token
localStorage.setItem('X-Access-Token', token)
localStorage.setItem('token', token)
localStorage.setItem('refreshToken', refreshToken)
// 如果选择了记住我,设置更长的过期时间
if (rememberMe.value) {
localStorage.setItem('rememberMe', 'true')
}
try {
// 登录成功后立即调用用户信息接口获取完整的用户信息
console.log('🔍 登录成功,正在获取用户信息...')
const userInfoResponse = await AuthApi.getUserInfo()
if (userInfoResponse.success && userInfoResponse.result) {
// 将后端用户信息转换为前端格式
const convertedUser = AuthApi.convertBackendUserToUser(userInfoResponse.result)
console.log('🔍 转换后的用户信息:', convertedUser)
console.log('🔍 用户真实姓名:', convertedUser.profile?.realName)
console.log('🔍 用户头像:', convertedUser.avatar)
// 保存转换后的用户信息
userStore.user = convertedUser
localStorage.setItem('user', JSON.stringify(convertedUser))
console.log('✅ 用户信息获取成功并保存到store:', userStore.user)
} else {
// 如果获取用户信息失败,使用登录接口返回的基本用户信息
console.warn('⚠️ 获取用户信息失败,使用登录返回的基本信息')
userStore.user = user
localStorage.setItem('user', JSON.stringify(user))
}
} catch (userInfoError) {
// 如果获取用户信息失败,使用登录接口返回的基本用户信息
console.warn('⚠️ 获取用户信息异常,使用登录返回的基本信息:', userInfoError)
userStore.user = user
localStorage.setItem('user', JSON.stringify(user))
}
message.success('登录成功!')
// 根据用户类型跳转到不同页面
const redirect = router.currentRoute.value.query.redirect as string
if (activeTab.value === 'teacher') {
router.push(redirect || '/teacher')
} else {
router.push(redirect || '/')
}
} else {
message.error(response.message || '登录失败')
}
} catch (error: any) {
console.error('登录失败:', error)
// 处理不同类型的错误
if (error.response?.status === 401) {
message.error('学号或密码错误')
} else if (error.response?.status === 429) {
message.error('登录尝试过于频繁,请稍后再试')
} else if (error.response?.data?.message) {
message.error(error.response.data.message)
} else {
message.error('网络错误,请检查网络连接')
}
} finally {
userStore.isLoading = false
}
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 250px; /* 增加右侧边距 */
}
/* 背景图片 - 居中显示,覆盖整个页面 */
.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
.background-image img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
/* 左上角logo */
.top-logo {
position: absolute;
top: 32px;
left: 48px;
z-index: 10;
}
.top-logo img {
height: 48px;
width: auto;
}
/* 右侧登录区域 */
.login-area {
position: relative;
width: 516px;
min-height: 520px; /* 降低高度 */
z-index: 10;
background: transparent; /* 透明背景 */
display: flex;
flex-direction: column;
justify-content: center;
}
/* 用户类型切换标签 */
.user-type-tabs {
display: flex;
justify-content: center;
gap: 40px; /* 增加按钮间距 */
margin-bottom: 40px;
position: relative;
}
.type-tab {
width: 84px;
height: 40px;
font-family: PingFangSC, PingFang SC;
font-weight: 400; /* 未点击状态字重 */
font-size: 24px;
color: #000000; /* 未点击状态颜色 */
line-height: 40px;
text-align: center;
font-style: normal;
padding: 0;
border: none;
background: transparent;
position: relative;
transition: all 0.3s ease;
cursor: pointer;
}
/* 激活状态样式 */
.type-tab.active {
font-weight: 500; /* 点击状态字重 */
color: #0288D1; /* 点击状态颜色 */
}
/* 激活状态底部横线 */
.type-tab.active::after {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 3px;
background: #0288D1;
border-radius: 2px;
}
/* 悬停效果 */
.type-tab:hover {
color: #0288D1;
}
/* 登录表单容器 */
.login-form {
background: rgba(255,255,255,0.5);
padding: 40px 35px; /* 减少内边距 */
border-radius: 12px; /* 添加适度的圆角 */
border: 2px solid #FFFFFF;
max-height: 630px; /* 限制最大高度 */
}
/* 表单样式 */
.form-header {
margin-bottom: 32px;
text-align: left;
}
.form-header h2 {
font-size: 20px;
font-weight: 600;
color: #333;
margin: 0;
}
:deep(.n-form-item-label) {
width: 48px;
height: 32px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px; /* 减小字体大小 */
color: #000000;
line-height: 32px;
text-align: left;
font-style: normal;
}
/* 隐藏必填字段的星号 */
:deep(.n-form-item-label__asterisk) {
display: none;
}
/* 学号标签行样式 */
:deep(.n-input) {
width: 520px;
height: 48x; /* 恢复到64px高度 */
background: #F5F8FB !important; /* 设置背景色 */
border-radius: 2px;
border: none !important; /* 移除边框 */
}
:deep(.n-input:hover) {
border: none !important; /* 悬停时也无边框 */
background: #F5F8FB !important; /* 保持背景色 */
}
:deep(.n-input.n-input--focus) {
border: none !important; /* 聚焦时也无边框 */
box-shadow: none !important; /* 移除聚焦阴影 */
background: #F5F8FB !important; /* 保持背景色 */
}
/* 输入框内部元素样式 */
:deep(.n-input__input-el) {
padding: 12px 16px;
font-size: 14px;
height: 48px; /* 与容器高度一致 */
line-height: 40px; /* 调整行高让文字垂直居中 */
background: transparent !important;
background-color: transparent !important;
}
:deep(.n-input__input) {
background: transparent !important;
background-color: transparent !important;
}
:deep(.n-input-wrapper) {
background: transparent !important;
background-color: transparent !important;
border: none !important;
}
:deep(.n-input__state-border) {
display: none !important; /* 完全隐藏状态边框 */
}
/* 密码输入框特殊处理 */
:deep(.n-input--password) {
border: none !important;
background: #F5F8FB !important;
}
:deep(.n-input--password:hover) {
border: none !important;
background: #F5F8FB !important;
}
:deep(.n-input--password.n-input--focus) {
border: none !important;
box-shadow: none !important;
background: #F5F8FB !important;
}
/* 移除所有可能的边框元素 */
:deep(.n-input__border),
:deep(.n-input__state-border),
:deep(.n-base-suffix__border),
:deep(.n-base-selection__border) {
display: none !important;
border: none !important;
}
.form-input {
margin-bottom: 8px;
}
/* 表单项间距调整 */
:deep(.n-form-item) {
margin-bottom: 16px; /* 表单项之间16px间距 */
}
:deep(.n-form-item-label) {
margin-bottom: 16px; /* 标签和输入框之间16px间距 */
}
/* 密码表单项特殊间距 */
:deep(.n-form-item:nth-child(2)) {
margin-top: -12px; /* 减少密码项与上方的距离 */
}
.input-hint {
height: 28px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 15px;
color: #999999;
line-height: 28px;
font-style: normal;
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: auto; /* 自动推到最右侧 */
flex-shrink: 0; /* 防止被压缩 */
}
/* 学号标签容器 */
.student-label-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
min-width: 400px; /* 确保有足够宽度 */
}
/* 学号标签文字 */
.label-text {
width: 48px;
height: 32px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 18px;
color: #000000;
line-height: 32px;
text-align: left;
font-style: normal;
flex-shrink: 0;
}
/* 右侧注册提示 */
.input-hint-right {
height: 28px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 15px;
color: #999999;
line-height: 28px;
font-style: normal;
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
}
/* 立即注册按钮样式 */
.input-hint-right :deep(.n-button) {
font-size: 15px !important;
padding: 0 !important;
margin-left: 4px;
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: -24px; /* 进一步向上移动 */
margin-bottom: 20px; /* 与下方登录按钮的间距 */
width: 400px; /* 增加宽度,让两个元素有合适间距 */
height: 28px; /* 固定高度 */
padding: 0; /* 移除内边距 */
box-sizing: border-box; /* 确保宽度包含边框 */
white-space: nowrap; /* 防止整个容器内容换行 */
}
/* 下次自动登录样式 */
:deep(.n-checkbox) {
width: auto; /* 改为自动宽度 */
height: 28px;
white-space: nowrap; /* 防止换行 */
flex-shrink: 0; /* 防止被压缩 */
display: flex;
align-items: center; /* 垂直居中对齐 */
}
/* 勾选框本身的对齐和高度 */
:deep(.n-checkbox .n-checkbox__input) {
align-self: flex-start; /* 改为顶部对齐 */
margin-top: 2px; /* 微调位置让它与文字基线对齐 */
}
/* 勾选框图标的高度调整 */
:deep(.n-checkbox .n-checkbox-box) {
height: 16px !important; /* 调整勾选框图标高度 */
width: 16px !important; /* 调整勾选框图标宽度 */
margin-top: 2px; /* 减少向下偏移,让勾选框往上 */
}
:deep(.n-checkbox .n-checkbox__label) {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 16px; /* 调整为16px */
color: #999999;
line-height: 28px;
text-align: left;
font-style: normal;
text-transform: none;
white-space: nowrap; /* 防止标签文字换行 */
display: flex;
align-items: center; /* 文字垂直居中 */
}
/* 忘记密码按钮样式 */
.form-options :deep(.n-button) {
width: 91px;
height: 28px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 16px !important; /* 调整为16px */
color: #0288D1 !important;
line-height: 28px;
text-align: left;
font-style: normal;
text-transform: none;
padding: 0 !important;
margin: 0 !important;
flex-shrink: 0; /* 防止按钮被压缩 */
}
.login-btn {
width: 420px;
height: 56px; /* 调整为56px */
background: #0288D1;
border: none;
border-radius: 6px; /* 添加轻微圆角 */
font-family: AppleSystemUIFont;
font-size: 22px;
color: #FFFFFF;
line-height: 39px;
text-align: center; /* 改为居中对齐 */
font-style: normal;
text-transform: none;
font-weight: 500;
margin-top: 20px;
transition: background-color 0.3s ease;
}
:deep(.login-btn:hover) {
background: #40a9ff;
}
.form-footer {
text-align: left; /* 改为左对齐 */
margin-top: -25px; /* 进一步减少与登录按钮的距离 */
}
.agreement-text {
width: 420px;
height: 28px;
font-family: AppleSystemUIFont;
font-size: 16px; /* 调整为16px */
color: #999999;
line-height: 28px;
text-align: left;
font-style: normal;
text-transform: none;
margin: 0;
}
/* 协议链接按钮样式 */
.agreement-text :deep(.n-button) {
font-size: 16px !important; /* 确保按钮字体也是16px */
padding: 0 !important;
margin-left: 4px;
}
/* 响应式设计 */
@media (max-width: 1400px) {
.login-page {
padding-right: 180px; /* 调整右边距 */
}
.login-area {
width: 500px;
}
}
@media (max-width: 1200px) {
.login-page {
padding-right: 120px; /* 调整右边距 */
}
.login-area {
width: 450px;
}
}
@media (max-width: 1024px) {
.login-page {
justify-content: center;
padding-right: 0;
}
.login-area {
width: 400px;
max-width: 90vw;
}
.background-image {
position: absolute;
width: 100%;
height: 100%;
}
.top-logo {
top: 20px;
left: 24px;
}
.user-type-tabs {
justify-content: center;
gap: 32px; /* 中等屏幕间距 */
}
.type-tab {
font-size: 24px;
width: 70px;
}
}
@media (max-width: 768px) {
.login-page {
justify-content: center;
padding-right: 0;
}
.login-area {
width: 350px;
max-width: 85vw;
}
.login-form {
padding: 36px 24px;
}
.top-logo {
top: 16px;
left: 20px;
}
.top-logo img {
height: 36px;
}
.user-type-tabs {
margin-bottom: 30px;
gap: 28px; /* 小屏幕间距 */
}
.type-tab {
font-size: 20px;
width: 60px;
height: 35px;
line-height: 35px;
}
.type-tab.active::after {
width: 30px;
height: 2px;
}
}
@media (max-width: 480px) {
.login-page {
justify-content: center;
padding-right: 0;
}
.login-area {
width: 300px;
max-width: 90vw;
}
.login-form {
padding: 32px 20px;
}
.form-header h2 {
font-size: 18px;
}
.top-logo {
top: 12px;
left: 16px;
}
.type-tab {
font-size: 18px;
width: 55px;
height: 32px;
line-height: 32px;
}
.type-tab.active::after {
width: 25px;
height: 2px;
}
}
</style>