InfoRadar / client /src /ts /storage /localFileIO.ts
dqy08's picture
增强多语言支持,网页默认加载示例
bb35e88
/**
* 本地文件 I/O 工具
* 负责与用户硬盘的交互:导入(读取)和导出(下载)文件
* 不负责状态管理和持久化
*/
import type { AnalysisData } from '../api/GLTR_API';
import { validateDemoFormat, ensureJsonExtension } from '../utils/localFileUtils';
import { extractErrorMessage } from '../utils/errorUtils';
import { tr } from '../lang/i18n-lite';
export interface ImportResult {
success: boolean;
data?: AnalysisData;
filename?: string;
message?: string;
cancelled?: boolean; // 用户取消操作
// 多选模式返回的文件列表
files?: Array<{
data: AnalysisData;
filename: string;
}>;
}
/**
* 本地文件 I/O 工具类
* 提供文件的导入(从硬盘读取)和导出(下载到硬盘)功能
*/
export class LocalFileIO {
/**
* 导入文件(弹出文件选择框)
* @param multiple 是否支持多选,默认 false(单选)
* @returns 单选模式返回 data 和 filename,多选模式返回 files 数组
*/
async import(multiple: boolean = false): Promise<ImportResult> {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,application/json';
input.multiple = multiple;
input.style.display = 'none';
const cleanup = () => {
if (input.parentNode) input.parentNode.removeChild(input);
};
input.onchange = async (e: Event) => {
const files = (e.target as HTMLInputElement).files;
if (!files || files.length === 0) {
cleanup();
resolve({ success: false, message: tr('No file selected') });
return;
}
try {
if (multiple) {
// 多选模式:处理所有文件(即使只有1个文件也返回 files 数组格式)
// 逐个处理文件,收集成功和失败的结果
const fileResults: Array<{data: AnalysisData; filename: string}> = [];
const errors: string[] = [];
for (const file of Array.from(files)) {
try {
const text = await file.text();
const data = JSON.parse(text);
validateDemoFormat(data);
const cleanFilename = ensureJsonExtension(file.name);
fileResults.push({
data: data as AnalysisData,
filename: cleanFilename
});
} catch (err) {
const message = extractErrorMessage(err, tr('Read failed'));
errors.push(`${file.name}: ${message}`);
}
}
cleanup();
// 如果所有文件都失败,返回失败结果
if (fileResults.length === 0) {
resolve({
success: false,
message: `${tr('All files failed to read:')}\n${errors.join('\n')}`
});
} else {
// 部分或全部成功,返回成功结果(如果有错误信息,可以包含在message中)
resolve({
success: true,
files: fileResults,
message: errors.length > 0 ? `${tr('Partial files failed:')}\n${errors.join('\n')}` : undefined
});
}
} else {
// 单选模式:只处理第一个文件(保持向后兼容)
const file = files[0];
const text = await file.text();
const data = JSON.parse(text);
validateDemoFormat(data);
cleanup();
// 确保文件名以 .json 结尾(统一入口处理)
const cleanFilename = ensureJsonExtension(file.name);
resolve({
success: true,
data: data as AnalysisData,
filename: cleanFilename
});
}
} catch (error) {
cleanup();
const message = extractErrorMessage(error, tr('Failed to read file'));
resolve({ success: false, message });
}
};
input.oncancel = () => {
cleanup();
resolve({ success: false, message: tr('User cancelled file selection'), cancelled: true });
};
document.body.appendChild(input);
input.click();
});
}
/**
* 导出文件(触发浏览器下载)
* @param data 要导出的数据
* @param filename 文件名
* @returns 是否成功
*/
async export(data: AnalysisData, filename: string): Promise<boolean> {
try {
const jsonString = JSON.stringify(data, null, 2);
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = ensureJsonExtension(filename);
document.body.appendChild(a);
a.click();
setTimeout(() => {
if (a.parentNode) document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
return true;
} catch (error) {
console.error('导出文件失败:', error);
return false;
}
}
}