技术栈:
SpingBoot+Vue+ElementUI+canvas工具
绘图工具:
HTML5 <canvas> 元素用于图形的绘制,通过脚本 (通常是JavaScript)来完成.
<canvas> 标签只是图形容器,您必须使用脚本来绘制图形。
你可以通过多种方法使用 canvas 绘制路径,盒、圆、字符以及添加图像。
canvas
这个技术目前应该算是主流的
效果生成
可以实现自动生成图片根据对应的关键字
生成的证书如图
证书预览
使用ruoyi做后台系统,进行管理,实现权限,用户,和参数持久化,方便用户持久使用,基于若依实现的证书生成系统
参数设置
批量生成压缩
增加了遮罩层,
绘画渲染核心函数
这个图其实动态的,我们将需要动态变化的数据进行,动态传进去
方法实例如下:
artistName, 艺术家的名字
artworkName,艺术作品名
category,作品分类
remark,备注
size,大小
,pieces_time,时间
col,其他字段
5个参数,数据会动态渲染到程序界面上,数据很多许多画布需要一点点调整和计算,这里放在这儿给大家参考实现的证书生成系统
async asyncreimg2(artistName, artworkName, category, remark, size ,pieces_time,col) {
this.downloadUrl = "";
const canvas = this.$refs["canvas"];
const ctx = canvas.getContext('2d');
// 清除主Canvas内容
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 加载背景图片
const backgroundImage = new Image();
backgroundImage.src = require('@/assets/images/back2.jpg');
await new Promise((resolve, reject) => {
backgroundImage.onload = () => {
resolve();
};
backgroundImage.onerror = (error) => {
reject(error);
};
});
const logo = new Image();
logo.src = require('@/assets/images/11.png');
await new Promise((resolve, reject) => {
logo.onload = () => {
resolve();
};
logo.onerror = (error) => {
reject(error);
};
});
const tile = new Image();
tile.src = require("@/assets/images/title.png");
await new Promise((resolve, reject) => {
tile.onload = () => {
resolve();
};
tile.onerror = (error) => {
reject(error);
};
});
/**########################### begin ########################*/
// 绘制背景图片
ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);
// 标题
const certificateText = "艺术品溯源鉴真数字证书"
// 设置字体样式,加粗
ctx.font = "bold 160px 黑体"; // 修改这里的字体样式
let letterSpacing = 10; // 修改这里的文字间距
// 计算文本宽度
let textWidth = ctx.measureText(certificateText).width;
// 计算文本的 x 坐标,使其在 Canvas 中央水平方向居中
let x = (canvas.width - textWidth - 110) / 2;
const y = canvas.height / 7;
for (const char of certificateText) {
// 将文本渲染到Canvas上
ctx.fillText(char, x, y);
const charWidth = ctx.measureText(char).width; // 获取当前字符的宽度
x = x + charWidth + letterSpacing; // 增加 x 坐标,加上字符宽度和间距
}
ctx.font = 'bold 90px "宋体"';
let name = " " + artistName + " ";
let title = "艺术家区块链艺术品溯源鉴真平台登记艺术家信"
let title2 = "息及艺术品上链,特此颁发《艺术品溯源鉴真数字证书》。"
//畅首才 艺术家区块链艺术品额源警真平台登记艺术家信
let textWidth1 = ctx.measureText(title).width;
let textWidth2 = ctx.measureText(name).width;
let nx = (canvas.width - textWidth1 - textWidth2) / 2 + 100;
let ny = canvas.height / 5 - 100;
ctx.fillText(name, nx, ny);
ctx.font = 'bold 80px "宋体"';
nx = (canvas.width - textWidth1) / 2 + 300;
ctx.fillText(title, nx, ny);
//聪及艺术品上酷,特此颁发《全国需源要真数字证书》
let textWidth3 = ctx.measureText(title2).width;
nx = (canvas.width - textWidth3) / 2
ny = ny + 150;
ctx.fillText(title2, nx, ny);
let nx_init = nx + 100
//字体样式
ctx.font = '80px "微软雅黑"';
ctx.letterSpacing = 0; // 修改这里的文字间距
//主题信息
// 定义要显示的多行文本
const text = "证 书 信 息"
ny = ny + 200;
let nx_t = (canvas.width - 1500) / 2;
let nx_t1 = (canvas.width - ctx.measureText(text).width) / 2
ctx.drawImage(tile, nx_t, ny - 100, 1500, 130)
ctx.fillStyle = "white"; // 设置为白色
ctx.fillText(text, nx_t1, ny-5.5);
ctx.fillStyle = "black"; // 设置为黑色
const text2 = "区块链地址:"
ctx.font = '60px "楷体"';
ny = ny + 150;
ctx.fillText(text2, nx_init, ny);
const randomNumber = Math.floor(Math.random() * 10000000); // 生成一个 0 到 99999999 的随机数
let text21 = "#0" + randomNumber
ctx.font = '80px "新宋体"';
nx = nx_init + 360
ctx.fillText(text21, nx, ny);
const text3 = "数字签名:"
ctx.font = '60px "楷体"';
ny = ny + 110;
ctx.fillText(text3, nx_init + 60, ny);
let text31 = this.generateRandomString(40)
ctx.font = '80px "新宋体"';
nx = nx_init + 360
ctx.fillText(text31, nx, ny);
const text4 = this.generateRandomString(24)
ny = ny + 100;
ctx.fillText(text4, nx, ny);
const text5 = "上链时间:"
ctx.font = '60px "楷体"';
ny = ny + 110;
ctx.fillText(text5, nx_init + 60, ny);
ctx.font = '80px "新宋体"';
// 格式化为 "yyyy-M-d HH:mm:ss" 的字符串
// 获取当前时间的 Date 对象
const currentDate = new Date();
// 提取年、月、日、小时、分钟和秒
const year = currentDate.getFullYear();
const month = currentDate.getMonth() + 1; // 月份从0开始,需要+1
const day = currentDate.getDate();
const hours = currentDate.getHours();
const minutes = currentDate.getMinutes();
const seconds = currentDate.getSeconds();
// 格式化日期
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
let text51 = pieces_time
nx = nx_init + 360
ctx.fillText(text51, nx, ny);
const text6 = "作 品 信 息"
ctx.font = '80px "微软雅黑"';
ny = ny + 200;
ctx.drawImage(tile, nx_t, ny - 100, 1500, 130)
ctx.fillStyle = "white"; // 设置为白色
ctx.fillText(text6, nx_t1, ny-5);
ctx.fillStyle = "black"; // 设置为黑色
const text7 = "作品名称:"
ny = ny + 150;
ctx.font = '60px "楷体"';
ctx.fillText(text7, nx_init + 60, ny);
let text71 = artworkName
ctx.font = '80px "新宋体"';
nx = nx_init + 360
ctx.fillText(text71, nx, ny);
const text9 = "作品分类:"
ctx.font = '60px "楷体"';
ny = ny + 110;
ctx.fillText(text9, nx_init + 60, ny);
let text91 = category
nx = nx_init + 360
ctx.font = '80px "新宋体"';
ctx.fillText(text91, nx, ny);
let text10 = "作品尺寸:"
ctx.font = '60px "楷体"';
ny = ny + 110;
ctx.fillText(text10, nx_init + 60, ny);
const text101 = size
ctx.font = '80px "新宋体"';
nx = nx_init + 360
ctx.fillText(text101, nx, ny);
let text11 = "艺 术 家 信 息"
ctx.font = '80px "微软雅黑"';
ny = ny + 200;
let nx_t2 = (canvas.width - ctx.measureText(text11).width) / 2
ctx.drawImage(tile, nx_t, ny - 100, 1500, 130)
ctx.fillStyle = "white"; // 设置为白色
ctx.fillText(text11, nx_t2, ny-5);
ctx.fillStyle = "black"; // 设置为黑色
text11 = "艺术家姓名:"
ny = ny + 150;
ctx.font = '60px "楷体"';
ctx.fillText(text11, nx_init, ny);
text11 = artistName
ctx.font = '80px "新宋体"';
ctx.fillText(text11, nx_init + 360, ny);
text11 = "艺术家公销:"
ny = ny + 110;
ctx.font = '60px "楷体"';
ctx.fillText(text11, nx_init, ny);
text11 = this.generateRandomString(40)
ctx.font = '80px "新宋体"';
ctx.fillText(text11, nx_init + 360, ny);
text11 = this.generateRandomString(27)
ny = ny + 100;
ctx.fillText(text11, nx_init + 360, ny);
text11 = "艺术家签名:"
ctx.font = '60px "楷体"';
ny = ny + 110;
ctx.fillText(text11, nx_init, ny);
text11 = this.generateRandomString(40)
ctx.font = '80px "新宋体"';
ctx.fillText(text11, nx_init + 360, ny);
text11 = this.generateRandomString(10)
ny = ny + 100;
ctx.fillText(text11, nx_init + 360, ny);
ctx.font = '100px "黑体"';
let len = ctx.measureText("全国艺术家艺术品溯源鉴真平台").width+130;
nx = (canvas.width - len) / 2
// logo
ctx.drawImage(logo, nx-30, ny + 100, 130, 130)
ctx.fillText("全国艺术家艺术品溯源鉴真平台", nx+150, ny + 200)
text11 = "上 链 方 信 息"
ctx.font = '80px "微软雅黑"';
ny = ny + 400;
nx_t2 = (canvas.width - ctx.measureText(text11).width) / 2
ctx.drawImage(tile, nx_t, ny - 100, 1500, 130)
ctx.fillStyle = "white"; // 设置为白色
ctx.fillText(text11, nx_t2, ny-5);
ctx.fillStyle = "black"; // 设置为黑色
text11 = "上链方名称:"
ny = ny + 150;
ctx.font = '60px "楷体"';
ctx.fillText(text11, nx_init, ny);
text11 = "北京松竹轩书画院"
ctx.font = '80px "新宋体"';
ctx.fillText(text11, nx_init + 360, ny);
text11 = "上链方公钥:"
ny = ny + 110;
ctx.font = '60px "楷体"';
ctx.fillText(text11, nx_init, ny);
text11 = this.generateRandomString(40)
ctx.font = '80px "新宋体"';
ctx.fillText(text11, nx_init + 360, ny);
text11 = this.generateRandomString(26)
ny = ny + 100;
ctx.fillText(text11, nx_init + 360, ny);
text11 = "上链方签名:"
ctx.font = '60px "楷体"';
ny = ny + 110;
ctx.fillText(text11, nx_init, ny);
text11 = this.generateRandomString(40)
ctx.font = '80px "新宋体"';
ctx.fillText(text11, nx_init + 360, ny);
text11 = this.generateRandomString(10)
ny = ny + 100;
ctx.fillText(text11, nx_init + 360, ny);
//注意消息
ctx.font = '50px "宋体"';
text11 = "1、本 平 台 保 证 上 述 溯 源 信 息 的 溯 源 过 程 真 实 有 效。"
ny = ny + 100;
nx = nx_init + 130;
ctx.fillText(text11, nx, ny);
text11 = "2、证书持有人或相关签方私钥丢失或被盗引起信息不实由遗失方负责。"
ny = ny + 80;
ctx.fillText(text11, nx, ny);
text11 = "3、 本 平 台 保 留 本 证 书 的 最 终 解 释 权 。"
ny = ny + 80;
ctx.fillText(text11, nx, ny);
/**###################################end ******************************************/
压缩函数
基于若依实现的证书生成压缩文件的函数:
downloadimg(row){
this.loading=true
// 创建一个新的JSZip实例
const JSZip = require("jszip");
const zip = new JSZip();
const id = row.id || this.ids
getPieces(id).then( async response => {
this.form = response.data;
let res = response.data;
await this.asyncreimg2(res.artistName, res.artworkName, res.category, "", res.size,res.piecesTime,res.col)
let i = 0
console.log(res.numbers)
while (i<res.numbers) {
try {
const response = await getPieces(id);
this.form = response.data;
let res = response.data;
// 使用 await 等待图像数据加载完成
let img = await this.asyncreimg2(res.artistName, res.artworkName, res.category, "", res.size,res.piecesTime,res.col);
// 将 res.size 中的 * 替换为 _
res.size = res.size.replace(/\*/g, "_");
// 将图像数据添加到压缩文件
zip.file(res.col+res.artworkName+i+ res.artistName + res.size+ ".png", img.split(",")[1], { base64: true });
i++
console.log(i)
} catch (error) {
console.error("获取图像数据失败", error);
}
}
// 生成压缩文件
zip.generateAsync({ type: "blob" }).then((content) => {
// 创建下载链接
const a = document.createElement("a");
a.href = URL.createObjectURL(content);
a.download = res.artworkName + res.artistName + res.size + "images.zip";
a.click();
a.remove()
console.log("download success")
this.$modal.msgSuccess("下载成功");
});
this.loading=false
});
},