upload.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. import Helper from './Upload/Helper'
  2. import Request from './Upload/Request'
  3. import Input from './Upload/Input'
  4. import Status from './Upload/Status'
  5. import AddFile from './Upload/AddFile'
  6. import AddUploadedFile from './Upload/AddUploadedFile'
  7. /**
  8. * WebUploader 上传组件
  9. *
  10. * @see http://fex.baidu.com/webuploader/
  11. */
  12. (function (w, $) {
  13. let Dcat = w.Dcat;
  14. class Uploader {
  15. constructor(options) {
  16. this.options = options = $.extend({
  17. wrapper: '.web-uploader', // 图片显示容器选择器
  18. addFileButton: '.add-file-button', // 继续添加按钮选择器
  19. inputSelector: '',
  20. isImage: false,
  21. preview: [], // 数据预览
  22. server: '',
  23. updateServer: '',
  24. autoUpload: false,
  25. sortable: false,
  26. deleteUrl: '',
  27. deleteData: {},
  28. thumbHeight: 160,
  29. elementName: '',
  30. disabled: false, // 禁止任何上传编辑
  31. autoUpdateColumn: false,
  32. removable: false, // 是否允许直接删除服务器图片
  33. downloadable: false, // 是否允许下载文件
  34. dimensions: {
  35. // width: 100, // 图片宽限制
  36. // height: 100, // 图片高限制
  37. // min_width: 100, //
  38. // min_height: 100,
  39. // max_width: 100,
  40. // max_height: 100,
  41. // ratio: 3/2, // 宽高比
  42. },
  43. lang: {
  44. exceed_size: '文件大小超出',
  45. interrupt: '上传暂停',
  46. upload_failed: '上传失败,请重试',
  47. selected_files: '选中:num个文件,共:size。',
  48. selected_has_failed: '已成功上传:success个文件,:fail个文件上传失败,<a class="retry" href="javascript:"";">重新上传</a>失败文件或<a class="ignore" href="javascript:"";">忽略</a>',
  49. selected_success: '共:num个(:size),已上传:success个。',
  50. dot: ',',
  51. failed_num: '失败:fail个。',
  52. pause_upload: '暂停上传',
  53. go_on_upload: '继续上传',
  54. start_upload: '开始上传',
  55. upload_success_message: '已成功上传:success个文件',
  56. go_on_add: '继续添加',
  57. Q_TYPE_DENIED: '对不起,不允许上传此类型文件',
  58. Q_EXCEED_NUM_LIMIT: '对不起,已超出文件上传数量限制,最多只能上传:num个文件',
  59. F_EXCEED_SIZE: '对不起,当前选择的文件过大',
  60. Q_EXCEED_SIZE_LIMIT: '对不起,已超出文件大小限制',
  61. F_DUPLICATE: '文件重复',
  62. confirm_delete_file: '您确定要删除这个文件吗?',
  63. },
  64. upload: { // web-uploader配置
  65. formData: {
  66. _id: null, // 唯一id
  67. },
  68. thumb: {
  69. width: 160,
  70. height: 160,
  71. quality: 70,
  72. allowMagnify: true,
  73. crop: true,
  74. preserveHeaders: false,
  75. // 为空的话则保留原有图片格式。
  76. // 否则强制转换成指定的类型。
  77. // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可
  78. // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg
  79. type: 'image/jpeg'
  80. },
  81. }
  82. }, options);
  83. let _this = this;
  84. // WebUploader
  85. // @see http://fex.baidu.com/webuploader/
  86. _this.uploader = WebUploader.create(options.upload);
  87. _this.$selector = $(options.selector);
  88. _this.updateColumn = options.upload.formData.upload_column || ('webup' + Dcat.helpers.random());
  89. _this.relation = options.upload.formData._relation; // 一对多关联关系名称
  90. // 帮助函数
  91. let helper = new Helper(this),
  92. // 请求处理
  93. request = new Request(this),
  94. // 状态管理
  95. status = new Status(this),
  96. // 添加文件
  97. addFile = new AddFile(this),
  98. // 添加已上传文件
  99. addUploadedFile = new AddUploadedFile(this),
  100. // 表单
  101. input = new Input(this);
  102. _this.helper = helper;
  103. _this.request = request;
  104. _this.status = status;
  105. _this.addFile = addFile;
  106. _this.addUploadedFile = addUploadedFile;
  107. _this.input = input;
  108. // 翻译
  109. _this.lang = Dcat.Translator(options.lang);
  110. // 所有文件的进度信息,key为file id
  111. _this.percentages = {};
  112. // 临时存储上传失败的文件,key为file id
  113. _this.faildFiles = {};
  114. // 临时存储添加到form表单的文件
  115. _this.formFiles = {};
  116. // 添加的文件数量
  117. _this.fileCount = 0;
  118. // 添加的文件总大小
  119. _this.fileSize = 0;
  120. if (typeof options.upload.formData._id === "undefined" || ! options.upload.formData._id) {
  121. options.upload.formData._id = _this.updateColumn + Dcat.helpers.random();
  122. }
  123. }
  124. // 初始化
  125. build() {
  126. let _this = this,
  127. uploader = _this.uploader,
  128. options = _this.options,
  129. $wrap = _this.$selector.find(options.wrapper),
  130. // 图片容器
  131. $queue = $('<ul class="filelist"></ul>').appendTo($wrap.find('.queueList')),
  132. // 状态栏,包括进度和控制按钮
  133. $statusBar = $wrap.find('.statusBar'),
  134. // 文件总体选择信息。
  135. $info = $statusBar.find('.info'),
  136. // 上传按钮
  137. $upload = $wrap.find('.upload-btn'),
  138. // 没选择文件之前的内容。
  139. $placeholder = $wrap.find('.placeholder'),
  140. $progress = $statusBar.find('.upload-progress').hide();
  141. // jq选择器
  142. _this.$wrapper = $wrap;
  143. _this.$files = $queue;
  144. _this.$statusBar = $statusBar;
  145. _this.$uploadButton = $upload;
  146. _this.$placeholder = $placeholder;
  147. _this.$progress = $progress;
  148. _this.$infoBox = $info;
  149. if (options.upload.fileNumLimit > 1 && ! options.disabled) {
  150. // 添加“添加文件”的按钮,
  151. uploader.addButton({
  152. id: options.addFileButton,
  153. label: '<i class="feather icon-folder"></i> &nbsp;' + _this.lang.trans('go_on_add')
  154. });
  155. }
  156. // 拖拽时不接受 js, txt 文件。
  157. _this.uploader.on('dndAccept', function (items) {
  158. var denied = false,
  159. len = items.length,
  160. i = 0,
  161. // 修改js类型
  162. unAllowed = 'text/plain;application/javascript ';
  163. for (; i < len; i++) {
  164. // 如果在列表里面
  165. if (~unAllowed.indexOf(items[i].type)) {
  166. denied = true;
  167. break;
  168. }
  169. }
  170. return !denied;
  171. });
  172. // 进度条更新
  173. uploader.onUploadProgress = function (file, percentage) {
  174. _this.percentages[file.id][1] = percentage;
  175. _this.status.updateProgress();
  176. };
  177. // uploader.onBeforeFileQueued = function (file) {};
  178. // 添加文件
  179. uploader.onFileQueued = function (file) {
  180. _this.fileCount++;
  181. _this.fileSize += file.size;
  182. if (_this.fileCount === 1) {
  183. // 隐藏 placeholder
  184. $placeholder.addClass('element-invisible');
  185. $statusBar.show();
  186. }
  187. // 添加文件
  188. _this.addFile.render(file);
  189. _this.status.switch('ready');
  190. // 更新进度条
  191. _this.status.updateProgress();
  192. if (!options.disabled && options.autoUpload) {
  193. // 自动上传
  194. uploader.upload()
  195. }
  196. };
  197. // 删除文件事件监听
  198. uploader.onFileDequeued = function (file) {
  199. _this.fileCount--;
  200. _this.fileSize -= file.size;
  201. if (! _this.fileCount && !Dcat.helpers.len(_this.formFiles)) {
  202. _this.status.switch('pending');
  203. }
  204. _this.removeUploadFile(file);
  205. };
  206. uploader.on('all', function (type, obj, reason) {
  207. switch (type) {
  208. case 'uploadFinished':
  209. _this.status.switch('confirm');
  210. // 保存已上传的文件名到服务器
  211. _this.request.update();
  212. break;
  213. case 'startUpload':
  214. _this.status.switch('uploading');
  215. break;
  216. case 'stopUpload':
  217. _this.status.switch('paused');
  218. break;
  219. case 'uploadAccept':
  220. if (_this._uploadAccept(obj, reason) === false) {
  221. return false;
  222. }
  223. break;
  224. }
  225. });
  226. uploader.onError = function (code) {
  227. switch (code) {
  228. case 'Q_TYPE_DENIED':
  229. Dcat.error(_this.lang.trans('Q_TYPE_DENIED'));
  230. break;
  231. case 'Q_EXCEED_NUM_LIMIT':
  232. Dcat.error(_this.lang.trans('Q_EXCEED_NUM_LIMIT', {num: options.upload.fileNumLimit}));
  233. break;
  234. case 'F_EXCEED_SIZE':
  235. Dcat.error(_this.lang.trans('F_EXCEED_SIZE'));
  236. break;
  237. case 'Q_EXCEED_SIZE_LIMIT':
  238. Dcat.error(_this.lang.trans('Q_EXCEED_SIZE_LIMIT'));
  239. break;
  240. case 'F_DUPLICATE':
  241. Dcat.warning(_this.lang.trans('F_DUPLICATE'));
  242. break;
  243. default:
  244. Dcat.error('Error: ' + code);
  245. }
  246. };
  247. // 上传按钮点击
  248. $upload.on('click', function () {
  249. let state = _this.status.state;
  250. if ($(this).hasClass('disabled')) {
  251. return false;
  252. }
  253. if (state === 'ready') {
  254. uploader.upload();
  255. } else if (state === 'paused') {
  256. uploader.upload();
  257. } else if (state === 'uploading') {
  258. uploader.stop();
  259. }
  260. });
  261. // 重试按钮
  262. $info.on('click', '.retry', function () {
  263. uploader.retry();
  264. });
  265. // 忽略按钮
  266. $info.on('click', '.ignore', function () {
  267. for (let i in _this.faildFiles) {
  268. uploader.removeFile(i, true);
  269. delete _this.faildFiles[i];
  270. }
  271. });
  272. // 初始化
  273. _this.status.switch('init');
  274. }
  275. _uploadAccept(obj, reason) {
  276. let _this = this,
  277. options = _this.options;
  278. // 上传失败,返回false
  279. if (! reason || ! reason.status) {
  280. _this.helper.showError(reason);
  281. _this.faildFiles[obj.file.id] = obj.file;
  282. return false;
  283. }
  284. if (reason.data && reason.data.merge) {
  285. // 分片上传
  286. return;
  287. }
  288. // 上传成功,保存新文件名和路径到file对象
  289. obj.file.serverId = reason.data.id;
  290. obj.file.serverName = reason.data.name;
  291. obj.file.serverPath = reason.data.path;
  292. obj.file.serverUrl = reason.data.url || null;
  293. _this.addUploadedFile.add(obj.file);
  294. _this.input.add(reason.data.id);
  295. let $li = _this.getFileView(obj.file.id);
  296. if (! _this.isImage()) {
  297. $li.find('.file-action').hide();
  298. $li.find('[data-file-act="delete"]').show();
  299. }
  300. if (options.sortable) {
  301. $li.find('[data-file-act="order"]').removeClass('d-none').show();
  302. }
  303. if (options.downloadable) {
  304. let $download = $li.find('[data-file-act="download"]');
  305. $download.removeClass('d-none').show();
  306. $download.attr('data-id', obj.file.serverUrl);
  307. }
  308. }
  309. // 预览
  310. preview() {
  311. let _this = this,
  312. options = _this.options,
  313. i;
  314. for (i in options.preview) {
  315. let path = options.preview[i].path, ext;
  316. if (path.indexOf('.')) {
  317. ext = path.split('.').pop();
  318. }
  319. let file = {
  320. serverId: options.preview[i].id,
  321. serverUrl: options.preview[i].url,
  322. serverPath: path,
  323. ext: ext,
  324. fake: 1,
  325. };
  326. _this.status.switch('incrOriginalFileNum');
  327. _this.status.switch('decrFileNumLimit');
  328. // 添加文件到预览区域
  329. _this.addUploadedFile.render(file);
  330. _this.addUploadedFile.add(file);
  331. }
  332. }
  333. // 重新渲染已上传文件
  334. reRenderUploadedFiles() {
  335. let _this = this;
  336. _this.$files.html('');
  337. _this.addUploadedFile.reRender();
  338. }
  339. // 重置按钮位置
  340. refreshButton() {
  341. this.uploader.refresh();
  342. }
  343. // 获取文件视图选择器
  344. getFileViewSelector(fileId) {
  345. return this.options.elementName.replace(/[\[\]]*/g, '_') + '-' + fileId;
  346. }
  347. getFileView(fileId) {
  348. return $('#' + this.getFileViewSelector(fileId));
  349. }
  350. // 负责view的销毁
  351. removeUploadFile(file) {
  352. let _this = this,
  353. $li = _this.getFileView(file.id);
  354. delete _this.percentages[file.id];
  355. _this.status.updateProgress();
  356. $li.off().find('.file-panel').off().end().remove();
  357. }
  358. // 上传字段名称
  359. getColumn() {
  360. return this.updateColumn
  361. }
  362. // 判断是否是图片上传
  363. isImage() {
  364. return this.options.isImage
  365. }
  366. }
  367. Dcat.Uploader = function (options) {
  368. return new Uploader(options)
  369. };
  370. })(window, jQuery);