bjui-upload.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. /*!
  2. * B-JUI v1.2 (http://b-jui.com)
  3. * Git@OSC (http://git.oschina.net/xknaan/B-JUI)
  4. * Copyright 2014 K'naan (xknaan@163.com).
  5. * Licensed under Apache (http://www.apache.org/licenses/LICENSE-2.0)
  6. */
  7. /* ========================================================================
  8. * B-JUI: bjui-upload.js v1.2
  9. * @author K'naan (xknaan@163.com)
  10. * -- Modified from Huploadify 2.0 (author:吕大豹)
  11. * http://git.oschina.net/xknaan/B-JUI/blob/master/BJUI/js/bjui-upload.js
  12. * ========================================================================
  13. * Copyright 2014 K'naan.
  14. * Licensed under Apache (http://www.apache.org/licenses/LICENSE-2.0)
  15. * ======================================================================== */
  16. +function ($) {
  17. 'use strict';
  18. // UPLOAD CLASS DEFINITION
  19. // ======================
  20. var Upload = function(element, options) {
  21. this.$element = $(element)
  22. this.options = options
  23. this.tools = this.TOOLS()
  24. }
  25. Upload.DEFAULTS = {
  26. fileTypeExts : '*.jpg;*.png', //允许上传的文件类型,格式'*.jpg;*.doc'
  27. uploader : '', //文件提交的地址
  28. auto : false, //是否开启自动上传
  29. method : 'POST', //发送请求的方式,get或post
  30. multi : false, //是否允许选择多个文件
  31. formData : {}, //发送给服务端的参数,格式:{key1:value1,key2:value2}
  32. fileObjName : 'file', //在后端接受文件的参数名称,如PHP中的$_FILES['file']
  33. fileSizeLimit : 204800, //允许上传的文件大小,单位KB
  34. previewImg : true, //是否预览上传图片
  35. previewLoadimg : null, //预览图片前的载入图标
  36. dragDrop : false, //是否允许拖动上传
  37. showUploadedPercent : true, //是否实时显示上传的百分比,如20%
  38. showUploadedSize : true, //是否实时显示已上传的文件大小,如1M/2M
  39. buttonText : '选择上传文件', //上传按钮上的文字
  40. removeTimeout : 1e3, //上传完成后进度条的消失时间
  41. itemTemplate : FRAG.uploadTemp, //上传队列显示的模板
  42. breakPoints : false, //是否开启断点续传
  43. fileSplitSize : 1024 * 1024, //断点续传的文件块大小,单位Byte,默认1M
  44. onUploadStart : null, //上传开始时的动作
  45. onUploadSuccess : null, //上传成功的动作
  46. onUploadComplete : null, //上传完成的动作
  47. onUploadError : null, //上传失败的动作
  48. onInit : null, //初始化时的动作
  49. onCancel : null, //删除掉某个文件后的回调函数,可传入参数file
  50. onSelect : null
  51. }
  52. Upload.MIMETYPES = {
  53. zip :[ 'application/x-zip-compressed' ],
  54. jpg :[ 'image/jpeg' ],
  55. png :[ 'image/png' ],
  56. gif :[ 'image/gif' ],
  57. swf :[ 'application/x-shockwave-flash' ],
  58. doc :[ 'application/msword' ],
  59. xls :[ 'application/vnd.ms-excel' ],
  60. docx :[ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ],
  61. xlsx :[ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ],
  62. ppt :[ 'application/vnd.ms-powerpoint' ],
  63. pptx :[ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ],
  64. mp3 :[ 'audio/mpeg' ],
  65. mp4 :[ 'video/mp4' ],
  66. pdf :[ 'application/pdf' ],
  67. txt :[ 'text/plain' ],
  68. xml :[ 'text/xml' ]
  69. }
  70. Upload.prototype.TOOLS = function() {
  71. var that = this, options = this.options
  72. var tools = {
  73. //将文件的单位由bytes转换为KB或MB,若第二个参数指定为true,则永远转换为KB
  74. formatFileSize: function(size, byKB) {
  75. if (size > 1024 * 1024 && !byKB)
  76. size = (Math.round(size * 100 / (1024 * 1024)) / 100).toString() + 'MB'
  77. else
  78. size = (Math.round(size * 100 / 1024) / 100).toString() + 'KB'
  79. return size
  80. },
  81. //根据文件序号获取文件
  82. getFile: function(index, files) {
  83. for (var i = 0; i < files.length; i++) {
  84. if (files[i].index == index) return files[i]
  85. }
  86. return false
  87. },
  88. //将输入的文件类型字符串转化为数组,原格式为*.jpg;*.png
  89. getFileTypes: function(str) {
  90. var result = []
  91. var arr1 = str.split(';')
  92. for (var i = 0; i < arr1.length; i++) {
  93. result.push(arr1[i].split('.').pop())
  94. }
  95. return result
  96. },
  97. //根据后缀名获得文件的mime类型
  98. getMimetype: function(name) {
  99. return Upload.MIMETYPES[name]
  100. },
  101. //根据配置的字符串,获得上传组件accept的值
  102. getAcceptString: function(str) {
  103. var types = this.getFileTypes(str)
  104. var result = []
  105. for (var i = 0; i < types.length; i++) {
  106. var mime = this.getMimetype(types[i])
  107. if (mime) result.push(mime)
  108. else result.push('.'+ types[i])
  109. }
  110. return result.join(',')
  111. },
  112. //过滤上传文件
  113. filter: function(files) {
  114. var arr = []
  115. var typeArray = this.getFileTypes(options.fileTypeExts)
  116. if (typeArray.length > 0) {
  117. for (var i = 0; i < files.length; i++) {
  118. var thisFile = files[i]
  119. if (parseInt(this.formatFileSize(thisFile.size, true)) > options.fileSizeLimit) {
  120. that.$element.alertmsg('error', '文件"'+ thisFile.name +'"大小超出限制!')
  121. continue
  122. }
  123. if ($.inArray(thisFile.name.split('.').pop().toLowerCase(), typeArray) >= 0) {
  124. arr.push(thisFile)
  125. } else {
  126. that.$element.alertmsg('error', '文件"'+ thisFile.name +'"类型不允许!')
  127. }
  128. }
  129. }
  130. return arr
  131. },
  132. //获取选择文件,file控件
  133. getFiles: function(e) {
  134. var files = e.target.files || e.dataTransfer.files // 获取文件列表对象
  135. files = this.filter(files)
  136. for (var i = 0; i < files.length; i++) {
  137. files[i].id = files[i].lastModifiedDate.getTime() +'_'+ files[i].size +'_'+ (files[i].type || '').replace(/\W/g, '')
  138. this.renderQueueItem(files[i])
  139. that.queueData.files++
  140. }
  141. return files
  142. },
  143. //生成上传队列Dom
  144. renderQueueItem: function(file) {
  145. var uploadedSize = 0
  146. var $temp = $(options.itemTemplate
  147. .replace('{fileId}', file.id)
  148. .replace('{fileName}', file.name)
  149. .replace('#upConfirm#', BJUI.regional.upload.upConfirm)
  150. .replace('#upPause#', BJUI.regional.upload.upPause)
  151. .replace('#upCancel#', BJUI.regional.upload.upCancel)
  152. .replace('{percent}', '0.00%')
  153. .replace('{uploadedSize}', '0KB')
  154. .replace('{fileSize}', this.formatFileSize(file.size)))
  155. //如果是自动上传,去掉上传按钮
  156. if (options.auto) {
  157. $temp.find('> .info > .up_confirm').remove()
  158. }
  159. $temp.data('upfile', file)
  160. that.$uploadFileList.append($temp)
  161. //如果断点续传
  162. if (options.breakPoints) {
  163. uploadedSize = this.getUploadedSize(file.id)
  164. if (uploadedSize > file.size) uploadedSize = file.size
  165. }
  166. this.showProgress(file.id, uploadedSize, file.size)
  167. //判断是否预览图片
  168. if (options.previewImg && file.type.indexOf('image') != -1) {
  169. var $prevbox = $temp.find('> .preview > .img')
  170. if (options.previewLoadimg) $prevbox.html('<img src="'+ options.previewLoadimg +'" height="114">')
  171. this.previewImg(file, $prevbox)
  172. } else {
  173. $temp.find('> .preview').remove()
  174. }
  175. //判断是否显示已上传文件大小
  176. if (options.showUploadedSize) {
  177. var $fileSize = $temp.find('> .filesize')
  178. $fileSize.find('> .uploadedsize').html(this.formatFileSize(uploadedSize))
  179. $fileSize.find('> .filesize').html(this.formatFileSize(file.size))
  180. } else {
  181. $temp.find('> .filesize').remove()
  182. }
  183. //判断是否显示上传百分比
  184. if (options.showUploadedPercent) {
  185. $temp.find('> .percent').html((uploadedSize / file.size * 100).toFixed(2) +'%')
  186. } else {
  187. $temp.find('> .percent').remove()
  188. }
  189. options.onSelect && options.onSelect(files)
  190. //判断是否是自动上传
  191. if (options.auto) that.fileUpload(file, uploadedSize)
  192. $temp.on('click.bjui.upload.confirm', '.up_confirm', function(e) {
  193. var $this = $(this), $queue = $this.closest('.item')
  194. $this.hide().next().show()
  195. that.fileUpload($queue.data('upfile'), uploadedSize)
  196. })
  197. $temp.on('click.bjui.upload.cancel', '.up_cancel', this.removeQueueItem)
  198. },
  199. successQueueItem: function(file, xhr) {
  200. tools.showProgress(file.id, file.size, file.size)
  201. options.onUploadSuccess && options.onUploadSuccess.toFunc().call(that, file, xhr.responseText, that.$element)
  202. //在指定的间隔时间后删掉进度条
  203. setTimeout(function() {
  204. that.$element.find('#'+ file.id).fadeOut('normal', function() {
  205. $(this).remove()
  206. var filelen = that.$element.find('> .queue > .item:visible').length
  207. if (filelen == 0) {
  208. that.$element.find('> .queue').hide()
  209. that.$file.val('')
  210. }
  211. })
  212. }, options.removeTimeout)
  213. },
  214. removeQueueItem: function(e, xhr) {
  215. if (xhr) xhr.abort()
  216. $(this).closest('.item').fadeOut('normal', function() {
  217. $(this).remove()
  218. var filelen = that.$element.find('> .queue > .item:visible').length
  219. if (filelen == 0) that.$element.find('> .queue').hide()
  220. })
  221. e.preventDefault()
  222. },
  223. //预览上传图片
  224. previewImg: function(file, $obj) {
  225. if (file && $obj.length) {
  226. var reader = new FileReader()
  227. reader.onload = function(e) {
  228. $obj.html('<img src="'+ e.target.result +'">')
  229. }
  230. reader.readAsDataURL(file)
  231. }
  232. },
  233. //上传进度条
  234. showProgress: function(fileId, uploadedSize, fileSize) {
  235. var initWidth = uploadedSize / fileSize * 100 +'%',
  236. initFileSize = that.tools.formatFileSize(uploadedSize),
  237. initUppercent = (uploadedSize / fileSize * 100).toFixed(2) +'%'
  238. that.$element.find('#'+ fileId +' > .progress > .bar').css('width', initWidth)
  239. },
  240. //断点续传:获取已上传的文件片断大小
  241. getUploadedSize: function(fileId) {
  242. return localStorage.getItem(fileId) * 1
  243. },
  244. //断点续传:保存已上传的文件片断大小
  245. saveUploadedSize:function(fileId, value) {
  246. localStorage.setItem(fileId, value)
  247. },
  248. //发送文件块函数
  249. sendBlob: function(url, xhr, file, formdata) {
  250. var fd = new FormData()
  251. xhr.open(options.method, url, true)
  252. xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
  253. //xhr.setRequestHeader('X_Requested_With', location.href.split('/')[5].replace(/[^a-z]+/g, '$'))
  254. fd.append(options.fileObjName, file)
  255. if (formdata) {
  256. for (var key in formdata) {
  257. fd.append(key, formdata[key])
  258. }
  259. }
  260. xhr.send(fd)
  261. }
  262. }
  263. return tools
  264. }
  265. Upload.prototype.init = function() {
  266. var that = this
  267. var $element = this.$element
  268. var options = this.options
  269. if (!(options.uploader)) {
  270. BJUI.debug('Upload Plugin: The options uploader is undefined!')
  271. return
  272. } else {
  273. options.uploader = decodeURI(options.uploader).replacePlh($element.closest('.unitBox'))
  274. if (!options.uploader.isFinishedTm()) {
  275. $element.alertmsg('error', (options.warn || FRAG.alertPlhMsg.replace('#plhmsg#', BJUI.regional.plhmsg)))
  276. BJUI.debug('Upload Plugin: The options uploader is incorrect: '+ options.uploader)
  277. return
  278. }
  279. options.uploader = encodeURI(options.uploader)
  280. }
  281. if ($element.hasClass('bjui-upload')) return
  282. var $uploadFrag = $(FRAG.uploadFrag
  283. .replaceAll('#multi#', options.multi ? 'multiple' : '')
  284. .replaceAll('#accept#', that.tools.getAcceptString(options.fileTypeExts))
  285. .replaceAll('#btnTxt#', (options.icon ? '<i class="fa fa-'+ options.icon +'">&nbsp;&nbsp;' : '') + options.buttonText))
  286. $element
  287. .addClass('bjui-upload')
  288. .append($uploadFrag)
  289. this.$file = $element.find('> .bjui-upload-select-file')
  290. this.$uploadFileList = $element.find('> .queue')
  291. this.queueData = { files:0, success:0, error:0 }
  292. //do select files
  293. $element
  294. .on('change.bjui.upload', '.bjui-upload-select-file', function(e) {
  295. that.fileSelect(e)
  296. })
  297. .on('click.bjui.upload', '.bjui-upload-select', function(e) {
  298. that.$file.trigger('click')
  299. })
  300. options.onInit && options.onInit()
  301. //如果允许拖动上传
  302. if (options.dragDrop) {
  303. /* 拖拽元素在目标元素头上移动的时候 */
  304. $element[0].ondragover = function(ev) {
  305. ev.preventDefault()
  306. return true
  307. }
  308. $element[0].ondrop = function(e) {
  309. that.fileSelect(e)
  310. e.stopPropagation()
  311. e.preventDefault()
  312. }
  313. }
  314. }
  315. Upload.prototype.fileSelect = function(e) {
  316. this.$uploadFileList.show()
  317. this.tools.getFiles(e)
  318. }
  319. Upload.prototype.fileUpload = function(file, uploadedSize) {
  320. var that = this, $element = that.$element, options = that.options, tools = that.tools
  321. var xhr = false, originalFile = file
  322. //校正进度条和上传比例的误差
  323. xhr = new XMLHttpRequest()
  324. if (options.breakPoints) {
  325. //对文件进行切割,并保留原来的信息
  326. file = originalFile.slice(uploadedSize, uploadedSize + options.fileSplitSize)
  327. }
  328. if (xhr.upload) {
  329. // 上传进度
  330. xhr.upload.onprogress = function(e) { that.onProgress(file, e.loaded, originalFile.size) }
  331. // 上传回调
  332. xhr.onreadystatechange = function(e) {
  333. if (xhr.readyState == 4 && xhr.status == 200) {
  334. var returnData = JSON.parse(xhr.responseText), upOver = false
  335. if (options.breakPoints) {
  336. //更新已上传文件大小,保存到本地
  337. uploadedSize += options.fileSplitSize
  338. tools.saveUploadedSize(originalFile.id, uploadedSize)
  339. //继续上传其他片段
  340. if (uploadedSize < originalFile.size) {
  341. file = originalFile.slice(uploadedSize, uploadedSize + options.fileSplitSize)
  342. //上传文件
  343. tools.sendBlob(options.uploader, xhr, file, options.formData)
  344. } else {
  345. upOver = true
  346. }
  347. } else {
  348. upOver = true
  349. }
  350. if (upOver) {
  351. that.queueData.success++
  352. tools.successQueueItem(originalFile, xhr)
  353. options.onUploadComplete && options.onUploadComplete(originalFile, xhr.responseText)
  354. }
  355. } else {
  356. that.queueData.error++
  357. //错误回调
  358. options.onUploadError && options.onUploadError(originalFile, xhr.responseText)
  359. }
  360. //队列完成回调
  361. if (options.onQueueComplete) {
  362. if (that.queueData.files = that.queueData.success + that.queueData.error)
  363. option.onQueueComplete(that.queueData)
  364. }
  365. }
  366. options.onUploadStart && options.onUploadStart()
  367. //开始上传
  368. options.formData.fileName = originalFile.name
  369. options.formData.lastModifiedDate = originalFile.lastModifiedDate.getTime()
  370. tools.sendBlob(options.uploader, xhr, file, options.formData)
  371. }
  372. //暂停事件
  373. $element
  374. .find('#'+ originalFile.id +' > .info > .up_pause')
  375. .on('click.bjui.upload.pause', function(e) {
  376. xhr.abort()
  377. $(this).hide().prev().show()
  378. })
  379. //取消事件
  380. $element
  381. .find('#'+ originalFile.id +' > .info > .up_cancel')
  382. .off('click.bjui.upload.cancel')
  383. .on('click.bjui.upload.cancel', $.proxy(function(e) {
  384. this.tools.removeQueueItem(e, xhr)
  385. }, this))
  386. }
  387. Upload.prototype.onProgress = function(file, loaded, total) {
  388. var that = this, options = that.options
  389. var $progress = that.$element.find('#'+ file.id + ' .progress')
  390. var thisLoaded = loaded
  391. //根据上一次触发progress时上传的大小,得到本次的增量
  392. var lastLoaded = $progress.attr('lastLoaded') || 0
  393. loaded -= parseInt(lastLoaded)
  394. if (loaded > file.size) loaded = file.size
  395. that.$progressBar = $progress.children('.bar')
  396. var oldWidth = options.breakPoints ? parseFloat(that.$progressBar.get(0).style.width || 0) : 0
  397. var percent = (loaded / total * 100 + oldWidth).toFixed(2)
  398. var percentText = percent > 100 ? '100%' : percent + '%'
  399. //校正四舍五入的计算误差
  400. if (options.showUploadedSize) {
  401. var $filesize = $progress.nextAll('.filesize')
  402. $filesize.find('> .uploadedsize').text(that.tools.formatFileSize(loaded))
  403. }
  404. if (options.showUploadedPercent) {
  405. $progress.nextAll('.up_percent').text(percentText)
  406. }
  407. that.$progressBar.css('width', percentText)
  408. //记录本次触发progress时已上传的大小,用来计算下次需增加的数量
  409. if (thisLoaded < options.fileSplitSize) {
  410. $progress.attr('lastLoaded', thisLoaded)
  411. } else {
  412. $progress.removeAttr('lastLoaded')
  413. }
  414. }
  415. Upload.prototype.destroy = function() {
  416. }
  417. // UPLOAD PLUGIN DEFINITION
  418. // =======================
  419. function Plugin(option) {
  420. var args = arguments
  421. var property = option
  422. if (window.FileReader) {
  423. return this.each(function () {
  424. var $this = $(this)
  425. var options = $.extend({}, Upload.DEFAULTS, $this.data(), typeof option == 'object' && option)
  426. var data = $this.data('bjui.upload')
  427. if (!data) $this.data('bjui.upload', (data = new Upload(this, options)))
  428. if (typeof property == 'string' && $.isFunction(data[property])) {
  429. [].shift.apply(args)
  430. if (!args) data[property]()
  431. else data[property].apply(data, args)
  432. } else {
  433. data.init()
  434. }
  435. })
  436. } else { //for IE8-9
  437. this.each(function() {
  438. if (!$.fn.uploadify) return
  439. var options = {
  440. swf : BJUI.PLUGINPATH +'uploadify/scripts/uploadify.swf',
  441. fileTypeExts : '*.jpg;*.png',
  442. id : 'fileInput',
  443. fileObjName : 'file',
  444. fileSizeLimit : 204800,
  445. buttonText : '选择上传文件',
  446. auto : false,
  447. multi : false,
  448. height : 24
  449. }
  450. var $element = $(this), op = $element.data()
  451. if (!op.id) op.id = $element.attr('id')
  452. $.extend(options, op)
  453. if (!(options.uploader)) {
  454. BJUI.debug('Upload Plugin: The options uploader is undefined!')
  455. return
  456. } else {
  457. options.uploader = decodeURI(options.uploader).replacePlh($element.closest('.unitBox'))
  458. if (!options.uploader.isFinishedTm()) {
  459. $element.alertmsg('error', (options.warn || FRAG.alertPlhMsg.replace('#plhmsg#', BJUI.regional.plhmsg)))
  460. BJUI.debug('Upload Plugin: The options uploader is incorrect: '+ options.uploader)
  461. return
  462. }
  463. options.uploader = encodeURI(options.uploader)
  464. }
  465. if (options.id == 'fileInput') options.id = options.id + (new Date().getTime())
  466. var $file = $('<input type="file" name="'+ options.name +'" id="'+ options.id +'">')
  467. if (options.onInit && typeof options.onInit == 'string')
  468. options.onInit = options.onInit.toFunc()
  469. if (options.onCancel && typeof options.onCancel == 'string')
  470. options.onCancel = options.onCancel.toFunc()
  471. if (options.onSelect && typeof options.onSelect == 'string')
  472. options.onSelect = options.onSelect.toFunc()
  473. if (options.onUploadSuccess && typeof options.onUploadSuccess == 'string')
  474. options.onUploadSuccess = options.onUploadSuccess.toFunc()
  475. if (options.onUploadComplete && typeof options.onUploadComplete == 'string')
  476. options.onUploadComplete = options.onUploadComplete.toFunc()
  477. if (options.onUploadError && typeof options.onUploadError == 'string')
  478. options.onUploadError = options.onUploadError.toFunc()
  479. $file.appendTo($element)
  480. if (!options.auto) {
  481. var $upBtn = $('<button class="btn btn-orange" data-icon="cloud-upload">开始上传</button>')
  482. $upBtn
  483. .hide()
  484. .insertAfter($element)
  485. .click(function() {
  486. $file.uploadify('upload', '*');
  487. $(this).hide()
  488. })
  489. options.onSelect = function() {
  490. $upBtn.show()
  491. }
  492. }
  493. $file.uploadify(options)
  494. })
  495. }
  496. }
  497. var old = $.fn.upload
  498. $.fn.upload = Plugin
  499. $.fn.upload.Constructor = Upload
  500. // UPLOAD NO CONFLICT
  501. // =================
  502. $.fn.upload.noConflict = function () {
  503. $.fn.upload = old
  504. return this
  505. }
  506. // UPLOAD DATA-API
  507. // ==============
  508. $(document).on(BJUI.eventType.initUI, function(e) {
  509. var $this = $(e.target).find('[data-toggle="upload"]')
  510. if (!$this.length) return
  511. Plugin.call($this)
  512. })
  513. }(jQuery);