开始实现之前先上个效果图
tips
- 网络图片需先配置download域名,可通过wx.getImageInfo转为临时路径;
- 个人习惯问题,我习惯使用async-await语法,所以需要引入regenerator这个库,使用方式可网上查。
一、封装通用微信api返回为Promise对象
/datas/common.js
// 封装获取微信图片信息。
export const getWxImageInfo = (imgPath) => {return new Promise((resolve, reject) => {wx.getImageInfo({src: imgPath,success: res => {resolve(res)},fail: res => {reject(res)}})})
}// 封装获取节点选择器信息
export const getSelectQurey = (queryStr) => {return new Promise(resolve => {var query = wx.createSelectorQuery();query.select(queryStr).boundingClientRect();query.exec(res => {resolve(res)})})
}// 封装把画布导出生成指定大小的图片
export const canvasToTempFilePath = (width, height, canvasId, fileType = 'jpg') => {return new Promise((resolve, reject) => {wx.canvasToTempFilePath({width,height,canvasId,fileType,success: res => {resolve(res)},fail: res => {reject(res)}})})
}// 封装保存图片到系统相册
export const saveImageToPhotosAlbum = (filePath) => {return new Promise((resolve, reject) => {wx.saveImageToPhotosAlbum({filePath,success: res => {resolve(res)},fail: res => {reject(res)}})})
}
二、视图的实现
.wxml
.wxss
/* 查看大图 */.shade {width: 100%;height: 100%;background-color: rgba(240, 235, 235, 0.5);position: fixed;z-index: 100;top: 0;left: 0;
}.qr-code {width: 600rpx;height: 1000rpx;background-color: #fff;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%); /* margin: 30rpx auto; */
}.qr-canvas {display: block;background-color: #fff;margin: 0 auto;width: 600rpx;height: 900rpx;
}.qr-btn {width: 600rpx;height: 100rpx;line-height: 100rpx;margin: 0 auto;font-size: 28rpx;color: #fff;display: flex;background-color: #658dc5;
}.qr-btn-save {flex: 0 0 500rpx;text-align: center;border-right: 1rpx solid #fff;
}.qr-btn-cancel {text-align: center;flex: 0 0 100rpx;
}
三、创建canvas并保存到系统相册
tips
- 商品图是正方形的,所以这里商品图的宽高都用canvas的宽
- 文字不能换行,这里只是简单的处理了一下
- 注意: wx.canvasToTempFilePath(Object object, Object this) 这个的使用,文档有一句话需要注意的:“把当前画布指定区域的内容导出生成指定大小的图片。在 draw() 回调里调用该方法才能保证图片导出成功。”
const app = getApp()
const regeneratorRuntime = app.globalData.regeneratorRuntimeconst
const util = require('../../utils/util.js')
import {getSelectQurey,getWxImageInfo,canvasToTempFilePath,saveImageToPhotosAlbum
} from &#39;../../datas/common.js&#39;Page({data: {isShowCanvas: false, // 是否显示canvas wxaCode: &#39;https://xxx..jpg&#39;, // 商品小程序码goodsImageUrl: &#39;https://xxx..jpg&#39;, // 商品图片 canvasTempFilePath: &#39;&#39;, // canvas导出生成图片的临时路径 },// 点击显示要生成的canvas getCanvas(e) {if (!this.data.wxaCode) {util.showToast(&#39;二维码生成失败&#39;);return;}this.setData({isShowCanvas: true}, () &#61;> {this.createCanvas();})},// 隐藏canvas hideCanvas() {this.setData({isShowCanvas: false})},// 创建canvas async createCanvas() {wx.showLoading({title: &#39;图片生成中...&#39;})const _this &#61; this// 创建节点选择器 const res &#61; await getSelectQurey(&#39;#qrCanvas&#39;);// canvas的宽高 const cvWidth &#61; res[0].width;const cvHeight &#61; res[0].height;const cvSubValue &#61; cvHeight - cvWidthconst qrWidth &#61; cvSubValue / 1.5const qrMargin &#61; (cvSubValue - qrWidth) / 2const qrX &#61; cvWidth - qrWidth - qrMargin / 2const qrY &#61; cvWidth &#43; qrMarginconst shopNameY &#61; cvWidth &#43; cvSubValue - qrWidth// 二维码网络图片转临时路径 let qrImagePath &#61; &#39;&#39;;try {const wxaCode &#61; _this.data.wxaCode;const qrImage &#61; await getWxImageInfo(wxaCode);qrImagePath &#61; qrImage.path} catch (e) {wx.hideLoading();this.hideCanvas();util.showToast(&#39;二维码生成失败&#39;);return;}// 商品网络图片转临时路径 let goodsImagePath &#61; &#39;/images/default_goods.png&#39;;const goodsImage &#61; _this.data.goodsImageUrl;if (goodsImage) {const goodsImageRes &#61; await getWxImageInfo(goodsImage);goodsImagePath &#61; goodsImageRes.path;}// 创建canvas var ctx &#61; wx.createCanvasContext(&#39;qrCanvas&#39;, _this);// 设置背景 ctx.setFillStyle(&#39;#fff&#39;);ctx.fillRect(0, 0, cvWidth, cvHeight);// 设置商品图片 商品图宽高是一样的 ctx.drawImage(goodsImagePath, 0, 0, cvWidth, cvWidth);// 设置二维码图片 ctx.drawImage(qrImagePath, qrX, qrY, qrWidth, qrWidth);// 设置店铺名称 const shopName &#61; &#39;我是店铺名称&#39;;ctx.setFillStyle(&#39;black&#39;)ctx.setFontSize(16)ctx.setTextAlign(&#39;left&#39;)ctx.fillText(shopName, 10, shopNameY, cvWidth - qrWidth);// 设置商品名称 文字不能换行&#xff0c;这里只是简单的处理了一下 const goodsName &#61; &#39;一个名字很长很长的商品就问你怕不怕&#39;;let goodsName1 &#61; &#39;&#39;;let goodsName2 &#61; &#39;&#39;;ctx.setFillStyle(&#39;black&#39;)ctx.setFontSize(14)ctx.setTextAlign(&#39;left&#39;)if (goodsName.length <&#61; 10) {ctx.fillText(goodsName, 10, shopNameY &#43; 30, cvWidth - qrWidth);} elseif (goodsName.length > 10 && goodsName.length <&#61; 22) {goodsName1 &#61; goodsName.substring(0, 10);goodsName2 &#61; goodsName.substring(10);ctx.fillText(goodsName1, 10, shopNameY &#43; 30, cvWidth - qrWidth);ctx.fillText(goodsName2, 10, shopNameY &#43; 50, cvWidth - qrWidth);} else {goodsName1 &#61; goodsName.substring(0, 10);goodsName2 &#61; goodsName.substring(10, 22) &#43; &#39;...&#39;;ctx.fillText(goodsName1, 10, shopNameY &#43; 30, cvWidth - qrWidth);ctx.fillText(goodsName2, 10, shopNameY &#43; 50, cvWidth - qrWidth);}// 设置提示 const tipText &#61; &#39;长按识别小程序&#xff0c;马上下单&#xff01;&#39;;ctx.setFillStyle(&#39;gray&#39;)ctx.setFontSize(8)ctx.setTextAlign(&#39;center&#39;)ctx.fillText(tipText, cvWidth / 2, cvHeight - 10);// 完成 ctx.draw(false, () &#61;> {wx.hideLoading();_this.canvasToTempFilePathFunc(cvWidth, cvHeight, &#39;qrCanvas&#39;)});},// 把当前画布指定区域的内容导出生成指定大小的图片 async canvasToTempFilePathFunc(cvWidth, cvHeight, qrCanvas) {try {let res &#61; await canvasToTempFilePath(cvWidth, cvHeight, qrCanvas);this.setData({canvasTempFilePath: res.tempFilePath});} catch (error) {console.log(error);util.showToast(error.errMsg);}},// 保存图片到本地 async saveImageToPhotosAlbumFunc() {try {let res &#61; await saveImageToPhotosAlbum(this.data.canvasTempFilePath);console.log(res);this.hideCanvas();util.showToast(&#39;图片保存成功&#39;);} catch (err) {console.log(err);}}
})
写得比较简单&#xff0c;因为主要是方便自己做记录的&#xff0c;所以也没有考虑到过多的使用场景。