原来的项目都是直接使用el-upload组件,结果遇到个新项目一个form里面可能需要使用多个el-upload,数据多了以后显得代码臃肿。
本篇做一个文件上传抽取处理。
文件上传公用方法
file.ts
中写一些公用方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| export const getFileType = (name) => { if (name) { if (name.lastIndexOf('.') > -1) { return name.slice(name.lastIndexOf('.') + 1) } else { return false } } }
export const acceptList = ['.pdf','.doc','.docx','.jpg','.png','.xls','.xlsx'] export const suffixList = ['pdf', 'doc', 'docx', 'jpg', 'png', 'xlsx', 'xls']
|
文件上传组件编写
uploadAttachment.vue
中将el-upload做一个封装,并把fileList暴露出去,这里获取的attachmentInfo是已经上传成功的文件列表(只有编辑状态下可能出现)。
- 设置可上传文件格式,通过组件accept属性判断
- 设置单文件不能超过10M,通过组件绑定on-change函数判断;
- 设置最大可上传10个文件,通过动态控制组件limit属性和upload按钮判断;
- 通过设置
auto-upload="false"
可实现手动上传;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| <template> <el-upload ref="upload" multiple :auto-upload="false" v-model:file-list="fileList" :on-remove="handleFileRemove" :on-change="handleFileChange" :accept="acceptList" :limit="maxNum" > <el-button type="" icon="Upload" :disabled="maxNum - chooseNum == 0">上传文件</el-button> <template #tip> <div class="el-upload__tip"> 支持扩展名:.doc .docx .pdf .jpg .png .xlsx .xls</div> </template> </el-upload> <div v-if="props.attachmentInfo && props.attachmentInfo.length > 0" class="attachmentList"> <span>已上传文件如下:</span> <ul v-for="(file, idx) in props.attachmentInfo" :key="idx"> <li> <el-tooltip placement="top" :content="file.name"> <div class="format-file-name2">{{ file.name }}</div> </el-tooltip> <el-button text type="danger" @click="handleFileDelete(file.id)">删除</el-button> </li> </ul> </div> </template>
<script lang="ts" setup> import { acceptList } from '@/utils/file' const props = defineProps<{ attachmentInfo?: Array<any> refName?: string }>()
const myEmit = defineEmits(['handleEmitDel']) const fileList = ref([]) const disUpload = ref(false)
let maxNum = computed(() => { const num1 = (props.attachmentInfo && props.attachmentInfo.length) || 0 return 10 - num1 })
let chooseNum = computed(() => { const num2 = (fileList.value && fileList.value.length) || 0 return num2 })
const handleFileRemove = (uploadFile, uploadFiles) => { fileList.value = toRaw(uploadFiles) console.log('remove ', fileList.value) }
const handleFileDelete = (id) => { if (props.refName) { myEmit('handleEmitDel', { id: id, name: props.refName }) } else { myEmit('handleEmitDel', id) } }
const handleFileChange = (file, fileList) => { console.log(file, fileList) const fileType = getFileType(file.name) if (suffixList.indexOf(fileType) < 0) { ElMessage.info('文件格式不符合要求') const currIdx = fileList.indexOf(file) fileList.splice(currIdx, 1) return } const isLt10M = file.size / 1024 / 1024 <= 10 if (!isLt10M) { ElMessage.info('文件大小不能超过10M') const currIdx = fileList.indexOf(file) fileList.splice(currIdx, 1) return } }
onUnmounted(() => {}) defineExpose({ fileList }) </script>
|
文件上传组件使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <upload-attachment :attachmentInfo="testForm.attachment_info" ref="uploadRef" @handleEmitDel="handleEmitDel" ></upload-attachment> </template>
<script lang="ts" setup> import { fdWithFiles } from '@/utils/file' import uploadAttachment from '@/components/File/uploadAttachment.vue' const emyptForm = { attachment_info: [] } const state = reactive({ testForm: JSON.parse(JSON.stringify(emyptForm)), })
const handleAdd = (formEl: typeof FormInstance) => { formEl.validate((valid: boolean) => { if (valid) { const params = { ...state.testForm } const fd = fdWithFiles(uploadRef.value.fileList, params) } else { return false } }) }
const handleEmitDel = (params) => { const preFiles = state.testForm[params.name] let currentFiles = [] console.log('preFiles=', preFiles) preFiles.forEach((item) => { if (item.id != params.id) { currentFiles.push(item) } }) state.testForm[params.name] = currentFiles } </script>
|
文件上传公用方法-交互数据处理
和后端约定和上传文件有关的接口以formdata形式交互,在/utils/file
中抽取数据转换方法。因为formdata可能将空字符串转为null,这里添加一层空值过滤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| export const fdWithFiles = (fileList: Array<any>, params: Object, fileName?: string) => { const fd = new FormData() if (fileList.length > 0) { fileList.forEach((item) => { console.log('item.raw=', item.raw) const name = fileName ? fileName : 'files' fd.append(name, item.raw) }) } for (const key in params) { if (Array.isArray(params[key])) { if (params[key].length > 0) fd.append(key, params[key]) } else { if (params[key]) fd.append(key, params[key]) } } return fd }
export const fdWithMultiFiles = (filesAll: Array<any>, params: Object) => { const fd = new FormData() filesAll.forEach((i) => { if (i.fileList.length > 0) { i.fileList.forEach((item) => { const name = i.fileName ? i.fileName : 'files' fd.append(name, item.raw) }) } }) for (const key in params) { if (Array.isArray(params[key])) { if (params[key].length > 0) fd.append(key, params[key]) } else { if (params[key]) fd.append(key, params[key]) } } return fd }
|
解决上传文件按钮在选取文件按钮禁用后仍可点击问题
这个问题测试同学提出之前,我是万万没想到…
按照要最多可以上传10个文件,我通过maxNum - chooseNum == 0来实现判断,问题是按钮已经置灰,但是居然还可以点击,出现文件选择框,虽然选了不会上传,依然影响用户体验。
查阅资料后,发现有个slot的改变可以防止这个现象出现,代码如下:
1 2 3 4 5 6 7 8
| <template #trigger> <el-button v-if="!(maxNum - chooseNum == 0)" icon="Upload" :disabled="maxNum - chooseNum == 0" >上传文件</el-button> </template> <template #tip> <el-button disabled v-if="maxNum - chooseNum == 0" icon="Upload"> 上传文件</el-button> </template>
|
附录
让element-ui的el-upload组件文件列表中文件图标自定义显示