require-upload.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. define(['jquery', 'bootstrap', 'dropzone', 'template'], function ($, undefined, Dropzone, Template) {
  2. var Upload = {
  3. list: {},
  4. options: {},
  5. config: {
  6. container: document.body,
  7. classname: '.plupload:not([initialized]),.faupload:not([initialized])',
  8. previewtpl: '<li class="col-xs-3"><a href="<%=fullurl%>" data-url="<%=url%>" target="_blank" class="thumbnail"><img src="<%=fullurl%>" onerror="this.src=\'' + Fast.api.fixurl("ajax/icon") + '?suffix=<%=suffix%>\';this.onerror=null;" class="img-responsive"></a><a href="javascript:;" class="btn btn-danger btn-xs btn-trash"><i class="fa fa-trash"></i></a></li>',
  9. },
  10. events: {
  11. //初始化
  12. onInit: function () {
  13. },
  14. //上传成功的回调
  15. onUploadSuccess: function (up, ret, file) {
  16. var button = up.element;
  17. var onUploadSuccess = up.options.onUploadSuccess;
  18. var data = typeof ret.data !== 'undefined' ? ret.data : null;
  19. //上传成功后回调
  20. if (button) {
  21. //如果有文本框则填充
  22. var input_id = $(button).data("input-id") ? $(button).data("input-id") : "";
  23. if (input_id) {
  24. var urlArr = [];
  25. var inputObj = $("#" + input_id);
  26. if ($(button).data("multiple") && inputObj.val() !== "") {
  27. urlArr.push(inputObj.val());
  28. }
  29. var url = Config.upload.fullmode ? (data.fullurl ? data.fullurl : Fast.api.cdnurl(data.url)) : data.url;
  30. urlArr.push(url);
  31. inputObj.val(urlArr.join(",")).trigger("change").trigger("validate");
  32. }
  33. //如果有回调函数
  34. var onDomUploadSuccess = $(button).data("upload-success");
  35. if (onDomUploadSuccess) {
  36. if (typeof onDomUploadSuccess !== 'function' && typeof Upload.api.custom[onDomUploadSuccess] === 'function') {
  37. onDomUploadSuccess = Upload.api.custom[onDomUploadSuccess];
  38. }
  39. if (typeof onDomUploadSuccess === 'function') {
  40. var result = onDomUploadSuccess.call(button, data, ret, up, file);
  41. if (result === false)
  42. return;
  43. }
  44. }
  45. }
  46. if (typeof onUploadSuccess === 'function') {
  47. var result = onUploadSuccess.call(button, data, ret, up, file);
  48. if (result === false)
  49. return;
  50. }
  51. },
  52. //上传错误的回调
  53. onUploadError: function (up, ret, file) {
  54. var button = up.element;
  55. var onUploadError = up.options.onUploadError;
  56. var data = typeof ret.data !== 'undefined' ? ret.data : null;
  57. if (button) {
  58. var onDomUploadError = $(button).data("upload-error");
  59. if (onDomUploadError) {
  60. if (typeof onDomUploadError !== 'function' && typeof Upload.api.custom[onDomUploadError] === 'function') {
  61. onDomUploadError = Upload.api.custom[onDomUploadError];
  62. }
  63. if (typeof onDomUploadError === 'function') {
  64. var result = onDomUploadError.call(button, data, ret, up, file);
  65. if (result === false)
  66. return;
  67. }
  68. }
  69. }
  70. if (typeof onUploadError === 'function') {
  71. var result = onUploadError.call(button, data, ret, up, file);
  72. if (result === false) {
  73. return;
  74. }
  75. }
  76. Toastr.error(ret.msg.toString().replace(/(<([^>]+)>)/gi, "") + "(code:" + ret.code + ")");
  77. },
  78. //服务器响应数据后
  79. onUploadResponse: function (response, up, file) {
  80. try {
  81. var ret = typeof response === 'object' ? response : JSON.parse(response);
  82. if (!ret.hasOwnProperty('code')) {
  83. $.extend(ret, {code: -2, msg: response, data: null});
  84. }
  85. } catch (e) {
  86. var ret = {code: -1, msg: e.message, data: null};
  87. }
  88. return ret;
  89. },
  90. //上传全部结束后
  91. onUploadComplete: function (up, files) {
  92. var button = up.element;
  93. var onUploadComplete = up.options.onUploadComplete;
  94. if (button) {
  95. var onDomUploadComplete = $(button).data("upload-complete");
  96. if (onDomUploadComplete) {
  97. if (typeof onDomUploadComplete !== 'function' && typeof Upload.api.custom[onDomUploadComplete] === 'function') {
  98. onDomUploadComplete = Upload.api.custom[onDomUploadComplete];
  99. }
  100. if (typeof onDomUploadComplete === 'function') {
  101. var result = onDomUploadComplete.call(button, files);
  102. if (result === false)
  103. return;
  104. }
  105. }
  106. }
  107. if (typeof onUploadComplete === 'function') {
  108. var result = onUploadComplete.call(button, files);
  109. if (result === false) {
  110. return;
  111. }
  112. }
  113. }
  114. },
  115. api: {
  116. //上传接口
  117. upload: function (element, onUploadSuccess, onUploadError, onUploadComplete) {
  118. element = typeof element === 'undefined' ? Upload.config.classname : element;
  119. $(element, Upload.config.container).each(function () {
  120. if ($(this).attr("initialized")) {
  121. return true;
  122. }
  123. $(this).attr("initialized", true);
  124. var that = this;
  125. var id = $(this).prop("id") || $(this).prop("name") || Dropzone.uuidv4();
  126. var url = $(this).data("url");
  127. var maxsize = $(this).data("maxsize");
  128. var maxcount = $(this).data("maxcount");
  129. var mimetype = $(this).data("mimetype");
  130. var multipart = $(this).data("multipart");
  131. var multiple = $(this).data("multiple");
  132. //填充ID
  133. var input_id = $(that).data("input-id") ? $(that).data("input-id") : "";
  134. //预览ID
  135. var preview_id = $(that).data("preview-id") ? $(that).data("preview-id") : "";
  136. //上传URL
  137. url = url ? url : Config.upload.uploadurl;
  138. url = Fast.api.fixurl(url);
  139. var chunking = false, chunkSize = Config.upload.chunksize || 2097152, timeout = Config.upload.timeout || 600000;
  140. //最大可上传文件大小
  141. maxsize = typeof maxsize !== "undefined" ? maxsize : Config.upload.maxsize;
  142. //文件类型
  143. mimetype = typeof mimetype !== "undefined" ? mimetype : Config.upload.mimetype;
  144. //请求的表单参数
  145. multipart = typeof multipart !== "undefined" ? multipart : Config.upload.multipart;
  146. //是否支持批量上传
  147. multiple = typeof multiple !== "undefined" ? multiple : Config.upload.multiple;
  148. //后缀特殊处理
  149. mimetype = mimetype.split(",").map(function (k) {
  150. return k.indexOf("/") > -1 ? k : (!k || k === "*" || k.charAt(0) === "." ? k : "." + k);
  151. }).join(",");
  152. mimetype = mimetype === '*' ? null : mimetype;
  153. //最大文件限制转换成mb
  154. var maxFilesize = (function (maxsize) {
  155. var matches = maxsize.toString().match(/^([0-9\.]+)(\w+)$/);
  156. var size = matches ? parseFloat(matches[1]) : parseFloat(maxsize),
  157. unit = matches ? matches[2].toLowerCase() : 'b';
  158. var unitDict = {'b': 0, 'k': 1, 'kb': 1, 'm': 2, 'mb': 2, 'gb': 3, 'g': 3, 'tb': 4, 't': 4};
  159. var y = typeof unitDict[unit] !== 'undefined' ? unitDict[unit] : 0;
  160. var bytes = size * Math.pow(1024, y);
  161. return bytes / Math.pow(1024, 2);
  162. }(maxsize));
  163. var options = $(this).data() || {};
  164. options = $.extend(true, {}, options, $(this).data("upload-options") || {});
  165. delete options.success;
  166. delete options.url;
  167. multipart = $.isArray(multipart) ? {} : multipart;
  168. var params = $(this).data("params") || {};
  169. var category = typeof params.category !== 'undefined' ? params.category : ($(this).data("category") || '');
  170. if (category) {
  171. // multipart.category = category;
  172. }
  173. Upload.list[id] = new Dropzone(this, $.extend({
  174. url: url,
  175. params: function (files, xhr, chunk) {
  176. var params = multipart;
  177. if (chunk) {
  178. return $.extend({}, params, {
  179. filesize: chunk.file.size,
  180. filename: chunk.file.name,
  181. chunkid: chunk.file.upload.uuid,
  182. chunkindex: chunk.index,
  183. chunkcount: chunk.file.upload.totalChunkCount,
  184. chunksize: this.options.chunkSize,
  185. chunkfilesize: chunk.dataBlock.data.size,
  186. width: chunk.file.width || 0,
  187. height: chunk.file.height || 0,
  188. type: chunk.file.type,
  189. });
  190. }
  191. return params;
  192. },
  193. chunking: chunking,
  194. chunkSize: chunkSize,
  195. maxFilesize: maxFilesize,
  196. acceptedFiles: mimetype,
  197. maxFiles: (maxcount && parseInt(maxcount) > 1 ? maxcount : (multiple ? null : 1)),
  198. timeout: timeout,
  199. parallelUploads: 1,
  200. previewsContainer: false,
  201. dictDefaultMessage: __("Drop files here to upload"),
  202. dictFallbackMessage: __("Your browser does not support drag'n'drop file uploads"),
  203. dictFallbackText: __("Please use the fallback form below to upload your files like in the olden days"),
  204. dictFileTooBig: __("File is too big (%sMiB), Max filesize: %sMiB", "{{filesize}}", "{{maxFilesize}}"),
  205. dictInvalidFileType: __("You can't upload files of this type"),
  206. dictResponseError: __("Server responded with %s code.", "{{statusCode}}"),
  207. dictCancelUpload: __("Cancel upload"),
  208. dictUploadCanceled: __("Upload canceled"),
  209. dictCancelUploadConfirmation: __("Are you sure you want to cancel this upload?"),
  210. dictRemoveFile: __("Remove file"),
  211. dictMaxFilesExceeded: __("You can only upload a maximum of %s files", "{{maxFiles}}"),
  212. init: function () {
  213. Upload.events.onInit.call(this);
  214. //必须添加dz-message,否则点击icon无法唤起上传窗口
  215. $(">i", this.element).addClass("dz-message");
  216. this.options.elementHtml = $(this.element).html();
  217. },
  218. sending: function (file, xhr, formData) {
  219. if (typeof file.category !== 'undefined') {
  220. formData.append('category', file.category);
  221. }
  222. },
  223. addedfile: function (file) {
  224. var params = $(this.element).data("params") || {};
  225. var category = typeof params.category !== 'undefined' ? params.category : ($(this.element).data("category") || '');
  226. file.category = typeof category === 'function' ? category.call(this, file) : category;
  227. },
  228. addedfiles: function (files) {
  229. if (this.options.maxFiles && (!this.options.maxFiles || this.options.maxFiles > 1) && this.options.inputId) {
  230. var inputObj = $("#" + this.options.inputId);
  231. if (inputObj.length > 0) {
  232. var value = $.trim(inputObj.val());
  233. var nums = value === '' ? 0 : value.split(/\,/).length;
  234. var remain = this.options.maxFiles - nums;
  235. if (remain === 0 || files.length > remain) {
  236. files = Array.prototype.slice.call(files, remain);
  237. for (var i = 0; i < files.length; i++) {
  238. this.removeFile(files[i]);
  239. }
  240. Toastr.error(__("You can only upload a maximum of %s files", this.options.maxFiles));
  241. }
  242. }
  243. }
  244. },
  245. success: function (file, response) {
  246. var ret = Upload.events.onUploadResponse(response, this, file);
  247. file.ret = ret;
  248. if (ret.code === 1) {
  249. Upload.events.onUploadSuccess(this, ret, file);
  250. } else {
  251. Upload.events.onUploadError(this, ret, file);
  252. }
  253. },
  254. error: function (file, response, xhr) {
  255. var responseObj = $("<div>" + (xhr && typeof xhr.responseText !== 'undefined' ? xhr.responseText : response) + "</div>");
  256. responseObj.find("style, title, script").remove();
  257. var msg = responseObj.text() || __('Network error');
  258. var ret = {code: 0, data: null, msg: msg};
  259. Upload.events.onUploadError(this, ret, file);
  260. },
  261. uploadprogress: function (file, progress, bytesSent) {
  262. if (file.upload.chunked) {
  263. $(this.element).prop("disabled", true).html("<i class='fa fa-upload'></i> " + __('Upload') + Math.floor((file.upload.bytesSent / file.size) * 100) + "%");
  264. }
  265. },
  266. totaluploadprogress: function (progress, bytesSent) {
  267. if (this.getActiveFiles().length > 0 && !this.options.chunking) {
  268. $(this.element).prop("disabled", true).html("<i class='fa fa-upload'></i> " + __('Upload') + Math.floor(progress) + "%");
  269. }
  270. },
  271. queuecomplete: function () {
  272. Upload.events.onUploadComplete(this, this.files);
  273. this.removeAllFiles(true);
  274. $(this.element).prop("disabled", false).html(this.options.elementHtml);
  275. },
  276. chunkSuccess: function (chunk, file, response) {
  277. },
  278. chunksUploaded: function (file, done) {
  279. var that = this;
  280. Fast.api.ajax({
  281. url: this.options.url,
  282. data: $.extend({}, multipart, {
  283. action: 'merge',
  284. filesize: file.size,
  285. filename: file.name,
  286. chunkid: file.upload.uuid,
  287. chunkcount: file.upload.totalChunkCount,
  288. })
  289. }, function (data, ret) {
  290. done(JSON.stringify(ret));
  291. return false;
  292. }, function (data, ret) {
  293. file.accepted = false;
  294. that._errorProcessing([file], ret.msg);
  295. });
  296. },
  297. onUploadSuccess: onUploadSuccess,
  298. onUploadError: onUploadError,
  299. onUploadComplete: onUploadComplete,
  300. }, Upload.options, options));
  301. //拖动排序
  302. if (preview_id && multiple) {
  303. require(['dragsort'], function () {
  304. $("#" + preview_id).dragsort({
  305. dragSelector: "li a:not(.btn-trash)",
  306. dragEnd: function () {
  307. $("#" + preview_id).trigger("fa.preview.change");
  308. },
  309. placeHolderTemplate: '<li class="col-xs-3"></li>'
  310. });
  311. });
  312. }
  313. //刷新隐藏textarea的值
  314. var refresh = function (name) {
  315. var data = {};
  316. var textarea = $("textarea[name='" + name + "']");
  317. var container = textarea.prev("ul");
  318. $.each($("input,select,textarea", container).serializeArray(), function (i, j) {
  319. var reg = /\[?(\w+)\]?\[(\w+)\]$/g;
  320. var match = reg.exec(j.name);
  321. if (!match)
  322. return true;
  323. if (!isNaN(match[2])) {
  324. data[i] = j.value;
  325. } else {
  326. match[1] = "x" + parseInt(match[1]);
  327. if (typeof data[match[1]] === 'undefined') {
  328. data[match[1]] = {};
  329. }
  330. data[match[1]][match[2]] = j.value;
  331. }
  332. });
  333. var result = [];
  334. $.each(data, function (i, j) {
  335. result.push(j);
  336. });
  337. textarea.val(JSON.stringify(result));
  338. };
  339. if (preview_id && input_id) {
  340. $(document.body).on("keyup change", "#" + input_id, function (e) {
  341. var inputStr = $("#" + input_id).val();
  342. var inputArr = inputStr.split(/\,/);
  343. var previewObj = $("#" + preview_id);
  344. previewObj.empty();
  345. var tpl = previewObj.data("template") ? previewObj.data("template") : "";
  346. var extend = previewObj.next().is("textarea") ? previewObj.next("textarea").val() : "{}";
  347. var json = {};
  348. try {
  349. json = JSON.parse(extend);
  350. } catch (e) {
  351. }
  352. $.each(inputArr, function (i, j) {
  353. if (!j) {
  354. return true;
  355. }
  356. var suffix = /[\.]?([a-zA-Z0-9]+)$/.exec(j);
  357. suffix = suffix ? suffix[1] : 'file';
  358. var btnData = $(that).data();
  359. var fullurl = typeof btnData.cdnurl!=='undefined' ? Fast.api.cdnurl(j, btnData.cdnurl) : Fast.api.cdnurl(j);
  360. j = Config.upload.fullmode ? fullurl : j;
  361. var value = (json && typeof json[i] !== 'undefined' ? json[i] : null);
  362. var data = {url: j, fullurl: fullurl, data: btnData, key: i, index: i, value: value, row: value, suffix: suffix};
  363. var html = tpl ? Template(tpl, data) : Template.render(Upload.config.previewtpl, data);
  364. previewObj.append(html);
  365. });
  366. refresh(previewObj.data("name"));
  367. });
  368. $("#" + input_id).trigger("change");
  369. }
  370. if (preview_id) {
  371. //监听文本框改变事件
  372. $("#" + preview_id).on('change keyup', "input,textarea,select", function () {
  373. refresh($(this).closest("ul").data("name"));
  374. });
  375. // 监听事件
  376. $(document.body).on("fa.preview.change", "#" + preview_id, function () {
  377. var urlArr = [];
  378. $("#" + preview_id + " [data-url]").each(function (i, j) {
  379. urlArr.push($(this).data("url"));
  380. });
  381. if (input_id) {
  382. $("#" + input_id).val(urlArr.join(","));
  383. }
  384. refresh($("#" + preview_id).data("name"));
  385. });
  386. // 移除按钮事件
  387. $(document.body).on("click", "#" + preview_id + " .btn-trash", function () {
  388. $(this).closest("li").remove();
  389. $("#" + preview_id).trigger("fa.preview.change");
  390. });
  391. }
  392. if (input_id) {
  393. $("#" + input_id).closest("form").on("reset", function () {
  394. setTimeout($.proxy(function () {
  395. $("#" + input_id, this).trigger("change");
  396. }, this), 0);
  397. });
  398. //粘贴上传、拖拽上传
  399. $("body").on('paste drop', "#" + input_id, function (event) {
  400. var originEvent = event.originalEvent;
  401. var button = $(".plupload[data-input-id='" + $(this).attr("id") + "'],.faupload[data-input-id='" + $(this).attr("id") + "']");
  402. if (event.type === 'paste' && originEvent.clipboardData && originEvent.clipboardData.items) {
  403. var items = originEvent.clipboardData.items;
  404. if ((items.length === 1 && items[0].type.indexOf("text") > -1) || (items.length === 2 && items[1].type.indexOf("text") > -1)) {
  405. } else {
  406. Upload.list[button.attr("id")].paste(originEvent);
  407. return false;
  408. }
  409. }
  410. if (event.type === 'drop' && originEvent.dataTransfer && originEvent.dataTransfer.files) {
  411. Upload.list[button.attr("id")].drop(originEvent);
  412. return false;
  413. }
  414. });
  415. }
  416. });
  417. },
  418. /**
  419. * @deprecated Use upload instead.
  420. */
  421. plupload: function (element, onUploadSuccess, onUploadError, onUploadComplete) {
  422. return Upload.api.upload(element, onUploadSuccess, onUploadError, onUploadComplete);
  423. },
  424. /**
  425. * @deprecated Use upload instead.
  426. */
  427. faupload: function (element, onUploadSuccess, onUploadError, onUploadComplete) {
  428. return Upload.api.upload(element, onUploadSuccess, onUploadError, onUploadComplete);
  429. },
  430. // AJAX异步上传
  431. send: function (file, onUploadSuccess, onUploadError, onUploadComplete) {
  432. var index = Layer.msg(__('Uploading'), {offset: 't', time: 0});
  433. var id = "dropzone-" + Dropzone.uuidv4();
  434. $('<button type="button" id="' + id + '" class="btn btn-danger hidden faupload" />').appendTo("body");
  435. $("#" + id).data("upload-complete", function (files) {
  436. Layer.close(index);
  437. Upload.list[id].removeAllFiles(true);
  438. });
  439. Upload.api.upload("#" + id, onUploadSuccess, onUploadError, onUploadComplete);
  440. setTimeout(function () {
  441. Upload.list[id].addFile(file);
  442. }, 1);
  443. },
  444. custom: {
  445. //自定义上传完成回调
  446. afteruploadcallback: function (response) {
  447. console.log(this, response);
  448. alert("Custom Callback,Response URL:" + response.url);
  449. },
  450. }
  451. }
  452. }
  453. ;
  454. return Upload;
  455. });