最近遇到了一个场景,在uniapp 微信小程序中,需要兼容支付宝支付。但是微信小程序里面不支持使用支付宝,所以就只能让后端对接支付宝的 api 生成一个收款链接,然后在前端绘制出一个收款海报,在海报里面生成一个二维码来展示这个收款链接,然后在海报里写上订单相关的信息,让需要使用支付宝支付的用户手动保存海报到手机,再支付宝扫码支付。生成的海报类似这样:
刚开始直接在uniapp中用 canvas 绘制这个海报,因为要先在页面上预先显示一个二维码,所以引入了 tki-qrcode 这个库来生成二维码,还可以很方便的拿到生成的二维码的临时路径。
<view class="close-space-t20 flex-column-justify">
<tki-qrcode
ref="qrcode"
:val="qrCode.val"
:size="qrCode.size"
:background="qrCode.background"
:foreground="qrCode.foreground"
:pdground="qrCode.pdground"
:icon="qrCode.icon"
:iconSize="qrCode.iconsize"
:lv="qrCode.lv"
:onval="qrCode.onval"
:loadMake="qrCode.loadMake"
:showLoading="qrCode.showLoading"
:loadingText="qrCode.loadingText"
@result="qrR"
/>
<button class="solidBlueBtn close-space-t20" @click="saveImageToPhotosAlbum1">
保存二维码
</button>
</view>
然后在 js 里面绘制海报,嵌入二维码,再保存图片到手机。一切都很顺利,可是最后在安卓手机上测试的时候,发现 uniapp 微信小程序中绘制的海报在安卓手机上不兼容,文字样式会出现各种混乱异常情况,比如颜色,大小随机变化,甚至有时候渲染不出来文字,在微信小程序社区和一些官方论坛多方寻找,发现这个安卓的兼容性问题很多人遇到,但是一直没有被修复,所以就更换了思路,在 node 服务里面去绘制海报,然后再把绘制好的海报文件返回给小程序端保存,这样就可以避开设备兼容性问题。 首先,在node里面引入 canvas ,fs ,path 这些模块,绘图的 api 与小程序里面的 canvas 几乎一样,下面贴一下代码:
async createCanvasImg(data) {
const currentUuid = uuidv4();
const { payQrCode, orderInfo } = data;
console.log(data, '请求体', currentUuid, orderInfo);
// 把请求体里面带的base64图片(中间的支付二维码)存在本地
const bufferData = Buffer.from(payQrCode.split(',')[1], 'base64');
const qrImgFilePath = path.resolve(`app/public/qr-code${currentUuid}.png`);
// 将 Buffer 对象写入文件系统
fs.writeFileSync(qrImgFilePath, bufferData, err => {
if (err) throw err;
});
// 缩放倍数,提升canvas图像清晰度
const scaleMultiple = 2;
// rpx 用于适配各种屏幕的尺寸
const rpx = 0.5706666666666667;
// 创建整个画报
const canvas = createCanvas(750 * rpx, 1300 * rpx);
canvas.width *= scaleMultiple;
canvas.height *= scaleMultiple;
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = true;
// 上面订单信息和收货地址区域
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, 750 * rpx * scaleMultiple, 448 * rpx * scaleMultiple);
// 添加背景水印
ctx.drawImage(
await loadImage(path.resolve('app/public/watermark.png')),
454 * rpx * scaleMultiple,
48 * rpx * scaleMultiple,
400 * rpx * scaleMultiple,
400 * rpx * scaleMultiple
);
ctx.fillStyle = 'rgba(0,0,0,0.9)';
ctx.font = `600 ${16 * scaleMultiple}px SIMHEI`;
ctx.fillText('订单信息', 48 * rpx * scaleMultiple, 80 * rpx * scaleMultiple);
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.font = `400 ${14 * scaleMultiple}px SIMHEI`;
ctx.fillText('订单编号:2023023048576748', 48 * rpx * scaleMultiple, 132 * rpx * scaleMultiple);
ctx.fillText(
'下单时间:2023-04-24 20:12:12',
48 * rpx * scaleMultiple,
180 * rpx * scaleMultiple
);
ctx.fillText(
`下单商品:${this.canvasTextEllipse(
ctx,
'浙江省 杭州市 上城区 文一西路356号商会大厦B座5单元6楼 姚女士 15612345678',
170 * rpx * scaleMultiple, // x轴坐标位置(视实际情况而定)
228 * rpx * scaleMultiple, // y轴坐标位置(视实际情况而定)
360 * rpx * scaleMultiple // 文字显示最大宽度(视实际情况而定)
)}`,
48 * rpx * scaleMultiple,
228 * rpx * scaleMultiple
);
ctx.fillText('等100种商品', 570 * rpx * scaleMultiple, 228 * rpx * scaleMultiple);
ctx.fillStyle = 'rgba(0,0,0,0.9)';
ctx.font = `600 ${16 * scaleMultiple}px SIMHEI`;
ctx.fillText('收货地址', 48 * rpx * scaleMultiple, 296 * rpx * scaleMultiple);
ctx.font = `400 ${14 * scaleMultiple}px SIMHEI`;
ctx.fillStyle = 'rgba(0,0,0,0.6)';
this.canvasTextAutoLine(
ctx,
'浙江省 杭州市 上城区 文一西路356号商会大厦B座5单元6楼 姚女士 15612345678',
48 * rpx * scaleMultiple, // x轴坐标位置(视实际情况而定)
348 * rpx * scaleMultiple, // y轴坐标位置(视实际情况而定)
654 * rpx * scaleMultiple, // 文字显示最大宽度(视实际情况而定)
40 * rpx * scaleMultiple // 文字行高(视实际情况而定)
);
ctx.fillStyle = '#1678FF';
ctx.fillRect(
0,
448 * rpx * scaleMultiple,
750 * rpx * scaleMultiple,
820 * rpx * scaleMultiple
);
ctx.closePath();
// 开启新的绘制
ctx.fillStyle = '#fff';
ctx.font = `bold ${20 * scaleMultiple}px SIMHEI`;
ctx.fillText('打开支付宝【扫一扫】', 214 * rpx * scaleMultiple, 528 * rpx * scaleMultiple);
ctx.fillRect(
128 * rpx * scaleMultiple,
588 * rpx * scaleMultiple,
500 * rpx * scaleMultiple,
610 * rpx * scaleMultiple
); // 二维码盒子
ctx.drawImage(
await loadImage(path.resolve(`app/public/qr-code${currentUuid}.png`)),
184 * rpx * scaleMultiple,
640 * rpx * scaleMultiple,
400 * rpx * scaleMultiple,
400 * rpx * scaleMultiple
);
ctx.font = `500 ${18 * scaleMultiple}px SIMHEI`;
ctx.fillStyle = '#000';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('XXXX有限公司', 380 * rpx * scaleMultiple, 1086 * rpx * scaleMultiple);
ctx.font = `bold ${20 * scaleMultiple}px SIMHEI`;
ctx.fillStyle = '#EB2533';
ctx.fillText(
`¥${this.slicePrice('178.9')[0]}${this.slicePrice('178.9')[1]}`,
380 * rpx * scaleMultiple,
1150 * rpx * scaleMultiple
);
ctx.closePath();
ctx.scale(2, 2);
const buffer = canvas.toBuffer('image/png');
// 写入本地预览生成图片
// fs.writeFileSync('output.png', buffer);
// 最后,删除本次生成的所有图片
const allFiles = [`app/public/qr-code${currentUuid}.png`];
this.deleteFiles(allFiles);
return buffer;
}