mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 18:28:43 +08:00 
			
		
		
		
	perf: 完善上传组件
This commit is contained in:
		| @@ -21,7 +21,7 @@ import { | ||||
| } from 'element-plus' | ||||
| import { InputPassword } from '@/components/InputPassword' | ||||
| import { Editor } from '@/components/Editor' | ||||
| import { UploadImg, UploadFile } from '@/components/UploadFile' | ||||
| import { UploadImg, UploadImgs, UploadFile } from '@/components/UploadFile' | ||||
| import { ComponentName } from '@/types/components' | ||||
|  | ||||
| const componentMap: Recordable<Component, ComponentName> = { | ||||
| @@ -48,6 +48,7 @@ const componentMap: Recordable<Component, ComponentName> = { | ||||
|   InputPassword: InputPassword, | ||||
|   Editor: Editor, | ||||
|   UploadImg: UploadImg, | ||||
|   UploadImgs: UploadImgs, | ||||
|   UploadFile: UploadFile | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import UploadImg from './src/UploadImg.vue' | ||||
| import UploadImgs from './src/UploadImgs.vue' | ||||
| import UploadFile from './src/UploadFile.vue' | ||||
|  | ||||
| export { UploadImg, UploadFile } | ||||
| export { UploadImg, UploadImgs, UploadFile } | ||||
|   | ||||
| @@ -32,8 +32,8 @@ | ||||
|     </el-upload> | ||||
|   </div> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import { ref, watch } from 'vue' | ||||
| <script setup lang="ts" name="UploadFile"> | ||||
| import { PropType, ref } from 'vue' | ||||
| import { useMessage } from '@/hooks/web/useMessage' | ||||
| import { propTypes } from '@/utils/propTypes' | ||||
| import { getAccessToken, getTenantId } from '@/utils/auth' | ||||
| @@ -43,7 +43,10 @@ const message = useMessage() // 消息弹窗 | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
|  | ||||
| const props = defineProps({ | ||||
|   modelValue: propTypes.oneOfType([String, Object, Array]), | ||||
|   modelValue: { | ||||
|     type: Array as PropType<UploadUserFile[]>, | ||||
|     required: true | ||||
|   }, | ||||
|   title: propTypes.string.def('文件上传'), | ||||
|   updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL), | ||||
|   fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg'] | ||||
| @@ -57,40 +60,12 @@ const props = defineProps({ | ||||
| const valueRef = ref(props.modelValue) | ||||
| const uploadRef = ref<UploadInstance>() | ||||
| const uploadList = ref<UploadUserFile[]>([]) | ||||
| const fileList = ref<UploadUserFile[]>([]) | ||||
| const fileList = ref<UploadUserFile[]>(props.modelValue) | ||||
| const uploadNumber = ref<number>(0) | ||||
| const uploadHeaders = ref({ | ||||
|   Authorization: 'Bearer ' + getAccessToken(), | ||||
|   'tenant-id': getTenantId() | ||||
| }) | ||||
| watch( | ||||
|   () => props.modelValue, | ||||
|   (val) => { | ||||
|     if (val) { | ||||
|       // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误 | ||||
|       const list = Array.isArray(props.modelValue) | ||||
|         ? props.modelValue | ||||
|         : Array.isArray(props.modelValue?.split(',')) | ||||
|         ? props.modelValue?.split(',') | ||||
|         : Array.of(props.modelValue) | ||||
|       // 然后将数组转为对象数组 | ||||
|       fileList.value = list.map((item) => { | ||||
|         if (typeof item === 'string') { | ||||
|           // edit by 芋道源码 | ||||
|           item = { name: item, url: item } | ||||
|         } | ||||
|         return item | ||||
|       }) | ||||
|     } else { | ||||
|       fileList.value = [] | ||||
|       return [] | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true | ||||
|   } | ||||
| ) | ||||
| // 文件上传之前判断 | ||||
| const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => { | ||||
|   if (fileList.value.length >= props.limit) { | ||||
|   | ||||
| @@ -1,176 +1,267 @@ | ||||
| <template> | ||||
|   <div class="component-upload-image"> | ||||
|   <div class="upload-box"> | ||||
|     <el-upload | ||||
|       ref="uploadRef" | ||||
|       :multiple="props.limit > 1" | ||||
|       name="file" | ||||
|       v-model="valueRef" | ||||
|       list-type="picture-card" | ||||
|       v-model:file-list="fileList" | ||||
|       :show-file-list="true" | ||||
|       :action="updateUrl" | ||||
|       :id="uuid" | ||||
|       :class="['upload', drag ? 'no-border' : '']" | ||||
|       :multiple="false" | ||||
|       :show-file-list="false" | ||||
|       :headers="uploadHeaders" | ||||
|       :limit="props.limit" | ||||
|       :before-upload="beforeUpload" | ||||
|       :on-exceed="handleExceed" | ||||
|       :on-success="handleFileSuccess" | ||||
|       :on-error="excelUploadError" | ||||
|       :on-remove="handleRemove" | ||||
|       :on-preview="handlePictureCardPreview" | ||||
|       :class="{ hide: fileList.length >= props.limit }" | ||||
|       :on-success="uploadSuccess" | ||||
|       :on-error="uploadError" | ||||
|       :drag="drag" | ||||
|       :accept="fileType.join(',')" | ||||
|     > | ||||
|       <Icon icon="ep:upload-filled" /> | ||||
|       <template v-if="modelValue"> | ||||
|         <img :src="modelValue" class="upload-image" /> | ||||
|         <div class="upload-handle" @click.stop> | ||||
|           <div class="handle-icon" @click="editImg"> | ||||
|             <Icon icon="ep:edit" /> | ||||
|             <span>{{ t('action.edit') }}</span> | ||||
|           </div> | ||||
|           <div class="handle-icon" @click="imgViewVisible = true"> | ||||
|             <Icon icon="ep:zoom-in" /> | ||||
|             <span>{{ t('action.detail') }}</span> | ||||
|           </div> | ||||
|           <div class="handle-icon" @click="deleteImg"> | ||||
|             <Icon icon="ep:delete" /> | ||||
|             <span>{{ t('action.del') }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </template> | ||||
|       <template v-else> | ||||
|         <div class="upload-empty"> | ||||
|           <slot name="empty"> | ||||
|             <Icon icon="ep:plus" /> | ||||
|             <!-- <span>请上传图片</span> --> | ||||
|           </slot> | ||||
|         </div> | ||||
|       </template> | ||||
|     </el-upload> | ||||
|     <div class="el-upload__tip"> | ||||
|       <slot name="tip"></slot> | ||||
|     </div> | ||||
|     <el-image-viewer | ||||
|       v-if="imgViewVisible" | ||||
|       @close="imgViewVisible = false" | ||||
|       :url-list="[modelValue]" | ||||
|     /> | ||||
|   </div> | ||||
|   <!-- 文件列表 --> | ||||
|   <Dialog v-model="dialogVisible" title="预览" width="800" append-to-body> | ||||
|     <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" /> | ||||
|   </Dialog> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import { ref, watch } from 'vue' | ||||
| import { Dialog } from '@/components/Dialog' | ||||
|  | ||||
| <script setup lang="ts" name="UploadImg"> | ||||
| import { ref } from 'vue' | ||||
| import type { UploadProps } from 'element-plus' | ||||
| import { ElUpload, ElNotification, ElImageViewer } from 'element-plus' | ||||
| import { useI18n } from '@/hooks/web/useI18n' | ||||
| import { useMessage } from '@/hooks/web/useMessage' | ||||
| import { generateUUID } from '@/utils' | ||||
| import { propTypes } from '@/utils/propTypes' | ||||
| import { getAccessToken, getTenantId } from '@/utils/auth' | ||||
| import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus' | ||||
|  | ||||
| type FileTypes = | ||||
|   | 'image/apng' | ||||
|   | 'image/bmp' | ||||
|   | 'image/gif' | ||||
|   | 'image/jpeg' | ||||
|   | 'image/pjpeg' | ||||
|   | 'image/png' | ||||
|   | 'image/svg+xml' | ||||
|   | 'image/tiff' | ||||
|   | 'image/webp' | ||||
|   | 'image/x-icon' | ||||
|  | ||||
| // 接受父组件参数 | ||||
| const props = defineProps({ | ||||
|   modelValue: propTypes.string.def(''), | ||||
|   updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL), | ||||
|   drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true) | ||||
|   disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false) | ||||
|   fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M) | ||||
|   fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"]) | ||||
|   height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px) | ||||
|   width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px) | ||||
|   borderRadius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px) | ||||
| }) | ||||
| const { t } = useI18n() // 国际化 | ||||
| const message = useMessage() // 消息弹窗 | ||||
| // 生成组件唯一id | ||||
| const uuid = ref('id-' + generateUUID()) | ||||
| // 查看图片 | ||||
| const imgViewVisible = ref(false) | ||||
|  | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
|  | ||||
| const props = defineProps({ | ||||
|   modelValue: propTypes.oneOfType([String, Object, Array]), | ||||
|   title: propTypes.string.def('图片上传'), | ||||
|   updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL), | ||||
|   fileType: propTypes.array.def(['jpg', 'png', 'gif', 'jpeg']), // 文件类型, 例如['png', 'jpg', 'jpeg'] | ||||
|   fileSize: propTypes.number.def(5), // 大小限制(MB) | ||||
|   limit: propTypes.number.def(1), // 数量限制 | ||||
|   isShowTip: propTypes.bool.def(false) // 是否显示提示 | ||||
| }) | ||||
| // ========== 上传相关 ========== | ||||
| const valueRef = ref(props.modelValue) | ||||
| const uploadRef = ref<UploadInstance>() | ||||
| const uploadList = ref<UploadUserFile[]>([]) | ||||
| const fileList = ref<UploadUserFile[]>([]) | ||||
| const uploadNumber = ref<number>(0) | ||||
| const dialogImageUrl = ref() | ||||
| const dialogVisible = ref(false) | ||||
| const deleteImg = () => { | ||||
|   emit('update:modelValue', '') | ||||
| } | ||||
|  | ||||
| const uploadHeaders = ref({ | ||||
|   Authorization: 'Bearer ' + getAccessToken(), | ||||
|   'tenant-id': getTenantId() | ||||
| }) | ||||
| watch( | ||||
|   () => props.modelValue, | ||||
|   (val) => { | ||||
|     if (val) { | ||||
|       // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误 | ||||
|       const list = Array.isArray(props.modelValue) | ||||
|         ? props.modelValue | ||||
|         : Array.isArray(props.modelValue?.split(',')) | ||||
|         ? props.modelValue?.split(',') | ||||
|         : Array.of(props.modelValue) | ||||
|       // 然后将数组转为对象数组 | ||||
|       fileList.value = list.map((item) => { | ||||
|         if (typeof item === 'string') { | ||||
|           // edit by 芋道源码 | ||||
|           item = { name: item, url: item } | ||||
|         } | ||||
|         return item | ||||
|       }) | ||||
|     } else { | ||||
|       fileList.value = [] | ||||
|       return [] | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     deep: true, | ||||
|     immediate: true | ||||
|   } | ||||
| ) | ||||
| // 文件上传之前判断 | ||||
| const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => { | ||||
|   if (fileList.value.length >= props.limit) { | ||||
|     message.error(`上传文件数量不能超过${props.limit}个!`) | ||||
|     return false | ||||
|   } | ||||
|   let fileExtension = '' | ||||
|   if (file.name.lastIndexOf('.') > -1) { | ||||
|     fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1) | ||||
|   } | ||||
|   const isImg = props.fileType.some((type: string) => { | ||||
|     if (file.type.indexOf(type) > -1) return true | ||||
|     return !!(fileExtension && fileExtension.indexOf(type) > -1) | ||||
|   }) | ||||
|   const isLimit = file.size < props.fileSize * 1024 * 1024 | ||||
|   if (!isImg) { | ||||
|     message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`) | ||||
|     return false | ||||
|   } | ||||
|   if (!isLimit) { | ||||
|     message.error(`上传文件大小不能超过${props.fileSize}MB!`) | ||||
|     return false | ||||
|   } | ||||
|   message.success('正在上传文件,请稍候...') | ||||
|   uploadNumber.value++ | ||||
|  | ||||
| const editImg = () => { | ||||
|   const dom = document.querySelector(`#${uuid.value} .el-upload__input`) | ||||
|   dom && dom.dispatchEvent(new MouseEvent('click')) | ||||
| } | ||||
| // 处理上传的文件发生变化 | ||||
| // const handleFileChange = (uploadFile: UploadFile): void => { | ||||
| //   uploadRef.value.data.path = uploadFile.name | ||||
| // } | ||||
| // 文件上传成功 | ||||
| const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => { | ||||
|  | ||||
| const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => { | ||||
|   const imgSize = rawFile.size / 1024 / 1024 < props.fileSize | ||||
|   const imgType = props.fileType | ||||
|   if (!imgType.includes(rawFile.type as FileTypes)) | ||||
|     ElNotification({ | ||||
|       title: '温馨提示', | ||||
|       message: '上传图片不符合所需的格式!', | ||||
|       type: 'warning' | ||||
|     }) | ||||
|   if (!imgSize) | ||||
|     ElNotification({ | ||||
|       title: '温馨提示', | ||||
|       message: `上传图片大小不能超过 ${props.fileSize}M!`, | ||||
|       type: 'warning' | ||||
|     }) | ||||
|   return imgType.includes(rawFile.type as FileTypes) && imgSize | ||||
| } | ||||
|  | ||||
| // 图片上传成功提示 | ||||
| const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => { | ||||
|   message.success('上传成功') | ||||
|   uploadList.value.push({ name: res.data, url: res.data }) | ||||
|   if (uploadList.value.length == uploadNumber.value) { | ||||
|     fileList.value = fileList.value.concat(uploadList.value) | ||||
|     uploadList.value = [] | ||||
|     uploadNumber.value = 0 | ||||
|     emit('update:modelValue', listToString(fileList.value)) | ||||
|   } | ||||
|   emit('update:modelValue', res.data) | ||||
| } | ||||
| // 文件数超出提示 | ||||
| const handleExceed: UploadProps['onExceed'] = (): void => { | ||||
|   message.error(`上传文件数量不能超过${props.limit}个!`) | ||||
| } | ||||
| // 上传错误提示 | ||||
| const excelUploadError: UploadProps['onError'] = (): void => { | ||||
|   message.error('导入数据失败,请您重新上传!') | ||||
| } | ||||
| // 删除上传文件 | ||||
| const handleRemove = (file) => { | ||||
|   const findex = fileList.value.map((f) => f.name).indexOf(file.name) | ||||
|   if (findex > -1) { | ||||
|     fileList.value.splice(findex, 1) | ||||
|     emit('update:modelValue', listToString(fileList.value)) | ||||
|   } | ||||
| } | ||||
| // 对象转成指定字符串分隔 | ||||
| const listToString = (list: UploadUserFile[], separator?: string) => { | ||||
|   let strs = '' | ||||
|   separator = separator || ',' | ||||
|   for (let i in list) { | ||||
|     strs += list[i].url + separator | ||||
|   } | ||||
|   return strs != '' ? strs.substr(0, strs.length - 1) : '' | ||||
| } | ||||
| // 预览 | ||||
| const handlePictureCardPreview: UploadProps['onPreview'] = (file) => { | ||||
|   dialogImageUrl.value = file.url | ||||
|   dialogVisible.value = true | ||||
|  | ||||
| // 图片上传错误提示 | ||||
| const uploadError = () => { | ||||
|   ElNotification({ | ||||
|     title: '温馨提示', | ||||
|     message: '图片上传失败,请您重新上传!', | ||||
|     type: 'error' | ||||
|   }) | ||||
| } | ||||
| </script> | ||||
| <style scoped lang="scss"> | ||||
| // .el-upload--picture-card 控制加号部分 | ||||
| :deep(.hide .el-upload--picture-card) { | ||||
|   display: none; | ||||
| .is-error { | ||||
|   .upload { | ||||
|     :deep(.el-upload), | ||||
|     :deep(.el-upload-dragger) { | ||||
|       border: 1px dashed var(--el-color-danger) !important; | ||||
|       &:hover { | ||||
|         border-color: var(--el-color-primary) !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| // 去掉动画效果 | ||||
| :deep(.el-list-enter-active, .el-list-leave-active) { | ||||
|   transition: all 0s; | ||||
| :deep(.disabled) { | ||||
|   .el-upload, | ||||
|   .el-upload-dragger { | ||||
|     cursor: not-allowed !important; | ||||
|     background: var(--el-disabled-bg-color); | ||||
|     border: 1px dashed var(--el-border-color-darker) !important; | ||||
|     &:hover { | ||||
|       border: 1px dashed var(--el-border-color-darker) !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| :deep(.el-list-enter, .el-list-leave-active) { | ||||
|   opacity: 0; | ||||
|   transform: translateY(0); | ||||
| .upload-box { | ||||
|   .no-border { | ||||
|     :deep(.el-upload) { | ||||
|       border: none !important; | ||||
|     } | ||||
|   } | ||||
|   :deep(.upload) { | ||||
|     .el-upload { | ||||
|       position: relative; | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       width: v-bind(width); | ||||
|       height: v-bind(height); | ||||
|       overflow: hidden; | ||||
|       border: 1px dashed var(--el-border-color-darker); | ||||
|       border-radius: v-bind(borderRadius); | ||||
|       transition: var(--el-transition-duration-fast); | ||||
|       &:hover { | ||||
|         border-color: var(--el-color-primary); | ||||
|         .upload-handle { | ||||
|           opacity: 1; | ||||
|         } | ||||
|       } | ||||
|       .el-upload-dragger { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         padding: 0; | ||||
|         overflow: hidden; | ||||
|         background-color: transparent; | ||||
|         border: 1px dashed var(--el-border-color-darker); | ||||
|         border-radius: v-bind(borderRadius); | ||||
|         &:hover { | ||||
|           border: 1px dashed var(--el-color-primary); | ||||
|         } | ||||
|       } | ||||
|       .el-upload-dragger.is-dragover { | ||||
|         background-color: var(--el-color-primary-light-9); | ||||
|         border: 2px dashed var(--el-color-primary) !important; | ||||
|       } | ||||
|       .upload-image { | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         object-fit: contain; | ||||
|       } | ||||
|       .upload-empty { | ||||
|         position: relative; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         font-size: 12px; | ||||
|         line-height: 30px; | ||||
|         color: var(--el-color-info); | ||||
|         .el-icon { | ||||
|           font-size: 28px; | ||||
|           color: var(--el-text-color-secondary); | ||||
|         } | ||||
|       } | ||||
|       .upload-handle { | ||||
|         position: absolute; | ||||
|         top: 0; | ||||
|         right: 0; | ||||
|         box-sizing: border-box; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         cursor: pointer; | ||||
|         background: rgb(0 0 0 / 60%); | ||||
|         opacity: 0; | ||||
|         transition: var(--el-transition-duration-fast); | ||||
|         .handle-icon { | ||||
|           display: flex; | ||||
|           flex-direction: column; | ||||
|           align-items: center; | ||||
|           justify-content: center; | ||||
|           padding: 0 6%; | ||||
|           color: aliceblue; | ||||
|           .el-icon { | ||||
|             margin-bottom: 40%; | ||||
|             font-size: 130%; | ||||
|             line-height: 130%; | ||||
|           } | ||||
|           span { | ||||
|             font-size: 85%; | ||||
|             line-height: 85%; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   .el-upload__tip { | ||||
|     line-height: 18px; | ||||
|     text-align: center; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
							
								
								
									
										277
									
								
								yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImgs.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImgs.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,277 @@ | ||||
| <template> | ||||
|   <div class="upload-box"> | ||||
|     <el-upload | ||||
|       :action="updateUrl" | ||||
|       list-type="picture-card" | ||||
|       :class="['upload', drag ? 'no-border' : '']" | ||||
|       v-model:file-list="fileList" | ||||
|       :multiple="true" | ||||
|       :limit="limit" | ||||
|       :headers="uploadHeaders" | ||||
|       :before-upload="beforeUpload" | ||||
|       :on-exceed="handleExceed" | ||||
|       :on-success="uploadSuccess" | ||||
|       :on-error="uploadError" | ||||
|       :drag="drag" | ||||
|       :accept="fileType.join(',')" | ||||
|     > | ||||
|       <div class="upload-empty"> | ||||
|         <slot name="empty"> | ||||
|           <Icon icon="ep:plus" /> | ||||
|           <!-- <span>请上传图片</span> --> | ||||
|         </slot> | ||||
|       </div> | ||||
|       <template #file="{ file }"> | ||||
|         <img :src="file.url" class="upload-image" /> | ||||
|         <div class="upload-handle" @click.stop> | ||||
|           <div class="handle-icon" @click="handlePictureCardPreview(file)"> | ||||
|             <Icon icon="ep:zoom-in" /> | ||||
|             <span>查看</span> | ||||
|           </div> | ||||
|           <div class="handle-icon" @click="handleRemove(file)"> | ||||
|             <Icon icon="ep:delete" /> | ||||
|             <span>删除</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </template> | ||||
|     </el-upload> | ||||
|     <div class="el-upload__tip"> | ||||
|       <slot name="tip"></slot> | ||||
|     </div> | ||||
|     <el-image-viewer | ||||
|       v-if="imgViewVisible" | ||||
|       @close="imgViewVisible = false" | ||||
|       :url-list="[viewImageUrl]" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
| <script setup lang="ts" name="UploadImgs"> | ||||
| import { PropType, ref } from 'vue' | ||||
| import { ElUpload, ElNotification, ElImageViewer } from 'element-plus' | ||||
| import type { UploadProps, UploadFile, UploadUserFile } from 'element-plus' | ||||
| import { useMessage } from '@/hooks/web/useMessage' | ||||
| import { propTypes } from '@/utils/propTypes' | ||||
| import { getAccessToken, getTenantId } from '@/utils/auth' | ||||
|  | ||||
| const message = useMessage() // 消息弹窗 | ||||
|  | ||||
| type FileTypes = | ||||
|   | 'image/apng' | ||||
|   | 'image/bmp' | ||||
|   | 'image/gif' | ||||
|   | 'image/jpeg' | ||||
|   | 'image/pjpeg' | ||||
|   | 'image/png' | ||||
|   | 'image/svg+xml' | ||||
|   | 'image/tiff' | ||||
|   | 'image/webp' | ||||
|   | 'image/x-icon' | ||||
|  | ||||
| const props = defineProps({ | ||||
|   modelValue: { | ||||
|     type: Array as PropType<UploadUserFile[]>, | ||||
|     required: true | ||||
|   }, | ||||
|   updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL), | ||||
|   drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true) | ||||
|   disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false) | ||||
|   limit: propTypes.number.def(5), // 最大图片上传数 ==> 非必传(默认为 5张) | ||||
|   fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M) | ||||
|   fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"]) | ||||
|   height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px) | ||||
|   width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px) | ||||
|   borderRadius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px) | ||||
| }) | ||||
|  | ||||
| const uploadHeaders = ref({ | ||||
|   Authorization: 'Bearer ' + getAccessToken(), | ||||
|   'tenant-id': getTenantId() | ||||
| }) | ||||
|  | ||||
| const fileList = ref<UploadUserFile[]>(props.modelValue) | ||||
|  | ||||
| /** | ||||
|  * @description 文件上传之前判断 | ||||
|  * @param rawFile 上传的文件 | ||||
|  * */ | ||||
| const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => { | ||||
|   const imgSize = rawFile.size / 1024 / 1024 < props.fileSize | ||||
|   const imgType = props.fileType | ||||
|   if (!imgType.includes(rawFile.type as FileTypes)) | ||||
|     ElNotification({ | ||||
|       title: '温馨提示', | ||||
|       message: '上传图片不符合所需的格式!', | ||||
|       type: 'warning' | ||||
|     }) | ||||
|   if (!imgSize) | ||||
|     ElNotification({ | ||||
|       title: '温馨提示', | ||||
|       message: `上传图片大小不能超过 ${props.fileSize}M!`, | ||||
|       type: 'warning' | ||||
|     }) | ||||
|   return imgType.includes(rawFile.type as FileTypes) && imgSize | ||||
| } | ||||
|  | ||||
| // 图片上传成功 | ||||
| interface UploadEmits { | ||||
|   (e: 'update:modelValue', value: UploadUserFile[]): void | ||||
| } | ||||
| const emit = defineEmits<UploadEmits>() | ||||
| const uploadSuccess = (response, uploadFile: UploadFile) => { | ||||
|   if (!response) return | ||||
|   uploadFile.url = response.data | ||||
|   emit('update:modelValue', fileList.value) | ||||
|   message.success('上传成功') | ||||
| } | ||||
|  | ||||
| // 删除图片 | ||||
| const handleRemove = (uploadFile: UploadFile) => { | ||||
|   fileList.value = fileList.value.filter( | ||||
|     (item) => item.url !== uploadFile.url || item.name !== uploadFile.name | ||||
|   ) | ||||
|   emit('update:modelValue', fileList.value) | ||||
| } | ||||
|  | ||||
| // 图片上传错误提示 | ||||
| const uploadError = () => { | ||||
|   ElNotification({ | ||||
|     title: '温馨提示', | ||||
|     message: '图片上传失败,请您重新上传!', | ||||
|     type: 'error' | ||||
|   }) | ||||
| } | ||||
|  | ||||
| // 文件数超出提示 | ||||
| const handleExceed = () => { | ||||
|   ElNotification({ | ||||
|     title: '温馨提示', | ||||
|     message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`, | ||||
|     type: 'warning' | ||||
|   }) | ||||
| } | ||||
|  | ||||
| // 图片预览 | ||||
| const viewImageUrl = ref('') | ||||
| const imgViewVisible = ref(false) | ||||
| const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => { | ||||
|   viewImageUrl.value = uploadFile.url! | ||||
|   imgViewVisible.value = true | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| .is-error { | ||||
|   .upload { | ||||
|     :deep(.el-upload--picture-card), | ||||
|     :deep(.el-upload-dragger) { | ||||
|       border: 1px dashed var(--el-color-danger) !important; | ||||
|       &:hover { | ||||
|         border-color: var(--el-color-primary) !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| :deep(.disabled) { | ||||
|   .el-upload--picture-card, | ||||
|   .el-upload-dragger { | ||||
|     cursor: not-allowed; | ||||
|     background: var(--el-disabled-bg-color) !important; | ||||
|     border: 1px dashed var(--el-border-color-darker); | ||||
|     &:hover { | ||||
|       border-color: var(--el-border-color-darker) !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| .upload-box { | ||||
|   .no-border { | ||||
|     :deep(.el-upload--picture-card) { | ||||
|       border: none !important; | ||||
|     } | ||||
|   } | ||||
|   :deep(.upload) { | ||||
|     .el-upload-dragger { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       width: 100%; | ||||
|       height: 100%; | ||||
|       padding: 0; | ||||
|       overflow: hidden; | ||||
|       border: 1px dashed var(--el-border-color-darker); | ||||
|       border-radius: v-bind(borderRadius); | ||||
|       &:hover { | ||||
|         border: 1px dashed var(--el-color-primary); | ||||
|       } | ||||
|     } | ||||
|     .el-upload-dragger.is-dragover { | ||||
|       background-color: var(--el-color-primary-light-9); | ||||
|       border: 2px dashed var(--el-color-primary) !important; | ||||
|     } | ||||
|     .el-upload-list__item, | ||||
|     .el-upload--picture-card { | ||||
|       width: v-bind(width); | ||||
|       height: v-bind(height); | ||||
|       background-color: transparent; | ||||
|       border-radius: v-bind(borderRadius); | ||||
|     } | ||||
|     .upload-image { | ||||
|       width: 100%; | ||||
|       height: 100%; | ||||
|       object-fit: contain; | ||||
|     } | ||||
|     .upload-handle { | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       right: 0; | ||||
|       box-sizing: border-box; | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
|       width: 100%; | ||||
|       height: 100%; | ||||
|       cursor: pointer; | ||||
|       background: rgb(0 0 0 / 60%); | ||||
|       opacity: 0; | ||||
|       transition: var(--el-transition-duration-fast); | ||||
|       .handle-icon { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         padding: 0 6%; | ||||
|         color: aliceblue; | ||||
|         .el-icon { | ||||
|           margin-bottom: 15%; | ||||
|           font-size: 140%; | ||||
|         } | ||||
|         span { | ||||
|           font-size: 100%; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     .el-upload-list__item { | ||||
|       &:hover { | ||||
|         .upload-handle { | ||||
|           opacity: 1; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     .upload-empty { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: center; | ||||
|       font-size: 12px; | ||||
|       line-height: 30px; | ||||
|       color: var(--el-color-info); | ||||
|       .el-icon { | ||||
|         font-size: 28px; | ||||
|         color: var(--el-text-color-secondary); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   .el-upload__tip { | ||||
|     line-height: 15px; | ||||
|     text-align: center; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @@ -22,6 +22,7 @@ export type ComponentName = | ||||
|   | 'InputPassword' | ||||
|   | 'Editor' | ||||
|   | 'UploadImg' | ||||
|   | 'UploadImgs' | ||||
|   | 'UploadFile' | ||||
|  | ||||
| export type ColProps = { | ||||
|   | ||||
| @@ -69,7 +69,7 @@ export const trim = (str: string) => { | ||||
|  * @param {Date | number | string} time 需要转换的时间 | ||||
|  * @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss | ||||
|  */ | ||||
| export function formatTime(time: Date | number | string, fmt: string) { | ||||
| export const formatTime = (time: Date | number | string, fmt: string) => { | ||||
|   if (!time) return '' | ||||
|   else { | ||||
|     const date = new Date(time) | ||||
| @@ -100,7 +100,7 @@ export function formatTime(time: Date | number | string, fmt: string) { | ||||
| /** | ||||
|  * 生成随机字符串 | ||||
|  */ | ||||
| export function toAnyString() { | ||||
| export const toAnyString = () => { | ||||
|   const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => { | ||||
|     const r: number = (Math.random() * 16) | 0 | ||||
|     const v: number = c === 'x' ? r : (r & 0x3) | 0x8 | ||||
| @@ -108,3 +108,34 @@ export function toAnyString() { | ||||
|   }) | ||||
|   return str | ||||
| } | ||||
|  | ||||
| export const generateUUID = () => { | ||||
|   if (typeof crypto === 'object') { | ||||
|     if (typeof crypto.randomUUID === 'function') { | ||||
|       return crypto.randomUUID() | ||||
|     } | ||||
|     if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') { | ||||
|       const callback = (c: any) => { | ||||
|         const num = Number(c) | ||||
|         return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString( | ||||
|           16 | ||||
|         ) | ||||
|       } | ||||
|       return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback) | ||||
|     } | ||||
|   } | ||||
|   let timestamp = new Date().getTime() | ||||
|   let performanceNow = | ||||
|     (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0 | ||||
|   return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { | ||||
|     let random = Math.random() * 16 | ||||
|     if (timestamp > 0) { | ||||
|       random = (timestamp + random) % 16 | 0 | ||||
|       timestamp = Math.floor(timestamp / 16) | ||||
|     } else { | ||||
|       random = (performanceNow + random) % 16 | 0 | ||||
|       performanceNow = Math.floor(performanceNow / 16) | ||||
|     } | ||||
|     return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16) | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -43,10 +43,7 @@ const crudSchemas = reactive<VxeCrudSchema>({ | ||||
|         } | ||||
|       }, | ||||
|       form: { | ||||
|         component: 'UploadImg', | ||||
|         componentProps: { | ||||
|           limit: 1 | ||||
|         } | ||||
|         component: 'UploadImg' | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 xingyu
					xingyu