feat(front-end): 初始化STP前端工程并提交页面框架
引入前端项目基础结构、页面骨架和UI组件,建立可运行的开发与构建配置,方便后续按模块迭代。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Lock,
|
||||
Unlock,
|
||||
ShieldCheck,
|
||||
Link2,
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
Play,
|
||||
Clock,
|
||||
} from "lucide-react";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Card } from "../components/ui/card";
|
||||
import { PageHeader } from "../components/shared/common";
|
||||
import { bidders } from "../data/mock";
|
||||
|
||||
interface LogItem {
|
||||
time: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
icon: any;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const logSeq: LogItem[] = [
|
||||
{ time: "10:00:00", title: "系统自动开标", desc: "开标时间到达,系统自动启动开标程序", icon: Play, color: "#2563eb" },
|
||||
{ time: "10:00:03", title: "投标截止确认", desc: "确认共收到 3 家有效投标文件", icon: CheckCircle2, color: "#0ea5e9" },
|
||||
{ time: "10:00:08", title: "文件自动解密", desc: "使用投标人 CA 证书自动解密投标文件", icon: Unlock, color: "#14b8a6" },
|
||||
{ time: "10:00:15", title: "投标文件哈希上链", desc: "各投标文件哈希值已写入区块链存证,不可篡改", icon: Link2, color: "#8b5cf6" },
|
||||
{ time: "10:00:20", title: "开标完成", desc: "所有投标文件解密成功,开标结束", icon: ShieldCheck, color: "#16a34a" },
|
||||
];
|
||||
|
||||
export function BidOpeningHall() {
|
||||
const navigate = useNavigate();
|
||||
const [started, setStarted] = useState(false);
|
||||
const [shown, setShown] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!started) return;
|
||||
if (shown >= logSeq.length) return;
|
||||
const t = setTimeout(() => setShown((s) => s + 1), 900);
|
||||
return () => clearTimeout(t);
|
||||
}, [started, shown]);
|
||||
|
||||
const done = shown >= logSeq.length;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-1 flex items-center gap-3">
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate(-1)}>
|
||||
<ArrowLeft className="size-5" />
|
||||
</Button>
|
||||
<PageHeader title="开标大厅" description="DCS 控制系统升级采购 · 自动化开标与链上存证" />
|
||||
</div>
|
||||
|
||||
{/* 顶部状态条 */}
|
||||
<Card className="mb-6 flex-row items-center justify-between bg-gradient-to-r from-[#0f1e3d] to-[#1e3a8a] p-6 text-white">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex size-14 items-center justify-center rounded-2xl bg-white/10">
|
||||
{done ? <ShieldCheck className="size-7" /> : <Clock className="size-7" />}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-white/70">开标状态</div>
|
||||
<div className="text-xl font-semibold">
|
||||
{!started ? "等待开标" : done ? "开标已完成" : "开标进行中…"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-8">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-semibold">3</div>
|
||||
<div className="text-xs text-white/60">投标家数</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-semibold">{started ? Math.min(shown, 3) : 0}</div>
|
||||
<div className="text-xs text-white/60">已解密</div>
|
||||
</div>
|
||||
{!started && (
|
||||
<Button className="bg-white text-primary hover:bg-white/90" onClick={() => setStarted(true)}>
|
||||
<Play className="size-4" /> 启动自动开标
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="grid grid-cols-5 gap-6">
|
||||
{/* 实时日志时间轴 */}
|
||||
<Card className="col-span-3 gap-0 p-6">
|
||||
<h3 className="mb-5 text-foreground">开标实时日志</h3>
|
||||
{!started ? (
|
||||
<div className="flex h-64 flex-col items-center justify-center gap-2 text-muted-foreground">
|
||||
<Clock className="size-10 text-muted-foreground/40" />
|
||||
<p className="text-sm">点击右上角「启动自动开标」开始</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-0">
|
||||
{logSeq.slice(0, shown).map((l, i) => {
|
||||
const Icon = l.icon;
|
||||
return (
|
||||
<div key={i} className="flex gap-4">
|
||||
<div className="flex flex-col items-center">
|
||||
<div
|
||||
className="flex size-9 shrink-0 items-center justify-center rounded-full text-white"
|
||||
style={{ backgroundColor: l.color }}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
{i < shown - 1 && <div className="my-1 w-0.5 flex-1 bg-border" />}
|
||||
</div>
|
||||
<div className="pb-5">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-foreground">{l.title}</span>
|
||||
<span className="font-mono text-xs text-muted-foreground">{l.time}</span>
|
||||
</div>
|
||||
<p className="mt-0.5 text-sm text-muted-foreground">{l.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{!done && (
|
||||
<div className="flex items-center gap-2 pl-1 text-sm text-muted-foreground">
|
||||
<Loader2 className="size-4 animate-spin" /> 处理中…
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 解密投标人列表 */}
|
||||
<Card className="col-span-2 gap-0 p-6">
|
||||
<h3 className="mb-4 text-foreground">解密投标人</h3>
|
||||
<div className="space-y-3">
|
||||
{bidders.map((b, i) => {
|
||||
const revealed = started && shown >= 3;
|
||||
return (
|
||||
<div key={b.id} className="rounded-xl border bg-card p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium text-foreground">{revealed ? b.realName : b.anon}</span>
|
||||
{revealed ? (
|
||||
<span className="inline-flex items-center gap-1 text-xs text-emerald-600">
|
||||
<Unlock className="size-3.5" /> 已解密
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Lock className="size-3.5" /> 加密中
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Link2 className="size-3.5 text-violet-500" />
|
||||
哈希存证:<span className="font-mono">{revealed ? b.hash : "····················"}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{done && (
|
||||
<Button className="mt-5 w-full" onClick={() => navigate("/app/projects/P2026001/evaluation")}>
|
||||
进入在线评标室
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user