Files
STP/front-end/src/app/pages/LoginPage.tsx
T
vame 3dba6db9d2 feat(frontend): 移除万华相关元素并更新门户登录展示
统一清理前端万华相关文案与公司展示口径为滨化集团,移除登录与门户页角色选框,并同步更新全量页面截图及Word汇总文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 15:48:44 +08:00

273 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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";
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="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 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>
<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>
);
}