feat(front-end): 初始化STP前端工程并提交页面框架
引入前端项目基础结构、页面骨架和UI组件,建立可运行的开发与构建配置,方便后续按模块迭代。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import { Sparkles, Check } from "lucide-react";
|
||||
import { cn } from "../ui/utils";
|
||||
import { Badge } from "../ui/badge";
|
||||
import type { ProjectStatus } from "../../data/mock";
|
||||
|
||||
/** AI 生成 / AI 能力标签 */
|
||||
export function AiBadge({ label = "AI 生成", className }: { label?: string; className?: string }) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-md bg-gradient-to-r from-violet-500 to-blue-500 px-2 py-0.5 text-xs font-medium text-white",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Sparkles className="size-3" />
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** 页面标题区 */
|
||||
export function PageHeader({
|
||||
title,
|
||||
description,
|
||||
actions,
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
actions?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-foreground">{title}</h1>
|
||||
{description && <p className="mt-1 text-sm text-muted-foreground">{description}</p>}
|
||||
</div>
|
||||
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const statusColor: Record<string, string> = {
|
||||
需求委托: "bg-slate-100 text-slate-700",
|
||||
立项审批: "bg-amber-100 text-amber-700",
|
||||
招标文件: "bg-sky-100 text-sky-700",
|
||||
投标开标: "bg-indigo-100 text-indigo-700",
|
||||
在线评标: "bg-blue-100 text-blue-700",
|
||||
定标审批: "bg-violet-100 text-violet-700",
|
||||
合同归档: "bg-teal-100 text-teal-700",
|
||||
已完成: "bg-emerald-100 text-emerald-700",
|
||||
};
|
||||
|
||||
export function StatusBadge({ status }: { status: ProjectStatus | string }) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
|
||||
statusColor[status] ?? "bg-slate-100 text-slate-700",
|
||||
)}
|
||||
>
|
||||
{status}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function RiskBadge({ risk }: { risk: string }) {
|
||||
const map: Record<string, string> = {
|
||||
正常: "bg-emerald-50 text-emerald-700 border-emerald-200",
|
||||
关注: "bg-amber-50 text-amber-700 border-amber-200",
|
||||
高风险: "bg-red-50 text-red-700 border-red-200",
|
||||
};
|
||||
return (
|
||||
<Badge variant="outline" className={cn("border", map[risk])}>
|
||||
{risk}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
/** AI 审核结论标签 */
|
||||
export function AiResultTag({ result }: { result: string }) {
|
||||
const ok = result === "资质有效";
|
||||
const map: Record<string, string> = {
|
||||
资质有效: "bg-emerald-50 text-emerald-700 border-emerald-200",
|
||||
存在关联风险: "bg-amber-50 text-amber-700 border-amber-200",
|
||||
工商异常: "bg-red-50 text-red-700 border-red-200",
|
||||
};
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-md border px-2 py-0.5 text-xs font-medium",
|
||||
map[result] ?? "bg-slate-50 text-slate-700 border-slate-200",
|
||||
)}
|
||||
>
|
||||
<Sparkles className="size-3" />
|
||||
{result}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** 横向步骤条 */
|
||||
export function Steps({
|
||||
steps,
|
||||
current,
|
||||
}: {
|
||||
steps: string[];
|
||||
current: number;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{steps.map((s, i) => {
|
||||
const done = i < current;
|
||||
const active = i === current;
|
||||
return (
|
||||
<div key={s} className="flex flex-1 items-center last:flex-none">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-8 shrink-0 items-center justify-center rounded-full border-2 text-sm font-medium transition-colors",
|
||||
done && "border-primary bg-primary text-primary-foreground",
|
||||
active && "border-primary bg-primary/10 text-primary",
|
||||
!done && !active && "border-border bg-card text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{done ? <Check className="size-4" /> : i + 1}
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"whitespace-nowrap text-sm",
|
||||
active ? "font-medium text-foreground" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{s}
|
||||
</span>
|
||||
</div>
|
||||
{i < steps.length - 1 && (
|
||||
<div className={cn("mx-3 h-0.5 flex-1 rounded", done ? "bg-primary" : "bg-border")} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ApprovalNode {
|
||||
name: string;
|
||||
role: string;
|
||||
status: "completed" | "current" | "pending";
|
||||
time?: string;
|
||||
}
|
||||
|
||||
/** 审批流程跟踪条 */
|
||||
export function ApprovalTracker({ nodes }: { nodes: ApprovalNode[] }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-0">
|
||||
{nodes.map((n, i) => (
|
||||
<div key={n.name} className="flex gap-3">
|
||||
<div className="flex flex-col items-center">
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-7 shrink-0 items-center justify-center rounded-full text-xs",
|
||||
n.status === "completed" && "bg-emerald-500 text-white",
|
||||
n.status === "current" && "bg-primary text-white ring-4 ring-primary/15",
|
||||
n.status === "pending" && "bg-muted text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{n.status === "completed" ? <Check className="size-4" /> : i + 1}
|
||||
</div>
|
||||
{i < nodes.length - 1 && (
|
||||
<div className={cn("my-1 w-0.5 flex-1", n.status === "completed" ? "bg-emerald-500" : "bg-border")} />
|
||||
)}
|
||||
</div>
|
||||
<div className="pb-5">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground">{n.role}</span>
|
||||
{n.status === "current" && (
|
||||
<span className="rounded bg-primary/10 px-1.5 py-0.5 text-xs text-primary">审批中</span>
|
||||
)}
|
||||
{n.status === "completed" && (
|
||||
<span className="rounded bg-emerald-50 px-1.5 py-0.5 text-xs text-emerald-600">已通过</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{n.name}</p>
|
||||
{n.time && <p className="mt-0.5 text-xs text-muted-foreground/70">{n.time}</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
LayoutDashboard,
|
||||
FolderKanban,
|
||||
Building2,
|
||||
UserCog,
|
||||
BarChart3,
|
||||
Settings,
|
||||
ClipboardCheck,
|
||||
ShieldAlert,
|
||||
PackageOpen,
|
||||
PackagePlus,
|
||||
Wallet,
|
||||
FileSignature,
|
||||
GitMerge,
|
||||
Users,
|
||||
Workflow,
|
||||
KeyRound,
|
||||
ScrollText,
|
||||
Network,
|
||||
FileCheck2,
|
||||
Gavel,
|
||||
FileText,
|
||||
Award,
|
||||
Eye,
|
||||
CheckCircle2,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
|
||||
export const ICONS: Record<string, any> = {
|
||||
LayoutDashboard,
|
||||
FolderKanban,
|
||||
Building2,
|
||||
UserCog,
|
||||
BarChart3,
|
||||
Settings,
|
||||
ClipboardCheck,
|
||||
ShieldAlert,
|
||||
PackageOpen,
|
||||
PackagePlus,
|
||||
Wallet,
|
||||
FileSignature,
|
||||
GitMerge,
|
||||
Users,
|
||||
Workflow,
|
||||
KeyRound,
|
||||
ScrollText,
|
||||
Network,
|
||||
FileCheck2,
|
||||
Gavel,
|
||||
FileText,
|
||||
Award,
|
||||
Eye,
|
||||
CheckCircle2,
|
||||
TrendingUp,
|
||||
};
|
||||
Reference in New Issue
Block a user