feat(front-end): 依据内部评审意见对前端 UI 进行二次升级优化
- 主题色统一改回科技蓝(呼应滨化 LOGO) - 合并"采购需求人员"、移除审批人角色(审批在 OA) - 发起委托归属采购需求人员,招标专员不含该功能 - 删除合并招标/定标审批/合同生成/合同管理/供应商入库/审批中心/流程配置页面 - 小滨助手默认展开可收起、各角色主动提醒、问答直接给出按钮/链接 - 待办中心去掉"事找人"前缀 - 新增后台智能体任务监控组件(招标专员/管理员) - 发起委托增加导入采购计划/直接填写/选择模板(含自动) - 项目列表:排序/高级搜索/委托人列/提交审批/列显隐 - 项目详情:修正流程步骤、委托人信息、方案与文件合一、提交审批 - 长短名单去分支与推荐部门列、寻源关联项目并支持三来源 - 开标去自动开标、专家管理去抽取与回避、供应商管理去新增与入库
This commit is contained in:
@@ -1,15 +1,41 @@
|
||||
import { useNavigate } from "react-router";
|
||||
import { ArrowLeft, Sparkles, Send, Eye, Check, Globe } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { ArrowLeft, Sparkles, Send, Eye, Check, Globe, Database, Boxes } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Card } from "../components/ui/card";
|
||||
import { AiBadge } from "../components/shared/common";
|
||||
import { sourcingRecords } from "../data/mock";
|
||||
import { sourcingRecords, projects } from "../data/mock";
|
||||
|
||||
type SourceKey = "all" | "internet" | "srm" | "pool";
|
||||
|
||||
const SOURCES: { key: SourceKey; label: string; icon: any; desc: string }[] = [
|
||||
{ key: "all", label: "全部来源", icon: Sparkles, desc: "AI 综合多渠道寻源" },
|
||||
{ key: "internet", label: "互联网寻源", icon: Globe, desc: "公共资源交易平台等" },
|
||||
{ key: "srm", label: "SRM 库寻源", icon: Database, desc: "集团合格供方库" },
|
||||
{ key: "pool", label: "临时供应商池", icon: Boxes, desc: "STP 平台临时池" },
|
||||
];
|
||||
|
||||
// 依据记录派生来源(示例):按序分配到三类来源
|
||||
function sourceOf(index: number): Exclude<SourceKey, "all"> {
|
||||
return (["internet", "srm", "pool"] as const)[index % 3];
|
||||
}
|
||||
const SOURCE_LABEL: Record<Exclude<SourceKey, "all">, string> = {
|
||||
internet: "互联网",
|
||||
srm: "SRM 库",
|
||||
pool: "临时池",
|
||||
};
|
||||
|
||||
export function SmartSourcing() {
|
||||
const navigate = useNavigate();
|
||||
const viewed = sourcingRecords.filter((r) => r.viewed).length;
|
||||
const registered = sourcingRecords.filter((r) => r.registered).length;
|
||||
const { id } = useParams();
|
||||
const project = projects.find((p) => p.id === id) ?? projects[0];
|
||||
const [tab, setTab] = useState<SourceKey>("all");
|
||||
|
||||
const records = sourcingRecords.map((r, i) => ({ ...r, source: sourceOf(i) }));
|
||||
const filtered = tab === "all" ? records : records.filter((r) => r.source === tab);
|
||||
const viewed = filtered.filter((r) => r.viewed).length;
|
||||
const registered = filtered.filter((r) => r.registered).length;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -17,15 +43,50 @@ export function SmartSourcing() {
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate(-1)}>
|
||||
<ArrowLeft className="size-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-foreground">AI 智慧寻源结果</h1>
|
||||
<AiBadge label="AI 主动寻源" />
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-foreground">AI 智慧寻源结果</h1>
|
||||
<AiBadge label="AI 主动寻源" />
|
||||
</div>
|
||||
{/* 关联项目 */}
|
||||
<p className="mt-0.5 text-sm text-muted-foreground">
|
||||
关联项目:<span className="font-medium text-foreground">{project.name}</span> · {project.code}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 三类寻源来源 */}
|
||||
<div className="mb-6 grid grid-cols-4 gap-4">
|
||||
{SOURCES.map((s) => {
|
||||
const Icon = s.icon;
|
||||
const active = tab === s.key;
|
||||
const count = s.key === "all" ? records.length : records.filter((r) => r.source === s.key).length;
|
||||
return (
|
||||
<button
|
||||
key={s.key}
|
||||
onClick={() => setTab(s.key)}
|
||||
className={`flex items-start gap-3 rounded-xl border p-4 text-left transition-all ${
|
||||
active ? "border-primary bg-secondary/60 shadow-sm" : "bg-card hover:border-primary/50"
|
||||
}`}
|
||||
>
|
||||
<div className={`flex size-10 shrink-0 items-center justify-center rounded-lg ${active ? "bg-primary text-white" : "bg-primary/8 text-primary"}`}>
|
||||
<Icon className="size-5" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="font-medium text-foreground">{s.label}</span>
|
||||
<span className="rounded-full bg-primary/10 px-1.5 text-xs text-primary">{count}</span>
|
||||
</div>
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">{s.desc}</p>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="mb-6 grid grid-cols-3 gap-4">
|
||||
{[
|
||||
{ l: "AI 外发邀请", v: sourcingRecords.length, c: "#2563eb" },
|
||||
{ l: "AI 外发邀请", v: filtered.length, c: "var(--primary)" },
|
||||
{ l: "已查看回执", v: viewed, c: "#f59e0b" },
|
||||
{ l: "已报名", v: registered, c: "#14b8a6" },
|
||||
].map((s) => (
|
||||
@@ -38,13 +99,17 @@ export function SmartSourcing() {
|
||||
|
||||
<Card className="gap-0 p-0">
|
||||
<div className="flex items-center gap-2 border-b px-6 py-4">
|
||||
<Globe className="size-5 text-primary" />
|
||||
<h3 className="text-foreground">互联网平台主动外发邀请记录</h3>
|
||||
<Sparkles className="size-5 text-primary" />
|
||||
<h3 className="text-foreground">主动外发邀请记录</h3>
|
||||
<span className="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">
|
||||
{SOURCES.find((s) => s.key === tab)?.label}
|
||||
</span>
|
||||
</div>
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b bg-muted/40 text-left text-muted-foreground">
|
||||
<th className="px-6 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>
|
||||
@@ -53,9 +118,12 @@ export function SmartSourcing() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{sourcingRecords.map((r) => (
|
||||
{filtered.map((r) => (
|
||||
<tr key={r.id} className={`hover:bg-muted/20 ${!r.viewed ? "bg-amber-50/30" : ""}`}>
|
||||
<td className="px-6 py-3 font-medium text-foreground">{r.supplier}</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className="rounded bg-secondary px-1.5 py-0.5 text-xs text-primary">{SOURCE_LABEL[r.source]}</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{r.channel}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{r.sentAt}</td>
|
||||
<td className="px-4 py-3">
|
||||
@@ -79,9 +147,9 @@ export function SmartSourcing() {
|
||||
</table>
|
||||
</Card>
|
||||
|
||||
<div className="mt-4 flex items-start gap-2 rounded-lg border border-violet-200 bg-violet-50/50 p-3 text-sm text-muted-foreground">
|
||||
<Sparkles className="mt-0.5 size-4 shrink-0 text-violet-500" />
|
||||
小滨已根据采购品类与历史成交,自动从全国公共资源交易平台等渠道匹配并外发邀请,未查看回执的供应商已为您高亮提醒。
|
||||
<div className="mt-4 flex items-start gap-2 rounded-lg border border-primary/20 bg-secondary/40 p-3 text-sm text-muted-foreground">
|
||||
<Sparkles className="mt-0.5 size-4 shrink-0 text-primary" />
|
||||
小滨已结合本项目采购品类与历史成交,从互联网平台、集团 SRM 合格供方库与 STP 临时供应商池三类来源综合寻源并外发邀请,未查看回执的供应商已为您高亮提醒。
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user