41fc6e86dd
- 新增门户导航子页面(交易信息、政策法规、操作指南、常见问题、采购平台)并统一门户壳层样式 - 按万华站点风格重构首页与登录页(导航、轮播、公告、登录区) - 优化小滨助手:首页/登录页接入、可拖拽吸边、圆形图标、圆形外框与切换动画优化 - 重新生成全量页面截图并更新《滨化智慧招标平台-前端界面截图汇总.docx》 Co-authored-by: Cursor <cursoragent@cursor.com>
318 lines
13 KiB
TypeScript
318 lines
13 KiB
TypeScript
import { useState } from "react";
|
||
import { useNavigate, Link } from "react-router";
|
||
import { Hexagon, User, Lock, Smartphone, ShieldCheck, Eye, EyeOff } from "lucide-react";
|
||
import { toast } from "sonner";
|
||
import { Button } from "../components/ui/button";
|
||
import { cn } from "../components/ui/utils";
|
||
import { XiaobinAssistant } from "../components/XiaobinAssistant";
|
||
|
||
type Mode = "employee" | "vendor";
|
||
type Tab = "account" | "mobile";
|
||
type LoginRole = "purchaser" | "supplier" | "expert" | "other";
|
||
|
||
const ROLE_BUTTONS: { id: LoginRole; label: string; en: string }[] = [
|
||
{ id: "purchaser", label: "采购人", en: "Purchaser" },
|
||
{ id: "supplier", label: "供应商", en: "Supplier" },
|
||
{ id: "expert", label: "专家", en: "Expert" },
|
||
{ id: "other", label: "其他", en: "Other" },
|
||
];
|
||
|
||
export function LoginPage() {
|
||
const navigate = useNavigate();
|
||
const [mode, setMode] = useState<Mode>("vendor");
|
||
const [tab, setTab] = useState<Tab>("account");
|
||
const [loginRole, setLoginRole] = useState<LoginRole>("supplier");
|
||
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");
|
||
}
|
||
|
||
function selectRole(role: LoginRole) {
|
||
setLoginRole(role);
|
||
// 与现有登录逻辑联动:供应商走外部账号,其他角色默认走内部员工
|
||
if (role === "supplier") {
|
||
setMode("vendor");
|
||
setTab("account");
|
||
} else {
|
||
setMode("employee");
|
||
setTab("account");
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-[#f3f5f8]">
|
||
<header className="border-b bg-[#0f4ea6] text-white">
|
||
<div className="mx-auto flex h-14 max-w-[1200px] items-center justify-between px-6">
|
||
<Link to="/portal" className="flex items-center gap-2">
|
||
<Hexagon className="size-5" />
|
||
<span className="text-base font-semibold">滨化电子招标投标交易网</span>
|
||
</Link>
|
||
<div className="text-xs text-white/80">欢迎登录 STP 系统</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main className="mx-auto grid max-w-[1200px] grid-cols-12 gap-4 px-6 py-8">
|
||
<section className="col-span-12 border bg-white p-5 lg:col-span-4">
|
||
<h2 className="text-lg font-semibold">用户登录</h2>
|
||
<p className="mt-1 text-xs text-muted-foreground">
|
||
还没有账号?
|
||
<Link to="/register" className="ml-1 text-primary hover:underline">去注册</Link>
|
||
</p>
|
||
|
||
{/* 角色登录按钮:对齐万华站点风格 */}
|
||
<div className="mt-4 grid grid-cols-2 gap-2">
|
||
{ROLE_BUTTONS.map((r) => (
|
||
<button
|
||
key={r.id}
|
||
type="button"
|
||
onClick={() => selectRole(r.id)}
|
||
className={cn(
|
||
"border px-3 py-3 text-center transition-colors",
|
||
loginRole === r.id
|
||
? "border-primary bg-[#eef5ff] text-primary"
|
||
: "border-[#d9dee7] bg-white text-[#374151] hover:border-primary/60 hover:bg-[#f8fbff]",
|
||
)}
|
||
>
|
||
<div className="text-sm font-medium leading-none">{r.label}</div>
|
||
<div className="mt-1 text-[11px] text-[#6b7280]">{r.en}</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="mt-4 flex border bg-[#f8fafc] 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 py-2 text-sm",
|
||
mode === m.id ? "bg-primary font-medium text-white" : "text-muted-foreground",
|
||
)}
|
||
>
|
||
{m.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
<p className="mt-1 text-[11px] text-muted-foreground">
|
||
当前角色:{ROLE_BUTTONS.find((x) => x.id === loginRole)?.label}
|
||
</p>
|
||
|
||
<div className="mt-3 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 px-4 py-2 text-sm",
|
||
tab === t.id ? "font-medium text-primary" : "text-muted-foreground",
|
||
)}
|
||
>
|
||
{t.label}
|
||
{tab === t.id && <span className="absolute inset-x-1 bottom-0 h-0.5 bg-primary" />}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<form className="mt-4 space-y-3" 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"
|
||
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">
|
||
忘记密码?
|
||
</button>
|
||
</div>
|
||
|
||
<Button type="submit" className="h-10 w-full text-base">
|
||
登 录
|
||
</Button>
|
||
</form>
|
||
|
||
<div className="mt-4 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>
|
||
<div className="mt-4 border bg-[#fffbea] px-3 py-2 text-xs text-[#92400e]">
|
||
原 SRM 注册用户,可直接使用 SRM 账号登录,无需重复注册。
|
||
</div>
|
||
|
||
<div className="mt-4 text-center">
|
||
<Link to="/portal" className="text-xs text-muted-foreground hover:text-primary">
|
||
返回门户首页
|
||
</Link>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="col-span-12 space-y-4 lg:col-span-8">
|
||
<div className="border bg-white">
|
||
<div className="border-b bg-[#f8fafc] px-4 py-3 text-sm font-semibold">交易信息</div>
|
||
<ul className="space-y-1 p-4 text-sm">
|
||
{[
|
||
["[公开招标] 扣电测试柜框架", "2026-07-13"],
|
||
["[公开招标] 2026年橡胶密封件框架招标O型圈", "2026-07-13"],
|
||
["[公开招标] 万华莱州100MW分散式风电项目EPC工程总承包[变更公告]", "2026-07-13"],
|
||
["[公开招标] 万华旋塞阀框架包一 金属旋塞阀、包二 衬氟旋塞阀", "2026-07-10"],
|
||
["[公开招标] 改性TPU熟化料仓", "2026-07-09"],
|
||
].map(([title, date]) => (
|
||
<li key={title} className="flex items-center gap-3 border-b border-dashed py-1.5">
|
||
<span className="line-clamp-1 flex-1 hover:text-primary">{title}</span>
|
||
<span className="text-xs text-muted-foreground">{date}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="border bg-white">
|
||
<div className="border-b bg-[#f8fafc] px-4 py-3 text-sm font-semibold">通知公告</div>
|
||
<div className="space-y-1 p-4 text-sm text-muted-foreground">
|
||
<p>万华蓬莱园区高温管道耐火衬里类物资供应商招募</p>
|
||
<p>万华化学桨叶冷却机类供应商招募</p>
|
||
<p>万华化学洗车平台服务类供应商招募</p>
|
||
</div>
|
||
</div>
|
||
<div className="border bg-white">
|
||
<div className="border-b bg-[#f8fafc] px-4 py-3 text-sm font-semibold">操作指南</div>
|
||
<div className="space-y-1 p-4 text-sm text-muted-foreground">
|
||
<p>供应商注册操作指南</p>
|
||
<p>投标文件制作软件操作手册</p>
|
||
<p>系统使用常见问题处理</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
<XiaobinAssistant pageType="login" />
|
||
</div>
|
||
);
|
||
}
|