Files
juip-web/src/components/composites/dialogs/realNameDialog.tsx

233 lines
7.6 KiB
TypeScript
Raw Normal View History

2026-09-03 14:37:18 +08:00
import { zodResolver } from "@hookform/resolvers/zod"
import { useState } from "react"
import { Controller, useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"
import { submitRealName } from "@/api/agreement"
import { QrCodeImage } from "@/components/qrCodeImage"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { FieldError } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
const realNameSchema = z.object({
name: z.string().trim().min(1, "请输入真实姓名"),
idCode: z
.string()
.trim()
.regex(/^\d{15}$|^\d{18}$|^\d{17}(\d|X|x)$/, "身份证号码不合法"),
})
type RealNameValues = z.infer<typeof realNameSchema>
export default function RealNameDialog({
open,
onOpenChange,
onVerified,
2026-09-11 17:06:29 +08:00
mandatory = false,
2026-09-03 14:37:18 +08:00
}: {
open: boolean
onOpenChange: (open: boolean) => void
onVerified: () => void
2026-09-11 17:06:29 +08:00
/** 强制模式:不可关闭,直到完成实名认证(由外层 Gate 在认证成功后卸载) */
mandatory?: boolean
2026-09-03 14:37:18 +08:00
}) {
const [stage, setStage] = useState<"form" | "scan">("form")
const [qrUrl, setQrUrl] = useState("")
const [submitting, setSubmitting] = useState(false)
const form = useForm<RealNameValues>({
resolver: zodResolver(realNameSchema),
defaultValues: { name: "", idCode: "" },
})
const handleOpenChange = (next: boolean) => {
2026-09-11 17:06:29 +08:00
if (!next && mandatory) return
2026-09-03 14:37:18 +08:00
onOpenChange(next)
if (!next) {
form.reset({ name: "", idCode: "" })
setStage("form")
setQrUrl("")
}
}
const handleFinish = () => {
onVerified()
2026-09-11 17:06:29 +08:00
if (!mandatory) onOpenChange(false)
2026-09-03 14:37:18 +08:00
}
const onSubmit = async (data: RealNameValues) => {
setSubmitting(true)
const res = await submitRealName({
idCode: data.idCode,
name: data.name,
})
setSubmitting(false)
if (!res.success) {
toast.error(res.message)
return
}
const { code, url } = res.data
if (code === 20000) {
toast.success("已经认证成功")
handleFinish()
return
}
if (code === 30000) {
toast.error("该身份已经进行认证,无法再次进行认证,请联系在线客服处理")
2026-09-11 17:06:29 +08:00
return
}
if (code === -10000) {
toast.error("接口问题,请联系客服")
2026-09-03 14:37:18 +08:00
return
}
if (code === 10000 && url) {
setQrUrl(url)
setStage("scan")
return
}
toast.error("请正确填写姓名和身份证号码")
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
2026-09-11 17:06:29 +08:00
<DialogContent
className="max-h-[85dvh] overflow-y-auto sm:max-w-lg"
showCloseButton={!mandatory}
onEscapeKeyDown={mandatory ? e => e.preventDefault() : undefined}
onPointerDownOutside={mandatory ? e => e.preventDefault() : undefined}
>
2026-09-03 14:37:18 +08:00
{stage === "form" ? (
<>
<DialogHeader>
<DialogTitle className="text-xl font-semibold text-blue-600">
尊敬的用户,您还未进行实名认证!
</DialogTitle>
</DialogHeader>
<div className="max-h-56 overflow-y-auto space-y-2 rounded-lg bg-gray-50 p-4 text-xs leading-5 text-gray-600">
<p>
《中华人民共和国网络安全法》第二十四条规定:
<br />
网络运营者为用户办理网络接入、域名注册服务,办理固定电话、移动电话等入网手续,或者为用户提供信息发布、即时通讯等服务,在与用户签订协议或者确认提供服务时,应当要求用户提供真实身份信息。用户不提供真实身份信息的,网络运营者不得为其提供相关服务。
</p>
<p className="text-red-500">
1、为了你的帐号安全请勿使用他人手机号或身份信息
</p>
<p>
2、聚IP承诺:用户的个人隐私数据仅与国家公安一所信息库比对,本公司不调用及储存用户的实人认证数据,仅接收公安一所方面的实人认证结果!
</p>
<p>3、若您扫脸认证不成功,请联系客服</p>
</div>
<form
className="space-y-4"
onSubmit={form.handleSubmit(onSubmit)}
noValidate
>
<div className="space-y-1.5">
<Label className="text-sm text-gray-600">真实姓名</Label>
<Controller
name="name"
control={form.control}
render={({ field, fieldState }) => (
<>
<Input
{...field}
placeholder="请输入真实姓名"
className="h-11 bg-gray-50 border-gray-200"
/>
<FieldError>{fieldState.error?.message}</FieldError>
</>
)}
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm text-gray-600">身份证号</Label>
<Controller
name="idCode"
control={form.control}
render={({ field, fieldState }) => (
<>
<Input
{...field}
placeholder="请输入身份证号码"
className="h-11 bg-gray-50 border-gray-200"
/>
<FieldError>{fieldState.error?.message}</FieldError>
</>
)}
/>
</div>
<DialogFooter className="gap-3">
2026-09-11 17:06:29 +08:00
{!mandatory && (
<Button
type="button"
variant="outline"
onClick={() => handleOpenChange(false)}
>
取消
</Button>
)}
2026-09-03 14:37:18 +08:00
<Button
type="submit"
disabled={submitting}
className="bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90"
>
{submitting ? "认证中..." : "确认"}
</Button>
</DialogFooter>
</form>
</>
) : (
<>
<DialogHeader>
<DialogTitle className="text-xl font-semibold text-blue-600">
请打开微信扫码进行认证
</DialogTitle>
</DialogHeader>
<p className="text-center text-sm text-gray-600">
若扫脸后仍未认证成功,请联系客服
</p>
<div className="flex justify-center py-2">
<div className="rounded-lg border border-gray-200 p-2">
<QrCodeImage text={qrUrl} size={240} />
</div>
</div>
<DialogFooter className="gap-3">
2026-09-11 17:06:29 +08:00
{!mandatory && (
<Button
variant="outline"
onClick={() => handleOpenChange(false)}
>
取消
</Button>
)}
2026-09-03 14:37:18 +08:00
<Button
onClick={handleFinish}
className="bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90"
>
实名扫脸结束后请点击
</Button>
</DialogFooter>
</>
)}
</DialogContent>
</Dialog>
)
}