当前位置: 首页 > web >正文

Vue将内容生成为二维码,并将所有二维码下载为图片,同时支持批量下载(下载为ZIP),含解决一次性生成过多时页面崩溃解决办法

文章目录

  • 1 下载所需的包
  • 2 直接下载二维码zip
    • 2.1 效果图
    • 2.2 完整代码
  • 3 直接下载二维码zip,添加以下功能:切片/每次切片后的判断是否继续生成/添加内存监测
    • 3.1 效果图
    • 3.2 完整代码
  • 4 不再直接下载二维码,添加以下功能:切片/每次切片后的判断是否继续生成/添加内存监测/返回生成的二维码Blob、file格式信息,供后续上传服务器等操作
    • 4.1 效果图
    • 4.2 完整代码

注意事项:当所需下载的二维码过多时,会很卡很卡,并且会导致浏览器崩溃,此时可以考虑采用 #2 / #3 进行切片分批下载,里面添加了内存监测,内存占用超出后,会暂停生成,直到内存占用回到阈值。

1 下载所需的包

vue-qr 生成二维码,html2canvas 将页面截取成图片,jszip将文件生成为zip, file-saver 下载文件

npm install vue-qr html2canvas jszip file-saver

2 直接下载二维码zip

2.1 效果图

在这里插入图片描述

在这里插入图片描述

2.2 完整代码

<template><div v-loading="loading"><div class="qr-wrapper" v-for="item in tableDataQr" :key="item.id" :ref="el => setQrContainer(el, item.id)"><vue-qr :text="item.qrUrl" :size="200"></vue-qr><div class="text-below">ID: {{ item.id }}</div></div><button @click="downloadQrZip">下载为ZIP</button></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import html2canvas from 'html2canvas'
import vueQr from 'vue-qr/src/packages/vue-qr.vue'
import JSZip from 'jszip'
import { saveAs } from 'file-saver'const tableDataQr = ref([{ id: 1, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 2, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 3, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 4, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 5, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },])
const qrText = ref("")
const qrContainers = ref({})
const loading = ref(false)// 创建所有容器的 ref
const setQrContainer = (el, index) => {if (el) qrContainers.value[index] = el
}// 直接下载二维码zip
const downloadQrZip = async () => {try {const zip = new JSZip()const imgFolder = zip.folder("二维码") // 二维码图片名称const promises = []// 确保所有DOM元素已加载await new Promise(resolve => setTimeout(resolve, 100))// 遍历收集到的所有容器Object.entries(qrContainers.value).forEach(([index, container]) => {promises.push(html2canvas(container, {backgroundColor: '#ffffff',scale: 2,logging: false, // 关闭日志减少内存useCORS: true, // 允许跨域removeContainer: true // 处理完成后移除DOM引用}).then(canvas => {return new Promise(resolve => {canvas.toBlob(blob => {if (!blob) {console.error('生成Blob失败:', canvas)resolve()return}imgFolder.file(`二维码_${index}.jpg`, blob)resolve()}, 'image/jpeg', 0.92)})}).catch(err => {console.error(`生成第${index}个二维码失败:`, err)}))})// 等待所有图片生成const results = await Promise.allSettled(promises)console.log('生成结果:', results)// 检查是否生成了任何文件if (Object.keys(imgFolder.files).length === 0) {throw new Error('没有生成任何图片文件')}// 生成ZIP并下载const content = await zip.generateAsync({ type: 'blob' })saveAs(content, '二维码.zip')} catch (error) {console.error('生成ZIP失败:', error)alert('生成ZIP失败: ' + error.message)}
}
</script><style scoped>
.qr-wrapper {display: flex;flex-direction: column;align-items: center;margin: 20px auto;padding: 20px;background-color: white;width: fit-content;
}.text-below {margin-top: 10px;font-size: 18px;font-weight: bold;
}button {margin-top: 20px;padding: 10px 20px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;
}
</style>

3 直接下载二维码zip,添加以下功能:切片/每次切片后的判断是否继续生成/添加内存监测

3.1 效果图

在这里插入图片描述

在这里插入图片描述

3.2 完整代码

<template><div v-loading="loading"><div class="qr-wrapper" v-for="item in tableDataQr" :key="item.id" :ref="el => setQrContainer(el, item.id)"><vue-qr :text="item.qrUrl" :size="200"></vue-qr><div class="text-below">ID: {{ item.id }}</div></div><button @click="downloadQrZip">下载为ZIP</button></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import html2canvas from 'html2canvas'
import vueQr from 'vue-qr/src/packages/vue-qr.vue'
import JSZip from 'jszip'
import { saveAs } from 'file-saver'const tableDataQr = ref([{ id: 1, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 2, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 3, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 4, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 5, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },])
const qrText = ref("")
const qrContainers = ref({})
const loading = ref(false)// 创建所有容器的 ref
const setQrContainer = (el, index) => {if (el) qrContainers.value[index] = el
}// 直接下载二维码,添加以下功能:切片/每次切片后的判断是否继续生成
const downloadQrZip = async () => {const startTime = performance.now();const BATCH_SIZE = 5; // 减少每批处理数量let processedCount = 0;try {const zip = new JSZip();const imgFolder = zip.folder("二维码");const currentBatchData = tableDataQr.value;const containerEntries = currentBatchData.map(item => [item.id, qrContainers.value[item.id]]);console.log('开始分批生成二维码...', containerEntries);// 内存监控函数const checkMemory = () => {if (window.performance && window.performance.memory) {const usedMB = window.performance.memory.usedJSHeapSize / 1024 / 1024;console.log(`内存使用: ${usedMB.toFixed(2)}MB`);return usedMB > 500; // 超过500MB返回true}return false;};// 分批处理for (let i = 0; i < containerEntries.length; i++) {if (!checkContinueCondition()) {console.log('条件不满足,停止生成')return}if (checkMemory()) {console.log('内存过高,暂停处理');await new Promise(resolve => setTimeout(resolve, 3000));}const [index, container] = containerEntries[i];try {const canvas = await html2canvas(container, {backgroundColor: '#ffffff',scale: 2,logging: false,useCORS: true,removeContainer: true});const blob = await new Promise(resolve => {canvas.toBlob(blob => {canvas.width = 1canvas.height = 1resolve(blob);}, 'image/jpeg', 0.1);});if (blob) {imgFolder.file(`二维码_${index}.jpg`, blob);processedCount++;console.log(`进度: ${processedCount}/${containerEntries.length}`);}// 每处理5个休息一次if (processedCount % BATCH_SIZE === 0) {await new Promise(resolve => setTimeout(resolve, 1000));}} catch (err) {console.error(`生成${index}二维码失败:`, err);}}if (processedCount === 0) {throw new Error('没有生成任何图片文件');}// 分片生成ZIP文件const content = await zip.generateAsync({type: 'blob',streamFiles: true,compression: 'DEFLATE',compressionOptions: { level: 6 }});const endTime = performance.now();const duration = (endTime - startTime) / 1000;console.log(`处理完成,共${processedCount}个,耗时: ${duration.toFixed(2)}`);// 添加生成后的判断逻辑if (content.size > 0) {console.log('ZIP文件生成成功,大小:', formatFileSize(content.size))saveAs(content, '二维码.zip')} else {console.warn('生成的ZIP文件为空')return {success: false,error: '生成的ZIP文件为空'}}} catch (error) {const endTime = performance.now()const duration = (endTime - startTime) / 1000console.log(`生成ZIP失败,已处理${processedCount}个,耗时: ${duration.toFixed(2)}`, error)console.log(`生成ZIP失败(已处理${processedCount}个): ` + error.message);}
}// 条件判断
const checkContinueCondition = () => {return true
}// 格式化文件大小
const formatFileSize = (bytes) => {if (bytes === 0) return '0 Bytes'const k = 1024const sizes = ['Bytes', 'KB', 'MB', 'GB']const i = Math.floor(Math.log(bytes) / Math.log(k))return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
</script><style scoped>
.qr-wrapper {display: flex;flex-direction: column;align-items: center;margin: 20px auto;padding: 20px;background-color: white;width: fit-content;
}.text-below {margin-top: 10px;font-size: 18px;font-weight: bold;
}button {margin-top: 20px;padding: 10px 20px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;
}
</style>

4 不再直接下载二维码,添加以下功能:切片/每次切片后的判断是否继续生成/添加内存监测/返回生成的二维码Blob、file格式信息,供后续上传服务器等操作

4.1 效果图

在这里插入图片描述

在这里插入图片描述

4.2 完整代码

<template><div v-loading="loading"><div class="qr-wrapper" v-for="item in tableDataQr" :key="item.id" :ref="el => setQrContainer(el, item.id)"><vue-qr :text="item.qrUrl" :size="200"></vue-qr><div class="text-below">ID: {{ item.id }}</div></div><button @click="downloadZip">下载为ZIP</button></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import html2canvas from 'html2canvas'
import vueQr from 'vue-qr/src/packages/vue-qr.vue'
import JSZip from 'jszip'
import { saveAs } from 'file-saver'const tableDataQr = ref([{ id: 1, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 2, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 3, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 4, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },{ id: 5, qrUrl: 'https://gitee.com/kuxiao-smile/node-nest' },])
const qrText = ref("")
const qrContainers = ref({})
const loading = ref(false)// 创建所有容器的 ref
const setQrContainer = (el, index) => {if (el) qrContainers.value[index] = el
}const downloadZip = async () => {try {loading.value = trueconst result = await downloadQrZip()console.log(result, 'result');} catch (error) {console.log(error);} finally {loading.value = false}
}// 不再直接下载二维码
const downloadQrZip = async () => {const startTime = performance.now();const BATCH_SIZE = 5; // 减少每批处理数量let processedCount = 0;try {const zip = new JSZip();const imgFolder = zip.folder("二维码");const currentBatchData = tableDataQr.value;const containerEntries = currentBatchData.map(item => [item.id, qrContainers.value[item.id]]);console.log('开始分批生成二维码...', containerEntries);// 内存监控函数const checkMemory = () => {if (window.performance && window.performance.memory) {const usedMB = window.performance.memory.usedJSHeapSize / 1024 / 1024;console.log(`内存使用: ${usedMB.toFixed(2)}MB`);return usedMB > 500; // 超过500MB返回true}return false;};// 分批处理for (let i = 0; i < containerEntries.length; i++) {if (!checkContinueCondition()) {console.log('条件不满足,停止生成')return}if (checkMemory()) {console.log('内存过高,暂停处理');await new Promise(resolve => setTimeout(resolve, 3000));}const [index, container] = containerEntries[i];try {const canvas = await html2canvas(container, {backgroundColor: '#ffffff',scale: 2,logging: false,useCORS: true,removeContainer: true});const blob = await new Promise(resolve => {canvas.toBlob(blob => {canvas.width = 1canvas.height = 1resolve(blob);}, 'image/jpeg', 0.1);});if (blob) {imgFolder.file(`二维码_${index}.jpg`, blob);processedCount++;console.log(`进度: ${processedCount}/${containerEntries.length}`);}// 每处理5个休息一次if (processedCount % BATCH_SIZE === 0) {await new Promise(resolve => setTimeout(resolve, 1000));}} catch (err) {console.error(`生成${index}二维码失败:`, err);}}if (processedCount === 0) {throw new Error('没有生成任何图片文件');}// 分片生成ZIP文件const content = await zip.generateAsync({type: 'blob',streamFiles: true,compression: 'DEFLATE',compressionOptions: { level: 6 }});const endTime = performance.now();const duration = (endTime - startTime) / 1000;console.log(`处理完成,共${processedCount}个,耗时: ${duration.toFixed(2)}`);// 添加生成后的判断逻辑if (content.size > 0) {console.log('ZIP文件生成成功,大小:', formatFileSize(content.size))// 创建实际文件对象// 如果此时生成时间不存在, 则以当前时间const fileName = `二维码.zip`const file = new File([content], fileName, {type: 'application/zip',lastModified: Date.now()})const fileInfo = {success: true,file: file,blob: content,count: processedCount,duration: duration.toFixed(2),size: formatFileSize(content.size),fileName: fileName}return fileInfo} else {console.warn('生成的ZIP文件为空')return {success: false,error: '生成的ZIP文件为空'}}} catch (error) {const endTime = performance.now()const duration = (endTime - startTime) / 1000console.log(`生成ZIP失败,已处理${processedCount}个,耗时: ${duration.toFixed(2)}`, error)console.log(`生成ZIP失败(已处理${processedCount}个): ` + error.message);}
}// 条件判断
const checkContinueCondition = () => {return true
}// 格式化文件大小
const formatFileSize = (bytes) => {if (bytes === 0) return '0 Bytes'const k = 1024const sizes = ['Bytes', 'KB', 'MB', 'GB']const i = Math.floor(Math.log(bytes) / Math.log(k))return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}</script><style scoped>
/* 保持原有样式不变 */
.qr-wrapper {display: flex;flex-direction: column;align-items: center;margin: 20px auto;padding: 20px;background-color: white;width: fit-content;
}.text-below {margin-top: 10px;font-size: 18px;font-weight: bold;
}button {margin-top: 20px;padding: 10px 20px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;
}
</style>
http://www.xdnf.cn/news/19053.html

相关文章:

  • TCP 并发服务器构建
  • 智芯MCU 勘误文档问题解析
  • 【Java知识】Java线程相关对象全面解析与最佳实践
  • 阿里云——应用交付与负载均衡
  • 数据对话的“通用语法”:SQL与KingbaseES的智能处理艺术
  • 从感知机到大模型:神经网络的全景解析与实践指南
  • ES01-环境安装
  • 盛大启幕!融智兴科技亮相 IOTE 2025 深圳国际物联网展
  • SegEarth-R1: Geospatial Pixel Reasoning via Large Language Model
  • 稀土:从“稀有”到“命脉”的科技核心
  • LeetCode算法日记 - Day 23: 外观数列、数青蛙
  • LeetCode - 155. 最小栈
  • 8.28 模拟
  • rust语言(1.88.0)sqlite数据库rusqlite库(0.37.0)学习笔记
  • 蘑兔音乐:帮你把灵感落地
  • 【新版发布】Apache DolphinScheduler 3.3.1 正式上线:更稳、更快、更安全!
  • 【Django + Pure Admin】基于Django+Vue3的前后端分离管理系统框架设计
  • 预处理详解
  • 【Spring Cloud 微服务】5.架构的智慧枢纽:深度剖析 Nacos 注册中心
  • 《Vuejs设计与实现》第 17 章(编译优化)
  • JMeter 5.3 性能测试:文件下载脚本编写与导出文件接收完整指南
  • 数据结构:堆排序 (Heap Sort)
  • spire.doc在word中生成公式
  • 设计模式理解
  • Shader开发(十七)着色器中的纹理采样与渲染
  • 农业物联网:科技赋能现代农业新篇章
  • 数模笔记day01(数据预处理、K-means聚类、遗传算法、概率密度分布)
  • UE5蓝图接口的创建和使用方法
  • 有鹿机器人如何用科技与创新模式破解行业难题
  • linux下的网络编程(2)