2025-08-10 22:42:56 +08:00
|
|
|
|
// 文件上传相关API接口
|
|
|
|
|
import { ApiRequest } from '../request'
|
|
|
|
|
import type { ApiResponse } from '../types'
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 上传API模块
|
|
|
|
|
*/
|
|
|
|
|
export class UploadApi {
|
|
|
|
|
// 上传单个文件
|
|
|
|
|
static uploadFile(
|
|
|
|
|
file: File,
|
|
|
|
|
type: 'image' | 'video' | 'document' | 'audio' = 'image',
|
|
|
|
|
onProgress?: (progress: number) => void
|
|
|
|
|
): Promise<ApiResponse<{
|
|
|
|
|
url: string
|
|
|
|
|
filename: string
|
|
|
|
|
size: number
|
|
|
|
|
type: string
|
|
|
|
|
hash?: string
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.upload(`/upload/${type}`, file, onProgress)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上传头像
|
|
|
|
|
static uploadAvatar(
|
|
|
|
|
file: File,
|
|
|
|
|
onProgress?: (progress: number) => void
|
|
|
|
|
): Promise<ApiResponse<{
|
|
|
|
|
url: string
|
|
|
|
|
filename: string
|
|
|
|
|
size: number
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.upload('/upload/avatar', file, onProgress)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上传课程封面
|
|
|
|
|
static uploadCourseThumbnail(
|
|
|
|
|
file: File,
|
|
|
|
|
courseId?: number,
|
|
|
|
|
onProgress?: (progress: number) => void
|
|
|
|
|
): Promise<ApiResponse<{
|
|
|
|
|
url: string
|
2025-09-12 22:59:14 +08:00
|
|
|
|
message: string
|
|
|
|
|
success: boolean
|
2025-08-10 22:42:56 +08:00
|
|
|
|
filename: string
|
|
|
|
|
size: number
|
|
|
|
|
}>> {
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
formData.append('file', file)
|
|
|
|
|
if (courseId) {
|
|
|
|
|
formData.append('courseId', courseId.toString())
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-12 22:59:14 +08:00
|
|
|
|
return ApiRequest.post('/sys/common/upload', formData, {
|
2025-08-10 22:42:56 +08:00
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'multipart/form-data',
|
|
|
|
|
},
|
|
|
|
|
onUploadProgress: (progressEvent: any) => {
|
|
|
|
|
if (onProgress && progressEvent.total) {
|
|
|
|
|
const progress = Math.round(
|
|
|
|
|
(progressEvent.loaded * 100) / progressEvent.total
|
|
|
|
|
)
|
|
|
|
|
onProgress(progress)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上传课程视频
|
|
|
|
|
static uploadCourseVideo(
|
|
|
|
|
file: File,
|
|
|
|
|
courseId?: number,
|
|
|
|
|
lessonId?: number,
|
|
|
|
|
onProgress?: (progress: number) => void
|
|
|
|
|
): Promise<ApiResponse<{
|
|
|
|
|
url: string
|
|
|
|
|
filename: string
|
|
|
|
|
size: number
|
|
|
|
|
duration?: number
|
|
|
|
|
resolution?: string
|
|
|
|
|
}>> {
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
formData.append('file', file)
|
|
|
|
|
if (courseId) {
|
|
|
|
|
formData.append('courseId', courseId.toString())
|
|
|
|
|
}
|
|
|
|
|
if (lessonId) {
|
|
|
|
|
formData.append('lessonId', lessonId.toString())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ApiRequest.post('/upload/course-video', formData, {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'multipart/form-data',
|
|
|
|
|
},
|
|
|
|
|
onUploadProgress: (progressEvent: any) => {
|
|
|
|
|
if (onProgress && progressEvent.total) {
|
|
|
|
|
const progress = Math.round(
|
|
|
|
|
(progressEvent.loaded * 100) / progressEvent.total
|
|
|
|
|
)
|
|
|
|
|
onProgress(progress)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上传课程资源文件
|
|
|
|
|
static uploadCourseResource(
|
|
|
|
|
file: File,
|
|
|
|
|
lessonId: number,
|
|
|
|
|
title?: string,
|
|
|
|
|
description?: string,
|
|
|
|
|
onProgress?: (progress: number) => void
|
|
|
|
|
): Promise<ApiResponse<{
|
|
|
|
|
id: number
|
|
|
|
|
url: string
|
|
|
|
|
filename: string
|
|
|
|
|
title: string
|
|
|
|
|
size: number
|
|
|
|
|
type: string
|
|
|
|
|
}>> {
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
formData.append('file', file)
|
|
|
|
|
formData.append('lessonId', lessonId.toString())
|
|
|
|
|
if (title) {
|
|
|
|
|
formData.append('title', title)
|
|
|
|
|
}
|
|
|
|
|
if (description) {
|
|
|
|
|
formData.append('description', description)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ApiRequest.post('/upload/course-resource', formData, {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'multipart/form-data',
|
|
|
|
|
},
|
|
|
|
|
onUploadProgress: (progressEvent: any) => {
|
|
|
|
|
if (onProgress && progressEvent.total) {
|
|
|
|
|
const progress = Math.round(
|
|
|
|
|
(progressEvent.loaded * 100) / progressEvent.total
|
|
|
|
|
)
|
|
|
|
|
onProgress(progress)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 批量上传文件
|
|
|
|
|
static uploadMultipleFiles(
|
|
|
|
|
files: File[],
|
|
|
|
|
type: 'image' | 'video' | 'document' | 'audio' = 'image',
|
|
|
|
|
onProgress?: (progress: number) => void
|
|
|
|
|
): Promise<ApiResponse<Array<{
|
|
|
|
|
url: string
|
|
|
|
|
filename: string
|
|
|
|
|
size: number
|
|
|
|
|
type: string
|
|
|
|
|
success: boolean
|
|
|
|
|
error?: string
|
|
|
|
|
}>>> {
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
files.forEach((file) => {
|
|
|
|
|
formData.append(`files`, file)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return ApiRequest.post(`/upload/multiple/${type}`, formData, {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'multipart/form-data',
|
|
|
|
|
},
|
|
|
|
|
onUploadProgress: (progressEvent: any) => {
|
|
|
|
|
if (onProgress && progressEvent.total) {
|
|
|
|
|
const progress = Math.round(
|
|
|
|
|
(progressEvent.loaded * 100) / progressEvent.total
|
|
|
|
|
)
|
|
|
|
|
onProgress(progress)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取上传配置
|
|
|
|
|
static getUploadConfig(): Promise<ApiResponse<{
|
|
|
|
|
maxFileSize: number
|
|
|
|
|
allowedTypes: string[]
|
|
|
|
|
imageTypes: string[]
|
|
|
|
|
videoTypes: string[]
|
|
|
|
|
documentTypes: string[]
|
|
|
|
|
audioTypes: string[]
|
|
|
|
|
uploadUrl: string
|
|
|
|
|
cdnUrl: string
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.get('/upload/config')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取上传token(用于直传OSS等)
|
|
|
|
|
static getUploadToken(type: string = 'image'): Promise<ApiResponse<{
|
|
|
|
|
token: string
|
|
|
|
|
accessKeyId: string
|
|
|
|
|
accessKeySecret: string
|
|
|
|
|
securityToken: string
|
|
|
|
|
bucket: string
|
|
|
|
|
region: string
|
|
|
|
|
endpoint: string
|
|
|
|
|
expiration: string
|
|
|
|
|
policy: string
|
|
|
|
|
signature: string
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.get(`/upload/token/${type}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 删除文件
|
|
|
|
|
static deleteFile(url: string): Promise<ApiResponse<null>> {
|
|
|
|
|
return ApiRequest.delete('/upload/file', { url })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 批量删除文件
|
|
|
|
|
static deleteMultipleFiles(urls: string[]): Promise<ApiResponse<{
|
|
|
|
|
success: string[]
|
|
|
|
|
failed: string[]
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.delete('/upload/files', { urls })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取文件信息
|
|
|
|
|
static getFileInfo(url: string): Promise<ApiResponse<{
|
|
|
|
|
url: string
|
|
|
|
|
filename: string
|
|
|
|
|
size: number
|
|
|
|
|
type: string
|
|
|
|
|
uploadedAt: string
|
|
|
|
|
uploadedBy?: string
|
|
|
|
|
downloads?: number
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.get('/upload/file-info', { url })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 压缩图片
|
|
|
|
|
static compressImage(
|
|
|
|
|
file: File,
|
|
|
|
|
options: {
|
|
|
|
|
quality?: number
|
|
|
|
|
maxWidth?: number
|
|
|
|
|
maxHeight?: number
|
|
|
|
|
format?: 'jpeg' | 'png' | 'webp'
|
|
|
|
|
} = {},
|
|
|
|
|
onProgress?: (progress: number) => void
|
|
|
|
|
): Promise<ApiResponse<{
|
|
|
|
|
url: string
|
|
|
|
|
filename: string
|
|
|
|
|
originalSize: number
|
|
|
|
|
compressedSize: number
|
|
|
|
|
compressionRatio: number
|
|
|
|
|
}>> {
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
formData.append('file', file)
|
|
|
|
|
formData.append('options', JSON.stringify(options))
|
|
|
|
|
|
|
|
|
|
return ApiRequest.post('/upload/compress-image', formData, {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'multipart/form-data',
|
|
|
|
|
},
|
|
|
|
|
onUploadProgress: (progressEvent: any) => {
|
|
|
|
|
if (onProgress && progressEvent.total) {
|
|
|
|
|
const progress = Math.round(
|
|
|
|
|
(progressEvent.loaded * 100) / progressEvent.total
|
|
|
|
|
)
|
|
|
|
|
onProgress(progress)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成缩略图
|
|
|
|
|
static generateThumbnail(
|
|
|
|
|
file: File,
|
|
|
|
|
sizes: Array<{ width: number; height: number }> = [
|
|
|
|
|
{ width: 150, height: 150 },
|
|
|
|
|
{ width: 300, height: 300 },
|
|
|
|
|
],
|
|
|
|
|
onProgress?: (progress: number) => void
|
|
|
|
|
): Promise<ApiResponse<{
|
|
|
|
|
original: string
|
|
|
|
|
thumbnails: Array<{
|
|
|
|
|
size: string
|
|
|
|
|
url: string
|
|
|
|
|
width: number
|
|
|
|
|
height: number
|
|
|
|
|
}>
|
|
|
|
|
}>> {
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
formData.append('file', file)
|
|
|
|
|
formData.append('sizes', JSON.stringify(sizes))
|
|
|
|
|
|
|
|
|
|
return ApiRequest.post('/upload/generate-thumbnail', formData, {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'multipart/form-data',
|
|
|
|
|
},
|
|
|
|
|
onUploadProgress: (progressEvent: any) => {
|
|
|
|
|
if (onProgress && progressEvent.total) {
|
|
|
|
|
const progress = Math.round(
|
|
|
|
|
(progressEvent.loaded * 100) / progressEvent.total
|
|
|
|
|
)
|
|
|
|
|
onProgress(progress)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取上传历史
|
|
|
|
|
static getUploadHistory(params?: {
|
|
|
|
|
page?: number
|
|
|
|
|
pageSize?: number
|
|
|
|
|
type?: string
|
|
|
|
|
startDate?: string
|
|
|
|
|
endDate?: string
|
|
|
|
|
}): Promise<ApiResponse<{
|
|
|
|
|
list: Array<{
|
|
|
|
|
id: number
|
|
|
|
|
filename: string
|
|
|
|
|
originalName: string
|
|
|
|
|
url: string
|
|
|
|
|
size: number
|
|
|
|
|
type: string
|
|
|
|
|
uploadedAt: string
|
|
|
|
|
downloads: number
|
|
|
|
|
}>
|
|
|
|
|
total: number
|
|
|
|
|
page: number
|
|
|
|
|
pageSize: number
|
|
|
|
|
}>> {
|
|
|
|
|
return ApiRequest.get('/upload/history', params)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default UploadApi
|