node+koa+canvas繪制出貨單、收據(jù)票據(jù)的方法
在生成票據(jù)需求中,我們會想到前端生成或者后端生成返回圖片地址訪問兩個方法,前端生成則不需要調用接口,而后端是在完成整個流程時就進行生成然后把上傳的地址保存數(shù)據(jù)庫
先看效果


下面我們就使用node +koa+canvas后端生成圖片的方法進行生成
使用庫
1、node
2、canvas npm install canvas
3、koa npm install koa
4、mime-types npm install mime-types -S
首先創(chuàng)建服務 index.js
把用到的庫都導入進去,當然如何創(chuàng)建node項目我這就不做過多的描述,創(chuàng)建成功后,直接使用 node index.js 就可以啟動服務了
const Koa = require('koa')
const app = new Koa()
const {createCanvas, Image} = require('canvas');
const router = require('koa-router')(); /*引入是實例化路由 推薦*/
//....這里需要做很多事
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000)創(chuàng)建一個api 提供外面可訪問的接口api
在末尾加了一個供外面訪問的接口,啟動服務后 訪問localhost:3000/img 就可以訪問了
const Koa = require('koa')
const app = new Koa()
const {createCanvas, Image} = require('canvas');
const router = require('koa-router')(); /*引入是實例化路由 推薦*/
//....這里需要做很多事
router.get('/img', async (ctx) => { });
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000)ok 服務已經(jīng)好了,正片開始,瓜子飲料礦泉水,前面的麻煩讓一讓,
首先我沒得知道,票據(jù)單有哪些內容
1、標題:編號,日期,地址;這些都是文字,所以我沒得繪制文字
2、表格:表頭,內容,線條;表格就是線條堆積而成的,內容就是文字,這里就得繪制線條,文字
3、尾部:文字,印章,簽名;這里需要繪制文字,圖片兩個
總的來說,我們想要繪制出一張票據(jù)單,得要繪制文字,繪制線條,通過線條與文字結合生成表格,最后添加印章與簽名照片
繪制畫布
1、給畫布設置長寬
const width = 700
const height = 460
const canvas = createCanvas(width, height)
const context = canvas.getContext('2d')2、創(chuàng)建畫布 給畫布添加背景顏色
const createMyCanvas=()=>{
context.fillStyle = '#a2bcd3'
context.fillRect(0, 0, width, height)
}畫布添加文字函數(shù)
/**
* @writeTxt: canvas 寫入字內容
* @param {str} t 內容
* @param {str} s 字體大小 粗體
* @param {arr} p 寫入文字的位置
* @param {arr} a 寫入文字對齊方式 默認 居中
* @param {obj} c 寫入文字顏色 默認 #000
*/
const writeTxt = (t, s='normal bold 12px', p, a = 'center', c = '#000') => {
if (!t) {
return;
}
context.font = `${s} 黑體`;
context.fillStyle = c;
context.textAlign = a;
context.textDecoration='underline'
context.textBaseline = 'middle';
context.fillText(t, p[0], p[1]);
}畫布繪表格線條函數(shù)
/**
* @drawLine: 畫table線
* @param list {arr} 表格豎線x軸位置
* @param tlist {arr} 表格需要填寫文字 無文字 填 ''
* @param startHei {num} 開始畫線的高度
* @param lineWidth {num} 橫線的長度
* @param n {num} 行數(shù)
* @param txtHei {num} 文字位置調整
* @param isTrue {boolean} 是否為物資列表
*/
const drawLine = (list, tlist, startHei, lineWidth, n, txtHei = 14, isTrue = false) => {
for (let i = 0; i < n; i++) {
for (let y in list) {
if (+y !== 0) {
const poi = list[y] - (list[y] - list[y - 1]) / 2;
writeTxt(tlist[i][y - 1], '12px', [poi, startHei + txtHei + 30 * i])
}
context.moveTo(list[y], startHei);
context.lineTo(list[y], startHei + 30 * (i + 1));
}
if (isTrue) {
const mtY = startHei + 30 * n;
if (i == 0) {
context.moveTo(10, startHei + 30 * i);
context.lineTo(690, startHei + 30 * i);
}
context.moveTo(10, mtY);
context.lineTo(690, mtY);
}
context.moveTo(10, startHei + 30 * i);
context.lineTo(lineWidth, startHei + 30 * i);
}
if (isTrue) {
const mtY = startHei + 30 * n;
context.moveTo(10, mtY);
context.lineTo(690, mtY);
}
context.strokeStyle = '#5a5a59';
context.stroke();
}繪制表格
/**
* @drawTable: 畫表格
*/
const drawTable = () => {
const titleArr = [10, 100, 290, 360, 430, 500, 600, 690];
const titleTxtArr = [
['貨號', '名稱及規(guī)格', '單位', '數(shù)量', '單價', '金額', '備注']
]
const goodsTxtArr = [
['', '', '', '', '', '', ''],
['', '', '', '', '', '', ''],
['', '', '', '', '', '', ''],
['', '', '', '', '', '', ''],
['', '', '', '', '', '', '']
]
const bottomArr=[10,100,690]
const bottomTxtArr=[
['合計大寫', ' 拾 萬 仟 佰 拾 元 角 分 ¥ ']
]
drawLine(titleArr, titleTxtArr, 120, 690, 1, 16)
drawLine(titleArr, goodsTxtArr, 151, 690, goodsTxtArr.length, 16, true)
drawLine(bottomArr, bottomTxtArr, 301, 690, bottomTxtArr.length, 16,true)
}繪制圖片,這里繪制圖片其實就是繪制印章
/**
* 添加圖片
* @param imgPath 圖片路徑 和圖片所在位置
* @returns {Promise<void>}
*/
const drawImg = async (imgPath = [{imgUrl: '', position: []}]) => {
let len = imgPath.length
for (let i = 0; i < len; i++) {
const image = await loadImage(imgPath[i].imgUrl)
context.drawImage(image, ...imgPath[i].position)
}
}ok,相關繪制的函數(shù)已經(jīng)好了,那么接下來就是進行排版了
1、首先的是頭部的標題,單位,位置 編號,和時間
//創(chuàng)建畫布
createMyCanvas()
//開始繪制
context.beginPath()
writeTxt('送 貨 單', 'normal bold 30px', [370, 30])
writeTxt('No', '20px', [450, 34])
writeTxt('收貨單地址:XXXXX', '14px', [12, 70], 'left')
writeTxt('地 址:XXXXXXXXXXX', '14px', [12, 100], 'left')
writeTxt('2022 年 9 月 22 日', '14px', [680, 100], 'right')2、表格部分,繪制表頭,表格,表尾
這里直接調用繪制表格的函數(shù)就可以了
3、票據(jù)尾部,簽章,簽字
writeTxt('收貨單位及經(jīng)手人(簽章):', '14px', [12, 350], 'left')
writeTxt('送貨單位及經(jīng)手人(簽章):', '14px', [400, 350],)
const imgList = [
{
// imgUrl: 'https://profile.csdnimg.cn/4/1/C/0_weixin_41277748',
imgUrl: path.join(__dirname + '/reject.png'),
position: [180, 350, 90, 80]
},
{
imgUrl: path.join(__dirname + '/pass.png'),
// imgUrl: 'https://profile.csdnimg.cn/4/1/C/0_weixin_41277748',
position: [500, 350, 90, 80]
},
]
//繪制簽章
await drawImg(imgList)
//簽名
writeTxt('井底的蝸牛', '24px', [240, 370],)
writeTxt('井底的蝸牛', '24px', [550, 370],)到這里,完整的票據(jù)就好了
完整代碼
const Koa = require('koa')
const app = new Koa()
const {createCanvas, loadImage, Image} = require('canvas');
const qr = require('qr-image');
const router = require('koa-router')(); /*引入是實例化路由 推薦*/
const path = require("path")
const fs = require("fs")
const width = 700
const height = 460
const canvas = createCanvas(width, height)
const context = canvas.getContext('2d')
/**
* @writeTxt: canvas 寫入字內容
* @param {str} t 內容
* @param {str} s 字體大小 粗體
* @param {arr} p 寫入文字的位置
* @param {arr} a 寫入文字對齊方式 默認 居中
* @param {obj} c 寫入文字顏色 默認 #000
*/
const writeTxt = (t, s = 'normal bold 12px', p, a = 'center', c = '#000') => {
if (!t) {
return;
}
context.font = `${s} 黑體`;
context.fillStyle = c;
context.textAlign = a;
context.textDecoration = 'underline'
context.textBaseline = 'middle';
context.fillText(t, p[0], p[1]);
}
/**
* @drawTable: 畫表格
*/
const drawTable = () => {
const titleArr = [10, 100, 290, 360, 430, 500, 600, 690];
const titleTxtArr = [
['貨號', '名稱及規(guī)格', '單位', '數(shù)量', '單價', '金額', '備注']
]
const goodsTxtArr = [
['', '', '', '', '', '', ''],
['', '', '', '', '', '', ''],
['', '', '', '', '', '', ''],
['', '', '', '', '', '', ''],
['', '', '', '', '', '', '']
]
const bottomArr = [10, 100, 690]
const bottomTxtArr = [
['合計大寫', ' 拾 萬 仟 佰 拾 元 角 分 ¥ ']
]
drawLine(titleArr, titleTxtArr, 120, 690, 1, 16)
drawLine(titleArr, goodsTxtArr, 151, 690, goodsTxtArr.length, 16, true)
drawLine(bottomArr, bottomTxtArr, 301, 690, bottomTxtArr.length, 16, true)
}
/**
* @drawLine: 畫table線
* @param list {arr} 表格豎線x軸位置
* @param tlist {arr} 表格需要填寫文字 無文字 填 ''
* @param startHei {num} 開始畫線的高度
* @param lineWidth {num} 橫線的長度
* @param n {num} 行數(shù)
* @param txtHei {num} 文字位置調整
* @param isTrue {boolean} 是否為物資列表
*/
const drawLine = (list, tlist, startHei, lineWidth, n, txtHei = 14, isTrue = false) => {
for (let i = 0; i < n; i++) {
for (let y in list) {
if (+y !== 0) {
const poi = list[y] - (list[y] - list[y - 1]) / 2;
writeTxt(tlist[i][y - 1], '12px', [poi, startHei + txtHei + 30 * i])
}
context.moveTo(list[y], startHei);
context.lineTo(list[y], startHei + 30 * (i + 1));
}
if (isTrue) {
const mtY = startHei + 30 * n;
if (i == 0) {
context.moveTo(10, startHei + 30 * i);
context.lineTo(690, startHei + 30 * i);
}
context.moveTo(10, mtY);
context.lineTo(690, mtY);
}
context.moveTo(10, startHei + 30 * i);
context.lineTo(lineWidth, startHei + 30 * i);
}
if (isTrue) {
const mtY = startHei + 30 * n;
context.moveTo(10, mtY);
context.lineTo(690, mtY);
}
context.strokeStyle = '#5a5a59';
context.stroke();
}
/**
* 添加圖片
* @param imgPath 圖片路徑 和圖片所在位置,圖片路徑是絕對路徑,可以使用path的方法去讀取
* @returns {Promise<void>}
*/
const drawImg = async (imgPath = [{imgUrl: '', position: []}]) => {
let len = imgPath.length
for (let i = 0; i < len; i++) {
const image = await loadImage(imgPath[i].imgUrl)
context.drawImage(image, ...imgPath[i].position)
}
}
// 創(chuàng)建畫布
const createMyCanvas = () => {
context.fillStyle = '#a2bcd3'
context.fillRect(0, 0, width, height)
}
const mime = require('mime-types');
router.get('/img', async (ctx) => {
//創(chuàng)建畫布
createMyCanvas()
//開始繪制
context.beginPath()
writeTxt('送 貨 單', 'normal bold 30px', [370, 30])
writeTxt('No', '20px', [450, 34])
writeTxt('收貨單地址:XXXXX', '14px', [12, 70], 'left')
writeTxt('地 址:XXXXXXXXXXX', '14px', [12, 100], 'left')
writeTxt('2022 年 9 月 22 日', '14px', [680, 100], 'right')
writeTxt('收貨單位及經(jīng)手人(簽章):', '14px', [12, 350], 'left')
writeTxt('送貨單位及經(jīng)手人(簽章):', '14px', [400, 350],)
const imgList = [
{
// imgUrl: 'https://profile.csdnimg.cn/4/1/C/0_weixin_41277748',
imgUrl: path.join(__dirname + '/reject.png'),
position: [180, 350, 90, 80]
},
{
imgUrl: path.join(__dirname + '/pass.png'),
// imgUrl: 'https://profile.csdnimg.cn/4/1/C/0_weixin_41277748',
position: [500, 350, 90, 80]
},
]
await drawImg(imgList)
writeTxt('井底的蝸牛', '24px', [240, 370],)
writeTxt('井底的蝸牛', '24px', [550, 370],)
drawTable()
const buffer = canvas.toBuffer("image/png")
const imgPath = new Date().getTime() + '.png'
let filPath = path.join(__dirname + '/static/', imgPath)
//把圖片寫入static文件夾
fs.writeFileSync(filPath, buffer)
let file = fs.readFileSync(filPath)
let mimeType = mime.lookup(filPath); //讀取圖片文件類型
ctx.set('content-type', mimeType); //設置返回類型
ctx.body = file; //返回圖片
context.clearRect(0, 0, width, height);
});
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000)文件中出現(xiàn)的圖片


目錄格式

啟動 node index.js
到此這篇關于node+koa+canvas繪制出貨單,收據(jù),票據(jù)的文章就介紹到這了,更多相關node+koa+canvas繪制出貨單內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
node.js中的fs.readlinkSync方法使用說明
這篇文章主要介紹了node.js中的fs.readlinkSync方法使用說明,本文介紹了fs.readlinkSync方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下2014-12-12
nodemon實現(xiàn)Typescript項目熱更新的示例代碼
這篇文章主要介紹了nodemon實現(xiàn)Typescript項目熱更新的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11

