imgs.vue 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. <template>
  2. <div class="mb-3 color-666">共<strong class="color-primary"> {{ srcList.length }} </strong>张,还能上传 <strong class="color-primary">{{ (Number(limit) - Number(srcList.length)) }}</strong> 张</div>
  3. <div class="d-flex flex-wrap">
  4. <div style="position: relative;" v-for="val in srcList" :key="val" class="mr-3 mb-3">
  5. <v-icon color="error" class="close" @click="handleClose(val)">mdi-close-circle</v-icon>
  6. <v-img :src="val" width="100" height="100" rounded class="imgBox"></v-img>
  7. </div>
  8. <div v-if="srcList.length < limit" class="upload d-flex align-center justify-center flex-column" @click="openFileInput">
  9. <v-icon color="#ccc" :size="tips ? 30 : 50">mdi-plus</v-icon>
  10. <div class="font-size-12 color-999">{{ tips }}</div>
  11. <input
  12. type="file"
  13. ref="fileInput"
  14. accept="image/*"
  15. style="display: none;"
  16. @change="handleUploadFile"
  17. />
  18. </div>
  19. </div>
  20. </template>
  21. <script setup>
  22. // 图片组上传
  23. defineOptions({ name: 'upload-imgs'})
  24. import { ref, watch } from 'vue'
  25. import { uploadFile } from '@/api/common'
  26. import { useI18n } from '@/hooks/web/useI18n'
  27. import Snackbar from '@/plugins/snackbar'
  28. const props = defineProps({
  29. modelValue: Array,
  30. tips: {
  31. type: String,
  32. default: '上传图片'
  33. },
  34. showSnackbar: {
  35. type: Boolean,
  36. default: false
  37. },
  38. limit: {
  39. type: [Number, String],
  40. default: 9
  41. }
  42. })
  43. const { t } = useI18n()
  44. const srcList = ref([])
  45. watch(() => props.modelValue, (newVal) => {
  46. srcList.value = newVal
  47. }, { immediate: true }, { deep: true })
  48. // 选择文件
  49. const fileInput = ref()
  50. const clicked = ref(false)
  51. const openFileInput = () => {
  52. if (srcList.value.length >= props.limit) return Snackbar.warning(`最多可上传${props.limit}张图片`)
  53. if (clicked.value) return
  54. clicked.value = true
  55. fileInput.value.click()
  56. clicked.value = false
  57. }
  58. // 文件上传
  59. const accept = ['jpg', 'png', 'webp', 'jpeg']
  60. const handleUploadFile = async (e) => {
  61. const file = e.target.files[0]
  62. const size = file.size
  63. if (size / (1024*1024) > 20) {
  64. Snackbar.warning(t('common.fileSizeExceed'))
  65. return
  66. }
  67. const arr = file.name.split('.')
  68. const fileType = arr?.length ? arr[arr.length-1] : ''
  69. if (!accept.includes(fileType)) return Snackbar.warning('请上传图片格式')
  70. const formData = new FormData()
  71. formData.append('file', file)
  72. formData.append('path', 'img')
  73. const { data } = await uploadFile(formData)
  74. if (!data) return Snackbar.error('上传失败')
  75. if (props.showSnackbar) Snackbar.success(t('common.uploadSucMsg'))
  76. srcList.value.push(data)
  77. }
  78. const handleClose = (src) => {
  79. const index = srcList.value.indexOf(src)
  80. if (index === -1) return
  81. srcList.value.splice(index, 1)
  82. }
  83. </script>
  84. <style scoped lang="scss">
  85. .upload {
  86. width: 100px;
  87. height: 100px;
  88. border: 1px solid #ccc;
  89. border-radius: 4px;
  90. cursor: pointer;
  91. }
  92. .imgBox {
  93. position: relative;
  94. border: 1px solid #ccc;
  95. border-radius: 4px;
  96. padding: 5px;
  97. }
  98. .close {
  99. position: absolute;
  100. top: -10px;
  101. right: -10px;
  102. cursor: pointer;
  103. z-index: 9;
  104. }
  105. </style>