fix(export): harden downloads and CSV encoding

This commit is contained in:
Supra4E8C
2026-02-22 12:42:32 +08:00
parent a47c990cf4
commit c75aeecec5
5 changed files with 48 additions and 47 deletions
@@ -12,6 +12,7 @@ import {
collectUsageDetails,
extractTotalTokens
} from '@/utils/usage';
import { downloadBlob } from '@/utils/download';
import styles from '@/pages/UsagePage.module.scss';
const ALL_FILTER = '__all__';
@@ -53,17 +54,9 @@ const toNumber = (value: unknown): number => {
const encodeCsv = (value: string | number): string => {
const text = String(value ?? '');
return `"${text.replace(/"/g, '""')}"`;
};
const downloadFile = (filename: string, content: string, mimeType: string) => {
const blob = new Blob([content], { type: mimeType });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
const trimmedLeft = text.replace(/^\s+/, '');
const safeText = trimmedLeft && /^[=+\-@]/.test(trimmedLeft) ? `'${text}` : text;
return `"${safeText.replace(/"/g, '""')}"`;
};
type CredentialInfo = {
@@ -370,7 +363,10 @@ export function RequestEventsDetailsCard({
const content = [csvHeader.join(','), ...csvRows].join('\n');
const fileTime = new Date().toISOString().replace(/[:.]/g, '-');
downloadFile(`usage-events-${fileTime}.csv`, content, 'text/csv;charset=utf-8');
downloadBlob({
filename: `usage-events-${fileTime}.csv`,
blob: new Blob([content], { type: 'text/csv;charset=utf-8' })
});
};
const handleExportJson = () => {
@@ -394,7 +390,10 @@ export function RequestEventsDetailsCard({
const content = JSON.stringify(payload, null, 2);
const fileTime = new Date().toISOString().replace(/[:.]/g, '-');
downloadFile(`usage-events-${fileTime}.json`, content, 'application/json;charset=utf-8');
downloadBlob({
filename: `usage-events-${fileTime}.json`,
blob: new Blob([content], { type: 'application/json;charset=utf-8' })
});
};
return (
+5 -7
View File
@@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { USAGE_STATS_STALE_TIME_MS, useNotificationStore, useUsageStatsStore } from '@/stores';
import { usageApi } from '@/services/api/usage';
import { downloadBlob } from '@/utils/download';
import { loadModelPrices, saveModelPrices, type ModelPrice } from '@/utils/usage';
export interface UsagePayload {
@@ -62,13 +63,10 @@ export function useUsageData(): UseUsageDataReturn {
? new Date().toISOString()
: exportedAt.toISOString();
const filename = `usage-export-${safeTimestamp.replace(/[:.]/g, '-')}.json`;
const blob = new Blob([JSON.stringify(data ?? {}, null, 2)], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
window.URL.revokeObjectURL(url);
downloadBlob({
filename,
blob: new Blob([JSON.stringify(data ?? {}, null, 2)], { type: 'application/json' })
});
showNotification(t('usage_stats.export_success'), 'success');
} catch (err: unknown) {
const message = err instanceof Error ? err.message : '';