(function (w, $) {
function Uploader(opts) {
opts = $.extend({
wrapper: '.web-uploader', // 图片显示容器选择器
addFileButton: '.add-file-button', // 继续添加按钮选择器
isImage: false,
preview: [], // 数据预览
server: '',
updateServer: '',
deleteUrl: '',
deleteData: {},
thumbHeight: 160,
disabled: false, // 禁止任何上传编辑
autoUpdateColumn: false,
disableRemove: false, // 禁止删除图片,允许替换
dimensions: {
// width: 100, // 图片宽限制
// height: 100, // 图片高限制
// min_width: 100, //
// min_height: 100,
// max_width: 100,
// max_height: 100,
// ratio: 3/2, // 宽高比
},
lang: {
exceed_size: '文件大小超出',
interrupt: '上传暂停',
upload_failed: '上传失败,请重试',
selected_files: '选中:num个文件,共:size。',
selected_has_failed: '已成功上传:success个文件,:fail个文件上传失败,重新上传失败文件或忽略',
selected_success: '共:num个(:size),已上传:success个。',
dot: ',',
failed_num: '失败:fail个。',
pause_upload: '暂停上传',
go_on_upload: '继续上传',
start_upload: '开始上传',
upload_success_message: '已成功上传:success个文件',
go_on_add: '继续添加',
Q_TYPE_DENIED: '对不起,不允许上传此类型文件',
Q_EXCEED_NUM_LIMIT: '对不起,已超出文件上传数量限制,最多只能上传:num个文件',
F_EXCEED_SIZE: '对不起,当前选择的文件过大',
Q_EXCEED_SIZE_LIMIT: '对不起,已超出文件大小限制',
F_DUPLICATE: '文件重复',
},
upload: { // web-uploader配置
formData: {
_id: null, // 唯一id
},
thumb: {
width: 160,
height: 160,
quality: 70,
allowMagnify: true,
crop: true,
preserveHeaders: false,
// 为空的话则保留原有图片格式。
// 否则强制转换成指定的类型。
// IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可
// 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg
type: 'image/jpeg'
},
}
}, opts);
var $selector = $(opts.selector),
updateColumn = opts.upload.formData.upload_column || ('webup' + Math.floor(Math.random()*10000)),
elementName = opts.elementName;
if (typeof opts.upload.formData._id == "undefined" || !opts.upload.formData._id) {
opts.upload.formData._id = updateColumn + Math.floor(Math.random()*10000);
}
let Dcat = w.Dcat,
$wrap,
// 展示图片
showImg = opts.isImage,
// 图片容器
$queue,
// 状态栏,包括进度和控制按钮
$statusBar,
// 文件总体选择信息。
$info,
// 上传按钮
$upload,
// 没选择文件之前的内容。
$placeHolder,
$progress,
// 已上传文件数量
originalFilesNum = Dcat.helpers.len(opts.preview),
// 上传表单
$input = $selector.find('input[name="' + elementName + '"]'),
// 获取文件视图选择器
getFileViewSelector = function (fileId) {
return elementName.replace(/[\[\]]*/g, '_')+'-'+fileId;
},
getFileView = function (fileId) {
return $('#' + getFileViewSelector(fileId));
},
// 继续添加按钮选择器
addFileButtonSelector = opts.addFileButton,
// 临时存储上传失败的文件,key为file id
faildFiles = {},
// 临时存储添加到form表单的文件
formFiles = {},
// 添加的文件数量
fileCount = 0,
// 添加的文件总大小
fileSize = 0,
// 可能有pedding, ready, uploading, confirm, done.
state = 'pedding',
// 所有文件的进度信息,key为file id
percentages = {},
// 判断浏览器是否支持图片的base64
isSupportBase64 = (function () {
var data = new Image();
var support = true;
data.onload = data.onerror = function () {
if (this.width != 1 || this.height != 1) {
support = false;
}
};
data.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
return support;
})(),
// 检测是否已经安装flash,检测flash的版本
flashVersion = (function () {
var version;
try {
version = navigator.plugins['Shockwave Flash'];
version = version.description;
} catch (ex) {
try {
version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
.GetVariable('$version');
} catch (ex2) {
version = '0.0';
}
}
version = version.match(/\d+/g);
return parseFloat(version[0] + '.' + version[1], 10);
})(),
// 判断是否是图片
isImage = function (file) {
return file.type.match(/^image/);
},
// 翻译
lang = Dcat.Translator(opts.lang),
__ = lang.trans.bind(lang),
// WebUploader实例
uploader;
// 当有文件添加进来时执行,负责view的创建
function addFile(file) {
var size = WebUploader.formatSize(file.size), $li, $btns;
if (showImg) {
$li = $(`
${(file.ext.toUpperCase() || 'FILE')}
${file.name}
(${size})
`);
$btns = $(``).appendTo($li);
} else {
$li = $(`
${file.name} (${size})
`);
$btns = $(`
`).appendTo($li);
}
var $wrap = $li.find('p.imgWrap'),
$info = $(''),
showError = function (code, file) {
var text = '';
switch (code) {
case 'exceed_size':
text = __('exceed_size');
break;
case 'interrupt':
text = __('interrupt');
break;
default:
text = __('upload_failed');
break;
}
faildFiles[file.id] = file;
$info.text(text).appendTo($li);
};
$li.appendTo($queue);
setTimeout(function () {
$li.css({margin: '5px'});
}, 50);
if (file.getStatus() === 'invalid') {
showError(file.statusText, file);
} else {
if (showImg) {
var image = uploader.makeThumb(file, function (error, src) {
var img;
$wrap.empty();
if (error) {
$li.find('.title').show();
$li.find('.file-type').show();
return;
}
if (isSupportBase64) {
img = $('
');
$wrap.append(img);
} else {
$li.find('.file-type').show();
}
});
try {
image.once('load', function () {
file._info = file._info || image.info();
file._meta = file._meta || image.meta();
var width = file._info.width,
height = file._info.height;
if (!validateDimensions(file)) {
Dcat.error('The image dimensions is invalid.');
uploader.removeFile(file);
return false;
}
image.resize(width, height);
});
} catch (e) {
// 不是图片
return setTimeout(function () {
uploader.removeFile(file);
}, 10);
}
}
percentages[file.id] = [file.size, 0];
file.rotation = 0;
}
file.on('statuschange', function (cur, prev) {
if (prev === 'progress') {
// $prgress.hide().width(0);
} else if (prev === 'queued') {
$btns.find('[data-file-act="cancel"]').hide();
$btns.find('[data-file-act="delete"]').show();
}
// 成功
if (cur === 'error' || cur === 'invalid') {
showError(file.statusText, file);
percentages[file.id][1] = 1;
} else if (cur === 'interrupt') {
showError('interrupt', file);
} else if (cur === 'queued') {
percentages[file.id][1] = 0;
} else if (cur === 'progress') {
$info.remove();
// $prgress.css('display', 'block');
} else if (cur === 'complete') {
if (showImg) {
$li.append('');
} else {
$li.find('._success').show();
}
}
$li.removeClass('state-' + prev).addClass('state-' + cur);
});
var $act = showImg ? $btns.find('a') : $btns;
$act.on('click', function () {
var index = $(this).data('file-act');
switch (index) {
case 'cancel':
uploader.removeFile(file);
return;
case 'deleteurl':
case 'delete':
if (opts.disableRemove) {
return uploader.removeFile(file);
}
var post = opts.deleteData;
post.key = file.serverId;
if (!post.key) {
return uploader.removeFile(file);
}
post._column = updateColumn;
Dcat.loading();
$.post(opts.deleteUrl, post, function (result) {
Dcat.loading(false);
if (result.status) {
deleteInput(file.serverId);
uploader.removeFile(file);
return;
}
Dcat.error(result.message || 'Remove file failed.');
});
break;
case 'preview':
Dcat.helpers.previewImage($wrap.find('img').attr('src'), null, file.name);
break;
}
});
}
// 图片宽高验证
function validateDimensions(file) {
// The image dimensions is invalid.
if (!showImg || !isImage(file) || !Dcat.helpers.len(opts.dimensions)) return true;
var dimensions = opts.dimensions,
width = file._info.width,
height = file._info.height,
isset = Dcat.helpers.isset;
if (
(isset(dimensions, 'width') && dimensions['width'] != width) ||
(isset(dimensions, 'min_width') && dimensions['min_width'] > width)||
(isset(dimensions, 'max_width') && dimensions['max_width'] < width) ||
(isset(dimensions, 'height') && dimensions['height'] != height) ||
(isset(dimensions, 'min_height') && dimensions['min_height'] > height) ||
(isset(dimensions, 'max_height') && dimensions['max_height'] < height) ||
(isset(dimensions, 'ratio') && dimensions['ratio'] != (width / height))
) {
return false;
}
return true;
}
// 负责view的销毁
function removeUploadFile(file) {
var $li = getFileView(file.id);
delete percentages[file.id];
updateTotalProgress();
$li.off().find('.file-panel').off().end().remove();
}
function updateTotalProgress() {
var loaded = 0,
total = 0,
$bar = $progress.find('.progress-bar'),
percent;
$.each(percentages, function (k, v) {
total += v[0];
loaded += v[0] * v[1];
});
percent = total ? loaded / total : 0;
percent = Math.round(percent * 100) + '%';
$bar.text(percent);
$bar.css('width', percent);
updateStatusText();
}
function updateStatusText() {
var text = '', stats;
if (!uploader) {
return;
}
if (state === 'ready') {
stats = uploader.getStats();
if (fileCount) {
text = __('selected_files', {num: fileCount, size: WebUploader.formatSize(fileSize)});
} else {
showSuccess();
}
} else if (state === 'confirm') {
stats = uploader.getStats();
if (stats.uploadFailNum) {
text = __('selected_has_failed', {success: stats.successNum, fail: stats.uploadFailNum});
}
} else {
showSuccess();
}
function showSuccess() {
stats = uploader.getStats();
if (stats.successNum) {
text = __('selected_success', {num: fileCount, size: WebUploader.formatSize(fileSize), success: stats.successNum});
}
if (stats.uploadFailNum) {
text += (text ? __('dot') : '') + __('failed_num', {fail: stats.uploadFailNum});
}
}
$info.html(text);
}
// 上传文件后修改字段值
function updateFileColumn() {
var values = getInput(),
num = uploader.getStats().successNum,
form = $.extend({}, opts.formData);
if (!num || !values || !opts.autoUpdateColumn) {
return;
}
form[updateColumn] = values.join(',');
delete form['upload_column'];
$.post(opts.updateServer, form);
}
function setState(val, args) {
var stats;
args = args || {};
if (val === state) {
return;
}
if ($upload) {
$upload.removeClass('state-' + state);
$upload.addClass('state-' + val);
}
state = val;
switch (state) {
case 'pedding':
if (opts.disabled) return;
$placeHolder.removeClass('element-invisible');
$queue.hide();
$statusBar.addClass('element-invisible');
if (showImg) {
$wrap.removeAttr('style');
$wrap.find('.queueList').removeAttr('style');
}
refreshButton();
break;
case 'ready':
$placeHolder.addClass('element-invisible');
$selector.find(addFileButtonSelector).removeClass('element-invisible');
$queue.show();
if (!opts.disabled) {
$statusBar.removeClass('element-invisible');
}
refreshButton();
if (showImg) {
$wrap.find('.queueList').css({'border': '1px solid #d3dde5', 'padding':'5px'});
// $wrap.find('.queueList').removeAttr('style');
}
break;
case 'uploading':
$selector.find(addFileButtonSelector).addClass('element-invisible');
$progress.show();
$upload.text(__('pause_upload'));
break;
case 'paused':
$progress.show();
$upload.text(__('go_on_upload'));
break;
case 'confirm':
if (uploader) {
$progress.hide();
$selector.find(addFileButtonSelector).removeClass('element-invisible');
$upload.text(__('start_upload'));
stats = uploader.getStats();
if (stats.successNum && !stats.uploadFailNum) {
setState('finish');
return;
}
}
break;
case 'finish':
if (uploader) {
stats = uploader.getStats();
if (stats.successNum) {
Dcat.success(__('upload_success_message', {success: stats.successNum}));
} else {
// 没有成功的图片,重设
state = 'done';
location.reload();
}
}
break;
case 'decrOriginalFileNum':
if (originalFilesNum > 0) originalFilesNum --;
break;
case 'incrOriginalFileNum':
originalFilesNum ++;
break;
case 'decrFileNumLimit': // 减少上传文件数量限制
if (!uploader) {
return;
}
var fileLimit = uploader.option('fileNumLimit'),
num = args.num || 1;
if (fileLimit == '-1') fileLimit = 0;
num = fileLimit >= num ? fileLimit - num : 0;
if (num == 0) num = '-1';
uploader.option('fileNumLimit', num);
break;
case 'incrFileNumLimit': // 增加上传文件数量限制
if (!uploader) {
return;
}
var fileLimit = uploader.option('fileNumLimit'),
num = args.num || 1;
if (fileLimit == '-1') fileLimit = 0;
num = fileLimit + num;
uploader.option('fileNumLimit', num);
break;
case 'init': // 初始化
$upload.addClass('state-' + state);
updateTotalProgress();
if (originalFilesNum || opts.disabled) {
$placeHolder.addClass('element-invisible');
if (!opts.disabled) {
$statusBar.show();
} else {
$wrap.addClass('disabled');
}
setState('ready');
} else if (showImg) {
$wrap.removeAttr('style');
$wrap.find('.queueList').css('margin', '0');
}
refreshButton();
break;
}
updateStatusText();
}
// 移除form表单的文件
function removeFormFile(fileId) {
if (!fileId) return;
var file = formFiles[fileId];
deleteInput(fileId);
delete formFiles[fileId];
if (uploader && !file.fake) {
uploader.removeFile(file);
}
setState('decrOriginalFileNum');
setState('incrFileNumLimit');
if (!Dcat.helpers.len(formFiles) && !Dcat.helpers.len(percentages)) {
setState('pedding');
}
}
// 获取表单值
function getInput() {
var val = $input.val();
return val ? val.split(',') : [];
}
// 新增表单值
function addInput(id) {
var val = getInput();
val.push(id);
setInput(val);
}
// 设置表单值
function setInput(arr) {
arr = arr.filter(function(v, k, self) {
return self.indexOf(v) === k;
}).filter(function (v) {
return v ? true : false;
});
$input.val(arr.join(','));
}
// 删除表单值
function deleteInput(id) {
if (!id) {
return $input.val('');
}
setInput(getInput().filter(function (v) {
return v != id;
}));
}
// 重新计算按钮定位
function refreshButton() {
uploader.refresh();
}
// 添加上传成功文件到表单区域
function appendUploadedFileForm(file) {
var html = "";
html += "";
if (showImg) {
html += `
`
} else if (! opts.disabled) {
html += `
`;
}
html += "";
html += file.serverPath;
html += "
";
if (showImg) {
html += "
";
html += "";
if (! opts.disabled) {
html += `
`;
}
html += `
`;
html += "
";
}
html += "";
html = $(html);
if (!showImg) {
html.find('.file-type').show();
html.find('.title').show();
$wrap.css('background', 'transparent');
}
var deleteFile = function () {
var fileId = $(this).data('id'), post = opts.deleteData;
if (opts.disableRemove) {
html.remove();
return removeFormFile(fileId);
}
post.key = fileId;
post._column = updateColumn;
Dcat.loading();
$.post(opts.deleteUrl, post, function (result) {
Dcat.loading(false);
if (result.status) {
// 移除
html.remove();
removeFormFile(fileId);
return;
}
Dcat.error(result.message || 'Remove file failed.')
});
};
// 删除按钮点击事件
html.find('[data-file-act="deleteurl"]').click(deleteFile);
html.find('[data-file-act="delete"]').click(deleteFile);
// 放大图片
html.find('[data-file-act="preview"]').click(function () {
var url = $(this).data('url');
Dcat.helpers.previewImage(url);
});
setState('incrOriginalFileNum');
setState('decrFileNumLimit');
formFiles[file.serverId] = file;
addInput(file.serverId);
$queue.append(html);
if (showImg) {
setTimeout(function () { html.css('margin', '5px');}, 80);
}
}
// 初始化web-uploader
function build() {
$wrap = $selector.find(opts.wrapper);
// 图片容器
$queue = $('').appendTo($wrap.find('.queueList'));
// 状态栏,包括进度和控制按钮
$statusBar = $wrap.find('.statusBar');
// 文件总体选择信息。
$info = $statusBar.find('.info');
// 上传按钮
$upload = $wrap.find('.upload-btn');
// 没选择文件之前的内容。
$placeHolder = $wrap.find('.placeholder');
$progress = $statusBar.find('.upload-progress').hide();
// IE;
supportIe();
// 实例化
this.uploader = uploader = WebUploader.create(opts.upload);
// 拖拽时不接受 js, txt 文件。
uploader.on('dndAccept', function (items) {
var denied = false,
len = items.length,
i = 0,
// 修改js类型
unAllowed = 'text/plain;application/javascript ';
for (; i < len; i++) {
// 如果在列表里面
if (~unAllowed.indexOf(items[i].type)) {
denied = true;
break;
}
}
return !denied;
});
if (opts.upload.fileNumLimit > 1 && !opts.disabled) {
// 添加“添加文件”的按钮,
uploader.addButton({
id: addFileButtonSelector,
label: ' ' + __('go_on_add')
});
}
uploader.onUploadProgress = function (file, percentage) {
percentages[file.id][1] = percentage;
updateTotalProgress();
};
uploader.onBeforeFileQueued = function (file) {
};
uploader.onFileQueued = function (file) {
fileCount++;
fileSize += file.size;
if (fileCount === 1) {
$placeHolder.addClass('element-invisible');
$statusBar.show();
}
addFile(file);
setState('ready');
updateTotalProgress();
};
// 删除文件事件监听
uploader.onFileDequeued = function (file) {
fileCount--;
fileSize -= file.size;
if (!fileCount && !Dcat.helpers.len(formFiles)) {
setState('pedding');
}
removeUploadFile(file);
};
uploader.on('all', function (type, obj, reason) {
switch (type) {
case 'uploadFinished':
setState('confirm');
updateFileColumn();
break;
case 'startUpload':
setState('uploading');
break;
case 'stopUpload':
setState('paused');
break;
case 'uploadAccept':
// 上传失败,返回false
if (reason && reason.error) {
Dcat.error(reason.error.message);
faildFiles[obj.file.id] = obj.file;
return false;
}
if (reason.merge) {
// 分片上传
return;
}
// 上传成功,保存新文件名和路径到file对象
obj.file.serverId = reason.id;
obj.file.serverName = reason.name;
obj.file.serverPath = reason.path;
obj.file.serverUrl = reason.url || null;
addInput(reason.id);
if (!showImg) {
var $li = getFileView(obj.file.id);
$li.find('.file-action').hide();
$li.find('[data-file-act="delete"]').show();
}
break;
}
});
uploader.onError = function (code) {
switch (code) {
case 'Q_TYPE_DENIED':
Dcat.error(__('Q_TYPE_DENIED'));
break;
case 'Q_EXCEED_NUM_LIMIT':
Dcat.error(__('Q_EXCEED_NUM_LIMIT', {num: opts.upload.fileNumLimit}));
break;
case 'F_EXCEED_SIZE':
Dcat.error(__('F_EXCEED_SIZE'));
break;
case 'Q_EXCEED_SIZE_LIMIT':
Dcat.error(__('Q_EXCEED_SIZE_LIMIT'));
break;
case 'F_DUPLICATE':
Dcat.warning(__('F_DUPLICATE'));
break;
default:
Dcat.error('Error: ' + code);
}
};
$upload.on('click', function () {
if ($(this).hasClass('disabled')) {
return false;
}
if (state === 'ready') {
uploader.upload();
} else if (state === 'paused') {
uploader.upload();
} else if (state === 'uploading') {
uploader.stop();
}
});
$info.on('click', '.retry', function () {
uploader.retry();
});
$info.on('click', '.ignore', function () {
for (var i in faildFiles) {
uploader.removeFile(i, true);
delete faildFiles[i];
}
});
setState('init');
}
// 预览
function preview() {
for (var i in opts.preview) {
var path = opts.preview[i].path, ext;
if (path.indexOf('.')) {
ext = path.split('.').pop();
}
appendUploadedFileForm({
serverId: opts.preview[i].id,
serverUrl: opts.preview[i].url,
serverPath: path,
ext: ext,
fake: 1,
})
}
}
this.uploader = uploader;
this.options = opts;
this.build = build;
this.preview = preview;
this.setState = setState;
this.refreshButton = refreshButton;
this.getFileView = getFileView;
this.getFileViewSelector = getFileViewSelector;
this.addFileView = addFile;
this.removeUploadFileView = removeUploadFile;
this.isImage = isImage;
this.getColumn = function () {
return updateColumn;
};
function supportIe() {
if (!WebUploader.Uploader.support('flash') && WebUploader.browser.ie) {
// flash 安装了但是版本过低。
if (flashVersion) {
(function (container) {
window['expressinstallcallback'] = function (state) {
switch (state) {
case 'Download.Cancelled':
break;
case 'Download.Failed':
Dcat.error('Install failed!');
break;
default:
Dcat.success('Install Success!');
break;
}
delete window['expressinstallcallback'];
};
var swf = './expressInstall.swf';
// insert flash object
var html = ``;
container.html(html);
})($wrap);
} else {
$wrap.html('
');
}
return;
} else if (!WebUploader.Uploader.support()) {
Dcat.error('您的浏览器不支持Web Uploader!');
return;
}
}
return this;
}
Dcat.Uploader = function (options) {
return new Uploader(options)
};
})(window, jQuery);