完善供应商频次卡片展示
- 展示近3年异常次数和合作频次代理值 - 增加频次口径和无供应商数据提示 - 兼容旧历史风险卡片数据 - 保留历史会话加载超时和流式响应处理优化
This commit is contained in:
parent
f0a940eb10
commit
ab20424aa9
@ -12,6 +12,7 @@ export interface ConversationStreamEvent {
|
||||
export function fetchConversations(limit = 50, search?: string) {
|
||||
return http.get<ApiResponse<ConversationSummary[]>>("/v1/conversations", {
|
||||
params: { limit, search },
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
@ -24,6 +25,7 @@ export function createConversation(title?: string) {
|
||||
export function fetchConversation(id: number, offset = 0) {
|
||||
return http.get<ApiResponse<Conversation>>(`/v1/conversations/${id}`, {
|
||||
params: { limit: 20, offset },
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@ -45,6 +47,8 @@ export async function streamConversationMessage(
|
||||
messageType: "text" | "image" | "file" | "audio" = "text",
|
||||
) {
|
||||
const token = localStorage.getItem("risk_prediction_token");
|
||||
const controller = new AbortController();
|
||||
const timeout = window.setTimeout(() => controller.abort(), 180_000);
|
||||
const response = await fetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL || "/api"}/v1/conversations/${id}/stream`,
|
||||
{
|
||||
@ -55,6 +59,7 @@ export async function streamConversationMessage(
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
body: JSON.stringify({ content, messageType }),
|
||||
signal: controller.signal,
|
||||
},
|
||||
);
|
||||
if (response.status === 401) {
|
||||
@ -64,6 +69,7 @@ export async function streamConversationMessage(
|
||||
if (!response.ok || !response.body)
|
||||
throw new Error(`流式请求失败(HTTP ${response.status})`);
|
||||
|
||||
try {
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
@ -78,7 +84,12 @@ export async function streamConversationMessage(
|
||||
if (!line.startsWith("data:")) continue;
|
||||
const raw = line.slice(5).trim();
|
||||
if (!raw) continue;
|
||||
const event = JSON.parse(raw) as ConversationStreamEvent;
|
||||
let event: ConversationStreamEvent;
|
||||
try {
|
||||
event = JSON.parse(raw) as ConversationStreamEvent;
|
||||
} catch {
|
||||
throw new Error("AI 流式响应格式错误,请重试");
|
||||
}
|
||||
onEvent(event);
|
||||
if (event.type === "done") completed = true;
|
||||
}
|
||||
@ -86,10 +97,23 @@ export async function streamConversationMessage(
|
||||
if (buffer.startsWith("data:")) {
|
||||
const raw = buffer.slice(5).trim();
|
||||
if (raw) {
|
||||
const event = JSON.parse(raw) as ConversationStreamEvent;
|
||||
let event: ConversationStreamEvent;
|
||||
try {
|
||||
event = JSON.parse(raw) as ConversationStreamEvent;
|
||||
} catch {
|
||||
throw new Error("AI 流式响应格式错误,请重试");
|
||||
}
|
||||
onEvent(event);
|
||||
if (event.type === "done") completed = true;
|
||||
}
|
||||
}
|
||||
if (!completed) throw new Error("AI 流式连接中断,请重试");
|
||||
} catch (reason) {
|
||||
if (controller.signal.aborted) {
|
||||
throw new Error("AI 响应超时,请稍后重试", { cause: reason });
|
||||
}
|
||||
throw reason;
|
||||
} finally {
|
||||
window.clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ interface CardData extends AssistantResponseCard {
|
||||
anomalyCount?: number;
|
||||
cooperationCount?: number | null;
|
||||
frequencyCoefficient?: number;
|
||||
frequencyBasis?: string;
|
||||
dataAvailable?: boolean;
|
||||
tier?: string;
|
||||
topProblems?: Array<{ name: string; count: number }>;
|
||||
frequentProblems?: Array<{ name: string; count: number }>;
|
||||
@ -94,14 +96,14 @@ function formatCount(value: unknown) {
|
||||
<div class="metric-grid">
|
||||
<div>
|
||||
<b>{{ formatCount(data.anomalyCount) }}</b
|
||||
><span>历史异常</span>
|
||||
><span>近3年异常</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ formatCount(data.cooperationCount) }}</b
|
||||
><span>合作次数</span>
|
||||
><span>合作频次(代理)</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>×{{ data.frequencyCoefficient }}</b
|
||||
<b>×{{ data.frequencyCoefficient?.toFixed(2) ?? "1.00" }}</b
|
||||
><span>频次系数</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -141,26 +141,58 @@ const stars = computed(() => {
|
||||
|
||||
<section class="supplier-analysis">
|
||||
<h3><Factory :size="14" /> 供应商合作频次分析</h3>
|
||||
<div v-if="result.supplierFreq?.supplierName">
|
||||
<template
|
||||
v-if="
|
||||
result.supplierFreq?.dataAvailable ||
|
||||
result.supplierFreq?.supplierName
|
||||
"
|
||||
>
|
||||
<div v-if="result.supplierFreq.supplierName">
|
||||
<span>{{ result.supplierFreq.supplierName }}</span>
|
||||
<strong>异常 {{ result.supplierFreq.anomalyCount ?? 0 }} 次</strong>
|
||||
<strong
|
||||
>近3年异常 {{ result.supplierFreq.anomalyCount ?? 0 }} 次</strong
|
||||
>
|
||||
</div>
|
||||
<div v-if="result.supplierFreq">
|
||||
<div v-if="result.supplierFreq.alpha != null">
|
||||
<span>频次风险系数</span
|
||||
><strong>×{{ result.supplierFreq.alpha.toFixed(2) }}</strong>
|
||||
</div>
|
||||
<div v-if="result.supplierFreq?.tier">
|
||||
<span>供应商层级</span><strong>{{ result.supplierFreq.tier }}</strong>
|
||||
<div v-if="result.supplierFreq.tier">
|
||||
<span>供应商层级</span
|
||||
><strong>{{ result.supplierFreq.tier }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>合作次数</span
|
||||
><strong>{{
|
||||
result.supplierFreq?.cooperationCount ?? "暂无数据"
|
||||
}}</strong>
|
||||
<span>合作频次(代理)</span>
|
||||
<strong
|
||||
>{{
|
||||
result.supplierFreq.cooperationCount ??
|
||||
result.supplierFreq.anomalyCount ??
|
||||
0
|
||||
}}
|
||||
次</strong
|
||||
>
|
||||
</div>
|
||||
<div v-if="result.supplierFreq?.suggestion">
|
||||
<span>建议</span>{{ result.supplierFreq.suggestion }}
|
||||
<div v-if="result.supplierFreq.suggestion">
|
||||
<span>管理建议</span>{{ result.supplierFreq.suggestion }}
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="supplier-analysis__empty">
|
||||
{{
|
||||
result.supplierFreq?.note || "未指定供应商,暂无供应商频次分析。"
|
||||
}}
|
||||
</p>
|
||||
<small
|
||||
v-if="
|
||||
result.supplierFreq?.dataAvailable ||
|
||||
result.supplierFreq?.supplierName
|
||||
"
|
||||
class="supplier-analysis__note"
|
||||
>
|
||||
{{
|
||||
result.supplierFreq.note ||
|
||||
"当前以近3年异常记录数作为合作频次代理,不代表真实采购订单合作次数。"
|
||||
}}
|
||||
</small>
|
||||
</section>
|
||||
|
||||
<section class="standards-card">
|
||||
@ -507,6 +539,15 @@ h3 {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.supplier-analysis__empty,
|
||||
.supplier-analysis__note {
|
||||
display: block;
|
||||
margin: 4px 0 0;
|
||||
color: #6d5a91;
|
||||
font-size: 10px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.standards-card {
|
||||
border: 1px solid #bfdbfe;
|
||||
}
|
||||
|
||||
@ -55,6 +55,13 @@ function startNewChat() {
|
||||
}
|
||||
|
||||
async function openConversation(id: number) {
|
||||
if (conversationStore.current?.id === id) {
|
||||
await router.push({
|
||||
name: "assessment",
|
||||
query: { conversation: String(id) },
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await conversationStore.open(id);
|
||||
await router.push({
|
||||
|
||||
@ -60,9 +60,12 @@ export interface RiskAssessmentResult {
|
||||
supplierName: string | null;
|
||||
cooperationCount: number | null;
|
||||
anomalyCount: number | null;
|
||||
alpha: number;
|
||||
alpha: number | null;
|
||||
tier: string | null;
|
||||
suggestion: string | null;
|
||||
frequencyBasis?: string;
|
||||
note?: string | null;
|
||||
dataAvailable?: boolean;
|
||||
} | null;
|
||||
standardJudgment?: Array<{
|
||||
category: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user