webuploader 多图片上传
WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。
具体接口参考 webuploader接口文档地址
一、图片上传功能
引入资源
<!--引入CSS--> <link rel="stylesheet" type="text/css" href="webuploader/webuploader.css"> <!--引入JS--> <script type="text/javascript" src="webuploader/webuploader.js"></script>
html
<div id="uploader" class="wu-example"> <div class="queueList"> <div id="dndArea" class="placeholder"> <div id="filePicker"></div> <p>或将照片拖到这里,单次最多可选300张</p> </div> </div> <div class="statusBar" style="display:none;"> <div class="progress"> <span class="text">0%</span> <span class="percentage"></span> </div> <div class="info"></div> <div class="btns"> <div id="filePicker2"></div> <div class="uploadBtn">开始上传</div> </div> </div> </div>
css样式
#container {
color: #838383;
font-size: 12px;
}
#uploader .queueList {
margin: 20px;
border: 3px dashed #e6e6e6;
}
#uploader .queueList.filled {
padding: 17px;
margin: 0;
border: 3px dashed transparent;
}
#uploader .queueList.webuploader-dnd-over {
border: 3px dashed #999999;
}
#uploader p {margin: 0;}
.element-invisible {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px,1px,1px,1px);
}
#uploader .placeholder {
min-height: 350px;
padding-top: 178px;
text-align: center;
background: url(../../../img/webuploader.png) center 93px no-repeat;
color: #cccccc;
font-size: 18px;
position: relative;
}
#uploader .placeholder .webuploader-pick {
font-size: 18px;
background: #00b7ee;
border-radius: 3px;
line-height: 44px;
padding: 0 30px;
*width: 120px;
color: #fff;
display: inline-block;
margin: 0 auto 20px auto;
cursor: pointer;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
#uploader .placeholder .webuploader-pick-hover {
background: #00a2d4;
}
#uploader .placeholder .flashTip {
color: #666666;
font-size: 12px;
position: absolute;
width: 100%;
text-align: center;
bottom: 20px;
}
#uploader .placeholder .flashTip a {
color: #0785d1;
text-decoration: none;
}
#uploader .placeholder .flashTip a:hover {
text-decoration: underline;
}
#uploader .filelist {
list-style: none;
margin: 0;
padding: 0;
}
#uploader .filelist:after {
content: '';
display: block;
width: 0;
height: 0;
overflow: hidden;
clear: both;
}
#uploader .filelist li {
width: 110px;
height: 110px;
background: url(../../img/bg.png) no-repeat;
text-align: center;
margin: 0 8px 20px 0;
position: relative;
display: inline;
float: left;
overflow: hidden;
font-size: 12px;
}
#uploader .filelist li p.log {
position: relative;
top: -45px;
}
#uploader .filelist li p.title {
position: absolute;
top: 0;
left: 0;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow : ellipsis;
top: 5px;
text-indent: 5px;
text-align: left;
}
#uploader .filelist li p.progress {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
height: 8px;
overflow: hidden;
z-index: 50;
margin: 0;
border-radius: 0;
background: none;
-webkit-box-shadow: 0 0 0;
}
#uploader .filelist li p.progress span {
display: none;
overflow: hidden;
width: 0;
height: 100%;
background: #1483d8 url(../../img/progress.png) repeat-x;
-webit-transition: width 200ms linear;
-moz-transition: width 200ms linear;
-o-transition: width 200ms linear;
-ms-transition: width 200ms linear;
transition: width 200ms linear;
-webkit-animation: progressmove 2s linear infinite;
-moz-animation: progressmove 2s linear infinite;
-o-animation: progressmove 2s linear infinite;
-ms-animation: progressmove 2s linear infinite;
animation: progressmove 2s linear infinite;
-webkit-transform: translateZ(0);
}
@-webkit-keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
@-moz-keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
@keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
#uploader .filelist li p.imgWrap {
position: relative;
z-index: 2;
line-height: 110px;
vertical-align: middle;
overflow: hidden;
width: 110px;
height: 110px;
-webkit-transform-origin: 50% 50%;
-moz-transform-origin: 50% 50%;
-o-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
-webit-transition: 200ms ease-out;
-moz-transition: 200ms ease-out;
-o-transition: 200ms ease-out;
-ms-transition: 200ms ease-out;
transition: 200ms ease-out;
}
#uploader .filelist li img {
width: 100%;
}
#uploader .filelist li p.error {
background: #f43838;
color: #fff;
position: absolute;
bottom: 0;
left: 0;
height: 28px;
line-height: 28px;
width: 100%;
z-index: 100;
}
#uploader .filelist li .success {
display: block;
position: absolute;
left: 0;
bottom: 0;
height: 40px;
width: 100%;
z-index: 200;
background: url(../../img/success.png) no-repeat right bottom;
}
#uploader .filelist div.file-panel {
position: absolute;
height: 0;
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#80000000', endColorstr='#80000000')\0;
background: rgba( 0, 0, 0, 0.5 );
width: 100%;
top: 0;
left: 0;
overflow: hidden;
z-index: 300;
}
#uploader .filelist div.file-panel span {
width: 24px;
height: 24px;
display: inline;
float: right;
text-indent: -9999px;
overflow: hidden;
background: url(../../img/icons.png) no-repeat;
margin: 5px 1px 1px;
cursor: pointer;
}
#uploader .filelist div.file-panel span.rotateLeft {
background-position: 0 -24px;
}
#uploader .filelist div.file-panel span.rotateLeft:hover {
background-position: 0 0;
}
#uploader .filelist div.file-panel span.rotateRight {
background-position: -24px -24px;
}
#uploader .filelist div.file-panel span.rotateRight:hover {
background-position: -24px 0;
}
#uploader .filelist div.file-panel span.cancel {
background-position: -48px -24px;
}
#uploader .filelist div.file-panel span.cancel:hover {
background-position: -48px 0;
}
#uploader .statusBar {
height: 63px;
border-top: 1px solid #dadada;
padding: 0 20px;
line-height: 63px;
vertical-align: middle;
position: relative;
}
#uploader .statusBar .progress {
border: 1px solid #1483d8;
width: 198px;
background: #fff;
height: 18px;
position: relative;
display: inline-block;
text-align: center;
line-height: 20px;
color: #6dbfff;
position: relative;
margin: 0 10px 0 0;
}
#uploader .statusBar .progress span.percentage {
width: 0;
height: 100%;
left: 0;
top: 0;
background: #1483d8;
position: absolute;
}
#uploader .statusBar .progress span.text {
position: relative;
z-index: 10;
}
#uploader .statusBar .info {
display: inline-block;
font-size: 14px;
color: #666666;
}
#uploader .statusBar .btns {
position: absolute;
top: 10px;
right: 20px;
line-height: 40px;
}
#filePicker2 {
display: inline-block;
float: left;
}
#uploader .statusBar .btns .webuploader-pick,
#uploader .statusBar .btns .uploadBtn,
#uploader .statusBar .btns .uploadBtn.state-uploading,
#uploader .statusBar .btns .uploadBtn.state-paused {
background: #ffffff;
border: 1px solid #cfcfcf;
color: #565656;
padding: 0 18px;
display: inline-block;
border-radius: 3px;
margin-left: 10px;
cursor: pointer;
font-size: 14px;
float: left;
}
#uploader .statusBar .btns .webuploader-pick-hover,
#uploader .statusBar .btns .uploadBtn:hover,
#uploader .statusBar .btns .uploadBtn.state-uploading:hover,
#uploader .statusBar .btns .uploadBtn.state-paused:hover {
background: #f0f0f0;
}
#uploader .statusBar .btns .uploadBtn {
background: #00b7ee;
color: #fff;
border-color: transparent;
}
#uploader .statusBar .btns .uploadBtn:hover {
background: #00a2d4;
}
#uploader .statusBar .btns .uploadBtn.disabled {
pointer-events: none;
opacity: 0.6;
}
用于保存swf文件 的html 文件
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>403 Forbidden</title> </head><body> <h1>Forbidden</h1> <p>You don't have permission to access /theme/hplus/js/plugins/webuploader/ on this server.</p> <hr> <address>Apache Server at www.zi-han.net Port 80</address> </body></html>
上js代码
var BASE_URL = 'js/plugins/webuploader/index.html'; //保存swf文件 jQuery(function() { function e(e) { var a = o('<li id="' + e.id + '"><p class="title">' + e.name + '</p><p class="imgWrap"></p><p class="progress"><span></span></p></li>'), s = o('<div class="file-panel"><span class="cancel">删除</span><span class="rotateRight">向右旋转</span><span class="rotateLeft">向左旋转</span></div>').appendTo(a), i = a.find("p.progress span"), t = a.find("p.imgWrap"), r = o('<p class="error"></p>'), d = function(e) { switch (e) { case "exceed_size": text = "文件大小超出"; break; case "interrupt": text = "上传暂停"; break; default: text = "上传失败,请重试" } r.text(text).appendTo(a) }; "invalid" === e.getStatus() ? d(e.statusText) : (t.text("预览中"), n.makeThumb(e, function(e, a) { if (e) return void t.text("不能预览"); var s = o('<img src="' + a + '">'); t.empty().append(s) }, v, b), w[e.id] = [e.size, 0], e.rotation = 0), e.on("statuschange", function(t, n) { "progress" === n ? i.hide().width(0) : "queued" === n && (a.off("mouseenter mouseleave"), s.remove()), "error" === t || "invalid" === t ? (console.log(e.statusText), d(e.statusText), w[e.id][1] = 1) : "interrupt" === t ? d("interrupt") : "queued" === t ? w[e.id][1] = 0 : "progress" === t ? (r.remove(), i.css("display", "block")) : "complete" === t && a.append('<span class="success"></span>'), a.removeClass("state-" + n).addClass("state-" + t) }), a.on("mouseenter", function() { s.stop().animate({ height: 30 }) }), a.on("mouseleave", function() { s.stop().animate({ height: 0 }) }), s.on("click", "span", function() { var a, s = o(this).index(); switch (s) { case 0: return void n.removeFile(e); case 1: e.rotation += 90; break; case 2: e.rotation -= 90 } x ? (a = "rotate(" + e.rotation + "deg)", t.css({ "-webkit-transform": a, "-mos-transform": a, "-o-transform": a, transform: a })) : t.css("filter", "progid:DXImageTransform.Microsoft.BasicImage(rotation=" + ~~ (e.rotation / 90 % 4 + 4) % 4 + ")") }), a.appendTo(l) } function a(e) { var a = o("#" + e.id); delete w[e.id], s(), a.off().find(".file-panel").off().end().remove() } function s() { var e, a = 0, s = 0, t = f.children(); o.each(w, function(e, i) { s += i[0], a += i[0] * i[1] }), e = s ? a / s: 0, t.eq(0).text(Math.round(100 * e) + "%"), t.eq(1).css("width", Math.round(100 * e) + "%"), i() } function i() { var e, a = ""; "ready" === k ? a = "选中" + m + "张图片,共" + WebUploader.formatSize(h) + "。": "confirm" === k ? (e = n.getStats(), e.uploadFailNum && (a = "已成功上传" + e.successNum + "张照片至XX相册," + e.uploadFailNum + '张照片上传失败,<a class="retry" href="#">重新上传</a>失败图片或<a class="ignore" href="#">忽略</a>')) : (e = n.getStats(), a = "共" + m + "张(" + WebUploader.formatSize(h) + "),已上传" + e.successNum + "张", e.uploadFailNum && (a += ",失败" + e.uploadFailNum + "张")), p.html(a) } function t(e) { var a; if (e !== k) { switch (c.removeClass("state-" + k), c.addClass("state-" + e), k = e) { case "pedding": u.removeClass("element-invisible"), l.parent().removeClass("filled"), l.hide(), d.addClass("element-invisible"), n.refresh(); break; case "ready": u.addClass("element-invisible"), o("#filePicker2").removeClass("element-invisible"), l.parent().addClass("filled"), l.show(), d.removeClass("element-invisible"), n.refresh(); break; case "uploading": o("#filePicker2").addClass("element-invisible"), f.show(), c.text("暂停上传"); break; case "paused": f.show(), c.text("继续上传"); break; case "confirm": if (f.hide(), c.text("开始上传").addClass("disabled"), a = n.getStats(), a.successNum && !a.uploadFailNum) return void t("finish"); break; case "finish": a = n.getStats(), a.successNum ? alert("上传成功") : (k = "done", location.reload()) } i() } } var n, o = jQuery, r = o("#uploader"), l = o('<ul class="filelist"></ul>').appendTo(r.find(".queueList")), d = r.find(".statusBar"), p = d.find(".info"), c = r.find(".uploadBtn"), u = r.find(".placeholder"), f = d.find(".progress").hide(), m = 0, h = 0, g = window.devicePixelRatio || 1, v = 110 * g, b = 110 * g, k = "pedding", w = {}, x = function() { var e = document.createElement("p").style, a = "transition" in e || "WebkitTransition" in e || "MozTransition" in e || "msTransition" in e || "OTransition" in e; return e = null, a } (); if (!WebUploader.Uploader.support()) throw alert("不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级 flash 播放器"), new Error("不支持您的浏览器"); n = WebUploader.create({ pick: { id: "#filePicker", label: "点击选择图片" }, dnd: "#uploader .queueList", paste: document.body, accept: { title: "Images", extensions: "gif,jpg,jpeg,bmp,png", mimeTypes: "image/*" }, swf: BASE_URL + "/Uploader.swf", disableGlobalDnd: !0, chunked: !0, server: "webuploader_photo.html", duplicate: true, fileNumLimit: 9, fileSizeLimit: 5242880, fileSingleSizeLimit: 1048576, headers: { "X-CSRFToken": $.cookie('csrftoken') }, }), n.addButton({ id: "#filePicker2", label: "继续添加" }), n.onUploadProgress = function(e, a) { var i = o("#" + e.id), t = i.find(".progress span"); t.css("width", 100 * a + "%"), w[e.id][1] = a, s() }, n.on('uploadSuccess', function (file, response) { var res=JSON.parse(response._raw); //这里可以得到后台返回的数据 // $('#' + file.id).addClass('upload-state-done'); var input = document.createElement("input"); console.log(res.data) var data = res.data input.type="hidden"; input.name=data.id; input.value=data.path; $("#"+data.id).append(input); console.log($("#"+data.id)) }); n.onFileQueued = function(a) { m++, h += a.size, 1 === m && (u.addClass("element-invisible"), d.show()), e(a), t("ready"), s() }, n.onFileDequeued = function(e) { m--, h -= e.size, m || t("pedding"), a(e), s() }, n.on("all", function(e) { switch (e) { case "uploadFinished": t("confirm"); break; case "startUpload": t("uploading"); break; case "stopUpload": t("paused") } }), n.onError=function(e){ // alert("Error: "+e) switch (e) { case 'Q_EXCEED_NUM_LIMIT': alert("错误:最多上传九张图片!"); break; case 'Q_EXCEED_SIZE_LIMIT': alert.msg("错误:文件总大小超出限制!"); break; case 'F_EXCEED_SIZE': alert.msg("错误:文件大小超出限制!"); break; case 'Q_TYPE_DENIED': alert.msg("错误:禁止上传该类型文件!"); break; case 'F_DUPLICATE': alert.msg("错误:请勿重复上传该文件!"); break; default: alert.msg('错误代码:' + type); break; } }, c.on("click", function() { return o(this).hasClass("disabled") ? !1 : void("ready" === k ? n.upload() : "paused" === k ? n.upload() : "uploading" === k && n.stop()) }), p.on("click", ".retry", function() { n.retry() }), p.on("click", ".ignore", function() { alert.msg("已忽略") }), c.addClass("state-" + k), s() });
服务端接收上传文件
def webuploader_photo(request): ret = {"status": False, "data": {"path": "", "name": ""}, "summary": ""} target_path = "media/upload/goods/" try: # 获取文件对象 post_obj = request.POST file_obj = request.FILES.get("file") raw_name = file_obj.name raw_id = post_obj.get("id") postfix = raw_name.split(".")[-1] if file_obj: file_name = str(uuid.uuid4()) + "." + postfix if not os.path.exists(os.path.dirname(target_path)): os.makedirs(target_path) file_path = os.path.join(target_path, file_name) # os.path.join()在Linux/macOS下会以斜杠(/)分隔路径,而在Windows下则会以反斜杠(\)分隔路径, # 故统一路径将'\'替换成'/' file_path = file_path.replace('\\', "/") with open(file_path, "wb") as f: for chunk in file_obj.chunks(): f.write(chunk) ret["status"] = True ret["data"]['path'] = file_path ret["data"]['name'] = raw_name ret["data"]["id"] = raw_id except Exception as e: ret["summary"] = str(e) return HttpResponse(json.dumps(ret))
python接收webuploader上传文件
webuploader真的是一个非常强大的文件上传组件
二 、服务端数据回显和编辑功能实现
html
<div class="page-container"> <div id="uploader" class="uploader" > <div class="wrapper" class="placeholder"> <div class="file-list"></div> <p>或将照片拖到这里,单次最多可选9张</p> </div> <div class="actions-area"> <div class="actions"> <div class="filePicker action">选择图片</div> <div class="uploadFile action upload-btn disabled">上传图片</div> </div> </div> </div> </div> <script type="text/javascript"> var BASE_URL = '/static/plugins/webuploader/index.html'; //webuploader 相关配置 var uploader = new WebUploaderSupport({ server: "webuploader_photo.html", paste: document.body, swf: BASE_URL + "/Uploader.swf", // swf文件所处路径 support: { uploader: "#uploader", //上传区域容器选择器 fileSize: 9, //文件总个数, -1时无限制 serverFiles: HandlerFile(), {#[{"src":"","name":"2.PNG","attrs":{"data-server-file":true,"data-delete-url":""}}]#} }, duplicate: true, fileNumLimit: 9, fileSizeLimit: 5242880, fileSingleSizeLimit: 1048576, headers: { "X-CSRFToken": $.cookie('csrftoken') }, }); </script>
css
.uploader {
position: relative;
padding: 15px 15px;
margin: 15px 0;
background-color: #fafafa;
box-shadow: inset 0 3px 6px rgba(0, 0, 0, .05);
border-color: #e5e5e5 #eee #eee;
border-style: solid;
border-width: 1px 0;
min-width: 250px;
}
.uploader:after {
display: block;
content: "";
overflow: hidden;
clear: both;
}
.uploader .message {
font-size: 16px;
margin-bottom: 20px;
}
.uploader .wrapper {
text-align: center;
border: 3px dashed #ccc;
/* background: url(../images/image.png) center 93px no-repeat;*/
color: #cccccc;
font-size: 18px;
position: relative;
}
.file-list {
list-style: none;
margin: 0;
padding: 0;
}
.file-list:after {
display: block;
content: "";
overflow: hidden;
clear: both;
}
.file-list .file-item {
float: left;
width: 150px;
height: 150px;
margin: 5px;
border: 1px solid;
overflow: hidden;
position: relative;
}
.file-list .file-item img {
width: 100%;
height: 100%;
}
.file-list .file-item .file-delete, .file-list .file-item .file-retry {
display: none;
position: absolute;
width: 100px;
height: 30px;
left: 50%;
margin-left: -50px;
margin-top: -15px;
top: 50%;
z-index: 1;
}
/*重试时显示重试按钮*/
.file-list .file-item.retry:hover .file-retry {
display: block;
}
/*删除重试按钮样式*/
.file-list .file-item button {
outline: none !important;
border: 0;
padding: 5px 12px;
opacity: 0.9;
color: #fff!important;
text-align: center;
border-radius: 3px;
display: inline-block;
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/*删除按钮*/
.file-list .file-item .file-delete button {
background: #f14641;
}
.file-list .file-item .file-delete button:hover {
background: #f1281a;
}
.file-list .file-item .file-retry button {
background: #daa7ff;
}
.file-list .file-item .file-retry button:hover {
background: #bf8cff;
}
/*重试时进度条隐藏*/
.file-list .file-item.retry .progress {
display: none!important;
}
.file-list .file-item:hover .file-delete {
display: block;
}
/*不能预览时的提示*/
.file-list .file-item .preview-tips {
position: absolute;
width: 100px;
height: 20px;
font-size: 16px;
left: 50%;
margin-left: -50px;
color: #949390;
margin-top: -10px;
top: 50%;
overflow: hidden;
z-index: 1;
}
/*鼠标经过当前item时隐藏提示不能预览的内容*/
.file-list .file-item:hover .preview-tips {
z-index: -1;
}
/*鼠标经过当前item时如果是不能编辑则不隐藏文字*/
.file-list .file-item.not-edit:hover .preview .preview-tips {
z-index: 1;
}
.file-list .file-item.not-edit:hover .file-delete {
display: none;
}
.file-list .file-item.not-edit:hover .file-retry {
display: none;
}
.file-item .file-info {
position: absolute;
left: 4px;
bottom: 4px;
right: 4px;
height: 20px;
line-height: 20px;
text-indent: 5px;
background: rgba(0, 0, 0, 0.6);
color: white;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 12px;
z-index: 1;
}
.file-item .state {
position: absolute;
left: 4px;
top: 4px;
right: 4px;
height: 20px;
line-height: 20px;
text-indent: 5px;
background: rgba(0, 0, 0, 0.6);
color: white;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 12px;
z-index: 1;
}
.file-item .state.ready {
background: rgba(169, 64, 64, 0.6);
}
.file-item .state.success {
background: rgba(68, 247, 22, 0.6);
}
.file-item .state.error {
background: red;
}
/*进度条*/
.file-item .progress {
display: none;
position: absolute;
width: 120px;
height: 14px;
left: 50%;
margin-left: -60px;
margin-top: -7px;
top: 50%;
}
.actions-area {
height: 55px;
position: relative;
}
.actions {
position: absolute;
top: 15px;
right: 0px;
}
.actions .action {
display: inline-block;
float: left;
height: 40px;
}
.upload-btn {
outline: none !important;
cursor: pointer;
border: 0;
padding: 10px 15px;
background: #76d67d!important;
color: #fff!important;
text-align: center;
border-radius: 3px;
overflow: hidden;
margin-left: 14px;
}
.upload-btn:hover {
background: #4bb953 !important;
}
.upload-btn.disabled {
background: #848080!important;
}
.upload-btn.disabled:hover {
background: #404040!important;
}
/*上传时使某些内容不显示*/
.uploading {
z-index: -1!important;
}
.file-list .file-item.retry .file-delete {
display: block;
margin-top: 0;
}
.file-list .file-item.retry button {
padding: 2px 5px;
}
.file-list .file-item.retry .file-retry {
display: block;
margin-top: -30px;
}
.actions-area {
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
}
.actions-area button.disabled{
cursor: not-allowed;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
box-shadow: none;
opacity: .65;
}
.actions-area button {
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
}
/*上传失败需要重试时未生成缩略图的提示文字隐藏*/
.file-list .file-item.retry .preview-tips {
display: none;
}
.progress {
height: 20px;
margin-bottom: 20px;
overflow: hidden;
background-color: #f5f5f5;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
}
.progress-bar {
float: left;
width: 0;
height: 100%;
font-size: 12px;
line-height: 20px;
color: #fff;
text-align: center;
background-color: #337ab7;
-webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
-webkit-transition: width .6s ease;
-o-transition: width .6s ease;
transition: width .6s ease;
}
js
function WebUploaderSupport(options) { var that = this; var fileStatus = { inited: "inited", //初始状态 queued: "queued", //已经进入队列, 等待上传 progress: "progress", //上传中 complete: "complete", //上传完成 error: "error", //上传出错,可重试 interrupt: "interrupt", //上传中断,可续传 invalid: "invalid", //文件不合格,不能重试上传。会自动从队列中移除 cancelled: "cancelled" //文件被移除 }; WebUploaderSupport.fileStatus = fileStatus; var $fns = { log: function (content) { if(support.log && console) { console.log(content); } }, logInfo: function () { var support = that.support; if(!support) { this.log("WebUploader does not support the browser you are using."); } else { if(this.getUploader() == null) { this.log("WebUploader has not inited, please use it after inited."); } } }, getUploader: function () { var uploader = that.uploader; return uploader; }, getFiles: function () { var result = null; var uploader = this.getUploader(); if(uploader) { result = uploader.getFiles(); } return result; }, getFileSize: function (status) { var result = 0; var uploader = this.getUploader(); if(uploader) { if(status != null) { result = uploader.getFiles(status).length; } else { result = uploader.getFiles().length; } } return result; }, getInitedFileSize: function () { //获取inited状态的文件个数 return this.getFileSize('inited'); }, retry: function (file) { var uploader = this.getUploader(); if(uploader) { if(that.edit) { if(file != null) { uploader.retry(file); } else { uploader.retry(); } } else { this.log("can't retry, because not in edit mode"); } } this.logInfo(); }, upload: function () { var uploader = this.getUploader(); if(uploader) { if(that.edit) { uploader.upload(); } else { this.log("can't upload, because not in edit mode"); } } this.logInfo(); }, removeFileWithItem: function (file) { var uploader = that.uploader; if(file) { support.removeFileItem(support.getItem(file)); if(uploader) { uploader.removeFile(file.id, true); } } } }; that.$fns = $fns; options = options || {}; var support = { $fns: {}, //公共函数 $elements: {}, //区域jquery元素 edit: true, uploader: ".uploader", //上传区域容器选择器 dndWrapper: ".wrapper", //拖拽区域选择器 chooseFileBtn: ".filePicker", //选择文件的按钮选择器 uploadFileBtn: ".uploadFile", //上传文件的按钮选择器 fileList: ".file-list", //显示文件列表的区域选择器 fileListHeight: 150, //初始默认高度 log: false, //是否打印信息 multiple: true, //默认多选 thumbnailWidth: 150, thumbnailHeight: 150, fileSize: -1, //文件总个数, -1时无限制 instance: null, //uploader实例 ratio: (function () { return window.devicePixelRatio || 1; //优化retina, 在retina下这个值是2 })(), getActualThumbnailWidth: function () { var that = this; var ratio = that.ratio; return that.thumbnailWidth * ratio; }, getActualThumbnailHeight: function () { var that = this; var ratio = that.ratio; return that.thumbnailHeight * ratio; }, /** * 获取不能预览的文件的样式,可覆盖(可根据名称解析后返回img对象显示) * @param name 文件名 * @param thumbnailHeight * @param thumbnailWidth * @returns {jQuery} */ getThumbErrorPreview: function (name, thumbnailHeight, thumbnailWidth) { var $element = $('<div class="preview"></div>').css({ height: thumbnailHeight, width: thumbnailWidth }).append($('<div class="preview-tips">不能预览</div>')); return $element; //显示图片的方式 //return $('<img src="../images/preview/1.jpg">'); }, showPreview: function ($item, file) { //显示文件中的预览效果 var $preview = $('<img />'); $item.append($preview); var uploader = this.instance; // 缩略图大小 var thumbnailWidth = this.getActualThumbnailWidth(), thumbnailHeight = this.getActualThumbnailHeight(); this.setItemStyle($item); //设置item宽高 var self = this; uploader.makeThumb(file, function (error, src) { if (error) { $preview.replaceWith(self.getThumbErrorPreview(file.name, self.thumbnailHeight, self.thumbnailWidth)); return; } $preview.attr('src', src); }, thumbnailWidth, thumbnailHeight); }, getItem: function (file) { //获取$item return $("#" + file.id); }, setItemStyle: function ($item) { //设置缩略图所在容器的宽高,默认是均150px,用于加载文件预览时设置 if($item) { var that = this; var thumbnailWidth = that.thumbnailWidth, thumbnailHeight = that.thumbnailHeight; $item.css({width: thumbnailWidth, height: thumbnailHeight}); //设置$item宽高 } }, loadUploadFileBtnStyle: function () { //用于加载上传按钮的样式 var $fns = this.$fns; var $uploadFileBtn = this.$elements.$uploadFileBtn; if($fns && $uploadFileBtn) { var initedSize = $fns.getInitedFileSize(); if (initedSize === 0) { //inited 文件个数 $uploadFileBtn.addClass("disabled"); //上传按钮禁用 } else { $uploadFileBtn.removeClass("disabled"); //移除上传按钮的禁用样式 } } }, retryFile: function ($item, file) { var $fns = this.$fns; $fns.retry(file); }, renderItem: function (isFile, data) { var name = data.name || ""; var html = '<div class="file-item thumbnail">' + '<div class="file-info">' + name + '</div>' + '<div class="file-operations">' + '<div class="file-delete">' + '<button type="button">' + '删除</button></div>' + '<div class="file-retry">' + '<button type="button">' + '重试</button></div>' + '</div>' + '<div class="progress">' + '<div class="progress-bar"></div>' + '</div>' + '</div>'; var $item = $(html); if (!isFile) { //服务端显示数据时 var $preview; //根据文件后缀进行展示预览结果 if(/(.jpg|.png|.gif|.bmp|.jpeg)$/.test(name.toLocaleLowerCase())) { $preview = $('<img src="'+ data.src + '"/>'); //自定义添加 var $input =$('<input type="text" value="'+ data.src +'" class="hidden" >') ; $item.append($input); } else { $preview = this.getThumbErrorPreview(data.name, this.thumbnailHeight, this.thumbnailWidth); } $item.append($preview); } else { $item.attr("id", data.id); $item.append('<div class="state ready">等待上传...</div>'); this.showPreview($item, data); //显示预览效果 } return $item[0]; }, fileQueued: function (file) { //文件被添加进队列 var self = this; var $item = $(this.renderItem(true, file)); var $fileList = this.$elements.$fileList; $fileList.append($item); //显示在文件列表中 self.loadUploadFileBtnStyle(); //加载上传按钮样式 $item.on("click", 'button', function () { var $this = $(this); if($this.parents(".file-retry")[0]) { self.retryFile($item, file); } else if ($this.parents(".file-delete")[0]) { self.deleteFile($item, file, self.deleteServerFileCallback, self.$fns.removeFileWithItem); } }); self.loadChooseFileBtnStyle(this.$elements.$chooseFileBtn, this.$elements.$uploadFileBtn); }, fileDequeued: function (file) { this.loadUploadFileBtnStyle(); }, uploadProgress: function (file, percentage) { //文件上传过程中创建进度条 var $item = this.getItem(file); $item.find('.file-delete, .preview-tips').addClass("uploading"); //隐藏删除按钮、提示文字 $item.removeClass("retry"); //移除重试class $item.find('.progress').show(); //显示进度条 var $percent = $item.find('.progress .progress-bar'); $percent.css('width', percentage * 100 + '%'); }, uploadComplete: function (file) { //完成上传时,无论成功或者失败 var $item = this.getItem(file); $item.find('.progress').fadeOut(); $item.find('.file-delete, .preview-tips').removeClass("uploading"); //显示删除按钮、提示文字 var $uploadFileBtn = this.$elements.$uploadFileBtn; var $chooseFileBtn = this.$elements.$chooseFileBtn; this.loadChooseFileBtnStyle($chooseFileBtn, $uploadFileBtn); this.loadUploadFileBtnStyle(); }, uploadSuccess: function (file, response) { // 文件上传完成后 var res=JSON.parse(response._raw);//这里可以得到后台返回的数据 var $item = this.getItem(file), $state = $item.find('.state'); $item.find('.progress').hide(); if (res.status) { //上传成功时 this.uploadSuccessCallbck($item, res); //用于标识为服务端文件 if (!$state.hasClass('success')) { $state.attr("class", "state success"); $state.text('上传成功'); } $item.removeClass("retry"); } else { if (!$state.hasClass('error')) { $state.attr("class", "state error"); $state.text('上传失败'); } $item.addClass("retry"); } }, uploadSuccessCallbck: function ($item, data) { //上传文件成功时的回调,用于标识为服务端文件 console.log("$item",$item) if($item && data) { var input = document.createElement("input"); console.log(data.data) var data = data.data input.type="hidden"; input.name=data.id; input.value="/"+data.path; $item .append(input); } }, /*** * 当文件被加入队列之前触发,若返回false则此文件不会被添加进入队列 * @param file * @returns {boolean} 为true时可以添加到webuploader中并进行显示 */ beforeFileQueued: function(file) { var fileSize = this.fileSize; //获取当前总个数 if(fileSize < 1) { //无限制个数 return true; } var currentFileSize = this.getCurrentFileSize(); //当前总个数 var flag = false; var edit = this.edit; if(edit) { //可编辑模式时 if(currentFileSize < fileSize) { flag = true; } } //执行beforeFileQueuedCallback回调函数 this.beforeFileQueuedCallback(edit, flag, file, fileSize, currentFileSize); return flag; }, /** * 当文件被加入队列返回结果之前触发 * @param edit 是否可编辑 * @param result 是否会添加并显示 * @param file --- file对象 * @param fileSize --- 总文件个数 * @param currentFileSize --- 当前文件个数 */ beforeFileQueuedCallback: function (edit, result, file, fileSize, currentFileSize) { }, /** * 当validate不通过时触发 * @param type * Q_EXCEED_SIZE_LIMIT 在设置了Q_EXCEED_SIZE_LIMIT且尝试给uploader添加的文件总大小超出这个值时 * Q_TYPE_DENIED 当文件类型不满足时 * Q_EXCEED_NUM_LIMIT 在设置了fileNumLimit且尝试给uploader添加的文件数量超出这个值时 */ errorTypeHanlder: function (type, file) { }, uploadError: function (file) { //文件上传失败后 console.log("uploadError") var $item = this.getItem(file), $state = $item.find('.state'); if (!$state.hasClass('error')) { $state.attr("class", "state error"); $state.text('上传失败'); } $item.addClass("retry"); this.loadUploadFileBtnStyle(); this.uploadErrorAfter(file); }, /** * 上传失败后执行 * @param file */ uploadErrorAfter: function (file) { }, uploadFinished: function () {}, //文件上传完后触发 serverFileAttrName: "data-server-file", //服务端文件的属性名称 getIsServerFile: function ($item) { //判断文件是否是服务端文件 var val = $item && $item.attr(this.serverFileAttrName); if(val && val === "true") { return true; } return false; }, getServerFileSize: function () { //获取服务端文件的个数 var $fileList = this.$elements.$fileList; var size = 0; var serverFileAttrName = this.serverFileAttrName; if($fileList) { size = $fileList.find(".file-item["+serverFileAttrName+"='true']").size(); } return size; }, getItemSize: function () { //获取当前item的文件个数 var $fileList = this.$elements.$fileList; var size = 0; if($fileList) { size = $fileList.find(".file-item").size(); } return size; }, getCurrentFileSize: function () { //获取当前uploader实例中文件的个数 var fileStatus = WebUploaderSupport.fileStatus; var $fns = this.$fns; var initedSize = $fns.getFileSize(fileStatus.inited); //初始状态个数 var errorSize = $fns.getFileSize(fileStatus.error); //上传失败个数 var size = initedSize + errorSize + this.getServerFileSize();//最终加上服务端文件个数 var itemSize = this.getItemSize(); var result = itemSize > size ? itemSize : size; return result; }, removeFileItem: function($item) { //移除$item if($item && $item[0]) { $item.off().remove(); } }, deleteFile: function ($item, file, deleteServerFileCallback, removeFileWithItem) { //删除文件的处理逻辑,包含服务端 if(this.getIsServerFile($item)) { //服务端时 if(this.edit) { this.deleteServerFile($item, deleteServerFileCallback); } else { this.$fns.log("can't delete server file"); } } else { if(removeFileWithItem && file) { removeFileWithItem(file); } var $chooseFileBtn = this.$elements.$chooseFileBtn, $uploadFileBtn = this.$elements.$uploadFileBtn; this.loadChooseFileBtnStyle($chooseFileBtn, $uploadFileBtn); } }, deleteServerFileAttrName: "data-delete-url", /** * 删除服务端文件(依赖于getIsServerFile的判断结果)的业务操作,可根据实际覆盖重写(support配置中直接重写该函数即可) * @param $item * @param deleteServerFileCallback */ deleteServerFile: function ($item, deleteServerFileCallback) { var self = this; //获取删除的url var url = $item && $item.attr(self.deleteServerFileAttrName); if(url) { $.ajax({ dataType: "json", type: "post", url: url, success: function (json) { if(deleteServerFileCallback && typeof deleteServerFileCallback === "function") { deleteServerFileCallback(self, $item, json); //通过callback执行业务操作 var $chooseFileBtn = self.$elements.$chooseFileBtn, $uploadFileBtn = self.$elements.$uploadFileBtn; self.loadChooseFileBtnStyle($chooseFileBtn, $uploadFileBtn); } } }); } }, /** * deleteServerFile 响应成功时的回调处理,可根据实际覆盖重写 * @param self 当前对象 * @param $item * @param data */ deleteServerFileCallback: function (self, $item, data) { if(data.status) { self.removeFileItem($item); } else { alert(data.content); } }, serverFiles: [], //加载服务端的数据,当前为 [{name:string, src: string, attrs: {}}] init: function (data, $fileList, $chooseFileBtn, $uploadFileBtn) { //初始化服务端数据,及加载样式 var self = this; var edit = self.edit; var $files = null; var thumbnailHeight = self.thumbnailHeight; $fileList.css({"min-height": thumbnailHeight + 20}); //设置该区域最小高度为thumbnailHeight + 20px //加载服务端数据 if(data && data.length > 0) { for(var i in data) { var item = data[i]; var $item = $(this.renderItem(false, item)); if(!edit) { $item.addClass("not-edit"); } self.setItemStyle($item); //以缩略图大小设置$item宽高 if($item && item) { var attrs = item.attrs; for(var key in attrs) { //设置$item属性值 $item.attr(key, attrs[key]); } } if(i === "0") { $files = $item; } else { $files = $files.add($item); } } } if($files) { //加载服务端数据 $fileList.append($files); $files.on('click', '.file-delete button', function () { var $item = $(this).parents(".file-item"); self.deleteFile($item, null, self.deleteServerFileCallback); }); } self.loadChooseFileBtnStyle($chooseFileBtn, $uploadFileBtn); }, editChange: function (edit) { //用于根据edit改变时进行设置webuploader模式 var that = this; that.edit = edit; var $chooseFileBtn = that.$elements.$chooseFileBtn, $uploadFileBtn = that.$elements.$uploadFileBtn; var $fileList = that.$elements.$fileList; if(edit) { $fileList.children().removeClass("not-edit"); } else { $fileList.children().addClass("not-edit"); } that.loadChooseFileBtnStyle($chooseFileBtn, $uploadFileBtn); }, getChooseFileLabel: function ($chooseFileBtn) { //获取当前上传文件按钮对应的label,该label用于触发选择文件 var $label = null; if($chooseFileBtn) { if($chooseFileBtn.hasClass("webuploader-container")) { $label = $chooseFileBtn.find(".webuploader-element-invisible").next("label"); } else { $label = $(".webuploader-container").not(this.chooseFileBtn).find(".webuploader-element-invisible").next("label"); } } return $label; }, loadChooseFileBtnStyle: function ($chooseFileBtn, $uploadFileBtn) { //根据文件个数进行展示选择文件的按钮(用于上传完成时,删除文件时,添加到队列时, 初次加载服务端数据时) var that = this; var $fns = that.$fns; var fileSize = that.fileSize; var $actions = $chooseFileBtn.parents(".actions"); var $actionsArea = $actions.parent(".actions-area"); var $label = that.getChooseFileLabel($chooseFileBtn); if (that.edit) { //可编辑时 $actionsArea.css("height", ""); $actions.show(); var uploader = $fns.getUploader(); if(uploader) { uploader.refresh(); //解决label按钮点击无反应 } if (fileSize > 0) { var currentSize = that.getCurrentFileSize(); if (fileSize === currentSize) { $label && $label.hide(); $chooseFileBtn.hide(); $uploadFileBtn.addClass("right"); } else { $label && $label.show(); $chooseFileBtn.show(); $uploadFileBtn.removeClass("right"); } } else { $label && $label.show(); $chooseFileBtn.show(); $uploadFileBtn.removeClass("right"); } } else { //不可编辑时 $actions.hide(); $actionsArea.css("height", 10); if ($label) { $label.hide(); } } } }; support = $.extend(support, options.support); support.$fns = $fns; //设置support方法 that.supports = support; var multiple = support.multiple; delete options.support; //删除额外的support属性 var $uploader = $(support.uploader), $chooseFileBtn = $uploader.find(support.chooseFileBtn), //选择文件的按钮选择器 $fileList = $uploader.find(support.fileList), //显示文件列表的区域 $uploadFileBtn = $uploader.find(support.uploadFileBtn), //上传文件的按钮 $dndWrapper = $uploader.find(support.dndWrapper); //支持拖拽到此区域 var $elements = { $uploader: $uploader, $chooseFileBtn: $chooseFileBtn, $fileList: $fileList, $uploadFileBtn: $uploadFileBtn, $dndWrapper: $dndWrapper }; support.$elements = $elements; var defaultOption = { accept: { title: 'Images', extensions: 'gif,jpg,jpeg,bmp,png', mimeTypes: 'image/!*' }, pick: { id: $chooseFileBtn, multiple: multiple }, disableGlobalDnd: true, dnd: $dndWrapper, //支持拖拽到此区域 resize: false, compress: false, //不压缩图片,原图 swf: 'Uploader.swf' // swf文件路径 }; var currentOptions = $.extend(true, {}, defaultOption, options); //当期webuploader的配置, options中的优先级最高 if(document.all || window.ActiveXObject || "ActiveXObject" in window) { if(currentOptions.paste != null) { currentOptions.paste = null; $fns.log("ie is not support paste"); } } that.edit = support.edit; that.support = WebUploader.Uploader.support(); //获取是否支持webuploader上传 jQuery(function() { var $ = jQuery; $fileList.css({"min-height": support.fileListHeight + 20}); var uploader; try { if(!that.support) { support.init(support.serverFiles, $fileList, $chooseFileBtn, $uploadFileBtn); $fns.log("WebUploader does not support the browser you are using."); return; } else { uploader = WebUploader.create(currentOptions); //实例化webuploader support.instance = uploader; support.init(support.serverFiles, $fileList, $chooseFileBtn, $uploadFileBtn); } } catch (e) { if(console) { console.log(e); } } if(uploader) { that.uploader = uploader; if($uploadFileBtn && $uploadFileBtn[0]) { $uploadFileBtn.click(function () { $fns.upload(); }); } //文件被添加进队列时 uploader.on('fileQueued', function (file) { support.fileQueued && support.fileQueued.apply(support, arguments); }); //移除文件时 uploader.on('fileDequeued', function (file) { support.fileDequeued && support.fileDequeued.apply(support, arguments); }); uploader.on('uploadProgress', function (file, percentage) { support.uploadProgress && support.uploadProgress.apply(support, arguments); }); //完成上传时,无论成功或者失败 uploader.on('uploadComplete', function (file) { support.uploadComplete && support.uploadComplete.apply(support, arguments); }); // 文件上传完成后,添加相应的样式(响应成功) uploader.on('uploadSuccess', function (file, data) { support.uploadSuccess && support.uploadSuccess.apply(support, arguments); }); // 文件上传失败,显示上传出错(上传失败出现错误状态码时) uploader.on('uploadError', function (file) { support.uploadError && support.uploadError.apply(support, arguments); }); // 当文件被加入队列之前触 uploader.on('beforeFileQueued', function (file) { return support.beforeFileQueued && support.beforeFileQueued.apply(support, arguments); }); //当前uploader实例文件上传完成后触发 uploader.on("uploadFinished", function () { support.uploadFinished && support.uploadFinished.apply(support, arguments); }); uploader.on('error', function () { support.errorTypeHanlder && support.errorTypeHanlder.apply(support, arguments); }); } }); } //上传该uploader实例的文件 WebUploaderSupport.prototype.upload = function () { this.$fns.upload(); } //判断是否正在上传中 WebUploaderSupport.prototype.isInProgress = function () { var flag = false; var uploader = this.uploader; if(uploader) { flag = uploader.isInProgress(); } this.$fns.logInfo(); return flag; } WebUploaderSupport.prototype.retry = function () { this.$fns.retry(); } WebUploaderSupport.prototype.getSupports = function () { var supports = this.supports; return supports; } //更换模式 WebUploaderSupport.prototype.editChange = function (edit) { if(typeof edit != "boolean") { throw new Error("the param type must be boolean"); } var supports = this.supports; this.edit = edit; supports.editChange(edit); }
参考github上的一个朋友对webuploader 的封装:https://github.com/joker-pper/WebUploaderSupport