fix:新登录页面初步实现
This commit is contained in:
parent
2c27fe8730
commit
8427f0ec82
@ -3,4 +3,5 @@ type: "manual"
|
||||
---
|
||||
|
||||
1、在接下来的每一个步骤当中,请帮我实现对页面的响应式设计
|
||||
2、必须严格执行我给你的指令,一步一步执行,不得有缩减
|
||||
2、必须严格执行我给你的指令,一步一步执行,不得有缩减
|
||||
3、我们用的是naive UI组件 ,TS,vue3
|
||||
|
BIN
public/images/loginImage/backImage.png
Normal file
BIN
public/images/loginImage/backImage.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 MiB |
BIN
public/images/loginImage/logo.png
Normal file
BIN
public/images/loginImage/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
25
src/App.vue
25
src/App.vue
@ -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>
|
||||
|
@ -363,6 +363,14 @@ const routes: RouteRecordRaw[] = [
|
||||
meta: { title: 'AI伴学' }
|
||||
},
|
||||
|
||||
// 登录页面
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('@/views/Login.vue'),
|
||||
meta: { title: '登录' }
|
||||
},
|
||||
|
||||
// 首页与课程
|
||||
{
|
||||
path: '/service-agreement',
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user