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
@@ -0,0 +1,210 @@
import { useState } from "react";
import { Search, Filter, Dices, Loader2, Check, ShieldAlert, UserCog } from "lucide-react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "../components/ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../components/ui/select";
import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { PageHeader } from "../components/shared/common";
import { experts } from "../data/mock";
export function ExpertManagement() {
const [q, setQ] = useState("");
const [cat, setCat] = useState("all");
const [open, setOpen] = useState(false);
const [drawing, setDrawing] = useState(false);
const [result, setResult] = useState<typeof experts | null>(null);
const list = experts.filter(
(e) => (cat === "all" || e.category === cat) && e.name.includes(q),
);
function draw() {
setDrawing(true);
setResult(null);
setTimeout(() => {
setDrawing(false);
// 抽取未需回避的专家
setResult(experts.filter((e) => !e.avoid).slice(0, 3));
}, 1800);
}
return (
<div>
<PageHeader
title="专家管理"
description="管理内外部评标专家,支持随机抽取与回避校验"
actions={
<Button onClick={() => { setOpen(true); setResult(null); }}>
<Dices className="size-4" />
</Button>
}
/>
<Card className="mb-4 flex-row items-center gap-3 p-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
<input
value={q}
onChange={(e) => setQ(e.target.value)}
placeholder="搜索专家姓名"
className="h-9 w-full rounded-lg border bg-input-background pl-9 pr-3 text-sm outline-none focus:border-primary"
/>
</div>
<Select value={cat} onValueChange={setCat}>
<SelectTrigger className="w-40">
<Filter className="size-4" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="化工工艺"></SelectItem>
<SelectItem value="自动化控制"></SelectItem>
<SelectItem value="环保工程"></SelectItem>
<SelectItem value="招标采购"></SelectItem>
<SelectItem value="机械设备"></SelectItem>
</SelectContent>
</Select>
</Card>
<Card className="gap-0 overflow-hidden p-0">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/40 text-left text-muted-foreground">
<th className="px-5 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>
<th className="px-4 py-3 font-medium"></th>
</tr>
</thead>
<tbody className="divide-y">
{list.map((e) => (
<tr key={e.id} className="hover:bg-muted/30">
<td className="px-5 py-4 font-medium text-foreground">{e.name}</td>
<td className="px-4 py-4 text-muted-foreground">{e.title}</td>
<td className="px-4 py-4 text-muted-foreground">{e.category}</td>
<td className="px-4 py-4 text-muted-foreground">{e.region}</td>
<td className="px-4 py-4 text-muted-foreground">{e.reviewCount}</td>
<td className="px-4 py-4">
<span
className={`rounded px-2 py-0.5 text-xs ${
e.type === "内部" ? "bg-blue-50 text-blue-600" : "bg-violet-50 text-violet-600"
}`}
>
{e.type}
</span>
</td>
<td className="px-4 py-4">
{e.avoid ? (
<span className="inline-flex items-center gap-1 text-red-600">
<ShieldAlert className="size-4" />
</span>
) : (
<span className="text-emerald-600"></span>
)}
</td>
</tr>
))}
</tbody>
</table>
</Card>
{/* 抽取弹窗 */}
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Dices className="size-5 text-primary" />
</DialogTitle>
</DialogHeader>
{!result && !drawing && (
<div className="space-y-4 py-2">
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<label className="mb-1.5 block"></label>
<Select defaultValue="自动化控制">
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="自动化控制"></SelectItem>
<SelectItem value="化工工艺"></SelectItem>
<SelectItem value="环保工程"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="mb-1.5 block"></label>
<Select defaultValue="3">
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="3">3 </SelectItem>
<SelectItem value="5">5 </SelectItem>
<SelectItem value="7">7 </SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex items-start gap-2 rounded-lg bg-muted/40 p-3 text-xs text-muted-foreground">
<ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-500" />
</div>
</div>
)}
{drawing && (
<div className="flex flex-col items-center justify-center gap-3 py-10">
<Loader2 className="size-10 animate-spin text-primary" />
<p className="text-sm text-muted-foreground"></p>
</div>
)}
{result && (
<div className="space-y-3 py-2">
<div className="flex items-center gap-2 rounded-lg bg-emerald-50 px-3 py-2 text-sm text-emerald-700">
<Check className="size-4" /> 1
</div>
{result.map((e) => (
<div key={e.id} className="flex items-center gap-3 rounded-lg border bg-card p-3">
<div className="flex size-9 items-center justify-center rounded-full bg-primary/10 text-primary">
<UserCog className="size-4" />
</div>
<div className="flex-1">
<div className="font-medium text-foreground">{e.name}</div>
<div className="text-xs text-muted-foreground">
{e.title} · {e.category} · {e.region}
</div>
</div>
<span className="text-xs text-emerald-600"></span>
</div>
))}
</div>
)}
<DialogFooter>
{!result ? (
<Button onClick={draw} disabled={drawing}>
<Dices className="size-4" /> {drawing ? "抽取中…" : "开始抽取"}
</Button>
) : (
<Button onClick={() => setOpen(false)}></Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}