1、上傳到本身的服務器:css
使用 AdminLTE 2 網站中的 form 表單的代碼:https://adminlte.io/themes/AdminLTE/pages/forms/general.html (使用其原先css樣式及HTML代碼,代碼內容通過精簡)html
前端代碼:前端
首先:一個帶有輸入框跟上傳按鈕的HTML文件(樣式圖以下:)python
1 <div class="form-group"> 2 <label for="thumbnail-form">縮略圖</label> 3 <div class="input-group"> 4 <input type="text" class="form-control" id="thumbnail-form" name="thumbnail"> 5 <span class="input-group-btn"> 6 <input type="file" class="btn btn-default" id="thumbnail-btn" value="上傳文件"> 7 </span> 8 </div> 9 </div>
使用 input標籤 , type=file 時顯示的樣式;jquery
將上圖修改成下圖的樣式,使其更美觀;git
1 <div class="form-group"> 2 <label for="thumbnail-form">縮略圖</label> 3 <div class="input-group"> 4 <input type="text" class="form-control" id="thumbnail-form" name="thumbnail"> 5 <span class="input-group-btn"> 6 <label class="btn btn-default btn-file"> 7 上傳圖片<input hidden type="file" class="btn btn-default" id="thumbnail-btn"> 8 </label> 9 </span> 10 </div> 11 </div>
id = "thumbnail-form":輸入框的ID;github
id = "thumbnail-btn":做爲隱藏起來的 file標籤 文件的一個按鈕ID;ajax
使用 label標籤,其中 for 中的值等於 input 的ID:至關於點擊「縮略圖」時會默認點擊下方輸入框;點擊「上傳圖片」會默認點擊「type=file」的標籤;兩者之間存在關聯;django
使用 hidden 將 file標籤 隱藏起來,使「上傳圖片」的字體將其覆蓋;json
使用js文件監聽其上傳時的點擊事件;
1 function News() { 2 3 } 4 5 News.prototype.run = function () { 6 var self = this; 7 self.ListenUploadFileEvent(); 8 }; 9 10 // 監聽文件上傳的事件; 11 News.prototype.ListenUploadFileEvent = function () { 12 var uploadBtn = $('#thumbnail-btn'); 13 uploadBtn.change(function () { 14 var file = uploadBtn[0].files[0]; 15 var formData = new FormData(); 16 formData.append('file', file); 17 xfzajax.post({ 18 'url': '/cms/upload_file/', 19 'data': formData, 20 'processData': false, 21 'contentType': false, 22 'success': function (result) { 23 if (result['code'] === 200){ 24 var url = result['data']['url']; 25 var thumbnailInput = $('#thumbnail-form'); 26 thumbnailInput.val(url); 27 } 28 } 29 }) 30 }); 31 }; 32 33 $(function () { 34 var news = new News(); 35 news.run(); 36 });
change:用來監聽進入上傳文件選定文件後點擊打開按鈕的事件,此處不能使用 click 點擊事件;
由於 uploadBtn 獲得的是一個集合,不是一個單一的對象。因此 var file = uploadBtn[0].files[0]; 中的 uploadBtn[0] 用來獲取其中的惟一一個按鈕,與其存儲的全部文件中的第一個文件。
formData:用來存儲文件;
formData.append('file', file); :引號中的字段名需與後臺經過 ‘get’ 獲取文件填寫的字段名保持一致;
而後經過ajax請求發送給後端服務器,跳轉的 url 及其數據 formData,後兩個 false 爲告知上層 jquery-3.3.1.min.js 文件不對該文件的數據再次進行處理或添加其它內容,以及成功或者失敗的返回內容(失敗內容定義在ajax文件中);
在後端中獲得的url完整路徑,調用 ‘restful’ 中的 ‘result’ 函數,再將獲取的 ‘url’ 的值給到前端輸入框中顯示。
ajax代碼
1 //static:xfzajax.js 2 function getCookie(name) { 3 var cookieValue = null; 4 if (document.cookie && document.cookie !== '') { 5 var cookies = document.cookie.split(';'); 6 for (var i = 0; i < cookies.length; i++) { 7 var cookie = jQuery.trim(cookies[i]); 8 // Does this cookie string begin with the name we want? 9 if (cookie.substring(0, name.length + 1) === (name + '=')) { 10 cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 11 break; 12 } 13 } 14 } 15 return cookieValue; 16 } 17 18 var xfzajax = { 19 'get': function (args) { 20 args['method'] = 'get'; 21 this.ajax(args); 22 }, 23 'post': function (args) { 24 args['method'] = 'post'; 25 this._ajaxSetup(); 26 this.ajax(args); 27 }, 28 'ajax': function (args) { 29 var success = args['success']; 30 args['success'] = function (result) { 31 if (result['code'] === 200) { 32 if (success) { 33 success(result) 34 } 35 } else { 36 var messageObject = result['message']; 37 if (typeof messageObject == 'string' || messageObject.constructor === String) { 38 window.messageBox.showError(messageObject); 39 } else { 40 // {"password":['密碼最大長度不能超過20爲!','xxx'],"telephone":['xx','x']} 41 for (var key in messageObject) { 42 var messages = messageObject[key]; 43 var message = messages[0]; 44 window.messageBox.showError(message); 45 } 46 } 47 if(success){ 48 success(result) 49 } 50 } 51 }; 52 args['fail'] = function (error) { 53 console.log(error); 54 window.showError('服務器內部錯誤!') 55 }; 56 $.ajax(args); 57 }, 58 '_ajaxSetup': function () { 59 $.ajaxSetup({ 60 beforeSend: function (xhr, settings) { 61 if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type) && !this.crossDomain) { 62 xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); 63 } 64 } 65 }); 66 } 67 };
後端代碼:
settings.py文件的設置:
1 # 在末尾添加內容; 2 3 STATIC_URL = '/static/' 4 STATICFILES_DIRS = [ 5 os.path.join(BASE_DIR,'front','dist'), 6 ] 7 8 MEDIA_URL = '/media/' 9 MEDIA_ROOT = os.path.join(BASE_DIR,'media')
使用MEDIA_ROOT與MEDIA_URL,用來指定文件上傳後存儲的位置,在主目錄中新建‘media’文件用來存儲內容;
文件的映射路徑,與前端 ajax 內容中跳轉的 url 一致。
1 # 總的url連接; 2 3 from django.urls import path,include 4 from django.conf.urls.static import static 5 from django.conf import settings 6 7 urlpatterns = [ 8 path('cms/',include('apps.cms.urls')), 9 ] + static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT) 10 11 12 # cms的url: 13 14 from django.urls import path 15 from . import views 16 17 app_name = 'cms' 18 19 urlpatterns = [ 20 path('upload_file/',views.upload_file,name='upload_file'), 21 ]
視圖函數
1 from django.views.decorators.http import require_POST, 2 from utils import restful 3 from django.conf import settings 4 import os 5 6 7 # 文件上傳; 8 @require_POST 9 def upload_file(request): 10 file = request.FILES.get('file') 11 name = file.name 12 with open(os.path.join(settings.MEDIA_ROOT,name),'wb') as fp: 13 for chunk in file.chunks(): 14 fp.write(chunk) 15 url = request.build_absolute_uri(settings.MEDIA_URL + name) 16 return restful.result(data={'url':url})
使用 「get」 獲取的 「file」 與前端提到 「file」 一致;
使用 build_absolute_uri 能夠獲取完整的url地址;
restful.py文件(簡化每一個文件代碼運行結果的代碼量)
1 from django.http import JsonResponse 2 3 class HttpCode(object): 4 ok = 200 # 正常運行; 5 paramserror = 400 # 參數錯誤; 6 unauth = 401 # 沒受權; 7 methoderror = 405 # 請求方法錯誤; 8 servererror = 500 # 服務器內部錯誤; 9 10 def result(code=HttpCode.ok,message='',data=None,kwargs=None): 11 json_dict = {'code':code,'message':message,'data':data} 12 if kwargs and isinstance(kwargs,dict) and kwargs.keys(): 13 json_dict.update(kwargs) 14 return JsonResponse(json_dict) 15 16 def ok(): 17 return result() 18 19 def params_error(message='',data=None): 20 return result(code=HttpCode.paramserror,message=message,data=data) 21 22 def unauth(message='',data=None): 23 return result(code=HttpCode.unauth,message=message,data=data) 24 25 def method_error(message='',data=None): 26 return result(code=HttpCode.methoderror,message=message,data=data) 27 28 def server_error(message='',data=''): 29 return result(code=HttpCode.servererror,message=message,data=data)
2、上傳到七牛雲
一、登陸註冊七牛雲:https://www.qiniu.com//
二、登陸後進入右上方管理控制檯後,再點擊右上角的我的圖標進入密鑰管理,記住 AK 與 SK ,後續用到;
三、點擊左側 對象存儲,新建存儲空間 bucket 。
後端:
一、下載 python SDK:pip install qiniu;
二、建立一個獲取 token 的 url;
1 # 上傳七牛雲; 2 @require_GET 3 def qntokon(request): 4 access_key = 'GjdN0XJxtrLbINSxx4ZjXXitk7Aux5h046x9viB8' 5 secret_key = '8ULlOQezsKZIRFl_Bie09GZIJyonH50aJH6RONhu' 6 bucket = 'qmspace' 7 # 建立受權信息q; 8 q = qiniu.Auth(access_key,secret_key) 9 # 生成上傳憑證,傳給前端; 10 tokon = q.upload_token(bucket) 11 return restful.result(data={'tokon':tokon})
前端:
基於七牛 API 開發的前端 JavaScript SDK:https://github.com/qiniu/js-sdk(詳細的官方文檔講解)
在HTML代碼的縮略圖代碼下添加進度條代碼,默認隱藏;
1 <!--html代碼--> 2 <script src="https://unpkg.com/qiniu-js@2.4.0/dist/qiniu.min.js 3 "></script> 4 --snip-- 5 <div class="form-group"> 6 <label for="thumbnail-form">縮略圖</label> 7 --snip-- 8 </div> 9 </div> 10 <!--上傳進度條--> 11 <div class="form-group" id="progress-group" style="display: none;"> 12 <div class="progress"> 13 <div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="20" 14 aria-valuemin="0" aria-valuemax="100" style="width: 0"> 15 0% 16 </div> 17 </div> 18 </div>
經過 js 代碼實現與七牛雲的對接及文件的上傳,以及進度條的顯示狀況;
1 // 上傳到七牛雲; 2 News.prototype.ListenQiniuuploadFileEvent = function(){ 3 var self = this; 4 var uploadBtn = $('#thumbnail-btn'); 5 uploadBtn.change(function () { 6 var file = this.files[0]; 7 xfzajax.get({ 8 'url': '/cms/qntokon/', 9 'success': function (result) { 10 if (result['code'] ===200 ){ 11 12 // token: 上傳驗證信息,前端經過接口請求後端得到; 13 var tokon = result['data']['tokon']; 14 15 // key: 文件資源名(當前時間+文件格式); 16 var key = (new Date()).getTime() + '.' + file.name.split('.')[-1]; 17 18 var putExtra = { 19 fname: key, 20 params: {}, 21 mimeType: ['image/png','image/jpeg','image/gif','video/x-ms-wmv','video/mp4','video/x-flv'] 22 }; 23 24 var config = { 25 useCdnDomain: true, 26 retryCount: 6, 27 region: qiniu.region.z2 28 }; 29 30 // 文件上傳; 31 var observable = qiniu.upload(file,key,tokon,putExtra,config); 32 observable.subscribe({ 33 'next': self.handFileUploadProcess, 34 'error': self.handFileUploadError, 35 'complete': self.handFileUploadComplete, 36 }) 37 } 38 } 39 }) 40 }) 41 }; 42 43 News.prototype.handFileUploadProcess = function(response){ 44 var total = response.total; 45 var percent = total.percent; 46 var percentText = percent.toFixed() + '%'; 47 var progressGroup = News.progressGroup; 48 progressGroup.show(); 49 var progressBar = $('.progress-bar'); 50 progressBar.css({"width":percentText}); 51 progressBar.text(percentText); 52 }; 53 54 News.prototype.handFileUploadError = function(error){ 55 window.messageBox.showError(error.message); 56 var progressGroup = News.progressGroup; 57 progressGroup.hide(); 58 console.log(error.message); 59 }; 60 61 News.prototype.handFileUploadComplete = function(response){ 62 console.log(response); 63 var progressGroup = News.progressGroup; 64 progressGroup.hide(); 65 var domain = 'http://ps96zui1h.bkt.clouddn.com/'; 66 var filename = response.key; 67 var url = domain + filename; 68 var thumbnailInput = $("input[name='thumbnail']"); 69 thumbnailInput.val(url) 70 }; 71 72 $(function () { 73 var news = new News(); 74 news.run(); 75 News.progressGroup = $('#progress-group'); 76 });
(官方文檔很詳細,再也不作我的註解)
上傳成功: