UploadFile.vue 7.1 KB

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