釘釘小程序通過 Canvas 將頁面生成圖片並保存到本地相冊


背景

最近公司有個賬戶充值業務場景需要從線下支付遷移到線上支付:

  • 線下支付場景:客戶通過 POS 機付款或者掃碼銷售同學提供的付款二維碼進行付款來完成支付,之后銷售同學將相關信息錄入到 CRM 后台,財務審核通過后才正式完成充值流程。
  • 線上支付場景:銷售同學先在 CRM 釘釘小程序中錄入充值信息后生成訂單,然后系統生成支付寶或者微信付款碼,銷售同學將付款碼頁面生成的圖片發送給客戶,客戶付款后即完成充值流程。

整個充值流程優化上線后,大大縮短了客戶賬戶從充值付款到充值到賬的時間,明顯提高了給客戶賬戶充值的效率。

需求分析

本次迭代功能的小程序是使用原生釘釘語言開發的小程序,至於為什么是原生語言開發,那是歷史原因了,不在本文討論范圍,原生語言開發體驗明顯沒有使用了 uni-app、taro 等小程序框架的開發體驗好,剛接手時還需要一邊查文檔一邊開發,效率比較低。

要實現線上支付功能,要解決的關鍵問題有以下兩個:

  • 后端接口返回給小程序的是微信或支付寶的支付鏈接,小程序需要將它轉成二維碼顯示到頁面上
  • 頁面上除了付款二維碼,還有公司 logo,客戶信息,付款金額等需要生成圖片的信息,點擊頁面底部的保存圖片按鈕后,將上述信息生成圖片並保存到本地相冊

綜合以上兩步,實現需求在技術上要解決的問題包括以下幾點:

  1. 使用 Canvas 將鏈接轉成二維碼,顯示到頁面上,可以借助一個第三方庫 weapp-qrcode 來實現,這個庫是給微信小程序使用的,但釘釘小程序里也可以使用,需要改下源碼
  2. 將整個頁面的元素畫到另一個 Canvas 上,但問題是如何將二維碼 Canvas 畫到另一個 Canvas 上呢,這一點開發時有遇到坑,后面會說, 本次我是采用了個小技巧,保存圖片時,先使用 toTempFilePath 將二維碼 Canvas 轉成臨時圖片,然后畫到另一個 Canvas 上,再使用 toTempFilePath 將另一個 Canvas 轉成臨時圖片,最后使用 dd.saveImage 將臨時圖片保存到本地相冊
  3. 小程序 Canvas 里面內容的自適應

技術實現

頁面實現
	<view class="container">
            // 省略一些代碼
            <canvas canvas-id="myQrCode" id="myQrCode" class="pay-code"></canvas>
            // 省略一些代碼
	</view>
</view>

最終效果如下:

image.png

頁面上的二維碼就是使用 weapp-qrcode 實現的,由於原生小程序不能使用 npm 安裝第三方庫,所以我們需要將源碼下載到項目目錄中,官方文檔也給了使用示例:

image.png

我下載后需要改下源碼,就是將 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 / 750
const 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() // 點擊保存圖片按鈕后展示 loading
let rpx = res.windowWidth / 750
const 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)
})())

到這里就基本實現需求了,當然還有可以優化的地方。導出的圖片效果圖如下:

微信截圖_20210627113209.png

總結

需求是實現了,但還是有幾個點是值得再思考一下的:

  • canvas 轉成圖片后不清晰的問題
  • 保存圖片到相冊時,如果用戶已禁止釘釘訪問相冊的話,如何給予用戶友好的提示

水平有限,文中難免有不足之處,歡迎大家關注我的微信公眾號。(前端民工)

image.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM