HTML文件上传与下载
文件下載
傳統(tǒng)的文件下載有兩種方法:
這兩種方法效果一樣。但有個(gè)很大的問(wèn)題,如果下載出現(xiàn)異常(連接路徑失效、文件不存在、網(wǎng)絡(luò)問(wèn)題等),會(huì)導(dǎo)致原本的頁(yè)面被覆蓋掉,顯示404等錯(cuò)誤信息。
大致的優(yōu)化思路如下:
我們來(lái)逐一分析:
- 如果服務(wù)端沒(méi)有指定文件名,就以此屬性規(guī)定的名稱命名。
- 如果下載出現(xiàn)異常,該屬性的存在能夠保證頁(yè)面不會(huì)出問(wèn)題。
- 如果服務(wù)端返回的不是文件、而是字符,如果download=‘’error.txt”,能夠通過(guò)打開(kāi)此文件查看到返回的文本信息。
- 如果沒(méi)有異常,文件將會(huì)直接下載。
- 如果出現(xiàn)異常,iframe子頁(yè)面會(huì)報(bào)錯(cuò),父頁(yè)面不會(huì)受任何影響。
- 在網(wǎng)上看了看,大致的流程是:發(fā)送異步請(qǐng)求時(shí)設(shè)置responseType為blob,即接收流數(shù)據(jù)為blob對(duì)象保存在內(nèi)存中。接收完成后,生成鏈接地址(1.通過(guò)FileReader對(duì)象將blob對(duì)象生成base64編碼 2.通過(guò)URL.createObjectURL生成指向文件內(nèi)存的鏈接),寫入<a/>標(biāo)簽的href屬性,然后模擬點(diǎn)擊<a/>按標(biāo)簽實(shí)現(xiàn)下載。
- 此方法最大的問(wèn)題是,因無(wú)法直接操作磁盤,故接收的文件必須先存放在內(nèi)存中(且只有傳輸完成后才能構(gòu)建blob對(duì)象),才能轉(zhuǎn)化成文件。因此,大文件的下載可能會(huì)把你的瀏覽器擠爆。
- 需要額外開(kāi)啟websocket服務(wù),此方法未做實(shí)踐。
總結(jié)以上方法,最推薦前兩種,方便簡(jiǎn)單。
附上后端Django代碼(適用于前兩種方法):
def syncDownLoad(request):"文件下載"print("同步下載文件")startTime = time.time()def file_iterator(file, chunk_size=1024):with open(file, "rb") as f:while True:c = f.read(chunk_size)if c:yield celse:endTime = time.time()print("傳輸時(shí)間", endTime - startTime)breakfileRoute = "/static/files/2018/12/18/第四章(1)學(xué)習(xí)動(dòng)機(jī)概述.mp4"fileName = "第四章(1)學(xué)習(xí)動(dòng)機(jī)概述.mp4"route = os.path.dirname(os.path.dirname(__file__)) + fileRouteif os.path.exists(route): # 如果存在文件response = StreamingHttpResponse(file_iterator(route))# response['Content-Type'] = 'application/octet-stream'response['Content-Type'] = 'text/html'response['Content-Disposition'] = 'attachment;filename="{0}"'.format(fileName).encode("utf-8")return responseelse:return HttpResponse("cannot find file")參考鏈接:
https://scarletsky.github.io/2016/07/03/download-file-using-javascript/
https://my.oschina.net/watcher/blog/1525962
文件上傳
?概述
文件上傳需要處理的問(wèn)題有:
1.多文件上傳? 2.異步上傳? 3.拖拽上傳? 4.上傳限制(限制大小、類型) 5.顯示上傳進(jìn)度、上傳速度、中途取消上傳? 6.預(yù)覽文件
HTML DEMO
<input type="file" id="file" name="myfile" οnchange="onchanges()" multiple="multiple"/> <input type="button" οnclick="SerialUploadFile()" value="上傳"/>一、多文件上傳
<input type="file" id="file" name="myfile" multiple="multiple"/> <!-- multiple屬性 -->二、異步上傳
通過(guò)ajax等方式異步上傳,FormData對(duì)象支持傳輸文件。
function UploadFile() {var fileObj = document.getElementById("file").files; // js 獲取文件對(duì)象(FileList對(duì)象)// FormData 對(duì)象var form = new FormData();form.append("author", "xueba"); // 可以增加表單數(shù)據(jù)for (let i = 0; i < fileObj.length; i++){form.append("file", fileObj[i]); // 文件對(duì)象}$.ajax({url: "/file_upload/",type: "POST",async: true, // 異步上傳data: form,contentType: false, // 必須false才會(huì)自動(dòng)加上正確的Content-TypeprocessData: false, // 必須false才會(huì)避開(kāi)jQuery對(duì) formdata 的默認(rèn)處理。XMLHttpRequest會(huì)對(duì) formdata 進(jìn)行正確的處理success: function (data) {data = JSON.parse(data);data.forEach((i)=>{console.log(i.code,i.file_url);});},error: function () {alert("aaa上傳失敗!");},});}三、拖拽上傳
默認(rèn)文本、圖像和鏈接可以被拖動(dòng)。其它的元素想要被拖動(dòng),只需為標(biāo)簽加一個(gè)draggable="true"屬性
<div draggable="true"><div/>?HTML5 API drag 和 drop
被拖動(dòng)元素發(fā)生的事件dragstart 被拖動(dòng)元素開(kāi)始拖動(dòng)時(shí)drag 正在被拖動(dòng)時(shí)dragend 取消拖拽時(shí)目標(biāo)元素發(fā)生的事件(當(dāng)某元素被綁定以下事件就變成了目標(biāo)元素)dragenter 拖動(dòng)元素進(jìn)入目標(biāo)上觸發(fā)dragover 拖動(dòng)元素在目標(biāo)元素上移動(dòng)觸發(fā)dragleave 拖動(dòng)元素離開(kāi)目標(biāo)時(shí)觸發(fā)drop 拖動(dòng)元素在目標(biāo)上釋放觸發(fā),這時(shí)不會(huì)觸發(fā)dragleave注意:1.目標(biāo)元素默認(rèn)不能夠被拖放drop,要在dragover事件中取消默認(rèn)事件(e.preventDefault())2.有些元素(img)被拖放后,默認(rèn)以鏈接形式打開(kāi),要在drop事件中取消默認(rèn)事件(e.preventDefault())【火狐瀏覽器可能不頂用,需要再加event.stopPropagation()】dataTransfer(事件對(duì)象屬性(對(duì)象))數(shù)據(jù)交換:只是簡(jiǎn)單的拖拽沒(méi)有意義,我們還需要數(shù)據(jù)交換,即被拖動(dòng)元素和目標(biāo)元素之間的數(shù)據(jù)交換。方法:setData(key,value) 設(shè)置數(shù)據(jù)(key和value都必須是string類型)getData(key) 獲取數(shù)據(jù)clearData() 清除數(shù)據(jù)(不傳參清空所有數(shù)據(jù))setDragImage(imgElement,x,y) 設(shè)置元素移動(dòng)過(guò)程中的圖像(參數(shù):圖像元素,xy表示圖像內(nèi)的偏移量)屬性:dropEffect 表示被拖動(dòng)元素可以執(zhí)行哪一種放置行為(一般在dragover事件內(nèi)設(shè)置)none禁止放置(默認(rèn)值) move移動(dòng)到新的位置 copy復(fù)制到新的位置 linkeffectAllowed 用來(lái)指定拖動(dòng)時(shí)被允許的行為(一般無(wú)需設(shè)置)copy,move,link,copyLink,copyMove,linkMove,all,none,uninitialized默認(rèn)值,相當(dāng)于all.files FileList對(duì)象。如果拖動(dòng)的不是文件,此為空列表 items 返回DataTransferItems對(duì)象,該對(duì)象代表了拖動(dòng)數(shù)據(jù)。types 返回一個(gè)DOMStringList對(duì)象,該對(duì)象包括了存入dataTransfer中數(shù)據(jù)的所有類型。注意:1.如果拖拽了文本,瀏覽器會(huì)自動(dòng)調(diào)用setData(),設(shè)置對(duì)應(yīng)文本數(shù)據(jù)該功能沒(méi)有Demo
參考鏈接:
https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API
https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer
https://www.zhangxinxu.com/wordpress/2018/09/drag-drop-datatransfer-js/
http://www.sohu.com/a/198973397_291052
四、上傳限制
<input type="file" accept="image/*" /> 接收全部格式的圖片此外,獲取到的File對(duì)象中有type屬性可以得知文件類型,size屬性的得知文件大小
五、上傳進(jìn)度、上傳速度、中途取消上傳
原生API
xhr.onload = function(e){};//上傳請(qǐng)求完成 xhr.onerror = function(e){};//上傳異常 xhr.upload.onloadstart = function(e){};//開(kāi)始上傳 xhr.upload.onprogress =function(e){};//上傳進(jìn)度 這個(gè)方法會(huì)在文件每上傳一定字節(jié)時(shí)調(diào)用e.loaded//表示已經(jīng)上傳了多少byte的文件大小 e.total//表示文件總大小為多少byte 通過(guò)這兩個(gè)關(guān)鍵的屬性就可以去計(jì)算 上傳進(jìn)度與速度xhr.onreadystatechange = function(){}//當(dāng)xhr的狀態(tài)(上傳開(kāi)始,結(jié)束,失敗)變化時(shí)會(huì)調(diào)用 該方法可以用來(lái)接收服務(wù)器返回的數(shù)據(jù)中途取消上傳 xhr.abort();單文件上傳 或 多文件串行上傳 Demo:(該Demo只會(huì)有一個(gè)進(jìn)度條,顯示上傳總進(jìn)度。對(duì)應(yīng)“異步上傳”的代碼)
xhr.upload.addEventListener("progess",progessSFunction,false); // 上傳過(guò)程中顯示進(jìn)度和速度f(wàn)unction progressSFunction(e) {var progressBar = document.getElementById(`pro`);var percentageDiv = document.getElementById(`per`);if (e.lengthComputable) // lengthComputable表示進(jìn)度信息是否可用{progressBar.max = e.total;progressBar.value = e.loaded;let speed = (e.loaded - progress[0].last_laoded) / (e.timeStamp - progress[0].last_time) + " bytes/s";let percent = Math.round(e.loaded / e.total * 100) + "%";progress[0].last_laoded = e.loaded, progress[0].last_time = e.timeStamp;percentageDiv.innerHTML = percent + " " + speed;}}多文件并行上傳進(jìn)度顯示:(多個(gè)進(jìn)度條,分別上傳)
// 多文件并行上傳function ParallelUploadFile() {last_laoded = 0;last_time = (new Date()).getTime();var fileObj = document.getElementById("file").files; // js 獲取文件對(duì)象for (let k = 0; k < fileObj.length; k++){let domStr = `<div> ${fileObj[k].name},大小${fileObj[k].size}字節(jié)<progress class='progressBar' id='pro${k}' value='' max=''></progress><span class='percentage' id='per${k}'></span></div>`;$("body").append(domStr);// FormData 對(duì)象var form = new FormData();form.append("author", "xueba"); // 可以增加表單數(shù)據(jù)form.append("csrfmiddlewaretoken", $("[name = 'csrfmiddlewaretoken']").val());form.append("file", fileObj[k]);// XMLHttpRequest 對(duì)象{#var xhr = new XMLHttpRequest();#}{#xhr.open("post", "/file_upload/", true);#}{#xhr.onload = function () {#}{# alert("上傳完成!");#}{# };#}{#xhr.upload.addEventListener("progress", progressFunction, false);#}{#xhr.send(form);#}// jQuery ajax$.ajax({url: "/file_upload/",type: "POST",async: true, // 異步上傳data: form,contentType: false, // 必須false才會(huì)自動(dòng)加上正確的Content-TypeprocessData: false, // 必須false才會(huì)避開(kāi)jQuery對(duì) formdata 的默認(rèn)處理。XMLHttpRequest會(huì)對(duì) formdata 進(jìn)行正確的處理xhr: function () {let xhr = $.ajaxSettings.xhr();xhr.upload.addEventListener("progress", (e) => {progressPFunction(e, k)}, false);xhr.upload.onloadstart = (e) => {progress[k] = {last_laoded: 0,last_time: e.timeStamp,};};xhr.upload.onloadend = () => {delete progress[k];};return xhr;},success: function (data) {data = JSON.parse(data);data.forEach((i) => {console.log(i.code, i.file_url);});},error: function () {alert("aaa上傳失敗!");},});}}六、預(yù)覽文件
預(yù)覽圖片
function onchanges() { // input file綁定onchange事件let files = document.getElementById("file").files;if(files[0].type.indexOf("image")>-1){let read = new FileReader();read.onload = function(e) { // 讀取操作完成時(shí)觸發(fā)let img = new Image();img.src = e.target.result; // 將base64編碼賦給src屬性$("body")[0].appendChild(img);};read.readAsDataURL(files[0]); // 讀取文件轉(zhuǎn)化成base64編碼} }?七、前后端匯總Demo
前端
HTML
<input type="file" id="file" name="myfile" οnchange="onchanges()" multiple="multiple"/> <input type="button" οnclick="SerialUploadFile()" value="上傳"/>JavaScript
let progress = {};let last_laoded;let last_time;function onchanges() {let files = document.getElementById("file").files;console.log(`共${files.length}個(gè)文件`);let countSize = 0;for (let i = 0; i < files.length; i++) {console.log(`${files[i].name} 大小${files[i].size}`);countSize += files[i].size;}console.log(`共計(jì)占用${countSize}字節(jié)`);if (files[0].type.indexOf("image") > -1){let read = new FileReader();read.onload = function (e) { // 讀取操作完成時(shí)觸發(fā)let img = new Image();img.src = e.target.result; // 將base64編碼賦給src屬性$("body")[0].appendChild(img);};read.readAsDataURL(files[0]); // 讀取文件轉(zhuǎn)化成base64編碼}}// 多文件并行上傳function ParallelUploadFile() {last_laoded = 0;last_time = (new Date()).getTime();var fileObj = document.getElementById("file").files; // js 獲取文件對(duì)象for (let k = 0; k < fileObj.length; k++){let domStr = `<div> ${fileObj[k].name},大小${fileObj[k].size}字節(jié)<progress class='progressBar' id='pro${k}' value='' max=''></progress><span class='percentage' id='per${k}'></span></div>`;$("body").append(domStr);// FormData 對(duì)象var form = new FormData();form.append("author", "xueba"); // 可以增加表單數(shù)據(jù)form.append("csrfmiddlewaretoken", $("[name = 'csrfmiddlewaretoken']").val());form.append("file", fileObj[k]);// XMLHttpRequest 對(duì)象{#var xhr = new XMLHttpRequest();#}{#xhr.open("post", "/file_upload/", true);#}{#xhr.onload = function () {#}{# alert("上傳完成!");#}{# };#}{#xhr.upload.addEventListener("progress", progressFunction, false);#}{#xhr.send(form);#}// jQuery ajax$.ajax({url: "/file_upload/",type: "POST",async: true, // 異步上傳data: form,contentType: false, // 必須false才會(huì)自動(dòng)加上正確的Content-TypeprocessData: false, // 必須false才會(huì)避開(kāi)jQuery對(duì) formdata 的默認(rèn)處理。XMLHttpRequest會(huì)對(duì) formdata 進(jìn)行正確的處理xhr: function () {let xhr = $.ajaxSettings.xhr();xhr.upload.addEventListener("progress", (e) => {progressPFunction(e, k)}, false);xhr.upload.onloadstart = (e) => {progress[k] = {last_laoded: 0,last_time: e.timeStamp,};};xhr.upload.onloadend = () => {delete progress[k];};return xhr;},success: function (data) {data = JSON.parse(data);data.forEach((i) => {console.log(i.code, i.file_url);});},error: function () {alert("aaa上傳失敗!");},});}}// 多文件串行上傳function SerialUploadFile() {var fileObj = document.getElementById("file").files; // js 獲取文件對(duì)象let domStr = `<div><progress class='progressBar' id='pro' value='' max=''></progress><span class='percentage' id='per'></span></div>`;$("body").append(domStr);// FormData 對(duì)象var form = new FormData();form.append("author", "xueba"); // 可以增加表單數(shù)據(jù)for (let i = 0; i < fileObj.length; i++){form.append("file", fileObj[i]); // 文件對(duì)象}// jQuery ajax$.ajax({url: "/file_upload/",type: "POST",async: true, // 異步上傳data: form,contentType: false, // 必須false才會(huì)自動(dòng)加上正確的Content-TypeprocessData: false, // 必須false才會(huì)避開(kāi)jQuery對(duì) formdata 的默認(rèn)處理。XMLHttpRequest會(huì)對(duì) formdata 進(jìn)行正確的處理xhr: function () {let xhr = $.ajaxSettings.xhr();xhr.upload.addEventListener("progress", progressSFunction, false);xhr.upload.onloadstart = (e) => {progress[0] = {last_laoded: 0,last_time: e.timeStamp,};console.log("開(kāi)始上傳",progress);};xhr.upload.onloadend = () => {delete progress[0];console.log("結(jié)束上傳",progress);};return xhr;},success: function (data) {data = JSON.parse(data);data.forEach((i) => {console.log(i.code, i.file_url);});},error: function () {alert("aaa上傳失敗!");},});}// jQuery版本進(jìn)度條function Progressbar(e) {var bar = $("#progressBar"); // 進(jìn)度條var num = $("#percentage"); // 百分比if (e.lengthComputable) {bar.attr("max", e.total);bar.attr("value", e.loaded);num.text(Math.round(e.loaded / e.total * 100) + "%");}}// 原生js版 并行進(jìn)度條function progressPFunction(e, k) {var progressBar = document.getElementById(`pro${k}`);var percentageDiv = document.getElementById(`per${k}`);if (e.lengthComputable) {progressBar.max = e.total;progressBar.value = e.loaded;let speed = (e.loaded - progress[k].last_laoded) / (e.timeStamp - progress[k].last_time) + " bytes/s";let percent = Math.round(e.loaded / e.total * 100) + "%";progress[k].last_laoded = e.loaded, progress[k].last_time = e.timeStamp;percentageDiv.innerHTML = percent + " " + speed;console.log(speed);}}// 原生js 串行進(jìn)度條function progressSFunction(e) {var progressBar = document.getElementById(`pro`);var percentageDiv = document.getElementById(`per`);if (e.lengthComputable) // lengthComputable表示進(jìn)度信息是否可用{progressBar.max = e.total;progressBar.value = e.loaded;let speed = (e.loaded - progress[0].last_laoded) / (e.timeStamp - progress[0].last_time) + " bytes/s";let percent = Math.round(e.loaded / e.total * 100) + "%";progress[0].last_laoded = e.loaded, progress[0].last_time = e.timeStamp;percentageDiv.innerHTML = percent + " " + speed;}}Django后端
def file_upload(request):"ajax文件上傳功能"resList, fileList = [], request.FILES.getlist("file")dir_path = 'static/files/{0}/{1}/{2}'.format(time.strftime("%Y"),time.strftime("%m"),time.strftime("%d"))if os.path.exists(dir_path) is False:os.makedirs(dir_path)for file in fileList:file_path = '%s/%s' % (dir_path, file.name)file_url = '/%s/%s' % (dir_path, file.name)res = {"code": 0, "file_url": ""}with open(file_path, 'wb') as f:if f == False:res['code'] = 1for chunk in file.chunks(): # chunks()代替read(),如果文件很大,可以保證不會(huì)拖慢系統(tǒng)內(nèi)存f.write(chunk)res['file_url'] = file_urlresList.append(res)return HttpResponse(json.dumps(resList))參考:
https://www.cnblogs.com/potatog/p/9342448.html
https://www.w3cmm.com/ajax/progress-events.html
?
轉(zhuǎn)載于:https://www.cnblogs.com/V587Chinese/p/11371380.html
總結(jié)
以上是生活随笔為你收集整理的HTML文件上传与下载的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 浦发银行信用卡还款日能改吗?还款日当天还
- 下一篇: 对前端Jenkins自动化部署的研究