Initial commit
This commit is contained in:
183
web-admin/src/features/auth/LoginPage.tsx
Normal file
183
web-admin/src/features/auth/LoginPage.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Alert, Button, Card, Form, Input, Typography, message } from 'antd'
|
||||
import { authApi, menuApi } from '@/lib/api'
|
||||
import { buildFullMenus, findDefaultRoute } from '@/lib/menu'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import type { CaptchaInfo } from '@/types/system'
|
||||
|
||||
type LoginForm = {
|
||||
username: string
|
||||
password: string
|
||||
captcha?: string
|
||||
}
|
||||
|
||||
function normalizeCaptchaSrc(picPath?: string) {
|
||||
if (!picPath) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (picPath.startsWith('data:image')) {
|
||||
return picPath
|
||||
}
|
||||
|
||||
return `data:image/png;base64,${picPath}`
|
||||
}
|
||||
|
||||
export function LoginPage() {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const applySession = useAuthStore((state) => state.applySession)
|
||||
const setMenus = useAuthStore((state) => state.setMenus)
|
||||
const setUser = useAuthStore((state) => state.setUser)
|
||||
const [form] = Form.useForm<LoginForm>()
|
||||
const [captcha, setCaptcha] = useState<CaptchaInfo | null>(null)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [loadCaptchaError, setLoadCaptchaError] = useState<string | null>(null)
|
||||
|
||||
const redirectTarget = useMemo(() => {
|
||||
const state = location.state as { redirectTo?: string } | null
|
||||
return state?.redirectTo
|
||||
}, [location.state])
|
||||
|
||||
const fetchCaptcha = async () => {
|
||||
try {
|
||||
const response = await authApi.getCaptcha()
|
||||
setCaptcha(response.data)
|
||||
setLoadCaptchaError(null)
|
||||
} catch (error) {
|
||||
const messageText = error instanceof Error ? error.message : '验证码加载失败'
|
||||
setLoadCaptchaError(messageText)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchCaptcha()
|
||||
}, [])
|
||||
|
||||
const submit = async () => {
|
||||
const values = await form.validateFields()
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const loginResponse = await authApi.login({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
captcha: values.captcha,
|
||||
captchaId: captcha?.captchaId,
|
||||
})
|
||||
|
||||
applySession({
|
||||
token: loginResponse.data.token,
|
||||
user: loginResponse.data.user,
|
||||
})
|
||||
|
||||
const [userInfo, menuRes] = await Promise.all([authApi.getUserInfo(), menuApi.getMenu()])
|
||||
const menus = buildFullMenus(menuRes.data.menus)
|
||||
setUser(userInfo.data.userInfo)
|
||||
setMenus(menus)
|
||||
message.success('登录成功')
|
||||
|
||||
const fallback = findDefaultRoute(menus, userInfo.data.userInfo.authority?.defaultRouter) || '/dashboard'
|
||||
navigate(redirectTarget || fallback, { replace: true })
|
||||
} catch {
|
||||
form.setFieldValue('captcha', '')
|
||||
fetchCaptcha()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login-shell">
|
||||
<section className="login-hero">
|
||||
<span className="capsule">Gin-Vue-Admin · React 重设计版</span>
|
||||
<div style={{ maxWidth: 620, marginTop: 72 }}>
|
||||
<Typography.Title style={{ color: 'rgba(255,255,255,0.96)', fontSize: 52, marginBottom: 18 }}>
|
||||
把现有后端协议
|
||||
<br />
|
||||
接成一套更清晰的管理后台
|
||||
</Typography.Title>
|
||||
<Typography.Paragraph style={{ color: 'rgba(255,255,255,0.78)', fontSize: 18, lineHeight: 1.8 }}>
|
||||
当前登录页兼容原有验证码与 JWT 流程。登录成功后会直接拉取真实菜单树,按当前角色动态生成可访问页面。
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
</section>
|
||||
<section className="login-form-wrap">
|
||||
<Card className="glass-panel login-card" bordered={false}>
|
||||
<Typography.Title level={2} style={{ marginBottom: 8 }}>
|
||||
登录管理后台
|
||||
</Typography.Title>
|
||||
<Typography.Paragraph className="text-muted">
|
||||
首版默认沿用原系统认证协议,避免后端改造。
|
||||
</Typography.Paragraph>
|
||||
{loadCaptchaError ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="验证码加载失败"
|
||||
description={loadCaptchaError}
|
||||
action={
|
||||
<Button size="small" onClick={fetchCaptcha}>
|
||||
重试
|
||||
</Button>
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
) : null}
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
username: 'admin',
|
||||
}}
|
||||
onFinish={submit}
|
||||
>
|
||||
<Form.Item name="username" label="用户名" rules={[{ required: true, min: 3, message: '请输入用户名' }]}>
|
||||
<Input size="large" placeholder="请输入用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item name="password" label="密码" rules={[{ required: true, min: 6, message: '请输入至少 6 位密码' }]}>
|
||||
<Input.Password size="large" placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
{captcha?.openCaptcha ? (
|
||||
<Form.Item
|
||||
name="captcha"
|
||||
label={`验证码(${captcha.captchaLength} 位)`}
|
||||
rules={[{ required: true, len: captcha.captchaLength, message: `请输入 ${captcha.captchaLength} 位验证码` }]}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: 12, alignItems: 'stretch' }}>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<div
|
||||
onClick={fetchCaptcha}
|
||||
style={{
|
||||
width: 128,
|
||||
height: 40,
|
||||
borderRadius: 12,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(16, 37, 66, 0.12)',
|
||||
background: '#f4efe8',
|
||||
cursor: 'pointer',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="captcha"
|
||||
src={normalizeCaptchaSrc(captcha.picPath)}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
) : null}
|
||||
<Button block size="large" type="primary" htmlType="submit" loading={submitting}>
|
||||
进入后台
|
||||
</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user