環(huán)境
- python 3.6 你換成其他3x的版本也沒(méi)關(guān)系
- flask
項(xiàng)目很小俭尖,主要是演示一下使用flask接收頁(yè)面上傳的文件的方法萨醒。項(xiàng)目包含一個(gè)批量示范頁(yè)面。
演示頁(yè)面文件,為了方便演示。我把html其监,css和js寫(xiě)在一起了
演示頁(yè)面 demo.html
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
<link rel="stylesheet" href="/static/css/bootstrap.min.css"> <!--bootstrap樣式,請(qǐng)自行下載限匣,版本v3-->
<script src="/static/js/jquery-3.2.1.min.js"></script> <!--jquery腳本抖苦,請(qǐng)自行下載,版本不要求-->
<script src="/static/js/bootstrap.min.js"></script> <!--bootstrap腳本,請(qǐng)自行下載,版本v3-->
<style>
body {
font-size: 14px;
}
.my_div {
min-height: 400px;
background-color: #f7f7f7;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.my_div .json_div {
min-width: 100%;
padding: 10px;
}
.my_div .json_div >.json_div_inner {
min-width: 100%;
min-height: 50px;
border: 1px solid #d3d3d3;
border-radius: 5px;
padding: 10px;
}
.my_div .json_div >.json_div_inner >li {
margin-left: 20px;
}
.my_div #my_progress {
width: 96%;
position: relative;
}
.my_div #my_progress >.my_per {
color: #ff7d7f;
position: absolute;
display: block;
width: 4em;
top: 1px;
left: 50%;
}
.my_div .my_bottom {
padding: 10px 10px 20px;
}
.my_div .my_bottom >#select_image {
display: none;
}
</style>
<title>批量上傳演示頁(yè)</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-lg-6 col-lg-offset-3 col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12 my_div">
<div class="json_div">
<ul class="json_div_inner"></ul>
</div>
<div id="my_progress" class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
</div>
<span class="my_per">0%</span>
</div>
<div class="my_bottom">
<!--
<input id="select_image" type="file" accept="image/*" capture="camera" multiple>
accept表示打開(kāi)系統(tǒng)文件目錄
accept="image/*" 打開(kāi)相冊(cè)
accept="video/*" 打開(kāi)視頻
capture 指調(diào)用哪些設(shè)備
capture='microphone' 調(diào)用錄音機(jī)
capture='camera' 調(diào)用相機(jī)
capture='camcorder' 調(diào)用攝像機(jī)
file類型的input還有一個(gè) multiple 的單值屬性.表示同時(shí)提供打開(kāi)文件和設(shè)備的選項(xiàng).
IOS中, multiple 屬性無(wú)效. 必須寫(xiě)2個(gè)input來(lái)自行實(shí)現(xiàn)選擇.
判斷是否是蘋(píng)果手機(jī)的方法:
navigator.userAgent.toLowerCase() == "iphone os"
由于目前拍攝的照片尺寸過(guò)大,所以暫時(shí)關(guān)閉拍照的功能(沒(méi)有capture="camera" 和 multiple屬性)
-->
<input id="select_image" type="file" multiple size="200">
<button id="select_btn" class="btn btn-default btn-primary">選擇</button>
<button id="upload_btn" class="btn btn-default btn-primary">上傳</button>
</div>
</div>
</div>
</div>
</body>
<script>
function upload_progress(event, progress_cb){
/*
上傳進(jìn)度處理函數(shù)
:params event: 文件上傳事的事件,
:params progress_cb: 回調(diào)函數(shù),本函數(shù)會(huì)把上傳完成的百分?jǐn)?shù)當(dāng)作地一個(gè)參數(shù)傳入此回調(diào)函數(shù).
默認(rèn)情況下.會(huì)在控制臺(tái)打印上傳完成度. 注意,100并不代表服務(wù)端完整的接收到了文件.
只代表頁(yè)面已經(jīng)發(fā)送完了所有的文件內(nèi)容.
*/
if (event.lengthComputable) {
var complete_percent = Math.round(event.loaded * 100 / event.total);
var handler = progress_cb?progress_cb: function(num){console.log(`上傳完成度:${num}`)};
handler(complete_percent);
}else{}
}
function upload_complete(event, success_cb){
/*
上傳文件success時(shí)的事件,只要服務(wù)器返回狀態(tài)碼200,就會(huì)執(zhí)行本函數(shù),并并不是代表服務(wù)器返回了正確的信息.
根據(jù)實(shí)際需要可以覆蓋.
:params event: 文件上傳事的事件,一般由XMLHttpRequest的upload的事件監(jiān)聽(tīng)器來(lái)傳遞事件.
:params success_cb: 成功時(shí)的回調(diào)函數(shù),
:return: nothing
*/
let str = event.target.responseText;
let handler = success_cb? success_cb: function(a){console.log(a);};
handler(str);
}
function upload_error(event, error_cb){
/*
上傳文件失敗時(shí)的事件,根據(jù)實(shí)際需要可以覆蓋.
:params event: 文件上傳事的事件,一般由XMLHttpRequest的upload的事件監(jiān)聽(tīng)器來(lái)傳遞事件.
:params error_cb: 失敗時(shí)的回調(diào)函數(shù),
:return: nothing
*/
let handler = error_cb? error_cb: function(a){console.log(event);};
handler(event);
}
function batch_upload(options){
/*
批量上傳文件. 不限制文件大小
options = {
files: 數(shù)據(jù)的序列,
url: str,
headers: 鍵值對(duì)對(duì)象,
success_cb: function,
error_cb: function,
progress_cb: function,
}
:params files: input標(biāo)簽的files
:params url: 上傳的服務(wù)器url
:params headers: 放入header的參數(shù),是鍵值對(duì)形式的字典,鍵名不要用下劃線,因?yàn)槟遣环弦?guī)范
:params success_cb: 成功時(shí)的回調(diào)函數(shù),會(huì)把服務(wù)器的返回信息作為第一個(gè)參數(shù)傳入此回調(diào)函數(shù).
:params error_cb: 失敗時(shí)的回調(diào)函數(shù),會(huì)把錯(cuò)誤信息作為第一個(gè)參數(shù)傳入此回調(diào)函數(shù).
:params progress_cb: 上傳時(shí)的返回上傳進(jìn)度的回調(diào)函數(shù),會(huì)把頁(yè)面上傳文件的百分書(shū)作為第一個(gè)參數(shù)傳入此回調(diào)函數(shù)..
:return: 不返回?cái)?shù)據(jù),由回調(diào)函數(shù)返回.
有關(guān)XMLHttpRequest對(duì)象的詳細(xì)信息,請(qǐng)參考.
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
有關(guān)XMLHttpRequest.send方法的詳細(xì)文檔地址:
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/send
*/
let files = options['files'];
let file_data = options['file_data'];
let url = options['url'];
let headers = options['headers'];
let success_cb = options['success_cb'];
let error_cb = options['error_cb'];
let progress_cb = options['progress_cb'];
let prog_func = function(event){upload_progress(event, progress_cb)}; // 進(jìn)度的回調(diào)函數(shù)
let comp_func = function(event){upload_complete(event, success_cb)}; // 成功時(shí)的回調(diào)函數(shù)
let erro_func = function(event){upload_error(event, error_cb)}; // 失敗時(shí)的回調(diào)函數(shù)
// 構(gòu)造數(shù)據(jù)容器
let data = new FormData();
for(let file of files){
data.append(file.name, file);
}
// 新建一個(gè)請(qǐng)求對(duì)象
let req = new XMLHttpRequest();
// 添加事件監(jiān)聽(tīng)器
req.upload.addEventListener("progress", prog_func, false);
req.addEventListener("load", comp_func, false);
req.addEventListener("error", erro_func, false);
req.addEventListener("abort", erro_func, false);
req.open("post", url);
// 必須在open之后才能給請(qǐng)求頭賦值
if(headers){
/*
* 傳送請(qǐng)求頭信息,目前服務(wù)端還未做對(duì)應(yīng)的處理.這只是與被給后來(lái)使用的.
* */
for(let k in headers){
req.setRequestHeader(k, headers[k]);
}
}
try{
req.send(data); // 404錯(cuò)誤會(huì)直接在此拋出
}catch(e){
let handler = error_cb? error_cb: function(ms){console.log(ms);};
handler(e);
}
}
/*擴(kuò)展函數(shù)注冊(cè)區(qū)域*/
$.extend({
batch_upload: batch_upload // 批量上傳文件锌历,無(wú)尺寸限制
});
// 點(diǎn)擊選擇按鈕的事件
$("#select_btn").click(function(){
$("#select_image").click();
});
let progress = function(per){
// 處理上傳進(jìn)度
console.log(`per is ${per}`);
$("#my_progress>.progress-bar").attr("style", `width: ${per}%;`).attr("aria-valuenow", `${per}`);
if(per > 52){
$(".my_per").css("color", "#fff");
}
$(".my_per").text(`${per}%`);
};
// 上傳圖片按鈕事件
$("#select_image").change(function(){
let ul = $(".json_div_inner:first");
ul.empty();
let files = this.files;
for(let f of files){
ul.append(`<li id="${f.name}"><span>${f.name}</span></li>`);
}
});
let call_back = function(){
$("#upload_btn, #select_btn").attr("disabled", false).removeClass("disabled");
alert("操作結(jié)束!");
};
// 上傳圖片模態(tài)框,提交按鈕事件
$("#upload_btn").click(function(){
let $obj = $("#select_image");
let files = $obj[0].files;
let opts = {
"url": "/file/save",
"files": files,
"success_cb": call_back,
"error_cb": call_back,
"progress_cb": progress
};
$("#upload_btn, #select_btn").attr("disabled", true).addClass("disabled");
$.batch_upload(opts);
});
</script>
</html>
服務(wù)端的文件也很簡(jiǎn)單 server.py
# -*- coding: utf-8 -*-
from flask import Flask
from flask import render_template
from flask import request
import json
import os
app = Flask(__name__)
port = 7002
root_dir = os.path.dirname(os.path.realpath(__file__))
resource_dir = os.path.join(root_dir, 'resource')
@app.route("/")
def upload_demo():
"""上傳頁(yè)面"""
return render_template("upload_demo.html", page_title="批量上傳")
@app.route("/file/<action>", methods=['post', 'get'])
def file_func(action):
"""
:param action: 動(dòng)作, save/get(保存/獲取)
:return:
"""
mes = {"message": "success"}
if action == "save":
"""保存文件"""
if os.path.exists(resource_dir):
pass
else:
os.makedirs(resource_dir)
for key_name, file_storage in request.files.items():
if file_storage is not None:
file_name = file_storage.filename
file_storage.save(os.path.join(resource_dir, file_name))
file_storage.close()
elif action == "get":
"""獲取文件/圖片,未實(shí)現(xiàn)"""
pass
return json.dumps(mes)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=port, debug=True, threaded=True)
項(xiàng)目的目錄結(jié)構(gòu)也很簡(jiǎn)單贮庞。
2018-09-27 15-40-54屏幕截圖.png