Img.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <template>
  2. <div class="upload-box">
  3. <el-upload
  4. :id="uuid"
  5. action="#"
  6. :class="['upload', self_disabled ? 'disabled' : '', drag ? 'no-border' : '']"
  7. :multiple="false"
  8. :disabled="self_disabled"
  9. :show-file-list="false"
  10. :http-request="handleHttpUpload"
  11. :before-upload="beforeUpload"
  12. :on-success="uploadSuccess"
  13. :on-error="uploadError"
  14. :drag="drag"
  15. :accept="fileType.join(',')"
  16. >
  17. <template v-if="imageUrl">
  18. <img :src="'/api' + imageUrl" class="upload-image" />
  19. <div class="upload-handle" @click.stop>
  20. <div v-if="!self_disabled" class="handle-icon" @click="editImg">
  21. <el-icon>
  22. <Edit />
  23. </el-icon>
  24. <span>编辑</span>
  25. </div>
  26. <div class="handle-icon" @click="imgViewVisible = true">
  27. <el-icon>
  28. <ZoomIn />
  29. </el-icon>
  30. <span>查看</span>
  31. </div>
  32. <div v-if="!self_disabled" class="handle-icon" @click="deleteImg">
  33. <el-icon>
  34. <Delete />
  35. </el-icon>
  36. <span>删除</span>
  37. </div>
  38. </div>
  39. </template>
  40. <template v-else>
  41. <div class="upload-empty">
  42. <slot name="empty">
  43. <el-icon>
  44. <Plus />
  45. </el-icon>
  46. <!-- <span>请上传图片</span> -->
  47. </slot>
  48. </div>
  49. </template>
  50. </el-upload>
  51. <div class="el-upload__tip">
  52. <slot name="tip"></slot>
  53. </div>
  54. <!-- <el-dialog id="dialog" v-if="isShowData" :visible="imgViewVisible">-->
  55. <!-- <el-image-viewer :url-list="['/api' + imageUrl]" />-->
  56. <!-- </el-dialog>-->
  57. <!-- <el-image-viewer v-else-if="imgViewVisible" :url-list="['/api' + imageUrl]" @close="imgViewVisible = false" />-->
  58. <el-image-viewer
  59. v-if="imgViewVisible"
  60. style="position: absolute; z-index: 9999"
  61. :z-index="9999"
  62. :url-list="['/api' + imageUrl]"
  63. @close="imgViewVisible = false"
  64. />
  65. </div>
  66. </template>
  67. <script setup lang="ts" name="UploadImg">
  68. import { ref, computed, inject } from 'vue'
  69. import { generateUUID } from '@/utils'
  70. import { uploadImg } from '@/api/modules/upload'
  71. import { ElNotification, formContextKey, formItemContextKey } from 'element-plus'
  72. import type { UploadProps, UploadRequestOptions } from 'element-plus'
  73. import mittBus from '@/utils/mittBus'
  74. interface UploadFileProps {
  75. imageUrl: string // 图片地址 ==> 必传
  76. api?: (params: any) => Promise<any> // 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可 ==> 非必传
  77. drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
  78. disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
  79. fileSize?: number // 图片大小限制 ==> 非必传(默认为 5M)
  80. fileType?: File.ImageMimeType[] // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
  81. height?: string // 组件高度 ==> 非必传(默认为 150px)
  82. width?: string // 组件宽度 ==> 非必传(默认为 150px)
  83. borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
  84. isShowData?: boolean
  85. }
  86. // 接受父组件参数
  87. const props = withDefaults(defineProps<UploadFileProps>(), {
  88. imageUrl: '',
  89. drag: true,
  90. disabled: false,
  91. fileSize: 5,
  92. fileType: () => ['image/jpeg', 'image/png', 'image/gif'],
  93. height: '150px',
  94. width: '150px',
  95. borderRadius: '8px',
  96. isShowData: false
  97. })
  98. // 生成组件唯一id
  99. const uuid = ref('id-' + generateUUID())
  100. // 查看图片
  101. const imgViewVisible = ref(false)
  102. // 获取 el-form 组件上下文
  103. const formContext = inject(formContextKey, void 0)
  104. // 获取 el-form-item 组件上下文
  105. const formItemContext = inject(formItemContextKey, void 0)
  106. // 判断是否禁用上传和删除
  107. const self_disabled = computed(() => {
  108. return props.disabled || formContext?.disabled
  109. })
  110. /**
  111. * @description 图片上传
  112. * @param options upload 所有配置项
  113. * */
  114. const emit = defineEmits<{
  115. 'update:imageUrl': [value: string]
  116. }>()
  117. const handleHttpUpload = async (options: UploadRequestOptions) => {
  118. let formData = new FormData()
  119. formData.append('file', options.file)
  120. try {
  121. const api = props.api ?? uploadImg
  122. const res = await api(formData)
  123. emit('update:imageUrl', res.data.url)
  124. mittBus.emit('data:fileName', res.data.fileName)
  125. // 调用 el-form 内部的校验方法(可自动校验)
  126. formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
  127. } catch (error) {
  128. options.onError(error as any)
  129. }
  130. }
  131. /**
  132. * @description 删除图片
  133. * */
  134. const deleteImg = () => {
  135. emit('update:imageUrl', '')
  136. }
  137. /**
  138. * @description 编辑图片
  139. * */
  140. const editImg = () => {
  141. const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
  142. dom && dom.dispatchEvent(new MouseEvent('click'))
  143. }
  144. /**
  145. * @description 文件上传之前判断
  146. * @param rawFile 选择的文件
  147. * */
  148. const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
  149. const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
  150. const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType)
  151. if (!imgType)
  152. ElNotification({
  153. title: '温馨提示',
  154. message: '上传图片不符合所需的格式!',
  155. type: 'warning'
  156. })
  157. if (!imgSize)
  158. setTimeout(() => {
  159. ElNotification({
  160. title: '温馨提示',
  161. message: `上传图片大小不能超过 ${props.fileSize}M!`,
  162. type: 'warning'
  163. })
  164. }, 0)
  165. return imgType && imgSize
  166. }
  167. /**
  168. * @description 图片上传成功
  169. * */
  170. const uploadSuccess = () => {
  171. ElNotification({
  172. title: '温馨提示',
  173. message: '图片上传成功!',
  174. type: 'success'
  175. })
  176. }
  177. /**
  178. * @description 图片上传错误
  179. * */
  180. const uploadError = () => {
  181. ElNotification({
  182. title: '温馨提示',
  183. message: '图片上传失败,请您重新上传!',
  184. type: 'error'
  185. })
  186. }
  187. </script>
  188. <style scoped lang="scss">
  189. .is-error {
  190. .upload {
  191. :deep(.el-upload),
  192. :deep(.el-upload-dragger) {
  193. border: 1px dashed var(--el-color-danger) !important;
  194. &:hover {
  195. border-color: var(--el-color-primary) !important;
  196. }
  197. }
  198. }
  199. }
  200. :deep(.disabled) {
  201. .el-upload,
  202. .el-upload-dragger {
  203. cursor: not-allowed !important;
  204. background: var(--el-disabled-bg-color);
  205. border: 1px dashed var(--el-border-color-darker) !important;
  206. &:hover {
  207. border: 1px dashed var(--el-border-color-darker) !important;
  208. }
  209. }
  210. }
  211. .upload-box {
  212. .no-border {
  213. :deep(.el-upload) {
  214. border: none !important;
  215. }
  216. }
  217. :deep(.upload) {
  218. .el-upload {
  219. position: relative;
  220. display: flex;
  221. align-items: center;
  222. justify-content: center;
  223. width: v-bind(width);
  224. height: v-bind(height);
  225. overflow: hidden;
  226. border: 1px dashed var(--el-border-color-darker);
  227. border-radius: v-bind(borderRadius);
  228. transition: var(--el-transition-duration-fast);
  229. &:hover {
  230. border-color: var(--el-color-primary);
  231. .upload-handle {
  232. opacity: 1;
  233. }
  234. }
  235. .el-upload-dragger {
  236. display: flex;
  237. align-items: center;
  238. justify-content: center;
  239. width: 100%;
  240. height: 100%;
  241. padding: 0;
  242. overflow: hidden;
  243. background-color: transparent;
  244. border: 1px dashed var(--el-border-color-darker);
  245. border-radius: v-bind(borderRadius);
  246. &:hover {
  247. border: 1px dashed var(--el-color-primary);
  248. }
  249. }
  250. .el-upload-dragger.is-dragover {
  251. background-color: var(--el-color-primary-light-9);
  252. border: 2px dashed var(--el-color-primary) !important;
  253. }
  254. .upload-image {
  255. width: 100%;
  256. height: 100%;
  257. object-fit: contain;
  258. }
  259. .upload-empty {
  260. position: relative;
  261. display: flex;
  262. flex-direction: column;
  263. align-items: center;
  264. justify-content: center;
  265. font-size: 12px;
  266. line-height: 30px;
  267. color: var(--el-color-info);
  268. .el-icon {
  269. font-size: 28px;
  270. color: var(--el-text-color-secondary);
  271. }
  272. }
  273. .upload-handle {
  274. position: absolute;
  275. top: 0;
  276. right: 0;
  277. box-sizing: border-box;
  278. display: flex;
  279. align-items: center;
  280. justify-content: center;
  281. width: 100%;
  282. height: 100%;
  283. cursor: pointer;
  284. background: rgb(0 0 0 / 60%);
  285. opacity: 0;
  286. transition: var(--el-transition-duration-fast);
  287. .handle-icon {
  288. display: flex;
  289. flex-direction: column;
  290. align-items: center;
  291. justify-content: center;
  292. padding: 0 6%;
  293. color: aliceblue;
  294. .el-icon {
  295. margin-bottom: 40%;
  296. font-size: 130%;
  297. line-height: 130%;
  298. }
  299. span {
  300. font-size: 85%;
  301. line-height: 85%;
  302. }
  303. }
  304. }
  305. }
  306. }
  307. .el-upload__tip {
  308. line-height: 18px;
  309. text-align: center;
  310. }
  311. }
  312. </style>