feat(front-end): 依据《详细设计》文档对前端 UI 进行全面升级
- 设计系统:品牌主色统一为品红渐变 #c2185b→#e91e63,新增品牌令牌与呼吸/入场动画 - 登录页重建:内部员工/外部账号切换、账号/手机验证码 Tab、SRM 存量提示 - 注册页重建为多步向导:企业信息→联系人→AI 审核,OCR 状态、密码复杂度校验与强度条 - 小滨 AI 助手重建:品牌渐变悬浮球+呼吸光环,角色感知主动提醒(紧急/AI完成/待办/风险/建议) - 新增公开门户首页 /portal:Banner 轮播 + 三列公告区 + 免责声明 - 各角色主题色对齐文档:供应商=红、评标专家=蓝、招标专员=绿 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,81 +1,591 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate, Link } from "react-router";
|
||||
import { Hexagon, ArrowLeft, CheckCircle2 } from "lucide-react";
|
||||
import {
|
||||
Hexagon,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
UploadCloud,
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
XCircle,
|
||||
Building2,
|
||||
UserRound,
|
||||
ShieldCheck,
|
||||
X,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Info,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { cn } from "../components/ui/utils";
|
||||
|
||||
const fields = [
|
||||
{ label: "企业全称", placeholder: "请输入营业执照上的企业全称", col: 2 },
|
||||
{ label: "统一社会信用代码", placeholder: "18 位信用代码" },
|
||||
{ label: "企业类型", placeholder: "如:有限责任公司" },
|
||||
{ label: "法定代表人", placeholder: "请输入法人姓名" },
|
||||
{ label: "注册资本(万元)", placeholder: "请输入注册资本" },
|
||||
{ label: "联系人", placeholder: "请输入联系人姓名" },
|
||||
{ label: "联系电话", placeholder: "请输入手机号码" },
|
||||
{ label: "电子邮箱", placeholder: "用于接收平台通知" },
|
||||
{ label: "经营类别", placeholder: "如:自动化控制 / 环保工程" },
|
||||
{ label: "企业地址", placeholder: "请输入详细注册地址", col: 2 },
|
||||
type Registrant = "supplier" | "expert";
|
||||
type AuditState = "idle" | "auditing" | "success" | "rejected";
|
||||
|
||||
const SUPPLIER_STEPS = ["企业信息", "联系人", "提交 / 自动审核"];
|
||||
const EXPERT_STEPS = ["基本信息", "专业资质 / 收款", "提交审核"];
|
||||
|
||||
interface PwdRule {
|
||||
label: string;
|
||||
test: (v: string) => boolean;
|
||||
}
|
||||
|
||||
const PWD_RULES: PwdRule[] = [
|
||||
{ label: "长度 8~20 位", test: (v) => v.length >= 8 && v.length <= 20 },
|
||||
{ label: "包含大写字母(A-Z)与小写字母(a-z)", test: (v) => /[A-Z]/.test(v) && /[a-z]/.test(v) },
|
||||
{ label: "包含数字(0-9)与特殊字符(!@#$%^&*)", test: (v) => /\d/.test(v) && /[!@#$%^&*()_+\-=]/.test(v) },
|
||||
{ label: "禁止连续/重复字符(如 12345678、aaaa)", test: (v) => !/(.)\1{3,}/.test(v) && !/0123|1234|2345|3456|4567|5678|6789/.test(v) },
|
||||
{ label: "非常见弱口令(password、admin123 等)", test: (v) => !["password", "admin123", "12345678", "qwerty"].includes(v.toLowerCase()) },
|
||||
];
|
||||
|
||||
function Field({
|
||||
label,
|
||||
placeholder,
|
||||
required,
|
||||
hint,
|
||||
className,
|
||||
type = "text",
|
||||
readOnly,
|
||||
}: {
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
hint?: string;
|
||||
className?: string;
|
||||
type?: string;
|
||||
readOnly?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">
|
||||
{label} {required && <span className="text-[#e91e63]">*</span>}
|
||||
</label>
|
||||
<input
|
||||
type={type}
|
||||
readOnly={readOnly}
|
||||
placeholder={placeholder}
|
||||
className={cn(
|
||||
"h-11 w-full rounded-lg border bg-input-background px-3 text-sm outline-none transition-colors focus:border-primary",
|
||||
readOnly && "bg-muted text-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
{hint && <p className="mt-1 text-xs text-muted-foreground">{hint}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StepBar({ steps, current }: { steps: string[]; current: number }) {
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-full border bg-white px-6 py-4 shadow-sm">
|
||||
{steps.map((s, i) => (
|
||||
<div key={s} className="flex items-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-9 items-center justify-center rounded-full text-sm font-bold transition-colors",
|
||||
i <= current ? "bg-brand-gradient text-white" : "bg-muted text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{i < current ? <CheckCircle2 className="size-5" /> : i + 1}
|
||||
</div>
|
||||
<span className={cn("mt-2 text-xs", i <= current ? "font-medium text-primary" : "text-muted-foreground")}>{s}</span>
|
||||
</div>
|
||||
{i < steps.length - 1 && (
|
||||
<div className={cn("mx-3 mb-6 h-0.5 w-16 rounded-full sm:w-24", i < current ? "bg-brand-gradient" : "bg-border")} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Notice({ items }: { items: string[] }) {
|
||||
return (
|
||||
<div className="rounded-xl border border-[#ffcc80] bg-[#fff3e0] p-4">
|
||||
<div className="flex items-center gap-1.5 text-sm font-semibold text-[#e65100]">
|
||||
<Info className="size-4" /> 注册须知
|
||||
</div>
|
||||
<ol className="mt-2 space-y-1.5 text-xs leading-relaxed text-[#bf360c]">
|
||||
{items.map((t, i) => (
|
||||
<li key={i}>
|
||||
{i + 1}. {t}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RegisterPage() {
|
||||
const navigate = useNavigate();
|
||||
const [registrant, setRegistrant] = useState<Registrant>("supplier");
|
||||
const [step, setStep] = useState(0);
|
||||
const [ocr, setOcr] = useState<"none" | "scanning" | "done">("none");
|
||||
const [audit, setAudit] = useState<AuditState>("idle");
|
||||
const [products, setProducts] = useState<string[]>(["工业阀门", "管道配件"]);
|
||||
const [productInput, setProductInput] = useState("");
|
||||
const [pwd, setPwd] = useState("");
|
||||
const [confirmPwd, setConfirmPwd] = useState("");
|
||||
const [showPwd, setShowPwd] = useState(false);
|
||||
const [smsCountdown, setSmsCountdown] = useState(0);
|
||||
|
||||
const steps = registrant === "supplier" ? SUPPLIER_STEPS : EXPERT_STEPS;
|
||||
|
||||
const passedRules = useMemo(() => PWD_RULES.map((r) => r.test(pwd)), [pwd]);
|
||||
const passedCount = passedRules.filter(Boolean).length;
|
||||
const strength: 0 | 1 | 2 = passedCount >= 5 && pwd.length >= 12 ? 2 : passedCount >= 4 ? 1 : 0;
|
||||
|
||||
function switchRegistrant(r: Registrant) {
|
||||
setRegistrant(r);
|
||||
setStep(0);
|
||||
setAudit("idle");
|
||||
setOcr("none");
|
||||
}
|
||||
|
||||
function triggerOcr() {
|
||||
setOcr("scanning");
|
||||
setTimeout(() => {
|
||||
setOcr("done");
|
||||
toast.success("营业执照识别完成,已自动回填企业信息");
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function addProduct() {
|
||||
const v = productInput.trim();
|
||||
if (v && products.length < 20 && !products.includes(v)) {
|
||||
setProducts((p) => [...p, v]);
|
||||
}
|
||||
setProductInput("");
|
||||
}
|
||||
|
||||
function sendSms() {
|
||||
toast.success("验证码已发送");
|
||||
setSmsCountdown(60);
|
||||
const t = setInterval(() => {
|
||||
setSmsCountdown((c) => {
|
||||
if (c <= 1) {
|
||||
clearInterval(t);
|
||||
return 0;
|
||||
}
|
||||
return c - 1;
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function submitAudit() {
|
||||
setStep(steps.length - 1);
|
||||
setAudit("auditing");
|
||||
setTimeout(() => {
|
||||
setAudit("success");
|
||||
}, 3800);
|
||||
}
|
||||
|
||||
const isLastFormStep = step === steps.length - 2;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<div className="min-h-screen bg-[#f5f7fa]">
|
||||
<header className="flex h-16 items-center justify-between border-b bg-card px-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<Hexagon className="size-6 text-primary" />
|
||||
<span className="font-semibold">滨化集团智慧招标平台 · 供应商注册</span>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex size-8 items-center justify-center rounded-lg bg-brand-gradient">
|
||||
<Hexagon className="size-5 text-white" />
|
||||
</div>
|
||||
<span className="font-semibold">滨化集团智慧招标平台 · 新用户注册</span>
|
||||
</div>
|
||||
<Link to="/" className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary">
|
||||
<ArrowLeft className="size-4" /> 返回登录
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<div className="mx-auto max-w-3xl px-6 py-10">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-foreground">供应商入驻注册</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
完善企业信息提交后,平台将自动同步工商风险信息并进入资质审核,预计 1-3 个工作日完成。
|
||||
</p>
|
||||
<div className="mx-auto max-w-5xl px-6 py-8">
|
||||
{/* 注册对象选择 */}
|
||||
<div className="mb-6 flex justify-center gap-3">
|
||||
{(
|
||||
[
|
||||
{ id: "supplier" as Registrant, label: "供应商注册", icon: Building2 },
|
||||
{ id: "expert" as Registrant, label: "外部专家注册", icon: UserRound },
|
||||
]
|
||||
).map((r) => {
|
||||
const Icon = r.icon;
|
||||
return (
|
||||
<button
|
||||
key={r.id}
|
||||
onClick={() => switchRegistrant(r.id)}
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-full border px-5 py-2.5 text-sm font-medium transition-all",
|
||||
registrant === r.id
|
||||
? "border-transparent bg-brand-gradient text-white shadow-sm"
|
||||
: "border-border bg-white text-muted-foreground hover:border-primary hover:text-primary",
|
||||
)}
|
||||
>
|
||||
<Icon className="size-4" /> {r.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border bg-card p-8">
|
||||
<h3 className="mb-5 text-foreground">企业基本信息</h3>
|
||||
<form
|
||||
className="grid grid-cols-2 gap-5"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
toast.success("注册申请已提交,平台正在进行资质审核");
|
||||
setTimeout(() => navigate("/"), 1200);
|
||||
}}
|
||||
>
|
||||
{fields.map((f) => (
|
||||
<div key={f.label} className={f.col === 2 ? "col-span-2" : ""}>
|
||||
<label className="mb-1.5 block text-sm">{f.label}</label>
|
||||
<input
|
||||
className="h-10 w-full rounded-lg border bg-input-background px-3 text-sm outline-none focus:border-primary"
|
||||
placeholder={f.placeholder}
|
||||
<div className="mb-6 flex items-center justify-between gap-4">
|
||||
<StepBar steps={steps} current={step} />
|
||||
<div className="hidden shrink-0 items-center gap-1.5 rounded-full border border-[#4caf50] bg-[#e8f5e9] px-3 py-1.5 text-xs text-[#2e7d32] lg:flex">
|
||||
<ShieldCheck className="size-3.5" /> AI 即时审核 · 秒级通过
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ============ 供应商注册 ============ */}
|
||||
{registrant === "supplier" && (
|
||||
<>
|
||||
{step === 0 && (
|
||||
<div className="grid gap-6 lg:grid-cols-[1fr_300px]">
|
||||
<div className="rounded-2xl bg-card p-8 shadow-sm">
|
||||
<h3 className="text-foreground">供应商注册</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">请填写企业真实信息,上传营业执照,系统将自动识别并回填</p>
|
||||
<div className="my-5 h-px bg-border" />
|
||||
|
||||
{/* 营业执照上传 */}
|
||||
<div
|
||||
onClick={triggerOcr}
|
||||
className="flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-primary bg-[#fafbfc] py-6 transition-colors hover:bg-secondary/40"
|
||||
>
|
||||
<UploadCloud className="mb-2 size-7 text-primary" />
|
||||
<div className="text-sm font-medium text-primary">上传营业执照(JPG/PNG/PDF,≤10MB)</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">支持拖拽或点击上传,AI 将自动识别企业信息并填充表单</div>
|
||||
</div>
|
||||
|
||||
{ocr === "scanning" && (
|
||||
<div className="mt-3 flex items-center gap-2 rounded-lg bg-[#fff8e1] px-3 py-2 text-sm text-[#e65100]">
|
||||
<Loader2 className="size-4 animate-spin" /> 正在识别营业执照...
|
||||
</div>
|
||||
)}
|
||||
{ocr === "done" && (
|
||||
<div className="mt-3 flex items-center gap-2 rounded-lg border border-[#a5d6a7] bg-[#e8f5e9] px-3 py-2 text-sm text-[#2e7d32]">
|
||||
<CheckCircle2 className="size-4" /> 已识别:营业执照信息提取成功(统一信用代码:91370100XXXXXXXXXX)
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 grid grid-cols-2 gap-x-5 gap-y-4">
|
||||
<Field label="企业全称" required placeholder="系统自动识别或手动输入" className="col-span-2" />
|
||||
<Field label="统一社会信用代码" required placeholder="18 位代码" />
|
||||
<Field label="注册资本(万元)" required placeholder="请输入数字" />
|
||||
<Field label="法定代表人" required placeholder="请输入法定代表人姓名" />
|
||||
<Field label="行业分类" required placeholder="请选择或搜索行业(GB/T 4754)" />
|
||||
<Field label="注册地址" required placeholder="请填写详细地址" className="col-span-2" />
|
||||
<div className="col-span-2">
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">
|
||||
经营范围 <span className="text-[#e91e63]">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
rows={2}
|
||||
placeholder="系统自动识别或手动输入,不超过 500 字"
|
||||
className="w-full resize-none rounded-lg border bg-input-background px-3 py-2 text-sm outline-none transition-colors focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">
|
||||
主要产品 <span className="text-[#e91e63]">*</span>
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
value={productInput}
|
||||
onChange={(e) => setProductInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && (e.preventDefault(), addProduct())}
|
||||
placeholder="输入产品名称,回车添加"
|
||||
className="h-11 flex-1 rounded-lg border bg-input-background px-3 text-sm outline-none transition-colors focus:border-primary"
|
||||
/>
|
||||
<Button type="button" variant="outline" className="h-11" onClick={addProduct}>
|
||||
+ 添加
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{products.map((p) => (
|
||||
<span
|
||||
key={p}
|
||||
className="flex items-center gap-1 rounded-full border border-primary bg-secondary px-3 py-1 text-xs text-primary"
|
||||
>
|
||||
{p}
|
||||
<button onClick={() => setProducts((ps) => ps.filter((x) => x !== p))}>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Notice
|
||||
items={[
|
||||
"请确保信息与营业执照一致",
|
||||
"统一社会信用代码将作为唯一标识",
|
||||
"提交后由 AI 即时自动审核",
|
||||
"审核通过后可参与投标报名",
|
||||
"已有 SRM 账号请直接登录",
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
|
||||
<div className="col-span-2">
|
||||
<label className="mb-1.5 block text-sm">营业执照 / 资质文件</label>
|
||||
<div className="flex h-28 cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted/30 text-sm text-muted-foreground hover:border-primary">
|
||||
<CheckCircle2 className="mb-2 size-6 text-muted-foreground/60" />
|
||||
点击上传或拖拽文件至此(支持 PDF / JPG / PNG)
|
||||
{step === 1 && (
|
||||
<div className="mx-auto max-w-2xl rounded-2xl bg-card p-8 shadow-sm">
|
||||
<h3 className="text-foreground">联系人信息</h3>
|
||||
<div className="my-5 h-px bg-border" />
|
||||
<div className="space-y-4">
|
||||
<Field label="联系人姓名" required placeholder="请输入联系人姓名" hint="必填,最大 64 字符" />
|
||||
<Field label="联系电话(手机号)" required placeholder="请输入 11 位手机号" hint="必填,格式校验,全局唯一" />
|
||||
<Field label="电子邮箱" required placeholder="请输入常用邮箱" hint="必填,格式校验,全局唯一" />
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">
|
||||
短信验证码 <span className="text-[#e91e63]">*</span>
|
||||
</label>
|
||||
<div className="flex gap-3">
|
||||
<input
|
||||
placeholder="点击发送验证码"
|
||||
className="h-11 flex-1 rounded-lg border bg-input-background px-3 text-sm outline-none transition-colors focus:border-primary"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={sendSms}
|
||||
disabled={smsCountdown > 0}
|
||||
className="h-11 w-32 shrink-0 rounded-lg bg-brand-gradient text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-50"
|
||||
>
|
||||
{smsCountdown > 0 ? `${smsCountdown}s 后重发` : "发送验证码"}
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">用于验证手机号真实性(注册必验)</p>
|
||||
</div>
|
||||
|
||||
{/* 密码 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">
|
||||
输入密码 <span className="text-[#e91e63]">*</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showPwd ? "text" : "password"}
|
||||
value={pwd}
|
||||
onChange={(e) => setPwd(e.target.value)}
|
||||
placeholder="请设置登录密码"
|
||||
className="h-11 w-full rounded-lg border bg-input-background px-3 pr-10 text-sm outline-none transition-colors focus:border-primary"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPwd((s) => !s)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||
>
|
||||
{showPwd ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">
|
||||
确认密码 <span className="text-[#e91e63]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type={showPwd ? "text" : "password"}
|
||||
value={confirmPwd}
|
||||
onChange={(e) => setConfirmPwd(e.target.value)}
|
||||
placeholder="请再次输入密码"
|
||||
className={cn(
|
||||
"h-11 w-full rounded-lg border bg-input-background px-3 text-sm outline-none transition-colors focus:border-primary",
|
||||
confirmPwd && confirmPwd !== pwd && "border-destructive",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 密码复杂度要求 */}
|
||||
<div className="rounded-xl border bg-[#f8fafc] p-4">
|
||||
<div className="text-sm font-medium text-[#374151]">密码复杂度要求</div>
|
||||
<ul className="mt-2 space-y-1.5">
|
||||
{PWD_RULES.map((r, i) => (
|
||||
<li key={r.label} className="flex items-center gap-2 text-[13px]">
|
||||
<span className={passedRules[i] ? "text-[#10b981]" : "text-[#ef4444]"}>{passedRules[i] ? "✔" : "✘"}</span>
|
||||
<span className="text-[#374151]">{r.label}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{/* 强度条 */}
|
||||
<div className="mt-3 flex items-center gap-3">
|
||||
<span className="text-xs text-muted-foreground">密码强度</span>
|
||||
<div className="h-2 w-40 overflow-hidden rounded-full bg-[#e5e7eb]">
|
||||
<div
|
||||
className={cn(
|
||||
"h-full rounded-full transition-all",
|
||||
strength === 0 ? "w-1/3 bg-[#ef4444]" : strength === 1 ? "w-2/3 bg-[#f59e0b]" : "w-full bg-[#10b981]",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs font-medium",
|
||||
strength === 0 ? "text-[#ef4444]" : strength === 1 ? "text-[#f59e0b]" : "text-[#10b981]",
|
||||
)}
|
||||
>
|
||||
{strength === 0 ? "弱" : strength === 1 ? "中" : "强"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="col-span-2 mt-2 flex items-center gap-3">
|
||||
<Button type="submit" className="h-11 flex-1">
|
||||
提交注册申请
|
||||
{/* ============ 外部专家注册 ============ */}
|
||||
{registrant === "expert" && (
|
||||
<>
|
||||
{step === 0 && (
|
||||
<div className="grid gap-6 lg:grid-cols-[1fr_300px]">
|
||||
<div className="rounded-2xl bg-card p-8 shadow-sm">
|
||||
<h3 className="text-foreground">外部专家注册</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">请完整填写以下信息,所有数据将受到严格加密保护</p>
|
||||
<div className="my-5 h-px bg-border" />
|
||||
<div className="grid grid-cols-2 gap-x-5 gap-y-4">
|
||||
<Field label="真实姓名" required placeholder="请输入中文姓名" />
|
||||
<Field label="性别" required placeholder="请选择" />
|
||||
<Field label="身份证号码" required placeholder="请输入 18 位身份证号" hint="🔒 将使用 AES-256 加密存储" className="col-span-2" />
|
||||
<Field label="出生日期" placeholder="由身份证自动识别" readOnly />
|
||||
<Field label="年龄" placeholder="自动计算" readOnly />
|
||||
<Field label="手机号码" required placeholder="请输入手机号" />
|
||||
<Field label="电子邮箱" required placeholder="请输入常用邮箱" />
|
||||
<Field label="工作单位" placeholder="请输入单位全称" />
|
||||
<Field label="现从事专业" required placeholder="如:化工工艺、设备管理" />
|
||||
<Field label="技术职称" required placeholder="高级 / 副高级 / 中级" />
|
||||
<Field label="职称专业" placeholder="如:化学工程" />
|
||||
<div className="col-span-2">
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">
|
||||
职称 / 资质证书扫描件 <span className="text-[#e91e63]">*</span>
|
||||
</label>
|
||||
<div className="flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-primary bg-[#fafbfc] py-5 hover:bg-secondary/40">
|
||||
<UploadCloud className="mb-1.5 size-6 text-primary" />
|
||||
<div className="text-sm text-primary">点击上传或拖拽文件(支持 PDF/JPG/PNG,≤20MB)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Notice
|
||||
items={[
|
||||
"所有信息须与身份证及职称证书一致",
|
||||
"集团公司将对信息进行真实性核验",
|
||||
"入库专家须遵守《评委管理规定》",
|
||||
"至少具备中级及以上职称方可入库",
|
||||
"个人信息变更需及时在线更新",
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 1 && (
|
||||
<div className="mx-auto max-w-2xl rounded-2xl bg-card p-8 shadow-sm">
|
||||
<h3 className="text-foreground">专业资质与收款账户</h3>
|
||||
<div className="my-5 h-px bg-border" />
|
||||
<div className="grid grid-cols-2 gap-x-5 gap-y-4">
|
||||
<div className="col-span-2">
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">擅长领域标签(用于智能匹配)</label>
|
||||
<input
|
||||
placeholder="如:石油化工、反应工程、分离技术,逗号分隔"
|
||||
className="h-11 w-full rounded-lg border bg-input-background px-3 text-sm outline-none focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<Field label="开户银行" placeholder="如:中国银行北京分行" />
|
||||
<Field label="银行账号" placeholder="请输入收款账号" hint="🔒 将使用 AES-256 加密存储" />
|
||||
<label className="col-span-2 mt-2 flex items-start gap-2 rounded-lg border border-[#ffcc80] bg-[#fff3e0] p-3 text-xs text-[#bf360c]">
|
||||
<input type="checkbox" className="mt-0.5 size-4 accent-[var(--primary)]" />
|
||||
我已阅读并同意签署《评标纪律承诺书》,承诺客观公正评审,遵守评委管理规定。
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ============ 提交 / 自动审核态(两种注册对象共用最后一步) ============ */}
|
||||
{step === steps.length - 1 && (
|
||||
<div className="mx-auto max-w-lg">
|
||||
{audit === "auditing" && (
|
||||
<div className="rounded-2xl bg-card p-10 text-center shadow-sm">
|
||||
<Loader2 className="mx-auto size-14 animate-spin text-primary" />
|
||||
<div className="mt-5 text-lg font-bold text-foreground">
|
||||
{registrant === "supplier" ? "AI 正在审核企业信息..." : "已提交,AI 正在初审..."}
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-muted-foreground">预计 3~8 秒,请勿关闭页面</div>
|
||||
<div className="mt-3 text-[13px] text-muted-foreground/80">✓ 工商信息核验 ✓ 风险扫描 ✓ 唯一性检查</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{audit === "success" && (
|
||||
<div className="rounded-2xl bg-card p-10 text-center shadow-sm">
|
||||
<div className="mx-auto flex size-24 items-center justify-center rounded-full bg-[#e8f5e9]">
|
||||
<CheckCircle2 className="size-14 text-[#4caf50]" />
|
||||
</div>
|
||||
<div className="mt-5 text-2xl font-bold text-foreground">🎉 注册成功!</div>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{registrant === "supplier" ? "您的供应商账号已激活,正在自动登录..." : "您的专家账号已创建,请留意激活通知短信/邮件。"}
|
||||
</p>
|
||||
<div className="my-5 h-px bg-border" />
|
||||
<div className="space-y-1.5 text-sm text-muted-foreground">
|
||||
<div>
|
||||
{registrant === "supplier" ? "供应商编号" : "专家编号"}:
|
||||
<span className="font-semibold text-foreground">
|
||||
{registrant === "supplier" ? "SUP_20260709_0158" : "EXP_20260709_002"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
登录账号:<span className="font-semibold text-foreground">138****8001</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 rounded-lg border border-[#ffcc80] bg-[#fff3e0] px-4 py-2.5 text-xs text-[#e65100]">
|
||||
📌 温馨提示:参与具体项目投标时,需按招标公告要求提交资质审核材料
|
||||
</div>
|
||||
<Button className="mt-6 h-11 w-full bg-brand-gradient hover:opacity-90" onClick={() => navigate("/app")}>
|
||||
进入工作台 <ArrowRight className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{audit === "rejected" && (
|
||||
<div className="rounded-2xl bg-card p-10 text-center shadow-sm">
|
||||
<div className="mx-auto flex size-24 items-center justify-center rounded-full bg-[#ffcdd2]">
|
||||
<XCircle className="size-14 text-[#c62828]" />
|
||||
</div>
|
||||
<div className="mt-5 text-2xl font-bold text-[#c62828]">审核未通过</div>
|
||||
<div className="mt-5 rounded-lg border bg-[#fafafa] p-4 text-left text-[13px] text-[#c62828]">
|
||||
<div>• 统一社会信用代码与工商登记信息不匹配</div>
|
||||
<div>• 企业状态为“注销”,不符合注册条件</div>
|
||||
<div className="mt-2 text-[#666]">💡 请核对信息后重新提交,如有疑问请联系客服</div>
|
||||
</div>
|
||||
<Button
|
||||
className="mt-6 h-11 w-full bg-brand-gradient hover:opacity-90"
|
||||
onClick={() => {
|
||||
setAudit("idle");
|
||||
setStep(0);
|
||||
}}
|
||||
>
|
||||
修改后重新提交
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 底部导航按钮 */}
|
||||
{audit === "idle" && (
|
||||
<div className="mx-auto mt-6 flex max-w-5xl items-center justify-end gap-3">
|
||||
{step > 0 && (
|
||||
<Button variant="outline" className="h-11 px-6" onClick={() => setStep((s) => s - 1)}>
|
||||
上一步
|
||||
</Button>
|
||||
<Button type="button" variant="outline" className="h-11" onClick={() => navigate("/")}>
|
||||
取消
|
||||
)}
|
||||
{isLastFormStep ? (
|
||||
<Button className="h-11 bg-brand-gradient px-8 hover:opacity-90" onClick={submitAudit}>
|
||||
提交注册
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
) : (
|
||||
<Button className="h-11 bg-brand-gradient px-8 hover:opacity-90" onClick={() => setStep((s) => s + 1)}>
|
||||
下一步 <ArrowRight className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user