EvidenceUpload.vue 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. <script setup lang="ts">
  2. import { inject, onBeforeUnmount, ref, watch } from 'vue'
  3. import { Upload, Delete } from '@element-plus/icons-vue'
  4. import type { Attachment } from '../api/indexType'
  5. import { createUploadId, uploadImage } from '../utils/upload'
  6. import { report } from '../utils/ui'
  7. import { uploadsKey } from '../utils/uploads'
  8. const props = defineProps<{ active: boolean; disabled?: boolean }>()
  9. const pendingUploads = inject(uploadsKey)
  10. let generation = 0
  11. function invalidate() { generation += 1 }
  12. watch(() => props.active, invalidate, { flush: 'sync' })
  13. onBeforeUnmount(invalidate)
  14. const model = defineModel<Attachment[]>({ required: true })
  15. const busy = ref(false)
  16. async function select(event : Event) {
  17. if (busy.value || props.disabled || !props.active) return
  18. const input = event.target as HTMLInputElement
  19. const currentGeneration = generation
  20. busy.value = true
  21. if (pendingUploads) pendingUploads.value += 1
  22. try {
  23. for (const file of Array.from(input.files || [])) {
  24. const url = await uploadImage(file)
  25. if (currentGeneration !== generation || !props.active) return
  26. model.value = [...model.value, { id: createUploadId(), name: file.name, url }]
  27. }
  28. }
  29. catch (error) { if (currentGeneration === generation) report(error) }
  30. finally { busy.value = false; input.value = ''; if (pendingUploads) pendingUploads.value -= 1 }
  31. }
  32. </script>
  33. <template>
  34. <div class="evidence">
  35. <label class="upload-button">
  36. <el-icon>
  37. <Upload />
  38. </el-icon>{{ busy ? '上传中…' : '上传图片' }}
  39. <input type="file" accept="image/png,image/jpeg,image/webp" multiple :disabled="busy || disabled" @change="select">
  40. </label>
  41. <div v-for="(file, index) in model" :key="file.id" class="attachment">
  42. <el-image :src="file.url" :preview-src-list="model.map(item => item.url)" :initial-index="index"
  43. preview-teleported fit="cover" />
  44. <el-button :disabled="busy || disabled" :icon="Delete" text aria-label="移除图片" title="移除图片"
  45. @click="model = model.filter(item => item.id !== file.id)" />
  46. </div>
  47. </div>
  48. </template>