/** * Export Record Service Module * * Provides methods for export record operations: * - listExportRecords: List export records with pagination * - downloadExportRecord: Download an exported file * - deleteExportRecord: Delete an export record * - getAdminStorage: Get storage usage info (admin only) * * All methods use the configured API client and provide error normalization. * * @module services/exportRecordService */ import apiClient, { getErrorMessage } from './api'; import type { ApiResponse } from '../types/api'; import type { ListExportRecordsResponse, ExportRecordListFilters, StorageInfo, } from '../types/export'; import { downloadBlob, getFileNameFromContentDisposition } from '../utils/download'; /** * List export records with pagination * * @param filters - Filters for listing export records (userId is required) * @returns List of export records and pagination information * @throws {Error} When the API request fails */ export const listExportRecords = async ( filters: ExportRecordListFilters ): Promise => { try { // Build query parameters const params: Record = { userId: filters.userId, page: filters.page || 1, pageSize: filters.pageSize || 20, sortOrder: filters.sortOrder || 'desc', }; const response = await apiClient.get>( '/api/v1/export/records', { params } ); const data = response.data.data; // Fix any incorrect base URLs in download URLs const correctBaseURL = import.meta.env.VITE_API_BASE_URL; if (correctBaseURL && data.records) { data.records = data.records.map(record => { if (record.downloadUrl && record.downloadUrl.startsWith('http')) { try { const urlObj = new URL(record.downloadUrl); const correctUrlObj = new URL(correctBaseURL); if (urlObj.origin !== correctUrlObj.origin) { return { ...record, downloadUrl: record.downloadUrl.replace(urlObj.origin, correctUrlObj.origin) }; } } catch { // Invalid URL, skip } } return record; }); } return data; } catch (error) { const message = getErrorMessage(error); throw new Error(`获取导出记录列表失败: ${message}`, { cause: error }); } }; /** * Download an exported file * * Opens the file in a new tab/window for download. * The backend returns a FileResponse with appropriate headers. * * @param recordId - The export record ID * @param userId - The user ID who owns the record * @throws {Error} When the download fails */ export const downloadExportRecord = async ( recordId: string, userId: string ): Promise => { try { // Use apiClient to download the file (goes through axios with proper config) // This avoids mixed content warnings by using relative URLs const response = await apiClient.get( `/api/v1/export/records/${recordId}/download`, { params: { userId }, responseType: 'blob', // Important: tell axios to expect binary data } ); // Get filename from Content-Disposition header const fileName = getFileNameFromContentDisposition( response.headers['content-disposition'], `export-${recordId}.doc` ); // Create blob and download const blob = new Blob([response.data], { type: 'application/msword' }); downloadBlob(blob, fileName); } catch (error) { const message = getErrorMessage(error); throw new Error(`下载文件失败: ${message}`, { cause: error }); } }; /** * Delete an export record * * Deletes the export record and its associated file from storage. * * @param recordId - The export record ID to delete * @param userId - The user ID who owns the record * @returns Promise that resolves when deletion is successful * @throws {Error} When the API request fails or record not found */ export const deleteExportRecord = async ( recordId: string, userId: string ): Promise => { try { await apiClient.delete>( `/api/v1/export/records/${recordId}`, { params: { userId }, } ); } catch (error) { const message = getErrorMessage(error); throw new Error(`删除导出记录失败: ${message}`, { cause: error }); } }; /** * Get storage usage information (admin only) * * Retrieves detailed storage usage statistics including: * - Total and used disk space * - Per-user storage usage * - Quota information * * @returns Storage usage information * @throws {Error} When the API request fails or user is not authorized */ export const getAdminStorage = async (): Promise => { try { const response = await apiClient.get>( '/api/v1/admin/storage' ); return response.data.data; } catch (error) { const message = getErrorMessage(error); throw new Error(`获取存储信息失败: ${message}`, { cause: error }); } }; /** * Export record service object (alternative export pattern) * Groups all export record operations into a single namespace */ export const exportRecordService = { list: listExportRecords, download: downloadExportRecord, delete: deleteExportRecord, getStorage: getAdminStorage, }; export default exportRecordService;