feat(frontend): 对齐万华门户风格并更新全量截图文档

- 新增门户导航子页面(交易信息、政策法规、操作指南、常见问题、采购平台)并统一门户壳层样式
- 按万华站点风格重构首页与登录页(导航、轮播、公告、登录区)
- 优化小滨助手:首页/登录页接入、可拖拽吸边、圆形图标、圆形外框与切换动画优化
- 重新生成全量页面截图并更新《滨化智慧招标平台-前端界面截图汇总.docx》

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 15:34:56 +08:00
parent aa023a6d45
commit 41fc6e86dd
56 changed files with 1503 additions and 460 deletions
+118 -85
View File
@@ -1,17 +1,27 @@
import { useState } from "react";
import { useNavigate, Link } from "react-router";
import { Hexagon, User, Lock, Smartphone, ShieldCheck, ArrowRight, Eye, EyeOff, Info } from "lucide-react";
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);
@@ -48,70 +58,59 @@ export function LoginPage() {
navigate("/app");
}
return (
<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-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/70">Smart Tendering Platform</div>
</div>
</Link>
function selectRole(role: LoginRole) {
setLoginRole(role);
// 与现有登录逻辑联动:供应商走外部账号,其他角色默认走内部员工
if (role === "supplier") {
setMode("vendor");
setTab("account");
} else {
setMode("employee");
setTab("account");
}
}
<div className="relative z-10">
<h1 className="text-white" style={{ fontSize: 44, lineHeight: 1.2, fontWeight: 700, letterSpacing: 2 }}>
</h1>
<p className="mt-5 max-w-md text-white/80">
AI · 线 ·
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>
<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: "链上存证", 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/70">{s.v}</div>
</div>
{/* 角色登录按钮:对齐万华站点风格 */}
<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>
<div className="text-xs text-white/50">© 2026 V1.0</div>
{/* 装饰 */}
<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-[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>
<div className="text-center">
<h2 className="text-foreground" style={{ fontSize: 24 }}></h2>
<p className="mt-1 text-sm text-muted-foreground"></p>
</div>
{/* 角色切换开关: 内部员工 / 外部账号 */}
<div className="mt-6 flex rounded-full bg-muted p-1">
<div className="mt-4 flex border bg-[#f8fafc] p-1">
{(
[
{ id: "employee" as Mode, label: "内部员工" },
@@ -123,20 +122,19 @@ export function LoginPage() {
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",
"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-2 text-center text-xs text-primary">
{mode === "employee" ? "当前:内部员工模式(走 IAM 集团统一认证)" : "当前:外部账号模式(走平台账号 / SRM 认证)"}
<p className="mt-1 text-[11px] text-muted-foreground">
{ROLE_BUTTONS.find((x) => x.id === loginRole)?.label}
</p>
{/* Tab: 账号登录 / 手机验证码 */}
<div className="mt-5 flex border-b border-border">
<div className="mt-3 flex border-b border-border">
{(
[
{ id: "account" as Tab, label: "账号登录" },
@@ -148,17 +146,17 @@ export function LoginPage() {
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",
"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-2 -bottom-px h-0.5 rounded-full bg-brand-gradient" />}
{tab === t.id && <span className="absolute inset-x-1 bottom-0 h-0.5 bg-primary" />}
</button>
))}
</div>
<form className="mt-5 space-y-4" onSubmit={submit}>
<form className="mt-4 space-y-3" onSubmit={submit}>
{tab === "account" ? (
<>
<div>
@@ -248,12 +246,12 @@ export function LoginPage() {
</button>
</div>
<Button type="submit" className="h-11 w-full bg-brand-gradient text-base hover:opacity-90">
<ArrowRight className="size-4" />
<Button type="submit" className="h-10 w-full text-base">
</Button>
</form>
<div className="mt-5 text-center text-sm text-muted-foreground">
<div className="mt-4 text-center text-sm text-muted-foreground">
{mode === "employee" ? (
<span className="ml-1 text-muted-foreground/70"> HR </span>
@@ -263,22 +261,57 @@ export function LoginPage() {
</Link>
)}
</div>
<div className="mt-4 border bg-[#fffbea] px-3 py-2 text-xs text-[#92400e]">
SRM 使 SRM
</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">
<div className="mt-4 text-center">
<Link to="/portal" className="text-xs text-muted-foreground hover:text-primary">
</Link>
</div>
</div>
</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>
);
}