nex_docus/frontend/src/pages/Login/Login.jsx

277 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { Form, Input, Button, Tabs } from 'antd'
import { UserOutlined, LockOutlined, MailOutlined, ArrowRightOutlined } from '@ant-design/icons'
import { login, register } from '@/api/auth'
import { getUserMenus } from '@/api/menu'
import useUserStore from '@/stores/userStore'
import Toast from '@/components/Toast/Toast'
import './Login.css'
function Login() {
const [loading, setLoading] = useState(false)
const [activeTab, setActiveTab] = useState('login')
const [loginForm] = Form.useForm()
const navigate = useNavigate()
const { setUser, setToken } = useUserStore()
const handleLogin = async (values) => {
setLoading(true)
try {
const res = await login(values)
Toast.success('登录成功')
// 保存 token 和用户信息
localStorage.setItem('access_token', res.data.access_token)
localStorage.setItem('user_info', JSON.stringify(res.data.user))
setToken(res.data.access_token)
setUser(res.data.user)
// 获取用户菜单并跳转到第一个菜单
try {
const menuRes = await getUserMenus()
if (menuRes.data && menuRes.data.length > 0) {
const firstMenu = menuRes.data[0]
// 如果第一个菜单有子菜单,跳转到第一个子菜单
if (firstMenu.children && firstMenu.children.length > 0) {
navigate(firstMenu.children[0].path)
} else if (firstMenu.path) {
navigate(firstMenu.path)
} else {
// 如果都没有路径,默认跳转到项目列表
navigate('/projects')
}
} else {
// 如果没有菜单,默认跳转到项目列表
navigate('/projects')
}
} catch (menuError) {
console.error('Load menus error:', menuError)
// 如果加载菜单失败,默认跳转到项目列表
navigate('/projects')
}
} catch (error) {
console.error('Login error:', error)
const errorMsg = error.response?.data?.detail || error.message || '登录失败,请检查用户名和密码'
Toast.error('登录失败', errorMsg)
} finally {
setLoading(false)
}
}
const handleRegister = async (values) => {
setLoading(true)
try {
await register(values)
Toast.success('注册成功', '请使用您的账号登录')
setActiveTab('login')
} catch (error) {
console.error('Register error:', error)
const errorMsg = error.response?.data?.detail || error.message || '注册失败'
Toast.error('注册失败', errorMsg)
} finally {
setLoading(false)
}
}
return (
<div className="login-page">
{/* 左侧介绍区域 */}
<div className="login-left">
<div className="login-left-content">
<div className="logo-section">
<div className="logo-icon">
<img src="/favicon.svg" alt="NexDocus" width="42" height="42" />
</div>
<span className="logo-text">NexDocus</span>
</div>
<div className="intro-section">
<h1 className="intro-title">
团队协作
<br />
<span className="highlight">文档管理平台</span>
</h1>
<p className="intro-desc">
全流程文档共享提升团队协作效率的新一代解决方案
</p>
</div>
<div className="footer-info">
<div className="footer-links">
<span>团队知识共享</span>
<span>大模型能力加持</span>
</div>
<p className="copyright">© 2024 NexDocus. All rights reserved.</p>
</div>
</div>
</div>
{/* 右侧登录区域 */}
<div className="login-right">
<div className="login-form-container">
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
items={[
{
key: 'login',
label: '账号登录',
children: (
<div className="form-wrapper">
<p className="form-subtitle">欢迎回来请输入您的登录凭证</p>
<Form
form={loginForm}
name="login"
onFinish={handleLogin}
autoComplete="off"
layout="vertical"
>
<Form.Item
label="用户名"
name="username"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input
prefix={<UserOutlined style={{ color: '#bfbfbf' }} />}
placeholder="请输入用户名"
size="large"
/>
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password
prefix={<LockOutlined style={{ color: '#bfbfbf' }} />}
placeholder="请输入密码"
size="large"
visibilityToggle
/>
</Form.Item>
<Form.Item style={{ marginBottom: '12px' }}>
<Button
type="primary"
htmlType="submit"
loading={loading}
block
size="large"
icon={<ArrowRightOutlined />}
iconPosition="end"
>
立即登录
</Button>
</Form.Item>
</Form>
</div>
)
},
{
key: 'register',
label: '注册账号',
children: (
<div className="form-wrapper">
<p className="form-subtitle">创建您的账号开始使用平台</p>
<Form
name="register"
onFinish={handleRegister}
autoComplete="off"
layout="vertical"
>
<Form.Item
label="用户名"
name="username"
rules={[
{ required: true, message: '请输入用户名' },
{ min: 3, message: '用户名至少3个字符' },
]}
>
<Input
prefix={<UserOutlined style={{ color: '#bfbfbf' }} />}
placeholder="用户名"
size="large"
/>
</Form.Item>
<Form.Item
label="邮箱"
name="email"
rules={[
{ type: 'email', message: '请输入有效的邮箱地址' },
]}
>
<Input
prefix={<MailOutlined style={{ color: '#bfbfbf' }} />}
placeholder="邮箱(选填)"
size="large"
/>
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[
{ required: true, message: '请输入密码' },
{ min: 6, message: '密码至少6个字符' },
]}
>
<Input.Password
prefix={<LockOutlined style={{ color: '#bfbfbf' }} />}
placeholder="密码"
size="large"
/>
</Form.Item>
<Form.Item
label="确认密码"
name="confirm"
dependencies={['password']}
rules={[
{ required: true, message: '请确认密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve()
}
return Promise.reject(new Error('两次输入的密码不一致'))
},
}),
]}
>
<Input.Password
prefix={<LockOutlined style={{ color: '#bfbfbf' }} />}
placeholder="确认密码"
size="large"
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={loading}
block
size="large"
>
注册
</Button>
</Form.Item>
</Form>
</div>
)
}
]}
/>
</div>
</div>
</div>
)
}
export default Login