Initial commit

This commit is contained in:
2026-04-07 09:03:48 +08:00
commit c9ffb52b7f
713 changed files with 111641 additions and 0 deletions

View 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>
)
}