Files
STP/front-end/src/app/pages/AwardApproval.tsx
T
vame 7f0f79420c feat(front-end): 初始化STP前端工程并提交页面框架
引入前端项目基础结构、页面骨架和UI组件,建立可运行的开发与构建配置,方便后续按模块迭代。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 14:25:22 +08:00

148 lines
6.9 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 { 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>
);
}