以前对文件处理一直很抵触,因为练习成本较高,原生的文件选择器了解不多,所以最近补了补课,然后自己手搓了一个文件选择器,先看看效果。
基础样式就是这样子,然后直接把文件拖入或者点击选择文件就行
图片文件支持预览,可以删除已选中文件,文件上传可以限制数量
引用的方法如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="FileUploader"></div>
</body>
</html>
<script src="js/Fileuploader.js"></script>
<script>
new FileUploader({
id: "FileUploader",
limit: 10,
fileSize: [{
type: "*",
size: "10MB",
}],
url: "http://localhost:7385/fileUpload"
}, this)
</script>
new一个对象就行,id绑定一个div,然后样式会自动注入,
limit是限制上传数量,
fileSize数组里插入文件限制的大小,“ * ”是全部,支持对单种文件类型限制大小,输入Mb、B、kb、Gb,或者数字都行,自动转成字节大小,比如:
new FileUploader({
id: "FileUploader",
limit: 10,
fileSize: [{
type: "png",
size: "500KB",
}, {
type: "JGP",
size: "151515",
}, {
type: "docx",
size: "1Mb",
}],
url: "http://localhost:7385/fileUpload"
}, this)
url是提交到的接口,使用内置的方法会发送一个formdata,用List<MultipartFile>接收使用方法如下:
let fileUpload=new FileUploader({
id: "FileUploader",
limit: 10,
fileSize: [{
type: "*",
size: "10MB",
}],
url: "http://localhost:7385/fileUpload"
}, this)
//运行这一句就行
fileUpload.submit()
但是不同的项目需求可能不太一样,所以还是推荐直接获取formData然后自己上传,方法如下:
let fileUploader=new FileUploader({
id: "FileUploader",
limit: 10,
fileSize: [{
type: "*",
size: "10MB",
}],
url: "http://localhost:7385/fileUpload"
}, this)
var formData=fileUploader.getFormData()
这玩意写了几个小时,考虑的东西还不太全面,代码样式比较难改,因为我想把玩意封装到一个js里,所以css也是js注入,具体代码如下,自取:
class FileUploader {
constructor(setting, vue) {
this.setting = setting
this.Vue = vue
this.fileSizeLimit = null
if (this.setting.fileSize != null) {
this.fileSizeLimit = new Map();
for (var a = 0; a < this.setting.fileSize.length; a++) {
//console.log(this.setting.fileSize[a])
this.fileSizeLimit.set(this.setting.fileSize[a].type, this.convertToBytes(this.setting.fileSize[a]
.size))
}
}
//console.log(this.fileSizeLimit)
this.styleNum = 0
this.fileList = []
this.fileUpload = document.getElementById(this.setting.id)
console.log(this.Vue)
this.fileUpload.classList.add("FILEUPLOADERfileBaseUploaderBlock")
this.styleElement = document.createElement('style');
document.head.appendChild(this.styleElement);
this.styleSheet = this.styleElement.sheet;
this.addCss()
this.init()
this.inputDisable()
this.fileListEle = this.dc("div", "FILEUPLOADERfileListEle")
this.fileUpload.append(this.fileListEle)
}
convertToBytes(size) {
const units = {
B: 1,
KB: 1024,
MB: 1024 * 1024,
GB: 1024 * 1024 * 1024,
TB: 1024 * 1024 * 1024 * 1024,
};
const regex = /^(\d+(\.\d+)?)\s*([BKMGT]B?)$/i;
const matches = size.match(regex);
if (matches) {
const value = parseFloat(matches[1]);
const unit = matches[3].toUpperCase();
if (units.hasOwnProperty(unit)) {
return value * units[unit];
}
} else if (!isNaN(size)) {
// 输入的是纯数字,直接返回大小
return parseFloat(size);
}
return NaN;
}
getFormData() {
const formData = new FormData();
// 将文件数组添加到 FormData 中
for (let i = 0; i < this.fileList.length; i++) {
formData.append('files', this.fileList[i]);
}
return formData
}
async submit() {
if (this.fileList.length == 0) {
this.message("未选择文件")
return
}
// 创建一个 FormData 对象
const formData = new FormData();
// 将文件数组添加到 FormData 中
for (let i = 0; i < this.fileList.length; i++) {
formData.append('files', this.fileList[i]);
}
const {
data: result
} = await this.Vue.$http.post(this.setting.url, formData)
this.message("上传成功")
if (result.code == 200) {
this.fileListEle.innerHTML = ''
this.fileList = []
return result
} else {
return result
}
// 发起 POST 请求到后端接口
// fetch('http://123.56.27.115:7385/fileUpload', {
// method: 'POST',
// body: formData,
// })
// .then(response => {
// //console.log(response)
// })
// .catch(error => {
// //console.error('请求错误:', error);
// });
}
message(msg) {
var Msg = this.dc("div", "FILEUPLOADERmessage").text(msg)
document.body.appendChild(Msg)
var op = 0
var top = 0
var emo = self.setInterval(() => {
op += 0.1
Msg.style.opacity = op;
top = top + (1.5 - (top / 7 * top / 7));
//console.log((1.5 - (top / 7 * top / 7)))
Msg.style.top = top + "%";
//console.log(top)
}, 17)
setTimeout(() => {
self.clearInterval(emo)
}, 250)
setTimeout(() => {
document.body.removeChild(Msg)
}, 2000)
}
viewFileList() {
if (this.fileList[this.fileList.length - 1].type.substr(0, 5) == "image") {
const reader = new FileReader();
reader.readAsDataURL(this.fileList[this.fileList.length - 1]);
//console.log(this.fileList[this.fileList.length - 1])
reader.onload = (event) => {
const img = new Image();
img.src = event.target.result;
img.onload = () => {
let delIcon = this.dc("div", "FILEUPLOADERdelIcon")
let index = this.fileList.length - 1
const fileViewBlock = this.dc("div", "FILEUPLOADERfileViewBlock")
const canvas = this.dc("canvas", "FILEUPLOADERviewCanvas");
fileViewBlock.append(delIcon).append(this.dc("div", "FILEUPLOADERviewCanvasBlock")
.append(canvas))
.append(this.dc(
"div", "FILEUPLOADERfileNameImg").text(this.fileList[this.fileList.length -
1].name)
.tit(
this.fileList[this.fileList.length - 1].name))
this.fileListEle.append(fileViewBlock)
delIcon.onclick = () => {
this.fileList.splice(index, 1)
this.fileListEle.removeChild(fileViewBlock)
this.inputAble()
//console.log(this.fileList)
}
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const width = img.width;
const height = img.height;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
img.onerror = (error) => {
reject(error);
};
};
} else {
let index = this.fileList.length - 1
let delIcon = this.dc("div", "FILEUPLOADERdelIcon")
const fileViewBlock = this.dc("div", "FILEUPLOADERfileViewBlock").append(delIcon).append(this.dc("div",
"FILEUPLOADERfile-icon").append(this
.dc("div", "FILEUPLOADERfileName").text(this.fileList[this.fileList.length - 1].name).tit(
this
.fileList[this.fileList.length - 1].name)))
this.fileListEle.append(fileViewBlock)
delIcon.onclick = () => {
this.fileList.splice(index, 1)
this.fileListEle.removeChild(fileViewBlock)
this.inputAble()
//console.log(this.fileList)
}
}
}
init() {
this.fileUpload.classList.add("FILEUPLOADERuploadInput")
this.fileInputBlock = this.dc("div", "FILEUPLOADERinputBlock")
this.fileInput = this.dc("input", "FILEUPLOADERInput")
this.fileInput.type = "file"
this.fileInput.addEventListener('change', (event) => {
if (this.fileInput.disabled) {
this.message("已达到限制上传数量")
return
}
for (var a = 0; a < this.fileList.length; a++) {
if (this.fileList[a].name == event.target.files[0].name && this.fileList[a].lastModified ==
event
.target.files[0].lastModified && this.fileList[a].size == event.target.files[0].size) {
this.message("请勿重复上传文件")
return
}
}
var fileType = event.target.files[0].name.split(".")
var fileType = fileType[fileType.length - 1];
if (this.fileSizeLimit.has("*")) {
if (this.fileSizeLimit.get("*") < event.target.files[0].size) {
this.message("超过限制大小(" + this.formatBytes(this.fileSizeLimit.get("*")) + ")")
return
}
} else if (this.fileSizeLimit.has(fileType)) {
if (this.fileSizeLimit.get(fileType) < event.target.files[0].size) {
this.message("超过限制大小(" + this.formatBytes(this.fileSizeLimit.get(fileType)) + ")")
return
}
}
this.fileList.push(event.target.files[0])
this.fileNumEle.text(this.setting.limit + "/" + this.fileList.length)
this.inputDisable()
//console.log(this.fileList)
this.viewFileList()
});
this.signFont = this.dc("font", "FILEUPLOADERaddFont").text("点击/拖拽选择")
this.addIcon = this.dc("font", "FILEUPLOADERaddIcon").text("+")
this.fileNumEle = this.dc("div", "FILEUPLOADERfileNum").text(this.setting.limit + "/0")
this.fileInputBlock.append(this.fileInput).append(this.dc("div", "FILEUPLOADERinputIcon").append(this
.addIcon).append(this.signFont))
this.fileUpload.append(this.fileInputBlock)
this.fileUpload.append(this.fileNumEle)
this.fileInputBlock.addEventListener('mouseover', (event) => {
this.fileInputBlock.style.opacity = "0.6"
});
this.fileInputBlock.addEventListener('mouseout', (event) => {
this.fileInputBlock.style.opacity = "1"
});
this.fileInputBlock.addEventListener('dragover', (event) => {
this.fileInputBlock.style.opacity = "0.6"
});
this.fileInputBlock.addEventListener('dragleave', (event) => {
this.fileInputBlock.style.opacity = "1"
});
this.fileInputBlock.addEventListener('drop', (event) => {
this.fileInputBlock.style.opacity = "1"
});
this.fileInputBlock.addEventListener('change', (event) => {
this.fileInputBlock.style.opacity = "1"
});
}
formatBytes(bytes) {
if (bytes === 0) {
return '0 B';
}
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const size = parseFloat((bytes / Math.pow(k, i)).toFixed(2));
return size + ' ' + sizes[i];
}
inputDisable() {
if (this.fileList.length == this.setting.limit) {
this.fileInput.disabled = true;
this.fileInputBlock.style.border = "2px solid #9e9e9e"
this.signFont.style.color = "#9e9e9e"
this.addIcon.style.color = "#9e9e9e"
}
}
inputAble() {
//console.log(this.fileList.length + "," + this.setting.limit)
if (this.fileList.length != this.setting.limit) {
this.fileInput.disabled = false;
this.fileInputBlock.style.border = "2px solid #599cff"
this.signFont.style.color = "#599cff"
this.addIcon.style.color = "#599cff"
}
}
addCss() {
// this.style(`.message{width:20%;padding:15px;margin-left:40%;}`)
this.style(`.FILEUPLOADERfileViewBlock{width:75px;height:95px;float:left;margin:5px;overflow:hidden;}`)
this.style(`.FILEUPLOADERfileBaseUploaderBlock {
display: flex;
width:98%;
flex-direction: column;
float: left;
padding:1%;
background-color:#fff;
}`)
this.style(`.FILEUPLOADERuploadInput{padding:10px;display:flex;flex-direction:column;width:100%}`)
this.style(
`.FILEUPLOADERInput{height:100%;width:100%; margin-top:-0px; float:left;background-color:red;opacity: 0;cursor: pointer;}`
)
this.style(`.FILEUPLOADERfileNum{float:left;margin-top:-30px;margin-left:115px}`)
this.style(
`.FILEUPLOADERfileName{background-color:#f8f8f8;width:71px;padding-left:2px;padding-right:2px; height:25px;text-align:center;line-height:20px;float:left;font-size: 12px;white-space: nowrap;overflow:hidden;margin-top:75px;}`
)
this.style(
`.FILEUPLOADERfileNameImg{background-color:#f8f8f8;width:71px;padding-left:2px;padding-right:2px; height:25px;text-align:center;line-height:20px;float:left;font-size: 12px;white-space: nowrap;overflow:hidden;}`
)
this.style(
`.FILEUPLOADERviewCanvasBlock{width:75px;height:75px;overflow:hidden;float:left;background-color:#f8f8f8;}`
)
this.style(`.FILEUPLOADERviewCanvas{width:75px; position: relative;
z-index: 1;}`)
this.style(
`.FILEUPLOADERinputBlock{width:100px;height:100px;float:left;border:2px solid #33a0ff;border-radius:3px;cursor: pointer;}`
)
this.style(
`.FILEUPLOADERinputIcon {width: 100% ;height: 100% ;float: left;margin-top: -100% ;color: #599cff;}`
)
this.style(`.FILEUPLOADERfile-icon {background-color:#f8f8f8;float:left;
width: 75px;
height: 75px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024' width='75' height='75'%3E%3Cpath d='M506.88 257.024H321.024c-25.6 0-46.08 20.992-46.08 46.592v371.2c0 25.6 20.48 46.592 46.08 46.592h279.04c25.6 0 46.592-20.992 46.592-46.592V396.288L506.88 257.024z m46.08 371.2H367.616v-46.592H552.96v46.592z m0-92.672H367.616v-46.592H552.96v46.592zM483.328 419.328V291.84l127.488 127.488H483.328z' fill='%23AAD4FF'/%3E%3Cpath d='M599.552 314.88H413.696c-25.6 0-46.08 20.992-46.08 46.592v371.2c0 25.6 20.48 46.592 46.08 46.592h279.04c25.6 0 46.592-20.992 46.592-46.592V454.144l-139.776-139.264z m46.592 371.2H460.288v-46.08h185.856v46.08z m0-92.672H460.288v-46.592h185.856v46.592z m-69.632-116.224V349.696l127.488 127.488h-127.488z' fill='%232B95FF'/%3E%3Cpath d='M827.904 233.472H798.72v-29.696c0-6.656-5.12-11.776-11.776-11.776-6.656 0-11.776 5.12-11.776 11.776v29.696h-29.696c-6.656 0-11.776 5.12-11.776 11.776s5.12 11.776 11.776 11.776h29.696V286.72c0 6.656 5.12 11.776 11.776 11.776 6.656 0 11.776-5.12 11.776-11.776v-29.696h29.696c6.656 0 11.776-5.12 11.776-11.776s-5.632-11.776-12.288-11.776z' fill='%23A8D4FF'/%3E%3Cpath d='M805.66272 620.4416l46.336-46.336 46.34624 46.336-46.34112 46.336zM786.944 831.488l34.816-34.816h-69.632z' fill='%23D9EDFF'/%3E%3Cpath d='M216.064 234.496m-32.768 0a32.768 32.768 0 1 0 65.536 0 32.768 32.768 0 1 0-65.536 0Z' fill='%23D9EDFF'/%3E%3Cpath d='M281.088 794.112l-89.088-89.088c-3.072-3.072-8.192-4.096-12.8-2.56-4.096 2.048-7.168 6.144-7.168 10.752v89.088c0 3.072 1.536 6.144 3.584 8.192 2.048 2.048 5.12 3.584 8.192 3.584h89.088c4.608 0 8.704-3.072 10.752-7.168 1.536-4.096 0.512-9.216-2.56-12.8z m-86.016-3.072v-49.152l49.152 49.152h-49.152z' fill='%23A8D4FF'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
}
`)
this.style(`.FILEUPLOADERmessage {
opacity:0;
position: fixed;
top: 0%;
border-radius:3px;
text-align: center;
min-width: 200px;
left: 50%;
font-size: 14px;
background-color: #c2c2c2;
padding: 5px;
box-shadow: 0 0px 3px #c2c2c2;
z-index: 9999;
color: #ffffff;
}`)
this.style(`.FILEUPLOADERdelIcon {cursor: pointer;
margin-left:50px;
background-color:#fff;
border-radius:50%;
margin-bottom:-25px;
position: relative;
z-index: 10;
width: 20px;
height: 20px;
background-image: url("data:image/svg+xml,%3Csvg t='1686553471493' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='3537' width='20' height='20'%3E%3Cpath d='M872.802928 755.99406 872.864326 755.99406 872.864326 755.624646Z' fill='%23272536' p-id='3538'%3E%3C/path%3E%3Cpath d='M927.846568 511.997953c0-229.315756-186.567139-415.839917-415.838893-415.839917-229.329059 0-415.85322 186.524161-415.85322 415.839917 0 229.300406 186.524161 415.84094 415.85322 415.84094C741.278405 927.838893 927.846568 741.29836 927.846568 511.997953M512.007675 868.171955c-196.375529 0-356.172979-159.827125-356.172979-356.174002 0-196.374506 159.797449-356.157629 356.172979-356.157629 196.34483 0 356.144326 159.783123 356.144326 356.157629C868.152001 708.34483 708.352505 868.171955 512.007675 868.171955' fill='%23272536' p-id='3539'%3E%3C/path%3E%3Cpath d='M682.378947 642.227993 553.797453 513.264806 682.261267 386.229528c11.661597-11.514241 11.749602-30.332842 0.234337-41.995463-11.514241-11.676947-30.362518-11.765975-42.026162-0.222057L511.888971 471.195665 385.223107 344.130711c-11.602246-11.603269-30.393217-11.661597-42.025139-0.059352-11.603269 11.618619-11.603269 30.407544-0.059352 42.011836l126.518508 126.887922L342.137823 639.104863c-11.662621 11.543917-11.780301 30.305213-0.23536 41.96988 5.830799 5.89015 13.429871 8.833179 21.086248 8.833179 7.53972 0 15.136745-2.8847 20.910239-8.569166l127.695311-126.311801L640.293433 684.195827c5.802146 5.8001 13.428847 8.717546 21.056572 8.717546 7.599072 0 15.165398-2.917446 20.968567-8.659217C693.922864 672.681586 693.950494 653.889591 682.378947 642.227993' fill='%23272536' p-id='3540'%3E%3C/path%3E%3C/svg%3E");
}
`)
this.style(`.FILEUPLOADERdelIcon:hover{background-color:#e5e5e5;}`)
this.style(
`.FILEUPLOADERaddIcon {width: 100%;text-align: center;font-size: 50px;float: left;}`
)
this.style(
`.FILEUPLOADERaddFont {margin-top: -5px;font-size: 13px;float: left;width: 100%;text-align: center;}`
)
this.style(`.FILEUPLOADERfileListEle {background-color:#f0f0f0;width:570px;margin-top:15px;}`)
}
style(css) {
// 插入 CSS 样式规则到 StyleSheet 中
this.styleSheet.insertRule(css, this.styleNum);
this.styleNum++
}
dc(element, Class, valueBody) {
var Element = document.createElement(element)
if (Class != "") {
try {
Element.classList.add(Class)
} catch (e) {
//TODO handle the exception
}
}
Element["text"] = function(text) {
Element.innerText = text
return Element
}
Element["addClass"] = function(newClass) {
Element.classList.add(newClass)
return Element
}
Element["append"] = function(child) {
Element.appendChild(child)
return Element
}
Element["tit"] = function(title) {
Element.title = title
return Element
}
valueBody = Element
return Element
}
}
问题肯定很多,代码写法肯定问题也有,如果有兴趣可以自己优化一下,毕竟是第一版。
叠个甲,如果你说elementui或者layui也有文件选择器,那么你说得对,我用过,但是我觉得那个耦合太高了,所以我就想只写一个文件选择器,并且能把前端代码量降到最小,全封装到一起,这样方便应该是比自己手搓方便,耦合度也低,但是就是功能不够强大,在js里写前端样式也比较逆天,所以图个乐,小练习。