效果图
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.js
Page({
/**
* 页面的初始数据
*/
data: {
rectH: 500, //canvas默认高度}
imgUrl: '',
list: {
consignee: '阿三大阿三大阿三大阿三大阿三大',
dataVos: [{
name: '哒哒人脸',
type: '人脸设备',
num: 1,
unit: '台'
},
]
},
canvas: null
},
rpx2px(arg) {
const info = wx.getSystemInfoSync()
const width = info.screenWidth
return 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 = this
wx.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().pixelRatio
console.log('---dpr', dpr)
// 手动改变canvas的宽和高,先给一个
canvas.width = rectW * dpr
canvas.height = gridH * row * dpr
ctx.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 = strokeStyle
canvasSum.lineWidth = lineWidth
canvasSum.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+1
ctx.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了,所以-11
line(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 + 5
line(ctx, 0, rectH + 5, rectW, rectH + 5) // 横线
}
// 左边的边线
line(ctx, 0.5, 5, 0.5, rectH + 5)
// 右边的边线 -0.5是为了能够完全显示线,线差不多1px
line(ctx, rectW-0.5, 5, rectW-0.5, rectH + 5)
rectH = rectH + 5
line(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* dpr
for (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-id
fileType: '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)
})
},
})