diff --git a/.env.dev b/.env.dev index daec9c5..5051fd6 100644 --- a/.env.dev +++ b/.env.dev @@ -34,4 +34,7 @@ VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn' VITE_APP_CAPTCHA_ENABLE=true # GoView域名 -VITE_GOVIEW_URL='http://127.0.0.1:3000' \ No newline at end of file +VITE_GOVIEW_URL='http://127.0.0.1:3000' + +# 文件上传地址(可选) +VITE_FILE_UPLOAD_URL='http://114.55.171.231:6042/api/file/upload-chunked' \ No newline at end of file diff --git a/.env.local b/.env.local index 3576558..b442b48 100644 --- a/.env.local +++ b/.env.local @@ -31,4 +31,7 @@ VITE_MALL_H5_DOMAIN='http://localhost:3000' VITE_APP_CAPTCHA_ENABLE=false # GoView域名 -VITE_GOVIEW_URL='http://127.0.0.1:3000' \ No newline at end of file +VITE_GOVIEW_URL='http://127.0.0.1:3000' + +# 文件上传地址(可选) +VITE_FILE_UPLOAD_URL='http://114.55.171.231:6042/api/file/upload-chunked' \ No newline at end of file diff --git a/src/api/ecgdata/index.ts b/src/api/ecgdata/index.ts index d2540f0..10e130e 100644 --- a/src/api/ecgdata/index.ts +++ b/src/api/ecgdata/index.ts @@ -60,6 +60,10 @@ export const ecgdataApi = { updatewearstarttime: async (data: any) => { return await request.put({ url: `/system/ecgdata/update-wearstarttime`, data }) }, + //更新心电图的文件名称和压缩文件名称 + upecgfilename: async (data: any) => { + return await request.put({ url: `/system/ecgdata/upecgfilename`, data }) + }, // 删除心电图动态数据 deleteecgdata: async (id: number) => { diff --git a/src/utils/upload.ts b/src/utils/upload.ts new file mode 100644 index 0000000..dc06abc --- /dev/null +++ b/src/utils/upload.ts @@ -0,0 +1,169 @@ +/** + * 分片上传工具类 + */ + +export interface ChunkUploadOptions { + file: File + chunkSize?: number // 分片大小,默认1MB + url: string + name: string + onProgress?: (progress: number, currentChunk: number, totalChunks: number) => void + onError?: (error: string, chunkIndex?: number) => void + retryTimes?: number // 重试次数,默认3次 + retryDelay?: number // 重试延迟,默认1000ms +} + +export interface ChunkUploadResult { + success: boolean + message?: string + data?: any +} + +/** + * 分片上传文件 + */ +export async function uploadFileInChunks(options: ChunkUploadOptions): Promise { + const { + file, + chunkSize = 1024 * 1024, // 默认1MB + url, + name, + onProgress, + onError, + retryTimes = 3, + retryDelay = 1000 + } = options + + const totalChunks = Math.ceil(file.size / chunkSize) + const uploadedChunks = new Set() // 记录已上传的分片 + + try { + // 分片上传 + for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { + let retryCount = 0 + let success = false + + while (retryCount < retryTimes && !success) { + try { + // 如果已经上传过,跳过 + if (uploadedChunks.has(chunkIndex)) { + success = true + break + } + + // 计算当前分片的起始和结束位置 + const start = chunkIndex * chunkSize + const end = Math.min(start + chunkSize, file.size) + const chunk = file.slice(start, end) + + // 创建FormData + const formData = new FormData() + formData.append('file', chunk, file.name) + formData.append('Name', name) + formData.append('chunkIndex', chunkIndex.toString()) + formData.append('totalChunks', totalChunks.toString()) + + // 发送请求 + const response = await fetch(url, { + method: 'POST', + body: formData + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.message || `分片 ${chunkIndex + 1} 上传失败`) + } + + const result = await response.json() + if (!result.success) { + throw new Error(result.message || `分片 ${chunkIndex + 1} 上传失败`) + } + + // 标记为已上传 + uploadedChunks.add(chunkIndex) + success = true + + // 调用进度回调 + if (onProgress) { + const progress = Math.round(((chunkIndex + 1) / totalChunks) * 100) + onProgress(progress, chunkIndex + 1, totalChunks) + } + + // 添加小延迟,避免请求过于频繁 + await new Promise((resolve) => setTimeout(resolve, 100)) + } catch (error) { + retryCount++ + console.error(`分片 ${chunkIndex + 1} 上传失败,重试 ${retryCount}/${retryTimes}:`, error) + + if (retryCount >= retryTimes) { + // 重试次数用完,抛出错误 + const errorMessage = error instanceof Error ? error.message : '上传失败' + if (onError) { + onError(errorMessage, chunkIndex) + } + throw new Error(`分片 ${chunkIndex + 1} 上传失败: ${errorMessage}`) + } + + // 等待后重试 + await new Promise((resolve) => setTimeout(resolve, retryDelay)) + } + } + } + + return { + success: true, + message: '文件上传成功', + data: { totalChunks, uploadedChunks: Array.from(uploadedChunks) } + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '文件上传失败' + return { + success: false, + message: errorMessage + } + } +} + +/** + * 检查文件是否支持分片上传 + */ +export function isFileSupported(file: File): boolean { + const maxSize = 100 * 1024 * 1024 // 100MB + return file.size <= maxSize +} + +/** + * 计算文件的分片信息 + */ +export function calculateChunkInfo(file: File, chunkSize: number = 1024 * 1024) { + const totalChunks = Math.ceil(file.size / chunkSize) + const lastChunkSize = file.size % chunkSize || chunkSize + + return { + totalChunks, + chunkSize, + lastChunkSize, + fileSize: file.size, + estimatedTime: Math.ceil(totalChunks * 0.5) // 预估时间(秒) + } +} + +/** + * 格式化文件大小 + */ +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(2)) + ' ' + sizes[i] +} + +/** + * 格式化上传速度 + */ +export function formatUploadSpeed(bytesPerSecond: number): string { + return formatFileSize(bytesPerSecond) + '/s' +} diff --git a/src/views/analysis/HOLTER.vue b/src/views/analysis/HOLTER.vue index 88dc7e6..de4627f 100644 --- a/src/views/analysis/HOLTER.vue +++ b/src/views/analysis/HOLTER.vue @@ -338,11 +338,12 @@