网络请求封装
let instance = axios.create();
// 服务器地址
instance.defaults.baseURL = 'http:127.0.0.1:8888';
instance.defaults.headers['Content-Type'] = 'multipart/form-data';
instance.defaults.transformRequest = (data, headers) => {
const contentType = headers['Content-Type'];
if (contentType === "application/x-www-form-urlencoded") {
return Qs.stringify(data);
}
return data;
};
instance.interceptors.response.use(response => {
return response.data;
});
1.单文件上传
from-data 格式,我这里只贴上传部分的代码,至于获取文件等等一些操作我就不贴了,都比较简单
const fromData = new FormData()
fromData.append('file', _file)
fromData.append('filename', _file.name)
instance.post('/xxxx', fromData).then(data => {
if (+data.code === 0) {
alert('上传成功!')
}
return Promise.reject(data.codeText)
}).catch(err => {
alert('上传失败!,请稍后再试')
}).finally(() => {
// 不管成功还是失败的操作
})
2.Base64上传
只需要将获取的图片专为base64,然后上传就可以了
// 将文件转为BASE64
const transfromBase64 = function (file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function (e) {
resolve(e.target.result)
}
})
}
上传代码
// 将图片专为BASE64
const base64 = await transfromBase64(file)
try {
const data = await instance.post('/xxxx', {
// encodeURIComponent 确保base64的正确性
file: encodeURIComponent(base64),
filename: file.name
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
if (+data.code === 0) {
alert('上传成功')
}
throw data.codeText
} catch (err) {
alert('上传失败!')
} finally {
// 不管成功还是失败时的操作
}
3.图片预览上传
图片预览 => 就是把用户获取到的图片转为base64,然后在将转换好的base64 赋值给img标签的src属性.
我们可以提前准备好一个img标签,把样式什么的都写好
const img = document.querySelector('#img')
const ipt = document.querySelector('#ipt')
ipt.addEventLister('change', async function(){
const file = ipt.files[0];
img.src = await transfromBase64(file)
// 其它操作
})
为了确保文件的唯一性,我们这里SparkMD5,生成文件的MD5哈希值( 可以省略 )
// 将文件转为BUFFER
const transfromBuffer = function (file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsArrayBuffer(file)
reader.onload = function (e) {
const buffer = e.target.result
const spark = new SparkMD5.ArrayBuffer()
spark.append(buffer)
const HASH = spark.end()
const suffix = /.([a-zA-Z0-9]+)$/.exec(file.name)[1]
console.log({
buffer,
hash: HASH,
suffix,
filename: `${HASH}.${suffix}`
})
resolve({
buffer,
hash: HASH,
suffix,
filename: `${HASH}.${suffix}`,
})
}
})
}
上传文件的代码
const { filename } = await transfromBuffer(file)
const fromData = new FormData()
fromData.append('file', file)
fromData.append('filename', filename)
instance.post('/xxxx', fromData).then(data => {
if (+data.code === 0) {
alert('上传成功!')
} else {
return Promise.reject(data.codeText)
}
}).catch(err => {
alert('上传失败!,请稍后再试')
}).finally(() => {
// 不管成功还是失败时操作,例如清除按钮loading等等
})
4.单文件上传 + 进度条
所有的操作和步骤都跟 " 单文件上传 " 是一样,唯一不一样的是我们需要获取文件的总大小,和已经上传了多少
try {
const formData = new FormData()
formData.append('file', file)
formData.append('name', file.name)
const data = await instance.post('/xxxx', formData, {
onUploadProgress: (progressEvent) => {
const { loaded, total } = progressEvent
// loaded 表示已经上传的字节
// total 总数
// 可以在这里给提前准备好的div的宽度赋值即可
div.style.width = `${loaded / total * 100}%`
}
})
if (+data.code === 0) {
// 这里表示成功,成功后需要将提前准备的div的宽度设置为100%
// 延迟函数.为了测试进度条
await delay(300)
alert('上传成功')
return
}
throw data.codeText
} catch (e) {
alert('上传失败,请重试')
} finally {
// 不管成功还是失败时操作,例如清除按钮loading等等
}
5.多文件上传
因为这里是多文件上传,我们需要展示用户选择的所有文件
这里使用li标签来展示用户选择的每一个文件
inp.addEventListener('change', async function () {
let file = Array.from(upload_inp.files)
if (!file.length) return
_files = file.map((file) => {
return {
file,
filename: file.name,
key: createRandom() // 唯一值,方便后期删除文件使用
}
})
let str = ''
_files.forEach((item, index) => {
str += `<li key=${item.key}>
<span>文件${index + 1}:${item.filename}</span>
<span><em>移除</em></span>
</li>
`
})
ul.style.display = 'block'
ul.innerHTML = str
})
// 随机数方法
const createRandom = () => {
// 使用随机数 + 时间戳在转16进制 降低随机数重复的概率
const random = Math.random() * new Date().getTime()
return random.toString(16).replace('.', '')
}
随机数展示
li长这样
上传代码
ipt.addEventListener('click', async function () {
// 获取所有的li 也就是用户选择的文件
let upload_list_li = Array.from(upload_list.querySelectorAll('li'))
_files = _files.map((item) => {
let fm = new FormData,
// 根据key找到对应的li
curLi = upload_list_li.find(liBox => liBox.getAttribute('key') === item.key),
// 根据li选中span,后续为了展示进度
curSpan = curLi ? curLi.querySelector('span:nth-last-child(1)') : null;
fm.append('file', item.file)
fm.append('filename', item.filename)
return instance.post('/xxx', fm, {
onUploadProgress: (progressEvent) => {
// 获取上传进度
const { loaded, total } = progressEvent
if (curSpan) {
curSpan.innerHTML = `${((loaded / total) * 100).toFixed(2)}%`
}
}
}).then(data => {
if (+data.code === 0) {
if (curSpan) {
curSpan.innerHTML = '100%'
}
return
}
return Promise.reject()
})
})
Promise.all(_files).then(() => {
alert('上传成功')
}).catch(() => {
alert('上传失败')
}).finally(() => {
// 不管成功还是失败时操作,例如清除按钮loading等等
})
})
6.拖拽上传
拖拽也很简单,一共有四个API,我们这里用到其中的两个就可以了,分别是 dragover、drop 事件
div.addEventListener('dragenter', function (ev) {
ev.preventDefault()
console.log(ev, '进入了');
})
div.addEventListener('dragleave', function (ev) {
ev.preventDefault()
console.log('ev', '离开了');
})
div.addEventListener('dragover', function (ev) {
console.log(ev, '拖动元素位于目标元素之上');
// 当我们拖动文件到浏览器时,浏览器会默认打开一个新的窗口,展示你的文件
// 所以我们需要组织事件的默认行文
ev.preventDefault()
})
div.addEventListener('drop', function (ev) {
ev.preventDefault()
console.log(ev, '当拖动的元素被放置到目标元素上时触发');
})
上传代码
upload.addEventListener('dragover', function (ev) {
// console.log(ev, '拖动元素位于目标元素之上');
ev.preventDefault()
})
upload.addEventListener('drop', function (ev) {
ev.preventDefault()
const file = ev.dataTransfer.files[0]
if (!file) return
uploadFile(file) // 上传方法
console.log(ev, '当拖动的元素被放置到目标元素上时触发');
})
ev.dataTransfer 展示
上传代码
const uploadFile = async file => {
try {
const fm = new FormData()
fm.append('file', file)
fm.append('filename', file.name)
const data = await instance.post('/xxxx', fm)
if (+data.code === 0) {
alert('上传成功')
return
}
throw data.codeText
} catch (err) {
alert('上传失败')
} finally {
// 不管成功还是失败时操作,例如清除按钮loading等等
}
}
7.大文件上传
大文件上传其实利用Blob对象给我们提供的slice方法,放大文件按照要求切成指定大小的文件,然后在上传
设置切片大小
let max = 1024 * 100 * 100, // 切片大小
count = Math.ceil(file.size / max), // 切片数量
index = 0, // 切片计数
chunks = []; // 存放切片的数组
if (count > 100) {
max = file.size / 100;
count = 100;
}
while (index < count) {
chunks.push({
file: file.slice(index * max, (index + 1) * max),
filename: `${HASH}_${index + 1}.${suffix}`,
})
index++
}
实现文件秒传,就是在文件上传之前,先向后端发送一个请求,来获取已经上传的文件
// transfromBuffer 代码在 *3.图片预览上传* 中
const { HASH, suffix } = await transfromBuffer(file)
let alreadyList = [] // 已经上传的文件
try {
let data = await instance.get('/xxx', {
params: {
HASH
}
});
if (+data.code === 0) {
alreadyList = data.fileList;
}
} catch (err) { }
上传代码
chunks.forEach(chunk => {
// 已经上传的无需在上传
if (already.length > 0 && already.includes(chunk.filename)) {
complate();
return;
}
let fm = new FormData;
fm.append('file', chunk.file);
fm.append('filename', chunk.filename);
instance.post('/xxxx', fm).then(data => {
if (+data.code === 0) {
complate();
return;
}
return Promise.reject(data.codeText);
}).catch(() => {
alert('当前切片上传失败,请您稍后再试~~');
clear();
});
});
complate 方法实现的事,当所有的切片上传完毕后,我们发送一个合并的请求,并将文件的HASH值和文件切片的数量传递给后端,然后后端开始和合并切片.
const complate = async () => {
// 管控进度条
index++;
console.log(`${index / count * 100}%`); // 进度条
// 当所有切片都上传成功,我们合并切片
if (index < count) return;
try {
data = await instance.post('/xxxx', {
HASH,
count
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (+data.code === 0) {
alert(`恭喜您,文件上传成功`);
return;
}
throw data.codeText;
} catch (err) {
alert('切片合并失败,请您稍后再试~~');
}
};