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,119 +1,246 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate, Link } from "react-router";
|
||||
import { Hexagon, User, Lock, ShieldCheck, Sparkles, ArrowRight } from "lucide-react";
|
||||
import { Hexagon, User, Lock, Smartphone, ShieldCheck, ArrowRight, Eye, EyeOff, Info } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { cn } from "../components/ui/utils";
|
||||
|
||||
type Mode = "employee" | "vendor";
|
||||
type Tab = "account" | "mobile";
|
||||
|
||||
export function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const [mode, setMode] = useState<Mode>("vendor");
|
||||
const [tab, setTab] = useState<Tab>("account");
|
||||
const [account, setAccount] = useState("zhangwei");
|
||||
const [pwd, setPwd] = useState("123456");
|
||||
const [showPwd, setShowPwd] = useState(false);
|
||||
const [phone, setPhone] = useState("");
|
||||
const [code, setCode] = useState("");
|
||||
const [remember, setRemember] = useState(true);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
|
||||
function sendCode() {
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
toast.error("请输入正确的手机号");
|
||||
return;
|
||||
}
|
||||
toast.success("验证码已发送,请注意查收");
|
||||
setCountdown(60);
|
||||
const timer = setInterval(() => {
|
||||
setCountdown((c) => {
|
||||
if (c <= 1) {
|
||||
clearInterval(timer);
|
||||
return 0;
|
||||
}
|
||||
return c - 1;
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function submit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (mode === "employee") {
|
||||
toast.info("正在跳转至集团 IAM 统一身份认证…");
|
||||
setTimeout(() => navigate("/app"), 800);
|
||||
return;
|
||||
}
|
||||
navigate("/app");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-full overflow-hidden bg-slate-50">
|
||||
<div className="flex h-screen w-full overflow-hidden bg-[#f5f7fa]">
|
||||
{/* 左侧品牌区 */}
|
||||
<div className="relative hidden w-1/2 flex-col justify-between overflow-hidden bg-gradient-to-br from-[#0f1e3d] via-[#13306b] to-[#2563eb] p-12 text-white lg:flex">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative hidden w-1/2 flex-col justify-between overflow-hidden bg-brand-gradient p-12 text-white lg:flex">
|
||||
<Link to="/portal" className="flex items-center gap-3">
|
||||
<div className="flex size-11 items-center justify-center rounded-xl bg-white/15 backdrop-blur">
|
||||
<Hexagon className="size-6" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-lg font-semibold">滨化集团智慧招标平台</div>
|
||||
<div className="text-sm text-white/60">Binhua Smart Tendering Platform</div>
|
||||
<div className="text-sm text-white/70">Smart Tendering Platform</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="mb-4 inline-flex items-center gap-2 rounded-full bg-white/10 px-3 py-1 text-sm backdrop-blur">
|
||||
<Sparkles className="size-4" /> AI 原生 · 全流程线上闭环
|
||||
</div>
|
||||
<h1 className="text-white" style={{ fontSize: 40, lineHeight: 1.2, fontWeight: 700 }}>
|
||||
智慧招标,AI 赋能
|
||||
<h1 className="text-white" style={{ fontSize: 44, lineHeight: 1.2, fontWeight: 700, letterSpacing: 2 }}>
|
||||
智慧招标平台
|
||||
</h1>
|
||||
<p className="mt-4 max-w-md text-white/70">
|
||||
从需求提报到合同归档,角色化工作台与智能助手让复杂招标流程直观、高效,实现「事找人」的主动式智能体验。
|
||||
<p className="mt-5 max-w-md text-white/80">
|
||||
AI 原生 · 全流程线上闭环 · 主动式智能
|
||||
</p>
|
||||
<p className="mt-2 max-w-md text-sm text-white/60">
|
||||
覆盖公开招标 · 邀请招标 · 竞争性谈判 · 单一来源
|
||||
</p>
|
||||
<div className="mt-8 grid grid-cols-3 gap-4">
|
||||
{[
|
||||
{ k: "全流程", v: "9 大阶段闭环" },
|
||||
{ k: "AI 预填", v: "委托效率 +60%" },
|
||||
{ k: "AI 寻源", v: "委托效率 +60%" },
|
||||
{ k: "链上存证", v: "投标哈希上链" },
|
||||
].map((s) => (
|
||||
<div key={s.k} className="rounded-xl bg-white/10 p-4 backdrop-blur">
|
||||
<div className="text-lg font-semibold">{s.k}</div>
|
||||
<div className="mt-1 text-xs text-white/60">{s.v}</div>
|
||||
<div className="mt-1 text-xs text-white/70">{s.v}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-white/40">© 2026 滨化集团股份有限公司 · 版权所有</div>
|
||||
<div className="text-xs text-white/50">© 2026 滨化集团 | 智慧招标平台 V1.0</div>
|
||||
|
||||
{/* 装饰 */}
|
||||
<div className="pointer-events-none absolute -right-20 top-1/4 size-72 rounded-full bg-blue-400/20 blur-3xl" />
|
||||
<div className="pointer-events-none absolute -bottom-10 right-1/3 size-60 rounded-full bg-violet-400/20 blur-3xl" />
|
||||
<div className="pointer-events-none absolute -right-24 top-1/4 size-80 rounded-full bg-white/10 blur-3xl" />
|
||||
<div className="pointer-events-none absolute -bottom-16 right-1/3 size-60 rounded-full bg-white/10 blur-3xl" />
|
||||
<div className="pointer-events-none absolute left-1/2 top-1/2 size-72 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/10" />
|
||||
<div className="pointer-events-none absolute left-1/2 top-1/2 size-52 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/10" />
|
||||
</div>
|
||||
|
||||
{/* 右侧表单区 */}
|
||||
<div className="flex flex-1 items-center justify-center p-8">
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="mb-8 lg:hidden">
|
||||
<div className="w-full max-w-[400px]">
|
||||
<div className="mb-6 lg:hidden">
|
||||
<div className="flex items-center gap-2">
|
||||
<Hexagon className="size-7 text-primary" />
|
||||
<span className="font-semibold">滨化智慧招标平台</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="text-foreground">账号登录</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">欢迎回来,请登录您的工作账号</p>
|
||||
<div className="text-center">
|
||||
<h2 className="text-foreground" style={{ fontSize: 24 }}>欢迎登录</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">登录您的账户,开始智能招采之旅</p>
|
||||
</div>
|
||||
|
||||
<form
|
||||
className="mt-8 space-y-4"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
navigate("/app");
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm">账号</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
value={account}
|
||||
onChange={(e) => setAccount(e.target.value)}
|
||||
className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none focus:border-primary"
|
||||
placeholder="请输入账号"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm">密码</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
type="password"
|
||||
value={pwd}
|
||||
onChange={(e) => setPwd(e.target.value)}
|
||||
className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none focus:border-primary"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="relative flex-1">
|
||||
<ShieldCheck className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none focus:border-primary"
|
||||
placeholder="验证码"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-11 w-28 items-center justify-center rounded-lg bg-gradient-to-r from-blue-100 to-violet-100 font-mono tracking-widest text-primary select-none">
|
||||
8 K 2 7
|
||||
</div>
|
||||
</div>
|
||||
{/* 角色切换开关: 内部员工 / 外部账号 */}
|
||||
<div className="mt-6 flex rounded-full bg-muted p-1">
|
||||
{(
|
||||
[
|
||||
{ id: "employee" as Mode, label: "内部员工" },
|
||||
{ id: "vendor" as Mode, label: "外部账号" },
|
||||
]
|
||||
).map((m) => (
|
||||
<button
|
||||
key={m.id}
|
||||
type="button"
|
||||
onClick={() => setMode(m.id)}
|
||||
className={cn(
|
||||
"flex-1 rounded-full py-2 text-sm font-medium transition-all",
|
||||
mode === m.id ? "bg-brand-gradient text-white shadow-sm" : "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{m.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-2 text-center text-xs text-primary">
|
||||
{mode === "employee" ? "当前:内部员工模式(走 IAM 集团统一认证)" : "当前:外部账号模式(走平台账号 / SRM 认证)"}
|
||||
</p>
|
||||
|
||||
{/* Tab: 账号登录 / 手机验证码 */}
|
||||
<div className="mt-5 flex border-b border-border">
|
||||
{(
|
||||
[
|
||||
{ id: "account" as Tab, label: "账号登录" },
|
||||
{ id: "mobile" as Tab, label: "手机验证码" },
|
||||
]
|
||||
).map((t) => (
|
||||
<button
|
||||
key={t.id}
|
||||
type="button"
|
||||
onClick={() => setTab(t.id)}
|
||||
className={cn(
|
||||
"relative -mb-px px-4 pb-2.5 text-sm transition-colors",
|
||||
tab === t.id ? "font-medium text-foreground" : "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{t.label}
|
||||
{tab === t.id && <span className="absolute inset-x-2 -bottom-px h-0.5 rounded-full bg-brand-gradient" />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<form className="mt-5 space-y-4" onSubmit={submit}>
|
||||
{tab === "account" ? (
|
||||
<>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">账号 / 手机号</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
value={account}
|
||||
onChange={(e) => setAccount(e.target.value)}
|
||||
className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none transition-colors focus:border-primary"
|
||||
placeholder="请输入您的账号"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">密码</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
type={showPwd ? "text" : "password"}
|
||||
value={pwd}
|
||||
onChange={(e) => setPwd(e.target.value)}
|
||||
className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-10 text-sm outline-none transition-colors focus:border-primary"
|
||||
placeholder="请输入您的密码"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPwd((s) => !s)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{showPwd ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">手机号</label>
|
||||
<div className="relative">
|
||||
<Smartphone className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none transition-colors focus:border-primary"
|
||||
placeholder="请输入11位手机号"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm text-[#4a5568]">验证码</label>
|
||||
<div className="flex gap-3">
|
||||
<div className="relative flex-1">
|
||||
<ShieldCheck className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
className="h-11 w-full rounded-lg border bg-input-background pl-10 pr-3 text-sm outline-none transition-colors focus:border-primary"
|
||||
placeholder="6 位验证码"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={sendCode}
|
||||
disabled={countdown > 0}
|
||||
className="h-11 w-28 shrink-0 rounded-lg border border-primary text-sm font-medium text-primary transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:border-border disabled:text-muted-foreground"
|
||||
>
|
||||
{countdown > 0 ? `${countdown}s 后重发` : "发送验证码"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<label className="flex items-center gap-2 text-muted-foreground">
|
||||
<input type="checkbox" className="size-4 rounded border-border accent-[var(--primary)]" defaultChecked />
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={remember}
|
||||
onChange={(e) => setRemember(e.target.checked)}
|
||||
className="size-4 rounded border-border accent-[var(--primary)]"
|
||||
/>
|
||||
记住我
|
||||
</label>
|
||||
<button type="button" className="text-primary hover:underline">
|
||||
@@ -121,15 +248,33 @@ export function LoginPage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="h-11 w-full">
|
||||
登录 <ArrowRight className="size-4" />
|
||||
<Button type="submit" className="h-11 w-full bg-brand-gradient text-base hover:opacity-90">
|
||||
登 录 <ArrowRight className="size-4" />
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center text-sm text-muted-foreground">
|
||||
还不是平台供应商?
|
||||
<Link to="/register" className="ml-1 font-medium text-primary hover:underline">
|
||||
立即注册
|
||||
<div className="mt-5 text-center text-sm text-muted-foreground">
|
||||
还没有账号?
|
||||
{mode === "employee" ? (
|
||||
<span className="ml-1 text-muted-foreground/70">员工账号由 HR 系统统一同步</span>
|
||||
) : (
|
||||
<Link to="/register" className="ml-1 font-medium text-primary hover:underline">
|
||||
立即注册
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SRM 温馨提示 */}
|
||||
{mode === "vendor" && (
|
||||
<div className="mt-5 flex items-start gap-2 rounded-lg border border-[#ffcc80] bg-[#fff3e0] px-3 py-2.5 text-xs text-[#e65100]">
|
||||
<Info className="mt-0.5 size-4 shrink-0" />
|
||||
<span>原 SRM 注册用户,可直接使用 SRM 账号密码登录,无需重复注册。</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<Link to="/portal" className="text-xs text-muted-foreground hover:text-primary">
|
||||
浏览平台首页与公开招标公告 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user