- vue断点续传代码(主要部分)
//重传;合并错误的文件删掉重新上传
reUpload(file, queueId) {
//获取文件对象
let that = this;
var total = file.size;//文件二进制总大小
var filename = file.name;
var identifier = filename + total + that.userId;//此文件的唯一标识(非块)
api.appReupload(queueId, identifier)
.then(res => {
console.log(res)
api.showVuxToast(that, res.data.message);
if (res.data && res.data.status) {
//重传准备完毕,可以开始上传
that.clickUploadBtn();
}
})
.catch(e => {
api.showVuxToast(that, "请求出错", "cancel");
})
},
//点击重传按钮
reUploadClick(id) {
this.stop = false;
this.switchUploadAndStop = false;
this.recentPerText = "重传准备中.."
let that = this;
let photoInfo = "";
for (let index in that.photoList) {
photoInfo = that.photoList[index];
if (photoInfo.id == id) {
break;
}
}
let path = photoInfo.path;// 通过路径获得文件
console.log("文件的全路径为==" + path);
if (window.LocalFileSystem != undefined) {//获得权限
let fileDir = path.substring(0, path.lastIndexOf("/"));
let fileName = path.substring(path.lastIndexOf("/") + 1);
window.resolveLocalFileSystemURL(fileDir, function (dirEntry) {//先获得路径文件夹,,
console.log("找到了文件所在路径=" + dirEntry.toURL())
dirEntry.getFile(fileName, {create: false}, function (fileEntry) {//读取文件
console.log("读取到了文件");
fileEntry.file(function (file) {//开始分块上传
console.log("调用重传方法")
that.reUpload(file, id);
}, onErrorReadFile);
}, onErrorGetFile)
}, onErrorLoadFs);
} else {
console.log("环境不支持")
}
function onErrorReadFile(msg) {
that.$vux.toast.text("读取文件失败");
console.log("读取文件失败=" + msg.toString())
}
function onErrorLoadFs(msg) {
that.$vux.toast.text("读取目录失败");
console.log("读取目录失败=" + msg.toString())
}
function onErrorGetFile(msg) {
that.$vux.toast.text("未找到文件");
console.log("未找到文件=" + msg.toString())
}
},
//计算进度
callPercent(cuLoaded, total) {
let percent = Math.round((cuLoaded / total) * 100);//计算上传百分比
if (this.photoList[0]) {
this.photoList[0].percent = percent + "%";
this.totalCount = this.photoList.length;
this.recentPercent = percent;//必须给外面一个赋值,不然数值渲染不上去
} else {
this.totalCount = 0;
this.recentPercent = percent;
this.recentPerText = "";
}
},
//点击暂停按钮
stopUploadClick() {
this.stop = true;
this.switchUploadAndStop = true;
this.recentPerText = "正在暂停"
},
//点击上传按钮(仅支持单个文件上传)
clickUploadBtn() {
this.stop = false;
this.switchUploadAndStop = false;
this.recentPerText = "准备上传中.."
this.uploadClick();
},
//上传(仅支持单个文件上传)
uploadClick() {
let that = this;
//获取第一个文件
that.photoInfo = that.photoList[0];
if (that.photoInfo) {
if (Vue.cordova.device.platform === "Android") {//是安卓的
//控制按钮显示隐藏
that.switchUploadAndStop = false;
// 通过路径获得文件
let path = that.photoInfo.path;
//获得权限
if (window.LocalFileSystem != undefined) {
let fileDir = path.substring(0, path.lastIndexOf("/"));
let fileName = path.substring(path.lastIndexOf("/") + 1);
//先获得路径文件夹,,
window.resolveLocalFileSystemURL(fileDir, function (dirEntry) {
//在当前文件夹里获得这个名字的文件
dirEntry.filesystem.root.getFile(fileName, {create: false}, function (fileEntry) {
//读取文件
fileEntry.file(function (file) {
//开始分块上传
that.toChunkUpload(file, path);
}, onErrorReadFile);
}, onErrorGetFile)
}, onErrorLoadFs);
} else {
console.log("环境不支持")
}
} else {//ios
that.switchUploadAndStop = false;//控制上传和暂停按钮显示隐藏
let path = that.photoInfo.path;// 通过路径获得文件
console.log("文件的全路径为==" + path);
if (window.LocalFileSystem != undefined) {//获得权限
let fileDir = path.substring(0, path.lastIndexOf("/"));
let fileName = path.substring(path.lastIndexOf("/") + 1);
window.resolveLocalFileSystemURL(fileDir, function (dirEntry) {//先获得路径文件夹,,
console.log("找到了文件所在路径=" + dirEntry.toURL())
dirEntry.getFile(fileName, {create: false}, function (fileEntry) {//读取文件
console.log("读取到了文件");
fileEntry.file(function (file) {//开始分块上传
console.log("开始分块上传")
that.toChunkUpload(file, path);
}, onErrorReadFile);
}, onErrorGetFile)
}, onErrorLoadFs);
} else {
console.log("环境不支持")
}
}
} else {
that.$vux.toast.text("队列文件上传完成");
that.recentPerText = "上传完成";
that.switchUploadAndStop = true;//显示上传按钮
that.stop = false;
//查找上传异常的文件
that.getUploadList();
}
function onErrorReadFile(msg) {
that.$vux.toast.text("读取文件失败");
console.log("读取文件失败=" + msg.toString())
that.updateStatusToError();//更新状态为异常
that.uploadNext();
}
function onErrorLoadFs(msg) {
that.$vux.toast.text("读取目录失败");
console.log("读取目录失败=" + msg.toString())
that.updateStatusToError();//更新状态为异常
that.uploadNext();
}
function onErrorGetFile(msg) {
that.$vux.toast.text("未找到文件");
console.log("未找到文件=" + msg.toString())
that.updateStatusToError();//更新状态为异常
that.uploadNext();
}
},
//获得文件分块
toChunkUpload(file, path) {
//获取文件对象
let that = this;
var reader = new FileReader();
var step = that.chunkReadSize;//指定分块大小
var total = file.size;//文件二进制总大小
var filename = file.name;
var cuLoaded = 0;//当前已传大小,相当于下次开始的起始位置
var identifier = filename + total + that.userId;//此文件的唯一标识(非块)
var type = filename.substring(filename.lastIndexOf("."));
//开始读取
readBlob(0);
//指定开始位置,分块读取文件
function readBlob(start) {
//指定开始位置和结束位置读取文件
//判断暂停
if (!that.stop) {
that.recentPerText = "上传中...";
//判断这个块有没有被上传
api
.androidCheckChunk(identifier, start, that.userId)
.then(res => {
if (!res.data.data) {
var blob = file.slice(start, start + step);
reader.readAsArrayBuffer(blob);
} else {
// console.log('0-该块已上传');
//已上传的继续下一个块
cuLoaded += step;
if (cuLoaded < total) {
that.callPercent(cuLoaded, total);
readBlob(cuLoaded);
} else {
// console.log("end-上传结束")
cuLoaded = total;
callMergeFile();//调用合并方法
}
}
})
.catch(res => {
that.$vux.toast.text("网络异常,请稍后重试");
that.stopUploadClick()
console.log("error====" + res);
})
} else {
that.stop = false;
that.recentPerText = "已暂停"
}
}
//读取一段成功后执行
reader.onloadend = function () {
//将分段数据上传到服务器
uploadFileChunk(reader.result, function () {
//上传完成回调
// console.info('8-当前已上传大小:' + cuLoaded);
if (cuLoaded < total) {
//如果没有读完,继续
// console.log('3-继续下一个块的上传');
that.callPercent(cuLoaded, total);
readBlob(cuLoaded);
} else {
// console.log('end-上传结束');
cuLoaded = total;
that.callPercent(cuLoaded, total);
callMergeFile();//调用合并方法
}
});
};
//执行上传
function uploadFileChunk(result, onSuccess) {
//封装成blob文件
var unit8blob = new Blob([new Uint8Array(result)]);
var fd = new FormData();
fd.prototype = 'multipart/form-data';//必须加这个类型,识别form表单
fd.append('file', unit8blob);
fd.append('createUserId', that.userId);
fd.append('filename', filename);
fd.append('identifier', identifier);//唯一标识,到时候创建该文件夹的
fd.append('chunkNumber', cuLoaded);
fd.append('currentChunkSize', unit8blob.size);
fd.append('totalSize', total);
fd.append('relativePath', path);
fd.append('type', type);
fd.append('applyId', that.photoInfo.applyId);
//分块下一个的起始位置
cuLoaded += unit8blob.size;
// //配置chunk参数
// console.log("1-开始执行上传");
api
.androidPhotoPost(fd)
.then(res => {
// console.log("2-上传over");
if (onSuccess)
onSuccess();
})
.catch(res => {
// that.$vux.toast.text("上传文件异常,请稍后重试");
console.log(res);
})
}
//调用后台合并方法
function callMergeFile() {
that.recentPerText = "合并中...";
// console.log("开始合并文件");
let fileInfo = {
filename: filename,
identifier: identifier,
totalSize: total,
type: type,
createUserId: that.userId,
applyId: that.photoInfo.applyId, //申请单号
taskId: that.photoInfo.taskId,
resource: '3',//文件类型
subjectInstanceId: that.photoInfo.id,//把队列表里的id给upload,到时两表可以关联
applyVersion: '001'
};
api
.androidChunkMerge(fileInfo, that.photoInfo.id)
.then(res => {
//更新进度信息
that.callPercent(1, 1);
//如果异常就回显显示信息
if (res.data && !res.data.status) {
that.photoInfo.percent = res.data.message;
}
that.uploadNext();//上传下一个文件
})
.catch(res => {
// that.$vux.toast.text("文件合并异常,请稍后重试");
console.log(res);
})
}
},
//上传下一个文件
uploadNext() {
//先把当前列表里的第一个文件删除
this.photoList.shift();
this.callPercent(0, 1);
this.uploadClick();
},
//更新状态为异常
updateStatusToError() {
api
.updateQuestionUploadStatus(this.photoInfo.id, {status: '3'})
},
- cordova获取文件系统工具类
api调用的获取目录的方法就是这个工具类的
/**
* 由于项目使用了finance作为根目录,所以以下方法针对这个目录
*/
import Vue from "vue"
//只在这个页面使用的常量
const Common = {
prefix: "-------------------"
}
/**
* cordova 的一些方法
* removeRecursively 可以删除目录,但是前期没有测试这个方法,所有文件都放在了 finance 文件夹下,区分较为困难,同时,没有一个完整的策略供删除文件夹使用
*/
export default {
/**
* 返回根目录的 dirEntry,用于页面上面拼接完整路径,显示图片
*
* 持久层目录:(存放录音图片等未上传时不可再现的文件,提交后清空)
* <appid....>/Documents/NoCloud/
* 临时目录:(存放从服务器下载的资料包,可重复再现的文件。设置里点击清理缓存的时候清空)
* <appid....>/tmp/
*
* 正式目录请调用另一个方法,获取finance完整路径:上面的加个/finance/
* FSgetRootFinanceEntry
*
* @param successCallback
* @param errorCallback
* @param type 1-持久层目录;2-临时目录;默认 1
* @constructor
*/
FSgetRootDirEntry(successCallback, errorCallback, type) {
if (typeof (Vue.cordova.device) == 'undefined') {
if (typeof errorCallback == 'function') {
errorCallback("环境不支持");
}
return;
}
let rootAndroidFinancePath = '';//安卓根目录
let rootiOSPath = '';//苹果根目录
//默认 持久层目录
if (type !== 1 && type !== 2) {
type = 1;
}
//判断使用哪个目录
/**
* 目录使用说明:由于之前iOS就使用了 requestFileSystem,后面修改有些麻烦,PERSISTENT 目录(document/),所以写公用方法,所有获取文件系统的地方都用此方法
* 1. resolveLocalFileSystemURL 里的路径通过 window.cordova.file.XXX 获取,返回 dirEntry 对象,可以指定任何目录
* 其中:
* ×? iOS——window.cordova.file.dataDirectory——Library/NoCloud/ 这个目录虽然好,但是录音插件不能指定到此目录
* ?? iOS——window.cordova.file.tempDirectory——tmp/
* ?? iOS——window.cordova.file.documentsDirectory——documents/ 同时,下面还有个 NoCloud/,不会同步数据【由于录音文件只能指定到 documents 或者 tmp 文件夹,所以干脆所有的文件都放到这边】
* 其中:安卓的都是永久目录,系统不会自动删除,但是建议不要使用 cache目录存放重要不可恢复的文件
* Android——window.cordova.file.dataDirectory——/data/data/<app-id>/files/
* Android——window.cordova.file.cacheDirectory——/data/data/<app-id>/cache/
* 一般使用下面两个,因为安卓的系统一般把存储空间看成是 sdcard 的空间
* ?? Android——window.cordova.file.externalCacheDirectory——<sdcard>/Android/data/<app-id>/cache/
* ?? Android——window.cordova.file.externalDataDirectory——<sdcard>/Android/data/<app-id>/files/
* 2. requestFileSystem 是获取文件系统,即当前app的根目录,只有 window.LocalFileSystem.PERSISTENT/window.LocalFileSystem.TEMPORARY 两种
* 其中:PERSISTENT 指的是 document目录,是会同步 icloud 【当前用的是这个,如以后有问题,可以修改为使用 1 方法里的 dataDirectory 目录】
* TEMPORARY 指的和 1 里的tmp 文件夹一样
*
* 3. 由于录音文件只能生成到 documents 或 tmp 目录下面,所以只能单独考虑录音文件了,.m4a/.mp3/.wav
*/
if (type === 1) {
rootAndroidFinancePath = window.cordova.file.externalDataDirectory;
rootiOSPath = window.cordova.file.documentsDirectory;
} else {
rootAndroidFinancePath = window.cordova.file.externalCacheDirectory;
rootiOSPath = window.cordova.file.tempDirectory;
}
console.log(Common.prefix + rootiOSPath + "NoCloud");
//判断是 Android iOS
if (Vue.cordova.device.platform === "Android") {
//是安卓的
window.resolveLocalFileSystemURL(rootAndroidFinancePath, function (rootEntry) {
//返回根路径
if (typeof successCallback == 'function') {
successCallback(rootEntry);
}
}, function (e) {
console.log(Common.prefix + "获取文件系统失败" + JSON.stringify(e))
if (typeof errorCallback == 'function') {
errorCallback("获取文件系统失败");
}
});
} else {//iOS
//这个 resolveLocalFileSystemURL 是直接返回一个 dirEntry,可以指定到任何目录
window.resolveLocalFileSystemURL(rootiOSPath, function (rootEntry) {
//判断type,持久层的要兼容NoCloud 文件夹,临时层的不用建
if (type === 1) {
//创建NoCloud文件夹,ios12以前的系统可能没有该文件夹
rootEntry.getDirectory("NoCloud", {create: true, exclusive: false}, function (rootNoCloudEntry) {
//返回根路径
if (typeof successCallback == 'function') {
successCallback(rootNoCloudEntry);
}
}, function (e) {
console.log(Common.prefix + "创建NoCloud文件夹失败" + JSON.stringify(e));
if (typeof errorCallback == 'function') {
errorCallback("创建NoCloud文件夹失败");
}
});
} else {
//返回根路径
if (typeof successCallback == 'function') {
successCallback(rootEntry);
}
}
}, function (e) {
console.log(Common.prefix + "获取文件系统失败" + JSON.stringify(e))
if (typeof errorCallback == 'function') {
errorCallback("获取文件系统失败");
}
});
// 这个是获取文件系统的目录,就两个 persistent和temp ,并且 持久层的目录是 document里,会同步 iCloud
// window.requestFileSystem(rootiOSPath, 0, function (fs) {
// //返回根路径
// if (typeof successCallback == 'function') {
// successCallback(fs.root.toURL());
// }
// }, function (e) {
// console.log("获取文件系统失败" + JSON.stringify(e))
// if (typeof errorCallback == 'function') {
// errorCallback("获取文件系统失败");
// }
// });
}
},
/**
* 获得录音的根目录
* 由于 new Media()的时候,里面的src不支持全路径,只支持 documents:// 或者 直接填文件名(tmp路径),所以这里写个公用的,返回路劲: /documents/Nocloud/finance/
* @returns {string} "documents://NoCloud/finance/"
* @constructor
*/
FSgetMediaRootPath() {
return "documents://NoCloud/finance/"
},
/**
* 获取 finance 根目录
* 支持 Android、iOS
* @param type 1-持久层目录;2-临时目录;默认 1
* @param successCallback finance的dirEntry
* @param errorCallback string错误信息
* @constructor
*/
FSgetRootFinanceEntry(successCallback, errorCallback, type) {
let _this = this;
_this.FSgetRootDirEntry(function (rootEntry) {
rootEntry.getDirectory("finance", {create: true, exclusive: false}, function (dirEntry) {
if (typeof successCallback == 'function') {
successCallback(dirEntry);
}
}, function (e) {
console.log(Common.prefix + "创建finance文件夹失败" + JSON.stringify(e));
if (typeof errorCallback == 'function') {
errorCallback("创建finance文件夹失败");
}
});
}, function (e) {
console.log(Common.prefix + "获取文件系统失败" + JSON.stringify(e));
if (typeof errorCallback == 'function') {
errorCallback("获取文件系统失败");
}
}, type)
},
/**
* 通过 dirEntry 检查文件是否存在
* 存在——返回 fileEntry
* 不存在——返回 errmsg
* @param filename
* @param dirEntry
* @param hasFileCallback
* @param noFileCallback
* @constructor
*/
FScheckFileExistByDirEntry(filename, dirEntry, hasFileCallback, noFileCallback) {
//方法一
dirEntry.getFile(filename, {create: false}, function (fileEntry) {
console.log(Common.prefix + "有文件");
if (typeof hasFileCallback == 'function') {
hasFileCallback(fileEntry);
}
}, function (e) {
console.log(Common.prefix + "没有文件");
if (typeof noFileCallback == 'function') {
noFileCallback("没找到文件");
}
})
},
/**
* 通过 dirEntry 创建文件
* 返回 fileEntry
* @param filename
* @param dirEntry
* @param successCallback
* @param errorCallback
* @constructor
*/
FSmakeFileByDirEntry(filename, dirEntry, successCallback, errorCallback) {
//方法一
dirEntry.getFile(filename, {create: true, exclusive: false}, function (fileEntry) {
console.log(Common.prefix + "创建文件成功");
if (typeof successCallback == 'function') {
successCallback(fileEntry);
}
}, function (e) {
console.log(Common.prefix + "创建文件失败");
if (typeof errorCallback == 'function') {
errorCallback("创建文件失败");
}
})
},
/**
* 获取相应目录里的 文件 fileEntry
* @param filename
* @param hasFileCallback
* @param noFileCallback
* @param dirErrorCallback
* @param type 1-持久层目录;2-临时目录;默认 1
* @constructor
*/
FSgetFileEntryByFilename(filename, hasFileCallback, noFileCallback, dirErrorCallback, type) {
let _this = this;
if (filename) {
_this.FSgetRootFinanceEntry(function (dirEntry) {
_this.FScheckFileExistByDirEntry(filename, dirEntry, function (fileEntry) {
if (typeof hasFileCallback == 'function') {
hasFileCallback(fileEntry);
}
}, function (errmsg) {
if (typeof noFileCallback == 'function') {
noFileCallback(errmsg);
}
})
}, function (errmsg) {
if (typeof dirErrorCallback == 'function') {
dirErrorCallback(errmsg);
}
}, type)
} else {
//没有把文件名传过来,直接走错误
if (typeof noFileCallback == 'function') {
noFileCallback("没有获取到文件名");
}
}
},
/**
* 创建相应目录里的 文件 fileEntry
* @param filename
* @param successCallback
* @param errorCallback
* @param type 1-持久层目录;2-临时目录;默认 1
* @constructor
*/
FSmakeFileEntryByFilename(filename, successCallback, errorCallback, type) {
let _this = this;
if (filename) {
_this.FSgetRootFinanceEntry(function (dirEntry) {
_this.FSmakeFileByDirEntry(filename, dirEntry, function (fileEntry) {
if (typeof successCallback == 'function') {
successCallback(fileEntry);
}
}, function (errmsg) {
if (typeof errorCallback == 'function') {
errorCallback(errmsg);
}
})
}, function (errmsg) {
if (typeof errorCallback == 'function') {
errorCallback(errmsg);
}
}, type)
} else {
//没有把文件名传过来,直接走错误
if (typeof errorCallback == 'function') {
errorCallback("没有获取到文件名");
}
}
},
/**
* 通过 mimeType 打开文件
* fileOpener2
* @param fileEntry
* @param mimeType 'application/x-zip-compressed'——zip
* @param successCallback
* @param errorCallback
* @constructor
*/
FSopenFileByMimeType(fileEntry, mimeType, successCallback, errorCallback) {
if (fileEntry && mimeType) {
window.cordova.plugins.fileOpener2.open(
fileEntry.toURL(),
mimeType,
{
error: function (error) {
if (typeof errorCallback == 'function') {
errorCallback("打开失败:" + error.message);
}
},
success: function () {
if (typeof successCallback == 'function') {
successCallback("打开成功");
}
}
}
);
} else {
if (typeof errorCallback == 'function') {
errorCallback("参数错误:" + mimeType);
}
}
},
/**
* 将文件拷贝到另一个文件里
* 前提是刚创建的 fileEntry
* @param uri
* @param fileEntry
* @param successCallback
* @param errorCallback
* @constructor
*/
FScopyFileFromUri(uri, fileEntry, successCallback, errorCallback) {
let fileTransfer = new window.FileTransfer();
console.log(Common.prefix + "开始复制图片")
fileTransfer.download(//下载(复制)图片,从返回的路径
uri,
fileEntry.toURL(),
function (entry) {//封装数据库实体
console.log(Common.prefix + "文件复制完成")
if (typeof successCallback == 'function') {
successCallback(entry);
}
},
function (error) {
console.log(Common.prefix + "文件复制失败" + JSON.stringify(error))
if (typeof errorCallback == 'function') {
errorCallback("文件复制失败");
}
},
false,
{headers: {}}
);
},
/**
* 通过文件名和文件系统目录类型删除文件
*
* @param filename
* @param type 1-持久成;2-临时层
* @param successCallback
* @param errorCallback
* @constructor
*/
FSremovePersistentFileByFilename(filename, type, successCallback, errorCallback) {
if (type !== 1 && type !== 2) {
if (typeof errorCallback == 'function') {
errorCallback("参数type错误");
return;
}
}
let _this = this;
if (filename && type) {
_this.FSgetRootFinanceEntry(function (dirEntry) {
_this.FScheckFileExistByDirEntry(filename, dirEntry, function hasFile(fileEntry) {
fileEntry.remove(function () {
console.log(Common.prefix + 'delete success');
if (typeof successCallback == 'function') {
successCallback();
}
}, function (err) {
console.error(Common.prefix + "清理失败" + err);
if (typeof errorCallback == 'function') {
errorCallback("清理失败");
}
});
}, function noFile(errmsg) {
if (typeof errorCallback == 'function') {
errorCallback(errmsg);
}
})
}, function (errmsg) {
if (typeof errorCallback == 'function') {
errorCallback(errmsg);
}
}, type)
}
},
/**
* 判断是否联网,返回true,false
* UNKNOWN = '未知连接' ;
* ETHERNET = '以太网连接' ;
* WIFI = ' WiFi连接' ;
* CELL_2G = '小区2G连接' ;
* CELL_3G = '小区3G连接' ;
* CELL_4G ]= '单元格4G连接' ;
* CELL = '单元格通用连接' ;
* NONE = '没有网络连接' ;
* @constructor
*/
NetworkCheckOnline(onlineCallback, offlineCallback) {
// document.addEventListener("offline", offlineCallback, false);
// document.addEventListener("online", onlineCallback, false);
var networkState = navigator.connection.type;
console.log("联网状况" + JSON.stringify(networkState));
if (networkState && networkState.toLowerCase() !== "none") {
console.log(Common.prefix + "有网" + networkState);
if (typeof onlineCallback == 'function') {
onlineCallback("online");
}
} else {
console.log(Common.prefix + "未连接网络");
if (typeof offlineCallback == 'function') {
offlineCallback("未连接网络");
}
}
}
}
版权声明:本文为akxj2022原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。