houzhongjian
2024-08-08 820397e43a0b64d35c6d31d2a55475061438593b
提交 | 用户 | 时间
820397 1 <template>
H 2   <div class="upload-file">
3     <el-upload
4       ref="uploadRef"
5       v-model:file-list="fileList"
6       :action="uploadUrl"
7       :auto-upload="autoUpload"
8       :before-upload="beforeUpload"
9       :disabled="disabled"
10       :drag="drag"
11       :http-request="httpRequest"
12       :limit="props.limit"
13       :multiple="props.limit > 1"
14       :on-error="excelUploadError"
15       :on-exceed="handleExceed"
16       :on-preview="handlePreview"
17       :on-remove="handleRemove"
18       :on-success="handleFileSuccess"
19       :show-file-list="true"
20       class="upload-file-uploader"
21       name="file"
22     >
23       <el-button v-if="!disabled" type="primary">
24         <Icon icon="ep:upload-filled" />
25         选取文件
26       </el-button>
27       <template v-if="isShowTip && !disabled" #tip>
28         <div style="font-size: 8px">
29           大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
30         </div>
31         <div style="font-size: 8px">
32           格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 的文件
33         </div>
34       </template>
35       <!-- TODO @puhui999:1)表单展示的时候,位置会偏掉,已发微信;2)disable 的时候,应该把【删除】按钮也隐藏掉? -->
36       <template #file="row">
37         <div class="flex items-center">
38           <span>{{ row.file.name }}</span>
39           <div class="ml-10px">
40             <el-link
41               :href="row.file.url"
42               :underline="false"
43               download
44               target="_blank"
45               type="primary"
46             >
47               下载
48             </el-link>
49           </div>
50           <div class="ml-10px">
51             <el-button link type="danger" @click="handleRemove(row.file)"> 删除</el-button>
52           </div>
53         </div>
54       </template>
55     </el-upload>
56   </div>
57 </template>
58 <script lang="ts" setup>
59 import { propTypes } from '@/utils/propTypes'
60 import type { UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
61 import { isString } from '@/utils/is'
62 import { useUpload } from '@/components/UploadFile/src/useUpload'
63 import { UploadFile } from 'element-plus/es/components/upload/src/upload'
64
65 defineOptions({ name: 'UploadFile' })
66
67 const message = useMessage() // 消息弹窗
68 const emit = defineEmits(['update:modelValue'])
69
70 const props = defineProps({
71   modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
72   fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
73   fileSize: propTypes.number.def(5), // 大小限制(MB)
74   limit: propTypes.number.def(5), // 数量限制
75   autoUpload: propTypes.bool.def(true), // 自动上传
76   drag: propTypes.bool.def(false), // 拖拽上传
77   isShowTip: propTypes.bool.def(true), // 是否显示提示
78   disabled: propTypes.bool.def(false) // 是否禁用上传组件 ==> 非必传(默认为 false)
79 })
80
81 // ========== 上传相关 ==========
82 const uploadRef = ref<UploadInstance>()
83 const uploadList = ref<UploadUserFile[]>([])
84 const fileList = ref<UploadUserFile[]>([])
85 const uploadNumber = ref<number>(0)
86
87 const { uploadUrl, httpRequest } = useUpload()
88
89 // 文件上传之前判断
90 const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
91   if (fileList.value.length >= props.limit) {
92     message.error(`上传文件数量不能超过${props.limit}个!`)
93     return false
94   }
95   let fileExtension = ''
96   if (file.name.lastIndexOf('.') > -1) {
97     fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
98   }
99   const isImg = props.fileType.some((type: string) => {
100     if (file.type.indexOf(type) > -1) return true
101     return !!(fileExtension && fileExtension.indexOf(type) > -1)
102   })
103   const isLimit = file.size < props.fileSize * 1024 * 1024
104   if (!isImg) {
105     message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
106     return false
107   }
108   if (!isLimit) {
109     message.error(`上传文件大小不能超过${props.fileSize}MB!`)
110     return false
111   }
112   message.success('正在上传文件,请稍候...')
113   uploadNumber.value++
114 }
115 // 处理上传的文件发生变化
116 // const handleFileChange = (uploadFile: UploadFile): void => {
117 //   uploadRef.value.data.path = uploadFile.name
118 // }
119 // 文件上传成功
120 const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
121   message.success('上传成功')
122   // 删除自身
123   const index = fileList.value.findIndex((item) => item.response?.data === res.data)
124   fileList.value.splice(index, 1)
125   uploadList.value.push({ name: res.data, url: res.data })
126   if (uploadList.value.length == uploadNumber.value) {
127     fileList.value.push(...uploadList.value)
128     uploadList.value = []
129     uploadNumber.value = 0
130     emitUpdateModelValue()
131   }
132 }
133 // 文件数超出提示
134 const handleExceed: UploadProps['onExceed'] = (): void => {
135   message.error(`上传文件数量不能超过${props.limit}个!`)
136 }
137 // 上传错误提示
138 const excelUploadError: UploadProps['onError'] = (): void => {
139   message.error('导入数据失败,请您重新上传!')
140 }
141 // 删除上传文件
142 const handleRemove = (file: UploadFile) => {
143   const index = fileList.value.map((f) => f.name).indexOf(file.name)
144   if (index > -1) {
145     fileList.value.splice(index, 1)
146     emitUpdateModelValue()
147   }
148 }
149 const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
150   console.log(uploadFile)
151 }
152
153 // 监听模型绑定值变动
154 watch(
155   () => props.modelValue,
156   (val: string | string[]) => {
157     if (!val) {
158       fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
159       return
160     }
161
162     fileList.value = [] // 保障数据为空
163     // 情况1:字符串
164     if (isString(val)) {
165       fileList.value.push(
166         ...val.split(',').map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
167       )
168       return
169     }
170     // 情况2:数组
171     fileList.value.push(
172       ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
173     )
174   },
175   { immediate: true, deep: true }
176 )
177 // 发送文件链接列表更新
178 const emitUpdateModelValue = () => {
179   // 情况1:数组结果
180   let result: string | string[] = fileList.value.map((file) => file.url!)
181   // 情况2:逗号分隔的字符串
182   if (props.limit === 1 || isString(props.modelValue)) {
183     result = result.join(',')
184   }
185   emit('update:modelValue', result)
186 }
187 </script>
188 <style lang="scss" scoped>
189 .upload-file-uploader {
190   margin-bottom: 5px;
191 }
192
193 :deep(.upload-file-list .el-upload-list__item) {
194   position: relative;
195   margin-bottom: 10px;
196   line-height: 2;
197   border: 1px solid #e4e7ed;
198 }
199
200 :deep(.el-upload-list__item-file-name) {
201   max-width: 250px;
202 }
203
204 :deep(.upload-file-list .ele-upload-list__item-content) {
205   display: flex;
206   justify-content: space-between;
207   align-items: center;
208   color: inherit;
209 }
210
211 :deep(.ele-upload-list__item-content-action .el-link) {
212   margin-right: 10px;
213 }
214 </style>