diff --git a/src/api/supplierProfile.ts b/src/api/supplierProfile.ts index b367ea6..cb255e6 100644 --- a/src/api/supplierProfile.ts +++ b/src/api/supplierProfile.ts @@ -2,8 +2,37 @@ import { http } from "@/api/http"; import type { ApiResponse } from "@/types/api"; import type { SupplierProfile } from "@/types/supplierProfile"; -export function fetchSupplierProfile(name: string) { - return http.get>( - `/v1/suppliers/${encodeURIComponent(name)}`, - ); +const CACHE_TTL_MS = 30_000; +const cache = new Map(); +const pending = new Map>(); + +export async function fetchSupplierProfile( + name: string, +): Promise<{ data: { data: SupplierProfile | null } }> { + const key = name.trim(); + const cached = cache.get(key); + if (cached && Date.now() - cached.at < CACHE_TTL_MS) { + return { data: { data: cached.data } }; + } + const running = pending.get(key); + if (running) return { data: { data: await running } }; + const request = (async () => { + const response = await http.get>( + `/v1/suppliers/${encodeURIComponent(key)}`, + ); + if (!response.data.data) throw new Error("供应商画像响应为空"); + cache.set(key, { data: response.data.data, at: Date.now() }); + return response.data.data; + })(); + pending.set(key, request); + try { + return { data: { data: await request } }; + } finally { + pending.delete(key); + } +} + +export function clearSupplierProfileCache(name?: string) { + if (name) cache.delete(name.trim()); + else cache.clear(); } diff --git a/src/api/suppliers.ts b/src/api/suppliers.ts index 5d707a5..1fe4f44 100644 --- a/src/api/suppliers.ts +++ b/src/api/suppliers.ts @@ -1,9 +1,44 @@ import { http } from "@/api/http"; import type { ApiResponse } from "@/types/api"; -export interface SupplierOption { name: string; anomalyCount: number; tier: string } -export interface SupplierListData { items: SupplierOption[]; defaultSupplier: string | null } - -export function fetchSupplierOptions(keyword?: string) { - return http.get>("/v1/suppliers", { params: { keyword, limit: 8 } }); +export interface SupplierOption { + name: string; + anomalyCount: number; + tier: string; +} +export interface SupplierListData { + items: SupplierOption[]; + defaultSupplier: string | null; +} + +const CACHE_TTL_MS = 30_000; +let cached: { data: SupplierListData; at: number } | null = null; +let pending: Promise | null = null; + +export async function fetchSupplierOptions( + keyword?: string, +): Promise<{ data: { data: SupplierListData } }> { + const normalized = keyword?.trim() || ""; + if (!normalized && cached && Date.now() - cached.at < CACHE_TTL_MS) + return { data: { data: cached.data } }; + if (!normalized && pending) return { data: { data: await pending } }; + const request = (async () => { + const response = await http.get>( + "/v1/suppliers", + { params: { keyword: normalized || undefined, limit: 8 } }, + ); + if (!response.data.data) throw new Error("供应商列表响应为空"); + if (!normalized) cached = { data: response.data.data, at: Date.now() }; + return response.data.data; + })(); + if (!normalized) pending = request; + try { + return { data: { data: await request } }; + } finally { + if (!normalized) pending = null; + } +} + +export function clearSupplierOptionsCache() { + cached = null; } diff --git a/src/components/assistant/AssistantResponseCard.vue b/src/components/assistant/AssistantResponseCard.vue index e1be85c..1e2ffa7 100644 --- a/src/components/assistant/AssistantResponseCard.vue +++ b/src/components/assistant/AssistantResponseCard.vue @@ -26,6 +26,8 @@ interface CardData extends AssistantResponseCard { anomalyCount?: number; cooperationCount?: number | null; frequencyCoefficient?: number; + supplierBaseScore?: number | null; + supplierScore?: number | null; frequencyBasis?: string; dataAvailable?: boolean; tier?: string; @@ -119,6 +121,10 @@ function formatCount(value: unknown) { ×{{ data.frequencyCoefficient?.toFixed(2) ?? "1.00" }}频次系数 +
+ {{ Math.round(data.supplierScore) }} 分供应商得分 S供 +
供应商层级{{ data.tier }} diff --git a/src/components/layout/AppHeader.vue b/src/components/layout/AppHeader.vue index ed1aac2..09d1c6a 100644 --- a/src/components/layout/AppHeader.vue +++ b/src/components/layout/AppHeader.vue @@ -20,6 +20,22 @@ const visibleNavigation = computed(() => (item) => item !== "管理" || auth.hasPermission("risk_item.review"), ), ); + +const routeChunks: Record Promise> = { + 看板: () => import("@/pages/OverviewPage.vue"), + 供应商: () => import("@/pages/SupplierProfilePage.vue"), + 标准库: () => import("@/pages/StandardsPage.vue"), + 报告: () => import("@/pages/ReportPage.vue"), + 管理: () => import("@/pages/ReviewPage.vue"), +}; +const prefetched = new Set(); +function prefetch(item: string) { + if (prefetched.has(item)) return; + const loader = routeChunks[item]; + if (!loader) return; + prefetched.add(item); + void loader(); +}