index.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <script lang="ts" setup name="ImageUpload">
  2. import type { UploadProps } from 'element-plus'
  3. import { ElMessage } from 'element-plus'
  4. const props = defineProps({
  5. action: {
  6. type: String,
  7. required: true,
  8. },
  9. headers: {
  10. type: Object,
  11. default: () => {},
  12. },
  13. data: {
  14. type: Object,
  15. default: () => {},
  16. },
  17. name: {
  18. type: String,
  19. default: 'file',
  20. },
  21. url: {
  22. type: String,
  23. default: '',
  24. },
  25. size: {
  26. type: Number,
  27. default: 2,
  28. },
  29. width: {
  30. type: Number,
  31. default: 150,
  32. },
  33. height: {
  34. type: Number,
  35. default: 150,
  36. },
  37. placeholder: {
  38. type: String,
  39. default: '',
  40. },
  41. notip: {
  42. type: Boolean,
  43. default: false,
  44. },
  45. ext: {
  46. type: Array,
  47. default: () => ['jpg', 'png', 'gif', 'bmp'],
  48. },
  49. })
  50. const emit = defineEmits(['update:url', 'onSuccess'])
  51. const uploadData = ref({
  52. imageViewerVisible: false,
  53. progress: {
  54. preview: '',
  55. percent: 0,
  56. },
  57. })
  58. // 预览
  59. function preview() {
  60. uploadData.value.imageViewerVisible = true
  61. }
  62. // 关闭预览
  63. function previewClose() {
  64. uploadData.value.imageViewerVisible = false
  65. }
  66. // 移除
  67. function remove() {
  68. emit('update:url', '')
  69. }
  70. const beforeUpload: UploadProps['beforeUpload'] = (file) => {
  71. const fileName = file.name.split('.')
  72. const fileExt = fileName.at(-1)
  73. const isTypeOk = props.ext.includes(fileExt)
  74. const isSizeOk = file.size / 1024 / 1024 < props.size
  75. if (!isTypeOk) {
  76. ElMessage.error(`上传图片只支持 ${props.ext.join(' / ')} 格式!`)
  77. }
  78. if (!isSizeOk) {
  79. ElMessage.error(`上传图片大小不能超过 ${props.size}MB!`)
  80. }
  81. if (isTypeOk && isSizeOk) {
  82. uploadData.value.progress.preview = URL.createObjectURL(file)
  83. }
  84. return isTypeOk && isSizeOk
  85. }
  86. const onProgress: UploadProps['onProgress'] = (file) => {
  87. uploadData.value.progress.percent = ~~file.percent
  88. }
  89. const onSuccess: UploadProps['onSuccess'] = (res) => {
  90. uploadData.value.progress.preview = ''
  91. uploadData.value.progress.percent = 0
  92. emit('onSuccess', res)
  93. }
  94. </script>
  95. <template>
  96. <div class="upload-container">
  97. <el-upload
  98. :show-file-list="false"
  99. :headers="headers"
  100. :action="action"
  101. :data="data"
  102. :name="name"
  103. :before-upload="beforeUpload"
  104. :on-progress="onProgress"
  105. :on-success="onSuccess"
  106. drag
  107. class="image-upload"
  108. >
  109. <el-image v-if="url === ''" :src="url === '' ? placeholder : url" :style="`width:${width}px;height:${height}px;`" fit="fill">
  110. <template #error>
  111. <div class="image-slot" :style="`width:${width}px;height:${height}px;`">
  112. <el-icon>
  113. <svg-icon name="i-ep:plus" />
  114. </el-icon>
  115. </div>
  116. </template>
  117. </el-image>
  118. <div v-else class="image">
  119. <el-image :src="url" :style="`width:${width}px;height:${height}px;`" fit="fill" />
  120. <div class="mask">
  121. <div class="actions">
  122. <span title="预览" @click.stop="preview">
  123. <el-icon>
  124. <svg-icon name="i-ep:zoom-in" />
  125. </el-icon>
  126. </span>
  127. <span title="移除" @click.stop="remove">
  128. <el-icon>
  129. <svg-icon name="i-ep:delete" />
  130. </el-icon>
  131. </span>
  132. </div>
  133. </div>
  134. </div>
  135. <div v-show="url === '' && uploadData.progress.percent" class="progress" :style="`width:${width}px;height:${height}px;`">
  136. <el-image :src="uploadData.progress.preview" :style="`width:${width}px;height:${height}px;`" fit="fill" />
  137. <el-progress type="circle" :width="Math.min(width, height) * 0.8" :percentage="uploadData.progress.percent" />
  138. </div>
  139. </el-upload>
  140. <div v-if="!notip" class="el-upload__tip">
  141. <div style="display: inline-block;">
  142. <el-alert :title="`上传图片支持 ${ext.join(' / ')} 格式,且图片大小不超过 ${size}MB,建议图片尺寸为 ${width}*${height}`" type="info" show-icon :closable="false" />
  143. </div>
  144. </div>
  145. <el-image-viewer v-if="uploadData.imageViewerVisible" :url-list="[url]" teleported @close="previewClose" />
  146. </div>
  147. </template>
  148. <style lang="scss" scoped>
  149. .upload-container {
  150. line-height: initial;
  151. }
  152. .el-image {
  153. display: block;
  154. }
  155. .image {
  156. position: relative;
  157. border-radius: 6px;
  158. overflow: hidden;
  159. .mask {
  160. opacity: 0;
  161. position: absolute;
  162. top: 0;
  163. width: 100%;
  164. height: 100%;
  165. background-color: var(--el-overlay-color-lighter);
  166. transition: opacity 0.3s;
  167. .actions {
  168. width: 100px;
  169. height: 100px;
  170. display: flex;
  171. flex-wrap: wrap;
  172. align-items: center;
  173. justify-content: center;
  174. @include position-center(xy);
  175. span {
  176. width: 50%;
  177. text-align: center;
  178. cursor: pointer;
  179. color: var(--el-color-white);
  180. transition: color 0.1s, transform 0.1s;
  181. &:hover {
  182. transform: scale(1.5);
  183. }
  184. .el-icon {
  185. font-size: 24px;
  186. }
  187. }
  188. }
  189. }
  190. &:hover .mask {
  191. opacity: 1;
  192. }
  193. }
  194. .image-upload {
  195. display: inline-block;
  196. vertical-align: top;
  197. }
  198. :deep(.el-upload) {
  199. .el-upload-dragger {
  200. display: inline-block;
  201. padding: 0;
  202. &.is-dragover {
  203. border-width: 1px;
  204. }
  205. .image-slot {
  206. display: flex;
  207. justify-content: center;
  208. align-items: center;
  209. width: 100%;
  210. height: 100%;
  211. color: var(--el-text-color-placeholder);
  212. background-color: transparent;
  213. i {
  214. font-size: 30px;
  215. }
  216. }
  217. .progress {
  218. position: absolute;
  219. top: 0;
  220. &::after {
  221. content: "";
  222. position: absolute;
  223. width: 100%;
  224. height: 100%;
  225. left: 0;
  226. top: 0;
  227. background-color: var(--el-overlay-color-lighter);
  228. }
  229. .el-progress {
  230. z-index: 1;
  231. @include position-center(xy);
  232. .el-progress__text {
  233. color: var(--el-text-color-placeholder);
  234. }
  235. }
  236. }
  237. }
  238. }
  239. </style>