原生微信小程序,canvas生成凭证,保存到手机
原生微信小程序通过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;// 减去imgif (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;
}