export function isAbortError(error) { return error && error.name === 'AbortError' } export function isSavePickerSupported() { return typeof window !== 'undefined' && typeof window.showSaveFilePicker === 'function' } export function isOpenPickerSupported() { return typeof window !== 'undefined' && typeof window.showOpenFilePicker === 'function' } function downloadBlob(blob, filename) { const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } export async function saveBlob(blob, filename, types) { if (isSavePickerSupported()) { const handle = await window.showSaveFilePicker({ suggestedName: filename, types }) const writable = await handle.createWritable() await writable.write(blob) await writable.close() return { method: 'picker' } } downloadBlob(blob, filename) return { method: 'download' } } export async function openJsonFile() { if (!isOpenPickerSupported()) return null const handles = await window.showOpenFilePicker({ multiple: false, types: [ { description: 'JSON 配置文件', accept: { 'application/json': ['.json'] } } ] }) if (!handles.length) return null return handles[0].getFile() } export async function openExcelFile() { if (!isOpenPickerSupported()) return null const handles = await window.showOpenFilePicker({ multiple: false, types: [ { description: 'Excel 工作簿', accept: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'], 'application/vnd.ms-excel': ['.xls'] } } ] }) if (!handles.length) return null return handles[0].getFile() }