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
|