'use client' import {useEffect, useRef, useState} from 'react' import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger} from '@/components/ui/dialog' import {Button} from '@/components/ui/button' import {Form, FormField} from '@/components/ui/form' import {Input} from '@/components/ui/input' import {useForm} from 'react-hook-form' import {zodResolver} from '@hookform/resolvers/zod' import * as z from 'zod' import {toast} from 'sonner' import {useRouter} from 'next/navigation' import {sendSMS} from '@/actions/verify' import {updatePassword} from '@/actions/user' interface ChangePasswordDialogProps { triggerClassName?: string open?: boolean onOpenChange?: (open: boolean) => void onSuccess?: () => void } export function ChangePasswordDialog({ triggerClassName, open, onOpenChange, onSuccess, }: ChangePasswordDialogProps) { const [internalOpen, setInternalOpen] = useState(false) const router = useRouter() const actualOpen = open !== undefined ? open : internalOpen const actualOnOpenChange = onOpenChange || setInternalOpen // 表单验证规则 const schema = z.object({ phone: z.string().regex(/^1\d{10}$/, `请输入正确的手机号`), captcha: z.string().nonempty('请输入验证码'), code: z.string().regex(/^\d{6}$/, `请输入正确的验证码`), password: z.string().min(6, `密码至少6位`), confirm_password: z.string(), }).refine(data => data.password === data.confirm_password, { message: '两次输入的密码不一致', path: ['confirm_password'], }) type Schema = z.infer // 表单初始化 const form = useForm({ resolver: zodResolver( schema.refine( data => /^(?=.*[a-z])(?=.*[A-Z]).{6,}$/.test(data.password), { message: '密码需包含大小写字母,且不少于6位', path: ['password'], }, ), ), defaultValues: { phone: '', captcha: '', code: '', password: '', confirm_password: '', }, }) // 提交处理 const handler = form.handleSubmit(async (value) => { try { const resp = await updatePassword({ phone: value.phone, code: value.code, password: value.password, }) if (!resp.success) { throw new Error(resp.message) } toast.success(`保存成功,请重新登录`) onSuccess?.() actualOnOpenChange(false) router.replace('/login') } catch (e) { console.error(e) toast.error(`保存失败`, { description: e instanceof Error ? e.message : String(e), }) } }) // 验证码相关状态 const [captchaUrl, setCaptchaUrl] = useState(`/captcha?t=${new Date().getTime()}`) const [captchaWait, setCaptchaWait] = useState(0) const interval = useRef(null) // 刷新验证码 const refreshCaptcha = () => { setCaptchaUrl(`/captcha?t=${new Date().getTime()}`) } // 发送短信验证码 const sendVerifier = async () => { const result = await form.trigger(['phone', 'captcha']) if (!result) return const {phone, captcha} = form.getValues() const resp = await sendSMS({phone, captcha}) if (!resp.success) { toast.error(resp.message) refreshCaptcha() return } setCaptchaWait(60) interval.current = setInterval(() => { setCaptchaWait((wait) => { if (wait <= 1) { clearInterval(interval.current!) return 0 } return wait - 1 }) }, 1000) toast.success(`验证码已发送,请注意查收`) } // 清理定时器 useEffect(() => { return () => { if (interval.current) { clearInterval(interval.current) } } }, []) return ( 修改密码
{/* 手机号输入 */} name="phone" label="手机号" className="flex-auto"> {({field}) => ( )} {/* 图形验证码 */} name="captcha" label="验证码"> {({field}) => (
)} {/* 短信验证码 */} name="code" label="短信令牌" className="flex-auto"> {({field}) => (
)} {/* 新密码 */} name="password" label="新密码" className="flex-auto"> {({field}) => ( )} {/* 确认密码 */} name="confirm_password" label="确认密码" className="flex-auto"> {({field}) => ( )}
) }