最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

在生成票據(jù)需求中,我們會想到前端生成或者后端生成返回圖片地址訪問兩個(gè)方法,前端生成則不需要調(diào)用接口,而后端是在完成整個(gè)流程時(shí)就進(jìn)行生成然后把上傳的地址保存數(shù)據(jù)庫

先看效果

下面我們就使用node +koa+canvas后端生成圖片的方法進(jìn)行生成

使用庫

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)入進(jìn)去,當(dāng)然如何創(chuàng)建node項(xiàng)目我這就不做過多的描述,創(chuàng)建成功后,直接使用 node index.js 就可以啟動服務(wù)了

const Koa = require('koa')
const app = new Koa()
const {createCanvas, Image} = require('canvas');
const router = require('koa-router')(); /*引入是實(shí)例化路由 推薦*/
 
//....這里需要做很多事
 
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000)

創(chuàng)建一個(gè)api 提供外面可訪問的接口api

在末尾加了一個(gè)供外面訪問的接口,啟動服務(wù)后 訪問localhost:3000/img 就可以訪問了

const Koa = require('koa')
const app = new Koa()
const {createCanvas, Image} = require('canvas');
const router = require('koa-router')(); /*引入是實(shí)例化路由 推薦*/
 
//....這里需要做很多事
 
router.get('/img', async (ctx) => { });
 
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000)

ok 服務(wù)已經(jīng)好了,正片開始,瓜子飲料礦泉水,前面的麻煩讓一讓,

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

1、標(biāo)題:編號,日期,地址;這些都是文字,所以我沒得繪制文字
2、表格:表頭,內(nèi)容,線條;表格就是線條堆積而成的,內(nèi)容就是文字,這里就得繪制線條,文字
3、尾部:文字,印章,簽名;這里需要繪制文字,圖片兩個(gè)
總的來說,我們想要繪制出一張票據(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 寫入文字對齊方式 默認(rèn) 居中
    * @param {obj} c 寫入文字顏色 默認(rèn) #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ù)量', '單價(jià)', '金額', '備注']
      ]
      const goodsTxtArr = [
          ['', '', '', '', '', '', ''],
          ['', '', '', '', '', '', ''],
          ['', '', '', '', '', '', ''],
          ['', '', '', '', '', '', ''],
          ['', '', '', '', '', '', '']
      ]
      const bottomArr=[10,100,690]
      const bottomTxtArr=[
        ['合計(jì)大寫', ' 拾 萬 仟 佰 拾 元 角 分 ¥ ']
      ]
      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)
  }

繪制圖片,這里繪制圖片其實(shí)就是繪制印章

/**
* 添加圖片
* @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)好了,那么接下來就是進(jìn)行排版了

1、首先的是頭部的標(biāo)題,單位,位置 編號,和時(shí)間

//創(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')(); /*引入是實(shí)例化路由 推薦*/
 
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 寫入文字對齊方式 默認(rèn) 居中
 * @param {obj} c 寫入文字顏色 默認(rèn) #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ù)量', '單價(jià)', '金額', '備注']
    ]
    const goodsTxtArr = [
        ['', '', '', '', '', '', ''],
        ['', '', '', '', '', '', ''],
        ['', '', '', '', '', '', ''],
        ['', '', '', '', '', '', ''],
        ['', '', '', '', '', '', '']
    ]
    const bottomArr = [10, 100, 690]
    const bottomTxtArr = [
        ['合計(jì)大寫', ' 拾 萬 仟 佰 拾 元 角 分 ¥ ']
    ]
    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)文章

  • node 安裝 windows-build-tools全過程

    node 安裝 windows-build-tools全過程

    這篇文章主要介紹了node 安裝 windows-build-tools全過程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 詳解Node.js中的模塊化方法

    詳解Node.js中的模塊化方法

    這篇文章主要為大家介紹了Node.js的模塊化,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • 解決koa2 ctx.render is not a function報(bào)錯(cuò)問題

    解決koa2 ctx.render is not a function報(bào)錯(cuò)問題

    這篇文章主要介紹了解決koa2 ctx.render is not a function報(bào)錯(cuò)問題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-08-08
  • 詳解Node.js服務(wù)器靜態(tài)資源處理

    詳解Node.js服務(wù)器靜態(tài)資源處理

    靜態(tài)資源服務(wù)器指的是不會被服務(wù)器的動態(tài)運(yùn)行所改變或者生成的文件,本文主要為大家詳細(xì)介紹了Node.js服務(wù)器靜態(tài)資源處理的相關(guān)知識,需要的可以了解下
    2024-04-04
  • Node.js使用JSONP接口的具體用法

    Node.js使用JSONP接口的具體用法

    JSONP是一個(gè)跨域解決方案,本文主要介紹了Node.js使用JSONP接口的具體用法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-03-03
  • node.js學(xué)習(xí)筆記之koa框架和簡單爬蟲練習(xí)

    node.js學(xué)習(xí)筆記之koa框架和簡單爬蟲練習(xí)

    這篇文章主要介紹了node.js學(xué)習(xí)筆記之koa框架和簡單爬蟲練習(xí),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-12-12
  • Node使用Selenium進(jìn)行前端自動化操作的代碼實(shí)現(xiàn)

    Node使用Selenium進(jìn)行前端自動化操作的代碼實(shí)現(xiàn)

    這篇文章主要介紹了Node使用Selenium進(jìn)行前端自動化操作的代碼實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • NodeJS開發(fā)人員常見五個(gè)錯(cuò)誤理解

    NodeJS開發(fā)人員常見五個(gè)錯(cuò)誤理解

    這篇文章主要介紹了NodeJS開發(fā)人員常見五個(gè)錯(cuò)誤理解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • node.js 模塊和其下載資源的鏡像設(shè)置的方法

    node.js 模塊和其下載資源的鏡像設(shè)置的方法

    這篇文章主要介紹了node.js 模塊和其下載資源的鏡像設(shè)置的方法,在設(shè)置淘寶鏡像共有三種方法,每種方法通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2018-09-09
  • node.js包管理工具Yarn使用簡介

    node.js包管理工具Yarn使用簡介

    這篇文章介紹了JS包管理工具Yarn的基本用法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評論

长顺县| 长顺县| 昌黎县| 竹山县| 枣强县| 沙坪坝区| 黄梅县| 霍林郭勒市| 石嘴山市| 华宁县| 莲花县| 施秉县| 南阳市| 日土县| 英超| 临清市| 五家渠市| 定安县| 麻江县| 合川市| 昆明市| 饶平县| 秦安县| 商洛市| 石柱| 黑龙江省| 贵港市| 自贡市| 康定县| 道真| 会昌县| 东阿县| 任丘市| 临海市| 繁昌县| 白山市| 隆德县| 金昌市| 海安县| 稻城县| 黄浦区|