useUpload.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import { isArray, isDef, isFunction } from '../common/util'
  2. import type { ChooseFile, ChooseFileOption, UploadFileItem, UploadMethod, UploadStatusType } from '../wd-upload/types'
  3. export const UPLOAD_STATUS: Record<string, UploadStatusType> = {
  4. PENDING: 'pending',
  5. LOADING: 'loading',
  6. SUCCESS: 'success',
  7. FAIL: 'fail'
  8. }
  9. export interface UseUploadReturn {
  10. // 开始上传文件
  11. startUpload: (file: UploadFileItem, options: UseUploadOptions) => UniApp.UploadTask | void | Promise<void>
  12. // 中断上传
  13. abort: (task?: UniApp.UploadTask) => void
  14. // 上传状态常量
  15. UPLOAD_STATUS: Record<string, UploadStatusType>
  16. // 选择文件
  17. chooseFile: (options: ChooseFileOption) => Promise<ChooseFile[]>
  18. }
  19. export interface UseUploadOptions {
  20. // 上传地址
  21. action: string
  22. // 请求头
  23. header?: Record<string, any>
  24. // 文件对应的 key
  25. name?: string
  26. // 其它表单数据
  27. formData?: Record<string, any>
  28. // 文件类型 仅支付宝支持且在支付宝平台必填
  29. fileType?: 'image' | 'video' | 'audio'
  30. // 成功状态码
  31. statusCode?: number
  32. // 文件状态的key
  33. statusKey?: string
  34. // 自定义上传方法
  35. uploadMethod?: UploadMethod
  36. // 上传成功回调
  37. onSuccess?: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
  38. // 上传失败回调
  39. onError?: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
  40. // 上传进度回调
  41. onProgress?: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => void
  42. // 是否自动中断之前的上传任务
  43. abortPrevious?: boolean
  44. // 根据文件拓展名过滤(H5支持全部类型过滤,微信小程序支持all和file时过滤,其余平台不支持)
  45. extension?: string[]
  46. }
  47. export function useUpload(): UseUploadReturn {
  48. let currentTask: UniApp.UploadTask | null = null
  49. // 中断上传
  50. const abort = (task?: UniApp.UploadTask) => {
  51. if (task) {
  52. task.abort()
  53. } else if (currentTask) {
  54. currentTask.abort()
  55. currentTask = null
  56. }
  57. }
  58. /**
  59. * 默认上传方法
  60. */
  61. const defaultUpload: UploadMethod = (file, formData, options) => {
  62. // 如果配置了自动中断,则中断之前的上传任务
  63. if (options.abortPrevious) {
  64. abort()
  65. }
  66. const uploadTask = uni.uploadFile({
  67. url: options.action,
  68. header: options.header,
  69. name: options.name,
  70. fileName: options.name,
  71. fileType: options.fileType,
  72. formData,
  73. filePath: file.url,
  74. success(res) {
  75. if (res.statusCode === options.statusCode) {
  76. // 上传成功
  77. options.onSuccess(res, file, formData)
  78. } else {
  79. // 上传失败
  80. options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
  81. }
  82. },
  83. fail(err) {
  84. // 上传失败
  85. options.onError(err, file, formData)
  86. }
  87. })
  88. currentTask = uploadTask
  89. // 获取当前文件加载的百分比
  90. uploadTask.onProgressUpdate((res) => {
  91. options.onProgress(res, file)
  92. })
  93. // 返回上传任务实例,让外部可以控制上传过程
  94. return uploadTask
  95. }
  96. /**
  97. * 开始上传文件
  98. */
  99. const startUpload = (file: UploadFileItem, options: UseUploadOptions) => {
  100. const {
  101. uploadMethod,
  102. formData = {},
  103. action,
  104. name = 'file',
  105. header = {},
  106. fileType = 'image',
  107. statusCode = 200,
  108. statusKey = 'status',
  109. abortPrevious = false
  110. } = options
  111. // 设置上传中状态
  112. file[statusKey] = UPLOAD_STATUS.LOADING
  113. const uploadOptions = {
  114. action,
  115. header,
  116. name,
  117. fileName: name,
  118. fileType,
  119. statusCode,
  120. abortPrevious,
  121. onSuccess: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
  122. // 更新文件状态
  123. file[statusKey] = UPLOAD_STATUS.SUCCESS
  124. currentTask = null
  125. options.onSuccess?.(res, file, formData)
  126. },
  127. onError: (error: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
  128. // 更新文件状态和错误信息
  129. file[statusKey] = UPLOAD_STATUS.FAIL
  130. file.error = error.errMsg
  131. currentTask = null
  132. options.onError?.(error, file, formData)
  133. },
  134. onProgress: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => {
  135. // 更新上传进度
  136. file.percent = res.progress
  137. options.onProgress?.(res, file)
  138. }
  139. }
  140. // 返回上传任务实例,支持外部获取uploadTask进行操作
  141. if (isFunction(uploadMethod)) {
  142. return uploadMethod(file, formData, uploadOptions)
  143. } else {
  144. return defaultUpload(file, formData, uploadOptions)
  145. }
  146. }
  147. /**
  148. * 格式化图片信息
  149. */
  150. function formatImage(res: UniApp.ChooseImageSuccessCallbackResult): ChooseFile[] {
  151. // #ifdef MP-DINGTALK
  152. // 钉钉文件在files中
  153. res.tempFiles = isDef((res as any).files) ? (res as any).files : res.tempFiles
  154. // #endif
  155. if (isArray(res.tempFiles)) {
  156. return res.tempFiles.map((item: any) => ({
  157. path: item.path || '',
  158. name: item.name || '',
  159. size: item.size,
  160. type: 'image',
  161. thumb: item.path || ''
  162. }))
  163. }
  164. return [
  165. {
  166. path: (res.tempFiles as any).path || '',
  167. name: (res.tempFiles as any).name || '',
  168. size: (res.tempFiles as any).size,
  169. type: 'image',
  170. thumb: (res.tempFiles as any).path || ''
  171. }
  172. ]
  173. }
  174. /**
  175. * 格式化视频信息
  176. */
  177. function formatVideo(res: UniApp.ChooseVideoSuccess): ChooseFile[] {
  178. return [
  179. {
  180. path: res.tempFilePath || (res as any).filePath || '',
  181. name: res.name || '',
  182. size: res.size,
  183. type: 'video',
  184. thumb: (res as any).thumbTempFilePath || '',
  185. duration: res.duration
  186. }
  187. ]
  188. }
  189. /**
  190. * 格式化媒体信息
  191. */
  192. function formatMedia(res: UniApp.ChooseMediaSuccessCallbackResult): ChooseFile[] {
  193. return res.tempFiles.map((item) => ({
  194. type: item.fileType,
  195. path: item.tempFilePath,
  196. thumb: item.fileType === 'video' ? item.thumbTempFilePath : item.tempFilePath,
  197. size: item.size,
  198. duration: item.duration
  199. }))
  200. }
  201. /**
  202. * 选择文件
  203. */
  204. function chooseFile({
  205. multiple,
  206. sizeType,
  207. sourceType,
  208. maxCount,
  209. accept,
  210. compressed,
  211. maxDuration,
  212. camera,
  213. extension
  214. }: ChooseFileOption): Promise<ChooseFile[]> {
  215. return new Promise((resolve, reject) => {
  216. switch (accept) {
  217. case 'image':
  218. // #ifdef MP-WEIXIN
  219. uni.chooseMedia({
  220. count: multiple ? maxCount : 1,
  221. mediaType: ['image'],
  222. sourceType,
  223. sizeType,
  224. camera,
  225. success: (res) => resolve(formatMedia(res)),
  226. fail: reject
  227. })
  228. // #endif
  229. // #ifndef MP-WEIXIN
  230. uni.chooseImage({
  231. count: multiple ? maxCount : 1,
  232. sizeType,
  233. sourceType,
  234. // #ifdef H5
  235. extension,
  236. // #endif
  237. success: (res) => resolve(formatImage(res)),
  238. fail: reject
  239. })
  240. // #endif
  241. break
  242. case 'video':
  243. // #ifdef MP-WEIXIN
  244. uni.chooseMedia({
  245. count: multiple ? maxCount : 1,
  246. mediaType: ['video'],
  247. sourceType,
  248. camera,
  249. maxDuration,
  250. success: (res) => resolve(formatMedia(res)),
  251. fail: reject
  252. })
  253. // #endif
  254. // #ifndef MP-WEIXIN
  255. uni.chooseVideo({
  256. sourceType,
  257. compressed,
  258. maxDuration,
  259. camera,
  260. // #ifdef H5
  261. extension,
  262. // #endif
  263. success: (res) => resolve(formatVideo(res)),
  264. fail: reject
  265. })
  266. // #endif
  267. break
  268. // #ifdef MP-WEIXIN
  269. case 'media':
  270. uni.chooseMedia({
  271. count: multiple ? maxCount : 1,
  272. sourceType,
  273. sizeType,
  274. camera,
  275. maxDuration,
  276. success: (res) => resolve(formatMedia(res)),
  277. fail: reject
  278. })
  279. break
  280. case 'file':
  281. uni.chooseMessageFile({
  282. count: multiple ? (isDef(maxCount) ? maxCount : 100) : 1,
  283. type: accept,
  284. extension,
  285. success: (res) => resolve(res.tempFiles),
  286. fail: reject
  287. })
  288. break
  289. // #endif
  290. case 'all':
  291. // #ifdef H5
  292. uni.chooseFile({
  293. count: multiple ? maxCount : 1,
  294. type: accept,
  295. extension,
  296. success: (res) => resolve(res.tempFiles as ChooseFile[]),
  297. fail: reject
  298. })
  299. // #endif
  300. // #ifdef MP-WEIXIN
  301. uni.chooseMessageFile({
  302. count: multiple ? Number(maxCount) : 1,
  303. type: accept,
  304. extension,
  305. success: (res) => resolve(res.tempFiles),
  306. fail: reject
  307. })
  308. // #endif
  309. break
  310. default:
  311. // #ifdef MP-WEIXIN
  312. uni.chooseMedia({
  313. count: multiple ? maxCount : 1,
  314. mediaType: ['image'],
  315. sourceType,
  316. sizeType,
  317. camera,
  318. success: (res) => resolve(formatMedia(res)),
  319. fail: reject
  320. })
  321. // #endif
  322. // #ifndef MP-WEIXIN
  323. uni.chooseImage({
  324. count: multiple ? maxCount : 1,
  325. sizeType,
  326. sourceType,
  327. // #ifdef H5
  328. extension,
  329. // #endif
  330. success: (res) => resolve(formatImage(res)),
  331. fail: reject
  332. })
  333. // #endif
  334. break
  335. }
  336. })
  337. }
  338. return {
  339. startUpload,
  340. abort,
  341. UPLOAD_STATUS,
  342. chooseFile
  343. }
  344. }