index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/classNames'
  3. baseComponent({
  4. properties: {
  5. prefixCls: {
  6. type: String,
  7. value: 'wux-upload',
  8. },
  9. max: {
  10. type: Number,
  11. value: -1,
  12. observer: 'updated',
  13. },
  14. count: {
  15. type: Number,
  16. value: 9,
  17. observer: 'updated',
  18. },
  19. defaultFileType: {
  20. type: String,
  21. value: 'image',
  22. },
  23. compressed: {
  24. type: Boolean,
  25. value: true,
  26. },
  27. maxDuration: {
  28. type: Number,
  29. value: 60,
  30. },
  31. camera: {
  32. type: String,
  33. value: 'back',
  34. },
  35. sizeType: {
  36. type: Array,
  37. value: ['original', 'compressed'],
  38. },
  39. sourceType: {
  40. type: Array,
  41. value: ['album', 'camera'],
  42. },
  43. url: {
  44. type: String,
  45. value: '',
  46. },
  47. name: {
  48. type: String,
  49. value: 'file',
  50. },
  51. header: {
  52. type: Object,
  53. value: {},
  54. },
  55. formData: {
  56. type: Object,
  57. value: {},
  58. },
  59. uploaded: {
  60. type: Boolean,
  61. value: true,
  62. },
  63. disabled: {
  64. type: Boolean,
  65. value: false,
  66. },
  67. progress: {
  68. type: Boolean,
  69. value: false,
  70. },
  71. listType: {
  72. type: String,
  73. value: 'text',
  74. },
  75. defaultFileList: {
  76. type: Array,
  77. value: [],
  78. },
  79. fileList: {
  80. type: Array,
  81. value: [],
  82. observer(newVal) {
  83. if (this.data.controlled) {
  84. this.setData({
  85. uploadFileList: newVal,
  86. })
  87. }
  88. },
  89. },
  90. controlled: {
  91. type: Boolean,
  92. value: false,
  93. },
  94. showUploadList: {
  95. type: Boolean,
  96. value: true,
  97. },
  98. showRemoveIcon: {
  99. type: Boolean,
  100. value: true,
  101. },
  102. },
  103. data: {
  104. uploadMax: -1,
  105. uploadCount: 9,
  106. uploadFileList: [],
  107. isVideo: false,
  108. },
  109. computed: {
  110. classes() {
  111. const { prefixCls, disabled, listType } = this.data
  112. const wrap = classNames(prefixCls, {
  113. [`${prefixCls}--${listType}`]: listType,
  114. [`${prefixCls}--disabled`]: disabled,
  115. })
  116. const files = `${prefixCls}__files`
  117. const file = `${prefixCls}__file`
  118. const thumb = `${prefixCls}__thumb`
  119. const remove = `${prefixCls}__remove`
  120. const select = `${prefixCls}__select`
  121. const button = `${prefixCls}__button`
  122. return {
  123. wrap,
  124. files,
  125. file,
  126. thumb,
  127. remove,
  128. select,
  129. button,
  130. }
  131. },
  132. },
  133. methods: {
  134. /**
  135. * 计算最多可以选择的图片张数
  136. */
  137. updated() {
  138. const { count, max } = this.data
  139. const { uploadMax, uploadCount } = this.calcValue(count, max)
  140. // 判断是否需要更新
  141. if (this.data.uploadMax !== uploadMax || this.data.uploadCount !== uploadCount) {
  142. this.setData({
  143. uploadMax,
  144. uploadCount,
  145. })
  146. }
  147. },
  148. /**
  149. * 计算最多可以选择的图片张数
  150. */
  151. calcValue(count, max) {
  152. const realCount = parseInt(count)
  153. const uploadMax = parseInt(max) > -1 ? parseInt(max) : -1
  154. let uploadCount = realCount
  155. // 限制总数时
  156. if (uploadMax !== -1 && uploadMax <= 9 && realCount > uploadMax) {
  157. uploadCount = uploadMax
  158. }
  159. return {
  160. uploadMax,
  161. uploadCount,
  162. }
  163. },
  164. /**
  165. * 从本地相册选择图片或使用相机拍照
  166. */
  167. onSelect() {
  168. const {
  169. uploadCount,
  170. uploadMax,
  171. sizeType,
  172. sourceType,
  173. uploaded,
  174. disabled,
  175. uploadFileList: fileList,
  176. isVideo,
  177. compressed,
  178. maxDuration,
  179. camera,
  180. } = this.data
  181. const { uploadCount: count } = this.calcValue(uploadCount, uploadMax - fileList.length)
  182. const success = (res) => {
  183. res.tempFilePaths = res.tempFilePaths || [res.tempFilePath]
  184. this.tempFilePaths = res.tempFilePaths.map((item) => ({ url: item, uid: this.getUid() }))
  185. this.triggerEvent('before', {...res, fileList })
  186. // 判断是否取消默认的上传行为
  187. if (uploaded) {
  188. this.uploadFile()
  189. }
  190. }
  191. // disabled
  192. if (disabled) return
  193. // choose video
  194. if (isVideo) {
  195. wx.chooseVideo({
  196. sourceType,
  197. compressed,
  198. maxDuration,
  199. camera,
  200. success,
  201. })
  202. return
  203. }
  204. // choose image
  205. wx.chooseImage({
  206. count,
  207. sizeType,
  208. sourceType,
  209. success,
  210. })
  211. },
  212. /**
  213. * 上传文件改变时的回调函数
  214. * @param {Object} info 文件信息
  215. */
  216. onChange(info = {}) {
  217. if (!this.data.controlled) {
  218. this.setData({
  219. uploadFileList: info.fileList,
  220. })
  221. }
  222. this.triggerEvent('change', info)
  223. },
  224. /**
  225. * 开始上传文件的回调函数
  226. * @param {Object} file 文件对象
  227. */
  228. onStart(file) {
  229. const targetItem = {
  230. ...file,
  231. status: 'uploading',
  232. }
  233. this.onChange({
  234. file: targetItem,
  235. fileList: [...this.data.uploadFileList, targetItem],
  236. })
  237. },
  238. /**
  239. * 上传文件成功时的回调函数
  240. * @param {Object} file 文件对象
  241. * @param {Object} res 请求响应对象
  242. */
  243. onSuccess(file, res) {
  244. const fileList = [...this.data.uploadFileList]
  245. const index = fileList.map((item) => item.uid).indexOf(file.uid)
  246. if (index !== -1) {
  247. const targetItem = {
  248. ...file,
  249. status: 'done',
  250. res,
  251. }
  252. const info = {
  253. file: targetItem,
  254. fileList,
  255. }
  256. // replace
  257. fileList.splice(index, 1, targetItem)
  258. this.triggerEvent('success', info)
  259. this.onChange(info)
  260. }
  261. },
  262. /**
  263. * 上传文件失败时的回调函数
  264. * @param {Object} file 文件对象
  265. * @param {Object} res 请求响应对象
  266. */
  267. onFail(file, res) {
  268. const fileList = [...this.data.uploadFileList]
  269. const index = fileList.map((item) => item.uid).indexOf(file.uid)
  270. if (index !== -1) {
  271. const targetItem = {
  272. ...file,
  273. status: 'error',
  274. res,
  275. }
  276. const info = {
  277. file: targetItem,
  278. fileList,
  279. }
  280. // replace
  281. fileList.splice(index, 1, targetItem)
  282. this.triggerEvent('fail', info)
  283. this.onChange(info)
  284. }
  285. },
  286. /**
  287. * 监听上传进度变化的回调函数
  288. * @param {Object} file 文件对象
  289. * @param {Object} res 请求响应对象
  290. */
  291. onProgress(file, res) {
  292. const fileList = [...this.data.uploadFileList]
  293. const index = fileList.map((item) => item.uid).indexOf(file.uid)
  294. if (index !== -1) {
  295. const targetItem = {
  296. ...file,
  297. progress: res.progress,
  298. res,
  299. }
  300. const info = {
  301. file: targetItem,
  302. fileList,
  303. }
  304. // replace
  305. fileList.splice(index, 1, targetItem)
  306. this.triggerEvent('progress', info)
  307. this.onChange(info)
  308. }
  309. },
  310. /**
  311. * 上传文件,支持多图递归上传
  312. */
  313. uploadFile() {
  314. if (!this.tempFilePaths.length) return
  315. const { url, name, header, formData, disabled, progress } = this.data
  316. const file = this.tempFilePaths.shift()
  317. const { uid, url: filePath } = file
  318. if (!url || !filePath || disabled) return
  319. this.onStart(file)
  320. this.uploadTask[uid] = wx.uploadFile({
  321. url,
  322. filePath,
  323. name,
  324. header,
  325. formData,
  326. success: (res) => this.onSuccess(file, res),
  327. fail: (res) => this.onFail(file, res),
  328. complete: (res) => {
  329. delete this.uploadTask[uid]
  330. this.triggerEvent('complete', res)
  331. this.uploadFile()
  332. },
  333. })
  334. // 判断是否监听上传进度变化
  335. if (progress) {
  336. this.uploadTask[uid].onProgressUpdate((res) => this.onProgress(file, res))
  337. }
  338. },
  339. /**
  340. * 点击文件时的回调函数
  341. * @param {Object} e 参数对象
  342. */
  343. onPreview(e) {
  344. this.triggerEvent('preview', {...e.currentTarget.dataset, fileList: this.data.uploadFileList })
  345. },
  346. /**
  347. * 点击删除图标时的回调函数
  348. * @param {Object} e 参数对象
  349. */
  350. onRemove(e) {
  351. const { file } = e.currentTarget.dataset
  352. const fileList = [...this.data.uploadFileList]
  353. const index = fileList.map((item) => item.uid).indexOf(file.uid)
  354. if (index !== -1) {
  355. const targetItem = {
  356. ...file,
  357. status: 'remove',
  358. }
  359. const info = {
  360. file: targetItem,
  361. fileList,
  362. }
  363. // delete
  364. fileList.splice(index, 1)
  365. this.triggerEvent('remove', {...e.currentTarget.dataset, ...info })
  366. this.onChange(info)
  367. }
  368. },
  369. /**
  370. * 中断上传任务
  371. * @param {String} uid 文件唯一标识
  372. */
  373. abort(uid) {
  374. const { uploadTask } = this
  375. if (uid) {
  376. if (uploadTask[uid]) {
  377. uploadTask[uid].abort()
  378. delete uploadTask[uid]
  379. }
  380. } else {
  381. Object.keys(uploadTask).forEach((uid) => {
  382. if (uploadTask[uid]) {
  383. uploadTask[uid].abort()
  384. delete uploadTask[uid]
  385. }
  386. })
  387. }
  388. },
  389. },
  390. /**
  391. * 组件生命周期函数,在组件实例进入页面节点树时执行
  392. */
  393. created() {
  394. this.index = 0
  395. this.createdAt = Date.now()
  396. this.getUid = () => `wux-upload--${this.createdAt}-${++this.index}`
  397. this.uploadTask = {}
  398. this.tempFilePaths = []
  399. },
  400. /**
  401. * 组件生命周期函数,在组件实例进入页面节点树时执
  402. */
  403. attached() {
  404. const { defaultFileType, defaultFileList, fileList, controlled } = this.data
  405. const uploadFileList = controlled ? fileList : defaultFileList
  406. const isVideo = defaultFileType === 'video'
  407. this.setData({ uploadFileList, isVideo })
  408. },
  409. /**
  410. * 组件生命周期函数,在组件实例被从页面节点树移除时执行
  411. */
  412. detached() {
  413. this.abort()
  414. },
  415. })