node+koa+canvas绘制出货单、收据票据的方法

目录
  • 先看效果
  • 使用库
  • 首先创建服务 index.js
  • 创建一个api 提供外面可访问的接口api
  • 绘制画布

在生成票据需求中,我们会想到前端生成或者后端生成返回图片地址访问两个方法,前端生成则不需要调用接口,而后端是在完成整个流程时就进行生成然后把上传的地址保存数据库

先看效果

下面我们就使用node +koa+canvas后端生成图片的方法进行生成

使用库

1、node
2、canvas npm install canvas
3、koa npm install koa
4、mime-types npm install mime-types -S

首先创建服务 index.js

把用到的库都导入进去,当然如何创建node项目我这就不做过多的描述,创建成功后,直接使用 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)

创建一个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 服务已经好了,正片开始,瓜子饮料矿泉水,前面的麻烦让一让,

首先我没得知道,票据单有哪些内容

1、标题:编号,日期,地址;这些都是文字,所以我没得绘制文字
2、表格:表头,内容,线条;表格就是线条堆积而成的,内容就是文字,这里就得绘制线条,文字
3、尾部:文字,印章,签名;这里需要绘制文字,图片两个
总的来说,我们想要绘制出一张票据单,得要绘制文字,绘制线条,通过线条与文字结合生成表格,最后添加印章与签名照片

绘制画布

1、给画布设置长宽

  const width = 700
  const height = 460
  const canvas = createCanvas(width, height)
  const context = canvas.getContext('2d')

2、创建画布 给画布添加背景颜色

  const createMyCanvas=()=>{
      context.fillStyle = '#a2bcd3'
      context.fillRect(0, 0, width, height)
  }

画布添加文字函数

  /**
    * @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]);
  }

画布绘表格线条函数

/**
    * @drawLine: 画table线
    * @param list {arr} 表格竖线x轴位置
    * @param tlist {arr} 表格需要填写文字 无文字 填 ''
    * @param startHei {num} 开始画线的高度
    * @param lineWidth {num} 横线的长度
    * @param n {num} 行数
    * @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 = [
        ['货号', '名称及规格', '单位', '数量', '单价', '金额', '备注']
      ]
      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,相关绘制的函数已经好了,那么接下来就是进行排版了

1、首先的是头部的标题,单位,位置 编号,和时间

//创建画布
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、表格部分,绘制表头,表格,表尾

这里直接调用绘制表格的函数就可以了

3、票据尾部,签章,签字

writeTxt('收货单位及经手人(签章):', '14px', [12, 350], 'left')
writeTxt('送货单位及经手人(签章):', '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],)

到这里,完整的票据就好了

完整代码

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 = [
        ['货号', '名称及规格', '单位', '数量', '单价', '金额', '备注']
    ]
    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} 行数
 * @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)
    }

}

// 创建画布
const createMyCanvas = () => {
    context.fillStyle = '#a2bcd3'
    context.fillRect(0, 0, width, height)
}
const mime = require('mime-types');

router.get('/img', async (ctx) => {
    //创建画布
    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('收货单位及经手人(签章):', '14px', [12, 350], 'left')
    writeTxt('送货单位及经手人(签章):', '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)

文件中出现的图片

目录格式

启动 node index.js

到此这篇关于node+koa+canvas绘制出货单,收据,票据的文章就介绍到这了,更多相关node+koa+canvas绘制出货单内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • NPM相关命令之报错node-gyp...的解决方法

    目录 NPM 相关命令,报错 node-gyp… 的解决方法 相关的异常信息 通用的解决方案 1. 在线安装,适合拥有纯净 Windows 开发环境的用户(没有安装过 Python, Visual Studio 等…) 2. 手动安装,针对拥有非纯净 Windows 开发环境的老鸟(安装过 Python, Visual Studio 等…) 查看 NPM 全局配置 总结 NPM 相关命令,报错 node-gyp… 的解决方法 'node-gyp-build' is not recognized

  • 使用node-canvas在服务端渲染echarts图表解析

    目录 踩了很长时间的坑,总算是能跑起来了 友情提示:入坑请慎重 在这个过程中,还有可能报错,比较常见的有这些 我只说说我试过有用的办法 踩了很长时间的坑,总算是能跑起来了 但是如果要我给echarts的SSR一个评价,那就是不好用……可能是我太菜了.而且,因为我是Windows用户,这个过程对Windows极其不友好. 友情提示:入坑请慎重 在服务端渲染图表,绕不开的一个问题就是,没有DOM怎么绘图?这个主要有两种解决方案,一个是用那些headless的浏览器去渲染,然后进行截图:另一个就是在N

  • Makefile/cmake/node-gyp中区分判断不同平台的方法

    最近用QTK开发一个下载(下载到开发板)工具,同时用到了Makefile/cmake和node-gyp,而且都要针对不同平台做不同的处理.这里做个记录,以备以后有需要时查阅. Makefile 在Makefile中,可以用OS变量判断当前系统是否是Windows,然后用uname来判断当前系统是MacOS还是其它系统. ifeq ($(OS),Windows_NT) PLATFORM="Windows" else ifeq ($(shell uname),Darwin) PLATFOR

  • node gyp安装canvas原生模块编译node pregyp详解

    目录 关于node-gyp node-pre-gyp canvas安装过程追踪 1. 安装canvas 2. canvas的package的命令脚本 3. node-pre-gyp install命令 4. 下载预构建的二进制文件 让node-pre-gyp通过淘宝源下载预构建文件 关于node-gyp node-gyp是一个用 Node.js 编写的跨平台命令行工具,用于为 Node.js 编译本机插件模块.它包含之前由 Chromium 团队使用的 gyp-next项目的供应副本,扩展以支持

  • node+koa+canvas绘制出货单、收据票据的方法

    目录 先看效果 使用库 首先创建服务 index.js 创建一个api 提供外面可访问的接口api 绘制画布 在生成票据需求中,我们会想到前端生成或者后端生成返回图片地址访问两个方法,前端生成则不需要调用接口,而后端是在完成整个流程时就进行生成然后把上传的地址保存数据库 先看效果 下面我们就使用node +koa+canvas后端生成图片的方法进行生成 使用库 1.node2.canvas npm install canvas3.koa npm install koa4.mime-types n

  • js+html5实现canvas绘制镂空字体文本的方法

    本文实例讲述了js+html5实现canvas绘制镂空字体文本的方法.分享给大家供大家参考.具体实现方法如下: <!DOCTYPE html> <html> <body> <canvas id="myCanvas" width="200" height="100" style="border:1px solid #d3d3d3;"> Your browser does not s

  • canvas绘制爱心的几种方法总结(推荐)

    第一种方法 代码实现的一种方法 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>使用桃心形方程绘制爱心</title> </head> <body> <canvas></canvas> <script> var canvas = document

  • canvas绘制的直线动画

    话不多说,请看代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>first line</title> <style type="text/css"> body{ background: #456E89; } .canvas { height: 300px; width: 300px; margin: 0 a

  • canvas绘制刮刮卡效果

    本文实例为大家分享了canvas绘制刮刮卡效果的具体代码,供大家参考,具体内容如下 先上图 代码 <!DOCTYPE html> <html> <head> <meta name="keywords" content="风舞红枫,前端技术,canvas"/> <meta name="description" content="风舞红枫,前端技术,canvas,vue,react,no

  • JS+Canvas绘制抽奖转盘

    本文实例为大家分享了JS+Canvas绘制抽奖转盘的具体代码,供大家参考,具体内容如下 给大家分享一个Canvas绘制的转盘抽奖,点击开关开始转动时,转盘开始转动,转盘停止时指针指向的区域即为抽中的奖品,并显示在转盘中间,效果图如下: 动画实的代码如下: <!DOCTYPE html> <html> <head> <style> canvas { background: #eee; } </style> <title>Canvas绘制

  • JavaScript实现使用Canvas绘制图形的基本教程

    由于这两年HTML5火的正热,所以研究了一下,最近有个想法也是要用到HTML的相关功能,所以也要好好学习一把. 好好看了一下Canvas的功能,感觉HTML5在客户端交互的功能性越来越强了,今天看了一下Canvas绘图,下边是几个实例,记下以备后用. 1.使用Canvas绘制直线: <!doctype html> <html> <head> <meta charset="UTF-8"> </head> <style ty

  • js+html5实现canvas绘制网页时钟的方法

    本文实例讲述了js+html5实现canvas绘制网页时钟的方法,画的是一个可用于网页的.带摆的钟表,可以通过按钮调整其大小和位置,具体实现内容如下 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Clock</title> <script type="tex

  • node+koa实现数据mock接口的方法

    基于node+koa实现的mock数据接口,Koa需要v7.6.0以上node版本,低于此版本请先升级node 目录结构 // server.js const Koa = require('koa'); const Router = require('koa-router'); const qs = require('qs'); const assert = require('assert'); const app = new Koa(); const router = new Router()

  • js+html5实现canvas绘制简单矩形的方法

    本文实例讲述了js+html5实现canvas绘制简单矩形的方法.分享给大家供大家参考.具体实现方法如下: <!DOCTYPE html> <html> <body> <canvas id="myCanvas" width="200" height="100" style="border:1px solid #c3c3c3;"> Your browser does not sup

随机推荐