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

先看效果

下面我们就使用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绘制出货单内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

node+koa+canvas绘制出货单、收据票据的方法的更多相关文章

  1. 微信小程序canvas实现水平、垂直居中效果

    这篇文章主要介绍了小程序中canvas实现水平、垂直居中效果,本文图文实例代码相结合给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

  2. H5最强接口之canvas实现动态图形功能

    这篇文章主要介绍了H5最强接口之canvas实现动态图形功能,需要的朋友可以参考下

  3. Canvas高级路径操作之拖拽对象的实现

    这篇文章主要介绍了Canvas高级路径操作之拖拽对象的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  4. canvas像素点操作之视频绿幕抠图

    这篇文章主要介绍了canvas像素点操作之视频绿幕抠图的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. html5利用canvas实现颜色容差抠图功能

    这篇文章主要介绍了html5利用canvas实现颜色容差抠图功能,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  6. canvas绘制视频封面的方法

    这篇文章主要介绍了canvas绘制视频封面的方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. HTML实现代码雨源码及效果示例

    这篇文章主要介绍了HTML实现代码雨源码及效果示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  8. 详解使用双缓存解决Canvas clearRect引起的闪屏问题

    这篇文章主要介绍了详解使用双缓存解决Canvas clearRect引起的闪屏问题的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. canvas实现按住鼠标移动绘制出轨迹的示例代码

    本篇文章主要介绍了canvas实现按住鼠标移动绘制出轨迹的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. 用canvas做一个DVD待机动画的实现代码

    这篇文章主要介绍了用canvas做一个DVD待机动画的实现代码的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部