vue3笔记(19-2)文件上传

原来的项目都是直接使用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是已经上传成功的文件列表(只有编辑状态下可能出现)。

  1. 设置可上传文件格式,通过组件accept属性判断
  2. 设置单文件不能超过10M,通过组件绑定on-change函数判断;
  3. 设置最大可上传10个文件,通过动态控制组件limit属性和upload按钮判断;
  4. 通过设置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)

// 最多上传10个文件
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)
}

// 已上传文件删除-可自定义组件refName
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)
// add API,传入fd
} 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
// 文件files,其他参数params
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
}

// 多组文件+参数 params=[{fileList:[], fileName:''}]
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组件文件列表中文件图标自定义显示


vue3笔记(19-2)文件上传
https://guoningyan.com/2023/04/04/vue3笔记(19-2)文件上传/
作者
Ningyan Guo
发布于
2023年4月4日
许可协议