效果图
html
<button catchtap="draw">打印</button<button bindtap="saveImg">保存本地</button><image src="{{imgUrl}}" style="height: 500rpx;width: 100%;" mode="aspectFit"></image><canvas style="width: 100%; height:{{rectH}}px;" type="2d" id="myCanvas"></canvas>
js
// index/index.jsPage({/*** 页面的初始数据*/data: {rectH: 500, //canvas默认高度}imgUrl: '',list: {consignee: '阿三大阿三大阿三大阿三大阿三大',dataVos: [{name: '哒哒人脸',type: '人脸设备',num: 1,unit: '台'},]},canvas: null},rpx2px(arg) {const info = wx.getSystemInfoSync()const width = info.screenWidthreturn arg * width / 750},// 获取图片对象async getImage(url) {const off = wx.createOffscreenCanvas({type: '2d'})const image = off.createImage()await new Promise((resolve, reject) => {image.onload = resolve // 绘制图片逻辑image.src = url})return image},saveImg() {wx.saveImageToPhotosAlbum({filePath: this.data.imgUrl,success: function (res) {wx.showToast({title: '保存成功',duration: 2000})},fail: function (err) {wx.showToast({title: '保存失败',icon: 'error',duration: 2000})}})},draw() {const $ = wx.createSelectorQuery()let that = thiswx.showLoading({title: '正在生成图片',})$.select('#myCanvas').fields({node: true,size: true}).exec((res) => {// Canvas 对象const canvas = res[0].node// Canvas 画布的实际绘制宽高const windowWidth = wx.getSystemInfoSync().windowWidth; // 屏幕的宽度const db = that.data.list; //定义后台数据let row = parseFloat(db.dataVos.length + 8) // 总行数let rectW = windowWidth - 30; // 表格宽度let rectH = 0; // 表格高度,用来记录当前已绘制的表格高度,初始默认高度let gridH = 20 // 单元格宽度,初始值100,即行高let gridW = parseInt(rectW / 5); // 单元格宽度,初始值100// 创建canvas渲染上下文const ctx = canvas.getContext('2d')const dpr = wx.getSystemInfoSync().pixelRatioconsole.log('---dpr', dpr)// 手动改变canvas的宽和高,先给一个canvas.width = rectW * dprcanvas.height = gridH * row * dprctx.scale(dpr, dpr)// 以上代码都是基础工作,给canvas写css样式时可以使用rpx单位。//绘制线: ctx;startX,stratY:起点坐标;endX,endY:末端坐标;strokeStyle:颜色(默认#000);lineWidth:宽度(默认rpx)function line(canvasSum, startX, stratY, endX, endY, strokeStyle = '#000', lineWidth = '2rpx') {try {canvasSum.beginPath()canvasSum.moveTo(startX, stratY);canvasSum.lineTo(endX, endY);canvasSum.strokeStyle = strokeStylecanvasSum.lineWidth = lineWidthcanvasSum.stroke()} catch (err) {console.log('绘制线失败:' + err);}}// 填充文字,自动换行,并返回文本高度// {string} str 在画布上输出的文本// {number} x 绘制文本的左上角 x 坐标位置// {number} y 绘制文本的左上角 y 坐标位置// {number} maxWidth 最大宽度// {number} fontSize 字体大小(默认16)// return {number}文本换行后的总高度function text(str, x, y, maxWidth, fontSize = 16) {try {str = String(str)x += 5 // 文字不贴边var lineWidth = 0; // 文本宽度var lastSubStrIndex = 0; //每次开始截取的字符串的索引var titleHeight = y + fontSize;rectH = (rectH < y) ? y : rectH// y += fontSize; // 文本高度for (let i = 0; i < str.length; i++) {lineWidth += ctx.measureText(str[i]).width; // 测量文本尺寸信息if (lineWidth > (maxWidth - 11)) {// 判断文本宽度是否超出最大宽度 左右不贴边各5,线宽1px 5+5+1ctx.fillText(str.substring(lastSubStrIndex, i), x, y); //绘制截取部分y += fontSize; //16为字体的高度lineWidth = 0; // 重置文本宽度lastSubStrIndex = i;titleHeight += fontSize;}if (i == str.length - 1) {//绘制剩余部分ctx.fillText(str.substring(lastSubStrIndex, i + 1), x, y);}}rectH = (rectH < y) ? y : rectH// console.log( titleHeight > y ? 'titleHeight' : 'y');} catch (error) {console.log('填充文字失败:' + error);}}// 绘制辅料、备注之前的行列function drawTable() {for (let i = 0; i <= (8 + db.dataVos.length); i++) {//填充文字 竖线if (i == 1) {text('项目名称', 0, titleH, gridW);text(db.consignee, gridW, titleH, rectW - gridW); // 这里的 rectW-gridW-10 是指 表格的宽度 - 上一个单元格宽度 - 左右预留5(5*2)空间免得贴边} else if (i == 2) {text('材料单次', 0, titleH, gridW);text(db.consignee, gridW, titleH, rectW - gridW);} else if (i === 3) {text('详细清单如下', 0, titleH, gridW);} else if (i === 4) {text('序号', 0, titleH, gridW);text('设备名', gridW, titleH, gridW);// 间隔线 -11, 应该-16,但是整体下移5了,所以-11line(ctx, gridW * 2,titleH - 11, gridW * 2, titleH + 5)text('型号', gridW * 2, titleH, gridW);// 间隔线line(ctx, gridW * 3,titleH - 11, gridW * 3, titleH + 5)text('数量', gridW * 3, titleH, gridW);// 间隔线line(ctx, gridW * 4,titleH - 11, gridW * 4, titleH + 5)text('单位', gridW * 4, titleH, gridW);} else if (i > 4 && ((i - 4) <= db.dataVos.length)) {text(parseFloat(i - 4), 0, titleH, gridW);text(db.dataVos[i - 5].name, gridW, titleH, gridW);// 间隔线line(ctx, gridW * 2,titleH - 11, gridW * 2, titleH + 5)text(db.dataVos[i - 5].type, gridW * 2, titleH, gridW);// 间隔线line(ctx, gridW * 3,titleH - 11, gridW * 3, titleH + 5)text(db.dataVos[i - 5].num, gridW * 3, titleH, gridW);// 间隔线line(ctx, gridW * 4,titleH - 11, gridW * 4, titleH + 5)text(db.dataVos[i - 5].unit, gridW * 4, titleH, gridW);} else if ((row - i) === 3) {text('辅料', 0, titleH, gridW);text(db.consignee, gridW, titleH, rectW - gridW);} else if ((row - i) === 2) {text('备注', 0, titleH, gridW);text(db.consignee, gridW, titleH, rectW - gridW);} else if ((row - i) === 1) {text('送货地址、联系人及电话', 0, titleH, gridW);text(db.consignee, gridW, titleH, rectW - gridW);} else if ((row - i) === 0) {console.log(rectH);text('项目负责人', 0, titleH, gridW);text(db.consignee, gridW, titleH, rectW - gridW);console.log(rectH);}let titleH = rectH + 16; // 文本所在高度, 16为11 + 5line(ctx, 0, rectH + 5, rectW, rectH + 5) // 横线}// 左边的边线line(ctx, 0.5, 5, 0.5, rectH + 5)// 右边的边线 -0.5是为了能够完全显示线,线差不多1pxline(ctx, rectW-0.5, 5, rectW-0.5, rectH + 5)rectH = rectH + 5line(ctx, 0, rectH, rectW, rectH) // 底部横线// 左侧名称竖线line(ctx, gridW, 5, gridW, rectH + 5)let canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);canvas.height = rectH* dprfor (var i = 0; i < canvasData.data.length; i += 4) {// 当该像素是透明的,则设置成白色if (canvasData.data[i + 3] == 0) {canvasData.data[i] = 255;canvasData.data[i + 1] = 255;canvasData.data[i + 2] = 255;canvasData.data[i + 3] = 255;}}ctx.scale(dpr, dpr)ctx.putImageData(canvasData, 0, 0)}drawTable();// 最后根据行的高度总和重新计算画布高度setTimeout(function(){that.setData({rectH:rectH * dpr, //所以当获取了数据的总行数后,需要重新计算画布的高度。canvas});})wx.canvasToTempFilePath({x: 0,y: 0,canvas, // 画布标识,传入 canvas 组件的 canvas-idfileType: 'png',quality: 1, // 图片质量 (0, 1]success: function (res) {console.log("store success: ", res);that.setData({imgUrl: res.tempFilePath})wx.hideLoading()},fail: function (res) {console.log("store fail: ", res);wx.hideLoading()},}, that)})},})
还有很多缺点没有完善,不太灵活,仅作为个人学习参考
如果觉得《微信小程序canvas绘制表格》对你有帮助,请点赞、收藏,并留下你的观点哦!