upload.js 15 KB

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