(function (w, $) {
    function Uploader(opts) {
        opts = $.extend({
            wrapper: '.web-uploader', // 图片显示容器选择器
            addFileButton: '.add-file-button', // 继续添加按钮选择器
            inputSelector: '',
            isImage: false,
            preview: [], // 数据预览
            server: '',
            updateServer: '',
            autoUpload: false,
            sortable: false,
            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个文件上传失败，<a class="retry"  href="javascript:"";">重新上传</a>失败文件或<a class="ignore" href="javascript:"";">忽略</a>',
                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: '文件重复',
                confirm_delete_file: '您确定要删除这个文件吗？',
            },
            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)),
            relation = opts.upload.formData._relation, // 一对多关联关系名称
            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(opts.inputSelector),

            // 获取文件视图选择器
            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,

            // 已上传的文件
            uploadedFiles = [],

            initImg;

        // 当有文件添加进来时执行，负责view的创建
        function addFile(file) {
            var size = WebUploader.formatSize(file.size), $li, $btns, fileName = file.name || null;

            if (showImg) {
                $li = $(`<li id="${getFileViewSelector(file.id)}" title="${fileName}" >
                    <p class="file-type">${(file.ext.toUpperCase() || 'FILE')}</p>
                    <p class="imgWrap "></p>
                    <p class="title" style="">${file.name}</p>
                    <p class="title" style="margin-bottom:20px;">(<b>${size}</b>)</p>
                    </li>`);

                $btns = $(`<div class="file-panel">
                    <a class="btn btn-sm btn-white" data-file-act="cancel"><i class="feather icon-x red-dark" style="font-size:13px"></i></a>
                    <a class="btn btn-sm btn-white" data-file-act="delete" style="display: none">
                    <i class="feather icon-trash red-dark" style="font-size:13px"></i></a>
                    <a class="btn btn-sm btn-white" data-file-act="preview" ><i class="feather icon-zoom-in"></i></a>
                    <a class='btn btn-sm btn-white' data-file-act='order' data-order="1" style="display: none"><i class='feather icon-arrow-up'></i></a>
                    <a class='btn btn-sm btn-white' data-file-act='order' data-order="0" style="display: none"><i class='feather icon-arrow-down'></i></a>

                    </div>`).appendTo($li);
            } else {
                $li = $(`
                    <li id="${getFileViewSelector(file.id)}" title="${file.nam}">
                    <p class="title" style="display:block">
                        <i class="feather icon-check green _success icon-success"></i>
                        ${file.name} (${size})
                    </p>
                    </li>
                `);

                $btns = $(`
<span style="right: 45px;" class="file-action d-none" data-file-act='order' data-order="1"><i class='feather icon-arrow-up'></i></span>
<span style="right: 25px;" class="file-action d-none" data-file-act='order' data-order="0"><i class='feather icon-arrow-down'></i></span>
<span data-file-act="cancel" class="file-action" style="font-size:13px">
    <i class="feather icon-x red-dark"></i>
</span>
<span data-file-act="delete" class="file-action" style="display:none">
    <i class="feather icon-trash red-dark"></i>
</span>
`).appendTo($li);
            }

            var $wrap = $li.find('p.imgWrap'),
                $info = $('<p class="error"></p>'),

                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 = $('<img src="' + src + '">');
                            $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('<span class="success"><em></em><i class="feather icon-check"></i></span>');
                    } 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) {
                            deleteInput(file.serverId);
                            return uploader.removeFile(file);
                        }

                        Dcat.confirm(__('confirm_delete_file'), file.serverId, function () {
                            var post = opts.deleteData;

                            post.key = file.serverId;
                            if (!post.key) {
                                deleteInput(file.serverId);
                                return uploader.removeFile(file);
                            }
                            post._column = updateColumn;
                            post._relation = relation;

                            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;
                    case 'order':
                        $(this).attr('data-id', file.serverId);

                        orderFiles.apply(this);
                        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;
            }

            if (relation) {
                if (! relation[1]) {
                    // 新增子表记录，则不调用update接口
                    return;
                }

                form[relation[0]] = {};

                form[relation[0]][relation[1]] = {};
                form[relation[0]][relation[1]][updateColumn] = values.join(',');
            } else {
                form[updateColumn] = values.join(',');
            }

            delete form['_relation'];
            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');
                    }

                    setTimeout(removeValidatorErrors, 1);
                    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}));

                            setTimeout(function () {
                                if (opts.upload.fileNumLimit == 1) {
                                    // 单文件上传，需要重置文件上传个数
                                    uploader.request('get-stats').numOfSuccess = 0;
                                }
                            }, 10);

                        } 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) {
            deleteUploadedFile(id);

            if (!id) {
                return $input.val('');
            }
            setInput(getInput().filter(function (v) {
                return v != id;
            }));
        }

        // 添加已上传文件
        function appendUploadedFile(file) {
            if (! file.serverId || searchUploadedFile(file.serverId) !== -1) {
                return;
            }

            uploadedFiles.push(file)
        }

        function syncUploadedFiles() {
            var files = [];
            for (var i in uploadedFiles) {
                if (uploadedFiles[i]) {
                    files.push(uploadedFiles[i].serverId);
                }
            }

            setInput(files);
        }

        function deleteUploadedFile(fileId) {
            uploadedFiles = uploadedFiles.filter(function (v) {
                return v.serverId != fileId;
            });
        }

        // 查找文件位置
        function searchUploadedFile(fileId) {
            for (var i in uploadedFiles) {
                if (uploadedFiles[i].serverId === fileId) {
                    return i;
                }
            }

            return -1;
        }

        // 交换文件排序
        function swrapUploadedFile(fileId, order) {
            var index = parseInt(searchUploadedFile(fileId)),
                currentFile = uploadedFiles[index],
                prevFile = uploadedFiles[index - 1],
                nextFile = uploadedFiles[index + 1];

            if (order) {
                if (index === 0) {
                    return;
                }

                uploadedFiles[index - 1] = currentFile;
                uploadedFiles[index] = prevFile;
            } else {
                if (! nextFile) {
                    return;
                }

                uploadedFiles[index + 1] = currentFile;
                uploadedFiles[index] = nextFile;
            }

            syncUploadedFiles();
        }

        // 重新渲染已上传文件
        function rerenderUploadedFiles() {
            $queue.html('');

            for (var i in uploadedFiles) {
                if (uploadedFiles[i]) {
                    appendUploadedFileForm(uploadedFiles[i])
                }
            }
        }

        // 重新计算按钮定位
        function refreshButton() {
            uploader.refresh();
        }

        function removeValidatorErrors() {
            $input.parents('.form-group,.form-label-group,.form-field').find('.with-errors').html('')
        }

        // 文件排序
        function orderFiles() {
            var $this = $(this),
                $li = $this.parents('li').first(),
                fileId = $this.data('id'),
                order = $this.data('order'),
                $prev = $li.prev(),
                $next = $li.next();

            if (order) {
                // 升序
                if (! $prev.length) {
                    return;
                }
                swrapUploadedFile(fileId, order);
                rerenderUploadedFiles();

                return;
            }

            if (! $next.length) {
                return;
            }

            swrapUploadedFile(fileId, order);
            rerenderUploadedFiles();
        }

        // 添加上传成功文件到表单区域
        function appendUploadedFileForm(file) {
            var html = "";
            html += "<li title='" + file.serverPath + "'>";

            if (! showImg && opts.sortable) {
                // 文件排序
                html += `
<p style="right: 45px" class="file-action" data-file-act='order' data-order="1" data-id='${file.serverId}'><i class='feather icon-arrow-up'></i></p>
<p style="right: 25px" class="file-action" data-file-act='order' data-order="0" data-id='${file.serverId}'><i class='feather icon-arrow-down'></i></p>
`;
            }

            if (showImg) {
                html += `<p class='imgWrap'><img src='${file.serverUrl}'></p>`
            } else if (! opts.disabled) {
                html += `<p class="file-action" data-file-act="delete" data-id="${file.serverId}"><i class="feather icon-trash red-dark"></i></p>`;
            }

            html += "<p class='title' style=''><i class='feather icon-check text-white icon-success text-white'></i>";
            html += file.serverPath;
            html += "</p>";

            if (showImg) {
                html += "<p class='title' style='margin-bottom:20px;'>&nbsp;</p>";
                html += "<div class='file-panel' >";

                if (! opts.disabled) {
                    html += `<a class='btn btn-sm btn-white' data-file-act='deleteurl' data-id='${file.serverId}'><i class='feather icon-trash red-dark' style='font-size:13px'></i></a>`;
                }
                html += `<a class='btn btn-sm btn-white' data-file-act='preview' data-url='${file.serverUrl}' ><i class='feather icon-zoom-in'></i></a>`;

                if (opts.sortable) {
                    // 文件排序
                    html += `
<a class='btn btn-sm btn-white' data-file-act='order' data-order="1" data-id='${file.serverId}'><i class='feather icon-arrow-up'></i></a>
<a class='btn btn-sm btn-white' data-file-act='order' data-order="0" data-id='${file.serverId}'><i class='feather icon-arrow-down'></i></a>
`;
                }

                html += "</div>";
            } else {

            }

            html += "</li>";
            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);
                }

                Dcat.confirm(__('confirm_delete_file'), file.serverId, function () {
                    post.key = fileId;
                    post._column = updateColumn;
                    post._relation = relation;

                    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);

            // 文件排序
            if (opts.sortable) {
                html.find('[data-file-act="order"').click(orderFiles);
            }

            // 放大图片
            html.find('[data-file-act="preview"]').click(function () {
                var url = $(this).data('url');

                Dcat.helpers.previewImage(url);
            });

            formFiles[file.serverId] = file;

            addInput(file.serverId);

            $queue.append(html);

            if (showImg) {
                setTimeout(function () {
                    html.css('margin', '5px');
                }, initImg ? 0 : 400);

                initImg = 1;
            }
        }

        // 初始化web-uploader
        function build() {
            $wrap = $selector.find(opts.wrapper);

            // 图片容器
            $queue = $('<ul class="filelist"></ul>').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: '<i class="feather icon-folder"></i> &nbsp;' + __('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();

                if (! opts.disabled && opts.autoUpload) {
                    // 自动上传
                    uploader.upload()
                }
            };

            // 删除文件事件监听
            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;

                        appendUploadedFile(obj.file);

                        addInput(reason.id);

                        var $li = getFileView(obj.file.id);

                        if (!showImg) {
                            $li.find('.file-action').hide();
                            $li.find('[data-file-act="delete"]').show();
                        }

                        if (opts.sortable) {
                            $li.find('[data-file-act="order"]').removeClass('d-none').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();
                }

                var file = {
                    serverId: opts.preview[i].id,
                    serverUrl: opts.preview[i].url,
                    serverPath: path,
                    ext: ext,
                    fake: 1,
                };

                setState('incrOriginalFileNum');
                setState('decrFileNumLimit');

                appendUploadedFileForm(file);
                appendUploadedFile(file);
            }
        }

        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 = `<object type="application/x-shockwave-flash" data="${swf}" `;

                        if (WebUploader.browser.ie) {
                            html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
                        }

                        html += `width="100%" height="100%" style="outline:0">
                            <param name="movie" value="${swf}" />
                            <param name="wmode" value="transparent" />
                            <param name="allowscriptaccess" value="always" />
                            </object>`;

                        container.html(html);

                    })($wrap);

                } else {
                    $wrap.html('<a href="http://www.adobe.com/go/getflashplayer" target="_blank" border="0"><img alt="get flash player" src="http://www.adobe.com/macromedia/style_guide/images/160x41_Get_Flash_Player.jpg" /></a>');
                }

                return;
            } else if (!WebUploader.Uploader.support()) {
                Dcat.error('您的浏览器不支持Web Uploader！');
                return;
            }
        }

        return this;
    }

    Dcat.Uploader = function (options) {
        return new Uploader(options)
    };

})(window, jQuery);