From 49fe25a99bb0c60b3a81a11ef7eb06e762fb1d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=A6=82=E6=91=B8=E9=B1=BC=E5=8E=BB?= <1780903673@qq.com> Date: Mon, 24 Mar 2025 21:26:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20=E6=8F=90=E4=BE=9B=20useUpl?= =?UTF-8?q?oad=20hooks=20=E7=94=A8=E4=BA=8E=E4=BE=BF=E6=8D=B7=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=20(#969)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/composables/useUpload.ts | 163 ++++++++++++++++++ .../components/wd-upload/types.ts | 9 +- .../components/wd-upload/wd-upload.vue | 142 ++++++--------- 3 files changed, 224 insertions(+), 90 deletions(-) create mode 100644 src/uni_modules/wot-design-uni/components/composables/useUpload.ts diff --git a/src/uni_modules/wot-design-uni/components/composables/useUpload.ts b/src/uni_modules/wot-design-uni/components/composables/useUpload.ts new file mode 100644 index 00000000..1cd86dcf --- /dev/null +++ b/src/uni_modules/wot-design-uni/components/composables/useUpload.ts @@ -0,0 +1,163 @@ +import { isFunction } from '../common/util' +import type { UploadFileItem, UploadMethod, UploadStatusType } from '../wd-upload/types' + +export const UPLOAD_STATUS: Record = { + PENDING: 'pending', + LOADING: 'loading', + SUCCESS: 'success', + FAIL: 'fail' +} + +export interface UseUploadReturn { + // 开始上传文件 + startUpload: (file: UploadFileItem, options: UseUploadOptions) => UniApp.UploadTask | void | Promise + // 中断上传 + abort: (task?: UniApp.UploadTask) => void + // 上传状态常量 + UPLOAD_STATUS: Record +} + +export interface UseUploadOptions { + // 上传地址 + action: string + // 请求头 + header?: Record + // 文件对应的 key + name?: string + // 其它表单数据 + formData?: Record + // 文件类型 + fileType?: 'image' | 'video' | 'audio' + // 成功状态码 + statusCode?: number + // 文件状态的key + statusKey?: string + // 自定义上传方法 + uploadMethod?: UploadMethod + // 上传成功回调 + onSuccess?: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record) => void + // 上传失败回调 + onError?: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record) => void + // 上传进度回调 + onProgress?: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => void + // 是否自动中断之前的上传任务 + abortPrevious?: boolean +} + +export function useUpload(): UseUploadReturn { + let currentTask: UniApp.UploadTask | null = null + + // 中断上传 + const abort = (task?: UniApp.UploadTask) => { + if (task) { + task.abort() + } else if (currentTask) { + currentTask.abort() + currentTask = null + } + } + + /** + * 默认上传方法 + */ + const defaultUpload: UploadMethod = (file, formData, options) => { + // 如果配置了自动中断,则中断之前的上传任务 + if (options.abortPrevious) { + abort() + } + + const uploadTask = uni.uploadFile({ + url: options.action, + header: options.header, + name: options.name, + fileName: options.name, + fileType: options.fileType, + formData, + filePath: file.url, + success(res) { + if (res.statusCode === options.statusCode) { + // 上传成功 + options.onSuccess(res, file, formData) + } else { + // 上传失败 + options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData) + } + }, + fail(err) { + // 上传失败 + options.onError(err, file, formData) + } + }) + + currentTask = uploadTask + + // 获取当前文件加载的百分比 + uploadTask.onProgressUpdate((res) => { + options.onProgress(res, file) + }) + + // 返回上传任务实例,让外部可以控制上传过程 + return uploadTask + } + + /** + * 开始上传文件 + */ + const startUpload = (file: UploadFileItem, options: UseUploadOptions) => { + const { + uploadMethod, + formData = {}, + action, + name = 'file', + header = {}, + fileType = 'image', + statusCode = 200, + statusKey = 'status', + abortPrevious = false + } = options + + // 设置上传中状态 + file[statusKey] = UPLOAD_STATUS.LOADING + + const uploadOptions = { + action, + header, + name, + fileName: name, + fileType, + statusCode, + abortPrevious, + onSuccess: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record) => { + // 更新文件状态 + file[statusKey] = UPLOAD_STATUS.SUCCESS + currentTask = null + options.onSuccess?.(res, file, formData) + }, + onError: (error: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record) => { + // 更新文件状态和错误信息 + file[statusKey] = UPLOAD_STATUS.FAIL + file.error = error.errMsg + currentTask = null + options.onError?.(error, file, formData) + }, + onProgress: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => { + // 更新上传进度 + file.percent = res.progress + options.onProgress?.(res, file) + } + } + + // 返回上传任务实例,支持外部获取uploadTask进行操作 + if (isFunction(uploadMethod)) { + return uploadMethod(file, formData, uploadOptions) + } else { + return defaultUpload(file, formData, uploadOptions) + } + } + + return { + startUpload, + abort, + UPLOAD_STATUS + } +} diff --git a/src/uni_modules/wot-design-uni/components/wd-upload/types.ts b/src/uni_modules/wot-design-uni/components/wd-upload/types.ts index 09d3f41f..edbe43d1 100644 --- a/src/uni_modules/wot-design-uni/components/wd-upload/types.ts +++ b/src/uni_modules/wot-design-uni/components/wd-upload/types.ts @@ -120,11 +120,13 @@ export type UploadMethod = ( fileName: string fileType: 'image' | 'video' | 'audio' statusCode: number + // 添加是否自动中断之前上传的选项 + abortPrevious?: boolean onSuccess: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: UploadFormData) => void onError: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: UploadFormData) => void onProgress: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => void } -) => void | Promise +) => UniApp.UploadTask | void | Promise // 修改这里,支持返回 UploadTask 类型 export const uploadProps = { ...baseProps, @@ -338,6 +340,11 @@ export type UploadExpose = { * 手动触发上传 */ submit: () => void + /** + * 取消上传 + * @param task 上传任务 + */ + abort: (task?: UniApp.UploadTask) => void } export type UploadErrorEvent = { diff --git a/src/uni_modules/wot-design-uni/components/wd-upload/wd-upload.vue b/src/uni_modules/wot-design-uni/components/wd-upload/wd-upload.vue index 57422366..355290ab 100644 --- a/src/uni_modules/wot-design-uni/components/wd-upload/wd-upload.vue +++ b/src/uni_modules/wot-design-uni/components/wd-upload/wd-upload.vue @@ -101,9 +101,10 @@ import wdVideoPreview from '../wd-video-preview/wd-video-preview.vue' import wdLoading from '../wd-loading/wd-loading.vue' import { computed, ref, watch } from 'vue' -import { context, getType, isEqual, isImageUrl, isVideoUrl, isFunction, isDef, deepClone } from '../common/util' +import { context, isEqual, isImageUrl, isVideoUrl, isFunction, isDef, deepClone } from '../common/util' import { chooseFile } from './utils' import { useTranslate } from '../composables/useTranslate' +import { useUpload } from '../composables/useUpload' import { uploadProps, type UploadFileItem, @@ -133,7 +134,8 @@ const emit = defineEmits<{ }>() defineExpose({ - submit: () => startUploadFiles() + submit: () => startUploadFiles(), + abort: () => abort() }) const { translate } = useTranslate('upload') @@ -144,6 +146,8 @@ const showUpload = computed(() => !props.limit || uploadFiles.value.length < pro const videoPreview = ref() +const { startUpload, abort, UPLOAD_STATUS } = useUpload() + watch( () => props.fileList, (val) => { @@ -258,48 +262,55 @@ function emitFileList() { } /** - * 组件内部上传方法 - * @param file 文件 - * @param formData - * @param options + * 开始上传文件 */ -const upload: UploadMethod = (file, formData, options) => { - const uploadTask = uni.uploadFile({ - url: options.action, - header: options.header, - name: options.name, - fileName: options.name, - fileType: options.fileType as 'image' | 'video' | 'audio', - formData, - filePath: file.url, - success(res) { - if (res.statusCode === options.statusCode) { - // 上传成功进行文件列表拼接 - options.onSuccess(res, file, formData) - } else { - // 上传失败处理 - options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData) - } - }, - fail(err) { - // 上传失败处理 - options.onError(err, file, formData) - } - }) - // 获取当前文件加载的百分比 - uploadTask.onProgressUpdate((res) => { - options.onProgress(res, file) - }) -} +function startUploadFiles() { + const { buildFormData, formData = {}, statusKey } = props + const { action, name, header = {}, accept, successStatus, uploadMethod } = props + const statusCode = isDef(successStatus) ? successStatus : 200 -const startUpload: UploadMethod = (file, formData, options) => { - const { statusKey, uploadMethod } = props - // 设置上传中,防止重复发起上传 - file[statusKey] = 'loading' - if (isFunction(uploadMethod)) { - uploadMethod(file, formData, options) - } else { - upload(file, formData, options) + for (const uploadFile of uploadFiles.value) { + // 仅开始未上传的文件 + if (uploadFile[statusKey] === UPLOAD_STATUS.PENDING) { + if (buildFormData) { + buildFormData({ + file: uploadFile, + formData, + resolve: (formData: Record) => { + formData && + startUpload(uploadFile, { + action, + header, + name, + formData, + fileType: accept as 'image' | 'video' | 'audio', + statusCode, + statusKey, + uploadMethod, + onSuccess: handleSuccess, + onError: handleError, + onProgress: handleProgress, + abortPrevious: true // 自动中断之前的上传 + }) + } + }) + } else { + startUpload(uploadFile, { + action, + header, + name, + formData, + fileType: accept as 'image' | 'video' | 'audio', + statusCode, + statusKey, + uploadMethod, + onSuccess: handleSuccess, + onError: handleError, + onProgress: handleProgress, + abortPrevious: true + }) + } + } } } @@ -348,53 +359,6 @@ function initFile(file: ChooseFile, currentIndex?: number) { } } -/** - * 开始上传文件 - */ -function startUploadFiles() { - const { buildFormData, formData = {}, statusKey } = props - const { action, name, header = {}, accept, successStatus } = props - const statusCode = isDef(successStatus) ? successStatus : 200 - - for (const uploadFile of uploadFiles.value) { - // 仅开始未上传的文件 - if (uploadFile[statusKey] == 'pending') { - if (buildFormData) { - buildFormData({ - file: uploadFile, - formData, - resolve: (formData: Record) => { - formData && - startUpload(uploadFile, formData, { - onSuccess: handleSuccess, - onError: handleError, - onProgress: handleProgress, - action, - header, - name, - fileName: name, - fileType: accept as 'image' | 'video' | 'audio', - statusCode - }) - } - }) - } else { - startUpload(uploadFile, formData, { - onSuccess: handleSuccess, - onError: handleError, - onProgress: handleProgress, - action, - header, - name, - fileName: name, - fileType: accept as 'image' | 'video' | 'audio', - statusCode - }) - } - } - } -} - /** * @description 上传失败捕获 * @param {Object} err 错误返回信息