普通的文件上传(不考虑大文件,断点续传等)这个东西确实很简单,但是里面又一些小套路。而我老是又记不住,每次还得重复思考。所以这次就记录一下,以后就自己抄自己。
静态按钮
首先是html+css,实现一个点击上传的按钮,因为我很懒,所以这里css是用tailwindcss来写。
套路一:用label把input包起来,然后即使input是display:none的还是能够触发input点击事件。
<div class="flex justify-end mb-2">
<label class="bg-primary text-white px-4 py-1 rounded cursor-pointer">
导入
<input type="file" accept=".csv" class="hidden" @change="(e) => handleUpload(e as HTMLInputEvent)" />
<!-- tailwindcss的hidden,就是display:none的意思 -->
</label>
</div>
页面如下:
点击上传
套路二:根据上传的内容格式设置请求头的content-type。这里我用的formData上传的,所以是用的multipart/form-data;charset=UTF-8。
export interface ResponseBase<T = any> {
success: boolean
data: T
message: string
}
export const uploadCsvFile = async (data: FormData): Promise<ResponseBase> => {
return service.post('/你的url', data, {
headers: {
'Content-Type': 'multipart/form-data;charset=UTF-8'
}
})
}
套路三:ts原生并没有HTMLInputEvent类型,所以我们可以自己定义一下,然后让全局可用。
// 文件名 xx.d.ts
declare interface HTMLInputEvent extends Event {
target: HTMLInputElement & EventTarget
}
<script lang="ts" setup>
import { uploadCsvFile } from '../api'
import { ElMessage } from 'element-plus';
const handleUpload = async (e: HTMLInputEvent) => {
if (!e.target || !e.target.files) return
const formData = new FormData()
formData.append('file', e.target.files[0])
const result = await uploadCsvFile(formData).catch(() => {
e.target.value = ''
})
e.target.value = ''
if (result && result.success) {
ElMessage.success('上传成功')
return
}
ElMessage.error(result?.message || '上传失败')
}
</script>
当然除了这种自己定义一个HTMLInputEvent的写法,还可以直接这样写,更简洁:
const handleUpload = async (e: Event) => {
const target = e.target as HTMLInputElement
//...
}
套路四:我们监听的的是input的change事件,所以如果浏览器觉得用户前后2次上传的文件是相同的,就不会触发我们的handleUpload事件,就不会触发上传!
所以,这里做了一个清除e.target.value的动作。
总结
尽管这里面每一点都很简单,但可能写的时候总可能会忘记,当然如果你使用别人封装好的,肯定就没有这些无聊的问题。