138 lines
5.0 KiB
TypeScript
138 lines
5.0 KiB
TypeScript
import { useState } from 'react'
|
||
import { Alert, App, Button, Form, Input, Typography } from 'antd'
|
||
import { LockOutlined } from '@ant-design/icons'
|
||
import { Navigate, useNavigate } from 'react-router-dom'
|
||
|
||
import { BrandLogo } from '@/components/BrandLogo'
|
||
import { getAdminBasePath, getAdminLoginPath } from '@/shared/api/adminClient'
|
||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||
|
||
interface ChangePasswordForm {
|
||
old_password: string
|
||
new_password: string
|
||
confirm_password: string
|
||
}
|
||
|
||
export function AdminChangePasswordPage() {
|
||
const { message } = App.useApp()
|
||
const navigate = useNavigate()
|
||
const admin = useAdminSessionStore(state => state.admin)
|
||
const logout = useAdminSessionStore(state => state.logout)
|
||
const changePassword = useAdminSessionStore(state => state.changePassword)
|
||
const [submitting, setSubmitting] = useState(false)
|
||
const [error, setError] = useState('')
|
||
const basePath = getAdminBasePath()
|
||
|
||
if (!admin) return <Navigate to={getAdminLoginPath()} replace />
|
||
if (!admin.password_must_change) return <Navigate to={basePath} replace />
|
||
|
||
const onFinish = async (values: ChangePasswordForm) => {
|
||
setSubmitting(true)
|
||
setError('')
|
||
try {
|
||
await changePassword(values.old_password, values.new_password)
|
||
message.success('初始密码已更新')
|
||
navigate(basePath, { replace: true })
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : '密码没有更新,请检查原密码')
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<main className="admin-login-page">
|
||
<section className="admin-login-brand">
|
||
<div className="admin-login-brand__inner">
|
||
<BrandLogo height={56} variant="dark" />
|
||
<div className="admin-login-brand__eyebrow">FIRST RUN · SECURITY</div>
|
||
<Typography.Title className="brand-title admin-login-brand__title">
|
||
先把钥匙
|
||
<br />
|
||
换掉。
|
||
</Typography.Title>
|
||
<Typography.Paragraph className="admin-login-brand__copy">
|
||
初始密码只用于第一次进入。修改完成后,这个后台会话才能打开订单、财务和分销操作。
|
||
</Typography.Paragraph>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="admin-login-form-panel">
|
||
<div className="admin-login-card">
|
||
<div className="admin-login-card__head">
|
||
<BrandLogo height={34} />
|
||
<Typography.Title level={2} className="brand-title">
|
||
修改初始密码
|
||
</Typography.Title>
|
||
<Typography.Text type="secondary">
|
||
当前账号:{admin.nickname || admin.username}({admin.username})
|
||
</Typography.Text>
|
||
</div>
|
||
|
||
{error ? <Alert type="error" showIcon message={error} className="login-alert" /> : null}
|
||
|
||
<Form<ChangePasswordForm>
|
||
layout="vertical"
|
||
size="large"
|
||
requiredMark={false}
|
||
onFinish={onFinish}
|
||
>
|
||
<Form.Item
|
||
label="原密码"
|
||
name="old_password"
|
||
rules={[{ required: true, message: '请输入原密码' }]}
|
||
>
|
||
<Input.Password prefix={<LockOutlined />} placeholder="请输入原密码" />
|
||
</Form.Item>
|
||
<Form.Item
|
||
label="新密码"
|
||
name="new_password"
|
||
rules={[
|
||
{ required: true, message: '请输入新密码' },
|
||
{ min: 8, max: 20, message: '密码长度需为 8–20 位' },
|
||
]}
|
||
>
|
||
<Input.Password prefix={<LockOutlined />} placeholder="8–20 位新密码" />
|
||
</Form.Item>
|
||
<Form.Item
|
||
label="确认新密码"
|
||
name="confirm_password"
|
||
dependencies={['new_password']}
|
||
rules={[
|
||
{ required: true, message: '请再次输入新密码' },
|
||
({ getFieldValue }) => ({
|
||
validator(_, value) {
|
||
if (!value || getFieldValue('new_password') === value) {
|
||
return Promise.resolve()
|
||
}
|
||
return Promise.reject(new Error('两次输入的新密码不一致'))
|
||
},
|
||
}),
|
||
]}
|
||
>
|
||
<Input.Password prefix={<LockOutlined />} placeholder="再次输入新密码" />
|
||
</Form.Item>
|
||
|
||
<Button type="primary" htmlType="submit" block loading={submitting}>
|
||
更新密码并进入后台
|
||
</Button>
|
||
</Form>
|
||
|
||
<div className="admin-login-card__foot">
|
||
<span>改密后旧会话会按后端规则刷新</span>
|
||
<Button
|
||
type="link"
|
||
onClick={async () => {
|
||
await logout()
|
||
navigate(getAdminLoginPath(), { replace: true })
|
||
}}
|
||
>
|
||
退出登录
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
)
|
||
}
|