From cbab55843b05d3d4ff4697b8a34bd105f91279d0 Mon Sep 17 00:00:00 2001 From: sinohqb Date: Wed, 29 Jul 2026 15:35:34 +0800 Subject: [PATCH] fix(reports): export via authed axios blob download instead of window.open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 启用登录鉴权后,导出 HTML/MD/JSON 用 window.open 直连 API 无法携带 X-Auth-Token,服务端返回 401 导致导出失效。改为经 axios 拉取 blob (拦截器自动附加凭据)后触发浏览器下载。 --- frontend/web/src/api.ts | 13 ++++++++++--- frontend/web/src/pages/Reports.tsx | 4 ++-- frontend/web/src/pages/Runs.tsx | 4 ++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/web/src/api.ts b/frontend/web/src/api.ts index 53f3d1a..34ea973 100644 --- a/frontend/web/src/api.ts +++ b/frontend/web/src/api.ts @@ -264,9 +264,16 @@ export interface RunLogsResponse { export const reportsApi = { get: (runId: string) => api.get(`/reports/${runId}`), - html: (runId: string) => api.get(`/reports/${runId}/html`), - json: (runId: string) => api.get(`/reports/${runId}/json`), - markdownUrl: (runId: string) => `/api/reports/${runId}/markdown`, + download: async (runId: string, format: 'html' | 'json' | 'markdown') => { + const ext = format === 'markdown' ? 'md' : format + const res = await api.get(`/reports/${runId}/${format}`, { responseType: 'blob' }) + const url = URL.createObjectURL(res.data as Blob) + const a = document.createElement('a') + a.href = url + a.download = `report-${runId.slice(0, 8)}.${ext}` + a.click() + URL.revokeObjectURL(url) + }, compare: (run1: string, run2: string) => api.get(`/reports/compare`, { params: { run1, run2 } }), } diff --git a/frontend/web/src/pages/Reports.tsx b/frontend/web/src/pages/Reports.tsx index d3335f2..b314f6a 100644 --- a/frontend/web/src/pages/Reports.tsx +++ b/frontend/web/src/pages/Reports.tsx @@ -325,10 +325,10 @@ export default function ReportsPage() { {viewMode === 'single' && report && ( - - diff --git a/frontend/web/src/pages/Runs.tsx b/frontend/web/src/pages/Runs.tsx index d818e5c..185db48 100644 --- a/frontend/web/src/pages/Runs.tsx +++ b/frontend/web/src/pages/Runs.tsx @@ -6,7 +6,7 @@ import { import { FileTextOutlined, PlayCircleOutlined, ReloadOutlined, StopOutlined, } from '@ant-design/icons' -import { runsApi, scenariosApi, targetsApi, type Run, type Scenario, type Target } from '../api' +import { reportsApi, runsApi, scenariosApi, targetsApi, type Run, type Scenario, type Target } from '../api' import RunList from '../components/RunList' import CaseBlock from '../components/CaseBlock' import RuleOverview from '../components/RuleOverview' @@ -226,7 +226,7 @@ export default function RunsPage() { } }} onOpenReport={handleOpenReport} - onExportJson={(runId) => window.open(`/api/reports/${runId}/json`, '_blank')} + onExportJson={(runId) => reportsApi.download(runId, 'json')} onRerun={async () => { if (!session.run) return await startRun(session.run.target_id, session.run.scenario_id)