<template> <div> <div class="container clearfix"> <!-- 图片显示框 --> <template v-if="currentImg.length"> <div class="imgList" v-for="(item, index) in fileList" :key="item"> <div class="imgContainer" @mouseenter="mouseEnter(index)" @mouseleave="mouseLeave(index)"> <img :src="currentImg[index]" class="imgCover" /> <span v-if="maskShow[index]" class="mask"> <EyeOutlined v-if="false" @click="handlePreview(item, index)" /> <DeleteOutlined @click="deleteImg(index)" /> </span> </div> </div> </template> <!-- 图片显示框 /--> <!-- 上传图片框/内置了input --> <a-upload list-type="picture-card" :before-upload="beforeUpload" @change="handleChange" :multiple="count != 1" :customRequest="selfUpload" :showUploadList="false" > <!-- :disabled="count == 1 && fileUuid ? true : false" --> <div v-if="count === 1 ? 'true' : fileList.length < count"> <template v-if="isLt5M && isJpgOrPngOrJpeg"> <UploadOutlined :style="{ fontSize: '14px' }" /> <div class="ant-upload-text">上传图片</div> </template> <template v-else> <LoadingOutlined /> <div class="ant-upload-text">校验失败</div> </template> </div> </a-upload> <!-- 上传图片框/内置input /--> </div> <!-- 预览图片弹窗 --> <a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel"> <img alt="example" style="width: 100%" :src="'/api/ihorn/objs/pictureBack/' + previewImage" /> </a-modal> <!-- 预览图片弹窗 /--> <!-- 错误消息提示 --> <div class="err-msg" v-if="!isJpgOrPngOrJpeg"> <p>{{ fileName }} 文件上传失败</p> <p :style="{ color: 'red' }"> 请选择{{ fileType.join(',') }}的图片 </p> </div> <div class="err-msg" v-if="isJpgOrPngOrJpeg ? !isLt5M : ''"> <p>{{ fileName }} 文件上传失败</p> <p :style="{ color: 'red' }"> 请选择{{ maxSize / 1024 / 1024 }}M内的图片 </p> </div> <!-- 错误消息提示 /--> </div> </template> <script lang="ts"> import { UploadOutlined, LoadingOutlined, EyeOutlined, DeleteOutlined, } from '@ant-design/icons-vue'; import { defineComponent, ref, computed } from 'vue'; import { http } from '/nerv-lib/util/http'; import { NsMessage } from '/nerv-lib/component/message'; interface FileItem { uid: string; type: string; size: number; name?: string; status?: string; response?: string; percent?: number; url?: string; preview?: string; originFileObj?: any; } interface FileInfo { file: FileItem; fileList: FileItem[]; } // 转base64 function getBase64(file: File) { return new Promise((resolve, reject) => { console.log(file); const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = (error) => reject(error); }); } export default defineComponent({ name: 'NsUpload', components: { UploadOutlined, LoadingOutlined, DeleteOutlined, EyeOutlined, }, props: { // 上传的地址 url: { type: String, require: true, }, // 上传的图片大小 maxSize: { type: Number, default: 5242880, }, // 上传的图片类型 fileType: { type: Array, default: () => { return ['jpg', 'png', 'jpeg']; }, }, // 展示图片数量 count: { type: Number, default: 1, }, // 上传文件类型,0-证书,1-图片,2-身份证件 uploadType: { type: Number, default: 1, }, baseImageUrl: { type: [String, Object], }, }, emits: ['change'], setup(props, { emit }) { const previewVisible = ref<boolean>(false); const isLt5M = ref<boolean>(true); const isJpgOrPngOrJpeg = ref<boolean>(true); const previewImage = ref<string | undefined>(''); const fileList = ref<FileItem[]>([]); const currentImg = ref<string[]>([]); const isIntImg = ref(false); if (props.baseImageUrl) { isIntImg.value = true; if (typeof props.baseImageUrl == 'object') { // currentImg.value.concat(props.baseImageUrl); props.baseImageUrl.map((item, index) => { fileList.value.push(item); currentImg.value.push(item); // previewImage.value = props.baseImageUrl[index]; }); // currentImg.value.unshift(...props.baseImageUrl); } else { fileList.value.push({}); currentImg.value.unshift(props.baseImageUrl); previewImage.value = props.baseImageUrl; } } const fileName = ref<string | undefined>(''); const maskShow = ref<boolean[]>([]); const fileUuid = ref<string>(''); const acceptType = computed(() => props.fileType.map((item: String) => { return 'image/' + item; }) ); const beforeUpload = (file: FileItem) => { // 上传出错后,下次上传图片前,重置为true,让图片可以上传 isLt5M.value = true; isJpgOrPngOrJpeg.value = true; // 限制图片格式,服务器不支持gif图片 if (file.type === 'image/gif') { NsMessage.warn('不支持gif图片'); return false; } isJpgOrPngOrJpeg.value = acceptType.value.includes(file.type); // 如果大于指定的大小,显示错误信息 if (file.size > props.maxSize) { isLt5M.value = false; } fileName.value = file.name; return isLt5M.value && isJpgOrPngOrJpeg.value; }; const handleChange = ({ fileList: newFileList }: FileInfo) => { // 单图上传 if (props.count === 1) { // 删除图片时,newFileList.length = 0 // 图片大小不符合规范时,!isLt5M.value为true if (!isLt5M.value || !isJpgOrPngOrJpeg.value || newFileList.length - 1 < 0) { // 让图片不显示,也不上传 fileList.value = []; } else { // 添加\更换图片 // newFileList[newFileList.length - 1]的目的是为了只显示最新一张图片 fileList.value = [newFileList[newFileList.length - 1]]; } } else { // 多图上传 // if (typeof props.baseImageUrl == 'object') { // props.baseImageUrl.map((item, index) => { // console.log(item); // fileList.value.push(item); // // currentImg.value.unshift(item); // // previewImage.value = props.baseImageUrl[index]; // }); // } if (!isLt5M.value || !isJpgOrPngOrJpeg.value) { // fileList.value = newFileList.slice(0, newFileList.length - 1); // fileList.value = fileList.slice(0, fileList.length - 1); // console.log(123444, fileList.value, newFileList); } else { fileList.value.push(newFileList); emit('change', fill(fileList.value)); // fileList.value = newFileList; } } }; const fill = (data) => { let arr = []; if (props.count == 1) return; data.map((item) => { if (item.indexOf('ParkPic') !== -1) { arr.push(item.split('ParkPic/')[1]); } }); return arr; }; const selfUpload = async ({ file }) => { if (props.count !== 1) { emit('change', fill(fileList.value)); } currentImg.value.push(await getBase64(file)); const params = { uploadType: props.uploadType, }; const formData = new FormData(); formData.append('file', file); formData.append('uploadType', props.uploadType); //formData.append('uploadType', 1); const config = { headers: { 'Content-Type': 'multipart/form-data', }, // params: params, }; console.log(formData); if (props.count == 1) { fileUuid.value = 'ceshi'; } http .post(props.url, formData, config) .then((res) => { if (props.count > 1) { // let imgBox = ref([]); console.log(fileList.value); fileList.value.push('/api/ihorn/objs/pictureBack/' + res.data.fileUuid); fileList.value.forEach((item, index) => { if (typeof item == 'object') { fileList.value.splice(index, 1); } }); emit('change', fill(fileList.value)); } else { fileUuid.value = res.data.picUuid || res.data.fileUuid; emit('change', fileUuid.value); } }) .catch(() => { fileList.value.forEach((item, index) => { if (typeof item == 'object') { fileList.value.splice(fileList.value.length - 1, 1); } }); NsMessage.error('上传失败,请重试'); }); }; const handlePreview = async (item, index: number) => { // emit('change', fill(fileList.value)); console.log(item, index); if (props.count > 1) { previewImage.value = fileList.value[index]; } else { previewImage.value = fileUuid.value; } // if (!isIntImg.value) { // previewImage.value = (await getBase64(fileList.value[index])) as string; // // previewImage.value = (await getBase64(fileList.value[index].originFileObj)) as string; // } previewVisible.value = true; }; const deleteImg = (index: number) => { currentImg.value.splice(index, 1); fileList.value.splice(index, 1); fileUuid.value = ''; isIntImg.value = false; if (props.count == 1) { emit('change', fileUuid.value); } else { emit('change', fill(fileList.value)); } }; const handleCancel = () => { previewVisible.value = false; }; const mouseEnter = (index: number) => { maskShow.value[index] = true; }; const mouseLeave = (index: number) => { maskShow.value[index] = false; }; return { previewVisible, previewImage, fileList, isLt5M, isJpgOrPngOrJpeg, fileName, currentImg, maskShow, fileUuid, selfUpload, handleCancel, handlePreview, handleChange, beforeUpload, mouseEnter, mouseLeave, deleteImg, }; }, }); </script> <style> .ant-upload-picture-card-wrapper .ant-upload.ant-upload-select-picture-card { margin: 0; width: 80px; height: 80px; border: 1px solid #d9d9d9; } .ant-upload-picture-card-wrapper .ant-upload.ant-upload-select-picture-card:hover { border-color: #d9d9d9; } .ant-upload-select-picture-card i { font-size: 32px; color: #999; } .ant-upload-select-picture-card .ant-upload-text { color: #666; font-size: 12px; } .ant-upload-picture-card-wrapper .ant-upload-list-picture-card-container { width: 88px; height: 80px; } .ant-upload-picture-card-wrapper .ant-upload-list-picture-card .ant-upload-list-item { margin: 0; padding: 0; width: 80px; height: 80px; } .title, .err-msg { text-align: left; } .err-msg p { margin: 0; } .container { display: flex; } .imgList { display: flex; } .imgContainer { margin-right: 16px; border: 1px solid #d9d9d9; position: relative; } .imgCover { width: 80px; height: 80px; object-fit: contain; } .imgContainer .mask { position: absolute; top: 0; left: 0; width: 80px; height: 80px; background: rgba(0, 0, 0, 0.5); text-align: center; line-height: 80px; } .mask .anticon-eye { color: white; margin-right: 18px; } .mask .anticon-eye:hover { color: #00acff; margin-right: 18px; } .mask .anticon-delete { color: white; } .mask .anticon-delete:hover { color: #00acff; } </style>