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

uniapp+vue3+ts 使用canvas实现安卓端、ios端及微信小程序端二维码生成及下载

加粗样式uniapp多端生成带二维码海报并保存至相册的实现

在微信小程序开发中,我们常常会遇到生成带有二维码的海报并保存到手机相册的需求,比如分享活动海报、产品宣传海报等。今天就来和大家分享一下如何通过代码实现这一功能。
准备工作 在开始之前,我们需要先安装 weapp-qrcode 库,使用 npm install weapp-qrcode
命令即可完成安装。这个库将帮助我们方便地生成二维码。

敲代码

- 代码实现

  • 模板部分
<template><!-- 海报容器 --><view class="poster-container" :style="{height:getClientHeight(0)}"><canvas id="posterCanvas" canvas-id="posterCanvas" type="2d":style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas><canvas style="position:absolute;left:-999px;" :style="{width:canvasWidth*0.29+'px',height:canvasWidth*0.29+'px'}"canvas-id="qrcodeCanvas" id="qrcodeCanvas"></canvas><!-- 统一保存按钮 --><button class="save-btn" @click="handleSave" :loading="btnLoading">保存到手机相册</button></view>
</template>

在模板中,我们定义了一个海报容器,包含两个 canvas
元素,一个用于绘制海报内容,另一个用于生成二维码。同时,还有一个保存按钮,点击该按钮会触发 handleSave 方法来保存海报到相册。

  • 脚本部分

```typescript
<script setup lang="ts">import QRCode from 'weapp-qrcode'import { getClientHeight } from '@/core/utils/verulia'const btnLoading = ref(false);const canvasWidth = ref(sysInfo.screenWidth)const canvasHeight = ref(canvasWidth.value * 1.56)const { proxy } = getCurrentInstance() || {}if (!proxy) {throw new Error('组件实例未找到')}//需要渲染到画布上的数据const xxxInfo = ref(null)let canvas// 生成二维码图片路径const generateQrcode = (text : string) => {return new Promise<string>((resolve, reject) => {try {const qrWidth = canvasWidth.value * 0.29;const qr = new QRCode({width: qrWidth,height: qrWidth,canvasId: 'qrcodeCanvas',text,correctLevel: 3})setTimeout(() => {uni.canvasToTempFilePath({canvasId: 'qrcodeCanvas',success: res => {resolve(res.tempFilePath)},fail: err => {uni.hideLoading()console.error('二维码生成失败:', err)reject(null)}})}, 500)} catch (e) {console.log(e)reject(e)}})}const loadWxImage = (path : string, canvas : any) => {return new Promise<HTMLImageElement>((resolve, reject) => {const img = canvas.createImage()img.onload = () => resolve(img)img.onerror = rejectimg.src = path})}// 微信小程序绘图处理const drawWeixinPoster = async () => {return new Promise((resolve, reject) => {try {console.log('准备获取 canvas 节点,id 为:', 'posterCanvas')uni.createSelectorQuery().select('#posterCanvas').fields({ node: true, size: true }).exec(async (res) => {console.log('获取 canvas 节点结果:', res)if (!res?.[0]?.node || JSON.stringify(res?.[0]?.node) === '{}') {uni.hideLoading()reject('获取canvas节点失败')return} else {canvas = res[0].nodeconsole.log('成功获取到 canvas 节点:', canvas)}const ctx = canvas.getContext('2d')if (!ctx) {reject('获取绘图上下文失败')return}//获取图片 微信小程序中需要这样转一下const [bgImage, orImage, deImage] = await Promise.all([loadWxImage("图片路径", canvas),loadWxImage("图片路径", canvas),loadWxImage("图片路径", canvas)])let text = "二维码内容";let qrcodePath, qrImageif (text) {qrcodePath = await generateQrcode(String(text))qrImage = await new Promise<any>((resolve) => {const img = canvas.createImage()img.onload = () => resolve(img)img.src = qrcodePath})} else {//这里重新生成了一下,防止生成二维码失败setTimeout(async () => {let text1 = "二维码内容";if (text1) {qrcodePath = await generateQrcode(String(text1))qrImage = await new Promise<any>((resolve) => {const img = canvas.createImage()img.onload = () => resolve(img)img.src = qrcodePath})} else {reject(null)uni.hideLoading()}}, 1000)}const dpr = sysInfo.pixelRatiocanvas.width = canvasWidth.value * dprcanvas.height = canvasHeight.value * dprctx.scale(dpr, dpr)//中间这里写你要在画布上要画的其他内容、根据自己需要改就可以,可以参考微信小程序/uniapp的官方文档//将二维码画上去ctx.drawImage(qrImage, canvasWidth.value * 0.29, canvasWidth.value * 0.915, canvasWidth.value * 0.42, canvasWidth.value * 0.42)uni.hideLoading()resolve(true)})} catch (e) {uni.hideLoading()console.log(e)reject(e)}})}//安卓端绘制方法const drawAppPoster = async () => {return new Promise(async (resolve, reject) => {try {console.log('准备获取 canvas 节点,id 为:', 'posterCanvas')const ctx = uni.createCanvasContext("posterCanvas",)if (!ctx) {reject('获取绘图上下文失败')return}let text = "二维码内容"let qrcodePathif (text) {qrcodePath = await generateQrcode(String(text))} else {setTimeout(async () => {let text1 = "二维码内容";if (text1) {qrcodePath = await generateQrcode(String(text1))} else {reject(null)uni.hideLoading()}}, 1000)}const dpr = sysInfo.pixelRatio//中间这里写你要在画布上要画的其他内容、根据自己需要改就可以,可以参考微信小程序/uniapp的官方文档//将二维码画上去await ctx.drawImage(qrcodePath, canvasWidth.value * 0.29, canvasWidth.value * 0.915, canvasWidth.value * 0.42, canvasWidth.value * 0.42)ctx.draw();uni.hideLoading()resolve(true)        } catch (e) {uni.hideLoading()console.log(e)reject(e)}})}//保存下载二维码const handleSave = async () => {btnLoading.value = true;try {if (uni.getSystemInfoSync().platform === 'android') {// #ifdef APP-PLUS// 安卓端使用 plus.android.requestPermissions 请求权限const Context = plus.android.importClass("android.content.Context");const PackageManager = plus.android.importClass("android.content.pm.PackageManager");const main = plus.android.runtimeMainActivity();const pm = main.getPackageManager();const permission = "android.permission.WRITE_EXTERNAL_STORAGE";const hasPermission = pm.checkPermission(permission, main.getPackageName()) === PackageManager.PERMISSION_GRANTED;if (!hasPermission) {plus.android.requestPermissions([permission], function (resultObj) {const result = resultObj.granted;if (result.indexOf(permission) !== -1) {console.log('已获取相册写入权限');// 在这里可以执行保存图片到相册的操作uni.canvasToTempFilePath({canvasId: 'posterCanvas',success: function (res) {console.log(res.tempFilePath)uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath })uni.showToast({ title: '保存成功' })btnLoading.value = false;},fail: function (e) {console.log(e);btnLoading.value = false;}})} else {console.log('用户拒绝授予相册写入权限');// 可以引导用户手动开启权限uni.showModal({title: '权限提示',content: '请在设置中开启相册写入权限,以便保存图片到相册。',success: (res) => {if (res.confirm) {// 打开设置页面btnLoading.value = false;uni.openSetting();}}, fail: (err) => {console.log(err);btnLoading.value = false;}});}}, function (error) {console.error('请求权限出错:', error);btnLoading.value = false;});} else {console.log('已有相册写入权限');// 在这里可以执行保存图片到相册的操作uni.canvasToTempFilePath({canvasId: 'posterCanvas',success: function (res) {console.log(res.tempFilePath)uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath })uni.showToast({ title: '保存成功' });btnLoading.value = false;},fail: function (e) {console.log(e);btnLoading.value = false;}})}// #endif//#ifdef MP-WEIXIN// 1. 检查权限//安卓端微信小程序端保存下载const { authSetting } = await uni.getSetting();if (!authSetting['scope.writePhotosAlbum']) {btnLoading.value = false;await uni.authorize({ scope: 'scope.writePhotosAlbum' });}await uni.canvasToTempFilePath({canvas: canvas,success: function (res) {// 在H5平台下,tempFilePath 为 base64console.log(res.tempFilePath)uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath });uni.showToast({ title: '保存成功' });btnLoading.value = false;}, fail: function (e) {btnLoading.value = false;console.log(e);}});//#endif} else {//ios端及ios端微信小程序处理// 1. 检查权限const { authSetting } = await uni.getSetting();if (!authSetting['scope.writePhotosAlbum']) {btnLoading.value = false;await uni.authorize({ scope: 'scope.writePhotosAlbum' });}await uni.canvasToTempFilePath({canvas: canvas,success: function (res) {// 在H5平台下,tempFilePath 为 base64console.log(res.tempFilePath)uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath });uni.showToast({ title: '保存成功' });btnLoading.value = false;}, fail: function (e) {console.log(e);btnLoading.value = false;}});}} catch (e) {console.log(e);btnLoading.value = false;}};onReady(async () => {const xxxId = uni.getStorageSync("xxxId");uni.showLoading({mask: true})try {//这里要获取一下你的数据内容,请求接口这里只是示例const res = await api.getInfo(xxxId);xxxInfo.value = res.dataawait nextTick()setTimeout(async () => {// #ifdef MP-WEIXINawait drawWeixinPoster();// #endif// #ifdef APP-VUEawait drawAppPoster()// #endif}, 1000)} catch (error) {uni.hideLoading()console.error('数据请求失败:', error)}})
</script>
  • 样式部分
<style>/* 统一样式 */.poster-container {background: #fff;}.save-btn {margin-top: 40rpx;width: 80%;height: 80rpx;line-height: 80rpx;background: #3366FF;color: white;border-radius: 40rpx;}#posterCanvas {width: 100%;height: auto;}
</style>
http://www.xdnf.cn/news/192349.html

相关文章:

  • 十一、引用与拷贝函数(References the Copy-Constructor)
  • C++实时统计数据均值、方差和标准差
  • WGCAT工单系统发现错误 定时处理工单数据任务错误
  • MySQL笔记-mysql -hlocalhost和mysql -h127.0.0.1的不同
  • C语言教程(十八):C 语言共用体详解
  • 基于Python的携程国际机票价格抓取与分析
  • 【MCP教程系列】如何自己打包MCP服务并部署到阿里云百炼上【nodejs+TypeScript】搭建自己的MCP【Cline】
  • 排序算法详解笔记
  • Fiddler+Yakit实现手机流量抓包和小程序抓包
  • 【ESP32】st7735s + LVGL移植
  • 输出圆周率的前n位数字
  • 出口转内销如何破局?“金融+数智供应链”模式含金量还在上升
  • OpenHarmony - 小型系统内核(LiteOS-A)(十三),LMS调测
  • 文献阅读(一)植物应对干旱的生理学反应 | The physiology of plant responses to drought
  • 早投早发表!3本 Nature 新子刊已开放投稿系统!
  • 【前端】从零开始的搭建顺序指南(技术栈:Node.js + Express + MongoDB + React)book-management
  • 97AB-ASEMI机器人功率器件专用97AB
  • transformer-实现单层encoder_layer
  • JavaScript性能优化实战(6):网络请求与资源加载优化
  • 优化MySQL性能:主从复制与读写分离实践指南
  • 设计模式(行为型)解释器模式
  • 用Python做有趣的AI项目5:AI 画画机器人(图像风格迁移)
  • 蓝牙耳机开发--TWS蓝牙耳机双向通信充电盒设计
  • 0805登录_注册_token_用户信息_退出-网络ajax请求2-react-仿低代码平台项目
  • DeepSeek+Dify之三工作流引用知识库案例
  • 第十四章-PHP与HTTP协议
  • Async/Await 必须使用 try/catch 吗?
  • 大模型如何应对内容安全:原理、挑战与技术路径探讨
  • Webug4.0通关笔记02- 第2关布尔注入与第3关延时注入
  • ubantu18.04(Hadoop3.1.3)之Flink安装与编程实践(Flink1.9.1)