优化了登录页
parent
72000d5660
commit
ce9ccd70ef
|
|
@ -249,7 +249,7 @@ async def get_document_activity(
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db)
|
db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""获取指定日期的文档操作日志"""
|
"""获取指定日期的文档操作日志(按项目+文件+操作类型聚合)"""
|
||||||
# 解析日期
|
# 解析日期
|
||||||
try:
|
try:
|
||||||
target_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
target_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||||
|
|
@ -284,14 +284,51 @@ async def get_document_activity(
|
||||||
|
|
||||||
logs = result.scalars().all()
|
logs = result.scalars().all()
|
||||||
|
|
||||||
# 构建返回数据,包含项目信息
|
# 操作类型中文映射
|
||||||
logs_data = []
|
operation_map = {
|
||||||
|
OperationType.CREATE_FILE: "创建",
|
||||||
|
OperationType.SAVE_FILE: "保存",
|
||||||
|
OperationType.DELETE_FILE: "删除",
|
||||||
|
OperationType.RENAME_FILE: "重命名",
|
||||||
|
OperationType.MOVE_FILE: "移动",
|
||||||
|
}
|
||||||
|
|
||||||
|
# 聚合日志:按 (project_id, file_path, operation_type) 分组
|
||||||
|
aggregated = {}
|
||||||
|
|
||||||
for log in logs:
|
for log in logs:
|
||||||
# 解析 detail 字段获取文件路径和项目ID
|
# 解析 detail 字段获取文件路径和项目ID
|
||||||
detail = json.loads(log.detail) if log.detail else {}
|
detail = json.loads(log.detail) if log.detail else {}
|
||||||
project_id = detail.get('project_id')
|
project_id = detail.get('project_id')
|
||||||
file_path = detail.get('path') or detail.get('file_path') or detail.get('old_path')
|
file_path = detail.get('path') or detail.get('file_path') or detail.get('old_path')
|
||||||
|
|
||||||
|
# 创建聚合键
|
||||||
|
key = (project_id, file_path, log.operation_type)
|
||||||
|
|
||||||
|
if key not in aggregated:
|
||||||
|
aggregated[key] = {
|
||||||
|
'project_id': project_id,
|
||||||
|
'file_path': file_path,
|
||||||
|
'operation_type': log.operation_type,
|
||||||
|
'count': 0,
|
||||||
|
'first_time': log.created_at,
|
||||||
|
'last_time': log.created_at,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregated[key]['count'] += 1
|
||||||
|
# 更新最早和最晚时间
|
||||||
|
if log.created_at < aggregated[key]['first_time']:
|
||||||
|
aggregated[key]['first_time'] = log.created_at
|
||||||
|
if log.created_at > aggregated[key]['last_time']:
|
||||||
|
aggregated[key]['last_time'] = log.created_at
|
||||||
|
|
||||||
|
# 构建返回数据,包含项目信息
|
||||||
|
logs_data = []
|
||||||
|
for key, agg in aggregated.items():
|
||||||
|
project_id = agg['project_id']
|
||||||
|
file_path = agg['file_path']
|
||||||
|
operation_type = agg['operation_type']
|
||||||
|
|
||||||
# 获取项目信息
|
# 获取项目信息
|
||||||
project_name = None
|
project_name = None
|
||||||
project_storage_key = None
|
project_storage_key = None
|
||||||
|
|
@ -306,29 +343,33 @@ async def get_document_activity(
|
||||||
|
|
||||||
# 检查文件是否存在(仅针对非删除操作)
|
# 检查文件是否存在(仅针对非删除操作)
|
||||||
file_exists = False
|
file_exists = False
|
||||||
if project_storage_key and file_path and log.operation_type != OperationType.DELETE_FILE:
|
if project_storage_key and file_path and operation_type != OperationType.DELETE_FILE:
|
||||||
full_path = os.path.join(settings.PROJECTS_PATH, project_storage_key, file_path)
|
full_path = os.path.join(settings.PROJECTS_PATH, project_storage_key, file_path)
|
||||||
file_exists = os.path.exists(full_path) and os.path.isfile(full_path)
|
file_exists = os.path.exists(full_path) and os.path.isfile(full_path)
|
||||||
|
|
||||||
# 操作类型中文映射
|
# 生成描述文本
|
||||||
operation_map = {
|
operation_text = operation_map.get(operation_type, operation_type)
|
||||||
OperationType.CREATE_FILE: "创建文件",
|
if agg['count'] > 1:
|
||||||
OperationType.SAVE_FILE: "保存文件",
|
description = f"{operation_text} {agg['count']} 次"
|
||||||
OperationType.DELETE_FILE: "删除文件",
|
else:
|
||||||
OperationType.RENAME_FILE: "重命名文件",
|
description = operation_text
|
||||||
OperationType.MOVE_FILE: "移动文件",
|
|
||||||
}
|
|
||||||
|
|
||||||
logs_data.append({
|
logs_data.append({
|
||||||
"id": log.id,
|
"id": f"{project_id}_{file_path}_{operation_type}", # 唯一ID
|
||||||
"operation_type": operation_map.get(log.operation_type, log.operation_type),
|
"operation_type": description,
|
||||||
|
"operation_count": agg['count'],
|
||||||
"project_id": project_id,
|
"project_id": project_id,
|
||||||
"project_name": project_name or "未知项目",
|
"project_name": project_name or "未知项目",
|
||||||
"file_path": file_path or "未知文件",
|
"file_path": file_path or "未知文件",
|
||||||
"file_exists": file_exists,
|
"file_exists": file_exists,
|
||||||
"created_at": log.created_at.isoformat() if log.created_at else None,
|
"first_time": agg['first_time'].isoformat() if agg['first_time'] else None,
|
||||||
"detail": detail,
|
"last_time": agg['last_time'].isoformat() if agg['last_time'] else None,
|
||||||
|
"created_at": agg['last_time'].isoformat() if agg['last_time'] else None, # 用最后操作时间排序
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 按最后操作时间降序排序
|
||||||
|
logs_data.sort(key=lambda x: x['last_time'] if x['last_time'] else '', reverse=True)
|
||||||
|
|
||||||
return success_response(data={"logs": logs_data})
|
return success_response(data={"logs": logs_data})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# NEX Docus Frontend
|
# NexDocus Frontend
|
||||||
|
|
||||||
NEX Docus 前端项目 - 基于 React + Vite + Ant Design 构建。
|
NexDocus 前端项目 - 基于 React + Vite + Ant Design 构建。
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>NEX Docus - 文档管理平台</title>
|
<title>NexDocus - 文档管理平台</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ function AppHeader({ collapsed, onToggle }) {
|
||||||
<div className="header-left">
|
<div className="header-left">
|
||||||
{/* Logo 区域 */}
|
{/* Logo 区域 */}
|
||||||
<div className="header-logo">
|
<div className="header-logo">
|
||||||
<h2 style={{ margin: 0, color: '#1677ff', fontWeight: 'bold' }}>NEX Docus</h2>
|
<h2 style={{ margin: 0, color: '#1677ff', fontWeight: 'bold' }}>NexDocus</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 折叠按钮 */}
|
{/* 折叠按钮 */}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,269 @@
|
||||||
.login-container {
|
.login-page {
|
||||||
min-height: 100vh;
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧介绍区域 */
|
||||||
|
.login-left {
|
||||||
|
flex: 1;
|
||||||
|
background: linear-gradient(135deg, #e6f0ff 0%, #f0f5ff 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
padding: 60px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card {
|
.login-left-content {
|
||||||
width: 400px;
|
max-width: 560px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
width: 100%;
|
||||||
border-radius: 8px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header {
|
/* Logo */
|
||||||
text-align: center;
|
.logo-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: #1890ff;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 介绍内容 */
|
||||||
|
.intro-section {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-title {
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #1f1f1f;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header h1 {
|
.intro-title .highlight {
|
||||||
font-size: 32px;
|
color: #1890ff;
|
||||||
font-weight: bold;
|
}
|
||||||
color: #667eea;
|
|
||||||
|
.intro-desc {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #666;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header p {
|
/* 底部信息 */
|
||||||
font-size: 14px;
|
.footer-info {
|
||||||
color: #666;
|
margin-top: auto;
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-footer {
|
.footer-links {
|
||||||
margin-top: 20px;
|
display: flex;
|
||||||
text-align: center;
|
gap: 24px;
|
||||||
color: white;
|
margin-bottom: 16px;
|
||||||
font-size: 12px;
|
}
|
||||||
|
|
||||||
|
.footer-links span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links span::before {
|
||||||
|
content: '';
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: #1890ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧登录区域 */
|
||||||
|
.login-right {
|
||||||
|
flex: 0 0 520px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container .ant-tabs-nav {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container .ant-tabs-tab {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-wrapper {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单样式 */
|
||||||
|
.login-form-container .ant-form-item-label > label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #262626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container .ant-input-affix-wrapper,
|
||||||
|
.login-form-container .ant-input {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container .ant-input-affix-wrapper:hover,
|
||||||
|
.login-form-container .ant-input:hover {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container .ant-input-affix-wrapper-focused,
|
||||||
|
.login-form-container .ant-input-affix-wrapper:focus,
|
||||||
|
.login-form-container .ant-input:focus {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container .ant-btn-primary {
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.3);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container .ant-btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试账号信息 */
|
||||||
|
.test-account-info {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f0f5ff;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 4px solid #1890ff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-account-info p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #595959;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-account-info strong {
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.login-left {
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-title {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-right {
|
||||||
|
flex: 0 0 480px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.login-page {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-left {
|
||||||
|
flex: none;
|
||||||
|
min-height: 40vh;
|
||||||
|
padding: 40px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-left-content {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-section {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-title {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-right {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40px 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.login-right {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-title {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Form, Input, Button, Card, Tabs, Checkbox } from 'antd'
|
import { Form, Input, Button, Tabs } from 'antd'
|
||||||
import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons'
|
import { UserOutlined, LockOutlined, MailOutlined, ArrowRightOutlined } from '@ant-design/icons'
|
||||||
import { login, register } from '@/api/auth'
|
import { login, register } from '@/api/auth'
|
||||||
import { getUserMenus } from '@/api/menu'
|
import { getUserMenus } from '@/api/menu'
|
||||||
import useUserStore from '@/stores/userStore'
|
import useUserStore from '@/stores/userStore'
|
||||||
|
|
@ -11,33 +11,16 @@ import './Login.css'
|
||||||
function Login() {
|
function Login() {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [activeTab, setActiveTab] = useState('login')
|
const [activeTab, setActiveTab] = useState('login')
|
||||||
const [rememberMe, setRememberMe] = useState(false)
|
|
||||||
const [loginForm] = Form.useForm()
|
const [loginForm] = Form.useForm()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { setUser, setToken } = useUserStore()
|
const { setUser, setToken } = useUserStore()
|
||||||
|
|
||||||
// 组件加载时检查是否有保存的用户名
|
|
||||||
useEffect(() => {
|
|
||||||
const savedUsername = localStorage.getItem('remembered_username')
|
|
||||||
if (savedUsername) {
|
|
||||||
loginForm.setFieldsValue({ username: savedUsername })
|
|
||||||
setRememberMe(true)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleLogin = async (values) => {
|
const handleLogin = async (values) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await login(values)
|
const res = await login(values)
|
||||||
Toast.success('登录成功')
|
Toast.success('登录成功')
|
||||||
|
|
||||||
// 处理"记住用户"
|
|
||||||
if (rememberMe) {
|
|
||||||
localStorage.setItem('remembered_username', values.username)
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem('remembered_username')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存 token 和用户信息
|
// 保存 token 和用户信息
|
||||||
localStorage.setItem('access_token', res.data.access_token)
|
localStorage.setItem('access_token', res.data.access_token)
|
||||||
localStorage.setItem('user_info', JSON.stringify(res.data.user))
|
localStorage.setItem('user_info', JSON.stringify(res.data.user))
|
||||||
|
|
@ -92,147 +75,199 @@ function Login() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="login-container">
|
<div className="login-page">
|
||||||
<Card className="login-card">
|
{/* 左侧介绍区域 */}
|
||||||
<div className="login-header">
|
<div className="login-left">
|
||||||
<h1>NEX Docus</h1>
|
<div className="login-left-content">
|
||||||
<p>团队协作文档管理平台</p>
|
<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>
|
||||||
|
|
||||||
<Tabs activeKey={activeTab} onChange={setActiveTab} centered>
|
{/* 右侧登录区域 */}
|
||||||
<Tabs.TabPane tab="登录" key="login">
|
<div className="login-right">
|
||||||
<Form
|
<div className="login-form-container">
|
||||||
form={loginForm}
|
<Tabs
|
||||||
name="login"
|
activeKey={activeTab}
|
||||||
onFinish={handleLogin}
|
onChange={setActiveTab}
|
||||||
autoComplete="off"
|
items={[
|
||||||
size="large"
|
{
|
||||||
>
|
key: 'login',
|
||||||
<Form.Item
|
label: '账号登录',
|
||||||
name="username"
|
children: (
|
||||||
rules={[{ required: true, message: '请输入用户名' }]}
|
<div className="form-wrapper">
|
||||||
>
|
<p className="form-subtitle">欢迎回来,请输入您的登录凭证。</p>
|
||||||
<Input
|
|
||||||
prefix={<UserOutlined />}
|
|
||||||
placeholder="用户名"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
<Form
|
||||||
name="password"
|
form={loginForm}
|
||||||
rules={[{ required: true, message: '请输入密码' }]}
|
name="login"
|
||||||
>
|
onFinish={handleLogin}
|
||||||
<Input.Password
|
autoComplete="off"
|
||||||
prefix={<LockOutlined />}
|
layout="vertical"
|
||||||
placeholder="密码"
|
>
|
||||||
/>
|
<Form.Item
|
||||||
</Form.Item>
|
label="用户名"
|
||||||
|
name="username"
|
||||||
|
rules={[{ required: true, message: '请输入用户名' }]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
prefix={<UserOutlined style={{ color: '#bfbfbf' }} />}
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item
|
||||||
<Checkbox
|
label="密码"
|
||||||
checked={rememberMe}
|
name="password"
|
||||||
onChange={(e) => setRememberMe(e.target.checked)}
|
rules={[{ required: true, message: '请输入密码' }]}
|
||||||
>
|
>
|
||||||
记住用户
|
<Input.Password
|
||||||
</Checkbox>
|
prefix={<LockOutlined style={{ color: '#bfbfbf' }} />}
|
||||||
</Form.Item>
|
placeholder="请输入密码"
|
||||||
|
size="large"
|
||||||
|
visibilityToggle
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item style={{ marginBottom: '12px' }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
block
|
block
|
||||||
>
|
size="large"
|
||||||
登录
|
icon={<ArrowRightOutlined />}
|
||||||
</Button>
|
iconPosition="end"
|
||||||
</Form.Item>
|
>
|
||||||
</Form>
|
立即登录
|
||||||
</Tabs.TabPane>
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'register',
|
||||||
|
label: '注册账号',
|
||||||
|
children: (
|
||||||
|
<div className="form-wrapper">
|
||||||
|
<p className="form-subtitle">创建您的账号,开始使用平台。</p>
|
||||||
|
|
||||||
<Tabs.TabPane tab="注册" key="register">
|
<Form
|
||||||
<Form
|
name="register"
|
||||||
name="register"
|
onFinish={handleRegister}
|
||||||
onFinish={handleRegister}
|
autoComplete="off"
|
||||||
autoComplete="off"
|
layout="vertical"
|
||||||
size="large"
|
>
|
||||||
>
|
<Form.Item
|
||||||
<Form.Item
|
label="用户名"
|
||||||
name="username"
|
name="username"
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: '请输入用户名' },
|
{ required: true, message: '请输入用户名' },
|
||||||
{ min: 3, message: '用户名至少3个字符' },
|
{ min: 3, message: '用户名至少3个字符' },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={<UserOutlined />}
|
prefix={<UserOutlined style={{ color: '#bfbfbf' }} />}
|
||||||
placeholder="用户名"
|
placeholder="用户名"
|
||||||
/>
|
size="large"
|
||||||
</Form.Item>
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="email"
|
label="邮箱"
|
||||||
rules={[
|
name="email"
|
||||||
{ type: 'email', message: '请输入有效的邮箱地址' },
|
rules={[
|
||||||
]}
|
{ type: 'email', message: '请输入有效的邮箱地址' },
|
||||||
>
|
]}
|
||||||
<Input
|
>
|
||||||
prefix={<MailOutlined />}
|
<Input
|
||||||
placeholder="邮箱(选填)"
|
prefix={<MailOutlined style={{ color: '#bfbfbf' }} />}
|
||||||
/>
|
placeholder="邮箱(选填)"
|
||||||
</Form.Item>
|
size="large"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
label="密码"
|
||||||
rules={[
|
name="password"
|
||||||
{ required: true, message: '请输入密码' },
|
rules={[
|
||||||
{ min: 6, message: '密码至少6个字符' },
|
{ required: true, message: '请输入密码' },
|
||||||
]}
|
{ min: 6, message: '密码至少6个字符' },
|
||||||
>
|
]}
|
||||||
<Input.Password
|
>
|
||||||
prefix={<LockOutlined />}
|
<Input.Password
|
||||||
placeholder="密码"
|
prefix={<LockOutlined style={{ color: '#bfbfbf' }} />}
|
||||||
/>
|
placeholder="密码"
|
||||||
</Form.Item>
|
size="large"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="confirm"
|
label="确认密码"
|
||||||
dependencies={['password']}
|
name="confirm"
|
||||||
rules={[
|
dependencies={['password']}
|
||||||
{ required: true, message: '请确认密码' },
|
rules={[
|
||||||
({ getFieldValue }) => ({
|
{ required: true, message: '请确认密码' },
|
||||||
validator(_, value) {
|
({ getFieldValue }) => ({
|
||||||
if (!value || getFieldValue('password') === value) {
|
validator(_, value) {
|
||||||
return Promise.resolve()
|
if (!value || getFieldValue('password') === value) {
|
||||||
}
|
return Promise.resolve()
|
||||||
return Promise.reject(new Error('两次输入的密码不一致'))
|
}
|
||||||
},
|
return Promise.reject(new Error('两次输入的密码不一致'))
|
||||||
}),
|
},
|
||||||
]}
|
}),
|
||||||
>
|
]}
|
||||||
<Input.Password
|
>
|
||||||
prefix={<LockOutlined />}
|
<Input.Password
|
||||||
placeholder="确认密码"
|
prefix={<LockOutlined style={{ color: '#bfbfbf' }} />}
|
||||||
/>
|
placeholder="确认密码"
|
||||||
</Form.Item>
|
size="large"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
block
|
block
|
||||||
>
|
size="large"
|
||||||
注册
|
>
|
||||||
</Button>
|
注册
|
||||||
</Form.Item>
|
</Button>
|
||||||
</Form>
|
</Form.Item>
|
||||||
</Tabs.TabPane>
|
</Form>
|
||||||
</Tabs>
|
</div>
|
||||||
</Card>
|
)
|
||||||
|
}
|
||||||
<div className="login-footer">
|
]}
|
||||||
<p>重置密码请联系管理员</p>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue