228 lines
5.3 KiB
TypeScript
228 lines
5.3 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import {
|
|
Button,
|
|
Card,
|
|
Checkbox,
|
|
Form,
|
|
Input,
|
|
Typography,
|
|
theme,
|
|
} from 'antd'
|
|
import {
|
|
LockOutlined,
|
|
LoginOutlined,
|
|
SafetyCertificateOutlined,
|
|
UserOutlined,
|
|
} from '@ant-design/icons'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { toast } from 'sonner'
|
|
import { apiUserLogin } from '@/apis/apisUser'
|
|
import { useUserStore } from '@/store/user'
|
|
|
|
const LOGIN_REMEMBER_KEY = 'mall-admin:remember-login'
|
|
|
|
interface LoginFormValues {
|
|
account: string
|
|
password: string
|
|
captcha: string
|
|
remember: boolean
|
|
}
|
|
|
|
interface RememberLoginData {
|
|
account: string
|
|
password: string
|
|
remember: boolean
|
|
}
|
|
|
|
const readRememberLogin = (): RememberLoginData => {
|
|
if (typeof window === 'undefined') {
|
|
return {
|
|
account: '',
|
|
password: '',
|
|
remember: true,
|
|
}
|
|
}
|
|
|
|
try {
|
|
const raw = window.localStorage.getItem(LOGIN_REMEMBER_KEY)
|
|
if (!raw) {
|
|
return {
|
|
account: '',
|
|
password: '',
|
|
remember: true,
|
|
}
|
|
}
|
|
|
|
const parsed = JSON.parse(raw) as Partial<RememberLoginData>
|
|
return {
|
|
account: parsed.account ?? '',
|
|
password: parsed.password ?? '',
|
|
remember: parsed.remember ?? true,
|
|
}
|
|
} catch {
|
|
return {
|
|
account: '',
|
|
password: '',
|
|
remember: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
const persistRememberLogin = (values: RememberLoginData) => {
|
|
if (typeof window === 'undefined') {
|
|
return
|
|
}
|
|
|
|
if (!values.remember) {
|
|
window.localStorage.removeItem(LOGIN_REMEMBER_KEY)
|
|
return
|
|
}
|
|
|
|
window.localStorage.setItem(LOGIN_REMEMBER_KEY, JSON.stringify(values))
|
|
}
|
|
|
|
export default function LoginPage() {
|
|
const navigate = useNavigate()
|
|
const [form] = Form.useForm<LoginFormValues>()
|
|
const [loading, setLoading] = useState(false)
|
|
const token = useUserStore(state => state.token)
|
|
const setToken = useUserStore(state => state.setToken)
|
|
const { token: themeToken } = theme.useToken()
|
|
|
|
useEffect(() => {
|
|
if (token?.accessToken) {
|
|
navigate('/merchant-review', { replace: true })
|
|
return
|
|
}
|
|
|
|
form.setFieldsValue(readRememberLogin())
|
|
}, [form, navigate, token?.accessToken])
|
|
|
|
const onFinish = async (values: LoginFormValues) => {
|
|
try {
|
|
setLoading(true)
|
|
|
|
const res = await apiUserLogin({
|
|
account: values.account.trim(),
|
|
password: values.password,
|
|
captcha: values.captcha.trim(),
|
|
captchaId: '',
|
|
loginType: '0',
|
|
type: '0',
|
|
remember: values.remember,
|
|
})
|
|
|
|
persistRememberLogin({
|
|
account: values.account.trim(),
|
|
password: values.password,
|
|
remember: values.remember,
|
|
})
|
|
|
|
setToken(res.data)
|
|
toast.success('登录成功')
|
|
navigate('/merchant-review', { replace: true })
|
|
} catch (error) {
|
|
toast.error(error instanceof Error ? error.message : '登录失败')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Card
|
|
bordered={false}
|
|
style={{
|
|
borderRadius: themeToken.borderRadiusLG,
|
|
boxShadow: '0 12px 40px rgba(0,0,0,0.08)',
|
|
}}
|
|
styles={{
|
|
body: {
|
|
padding: 22,
|
|
},
|
|
}}
|
|
>
|
|
<div style={{ marginBottom: 18 }}>
|
|
<Typography.Title level={3} style={{ marginTop: 4, marginBottom: 4 }}>
|
|
商城平台管理端
|
|
</Typography.Title>
|
|
</div>
|
|
|
|
<Form<LoginFormValues>
|
|
form={form}
|
|
layout="vertical"
|
|
requiredMark={false}
|
|
onFinish={onFinish}
|
|
initialValues={{
|
|
account: '',
|
|
password: '',
|
|
captcha: '',
|
|
remember: true,
|
|
}}
|
|
>
|
|
<Form.Item
|
|
name="account"
|
|
rules={[{ required: true, message: '请输入登陆账号' }]}
|
|
>
|
|
<Input placeholder="请输入登陆账号" autoComplete="username" prefix={<UserOutlined />} />
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="password"
|
|
rules={[
|
|
{ required: true, message: '请输入登陆密码' },
|
|
]}
|
|
>
|
|
<Input.Password
|
|
placeholder="请输入登陆密码"
|
|
autoComplete="current-password"
|
|
prefix={<LockOutlined />}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="captcha"
|
|
rules={[
|
|
{ required: true, message: '请输入谷歌验证码' },
|
|
]}
|
|
>
|
|
<Input
|
|
placeholder="谷歌验证码"
|
|
autoComplete="one-time-code"
|
|
inputMode="numeric"
|
|
prefix={<SafetyCertificateOutlined />}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 16,
|
|
}}
|
|
>
|
|
<Form.Item name="remember" valuePropName="checked" noStyle>
|
|
<Checkbox>记住密码</Checkbox>
|
|
</Form.Item>
|
|
|
|
<Button
|
|
type="link"
|
|
style={{ paddingInline: 0 }}
|
|
onClick={() => {
|
|
toast.info('忘记密码请联系管理员处理')
|
|
}}
|
|
>
|
|
忘记密码?
|
|
</Button>
|
|
</div>
|
|
|
|
<Form.Item style={{ marginBottom: 0 }}>
|
|
<Button type="primary" htmlType="submit" block icon={<LoginOutlined />} loading={loading}>
|
|
登录
|
|
</Button>
|
|
</Form.Item>
|
|
</Form>
|
|
</Card>
|
|
)
|
|
}
|