From 6f810b6d0161656280b5a6dca6b9c400d8e20a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 1 Sep 2026 11:43:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=8A=A5=E5=91=8A=E9=87=87=E9=9B=86=E4=B8=8E?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一错误报告打开事件常量并补充快捷入口断言 修复后台详情竞态、下载兼容性、状态选项和错误提示 加强客户端脱敏、指纹降级并过滤预期鉴权错误 --- apps/admin-web/src/api/adminApiClient.ts | 8 ++-- apps/admin-web/src/api/adminApiTypes.ts | 1 - .../src/pages/AdminErrorReportsPage.tsx | 38 +++++++++++---- .../src/app/AuthenticatedClient.tsx | 5 +- .../projectSummaryCommands.ts | 3 +- .../src/services/clientApi.ts | 30 +++++++----- .../src/services/errorReporting.ts | 46 +++++++++++++++---- .../assert-playtest-and-release-shortcuts.ts | 10 +++- 8 files changed, 102 insertions(+), 39 deletions(-) diff --git a/apps/admin-web/src/api/adminApiClient.ts b/apps/admin-web/src/api/adminApiClient.ts index 52491b61c..5155a2470 100644 --- a/apps/admin-web/src/api/adminApiClient.ts +++ b/apps/admin-web/src/api/adminApiClient.ts @@ -23,6 +23,8 @@ import type { AdminEditorShowcaseListQuery, AdminEditorShowcaseListResponse, AdminEditorShowcaseReviewRequest, + AdminErrorReportDetail, + AdminErrorReportListResponse, AdminFeatureGateConfigResponse, AdminLoginResponse, AdminMeResponse, @@ -280,14 +282,14 @@ export function listAdminErrorReports( limit?: number; } = {}, ) { - return request( + return request( `/admin/api/error-reports${buildErrorReportQueryString(query)}`, { token }, ); } export function getAdminErrorReport(token: string, batchId: string) { - return request( + return request( `/admin/api/error-reports/${encodeURIComponent(batchId)}`, { token }, ); @@ -298,7 +300,7 @@ export function updateAdminErrorReport( batchId: string, payload: { status: string; note?: string }, ) { - return request( + return request( `/admin/api/error-reports/${encodeURIComponent(batchId)}`, { method: 'PATCH', token, body: payload }, ); diff --git a/apps/admin-web/src/api/adminApiTypes.ts b/apps/admin-web/src/api/adminApiTypes.ts index 59efb2b56..c2f9b3b52 100644 --- a/apps/admin-web/src/api/adminApiTypes.ts +++ b/apps/admin-web/src/api/adminApiTypes.ts @@ -112,7 +112,6 @@ export interface AdminErrorReportEntry { ossObjectKey?: string; archiveSha256?: string; uploadStatus?: 'uploading' | 'ready' | 'failed'; - reviewStatus?: 'new' | 'in-progress' | 'resolved'; } export interface AdminErrorReportListResponse { diff --git a/apps/admin-web/src/pages/AdminErrorReportsPage.tsx b/apps/admin-web/src/pages/AdminErrorReportsPage.tsx index 949fa00ae..9c58a5ef7 100644 --- a/apps/admin-web/src/pages/AdminErrorReportsPage.tsx +++ b/apps/admin-web/src/pages/AdminErrorReportsPage.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { downloadAdminErrorReport, @@ -15,14 +15,18 @@ import type { type Props = { token: string; onUnauthorized: (message?: string) => void }; +const ADMIN_ERROR_REPORT_STATUSES = ['new', 'in-progress', 'resolved'] as const; + export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { const [reports, setReports] = useState([]); const [selected, setSelected] = useState(null); const [status, setStatus] = useState(''); const [busy, setBusy] = useState(false); const [filterStatus, setFilterStatus] = useState(''); + const openReportRequestId = useRef(0); const load = useCallback(async () => { + setStatus(''); try { const response = await listAdminErrorReports(token, { status: filterStatus || undefined, @@ -40,9 +44,13 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { }, [load]); async function openReport(batchId: string) { + const requestId = ++openReportRequestId.current; + setStatus(''); try { - setSelected(await getAdminErrorReport(token, batchId)); + const detail = await getAdminErrorReport(token, batchId); + if (requestId === openReportRequestId.current) setSelected(detail); } catch (error) { + if (requestId !== openReportRequestId.current) return; if (isAdminApiError(error) && error.status === 401) return onUnauthorized(); setStatus(formatAdminApiError(error)); @@ -52,6 +60,7 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { async function saveStatus(nextStatus: string) { if (!selected || busy) return; setBusy(true); + setStatus(''); try { setSelected( await updateAdminErrorReport(token, selected.batchId, { @@ -69,13 +78,16 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { } async function download(batchId: string) { + setStatus(''); try { const blob = await downloadAdminErrorReport(token, batchId); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = `${batchId}.zip`; + document.body.appendChild(anchor); anchor.click(); + anchor.remove(); URL.revokeObjectURL(url); } catch (error) { if (isAdminApiError(error) && error.status === 401) onUnauthorized(); @@ -97,13 +109,19 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { onChange={(event) => setFilterStatus(event.target.value)} > - - - + {ADMIN_ERROR_REPORT_STATUSES.map((value) => ( + + ))} - {status ?

{status}

: null} + {status ? ( +

+ {status} +

+ ) : null}
@@ -160,9 +178,11 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) { onChange={(event) => void saveStatus(event.target.value)} disabled={busy} > - - - + {ADMIN_ERROR_REPORT_STATUSES.map((value) => ( + + ))}