Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

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