fix(reports): export via authed axios blob download instead of window.open
Some checks failed
CI / test (push) Failing after 2m50s

启用登录鉴权后,导出 HTML/MD/JSON 用 window.open 直连 API 无法携带
X-Auth-Token,服务端返回 401 导致导出失效。改为经 axios 拉取 blob
(拦截器自动附加凭据)后触发浏览器下载。
This commit is contained in:
sinohqb 2026-07-29 15:35:34 +08:00
parent 5dd3c4ff1c
commit cbab55843b
3 changed files with 14 additions and 7 deletions

View File

@ -264,9 +264,16 @@ export interface RunLogsResponse {
export const reportsApi = { export const reportsApi = {
get: (runId: string) => api.get(`/reports/${runId}`), get: (runId: string) => api.get(`/reports/${runId}`),
html: (runId: string) => api.get(`/reports/${runId}/html`), download: async (runId: string, format: 'html' | 'json' | 'markdown') => {
json: (runId: string) => api.get(`/reports/${runId}/json`), const ext = format === 'markdown' ? 'md' : format
markdownUrl: (runId: string) => `/api/reports/${runId}/markdown`, 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) => compare: (run1: string, run2: string) =>
api.get(`/reports/compare`, { params: { run1, run2 } }), api.get(`/reports/compare`, { params: { run1, run2 } }),
} }

View File

@ -325,10 +325,10 @@ export default function ReportsPage() {
{viewMode === 'single' && report && ( {viewMode === 'single' && report && (
<Space style={{ flexShrink: 0 }}> <Space style={{ flexShrink: 0 }}>
<Button icon={<DownloadOutlined />} onClick={() => window.open(`/api/reports/${selectedRunId}/html`, '_blank')}> <Button icon={<DownloadOutlined />} onClick={() => reportsApi.download(selectedRunId, 'html')}>
HTML HTML
</Button> </Button>
<Button icon={<FileMarkdownOutlined />} onClick={() => window.open(reportsApi.markdownUrl(selectedRunId), '_blank')}> <Button icon={<FileMarkdownOutlined />} onClick={() => reportsApi.download(selectedRunId, 'markdown')}>
MD MD
</Button> </Button>
</Space> </Space>

View File

@ -6,7 +6,7 @@ import {
import { import {
FileTextOutlined, PlayCircleOutlined, ReloadOutlined, StopOutlined, FileTextOutlined, PlayCircleOutlined, ReloadOutlined, StopOutlined,
} from '@ant-design/icons' } 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 RunList from '../components/RunList'
import CaseBlock from '../components/CaseBlock' import CaseBlock from '../components/CaseBlock'
import RuleOverview from '../components/RuleOverview' import RuleOverview from '../components/RuleOverview'
@ -226,7 +226,7 @@ export default function RunsPage() {
} }
}} }}
onOpenReport={handleOpenReport} onOpenReport={handleOpenReport}
onExportJson={(runId) => window.open(`/api/reports/${runId}/json`, '_blank')} onExportJson={(runId) => reportsApi.download(runId, 'json')}
onRerun={async () => { onRerun={async () => {
if (!session.run) return if (!session.run) return
await startRun(session.run.target_id, session.run.scenario_id) await startRun(session.run.target_id, session.run.scenario_id)