mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 10:18:42 +08:00 
			
		
		
		
	feat: add upload component
This commit is contained in:
		| @@ -21,6 +21,7 @@ import { | ||||
| } from 'element-plus' | ||||
| import { InputPassword } from '@/components/InputPassword' | ||||
| import { Editor } from '@/components/Editor' | ||||
| import { UploadImg, UploadFile } from '@/components/UploadFile' | ||||
| import { ComponentName } from '@/types/components' | ||||
|  | ||||
| const componentMap: Recordable<Component, ComponentName> = { | ||||
| @@ -45,7 +46,9 @@ const componentMap: Recordable<Component, ComponentName> = { | ||||
|   TreeSelect: ElTreeSelect, | ||||
|   RadioButton: ElRadioGroup, | ||||
|   InputPassword: InputPassword, | ||||
|   Editor: Editor | ||||
|   Editor: Editor, | ||||
|   UploadImg: UploadImg, | ||||
|   UploadFile: UploadFile | ||||
| } | ||||
|  | ||||
| export { componentMap } | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import UploadImg from './src/UploadImg.vue' | ||||
| import UploadFile from './src/UploadFile.vue' | ||||
|  | ||||
| export { UploadImg } | ||||
| export { UploadImg, UploadFile } | ||||
|   | ||||
							
								
								
									
										167
									
								
								yudao-ui-admin-vue3/src/components/UploadFile/src/UploadFile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								yudao-ui-admin-vue3/src/components/UploadFile/src/UploadFile.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | ||||
| <template> | ||||
|   <div class="upload-file"> | ||||
|     <el-upload | ||||
|       ref="uploadRef" | ||||
|       :multiple="props.limit > 1" | ||||
|       name="file" | ||||
|       v-model="valueRef" | ||||
|       :file-list="fileList" | ||||
|       :show-file-list="false" | ||||
|       :action="updateUrl" | ||||
|       :headers="uploadHeaders" | ||||
|       :limit="props.limit" | ||||
|       :before-upload="beforeUpload" | ||||
|       :on-exceed="handleExceed" | ||||
|       :on-success="handleFileSuccess" | ||||
|       :on-error="excelUploadError" | ||||
|       :on-remove="handleRemove" | ||||
|       class="upload-file-uploader" | ||||
|     > | ||||
|       <Icon icon="ep:upload-filled" /> | ||||
|     </el-upload> | ||||
|   </div> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| import { ref, watch } from 'vue' | ||||
| import { useMessage } from '@/hooks/web/useMessage' | ||||
| import { propTypes } from '@/utils/propTypes' | ||||
| import { getAccessToken, getTenantId } from '@/utils/auth' | ||||
| import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus' | ||||
|  | ||||
| const message = useMessage() // 消息弹窗 | ||||
| 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(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg'] | ||||
|   fileSize: propTypes.number.def(5), // 大小限制(MB) | ||||
|   limit: propTypes.number.def(5), // 数量限制 | ||||
|   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 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 handleFileChange = (uploadFile: UploadFile): void => { | ||||
| //   uploadRef.value.data.path = uploadFile.name | ||||
| // } | ||||
| // 文件上传成功 | ||||
| const handleFileSuccess: 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)) | ||||
|   } | ||||
| } | ||||
| // 文件数超出提示 | ||||
| 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) : '' | ||||
| } | ||||
| </script> | ||||
| <style scoped lang="scss"> | ||||
| .upload-file-uploader { | ||||
|   margin-bottom: 5px; | ||||
| } | ||||
| .upload-file-list .el-upload-list__item { | ||||
|   border: 1px solid #e4e7ed; | ||||
|   line-height: 2; | ||||
|   margin-bottom: 10px; | ||||
|   position: relative; | ||||
| } | ||||
| .upload-file-list .ele-upload-list__item-content { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   color: inherit; | ||||
| } | ||||
| .ele-upload-list__item-content-action .el-link { | ||||
|   margin-right: 10px; | ||||
| } | ||||
| </style> | ||||
| @@ -1,24 +1,27 @@ | ||||
| <template> | ||||
|   <el-upload | ||||
|     ref="uploadRef" | ||||
|     :multiple="limit > 1" | ||||
|     name="file" | ||||
|     list-type="picture-card" | ||||
|     v-model:file-list="fileList" | ||||
|     :show-file-list="true" | ||||
|     :action="updateUrl" | ||||
|     :headers="uploadHeaders" | ||||
|     :limit="limit" | ||||
|     :before-upload="beforeUpload" | ||||
|     :on-exceed="handleExceed" | ||||
|     :on-success="handleFileSuccess" | ||||
|     :on-error="excelUploadError" | ||||
|     :on-remove="handleRemove" | ||||
|     :on-preview="handlePictureCardPreview" | ||||
|     :class="{ hide: fileList.length >= limit }" | ||||
|   > | ||||
|     <Icon icon="ep:upload-filled" /> | ||||
|   </el-upload> | ||||
|   <div class="component-upload-image"> | ||||
|     <el-upload | ||||
|       ref="uploadRef" | ||||
|       :multiple="props.limit > 1" | ||||
|       name="file" | ||||
|       v-model="valueRef" | ||||
|       list-type="picture-card" | ||||
|       :file-list="fileList" | ||||
|       :show-file-list="true" | ||||
|       :action="updateUrl" | ||||
|       :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 }" | ||||
|     > | ||||
|       <Icon icon="ep:upload-filled" /> | ||||
|     </el-upload> | ||||
|   </div> | ||||
|   <!-- 文件列表 --> | ||||
|   <Dialog v-model="dialogVisible" title="预览" width="800" append-to-body> | ||||
|     <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" /> | ||||
| @@ -32,10 +35,10 @@ import { getAccessToken, getTenantId } from '@/utils/auth' | ||||
| import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus' | ||||
|  | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const emit = defineEmits(['input']) | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
|  | ||||
| const props = defineProps({ | ||||
|   imgs: propTypes.oneOfType([String, Object, Array]), | ||||
|   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'] | ||||
| @@ -44,6 +47,7 @@ const props = defineProps({ | ||||
|   isShowTip: propTypes.bool.def(false) // 是否显示提示 | ||||
| }) | ||||
| // ========== 上传相关 ========== | ||||
| const valueRef = ref(props.modelValue) | ||||
| const uploadRef = ref<UploadInstance>() | ||||
| const uploadList = ref<UploadUserFile[]>([]) | ||||
| const fileList = ref<UploadUserFile[]>([]) | ||||
| @@ -55,15 +59,15 @@ const uploadHeaders = ref({ | ||||
|   'tenant-id': getTenantId() | ||||
| }) | ||||
| watch( | ||||
|   () => props.imgs, | ||||
|   () => props.modelValue, | ||||
|   (val) => { | ||||
|     if (val) { | ||||
|       // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误 | ||||
|       const list = Array.isArray(props.imgs) | ||||
|         ? props.imgs | ||||
|         : Array.isArray(props.imgs?.split(',')) | ||||
|         ? props.imgs?.split(',') | ||||
|         : Array.of(props.imgs) | ||||
|       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') { | ||||
| @@ -84,6 +88,10 @@ watch( | ||||
| ) | ||||
| // 文件上传之前判断 | ||||
| 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) | ||||
| @@ -111,14 +119,12 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => { | ||||
| // 文件上传成功 | ||||
| const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => { | ||||
|   message.success('上传成功') | ||||
|   console.info(uploadList.value) | ||||
|   console.info(fileList.value) | ||||
|   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('input', listToString(fileList.value)) | ||||
|     emit('update:modelValue', listToString(fileList.value)) | ||||
|   } | ||||
| } | ||||
| // 文件数超出提示 | ||||
| @@ -134,7 +140,7 @@ const handleRemove = (file) => { | ||||
|   const findex = fileList.value.map((f) => f.name).indexOf(file.name) | ||||
|   if (findex > -1) { | ||||
|     fileList.value.splice(findex, 1) | ||||
|     emit('input', listToString(fileList.value)) | ||||
|     emit('update:modelValue', listToString(fileList.value)) | ||||
|   } | ||||
| } | ||||
| // 对象转成指定字符串分隔 | ||||
|   | ||||
| @@ -21,6 +21,8 @@ export type ComponentName = | ||||
|   | 'TreeSelect' | ||||
|   | 'InputPassword' | ||||
|   | 'Editor' | ||||
|   | 'UploadImg' | ||||
|   | 'UploadFile' | ||||
|  | ||||
| export type ColProps = { | ||||
|   span?: number | ||||
|   | ||||
| @@ -41,6 +41,12 @@ const crudSchemas = reactive<VxeCrudSchema>({ | ||||
|         cellRender: { | ||||
|           name: 'XImg' | ||||
|         } | ||||
|       }, | ||||
|       form: { | ||||
|         component: 'UploadImg', | ||||
|         componentProps: { | ||||
|           limit: 1 | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|   | ||||
| @@ -61,16 +61,12 @@ | ||||
|       v-if="['create', 'update'].includes(actionType)" | ||||
|       :schema="allSchemas.formSchema" | ||||
|       :rules="rules" | ||||
|     > | ||||
|       <template #logo="form"> | ||||
|         <UploadImg :imgs="form['logo']" :limit="1" /> | ||||
|       </template> | ||||
|     </Form> | ||||
|     /> | ||||
|     <!-- 表单:详情 --> | ||||
|     <Descriptions | ||||
|       v-if="actionType === 'detail'" | ||||
|       :schema="allSchemas.detailSchema" | ||||
|       :data="detailRef" | ||||
|       :data="detailData" | ||||
|     > | ||||
|       <template #accessTokenValiditySeconds="{ row }"> | ||||
|         {{ row.accessTokenValiditySeconds + '秒' }} | ||||
| @@ -142,7 +138,6 @@ import { useMessage } from '@/hooks/web/useMessage' | ||||
| import { useVxeGrid } from '@/hooks/web/useVxeGrid' | ||||
| import { VxeGridInstance } from 'vxe-table' | ||||
| import { FormExpose } from '@/components/Form' | ||||
| import { UploadImg } from '@/components/UploadFile' | ||||
| // 业务相关的 import | ||||
| import * as ClientApi from '@/api/system/oauth2/client' | ||||
| import { rules, allSchemas } from './client.data' | ||||
| @@ -163,7 +158,7 @@ const dialogTitle = ref('edit') // 弹出层标题 | ||||
| const actionType = ref('') // 操作按钮的类型 | ||||
| const actionLoading = ref(false) // 按钮 Loading | ||||
| const formRef = ref<FormExpose>() // 表单 Ref | ||||
| const detailRef = ref() // 详情 Ref | ||||
| const detailData = ref() // 详情 Ref | ||||
| // 设置标题 | ||||
| const setDialogTile = (type: string) => { | ||||
|   dialogTitle.value = t('action.' + type) | ||||
| @@ -188,7 +183,7 @@ const handleUpdate = async (rowId: number) => { | ||||
| const handleDetail = async (rowId: number) => { | ||||
|   setDialogTile('detail') | ||||
|   const res = await ClientApi.getOAuth2ClientApi(rowId) | ||||
|   detailRef.value = res | ||||
|   detailData.value = res | ||||
| } | ||||
|  | ||||
| // 删除操作 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 xingyu
					xingyu