FileUploader.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. <script setup lang="ts">
  2. import { ref, watch } from 'vue'
  3. import { Plus, Upload } from '@element-plus/icons-vue'
  4. import { ElButton, ElUpload, } from 'element-plus'
  5. import type {UploadProgressEvent, UploadFile, UploadFiles } from 'element-plus'
  6. import { toast } from 'vue-sonner'
  7. import { BASE_URL } from '@/api/index'
  8. const props = withDefaults(defineProps<{
  9. modelValue?: UploadFile | null | undefined
  10. action?: string
  11. method?: string
  12. headers?: Headers | Record<string, any>
  13. data?: Record<string, any>
  14. name?: string
  15. afterUpload?: (response: any) => string | Promise<string>
  16. multiple?: boolean
  17. ext?: string[]
  18. max?: number
  19. width?: number
  20. height?: number
  21. dimension?: {
  22. width: number
  23. height: number
  24. }
  25. size?: number
  26. hideTips?: boolean
  27. disabled?: boolean
  28. }>(), {
  29. method: 'post',
  30. headers: () => {
  31. const userStore = useUserStore()
  32. if (userStore.isLogin) {
  33. return {
  34. accessToken: userStore.token
  35. }
  36. }
  37. return {}
  38. },
  39. data: () => ({}),
  40. name: 'file',
  41. multiple: false,
  42. ext: () => [],
  43. max: 1,
  44. width: 100,
  45. height: 100,
  46. size: 5 * 1024 * 1024,
  47. hideTips: false,
  48. disabled: false,
  49. })
  50. const emit = defineEmits<{
  51. // 上传成功时 emit 事件
  52. 'success': [response: any, file: UploadFile, fileList: UploadFiles]
  53. // 更新 v-model: 必须命名为 'update:modelValue' 以支持 v-model
  54. 'update:modelValue': [value: UploadFile | null]
  55. }>()
  56. const internalFileList = ref<UploadFiles>(props.modelValue ? [props.modelValue] : [])
  57. watch(
  58. () => props.modelValue,
  59. (newVal) => {
  60. // 如果父组件的 modelValue 变为 null/undefined,清空内部列表
  61. if (!newVal) {
  62. internalFileList.value = []
  63. } else {
  64. // 如果有新值,更新(避免重复添加)
  65. if (internalFileList.value[0]?.uid !== newVal.uid) {
  66. internalFileList.value = [newVal]
  67. }
  68. }
  69. }
  70. )
  71. const loading = ref(false)
  72. function handleUploadSuccess(response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) {
  73. console.log('【上传成功】响应:', response)
  74. console.log('【上传成功】文件对象:', uploadFile) // 这是最新的文件对象 (包含 url 等)
  75. loading.value = false
  76. emit('update:modelValue', uploadFile)
  77. // 7. 触发成功事件
  78. emit('success', response, uploadFile, uploadFiles)
  79. }
  80. // 8. 上传失败的回调
  81. function handleUploadError(error: Error, uploadFile: UploadFile, uploadFiles: UploadFiles) {
  82. console.error('【上传失败】:', error, uploadFile)
  83. emit('update:modelValue', null) // 失败时也通知父组件
  84. toast.error('文件上传失败')
  85. }
  86. function handleProgress (evt: UploadProgressEvent, uploadFile: UploadFile, uploadFiles: UploadFiles) {
  87. loading.value = true
  88. console.log(evt, uploadFile, uploadFiles)
  89. }
  90. // 添加文件选择变化的处理
  91. function handleFileChange(file: UploadFile, fileList: UploadFiles) {
  92. console.log(file, fileList)
  93. // 当有新文件被选择时,清空现有列表,允许新文件上传
  94. if (file.status === 'success') {
  95. internalFileList.value = [];
  96. }
  97. }
  98. </script>
  99. <template>
  100. <ElUpload
  101. v-model:file-list="internalFileList"
  102. :action="`${BASE_URL}/anycall/import`"
  103. :show-file-list="false"
  104. :limit="1"
  105. :headers="props.headers"
  106. @success="handleUploadSuccess"
  107. @progress="handleProgress"
  108. @change="handleFileChange"
  109. @error="handleUploadError"
  110. :auto-upload="true"
  111. >
  112. <ElButton :loading="loading" type="primary" :icon="Upload">Import</ElButton>
  113. </ElUpload>
  114. </template>