import {BaseService} from '../core/base.service' import type {ListUploadsResponse, Upload, UploadImageResponse, FileStatsResponse} from './types' import type {InternalAxiosRequestConfig} from 'axios' import apiClient from '../core/api-client' export type ImageQuality = 'low' | 'medium' | 'high' | 'origin' /** * 根据上传ID构造文件访问URL * @param id - 上传记录ID * @param quality - 图片质量 * @returns 文件访问URL */ export function getFileUrl( id: string | number | null | undefined, quality: ImageQuality = 'origin' ): string | null { if (!id) return null if (quality === 'origin') return `/f/${id}` return `/f/${id}?quality=${quality}` } /** * 格式化文件大小 */ export function formatFileSize(bytes: number): string { if (bytes === 0) return '0 B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}` } /** * 上传服务 * 处理文件上传相关的 API 请求 */ export class UploadService extends BaseService { protected static readonly basePath = '/api/v1/admin/uploads' /** * 通用文件上传 * @param file - 文件对象 * @param type - 业务分类(如 avatar、attachment、generic) * @param metadata - 可选额外 JSON 元数据 */ static async uploadFile( file: File, type: string = 'generic', metadata?: Record, accessMode?: number ): Promise { const formData = new FormData() formData.append('file', file) formData.append('type', type) if (metadata) { formData.append('metadata', JSON.stringify(metadata)) } if (accessMode !== undefined) { formData.append('access_mode', String(accessMode)) } const response = await apiClient.post<{ data: Upload }>('/api/v1/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' }, } as InternalAxiosRequestConfig) return response.data.data } /** * 获取文件列表 * @param page - 页码(1-based) * @param pageSize - 每页数量 * @param keyword - 搜索关键词(文件名模糊) * @param type - 业务分类过滤 * @param extension - 扩展名过滤 */ static async listUploads( page = 1, pageSize = 20, keyword?: string, type?: string, extension?: string ): Promise { const params: Record = { page, page_size: pageSize } if (keyword) params.keyword = keyword if (type) params.type = type if (extension) params.extension = extension return this.get('', params) } /** * 获取当前用户文件统计信息 */ static async getFileStats(): Promise { return this.get('/stats') } /** * 删除文件 */ static async deleteFile(id: string): Promise { return this.delete(`/${id}`) } /** * 获取单文件下载 URL(触发 attachment 下载) */ static getDownloadUrl(id: string): string { return `/api/v1/admin/uploads/download/${id}` } /** * 批量 ZIP 打包下载 * @param ids - 文件 ID 数组 */ static async batchDownload(ids: string[]): Promise { const response = await this.post('/download/batch', { ids }, { responseType: 'blob', } as InternalAxiosRequestConfig) return response } /** * 将 base64 图片转换为 Blob 并上传(兼容旧接口) */ static async uploadBase64Image( base64: string, type: string = 'generic', filename: string = 'image.png', accessMode?: number ): Promise { const response = await fetch(base64) const blob = await response.blob() const mimeType = base64.match(/data:([^;]+);/)?.[1] || 'image/png' const file = new File([blob], filename, { type: mimeType }) const result = await this.uploadFile(file, type, undefined, accessMode) return { id: result.id } } /** * 获取我的文件列表 */ static async listMyUploads( page = 1, pageSize = 20, keyword?: string, type?: string, extension?: string ): Promise { const params: Record = { page, page_size: pageSize } if (keyword) params.keyword = keyword if (type) params.type = type if (extension) params.extension = extension const response = await apiClient.get<{ data: ListUploadsResponse }>('/api/v1/upload/my', { params } as InternalAxiosRequestConfig) return response.data.data } /** * 删除我的文件 */ static async deleteMyFile(id: string): Promise { const response = await apiClient.delete<{ data: void }>(`/api/v1/upload/${id}`) return response.data.data } /** * 更新我的文件 */ static async updateMyFile(id: string, fileName: string, accessMode?: number): Promise { const response = await apiClient.put<{ data: Upload }>(`/api/v1/upload/${id}`, { file_name: fileName, access_mode: accessMode }) return response.data.data } /** * 我的文件批量 ZIP 打包下载 */ static async batchDownloadMyFiles(ids: string[]): Promise { const response = await apiClient.post('/api/v1/upload/download/batch', { ids }, { responseType: 'blob', } as InternalAxiosRequestConfig) return response.data } }