亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

node+koa+canvas繪制出貨單、收據(jù)票據(jù)的方法

 更新時間:2022年09月27日 14:31:54   作者:井底的蝸牛  
在生成票據(jù)需求中,我們會想到前端生成或者后端生成返回圖片地址訪問兩個方法,前端生成則不需要調(diào)用接口,而后端是在完成整個流程時就進行生成然后把上傳的地址保存數(shù)據(jù)庫,這篇文章主要介紹了node+koa+canvas繪制出貨單,收據(jù),票據(jù),需要的朋友可以參考下

在生成票據(jù)需求中,我們會想到前端生成或者后端生成返回圖片地址訪問兩個方法,前端生成則不需要調(diào)用接口,而后端是在完成整個流程時就進行生成然后把上傳的地址保存數(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)建服務(wù) index.js

把用到的庫都導(dǎo)入進去,當(dāng)然如何創(chuàng)建node項目我這就不做過多的描述,創(chuàng)建成功后,直接使用 node index.js 就可以啟動服務(wù)了

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

在末尾加了一個供外面訪問的接口,啟動服務(wù)后 訪問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 服務(wù)已經(jīng)好了,正片開始,瓜子飲料礦泉水,前面的麻煩讓一讓,

首先我沒得知道,票據(jù)單有哪些內(nèi)容

1、標題:編號,日期,地址;這些都是文字,所以我沒得繪制文字
2、表格:表頭,內(nèi)容,線條;表格就是線條堆積而成的,內(nèi)容就是文字,這里就得繪制線條,文字
3、尾部:文字,印章,簽名;這里需要繪制文字,圖片兩個
總的來說,我們想要繪制出一張票據(jù)單,得要繪制文字,繪制線條,通過線條與文字結(jié)合生成表格,最后添加印章與簽名照片

繪制畫布

1、給畫布設(shè)置長寬

  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 寫入字內(nèi)容
    * @param {str} t 內(nèi)容
    * @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} 文字位置調(diào)整
    * @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,相關(guān)繪制的函數(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、表格部分,繪制表頭,表格,表尾

這里直接調(diào)用繪制表格的函數(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 寫入字內(nèi)容
 * @param {str} t 內(nèi)容
 * @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} 文字位置調(diào)整
 * @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); //設(shè)置返回類型
    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

到此這篇關(guān)于node+koa+canvas繪制出貨單,收據(jù),票據(jù)的文章就介紹到這了,更多相關(guān)node+koa+canvas繪制出貨單內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • package.json的版本號更新優(yōu)化方法

    package.json的版本號更新優(yōu)化方法

    這篇文章主要為大家介紹了package.json的版本號更新優(yōu)化方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • 用Node寫一條配置環(huán)境的指令

    用Node寫一條配置環(huán)境的指令

    這篇文章主要介紹了用Node寫一條配置環(huán)境的指令,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Node.js高級編程使用RPC通信示例詳解

    Node.js高級編程使用RPC通信示例詳解

    這篇文章主要為大家介紹了Node.js高級編程使用RPC通信示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • NodeJS實現(xiàn)一個聊天室功能

    NodeJS實現(xiàn)一個聊天室功能

    這篇文章主要介紹了NodeJS實現(xiàn)一個聊天室功能,本文實例截圖相結(jié)合給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-11-11
  • nodejs超出最大的調(diào)用棧錯誤問題

    nodejs超出最大的調(diào)用棧錯誤問題

    這篇文章主要介紹了nodejs超出最大的調(diào)用棧錯誤問題,需要的朋友可以參考下
    2017-12-12
  • node.js微信小程序配置消息推送的實現(xiàn)

    node.js微信小程序配置消息推送的實現(xiàn)

    這篇文章主要介紹了node.js微信小程序配置消息推送的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-02-02
  • npm與nrm兩種方式查看源和切換鏡像詳解

    npm與nrm兩種方式查看源和切換鏡像詳解

    nrm(npm registry manager )是npm的鏡像源管理工具,它可以快速在讓你在本地源之間切換,下面這篇文章主要給大家介紹了關(guān)于npm與nrm兩種方式查看源和切換鏡像的相關(guān)資料,需要的朋友可以參考下
    2023-02-02
  • node.js中的fs.readlinkSync方法使用說明

    node.js中的fs.readlinkSync方法使用說明

    這篇文章主要介紹了node.js中的fs.readlinkSync方法使用說明,本文介紹了fs.readlinkSync方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • nodejs下打包模塊archiver詳解

    nodejs下打包模塊archiver詳解

    這篇文章主要介紹了nodejs下打包模塊archiver的使用方法,非常簡單實用,這里推薦給有需要的小伙伴。
    2014-12-12
  • nodemon實現(xiàn)Typescript項目熱更新的示例代碼

    nodemon實現(xiàn)Typescript項目熱更新的示例代碼

    這篇文章主要介紹了nodemon實現(xiàn)Typescript項目熱更新的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11

最新評論