houzhongjian
2024-08-08 820397e43a0b64d35c6d31d2a55475061438593b
提交 | 用户 | 时间
820397 1 <template>
H 2   <div class="upload-box">
3     <el-upload
4       :id="uuid"
5       :accept="fileType.join(',')"
6       :action="uploadUrl"
7       :before-upload="beforeUpload"
8       :class="['upload', drag ? 'no-border' : '']"
9       :disabled="disabled"
10       :drag="drag"
11       :http-request="httpRequest"
12       :multiple="false"
13       :on-error="uploadError"
14       :on-success="uploadSuccess"
15       :show-file-list="false"
16     >
17       <template v-if="modelValue">
18         <img :src="modelValue" class="upload-image" />
19         <div class="upload-handle" @click.stop>
20           <div v-if="!disabled" class="handle-icon" @click="editImg">
21             <Icon icon="ep:edit" />
22             <span v-if="showBtnText">{{ t('action.edit') }}</span>
23           </div>
24           <div class="handle-icon" @click="imagePreview(modelValue)">
25             <Icon icon="ep:zoom-in" />
26             <span v-if="showBtnText">{{ t('action.detail') }}</span>
27           </div>
28           <div v-if="showDelete && !disabled" class="handle-icon" @click="deleteImg">
29             <Icon icon="ep:delete" />
30             <span v-if="showBtnText">{{ t('action.del') }}</span>
31           </div>
32         </div>
33       </template>
34       <template v-else>
35         <div class="upload-empty">
36           <slot name="empty">
37             <Icon icon="ep:plus" />
38             <!-- <span>请上传图片</span> -->
39           </slot>
40         </div>
41       </template>
42     </el-upload>
43     <div class="el-upload__tip">
44       <slot name="tip"></slot>
45     </div>
46   </div>
47 </template>
48
49 <script lang="ts" setup>
50 import type { UploadProps } from 'element-plus'
51
52 import { generateUUID } from '@/utils'
53 import { propTypes } from '@/utils/propTypes'
54 import { createImageViewer } from '@/components/ImageViewer'
55 import { useUpload } from '@/components/UploadFile/src/useUpload'
56
57 defineOptions({ name: 'UploadImg' })
58
59 type FileTypes =
60   | 'image/apng'
61   | 'image/bmp'
62   | 'image/gif'
63   | 'image/jpeg'
64   | 'image/pjpeg'
65   | 'image/png'
66   | 'image/svg+xml'
67   | 'image/tiff'
68   | 'image/webp'
69   | 'image/x-icon'
70
71 // 接受父组件参数
72 const props = defineProps({
73   modelValue: propTypes.string.def(''),
74   drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true)
75   disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
76   fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M)
77   fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
78   height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
79   width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
80   borderradius: propTypes.string.def('8px'), // 组件边框圆角 ==> 非必传(默认为 8px)
81   showDelete: propTypes.bool.def(true), // 是否显示删除按钮
82   showBtnText: propTypes.bool.def(true) // 是否显示按钮文字
83 })
84 const { t } = useI18n() // 国际化
85 const message = useMessage() // 消息弹窗
86 // 生成组件唯一id
87 const uuid = ref('id-' + generateUUID())
88 // 查看图片
89 const imagePreview = (imgUrl: string) => {
90   createImageViewer({
91     zIndex: 9999999,
92     urlList: [imgUrl]
93   })
94 }
95
96 const emit = defineEmits(['update:modelValue'])
97
98 const deleteImg = () => {
99   emit('update:modelValue', '')
100 }
101
102 const { uploadUrl, httpRequest } = useUpload()
103
104 const editImg = () => {
105   const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
106   dom && dom.dispatchEvent(new MouseEvent('click'))
107 }
108
109 const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
110   const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
111   const imgType = props.fileType
112   if (!imgType.includes(rawFile.type as FileTypes))
113     message.notifyWarning('上传图片不符合所需的格式!')
114   if (!imgSize) message.notifyWarning(`上传图片大小不能超过 ${props.fileSize}M!`)
115   return imgType.includes(rawFile.type as FileTypes) && imgSize
116 }
117
118 // 图片上传成功提示
119 const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
120   message.success('上传成功')
121   emit('update:modelValue', res.data)
122 }
123
124 // 图片上传错误提示
125 const uploadError = () => {
126   message.notifyError('图片上传失败,请您重新上传!')
127 }
128 </script>
129 <style lang="scss" scoped>
130 .is-error {
131   .upload {
132     :deep(.el-upload),
133     :deep(.el-upload-dragger) {
134       border: 1px dashed var(--el-color-danger) !important;
135
136       &:hover {
137         border-color: var(--el-color-primary) !important;
138       }
139     }
140   }
141 }
142
143 :deep(.disabled) {
144   .el-upload,
145   .el-upload-dragger {
146     cursor: not-allowed !important;
147     background: var(--el-disabled-bg-color);
148     border: 1px dashed var(--el-border-color-darker) !important;
149
150     &:hover {
151       border: 1px dashed var(--el-border-color-darker) !important;
152     }
153   }
154 }
155
156 .upload-box {
157   .no-border {
158     :deep(.el-upload) {
159       border: none !important;
160     }
161   }
162
163   :deep(.upload) {
164     .el-upload {
165       position: relative;
166       display: flex;
167       align-items: center;
168       justify-content: center;
169       width: v-bind(width);
170       height: v-bind(height);
171       overflow: hidden;
172       border: 1px dashed var(--el-border-color-darker);
173       border-radius: v-bind(borderradius);
174       transition: var(--el-transition-duration-fast);
175
176       &:hover {
177         border-color: var(--el-color-primary);
178
179         .upload-handle {
180           opacity: 1;
181         }
182       }
183
184       .el-upload-dragger {
185         display: flex;
186         align-items: center;
187         justify-content: center;
188         width: 100%;
189         height: 100%;
190         padding: 0;
191         overflow: hidden;
192         background-color: transparent;
193         border: 1px dashed var(--el-border-color-darker);
194         border-radius: v-bind(borderradius);
195
196         &:hover {
197           border: 1px dashed var(--el-color-primary);
198         }
199       }
200
201       .el-upload-dragger.is-dragover {
202         background-color: var(--el-color-primary-light-9);
203         border: 2px dashed var(--el-color-primary) !important;
204       }
205
206       .upload-image {
207         width: 100%;
208         height: 100%;
209         object-fit: contain;
210       }
211
212       .upload-empty {
213         position: relative;
214         display: flex;
215         flex-direction: column;
216         align-items: center;
217         justify-content: center;
218         font-size: 12px;
219         line-height: 30px;
220         color: var(--el-color-info);
221
222         .el-icon {
223           font-size: 28px;
224           color: var(--el-text-color-secondary);
225         }
226       }
227
228       .upload-handle {
229         position: absolute;
230         top: 0;
231         right: 0;
232         display: flex;
233         width: 100%;
234         height: 100%;
235         cursor: pointer;
236         background: rgb(0 0 0 / 60%);
237         opacity: 0;
238         box-sizing: border-box;
239         transition: var(--el-transition-duration-fast);
240         align-items: center;
241         justify-content: center;
242
243         .handle-icon {
244           display: flex;
245           flex-direction: column;
246           align-items: center;
247           justify-content: center;
248           padding: 0 6%;
249           color: aliceblue;
250
251           .el-icon {
252             margin-bottom: 40%;
253             font-size: 130%;
254             line-height: 130%;
255           }
256
257           span {
258             font-size: 85%;
259             line-height: 85%;
260           }
261         }
262       }
263     }
264   }
265
266   .el-upload__tip {
267     line-height: 18px;
268     text-align: center;
269   }
270 }
271 </style>