| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- /**
- * 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<ListExportRecordsResponse> => {
- try {
- // Build query parameters
- const params: Record<string, string | number> = {
- userId: filters.userId,
- page: filters.page || 1,
- pageSize: filters.pageSize || 20,
- sortOrder: filters.sortOrder || 'desc',
- };
- const response = await apiClient.get<ApiResponse<ListExportRecordsResponse>>(
- '/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<void> => {
- 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<void> => {
- try {
- await apiClient.delete<ApiResponse<void>>(
- `/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<StorageInfo> => {
- try {
- const response = await apiClient.get<ApiResponse<StorageInfo>>(
- '/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;
|