feat(sse): 添加风险评估任务的SSE事件监听及状态管理
Some checks failed
Lint Code / Lint Code (push) Failing after 17m16s
Some checks failed
Lint Code / Lint Code (push) Failing after 17m16s
- 在SseClient中新增RiskAssessTaskVO接口定义 - 在SseClient事件处理里支持风险评估相关事件推送处理 - 在sse模块中新增风险评估任务状态及进度等响应式变量 - 实现风险评估任务的事件监听逻辑,处理提交、进度、完成、错误事件 - 发送风险评估完成和错误的通知弹窗提示 - 添加重置风险评估状态的方法resetRiskAssessStatus - 风险评估页面引入SSE Store,响应任务状态变化展示进度和提示信息 - 提交风险评估任务时确保SSE连接已建立 - 新增风险评估任务进行中的加载状态显示及进度条UI - 监听风险评估完成和错误状态,自动加载数据并重置状态 - 优化风险评估接口调用参数类型转换和错误处理 - 生命周期钩子内移除窗口resize事件监听防止内存泄漏
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { SseClient, type ProjectInitTaskVO } from "@/utils/sse/SseClient";
|
import {
|
||||||
|
SseClient,
|
||||||
|
type ProjectInitTaskVO,
|
||||||
|
type RiskAssessTaskVO
|
||||||
|
} from "@/utils/sse/SseClient";
|
||||||
import { store } from "../utils";
|
import { store } from "../utils";
|
||||||
import {
|
import {
|
||||||
getMyTasks as fetchTasksApi,
|
getMyTasks as fetchTasksApi,
|
||||||
@@ -25,6 +29,14 @@ export const useSseStore = defineStore("sse", () => {
|
|||||||
myTasks.value.some(t => t.status === "processing" || t.status === "pending")
|
myTasks.value.some(t => t.status === "processing" || t.status === "pending")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 风险评估任务状态
|
||||||
|
const riskAssessTask = ref<RiskAssessTaskVO | null>(null);
|
||||||
|
const riskAssessProgress = ref(0);
|
||||||
|
const riskAssessStatus = ref<
|
||||||
|
"idle" | "submitted" | "processing" | "completed" | "error"
|
||||||
|
>("idle");
|
||||||
|
const riskAssessErrorMessage = ref("");
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
const getIsConnected = computed(() => isConnected.value);
|
const getIsConnected = computed(() => isConnected.value);
|
||||||
const getCurrentTask = computed(() => currentTask.value);
|
const getCurrentTask = computed(() => currentTask.value);
|
||||||
@@ -115,6 +127,56 @@ export const useSseStore = defineStore("sse", () => {
|
|||||||
isConnected.value = false;
|
isConnected.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============ 风险评估事件监听 ============
|
||||||
|
// 监听风险评估任务提交
|
||||||
|
sseClient.value.on("risk-assess-submitted", (data: any) => {
|
||||||
|
riskAssessStatus.value = "submitted";
|
||||||
|
console.log("SSE Store: 风险评估任务已提交", data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听风险评估进度
|
||||||
|
sseClient.value.on("risk-assess-progress", (data: RiskAssessTaskVO) => {
|
||||||
|
riskAssessTask.value = data;
|
||||||
|
riskAssessProgress.value = data.progress;
|
||||||
|
riskAssessStatus.value = "processing";
|
||||||
|
console.log(
|
||||||
|
`SSE Store: 风险评估进度 ${data.progress}% - ${data.progressMessage}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听风险评估完成
|
||||||
|
sseClient.value.on("risk-assess-complete", (data: RiskAssessTaskVO) => {
|
||||||
|
riskAssessTask.value = data;
|
||||||
|
riskAssessProgress.value = 100;
|
||||||
|
riskAssessStatus.value = "completed";
|
||||||
|
console.log("SSE Store: 风险评估完成", data.result);
|
||||||
|
|
||||||
|
// 发送通知
|
||||||
|
ElNotification({
|
||||||
|
title: "风险评估完成",
|
||||||
|
message: `项目风险评估已完成,已识别 ${data.result?.identifiedRisks?.length || 0} 个风险。`,
|
||||||
|
type: "success",
|
||||||
|
duration: 5000,
|
||||||
|
position: "top-right"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听风险评估错误
|
||||||
|
sseClient.value.on("risk-assess-error", (data: { error: string }) => {
|
||||||
|
riskAssessStatus.value = "error";
|
||||||
|
riskAssessErrorMessage.value = data.error;
|
||||||
|
console.error("SSE Store: 风险评估错误", data.error);
|
||||||
|
|
||||||
|
// 发送错误通知
|
||||||
|
ElNotification({
|
||||||
|
title: "风险评估失败",
|
||||||
|
message: data.error || "风险评估失败,请重试",
|
||||||
|
type: "error",
|
||||||
|
duration: 5000,
|
||||||
|
position: "top-right"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 建立连接(异步,在后台运行)
|
// 建立连接(异步,在后台运行)
|
||||||
sseClient.value.connect().catch(err => {
|
sseClient.value.connect().catch(err => {
|
||||||
console.error("SSE Store: 连接失败", err);
|
console.error("SSE Store: 连接失败", err);
|
||||||
@@ -159,6 +221,16 @@ export const useSseStore = defineStore("sse", () => {
|
|||||||
errorMessage.value = "";
|
errorMessage.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置风险评估任务状态
|
||||||
|
*/
|
||||||
|
function resetRiskAssessStatus() {
|
||||||
|
riskAssessTask.value = null;
|
||||||
|
riskAssessProgress.value = 0;
|
||||||
|
riskAssessStatus.value = "idle";
|
||||||
|
riskAssessErrorMessage.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询我的任务列表
|
* 查询我的任务列表
|
||||||
*/
|
*/
|
||||||
@@ -182,6 +254,11 @@ export const useSseStore = defineStore("sse", () => {
|
|||||||
taskStatus,
|
taskStatus,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
myTasks,
|
myTasks,
|
||||||
|
// 风险评估状态
|
||||||
|
riskAssessTask,
|
||||||
|
riskAssessProgress,
|
||||||
|
riskAssessStatus,
|
||||||
|
riskAssessErrorMessage,
|
||||||
// Getters
|
// Getters
|
||||||
getIsConnected,
|
getIsConnected,
|
||||||
getCurrentTask,
|
getCurrentTask,
|
||||||
@@ -194,6 +271,7 @@ export const useSseStore = defineStore("sse", () => {
|
|||||||
closeSse,
|
closeSse,
|
||||||
submitProjectInitTask,
|
submitProjectInitTask,
|
||||||
resetTaskStatus,
|
resetTaskStatus,
|
||||||
|
resetRiskAssessStatus,
|
||||||
fetchMyTasks
|
fetchMyTasks
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,6 +28,23 @@ export interface ProjectInitTaskVO {
|
|||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 风险评估任务VO */
|
||||||
|
export interface RiskAssessTaskVO {
|
||||||
|
taskId: string;
|
||||||
|
userId: string;
|
||||||
|
projectId: string;
|
||||||
|
projectName?: string;
|
||||||
|
status: string;
|
||||||
|
statusDesc: string;
|
||||||
|
progress: number;
|
||||||
|
progressMessage: string;
|
||||||
|
createTime: string;
|
||||||
|
startTime?: string;
|
||||||
|
completeTime?: string;
|
||||||
|
result?: any;
|
||||||
|
errorMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
type SseEventCallback = (data: any, message?: SseMessage) => void;
|
type SseEventCallback = (data: any, message?: SseMessage) => void;
|
||||||
|
|
||||||
export class SseClient {
|
export class SseClient {
|
||||||
@@ -181,22 +198,30 @@ export class SseClient {
|
|||||||
console.log("任务已提交:", message);
|
console.log("任务已提交:", message);
|
||||||
if (message.type === "project-init") {
|
if (message.type === "project-init") {
|
||||||
this.emit("project-init-submitted", message.data, message);
|
this.emit("project-init-submitted", message.data, message);
|
||||||
|
} else if (message.type === "risk-assess") {
|
||||||
|
this.emit("risk-assess-submitted", message.data, message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "progress":
|
case "progress":
|
||||||
if (message.type === "project-init") {
|
if (message.type === "project-init") {
|
||||||
this.emit("project-init-progress", message.data, message);
|
this.emit("project-init-progress", message.data, message);
|
||||||
|
} else if (message.type === "risk-assess") {
|
||||||
|
this.emit("risk-assess-progress", message.data, message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "complete":
|
case "complete":
|
||||||
if (message.type === "project-init") {
|
if (message.type === "project-init") {
|
||||||
this.emit("project-init-complete", message.data, message);
|
this.emit("project-init-complete", message.data, message);
|
||||||
|
} else if (message.type === "risk-assess") {
|
||||||
|
this.emit("risk-assess-complete", message.data, message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
console.error("SSE 错误事件:", message);
|
console.error("SSE 错误事件:", message);
|
||||||
if (message.type === "project-init") {
|
if (message.type === "project-init") {
|
||||||
this.emit("project-init-error", message.data, message);
|
this.emit("project-init-error", message.data, message);
|
||||||
|
} else if (message.type === "risk-assess") {
|
||||||
|
this.emit("risk-assess-error", message.data, message);
|
||||||
}
|
}
|
||||||
this.emit("error", message.data, message);
|
this.emit("error", message.data, message);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from "vue";
|
import { ref, onMounted, computed, onUnmounted, watch } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { useUserStoreHook } from "@/store/modules/user";
|
||||||
|
import { useSseStoreHook } from "@/store/modules/sse";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
import {
|
import {
|
||||||
getRiskList,
|
getRiskList,
|
||||||
@@ -67,6 +69,34 @@ const trendPeriod = ref("month");
|
|||||||
// 项目列表
|
// 项目列表
|
||||||
const projectList = ref<ProjectItem[]>([]);
|
const projectList = ref<ProjectItem[]>([]);
|
||||||
|
|
||||||
|
// SSE Store
|
||||||
|
const sseStore = useSseStoreHook();
|
||||||
|
const userStore = useUserStoreHook();
|
||||||
|
|
||||||
|
// 风险评估进度相关
|
||||||
|
const riskAssessProgress = computed(() => sseStore.riskAssessProgress);
|
||||||
|
const riskAssessStatus = computed(() => sseStore.riskAssessStatus);
|
||||||
|
const riskAssessTask = computed(() => sseStore.riskAssessTask);
|
||||||
|
const riskAssessErrorMessage = computed(() => sseStore.riskAssessErrorMessage);
|
||||||
|
const isAssessing = computed(
|
||||||
|
() =>
|
||||||
|
riskAssessStatus.value === "submitted" ||
|
||||||
|
riskAssessStatus.value === "processing"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听风险评估完成
|
||||||
|
watch(riskAssessStatus, newStatus => {
|
||||||
|
if (newStatus === "completed") {
|
||||||
|
message("风险评估完成!", { type: "success" });
|
||||||
|
loadRiskList();
|
||||||
|
loadStatistics();
|
||||||
|
sseStore.resetRiskAssessStatus();
|
||||||
|
} else if (newStatus === "error") {
|
||||||
|
message(riskAssessErrorMessage.value || "风险评估失败", { type: "error" });
|
||||||
|
sseStore.resetRiskAssessStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 分页
|
// 分页
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
@@ -473,16 +503,36 @@ async function handleCreate() {
|
|||||||
message("请先选择项目", { type: "warning" });
|
message("请先选择项目", { type: "warning" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userId = userStore.userId;
|
||||||
|
if (!userId) {
|
||||||
|
message("用户信息不存在", { type: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保SSE连接已建立
|
||||||
|
if (!sseStore.getIsConnected) {
|
||||||
|
sseStore.initSse(String(userId));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await submitRiskAssessment(queryParams.value.projectId);
|
const res = await submitRiskAssessment(Number(queryParams.value.projectId));
|
||||||
|
console.log("风险评估API响应:", res);
|
||||||
|
console.log("res.data:", res.data);
|
||||||
const responseData = res.data as any;
|
const responseData = res.data as any;
|
||||||
|
// 扁平化结构:res.data 直接是 { code: 200, data: {...}, message: "..." }
|
||||||
if (responseData.code === 200) {
|
if (responseData.code === 200) {
|
||||||
message("风险评估任务已提交,AI正在分析中...", { type: "success" });
|
message("风险评估任务已提交,AI正在分析中...", { type: "success" });
|
||||||
// 可以在这里启动SSE连接监听进度
|
|
||||||
} else {
|
} else {
|
||||||
|
console.error(
|
||||||
|
"响应code不是200:",
|
||||||
|
responseData.code,
|
||||||
|
responseData.message
|
||||||
|
);
|
||||||
message(responseData.message || "提交失败", { type: "error" });
|
message(responseData.message || "提交失败", { type: "error" });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("风险评估请求异常:", error);
|
||||||
message("提交风险评估任务失败", { type: "error" });
|
message("提交风险评估任务失败", { type: "error" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -534,6 +584,10 @@ onMounted(() => {
|
|||||||
initTrendChart();
|
initTrendChart();
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -572,15 +626,38 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
筛选
|
筛选
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" @click="handleCreate">
|
<el-button type="primary" :loading="isAssessing" @click="handleCreate">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<component :is="useRenderIcon(AddIcon)" />
|
<component :is="useRenderIcon(AddIcon)" />
|
||||||
</template>
|
</template>
|
||||||
新建评估
|
{{ isAssessing ? "评估中..." : "新建评估" }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- AI风险评估进度显示 -->
|
||||||
|
<el-card v-if="isAssessing" class="mb-4" shadow="hover">
|
||||||
|
<div class="flex-c gap-4">
|
||||||
|
<el-icon class="is-loading text-primary" :size="24">
|
||||||
|
<component :is="useRenderIcon('ri:loader-4-line')" />
|
||||||
|
</el-icon>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex-bc mb-2">
|
||||||
|
<span class="font-medium">AI 正在进行风险评估</span>
|
||||||
|
<span class="text-sm text-gray-500">{{ riskAssessProgress }}%</span>
|
||||||
|
</div>
|
||||||
|
<el-progress
|
||||||
|
:percentage="riskAssessProgress"
|
||||||
|
:stroke-width="8"
|
||||||
|
:show-text="false"
|
||||||
|
/>
|
||||||
|
<p class="text-sm text-gray-500 mt-2">
|
||||||
|
{{ riskAssessTask?.progressMessage || "正在分析项目数据..." }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
<!-- 统计卡片 -->
|
||||||
<el-row :gutter="16" class="mb-4">
|
<el-row :gutter="16" class="mb-4">
|
||||||
<el-col
|
<el-col
|
||||||
|
|||||||
Reference in New Issue
Block a user