mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-06 09:08:51 +08:00
feat: ✨ 提供 useUpload hooks 用于便捷上传 (#969)
This commit is contained in:
parent
460a83802b
commit
49fe25a99b
@ -0,0 +1,163 @@
|
|||||||
|
import { isFunction } from '../common/util'
|
||||||
|
import type { UploadFileItem, UploadMethod, UploadStatusType } from '../wd-upload/types'
|
||||||
|
|
||||||
|
export const UPLOAD_STATUS: Record<string, UploadStatusType> = {
|
||||||
|
PENDING: 'pending',
|
||||||
|
LOADING: 'loading',
|
||||||
|
SUCCESS: 'success',
|
||||||
|
FAIL: 'fail'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseUploadReturn {
|
||||||
|
// 开始上传文件
|
||||||
|
startUpload: (file: UploadFileItem, options: UseUploadOptions) => UniApp.UploadTask | void | Promise<void>
|
||||||
|
// 中断上传
|
||||||
|
abort: (task?: UniApp.UploadTask) => void
|
||||||
|
// 上传状态常量
|
||||||
|
UPLOAD_STATUS: Record<string, UploadStatusType>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseUploadOptions {
|
||||||
|
// 上传地址
|
||||||
|
action: string
|
||||||
|
// 请求头
|
||||||
|
header?: Record<string, any>
|
||||||
|
// 文件对应的 key
|
||||||
|
name?: string
|
||||||
|
// 其它表单数据
|
||||||
|
formData?: Record<string, any>
|
||||||
|
// 文件类型
|
||||||
|
fileType?: 'image' | 'video' | 'audio'
|
||||||
|
// 成功状态码
|
||||||
|
statusCode?: number
|
||||||
|
// 文件状态的key
|
||||||
|
statusKey?: string
|
||||||
|
// 自定义上传方法
|
||||||
|
uploadMethod?: UploadMethod
|
||||||
|
// 上传成功回调
|
||||||
|
onSuccess?: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
|
||||||
|
// 上传失败回调
|
||||||
|
onError?: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => 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<string, any>) => {
|
||||||
|
// 更新文件状态
|
||||||
|
file[statusKey] = UPLOAD_STATUS.SUCCESS
|
||||||
|
currentTask = null
|
||||||
|
options.onSuccess?.(res, file, formData)
|
||||||
|
},
|
||||||
|
onError: (error: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
|
||||||
|
// 更新文件状态和错误信息
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -120,11 +120,13 @@ export type UploadMethod = (
|
|||||||
fileName: string
|
fileName: string
|
||||||
fileType: 'image' | 'video' | 'audio'
|
fileType: 'image' | 'video' | 'audio'
|
||||||
statusCode: number
|
statusCode: number
|
||||||
|
// 添加是否自动中断之前上传的选项
|
||||||
|
abortPrevious?: boolean
|
||||||
onSuccess: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: UploadFormData) => void
|
onSuccess: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: UploadFormData) => void
|
||||||
onError: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: UploadFormData) => void
|
onError: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: UploadFormData) => void
|
||||||
onProgress: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => void
|
onProgress: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => void
|
||||||
}
|
}
|
||||||
) => void | Promise<void>
|
) => UniApp.UploadTask | void | Promise<void> // 修改这里,支持返回 UploadTask 类型
|
||||||
|
|
||||||
export const uploadProps = {
|
export const uploadProps = {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
@ -338,6 +340,11 @@ export type UploadExpose = {
|
|||||||
* 手动触发上传
|
* 手动触发上传
|
||||||
*/
|
*/
|
||||||
submit: () => void
|
submit: () => void
|
||||||
|
/**
|
||||||
|
* 取消上传
|
||||||
|
* @param task 上传任务
|
||||||
|
*/
|
||||||
|
abort: (task?: UniApp.UploadTask) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UploadErrorEvent = {
|
export type UploadErrorEvent = {
|
||||||
|
|||||||
@ -101,9 +101,10 @@ import wdVideoPreview from '../wd-video-preview/wd-video-preview.vue'
|
|||||||
import wdLoading from '../wd-loading/wd-loading.vue'
|
import wdLoading from '../wd-loading/wd-loading.vue'
|
||||||
|
|
||||||
import { computed, ref, watch } from '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 { chooseFile } from './utils'
|
||||||
import { useTranslate } from '../composables/useTranslate'
|
import { useTranslate } from '../composables/useTranslate'
|
||||||
|
import { useUpload } from '../composables/useUpload'
|
||||||
import {
|
import {
|
||||||
uploadProps,
|
uploadProps,
|
||||||
type UploadFileItem,
|
type UploadFileItem,
|
||||||
@ -133,7 +134,8 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineExpose<UploadExpose>({
|
defineExpose<UploadExpose>({
|
||||||
submit: () => startUploadFiles()
|
submit: () => startUploadFiles(),
|
||||||
|
abort: () => abort()
|
||||||
})
|
})
|
||||||
|
|
||||||
const { translate } = useTranslate('upload')
|
const { translate } = useTranslate('upload')
|
||||||
@ -144,6 +146,8 @@ const showUpload = computed(() => !props.limit || uploadFiles.value.length < pro
|
|||||||
|
|
||||||
const videoPreview = ref<VideoPreviewInstance>()
|
const videoPreview = ref<VideoPreviewInstance>()
|
||||||
|
|
||||||
|
const { startUpload, abort, UPLOAD_STATUS } = useUpload()
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.fileList,
|
() => props.fileList,
|
||||||
(val) => {
|
(val) => {
|
||||||
@ -258,48 +262,55 @@ function emitFileList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组件内部上传方法
|
* 开始上传文件
|
||||||
* @param file 文件
|
|
||||||
* @param formData
|
|
||||||
* @param options
|
|
||||||
*/
|
*/
|
||||||
const upload: UploadMethod = (file, formData, options) => {
|
function startUploadFiles() {
|
||||||
const uploadTask = uni.uploadFile({
|
const { buildFormData, formData = {}, statusKey } = props
|
||||||
url: options.action,
|
const { action, name, header = {}, accept, successStatus, uploadMethod } = props
|
||||||
header: options.header,
|
const statusCode = isDef(successStatus) ? successStatus : 200
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const startUpload: UploadMethod = (file, formData, options) => {
|
for (const uploadFile of uploadFiles.value) {
|
||||||
const { statusKey, uploadMethod } = props
|
// 仅开始未上传的文件
|
||||||
// 设置上传中,防止重复发起上传
|
if (uploadFile[statusKey] === UPLOAD_STATUS.PENDING) {
|
||||||
file[statusKey] = 'loading'
|
if (buildFormData) {
|
||||||
if (isFunction(uploadMethod)) {
|
buildFormData({
|
||||||
uploadMethod(file, formData, options)
|
file: uploadFile,
|
||||||
} else {
|
formData,
|
||||||
upload(file, formData, options)
|
resolve: (formData: Record<string, any>) => {
|
||||||
|
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<string, any>) => {
|
|
||||||
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 上传失败捕获
|
* @description 上传失败捕获
|
||||||
* @param {Object} err 错误返回信息
|
* @param {Object} err 错误返回信息
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user