ElImageUpload.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. <script lang="ts" setup>
  2. import { useUserStore } from '@/stores/user'
  3. import { ACCESS_TOKEN } from '@/utils/constants'
  4. import { ElMessage } from 'element-plus'
  5. import { isAbsolutePath, ossUpload } from '@/utils/utils'
  6. import config from '@/config/defaultSetting'
  7. import type { UploadProps, UploadFile } from 'element-plus'
  8. import type { BasicFormItem } from '@/types/form'
  9. interface Props {
  10. item: BasicFormItem
  11. modelValue: any
  12. size?: number | string
  13. iconSize?: number | string
  14. fileSize?: number | string
  15. uploadApi?: string
  16. oss?: boolean
  17. limit?: number
  18. }
  19. const props = withDefaults(defineProps<Props>(), {
  20. size: '148px',
  21. iconSize: '28px',
  22. uploadApi: config.uploadApi,
  23. fileSize: 10,
  24. oss: config.oss,
  25. limit: 1
  26. })
  27. const emits = defineEmits(['update:modelValue'])
  28. const baseApi = import.meta.env.VITE_BASE_API
  29. const modelValue = computed({
  30. get: () => props.modelValue,
  31. set: value => emits('update:modelValue', value)
  32. })
  33. const user = useUserStore()
  34. const headers = reactive({
  35. [ACCESS_TOKEN]: user.token
  36. })
  37. const limit = Number(props.limit) || 1
  38. // 生成图片地址
  39. const prefix = props.oss ? config.ossHost : baseApi
  40. const genImageUrl = (url: string) => {
  41. if (url.startsWith('blob:') || isAbsolutePath(url)) {
  42. return url
  43. } else {
  44. return prefix + url
  45. }
  46. }
  47. const uploadRef = ref()
  48. // 在上传之前对文件进行验证
  49. const fileSize = Number(props.fileSize) || 10
  50. const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
  51. if (rawFile.size / 1024 / 1024 > fileSize) {
  52. ElMessage.error(`图片大小不能超过${fileSize}MB!`)
  53. return false
  54. }
  55. return true
  56. }
  57. // 图片上传成功
  58. const uploadList = ref([...props.modelValue])
  59. const handleUploadSuccess: UploadProps['onSuccess'] = (response, uploadFile: UploadFile, uploadFiles: UploadFile[]) => {
  60. if (response.success || response.code === 200) {
  61. // 附加oss路径
  62. uploadList.value = uploadFiles
  63. } else {
  64. ElMessage.error(response.msg)
  65. }
  66. }
  67. // 删除图片
  68. const handleRemove = (file: UploadFile) => {
  69. uploadRef.value.handleRemove(file)
  70. uploadList.value = modelValue.value
  71. }
  72. // 图片预览
  73. const showViewer = ref(false)
  74. const previewIndex = ref(0)
  75. const previewList = computed(() => modelValue.value.map((item: any) => genImageUrl(item.url)))
  76. const handlePreview = (file: UploadFile) => {
  77. previewIndex.value = modelValue.value.findIndex((item: any) => item.url === file.url)
  78. showViewer.value = true
  79. }
  80. const closeViewer = () => {
  81. showViewer.value = false
  82. }
  83. // 接口需要的属性
  84. props.item.extra = computed(() => uploadList.value.map((item: any) => item.response?.url || item.url))
  85. // 动态属性
  86. const attrs: any = props.oss ? { 'http-request': ossUpload } : {}
  87. if (!props.item.props?.onSuccess) {
  88. attrs['on-success'] = handleUploadSuccess
  89. }
  90. </script>
  91. <template>
  92. <el-upload
  93. ref="uploadRef"
  94. v-model:file-list="modelValue"
  95. :class="{ 'is-disabled': modelValue.length >= limit }"
  96. :action="isAbsolutePath(props.uploadApi) ? props.uploadApi : baseApi + props.uploadApi"
  97. :headers="headers"
  98. :before-upload="beforeUpload"
  99. :limit="limit"
  100. list-type="picture-card"
  101. accept="image/*"
  102. v-bind="{ ...attrs, ...$attrs }"
  103. >
  104. <el-icon class="avatar-uploader-icon" :style="{ fontSize: iconSize }"><Plus /></el-icon>
  105. <template #file="{ file }">
  106. <el-image class="el-upload-list__item-thumbnail" :src="genImageUrl(file.url)" fit="cover" />
  107. <span class="el-upload-list__item-actions">
  108. <span class="el-upload-list__item-preview" @click="handlePreview(file)">
  109. <el-icon><zoom-in /></el-icon>
  110. </span>
  111. <span class="el-upload-list__item-delete" @click="handleRemove(file)">
  112. <el-icon><Delete /></el-icon>
  113. </span>
  114. </span>
  115. </template>
  116. </el-upload>
  117. <el-image-viewer v-if="showViewer" :url-list="previewList" :initial-index="previewIndex" @close="closeViewer" />
  118. </template>
  119. <style lang="scss" scoped>
  120. :deep(.el-upload) {
  121. width: v-bind(size);
  122. height: v-bind(size);
  123. border: 1px dashed var(--el-border-color);
  124. border-radius: 6px;
  125. cursor: pointer;
  126. position: relative;
  127. overflow: hidden;
  128. transition: var(--el-transition-duration-fast);
  129. &:hover {
  130. border-color: var(--el-color-primary);
  131. }
  132. }
  133. :deep(.el-upload-list--picture-card .el-upload-list__item) {
  134. width: v-bind(size);
  135. height: v-bind(size);
  136. }
  137. .is-disabled :deep(.el-upload--picture-card) {
  138. display: none;
  139. }
  140. </style>