登录界面增加忘记密码. 后台增加密码重置
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
fetchAdminWorkerLevels,
|
||||
fetchAdminWorkerUsers,
|
||||
fetchAdminWorkerWithdrawalAccounts,
|
||||
resetAdminWorkerPassword,
|
||||
reviewAdminWorkerUser,
|
||||
saveAdminWorkerWithdrawalAccount,
|
||||
} from '@/services/admin'
|
||||
@@ -53,12 +54,14 @@ export default function WorkersPanel() {
|
||||
const [creditWorker, setCreditWorker] = useState<WorkerUser | null>(null)
|
||||
const [levelWorker, setLevelWorker] = useState<WorkerUser | null>(null)
|
||||
const [withdrawalAccountWorker, setWithdrawalAccountWorker] = useState<WorkerUser | null>(null)
|
||||
const [resetPasswordWorker, setResetPasswordWorker] = useState<WorkerUser | null>(null)
|
||||
const [withdrawalAccounts, setWithdrawalAccounts] = useState<AdminWorkerWithdrawalAccount[]>([])
|
||||
const [loadingWithdrawalAccounts, setLoadingWithdrawalAccounts] = useState(false)
|
||||
const [savingWithdrawalAccount, setSavingWithdrawalAccount] = useState(false)
|
||||
const [creditForm] = Form.useForm()
|
||||
const [levelForm] = Form.useForm<{ levelId?: number }>()
|
||||
const [withdrawalAccountForm] = Form.useForm<WithdrawalAccountFormValues>()
|
||||
const [resetPasswordForm] = Form.useForm<{ password: string }>()
|
||||
const withdrawalAccountChannel = Form.useWatch('accountChannel', withdrawalAccountForm)
|
||||
|
||||
const workersQuery = useQuery({
|
||||
@@ -145,6 +148,21 @@ export default function WorkersPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
async function submitResetPassword(values: { password: string }) {
|
||||
if (!resetPasswordWorker) return
|
||||
try {
|
||||
await resetAdminWorkerPassword(resetPasswordWorker.workerId, {
|
||||
password: values.password.trim(),
|
||||
})
|
||||
message.success('打手密码已重置,旧登录设备已退出')
|
||||
setResetPasswordWorker(null)
|
||||
resetPasswordForm.resetFields()
|
||||
await refreshWorkers()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '重置打手密码失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function submitLevel(values: { levelId?: number }) {
|
||||
if (!levelWorker) return
|
||||
try {
|
||||
@@ -306,6 +324,7 @@ export default function WorkersPanel() {
|
||||
调等级
|
||||
</Button>
|
||||
<Button onClick={() => setCreditWorker(row)}>充值</Button>
|
||||
<Button onClick={() => setResetPasswordWorker(row)}>重置密码</Button>
|
||||
<Button onClick={() => void openWithdrawalAccountModal(row)}>提现信息</Button>
|
||||
</Space>
|
||||
),
|
||||
@@ -387,6 +406,45 @@ export default function WorkersPanel() {
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title="重置打手密码"
|
||||
open={Boolean(resetPasswordWorker)}
|
||||
onCancel={() => {
|
||||
setResetPasswordWorker(null)
|
||||
resetPasswordForm.resetFields()
|
||||
}}
|
||||
onOk={() => resetPasswordForm.submit()}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form form={resetPasswordForm} layout="vertical" onFinish={submitResetPassword}>
|
||||
<Form.Item label="打手">
|
||||
<Typography.Text>
|
||||
{resetPasswordWorker?.displayName || resetPasswordWorker?.username || '-'}
|
||||
</Typography.Text>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="新密码"
|
||||
name="password"
|
||||
rules={[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{ min: 6, message: '密码至少 6 位' },
|
||||
{
|
||||
validator: (_, value) =>
|
||||
!value ||
|
||||
(/[A-Z]/.test(value) &&
|
||||
/[a-z]/.test(value) &&
|
||||
/\d/.test(value) &&
|
||||
/[\p{P}\p{S}]/u.test(value))
|
||||
? Promise.resolve()
|
||||
: Promise.reject(new Error('密码需包含大写字母、小写字母、数字和标点符号')),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.Password placeholder="至少 6 位,包含大小写、数字和标点" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="编辑打手提现信息"
|
||||
open={Boolean(withdrawalAccountWorker)}
|
||||
|
||||
@@ -16,7 +16,9 @@ import {
|
||||
loginWorker,
|
||||
manageWorkerSessions,
|
||||
registerWorker,
|
||||
resetWorkerPassword,
|
||||
sendWorkerSmsCode,
|
||||
sendWorkerPasswordResetSmsCode,
|
||||
} from '@/services/worker'
|
||||
import type { WorkerSessionDevice } from '@/types/worker-platform'
|
||||
import { hasWorkerSession, setWorkerSession } from '@/utils/worker-auth'
|
||||
@@ -47,8 +49,11 @@ export default function WorkerLoginPage() {
|
||||
const { message, modal } = App.useApp()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [registerForm] = Form.useForm()
|
||||
const [resetForm] = Form.useForm<{ phone: string; code: string; password: string }>()
|
||||
const [deviceForm] = Form.useForm<{ username: string; password: string }>()
|
||||
const [countdown, setCountdown] = useState(0)
|
||||
const [resetCountdown, setResetCountdown] = useState(0)
|
||||
const [forgotPasswordOpen, setForgotPasswordOpen] = useState(false)
|
||||
const [deviceManagerOpen, setDeviceManagerOpen] = useState(false)
|
||||
const [deviceManagerLoading, setDeviceManagerLoading] = useState(false)
|
||||
const [managedDevices, setManagedDevices] = useState<WorkerSessionDevice[]>([])
|
||||
@@ -59,6 +64,12 @@ export default function WorkerLoginPage() {
|
||||
return () => window.clearInterval(timer)
|
||||
}, [countdown])
|
||||
|
||||
useEffect(() => {
|
||||
if (resetCountdown <= 0) return
|
||||
const timer = window.setInterval(() => setResetCountdown((value) => value - 1), 1000)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [resetCountdown])
|
||||
|
||||
if (hasWorkerSession()) {
|
||||
return <Navigate to="/hall" replace />
|
||||
}
|
||||
@@ -146,6 +157,42 @@ export default function WorkerLoginPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function requestResetSmsCode() {
|
||||
const phone = String(resetForm.getFieldValue('phone') || '').trim()
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
message.error('请先填写正确的手机号')
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await sendWorkerPasswordResetSmsCode({ phone })
|
||||
setResetCountdown(SMS_COUNTDOWN_SECONDS)
|
||||
message.success(
|
||||
response.data.provider === 'mock'
|
||||
? '验证码已发送(开发模式,验证码见服务日志)'
|
||||
: `验证码已发送,${response.data.expiresInSeconds} 秒内有效`,
|
||||
)
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '验证码发送失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function submitResetPassword(values: { phone: string; code: string; password: string }) {
|
||||
setLoading(true)
|
||||
try {
|
||||
await resetWorkerPassword(values)
|
||||
message.success('密码已重置,请使用新密码登录')
|
||||
setForgotPasswordOpen(false)
|
||||
resetForm.resetFields()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '密码重置失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function submitRegister(values: {
|
||||
phone: string
|
||||
code: string
|
||||
@@ -269,6 +316,9 @@ export default function WorkerLoginPage() {
|
||||
>
|
||||
登录设备管理
|
||||
</Button>
|
||||
<Button type="link" block onClick={() => setForgotPasswordOpen(true)}>
|
||||
忘记密码?
|
||||
</Button>
|
||||
</Form>
|
||||
</>
|
||||
),
|
||||
@@ -377,6 +427,70 @@ export default function WorkerLoginPage() {
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<Modal
|
||||
title="忘记密码"
|
||||
open={forgotPasswordOpen}
|
||||
onCancel={() => {
|
||||
setForgotPasswordOpen(false)
|
||||
resetForm.resetFields()
|
||||
}}
|
||||
footer={null}
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form
|
||||
form={resetForm}
|
||||
layout="vertical"
|
||||
onFinish={submitResetPassword}
|
||||
requiredMark={false}
|
||||
>
|
||||
<Form.Item
|
||||
label="手机号"
|
||||
name="phone"
|
||||
rules={[
|
||||
{ required: true, message: '请输入手机号' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
|
||||
]}
|
||||
>
|
||||
<Input prefix={<MobileOutlined />} maxLength={11} placeholder="注册时使用的手机号" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="短信验证码"
|
||||
name="code"
|
||||
rules={[{ required: true, message: '请输入短信验证码' }]}
|
||||
>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Input
|
||||
prefix={<SafetyCertificateOutlined />}
|
||||
maxLength={6}
|
||||
placeholder="6 位数字验证码"
|
||||
/>
|
||||
<Button loading={loading} disabled={resetCountdown > 0} onClick={requestResetSmsCode}>
|
||||
{resetCountdown > 0 ? `${resetCountdown}s 后重发` : '获取验证码'}
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="新密码"
|
||||
name="password"
|
||||
rules={[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{
|
||||
validator: (_, value) =>
|
||||
!value || isWorkerPasswordStrong(value)
|
||||
? Promise.resolve()
|
||||
: Promise.reject(new Error(WORKER_PASSWORD_RULE_MESSAGE)),
|
||||
},
|
||||
]}
|
||||
extra={WORKER_PASSWORD_RULE_MESSAGE}
|
||||
>
|
||||
<Input.Password prefix={<LockOutlined />} autoComplete="new-password" />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={loading} size="large" block>
|
||||
重置密码
|
||||
</Button>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="登录设备管理"
|
||||
open={deviceManagerOpen}
|
||||
|
||||
Reference in New Issue
Block a user