2026/1/17 23:16:14
网站建设
项目流程
专业的营销网站,做网站的收费,北京建站公司兴田德润信任,长安做英文网站一.效果展示1.展开折叠项#xff0c;加载视图中图片#xff0c;2.随着滚动条移动#xff0c;逐渐加载对应图片二.处理前的效果图。1.问题截图这就是部分大图加载不出来的截图#xff0c;就是因为一下加载整个表格的所有图片#xff0c;一个是卡#xff0c;第二个最主要的…一.效果展示1.展开折叠项加载视图中图片2.随着滚动条移动逐渐加载对应图片二.处理前的效果图。1.问题截图这就是部分大图加载不出来的截图就是因为一下加载整个表格的所有图片一个是卡第二个最主要的是加载不出来但是点击单个图片预览却可以这也是我做这个懒加载的原因。三.解决方案。1.页面模版展示图片部分div vxe-grid :ref(el) (gridRefs[tableIndex] el) v-bindgridOptions2 v-ongridEvents bordernone :stripetrue maxHeight300px :dataitem.checkItemDetail template #referenceImageUrl_content{ row } div v-if props.type view || formData.documentStatus?.includes(审批) || formData.documentStatus 已完成 || tableIndex secondTable.length - 1 template v-ifrow.pictureList.length elImageDialog :pictureListrow.pictureList / /template img v-else classcustom-image src../../../../assets/imgs/暂无图片.png/ /div div v-else div v-visible(v) (row._imgVisible v) stylemin-height: 110px UploadMoreImage v-ifrow._imgVisible :limit3 :fileListrow.pictureList update:fileList(val) (row.pictureList val) viewhandlePreview fileStatusChange(status) handleFileStatusChange(status, row) / /div /div /template /vxe-grid /div /div !-- 图片预览弹窗 -- el-dialog v-modeldialogVisible width60% img w-full :srcdialogImageUrl altPreview Image stylewidth: 100% / /el-dialog //引入v-visible import { vVisible } from /directive/visible // 预览图片 const handlePreview (file) { dialogImageUrl.value file.url // 设置预览的图片 URL dialogVisible.value true // 显示预览弹窗 }注释大家只需看template #referenceImageUrl_content{ row }... /template里面这部分2.v-visible实现directive/visible.ts文件import type { Directive } from vue const observers new WeakMapElement, IntersectionObserver() export const vVisible: DirectiveHTMLElement, (v: boolean) void { mounted(el, binding) { createObserver(el, binding.value) }, updated(el, binding) { // 当绑定函数变化 or 重新渲染时重新监听 if (binding.value ! binding.oldValue) { cleanup(el) createObserver(el, binding.value) } }, unmounted(el) { cleanup(el) } } function createObserver(el: HTMLElement, callback: (v: boolean) void) { if (typeof callback ! function) return const observer new IntersectionObserver( ([entry]) { if (entry.isIntersecting) { callback(true) observer.disconnect() } }, { rootMargin: 150px } ) observers.set(el, observer) observer.observe(el) } function cleanup(el: Element) { const observer observers.get(el) if (observer) { observer.disconnect() observers.delete(el) } }3.UploadMoreImage上传组件template el-upload refupload classcustom-upload :actionuploadUrl v-model:file-listfileList :limitprops.limit list-typepicture-card :on-previewhandlePreview :on-removehandleRemove :auto-uploadfalse :show-file-listtrue multiple :on-changehandleChange :on-exceedhandleExceed el-iconPlus //el-icon /el-upload /template script langts setup import { ref, computed, defineProps, watch } from vue import { ElUpload, ElMessage, ElIcon, genFileId } from element-plus import { Plus } from element-plus/icons-vue import type { UploadInstance, UploadProps, UploadRawFile } from element-plus import { compressImage } from /utils/compressImage // Emit事件 const $emit defineEmits([view, update:fileList, fileStatusChange]) // Props const props defineProps({ limit: { type: Number, default: 1 // 默认限制为1张图片 }, fileList: { type: Array, default: () [] } }) const upload refUploadInstance() const fileList ref(props.fileList) // 图片文件列表 const uploadUrl https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15 // 上传接口 // 预览图片 const handlePreview (file) { $emit(view, file) } // 删除图片 const handleRemove (file) { $emit(fileStatusChange, true) const index fileList.value.indexOf(file) if (index -1) { fileList.value.splice(index, 1) // 删除选中的图片 } $emit(update:fileList, fileList.value) } // 当超出限制数量时清空已上传的图片重新上传 const handleExceed: UploadProps[onExceed] (files) { // 判断当前上传的文件数量超过限制的文件进行处理 const excessFiles files.slice(0, files.length - props.limit) // 校验上传的文件 files.forEach((newFile, index) { // 只处理没有超过限制的文件 if (index excessFiles.length) { if (!newFile.type.startsWith(image/) || newFile.size / 1024 / 1024 20) { ElMessage.error(请上传20MB以内的图片格式!) return } const raw newFile as UploadRawFile raw.uid genFileId() // 构造可展示的文件对象 const uploadFile { name: raw.name, url: URL.createObjectURL(raw), raw } // 仅在未超出限制时添加文件 // if (fileList.value.length props.limit) { // fileList.value.push(uploadFile) // } else { // // 如果超过限制替换掉最后一张 // fileList.value.splice(fileList.value.length - 1, 1, uploadFile) // } if (fileList.value.length props.limit) { // 仅在未超出限制时添加文件 fileList.value.push(uploadFile) } else { // 超过限制时删除上传的文件并提示图片最大上传数量3张 ElMessage.error(图片最大上传数量${props.limit}张!) return // 直接退出避免继续添加文件 } } }) } // 格式化文件大小 const formatFileSize (bytes) { if (bytes 0 || bytes null) return 0 B const units [B, KB, MB, GB, TB] const k 1024 const i Math.floor(Math.log(bytes) / Math.log(k)) const size (bytes / Math.pow(k, i)).toFixed(2) return ${size} ${units[i]} } const handleChange async (file, flist) { const rawFile file.raw as File const sizeMB rawFile.size / 1024 / 1024 const isImg rawFile.type.startsWith(image/) console.log(当前图片大小, formatFileSize(rawFile?.size)) if (!isImg) { ElMessage.error(请上传图片格式) const index fileList.value.length - 1 fileList.value.splice(index, 1) return } if (sizeMB 20) { ElMessage.error(请上传 20MB 以内的图片格式) const index fileList.value.length - 1 fileList.value.splice(index, 1) return } if (sizeMB 5) { try { const compressedFile await compressImage(rawFile, 1280, 0.8) file.raw compressedFile file.size compressedFile.size file.url URL.createObjectURL(compressedFile) } catch (e) { ElMessage.error(图片压缩失败) return } } if (flist.length props.limit) return $emit(update:fileList, flist) $emit(fileStatusChange, true) } /script style langless scoped .custom-upload { position: relative; } ::v-deep(.el-upload--picture-card) { --el-upload-picture-card-size: 103px; } ::v-deep(.el-upload-list--picture-card) { --el-upload-list-picture-card-size: 103px; } ::v-deep(.el-icon--close-tip) { display: none !important; } /style注释图片压缩import { compressImage } from /utils/compressImageif (sizeMB 5) {try {const compressedFile await compressImage(rawFile, 1280, 0.8)file.raw compressedFilefile.size compressedFile.sizefile.url URL.createObjectURL(compressedFile)} catch (e) {ElMessage.error(图片压缩失败)return}}压缩前后大小截图直接从19.77MB压缩到6.92 MB4.elImageDialog组件template div !-- 图片展示区域 -- div classimage-gallery img v-for(img, index) in props.pictureList :keyindex v-lazyimg.url :classprops.isWidth ? thumbnail2 : thumbnail clickhandlePreview(index) / /div !-- 预览图片区域 -- div v-ifshowPreview classpreview-overlay clickclosePreview img :srcprops.pictureList[currentPreviewIndex].url classpreview-image / /div /div /template script setup import { ref, defineProps } from vue; // 接收外部传入的图片数据 const props defineProps({ pictureList: { type: Array, required: true }, isWidth:{ type: Boolean, default:false } }); // 当前预览图片的索引 const currentPreviewIndex ref(0); // 是否显示预览图片 const showPreview ref(false); // 处理点击图片时打开预览 const handlePreview (index) { currentPreviewIndex.value index; showPreview.value true; }; // 关闭预览 const closePreview () { showPreview.value false; }; /script style langless scoped .image-gallery { display: flex; flex-wrap: wrap; gap: 10px; } .thumbnail { width: 100px; height: 100px; object-fit: cover; cursor: pointer; transition: transform 0.3s ease; } .thumbnail2 { width: 70px; height: 70px; object-fit: cover; cursor: pointer; transition: transform 0.3s ease; } .thumbnail:hover { transform: scale(1.1); } .preview-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 1000; cursor: pointer; } .preview-image { max-width: 90%; max-height: 90%; } /style5.v-lazy实现directive/lazy.ts文件import type { Directive } from vue const observers new WeakMapElement, IntersectionObserver() export const vLazy: DirectiveHTMLImageElement, string { mounted(el, binding) { initObserver(el, binding.value) }, updated(el, binding) { // 关键图片地址变了 if (binding.value ! binding.oldValue) { // 重置 src避免复用旧图 el.src // 重新监听 observers.get(el)?.disconnect() initObserver(el, binding.value) } }, /** * 清理元素关联的MutationObserver并移除观察者引用 * param {HTMLElement} el - 需要清理观察者的DOM元素 */ unmounted(el) { observers.get(el)?.disconnect() observers.delete(el) } } function initObserver(el: HTMLImageElement, src: string) { const observer new IntersectionObserver(([entry]) { if (entry.isIntersecting) { el.src src observer.unobserve(el) } }) observers.set(el, observer) observer.observe(el) }6.全局注册v-lazyimport { createApp } from vueimport App from ./App.vueimport { vLazy } from /directive/lazyconst app createApp(App)app.directive(lazy, vLazy)app.mount(#app)7.图片压缩组件utils/compressImage.ts/** * 图片压缩 * param file 原始图片 File * param maxWidth 最大宽度 * param quality 压缩质量 0~1 */ export function compressImage( file: File, maxWidth 1280, quality 0.8 ): PromiseFile { return new Promise((resolve, reject) { const reader new FileReader() reader.onload (e) { const img new Image() img.src e.target?.result as string img.onload () { const canvas document.createElement(canvas) const ctx canvas.getContext(2d)! let { width, height } img // 等比缩放 if (width maxWidth) { height (maxWidth / width) * height width maxWidth } canvas.width width canvas.height height ctx.drawImage(img, 0, 0, width, height) canvas.toBlob( (blob) { if (!blob) return reject(压缩失败) const compressedFile new File([blob], file.name, { type: file.type, lastModified: Date.now() }) resolve(compressedFile) }, file.type, quality ) } img.onerror reject } reader.onerror reject reader.readAsDataURL(file) }) }