feat(front-end): 初始化STP前端工程并提交页面框架

引入前端项目基础结构、页面骨架和UI组件,建立可运行的开发与构建配置,方便后续按模块迭代。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-22 14:25:22 +08:00
parent f6ff2e3625
commit 7f0f79420c
107 changed files with 18103 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
import { useNavigate } from "react-router";
import { ArrowLeft, Sparkles, Trophy, ThumbsUp, RotateCcw, FileText } from "lucide-react";
import { toast } from "sonner";
import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { AiBadge, ApprovalTracker } from "../components/shared/common";
const ranking = [
{ rank: 1, name: "供应商 A(浙江中控技术)", tech: 56, biz: 18, price: 28, total: 102, reason: "技术方案最优,冗余架构与本地化服务承诺突出,综合得分第一。" },
{ rank: 2, name: "供应商 B(和利时科技)", tech: 52, biz: 17, price: 30, total: 99, reason: "报价最具竞争力,技术方案满足要求,略逊于第一名。" },
{ rank: 3, name: "供应商 C(西门子中国)", tech: 54, biz: 16, price: 24, total: 94, reason: "品牌与性能优秀,但报价偏高,价格分较低。" },
];
export function AwardApproval() {
const navigate = useNavigate();
return (
<div>
<div className="mb-1 flex items-center gap-3">
<Button variant="ghost" size="icon" onClick={() => navigate(-1)}>
<ArrowLeft className="size-5" />
</Button>
<div>
<h1 className="text-foreground"></h1>
<p className="text-sm text-muted-foreground"> · </p>
</div>
</div>
<div className="mt-5 grid grid-cols-3 gap-6">
<div className="col-span-2 space-y-6">
{/* 评标报告 */}
<Card className="gap-0 p-6">
<div className="mb-4 flex items-center gap-2">
<FileText className="size-5 text-primary" />
<h3 className="text-foreground"></h3>
<AiBadge label="AI 汇总" />
</div>
{/* AI 评标汇总表 */}
<div className="overflow-hidden rounded-lg border">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/40 text-left text-muted-foreground">
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 font-medium"></th>
</tr>
</thead>
<tbody className="divide-y">
{ranking.map((r) => (
<tr key={r.rank} className={r.rank === 1 ? "bg-amber-50/50" : ""}>
<td className="px-4 py-3">
{r.rank === 1 ? (
<span className="inline-flex items-center gap-1 font-medium text-amber-600">
<Trophy className="size-4" /> 1
</span>
) : (
<span className="text-muted-foreground"> {r.rank} </span>
)}
</td>
<td className="px-4 py-3 font-medium text-foreground">{r.name}</td>
<td className="px-4 py-3 text-muted-foreground">{r.tech}</td>
<td className="px-4 py-3 text-muted-foreground">{r.biz}</td>
<td className="px-4 py-3 text-muted-foreground">{r.price}</td>
<td className="px-4 py-3 font-semibold text-foreground">{r.total}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* 中标候选人排名及理由 */}
<div className="mt-5">
<h4 className="mb-3 text-foreground"></h4>
<div className="space-y-3">
{ranking.map((r) => (
<div key={r.rank} className="flex gap-3 rounded-lg border bg-card p-4">
<div
className={`flex size-8 shrink-0 items-center justify-center rounded-full text-sm font-semibold ${
r.rank === 1 ? "bg-amber-100 text-amber-700" : "bg-muted text-muted-foreground"
}`}
>
{r.rank}
</div>
<div>
<div className="font-medium text-foreground">{r.name}</div>
<p className="mt-1 text-sm text-muted-foreground">
<Sparkles className="mr-1 inline size-3.5 text-violet-500" />
{r.reason}
</p>
</div>
</div>
))}
</div>
</div>
</Card>
{/* 审批决策 */}
<Card className="gap-0 p-6">
<h3 className="text-foreground"></h3>
<div className="mt-3 rounded-lg bg-primary/5 p-4 text-sm">
<span className="font-semibold text-primary"> A</span>
</div>
<label className="mb-1.5 mt-4 block text-sm"></label>
<textarea
rows={3}
placeholder="请填写您的审批意见…"
className="w-full resize-none rounded-lg border bg-input-background p-3 text-sm outline-none focus:border-primary"
/>
<div className="mt-4 flex gap-3">
<Button
className="flex-1"
onClick={() => {
toast.success("已同意推荐中标人,进入合同生成阶段");
setTimeout(() => navigate("/app/projects/P2026003/contract"), 800);
}}
>
<ThumbsUp className="size-4" />
</Button>
<Button
variant="outline"
className="flex-1 text-destructive"
onClick={() => toast.error("已驳回,项目将重新评标")}
>
<RotateCcw className="size-4" />
</Button>
</div>
</Card>
</div>
<Card className="gap-0 p-6">
<h4 className="mb-4 text-foreground"></h4>
<ApprovalTracker
nodes={[
{ role: "评标委员会", name: "完成评标并出具报告", status: "completed", time: "06-20 16:00" },
{ role: "招标专员复核", name: "王强 · 已复核", status: "completed", time: "06-21 09:30" },
{ role: "分管领导审批", name: "分管副总 · 审批中", status: "current" },
{ role: "定标完成", name: "发出中标通知书", status: "pending" },
]}
/>
</Card>
</div>
</div>
);
}