背景
最近公司有个账户充值业务场景须要从线下领取迁徙到线上领取:
- 线下领取场景:客户通过 POS 机付款或者扫码销售同学提供的付款二维码进行付款来实现领取,之后销售同学将相干信息录入到 CRM 后盾,财务审核通过后才正式实现充值流程。
- 线上领取场景:销售同学先在 CRM 钉钉小程序中录入充值信息后生成订单,而后系统生成支付宝或者微信付款码,销售同学将付款码页面生成的图片发送给客户,客户付款后即实现充值流程。
整个充值流程优化上线后,大大缩短了客户账户从充值付款到充值到账的工夫,明显提高了给客户账户充值的效率。
需要剖析
本次迭代性能的小程序是应用原生钉钉语言开发的小程序,至于为什么是原生语言开发,那是历史起因了,不在本文探讨范畴,原生语言开发体验显著没有应用了 uni-app、taro 等小程序框架的开发体验好,刚接手时还须要一边查文档一边开发,效率比拟低。
要实现线上领取性能,要解决的关键问题有以下两个:
- 后端接口返回给小程序的是微信或支付宝的领取链接,小程序须要将它转成二维码显示到页面上
- 页面上除了付款二维码,还有公司 logo,客户信息,付款金额等须要生成图片的信息,点击页面底部的保留图片按钮后,将上述信息生成图片并保留到本地相册
综合以上两步,实现需求在技术上要解决的问题包含以下几点:
- 应用 Canvas 将链接转成二维码,显示到页面上,能够借助一个第三方库 weapp-qrcode 来实现,这个库是给微信小程序应用的,但钉钉小程序里也能够应用,须要改下源码
- 将整个页面的元素画到另一个 Canvas 上,但问题是如何将二维码 Canvas 画到另一个 Canvas 上呢,这一点开发时有遇到坑,前面会说, 本次我是采纳了个小技巧,保留图片时,先应用 toTempFilePath 将二维码 Canvas 转成长期图片,而后画到另一个 Canvas 上,再应用 toTempFilePath 将另一个 Canvas 转成长期图片,最初应用 dd.saveImage 将长期图片保留到本地相册
- 小程序 Canvas 外面内容的自适应
技术实现
页面实现
<view class="container"> // 省略一些代码 <canvas canvas-id="myQrCode" id="myQrCode" class="pay-code"></canvas> // 省略一些代码 </view></view>
最终成果如下:
页面上的二维码就是应用 weapp-qrcode 实现的,因为原生小程序不能应用 npm 装置第三方库,所以咱们须要将源码下载到我的项目目录中,官网文档也给了应用示例:
我下载后须要改下源码,就是将 weapp.qrcode.esm.js 文件中应用到的微信小程序api替换成钉钉小程序的api,全局搜寻 wx. 并替换为 dd. 。
第一步:在页面引入插件:
import drawQrcode from '/utils/weapp.qrcode.esm.js'const app = getApp()page({ data:{ }, onload() { }})
第二步:在 onload 生命周期将二维码画到 Canvas 上:
import drawQrcode from '/utils/weapp.qrcode.esm.js'const app = getApp()page({ data:{ }, onload(query) { let self = this let { qrCodeLink } = query setTimeout(() => { drawQrcode({ width: 250, height: 250, canvasId: 'myQrCode', text: qrCodeLink, }) }, 500) }})
这一步有两个要留神的中央,一个是设置了一个倒计时,是为了保障执行 drawQrcode 的时候为了保障能获取到页面上的 canvas 了,否则二维码画不进去,另一个就是 canvas 的 id,插件上的 canvasId 对应的是页面元素上的 canvas-id 属性,而钉钉小程序的 canvasId 对应的是页面元素上的 id,这一点没留神到的话会影响下一步。
第三步:将二维码转为长期图片文件
import drawQrcode from '/utils/weapp.qrcode.esm.js'const app = getApp()page({ data:{ filePath: '' }, onload() { let self = this let { qrCodeLink } = query setTimeout(() => { drawQrcode({ width: 220, height: 220, canvasId: 'myQrCode', text: qrCodeLink, }) setTimeout(() => { let ctx = dd.createCanvasContext('myQrCode') ctx.toTempFilePath({ fileType: "jpg", quality: 1, canvasId: 'myQrCode', success: function(res) { self.setData({ filePath: res.filePath }) }, fail: function(e) { console.log('fail:', e) } }) }, 500) }, 500) }})
这一步应用到了 toTempFilePath 办法,仍旧了设置了一个1秒的倒计时,为什么这样做呢? 因为上一步的 drawQrcode 是个耗时的同步工作,将 canvaas 转成图片前须要保障 canvas 曾经在页面上生成了。须要留神的是 dd.createCanvasContext('myQrCode') 和 toTempFilePath 办法里的 canvasId 对应的是页面元素上的 id 属性。
第四步:使 Canvas 上的内容自适应
在onload生命周期里曾经获取到屏幕尺寸:
dd.getSystemInfo({ success(res){ self.setData({ canWidth: res.windowWidth / 750, // 750宽的设计稿 canHeight: res.windowWidth / 750 * 1239 // 750px 宽设计稿导出图片的高度像素 }) }})
设置最终要转成图片的 canvas 宽高:
<canvas style="width:{{canWidth*750}}px;height:{{canHeight}}px;position:absolute;left:-1000px;top:-1000px;" canvas-id="myCanvas" id="myCanvas" class="myCanvas"></canvas>
同时我还设置了相对定位,目标是让这个画布脱离文档流并且显示在屏幕之外。
将元素绘制到 canvas 上:
let rpx = res.windowWidth / 750const ctx = dd.createCanvasContext('myCanvas')ctx.setFillStyle('#fff'); // 默认红色ctx.drawImage('/static/icon/logo.png',rpx * 307, rpx * 32, rpx * 135.2, rpx * 64)ctx.fillRect(0, 0, rpx * 750, res.windowWidth / 750 * 1239) // fillRect(x,y,宽度,高度)ctx.setFontSize(rpx * 56)ctx.setFillStyle('#191F25')ctx.setTextAlign('center')ctx.fillText(self.data.shopName, rpx * 750 / 2, rpx * 176)ctx.setFontSize(rpx * 24)ctx.setFillStyle('#333333')ctx.fillText('档口ID:'+ self.data.shopId, rpx * 750 / 2, rpx * 246)ctx.setFontSize(rpx * 28)ctx.setFillStyle('#333333')ctx.fillText('领取金额', rpx * 750 / 2, rpx * 338)ctx.setFontSize(rpx * 48)ctx.setFillStyle('#333333')ctx.fillText('¥' + self.data.totalAmount, rpx * 750 / 2, rpx * 396)ctx.drawImage(self.data.bankType == 2 ? '/static/icon/wechat.png' : '/static/icon/alipay.png',rpx * 153, rpx * 478, rpx * 64, rpx * 64)ctx.setFontSize(rpx * 28)ctx.setFillStyle('#333333')ctx.setTextAlign('left')ctx.fillText(self.data.bankType == 2 ? '微信' : '支付宝', rpx * 236, rpx * 520)ctx.setFontSize(rpx * 28)ctx.setFillStyle('#3296FA')ctx.setTextAlign('left')ctx.fillText('请应用' + (self.data.bankType == 2 ? '微信' : '支付宝') + '扫一扫', rpx * 355, rpx * 500)ctx.setFontSize(rpx * 28)ctx.setFillStyle('#3296FA')ctx.fillText('扫描二维码领取', rpx * 355, rpx * 544)ctx.drawImage(self.data.filePath, rpx * 149, rpx * 570, rpx * 452, rpx * 458)ctx.setFontSize(rpx * 24)ctx.setFillStyle('#919497')ctx.setTextAlign('center')ctx.fillText('充值单号', rpx * 750 / 2, rpx * 1095)ctx.setFontSize(rpx * 24)ctx.setFillStyle('#919497')ctx.setTextAlign('center')ctx.fillText(self.data.applyId, rpx * 750 / 2, rpx * 1134)ctx.draw(true)
下面代码中的数值都是间接在设计稿上量进去的乘以 rpx 后就能自适应显示了。
最初一步:将 canvas 转成图片并保留到相册,这些操作在 draw 办法的回调办法里执行:
dd.showLoading() // 点击保留图片按钮后展现 loadinglet rpx = res.windowWidth / 750const ctx = dd.createCanvasContext('myCanvas')// 省略一些代码ctx.draw(true, (()=>{ setTimeout(()=>{ ctx.toTempFilePath({ fileType: "jpg", quality: 1, canvasId: 'myCanvas', success: function(res) { dd.saveImage({ url: res.filePath, showActionSheet: true, success: () => { dd.hideLoading() dd.alert({ title: '保留胜利', }); }, fail: function() { dd.hideLoading() dd.alert({ title: '保留失败', }); } }); }, fail: function() { dd.hideLoading() dd.alert({ title: '保留失败', }); } }) }, 1000)})())
到这里就根本实现需求了,当然还有能够优化的中央。导出的图片效果图如下:
总结
需要是实现了,但还是有几个点是值得再思考一下的:
- canvas 转成图片后不清晰的问题
- 保留图片到相册时,如果用户已禁止钉钉拜访相册的话,如果给予用户敌对的提醒
程度无限,文中不免有不足之处,欢送大家与我交换。