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

277 lines
9.9 KiB
React
Raw Normal View History

2025-12-23 05:02:10 +00:00
import { useState, useEffect } from 'react'
2025-12-20 11:18:59 +00:00
import { useNavigate } from 'react-router-dom'
2026-01-14 03:35:50 +00:00
import { Form, Input, Button, Tabs } from 'antd'
import { UserOutlined, LockOutlined, MailOutlined, ArrowRightOutlined } from '@ant-design/icons'
2025-12-20 11:18:59 +00:00
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')
2025-12-23 05:02:10 +00:00
const [loginForm] = Form.useForm()
2025-12-20 11:18:59 +00:00
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 (
2026-01-14 03:35:50 +00:00
<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>
2025-12-20 11:18:59 +00:00
</div>
2026-01-14 03:35:50 +00:00
</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>
2025-12-20 11:18:59 +00:00
2026-01-14 03:35:50 +00:00
<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>
2025-12-20 11:18:59 +00:00
</div>
</div>
)
}
export default Login