fix:新登录页面初步实现

This commit is contained in:
小张 2025-08-28 03:26:25 +08:00
parent 2c27fe8730
commit 8427f0ec82
6 changed files with 386 additions and 131 deletions

View File

@ -4,3 +4,4 @@ type: "manual"
1、在接下来的每一个步骤当中请帮我实现对页面的响应式设计
2、必须严格执行我给你的指令一步一步执行不得有缩减
3、我们用的是naive UI组件 TSvue3

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { RouterView } from 'vue-router'
import { onMounted, computed } from 'vue'
import { RouterView, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
import AppLayout from '@/components/layout/AppLayout.vue'
import { NConfigProvider } from 'naive-ui'
import { NConfigProvider, NMessageProvider } from 'naive-ui'
import type { GlobalThemeOverrides } from 'naive-ui';
@ -58,6 +58,10 @@ const themeOverrides: GlobalThemeOverrides = {
};
const userStore = useUserStore()
const route = useRoute()
//
const isLoginPage = computed(() => route.name === 'Login')
onMounted(() => {
//
@ -68,9 +72,18 @@ onMounted(() => {
<template>
<div id="app">
<n-config-provider :theme-overrides="themeOverrides">
<AppLayout>
<RouterView />
</AppLayout>
<!-- 登录页面不使用 AppLayout但需要 message provider -->
<template v-if="isLoginPage">
<n-message-provider>
<RouterView />
</n-message-provider>
</template>
<!-- 其他页面使用 AppLayout -->
<template v-else>
<AppLayout>
<RouterView />
</AppLayout>
</template>
</n-config-provider>
</div>
</template>

View File

@ -363,6 +363,14 @@ const routes: RouteRecordRaw[] = [
meta: { title: 'AI伴学' }
},
// 登录页面
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { title: '登录' }
},
// 首页与课程
{
path: '/service-agreement',

View File

@ -1,10 +1,43 @@
<template>
<div class="login-page">
<div class="login-container">
<!-- 背景图片 -->
<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">
<h1>登录</h1>
<p>欢迎回到在线学习平台</p>
<h2>账号密码登录</h2>
</div>
<n-form
@ -14,18 +47,15 @@
size="large"
@submit.prevent="handleSubmit"
>
<n-form-item path="email" label="邮箱">
<n-form-item path="studentId" label="学号">
<n-input
v-model:value="formData.email"
placeholder="请输入邮箱地址"
type="email"
>
<template #prefix>
<n-icon>
<MailOutline />
</n-icon>
</template>
</n-input>
v-model:value="formData.studentId"
placeholder="2014195268"
class="form-input"
/>
<div class="input-hint">
没有账号<n-button text type="primary" size="small">立即注册</n-button>
</div>
</n-form-item>
<n-form-item path="password" label="密码">
@ -33,22 +63,17 @@
v-model:value="formData.password"
placeholder="请输入密码"
type="password"
show-password-on="mousedown"
>
<template #prefix>
<n-icon>
<LockClosedOutline />
</n-icon>
</template>
</n-input>
show-password-on="click"
class="form-input"
/>
</n-form-item>
<n-form-item>
<div class="form-options">
<n-checkbox v-model:checked="rememberMe">
记住我
<n-checkbox v-model:checked="rememberMe" size="small">
下次自动登录
</n-checkbox>
<n-button text type="primary">
<n-button text type="primary" size="small">
忘记密码
</n-button>
</div>
@ -61,6 +86,7 @@
block
:loading="userStore.isLoading"
attr-type="submit"
class="login-btn"
>
登录
</n-button>
@ -68,45 +94,11 @@
</n-form>
<div class="form-footer">
<p>
还没有账号
<n-button text type="primary" @click="$router.push('/register')">
立即注册
</n-button>
<p class="agreement-text">
登录即同意我们的用户协议
<n-button text type="primary" size="small">服务协议和隐私政策</n-button>
</p>
</div>
<!-- 社交登录 -->
<div class="social-login">
<n-divider>或使用以下方式登录</n-divider>
<n-space justify="center">
<n-button circle size="large">
<n-icon size="20">
<LogoGithub />
</n-icon>
</n-button>
<n-button circle size="large">
<n-icon size="20">
<LogoGoogle />
</n-icon>
</n-button>
<n-button circle size="large">
<n-icon size="20">
<LogoWechat />
</n-icon>
</n-button>
</n-space>
</div>
</div>
<!-- 侧边图片 -->
<div class="login-image">
<PlaceholderImage
:width="600"
:height="800"
text="登录背景图"
icon="🎨"
/>
</div>
</div>
</div>
@ -117,14 +109,6 @@ 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 PlaceholderImage from '@/components/common/PlaceholderImage.vue'
import {
MailOutline,
LockClosedOutline,
LogoGithub,
LogoGoogle,
LogoWechat
} from '@vicons/ionicons5'
import { AuthApi } from '@/api'
const router = useRouter()
@ -133,24 +117,20 @@ const userStore = useUserStore()
const formRef = ref<FormInst | null>(null)
const rememberMe = ref(false)
const activeTab = ref('student') //
//
const formData = reactive({
email: '',
studentId: '',
password: ''
})
//
const rules: FormRules = {
email: [
studentId: [
{
required: true,
message: '请输入邮箱地址',
trigger: ['input', 'blur']
},
{
type: 'email',
message: '请输入有效的邮箱地址',
message: '请输入学号',
trigger: ['input', 'blur']
}
],
@ -178,9 +158,9 @@ const handleSubmit = async () => {
//
userStore.isLoading = true
// API
// API - 使
const response = await AuthApi.login({
email: formData.email,
username: formData.studentId, // 使username
password: formData.password
})
@ -231,9 +211,13 @@ const handleSubmit = async () => {
message.success('登录成功!')
//
//
const redirect = router.currentRoute.value.query.redirect as string
router.push(redirect || '/')
if (activeTab.value === 'teacher') {
router.push(redirect || '/teacher')
} else {
router.push(redirect || '/')
}
} else {
message.error(response.message || '登录失败')
}
@ -242,7 +226,7 @@ const handleSubmit = async () => {
//
if (error.response?.status === 401) {
message.error('邮箱或密码错误')
message.error('学号或密码错误')
} else if (error.response?.status === 429) {
message.error('登录尝试过于频繁,请稍后再试')
} else if (error.response?.data?.message) {
@ -259,88 +243,337 @@ const handleSubmit = async () => {
<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;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
z-index: 1;
}
.login-container {
display: grid;
grid-template-columns: 1fr 1fr;
max-width: 1000px;
.background-image img {
width: 100%;
background: white;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
height: 100%;
object-fit: cover;
object-position: center;
}
.login-form {
padding: 60px 40px;
/* 左上角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;
}
.form-header {
text-align: center;
/* 用户类型切换标签 */
.user-type-tabs {
display: flex;
justify-content: center;
gap: 40px; /* 增加按钮间距 */
margin-bottom: 40px;
position: relative;
}
.form-header h1 {
font-size: 2rem;
.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) {
font-weight: 500;
color: #333;
font-size: 14px;
}
:deep(.n-input) {
border-radius: 6px; /* 添加轻微圆角 */
border: 1px solid #e0e0e0;
}
:deep(.n-input:hover) {
border-color: #1890ff;
}
:deep(.n-input.n-input--focus) {
border-color: #1890ff;
box-shadow: none; /* 移除聚焦阴影 */
}
:deep(.n-input__input-el) {
padding: 12px 16px;
font-size: 14px;
}
.form-input {
margin-bottom: 8px;
}
.form-header p {
color: #666;
font-size: 1rem;
.input-hint {
font-size: 12px;
color: #999;
text-align: right;
margin-top: 4px;
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin: 20px 0;
}
:deep(.n-checkbox .n-checkbox__label) {
font-size: 14px;
color: #666;
}
.login-btn {
background: #1890ff;
border: none;
border-radius: 6px; /* 添加轻微圆角 */
height: 48px;
font-size: 16px;
font-weight: 500;
margin-top: 20px;
transition: background-color 0.3s ease;
}
:deep(.login-btn:hover) {
background: #40a9ff;
}
.form-footer {
text-align: center;
margin-top: 24px;
}
.social-login {
margin-top: 32px;
}
.login-image {
background: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
.agreement-text {
font-size: 12px;
color: #999;
margin: 0;
line-height: 1.6;
}
.login-image img {
width: 100%;
height: 100%;
object-fit: cover;
/* 响应式设计 */
@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-container {
grid-template-columns: 1fr;
max-width: 400px;
.login-page {
justify-content: center;
padding-right: 0;
}
.login-image {
display: none;
.login-area {
width: 350px;
max-width: 85vw;
}
.login-form {
padding: 40px 24px;
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>