diff --git a/frontend/src/api/simulation.js b/frontend/src/api/simulation.js index 907911a..33efca0 100644 --- a/frontend/src/api/simulation.js +++ b/frontend/src/api/simulation.js @@ -152,3 +152,19 @@ export const getSimulationActions = (simulationId, params = {}) => { return service.get(`/api/simulation/${simulationId}/actions`, { params }) } +/** + * 关闭模拟环境(优雅退出) + * @param {Object} data - { simulation_id, timeout? } + */ +export const closeSimulationEnv = (data) => { + return service.post('/api/simulation/close-env', data) +} + +/** + * 获取模拟环境状态 + * @param {Object} data - { simulation_id } + */ +export const getEnvStatus = (data) => { + return service.post('/api/simulation/env-status', data) +} + diff --git a/frontend/src/views/SimulationRunView.vue b/frontend/src/views/SimulationRunView.vue index c12da8a..5302e8e 100644 --- a/frontend/src/views/SimulationRunView.vue +++ b/frontend/src/views/SimulationRunView.vue @@ -71,7 +71,7 @@ import { useRoute, useRouter } from 'vue-router' import GraphPanel from '../components/GraphPanel.vue' import Step3Simulation from '../components/Step3Simulation.vue' import { getProject, getGraphData } from '../api/graph' -import { getSimulation } from '../api/simulation' +import { getSimulation, stopSimulation, closeSimulationEnv, getEnvStatus } from '../api/simulation' const route = useRoute() const router = useRouter() @@ -142,7 +142,50 @@ const toggleMaximize = (target) => { } } -const handleGoBack = () => { +const handleGoBack = async () => { + // 在返回 Step 2 之前,先关闭正在运行的模拟 + addLog('准备返回 Step 2,正在关闭模拟...') + + // 停止轮询 + stopGraphRefresh() + + try { + // 先尝试优雅关闭模拟环境 + const envStatusRes = await getEnvStatus({ simulation_id: currentSimulationId.value }) + + if (envStatusRes.success && envStatusRes.data?.env_alive) { + addLog('正在关闭模拟环境...') + try { + await closeSimulationEnv({ + simulation_id: currentSimulationId.value, + timeout: 10 + }) + addLog('✓ 模拟环境已关闭') + } catch (closeErr) { + addLog(`关闭模拟环境失败,尝试强制停止...`) + try { + await stopSimulation({ simulation_id: currentSimulationId.value }) + addLog('✓ 模拟已强制停止') + } catch (stopErr) { + addLog(`强制停止失败: ${stopErr.message}`) + } + } + } else { + // 环境未运行,检查是否需要停止进程 + if (isSimulating.value) { + addLog('正在停止模拟进程...') + try { + await stopSimulation({ simulation_id: currentSimulationId.value }) + addLog('✓ 模拟已停止') + } catch (err) { + addLog(`停止模拟失败: ${err.message}`) + } + } + } + } catch (err) { + addLog(`检查模拟状态失败: ${err.message}`) + } + // 返回到 Step 2 (环境搭建) router.push({ name: 'Simulation', params: { simulationId: currentSimulationId.value } }) } diff --git a/frontend/src/views/SimulationView.vue b/frontend/src/views/SimulationView.vue index ad41e08..1e218d6 100644 --- a/frontend/src/views/SimulationView.vue +++ b/frontend/src/views/SimulationView.vue @@ -69,7 +69,7 @@ import { useRoute, useRouter } from 'vue-router' import GraphPanel from '../components/GraphPanel.vue' import Step2EnvSetup from '../components/Step2EnvSetup.vue' import { getProject, getGraphData } from '../api/graph' -import { getSimulation } from '../api/simulation' +import { getSimulation, stopSimulation, getEnvStatus, closeSimulationEnv } from '../api/simulation' const route = useRoute() const router = useRouter() @@ -171,6 +171,70 @@ const handleNextStep = (params = {}) => { } // --- Data Logic --- + +/** + * 检查并关闭正在运行的模拟 + * 当用户从 Step 3 返回到 Step 2 时,默认用户要退出模拟 + */ +const checkAndStopRunningSimulation = async () => { + if (!currentSimulationId.value) return + + try { + // 先检查模拟环境是否存活 + const envStatusRes = await getEnvStatus({ simulation_id: currentSimulationId.value }) + + if (envStatusRes.success && envStatusRes.data?.env_alive) { + addLog('检测到模拟环境正在运行,正在关闭...') + + // 尝试优雅关闭模拟环境 + try { + const closeRes = await closeSimulationEnv({ + simulation_id: currentSimulationId.value, + timeout: 10 // 10秒超时 + }) + + if (closeRes.success) { + addLog('✓ 模拟环境已关闭') + } else { + addLog(`关闭模拟环境失败: ${closeRes.error || '未知错误'}`) + // 如果优雅关闭失败,尝试强制停止 + await forceStopSimulation() + } + } catch (closeErr) { + addLog(`关闭模拟环境异常: ${closeErr.message}`) + // 如果优雅关闭异常,尝试强制停止 + await forceStopSimulation() + } + } else { + // 环境未运行,但可能进程还在,检查模拟状态 + const simRes = await getSimulation(currentSimulationId.value) + if (simRes.success && simRes.data?.status === 'running') { + addLog('检测到模拟状态为运行中,正在停止...') + await forceStopSimulation() + } + } + } catch (err) { + // 检查环境状态失败不影响后续流程 + console.warn('检查模拟状态失败:', err) + } +} + +/** + * 强制停止模拟 + */ +const forceStopSimulation = async () => { + try { + const stopRes = await stopSimulation({ simulation_id: currentSimulationId.value }) + if (stopRes.success) { + addLog('✓ 模拟已强制停止') + } else { + addLog(`强制停止模拟失败: ${stopRes.error || '未知错误'}`) + } + } catch (err) { + addLog(`强制停止模拟异常: ${err.message}`) + } +} + const loadSimulationData = async () => { try { addLog(`加载模拟数据: ${currentSimulationId.value}`) @@ -222,8 +286,13 @@ const refreshGraph = () => { } } -onMounted(() => { +onMounted(async () => { addLog('SimulationView 初始化') + + // 检查并关闭正在运行的模拟(用户从 Step 3 返回时) + await checkAndStopRunningSimulation() + + // 加载模拟数据 loadSimulationData() })