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

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.dtcms.com/a/354378.html

相关文章:

  • 【雅思020】Opening a bank account
  • C语言二级考试环境配置教程【window篇】
  • 能源行业数据库远程运维安全合规实践:Web化平台的落地经验
  • 【系统分析师】高分论文:论快速应用开发方法及应用
  • Linux初始——基础指令篇
  • Libvio 访问异常排查指南​
  • List | 常见的List实现类(ArrayList、LinkedList、Vector)以及ArrayList源码解读
  • 【Redis】数据分片机制和集群机制
  • 8.28 模拟|双指针
  • 零基础-力扣100题从易到难详解(持续更新1-10题)
  • Windows 命令行:rmdir 命令
  • Qt 6 与 Qt 5 存在的兼容性差异
  • C# 数组C# 多维数组
  • QML(2) - Qt 中如何注册一个 C++ 类到 QML
  • MySQL的类split方法实现
  • Java表格处理详解以及结合实际项目使用
  • WebStrom-如何设置前端项目快捷启动
  • 29. String, StringBuffer,StringBuilder 的区别是什么
  • 统一虚拟试穿框架OmniTry:突破服装局限,实现多品类可穿戴物品虚拟试穿无蒙版新跨越。
  • 【小白笔记】网速
  • TypeScript:完整的函数类型书写方式
  • 【开题答辩全过程】以超市管理系统为例,包含答辩的问题和答案
  • Linux 系统核心调优:CPU、磁盘 I/O、网络与内核参数实战
  • 流行蝴蝶剑高清重制版Windows10可玩!
  • 小程子找Bug之for循环的初始化表达类型
  • 【美团】放它一马
  • 今日行情明日机会——20250827
  • 即时配送运营平台系统功能分析
  • 寄存器, 堆栈, 汇编指令详解
  • 入门概念|Thymeleaf与Vue