mirror of
https://gitee.com/bookshelfplus/bookshelfplus
synced 2025-09-03 23:52:51 +08:00
510 lines
20 KiB
HTML
510 lines
20 KiB
HTML
<!--
|
||
文件选择框
|
||
refer: https://developer.mozilla.org/zh-CN/docs/web/api/file/using_files_from_web_applications
|
||
-->
|
||
<style>
|
||
#dropbox {
|
||
width: 100%;
|
||
height: 200px;
|
||
display: grid;
|
||
place-items: center;
|
||
cursor: pointer;
|
||
transition: all 0.5s;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
#dropbox:hover {
|
||
transform: scale(1.03);
|
||
}
|
||
|
||
.lightgrey {
|
||
background-color: lightgrey;
|
||
}
|
||
|
||
.green {
|
||
background-color: #39a705;
|
||
color: #caf6b6;
|
||
}
|
||
|
||
.grey {
|
||
background-color: grey;
|
||
color: #b5b5b5;
|
||
}
|
||
|
||
.process-bar {
|
||
background-color: #b5b5b5;
|
||
width: 100%;
|
||
height: 5px;
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.process-bar-inner {
|
||
background-color: #39a705;
|
||
width: 0;
|
||
height: 100%;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.file-info-paragraph>span {
|
||
background-color: lightpink;
|
||
color: darkred;
|
||
padding: 1px 5px;
|
||
border-radius: 4px;
|
||
font-size: 0.8em;
|
||
}
|
||
|
||
/* 上传按钮样式 */
|
||
#beginUpload {
|
||
margin-top: 30px;
|
||
padding: 5px 12px;
|
||
transition: all 0.25s;
|
||
border: 0;
|
||
}
|
||
|
||
#beginUpload:hover {
|
||
transform: scale(1.1);
|
||
background-color: #39a705;
|
||
color: #caf6b6;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
#beginUpload[disabled]:hover {
|
||
transform: initial;
|
||
background-color: initial;
|
||
color: initial;
|
||
border-radius: initial;
|
||
cursor: initial;
|
||
}
|
||
|
||
#fileAssociator {
|
||
padding: 3px 12px;
|
||
min-width: 200px;
|
||
}
|
||
</style>
|
||
<p>
|
||
<a href="<%= pageUrl %>../">返回文件管理</a>
|
||
</p>
|
||
<div id="dropbox" class="lightgrey">
|
||
<p>
|
||
点击此处或将文件拖入此处以选择文件<br>
|
||
<!-- 文件个数: <span id="fileNum">0</span>; -->
|
||
文件大小: <span id="fileSize">0</span>
|
||
</p>
|
||
</div>
|
||
<!-- <input type="file" id="input" multiple onchange="handleFiles(this.files)"> -->
|
||
<input type="file" id="fileSelector" style="display: none;">
|
||
<p>
|
||
仅支持 Chrome 内核浏览器
|
||
</p>
|
||
<div id="file-detail-container" style="display: none;">
|
||
<p style="text-align: left;" class="file-info-paragraph">
|
||
文件名: <span id="file-name"></span><br>
|
||
文件扩展名:<span id="file-ext"></span><br>
|
||
文件名(不含扩展名):<span id="file-name-no-ext"></span><br>
|
||
文件大小: <span id="file-size"></span><br>
|
||
修改日期: <span id="file-lastModified"></span><br>
|
||
文件SHA1: <span id="file-sha1"></span><br>
|
||
</p>
|
||
<p>计算文件哈希进度</p>
|
||
<div id="processBar1" class="process-bar">
|
||
<div id="processBar1Inner" class="process-bar-inner"></div>
|
||
</div>
|
||
<p>文件上传进度</p>
|
||
<div id="processBar2" class="process-bar">
|
||
<div id="processBar2Inner" class="process-bar-inner"></div>
|
||
</div>
|
||
<p>关联文件<br><span style="font-size: 12px;">(仅可关联系统中未设置SHA1,或SHA1值相同的文件记录)</span></p>
|
||
<select id="fileAssociator">
|
||
<!-- <option value="0">新建文件</option> -->
|
||
</select>
|
||
</div>
|
||
<button id="beginUpload" disabled="true">开始上传</button>
|
||
|
||
<p>
|
||
<input type="checkbox" id="checkbox-auto-upload"><!-- checked -->
|
||
<label for="checkbox-auto-upload" style="font-size: small;">连续上传(上传完成后留在本页)</label>
|
||
</p>
|
||
|
||
<script src="/assets/lib/crypto-js/sha1.js"></script>
|
||
<script>
|
||
var file = null;
|
||
var fileInfo = {
|
||
fileName: "",
|
||
fileSize: 0,
|
||
fileType: "",
|
||
lastModified: "",
|
||
fileSha1: "",
|
||
fileExt: "",
|
||
fileNameWithoutExt: "",
|
||
};
|
||
|
||
const inputElement = document.getElementById("fileSelector");
|
||
inputElement.addEventListener("change", fileSelectorHandleFiles, false);
|
||
|
||
const beginUpload = document.getElementById("beginUpload");
|
||
beginUpload.addEventListener("click", upload);
|
||
|
||
//##############################################
|
||
// 选择文件
|
||
//##############################################
|
||
function fileSelectorHandleFiles() {
|
||
const fileList = this.files; /* now you can work with the file list */
|
||
// console.log(fileList);
|
||
handleFiles(fileList);
|
||
}
|
||
async function handleFiles(fileList) {
|
||
updateUI({ isFileEmpty: true });
|
||
if (fileList.length === 0) {
|
||
// 还未选择文件
|
||
return;
|
||
}
|
||
if (fileList.length > 1) {
|
||
swal("一次只能选择1个文件!");
|
||
return;
|
||
}
|
||
|
||
// 获取用户选择的文件
|
||
file = fileList[0];
|
||
console.log(file);
|
||
|
||
// // 判断用户选择的文件是否为文件夹
|
||
// if (await isFolder(file)) {
|
||
// swal("不支持文件夹,请选择文件!");
|
||
// return;
|
||
// }
|
||
|
||
// 输出所选文件的总大小
|
||
let nBytes = 0;
|
||
for (let nFileId = 0; nFileId < fileList.length; nFileId++) {
|
||
nBytes += fileList[nFileId].size;
|
||
}
|
||
let sOutput = nBytes + " bytes";
|
||
// optional code for multiples approximation
|
||
const aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
||
for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
|
||
sOutput = nApprox.toFixed(2) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)";
|
||
}
|
||
|
||
// document.getElementById("fileNum").innerHTML = fileList.length;
|
||
document.getElementById("fileSize").innerHTML = sOutput;
|
||
|
||
updateUI({ isFileEmpty: false });
|
||
|
||
// 记录文件信息
|
||
fileInfo.fileName = file.name;
|
||
fileInfo.fileSize = file.size;
|
||
fileInfo.fileType = file.type;
|
||
fileInfo.lastModified = file.lastModified;
|
||
// 获取文件扩展名:首先按照 . 拆分,然后删掉第一个元素(考虑无扩展名文件),再取出最后一个元素
|
||
let fileNameSplit = file.name.split(".");
|
||
fileNameSplit.shift();
|
||
fileInfo.fileExt = fileNameSplit.pop() || "";
|
||
// 获取文件名(不包含扩展名)
|
||
fileInfo.fileNameWithoutExt = file.name.substr(0, (file.name.length + file.name.lastIndexOf('.')) % (file.name.length));
|
||
|
||
// 更新UI
|
||
document.getElementById("file-name").innerHTML = fileInfo.fileName;
|
||
document.getElementById("file-ext").innerHTML = fileInfo.fileExt;
|
||
document.getElementById("file-name-no-ext").innerHTML = fileInfo.fileNameWithoutExt;
|
||
document.getElementById("file-size").innerHTML = sOutput;
|
||
document.getElementById("file-lastModified").innerHTML = new Date(fileInfo.lastModified).toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
||
|
||
// 计算文件哈希
|
||
let sha1 = await sha1File(file, (file) => {
|
||
document.getElementById("processBar1Inner").style.width = `${file.sha1_progress}%`;
|
||
// console.log("计算文件哈希 进度", file.sha1_progress);
|
||
});
|
||
fileInfo.fileSha1 = sha1;
|
||
document.getElementById("file-sha1").innerHTML = fileInfo.fileSha1;
|
||
|
||
// 获取关联文件下拉框列表并渲染
|
||
getFileAssociatorList(sha1);
|
||
|
||
// 完成
|
||
console.log(fileInfo);
|
||
}
|
||
|
||
// dropbox 点击时触发 file 的 click 事件
|
||
const fileSelect = document.getElementById("dropbox");
|
||
fileSelect.addEventListener("click", function (e) {
|
||
if (inputElement) {
|
||
inputElement.click();
|
||
}
|
||
}, false);
|
||
|
||
|
||
//##############################################
|
||
// 使用拖放来选择文件
|
||
//##############################################
|
||
let dropbox = document.getElementById("dropbox");
|
||
dropbox.addEventListener("dragenter", dragenter, false);
|
||
dropbox.addEventListener("dragleave", dragleave, false);
|
||
dropbox.addEventListener("dragover", dragover, false);
|
||
dropbox.addEventListener("drop", drop, false);
|
||
|
||
var lastenter = null; // refer: https://www.jianshu.com/p/f96b754032a1
|
||
|
||
function dragenter(e) {
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
lastenter = e.target; // 记录最后进入的元素
|
||
}
|
||
|
||
function dragleave(e) {
|
||
if (lastenter == e.target)
|
||
dropbox.classList.remove('grey');
|
||
}
|
||
|
||
function dragover(e) {
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
dropbox.classList.add('grey');
|
||
}
|
||
|
||
function drop(e) {
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
dropbox.classList.remove('grey');
|
||
|
||
var dt = e.dataTransfer;
|
||
var files = dt.files;
|
||
|
||
console.log("dt", dt.items);
|
||
console.log("dt", dt.items[0]);
|
||
console.log("dt", dt.items[0].webkitGetAsEntry());
|
||
|
||
updateUI({ isFileEmpty: true });
|
||
if (dt.items.length > 1) {
|
||
swal("一次只能选择1个文件!");
|
||
return;
|
||
}
|
||
if (dt.items[0].webkitGetAsEntry().isDirectory) {
|
||
swal("不支持文件夹,请选择文件!");
|
||
return;
|
||
}
|
||
handleFiles(files);
|
||
}
|
||
|
||
|
||
// //##############################################
|
||
// // 判断拖入的文件是否是文件夹 (非 Chrome 内核浏览器逻辑)
|
||
// // 读取文件的第一个字符,如果能够读取成功,那么就是文件,否则就是文件夹
|
||
// // refer: https://segmentfault.com/a/1190000013298317
|
||
// //##############################################
|
||
// async function isFolder(file) {
|
||
// return await new Promise((resolve, reject) => {
|
||
// try {
|
||
// var fileReader = new FileReader();
|
||
|
||
// fileReader.addEventListener('load', function (e) {
|
||
// console.log(e, 'load');
|
||
// resolve(false);
|
||
// }, false);
|
||
|
||
// fileReader.addEventListener('error', function (e) {
|
||
// console.log(e, 'error,不可以上传文件夹');
|
||
// resolve(true);
|
||
// }, false);
|
||
|
||
// fileReader.readAsDataURL(file.slice(0, 3));
|
||
// } catch (e) {
|
||
// console.log(e, 'catch error,不可以上传文件夹');
|
||
// resolve(true);
|
||
// }
|
||
// });
|
||
// }
|
||
|
||
|
||
//##############################################
|
||
// 更新界面显示
|
||
//##############################################
|
||
function updateUI({ isFileEmpty }) {
|
||
if (isFileEmpty) {
|
||
// 未选择文件
|
||
this.outerHTML = this.outerHTML; // 清除选择的文件
|
||
file = null;
|
||
fileInfo = {};
|
||
document.getElementById("fileSize").innerHTML = "0";
|
||
document.getElementById("processBar1Inner").style.width = 0;
|
||
document.getElementById("processBar2Inner").style.width = 0;
|
||
document.getElementById("file-name").innerHTML = "";
|
||
document.getElementById("file-ext").innerHTML = "";
|
||
document.getElementById("file-name-no-ext").innerHTML = "";
|
||
document.getElementById("file-size").innerHTML = "";
|
||
document.getElementById("file-sha1").innerHTML = "";
|
||
dropbox.classList.remove("green");
|
||
beginUpload.setAttribute("disabled", "true");
|
||
document.getElementById("file-detail-container").style.display = "none";
|
||
} else {
|
||
// 已选择文件
|
||
dropbox.classList.add("green");
|
||
beginUpload.removeAttribute("disabled");
|
||
document.getElementById("file-detail-container").style.display = "";
|
||
}
|
||
}
|
||
|
||
//##############################################
|
||
// 获取预授权URL
|
||
//##############################################
|
||
function upload() {
|
||
if (!file) {
|
||
swal("您还未选择文件!");
|
||
return;
|
||
}
|
||
var postParams = {
|
||
token: localStorageUtils.getToken(),
|
||
expireMinute: 30,
|
||
fileName: fileInfo.fileNameWithoutExt,
|
||
fileSize: fileInfo.fileSize,
|
||
// fileType: fileInfo.fileType,
|
||
lastModified: fileInfo.lastModified,
|
||
fileSha1: fileInfo.fileSha1,
|
||
fileExt: fileInfo.fileExt,
|
||
fileId: $("#fileAssociator").val() // 关联的文件ID,创建新文件则为0
|
||
};
|
||
console.log(postParams);
|
||
// 获取预授权URL
|
||
postRequest("/file/object/cos/put", postParams)
|
||
.then(function (response) {
|
||
var axiosData = response.data;
|
||
var status = axiosData.status;
|
||
var data = axiosData.data;
|
||
if (status === "success") {
|
||
console.log("data", data);
|
||
// 取得预授权URL,使用该URL进行文件上传
|
||
uploadFile(file, data.url, data.fileId, data.fileObjectId);
|
||
} else {
|
||
if (data.errCode == "60001") {
|
||
// 文件已存在于对象存储中
|
||
console.log(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||
|
||
// 再次发送请求,查询这个已存在文件的 fileId
|
||
postRequest("/file/getFileByHash", { token: localStorageUtils.getToken(), fileSha1: fileInfo.fileSha1 })
|
||
.then(function (responseData) {
|
||
var axiosData = responseData.data;
|
||
var status = axiosData.status;
|
||
var data = axiosData.data;
|
||
if (status === "success") {
|
||
console.log(data);
|
||
if (data) {
|
||
// 查询到之后,询问用户是否跳转到文件详情页
|
||
var isRedirect = confirm(`文件已存在,是否前往查看详情?\n(文件ID: ${data.id})`);
|
||
if (isRedirect)
|
||
location.href = `<%= pageUrl %>../detail?fileId=${data.id}`;
|
||
} else {
|
||
// 对象存储中存在该文件,但是系统数据库中找不到
|
||
swal("出错啦!该文件已存在于腾讯云对象存储中,但是系统中不存在")
|
||
}
|
||
} else {
|
||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||
}
|
||
}).catch(function (error) {
|
||
console.log(error);
|
||
swal("无法连接到服务器,请检查网络连接!");
|
||
});
|
||
} else {
|
||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||
}
|
||
}
|
||
}).catch(function (error) {
|
||
console.log(error);
|
||
swal("无法连接到服务器,请检查网络连接!");
|
||
});
|
||
}
|
||
|
||
//##############################################
|
||
//传入预授权 URL ,将文件上传到这个地址
|
||
//##############################################
|
||
function uploadFile(file, preSignedUrl, fileId, fileObjectId) {
|
||
// refer: https://cloud.tencent.com/document/product/436/35651
|
||
|
||
// 获取到 Url 后,前端可以这样 ajax 上传
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('PUT', preSignedUrl, true);
|
||
xhr.upload.onprogress = function (e) {
|
||
// e.lengthComputable是一个布尔值,表示当前上传的资源是否有可计算的长度
|
||
if (e.lengthComputable) {
|
||
// 计算出上传的进度
|
||
// e.loaded 已传输的字节
|
||
// e.total 需传输的总字节
|
||
var procentComplete = Math.ceil((e.loaded / e.total) * 100);
|
||
document.getElementById("processBar2Inner").style.width = `${procentComplete}%`;
|
||
console.log("上传进度", procentComplete);
|
||
}
|
||
};
|
||
xhr.onload = function (e) {
|
||
console.log('上传成功', xhr.status, xhr.statusText);
|
||
// 等待进度条走到 100% 否则小文件进度条还没有走完就提示上传完成会让人感觉有点奇怪
|
||
setTimeout(function () {
|
||
// 上传成功触发一次 “刷新文件对象上传状态”,避免因为云函数回调不成功导致文件对象上传状态没有及时更新
|
||
refreshFileObjectStatus(fileObjectId, function () {
|
||
swal("上传成功!").then(function () {
|
||
if ($("#checkbox-auto-upload").is(":checked")) {
|
||
window.location.reload();
|
||
} else {
|
||
// location.href = "<%= pageUrl %>../";
|
||
location.href = "<%= pageUrl %>../detail?id=" + fileId;
|
||
}
|
||
});
|
||
});
|
||
}, 300);
|
||
};
|
||
xhr.onerror = function (e) {
|
||
console.log('上传出错', xhr.status, xhr.statusText);
|
||
};
|
||
xhr.send(file); // file 是要上传的文件对象
|
||
}
|
||
</script>
|
||
<script>
|
||
// 关联文件下拉框列表
|
||
function getFileAssociatorList(fileSha1) {
|
||
var fileAssociator = document.getElementById("fileAssociator");
|
||
// 下拉框列表
|
||
postRequest("/file/list/MatchfileHashWithNullValue", { token: localStorageUtils.getToken(), fileSha1: fileSha1 })
|
||
.then(function (responseData) {
|
||
var axiosData = responseData.data;
|
||
var status = axiosData.status;
|
||
var data = axiosData.data;
|
||
if (status === "success") {
|
||
// console.log(data);
|
||
// 数据进行转换
|
||
var optionHTML = "";
|
||
for (var i = 0; i < data.length; i++) {
|
||
const element = data[i];
|
||
optionHTML += `<option value="${element.id}">[${element.id}] ${element.fileName}.${element.fileExt}</option>`;
|
||
}
|
||
// 渲染到下拉框内
|
||
// fileAssociator.innerHTML += optionHTML;
|
||
fileAssociator.innerHTML = `<option value="0">新建文件</option>` + optionHTML;
|
||
} else {
|
||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||
}
|
||
}).catch(function (error) {
|
||
console.log(error);
|
||
swal("无法连接到服务器,请检查网络连接!");
|
||
});
|
||
}
|
||
</script>
|
||
<script>
|
||
// 刷新文件对象上传状态
|
||
async function refreshFileObjectStatus(fileObjectId, callback) {
|
||
postRequest("/file/object/refreshFileObjectStatus", { token: localStorageUtils.getToken(), fileObjectId: fileObjectId })
|
||
.then(function (responseData) {
|
||
var axiosData = responseData.data;
|
||
var status = axiosData.status;
|
||
var data = axiosData.data;
|
||
if (status === "success") {
|
||
// console.log(data);
|
||
if (callback)
|
||
callback();
|
||
} else {
|
||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||
}
|
||
}).catch(function (error) {
|
||
console.log(error);
|
||
swal("无法连接到服务器,请检查网络连接!");
|
||
});
|
||
}
|
||
</script> |