原生微信小程序通过canvas可以将表单( 文本、图片 )转化成图片,同时添加水印,生成凭证,这里只是基本功能实现。可以继续完善和扩展。
<view class="container">
<!-- Canvas 组件 -->
<canvas type="2d" id="myCanvas" style="width: {{canvasWidth}}px;height: {{canvasHeight}}px;"></canvas>
<!-- 保存按钮 -->
<button bindtap="saveImage">保存图片到手机</button>
</view>
Page({
data: {
canvasWidth: 300,
canvasHeight: 0,
fields: {
schemeType: "方案类型:普通保险",
mainInsurance: "主险金额:100,000元",
medicalInsurance: "附加医疗险:20,000元",
employerInsurance: "附加补充雇主险:50,000元",
thirdPartyInsurance: "附加第三者财产险:30,000元",
imgs: [
'https://cdn.bellmesse.com/upload/images/article/20241031/17303356875926158.jpg', // 示例图片1
'https://cdn.bellmesse.com/upload/images/article/20241031/17303356875926158.jpg', // 示例图片2
'https://cdn.bellmesse.com/upload/images/article/20241031/17303356875926158.jpg', // 示例图片3
],
},
watermarkText: "2025-4-24 xx保险公司 xx",
},
onReady() {
this.init();
},
async init() {
let {
canvas,
ctx
} = await this.initCanvas();
let paddingTop = 30; // 顶部预留空间
let titleHeight = 30; // 标题高度
let oneFieldHeight = 30; // 单字段高度
// 总高度
let totalHeight = paddingTop + titleHeight + Object.keys(this.data.fields).length * oneFieldHeight;
// 减去img
if (this.data.fields.imgs) {
totalHeight -= oneFieldHeight;
}
// 计算图片的缩放比例和高度
let images = await this.initImg(canvas, this.data.fields.imgs);
if (images.length) {
images = images.map(image => {
const originalWidth = image.width;
const originalHeight = image.height;
const newHeight = (this.data.canvasWidth / originalWidth) * originalHeight;
let result = {
imageSource: image,
x: 0,
y: totalHeight,
width: this.data.canvasWidth,
height: newHeight
}
totalHeight = totalHeight + newHeight + 16;
return result;
})
}
// 设置 Canvas 尺寸
this.setData({
canvasHeight: totalHeight
})
const dpr = wx.getSystemInfoSync().pixelRatio;
canvas.width = this.data.canvasWidth * dpr;
canvas.height = totalHeight * dpr;
ctx.scale(dpr, dpr);
// 设置背景颜色
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制标题
ctx.font = '16px sans-serif';
ctx.fillStyle = '#333333';
ctx.fillText("保险凭证", 10, titleHeight);
// 绘制字段
let keyIndex = 0;
for (const key in this.data.fields) {
if (key !== 'imgs') {
ctx.font = '14px sans-serif';
ctx.fillStyle = '#666666';
ctx.fillText(this.data.fields[key], 10, (keyIndex * oneFieldHeight + titleHeight + paddingTop));
keyIndex += 1;
}
}
// 渲染图片
images.forEach(image => {
ctx.drawImage(image.imageSource, image.x, image.y, image.width, image.height);
})
// 渲染水印
this.addEnhancedWatermark(ctx, canvas.width, canvas.height, this.data.watermarkText);
},
// 获取canvas对象
initCanvas() {
return new Promise((resolve, reject) => {
const query = wx.createSelectorQuery();
query
.select('#myCanvas')
.fields({
node: true,
size: true
})
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
if (res[0].node) {
resolve({
canvas,
ctx
})
} else {
reject()
}
})
})
},
// 加载图片
initImg(canvas, imgs) {
if (!canvas) {
wx.showToast({
title: '未找到canvas',
icon: 'none'
})
return;
}
const imgPromises = imgs.map((imgUrl) =>
new Promise((resolve, reject) => {
const image = canvas.createImage();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = imgUrl; // 设置图片路径
})
);
return Promise.all(imgPromises);
},
addEnhancedWatermark(ctx, canvasWidth, canvasHeight, watermarkText) {
ctx.save();
// 水印样式设置
ctx.font = '16px sans-serif';
ctx.fillStyle = 'rgba(200, 200, 200, 0.5)'; // 更浅的透明度
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.rotate(-20 * Math.PI / 180); // 保持倾斜
// 水印间距参数
const watermarkWidth = 280;
const watermarkHeight = 60;
const horizontalOffset = watermarkWidth * 0.5; // 水平交错偏移量(半格)
// 计算需要绘制的水印行数和列数(扩大范围确保覆盖整个Canvas)
const cols = Math.ceil(canvasWidth / watermarkWidth) + 2;
const rows = Math.ceil(canvasHeight / watermarkHeight) + 2;
// 绘制交错水印
for (let i = -1; i < rows; i++) {
for (let j = -1; j < cols; j++) {
// 关键修改:奇数行水平偏移半格
const xOffset = (i % 2 === 0) ? 0 : horizontalOffset;
const x = j * watermarkWidth + xOffset;
const y = i * watermarkHeight;
ctx.fillText(watermarkText, x, y);
// 可选:添加随机微调(更自然的效果)
// const randomOffsetX = (Math.random() - 0.5) * 10;
// const randomOffsetY = (Math.random() - 0.5) * 10;
// ctx.fillText(watermarkText, x + randomOffsetX, y + randomOffsetY);
}
}
ctx.restore();
},
// 保存图片到手机
saveImage() {
const query = wx.createSelectorQuery();
query
.select('#myCanvas')
.fields({
node: true,
size: true
})
.exec((res) => {
const canvas = res[0].node;
// 导出 Canvas 为临时文件
wx.canvasToTempFilePath({
canvas,
success: (res) => {
const tempFilePath = res.tempFilePath;
// 保存图片到相册
wx.saveImageToPhotosAlbum({
filePath: tempFilePath,
success: () => {
wx.showToast({
title: '保存成功',
icon: 'success',
});
},
fail: () => {
wx.showToast({
title: '保存失败',
icon: 'none',
});
},
});
},
fail: () => {
wx.showToast({
title: '生成图片失败',
icon: 'none',
});
},
});
});
},
});
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
}
button {
margin-top: 20px;
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
}