feat(front-end): 依据内部评审意见对前端 UI 进行二次升级优化

- 主题色统一改回科技蓝(呼应滨化 LOGO)
- 合并"采购需求人员"、移除审批人角色(审批在 OA)
- 发起委托归属采购需求人员,招标专员不含该功能
- 删除合并招标/定标审批/合同生成/合同管理/供应商入库/审批中心/流程配置页面
- 小滨助手默认展开可收起、各角色主动提醒、问答直接给出按钮/链接
- 待办中心去掉"事找人"前缀
- 新增后台智能体任务监控组件(招标专员/管理员)
- 发起委托增加导入采购计划/直接填写/选择模板(含自动)
- 项目列表:排序/高级搜索/委托人列/提交审批/列显隐
- 项目详情:修正流程步骤、委托人信息、方案与文件合一、提交审批
- 长短名单去分支与推荐部门列、寻源关联项目并支持三来源
- 开标去自动开标、专家管理去抽取与回避、供应商管理去新增与入库
This commit is contained in:
vame
2026-07-10 23:35:54 +08:00
parent 38a804ce41
commit d81ac936e7
23 changed files with 844 additions and 1720 deletions
+5 -122
View File
@@ -1,12 +1,5 @@
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 { Search, Filter } from "lucide-react";
import {
Select,
SelectContent,
@@ -14,42 +7,25 @@ import {
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>
}
description="维护内外部评标专家库信息(随机抽取与回避校验在具体项目评标流程中进行)"
/>
<Card className="mb-4 flex-row items-center gap-3 p-4">
@@ -88,7 +64,6 @@ export function ExpertManagement() {
<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">
@@ -102,109 +77,17 @@ export function ExpertManagement() {
<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 === "内部" ? "bg-blue-50 text-blue-600" : "bg-sky-50 text-sky-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>
);
}