ElImageUpload.vue 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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. modelValue: any
  11. item?: BasicFormItem
  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. const attrs: any = props.oss ? { 'http-request': ossUpload } : {}
  85. if (props.item) {
  86. // 接口需要的属性
  87. props.item.extra = computed(() => uploadList.value.map((item: any) => item.response?.url || item.url))
  88. if (!props.item.props?.onSuccess) {
  89. attrs['on-success'] = handleUploadSuccess
  90. }
  91. }
  92. </script>
  93. <template>
  94. <el-upload
  95. ref="uploadRef"
  96. v-model:file-list="modelValue"
  97. :class="{ 'is-disabled': modelValue.length >= limit }"
  98. :action="isAbsolutePath(props.uploadApi) ? props.uploadApi : baseApi + props.uploadApi"
  99. :headers="headers"
  100. :before-upload="beforeUpload"
  101. :limit="limit"
  102. list-type="picture-card"
  103. accept="image/*"
  104. v-bind="{ ...attrs, ...$attrs }"
  105. >
  106. <el-icon class="avatar-uploader-icon" :style="{ fontSize: iconSize }"><Plus /></el-icon>
  107. <template #file="{ file }">
  108. <el-image class="el-upload-list__item-thumbnail" :src="genImageUrl(file.url)" fit="cover" />
  109. <span class="el-upload-list__item-actions">
  110. <span class="el-upload-list__item-preview" @click="handlePreview(file)">
  111. <el-icon><zoom-in /></el-icon>
  112. </span>
  113. <span class="el-upload-list__item-delete" @click="handleRemove(file)">
  114. <el-icon><Delete /></el-icon>
  115. </span>
  116. </span>
  117. </template>
  118. </el-upload>
  119. <el-image-viewer v-if="showViewer" :url-list="previewList" :initial-index="previewIndex" @close="closeViewer" />
  120. </template>
  121. <style lang="scss" scoped>
  122. :deep(.el-upload) {
  123. width: v-bind(size);
  124. height: v-bind(size);
  125. border: 1px dashed var(--el-border-color);
  126. border-radius: 6px;
  127. cursor: pointer;
  128. position: relative;
  129. overflow: hidden;
  130. transition: var(--el-transition-duration-fast);
  131. &:hover {
  132. border-color: var(--el-color-primary);
  133. }
  134. }
  135. :deep(.el-upload-list--picture-card .el-upload-list__item) {
  136. width: v-bind(size);
  137. height: v-bind(size);
  138. }
  139. .is-disabled :deep(.el-upload--picture-card) {
  140. display: none;
  141. }
  142. </style>